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