Package pyxmpp :: Module resolver |
[hide private]

# Source Code for Module pyxmpp.resolver

      1  #
      2  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net>
      3  #
      4  # This program is free software; you can redistribute it and/or modify
      5  # it under the terms of the GNU Lesser General Public License Version
      6  # 2.1 as published by the Free Software Foundation.
      7  #
      8  # This program is distributed in the hope that it will be useful,
      9  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  # GNU Lesser General Public License for more details.
     12  #
     13  # You should have received a copy of the GNU Lesser General Public
     14  # License along with this program; if not, write to the Free Software
     15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     16  #
     17
     18  """DNS resolever with SRV record support.
     19
     20  Normative reference:
     21    - `RFC 1035 <http://www.ietf.org/rfc/rfc1035.txt>`__
     22    - `RFC 2782 <http://www.ietf.org/rfc/rfc2782.txt>`__
     23  """
     24
     25  __docformat__="restructuredtext en"
     26
     27  import re
     28  import socket
     29  from socket import AF_UNSPEC, AF_INET, AF_INET6
     30  import dns.resolver
     31  import dns.name
     32  import dns.exception
     33  import random
     34  import logging
     35
     36  from .exceptions import DNSError, UnexpectedCNAMEError
     37
     38  logger = logging.getLogger("pyxmpp.resolver")
     39
     40  # check IPv6 support
     41  try:
     42      socket.socket(AF_INET6)
     43  except socket.error:
     44      default_address_family = AF_INET
     45  else:
     46      default_address_family = AF_UNSPEC
     47
     48 -def set_default_address_family(family):
     49      """Select default address family.
     50
     51      :Parameters:
     52        - `family`: `AF_INET` for IPv4, `AF_INET6` for IPv6 and `AF_UNSPEC` for
     53          dual stack."""
     54      global default_address_family
     55      default_address_family = family
     56
     57  service_aliases={"xmpp-server": ("jabber-server","jabber")}
     58
     59  # should match all valid IP addresses, but can pass some false-positives,
     60  # which are not valid domain names
     61  ipv4_re=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
     62  ipv6_re=re.compile(r"^[0-9a-f]{0,4}:[0-9a-f:]{0,29}:([0-9a-f]{0,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$")
     63
     64 -def shuffle_srv(records):
     65      """Randomly reorder SRV records using their weights.
     66
     67      :Parameters:
     68          - `records`: SRV records to shuffle.
     69      :Types:
     70          - `records`: sequence of `dns.rdtypes.IN.SRV`
     71
     72      :return: reordered records.
     73      :returntype: `list` of `dns.rdtypes.IN.SRV`"""
     74      if not records:
     75          return []
     76      ret=[]
     77      while len(records)>1:
     78          weight_sum=0
     79          for rr in records:
     80              weight_sum+=rr.weight+0.1
     81          thres=random.random()*weight_sum
     82          weight_sum=0
     83          for rr in records:
     84              weight_sum+=rr.weight+0.1
     85              if thres<weight_sum:
     86                  records.remove(rr)
     87                  ret.append(rr)
     88                  break
     89      ret.append(records[0])
     90      return ret
     91
     92 -def reorder_srv(records):
     93      """Reorder SRV records using their priorities and weights.
     94
     95      :Parameters:
     96          - `records`: SRV records to shuffle.
     97      :Types:
     98          - `records`: `list` of `dns.rdtypes.IN.SRV`
     99
    100      :return: reordered records.
    101      :returntype: `list` of `dns.rdtypes.IN.SRV`"""
    102      records=list(records)
    103      records.sort()
    104      ret=[]
    105      tmp=[]
    106      for rr in records:
    107          if not tmp or rr.priority==tmp[0].priority:
    108              tmp.append(rr)
    109              continue
    110          ret+=shuffle_srv(tmp)
    111          tmp = [rr]
    112      if tmp:
    113          ret+=shuffle_srv(tmp)
    114      return ret
    115
    116 -def resolve_srv(domain, service, proto="tcp"):
    117      """Resolve service domain to server name and port number using SRV records.
    118
    119      A built-in service alias table will be used to lookup also some obsolete
    120      record names.
    121
    122      :Parameters:
    123          - `domain`: domain name.
    124          - `service`: service name.
    125          - `proto`: protocol name.
    126      :Types:
    127          - `domain`: `unicode` or `str`
    128          - `service`: `unicode` or `str`
    129          - `proto`: `str`
    130
    131      :return: host names and port numbers for the service or None.
    132      :returntype: `list` of (`str`,`int`)"""
    133      names_to_try=[u"_%s._%s.%s" % (service,proto,domain)]
    134      if service_aliases.has_key(service):
    135          for a in service_aliases[service]:
    136              names_to_try.append(u"_%s._%s.%s" % (a,proto,domain))
    137      for name in names_to_try:
    138          if isinstance(name, unicode):
    139              name = name.encode("idna")
    140          try:
    141              r = dns.resolver.query(name, 'SRV')
    142          except dns.exception.DNSException, err:
    143              logger.warning("Could not resolve %r: %s", name,
    144                                                      err.__class__.__name__)
    145              continue
    146          if not r:
    147              continue
    148          return [(rr.target.to_text(),rr.port) for rr in reorder_srv(r)]
    149      return None
    150
    151 -def getaddrinfo(host, port, family = None,
    152                  socktype = socket.SOCK_STREAM, proto = 0, allow_cname = True):
    153      """Resolve host and port into addrinfo struct.
    154
    155      Does the same thing as socket.getaddrinfo, but using `dns.resolver`,
    156      so the cache content from the SRV query can be used.
    157
    158      :Parameters:
    159          - `host`: service domain name.
    160          - `port`: service port number or name.
    161          - `family`: address family (`AF_INET` for IPv4, `AF_INET6` for IPv6 or
    162            `AF_UNSPEC` for either, `None` for the auto-configured default).
    163          - `socktype`: socket type.
    164          - `proto`: protocol number or name.
    165          - `allow_cname`: when False CNAME responses are not allowed.
    166      :Types:
    167          - `host`: `unicode` or `str`
    168          - `port`: `int` or `str`
    169          - `family`: `int`
    170          - `socktype`: `int`
    171          - `proto`: `int` or `str`
    172          - `allow_cname`: `bool`
    173
    174      :return: list of (family, socktype, proto, canonname, sockaddr).
    175      :returntype: `list` of (`int`, `int`, `int`, `str`, (`str`, `int`))"""
    176      if family is None:
    177          family = default_address_family
    178      ret=[]
    179      if proto==0:
    180          proto=socket.getprotobyname("tcp")
    181      elif type(proto)!=int:
    182          proto=socket.getprotobyname(proto)
    183      if type(port)!=int:
    184          port=socket.getservbyname(port,proto)
    185      if family not in (AF_UNSPEC, AF_INET, AF_INET6):
    186          raise NotImplementedError, "Unsupported protocol family."
    187      if ipv4_re.match(host) and family in (AF_UNSPEC, AF_INET):
    188          return [(AF_INET, socktype, proto, host, (host, port))]
    189      if ipv6_re.match(host) and family in (AF_UNSPEC, AF_INET6):
    190          return [(AF_INET6, socktype, proto, host, (host, port))]
    191      if isinstance(host, unicode):
    192          host = host.encode("idna")
    193      rtypes = []
    194      if family in (AF_UNSPEC, AF_INET6):
    195          rtypes.append(("AAAA", AF_INET6))
    196      if family in (AF_UNSPEC, AF_INET):
    197          rtypes.append(("A", AF_INET))
    198      exception = None
    199      for rtype, rfamily in rtypes:
    200          try:
    201              try:
    202                  r=dns.resolver.query(host, rtype)
    203              except dns.exception.DNSException:
    204                  r=dns.resolver.query(host+".", rtype)
    205          except dns.exception.DNSException, err:
    206              exception = err
    207              continue
    208          if not allow_cname and r.rrset.name != dns.name.from_text(host):
    209              raise UnexpectedCNAMEError, (
    210                      "Unexpected CNAME record found for %r" % (host,))
    211          if r:
    212              for rr in r:
    213                  ret.append((rfamily, socktype, proto, r.rrset.name,
    214                                                          (rr.to_text(),port)))
    215      if not ret and exception:
    216          raise DNSError, "Could not resolve %r: %s" % (host,
    217                                                  exception.__class__.__name__)
    218      return ret
    219
    220  # vi: sts=4 et sw=4
    221