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 _poll_for(fd
, readable
, writable
, error
, timeout
):
50 @param fd: File descriptor (int).
51 @param readable: Whether to wait for readability (bool).
52 @param writable: Whether to wait for writability (bool).
53 @param expiration: Deadline timeout (expiration time, in seconds (float)).
55 @return True on success, False on timeout
59 event_mask |
= select
.POLLIN
61 event_mask |
= select
.POLLOUT
63 event_mask |
= select
.POLLERR
65 pollable
= select
.poll()
66 pollable
.register(fd
, event_mask
)
69 event_list
= pollable
.poll(long(timeout
* 1000))
71 event_list
= pollable
.poll()
73 return bool(event_list
)
75 def _select_for(fd
, readable
, writable
, error
, timeout
):
77 @param fd: File descriptor (int).
78 @param readable: Whether to wait for readability (bool).
79 @param writable: Whether to wait for writability (bool).
80 @param expiration: Deadline timeout (expiration time, in seconds (float)).
82 @return True on success, False on timeout
84 rset
, wset
, xset
= [], [], []
94 (rcount
, wcount
, xcount
) = select
.select(rset
, wset
, xset
)
96 (rcount
, wcount
, xcount
) = select
.select(rset
, wset
, xset
, timeout
)
98 return bool((rcount
or wcount
or xcount
))
100 def _wait_for(fd
, readable
, writable
, error
, expiration
):
103 if expiration
is None:
106 timeout
= expiration
- time
.time()
108 raise dns
.exception
.Timeout
110 if not _polling_backend(fd
, readable
, writable
, error
, timeout
):
111 raise dns
.exception
.Timeout
112 except select
.error
, e
:
113 if e
.args
[0] != errno
.EINTR
:
117 def _set_polling_backend(fn
):
119 Internal API. Do not use.
121 global _polling_backend
123 _polling_backend
= fn
125 if hasattr(select
, 'poll'):
126 # Prefer poll() on platforms that support it because it has no
127 # limits on the maximum value of a file descriptor (plus it will
128 # be more efficient for high values).
129 _polling_backend
= _poll_for
131 _polling_backend
= _select_for
133 def _wait_for_readable(s
, expiration
):
134 _wait_for(s
, True, False, True, expiration
)
136 def _wait_for_writable(s
, expiration
):
137 _wait_for(s
, False, True, True, expiration
)
139 def _addresses_equal(af
, a1
, a2
):
140 # Convert the first value of the tuple, which is a textual format
141 # address into binary form, so that we are not confused by different
142 # textual representations of the same address
143 n1
= dns
.inet
.inet_pton(af
, a1
[0])
144 n2
= dns
.inet
.inet_pton(af
, a2
[0])
145 return n1
== n2
and a1
[1:] == a2
[1:]
147 def udp(q
, where
, timeout
=None, port
=53, af
=None, source
=None, source_port
=0,
148 ignore_unexpected
=False, one_rr_per_rrset
=False):
149 """Return the response obtained after sending a query via UDP.
152 @type q: dns.message.Message
153 @param where: where to send the message
154 @type where: string containing an IPv4 or IPv6 address
155 @param timeout: The number of seconds to wait before the query times out.
156 If None, the default, wait forever.
158 @param port: The port to which to send the message. The default is 53.
160 @param af: the address family to use. The default is None, which
161 causes the address family to use to be inferred from the form of of where.
162 If the inference attempt fails, AF_INET is used.
164 @rtype: dns.message.Message object
165 @param source: source address. The default is the IPv4 wildcard address.
167 @param source_port: The port from which to send the message.
169 @type source_port: int
170 @param ignore_unexpected: If True, ignore responses from unexpected
171 sources. The default is False.
172 @type ignore_unexpected: bool
173 @param one_rr_per_rrset: Put each RR into its own RRset
174 @type one_rr_per_rrset: bool
180 af
= dns
.inet
.af_for_address(where
)
182 af
= dns
.inet
.AF_INET
183 if af
== dns
.inet
.AF_INET
:
184 destination
= (where
, port
)
185 if source
is not None:
186 source
= (source
, source_port
)
187 elif af
== dns
.inet
.AF_INET6
:
188 destination
= (where
, port
, 0, 0)
189 if source
is not None:
190 source
= (source
, source_port
, 0, 0)
191 s
= socket
.socket(af
, socket
.SOCK_DGRAM
, 0)
193 expiration
= _compute_expiration(timeout
)
195 if source
is not None:
197 _wait_for_writable(s
, expiration
)
198 s
.sendto(wire
, destination
)
200 _wait_for_readable(s
, expiration
)
201 (wire
, from_address
) = s
.recvfrom(65535)
202 if _addresses_equal(af
, from_address
, destination
) or \
203 (dns
.inet
.is_multicast(where
) and \
204 from_address
[1:] == destination
[1:]):
206 if not ignore_unexpected
:
207 raise UnexpectedSource('got a response from '
208 '%s instead of %s' % (from_address
,
212 r
= dns
.message
.from_wire(wire
, keyring
=q
.keyring
, request_mac
=q
.mac
,
213 one_rr_per_rrset
=one_rr_per_rrset
)
214 if not q
.is_response(r
):
218 def _net_read(sock
, count
, expiration
):
219 """Read the specified number of bytes from sock. Keep trying until we
220 either get the desired amount, or we hit EOF.
221 A Timeout exception will be raised if the operation is not completed
222 by the expiration time.
226 _wait_for_readable(sock
, expiration
)
230 count
= count
- len(n
)
234 def _net_write(sock
, data
, expiration
):
235 """Write the specified data to the socket.
236 A Timeout exception will be raised if the operation is not completed
237 by the expiration time.
242 _wait_for_writable(sock
, expiration
)
243 current
+= sock
.send(data
[current
:])
245 def _connect(s
, address
):
249 (ty
, v
) = sys
.exc_info()[:2]
250 if v
[0] != errno
.EINPROGRESS
and \
251 v
[0] != errno
.EWOULDBLOCK
and \
252 v
[0] != errno
.EALREADY
:
255 def tcp(q
, where
, timeout
=None, port
=53, af
=None, source
=None, source_port
=0,
256 one_rr_per_rrset
=False):
257 """Return the response obtained after sending a query via TCP.
260 @type q: dns.message.Message object
261 @param where: where to send the message
262 @type where: string containing an IPv4 or IPv6 address
263 @param timeout: The number of seconds to wait before the query times out.
264 If None, the default, wait forever.
266 @param port: The port to which to send the message. The default is 53.
268 @param af: the address family to use. The default is None, which
269 causes the address family to use to be inferred from the form of of where.
270 If the inference attempt fails, AF_INET is used.
272 @rtype: dns.message.Message object
273 @param source: source address. The default is the IPv4 wildcard address.
275 @param source_port: The port from which to send the message.
277 @type source_port: int
278 @param one_rr_per_rrset: Put each RR into its own RRset
279 @type one_rr_per_rrset: bool
285 af
= dns
.inet
.af_for_address(where
)
287 af
= dns
.inet
.AF_INET
288 if af
== dns
.inet
.AF_INET
:
289 destination
= (where
, port
)
290 if source
is not None:
291 source
= (source
, source_port
)
292 elif af
== dns
.inet
.AF_INET6
:
293 destination
= (where
, port
, 0, 0)
294 if source
is not None:
295 source
= (source
, source_port
, 0, 0)
296 s
= socket
.socket(af
, socket
.SOCK_STREAM
, 0)
298 expiration
= _compute_expiration(timeout
)
300 if source
is not None:
302 _connect(s
, destination
)
306 # copying the wire into tcpmsg is inefficient, but lets us
307 # avoid writev() or doing a short write that would get pushed
309 tcpmsg
= struct
.pack("!H", l
) + wire
310 _net_write(s
, tcpmsg
, expiration
)
311 ldata
= _net_read(s
, 2, expiration
)
312 (l
,) = struct
.unpack("!H", ldata
)
313 wire
= _net_read(s
, l
, expiration
)
316 r
= dns
.message
.from_wire(wire
, keyring
=q
.keyring
, request_mac
=q
.mac
,
317 one_rr_per_rrset
=one_rr_per_rrset
)
318 if not q
.is_response(r
):
322 def xfr(where
, zone
, rdtype
=dns
.rdatatype
.AXFR
, rdclass
=dns
.rdataclass
.IN
,
323 timeout
=None, port
=53, keyring
=None, keyname
=None, relativize
=True,
324 af
=None, lifetime
=None, source
=None, source_port
=0, serial
=0,
325 use_udp
=False, keyalgorithm
=dns
.tsig
.default_algorithm
):
326 """Return a generator for the responses to a zone transfer.
328 @param where: where to send the message
329 @type where: string containing an IPv4 or IPv6 address
330 @param zone: The name of the zone to transfer
331 @type zone: dns.name.Name object or string
332 @param rdtype: The type of zone transfer. The default is
334 @type rdtype: int or string
335 @param rdclass: The class of the zone transfer. The default is
337 @type rdclass: int or string
338 @param timeout: The number of seconds to wait for each response message.
339 If None, the default, wait forever.
341 @param port: The port to which to send the message. The default is 53.
343 @param keyring: The TSIG keyring to use
345 @param keyname: The name of the TSIG key to use
346 @type keyname: dns.name.Name object or string
347 @param relativize: If True, all names in the zone will be relativized to
348 the zone origin. It is essential that the relativize setting matches
349 the one specified to dns.zone.from_xfr().
350 @type relativize: bool
351 @param af: the address family to use. The default is None, which
352 causes the address family to use to be inferred from the form of of where.
353 If the inference attempt fails, AF_INET is used.
355 @param lifetime: The total number of seconds to spend doing the transfer.
356 If None, the default, then there is no limit on the time the transfer may
358 @type lifetime: float
359 @rtype: generator of dns.message.Message objects.
360 @param source: source address. The default is the IPv4 wildcard address.
362 @param source_port: The port from which to send the message.
364 @type source_port: int
365 @param serial: The SOA serial number to use as the base for an IXFR diff
366 sequence (only meaningful if rdtype == dns.rdatatype.IXFR).
368 @param use_udp: Use UDP (only meaningful for IXFR)
370 @param keyalgorithm: The TSIG algorithm to use; defaults to
371 dns.tsig.default_algorithm
372 @type keyalgorithm: string
375 if isinstance(zone
, (str, unicode)):
376 zone
= dns
.name
.from_text(zone
)
377 if isinstance(rdtype
, (str, unicode)):
378 rdtype
= dns
.rdatatype
.from_text(rdtype
)
379 q
= dns
.message
.make_query(zone
, rdtype
, rdclass
)
380 if rdtype
== dns
.rdatatype
.IXFR
:
381 rrset
= dns
.rrset
.from_text(zone
, 0, 'IN', 'SOA',
382 '. . %u 0 0 0 0' % serial
)
383 q
.authority
.append(rrset
)
384 if not keyring
is None:
385 q
.use_tsig(keyring
, keyname
, algorithm
=keyalgorithm
)
389 af
= dns
.inet
.af_for_address(where
)
391 af
= dns
.inet
.AF_INET
392 if af
== dns
.inet
.AF_INET
:
393 destination
= (where
, port
)
394 if source
is not None:
395 source
= (source
, source_port
)
396 elif af
== dns
.inet
.AF_INET6
:
397 destination
= (where
, port
, 0, 0)
398 if source
is not None:
399 source
= (source
, source_port
, 0, 0)
401 if rdtype
!= dns
.rdatatype
.IXFR
:
402 raise ValueError('cannot do a UDP AXFR')
403 s
= socket
.socket(af
, socket
.SOCK_DGRAM
, 0)
405 s
= socket
.socket(af
, socket
.SOCK_STREAM
, 0)
407 if source
is not None:
409 expiration
= _compute_expiration(lifetime
)
410 _connect(s
, destination
)
413 _wait_for_writable(s
, expiration
)
416 tcpmsg
= struct
.pack("!H", l
) + wire
417 _net_write(s
, tcpmsg
, expiration
)
423 oname
= dns
.name
.empty
430 mexpiration
= _compute_expiration(timeout
)
431 if mexpiration
is None or mexpiration
> expiration
:
432 mexpiration
= expiration
434 _wait_for_readable(s
, expiration
)
435 (wire
, from_address
) = s
.recvfrom(65535)
437 ldata
= _net_read(s
, 2, mexpiration
)
438 (l
,) = struct
.unpack("!H", ldata
)
439 wire
= _net_read(s
, l
, mexpiration
)
440 r
= dns
.message
.from_wire(wire
, keyring
=q
.keyring
, request_mac
=q
.mac
,
441 xfr
=True, origin
=origin
, tsig_ctx
=tsig_ctx
,
442 multi
=True, first
=first
,
443 one_rr_per_rrset
=(rdtype
==dns
.rdatatype
.IXFR
))
444 tsig_ctx
= r
.tsig_ctx
448 expecting_SOA
= False
449 if soa_rrset
is None:
450 if not r
.answer
or r
.answer
[0].name
!= oname
:
451 raise dns
.exception
.FormError
453 if rrset
.rdtype
!= dns
.rdatatype
.SOA
:
454 raise dns
.exception
.FormError("first RRset is not an SOA")
456 soa_rrset
= rrset
.copy()
457 if rdtype
== dns
.rdatatype
.IXFR
:
458 if soa_rrset
[0].serial
== serial
:
460 # We're already up-to-date.
466 # Process SOAs in the answer section (other than the initial
467 # SOA in the first message).
469 for rrset
in r
.answer
[answer_index
:]:
471 raise dns
.exception
.FormError("answers after final SOA")
472 if rrset
.rdtype
== dns
.rdatatype
.SOA
and rrset
.name
== oname
:
474 if rrset
[0].serial
!= serial
:
475 raise dns
.exception
.FormError("IXFR base serial mismatch")
476 expecting_SOA
= False
477 elif rdtype
== dns
.rdatatype
.IXFR
:
478 delete_mode
= not delete_mode
479 if rrset
== soa_rrset
and not delete_mode
:
483 # We made an IXFR request and are expecting another
484 # SOA RR, but saw something else, so this must be an
487 rdtype
= dns
.rdatatype
.AXFR
488 expecting_SOA
= False
489 if done
and q
.keyring
and not r
.had_tsig
:
490 raise dns
.exception
.FormError("missing TSIG")