Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pycares/__init__.py: 33%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
2from ._cares import ffi as _ffi, lib as _lib
3import _cffi_backend # hint for bundler tools
5if _lib.ARES_SUCCESS != _lib.ares_library_init(_lib.ARES_LIB_INIT_ALL) or _ffi is None:
6 raise RuntimeError('Could not initialize c-ares')
8if not _lib.ares_threadsafety():
9 raise RuntimeError("c-ares is not built with thread safety")
11from . import errno
12from .utils import ascii_bytes, maybe_str, parse_name
13from ._version import __version__
15import math
16import socket
17import threading
18from collections.abc import Callable, Iterable
19from dataclasses import dataclass
20from typing import Any, Callable, Literal, Optional, Dict, Union
21from queue import SimpleQueue
23IP4 = tuple[str, int]
24IP6 = tuple[str, int, int, int]
26# Flag values
27ARES_FLAG_USEVC = _lib.ARES_FLAG_USEVC
28ARES_FLAG_PRIMARY = _lib.ARES_FLAG_PRIMARY
29ARES_FLAG_IGNTC = _lib.ARES_FLAG_IGNTC
30ARES_FLAG_NORECURSE = _lib.ARES_FLAG_NORECURSE
31ARES_FLAG_STAYOPEN = _lib.ARES_FLAG_STAYOPEN
32ARES_FLAG_NOSEARCH = _lib.ARES_FLAG_NOSEARCH
33ARES_FLAG_NOALIASES = _lib.ARES_FLAG_NOALIASES
34ARES_FLAG_NOCHECKRESP = _lib.ARES_FLAG_NOCHECKRESP
35ARES_FLAG_EDNS = _lib.ARES_FLAG_EDNS
36ARES_FLAG_NO_DFLT_SVR = _lib.ARES_FLAG_NO_DFLT_SVR
38# Nameinfo flag values
39ARES_NI_NOFQDN = _lib.ARES_NI_NOFQDN
40ARES_NI_NUMERICHOST = _lib.ARES_NI_NUMERICHOST
41ARES_NI_NAMEREQD = _lib.ARES_NI_NAMEREQD
42ARES_NI_NUMERICSERV = _lib.ARES_NI_NUMERICSERV
43ARES_NI_DGRAM = _lib.ARES_NI_DGRAM
44ARES_NI_TCP = _lib.ARES_NI_TCP
45ARES_NI_UDP = _lib.ARES_NI_UDP
46ARES_NI_SCTP = _lib.ARES_NI_SCTP
47ARES_NI_DCCP = _lib.ARES_NI_DCCP
48ARES_NI_NUMERICSCOPE = _lib.ARES_NI_NUMERICSCOPE
49ARES_NI_LOOKUPHOST = _lib.ARES_NI_LOOKUPHOST
50ARES_NI_LOOKUPSERVICE = _lib.ARES_NI_LOOKUPSERVICE
51ARES_NI_IDN = _lib.ARES_NI_IDN
52ARES_NI_IDN_ALLOW_UNASSIGNED = _lib.ARES_NI_IDN_ALLOW_UNASSIGNED
53ARES_NI_IDN_USE_STD3_ASCII_RULES = _lib.ARES_NI_IDN_USE_STD3_ASCII_RULES
55# Bad socket
56ARES_SOCKET_BAD = _lib.ARES_SOCKET_BAD
58# Query types
59QUERY_TYPE_A = _lib.ARES_REC_TYPE_A
60QUERY_TYPE_AAAA = _lib.ARES_REC_TYPE_AAAA
61QUERY_TYPE_ANY = _lib.ARES_REC_TYPE_ANY
62QUERY_TYPE_CAA = _lib.ARES_REC_TYPE_CAA
63QUERY_TYPE_CNAME = _lib.ARES_REC_TYPE_CNAME
64QUERY_TYPE_MX = _lib.ARES_REC_TYPE_MX
65QUERY_TYPE_NAPTR = _lib.ARES_REC_TYPE_NAPTR
66QUERY_TYPE_NS = _lib.ARES_REC_TYPE_NS
67QUERY_TYPE_PTR = _lib.ARES_REC_TYPE_PTR
68QUERY_TYPE_SOA = _lib.ARES_REC_TYPE_SOA
69QUERY_TYPE_SRV = _lib.ARES_REC_TYPE_SRV
70QUERY_TYPE_TXT = _lib.ARES_REC_TYPE_TXT
71QUERY_TYPE_TLSA = _lib.ARES_REC_TYPE_TLSA
72QUERY_TYPE_HTTPS = _lib.ARES_REC_TYPE_HTTPS
73QUERY_TYPE_URI = _lib.ARES_REC_TYPE_URI
75# Query classes
76QUERY_CLASS_IN = _lib.ARES_CLASS_IN
77QUERY_CLASS_CHAOS = _lib.ARES_CLASS_CHAOS
78QUERY_CLASS_HS = _lib.ARES_CLASS_HESOID
79QUERY_CLASS_NONE = _lib.ARES_CLASS_NONE
80QUERY_CLASS_ANY = _lib.ARES_CLASS_ANY
82ARES_VERSION = maybe_str(_ffi.string(_lib.ares_version(_ffi.NULL)))
83PYCARES_ADDRTTL_SIZE = 256
86class AresError(Exception):
87 pass
90# callback helpers
92_handle_to_channel: Dict[Any, "Channel"] = {} # Maps handle to channel to prevent use-after-free
95@_ffi.def_extern()
96def _sock_state_cb(data, socket_fd, readable, writable):
97 # Note: sock_state_cb handle is not tracked in _handle_to_channel
98 # because it has a different lifecycle (tied to the channel, not individual queries)
99 sock_state_cb = _ffi.from_handle(data)
100 sock_state_cb(socket_fd, readable, writable)
102@_ffi.def_extern()
103def _host_cb(arg, status, timeouts, hostent):
104 # Get callback data without removing the reference yet
105 if arg not in _handle_to_channel:
106 return
108 callback = _ffi.from_handle(arg)
110 if status != _lib.ARES_SUCCESS:
111 result = None
112 else:
113 result = parse_hostent(hostent)
114 status = None
116 callback(result, status)
117 _handle_to_channel.pop(arg, None)
119@_ffi.def_extern()
120def _nameinfo_cb(arg, status, timeouts, node, service):
121 # Get callback data without removing the reference yet
122 if arg not in _handle_to_channel:
123 return
125 callback = _ffi.from_handle(arg)
127 if status != _lib.ARES_SUCCESS:
128 result = None
129 else:
130 result = parse_nameinfo(node, service)
131 status = None
133 callback(result, status)
134 _handle_to_channel.pop(arg, None)
136@_ffi.def_extern()
137def _query_dnsrec_cb(arg, status, timeouts, dnsrec):
138 """Callback for new DNS record API queries"""
139 # Get callback data without removing the reference yet
140 if arg not in _handle_to_channel:
141 return
143 callback = _ffi.from_handle(arg)
145 if status != _lib.ARES_SUCCESS:
146 result = None
147 else:
148 result, parse_status = parse_dnsrec(dnsrec)
149 if parse_status is not None:
150 status = parse_status
151 else:
152 # Success - set status to None
153 status = None
155 callback(result, status)
156 _handle_to_channel.pop(arg, None)
159@_ffi.def_extern()
160def _addrinfo_cb(arg, status, timeouts, res):
161 # Get callback data without removing the reference yet
162 if arg not in _handle_to_channel:
163 return
165 callback = _ffi.from_handle(arg)
167 if status != _lib.ARES_SUCCESS:
168 result = None
169 else:
170 result = parse_addrinfo(res)
171 status = None
173 callback(result, status)
174 _handle_to_channel.pop(arg, None)
177def _extract_opt_params(rr, key):
178 """Extract OPT params as list of (key, value) tuples for HTTPS/SVCB records."""
179 opt_cnt = _lib.ares_dns_rr_get_opt_cnt(rr, key)
180 if opt_cnt == 0:
181 return []
182 # Collect all options as a list of (key, value) tuples
183 params = []
184 for i in range(opt_cnt):
185 val_ptr = _ffi.new("unsigned char **")
186 val_len = _ffi.new("size_t *")
187 opt_key = _lib.ares_dns_rr_get_opt(rr, key, i, val_ptr, val_len)
188 if val_ptr[0] != _ffi.NULL:
189 val = bytes(_ffi.buffer(val_ptr[0], val_len[0]))
190 else:
191 val = b''
192 params.append((opt_key, val))
193 return params
196def extract_record_data(rr, record_type):
197 """Extract type-specific data from a DNS resource record and return appropriate dataclass"""
198 if record_type == _lib.ARES_REC_TYPE_A:
199 addr = _lib.ares_dns_rr_get_addr(rr, _lib.ARES_RR_A_ADDR)
200 buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
201 _lib.ares_inet_ntop(socket.AF_INET, addr, buf, _lib.INET6_ADDRSTRLEN)
202 return ARecordData(addr=maybe_str(_ffi.string(buf)))
204 elif record_type == _lib.ARES_REC_TYPE_AAAA:
205 addr = _lib.ares_dns_rr_get_addr6(rr, _lib.ARES_RR_AAAA_ADDR)
206 buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
207 _lib.ares_inet_ntop(socket.AF_INET6, addr, buf, _lib.INET6_ADDRSTRLEN)
208 return AAAARecordData(addr=maybe_str(_ffi.string(buf)))
210 elif record_type == _lib.ARES_REC_TYPE_MX:
211 priority = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_MX_PREFERENCE)
212 exchange = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_MX_EXCHANGE)
213 return MXRecordData(priority=priority, exchange=maybe_str(_ffi.string(exchange)))
215 elif record_type == _lib.ARES_REC_TYPE_TXT:
216 # TXT records use ABIN (array of binary) for chunks
217 cnt = _lib.ares_dns_rr_get_abin_cnt(rr, _lib.ARES_RR_TXT_DATA)
218 chunks = []
219 for i in range(cnt):
220 length = _ffi.new("size_t *")
221 data = _lib.ares_dns_rr_get_abin(rr, _lib.ARES_RR_TXT_DATA, i, length)
222 if data != _ffi.NULL:
223 chunks.append(_ffi.buffer(data, length[0])[:])
224 return TXTRecordData(data=b''.join(chunks))
226 elif record_type == _lib.ARES_REC_TYPE_CAA:
227 critical = _lib.ares_dns_rr_get_u8(rr, _lib.ARES_RR_CAA_CRITICAL)
228 tag = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_CAA_TAG)
229 length = _ffi.new("size_t *")
230 value = _lib.ares_dns_rr_get_bin(rr, _lib.ARES_RR_CAA_VALUE, length)
231 value_str = maybe_str(_ffi.buffer(value, length[0])[:])
232 return CAARecordData(critical=critical, tag=maybe_str(_ffi.string(tag)), value=value_str)
234 elif record_type == _lib.ARES_REC_TYPE_CNAME:
235 cname = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_CNAME_CNAME)
236 return CNAMERecordData(cname=maybe_str(_ffi.string(cname)))
238 elif record_type == _lib.ARES_REC_TYPE_NAPTR:
239 order = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_NAPTR_ORDER)
240 preference = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_NAPTR_PREFERENCE)
241 flags = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_NAPTR_FLAGS)
242 service = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_NAPTR_SERVICES)
243 regexp = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_NAPTR_REGEXP)
244 replacement = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_NAPTR_REPLACEMENT)
245 return NAPTRRecordData(
246 order=order,
247 preference=preference,
248 flags=maybe_str(_ffi.string(flags)),
249 service=maybe_str(_ffi.string(service)),
250 regexp=maybe_str(_ffi.string(regexp)),
251 replacement=maybe_str(_ffi.string(replacement))
252 )
254 elif record_type == _lib.ARES_REC_TYPE_NS:
255 nsdname = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_NS_NSDNAME)
256 return NSRecordData(nsdname=maybe_str(_ffi.string(nsdname)))
258 elif record_type == _lib.ARES_REC_TYPE_PTR:
259 dname = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_PTR_DNAME)
260 return PTRRecordData(dname=maybe_str(_ffi.string(dname)))
262 elif record_type == _lib.ARES_REC_TYPE_SOA:
263 mname = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_SOA_MNAME)
264 rname = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_SOA_RNAME)
265 serial = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_SERIAL)
266 refresh = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_REFRESH)
267 retry = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_RETRY)
268 expire = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_EXPIRE)
269 minimum = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_MINIMUM)
270 return SOARecordData(
271 mname=maybe_str(_ffi.string(mname)),
272 rname=maybe_str(_ffi.string(rname)),
273 serial=serial,
274 refresh=refresh,
275 retry=retry,
276 expire=expire,
277 minimum=minimum
278 )
280 elif record_type == _lib.ARES_REC_TYPE_SRV:
281 priority = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_SRV_PRIORITY)
282 weight = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_SRV_WEIGHT)
283 port = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_SRV_PORT)
284 target = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_SRV_TARGET)
285 return SRVRecordData(
286 priority=priority,
287 weight=weight,
288 port=port,
289 target=maybe_str(_ffi.string(target))
290 )
292 elif record_type == _lib.ARES_REC_TYPE_TLSA:
293 cert_usage = _lib.ares_dns_rr_get_u8(rr, _lib.ARES_RR_TLSA_CERT_USAGE)
294 selector = _lib.ares_dns_rr_get_u8(rr, _lib.ARES_RR_TLSA_SELECTOR)
295 matching_type = _lib.ares_dns_rr_get_u8(rr, _lib.ARES_RR_TLSA_MATCH)
296 data_len = _ffi.new("size_t *")
297 data_ptr = _lib.ares_dns_rr_get_bin(rr, _lib.ARES_RR_TLSA_DATA, data_len)
298 cert_data = bytes(_ffi.buffer(data_ptr, data_len[0])) if data_ptr != _ffi.NULL else b''
299 return TLSARecordData(
300 cert_usage=cert_usage,
301 selector=selector,
302 matching_type=matching_type,
303 cert_association_data=cert_data
304 )
306 elif record_type == _lib.ARES_REC_TYPE_HTTPS:
307 priority = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_HTTPS_PRIORITY)
308 target = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_HTTPS_TARGET)
309 params = _extract_opt_params(rr, _lib.ARES_RR_HTTPS_PARAMS)
310 return HTTPSRecordData(
311 priority=priority,
312 target=maybe_str(_ffi.string(target)),
313 params=params
314 )
316 elif record_type == _lib.ARES_REC_TYPE_URI:
317 priority = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_URI_PRIORITY)
318 weight = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_URI_WEIGHT)
319 target = _lib.ares_dns_rr_get_str(rr, _lib.ARES_RR_URI_TARGET)
320 return URIRecordData(
321 priority=priority,
322 weight=weight,
323 target=maybe_str(_ffi.string(target))
324 )
326 else:
327 # Unknown record type - return None or raise error
328 raise ValueError(f"Unsupported DNS record type: {record_type}")
331def parse_dnsrec(dnsrec):
332 """Parse ares_dns_record_t into DNSResult with all sections"""
333 if dnsrec == _ffi.NULL:
334 return None, _lib.ARES_EBADRESP
336 answer_records = []
337 authority_records = []
338 additional_records = []
340 # Parse answer section
341 answer_count = _lib.ares_dns_record_rr_cnt(dnsrec, _lib.ARES_SECTION_ANSWER)
342 for i in range(answer_count):
343 rr = _lib.ares_dns_record_rr_get_const(dnsrec, _lib.ARES_SECTION_ANSWER, i)
344 if rr != _ffi.NULL:
345 name = maybe_str(_ffi.string(_lib.ares_dns_rr_get_name(rr)))
346 rec_type = _lib.ares_dns_rr_get_type(rr)
347 rec_class = _lib.ares_dns_rr_get_class(rr)
348 ttl = _lib.ares_dns_rr_get_ttl(rr)
350 try:
351 data = extract_record_data(rr, rec_type)
352 answer_records.append(DNSRecord(
353 name=name,
354 type=rec_type,
355 record_class=rec_class,
356 ttl=ttl,
357 data=data
358 ))
359 except (ValueError, Exception):
360 # Skip unsupported record types
361 pass
363 # Parse authority section
364 authority_count = _lib.ares_dns_record_rr_cnt(dnsrec, _lib.ARES_SECTION_AUTHORITY)
365 for i in range(authority_count):
366 rr = _lib.ares_dns_record_rr_get_const(dnsrec, _lib.ARES_SECTION_AUTHORITY, i)
367 if rr != _ffi.NULL:
368 name = maybe_str(_ffi.string(_lib.ares_dns_rr_get_name(rr)))
369 rec_type = _lib.ares_dns_rr_get_type(rr)
370 rec_class = _lib.ares_dns_rr_get_class(rr)
371 ttl = _lib.ares_dns_rr_get_ttl(rr)
373 try:
374 data = extract_record_data(rr, rec_type)
375 authority_records.append(DNSRecord(
376 name=name,
377 type=rec_type,
378 record_class=rec_class,
379 ttl=ttl,
380 data=data
381 ))
382 except (ValueError, Exception):
383 # Skip unsupported record types
384 pass
386 # Parse additional section
387 additional_count = _lib.ares_dns_record_rr_cnt(dnsrec, _lib.ARES_SECTION_ADDITIONAL)
388 for i in range(additional_count):
389 rr = _lib.ares_dns_record_rr_get_const(dnsrec, _lib.ARES_SECTION_ADDITIONAL, i)
390 if rr != _ffi.NULL:
391 name = maybe_str(_ffi.string(_lib.ares_dns_rr_get_name(rr)))
392 rec_type = _lib.ares_dns_rr_get_type(rr)
393 rec_class = _lib.ares_dns_rr_get_class(rr)
394 ttl = _lib.ares_dns_rr_get_ttl(rr)
396 try:
397 data = extract_record_data(rr, rec_type)
398 additional_records.append(DNSRecord(
399 name=name,
400 type=rec_type,
401 record_class=rec_class,
402 ttl=ttl,
403 data=data
404 ))
405 except (ValueError, Exception):
406 # Skip unsupported record types
407 pass
409 result = DNSResult(
410 answer=answer_records,
411 authority=authority_records,
412 additional=additional_records
413 )
415 return result, None
418class _ChannelShutdownManager:
419 """Manages channel destruction in a single background thread using SimpleQueue."""
421 def __init__(self) -> None:
422 self._queue: SimpleQueue = SimpleQueue()
423 self._thread: Optional[threading.Thread] = None
424 self._start_lock = threading.Lock()
426 def _run_safe_shutdown_loop(self) -> None:
427 """Process channel destruction requests from the queue."""
428 while True:
429 # Block forever until we get a channel to destroy
430 channel, _ = self._queue.get()
432 # Cancel all pending queries - this will trigger callbacks with ARES_ECANCELLED
433 _lib.ares_cancel(channel[0])
435 # Wait for all queries to finish
436 _lib.ares_queue_wait_empty(channel[0], -1)
438 # Destroy the channel
439 if channel is not None:
440 _lib.ares_destroy(channel[0])
442 def start(self) -> None:
443 """Start the background thread if not already started."""
444 if self._thread is not None:
445 return
446 with self._start_lock:
447 if self._thread is not None:
448 # Started by another thread while waiting for the lock
449 return
450 self._thread = threading.Thread(target=self._run_safe_shutdown_loop, daemon=True)
451 self._thread.start()
453 def destroy_channel(self, channel, sock_state_cb_handle) -> None:
454 """
455 Schedule channel destruction on the background thread.
457 The socket state callback handle is passed along to ensure it remains
458 alive until the channel is destroyed.
460 Thread Safety and Synchronization:
461 This method uses SimpleQueue which is thread-safe for putting items
462 from multiple threads. The background thread processes channels
463 sequentially waiting for queries to end before each destruction.
464 """
465 self._queue.put((channel, sock_state_cb_handle))
468# Global shutdown manager instance
469_shutdown_manager = _ChannelShutdownManager()
472class Channel:
473 __qtypes__ = (_lib.ARES_REC_TYPE_A, _lib.ARES_REC_TYPE_AAAA, _lib.ARES_REC_TYPE_ANY, _lib.ARES_REC_TYPE_CAA, _lib.ARES_REC_TYPE_CNAME, _lib.ARES_REC_TYPE_HTTPS, _lib.ARES_REC_TYPE_MX, _lib.ARES_REC_TYPE_NAPTR, _lib.ARES_REC_TYPE_NS, _lib.ARES_REC_TYPE_PTR, _lib.ARES_REC_TYPE_SOA, _lib.ARES_REC_TYPE_SRV, _lib.ARES_REC_TYPE_TLSA, _lib.ARES_REC_TYPE_TXT, _lib.ARES_REC_TYPE_URI)
474 __qclasses__ = (_lib.ARES_CLASS_IN, _lib.ARES_CLASS_CHAOS, _lib.ARES_CLASS_HESOID, _lib.ARES_CLASS_NONE, _lib.ARES_CLASS_ANY)
476 def __init__(self,
477 *,
478 flags: Optional[int] = None,
479 timeout: Optional[float] = None,
480 tries: Optional[int] = None,
481 ndots: Optional[int] = None,
482 tcp_port: Optional[int] = None,
483 udp_port: Optional[int] = None,
484 servers: Optional[Iterable[Union[str, bytes]]] = None,
485 domains: Optional[Iterable[Union[str, bytes]]] = None,
486 lookups: Union[str, bytes, None] = None,
487 sock_state_cb: Optional[Callable[[int, bool, bool], None]] = None,
488 socket_send_buffer_size: Optional[int] = None,
489 socket_receive_buffer_size: Optional[int] = None,
490 rotate: bool = False,
491 local_ip: Union[str, bytes, None] = None,
492 local_dev: Optional[str] = None,
493 resolvconf_path: Union[str, bytes, None] = None) -> None:
495 # Initialize _channel to None first to ensure __del__ doesn't fail
496 self._channel = None
498 # Store flags for later use (default is 0 if not specified)
499 self._flags = flags if flags is not None else 0
501 channel = _ffi.new("ares_channel *")
502 options = _ffi.new("struct ares_options *")
503 optmask = 0
505 if flags is not None:
506 options.flags = flags
507 optmask = optmask | _lib.ARES_OPT_FLAGS
509 if timeout is not None:
510 options.timeout = int(timeout * 1000)
511 optmask = optmask | _lib.ARES_OPT_TIMEOUTMS
513 if tries is not None:
514 options.tries = tries
515 optmask = optmask | _lib.ARES_OPT_TRIES
517 if ndots is not None:
518 options.ndots = ndots
519 optmask = optmask | _lib.ARES_OPT_NDOTS
521 if tcp_port is not None:
522 options.tcp_port = tcp_port
523 optmask = optmask | _lib.ARES_OPT_TCP_PORT
525 if udp_port is not None:
526 options.udp_port = udp_port
527 optmask = optmask | _lib.ARES_OPT_UDP_PORT
529 if socket_send_buffer_size is not None:
530 options.socket_send_buffer_size = socket_send_buffer_size
531 optmask = optmask | _lib.ARES_OPT_SOCK_SNDBUF
533 if socket_receive_buffer_size is not None:
534 options.socket_receive_buffer_size = socket_receive_buffer_size
535 optmask = optmask | _lib.ARES_OPT_SOCK_RCVBUF
537 if sock_state_cb:
538 if not callable(sock_state_cb):
539 raise TypeError("sock_state_cb is not callable")
541 userdata = _ffi.new_handle(sock_state_cb)
543 # This must be kept alive while the channel is alive.
544 self._sock_state_cb_handle = userdata
546 options.sock_state_cb = _lib._sock_state_cb
547 options.sock_state_cb_data = userdata
548 optmask = optmask | _lib.ARES_OPT_SOCK_STATE_CB
549 else:
550 self._sock_state_cb_handle = None
551 optmask = optmask | _lib.ARES_OPT_EVENT_THREAD
552 options.evsys = _lib.ARES_EVSYS_DEFAULT
554 if lookups:
555 options.lookups = _ffi.new('char[]', ascii_bytes(lookups))
556 optmask = optmask | _lib.ARES_OPT_LOOKUPS
558 if domains:
559 strs = [_ffi.new("char[]", ascii_bytes(i)) for i in domains]
560 c = _ffi.new("char *[%d]" % (len(domains) + 1))
561 for i in range(len(domains)):
562 c[i] = strs[i]
564 options.domains = c
565 options.ndomains = len(domains)
566 optmask = optmask | _lib.ARES_OPT_DOMAINS
568 if rotate:
569 optmask = optmask | _lib.ARES_OPT_ROTATE
571 if resolvconf_path is not None:
572 optmask = optmask | _lib.ARES_OPT_RESOLVCONF
573 options.resolvconf_path = _ffi.new('char[]', ascii_bytes(resolvconf_path))
575 r = _lib.ares_init_options(channel, options, optmask)
576 if r != _lib.ARES_SUCCESS:
577 raise AresError('Failed to initialize c-ares channel')
579 self._channel = channel
580 if servers:
581 self.servers = servers
583 if local_ip:
584 self.set_local_ip(local_ip)
586 if local_dev:
587 self.set_local_dev(local_dev)
589 # Ensure the shutdown thread is started
590 _shutdown_manager.start()
592 def __del__(self) -> None:
593 """Ensure the channel is destroyed when the object is deleted."""
594 self.close()
596 def _create_callback_handle(self, callback_data):
597 """
598 Create a callback handle and register it for tracking.
600 This ensures that:
601 1. The callback data is wrapped in a CFFI handle
602 2. The handle is mapped to this channel to keep it alive
604 Args:
605 callback_data: The data to pass to the callback (usually a callable or tuple)
607 Returns:
608 The CFFI handle that can be passed to C functions
610 Raises:
611 RuntimeError: If the channel is destroyed
613 """
614 if self._channel is None:
615 raise RuntimeError("Channel is destroyed, no new queries allowed")
617 userdata = _ffi.new_handle(callback_data)
618 _handle_to_channel[userdata] = self
619 return userdata
621 def cancel(self) -> None:
622 _lib.ares_cancel(self._channel[0])
624 def reinit(self) -> None:
625 r = _lib.ares_reinit(self._channel[0])
626 if r != _lib.ARES_SUCCESS:
627 raise AresError(r, errno.strerror(r))
629 @property
630 def servers(self) -> list[str]:
631 csv_str = _lib.ares_get_servers_csv(self._channel[0])
633 if csv_str == _ffi.NULL:
634 raise AresError(_lib.ARES_ENOMEM, errno.strerror(_lib.ARES_ENOMEM))
636 server_list = []
637 csv_string = maybe_str(_ffi.string(csv_str))
638 _lib.ares_free_string(csv_str)
639 server_list = [s.strip() for s in csv_string.split(',')]
641 return server_list
643 @servers.setter
644 def servers(self, servers: Iterable[Union[str, bytes]]) -> None:
645 server_list = [ascii_bytes(s).decode('ascii') if isinstance(s, bytes) else s for s in servers]
646 csv_str = ','.join(server_list)
648 r = _lib.ares_set_servers_csv(self._channel[0], csv_str.encode('ascii'))
649 if r != _lib.ARES_SUCCESS:
650 raise AresError(r, errno.strerror(r))
652 def process_fd(self, read_fd: int, write_fd: int) -> None:
653 _lib.ares_process_fd(self._channel[0], _ffi.cast("ares_socket_t", read_fd), _ffi.cast("ares_socket_t", write_fd))
655 def process_read_fd(self, read_fd:int) -> None:
656 _lib.ares_process_fd(self._channel[0], _ffi.cast("ares_socket_t", read_fd), _ffi.cast("ares_socket_t", ARES_SOCKET_BAD))
658 def process_write_fd(self, write_fd:int) -> None:
659 _lib.ares_process_fd(self._channel[0], _ffi.cast("ares_socket_t", ARES_SOCKET_BAD), _ffi.cast("ares_socket_t", write_fd))
661 def timeout(self, t = None):
662 maxtv = _ffi.NULL
663 tv = _ffi.new("struct timeval*")
665 if t is not None:
666 if t >= 0.0:
667 maxtv = _ffi.new("struct timeval*")
668 maxtv.tv_sec = int(math.floor(t))
669 maxtv.tv_usec = int(math.fmod(t, 1.0) * 1000000)
670 else:
671 raise ValueError("timeout needs to be a positive number or None")
673 _lib.ares_timeout(self._channel[0], maxtv, tv)
675 if tv == _ffi.NULL:
676 return 0.0
678 return (tv.tv_sec + tv.tv_usec / 1000000.0)
680 def gethostbyaddr(self, addr: str, *, callback: Callable[[Any, int], None]) -> None:
681 if not callable(callback):
682 raise TypeError("a callable is required")
684 addr4 = _ffi.new("struct in_addr*")
685 addr6 = _ffi.new("struct ares_in6_addr*")
686 if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(addr), (addr4)) == 1:
687 address = addr4
688 family = socket.AF_INET
689 elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(addr), (addr6)) == 1:
690 address = addr6
691 family = socket.AF_INET6
692 else:
693 raise ValueError("invalid IP address")
695 userdata = self._create_callback_handle(callback)
696 _lib.ares_gethostbyaddr(self._channel[0], address, _ffi.sizeof(address[0]), family, _lib._host_cb, userdata)
698 def getaddrinfo(
699 self,
700 host: str,
701 port: Optional[int],
702 *,
703 family: socket.AddressFamily = 0,
704 type: int = 0,
705 proto: int = 0,
706 flags: int = 0,
707 callback: Callable[[Any, int], None]
708 ) -> None:
709 if not callable(callback):
710 raise TypeError("a callable is required")
712 if port is None:
713 service = _ffi.NULL
714 elif isinstance(port, int):
715 service = str(port).encode('ascii')
716 else:
717 service = ascii_bytes(port)
719 userdata = self._create_callback_handle(callback)
721 hints = _ffi.new('struct ares_addrinfo_hints*')
722 hints.ai_flags = flags
723 hints.ai_family = family
724 hints.ai_socktype = type
725 hints.ai_protocol = proto
726 _lib.ares_getaddrinfo(self._channel[0], parse_name(host), service, hints, _lib._addrinfo_cb, userdata)
728 def query(self, name: str, query_type: int, *, query_class: int = QUERY_CLASS_IN, callback: Callable[[Any, int], None]) -> None:
729 """
730 Perform a DNS query.
732 Args:
733 name: Domain name to query
734 query_type: Type of query (e.g., QUERY_TYPE_A, QUERY_TYPE_AAAA, etc.)
735 query_class: Query class (default: QUERY_CLASS_IN)
736 callback: Callback function that receives (result, errno)
738 The callback will receive a DNSResult object containing answer, authority, and additional sections.
739 """
740 if not callable(callback):
741 raise TypeError('a callable is required')
743 if query_type not in self.__qtypes__:
744 raise ValueError('invalid query type specified')
746 if query_class not in self.__qclasses__:
747 raise ValueError('invalid query class specified')
749 userdata = self._create_callback_handle(callback)
750 qid = _ffi.new("unsigned short *")
751 status = _lib.ares_query_dnsrec(
752 self._channel[0],
753 parse_name(name),
754 query_class,
755 query_type,
756 _lib._query_dnsrec_cb,
757 userdata,
758 qid
759 )
760 if status != _lib.ARES_SUCCESS:
761 _handle_to_channel.pop(userdata, None)
762 raise AresError(status, errno.strerror(status))
764 def search(self, name: str, query_type: int, *, query_class: int = QUERY_CLASS_IN, callback: Callable[[Any, int], None]) -> None:
765 """
766 Perform a DNS search (honors resolv.conf search domains).
768 Args:
769 name: Domain name to search
770 query_type: Type of query (e.g., QUERY_TYPE_A, QUERY_TYPE_AAAA, etc.)
771 query_class: Query class (default: QUERY_CLASS_IN)
772 callback: Callback function that receives (result, errno)
774 The callback will receive a DNSResult object containing answer, authority, and additional sections.
775 """
776 if not callable(callback):
777 raise TypeError('a callable is required')
779 if query_type not in self.__qtypes__:
780 raise ValueError('invalid query type specified')
782 if query_class not in self.__qclasses__:
783 raise ValueError('invalid query class specified')
785 # Create a DNS record for the search query
786 # Set RD (Recursion Desired) flag unless ARES_FLAG_NORECURSE is set
787 dns_flags = 0 if (self._flags & _lib.ARES_FLAG_NORECURSE) else _lib.ARES_FLAG_RD
789 dnsrec_p = _ffi.new("ares_dns_record_t **")
790 status = _lib.ares_dns_record_create(
791 dnsrec_p,
792 0, # id (will be set by c-ares)
793 dns_flags, # flags - include RD for recursive queries
794 _lib.ARES_OPCODE_QUERY,
795 _lib.ARES_RCODE_NOERROR
796 )
797 if status != _lib.ARES_SUCCESS:
798 raise AresError(status, errno.strerror(status))
800 dnsrec = dnsrec_p[0]
802 # Add the query to the DNS record
803 status = _lib.ares_dns_record_query_add(
804 dnsrec,
805 parse_name(name),
806 query_type,
807 query_class
808 )
809 if status != _lib.ARES_SUCCESS:
810 _lib.ares_dns_record_destroy(dnsrec)
811 raise AresError(status, errno.strerror(status))
813 # Wrap callback to destroy DNS record after it's called
814 original_callback = callback
815 def cleanup_callback(result, error):
816 try:
817 original_callback(result, error)
818 finally:
819 # Clean up the DNS record after the callback completes
820 _lib.ares_dns_record_destroy(dnsrec)
822 # Perform the search with the created DNS record
823 userdata = self._create_callback_handle(cleanup_callback)
824 status = _lib.ares_search_dnsrec(
825 self._channel[0],
826 dnsrec,
827 _lib._query_dnsrec_cb,
828 userdata
829 )
830 if status != _lib.ARES_SUCCESS:
831 _handle_to_channel.pop(userdata, None)
832 _lib.ares_dns_record_destroy(dnsrec)
833 raise AresError(status, errno.strerror(status))
835 def set_local_ip(self, ip):
836 addr4 = _ffi.new("struct in_addr*")
837 addr6 = _ffi.new("struct ares_in6_addr*")
838 if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), addr4) == 1:
839 _lib.ares_set_local_ip4(self._channel[0], socket.ntohl(addr4.s_addr))
840 elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), addr6) == 1:
841 _lib.ares_set_local_ip6(self._channel[0], addr6)
842 else:
843 raise ValueError("invalid IP address")
845 def getnameinfo(self, address: Union[IP4, IP6], flags: int, *, callback: Callable[[Any, int], None]) -> None:
846 if not callable(callback):
847 raise TypeError("a callable is required")
849 if len(address) == 2:
850 (ip, port) = address
851 sa4 = _ffi.new("struct sockaddr_in*")
852 if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), _ffi.addressof(sa4.sin_addr)) != 1:
853 raise ValueError("Invalid IPv4 address %r" % ip)
854 sa4.sin_family = socket.AF_INET
855 sa4.sin_port = socket.htons(port)
856 sa = sa4
857 elif len(address) == 4:
858 (ip, port, flowinfo, scope_id) = address
859 sa6 = _ffi.new("struct sockaddr_in6*")
860 if _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), _ffi.addressof(sa6.sin6_addr)) != 1:
861 raise ValueError("Invalid IPv6 address %r" % ip)
862 sa6.sin6_family = socket.AF_INET6
863 sa6.sin6_port = socket.htons(port)
864 sa6.sin6_flowinfo = socket.htonl(flowinfo) # I'm unsure about byteorder here.
865 sa6.sin6_scope_id = scope_id # Yes, without htonl.
866 sa = sa6
867 else:
868 raise ValueError("Invalid address argument")
870 userdata = self._create_callback_handle(callback)
871 _lib.ares_getnameinfo(self._channel[0], _ffi.cast("struct sockaddr*", sa), _ffi.sizeof(sa[0]), flags, _lib._nameinfo_cb, userdata)
873 def set_local_dev(self, dev):
874 _lib.ares_set_local_dev(self._channel[0], dev)
876 def close(self) -> None:
877 """
878 Close the channel as soon as it's safe to do so.
880 This method can be called from any thread. The channel will be destroyed
881 safely using a background thread with a 1-second delay to ensure c-ares
882 has completed its cleanup.
884 Note: Once close() is called, no new queries can be started. Any pending
885 queries will be cancelled and their callbacks will receive ARES_ECANCELLED.
887 """
888 if self._channel is None:
889 # Already destroyed
890 return
892 # NB: don't cancel queries here, it may lead to problem if done from a
893 # query callback.
895 # Schedule channel destruction
896 channel, self._channel = self._channel, None
897 _shutdown_manager.destroy_channel(channel, self._sock_state_cb_handle)
899 def wait(self, timeout: float=None) -> bool:
900 """
901 Wait until all pending queries are complete or timeout occurs.
903 Args:
904 timeout: Maximum time to wait in seconds. Use -1 for infinite wait.
905 """
906 r = _lib.ares_queue_wait_empty(self._channel[0], int(timeout * 1000) if timeout is not None and timeout >= 0 else -1)
907 if r == _lib.ARES_SUCCESS:
908 return True
909 elif r == _lib.ARES_ETIMEOUT:
910 return False
911 else:
912 raise AresError(r, errno.strerror(r))
915# DNS query result types - New dataclass-based API
916#
918@dataclass
919class ARecordData:
920 """Data for A (IPv4 address) record"""
921 addr: str
923@dataclass
924class AAAARecordData:
925 """Data for AAAA (IPv6 address) record"""
926 addr: str
928@dataclass
929class MXRecordData:
930 """Data for MX (mail exchange) record"""
931 priority: int
932 exchange: str
934@dataclass
935class TXTRecordData:
936 """Data for TXT (text) record"""
937 data: bytes
939@dataclass
940class CAARecordData:
941 """Data for CAA (certification authority authorization) record"""
942 critical: int
943 tag: str
944 value: str
946@dataclass
947class CNAMERecordData:
948 """Data for CNAME (canonical name) record"""
949 cname: str
951@dataclass
952class NAPTRRecordData:
953 """Data for NAPTR (naming authority pointer) record"""
954 order: int
955 preference: int
956 flags: str
957 service: str
958 regexp: str
959 replacement: str
961@dataclass
962class NSRecordData:
963 """Data for NS (name server) record"""
964 nsdname: str
966@dataclass
967class PTRRecordData:
968 """Data for PTR (pointer) record"""
969 dname: str
971@dataclass
972class SOARecordData:
973 """Data for SOA (start of authority) record"""
974 mname: str
975 rname: str
976 serial: int
977 refresh: int
978 retry: int
979 expire: int
980 minimum: int
982@dataclass
983class SRVRecordData:
984 """Data for SRV (service) record"""
985 priority: int
986 weight: int
987 port: int
988 target: str
990@dataclass
991class TLSARecordData:
992 """Data for TLSA (DANE TLS authentication) record - RFC 6698"""
993 cert_usage: int
994 selector: int
995 matching_type: int
996 cert_association_data: bytes
998@dataclass
999class HTTPSRecordData:
1000 """Data for HTTPS (service binding) record - RFC 9460"""
1001 priority: int
1002 target: str
1003 params: list # List of (key: int, value: bytes) tuples
1005@dataclass
1006class URIRecordData:
1007 """Data for URI (Uniform Resource Identifier) record - RFC 7553"""
1008 priority: int
1009 weight: int
1010 target: str
1012@dataclass
1013class DNSRecord:
1014 """Represents a single DNS resource record"""
1015 name: str
1016 type: int
1017 record_class: int
1018 ttl: int
1019 data: Union[ARecordData, AAAARecordData, MXRecordData, TXTRecordData,
1020 CAARecordData, CNAMERecordData, HTTPSRecordData, NAPTRRecordData,
1021 NSRecordData, PTRRecordData, SOARecordData, SRVRecordData,
1022 TLSARecordData, URIRecordData]
1024@dataclass
1025class DNSResult:
1026 """Represents a complete DNS query result with all sections"""
1027 answer: list[DNSRecord]
1028 authority: list[DNSRecord]
1029 additional: list[DNSRecord]
1032# Host/AddrInfo result types
1034@dataclass
1035class HostResult:
1036 """Result from gethostbyaddr() operation"""
1037 name: str
1038 aliases: list[str]
1039 addresses: list[str]
1041@dataclass
1042class NameInfoResult:
1043 """Result from getnameinfo() operation"""
1044 node: str
1045 service: Optional[str]
1047@dataclass
1048class AddrInfoNode:
1049 """Single address node from getaddrinfo() result"""
1050 ttl: int
1051 flags: int
1052 family: int
1053 socktype: int
1054 protocol: int
1055 addr: tuple # (ip, port) or (ip, port, flowinfo, scope_id)
1057@dataclass
1058class AddrInfoCname:
1059 """CNAME information from getaddrinfo() result"""
1060 ttl: int
1061 alias: str
1062 name: str
1064@dataclass
1065class AddrInfoResult:
1066 """Complete result from getaddrinfo() operation"""
1067 cnames: list[AddrInfoCname]
1068 nodes: list[AddrInfoNode]
1071# Parser functions for Host/AddrInfo results
1073def parse_hostent(hostent) -> HostResult:
1074 """Parse c-ares hostent structure into HostResult"""
1075 name = maybe_str(_ffi.string(hostent.h_name))
1076 aliases = []
1077 addresses = []
1079 i = 0
1080 while hostent.h_aliases[i] != _ffi.NULL:
1081 aliases.append(maybe_str(_ffi.string(hostent.h_aliases[i])))
1082 i += 1
1084 i = 0
1085 while hostent.h_addr_list[i] != _ffi.NULL:
1086 buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
1087 if _ffi.NULL != _lib.ares_inet_ntop(hostent.h_addrtype, hostent.h_addr_list[i], buf, _lib.INET6_ADDRSTRLEN):
1088 addresses.append(maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN)))
1089 i += 1
1091 return HostResult(name=name, aliases=aliases, addresses=addresses)
1094def parse_nameinfo(node, service) -> NameInfoResult:
1095 """Parse c-ares nameinfo into NameInfoResult"""
1096 node_str = maybe_str(_ffi.string(node))
1097 service_str = maybe_str(_ffi.string(service)) if service != _ffi.NULL else None
1098 return NameInfoResult(node=node_str, service=service_str)
1101def parse_addrinfo_node(ares_node) -> AddrInfoNode:
1102 """Parse a single c-ares addrinfo node into AddrInfoNode"""
1103 ttl = ares_node.ai_ttl
1104 flags = ares_node.ai_flags
1105 socktype = ares_node.ai_socktype
1106 protocol = ares_node.ai_protocol
1108 addr_struct = ares_node.ai_addr
1109 assert addr_struct.sa_family == ares_node.ai_family
1110 ip = _ffi.new("char []", _lib.INET6_ADDRSTRLEN)
1112 if addr_struct.sa_family == socket.AF_INET:
1113 family = socket.AF_INET
1114 s = _ffi.cast("struct sockaddr_in*", addr_struct)
1115 if _ffi.NULL != _lib.ares_inet_ntop(s.sin_family, _ffi.addressof(s.sin_addr), ip, _lib.INET6_ADDRSTRLEN):
1116 # (address, port) 2-tuple for AF_INET
1117 addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin_port))
1118 else:
1119 raise ValueError("failed to convert IPv4 address")
1120 elif addr_struct.sa_family == socket.AF_INET6:
1121 family = socket.AF_INET6
1122 s = _ffi.cast("struct sockaddr_in6*", addr_struct)
1123 if _ffi.NULL != _lib.ares_inet_ntop(s.sin6_family, _ffi.addressof(s.sin6_addr), ip, _lib.INET6_ADDRSTRLEN):
1124 # (address, port, flow info, scope id) 4-tuple for AF_INET6
1125 addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin6_port), s.sin6_flowinfo, s.sin6_scope_id)
1126 else:
1127 raise ValueError("failed to convert IPv6 address")
1128 else:
1129 raise ValueError("invalid sockaddr family")
1131 return AddrInfoNode(ttl=ttl, flags=flags, family=family, socktype=socktype, protocol=protocol, addr=addr)
1134def parse_addrinfo_cname(ares_cname) -> AddrInfoCname:
1135 """Parse a single c-ares addrinfo cname into AddrInfoCname"""
1136 return AddrInfoCname(
1137 ttl=ares_cname.ttl,
1138 alias=maybe_str(_ffi.string(ares_cname.alias)),
1139 name=maybe_str(_ffi.string(ares_cname.name))
1140 )
1143def parse_addrinfo(ares_addrinfo) -> AddrInfoResult:
1144 """Parse c-ares addrinfo structure into AddrInfoResult"""
1145 cnames = []
1146 nodes = []
1148 cname_ptr = ares_addrinfo.cnames
1149 while cname_ptr != _ffi.NULL:
1150 cnames.append(parse_addrinfo_cname(cname_ptr))
1151 cname_ptr = cname_ptr.next
1153 node_ptr = ares_addrinfo.nodes
1154 while node_ptr != _ffi.NULL:
1155 nodes.append(parse_addrinfo_node(node_ptr))
1156 node_ptr = node_ptr.ai_next
1158 _lib.ares_freeaddrinfo(ares_addrinfo)
1160 return AddrInfoResult(cnames=cnames, nodes=nodes)
1163__all__ = (
1164 # Channel flags
1165 "ARES_FLAG_USEVC",
1166 "ARES_FLAG_PRIMARY",
1167 "ARES_FLAG_IGNTC",
1168 "ARES_FLAG_NORECURSE",
1169 "ARES_FLAG_STAYOPEN",
1170 "ARES_FLAG_NOSEARCH",
1171 "ARES_FLAG_NOALIASES",
1172 "ARES_FLAG_NOCHECKRESP",
1173 "ARES_FLAG_EDNS",
1174 "ARES_FLAG_NO_DFLT_SVR",
1176 # Nameinfo flag values
1177 "ARES_NI_NOFQDN",
1178 "ARES_NI_NUMERICHOST",
1179 "ARES_NI_NAMEREQD",
1180 "ARES_NI_NUMERICSERV",
1181 "ARES_NI_DGRAM",
1182 "ARES_NI_TCP",
1183 "ARES_NI_UDP",
1184 "ARES_NI_SCTP",
1185 "ARES_NI_DCCP",
1186 "ARES_NI_NUMERICSCOPE",
1187 "ARES_NI_LOOKUPHOST",
1188 "ARES_NI_LOOKUPSERVICE",
1189 "ARES_NI_IDN",
1190 "ARES_NI_IDN_ALLOW_UNASSIGNED",
1191 "ARES_NI_IDN_USE_STD3_ASCII_RULES",
1193 # Bad socket
1194 "ARES_SOCKET_BAD",
1196 # Query types
1197 "QUERY_TYPE_A",
1198 "QUERY_TYPE_AAAA",
1199 "QUERY_TYPE_ANY",
1200 "QUERY_TYPE_CAA",
1201 "QUERY_TYPE_CNAME",
1202 "QUERY_TYPE_HTTPS",
1203 "QUERY_TYPE_MX",
1204 "QUERY_TYPE_NAPTR",
1205 "QUERY_TYPE_NS",
1206 "QUERY_TYPE_PTR",
1207 "QUERY_TYPE_SOA",
1208 "QUERY_TYPE_SRV",
1209 "QUERY_TYPE_TLSA",
1210 "QUERY_TYPE_TXT",
1211 "QUERY_TYPE_URI",
1213 # Query classes
1214 "QUERY_CLASS_IN",
1215 "QUERY_CLASS_CHAOS",
1216 "QUERY_CLASS_HS",
1217 "QUERY_CLASS_NONE",
1218 "QUERY_CLASS_ANY",
1220 # Core stuff
1221 "ARES_VERSION",
1222 "AresError",
1223 "Channel",
1224 "errno",
1225 "__version__",
1227 # DNS record result types
1228 "DNSResult",
1229 "DNSRecord",
1230 "ARecordData",
1231 "AAAARecordData",
1232 "MXRecordData",
1233 "TXTRecordData",
1234 "CAARecordData",
1235 "CNAMERecordData",
1236 "HTTPSRecordData",
1237 "NAPTRRecordData",
1238 "NSRecordData",
1239 "PTRRecordData",
1240 "SOARecordData",
1241 "SRVRecordData",
1242 "TLSARecordData",
1243 "URIRecordData",
1245 # Host/AddrInfo result types
1246 "HostResult",
1247 "NameInfoResult",
1248 "AddrInfoResult",
1249 "AddrInfoNode",
1250 "AddrInfoCname",
1251)