Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pycares/__init__.py: 31%

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

736 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 contextlib 

16import math 

17import socket 

18import threading 

19from collections.abc import Callable, Iterable 

20from dataclasses import dataclass 

21from typing import Any, Callable, Literal, Optional, Dict, Union 

22from queue import SimpleQueue 

23 

24IP4 = tuple[str, int] 

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

26 

27# Flag values 

28ARES_FLAG_USEVC = _lib.ARES_FLAG_USEVC 

29ARES_FLAG_PRIMARY = _lib.ARES_FLAG_PRIMARY 

30ARES_FLAG_IGNTC = _lib.ARES_FLAG_IGNTC 

31ARES_FLAG_NORECURSE = _lib.ARES_FLAG_NORECURSE 

32ARES_FLAG_STAYOPEN = _lib.ARES_FLAG_STAYOPEN 

33ARES_FLAG_NOSEARCH = _lib.ARES_FLAG_NOSEARCH 

34ARES_FLAG_NOALIASES = _lib.ARES_FLAG_NOALIASES 

35ARES_FLAG_NOCHECKRESP = _lib.ARES_FLAG_NOCHECKRESP 

36ARES_FLAG_EDNS = _lib.ARES_FLAG_EDNS 

37ARES_FLAG_NO_DFLT_SVR = _lib.ARES_FLAG_NO_DFLT_SVR 

38 

39# Nameinfo flag values 

40ARES_NI_NOFQDN = _lib.ARES_NI_NOFQDN 

41ARES_NI_NUMERICHOST = _lib.ARES_NI_NUMERICHOST 

42ARES_NI_NAMEREQD = _lib.ARES_NI_NAMEREQD 

43ARES_NI_NUMERICSERV = _lib.ARES_NI_NUMERICSERV 

44ARES_NI_DGRAM = _lib.ARES_NI_DGRAM 

45ARES_NI_TCP = _lib.ARES_NI_TCP 

46ARES_NI_UDP = _lib.ARES_NI_UDP 

47ARES_NI_SCTP = _lib.ARES_NI_SCTP 

48ARES_NI_DCCP = _lib.ARES_NI_DCCP 

49ARES_NI_NUMERICSCOPE = _lib.ARES_NI_NUMERICSCOPE 

50ARES_NI_LOOKUPHOST = _lib.ARES_NI_LOOKUPHOST 

51ARES_NI_LOOKUPSERVICE = _lib.ARES_NI_LOOKUPSERVICE 

52ARES_NI_IDN = _lib.ARES_NI_IDN 

53ARES_NI_IDN_ALLOW_UNASSIGNED = _lib.ARES_NI_IDN_ALLOW_UNASSIGNED 

54ARES_NI_IDN_USE_STD3_ASCII_RULES = _lib.ARES_NI_IDN_USE_STD3_ASCII_RULES 

55 

56# Bad socket 

57ARES_SOCKET_BAD = _lib.ARES_SOCKET_BAD 

58 

59# Query types 

60QUERY_TYPE_A = _lib.ARES_REC_TYPE_A 

61QUERY_TYPE_AAAA = _lib.ARES_REC_TYPE_AAAA 

62QUERY_TYPE_ANY = _lib.ARES_REC_TYPE_ANY 

63QUERY_TYPE_CAA = _lib.ARES_REC_TYPE_CAA 

64QUERY_TYPE_CNAME = _lib.ARES_REC_TYPE_CNAME 

65QUERY_TYPE_MX = _lib.ARES_REC_TYPE_MX 

66QUERY_TYPE_NAPTR = _lib.ARES_REC_TYPE_NAPTR 

67QUERY_TYPE_NS = _lib.ARES_REC_TYPE_NS 

68QUERY_TYPE_PTR = _lib.ARES_REC_TYPE_PTR 

69QUERY_TYPE_SOA = _lib.ARES_REC_TYPE_SOA 

70QUERY_TYPE_SRV = _lib.ARES_REC_TYPE_SRV 

71QUERY_TYPE_TXT = _lib.ARES_REC_TYPE_TXT 

72QUERY_TYPE_TLSA = _lib.ARES_REC_TYPE_TLSA 

73QUERY_TYPE_HTTPS = _lib.ARES_REC_TYPE_HTTPS 

74QUERY_TYPE_URI = _lib.ARES_REC_TYPE_URI 

75 

76# Query classes 

77QUERY_CLASS_IN = _lib.ARES_CLASS_IN 

78QUERY_CLASS_CHAOS = _lib.ARES_CLASS_CHAOS 

79QUERY_CLASS_HS = _lib.ARES_CLASS_HESOID 

80QUERY_CLASS_NONE = _lib.ARES_CLASS_NONE 

81QUERY_CLASS_ANY = _lib.ARES_CLASS_ANY 

82 

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

84PYCARES_ADDRTTL_SIZE = 256 

85 

86 

87class AresError(Exception): 

88 pass 

89 

90 

91# callback helpers 

92 

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

94 

95 

96@_ffi.def_extern() 

97def _sock_state_cb(data, socket_fd, readable, writable): 

98 # Note: sock_state_cb handle is not tracked in _handle_to_channel 

99 # because it has a different lifecycle (tied to the channel, not individual queries) 

100 sock_state_cb = _ffi.from_handle(data) 

101 sock_state_cb(socket_fd, readable, writable) 

102 

103@_ffi.def_extern() 

104def _host_cb(arg, status, timeouts, hostent): 

105 if arg not in _handle_to_channel: 

106 return 

107 

108 callback = _ffi.from_handle(arg) 

109 

110 try: 

111 if status != _lib.ARES_SUCCESS: 

112 result = None 

113 else: 

114 result = parse_hostent(hostent) 

115 status = None 

116 callback(result, status) 

117 finally: 

118 _handle_to_channel.pop(arg, None) 

119 

120@_ffi.def_extern() 

121def _nameinfo_cb(arg, status, timeouts, node, service): 

122 if arg not in _handle_to_channel: 

123 return 

124 

125 callback = _ffi.from_handle(arg) 

126 

127 try: 

128 if status != _lib.ARES_SUCCESS: 

129 result = None 

130 else: 

131 result = parse_nameinfo(node, service) 

132 status = None 

133 callback(result, status) 

134 finally: 

135 _handle_to_channel.pop(arg, None) 

136 

137@_ffi.def_extern() 

138def _query_dnsrec_cb(arg, status, timeouts, dnsrec): 

139 """Callback for new DNS record API queries""" 

140 

141 if arg not in _handle_to_channel: 

142 return 

143 

144 callback = _ffi.from_handle(arg) 

145 

146 try: 

147 if status != _lib.ARES_SUCCESS: 

148 result = None 

149 else: 

150 result, parse_status = parse_dnsrec(dnsrec) 

151 if parse_status is not None: 

152 status = parse_status 

153 else: 

154 # Success - set status to None 

155 status = None 

156 callback(result, status) 

157 finally: 

158 _handle_to_channel.pop(arg, None) 

159 

160 

161@_ffi.def_extern() 

162def _addrinfo_cb(arg, status, timeouts, res): 

163 if arg not in _handle_to_channel: 

164 return 

165 

166 callback = _ffi.from_handle(arg) 

167 

168 try: 

169 if status != _lib.ARES_SUCCESS: 

170 result = None 

171 else: 

172 result = parse_addrinfo(res) 

173 status = None 

174 callback(result, status) 

175 finally: 

176 _handle_to_channel.pop(arg, None) 

177 

178 

179def _extract_opt_params(rr, key): 

180 """Extract OPT params as list of (key, value) tuples for HTTPS/SVCB records.""" 

181 opt_cnt = _lib.ares_dns_rr_get_opt_cnt(rr, key) 

182 if opt_cnt == 0: 

183 return [] 

184 # Collect all options as a list of (key, value) tuples 

185 params = [] 

186 for i in range(opt_cnt): 

187 val_ptr = _ffi.new("unsigned char **") 

188 val_len = _ffi.new("size_t *") 

189 opt_key = _lib.ares_dns_rr_get_opt(rr, key, i, val_ptr, val_len) 

190 if val_ptr[0] != _ffi.NULL: 

191 val = bytes(_ffi.buffer(val_ptr[0], val_len[0])) 

192 else: 

193 val = b'' 

194 params.append((opt_key, val)) 

195 return params 

196 

197def _extract_str_data(rr, key): 

198 """Retrieves a string""" 

199 value = _lib.ares_dns_rr_get_str(rr, key) 

200 return maybe_str(_ffi.string(value)) if value != _ffi.NULL else "" 

201 

202def _extract_bin_data_as_str(rr, key): 

203 """Retrieves binary data as a string""" 

204 length = _ffi.new("size_t *") 

205 value = _lib.ares_dns_rr_get_bin(rr, key, length) 

206 return maybe_str(_ffi.string(value)) if value != _ffi.NULL else "" 

207 

208def _extract_bin_data_as_bytes(rr, key): 

209 """Retrieves binary data as bytes""" 

210 length = _ffi.new("size_t *") 

211 value = _lib.ares_dns_rr_get_bin(rr, key, length) 

212 return bytes(_ffi.buffer(value, length[0])) if value != _ffi.NULL else b'' 

213 

214 

215def extract_record_data(rr, record_type): 

216 """Extract type-specific data from a DNS resource record and return appropriate dataclass""" 

217 if record_type == _lib.ARES_REC_TYPE_A: 

218 addr = _lib.ares_dns_rr_get_addr(rr, _lib.ARES_RR_A_ADDR) 

219 buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN) 

220 _lib.ares_inet_ntop(socket.AF_INET, addr, buf, _lib.INET6_ADDRSTRLEN) 

221 return ARecordData(addr=maybe_str(_ffi.string(buf))) 

222 

223 elif record_type == _lib.ARES_REC_TYPE_AAAA: 

224 addr = _lib.ares_dns_rr_get_addr6(rr, _lib.ARES_RR_AAAA_ADDR) 

225 buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN) 

226 _lib.ares_inet_ntop(socket.AF_INET6, addr, buf, _lib.INET6_ADDRSTRLEN) 

227 return AAAARecordData(addr=maybe_str(_ffi.string(buf))) 

228 

229 elif record_type == _lib.ARES_REC_TYPE_MX: 

230 priority = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_MX_PREFERENCE) 

231 exchange = _extract_str_data(rr, _lib.ARES_RR_MX_EXCHANGE) 

232 return MXRecordData(priority=priority, exchange=exchange) 

233 

234 elif record_type == _lib.ARES_REC_TYPE_TXT: 

235 # TXT records use ABIN (array of binary) for chunks 

236 cnt = _lib.ares_dns_rr_get_abin_cnt(rr, _lib.ARES_RR_TXT_DATA) 

237 chunks = [] 

238 for i in range(cnt): 

239 length = _ffi.new("size_t *") 

240 data = _lib.ares_dns_rr_get_abin(rr, _lib.ARES_RR_TXT_DATA, i, length) 

241 if data != _ffi.NULL: 

242 chunks.append(_ffi.buffer(data, length[0])[:]) 

243 return TXTRecordData(data=b''.join(chunks)) 

244 

245 elif record_type == _lib.ARES_REC_TYPE_CAA: 

246 critical = _lib.ares_dns_rr_get_u8(rr, _lib.ARES_RR_CAA_CRITICAL) 

247 tag = _extract_str_data(rr, _lib.ARES_RR_CAA_TAG) 

248 value_str = _extract_bin_data_as_str(rr, _lib.ARES_RR_CAA_VALUE) 

249 return CAARecordData(critical=critical, tag=tag, value=value_str) 

250 

251 elif record_type == _lib.ARES_REC_TYPE_CNAME: 

252 cname = _extract_str_data(rr, _lib.ARES_RR_CNAME_CNAME) 

253 return CNAMERecordData(cname=cname) 

254 

255 elif record_type == _lib.ARES_REC_TYPE_NAPTR: 

256 order = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_NAPTR_ORDER) 

257 preference = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_NAPTR_PREFERENCE) 

258 flags = _extract_str_data(rr, _lib.ARES_RR_NAPTR_FLAGS) 

259 service = _extract_str_data(rr, _lib.ARES_RR_NAPTR_SERVICES) 

260 regexp = _extract_str_data(rr, _lib.ARES_RR_NAPTR_REGEXP) 

261 replacement = _extract_str_data(rr, _lib.ARES_RR_NAPTR_REPLACEMENT) 

262 return NAPTRRecordData( 

263 order=order, 

264 preference=preference, 

265 flags=flags, 

266 service=service, 

267 regexp=regexp, 

268 replacement=replacement 

269 ) 

270 

271 elif record_type == _lib.ARES_REC_TYPE_NS: 

272 nsdname = _extract_str_data(rr, _lib.ARES_RR_NS_NSDNAME) 

273 return NSRecordData(nsdname=nsdname) 

274 

275 elif record_type == _lib.ARES_REC_TYPE_PTR: 

276 dname = _extract_str_data(rr, _lib.ARES_RR_PTR_DNAME) 

277 return PTRRecordData(dname=dname) 

278 

279 elif record_type == _lib.ARES_REC_TYPE_SOA: 

280 mname = _extract_str_data(rr, _lib.ARES_RR_SOA_MNAME) 

281 rname = _extract_str_data(rr, _lib.ARES_RR_SOA_RNAME) 

282 serial = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_SERIAL) 

283 refresh = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_REFRESH) 

284 retry = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_RETRY) 

285 expire = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_EXPIRE) 

286 minimum = _lib.ares_dns_rr_get_u32(rr, _lib.ARES_RR_SOA_MINIMUM) 

287 return SOARecordData( 

288 mname=mname, 

289 rname=rname, 

290 serial=serial, 

291 refresh=refresh, 

292 retry=retry, 

293 expire=expire, 

294 minimum=minimum 

295 ) 

296 

297 elif record_type == _lib.ARES_REC_TYPE_SRV: 

298 priority = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_SRV_PRIORITY) 

299 weight = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_SRV_WEIGHT) 

300 port = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_SRV_PORT) 

301 target = _extract_str_data(rr, _lib.ARES_RR_SRV_TARGET) 

302 return SRVRecordData( 

303 priority=priority, 

304 weight=weight, 

305 port=port, 

306 target=target 

307 ) 

308 

309 elif record_type == _lib.ARES_REC_TYPE_TLSA: 

310 cert_usage = _lib.ares_dns_rr_get_u8(rr, _lib.ARES_RR_TLSA_CERT_USAGE) 

311 selector = _lib.ares_dns_rr_get_u8(rr, _lib.ARES_RR_TLSA_SELECTOR) 

312 matching_type = _lib.ares_dns_rr_get_u8(rr, _lib.ARES_RR_TLSA_MATCH) 

313 cert_data = _extract_bin_data_as_bytes(rr, _lib.ARES_RR_TLSA_DATA) 

314 return TLSARecordData( 

315 cert_usage=cert_usage, 

316 selector=selector, 

317 matching_type=matching_type, 

318 cert_association_data=cert_data 

319 ) 

320 

321 elif record_type == _lib.ARES_REC_TYPE_HTTPS: 

322 priority = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_HTTPS_PRIORITY) 

323 target = _extract_str_data(rr, _lib.ARES_RR_HTTPS_TARGET) 

324 params = _extract_opt_params(rr, _lib.ARES_RR_HTTPS_PARAMS) 

325 return HTTPSRecordData( 

326 priority=priority, 

327 target=target, 

328 params=params 

329 ) 

330 

331 elif record_type == _lib.ARES_REC_TYPE_URI: 

332 priority = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_URI_PRIORITY) 

333 weight = _lib.ares_dns_rr_get_u16(rr, _lib.ARES_RR_URI_WEIGHT) 

334 target = _extract_str_data(rr, _lib.ARES_RR_URI_TARGET) 

335 return URIRecordData( 

336 priority=priority, 

337 weight=weight, 

338 target=target 

339 ) 

340 

341 else: 

342 # Unknown record type - return None or raise error 

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

344 

345 

346def parse_dnsrec(dnsrec): 

347 """Parse ares_dns_record_t into DNSResult with all sections""" 

348 if dnsrec == _ffi.NULL: 

349 return None, _lib.ARES_EBADRESP 

350 

351 answer_records = [] 

352 authority_records = [] 

353 additional_records = [] 

354 

355 # Parse answer section 

356 answer_count = _lib.ares_dns_record_rr_cnt(dnsrec, _lib.ARES_SECTION_ANSWER) 

357 for i in range(answer_count): 

358 rr = _lib.ares_dns_record_rr_get_const(dnsrec, _lib.ARES_SECTION_ANSWER, i) 

359 if rr != _ffi.NULL: 

360 name = maybe_str(_ffi.string(_lib.ares_dns_rr_get_name(rr))) 

361 rec_type = _lib.ares_dns_rr_get_type(rr) 

362 rec_class = _lib.ares_dns_rr_get_class(rr) 

363 ttl = _lib.ares_dns_rr_get_ttl(rr) 

364 

365 try: 

366 data = extract_record_data(rr, rec_type) 

367 answer_records.append(DNSRecord( 

368 name=name, 

369 type=rec_type, 

370 record_class=rec_class, 

371 ttl=ttl, 

372 data=data 

373 )) 

374 except (ValueError, Exception): 

375 # Skip unsupported record types 

376 pass 

377 

378 # Parse authority section 

379 authority_count = _lib.ares_dns_record_rr_cnt(dnsrec, _lib.ARES_SECTION_AUTHORITY) 

380 for i in range(authority_count): 

381 rr = _lib.ares_dns_record_rr_get_const(dnsrec, _lib.ARES_SECTION_AUTHORITY, i) 

382 if rr != _ffi.NULL: 

383 name = maybe_str(_ffi.string(_lib.ares_dns_rr_get_name(rr))) 

384 rec_type = _lib.ares_dns_rr_get_type(rr) 

385 rec_class = _lib.ares_dns_rr_get_class(rr) 

386 ttl = _lib.ares_dns_rr_get_ttl(rr) 

387 

388 try: 

389 data = extract_record_data(rr, rec_type) 

390 authority_records.append(DNSRecord( 

391 name=name, 

392 type=rec_type, 

393 record_class=rec_class, 

394 ttl=ttl, 

395 data=data 

396 )) 

397 except (ValueError, Exception): 

398 # Skip unsupported record types 

399 pass 

400 

401 # Parse additional section 

402 additional_count = _lib.ares_dns_record_rr_cnt(dnsrec, _lib.ARES_SECTION_ADDITIONAL) 

403 for i in range(additional_count): 

404 rr = _lib.ares_dns_record_rr_get_const(dnsrec, _lib.ARES_SECTION_ADDITIONAL, i) 

405 if rr != _ffi.NULL: 

406 name = maybe_str(_ffi.string(_lib.ares_dns_rr_get_name(rr))) 

407 rec_type = _lib.ares_dns_rr_get_type(rr) 

408 rec_class = _lib.ares_dns_rr_get_class(rr) 

409 ttl = _lib.ares_dns_rr_get_ttl(rr) 

410 

411 try: 

412 data = extract_record_data(rr, rec_type) 

413 additional_records.append(DNSRecord( 

414 name=name, 

415 type=rec_type, 

416 record_class=rec_class, 

417 ttl=ttl, 

418 data=data 

419 )) 

420 except (ValueError, Exception): 

421 # Skip unsupported record types 

422 pass 

423 

424 result = DNSResult( 

425 answer=answer_records, 

426 authority=authority_records, 

427 additional=additional_records 

428 ) 

429 

430 return result, None 

431 

432 

433class _ChannelShutdownManager: 

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

435 

436 def __init__(self) -> None: 

437 self._queue: SimpleQueue = SimpleQueue() 

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

439 self._start_lock = threading.Lock() 

440 

441 def _run_safe_shutdown_loop(self) -> None: 

442 """Process channel destruction requests from the queue.""" 

443 while True: 

444 # Block forever until we get a channel to destroy 

445 ch, _ = self._queue.get() 

446 channel = ch[0] 

447 

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

449 _lib.ares_cancel(channel) 

450 

451 # Wait for all queries to finish 

452 _lib.ares_queue_wait_empty(channel, -1) 

453 

454 # Destroy the channel 

455 _lib.ares_destroy(channel) 

456 

457 def start(self) -> None: 

458 """Start the background thread if not already started.""" 

459 if self._thread is not None: 

460 return 

461 with self._start_lock: 

462 if self._thread is not None: 

463 # Started by another thread while waiting for the lock 

464 return 

465 self._thread = threading.Thread(target=self._run_safe_shutdown_loop, daemon=True) 

466 self._thread.start() 

467 

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

469 """ 

470 Schedule channel destruction on the background thread. 

471 

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

473 alive until the channel is destroyed. 

474 

475 Thread Safety and Synchronization: 

476 This method uses SimpleQueue which is thread-safe for putting items 

477 from multiple threads. The background thread processes channels 

478 sequentially waiting for queries to end before each destruction. 

479 """ 

480 self._queue.put((channel, sock_state_cb_handle)) 

481 

482 

483# Global shutdown manager instance 

484_shutdown_manager = _ChannelShutdownManager() 

485 

486 

487class Channel: 

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

489 __qclasses__ = (_lib.ARES_CLASS_IN, _lib.ARES_CLASS_CHAOS, _lib.ARES_CLASS_HESOID, _lib.ARES_CLASS_NONE, _lib.ARES_CLASS_ANY) 

490 

491 def __init__(self, 

492 *, 

493 flags: Optional[int] = None, 

494 timeout: Optional[float] = None, 

495 tries: Optional[int] = None, 

496 ndots: Optional[int] = None, 

497 tcp_port: Optional[int] = None, 

498 udp_port: Optional[int] = None, 

499 servers: Optional[Iterable[Union[str, bytes]]] = None, 

500 domains: Optional[Iterable[Union[str, bytes]]] = None, 

501 lookups: Union[str, bytes, None] = None, 

502 sock_state_cb: Optional[Callable[[int, bool, bool], None]] = None, 

503 socket_send_buffer_size: Optional[int] = None, 

504 socket_receive_buffer_size: Optional[int] = None, 

505 rotate: bool = False, 

506 local_ip: Union[str, bytes, None] = None, 

507 local_dev: Optional[str] = None, 

508 resolvconf_path: Union[str, bytes, None] = None) -> None: 

509 

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

511 self._channel = None 

512 self._lock = threading.RLock() 

513 

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

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

516 

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

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

519 optmask = 0 

520 

521 if flags is not None: 

522 options.flags = flags 

523 optmask = optmask | _lib.ARES_OPT_FLAGS 

524 

525 if timeout is not None: 

526 options.timeout = int(timeout * 1000) 

527 optmask = optmask | _lib.ARES_OPT_TIMEOUTMS 

528 

529 if tries is not None: 

530 options.tries = tries 

531 optmask = optmask | _lib.ARES_OPT_TRIES 

532 

533 if ndots is not None: 

534 options.ndots = ndots 

535 optmask = optmask | _lib.ARES_OPT_NDOTS 

536 

537 if tcp_port is not None: 

538 options.tcp_port = tcp_port 

539 optmask = optmask | _lib.ARES_OPT_TCP_PORT 

540 

541 if udp_port is not None: 

542 options.udp_port = udp_port 

543 optmask = optmask | _lib.ARES_OPT_UDP_PORT 

544 

545 if socket_send_buffer_size is not None: 

546 options.socket_send_buffer_size = socket_send_buffer_size 

547 optmask = optmask | _lib.ARES_OPT_SOCK_SNDBUF 

548 

549 if socket_receive_buffer_size is not None: 

550 options.socket_receive_buffer_size = socket_receive_buffer_size 

551 optmask = optmask | _lib.ARES_OPT_SOCK_RCVBUF 

552 

553 if sock_state_cb: 

554 if not callable(sock_state_cb): 

555 raise TypeError("sock_state_cb is not callable") 

556 

557 userdata = _ffi.new_handle(sock_state_cb) 

558 

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

560 self._sock_state_cb_handle = userdata 

561 

562 options.sock_state_cb = _lib._sock_state_cb 

563 options.sock_state_cb_data = userdata 

564 optmask = optmask | _lib.ARES_OPT_SOCK_STATE_CB 

565 else: 

566 self._sock_state_cb_handle = None 

567 optmask = optmask | _lib.ARES_OPT_EVENT_THREAD 

568 options.evsys = _lib.ARES_EVSYS_DEFAULT 

569 

570 if lookups: 

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

572 optmask = optmask | _lib.ARES_OPT_LOOKUPS 

573 

574 if domains: 

575 strs = [_ffi.new("char[]", ascii_bytes(i)) for i in domains] 

576 c = _ffi.new("char *[%d]" % (len(domains) + 1)) 

577 for i in range(len(domains)): 

578 c[i] = strs[i] 

579 

580 options.domains = c 

581 options.ndomains = len(domains) 

582 optmask = optmask | _lib.ARES_OPT_DOMAINS 

583 

584 if rotate: 

585 optmask = optmask | _lib.ARES_OPT_ROTATE 

586 

587 if resolvconf_path is not None: 

588 optmask = optmask | _lib.ARES_OPT_RESOLVCONF 

589 options.resolvconf_path = _ffi.new('char[]', ascii_bytes(resolvconf_path)) 

590 

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

592 if r != _lib.ARES_SUCCESS: 

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

594 

595 self._channel = channel 

596 if servers: 

597 self.servers = servers 

598 

599 if local_ip: 

600 self.set_local_ip(local_ip) 

601 

602 if local_dev: 

603 self.set_local_dev(local_dev) 

604 

605 # Ensure the shutdown thread is started 

606 _shutdown_manager.start() 

607 

608 def __del__(self) -> None: 

609 # Deliberately lock-free: when __del__ runs, no submitter holds 

610 # self and no callback is pending, so the read-and-null in 

611 # _destroy_channel is uncontested. 

612 self._destroy_channel() 

613 

614 @contextlib.contextmanager 

615 def _capture_channel(self): 

616 """ 

617 Yield the c-ares channel pointer while holding self._lock so that 

618 a concurrent close() cannot destroy the channel mid-call. 

619 

620 Raises RuntimeError if the channel has been closed. 

621 """ 

622 with self._lock: 

623 channel = self._channel 

624 if channel is None: 

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

626 yield channel[0] 

627 

628 def _register_callback_handle(self, callback_data): 

629 """ 

630 Wrap callback_data in a cffi handle and register it so that it (and 

631 the Channel) stay alive until the c-ares callback fires. Callers 

632 MUST pop the handle from _handle_to_channel on any exception path 

633 before the c-ares callback could ever observe it. 

634 """ 

635 userdata = _ffi.new_handle(callback_data) 

636 _handle_to_channel[userdata] = self 

637 return userdata 

638 

639 def cancel(self) -> None: 

640 with self._capture_channel() as channel: 

641 _lib.ares_cancel(channel) 

642 

643 def reinit(self) -> None: 

644 with self._capture_channel() as channel: 

645 r = _lib.ares_reinit(channel) 

646 if r != _lib.ARES_SUCCESS: 

647 raise AresError(r, errno.strerror(r)) 

648 

649 @property 

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

651 with self._capture_channel() as channel: 

652 csv_str = _lib.ares_get_servers_csv(channel) 

653 

654 if csv_str == _ffi.NULL: 

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

656 

657 server_list = [] 

658 csv_string = maybe_str(_ffi.string(csv_str)) 

659 _lib.ares_free_string(csv_str) 

660 server_list = [s.strip() for s in csv_string.split(',')] 

661 

662 return server_list 

663 

664 @servers.setter 

665 def servers(self, servers: Iterable[Union[str, bytes]]) -> None: 

666 server_list = [ascii_bytes(s).decode('ascii') if isinstance(s, bytes) else s for s in servers] 

667 csv_str = ','.join(server_list) 

668 

669 with self._capture_channel() as channel: 

670 r = _lib.ares_set_servers_csv(channel, csv_str.encode('ascii')) 

671 if r != _lib.ARES_SUCCESS: 

672 raise AresError(r, errno.strerror(r)) 

673 

674 def process_fd(self, read_fd: int, write_fd: int) -> None: 

675 with self._capture_channel() as channel: 

676 _lib.ares_process_fd(channel, _ffi.cast("ares_socket_t", read_fd), _ffi.cast("ares_socket_t", write_fd)) 

677 

678 def process_read_fd(self, read_fd:int) -> None: 

679 with self._capture_channel() as channel: 

680 _lib.ares_process_fd(channel, _ffi.cast("ares_socket_t", read_fd), _ffi.cast("ares_socket_t", ARES_SOCKET_BAD)) 

681 

682 def process_write_fd(self, write_fd:int) -> None: 

683 with self._capture_channel() as channel: 

684 _lib.ares_process_fd(channel, _ffi.cast("ares_socket_t", ARES_SOCKET_BAD), _ffi.cast("ares_socket_t", write_fd)) 

685 

686 def timeout(self, t = None): 

687 maxtv = _ffi.NULL 

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

689 

690 if t is not None: 

691 if t >= 0.0: 

692 maxtv = _ffi.new("struct timeval*") 

693 maxtv.tv_sec = int(math.floor(t)) 

694 maxtv.tv_usec = int(math.fmod(t, 1.0) * 1000000) 

695 else: 

696 raise ValueError("timeout needs to be a positive number or None") 

697 

698 with self._capture_channel() as channel: 

699 result = _lib.ares_timeout(channel, maxtv, tv) 

700 

701 if result == _ffi.NULL: 

702 return 0.0 

703 

704 return (result.tv_sec + result.tv_usec / 1000000.0) 

705 

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

707 if not callable(callback): 

708 raise TypeError("a callable is required") 

709 

710 addr4 = _ffi.new("struct in_addr*") 

711 addr6 = _ffi.new("struct ares_in6_addr*") 

712 if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(addr), (addr4)) == 1: 

713 address = addr4 

714 family = socket.AF_INET 

715 elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(addr), (addr6)) == 1: 

716 address = addr6 

717 family = socket.AF_INET6 

718 else: 

719 raise ValueError("invalid IP address") 

720 

721 with self._capture_channel() as channel: 

722 userdata = self._register_callback_handle(callback) 

723 try: 

724 _lib.ares_gethostbyaddr(channel, address, _ffi.sizeof(address[0]), family, _lib._host_cb, userdata) 

725 except BaseException: 

726 _handle_to_channel.pop(userdata, None) 

727 raise 

728 

729 def getaddrinfo( 

730 self, 

731 host: str, 

732 port: Optional[int], 

733 *, 

734 family: socket.AddressFamily = 0, 

735 type: int = 0, 

736 proto: int = 0, 

737 flags: int = 0, 

738 callback: Callable[[Any, int], None] 

739 ) -> None: 

740 if not callable(callback): 

741 raise TypeError("a callable is required") 

742 

743 if port is None: 

744 service = _ffi.NULL 

745 elif isinstance(port, int): 

746 service = str(port).encode('ascii') 

747 else: 

748 service = ascii_bytes(port) 

749 

750 with self._capture_channel() as channel: 

751 userdata = self._register_callback_handle(callback) 

752 try: 

753 hints = _ffi.new('struct ares_addrinfo_hints*') 

754 hints.ai_flags = flags 

755 hints.ai_family = family 

756 hints.ai_socktype = type 

757 hints.ai_protocol = proto 

758 _lib.ares_getaddrinfo(channel, parse_name(host), service, hints, _lib._addrinfo_cb, userdata) 

759 except BaseException: 

760 _handle_to_channel.pop(userdata, None) 

761 raise 

762 

763 def query(self, name: str, query_type: int, *, query_class: int = QUERY_CLASS_IN, callback: Callable[[Any, int], None]) -> None: 

764 """ 

765 Perform a DNS query. 

766 

767 Args: 

768 name: Domain name to query 

769 query_type: Type of query (e.g., QUERY_TYPE_A, QUERY_TYPE_AAAA, etc.) 

770 query_class: Query class (default: QUERY_CLASS_IN) 

771 callback: Callback function that receives (result, errno) 

772 

773 The callback will receive a DNSResult object containing answer, authority, and additional sections. 

774 """ 

775 if not callable(callback): 

776 raise TypeError('a callable is required') 

777 

778 if query_type not in self.__qtypes__: 

779 raise ValueError('invalid query type specified') 

780 

781 if query_class not in self.__qclasses__: 

782 raise ValueError('invalid query class specified') 

783 

784 with self._capture_channel() as channel: 

785 userdata = self._register_callback_handle(callback) 

786 try: 

787 qid = _ffi.new("unsigned short *") 

788 status = _lib.ares_query_dnsrec( 

789 channel, 

790 parse_name(name), 

791 query_class, 

792 query_type, 

793 _lib._query_dnsrec_cb, 

794 userdata, 

795 qid 

796 ) 

797 except BaseException: 

798 _handle_to_channel.pop(userdata, None) 

799 raise 

800 if status != _lib.ARES_SUCCESS: 

801 _handle_to_channel.pop(userdata, None) 

802 raise AresError(status, errno.strerror(status)) 

803 

804 def search(self, name: str, query_type: int, *, query_class: int = QUERY_CLASS_IN, callback: Callable[[Any, int], None]) -> None: 

805 """ 

806 Perform a DNS search (honors resolv.conf search domains). 

807 

808 Args: 

809 name: Domain name to search 

810 query_type: Type of query (e.g., QUERY_TYPE_A, QUERY_TYPE_AAAA, etc.) 

811 query_class: Query class (default: QUERY_CLASS_IN) 

812 callback: Callback function that receives (result, errno) 

813 

814 The callback will receive a DNSResult object containing answer, authority, and additional sections. 

815 """ 

816 if not callable(callback): 

817 raise TypeError('a callable is required') 

818 

819 if query_type not in self.__qtypes__: 

820 raise ValueError('invalid query type specified') 

821 

822 if query_class not in self.__qclasses__: 

823 raise ValueError('invalid query class specified') 

824 

825 # Create a DNS record for the search query 

826 # Set RD (Recursion Desired) flag unless ARES_FLAG_NORECURSE is set 

827 dns_flags = 0 if (self._flags & _lib.ARES_FLAG_NORECURSE) else _lib.ARES_FLAG_RD 

828 

829 dnsrec_p = _ffi.new("ares_dns_record_t **") 

830 status = _lib.ares_dns_record_create( 

831 dnsrec_p, 

832 0, # id (will be set by c-ares) 

833 dns_flags, # flags - include RD for recursive queries 

834 _lib.ARES_OPCODE_QUERY, 

835 _lib.ARES_RCODE_NOERROR 

836 ) 

837 if status != _lib.ARES_SUCCESS: 

838 raise AresError(status, errno.strerror(status)) 

839 

840 dnsrec = dnsrec_p[0] 

841 

842 # Add the query to the DNS record 

843 status = _lib.ares_dns_record_query_add( 

844 dnsrec, 

845 parse_name(name), 

846 query_type, 

847 query_class 

848 ) 

849 if status != _lib.ARES_SUCCESS: 

850 _lib.ares_dns_record_destroy(dnsrec) 

851 raise AresError(status, errno.strerror(status)) 

852 

853 # dnsrec is only needed for this call (it's a const parameter); free 

854 # it once ares_search_dnsrec() returns, regardless of outcome. 

855 try: 

856 with self._capture_channel() as channel: 

857 userdata = self._register_callback_handle(callback) 

858 try: 

859 status = _lib.ares_search_dnsrec( 

860 channel, 

861 dnsrec, 

862 _lib._query_dnsrec_cb, 

863 userdata 

864 ) 

865 except BaseException: 

866 _handle_to_channel.pop(userdata, None) 

867 raise 

868 if status != _lib.ARES_SUCCESS: 

869 raise AresError(status, errno.strerror(status)) 

870 finally: 

871 _lib.ares_dns_record_destroy(dnsrec) 

872 

873 def set_local_ip(self, ip): 

874 addr4 = _ffi.new("struct in_addr*") 

875 addr6 = _ffi.new("struct ares_in6_addr*") 

876 

877 with self._capture_channel() as channel: 

878 if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), addr4) == 1: 

879 _lib.ares_set_local_ip4(channel, socket.ntohl(addr4.s_addr)) 

880 elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), addr6) == 1: 

881 _lib.ares_set_local_ip6( 

882 channel, 

883 _ffi.cast("unsigned char *", addr6), 

884 ) 

885 else: 

886 raise ValueError("invalid IP address") 

887 

888 def getnameinfo(self, address: Union[IP4, IP6], flags: int, *, callback: Callable[[Any, int], None]) -> None: 

889 if not callable(callback): 

890 raise TypeError("a callable is required") 

891 

892 if len(address) == 2: 

893 (ip, port) = address 

894 sa4 = _ffi.new("struct sockaddr_in*") 

895 if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), _ffi.addressof(sa4.sin_addr)) != 1: 

896 raise ValueError("Invalid IPv4 address %r" % ip) 

897 sa4.sin_family = socket.AF_INET 

898 sa4.sin_port = socket.htons(port) 

899 sa = sa4 

900 elif len(address) == 4: 

901 (ip, port, flowinfo, scope_id) = address 

902 sa6 = _ffi.new("struct sockaddr_in6*") 

903 if _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), _ffi.addressof(sa6.sin6_addr)) != 1: 

904 raise ValueError("Invalid IPv6 address %r" % ip) 

905 sa6.sin6_family = socket.AF_INET6 

906 sa6.sin6_port = socket.htons(port) 

907 sa6.sin6_flowinfo = socket.htonl(flowinfo) # I'm unsure about byteorder here. 

908 sa6.sin6_scope_id = scope_id # Yes, without htonl. 

909 sa = sa6 

910 else: 

911 raise ValueError("Invalid address argument") 

912 

913 with self._capture_channel() as channel: 

914 userdata = self._register_callback_handle(callback) 

915 try: 

916 _lib.ares_getnameinfo(channel, _ffi.cast("struct sockaddr*", sa), _ffi.sizeof(sa[0]), flags, _lib._nameinfo_cb, userdata) 

917 except BaseException: 

918 _handle_to_channel.pop(userdata, None) 

919 raise 

920 

921 def set_local_dev(self, dev): 

922 with self._capture_channel() as channel: 

923 _lib.ares_set_local_dev(channel, ascii_bytes(dev)) 

924 

925 def close(self) -> None: 

926 """ 

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

928 

929 This method can be called from any thread. The channel will be destroyed 

930 safely using a background thread, after any in-flight submission has 

931 returned, to ensure c-ares has completed its cleanup. 

932 

933 Note: Once close() is called, no new queries can be started. Any pending 

934 queries will be cancelled and their callbacks will receive ARES_ECANCELLED. 

935 

936 """ 

937 with self._lock: 

938 self._destroy_channel() 

939 

940 def _destroy_channel(self) -> None: 

941 channel, self._channel = self._channel, None 

942 if channel is None: 

943 # Already destroyed 

944 return 

945 

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

947 # query callback. 

948 

949 # Schedule channel destruction 

950 _shutdown_manager.destroy_channel(channel, self._sock_state_cb_handle) 

951 

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

953 """ 

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

955 

956 Args: 

957 timeout: Maximum time to wait in seconds. Use -1 for infinite wait. 

958 """ 

959 with self._capture_channel() as channel: 

960 r = _lib.ares_queue_wait_empty(channel, int(timeout * 1000) if timeout is not None and timeout >= 0 else -1) 

961 if r == _lib.ARES_SUCCESS: 

962 return True 

963 elif r == _lib.ARES_ETIMEOUT: 

964 return False 

965 else: 

966 raise AresError(r, errno.strerror(r)) 

967 

968 

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

970# 

971 

972@dataclass 

973class ARecordData: 

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

975 addr: str 

976 

977@dataclass 

978class AAAARecordData: 

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

980 addr: str 

981 

982@dataclass 

983class MXRecordData: 

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

985 priority: int 

986 exchange: str 

987 

988@dataclass 

989class TXTRecordData: 

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

991 data: bytes 

992 

993@dataclass 

994class CAARecordData: 

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

996 critical: int 

997 tag: str 

998 value: str 

999 

1000@dataclass 

1001class CNAMERecordData: 

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

1003 cname: str 

1004 

1005@dataclass 

1006class NAPTRRecordData: 

1007 """Data for NAPTR (naming authority pointer) record""" 

1008 order: int 

1009 preference: int 

1010 flags: str 

1011 service: str 

1012 regexp: str 

1013 replacement: str 

1014 

1015@dataclass 

1016class NSRecordData: 

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

1018 nsdname: str 

1019 

1020@dataclass 

1021class PTRRecordData: 

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

1023 dname: str 

1024 

1025@dataclass 

1026class SOARecordData: 

1027 """Data for SOA (start of authority) record""" 

1028 mname: str 

1029 rname: str 

1030 serial: int 

1031 refresh: int 

1032 retry: int 

1033 expire: int 

1034 minimum: int 

1035 

1036@dataclass 

1037class SRVRecordData: 

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

1039 priority: int 

1040 weight: int 

1041 port: int 

1042 target: str 

1043 

1044@dataclass 

1045class TLSARecordData: 

1046 """Data for TLSA (DANE TLS authentication) record - RFC 6698""" 

1047 cert_usage: int 

1048 selector: int 

1049 matching_type: int 

1050 cert_association_data: bytes 

1051 

1052@dataclass 

1053class HTTPSRecordData: 

1054 """Data for HTTPS (service binding) record - RFC 9460""" 

1055 priority: int 

1056 target: str 

1057 params: list # List of (key: int, value: bytes) tuples 

1058 

1059@dataclass 

1060class URIRecordData: 

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

1062 priority: int 

1063 weight: int 

1064 target: str 

1065 

1066@dataclass 

1067class DNSRecord: 

1068 """Represents a single DNS resource record""" 

1069 name: str 

1070 type: int 

1071 record_class: int 

1072 ttl: int 

1073 data: Union[ARecordData, AAAARecordData, MXRecordData, TXTRecordData, 

1074 CAARecordData, CNAMERecordData, HTTPSRecordData, NAPTRRecordData, 

1075 NSRecordData, PTRRecordData, SOARecordData, SRVRecordData, 

1076 TLSARecordData, URIRecordData] 

1077 

1078@dataclass 

1079class DNSResult: 

1080 """Represents a complete DNS query result with all sections""" 

1081 answer: list[DNSRecord] 

1082 authority: list[DNSRecord] 

1083 additional: list[DNSRecord] 

1084 

1085 

1086# Host/AddrInfo result types 

1087 

1088@dataclass 

1089class HostResult: 

1090 """Result from gethostbyaddr() operation""" 

1091 name: str 

1092 aliases: list[str] 

1093 addresses: list[str] 

1094 

1095@dataclass 

1096class NameInfoResult: 

1097 """Result from getnameinfo() operation""" 

1098 node: str 

1099 service: Optional[str] 

1100 

1101@dataclass 

1102class AddrInfoNode: 

1103 """Single address node from getaddrinfo() result""" 

1104 ttl: int 

1105 flags: int 

1106 family: int 

1107 socktype: int 

1108 protocol: int 

1109 addr: tuple # (ip, port) or (ip, port, flowinfo, scope_id) 

1110 

1111@dataclass 

1112class AddrInfoCname: 

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

1114 ttl: int 

1115 alias: str 

1116 name: str 

1117 

1118@dataclass 

1119class AddrInfoResult: 

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

1121 cnames: list[AddrInfoCname] 

1122 nodes: list[AddrInfoNode] 

1123 

1124 

1125# Parser functions for Host/AddrInfo results 

1126 

1127def parse_hostent(hostent) -> HostResult: 

1128 """Parse c-ares hostent structure into HostResult""" 

1129 name = maybe_str(_ffi.string(hostent.h_name)) 

1130 aliases = [] 

1131 addresses = [] 

1132 

1133 i = 0 

1134 while hostent.h_aliases[i] != _ffi.NULL: 

1135 aliases.append(maybe_str(_ffi.string(hostent.h_aliases[i]))) 

1136 i += 1 

1137 

1138 i = 0 

1139 while hostent.h_addr_list[i] != _ffi.NULL: 

1140 buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN) 

1141 if _ffi.NULL != _lib.ares_inet_ntop(hostent.h_addrtype, hostent.h_addr_list[i], buf, _lib.INET6_ADDRSTRLEN): 

1142 addresses.append(maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN))) 

1143 i += 1 

1144 

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

1146 

1147 

1148def parse_nameinfo(node, service) -> NameInfoResult: 

1149 """Parse c-ares nameinfo into NameInfoResult""" 

1150 node_str = maybe_str(_ffi.string(node)) 

1151 service_str = maybe_str(_ffi.string(service)) if service != _ffi.NULL else None 

1152 return NameInfoResult(node=node_str, service=service_str) 

1153 

1154 

1155def parse_addrinfo_node(ares_node) -> AddrInfoNode: 

1156 """Parse a single c-ares addrinfo node into AddrInfoNode""" 

1157 ttl = ares_node.ai_ttl 

1158 flags = ares_node.ai_flags 

1159 socktype = ares_node.ai_socktype 

1160 protocol = ares_node.ai_protocol 

1161 

1162 addr_struct = ares_node.ai_addr 

1163 assert addr_struct.sa_family == ares_node.ai_family 

1164 ip = _ffi.new("char []", _lib.INET6_ADDRSTRLEN) 

1165 

1166 if addr_struct.sa_family == socket.AF_INET: 

1167 family = socket.AF_INET 

1168 s = _ffi.cast("struct sockaddr_in*", addr_struct) 

1169 if _ffi.NULL != _lib.ares_inet_ntop(s.sin_family, _ffi.addressof(s.sin_addr), ip, _lib.INET6_ADDRSTRLEN): 

1170 # (address, port) 2-tuple for AF_INET 

1171 addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin_port)) 

1172 else: 

1173 raise ValueError("failed to convert IPv4 address") 

1174 elif addr_struct.sa_family == socket.AF_INET6: 

1175 family = socket.AF_INET6 

1176 s = _ffi.cast("struct sockaddr_in6*", addr_struct) 

1177 if _ffi.NULL != _lib.ares_inet_ntop(s.sin6_family, _ffi.addressof(s.sin6_addr), ip, _lib.INET6_ADDRSTRLEN): 

1178 # (address, port, flow info, scope id) 4-tuple for AF_INET6 

1179 addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin6_port), s.sin6_flowinfo, s.sin6_scope_id) 

1180 else: 

1181 raise ValueError("failed to convert IPv6 address") 

1182 else: 

1183 raise ValueError("invalid sockaddr family") 

1184 

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

1186 

1187 

1188def parse_addrinfo_cname(ares_cname) -> AddrInfoCname: 

1189 """Parse a single c-ares addrinfo cname into AddrInfoCname""" 

1190 return AddrInfoCname( 

1191 ttl=ares_cname.ttl, 

1192 alias=maybe_str(_ffi.string(ares_cname.alias)), 

1193 name=maybe_str(_ffi.string(ares_cname.name)) 

1194 ) 

1195 

1196 

1197def parse_addrinfo(ares_addrinfo) -> AddrInfoResult: 

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

1199 cnames = [] 

1200 nodes = [] 

1201 

1202 cname_ptr = ares_addrinfo.cnames 

1203 while cname_ptr != _ffi.NULL: 

1204 cnames.append(parse_addrinfo_cname(cname_ptr)) 

1205 cname_ptr = cname_ptr.next 

1206 

1207 node_ptr = ares_addrinfo.nodes 

1208 while node_ptr != _ffi.NULL: 

1209 nodes.append(parse_addrinfo_node(node_ptr)) 

1210 node_ptr = node_ptr.ai_next 

1211 

1212 _lib.ares_freeaddrinfo(ares_addrinfo) 

1213 

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

1215 

1216 

1217__all__ = ( 

1218 # Channel flags 

1219 "ARES_FLAG_USEVC", 

1220 "ARES_FLAG_PRIMARY", 

1221 "ARES_FLAG_IGNTC", 

1222 "ARES_FLAG_NORECURSE", 

1223 "ARES_FLAG_STAYOPEN", 

1224 "ARES_FLAG_NOSEARCH", 

1225 "ARES_FLAG_NOALIASES", 

1226 "ARES_FLAG_NOCHECKRESP", 

1227 "ARES_FLAG_EDNS", 

1228 "ARES_FLAG_NO_DFLT_SVR", 

1229 

1230 # Nameinfo flag values 

1231 "ARES_NI_NOFQDN", 

1232 "ARES_NI_NUMERICHOST", 

1233 "ARES_NI_NAMEREQD", 

1234 "ARES_NI_NUMERICSERV", 

1235 "ARES_NI_DGRAM", 

1236 "ARES_NI_TCP", 

1237 "ARES_NI_UDP", 

1238 "ARES_NI_SCTP", 

1239 "ARES_NI_DCCP", 

1240 "ARES_NI_NUMERICSCOPE", 

1241 "ARES_NI_LOOKUPHOST", 

1242 "ARES_NI_LOOKUPSERVICE", 

1243 "ARES_NI_IDN", 

1244 "ARES_NI_IDN_ALLOW_UNASSIGNED", 

1245 "ARES_NI_IDN_USE_STD3_ASCII_RULES", 

1246 

1247 # Bad socket 

1248 "ARES_SOCKET_BAD", 

1249 

1250 # Query types 

1251 "QUERY_TYPE_A", 

1252 "QUERY_TYPE_AAAA", 

1253 "QUERY_TYPE_ANY", 

1254 "QUERY_TYPE_CAA", 

1255 "QUERY_TYPE_CNAME", 

1256 "QUERY_TYPE_HTTPS", 

1257 "QUERY_TYPE_MX", 

1258 "QUERY_TYPE_NAPTR", 

1259 "QUERY_TYPE_NS", 

1260 "QUERY_TYPE_PTR", 

1261 "QUERY_TYPE_SOA", 

1262 "QUERY_TYPE_SRV", 

1263 "QUERY_TYPE_TLSA", 

1264 "QUERY_TYPE_TXT", 

1265 "QUERY_TYPE_URI", 

1266 

1267 # Query classes 

1268 "QUERY_CLASS_IN", 

1269 "QUERY_CLASS_CHAOS", 

1270 "QUERY_CLASS_HS", 

1271 "QUERY_CLASS_NONE", 

1272 "QUERY_CLASS_ANY", 

1273 

1274 # Core stuff 

1275 "ARES_VERSION", 

1276 "AresError", 

1277 "Channel", 

1278 "errno", 

1279 "__version__", 

1280 

1281 # DNS record result types 

1282 "DNSResult", 

1283 "DNSRecord", 

1284 "ARecordData", 

1285 "AAAARecordData", 

1286 "MXRecordData", 

1287 "TXTRecordData", 

1288 "CAARecordData", 

1289 "CNAMERecordData", 

1290 "HTTPSRecordData", 

1291 "NAPTRRecordData", 

1292 "NSRecordData", 

1293 "PTRRecordData", 

1294 "SOARecordData", 

1295 "SRVRecordData", 

1296 "TLSARecordData", 

1297 "URIRecordData", 

1298 

1299 # Host/AddrInfo result types 

1300 "HostResult", 

1301 "NameInfoResult", 

1302 "AddrInfoResult", 

1303 "AddrInfoNode", 

1304 "AddrInfoCname", 

1305)