Package dns :: Module query
[hide private]
[frames] | no frames]

Source Code for Module dns.query

  1  # Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc. 
  2  # 
  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. 
  7  # 
  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. 
 15   
 16  """Talk to a DNS server.""" 
 17   
 18  from __future__ import generators 
 19   
 20  import errno 
 21  import select 
 22  import socket 
 23  import struct 
 24  import sys 
 25  import time 
 26   
 27  import dns.exception 
 28  import dns.inet 
 29  import dns.name 
 30  import dns.message 
 31  import dns.rdataclass 
 32  import dns.rdatatype 
 33   
34 -class UnexpectedSource(dns.exception.DNSException):
35 """Raised if a query response comes from an unexpected address or port.""" 36 pass
37
38 -class BadResponse(dns.exception.FormError):
39 """Raised if a query response does not respond to the question asked.""" 40 pass
41
42 -def _compute_expiration(timeout):
43 if timeout is None: 44 return None 45 else: 46 return time.time() + timeout
47
48 -def _poll_for(fd, readable, writable, error, timeout):
49 """ 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)). 54 55 @return True on success, False on timeout 56 """ 57 event_mask = 0 58 if readable: 59 event_mask |= select.POLLIN 60 if writable: 61 event_mask |= select.POLLOUT 62 if error: 63 event_mask |= select.POLLERR 64 65 pollable = select.poll() 66 pollable.register(fd, event_mask) 67 68 if timeout: 69 event_list = pollable.poll(long(timeout * 1000)) 70 else: 71 event_list = pollable.poll() 72 73 return bool(event_list)
74
75 -def _select_for(fd, readable, writable, error, timeout):
76 """ 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)). 81 82 @return True on success, False on timeout 83 """ 84 rset, wset, xset = [], [], [] 85 86 if readable: 87 rset = [fd] 88 if writable: 89 wset = [fd] 90 if error: 91 xset = [fd] 92 93 if timeout is None: 94 (rcount, wcount, xcount) = select.select(rset, wset, xset) 95 else: 96 (rcount, wcount, xcount) = select.select(rset, wset, xset, timeout) 97 98 return bool((rcount or wcount or xcount))
99
100 -def _wait_for(fd, readable, writable, error, expiration):
101 done = False 102 while not done: 103 if expiration is None: 104 timeout = None 105 else: 106 timeout = expiration - time.time() 107 if timeout <= 0.0: 108 raise dns.exception.Timeout 109 try: 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: 114 raise e 115 done = True
116
117 -def _set_polling_backend(fn):
118 """ 119 Internal API. Do not use. 120 """ 121 global _polling_backend 122 123 _polling_backend = fn
124 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 130 else: 131 _polling_backend = _select_for 132
133 -def _wait_for_readable(s, expiration):
134 _wait_for(s, True, False, True, expiration)
135
136 -def _wait_for_writable(s, expiration):
137 _wait_for(s, False, True, True, expiration)
138
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:]
146
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. 150 151 @param q: the query 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. 157 @type timeout: float 158 @param port: The port to which to send the message. The default is 53. 159 @type port: int 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. 163 @type af: int 164 @rtype: dns.message.Message object 165 @param source: source address. The default is the IPv4 wildcard address. 166 @type source: string 167 @param source_port: The port from which to send the message. 168 The default is 0. 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 175 """ 176 177 wire = q.to_wire() 178 if af is None: 179 try: 180 af = dns.inet.af_for_address(where) 181 except: 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) 192 try: 193 expiration = _compute_expiration(timeout) 194 s.setblocking(0) 195 if source is not None: 196 s.bind(source) 197 _wait_for_writable(s, expiration) 198 s.sendto(wire, destination) 199 while 1: 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:]): 205 break 206 if not ignore_unexpected: 207 raise UnexpectedSource('got a response from ' 208 '%s instead of %s' % (from_address, 209 destination)) 210 finally: 211 s.close() 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): 215 raise BadResponse 216 return r
217
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. 223 """ 224 s = '' 225 while count > 0: 226 _wait_for_readable(sock, expiration) 227 n = sock.recv(count) 228 if n == '': 229 raise EOFError 230 count = count - len(n) 231 s = s + n 232 return s
233
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. 238 """ 239 current = 0 240 l = len(data) 241 while current < l: 242 _wait_for_writable(sock, expiration) 243 current += sock.send(data[current:])
244
245 -def _connect(s, address):
246 try: 247 s.connect(address) 248 except socket.error: 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: 253 raise v
254
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. 258 259 @param q: the query 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. 265 @type timeout: float 266 @param port: The port to which to send the message. The default is 53. 267 @type port: int 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. 271 @type af: int 272 @rtype: dns.message.Message object 273 @param source: source address. The default is the IPv4 wildcard address. 274 @type source: string 275 @param source_port: The port from which to send the message. 276 The default is 0. 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 280 """ 281 282 wire = q.to_wire() 283 if af is None: 284 try: 285 af = dns.inet.af_for_address(where) 286 except: 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) 297 try: 298 expiration = _compute_expiration(timeout) 299 s.setblocking(0) 300 if source is not None: 301 s.bind(source) 302 _connect(s, destination) 303 304 l = len(wire) 305 306 # copying the wire into tcpmsg is inefficient, but lets us 307 # avoid writev() or doing a short write that would get pushed 308 # onto the net 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) 314 finally: 315 s.close() 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): 319 raise BadResponse 320 return r
321
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. 327 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 333 dns.rdatatype.AXFR. 334 @type rdtype: int or string 335 @param rdclass: The class of the zone transfer. The default is 336 dns.rdatatype.IN. 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. 340 @type timeout: float 341 @param port: The port to which to send the message. The default is 53. 342 @type port: int 343 @param keyring: The TSIG keyring to use 344 @type keyring: dict 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. 354 @type af: int 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 357 take. 358 @type lifetime: float 359 @rtype: generator of dns.message.Message objects. 360 @param source: source address. The default is the IPv4 wildcard address. 361 @type source: string 362 @param source_port: The port from which to send the message. 363 The default is 0. 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). 367 @type serial: int 368 @param use_udp: Use UDP (only meaningful for IXFR) 369 @type use_udp: bool 370 @param keyalgorithm: The TSIG algorithm to use; defaults to 371 dns.tsig.default_algorithm 372 @type keyalgorithm: string 373 """ 374 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) 386 wire = q.to_wire() 387 if af is None: 388 try: 389 af = dns.inet.af_for_address(where) 390 except: 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) 400 if use_udp: 401 if rdtype != dns.rdatatype.IXFR: 402 raise ValueError('cannot do a UDP AXFR') 403 s = socket.socket(af, socket.SOCK_DGRAM, 0) 404 else: 405 s = socket.socket(af, socket.SOCK_STREAM, 0) 406 s.setblocking(0) 407 if source is not None: 408 s.bind(source) 409 expiration = _compute_expiration(lifetime) 410 _connect(s, destination) 411 l = len(wire) 412 if use_udp: 413 _wait_for_writable(s, expiration) 414 s.send(wire) 415 else: 416 tcpmsg = struct.pack("!H", l) + wire 417 _net_write(s, tcpmsg, expiration) 418 done = False 419 soa_rrset = None 420 soa_count = 0 421 if relativize: 422 origin = zone 423 oname = dns.name.empty 424 else: 425 origin = None 426 oname = zone 427 tsig_ctx = None 428 first = True 429 while not done: 430 mexpiration = _compute_expiration(timeout) 431 if mexpiration is None or mexpiration > expiration: 432 mexpiration = expiration 433 if use_udp: 434 _wait_for_readable(s, expiration) 435 (wire, from_address) = s.recvfrom(65535) 436 else: 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 445 first = False 446 answer_index = 0 447 delete_mode = False 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 452 rrset = r.answer[0] 453 if rrset.rdtype != dns.rdatatype.SOA: 454 raise dns.exception.FormError("first RRset is not an SOA") 455 answer_index = 1 456 soa_rrset = rrset.copy() 457 if rdtype == dns.rdatatype.IXFR: 458 if soa_rrset[0].serial == serial: 459 # 460 # We're already up-to-date. 461 # 462 done = True 463 else: 464 expecting_SOA = True 465 # 466 # Process SOAs in the answer section (other than the initial 467 # SOA in the first message). 468 # 469 for rrset in r.answer[answer_index:]: 470 if done: 471 raise dns.exception.FormError("answers after final SOA") 472 if rrset.rdtype == dns.rdatatype.SOA and rrset.name == oname: 473 if expecting_SOA: 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: 480 done = True 481 elif expecting_SOA: 482 # 483 # We made an IXFR request and are expecting another 484 # SOA RR, but saw something else, so this must be an 485 # AXFR response. 486 # 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") 491 yield r 492 s.close()
493