1 # Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc.
3 # Permission to use, copy, modify, and distribute this software and its
4 # documentation for any purpose with or without fee is hereby granted,
5 # provided that the above copyright notice and this permission notice
6 # appear in all copies.
8 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
9 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
11 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
14 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 """Talk to a DNS server."""
18 from __future__ import generators
34 class UnexpectedSource(dns.exception.DNSException):
35 """Raised if a query response comes from an unexpected address or port."""
38 class BadResponse(dns.exception.FormError):
39 """Raised if a query response does not respond to the question asked."""
42 def _compute_expiration(timeout):
46 return time.time() + timeout
48 def _wait_for(ir, iw, ix, expiration):
51 if expiration is None:
54 timeout = expiration - time.time()
56 raise dns.exception.Timeout
59 (r, w, x) = select.select(ir, iw, ix)
61 (r, w, x) = select.select(ir, iw, ix, timeout)
62 except select.error, e:
63 if e.args[0] != errno.EINTR:
66 if len(r) == 0 and len(w) == 0 and len(x) == 0:
67 raise dns.exception.Timeout
69 def _wait_for_readable(s, expiration):
70 _wait_for([s], [], [s], expiration)
72 def _wait_for_writable(s, expiration):
73 _wait_for([], [s], [s], expiration)
75 def _addresses_equal(af, a1, a2):
76 # Convert the first value of the tuple, which is a textual format
77 # address into binary form, so that we are not confused by different
78 # textual representations of the same address
79 n1 = dns.inet.inet_pton(af, a1[0])
80 n2 = dns.inet.inet_pton(af, a2[0])
81 return n1 == n2 and a1[1:] == a2[1:]
83 def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
84 ignore_unexpected=False, one_rr_per_rrset=False):
85 """Return the response obtained after sending a query via UDP.
88 @type q: dns.message.Message
89 @param where: where to send the message
90 @type where: string containing an IPv4 or IPv6 address
91 @param timeout: The number of seconds to wait before the query times out.
92 If None, the default, wait forever.
94 @param port: The port to which to send the message. The default is 53.
96 @param af: the address family to use. The default is None, which
97 causes the address family to use to be inferred from the form of of where.
98 If the inference attempt fails, AF_INET is used.
100 @rtype: dns.message.Message object
101 @param source: source address. The default is the IPv4 wildcard address.
103 @param source_port: The port from which to send the message.
105 @type source_port: int
106 @param ignore_unexpected: If True, ignore responses from unexpected
107 sources. The default is False.
108 @type ignore_unexpected: bool
109 @param one_rr_per_rrset: Put each RR into its own RRset
110 @type one_rr_per_rrset: bool
116 af = dns.inet.af_for_address(where)
118 af = dns.inet.AF_INET
119 if af == dns.inet.AF_INET:
120 destination = (where, port)
121 if source is not None:
122 source = (source, source_port)
123 elif af == dns.inet.AF_INET6:
124 destination = (where, port, 0, 0)
125 if source is not None:
126 source = (source, source_port, 0, 0)
127 s = socket.socket(af, socket.SOCK_DGRAM, 0)
129 expiration = _compute_expiration(timeout)
131 if source is not None:
133 _wait_for_writable(s, expiration)
134 s.sendto(wire, destination)
136 _wait_for_readable(s, expiration)
137 (wire, from_address) = s.recvfrom(65535)
138 if _addresses_equal(af, from_address, destination) or \
139 (dns.inet.is_multicast(where) and \
140 from_address[1:] == destination[1:]):
142 if not ignore_unexpected:
143 raise UnexpectedSource('got a response from '
144 '%s instead of %s' % (from_address,
148 r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac,
149 one_rr_per_rrset=one_rr_per_rrset)
150 if not q.is_response(r):
154 def _net_read(sock, count, expiration):
155 """Read the specified number of bytes from sock. Keep trying until we
156 either get the desired amount, or we hit EOF.
157 A Timeout exception will be raised if the operation is not completed
158 by the expiration time.
162 _wait_for_readable(sock, expiration)
166 count = count - len(n)
170 def _net_write(sock, data, expiration):
171 """Write the specified data to the socket.
172 A Timeout exception will be raised if the operation is not completed
173 by the expiration time.
178 _wait_for_writable(sock, expiration)
179 current += sock.send(data[current:])
181 def _connect(s, address):
185 (ty, v) = sys.exc_info()[:2]
186 if v[0] != errno.EINPROGRESS and \
187 v[0] != errno.EWOULDBLOCK and \
188 v[0] != errno.EALREADY:
191 def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
192 one_rr_per_rrset=False):
193 """Return the response obtained after sending a query via TCP.
196 @type q: dns.message.Message object
197 @param where: where to send the message
198 @type where: string containing an IPv4 or IPv6 address
199 @param timeout: The number of seconds to wait before the query times out.
200 If None, the default, wait forever.
202 @param port: The port to which to send the message. The default is 53.
204 @param af: the address family to use. The default is None, which
205 causes the address family to use to be inferred from the form of of where.
206 If the inference attempt fails, AF_INET is used.
208 @rtype: dns.message.Message object
209 @param source: source address. The default is the IPv4 wildcard address.
211 @param source_port: The port from which to send the message.
213 @type source_port: int
214 @param one_rr_per_rrset: Put each RR into its own RRset
215 @type one_rr_per_rrset: bool
221 af = dns.inet.af_for_address(where)
223 af = dns.inet.AF_INET
224 if af == dns.inet.AF_INET:
225 destination = (where, port)
226 if source is not None:
227 source = (source, source_port)
228 elif af == dns.inet.AF_INET6:
229 destination = (where, port, 0, 0)
230 if source is not None:
231 source = (source, source_port, 0, 0)
232 s = socket.socket(af, socket.SOCK_STREAM, 0)
234 expiration = _compute_expiration(timeout)
236 if source is not None:
238 _connect(s, destination)
242 # copying the wire into tcpmsg is inefficient, but lets us
243 # avoid writev() or doing a short write that would get pushed
245 tcpmsg = struct.pack("!H", l) + wire
246 _net_write(s, tcpmsg, expiration)
247 ldata = _net_read(s, 2, expiration)
248 (l,) = struct.unpack("!H", ldata)
249 wire = _net_read(s, l, expiration)
252 r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac,
253 one_rr_per_rrset=one_rr_per_rrset)
254 if not q.is_response(r):
258 def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
259 timeout=None, port=53, keyring=None, keyname=None, relativize=True,
260 af=None, lifetime=None, source=None, source_port=0, serial=0,
261 use_udp=False, keyalgorithm=dns.tsig.default_algorithm):
262 """Return a generator for the responses to a zone transfer.
264 @param where: where to send the message
265 @type where: string containing an IPv4 or IPv6 address
266 @param zone: The name of the zone to transfer
267 @type zone: dns.name.Name object or string
268 @param rdtype: The type of zone transfer. The default is
270 @type rdtype: int or string
271 @param rdclass: The class of the zone transfer. The default is
273 @type rdclass: int or string
274 @param timeout: The number of seconds to wait for each response message.
275 If None, the default, wait forever.
277 @param port: The port to which to send the message. The default is 53.
279 @param keyring: The TSIG keyring to use
281 @param keyname: The name of the TSIG key to use
282 @type keyname: dns.name.Name object or string
283 @param relativize: If True, all names in the zone will be relativized to
284 the zone origin. It is essential that the relativize setting matches
285 the one specified to dns.zone.from_xfr().
286 @type relativize: bool
287 @param af: the address family to use. The default is None, which
288 causes the address family to use to be inferred from the form of of where.
289 If the inference attempt fails, AF_INET is used.
291 @param lifetime: The total number of seconds to spend doing the transfer.
292 If None, the default, then there is no limit on the time the transfer may
294 @type lifetime: float
295 @rtype: generator of dns.message.Message objects.
296 @param source: source address. The default is the IPv4 wildcard address.
298 @param source_port: The port from which to send the message.
300 @type source_port: int
301 @param serial: The SOA serial number to use as the base for an IXFR diff
302 sequence (only meaningful if rdtype == dns.rdatatype.IXFR).
304 @param use_udp: Use UDP (only meaningful for IXFR)
306 @param keyalgorithm: The TSIG algorithm to use; defaults to
307 dns.tsig.default_algorithm
308 @type keyalgorithm: string
311 if isinstance(zone, (str, unicode)):
312 zone = dns.name.from_text(zone)
313 if isinstance(rdtype, str):
314 rdtype = dns.rdatatype.from_text(rdtype)
315 q = dns.message.make_query(zone, rdtype, rdclass)
316 if rdtype == dns.rdatatype.IXFR:
317 rrset = dns.rrset.from_text(zone, 0, 'IN', 'SOA',
318 '. . %u 0 0 0 0' % serial)
319 q.authority.append(rrset)
320 if not keyring is None:
321 q.use_tsig(keyring, keyname, algorithm=keyalgorithm)
325 af = dns.inet.af_for_address(where)
327 af = dns.inet.AF_INET
328 if af == dns.inet.AF_INET:
329 destination = (where, port)
330 if source is not None:
331 source = (source, source_port)
332 elif af == dns.inet.AF_INET6:
333 destination = (where, port, 0, 0)
334 if source is not None:
335 source = (source, source_port, 0, 0)
337 if rdtype != dns.rdatatype.IXFR:
338 raise ValueError('cannot do a UDP AXFR')
339 s = socket.socket(af, socket.SOCK_DGRAM, 0)
341 s = socket.socket(af, socket.SOCK_STREAM, 0)
343 if source is not None:
345 expiration = _compute_expiration(lifetime)
346 _connect(s, destination)
349 _wait_for_writable(s, expiration)
352 tcpmsg = struct.pack("!H", l) + wire
353 _net_write(s, tcpmsg, expiration)
359 oname = dns.name.empty
366 mexpiration = _compute_expiration(timeout)
367 if mexpiration is None or mexpiration > expiration:
368 mexpiration = expiration
370 _wait_for_readable(s, expiration)
371 (wire, from_address) = s.recvfrom(65535)
373 ldata = _net_read(s, 2, mexpiration)
374 (l,) = struct.unpack("!H", ldata)
375 wire = _net_read(s, l, mexpiration)
376 r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac,
377 xfr=True, origin=origin, tsig_ctx=tsig_ctx,
378 multi=True, first=first,
379 one_rr_per_rrset=(rdtype==dns.rdatatype.IXFR))
380 tsig_ctx = r.tsig_ctx
384 expecting_SOA = False
385 if soa_rrset is None:
386 if not r.answer or r.answer[0].name != oname:
387 raise dns.exception.FormError
389 if rrset.rdtype != dns.rdatatype.SOA:
390 raise dns.exception.FormError("first RRset is not an SOA")
392 soa_rrset = rrset.copy()
393 if rdtype == dns.rdatatype.IXFR:
394 if soa_rrset[0].serial == serial:
396 # We're already up-to-date.
402 # Process SOAs in the answer section (other than the initial
403 # SOA in the first message).
405 for rrset in r.answer[answer_index:]:
407 raise dns.exception.FormError("answers after final SOA")
408 if rrset.rdtype == dns.rdatatype.SOA and rrset.name == oname:
410 if rrset[0].serial != serial:
411 raise dns.exception.FormError("IXFR base serial mismatch")
412 expecting_SOA = False
413 elif rdtype == dns.rdatatype.IXFR:
414 delete_mode = not delete_mode
415 if rrset == soa_rrset and not delete_mode:
419 # We made an IXFR request and are expecting another
420 # SOA RR, but saw something else, so this must be an
423 rdtype = dns.rdatatype.AXFR
424 expecting_SOA = False
425 if done and q.keyring and not r.had_tsig:
426 raise dns.exception.FormError("missing TSIG")