pycares 4.5.0__cp313-cp313-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycares/__init__.py +855 -0
- pycares/__main__.py +84 -0
- pycares/_cares.pyd +0 -0
- pycares/_version.py +2 -0
- pycares/errno.py +50 -0
- pycares/utils.py +56 -0
- pycares-4.5.0.dist-info/LICENSE +20 -0
- pycares-4.5.0.dist-info/METADATA +151 -0
- pycares-4.5.0.dist-info/RECORD +11 -0
- pycares-4.5.0.dist-info/WHEEL +5 -0
- pycares-4.5.0.dist-info/top_level.txt +2 -0
pycares/__init__.py
ADDED
@@ -0,0 +1,855 @@
|
|
1
|
+
|
2
|
+
from ._cares import ffi as _ffi, lib as _lib
|
3
|
+
import _cffi_backend # hint for bundler tools
|
4
|
+
|
5
|
+
if _lib.ARES_SUCCESS != _lib.ares_library_init(_lib.ARES_LIB_INIT_ALL):
|
6
|
+
raise RuntimeError('Could not initialize c-ares')
|
7
|
+
|
8
|
+
from . import errno
|
9
|
+
from .utils import ascii_bytes, maybe_str, parse_name
|
10
|
+
from ._version import __version__
|
11
|
+
|
12
|
+
import collections.abc
|
13
|
+
import socket
|
14
|
+
import math
|
15
|
+
import functools
|
16
|
+
import sys
|
17
|
+
|
18
|
+
|
19
|
+
exported_pycares_symbols = [
|
20
|
+
# Flag values
|
21
|
+
'ARES_FLAG_USEVC',
|
22
|
+
'ARES_FLAG_PRIMARY',
|
23
|
+
'ARES_FLAG_IGNTC',
|
24
|
+
'ARES_FLAG_NORECURSE',
|
25
|
+
'ARES_FLAG_STAYOPEN',
|
26
|
+
'ARES_FLAG_NOSEARCH',
|
27
|
+
'ARES_FLAG_NOALIASES',
|
28
|
+
'ARES_FLAG_NOCHECKRESP',
|
29
|
+
|
30
|
+
# Nameinfo flag values
|
31
|
+
'ARES_NI_NOFQDN',
|
32
|
+
'ARES_NI_NUMERICHOST',
|
33
|
+
'ARES_NI_NAMEREQD',
|
34
|
+
'ARES_NI_NUMERICSERV',
|
35
|
+
'ARES_NI_DGRAM',
|
36
|
+
'ARES_NI_TCP',
|
37
|
+
'ARES_NI_UDP',
|
38
|
+
'ARES_NI_SCTP',
|
39
|
+
'ARES_NI_DCCP',
|
40
|
+
'ARES_NI_NUMERICSCOPE',
|
41
|
+
'ARES_NI_LOOKUPHOST',
|
42
|
+
'ARES_NI_LOOKUPSERVICE',
|
43
|
+
'ARES_NI_IDN',
|
44
|
+
'ARES_NI_IDN_ALLOW_UNASSIGNED',
|
45
|
+
'ARES_NI_IDN_USE_STD3_ASCII_RULES',
|
46
|
+
|
47
|
+
# Bad socket
|
48
|
+
'ARES_SOCKET_BAD',
|
49
|
+
]
|
50
|
+
|
51
|
+
for symbol in exported_pycares_symbols:
|
52
|
+
globals()[symbol] = getattr(_lib, symbol)
|
53
|
+
|
54
|
+
|
55
|
+
exported_pycares_symbols_map = {
|
56
|
+
# Query types
|
57
|
+
"QUERY_TYPE_A" : "T_A",
|
58
|
+
"QUERY_TYPE_AAAA" : "T_AAAA",
|
59
|
+
"QUERY_TYPE_ANY" : "T_ANY",
|
60
|
+
"QUERY_TYPE_CAA" : "T_CAA",
|
61
|
+
"QUERY_TYPE_CNAME" : "T_CNAME",
|
62
|
+
"QUERY_TYPE_MX" : "T_MX",
|
63
|
+
"QUERY_TYPE_NAPTR" : "T_NAPTR",
|
64
|
+
"QUERY_TYPE_NS" : "T_NS",
|
65
|
+
"QUERY_TYPE_PTR" : "T_PTR",
|
66
|
+
"QUERY_TYPE_SOA" : "T_SOA",
|
67
|
+
"QUERY_TYPE_SRV" : "T_SRV",
|
68
|
+
"QUERY_TYPE_TXT" : "T_TXT",
|
69
|
+
|
70
|
+
# Query classes
|
71
|
+
"QUERY_CLASS_IN" : "C_IN",
|
72
|
+
"QUERY_CLASS_CHAOS": "C_CHAOS",
|
73
|
+
"QUERY_CLASS_HS" : "C_HS",
|
74
|
+
"QUERY_CLASS_NONE" :"C_NONE",
|
75
|
+
"QUERY_CLASS_ANY" : "C_ANY",
|
76
|
+
}
|
77
|
+
|
78
|
+
for k, v in exported_pycares_symbols_map.items():
|
79
|
+
globals()[k] = getattr(_lib, v)
|
80
|
+
|
81
|
+
|
82
|
+
globals()['ARES_VERSION'] = maybe_str(_ffi.string(_lib.ares_version(_ffi.NULL)))
|
83
|
+
|
84
|
+
|
85
|
+
PYCARES_ADDRTTL_SIZE = 256
|
86
|
+
|
87
|
+
|
88
|
+
class AresError(Exception):
|
89
|
+
pass
|
90
|
+
|
91
|
+
|
92
|
+
# callback helpers
|
93
|
+
|
94
|
+
_global_set = set()
|
95
|
+
|
96
|
+
@_ffi.def_extern()
|
97
|
+
def _sock_state_cb(data, socket_fd, readable, writable):
|
98
|
+
sock_state_cb = _ffi.from_handle(data)
|
99
|
+
sock_state_cb(socket_fd, readable, writable)
|
100
|
+
|
101
|
+
@_ffi.def_extern()
|
102
|
+
def _host_cb(arg, status, timeouts, hostent):
|
103
|
+
callback = _ffi.from_handle(arg)
|
104
|
+
_global_set.discard(arg)
|
105
|
+
|
106
|
+
if status != _lib.ARES_SUCCESS:
|
107
|
+
result = None
|
108
|
+
else:
|
109
|
+
result = ares_host_result(hostent)
|
110
|
+
status = None
|
111
|
+
|
112
|
+
callback(result, status)
|
113
|
+
|
114
|
+
@_ffi.def_extern()
|
115
|
+
def _nameinfo_cb(arg, status, timeouts, node, service):
|
116
|
+
callback = _ffi.from_handle(arg)
|
117
|
+
_global_set.discard(arg)
|
118
|
+
|
119
|
+
if status != _lib.ARES_SUCCESS:
|
120
|
+
result = None
|
121
|
+
else:
|
122
|
+
result = ares_nameinfo_result(node, service)
|
123
|
+
status = None
|
124
|
+
|
125
|
+
callback(result, status)
|
126
|
+
|
127
|
+
@_ffi.def_extern()
|
128
|
+
def _query_cb(arg, status, timeouts, abuf, alen):
|
129
|
+
callback, query_type = _ffi.from_handle(arg)
|
130
|
+
_global_set.discard(arg)
|
131
|
+
|
132
|
+
if status == _lib.ARES_SUCCESS:
|
133
|
+
if query_type == _lib.T_ANY:
|
134
|
+
result = []
|
135
|
+
for qtype in (_lib.T_A, _lib.T_AAAA, _lib.T_CAA, _lib.T_CNAME, _lib.T_MX, _lib.T_NAPTR, _lib.T_NS, _lib.T_PTR, _lib.T_SOA, _lib.T_SRV, _lib.T_TXT):
|
136
|
+
r, status = parse_result(qtype, abuf, alen)
|
137
|
+
if status not in (None, _lib.ARES_ENODATA, _lib.ARES_EBADRESP):
|
138
|
+
result = None
|
139
|
+
break
|
140
|
+
if r is not None:
|
141
|
+
if isinstance(r, collections.abc.Iterable):
|
142
|
+
result.extend(r)
|
143
|
+
else:
|
144
|
+
result.append(r)
|
145
|
+
else:
|
146
|
+
status = None
|
147
|
+
else:
|
148
|
+
result, status = parse_result(query_type, abuf, alen)
|
149
|
+
else:
|
150
|
+
result = None
|
151
|
+
|
152
|
+
callback(result, status)
|
153
|
+
|
154
|
+
@_ffi.def_extern()
|
155
|
+
def _addrinfo_cb(arg, status, timeouts, res):
|
156
|
+
callback = _ffi.from_handle(arg)
|
157
|
+
_global_set.discard(arg)
|
158
|
+
|
159
|
+
if status != _lib.ARES_SUCCESS:
|
160
|
+
result = None
|
161
|
+
else:
|
162
|
+
result = ares_addrinfo_result(res)
|
163
|
+
status = None
|
164
|
+
|
165
|
+
callback(result, status)
|
166
|
+
|
167
|
+
def parse_result(query_type, abuf, alen):
|
168
|
+
if query_type == _lib.T_A:
|
169
|
+
addrttls = _ffi.new("struct ares_addrttl[]", PYCARES_ADDRTTL_SIZE)
|
170
|
+
naddrttls = _ffi.new("int*", PYCARES_ADDRTTL_SIZE)
|
171
|
+
parse_status = _lib.ares_parse_a_reply(abuf, alen, _ffi.NULL, addrttls, naddrttls)
|
172
|
+
if parse_status != _lib.ARES_SUCCESS:
|
173
|
+
result = None
|
174
|
+
status = parse_status
|
175
|
+
else:
|
176
|
+
result = [ares_query_a_result(addrttls[i]) for i in range(naddrttls[0])]
|
177
|
+
status = None
|
178
|
+
elif query_type == _lib.T_AAAA:
|
179
|
+
addrttls = _ffi.new("struct ares_addr6ttl[]", PYCARES_ADDRTTL_SIZE)
|
180
|
+
naddrttls = _ffi.new("int*", PYCARES_ADDRTTL_SIZE)
|
181
|
+
parse_status = _lib.ares_parse_aaaa_reply(abuf, alen, _ffi.NULL, addrttls, naddrttls)
|
182
|
+
if parse_status != _lib.ARES_SUCCESS:
|
183
|
+
result = None
|
184
|
+
status = parse_status
|
185
|
+
else:
|
186
|
+
result = [ares_query_aaaa_result(addrttls[i]) for i in range(naddrttls[0])]
|
187
|
+
status = None
|
188
|
+
elif query_type == _lib.T_CAA:
|
189
|
+
caa_reply = _ffi.new("struct ares_caa_reply **")
|
190
|
+
parse_status = _lib.ares_parse_caa_reply(abuf, alen, caa_reply)
|
191
|
+
if parse_status != _lib.ARES_SUCCESS:
|
192
|
+
result = None
|
193
|
+
status = parse_status
|
194
|
+
else:
|
195
|
+
result = []
|
196
|
+
caa_reply_ptr = caa_reply[0]
|
197
|
+
while caa_reply_ptr != _ffi.NULL:
|
198
|
+
result.append(ares_query_caa_result(caa_reply_ptr))
|
199
|
+
caa_reply_ptr = caa_reply_ptr.next
|
200
|
+
_lib.ares_free_data(caa_reply[0])
|
201
|
+
status = None
|
202
|
+
elif query_type == _lib.T_CNAME:
|
203
|
+
host = _ffi.new("struct hostent **")
|
204
|
+
parse_status = _lib.ares_parse_a_reply(abuf, alen, host, _ffi.NULL, _ffi.NULL)
|
205
|
+
if parse_status != _lib.ARES_SUCCESS:
|
206
|
+
result = None
|
207
|
+
status = parse_status
|
208
|
+
else:
|
209
|
+
result = ares_query_cname_result(host[0])
|
210
|
+
_lib.ares_free_hostent(host[0])
|
211
|
+
status = None
|
212
|
+
elif query_type == _lib.T_MX:
|
213
|
+
mx_reply = _ffi.new("struct ares_mx_reply **")
|
214
|
+
parse_status = _lib.ares_parse_mx_reply(abuf, alen, mx_reply)
|
215
|
+
if parse_status != _lib.ARES_SUCCESS:
|
216
|
+
result = None
|
217
|
+
status = parse_status
|
218
|
+
else:
|
219
|
+
result = []
|
220
|
+
mx_reply_ptr = mx_reply[0]
|
221
|
+
while mx_reply_ptr != _ffi.NULL:
|
222
|
+
result.append(ares_query_mx_result(mx_reply_ptr))
|
223
|
+
mx_reply_ptr = mx_reply_ptr.next
|
224
|
+
_lib.ares_free_data(mx_reply[0])
|
225
|
+
status = None
|
226
|
+
elif query_type == _lib.T_NAPTR:
|
227
|
+
naptr_reply = _ffi.new("struct ares_naptr_reply **")
|
228
|
+
parse_status = _lib.ares_parse_naptr_reply(abuf, alen, naptr_reply)
|
229
|
+
if parse_status != _lib.ARES_SUCCESS:
|
230
|
+
result = None
|
231
|
+
status = parse_status
|
232
|
+
else:
|
233
|
+
result = []
|
234
|
+
naptr_reply_ptr = naptr_reply[0]
|
235
|
+
while naptr_reply_ptr != _ffi.NULL:
|
236
|
+
result.append(ares_query_naptr_result(naptr_reply_ptr))
|
237
|
+
naptr_reply_ptr = naptr_reply_ptr.next
|
238
|
+
_lib.ares_free_data(naptr_reply[0])
|
239
|
+
status = None
|
240
|
+
elif query_type == _lib.T_NS:
|
241
|
+
hostent = _ffi.new("struct hostent **")
|
242
|
+
parse_status = _lib.ares_parse_ns_reply(abuf, alen, hostent)
|
243
|
+
if parse_status != _lib.ARES_SUCCESS:
|
244
|
+
result = None
|
245
|
+
status = parse_status
|
246
|
+
else:
|
247
|
+
result = []
|
248
|
+
host = hostent[0]
|
249
|
+
i = 0
|
250
|
+
while host.h_aliases[i] != _ffi.NULL:
|
251
|
+
result.append(ares_query_ns_result(host.h_aliases[i]))
|
252
|
+
i += 1
|
253
|
+
_lib.ares_free_hostent(host)
|
254
|
+
status = None
|
255
|
+
elif query_type == _lib.T_PTR:
|
256
|
+
hostent = _ffi.new("struct hostent **")
|
257
|
+
parse_status = _lib.ares_parse_ptr_reply(abuf, alen, _ffi.NULL, 0, socket.AF_UNSPEC, hostent)
|
258
|
+
if parse_status != _lib.ARES_SUCCESS:
|
259
|
+
result = None
|
260
|
+
status = parse_status
|
261
|
+
else:
|
262
|
+
aliases = []
|
263
|
+
host = hostent[0]
|
264
|
+
i = 0
|
265
|
+
while host.h_aliases[i] != _ffi.NULL:
|
266
|
+
aliases.append(maybe_str(_ffi.string(host.h_aliases[i])))
|
267
|
+
i += 1
|
268
|
+
result = ares_query_ptr_result(host, aliases)
|
269
|
+
_lib.ares_free_hostent(host)
|
270
|
+
status = None
|
271
|
+
elif query_type == _lib.T_SOA:
|
272
|
+
soa_reply = _ffi.new("struct ares_soa_reply **")
|
273
|
+
parse_status = _lib.ares_parse_soa_reply(abuf, alen, soa_reply)
|
274
|
+
if parse_status != _lib.ARES_SUCCESS:
|
275
|
+
result = None
|
276
|
+
status = parse_status
|
277
|
+
else:
|
278
|
+
result = ares_query_soa_result(soa_reply[0])
|
279
|
+
_lib.ares_free_data(soa_reply[0])
|
280
|
+
status = None
|
281
|
+
elif query_type == _lib.T_SRV:
|
282
|
+
srv_reply = _ffi.new("struct ares_srv_reply **")
|
283
|
+
parse_status = _lib.ares_parse_srv_reply(abuf, alen, srv_reply)
|
284
|
+
if parse_status != _lib.ARES_SUCCESS:
|
285
|
+
result = None
|
286
|
+
status = parse_status
|
287
|
+
else:
|
288
|
+
result = []
|
289
|
+
srv_reply_ptr = srv_reply[0]
|
290
|
+
while srv_reply_ptr != _ffi.NULL:
|
291
|
+
result.append(ares_query_srv_result(srv_reply_ptr))
|
292
|
+
srv_reply_ptr = srv_reply_ptr.next
|
293
|
+
_lib.ares_free_data(srv_reply[0])
|
294
|
+
status = None
|
295
|
+
elif query_type == _lib.T_TXT:
|
296
|
+
txt_reply = _ffi.new("struct ares_txt_ext **")
|
297
|
+
parse_status = _lib.ares_parse_txt_reply_ext(abuf, alen, txt_reply)
|
298
|
+
if parse_status != _lib.ARES_SUCCESS:
|
299
|
+
result = None
|
300
|
+
status = parse_status
|
301
|
+
else:
|
302
|
+
result = []
|
303
|
+
txt_reply_ptr = txt_reply[0]
|
304
|
+
tmp_obj = None
|
305
|
+
while True:
|
306
|
+
if txt_reply_ptr == _ffi.NULL:
|
307
|
+
if tmp_obj is not None:
|
308
|
+
result.append(ares_query_txt_result(tmp_obj))
|
309
|
+
break
|
310
|
+
if txt_reply_ptr.record_start == 1:
|
311
|
+
if tmp_obj is not None:
|
312
|
+
result.append(ares_query_txt_result(tmp_obj))
|
313
|
+
tmp_obj = ares_query_txt_result_chunk(txt_reply_ptr)
|
314
|
+
else:
|
315
|
+
new_chunk = ares_query_txt_result_chunk(txt_reply_ptr)
|
316
|
+
tmp_obj.text += new_chunk.text
|
317
|
+
txt_reply_ptr = txt_reply_ptr.next
|
318
|
+
_lib.ares_free_data(txt_reply[0])
|
319
|
+
status = None
|
320
|
+
else:
|
321
|
+
raise ValueError("invalid query type specified")
|
322
|
+
|
323
|
+
return result, status
|
324
|
+
|
325
|
+
|
326
|
+
class Channel:
|
327
|
+
__qtypes__ = (_lib.T_A, _lib.T_AAAA, _lib.T_ANY, _lib.T_CAA, _lib.T_CNAME, _lib.T_MX, _lib.T_NAPTR, _lib.T_NS, _lib.T_PTR, _lib.T_SOA, _lib.T_SRV, _lib.T_TXT)
|
328
|
+
__qclasses__ = (_lib.C_IN, _lib.C_CHAOS, _lib.C_HS, _lib.C_NONE, _lib.C_ANY)
|
329
|
+
|
330
|
+
def __init__(self,
|
331
|
+
flags = None,
|
332
|
+
timeout = None,
|
333
|
+
tries = None,
|
334
|
+
ndots = None,
|
335
|
+
tcp_port = None,
|
336
|
+
udp_port = None,
|
337
|
+
servers = None,
|
338
|
+
domains = None,
|
339
|
+
lookups = None,
|
340
|
+
sock_state_cb = None,
|
341
|
+
socket_send_buffer_size = None,
|
342
|
+
socket_receive_buffer_size = None,
|
343
|
+
rotate = False,
|
344
|
+
local_ip = None,
|
345
|
+
local_dev = None,
|
346
|
+
resolvconf_path = None):
|
347
|
+
|
348
|
+
channel = _ffi.new("ares_channel *")
|
349
|
+
options = _ffi.new("struct ares_options *")
|
350
|
+
optmask = 0
|
351
|
+
|
352
|
+
if flags is not None:
|
353
|
+
options.flags = flags
|
354
|
+
optmask = optmask | _lib.ARES_OPT_FLAGS
|
355
|
+
|
356
|
+
if timeout is not None:
|
357
|
+
options.timeout = int(timeout * 1000)
|
358
|
+
optmask = optmask | _lib.ARES_OPT_TIMEOUTMS
|
359
|
+
|
360
|
+
if tries is not None:
|
361
|
+
options.tries = tries
|
362
|
+
optmask = optmask | _lib.ARES_OPT_TRIES
|
363
|
+
|
364
|
+
if ndots is not None:
|
365
|
+
options.ndots = ndots
|
366
|
+
optmask = optmask | _lib.ARES_OPT_NDOTS
|
367
|
+
|
368
|
+
if tcp_port is not None:
|
369
|
+
options.tcp_port = tcp_port
|
370
|
+
optmask = optmask | _lib.ARES_OPT_TCP_PORT
|
371
|
+
|
372
|
+
if udp_port is not None:
|
373
|
+
options.udp_port = udp_port
|
374
|
+
optmask = optmask | _lib.ARES_OPT_UDP_PORT
|
375
|
+
|
376
|
+
if socket_send_buffer_size is not None:
|
377
|
+
options.socket_send_buffer_size = socket_send_buffer_size
|
378
|
+
optmask = optmask | _lib.ARES_OPT_SOCK_SNDBUF
|
379
|
+
|
380
|
+
if socket_receive_buffer_size is not None:
|
381
|
+
options.socket_receive_buffer_size = socket_receive_buffer_size
|
382
|
+
optmask = optmask | _lib.ARES_OPT_SOCK_RCVBUF
|
383
|
+
|
384
|
+
if sock_state_cb:
|
385
|
+
if not callable(sock_state_cb):
|
386
|
+
raise TypeError("sock_state_cb is not callable")
|
387
|
+
|
388
|
+
userdata = _ffi.new_handle(sock_state_cb)
|
389
|
+
|
390
|
+
# This must be kept alive while the channel is alive.
|
391
|
+
self._sock_state_cb_handle = userdata
|
392
|
+
|
393
|
+
options.sock_state_cb = _lib._sock_state_cb
|
394
|
+
options.sock_state_cb_data = userdata
|
395
|
+
optmask = optmask | _lib.ARES_OPT_SOCK_STATE_CB
|
396
|
+
|
397
|
+
if lookups:
|
398
|
+
options.lookups = _ffi.new('char[]', ascii_bytes(lookups))
|
399
|
+
optmask = optmask | _lib.ARES_OPT_LOOKUPS
|
400
|
+
|
401
|
+
if domains:
|
402
|
+
strs = [_ffi.new("char[]", ascii_bytes(i)) for i in domains]
|
403
|
+
c = _ffi.new("char *[%d]" % (len(domains) + 1))
|
404
|
+
for i in range(len(domains)):
|
405
|
+
c[i] = strs[i]
|
406
|
+
|
407
|
+
options.domains = c
|
408
|
+
options.ndomains = len(domains)
|
409
|
+
optmask = optmask | _lib.ARES_OPT_DOMAINS
|
410
|
+
|
411
|
+
if rotate:
|
412
|
+
optmask = optmask | _lib.ARES_OPT_ROTATE
|
413
|
+
|
414
|
+
if resolvconf_path is not None:
|
415
|
+
optmask = optmask | _lib.ARES_OPT_RESOLVCONF
|
416
|
+
options.resolvconf_path = _ffi.new('char[]', ascii_bytes(resolvconf_path))
|
417
|
+
|
418
|
+
r = _lib.ares_init_options(channel, options, optmask)
|
419
|
+
if r != _lib.ARES_SUCCESS:
|
420
|
+
raise AresError('Failed to initialize c-ares channel')
|
421
|
+
|
422
|
+
self._channel = _ffi.gc(channel, lambda x: _lib.ares_destroy(x[0]))
|
423
|
+
|
424
|
+
if servers:
|
425
|
+
self.servers = servers
|
426
|
+
|
427
|
+
if local_ip:
|
428
|
+
self.set_local_ip(local_ip)
|
429
|
+
|
430
|
+
if local_dev:
|
431
|
+
self.set_local_dev(local_dev)
|
432
|
+
|
433
|
+
def cancel(self):
|
434
|
+
_lib.ares_cancel(self._channel[0])
|
435
|
+
|
436
|
+
@property
|
437
|
+
def servers(self):
|
438
|
+
servers = _ffi.new("struct ares_addr_node **")
|
439
|
+
|
440
|
+
r = _lib.ares_get_servers(self._channel[0], servers)
|
441
|
+
if r != _lib.ARES_SUCCESS:
|
442
|
+
raise AresError(r, errno.strerror(r))
|
443
|
+
|
444
|
+
server_list = []
|
445
|
+
server = _ffi.new("struct ares_addr_node **", servers[0])
|
446
|
+
while True:
|
447
|
+
if server == _ffi.NULL:
|
448
|
+
break
|
449
|
+
|
450
|
+
ip = _ffi.new("char []", _lib.INET6_ADDRSTRLEN)
|
451
|
+
s = server[0]
|
452
|
+
if _ffi.NULL != _lib.ares_inet_ntop(s.family, _ffi.addressof(s.addr), ip, _lib.INET6_ADDRSTRLEN):
|
453
|
+
server_list.append(maybe_str(_ffi.string(ip, _lib.INET6_ADDRSTRLEN)))
|
454
|
+
|
455
|
+
server = s.next
|
456
|
+
|
457
|
+
return server_list
|
458
|
+
|
459
|
+
@servers.setter
|
460
|
+
def servers(self, servers):
|
461
|
+
c = _ffi.new("struct ares_addr_node[%d]" % len(servers))
|
462
|
+
for i, server in enumerate(servers):
|
463
|
+
if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(server), _ffi.addressof(c[i].addr.addr4)) == 1:
|
464
|
+
c[i].family = socket.AF_INET
|
465
|
+
elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(server), _ffi.addressof(c[i].addr.addr6)) == 1:
|
466
|
+
c[i].family = socket.AF_INET6
|
467
|
+
else:
|
468
|
+
raise ValueError("invalid IP address")
|
469
|
+
|
470
|
+
if i > 0:
|
471
|
+
c[i - 1].next = _ffi.addressof(c[i])
|
472
|
+
|
473
|
+
r = _lib.ares_set_servers(self._channel[0], c)
|
474
|
+
if r != _lib.ARES_SUCCESS:
|
475
|
+
raise AresError(r, errno.strerror(r))
|
476
|
+
|
477
|
+
def getsock(self):
|
478
|
+
rfds = []
|
479
|
+
wfds = []
|
480
|
+
socks = _ffi.new("ares_socket_t [%d]" % _lib.ARES_GETSOCK_MAXNUM)
|
481
|
+
bitmask = _lib.ares_getsock(self._channel[0], socks, _lib.ARES_GETSOCK_MAXNUM)
|
482
|
+
for i in range(_lib.ARES_GETSOCK_MAXNUM):
|
483
|
+
if _lib.ARES_GETSOCK_READABLE(bitmask, i):
|
484
|
+
rfds.append(socks[i])
|
485
|
+
if _lib.ARES_GETSOCK_WRITABLE(bitmask, i):
|
486
|
+
wfds.append(socks[i])
|
487
|
+
|
488
|
+
return rfds, wfds
|
489
|
+
|
490
|
+
def process_fd(self, read_fd, write_fd):
|
491
|
+
_lib.ares_process_fd(self._channel[0], _ffi.cast("ares_socket_t", read_fd), _ffi.cast("ares_socket_t", write_fd))
|
492
|
+
|
493
|
+
def timeout(self, t = None):
|
494
|
+
maxtv = _ffi.NULL
|
495
|
+
tv = _ffi.new("struct timeval*")
|
496
|
+
|
497
|
+
if t is not None:
|
498
|
+
if t >= 0.0:
|
499
|
+
maxtv = _ffi.new("struct timeval*")
|
500
|
+
maxtv.tv_sec = int(math.floor(t))
|
501
|
+
maxtv.tv_usec = int(math.fmod(t, 1.0) * 1000000)
|
502
|
+
else:
|
503
|
+
raise ValueError("timeout needs to be a positive number or None")
|
504
|
+
|
505
|
+
_lib.ares_timeout(self._channel[0], maxtv, tv)
|
506
|
+
|
507
|
+
if tv == _ffi.NULL:
|
508
|
+
return 0.0
|
509
|
+
|
510
|
+
return (tv.tv_sec + tv.tv_usec / 1000000.0)
|
511
|
+
|
512
|
+
def gethostbyaddr(self, addr, callback):
|
513
|
+
if not callable(callback):
|
514
|
+
raise TypeError("a callable is required")
|
515
|
+
|
516
|
+
addr4 = _ffi.new("struct in_addr*")
|
517
|
+
addr6 = _ffi.new("struct ares_in6_addr*")
|
518
|
+
if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(addr), (addr4)) == 1:
|
519
|
+
address = addr4
|
520
|
+
family = socket.AF_INET
|
521
|
+
elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(addr), (addr6)) == 1:
|
522
|
+
address = addr6
|
523
|
+
family = socket.AF_INET6
|
524
|
+
else:
|
525
|
+
raise ValueError("invalid IP address")
|
526
|
+
|
527
|
+
userdata = _ffi.new_handle(callback)
|
528
|
+
_global_set.add(userdata)
|
529
|
+
_lib.ares_gethostbyaddr(self._channel[0], address, _ffi.sizeof(address[0]), family, _lib._host_cb, userdata)
|
530
|
+
|
531
|
+
def gethostbyname(self, name, family, callback):
|
532
|
+
if not callable(callback):
|
533
|
+
raise TypeError("a callable is required")
|
534
|
+
|
535
|
+
userdata = _ffi.new_handle(callback)
|
536
|
+
_global_set.add(userdata)
|
537
|
+
_lib.ares_gethostbyname(self._channel[0], parse_name(name), family, _lib._host_cb, userdata)
|
538
|
+
|
539
|
+
def getaddrinfo(self, host, port, callback, family=0, type=0, proto=0, flags=0):
|
540
|
+
if not callable(callback):
|
541
|
+
raise TypeError("a callable is required")
|
542
|
+
|
543
|
+
if port is None:
|
544
|
+
service = _ffi.NULL
|
545
|
+
elif isinstance(port, int):
|
546
|
+
service = str(port).encode('ascii')
|
547
|
+
else:
|
548
|
+
service = ascii_bytes(port)
|
549
|
+
|
550
|
+
userdata = _ffi.new_handle(callback)
|
551
|
+
_global_set.add(userdata)
|
552
|
+
|
553
|
+
hints = _ffi.new('struct ares_addrinfo_hints*')
|
554
|
+
hints.ai_flags = flags
|
555
|
+
hints.ai_family = family
|
556
|
+
hints.ai_socktype = type
|
557
|
+
hints.ai_protocol = proto
|
558
|
+
_lib.ares_getaddrinfo(self._channel[0], parse_name(host), service, hints, _lib._addrinfo_cb, userdata)
|
559
|
+
|
560
|
+
def query(self, name, query_type, callback, query_class=None):
|
561
|
+
self._do_query(_lib.ares_query, name, query_type, callback, query_class=query_class)
|
562
|
+
|
563
|
+
def search(self, name, query_type, callback, query_class=None):
|
564
|
+
self._do_query(_lib.ares_search, name, query_type, callback, query_class=query_class)
|
565
|
+
|
566
|
+
def _do_query(self, func, name, query_type, callback, query_class=None):
|
567
|
+
if not callable(callback):
|
568
|
+
raise TypeError('a callable is required')
|
569
|
+
|
570
|
+
if query_type not in self.__qtypes__:
|
571
|
+
raise ValueError('invalid query type specified')
|
572
|
+
|
573
|
+
if query_class is None:
|
574
|
+
query_class = _lib.C_IN
|
575
|
+
|
576
|
+
if query_class not in self.__qclasses__:
|
577
|
+
raise ValueError('invalid query class specified')
|
578
|
+
|
579
|
+
userdata = _ffi.new_handle((callback, query_type))
|
580
|
+
_global_set.add(userdata)
|
581
|
+
func(self._channel[0], parse_name(name), query_class, query_type, _lib._query_cb, userdata)
|
582
|
+
|
583
|
+
def set_local_ip(self, ip):
|
584
|
+
addr4 = _ffi.new("struct in_addr*")
|
585
|
+
addr6 = _ffi.new("struct ares_in6_addr*")
|
586
|
+
if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), addr4) == 1:
|
587
|
+
_lib.ares_set_local_ip4(self._channel[0], socket.ntohl(addr4.s_addr))
|
588
|
+
elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), addr6) == 1:
|
589
|
+
_lib.ares_set_local_ip6(self._channel[0], addr6)
|
590
|
+
else:
|
591
|
+
raise ValueError("invalid IP address")
|
592
|
+
|
593
|
+
def getnameinfo(self, address, flags, callback):
|
594
|
+
if not callable(callback):
|
595
|
+
raise TypeError("a callable is required")
|
596
|
+
|
597
|
+
if len(address) == 2:
|
598
|
+
(ip, port) = address
|
599
|
+
sa4 = _ffi.new("struct sockaddr_in*")
|
600
|
+
if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), _ffi.addressof(sa4.sin_addr)) != 1:
|
601
|
+
raise ValueError("Invalid IPv4 address %r" % ip)
|
602
|
+
sa4.sin_family = socket.AF_INET
|
603
|
+
sa4.sin_port = socket.htons(port)
|
604
|
+
sa = sa4
|
605
|
+
elif len(address) == 4:
|
606
|
+
(ip, port, flowinfo, scope_id) = address
|
607
|
+
sa6 = _ffi.new("struct sockaddr_in6*")
|
608
|
+
if _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), _ffi.addressof(sa6.sin6_addr)) != 1:
|
609
|
+
raise ValueError("Invalid IPv6 address %r" % ip)
|
610
|
+
sa6.sin6_family = socket.AF_INET6
|
611
|
+
sa6.sin6_port = socket.htons(port)
|
612
|
+
sa6.sin6_flowinfo = socket.htonl(flowinfo) # I'm unsure about byteorder here.
|
613
|
+
sa6.sin6_scope_id = scope_id # Yes, without htonl.
|
614
|
+
sa = sa6
|
615
|
+
else:
|
616
|
+
raise ValueError("Invalid address argument")
|
617
|
+
|
618
|
+
userdata = _ffi.new_handle(callback)
|
619
|
+
_global_set.add(userdata)
|
620
|
+
_lib.ares_getnameinfo(self._channel[0], _ffi.cast("struct sockaddr*", sa), _ffi.sizeof(sa[0]), flags, _lib._nameinfo_cb, userdata)
|
621
|
+
|
622
|
+
def set_local_dev(self, dev):
|
623
|
+
_lib.ares_set_local_dev(self._channel[0], dev)
|
624
|
+
|
625
|
+
|
626
|
+
class AresResult:
|
627
|
+
__slots__ = ()
|
628
|
+
|
629
|
+
def __repr__(self):
|
630
|
+
attrs = ['%s=%s' % (a, getattr(self, a)) for a in self.__slots__]
|
631
|
+
return '<%s> %s' % (self.__class__.__name__, ', '.join(attrs))
|
632
|
+
|
633
|
+
|
634
|
+
# DNS query result types
|
635
|
+
#
|
636
|
+
|
637
|
+
class ares_query_a_result(AresResult):
|
638
|
+
__slots__ = ('host', 'ttl')
|
639
|
+
type = 'A'
|
640
|
+
|
641
|
+
def __init__(self, ares_addrttl):
|
642
|
+
buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
|
643
|
+
_lib.ares_inet_ntop(socket.AF_INET, _ffi.addressof(ares_addrttl.ipaddr), buf, _lib.INET6_ADDRSTRLEN)
|
644
|
+
self.host = maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN))
|
645
|
+
self.ttl = ares_addrttl.ttl
|
646
|
+
|
647
|
+
|
648
|
+
class ares_query_aaaa_result(AresResult):
|
649
|
+
__slots__ = ('host', 'ttl')
|
650
|
+
type = 'AAAA'
|
651
|
+
|
652
|
+
def __init__(self, ares_addrttl):
|
653
|
+
buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
|
654
|
+
_lib.ares_inet_ntop(socket.AF_INET6, _ffi.addressof(ares_addrttl.ip6addr), buf, _lib.INET6_ADDRSTRLEN)
|
655
|
+
self.host = maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN))
|
656
|
+
self.ttl = ares_addrttl.ttl
|
657
|
+
|
658
|
+
|
659
|
+
class ares_query_caa_result(AresResult):
|
660
|
+
__slots__ = ('critical', 'property', 'value', 'ttl')
|
661
|
+
type = 'CAA'
|
662
|
+
|
663
|
+
def __init__(self, caa):
|
664
|
+
self.critical = caa.critical
|
665
|
+
self.property = maybe_str(_ffi.string(caa.property, caa.plength))
|
666
|
+
self.value = maybe_str(_ffi.string(caa.value, caa.length))
|
667
|
+
self.ttl = -1
|
668
|
+
|
669
|
+
|
670
|
+
class ares_query_cname_result(AresResult):
|
671
|
+
__slots__ = ('cname', 'ttl')
|
672
|
+
type = 'CNAME'
|
673
|
+
|
674
|
+
def __init__(self, host):
|
675
|
+
self.cname = maybe_str(_ffi.string(host.h_name))
|
676
|
+
self.ttl = -1
|
677
|
+
|
678
|
+
|
679
|
+
class ares_query_mx_result(AresResult):
|
680
|
+
__slots__ = ('host', 'priority', 'ttl')
|
681
|
+
type = 'MX'
|
682
|
+
|
683
|
+
def __init__(self, mx):
|
684
|
+
self.host = maybe_str(_ffi.string(mx.host))
|
685
|
+
self.priority = mx.priority
|
686
|
+
self.ttl = -1
|
687
|
+
|
688
|
+
|
689
|
+
class ares_query_naptr_result(AresResult):
|
690
|
+
__slots__ = ('order', 'preference', 'flags', 'service', 'regex', 'replacement', 'ttl')
|
691
|
+
type = 'NAPTR'
|
692
|
+
|
693
|
+
def __init__(self, naptr):
|
694
|
+
self.order = naptr.order
|
695
|
+
self.preference = naptr.preference
|
696
|
+
self.flags = maybe_str(_ffi.string(naptr.flags))
|
697
|
+
self.service = maybe_str(_ffi.string(naptr.service))
|
698
|
+
self.regex = maybe_str(_ffi.string(naptr.regexp))
|
699
|
+
self.replacement = maybe_str(_ffi.string(naptr.replacement))
|
700
|
+
self.ttl = -1
|
701
|
+
|
702
|
+
|
703
|
+
class ares_query_ns_result(AresResult):
|
704
|
+
__slots__ = ('host', 'ttl')
|
705
|
+
type = 'NS'
|
706
|
+
|
707
|
+
def __init__(self, ns):
|
708
|
+
self.host = maybe_str(_ffi.string(ns))
|
709
|
+
self.ttl = -1
|
710
|
+
|
711
|
+
|
712
|
+
class ares_query_ptr_result(AresResult):
|
713
|
+
__slots__ = ('name', 'ttl', 'aliases')
|
714
|
+
type = 'PTR'
|
715
|
+
|
716
|
+
def __init__(self, hostent, aliases):
|
717
|
+
self.name = maybe_str(_ffi.string(hostent.h_name))
|
718
|
+
self.aliases = aliases
|
719
|
+
self.ttl = -1
|
720
|
+
|
721
|
+
|
722
|
+
class ares_query_soa_result(AresResult):
|
723
|
+
__slots__ = ('nsname', 'hostmaster', 'serial', 'refresh', 'retry', 'expires', 'minttl', 'ttl')
|
724
|
+
type = 'SOA'
|
725
|
+
|
726
|
+
def __init__(self, soa):
|
727
|
+
self.nsname = maybe_str(_ffi.string(soa.nsname))
|
728
|
+
self.hostmaster = maybe_str(_ffi.string(soa.hostmaster))
|
729
|
+
self.serial = soa.serial
|
730
|
+
self.refresh = soa.refresh
|
731
|
+
self.retry = soa.retry
|
732
|
+
self.expires = soa.expire
|
733
|
+
self.minttl = soa.minttl
|
734
|
+
self.ttl = -1
|
735
|
+
|
736
|
+
|
737
|
+
class ares_query_srv_result(AresResult):
|
738
|
+
__slots__ = ('host', 'port', 'priority', 'weight', 'ttl')
|
739
|
+
type = 'SRV'
|
740
|
+
|
741
|
+
def __init__(self, srv):
|
742
|
+
self.host = maybe_str(_ffi.string(srv.host))
|
743
|
+
self.port = srv.port
|
744
|
+
self.priority = srv.priority
|
745
|
+
self.weight = srv.weight
|
746
|
+
self.ttl = -1
|
747
|
+
|
748
|
+
|
749
|
+
class ares_query_txt_result(AresResult):
|
750
|
+
__slots__ = ('text', 'ttl')
|
751
|
+
type = 'TXT'
|
752
|
+
|
753
|
+
def __init__(self, txt_chunk):
|
754
|
+
self.text = maybe_str(txt_chunk.text)
|
755
|
+
self.ttl = -1
|
756
|
+
|
757
|
+
|
758
|
+
class ares_query_txt_result_chunk(AresResult):
|
759
|
+
__slots__ = ('text', 'ttl')
|
760
|
+
type = 'TXT'
|
761
|
+
|
762
|
+
def __init__(self, txt):
|
763
|
+
self.text = _ffi.string(txt.txt)
|
764
|
+
self.ttl = -1
|
765
|
+
|
766
|
+
|
767
|
+
# Other result types
|
768
|
+
#
|
769
|
+
|
770
|
+
class ares_host_result(AresResult):
|
771
|
+
__slots__ = ('name', 'aliases', 'addresses')
|
772
|
+
|
773
|
+
def __init__(self, hostent):
|
774
|
+
self.name = maybe_str(_ffi.string(hostent.h_name))
|
775
|
+
self.aliases = []
|
776
|
+
self.addresses = []
|
777
|
+
i = 0
|
778
|
+
while hostent.h_aliases[i] != _ffi.NULL:
|
779
|
+
self.aliases.append(maybe_str(_ffi.string(hostent.h_aliases[i])))
|
780
|
+
i += 1
|
781
|
+
|
782
|
+
i = 0
|
783
|
+
while hostent.h_addr_list[i] != _ffi.NULL:
|
784
|
+
buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
|
785
|
+
if _ffi.NULL != _lib.ares_inet_ntop(hostent.h_addrtype, hostent.h_addr_list[i], buf, _lib.INET6_ADDRSTRLEN):
|
786
|
+
self.addresses.append(maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN)))
|
787
|
+
i += 1
|
788
|
+
|
789
|
+
|
790
|
+
class ares_nameinfo_result(AresResult):
|
791
|
+
__slots__ = ('node', 'service')
|
792
|
+
|
793
|
+
def __init__(self, node, service):
|
794
|
+
self.node = maybe_str(_ffi.string(node))
|
795
|
+
self.service = maybe_str(_ffi.string(service)) if service != _ffi.NULL else None
|
796
|
+
|
797
|
+
|
798
|
+
class ares_addrinfo_node_result(AresResult):
|
799
|
+
__slots__ = ('ttl', 'flags', 'family', 'socktype', 'protocol', 'addr')
|
800
|
+
|
801
|
+
def __init__(self, ares_node):
|
802
|
+
self.ttl = ares_node.ai_ttl
|
803
|
+
self.flags = ares_node.ai_flags
|
804
|
+
self.socktype = ares_node.ai_socktype
|
805
|
+
self.protocol = ares_node.ai_protocol
|
806
|
+
|
807
|
+
addr = ares_node.ai_addr
|
808
|
+
assert addr.sa_family == ares_node.ai_family
|
809
|
+
ip = _ffi.new("char []", _lib.INET6_ADDRSTRLEN)
|
810
|
+
if addr.sa_family == socket.AF_INET:
|
811
|
+
self.family = socket.AF_INET
|
812
|
+
s = _ffi.cast("struct sockaddr_in*", addr)
|
813
|
+
if _ffi.NULL != _lib.ares_inet_ntop(s.sin_family, _ffi.addressof(s.sin_addr), ip, _lib.INET6_ADDRSTRLEN):
|
814
|
+
# (address, port) 2-tuple for AF_INET
|
815
|
+
self.addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin_port))
|
816
|
+
elif addr.sa_family == socket.AF_INET6:
|
817
|
+
self.family = socket.AF_INET6
|
818
|
+
s = _ffi.cast("struct sockaddr_in6*", addr)
|
819
|
+
if _ffi.NULL != _lib.ares_inet_ntop(s.sin6_family, _ffi.addressof(s.sin6_addr), ip, _lib.INET6_ADDRSTRLEN):
|
820
|
+
# (address, port, flow info, scope id) 4-tuple for AF_INET6
|
821
|
+
self.addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin6_port), s.sin6_flowinfo, s.sin6_scope_id)
|
822
|
+
else:
|
823
|
+
raise ValueError("invalid sockaddr family")
|
824
|
+
|
825
|
+
|
826
|
+
class ares_addrinfo_cname_result(AresResult):
|
827
|
+
__slots__ = ('ttl', 'alias', 'name')
|
828
|
+
|
829
|
+
def __init__(self, ares_cname):
|
830
|
+
self.ttl = ares_cname.ttl
|
831
|
+
self.alias = maybe_str(_ffi.string(ares_cname.alias))
|
832
|
+
self.name = maybe_str(_ffi.string(ares_cname.name))
|
833
|
+
|
834
|
+
|
835
|
+
class ares_addrinfo_result(AresResult):
|
836
|
+
__slots__ = ('cnames', 'nodes')
|
837
|
+
|
838
|
+
def __init__(self, ares_addrinfo):
|
839
|
+
self.cnames = []
|
840
|
+
self.nodes = []
|
841
|
+
cname_ptr = ares_addrinfo.cnames
|
842
|
+
while cname_ptr != _ffi.NULL:
|
843
|
+
self.cnames.append(ares_addrinfo_cname_result(cname_ptr))
|
844
|
+
cname_ptr = cname_ptr.next
|
845
|
+
node_ptr = ares_addrinfo.nodes
|
846
|
+
while node_ptr != _ffi.NULL:
|
847
|
+
self.nodes.append(ares_addrinfo_node_result(node_ptr))
|
848
|
+
node_ptr = node_ptr.ai_next
|
849
|
+
_lib.ares_freeaddrinfo(ares_addrinfo)
|
850
|
+
|
851
|
+
|
852
|
+
|
853
|
+
__all__ = exported_pycares_symbols + list(exported_pycares_symbols_map.keys()) + ['AresError', 'Channel', 'errno', '__version__']
|
854
|
+
|
855
|
+
del exported_pycares_symbols, exported_pycares_symbols_map
|
pycares/__main__.py
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
|
2
|
+
import collections.abc
|
3
|
+
import pycares
|
4
|
+
import select
|
5
|
+
import socket
|
6
|
+
import sys
|
7
|
+
|
8
|
+
|
9
|
+
def wait_channel(channel):
|
10
|
+
while True:
|
11
|
+
read_fds, write_fds = channel.getsock()
|
12
|
+
if not read_fds and not write_fds:
|
13
|
+
break
|
14
|
+
timeout = channel.timeout()
|
15
|
+
if not timeout:
|
16
|
+
channel.process_fd(pycares.ARES_SOCKET_BAD, pycares.ARES_SOCKET_BAD)
|
17
|
+
continue
|
18
|
+
rlist, wlist, xlist = select.select(read_fds, write_fds, [], timeout)
|
19
|
+
for fd in rlist:
|
20
|
+
channel.process_fd(fd, pycares.ARES_SOCKET_BAD)
|
21
|
+
for fd in wlist:
|
22
|
+
channel.process_fd(pycares.ARES_SOCKET_BAD, fd)
|
23
|
+
|
24
|
+
|
25
|
+
def cb(result, error):
|
26
|
+
if error is not None:
|
27
|
+
print('Error: (%d) %s' % (error, pycares.errno.strerror(error)))
|
28
|
+
else:
|
29
|
+
parts = [
|
30
|
+
';; QUESTION SECTION:',
|
31
|
+
';%s\t\t\tIN\t%s' % (hostname, qtype.upper()),
|
32
|
+
'',
|
33
|
+
';; ANSWER SECTION:'
|
34
|
+
]
|
35
|
+
|
36
|
+
if not isinstance(result, collections.abc.Iterable):
|
37
|
+
result = [result]
|
38
|
+
|
39
|
+
for r in result:
|
40
|
+
txt = '%s\t\t%d\tIN\t%s' % (hostname, r.ttl, r.type)
|
41
|
+
if r.type in ('A', 'AAAA'):
|
42
|
+
parts.append('%s\t%s' % (txt, r.host))
|
43
|
+
elif r.type == 'CAA':
|
44
|
+
parts.append('%s\t%d %s "%s"' % (txt, r.critical, r.property, r.value))
|
45
|
+
elif r.type == 'CNAME':
|
46
|
+
parts.append('%s\t%s' % (txt, r.cname))
|
47
|
+
elif r.type == 'MX':
|
48
|
+
parts.append('%s\t%d %s' % (txt, r.priority, r.host))
|
49
|
+
elif r.type == 'NAPTR':
|
50
|
+
parts.append('%s\t%d %d "%s" "%s" "%s" %s' % (txt, r.order, r.preference, r.flags, r.service, r.regex, r.replacement))
|
51
|
+
elif r.type == 'NS':
|
52
|
+
parts.append('%s\t%s' % (txt, r.host))
|
53
|
+
elif r.type == 'PTR':
|
54
|
+
parts.append('%s\t%s' % (txt, r.name))
|
55
|
+
elif r.type == 'SOA':
|
56
|
+
parts.append('%s\t%s %s %d %d %d %d %d' % (txt, r.nsname, r.hostmaster, r.serial, r.refresh, r.retry, r.expires, r.minttl))
|
57
|
+
elif r.type == 'SRV':
|
58
|
+
parts.append('%s\t%d %d %d %s' % (txt, r.priority, r.weight, r.port, r.host))
|
59
|
+
elif r.type == 'TXT':
|
60
|
+
parts.append('%s\t"%s"' % (txt, r.text))
|
61
|
+
|
62
|
+
print('\n'.join(parts))
|
63
|
+
|
64
|
+
|
65
|
+
channel = pycares.Channel()
|
66
|
+
|
67
|
+
if len(sys.argv) not in (2, 3):
|
68
|
+
print('Invalid arguments! Usage: python -m pycares [query_type] hostname')
|
69
|
+
sys.exit(1)
|
70
|
+
|
71
|
+
if len(sys.argv) == 2:
|
72
|
+
_, hostname = sys.argv
|
73
|
+
qtype = 'A'
|
74
|
+
else:
|
75
|
+
_, qtype, hostname = sys.argv
|
76
|
+
|
77
|
+
try:
|
78
|
+
query_type = getattr(pycares, 'QUERY_TYPE_%s' % qtype.upper())
|
79
|
+
except Exception:
|
80
|
+
print('Invalid query type: %s' % qtype)
|
81
|
+
sys.exit(1)
|
82
|
+
|
83
|
+
channel.query(hostname, query_type, cb)
|
84
|
+
wait_channel(channel)
|
pycares/_cares.pyd
ADDED
Binary file
|
pycares/_version.py
ADDED
pycares/errno.py
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
from ._cares import ffi as _ffi, lib as _lib
|
3
|
+
from .utils import maybe_str
|
4
|
+
|
5
|
+
|
6
|
+
exported_pycares_symbols = [
|
7
|
+
'ARES_SUCCESS',
|
8
|
+
# error codes
|
9
|
+
'ARES_ENODATA',
|
10
|
+
'ARES_EFORMERR',
|
11
|
+
'ARES_ESERVFAIL',
|
12
|
+
'ARES_ENOTFOUND',
|
13
|
+
'ARES_ENOTIMP',
|
14
|
+
'ARES_EREFUSED',
|
15
|
+
'ARES_EBADQUERY',
|
16
|
+
'ARES_EBADNAME',
|
17
|
+
'ARES_EBADFAMILY',
|
18
|
+
'ARES_EBADRESP',
|
19
|
+
'ARES_ECONNREFUSED',
|
20
|
+
'ARES_ETIMEOUT',
|
21
|
+
'ARES_EOF',
|
22
|
+
'ARES_EFILE',
|
23
|
+
'ARES_ENOMEM',
|
24
|
+
'ARES_EDESTRUCTION',
|
25
|
+
'ARES_EBADSTR',
|
26
|
+
'ARES_EBADFLAGS',
|
27
|
+
'ARES_ENONAME',
|
28
|
+
'ARES_EBADHINTS',
|
29
|
+
'ARES_ENOTINITIALIZED',
|
30
|
+
'ARES_ELOADIPHLPAPI',
|
31
|
+
'ARES_EADDRGETNETWORKPARAMS',
|
32
|
+
'ARES_ECANCELLED',
|
33
|
+
'ARES_ESERVICE'
|
34
|
+
]
|
35
|
+
|
36
|
+
errorcode = {}
|
37
|
+
|
38
|
+
for symbol in exported_pycares_symbols:
|
39
|
+
value = getattr(_lib, symbol)
|
40
|
+
globals()[symbol] = value
|
41
|
+
globals()["errorcode"][value] = symbol
|
42
|
+
|
43
|
+
|
44
|
+
def strerror(code):
|
45
|
+
return maybe_str(_ffi.string(_lib.ares_strerror(code)))
|
46
|
+
|
47
|
+
|
48
|
+
__all__ = exported_pycares_symbols + ['errorcode', 'strerror']
|
49
|
+
|
50
|
+
del exported_pycares_symbols
|
pycares/utils.py
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
try:
|
3
|
+
import idna as idna2008
|
4
|
+
except ImportError:
|
5
|
+
idna2008 = None
|
6
|
+
|
7
|
+
|
8
|
+
def ascii_bytes(data):
|
9
|
+
if isinstance(data, str):
|
10
|
+
return data.encode('ascii')
|
11
|
+
if isinstance(data, bytes):
|
12
|
+
return data
|
13
|
+
raise TypeError('only str (ascii encoding) and bytes are supported')
|
14
|
+
|
15
|
+
|
16
|
+
def maybe_str(data):
|
17
|
+
if isinstance(data, str):
|
18
|
+
return data
|
19
|
+
if isinstance(data, bytes):
|
20
|
+
try:
|
21
|
+
return data.decode('ascii')
|
22
|
+
except UnicodeDecodeError:
|
23
|
+
return data
|
24
|
+
raise TypeError('only str (ascii encoding) and bytes are supported')
|
25
|
+
|
26
|
+
|
27
|
+
def is_all_ascii(text):
|
28
|
+
for c in text:
|
29
|
+
if ord(c) > 0x7f:
|
30
|
+
return False
|
31
|
+
return True
|
32
|
+
|
33
|
+
def parse_name_idna2008(name):
|
34
|
+
parts = name.split('.')
|
35
|
+
r = []
|
36
|
+
for part in parts:
|
37
|
+
if is_all_ascii(part):
|
38
|
+
r.append(part.encode('ascii'))
|
39
|
+
else:
|
40
|
+
r.append(idna2008.encode(part))
|
41
|
+
return b'.'.join(r)
|
42
|
+
|
43
|
+
def parse_name(name):
|
44
|
+
if isinstance(name, str):
|
45
|
+
if is_all_ascii(name):
|
46
|
+
return name.encode('ascii')
|
47
|
+
if idna2008 is not None:
|
48
|
+
return parse_name_idna2008(name)
|
49
|
+
return name.encode('idna')
|
50
|
+
if isinstance(name, bytes):
|
51
|
+
return name
|
52
|
+
raise TypeError('only str and bytes are supported')
|
53
|
+
|
54
|
+
|
55
|
+
__all__ = ['ascii_bytes', 'maybe_str', 'parse_name']
|
56
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (C) 2012 by Saúl Ibarra Corretgé
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
@@ -0,0 +1,151 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: pycares
|
3
|
+
Version: 4.5.0
|
4
|
+
Summary: Python interface for c-ares
|
5
|
+
Home-page: http://github.com/saghul/pycares
|
6
|
+
Author: Saúl Ibarra Corretgé
|
7
|
+
Author-email: s@saghul.net
|
8
|
+
License: MIT
|
9
|
+
Platform: POSIX
|
10
|
+
Platform: Microsoft Windows
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Classifier: Operating System :: POSIX
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
16
|
+
Classifier: Programming Language :: Python
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
23
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
24
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
25
|
+
Requires-Python: >=3.9
|
26
|
+
Description-Content-Type: text/x-rst
|
27
|
+
License-File: LICENSE
|
28
|
+
Requires-Dist: cffi>=1.5.0
|
29
|
+
Provides-Extra: idna
|
30
|
+
Requires-Dist: idna>=2.1; extra == "idna"
|
31
|
+
|
32
|
+
Looking for new maintainers
|
33
|
+
===========================
|
34
|
+
|
35
|
+
https://github.com/saghul/pycares/issues/139
|
36
|
+
|
37
|
+
pycares: Python interface for c-ares
|
38
|
+
====================================
|
39
|
+
|
40
|
+
pycares is a Python module which provides an interface to c-ares.
|
41
|
+
`c-ares <https://c-ares.org>`_ is a C library that performs
|
42
|
+
DNS requests and name resolutions asynchronously.
|
43
|
+
|
44
|
+
|
45
|
+
Documentation
|
46
|
+
-------------
|
47
|
+
|
48
|
+
http://readthedocs.org/docs/pycares/
|
49
|
+
|
50
|
+
|
51
|
+
Bundled c-ares
|
52
|
+
--------------
|
53
|
+
|
54
|
+
pycares currently bundles c-ares as a submodule for ease of building. Using the system
|
55
|
+
provided c-ares is possible if the ``PYCARES_USE_SYSTEM_LIB`` environment variable is
|
56
|
+
set to ``1`` when building.
|
57
|
+
|
58
|
+
NOTE: Versions prior to 4.0.0 used to embed a modified c-ares with extended TTL support.
|
59
|
+
That is no longer the case and as a result only A and AAAA records will have TTL information.
|
60
|
+
Follow this PR in uppstream c-ares, looks like TTLs will be added: https://github.com/c-ares/c-ares/pull/393
|
61
|
+
|
62
|
+
|
63
|
+
Installation
|
64
|
+
------------
|
65
|
+
|
66
|
+
GNU/Linux, macOS, Windows, others:
|
67
|
+
|
68
|
+
::
|
69
|
+
|
70
|
+
pip install pycares
|
71
|
+
|
72
|
+
FreeBSD:
|
73
|
+
|
74
|
+
::
|
75
|
+
|
76
|
+
cd /usr/ports/dns/py-pycares && make install
|
77
|
+
|
78
|
+
|
79
|
+
IDNA 2008 support
|
80
|
+
^^^^^^^^^^^^^^^^^
|
81
|
+
|
82
|
+
If the ``idna`` package is installed, pycares will support IDNA 2008 encoding otherwise the builtin idna codec will be used,
|
83
|
+
which provides IDNA 2003 support.
|
84
|
+
|
85
|
+
You can force this at installation time as follows:
|
86
|
+
|
87
|
+
::
|
88
|
+
|
89
|
+
pip install pycares[idna]
|
90
|
+
|
91
|
+
|
92
|
+
Running the test suite
|
93
|
+
----------------------
|
94
|
+
|
95
|
+
From the top level directory, run: ``python -m unittest -v``
|
96
|
+
|
97
|
+
NOTE: Running the tests requires internet access and are somewhat environment sensitive because real DNS quesries
|
98
|
+
are made, there is no mocking. If you observe a failure that the CI cannot reproduce, please try to setup an
|
99
|
+
environment as close as the current CI.
|
100
|
+
|
101
|
+
|
102
|
+
Using it from the cli, a la dig
|
103
|
+
-------------------------------
|
104
|
+
|
105
|
+
This module can be used directly from the command line in a similar fashion to dig (limited, of course):
|
106
|
+
|
107
|
+
::
|
108
|
+
|
109
|
+
$ python -m pycares google.com
|
110
|
+
;; QUESTION SECTION:
|
111
|
+
;google.com IN A
|
112
|
+
|
113
|
+
;; ANSWER SECTION:
|
114
|
+
google.com 300 IN A 172.217.17.142
|
115
|
+
|
116
|
+
$ python -m pycares mx google.com
|
117
|
+
;; QUESTION SECTION:
|
118
|
+
;google.com IN MX
|
119
|
+
|
120
|
+
;; ANSWER SECTION:
|
121
|
+
google.com 600 IN MX 50 alt4.aspmx.l.google.com
|
122
|
+
google.com 600 IN MX 10 aspmx.l.google.com
|
123
|
+
google.com 600 IN MX 40 alt3.aspmx.l.google.com
|
124
|
+
google.com 600 IN MX 20 alt1.aspmx.l.google.com
|
125
|
+
google.com 600 IN MX 30 alt2.aspmx.l.google.com
|
126
|
+
|
127
|
+
|
128
|
+
Author
|
129
|
+
------
|
130
|
+
|
131
|
+
Saúl Ibarra Corretgé <s@saghul.net>
|
132
|
+
|
133
|
+
|
134
|
+
License
|
135
|
+
-------
|
136
|
+
|
137
|
+
Unless stated otherwise on-file pycares uses the MIT license, check LICENSE file.
|
138
|
+
|
139
|
+
|
140
|
+
Supported Python versions
|
141
|
+
-------------------------
|
142
|
+
|
143
|
+
Python >= 3.9 are supported. Both CPython and PyPy are supported.
|
144
|
+
|
145
|
+
|
146
|
+
Contributing
|
147
|
+
------------
|
148
|
+
|
149
|
+
If you'd like to contribute, fork the project, make a patch and send a pull
|
150
|
+
request. Have a look at the surrounding code and please, make yours look
|
151
|
+
alike :-)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
pycares/__init__.py,sha256=EIkDaFs0MmRAYi6Ce2VzcA3H14-25zPyiD_6TfSBCSQ,30609
|
2
|
+
pycares/__main__.py,sha256=-WwwGX4NQ8hpOqrNuCy59quCQJt7IAwQXdQjga5s4WA,2880
|
3
|
+
pycares/_cares.pyd,sha256=lda5UE9DOhQQlJpPbieXRsLYk7OYHybPy97BnquuXzQ,107008
|
4
|
+
pycares/_version.py,sha256=LndxDVTCo2jR91ipCX7gSKfdLq7B21t2ofpRE-CSa_E,25
|
5
|
+
pycares/errno.py,sha256=cForwpErZsnPUQYttEgUjWZgi37cyrOGCWxUcO76HUE,1089
|
6
|
+
pycares/utils.py,sha256=lKohvVcx0fjDcbkK0hGgvgY5WWnshj1Snxy-gGRbDNo,1399
|
7
|
+
pycares-4.5.0.dist-info/LICENSE,sha256=ZzIVbIpf5QFzaiLCDSjxhvH5EViAWLVO-W4ZgBzWvb8,1090
|
8
|
+
pycares-4.5.0.dist-info/METADATA,sha256=2zq6M_4O0AU22YCieCf9t1DXPLZzHvlce5fYCA1JFYs,4331
|
9
|
+
pycares-4.5.0.dist-info/WHEEL,sha256=pUXSsnGWJ09R6rv65E7mPoM8rWovBqrkySmEuK3ly_A,97
|
10
|
+
pycares-4.5.0.dist-info/top_level.txt,sha256=nIeo7L2XUVBQZO2YE6pH7tlKaBWTfmmRcXbqe_NWYCw,15
|
11
|
+
pycares-4.5.0.dist-info/RECORD,,
|