pycares 4.7.0__cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl

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