1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
|
# Copyright (c) 2011-2012 Denis Bilenko. See LICENSE for details.
cimport cares
import sys
from python cimport *
from _socket import gaierror
__all__ = ['channel']
cdef object string_types
cdef object text_type
if sys.version_info[0] >= 3:
string_types = str,
text_type = str
else:
string_types = __builtins__.basestring,
text_type = __builtins__.unicode
TIMEOUT = 1
DEF EV_READ = 1
DEF EV_WRITE = 2
cdef extern from "dnshelper.c":
int AF_INET
int AF_INET6
struct hostent:
char* h_name
int h_addrtype
struct sockaddr_t "sockaddr":
pass
struct ares_channeldata:
pass
object parse_h_name(hostent*)
object parse_h_aliases(hostent*)
object parse_h_addr_list(hostent*)
void* create_object_from_hostent(void*)
# this imports _socket lazily
object PyUnicode_FromString(char*)
int PyTuple_Check(object)
int PyArg_ParseTuple(object, char*, ...) except 0
struct sockaddr_in6:
pass
int gevent_make_sockaddr(char* hostp, int port, int flowinfo, int scope_id, sockaddr_in6* sa6)
void* malloc(int)
void free(void*)
void memset(void*, int, int)
ARES_SUCCESS = cares.ARES_SUCCESS
ARES_ENODATA = cares.ARES_ENODATA
ARES_EFORMERR = cares.ARES_EFORMERR
ARES_ESERVFAIL = cares.ARES_ESERVFAIL
ARES_ENOTFOUND = cares.ARES_ENOTFOUND
ARES_ENOTIMP = cares.ARES_ENOTIMP
ARES_EREFUSED = cares.ARES_EREFUSED
ARES_EBADQUERY = cares.ARES_EBADQUERY
ARES_EBADNAME = cares.ARES_EBADNAME
ARES_EBADFAMILY = cares.ARES_EBADFAMILY
ARES_EBADRESP = cares.ARES_EBADRESP
ARES_ECONNREFUSED = cares.ARES_ECONNREFUSED
ARES_ETIMEOUT = cares.ARES_ETIMEOUT
ARES_EOF = cares.ARES_EOF
ARES_EFILE = cares.ARES_EFILE
ARES_ENOMEM = cares.ARES_ENOMEM
ARES_EDESTRUCTION = cares.ARES_EDESTRUCTION
ARES_EBADSTR = cares.ARES_EBADSTR
ARES_EBADFLAGS = cares.ARES_EBADFLAGS
ARES_ENONAME = cares.ARES_ENONAME
ARES_EBADHINTS = cares.ARES_EBADHINTS
ARES_ENOTINITIALIZED = cares.ARES_ENOTINITIALIZED
ARES_ELOADIPHLPAPI = cares.ARES_ELOADIPHLPAPI
ARES_EADDRGETNETWORKPARAMS = cares.ARES_EADDRGETNETWORKPARAMS
ARES_ECANCELLED = cares.ARES_ECANCELLED
ARES_FLAG_USEVC = cares.ARES_FLAG_USEVC
ARES_FLAG_PRIMARY = cares.ARES_FLAG_PRIMARY
ARES_FLAG_IGNTC = cares.ARES_FLAG_IGNTC
ARES_FLAG_NORECURSE = cares.ARES_FLAG_NORECURSE
ARES_FLAG_STAYOPEN = cares.ARES_FLAG_STAYOPEN
ARES_FLAG_NOSEARCH = cares.ARES_FLAG_NOSEARCH
ARES_FLAG_NOALIASES = cares.ARES_FLAG_NOALIASES
ARES_FLAG_NOCHECKRESP = cares.ARES_FLAG_NOCHECKRESP
_ares_errors = dict([
(cares.ARES_SUCCESS, 'ARES_SUCCESS'),
(cares.ARES_ENODATA, 'ARES_ENODATA'),
(cares.ARES_EFORMERR, 'ARES_EFORMERR'),
(cares.ARES_ESERVFAIL, 'ARES_ESERVFAIL'),
(cares.ARES_ENOTFOUND, 'ARES_ENOTFOUND'),
(cares.ARES_ENOTIMP, 'ARES_ENOTIMP'),
(cares.ARES_EREFUSED, 'ARES_EREFUSED'),
(cares.ARES_EBADQUERY, 'ARES_EBADQUERY'),
(cares.ARES_EBADNAME, 'ARES_EBADNAME'),
(cares.ARES_EBADFAMILY, 'ARES_EBADFAMILY'),
(cares.ARES_EBADRESP, 'ARES_EBADRESP'),
(cares.ARES_ECONNREFUSED, 'ARES_ECONNREFUSED'),
(cares.ARES_ETIMEOUT, 'ARES_ETIMEOUT'),
(cares.ARES_EOF, 'ARES_EOF'),
(cares.ARES_EFILE, 'ARES_EFILE'),
(cares.ARES_ENOMEM, 'ARES_ENOMEM'),
(cares.ARES_EDESTRUCTION, 'ARES_EDESTRUCTION'),
(cares.ARES_EBADSTR, 'ARES_EBADSTR'),
(cares.ARES_EBADFLAGS, 'ARES_EBADFLAGS'),
(cares.ARES_ENONAME, 'ARES_ENONAME'),
(cares.ARES_EBADHINTS, 'ARES_EBADHINTS'),
(cares.ARES_ENOTINITIALIZED, 'ARES_ENOTINITIALIZED'),
(cares.ARES_ELOADIPHLPAPI, 'ARES_ELOADIPHLPAPI'),
(cares.ARES_EADDRGETNETWORKPARAMS, 'ARES_EADDRGETNETWORKPARAMS'),
(cares.ARES_ECANCELLED, 'ARES_ECANCELLED')])
# maps c-ares flag to _socket module flag
_cares_flag_map = None
cdef _prepare_cares_flag_map():
global _cares_flag_map
import _socket
_cares_flag_map = [
(getattr(_socket, 'NI_NUMERICHOST', 1), cares.ARES_NI_NUMERICHOST),
(getattr(_socket, 'NI_NUMERICSERV', 2), cares.ARES_NI_NUMERICSERV),
(getattr(_socket, 'NI_NOFQDN', 4), cares.ARES_NI_NOFQDN),
(getattr(_socket, 'NI_NAMEREQD', 8), cares.ARES_NI_NAMEREQD),
(getattr(_socket, 'NI_DGRAM', 16), cares.ARES_NI_DGRAM)]
cpdef _convert_cares_flags(int flags, int default=cares.ARES_NI_LOOKUPHOST|cares.ARES_NI_LOOKUPSERVICE):
if _cares_flag_map is None:
_prepare_cares_flag_map()
for socket_flag, cares_flag in _cares_flag_map:
if socket_flag & flags:
default |= cares_flag
flags &= ~socket_flag
if not flags:
return default
raise gaierror(-1, "Bad value for ai_flags: 0x%x" % flags)
cpdef strerror(code):
return '%s: %s' % (_ares_errors.get(code) or code, cares.ares_strerror(code))
class InvalidIP(ValueError):
pass
cdef void gevent_sock_state_callback(void *data, int s, int read, int write):
if not data:
return
cdef channel ch = <channel>data
ch._sock_state_callback(s, read, write)
cdef class result:
cdef public object value
cdef public object exception
def __init__(self, object value=None, object exception=None):
self.value = value
self.exception = exception
def __repr__(self):
if self.exception is None:
return '%s(%r)' % (self.__class__.__name__, self.value)
elif self.value is None:
return '%s(exception=%r)' % (self.__class__.__name__, self.exception)
else:
return '%s(value=%r, exception=%r)' % (self.__class__.__name__, self.value, self.exception)
# add repr_recursive precaution
def successful(self):
return self.exception is None
def get(self):
if self.exception is not None:
raise self.exception
return self.value
class ares_host_result(tuple):
def __new__(cls, family, iterable):
cdef object self = tuple.__new__(cls, iterable)
self.family = family
return self
def __getnewargs__(self):
return (self.family, tuple(self))
cdef void gevent_ares_host_callback(void *arg, int status, int timeouts, hostent* host):
cdef channel channel
cdef object callback
channel, callback = <tuple>arg
Py_DECREF(<PyObjectPtr>arg)
cdef object host_result
try:
if status or not host:
callback(result(None, gaierror(status, strerror(status))))
else:
try:
host_result = ares_host_result(host.h_addrtype, (parse_h_name(host), parse_h_aliases(host), parse_h_addr_list(host)))
except:
callback(result(None, sys.exc_info()[1]))
else:
callback(result(host_result))
except:
channel.loop.handle_error(callback, *sys.exc_info())
cdef void gevent_ares_nameinfo_callback(void *arg, int status, int timeouts, char *c_node, char *c_service):
cdef channel channel
cdef object callback
channel, callback = <tuple>arg
Py_DECREF(<PyObjectPtr>arg)
cdef object node
cdef object service
try:
if status:
callback(result(None, gaierror(status, strerror(status))))
else:
if c_node:
node = PyUnicode_FromString(c_node)
else:
node = None
if c_service:
service = PyUnicode_FromString(c_service)
else:
service = None
callback(result((node, service)))
except:
channel.loop.handle_error(callback, *sys.exc_info())
cdef public class channel [object PyGeventAresChannelObject, type PyGeventAresChannel_Type]:
cdef public object loop
cdef ares_channeldata* channel
cdef public dict _watchers
cdef public object _timer
def __init__(self, object loop, flags=None, timeout=None, tries=None, ndots=None,
udp_port=None, tcp_port=None, servers=None):
cdef ares_channeldata* channel = NULL
cdef cares.ares_options options
memset(&options, 0, sizeof(cares.ares_options))
cdef int optmask = cares.ARES_OPT_SOCK_STATE_CB
options.sock_state_cb = <void*>gevent_sock_state_callback
options.sock_state_cb_data = <void*>self
if flags is not None:
options.flags = int(flags)
optmask |= cares.ARES_OPT_FLAGS
if timeout is not None:
options.timeout = int(float(timeout) * 1000)
optmask |= cares.ARES_OPT_TIMEOUTMS
if tries is not None:
options.tries = int(tries)
optmask |= cares.ARES_OPT_TRIES
if ndots is not None:
options.ndots = int(ndots)
optmask |= cares.ARES_OPT_NDOTS
if udp_port is not None:
options.udp_port = int(udp_port)
optmask |= cares.ARES_OPT_UDP_PORT
if tcp_port is not None:
options.tcp_port = int(tcp_port)
optmask |= cares.ARES_OPT_TCP_PORT
cdef int result = cares.ares_library_init(cares.ARES_LIB_INIT_ALL) # ARES_LIB_INIT_WIN32 -DUSE_WINSOCK?
if result:
raise gaierror(result, strerror(result))
result = cares.ares_init_options(&channel, &options, optmask)
if result:
raise gaierror(result, strerror(result))
self._timer = loop.timer(TIMEOUT, TIMEOUT)
self._watchers = {}
self.channel = channel
try:
if servers is not None:
self.set_servers(servers)
self.loop = loop
except:
self.destroy()
raise
def __repr__(self):
args = (self.__class__.__name__, id(self), self._timer, len(self._watchers))
return '<%s at 0x%x _timer=%r _watchers[%s]>' % args
def destroy(self):
if self.channel:
# XXX ares_library_cleanup?
cares.ares_destroy(self.channel)
self.channel = NULL
self._watchers.clear()
self._timer.stop()
self.loop = None
def __dealloc__(self):
if self.channel:
# XXX ares_library_cleanup?
cares.ares_destroy(self.channel)
self.channel = NULL
def set_servers(self, servers=None):
if not self.channel:
raise gaierror(cares.ARES_EDESTRUCTION, 'this ares channel has been destroyed')
if not servers:
servers = []
if isinstance(servers, string_types):
servers = servers.split(',')
cdef int length = len(servers)
cdef int result, index
cdef char* string
cdef cares.ares_addr_node* c_servers
if length <= 0:
result = cares.ares_set_servers(self.channel, NULL)
else:
c_servers = <cares.ares_addr_node*>malloc(sizeof(cares.ares_addr_node) * length)
if not c_servers:
raise MemoryError
try:
index = 0
for server in servers:
if isinstance(server, unicode):
server = server.encode('ascii')
string = <char*?>server
if cares.ares_inet_pton(AF_INET, string, &c_servers[index].addr) > 0:
c_servers[index].family = AF_INET
elif cares.ares_inet_pton(AF_INET6, string, &c_servers[index].addr) > 0:
c_servers[index].family = AF_INET6
else:
raise InvalidIP(repr(string))
c_servers[index].next = &c_servers[index] + 1
index += 1
if index >= length:
break
c_servers[length - 1].next = NULL
index = cares.ares_set_servers(self.channel, c_servers)
if index:
raise ValueError(strerror(index))
finally:
free(c_servers)
# this crashes c-ares
#def cancel(self):
# cares.ares_cancel(self.channel)
cdef _sock_state_callback(self, int socket, int read, int write):
if not self.channel:
return
cdef object watcher = self._watchers.get(socket)
cdef int events = 0
if read:
events |= EV_READ
if write:
events |= EV_WRITE
if watcher is None:
if not events:
return
watcher = self.loop.io(socket, events)
self._watchers[socket] = watcher
elif events:
if watcher.events == events:
return
watcher.stop()
watcher.events = events
else:
watcher.stop()
self._watchers.pop(socket, None)
if not self._watchers:
self._timer.stop()
return
watcher.start(self._process_fd, watcher, pass_events=True)
self._timer.again(self._on_timer)
def _on_timer(self):
cares.ares_process_fd(self.channel, cares.ARES_SOCKET_BAD, cares.ARES_SOCKET_BAD)
def _process_fd(self, int events, object watcher):
if not self.channel:
return
cdef int read_fd = watcher.fd
cdef int write_fd = read_fd
if not (events & EV_READ):
read_fd = cares.ARES_SOCKET_BAD
if not (events & EV_WRITE):
write_fd = cares.ARES_SOCKET_BAD
cares.ares_process_fd(self.channel, read_fd, write_fd)
def gethostbyname(self, object callback, char* name, int family=AF_INET):
if not self.channel:
raise gaierror(cares.ARES_EDESTRUCTION, 'this ares channel has been destroyed')
# note that for file lookups still AF_INET can be returned for AF_INET6 request
cdef object arg = (self, callback)
Py_INCREF(<PyObjectPtr>arg)
cares.ares_gethostbyname(self.channel, name, family, <void*>gevent_ares_host_callback, <void*>arg)
def gethostbyaddr(self, object callback, char* addr):
if not self.channel:
raise gaierror(cares.ARES_EDESTRUCTION, 'this ares channel has been destroyed')
# will guess the family
cdef char addr_packed[16]
cdef int family
cdef int length
if cares.ares_inet_pton(AF_INET, addr, addr_packed) > 0:
family = AF_INET
length = 4
elif cares.ares_inet_pton(AF_INET6, addr, addr_packed) > 0:
family = AF_INET6
length = 16
else:
raise InvalidIP(repr(addr))
cdef object arg = (self, callback)
Py_INCREF(<PyObjectPtr>arg)
cares.ares_gethostbyaddr(self.channel, addr_packed, length, family, <void*>gevent_ares_host_callback, <void*>arg)
cpdef _getnameinfo(self, object callback, tuple sockaddr, int flags):
if not self.channel:
raise gaierror(cares.ARES_EDESTRUCTION, 'this ares channel has been destroyed')
cdef char* hostp = NULL
cdef int port = 0
cdef int flowinfo = 0
cdef int scope_id = 0
cdef sockaddr_in6 sa6
if not PyTuple_Check(sockaddr):
raise TypeError('expected a tuple, got %r' % (sockaddr, ))
PyArg_ParseTuple(sockaddr, "si|ii", &hostp, &port, &flowinfo, &scope_id)
if port < 0 or port > 65535:
raise gaierror(-8, 'Invalid value for port: %r' % port)
cdef int length = gevent_make_sockaddr(hostp, port, flowinfo, scope_id, &sa6)
if length <= 0:
raise InvalidIP(repr(hostp))
cdef object arg = (self, callback)
Py_INCREF(<PyObjectPtr>arg)
cdef sockaddr_t* x = <sockaddr_t*>&sa6
cares.ares_getnameinfo(self.channel, x, length, flags, <void*>gevent_ares_nameinfo_callback, <void*>arg)
def getnameinfo(self, object callback, tuple sockaddr, int flags):
try:
flags = _convert_cares_flags(flags)
except gaierror:
# The stdlib just ignores bad flags
flags = 0
return self._getnameinfo(callback, sockaddr, flags)
|