1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 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   
 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   
 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   
 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   
170