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

684 statements  

1 

2from ._cares import ffi as _ffi, lib as _lib 

3import _cffi_backend # hint for bundler tools 

4 

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') 

7 

8if not _lib.ares_threadsafety(): 

9 raise RuntimeError("c-ares is not built with thread safety") 

10 

11from . import errno 

12from .utils import ascii_bytes, maybe_str, parse_name 

13from ._version import __version__ 

14 

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 

22 

23IP4 = tuple[str, int] 

24IP6 = tuple[str, int, int, int] 

25 

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 

37 

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 

54 

55# Bad socket 

56ARES_SOCKET_BAD = _lib.ARES_SOCKET_BAD 

57 

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 

74 

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 

81 

82ARES_VERSION = maybe_str(_ffi.string(_lib.ares_version(_ffi.NULL))) 

83PYCARES_ADDRTTL_SIZE = 256 

84 

85 

86class AresError(Exception): 

87 pass 

88 

89 

90# callback helpers 

91 

92_handle_to_channel: Dict[Any, "Channel"] = {} # Maps handle to channel to prevent use-after-free 

93 

94 

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) 

101 

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 

107 

108 callback = _ffi.from_handle(arg) 

109 

110 if status != _lib.ARES_SUCCESS: 

111 result = None 

112 else: 

113 result = parse_hostent(hostent) 

114 status = None 

115 

116 callback(result, status) 

117 _handle_to_channel.pop(arg, None) 

118 

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 

124 

125 callback = _ffi.from_handle(arg) 

126 

127 if status != _lib.ARES_SUCCESS: 

128 result = None 

129 else: 

130 result = parse_nameinfo(node, service) 

131 status = None 

132 

133 callback(result, status) 

134 _handle_to_channel.pop(arg, None) 

135 

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 

142 

143 callback = _ffi.from_handle(arg) 

144 

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 

154 

155 callback(result, status) 

156 _handle_to_channel.pop(arg, None) 

157 

158 

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 

164 

165 callback = _ffi.from_handle(arg) 

166 

167 if status != _lib.ARES_SUCCESS: 

168 result = None 

169 else: 

170 result = parse_addrinfo(res) 

171 status = None 

172 

173 callback(result, status) 

174 _handle_to_channel.pop(arg, None) 

175 

176 

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 

194 

195 

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))) 

203 

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))) 

209 

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))) 

214 

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)) 

225 

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) 

233 

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))) 

237 

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 ) 

253 

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))) 

257 

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))) 

261 

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 ) 

279 

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 ) 

291 

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 ) 

305 

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 ) 

315 

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 ) 

325 

326 else: 

327 # Unknown record type - return None or raise error 

328 raise ValueError(f"Unsupported DNS record type: {record_type}") 

329 

330 

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 

335 

336 answer_records = [] 

337 authority_records = [] 

338 additional_records = [] 

339 

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) 

349 

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 

362 

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) 

372 

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 

385 

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) 

395 

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 

408 

409 result = DNSResult( 

410 answer=answer_records, 

411 authority=authority_records, 

412 additional=additional_records 

413 ) 

414 

415 return result, None 

416 

417 

418class _ChannelShutdownManager: 

419 """Manages channel destruction in a single background thread using SimpleQueue.""" 

420 

421 def __init__(self) -> None: 

422 self._queue: SimpleQueue = SimpleQueue() 

423 self._thread: Optional[threading.Thread] = None 

424 self._start_lock = threading.Lock() 

425 

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() 

431 

432 # Cancel all pending queries - this will trigger callbacks with ARES_ECANCELLED 

433 _lib.ares_cancel(channel[0]) 

434 

435 # Wait for all queries to finish 

436 _lib.ares_queue_wait_empty(channel[0], -1) 

437 

438 # Destroy the channel 

439 if channel is not None: 

440 _lib.ares_destroy(channel[0]) 

441 

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() 

452 

453 def destroy_channel(self, channel, sock_state_cb_handle) -> None: 

454 """ 

455 Schedule channel destruction on the background thread. 

456 

457 The socket state callback handle is passed along to ensure it remains 

458 alive until the channel is destroyed. 

459 

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)) 

466 

467 

468# Global shutdown manager instance 

469_shutdown_manager = _ChannelShutdownManager() 

470 

471 

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) 

475 

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: 

494 

495 # Initialize _channel to None first to ensure __del__ doesn't fail 

496 self._channel = None 

497 

498 # Store flags for later use (default is 0 if not specified) 

499 self._flags = flags if flags is not None else 0 

500 

501 channel = _ffi.new("ares_channel *") 

502 options = _ffi.new("struct ares_options *") 

503 optmask = 0 

504 

505 if flags is not None: 

506 options.flags = flags 

507 optmask = optmask | _lib.ARES_OPT_FLAGS 

508 

509 if timeout is not None: 

510 options.timeout = int(timeout * 1000) 

511 optmask = optmask | _lib.ARES_OPT_TIMEOUTMS 

512 

513 if tries is not None: 

514 options.tries = tries 

515 optmask = optmask | _lib.ARES_OPT_TRIES 

516 

517 if ndots is not None: 

518 options.ndots = ndots 

519 optmask = optmask | _lib.ARES_OPT_NDOTS 

520 

521 if tcp_port is not None: 

522 options.tcp_port = tcp_port 

523 optmask = optmask | _lib.ARES_OPT_TCP_PORT 

524 

525 if udp_port is not None: 

526 options.udp_port = udp_port 

527 optmask = optmask | _lib.ARES_OPT_UDP_PORT 

528 

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 

532 

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 

536 

537 if sock_state_cb: 

538 if not callable(sock_state_cb): 

539 raise TypeError("sock_state_cb is not callable") 

540 

541 userdata = _ffi.new_handle(sock_state_cb) 

542 

543 # This must be kept alive while the channel is alive. 

544 self._sock_state_cb_handle = userdata 

545 

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 

553 

554 if lookups: 

555 options.lookups = _ffi.new('char[]', ascii_bytes(lookups)) 

556 optmask = optmask | _lib.ARES_OPT_LOOKUPS 

557 

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] 

563 

564 options.domains = c 

565 options.ndomains = len(domains) 

566 optmask = optmask | _lib.ARES_OPT_DOMAINS 

567 

568 if rotate: 

569 optmask = optmask | _lib.ARES_OPT_ROTATE 

570 

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)) 

574 

575 r = _lib.ares_init_options(channel, options, optmask) 

576 if r != _lib.ARES_SUCCESS: 

577 raise AresError('Failed to initialize c-ares channel') 

578 

579 self._channel = channel 

580 if servers: 

581 self.servers = servers 

582 

583 if local_ip: 

584 self.set_local_ip(local_ip) 

585 

586 if local_dev: 

587 self.set_local_dev(local_dev) 

588 

589 # Ensure the shutdown thread is started 

590 _shutdown_manager.start() 

591 

592 def __del__(self) -> None: 

593 """Ensure the channel is destroyed when the object is deleted.""" 

594 self.close() 

595 

596 def _create_callback_handle(self, callback_data): 

597 """ 

598 Create a callback handle and register it for tracking. 

599 

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 

603 

604 Args: 

605 callback_data: The data to pass to the callback (usually a callable or tuple) 

606 

607 Returns: 

608 The CFFI handle that can be passed to C functions 

609 

610 Raises: 

611 RuntimeError: If the channel is destroyed 

612 

613 """ 

614 if self._channel is None: 

615 raise RuntimeError("Channel is destroyed, no new queries allowed") 

616 

617 userdata = _ffi.new_handle(callback_data) 

618 _handle_to_channel[userdata] = self 

619 return userdata 

620 

621 def cancel(self) -> None: 

622 _lib.ares_cancel(self._channel[0]) 

623 

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)) 

628 

629 @property 

630 def servers(self) -> list[str]: 

631 csv_str = _lib.ares_get_servers_csv(self._channel[0]) 

632 

633 if csv_str == _ffi.NULL: 

634 raise AresError(_lib.ARES_ENOMEM, errno.strerror(_lib.ARES_ENOMEM)) 

635 

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(',')] 

640 

641 return server_list 

642 

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) 

647 

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)) 

651 

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)) 

654 

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)) 

657 

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)) 

660 

661 def timeout(self, t = None): 

662 maxtv = _ffi.NULL 

663 tv = _ffi.new("struct timeval*") 

664 

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") 

672 

673 _lib.ares_timeout(self._channel[0], maxtv, tv) 

674 

675 if tv == _ffi.NULL: 

676 return 0.0 

677 

678 return (tv.tv_sec + tv.tv_usec / 1000000.0) 

679 

680 def gethostbyaddr(self, addr: str, *, callback: Callable[[Any, int], None]) -> None: 

681 if not callable(callback): 

682 raise TypeError("a callable is required") 

683 

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") 

694 

695 userdata = self._create_callback_handle(callback) 

696 _lib.ares_gethostbyaddr(self._channel[0], address, _ffi.sizeof(address[0]), family, _lib._host_cb, userdata) 

697 

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") 

711 

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) 

718 

719 userdata = self._create_callback_handle(callback) 

720 

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) 

727 

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. 

731 

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) 

737 

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') 

742 

743 if query_type not in self.__qtypes__: 

744 raise ValueError('invalid query type specified') 

745 

746 if query_class not in self.__qclasses__: 

747 raise ValueError('invalid query class specified') 

748 

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)) 

763 

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). 

767 

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) 

773 

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') 

778 

779 if query_type not in self.__qtypes__: 

780 raise ValueError('invalid query type specified') 

781 

782 if query_class not in self.__qclasses__: 

783 raise ValueError('invalid query class specified') 

784 

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 

788 

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)) 

799 

800 dnsrec = dnsrec_p[0] 

801 

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)) 

812 

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) 

821 

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)) 

834 

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") 

844 

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") 

848 

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") 

869 

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) 

872 

873 def set_local_dev(self, dev): 

874 _lib.ares_set_local_dev(self._channel[0], dev) 

875 

876 def close(self) -> None: 

877 """ 

878 Close the channel as soon as it's safe to do so. 

879 

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. 

883 

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. 

886 

887 """ 

888 if self._channel is None: 

889 # Already destroyed 

890 return 

891 

892 # NB: don't cancel queries here, it may lead to problem if done from a 

893 # query callback. 

894 

895 # Schedule channel destruction 

896 channel, self._channel = self._channel, None 

897 _shutdown_manager.destroy_channel(channel, self._sock_state_cb_handle) 

898 

899 def wait(self, timeout: float=None) -> bool: 

900 """ 

901 Wait until all pending queries are complete or timeout occurs. 

902 

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)) 

913 

914 

915# DNS query result types - New dataclass-based API 

916# 

917 

918@dataclass 

919class ARecordData: 

920 """Data for A (IPv4 address) record""" 

921 addr: str 

922 

923@dataclass 

924class AAAARecordData: 

925 """Data for AAAA (IPv6 address) record""" 

926 addr: str 

927 

928@dataclass 

929class MXRecordData: 

930 """Data for MX (mail exchange) record""" 

931 priority: int 

932 exchange: str 

933 

934@dataclass 

935class TXTRecordData: 

936 """Data for TXT (text) record""" 

937 data: bytes 

938 

939@dataclass 

940class CAARecordData: 

941 """Data for CAA (certification authority authorization) record""" 

942 critical: int 

943 tag: str 

944 value: str 

945 

946@dataclass 

947class CNAMERecordData: 

948 """Data for CNAME (canonical name) record""" 

949 cname: str 

950 

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 

960 

961@dataclass 

962class NSRecordData: 

963 """Data for NS (name server) record""" 

964 nsdname: str 

965 

966@dataclass 

967class PTRRecordData: 

968 """Data for PTR (pointer) record""" 

969 dname: str 

970 

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 

981 

982@dataclass 

983class SRVRecordData: 

984 """Data for SRV (service) record""" 

985 priority: int 

986 weight: int 

987 port: int 

988 target: str 

989 

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 

997 

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 

1004 

1005@dataclass 

1006class URIRecordData: 

1007 """Data for URI (Uniform Resource Identifier) record - RFC 7553""" 

1008 priority: int 

1009 weight: int 

1010 target: str 

1011 

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] 

1023 

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] 

1030 

1031 

1032# Host/AddrInfo result types 

1033 

1034@dataclass 

1035class HostResult: 

1036 """Result from gethostbyaddr() operation""" 

1037 name: str 

1038 aliases: list[str] 

1039 addresses: list[str] 

1040 

1041@dataclass 

1042class NameInfoResult: 

1043 """Result from getnameinfo() operation""" 

1044 node: str 

1045 service: Optional[str] 

1046 

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) 

1056 

1057@dataclass 

1058class AddrInfoCname: 

1059 """CNAME information from getaddrinfo() result""" 

1060 ttl: int 

1061 alias: str 

1062 name: str 

1063 

1064@dataclass 

1065class AddrInfoResult: 

1066 """Complete result from getaddrinfo() operation""" 

1067 cnames: list[AddrInfoCname] 

1068 nodes: list[AddrInfoNode] 

1069 

1070 

1071# Parser functions for Host/AddrInfo results 

1072 

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 = [] 

1078 

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 

1083 

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 

1090 

1091 return HostResult(name=name, aliases=aliases, addresses=addresses) 

1092 

1093 

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) 

1099 

1100 

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 

1107 

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) 

1111 

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") 

1130 

1131 return AddrInfoNode(ttl=ttl, flags=flags, family=family, socktype=socktype, protocol=protocol, addr=addr) 

1132 

1133 

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 ) 

1141 

1142 

1143def parse_addrinfo(ares_addrinfo) -> AddrInfoResult: 

1144 """Parse c-ares addrinfo structure into AddrInfoResult""" 

1145 cnames = [] 

1146 nodes = [] 

1147 

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 

1152 

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 

1157 

1158 _lib.ares_freeaddrinfo(ares_addrinfo) 

1159 

1160 return AddrInfoResult(cnames=cnames, nodes=nodes) 

1161 

1162 

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", 

1175 

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", 

1192 

1193 # Bad socket 

1194 "ARES_SOCKET_BAD", 

1195 

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", 

1212 

1213 # Query classes 

1214 "QUERY_CLASS_IN", 

1215 "QUERY_CLASS_CHAOS", 

1216 "QUERY_CLASS_HS", 

1217 "QUERY_CLASS_NONE", 

1218 "QUERY_CLASS_ANY", 

1219 

1220 # Core stuff 

1221 "ARES_VERSION", 

1222 "AresError", 

1223 "Channel", 

1224 "errno", 

1225 "__version__", 

1226 

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", 

1244 

1245 # Host/AddrInfo result types 

1246 "HostResult", 

1247 "NameInfoResult", 

1248 "AddrInfoResult", 

1249 "AddrInfoNode", 

1250 "AddrInfoCname", 

1251)