Package pyxmpp :: Module resolver |
[hide private]

# Source Code for Module pyxmpp.resolver

      1  #
      2  # (C) Copyright 2003-2004 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  __revision__="$Id: resolver.py 503 2005-01-03 21:59:36Z jajcus $"
     26  __docformat__="restructuredtext en"
     27
     28  import re
     29  import socket
     30  import dns.resolver
     31  import dns.name
     32  import dns.exception
     33  import random
     34  from encodings import idna
     35
     36  service_aliases={"xmpp-server": ("jabber-server","jabber")}
     37  ip_re=re.compile(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")
     38
     39 -def shuffle_srv(records):
     40      """Randomly reorder SRV records using their weights.
     41
     42      :Parameters:
     43          - `records`: SRV records to shuffle.
     44      :Types:
     45          - `records`: sequence of `dns.rdtypes.IN.SRV`
     46
     47      :return: reordered records.
     48      :returntype: `list` of `dns.rdtypes.IN.SRV`"""
     49      if not records:
     50          return []
     51      ret=[]
     52      while len(records)>1:
     53          weight_sum=0
     54          for rr in records:
     55              weight_sum+=rr.weight+0.1
     56          thres=random.random()*weight_sum
     57          weight_sum=0
     58          for rr in records:
     59              weight_sum+=rr.weight+0.1
     60              if thres<weight_sum:
     61                  records.remove(rr)
     62                  ret.append(rr)
     63                  break
     64      ret.append(records[0])
     65      return ret
     66
     67 -def reorder_srv(records):
     68      """Reorder SRV records using their priorities and weights.
     69
     70      :Parameters:
     71          - `records`: SRV records to shuffle.
     72      :Types:
     73          - `records`: `list` of `dns.rdtypes.IN.SRV`
     74
     75      :return: reordered records.
     76      :returntype: `list` of `dns.rdtypes.IN.SRV`"""
     77      records=list(records)
     78      records.sort()
     79      ret=[]
     80      tmp=[]
     81      for rr in records:
     82          if not tmp or rr.priority==tmp[0].priority:
     83              tmp.append(rr)
     84              continue
     85          ret+=shuffle_srv(tmp)
     86      if tmp:
     87          ret+=shuffle_srv(tmp)
     88      return ret
     89
     90 -def resolve_srv(domain,service,proto="tcp"):
     91      """Resolve service domain to server name and port number using SRV records.
     92
     93      A built-in service alias table will be used to lookup also some obsolete
     94      record names.
     95
     96      :Parameters:
     97          - `domain`: domain name.
     98          - `service`: service name.
     99          - `proto`: protocol name.
    100      :Types:
    101          - `domain`: `unicode` or `str`
    102          - `service`: `unicode` or `str`
    103          - `proto`: `str`
    104
    105      :return: host names and port numbers for the service or None.
    106      :returntype: `list` of (`str`,`int`)"""
    107      names_to_try=[u"_%s._%s.%s" % (service,proto,domain)]
    108      if service_aliases.has_key(service):
    109          for a in service_aliases[service]:
    110              names_to_try.append(u"_%s._%s.%s" % (a,proto,domain))
    111      for name in names_to_try:
    112          name=idna.ToASCII(name)
    113          try:
    114              r=dns.resolver.query(name, 'SRV')
    115          except dns.exception.DNSException:
    116              continue
    117          if not r:
    118              continue
    119          return [(rr.target.to_text(),rr.port) for rr in reorder_srv(r)]
    120      return None
    121
    122 -def getaddrinfo(host,port,family=0,socktype=socket.SOCK_STREAM,proto=0,allow_cname=True):
    123      """Resolve host and port into addrinfo struct.
    124
    125      Does the same thing as socket.getaddrinfo, but using `pyxmpp.resolver`. This
    126      makes it possible to reuse data (A records from the additional section of
    127      DNS reply) returned with SRV records lookup done using this module.
    128
    129      :Parameters:
    130          - `host`: service domain name.
    131          - `port`: service port number or name.
    132          - `family`: address family.
    133          - `socktype`: socket type.
    134          - `proto`: protocol number or name.
    135          - `allow_cname`: when False CNAME responses are not allowed.
    136      :Types:
    137          - `host`: `unicode` or `str`
    138          - `port`: `int` or `str`
    139          - `family`: `int`
    140          - `socktype`: `int`
    141          - `proto`: `int` or `str`
    142          - `allow_cname`: `bool`
    143
    144      :return: list of (family, socktype, proto, canonname, sockaddr).
    145      :returntype: `list` of (`int`, `int`, `int`, `str`, (`str`, `int`))"""
    146      ret=[]
    147      if proto==0:
    148          proto=socket.getprotobyname("tcp")
    149      elif type(proto)!=int:
    150          proto=socket.getprotobyname(proto)
    151      if type(port)!=int:
    152          port=socket.getservbyname(port,proto)
    153      if family not in (0,socket.AF_INET):
    154          raise NotImplementedError,"Protocol family other than AF_INET not supported, yet"
    155      if ip_re.match(host):
    156          return [(socket.AF_INET,socktype,proto,host,(host,port))]
    157      host=idna.ToASCII(host)
    158      try:
    159          r=dns.resolver.query(host, 'A')
    160      except dns.exception.DNSException:
    161          r=dns.resolver.query(host+".", 'A')
    162      if not allow_cname and r.rrset.name!=dns.name.from_text(host):
    163          raise ValueError,"Unexpected CNAME record found for %s" % (host,)
    164      if r:
    165          for rr in r:
    166              ret.append((socket.AF_INET,socktype,proto,r.rrset.name,(rr.to_text(),port)))
    167      return ret
    168
    169  # vi: sts=4 et sw=4
    170