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