Add python-eventlet package to MOS 8.0 repository
[packages/trusty/python-eventlet.git] / python-eventlet / eventlet / support / greendns.py
1 '''greendns - non-blocking DNS support for Eventlet
2 '''
3
4 # Portions of this code taken from the gogreen project:
5 #   http://github.com/slideinc/gogreen
6 #
7 # Copyright (c) 2005-2010 Slide, Inc.
8 # All rights reserved.
9 #
10 # Redistribution and use in source and binary forms, with or without
11 # modification, are permitted provided that the following conditions are
12 # met:
13 #
14 #     * Redistributions of source code must retain the above copyright
15 #       notice, this list of conditions and the following disclaimer.
16 #     * Redistributions in binary form must reproduce the above
17 #       copyright notice, this list of conditions and the following
18 #       disclaimer in the documentation and/or other materials provided
19 #       with the distribution.
20 #     * Neither the name of the author nor the names of other
21 #       contributors may be used to endorse or promote products derived
22 #       from this software without specific prior written permission.
23 #
24 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 import struct
36
37 from eventlet import patcher
38 from eventlet.green import _socket_nodns
39 from eventlet.green import os
40 from eventlet.green import time
41 from eventlet.green import select
42 from eventlet.support import six
43
44
45 dns = patcher.import_patched('dns',
46                              select=select,
47                              time=time,
48                              os=os,
49                              socket=_socket_nodns)
50 dns.resolver = patcher.import_patched('dns.resolver',
51                                       select=select,
52                                       time=time,
53                                       os=os,
54                                       socket=_socket_nodns)
55
56 for pkg in ('dns.entropy', 'dns.inet', 'dns.query'):
57     setattr(dns, pkg.split('.')[1], patcher.import_patched(pkg,
58                                                            select=select,
59                                                            time=time,
60                                                            os=os,
61                                                            socket=_socket_nodns))
62 import dns.rdtypes
63 for pkg in ['dns.rdtypes.IN', 'dns.rdtypes.ANY']:
64     setattr(dns.rdtypes, pkg.split('.')[-1], patcher.import_patched(pkg))
65 for pkg in ['dns.rdtypes.IN.A', 'dns.rdtypes.IN.AAAA']:
66     setattr(dns.rdtypes.IN, pkg.split('.')[-1], patcher.import_patched(pkg))
67 for pkg in ['dns.rdtypes.ANY.CNAME']:
68     setattr(dns.rdtypes.ANY, pkg.split('.')[-1], patcher.import_patched(pkg))
69
70
71 socket = _socket_nodns
72
73 DNS_QUERY_TIMEOUT = 10.0
74 HOSTS_TTL = 10.0
75
76 EAI_EAGAIN_ERROR = socket.gaierror(socket.EAI_AGAIN, 'Lookup timed out')
77 EAI_NODATA_ERROR = socket.gaierror(socket.EAI_NODATA, 'No address associated with hostname')
78 EAI_NONAME_ERROR = socket.gaierror(socket.EAI_NONAME, 'Name or service not known')
79
80
81 def is_ipv4_addr(host):
82     """Return True if host is a valid IPv4 address"""
83     if not isinstance(host, six.string_types):
84         return False
85     try:
86         dns.ipv4.inet_aton(host)
87     except dns.exception.SyntaxError:
88         return False
89     else:
90         return True
91
92
93 def is_ipv6_addr(host):
94     """Return True if host is a valid IPv6 address"""
95     if not isinstance(host, six.string_types):
96         return False
97     try:
98         dns.ipv6.inet_aton(host)
99     except dns.exception.SyntaxError:
100         return False
101     else:
102         return True
103
104
105 def is_ip_addr(host):
106     """Return True if host is a valid IPv4 or IPv6 address"""
107     return is_ipv4_addr(host) or is_ipv6_addr(host)
108
109
110 class HostsAnswer(dns.resolver.Answer):
111     """Answer class for HostsResolver object"""
112
113     def __init__(self, qname, rdtype, rdclass, rrset, raise_on_no_answer=True):
114         """Create a new answer
115
116         :qname: A dns.name.Name instance of the query name
117         :rdtype: The rdatatype of the query
118         :rdclass: The rdataclass of the query
119         :rrset: The dns.rrset.RRset with the response, must have ttl attribute
120         :raise_on_no_answer: Whether to raise dns.resolver.NoAnswer if no
121            answer.
122         """
123         self.response = None
124         self.qname = qname
125         self.rdtype = rdtype
126         self.rdclass = rdclass
127         self.canonical_name = qname
128         if not rrset and raise_on_no_answer:
129             raise dns.resolver.NoAnswer()
130         self.rrset = rrset
131         self.expiration = (time.time() +
132                            rrset.ttl if hasattr(rrset, 'ttl') else 0)
133
134
135 class HostsResolver(object):
136     """Class to parse the hosts file
137
138     Attributes
139     ----------
140
141     :fname: The filename of the hosts file in use.
142     :interval: The time between checking for hosts file modification
143     """
144
145     def __init__(self, fname=None, interval=HOSTS_TTL):
146         self._v4 = {}           # name -> ipv4
147         self._v6 = {}           # name -> ipv6
148         self._aliases = {}      # name -> cannonical_name
149         self.interval = interval
150         self.fname = fname
151         if fname is None:
152             if os.name == 'posix':
153                 self.fname = '/etc/hosts'
154             elif os.name == 'nt':
155                 self.fname = os.path.expandvars(
156                     r'%SystemRoot%\system32\drivers\etc\hosts')
157         self._last_load = 0
158         if self.fname:
159             self._load()
160
161     def _readlines(self):
162         """Read the contents of the hosts file
163
164         Return list of lines, comment lines and empty lines are
165         excluded.
166
167         Note that this performs disk I/O so can be blocking.
168         """
169         lines = []
170         try:
171             with open(self.fname, 'rU') as fp:
172                 for line in fp:
173                     line = line.strip()
174                     if line and line[0] != '#':
175                         lines.append(line)
176         except (IOError, OSError):
177             pass
178         return lines
179
180     def _load(self):
181         """Load hosts file
182
183         This will unconditionally (re)load the data from the hosts
184         file.
185         """
186         lines = self._readlines()
187         self._v4.clear()
188         self._v6.clear()
189         self._aliases.clear()
190         for line in lines:
191             parts = line.split()
192             if len(parts) < 2:
193                 continue
194             ip = parts.pop(0)
195             if is_ipv4_addr(ip):
196                 ipmap = self._v4
197             elif is_ipv6_addr(ip):
198                 if ip.startswith('fe80'):
199                     # Do not use link-local addresses, OSX stores these here
200                     continue
201                 ipmap = self._v6
202             else:
203                 continue
204             cname = parts.pop(0)
205             ipmap[cname] = ip
206             for alias in parts:
207                 ipmap[alias] = ip
208                 self._aliases[alias] = cname
209         self._last_load = time.time()
210
211     def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
212               tcp=False, source=None, raise_on_no_answer=True):
213         """Query the hosts file
214
215         The known rdtypes are dns.rdatatype.A, dns.rdatatype.AAAA and
216         dns.rdatatype.CNAME.
217
218         The ``rdclass`` parameter must be dns.rdataclass.IN while the
219         ``tcp`` and ``source`` parameters are ignored.
220
221         Return a HostAnswer instance or raise a dns.resolver.NoAnswer
222         exception.
223         """
224         now = time.time()
225         if self._last_load + self.interval < now:
226             self._load()
227         rdclass = dns.rdataclass.IN
228         if isinstance(qname, six.string_types):
229             name = qname
230             qname = dns.name.from_text(qname)
231         else:
232             name = str(qname)
233         rrset = dns.rrset.RRset(qname, rdclass, rdtype)
234         rrset.ttl = self._last_load + self.interval - now
235         if rdclass == dns.rdataclass.IN and rdtype == dns.rdatatype.A:
236             addr = self._v4.get(name)
237             if not addr and qname.is_absolute():
238                 addr = self._v4.get(name[:-1])
239             if addr:
240                 rrset.add(dns.rdtypes.IN.A.A(rdclass, rdtype, addr))
241         elif rdclass == dns.rdataclass.IN and rdtype == dns.rdatatype.AAAA:
242             addr = self._v6.get(name)
243             if not addr and qname.is_absolute():
244                 addr = self._v6.get(name[:-1])
245             if addr:
246                 rrset.add(dns.rdtypes.IN.AAAA.AAAA(rdclass, rdtype, addr))
247         elif rdclass == dns.rdataclass.IN and rdtype == dns.rdatatype.CNAME:
248             cname = self._aliases.get(name)
249             if not cname and qname.is_absolute():
250                 cname = self._aliases.get(name[:-1])
251             if cname:
252                 rrset.add(dns.rdtypes.ANY.CNAME.CNAME(
253                     rdclass, rdtype, dns.name.from_text(cname)))
254         return HostsAnswer(qname, rdtype, rdclass, rrset, raise_on_no_answer)
255
256     def getaliases(self, hostname):
257         """Return a list of all the aliases of a given cname"""
258         # Due to the way store aliases this is a bit inefficient, this
259         # clearly was an afterthought.  But this is only used by
260         # gethostbyname_ex so it's probably fine.
261         aliases = []
262         if hostname in self._aliases:
263             cannon = self._aliases[hostname]
264         else:
265             cannon = hostname
266         aliases.append(cannon)
267         for alias, cname in six.iteritems(self._aliases):
268             if cannon == cname:
269                 aliases.append(alias)
270         aliases.remove(hostname)
271         return aliases
272
273
274 class ResolverProxy(object):
275     """Resolver class which can also use /etc/hosts
276
277     Initialise with a HostsResolver instance in order for it to also
278     use the hosts file.
279     """
280
281     def __init__(self, hosts_resolver=None, filename='/etc/resolv.conf'):
282         """Initialise the resolver proxy
283
284         :param hosts_resolver: An instance of HostsResolver to use.
285
286         :param filename: The filename containing the resolver
287            configuration.  The default value is correct for both UNIX
288            and Windows, on Windows it will result in the configuration
289            being read from the Windows registry.
290         """
291         self._hosts = hosts_resolver
292         self._filename = filename
293         self._resolver = dns.resolver.Resolver(filename=self._filename)
294         self._resolver.cache = dns.resolver.LRUCache()
295
296     def clear(self):
297         self._resolver = dns.resolver.Resolver(filename=self._filename)
298         self._resolver.cache = dns.resolver.Cache()
299
300     def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
301               tcp=False, source=None, raise_on_no_answer=True):
302         """Query the resolver, using /etc/hosts if enabled"""
303         if qname is None:
304             qname = '0.0.0.0'
305         if rdclass == dns.rdataclass.IN and self._hosts:
306             try:
307                 return self._hosts.query(qname, rdtype)
308             except dns.resolver.NoAnswer:
309                 pass
310         return self._resolver.query(qname, rdtype, rdclass,
311                                     tcp, source, raise_on_no_answer)
312
313     def getaliases(self, hostname):
314         """Return a list of all the aliases of a given hostname"""
315         if self._hosts:
316             aliases = self._hosts.getaliases(hostname)
317         else:
318             aliases = []
319         while True:
320             try:
321                 ans = self._resolver.query(hostname, dns.rdatatype.CNAME)
322             except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
323                 break
324             else:
325                 aliases.extend(str(rr.target) for rr in ans.rrset)
326                 hostname = ans[0].target
327         return aliases
328
329
330 resolver = ResolverProxy(hosts_resolver=HostsResolver())
331
332
333 def resolve(name, family=socket.AF_INET, raises=True):
334     """Resolve a name for a given family using the global resolver proxy
335
336     This method is called by the global getaddrinfo() function.
337
338     Return a dns.resolver.Answer instance.  If there is no answer it's
339     rrset will be emtpy.
340     """
341     if family == socket.AF_INET:
342         rdtype = dns.rdatatype.A
343     elif family == socket.AF_INET6:
344         rdtype = dns.rdatatype.AAAA
345     else:
346         raise socket.gaierror(socket.EAI_FAMILY,
347                               'Address family not supported')
348     try:
349         try:
350             return resolver.query(name, rdtype, raise_on_no_answer=raises)
351         except dns.resolver.NXDOMAIN:
352             if not raises:
353                 return HostsAnswer(dns.name.Name(name),
354                                    rdtype, dns.rdataclass.IN, None, False)
355             raise
356     except dns.exception.Timeout:
357         raise EAI_EAGAIN_ERROR
358     except dns.exception.DNSException:
359         raise EAI_NODATA_ERROR
360
361
362 def resolve_cname(host):
363     """Return the canonical name of a hostname"""
364     try:
365         ans = resolver.query(host, dns.rdatatype.CNAME)
366     except dns.resolver.NoAnswer:
367         return host
368     except dns.exception.Timeout:
369         raise EAI_EAGAIN_ERROR
370     except dns.exception.DNSException:
371         raise EAI_NODATA_ERROR
372     else:
373         return str(ans[0].target)
374
375
376 def getaliases(host):
377     """Return a list of for aliases for the given hostname
378
379     This method does translate the dnspython exceptions into
380     socket.gaierror exceptions.  If no aliases are available an empty
381     list will be returned.
382     """
383     try:
384         return resolver.getaliases(host)
385     except dns.exception.Timeout:
386         raise EAI_EAGAIN_ERROR
387     except dns.exception.DNSException:
388         raise EAI_NODATA_ERROR
389
390
391 def _getaddrinfo_lookup(host, family, flags):
392     """Resolve a hostname to a list of addresses
393
394     Helper function for getaddrinfo.
395     """
396     if flags & socket.AI_NUMERICHOST:
397         raise EAI_NONAME_ERROR
398     addrs = []
399     if family == socket.AF_UNSPEC:
400         for qfamily in [socket.AF_INET6, socket.AF_INET]:
401             answer = resolve(host, qfamily, False)
402             if answer.rrset:
403                 addrs.extend([rr.address for rr in answer.rrset])
404     elif family == socket.AF_INET6 and flags & socket.AI_V4MAPPED:
405         answer = resolve(host, socket.AF_INET6, False)
406         if answer.rrset:
407             addrs = [rr.address for rr in answer.rrset]
408         if not addrs or flags & socket.AI_ALL:
409             answer = resolve(host, socket.AF_INET, False)
410             if answer.rrset:
411                 addrs = ['::ffff:' + rr.address for rr in answer.rrset]
412     else:
413         answer = resolve(host, family, False)
414         if answer.rrset:
415             addrs = [rr.address for rr in answer.rrset]
416     return str(answer.qname), addrs
417
418
419 def getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0):
420     """Replacement for Python's socket.getaddrinfo
421
422     This does the A and AAAA lookups asynchronously after which it
423     calls the OS' getaddrinfo(3) using the AI_NUMERICHOST flag.  This
424     flag ensures getaddrinfo(3) does not use the network itself and
425     allows us to respect all the other arguments like the native OS.
426     """
427     if isinstance(host, six.string_types):
428         host = host.encode('idna').decode('ascii')
429     if host is not None and not is_ip_addr(host):
430         qname, addrs = _getaddrinfo_lookup(host, family, flags)
431     else:
432         qname = host
433         addrs = [host]
434     aiflags = (flags | socket.AI_NUMERICHOST) & (0xffff ^ socket.AI_CANONNAME)
435     res = []
436     err = None
437     for addr in addrs:
438         try:
439             ai = socket.getaddrinfo(addr, port, family,
440                                     socktype, proto, aiflags)
441         except socket.error as e:
442             if flags & socket.AI_ADDRCONFIG:
443                 err = e
444                 continue
445             raise
446         res.extend(ai)
447     if not res:
448         if err:
449             raise err
450         raise socket.gaierror(socket.EAI_NONAME, 'No address found')
451     if flags & socket.AI_CANONNAME:
452         if not is_ip_addr(qname):
453             qname = resolve_cname(qname).encode('ascii').decode('idna')
454         ai = res[0]
455         res[0] = (ai[0], ai[1], ai[2], qname, ai[4])
456     return res
457
458
459 def gethostbyname(hostname):
460     """Replacement for Python's socket.gethostbyname"""
461     if is_ipv4_addr(hostname):
462         return hostname
463     rrset = resolve(hostname)
464     return rrset[0].address
465
466
467 def gethostbyname_ex(hostname):
468     """Replacement for Python's socket.gethostbyname_ex"""
469     if is_ipv4_addr(hostname):
470         return (hostname, [], [hostname])
471     ans = resolve(hostname)
472     aliases = getaliases(hostname)
473     addrs = [rr.address for rr in ans.rrset]
474     qname = str(ans.qname)
475     if qname[-1] == '.':
476         qname = qname[:-1]
477     return (qname, aliases, addrs)
478
479
480 def getnameinfo(sockaddr, flags):
481     """Replacement for Python's socket.getnameinfo.
482
483     Currently only supports IPv4.
484     """
485     try:
486         host, port = sockaddr
487     except (ValueError, TypeError):
488         if not isinstance(sockaddr, tuple):
489             del sockaddr  # to pass a stdlib test that is
490             # hyper-careful about reference counts
491             raise TypeError('getnameinfo() argument 1 must be a tuple')
492         else:
493             # must be ipv6 sockaddr, pretending we don't know how to resolve it
494             raise EAI_NONAME_ERROR
495
496     if (flags & socket.NI_NAMEREQD) and (flags & socket.NI_NUMERICHOST):
497         # Conflicting flags.  Punt.
498         raise EAI_NONAME_ERROR
499
500     if is_ipv4_addr(host):
501         try:
502             rrset = resolver.query(
503                 dns.reversename.from_address(host), dns.rdatatype.PTR)
504             if len(rrset) > 1:
505                 raise socket.error('sockaddr resolved to multiple addresses')
506             host = rrset[0].target.to_text(omit_final_dot=True)
507         except dns.exception.Timeout:
508             if flags & socket.NI_NAMEREQD:
509                 raise EAI_EAGAIN_ERROR
510         except dns.exception.DNSException:
511             if flags & socket.NI_NAMEREQD:
512                 raise EAI_NONAME_ERROR
513     else:
514         try:
515             rrset = resolver.query(host)
516             if len(rrset) > 1:
517                 raise socket.error('sockaddr resolved to multiple addresses')
518             if flags & socket.NI_NUMERICHOST:
519                 host = rrset[0].address
520         except dns.exception.Timeout:
521             raise EAI_EAGAIN_ERROR
522         except dns.exception.DNSException:
523             raise socket.gaierror(
524                 (socket.EAI_NODATA, 'No address associated with hostname'))
525
526         if not (flags & socket.NI_NUMERICSERV):
527             proto = (flags & socket.NI_DGRAM) and 'udp' or 'tcp'
528             port = socket.getservbyport(port, proto)
529
530     return (host, port)
531
532
533 def _net_read(sock, count, expiration):
534     """coro friendly replacement for dns.query._net_write
535     Read the specified number of bytes from sock.  Keep trying until we
536     either get the desired amount, or we hit EOF.
537     A Timeout exception will be raised if the operation is not completed
538     by the expiration time.
539     """
540     s = ''
541     while count > 0:
542         try:
543             n = sock.recv(count)
544         except socket.timeout:
545             # Q: Do we also need to catch coro.CoroutineSocketWake and pass?
546             if expiration - time.time() <= 0.0:
547                 raise dns.exception.Timeout
548         if n == '':
549             raise EOFError
550         count = count - len(n)
551         s = s + n
552     return s
553
554
555 def _net_write(sock, data, expiration):
556     """coro friendly replacement for dns.query._net_write
557     Write the specified data to the socket.
558     A Timeout exception will be raised if the operation is not completed
559     by the expiration time.
560     """
561     current = 0
562     l = len(data)
563     while current < l:
564         try:
565             current += sock.send(data[current:])
566         except socket.timeout:
567             # Q: Do we also need to catch coro.CoroutineSocketWake and pass?
568             if expiration - time.time() <= 0.0:
569                 raise dns.exception.Timeout
570
571
572 def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53,
573         af=None, source=None, source_port=0, ignore_unexpected=False):
574     """coro friendly replacement for dns.query.udp
575     Return the response obtained after sending a query via UDP.
576
577     @param q: the query
578     @type q: dns.message.Message
579     @param where: where to send the message
580     @type where: string containing an IPv4 or IPv6 address
581     @param timeout: The number of seconds to wait before the query times out.
582     If None, the default, wait forever.
583     @type timeout: float
584     @param port: The port to which to send the message.  The default is 53.
585     @type port: int
586     @param af: the address family to use.  The default is None, which
587     causes the address family to use to be inferred from the form of of where.
588     If the inference attempt fails, AF_INET is used.
589     @type af: int
590     @rtype: dns.message.Message object
591     @param source: source address.  The default is the IPv4 wildcard address.
592     @type source: string
593     @param source_port: The port from which to send the message.
594     The default is 0.
595     @type source_port: int
596     @param ignore_unexpected: If True, ignore responses from unexpected
597     sources.  The default is False.
598     @type ignore_unexpected: bool"""
599
600     wire = q.to_wire()
601     if af is None:
602         try:
603             af = dns.inet.af_for_address(where)
604         except:
605             af = dns.inet.AF_INET
606     if af == dns.inet.AF_INET:
607         destination = (where, port)
608         if source is not None:
609             source = (source, source_port)
610     elif af == dns.inet.AF_INET6:
611         destination = (where, port, 0, 0)
612         if source is not None:
613             source = (source, source_port, 0, 0)
614
615     s = socket.socket(af, socket.SOCK_DGRAM)
616     s.settimeout(timeout)
617     try:
618         expiration = dns.query._compute_expiration(timeout)
619         if source is not None:
620             s.bind(source)
621         try:
622             s.sendto(wire, destination)
623         except socket.timeout:
624             # Q: Do we also need to catch coro.CoroutineSocketWake and pass?
625             if expiration - time.time() <= 0.0:
626                 raise dns.exception.Timeout
627         while 1:
628             try:
629                 (wire, from_address) = s.recvfrom(65535)
630             except socket.timeout:
631                 # Q: Do we also need to catch coro.CoroutineSocketWake and pass?
632                 if expiration - time.time() <= 0.0:
633                     raise dns.exception.Timeout
634             if from_address == destination:
635                 break
636             if not ignore_unexpected:
637                 raise dns.query.UnexpectedSource(
638                     'got a response from %s instead of %s'
639                     % (from_address, destination))
640     finally:
641         s.close()
642
643     r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac)
644     if not q.is_response(r):
645         raise dns.query.BadResponse()
646     return r
647
648
649 def tcp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53,
650         af=None, source=None, source_port=0):
651     """coro friendly replacement for dns.query.tcp
652     Return the response obtained after sending a query via TCP.
653
654     @param q: the query
655     @type q: dns.message.Message object
656     @param where: where to send the message
657     @type where: string containing an IPv4 or IPv6 address
658     @param timeout: The number of seconds to wait before the query times out.
659     If None, the default, wait forever.
660     @type timeout: float
661     @param port: The port to which to send the message.  The default is 53.
662     @type port: int
663     @param af: the address family to use.  The default is None, which
664     causes the address family to use to be inferred from the form of of where.
665     If the inference attempt fails, AF_INET is used.
666     @type af: int
667     @rtype: dns.message.Message object
668     @param source: source address.  The default is the IPv4 wildcard address.
669     @type source: string
670     @param source_port: The port from which to send the message.
671     The default is 0.
672     @type source_port: int"""
673
674     wire = q.to_wire()
675     if af is None:
676         try:
677             af = dns.inet.af_for_address(where)
678         except:
679             af = dns.inet.AF_INET
680     if af == dns.inet.AF_INET:
681         destination = (where, port)
682         if source is not None:
683             source = (source, source_port)
684     elif af == dns.inet.AF_INET6:
685         destination = (where, port, 0, 0)
686         if source is not None:
687             source = (source, source_port, 0, 0)
688     s = socket.socket(af, socket.SOCK_STREAM)
689     s.settimeout(timeout)
690     try:
691         expiration = dns.query._compute_expiration(timeout)
692         if source is not None:
693             s.bind(source)
694         try:
695             s.connect(destination)
696         except socket.timeout:
697             # Q: Do we also need to catch coro.CoroutineSocketWake and pass?
698             if expiration - time.time() <= 0.0:
699                 raise dns.exception.Timeout
700
701         l = len(wire)
702         # copying the wire into tcpmsg is inefficient, but lets us
703         # avoid writev() or doing a short write that would get pushed
704         # onto the net
705         tcpmsg = struct.pack("!H", l) + wire
706         _net_write(s, tcpmsg, expiration)
707         ldata = _net_read(s, 2, expiration)
708         (l,) = struct.unpack("!H", ldata)
709         wire = _net_read(s, l, expiration)
710     finally:
711         s.close()
712     r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac)
713     if not q.is_response(r):
714         raise dns.query.BadResponse()
715     return r
716
717
718 def reset():
719     resolver.clear()
720
721 # Install our coro-friendly replacements for the tcp and udp query methods.
722 dns.query.tcp = tcp
723 dns.query.udp = udp