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 __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
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
56
57 service_aliases={"xmpp-server": ("jabber-server","jabber")}
58
59
60
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
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
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
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
221