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
|
# Copyright (c) 2011-2012 Denis Bilenko. See LICENSE for details.
cimport cares
import sys
from python cimport *
from _socket import gaierror
__all__ = ['channel']
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_aliases(hostent*)
object parse_h_addr_list(hostent*)
void* create_object_from_hostent(void*)
# this imports _socket lazily
object PyBytes_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, (host.h_name, 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 = PyBytes_FromString(c_node)
else:
node = None
if c_service:
service = PyBytes_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, basestring):
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:
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):
return self._getnameinfo(callback, sockaddr, _convert_cares_flags(flags))
|