pycares 4.9.0__cp311-cp311-win_arm64.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 +1083 -0
- pycares/__main__.py +84 -0
- pycares/_cares.pyd +0 -0
- pycares/_version.py +2 -0
- pycares/errno.py +69 -0
- pycares/py.typed +0 -0
- pycares/utils.py +53 -0
- pycares-4.9.0.dist-info/METADATA +164 -0
- pycares-4.9.0.dist-info/RECORD +12 -0
- pycares-4.9.0.dist-info/WHEEL +5 -0
- pycares-4.9.0.dist-info/licenses/LICENSE +20 -0
- pycares-4.9.0.dist-info/top_level.txt +2 -0
pycares/__init__.py
ADDED
@@ -0,0 +1,1083 @@
|
|
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 socket
|
13
|
+
import math
|
14
|
+
import threading
|
15
|
+
import time
|
16
|
+
import weakref
|
17
|
+
from collections.abc import Callable, Iterable
|
18
|
+
from contextlib import suppress
|
19
|
+
from typing import Any, Callable, Optional, Dict, Union
|
20
|
+
from queue import SimpleQueue
|
21
|
+
|
22
|
+
IP4 = tuple[str, int]
|
23
|
+
IP6 = tuple[str, int, int, int]
|
24
|
+
|
25
|
+
# Flag values
|
26
|
+
ARES_FLAG_USEVC = _lib.ARES_FLAG_USEVC
|
27
|
+
ARES_FLAG_PRIMARY = _lib.ARES_FLAG_PRIMARY
|
28
|
+
ARES_FLAG_IGNTC = _lib.ARES_FLAG_IGNTC
|
29
|
+
ARES_FLAG_NORECURSE = _lib.ARES_FLAG_NORECURSE
|
30
|
+
ARES_FLAG_STAYOPEN = _lib.ARES_FLAG_STAYOPEN
|
31
|
+
ARES_FLAG_NOSEARCH = _lib.ARES_FLAG_NOSEARCH
|
32
|
+
ARES_FLAG_NOALIASES = _lib.ARES_FLAG_NOALIASES
|
33
|
+
ARES_FLAG_NOCHECKRESP = _lib.ARES_FLAG_NOCHECKRESP
|
34
|
+
ARES_FLAG_EDNS = _lib.ARES_FLAG_EDNS
|
35
|
+
ARES_FLAG_NO_DFLT_SVR = _lib.ARES_FLAG_NO_DFLT_SVR
|
36
|
+
|
37
|
+
# Nameinfo flag values
|
38
|
+
ARES_NI_NOFQDN = _lib.ARES_NI_NOFQDN
|
39
|
+
ARES_NI_NUMERICHOST = _lib.ARES_NI_NUMERICHOST
|
40
|
+
ARES_NI_NAMEREQD = _lib.ARES_NI_NAMEREQD
|
41
|
+
ARES_NI_NUMERICSERV = _lib.ARES_NI_NUMERICSERV
|
42
|
+
ARES_NI_DGRAM = _lib.ARES_NI_DGRAM
|
43
|
+
ARES_NI_TCP = _lib.ARES_NI_TCP
|
44
|
+
ARES_NI_UDP = _lib.ARES_NI_UDP
|
45
|
+
ARES_NI_SCTP = _lib.ARES_NI_SCTP
|
46
|
+
ARES_NI_DCCP = _lib.ARES_NI_DCCP
|
47
|
+
ARES_NI_NUMERICSCOPE = _lib.ARES_NI_NUMERICSCOPE
|
48
|
+
ARES_NI_LOOKUPHOST = _lib.ARES_NI_LOOKUPHOST
|
49
|
+
ARES_NI_LOOKUPSERVICE = _lib.ARES_NI_LOOKUPSERVICE
|
50
|
+
ARES_NI_IDN = _lib.ARES_NI_IDN
|
51
|
+
ARES_NI_IDN_ALLOW_UNASSIGNED = _lib.ARES_NI_IDN_ALLOW_UNASSIGNED
|
52
|
+
ARES_NI_IDN_USE_STD3_ASCII_RULES = _lib.ARES_NI_IDN_USE_STD3_ASCII_RULES
|
53
|
+
|
54
|
+
# Bad socket
|
55
|
+
ARES_SOCKET_BAD = _lib.ARES_SOCKET_BAD
|
56
|
+
|
57
|
+
# Query types
|
58
|
+
QUERY_TYPE_A = _lib.T_A
|
59
|
+
QUERY_TYPE_AAAA = _lib.T_AAAA
|
60
|
+
QUERY_TYPE_ANY = _lib.T_ANY
|
61
|
+
QUERY_TYPE_CAA = _lib.T_CAA
|
62
|
+
QUERY_TYPE_CNAME = _lib.T_CNAME
|
63
|
+
QUERY_TYPE_MX = _lib.T_MX
|
64
|
+
QUERY_TYPE_NAPTR = _lib.T_NAPTR
|
65
|
+
QUERY_TYPE_NS = _lib.T_NS
|
66
|
+
QUERY_TYPE_PTR = _lib.T_PTR
|
67
|
+
QUERY_TYPE_SOA = _lib.T_SOA
|
68
|
+
QUERY_TYPE_SRV = _lib.T_SRV
|
69
|
+
QUERY_TYPE_TXT = _lib.T_TXT
|
70
|
+
|
71
|
+
# Query classes
|
72
|
+
QUERY_CLASS_IN = _lib.C_IN
|
73
|
+
QUERY_CLASS_CHAOS = _lib.C_CHAOS
|
74
|
+
QUERY_CLASS_HS = _lib.C_HS
|
75
|
+
QUERY_CLASS_NONE = _lib.C_NONE
|
76
|
+
QUERY_CLASS_ANY = _lib.C_ANY
|
77
|
+
|
78
|
+
ARES_VERSION = maybe_str(_ffi.string(_lib.ares_version(_ffi.NULL)))
|
79
|
+
PYCARES_ADDRTTL_SIZE = 256
|
80
|
+
|
81
|
+
|
82
|
+
class AresError(Exception):
|
83
|
+
pass
|
84
|
+
|
85
|
+
|
86
|
+
# callback helpers
|
87
|
+
|
88
|
+
_handle_to_channel: Dict[Any, "Channel"] = {} # Maps handle to channel to prevent use-after-free
|
89
|
+
|
90
|
+
|
91
|
+
@_ffi.def_extern()
|
92
|
+
def _sock_state_cb(data, socket_fd, readable, writable):
|
93
|
+
# Note: sock_state_cb handle is not tracked in _handle_to_channel
|
94
|
+
# because it has a different lifecycle (tied to the channel, not individual queries)
|
95
|
+
if _ffi is None:
|
96
|
+
return
|
97
|
+
sock_state_cb = _ffi.from_handle(data)
|
98
|
+
sock_state_cb(socket_fd, readable, writable)
|
99
|
+
|
100
|
+
@_ffi.def_extern()
|
101
|
+
def _host_cb(arg, status, timeouts, hostent):
|
102
|
+
# Get callback data without removing the reference yet
|
103
|
+
if _ffi is None or arg not in _handle_to_channel:
|
104
|
+
return
|
105
|
+
|
106
|
+
callback = _ffi.from_handle(arg)
|
107
|
+
|
108
|
+
if status != _lib.ARES_SUCCESS:
|
109
|
+
result = None
|
110
|
+
else:
|
111
|
+
result = ares_host_result(hostent)
|
112
|
+
status = None
|
113
|
+
|
114
|
+
callback(result, status)
|
115
|
+
_handle_to_channel.pop(arg, None)
|
116
|
+
|
117
|
+
@_ffi.def_extern()
|
118
|
+
def _nameinfo_cb(arg, status, timeouts, node, service):
|
119
|
+
# Get callback data without removing the reference yet
|
120
|
+
if _ffi is None or arg not in _handle_to_channel:
|
121
|
+
return
|
122
|
+
|
123
|
+
callback = _ffi.from_handle(arg)
|
124
|
+
|
125
|
+
if status != _lib.ARES_SUCCESS:
|
126
|
+
result = None
|
127
|
+
else:
|
128
|
+
result = ares_nameinfo_result(node, service)
|
129
|
+
status = None
|
130
|
+
|
131
|
+
callback(result, status)
|
132
|
+
_handle_to_channel.pop(arg, None)
|
133
|
+
|
134
|
+
@_ffi.def_extern()
|
135
|
+
def _query_cb(arg, status, timeouts, abuf, alen):
|
136
|
+
# Get callback data without removing the reference yet
|
137
|
+
if _ffi is None or arg not in _handle_to_channel:
|
138
|
+
return
|
139
|
+
|
140
|
+
callback, query_type = _ffi.from_handle(arg)
|
141
|
+
|
142
|
+
if status == _lib.ARES_SUCCESS:
|
143
|
+
if query_type == _lib.T_ANY:
|
144
|
+
result = []
|
145
|
+
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):
|
146
|
+
r, status = parse_result(qtype, abuf, alen)
|
147
|
+
if status not in (None, _lib.ARES_ENODATA, _lib.ARES_EBADRESP):
|
148
|
+
result = None
|
149
|
+
break
|
150
|
+
if r is not None:
|
151
|
+
if isinstance(r, Iterable):
|
152
|
+
result.extend(r)
|
153
|
+
else:
|
154
|
+
result.append(r)
|
155
|
+
else:
|
156
|
+
status = None
|
157
|
+
else:
|
158
|
+
result, status = parse_result(query_type, abuf, alen)
|
159
|
+
else:
|
160
|
+
result = None
|
161
|
+
|
162
|
+
callback(result, status)
|
163
|
+
_handle_to_channel.pop(arg, None)
|
164
|
+
|
165
|
+
@_ffi.def_extern()
|
166
|
+
def _addrinfo_cb(arg, status, timeouts, res):
|
167
|
+
# Get callback data without removing the reference yet
|
168
|
+
if _ffi is None or arg not in _handle_to_channel:
|
169
|
+
return
|
170
|
+
|
171
|
+
callback = _ffi.from_handle(arg)
|
172
|
+
|
173
|
+
if status != _lib.ARES_SUCCESS:
|
174
|
+
result = None
|
175
|
+
else:
|
176
|
+
result = ares_addrinfo_result(res)
|
177
|
+
status = None
|
178
|
+
|
179
|
+
callback(result, status)
|
180
|
+
_handle_to_channel.pop(arg, None)
|
181
|
+
|
182
|
+
def parse_result(query_type, abuf, alen):
|
183
|
+
if query_type == _lib.T_A:
|
184
|
+
addrttls = _ffi.new("struct ares_addrttl[]", PYCARES_ADDRTTL_SIZE)
|
185
|
+
naddrttls = _ffi.new("int*", PYCARES_ADDRTTL_SIZE)
|
186
|
+
parse_status = _lib.ares_parse_a_reply(abuf, alen, _ffi.NULL, addrttls, naddrttls)
|
187
|
+
if parse_status != _lib.ARES_SUCCESS:
|
188
|
+
result = None
|
189
|
+
status = parse_status
|
190
|
+
else:
|
191
|
+
result = [ares_query_a_result(addrttls[i]) for i in range(naddrttls[0])]
|
192
|
+
status = None
|
193
|
+
elif query_type == _lib.T_AAAA:
|
194
|
+
addrttls = _ffi.new("struct ares_addr6ttl[]", PYCARES_ADDRTTL_SIZE)
|
195
|
+
naddrttls = _ffi.new("int*", PYCARES_ADDRTTL_SIZE)
|
196
|
+
parse_status = _lib.ares_parse_aaaa_reply(abuf, alen, _ffi.NULL, addrttls, naddrttls)
|
197
|
+
if parse_status != _lib.ARES_SUCCESS:
|
198
|
+
result = None
|
199
|
+
status = parse_status
|
200
|
+
else:
|
201
|
+
result = [ares_query_aaaa_result(addrttls[i]) for i in range(naddrttls[0])]
|
202
|
+
status = None
|
203
|
+
elif query_type == _lib.T_CAA:
|
204
|
+
caa_reply = _ffi.new("struct ares_caa_reply **")
|
205
|
+
parse_status = _lib.ares_parse_caa_reply(abuf, alen, caa_reply)
|
206
|
+
if parse_status != _lib.ARES_SUCCESS:
|
207
|
+
result = None
|
208
|
+
status = parse_status
|
209
|
+
else:
|
210
|
+
result = []
|
211
|
+
caa_reply_ptr = caa_reply[0]
|
212
|
+
while caa_reply_ptr != _ffi.NULL:
|
213
|
+
result.append(ares_query_caa_result(caa_reply_ptr))
|
214
|
+
caa_reply_ptr = caa_reply_ptr.next
|
215
|
+
_lib.ares_free_data(caa_reply[0])
|
216
|
+
status = None
|
217
|
+
elif query_type == _lib.T_CNAME:
|
218
|
+
host = _ffi.new("struct hostent **")
|
219
|
+
parse_status = _lib.ares_parse_a_reply(abuf, alen, host, _ffi.NULL, _ffi.NULL)
|
220
|
+
if parse_status != _lib.ARES_SUCCESS:
|
221
|
+
result = None
|
222
|
+
status = parse_status
|
223
|
+
else:
|
224
|
+
result = ares_query_cname_result(host[0])
|
225
|
+
_lib.ares_free_hostent(host[0])
|
226
|
+
status = None
|
227
|
+
elif query_type == _lib.T_MX:
|
228
|
+
mx_reply = _ffi.new("struct ares_mx_reply **")
|
229
|
+
parse_status = _lib.ares_parse_mx_reply(abuf, alen, mx_reply)
|
230
|
+
if parse_status != _lib.ARES_SUCCESS:
|
231
|
+
result = None
|
232
|
+
status = parse_status
|
233
|
+
else:
|
234
|
+
result = []
|
235
|
+
mx_reply_ptr = mx_reply[0]
|
236
|
+
while mx_reply_ptr != _ffi.NULL:
|
237
|
+
result.append(ares_query_mx_result(mx_reply_ptr))
|
238
|
+
mx_reply_ptr = mx_reply_ptr.next
|
239
|
+
_lib.ares_free_data(mx_reply[0])
|
240
|
+
status = None
|
241
|
+
elif query_type == _lib.T_NAPTR:
|
242
|
+
naptr_reply = _ffi.new("struct ares_naptr_reply **")
|
243
|
+
parse_status = _lib.ares_parse_naptr_reply(abuf, alen, naptr_reply)
|
244
|
+
if parse_status != _lib.ARES_SUCCESS:
|
245
|
+
result = None
|
246
|
+
status = parse_status
|
247
|
+
else:
|
248
|
+
result = []
|
249
|
+
naptr_reply_ptr = naptr_reply[0]
|
250
|
+
while naptr_reply_ptr != _ffi.NULL:
|
251
|
+
result.append(ares_query_naptr_result(naptr_reply_ptr))
|
252
|
+
naptr_reply_ptr = naptr_reply_ptr.next
|
253
|
+
_lib.ares_free_data(naptr_reply[0])
|
254
|
+
status = None
|
255
|
+
elif query_type == _lib.T_NS:
|
256
|
+
hostent = _ffi.new("struct hostent **")
|
257
|
+
parse_status = _lib.ares_parse_ns_reply(abuf, alen, hostent)
|
258
|
+
if parse_status != _lib.ARES_SUCCESS:
|
259
|
+
result = None
|
260
|
+
status = parse_status
|
261
|
+
else:
|
262
|
+
result = []
|
263
|
+
host = hostent[0]
|
264
|
+
i = 0
|
265
|
+
while host.h_aliases[i] != _ffi.NULL:
|
266
|
+
result.append(ares_query_ns_result(host.h_aliases[i]))
|
267
|
+
i += 1
|
268
|
+
_lib.ares_free_hostent(host)
|
269
|
+
status = None
|
270
|
+
elif query_type == _lib.T_PTR:
|
271
|
+
hostent = _ffi.new("struct hostent **")
|
272
|
+
parse_status = _lib.ares_parse_ptr_reply(abuf, alen, _ffi.NULL, 0, socket.AF_UNSPEC, hostent)
|
273
|
+
if parse_status != _lib.ARES_SUCCESS:
|
274
|
+
result = None
|
275
|
+
status = parse_status
|
276
|
+
else:
|
277
|
+
aliases = []
|
278
|
+
host = hostent[0]
|
279
|
+
i = 0
|
280
|
+
while host.h_aliases[i] != _ffi.NULL:
|
281
|
+
aliases.append(maybe_str(_ffi.string(host.h_aliases[i])))
|
282
|
+
i += 1
|
283
|
+
result = ares_query_ptr_result(host, aliases)
|
284
|
+
_lib.ares_free_hostent(host)
|
285
|
+
status = None
|
286
|
+
elif query_type == _lib.T_SOA:
|
287
|
+
soa_reply = _ffi.new("struct ares_soa_reply **")
|
288
|
+
parse_status = _lib.ares_parse_soa_reply(abuf, alen, soa_reply)
|
289
|
+
if parse_status != _lib.ARES_SUCCESS:
|
290
|
+
result = None
|
291
|
+
status = parse_status
|
292
|
+
else:
|
293
|
+
result = ares_query_soa_result(soa_reply[0])
|
294
|
+
_lib.ares_free_data(soa_reply[0])
|
295
|
+
status = None
|
296
|
+
elif query_type == _lib.T_SRV:
|
297
|
+
srv_reply = _ffi.new("struct ares_srv_reply **")
|
298
|
+
parse_status = _lib.ares_parse_srv_reply(abuf, alen, srv_reply)
|
299
|
+
if parse_status != _lib.ARES_SUCCESS:
|
300
|
+
result = None
|
301
|
+
status = parse_status
|
302
|
+
else:
|
303
|
+
result = []
|
304
|
+
srv_reply_ptr = srv_reply[0]
|
305
|
+
while srv_reply_ptr != _ffi.NULL:
|
306
|
+
result.append(ares_query_srv_result(srv_reply_ptr))
|
307
|
+
srv_reply_ptr = srv_reply_ptr.next
|
308
|
+
_lib.ares_free_data(srv_reply[0])
|
309
|
+
status = None
|
310
|
+
elif query_type == _lib.T_TXT:
|
311
|
+
txt_reply = _ffi.new("struct ares_txt_ext **")
|
312
|
+
parse_status = _lib.ares_parse_txt_reply_ext(abuf, alen, txt_reply)
|
313
|
+
if parse_status != _lib.ARES_SUCCESS:
|
314
|
+
result = None
|
315
|
+
status = parse_status
|
316
|
+
else:
|
317
|
+
result = []
|
318
|
+
txt_reply_ptr = txt_reply[0]
|
319
|
+
tmp_obj = None
|
320
|
+
while True:
|
321
|
+
if txt_reply_ptr == _ffi.NULL:
|
322
|
+
if tmp_obj is not None:
|
323
|
+
result.append(ares_query_txt_result(tmp_obj))
|
324
|
+
break
|
325
|
+
if txt_reply_ptr.record_start == 1:
|
326
|
+
if tmp_obj is not None:
|
327
|
+
result.append(ares_query_txt_result(tmp_obj))
|
328
|
+
tmp_obj = ares_query_txt_result_chunk(txt_reply_ptr)
|
329
|
+
else:
|
330
|
+
new_chunk = ares_query_txt_result_chunk(txt_reply_ptr)
|
331
|
+
tmp_obj.text += new_chunk.text
|
332
|
+
txt_reply_ptr = txt_reply_ptr.next
|
333
|
+
_lib.ares_free_data(txt_reply[0])
|
334
|
+
status = None
|
335
|
+
else:
|
336
|
+
raise ValueError("invalid query type specified")
|
337
|
+
|
338
|
+
return result, status
|
339
|
+
|
340
|
+
|
341
|
+
class _ChannelShutdownManager:
|
342
|
+
"""Manages channel destruction in a single background thread using SimpleQueue."""
|
343
|
+
|
344
|
+
def __init__(self) -> None:
|
345
|
+
self._queue: SimpleQueue = SimpleQueue()
|
346
|
+
self._thread: Optional[threading.Thread] = None
|
347
|
+
self._thread_started = False
|
348
|
+
|
349
|
+
def _run_safe_shutdown_loop(self) -> None:
|
350
|
+
"""Process channel destruction requests from the queue."""
|
351
|
+
while True:
|
352
|
+
# Block forever until we get a channel to destroy
|
353
|
+
channel = self._queue.get()
|
354
|
+
|
355
|
+
# Sleep for 1 second to ensure c-ares has finished processing
|
356
|
+
# Its important that c-ares is past this critcial section
|
357
|
+
# so we use a delay to ensure it has time to finish processing
|
358
|
+
# https://github.com/c-ares/c-ares/blob/4f42928848e8b73d322b15ecbe3e8d753bf8734e/src/lib/ares_process.c#L1422
|
359
|
+
time.sleep(1.0)
|
360
|
+
|
361
|
+
# Destroy the channel
|
362
|
+
if _lib is not None and channel is not None:
|
363
|
+
_lib.ares_destroy(channel[0])
|
364
|
+
|
365
|
+
def destroy_channel(self, channel) -> None:
|
366
|
+
"""
|
367
|
+
Schedule channel destruction on the background thread with a safety delay.
|
368
|
+
|
369
|
+
Thread Safety and Synchronization:
|
370
|
+
This method uses SimpleQueue which is thread-safe for putting items
|
371
|
+
from multiple threads. The background thread processes channels
|
372
|
+
sequentially with a 1-second delay before each destruction.
|
373
|
+
"""
|
374
|
+
# Put the channel in the queue
|
375
|
+
self._queue.put(channel)
|
376
|
+
|
377
|
+
# Start the background thread if not already started
|
378
|
+
if not self._thread_started:
|
379
|
+
self._thread_started = True
|
380
|
+
self._thread = threading.Thread(target=self._run_safe_shutdown_loop, daemon=True)
|
381
|
+
self._thread.start()
|
382
|
+
|
383
|
+
|
384
|
+
# Global shutdown manager instance
|
385
|
+
_shutdown_manager = _ChannelShutdownManager()
|
386
|
+
|
387
|
+
|
388
|
+
class Channel:
|
389
|
+
__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)
|
390
|
+
__qclasses__ = (_lib.C_IN, _lib.C_CHAOS, _lib.C_HS, _lib.C_NONE, _lib.C_ANY)
|
391
|
+
|
392
|
+
def __init__(self,
|
393
|
+
flags: Optional[int] = None,
|
394
|
+
timeout: Optional[float] = None,
|
395
|
+
tries: Optional[int] = None,
|
396
|
+
ndots: Optional[int] = None,
|
397
|
+
tcp_port: Optional[int] = None,
|
398
|
+
udp_port: Optional[int] = None,
|
399
|
+
servers: Optional[Iterable[Union[str, bytes]]] = None,
|
400
|
+
domains: Optional[Iterable[Union[str, bytes]]] = None,
|
401
|
+
lookups: Union[str, bytes, None] = None,
|
402
|
+
sock_state_cb: Optional[Callable[[int, bool, bool], None]] = None,
|
403
|
+
socket_send_buffer_size: Optional[int] = None,
|
404
|
+
socket_receive_buffer_size: Optional[int] = None,
|
405
|
+
rotate: bool = False,
|
406
|
+
local_ip: Union[str, bytes, None] = None,
|
407
|
+
local_dev: Optional[str] = None,
|
408
|
+
resolvconf_path: Union[str, bytes, None] = None,
|
409
|
+
event_thread: bool = False) -> None:
|
410
|
+
|
411
|
+
# Initialize _channel to None first to ensure __del__ doesn't fail
|
412
|
+
self._channel = None
|
413
|
+
|
414
|
+
channel = _ffi.new("ares_channel *")
|
415
|
+
options = _ffi.new("struct ares_options *")
|
416
|
+
optmask = 0
|
417
|
+
|
418
|
+
if flags is not None:
|
419
|
+
options.flags = flags
|
420
|
+
optmask = optmask | _lib.ARES_OPT_FLAGS
|
421
|
+
|
422
|
+
if timeout is not None:
|
423
|
+
options.timeout = int(timeout * 1000)
|
424
|
+
optmask = optmask | _lib.ARES_OPT_TIMEOUTMS
|
425
|
+
|
426
|
+
if tries is not None:
|
427
|
+
options.tries = tries
|
428
|
+
optmask = optmask | _lib.ARES_OPT_TRIES
|
429
|
+
|
430
|
+
if ndots is not None:
|
431
|
+
options.ndots = ndots
|
432
|
+
optmask = optmask | _lib.ARES_OPT_NDOTS
|
433
|
+
|
434
|
+
if tcp_port is not None:
|
435
|
+
options.tcp_port = tcp_port
|
436
|
+
optmask = optmask | _lib.ARES_OPT_TCP_PORT
|
437
|
+
|
438
|
+
if udp_port is not None:
|
439
|
+
options.udp_port = udp_port
|
440
|
+
optmask = optmask | _lib.ARES_OPT_UDP_PORT
|
441
|
+
|
442
|
+
if socket_send_buffer_size is not None:
|
443
|
+
options.socket_send_buffer_size = socket_send_buffer_size
|
444
|
+
optmask = optmask | _lib.ARES_OPT_SOCK_SNDBUF
|
445
|
+
|
446
|
+
if socket_receive_buffer_size is not None:
|
447
|
+
options.socket_receive_buffer_size = socket_receive_buffer_size
|
448
|
+
optmask = optmask | _lib.ARES_OPT_SOCK_RCVBUF
|
449
|
+
|
450
|
+
if sock_state_cb:
|
451
|
+
if not callable(sock_state_cb):
|
452
|
+
raise TypeError("sock_state_cb is not callable")
|
453
|
+
if event_thread:
|
454
|
+
raise RuntimeError("sock_state_cb and event_thread cannot be used together")
|
455
|
+
|
456
|
+
userdata = _ffi.new_handle(sock_state_cb)
|
457
|
+
|
458
|
+
# This must be kept alive while the channel is alive.
|
459
|
+
self._sock_state_cb_handle = userdata
|
460
|
+
|
461
|
+
options.sock_state_cb = _lib._sock_state_cb
|
462
|
+
options.sock_state_cb_data = userdata
|
463
|
+
optmask = optmask | _lib.ARES_OPT_SOCK_STATE_CB
|
464
|
+
|
465
|
+
if event_thread:
|
466
|
+
if not ares_threadsafety():
|
467
|
+
raise RuntimeError("c-ares is not built with thread safety")
|
468
|
+
if sock_state_cb:
|
469
|
+
raise RuntimeError("sock_state_cb and event_thread cannot be used together")
|
470
|
+
optmask = optmask | _lib.ARES_OPT_EVENT_THREAD
|
471
|
+
options.evsys = _lib.ARES_EVSYS_DEFAULT
|
472
|
+
|
473
|
+
if lookups:
|
474
|
+
options.lookups = _ffi.new('char[]', ascii_bytes(lookups))
|
475
|
+
optmask = optmask | _lib.ARES_OPT_LOOKUPS
|
476
|
+
|
477
|
+
if domains:
|
478
|
+
strs = [_ffi.new("char[]", ascii_bytes(i)) for i in domains]
|
479
|
+
c = _ffi.new("char *[%d]" % (len(domains) + 1))
|
480
|
+
for i in range(len(domains)):
|
481
|
+
c[i] = strs[i]
|
482
|
+
|
483
|
+
options.domains = c
|
484
|
+
options.ndomains = len(domains)
|
485
|
+
optmask = optmask | _lib.ARES_OPT_DOMAINS
|
486
|
+
|
487
|
+
if rotate:
|
488
|
+
optmask = optmask | _lib.ARES_OPT_ROTATE
|
489
|
+
|
490
|
+
if resolvconf_path is not None:
|
491
|
+
optmask = optmask | _lib.ARES_OPT_RESOLVCONF
|
492
|
+
options.resolvconf_path = _ffi.new('char[]', ascii_bytes(resolvconf_path))
|
493
|
+
|
494
|
+
r = _lib.ares_init_options(channel, options, optmask)
|
495
|
+
if r != _lib.ARES_SUCCESS:
|
496
|
+
raise AresError('Failed to initialize c-ares channel')
|
497
|
+
|
498
|
+
# Initialize all attributes for consistency
|
499
|
+
self._event_thread = event_thread
|
500
|
+
self._channel = channel
|
501
|
+
if servers:
|
502
|
+
self.servers = servers
|
503
|
+
|
504
|
+
if local_ip:
|
505
|
+
self.set_local_ip(local_ip)
|
506
|
+
|
507
|
+
if local_dev:
|
508
|
+
self.set_local_dev(local_dev)
|
509
|
+
|
510
|
+
def __enter__(self):
|
511
|
+
"""Enter the context manager."""
|
512
|
+
return self
|
513
|
+
|
514
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
515
|
+
"""Exit the context manager and close the channel."""
|
516
|
+
self.close()
|
517
|
+
return False
|
518
|
+
|
519
|
+
def __del__(self) -> None:
|
520
|
+
"""Ensure the channel is destroyed when the object is deleted."""
|
521
|
+
if self._channel is not None:
|
522
|
+
# Schedule channel destruction using the global shutdown manager
|
523
|
+
self._schedule_destruction()
|
524
|
+
|
525
|
+
def _create_callback_handle(self, callback_data):
|
526
|
+
"""
|
527
|
+
Create a callback handle and register it for tracking.
|
528
|
+
|
529
|
+
This ensures that:
|
530
|
+
1. The callback data is wrapped in a CFFI handle
|
531
|
+
2. The handle is mapped to this channel to keep it alive
|
532
|
+
|
533
|
+
Args:
|
534
|
+
callback_data: The data to pass to the callback (usually a callable or tuple)
|
535
|
+
|
536
|
+
Returns:
|
537
|
+
The CFFI handle that can be passed to C functions
|
538
|
+
|
539
|
+
Raises:
|
540
|
+
RuntimeError: If the channel is destroyed
|
541
|
+
|
542
|
+
"""
|
543
|
+
if self._channel is None:
|
544
|
+
raise RuntimeError("Channel is destroyed, no new queries allowed")
|
545
|
+
|
546
|
+
userdata = _ffi.new_handle(callback_data)
|
547
|
+
_handle_to_channel[userdata] = self
|
548
|
+
return userdata
|
549
|
+
|
550
|
+
def cancel(self) -> None:
|
551
|
+
_lib.ares_cancel(self._channel[0])
|
552
|
+
|
553
|
+
def reinit(self) -> None:
|
554
|
+
r = _lib.ares_reinit(self._channel[0])
|
555
|
+
if r != _lib.ARES_SUCCESS:
|
556
|
+
raise AresError(r, errno.strerror(r))
|
557
|
+
|
558
|
+
@property
|
559
|
+
def servers(self) -> list[str]:
|
560
|
+
servers = _ffi.new("struct ares_addr_node **")
|
561
|
+
|
562
|
+
r = _lib.ares_get_servers(self._channel[0], servers)
|
563
|
+
if r != _lib.ARES_SUCCESS:
|
564
|
+
raise AresError(r, errno.strerror(r))
|
565
|
+
|
566
|
+
server_list = []
|
567
|
+
server = _ffi.new("struct ares_addr_node **", servers[0])
|
568
|
+
while True:
|
569
|
+
if server == _ffi.NULL:
|
570
|
+
break
|
571
|
+
|
572
|
+
ip = _ffi.new("char []", _lib.INET6_ADDRSTRLEN)
|
573
|
+
s = server[0]
|
574
|
+
if _ffi.NULL != _lib.ares_inet_ntop(s.family, _ffi.addressof(s.addr), ip, _lib.INET6_ADDRSTRLEN):
|
575
|
+
server_list.append(maybe_str(_ffi.string(ip, _lib.INET6_ADDRSTRLEN)))
|
576
|
+
|
577
|
+
server = s.next
|
578
|
+
|
579
|
+
return server_list
|
580
|
+
|
581
|
+
@servers.setter
|
582
|
+
def servers(self, servers: Iterable[Union[str, bytes]]) -> None:
|
583
|
+
c = _ffi.new("struct ares_addr_node[%d]" % len(servers))
|
584
|
+
for i, server in enumerate(servers):
|
585
|
+
if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(server), _ffi.addressof(c[i].addr.addr4)) == 1:
|
586
|
+
c[i].family = socket.AF_INET
|
587
|
+
elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(server), _ffi.addressof(c[i].addr.addr6)) == 1:
|
588
|
+
c[i].family = socket.AF_INET6
|
589
|
+
else:
|
590
|
+
raise ValueError("invalid IP address")
|
591
|
+
|
592
|
+
if i > 0:
|
593
|
+
c[i - 1].next = _ffi.addressof(c[i])
|
594
|
+
|
595
|
+
r = _lib.ares_set_servers(self._channel[0], c)
|
596
|
+
if r != _lib.ARES_SUCCESS:
|
597
|
+
raise AresError(r, errno.strerror(r))
|
598
|
+
|
599
|
+
def getsock(self):
|
600
|
+
rfds = []
|
601
|
+
wfds = []
|
602
|
+
socks = _ffi.new("ares_socket_t [%d]" % _lib.ARES_GETSOCK_MAXNUM)
|
603
|
+
bitmask = _lib.ares_getsock(self._channel[0], socks, _lib.ARES_GETSOCK_MAXNUM)
|
604
|
+
for i in range(_lib.ARES_GETSOCK_MAXNUM):
|
605
|
+
if _lib.ARES_GETSOCK_READABLE(bitmask, i):
|
606
|
+
rfds.append(socks[i])
|
607
|
+
if _lib.ARES_GETSOCK_WRITABLE(bitmask, i):
|
608
|
+
wfds.append(socks[i])
|
609
|
+
|
610
|
+
return rfds, wfds
|
611
|
+
|
612
|
+
def process_fd(self, read_fd: int, write_fd: int) -> None:
|
613
|
+
_lib.ares_process_fd(self._channel[0], _ffi.cast("ares_socket_t", read_fd), _ffi.cast("ares_socket_t", write_fd))
|
614
|
+
|
615
|
+
def timeout(self, t = None):
|
616
|
+
maxtv = _ffi.NULL
|
617
|
+
tv = _ffi.new("struct timeval*")
|
618
|
+
|
619
|
+
if t is not None:
|
620
|
+
if t >= 0.0:
|
621
|
+
maxtv = _ffi.new("struct timeval*")
|
622
|
+
maxtv.tv_sec = int(math.floor(t))
|
623
|
+
maxtv.tv_usec = int(math.fmod(t, 1.0) * 1000000)
|
624
|
+
else:
|
625
|
+
raise ValueError("timeout needs to be a positive number or None")
|
626
|
+
|
627
|
+
_lib.ares_timeout(self._channel[0], maxtv, tv)
|
628
|
+
|
629
|
+
if tv == _ffi.NULL:
|
630
|
+
return 0.0
|
631
|
+
|
632
|
+
return (tv.tv_sec + tv.tv_usec / 1000000.0)
|
633
|
+
|
634
|
+
def gethostbyaddr(self, addr: str, callback: Callable[[Any, int], None]) -> None:
|
635
|
+
if not callable(callback):
|
636
|
+
raise TypeError("a callable is required")
|
637
|
+
|
638
|
+
addr4 = _ffi.new("struct in_addr*")
|
639
|
+
addr6 = _ffi.new("struct ares_in6_addr*")
|
640
|
+
if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(addr), (addr4)) == 1:
|
641
|
+
address = addr4
|
642
|
+
family = socket.AF_INET
|
643
|
+
elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(addr), (addr6)) == 1:
|
644
|
+
address = addr6
|
645
|
+
family = socket.AF_INET6
|
646
|
+
else:
|
647
|
+
raise ValueError("invalid IP address")
|
648
|
+
|
649
|
+
userdata = self._create_callback_handle(callback)
|
650
|
+
_lib.ares_gethostbyaddr(self._channel[0], address, _ffi.sizeof(address[0]), family, _lib._host_cb, userdata)
|
651
|
+
|
652
|
+
def gethostbyname(self, name: str, family: socket.AddressFamily, callback: Callable[[Any, int], None]) -> None:
|
653
|
+
if not callable(callback):
|
654
|
+
raise TypeError("a callable is required")
|
655
|
+
|
656
|
+
userdata = self._create_callback_handle(callback)
|
657
|
+
_lib.ares_gethostbyname(self._channel[0], parse_name(name), family, _lib._host_cb, userdata)
|
658
|
+
|
659
|
+
def getaddrinfo(
|
660
|
+
self,
|
661
|
+
host: str,
|
662
|
+
port: Optional[int],
|
663
|
+
callback: Callable[[Any, int], None],
|
664
|
+
family: socket.AddressFamily = 0,
|
665
|
+
type: int = 0,
|
666
|
+
proto: int = 0,
|
667
|
+
flags: int = 0
|
668
|
+
) -> None:
|
669
|
+
if not callable(callback):
|
670
|
+
raise TypeError("a callable is required")
|
671
|
+
|
672
|
+
if port is None:
|
673
|
+
service = _ffi.NULL
|
674
|
+
elif isinstance(port, int):
|
675
|
+
service = str(port).encode('ascii')
|
676
|
+
else:
|
677
|
+
service = ascii_bytes(port)
|
678
|
+
|
679
|
+
userdata = self._create_callback_handle(callback)
|
680
|
+
|
681
|
+
hints = _ffi.new('struct ares_addrinfo_hints*')
|
682
|
+
hints.ai_flags = flags
|
683
|
+
hints.ai_family = family
|
684
|
+
hints.ai_socktype = type
|
685
|
+
hints.ai_protocol = proto
|
686
|
+
_lib.ares_getaddrinfo(self._channel[0], parse_name(host), service, hints, _lib._addrinfo_cb, userdata)
|
687
|
+
|
688
|
+
def query(self, name: str, query_type: str, callback: Callable[[Any, int], None], query_class: Optional[str] = None) -> None:
|
689
|
+
self._do_query(_lib.ares_query, name, query_type, callback, query_class=query_class)
|
690
|
+
|
691
|
+
def search(self, name, query_type, callback, query_class=None):
|
692
|
+
self._do_query(_lib.ares_search, name, query_type, callback, query_class=query_class)
|
693
|
+
|
694
|
+
def _do_query(self, func, name, query_type, callback, query_class=None):
|
695
|
+
if not callable(callback):
|
696
|
+
raise TypeError('a callable is required')
|
697
|
+
|
698
|
+
if query_type not in self.__qtypes__:
|
699
|
+
raise ValueError('invalid query type specified')
|
700
|
+
|
701
|
+
if query_class is None:
|
702
|
+
query_class = _lib.C_IN
|
703
|
+
|
704
|
+
if query_class not in self.__qclasses__:
|
705
|
+
raise ValueError('invalid query class specified')
|
706
|
+
|
707
|
+
userdata = self._create_callback_handle((callback, query_type))
|
708
|
+
func(self._channel[0], parse_name(name), query_class, query_type, _lib._query_cb, userdata)
|
709
|
+
|
710
|
+
def set_local_ip(self, ip):
|
711
|
+
addr4 = _ffi.new("struct in_addr*")
|
712
|
+
addr6 = _ffi.new("struct ares_in6_addr*")
|
713
|
+
if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), addr4) == 1:
|
714
|
+
_lib.ares_set_local_ip4(self._channel[0], socket.ntohl(addr4.s_addr))
|
715
|
+
elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), addr6) == 1:
|
716
|
+
_lib.ares_set_local_ip6(self._channel[0], addr6)
|
717
|
+
else:
|
718
|
+
raise ValueError("invalid IP address")
|
719
|
+
|
720
|
+
def getnameinfo(self, address: Union[IP4, IP6], flags: int, callback: Callable[[Any, int], None]) -> None:
|
721
|
+
if not callable(callback):
|
722
|
+
raise TypeError("a callable is required")
|
723
|
+
|
724
|
+
if len(address) == 2:
|
725
|
+
(ip, port) = address
|
726
|
+
sa4 = _ffi.new("struct sockaddr_in*")
|
727
|
+
if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), _ffi.addressof(sa4.sin_addr)) != 1:
|
728
|
+
raise ValueError("Invalid IPv4 address %r" % ip)
|
729
|
+
sa4.sin_family = socket.AF_INET
|
730
|
+
sa4.sin_port = socket.htons(port)
|
731
|
+
sa = sa4
|
732
|
+
elif len(address) == 4:
|
733
|
+
(ip, port, flowinfo, scope_id) = address
|
734
|
+
sa6 = _ffi.new("struct sockaddr_in6*")
|
735
|
+
if _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), _ffi.addressof(sa6.sin6_addr)) != 1:
|
736
|
+
raise ValueError("Invalid IPv6 address %r" % ip)
|
737
|
+
sa6.sin6_family = socket.AF_INET6
|
738
|
+
sa6.sin6_port = socket.htons(port)
|
739
|
+
sa6.sin6_flowinfo = socket.htonl(flowinfo) # I'm unsure about byteorder here.
|
740
|
+
sa6.sin6_scope_id = scope_id # Yes, without htonl.
|
741
|
+
sa = sa6
|
742
|
+
else:
|
743
|
+
raise ValueError("Invalid address argument")
|
744
|
+
|
745
|
+
userdata = self._create_callback_handle(callback)
|
746
|
+
_lib.ares_getnameinfo(self._channel[0], _ffi.cast("struct sockaddr*", sa), _ffi.sizeof(sa[0]), flags, _lib._nameinfo_cb, userdata)
|
747
|
+
|
748
|
+
def set_local_dev(self, dev):
|
749
|
+
_lib.ares_set_local_dev(self._channel[0], dev)
|
750
|
+
|
751
|
+
def close(self) -> None:
|
752
|
+
"""
|
753
|
+
Close the channel as soon as it's safe to do so.
|
754
|
+
|
755
|
+
This method can be called from any thread. The channel will be destroyed
|
756
|
+
safely using a background thread with a 1-second delay to ensure c-ares
|
757
|
+
has completed its cleanup.
|
758
|
+
|
759
|
+
Note: Once close() is called, no new queries can be started. Any pending
|
760
|
+
queries will be cancelled and their callbacks will receive ARES_ECANCELLED.
|
761
|
+
|
762
|
+
"""
|
763
|
+
if self._channel is None:
|
764
|
+
# Already destroyed
|
765
|
+
return
|
766
|
+
|
767
|
+
# Cancel all pending queries - this will trigger callbacks with ARES_ECANCELLED
|
768
|
+
self.cancel()
|
769
|
+
|
770
|
+
# Schedule channel destruction
|
771
|
+
self._schedule_destruction()
|
772
|
+
|
773
|
+
def _schedule_destruction(self) -> None:
|
774
|
+
"""Schedule channel destruction using the global shutdown manager."""
|
775
|
+
if self._channel is None:
|
776
|
+
return
|
777
|
+
channel = self._channel
|
778
|
+
self._channel = None
|
779
|
+
# Can't start threads during interpreter shutdown
|
780
|
+
# The channel will be cleaned up by the OS
|
781
|
+
# TODO: Change to PythonFinalizationError when Python 3.12 support is dropped
|
782
|
+
with suppress(RuntimeError):
|
783
|
+
_shutdown_manager.destroy_channel(channel)
|
784
|
+
|
785
|
+
|
786
|
+
|
787
|
+
class AresResult:
|
788
|
+
__slots__ = ()
|
789
|
+
|
790
|
+
def __repr__(self):
|
791
|
+
attrs = ['%s=%s' % (a, getattr(self, a)) for a in self.__slots__]
|
792
|
+
return '<%s> %s' % (self.__class__.__name__, ', '.join(attrs))
|
793
|
+
|
794
|
+
|
795
|
+
# DNS query result types
|
796
|
+
#
|
797
|
+
|
798
|
+
class ares_query_a_result(AresResult):
|
799
|
+
__slots__ = ('host', 'ttl')
|
800
|
+
type = 'A'
|
801
|
+
|
802
|
+
def __init__(self, ares_addrttl):
|
803
|
+
buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
|
804
|
+
_lib.ares_inet_ntop(socket.AF_INET, _ffi.addressof(ares_addrttl.ipaddr), buf, _lib.INET6_ADDRSTRLEN)
|
805
|
+
self.host = maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN))
|
806
|
+
self.ttl = ares_addrttl.ttl
|
807
|
+
|
808
|
+
|
809
|
+
class ares_query_aaaa_result(AresResult):
|
810
|
+
__slots__ = ('host', 'ttl')
|
811
|
+
type = 'AAAA'
|
812
|
+
|
813
|
+
def __init__(self, ares_addrttl):
|
814
|
+
buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
|
815
|
+
_lib.ares_inet_ntop(socket.AF_INET6, _ffi.addressof(ares_addrttl.ip6addr), buf, _lib.INET6_ADDRSTRLEN)
|
816
|
+
self.host = maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN))
|
817
|
+
self.ttl = ares_addrttl.ttl
|
818
|
+
|
819
|
+
|
820
|
+
class ares_query_caa_result(AresResult):
|
821
|
+
__slots__ = ('critical', 'property', 'value', 'ttl')
|
822
|
+
type = 'CAA'
|
823
|
+
|
824
|
+
def __init__(self, caa):
|
825
|
+
self.critical = caa.critical
|
826
|
+
self.property = maybe_str(_ffi.string(caa.property, caa.plength))
|
827
|
+
self.value = maybe_str(_ffi.string(caa.value, caa.length))
|
828
|
+
self.ttl = -1
|
829
|
+
|
830
|
+
|
831
|
+
class ares_query_cname_result(AresResult):
|
832
|
+
__slots__ = ('cname', 'ttl')
|
833
|
+
type = 'CNAME'
|
834
|
+
|
835
|
+
def __init__(self, host):
|
836
|
+
self.cname = maybe_str(_ffi.string(host.h_name))
|
837
|
+
self.ttl = -1
|
838
|
+
|
839
|
+
|
840
|
+
class ares_query_mx_result(AresResult):
|
841
|
+
__slots__ = ('host', 'priority', 'ttl')
|
842
|
+
type = 'MX'
|
843
|
+
|
844
|
+
def __init__(self, mx):
|
845
|
+
self.host = maybe_str(_ffi.string(mx.host))
|
846
|
+
self.priority = mx.priority
|
847
|
+
self.ttl = -1
|
848
|
+
|
849
|
+
|
850
|
+
class ares_query_naptr_result(AresResult):
|
851
|
+
__slots__ = ('order', 'preference', 'flags', 'service', 'regex', 'replacement', 'ttl')
|
852
|
+
type = 'NAPTR'
|
853
|
+
|
854
|
+
def __init__(self, naptr):
|
855
|
+
self.order = naptr.order
|
856
|
+
self.preference = naptr.preference
|
857
|
+
self.flags = maybe_str(_ffi.string(naptr.flags))
|
858
|
+
self.service = maybe_str(_ffi.string(naptr.service))
|
859
|
+
self.regex = maybe_str(_ffi.string(naptr.regexp))
|
860
|
+
self.replacement = maybe_str(_ffi.string(naptr.replacement))
|
861
|
+
self.ttl = -1
|
862
|
+
|
863
|
+
|
864
|
+
class ares_query_ns_result(AresResult):
|
865
|
+
__slots__ = ('host', 'ttl')
|
866
|
+
type = 'NS'
|
867
|
+
|
868
|
+
def __init__(self, ns):
|
869
|
+
self.host = maybe_str(_ffi.string(ns))
|
870
|
+
self.ttl = -1
|
871
|
+
|
872
|
+
|
873
|
+
class ares_query_ptr_result(AresResult):
|
874
|
+
__slots__ = ('name', 'ttl', 'aliases')
|
875
|
+
type = 'PTR'
|
876
|
+
|
877
|
+
def __init__(self, hostent, aliases):
|
878
|
+
self.name = maybe_str(_ffi.string(hostent.h_name))
|
879
|
+
self.aliases = aliases
|
880
|
+
self.ttl = -1
|
881
|
+
|
882
|
+
|
883
|
+
class ares_query_soa_result(AresResult):
|
884
|
+
__slots__ = ('nsname', 'hostmaster', 'serial', 'refresh', 'retry', 'expires', 'minttl', 'ttl')
|
885
|
+
type = 'SOA'
|
886
|
+
|
887
|
+
def __init__(self, soa):
|
888
|
+
self.nsname = maybe_str(_ffi.string(soa.nsname))
|
889
|
+
self.hostmaster = maybe_str(_ffi.string(soa.hostmaster))
|
890
|
+
self.serial = soa.serial
|
891
|
+
self.refresh = soa.refresh
|
892
|
+
self.retry = soa.retry
|
893
|
+
self.expires = soa.expire
|
894
|
+
self.minttl = soa.minttl
|
895
|
+
self.ttl = -1
|
896
|
+
|
897
|
+
|
898
|
+
class ares_query_srv_result(AresResult):
|
899
|
+
__slots__ = ('host', 'port', 'priority', 'weight', 'ttl')
|
900
|
+
type = 'SRV'
|
901
|
+
|
902
|
+
def __init__(self, srv):
|
903
|
+
self.host = maybe_str(_ffi.string(srv.host))
|
904
|
+
self.port = srv.port
|
905
|
+
self.priority = srv.priority
|
906
|
+
self.weight = srv.weight
|
907
|
+
self.ttl = -1
|
908
|
+
|
909
|
+
|
910
|
+
class ares_query_txt_result(AresResult):
|
911
|
+
__slots__ = ('text', 'ttl')
|
912
|
+
type = 'TXT'
|
913
|
+
|
914
|
+
def __init__(self, txt_chunk):
|
915
|
+
self.text = maybe_str(txt_chunk.text)
|
916
|
+
self.ttl = -1
|
917
|
+
|
918
|
+
|
919
|
+
class ares_query_txt_result_chunk(AresResult):
|
920
|
+
__slots__ = ('text', 'ttl')
|
921
|
+
type = 'TXT'
|
922
|
+
|
923
|
+
def __init__(self, txt):
|
924
|
+
self.text = _ffi.string(txt.txt)
|
925
|
+
self.ttl = -1
|
926
|
+
|
927
|
+
|
928
|
+
# Other result types
|
929
|
+
#
|
930
|
+
|
931
|
+
class ares_host_result(AresResult):
|
932
|
+
__slots__ = ('name', 'aliases', 'addresses')
|
933
|
+
|
934
|
+
def __init__(self, hostent):
|
935
|
+
self.name = maybe_str(_ffi.string(hostent.h_name))
|
936
|
+
self.aliases = []
|
937
|
+
self.addresses = []
|
938
|
+
i = 0
|
939
|
+
while hostent.h_aliases[i] != _ffi.NULL:
|
940
|
+
self.aliases.append(maybe_str(_ffi.string(hostent.h_aliases[i])))
|
941
|
+
i += 1
|
942
|
+
|
943
|
+
i = 0
|
944
|
+
while hostent.h_addr_list[i] != _ffi.NULL:
|
945
|
+
buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
|
946
|
+
if _ffi.NULL != _lib.ares_inet_ntop(hostent.h_addrtype, hostent.h_addr_list[i], buf, _lib.INET6_ADDRSTRLEN):
|
947
|
+
self.addresses.append(maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN)))
|
948
|
+
i += 1
|
949
|
+
|
950
|
+
|
951
|
+
class ares_nameinfo_result(AresResult):
|
952
|
+
__slots__ = ('node', 'service')
|
953
|
+
|
954
|
+
def __init__(self, node, service):
|
955
|
+
self.node = maybe_str(_ffi.string(node))
|
956
|
+
self.service = maybe_str(_ffi.string(service)) if service != _ffi.NULL else None
|
957
|
+
|
958
|
+
|
959
|
+
class ares_addrinfo_node_result(AresResult):
|
960
|
+
__slots__ = ('ttl', 'flags', 'family', 'socktype', 'protocol', 'addr')
|
961
|
+
|
962
|
+
def __init__(self, ares_node):
|
963
|
+
self.ttl = ares_node.ai_ttl
|
964
|
+
self.flags = ares_node.ai_flags
|
965
|
+
self.socktype = ares_node.ai_socktype
|
966
|
+
self.protocol = ares_node.ai_protocol
|
967
|
+
|
968
|
+
addr = ares_node.ai_addr
|
969
|
+
assert addr.sa_family == ares_node.ai_family
|
970
|
+
ip = _ffi.new("char []", _lib.INET6_ADDRSTRLEN)
|
971
|
+
if addr.sa_family == socket.AF_INET:
|
972
|
+
self.family = socket.AF_INET
|
973
|
+
s = _ffi.cast("struct sockaddr_in*", addr)
|
974
|
+
if _ffi.NULL != _lib.ares_inet_ntop(s.sin_family, _ffi.addressof(s.sin_addr), ip, _lib.INET6_ADDRSTRLEN):
|
975
|
+
# (address, port) 2-tuple for AF_INET
|
976
|
+
self.addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin_port))
|
977
|
+
elif addr.sa_family == socket.AF_INET6:
|
978
|
+
self.family = socket.AF_INET6
|
979
|
+
s = _ffi.cast("struct sockaddr_in6*", addr)
|
980
|
+
if _ffi.NULL != _lib.ares_inet_ntop(s.sin6_family, _ffi.addressof(s.sin6_addr), ip, _lib.INET6_ADDRSTRLEN):
|
981
|
+
# (address, port, flow info, scope id) 4-tuple for AF_INET6
|
982
|
+
self.addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin6_port), s.sin6_flowinfo, s.sin6_scope_id)
|
983
|
+
else:
|
984
|
+
raise ValueError("invalid sockaddr family")
|
985
|
+
|
986
|
+
|
987
|
+
class ares_addrinfo_cname_result(AresResult):
|
988
|
+
__slots__ = ('ttl', 'alias', 'name')
|
989
|
+
|
990
|
+
def __init__(self, ares_cname):
|
991
|
+
self.ttl = ares_cname.ttl
|
992
|
+
self.alias = maybe_str(_ffi.string(ares_cname.alias))
|
993
|
+
self.name = maybe_str(_ffi.string(ares_cname.name))
|
994
|
+
|
995
|
+
|
996
|
+
class ares_addrinfo_result(AresResult):
|
997
|
+
__slots__ = ('cnames', 'nodes')
|
998
|
+
|
999
|
+
def __init__(self, ares_addrinfo):
|
1000
|
+
self.cnames = []
|
1001
|
+
self.nodes = []
|
1002
|
+
cname_ptr = ares_addrinfo.cnames
|
1003
|
+
while cname_ptr != _ffi.NULL:
|
1004
|
+
self.cnames.append(ares_addrinfo_cname_result(cname_ptr))
|
1005
|
+
cname_ptr = cname_ptr.next
|
1006
|
+
node_ptr = ares_addrinfo.nodes
|
1007
|
+
while node_ptr != _ffi.NULL:
|
1008
|
+
self.nodes.append(ares_addrinfo_node_result(node_ptr))
|
1009
|
+
node_ptr = node_ptr.ai_next
|
1010
|
+
_lib.ares_freeaddrinfo(ares_addrinfo)
|
1011
|
+
|
1012
|
+
|
1013
|
+
def ares_threadsafety() -> bool:
|
1014
|
+
"""
|
1015
|
+
Check if c-ares was compiled with thread safety support.
|
1016
|
+
|
1017
|
+
:return: True if thread-safe, False otherwise.
|
1018
|
+
:rtype: bool
|
1019
|
+
"""
|
1020
|
+
return bool(_lib.ares_threadsafety())
|
1021
|
+
|
1022
|
+
__all__ = (
|
1023
|
+
"ARES_FLAG_USEVC",
|
1024
|
+
"ARES_FLAG_PRIMARY",
|
1025
|
+
"ARES_FLAG_IGNTC",
|
1026
|
+
"ARES_FLAG_NORECURSE",
|
1027
|
+
"ARES_FLAG_STAYOPEN",
|
1028
|
+
"ARES_FLAG_NOSEARCH",
|
1029
|
+
"ARES_FLAG_NOALIASES",
|
1030
|
+
"ARES_FLAG_NOCHECKRESP",
|
1031
|
+
"ARES_FLAG_EDNS",
|
1032
|
+
"ARES_FLAG_NO_DFLT_SVR",
|
1033
|
+
|
1034
|
+
# Nameinfo flag values
|
1035
|
+
"ARES_NI_NOFQDN",
|
1036
|
+
"ARES_NI_NUMERICHOST",
|
1037
|
+
"ARES_NI_NAMEREQD",
|
1038
|
+
"ARES_NI_NUMERICSERV",
|
1039
|
+
"ARES_NI_DGRAM",
|
1040
|
+
"ARES_NI_TCP",
|
1041
|
+
"ARES_NI_UDP",
|
1042
|
+
"ARES_NI_SCTP",
|
1043
|
+
"ARES_NI_DCCP",
|
1044
|
+
"ARES_NI_NUMERICSCOPE",
|
1045
|
+
"ARES_NI_LOOKUPHOST",
|
1046
|
+
"ARES_NI_LOOKUPSERVICE",
|
1047
|
+
"ARES_NI_IDN",
|
1048
|
+
"ARES_NI_IDN_ALLOW_UNASSIGNED",
|
1049
|
+
"ARES_NI_IDN_USE_STD3_ASCII_RULES",
|
1050
|
+
|
1051
|
+
# Bad socket
|
1052
|
+
"ARES_SOCKET_BAD",
|
1053
|
+
|
1054
|
+
|
1055
|
+
# Query types
|
1056
|
+
"QUERY_TYPE_A",
|
1057
|
+
"QUERY_TYPE_AAAA",
|
1058
|
+
"QUERY_TYPE_ANY",
|
1059
|
+
"QUERY_TYPE_CAA",
|
1060
|
+
"QUERY_TYPE_CNAME",
|
1061
|
+
"QUERY_TYPE_MX",
|
1062
|
+
"QUERY_TYPE_NAPTR",
|
1063
|
+
"QUERY_TYPE_NS",
|
1064
|
+
"QUERY_TYPE_PTR",
|
1065
|
+
"QUERY_TYPE_SOA",
|
1066
|
+
"QUERY_TYPE_SRV",
|
1067
|
+
"QUERY_TYPE_TXT",
|
1068
|
+
|
1069
|
+
# Query classes
|
1070
|
+
"QUERY_CLASS_IN",
|
1071
|
+
"QUERY_CLASS_CHAOS",
|
1072
|
+
"QUERY_CLASS_HS",
|
1073
|
+
"QUERY_CLASS_NONE",
|
1074
|
+
"QUERY_CLASS_ANY",
|
1075
|
+
|
1076
|
+
|
1077
|
+
"ARES_VERSION",
|
1078
|
+
"AresError",
|
1079
|
+
"Channel",
|
1080
|
+
"ares_threadsafety",
|
1081
|
+
"errno",
|
1082
|
+
"__version__"
|
1083
|
+
)
|