From 55ea2cb6973c9184a67fcd3ec0f880d7703cc279 Mon Sep 17 00:00:00 2001 From: Bob Halley Date: Mon, 16 Jan 2017 06:29:01 -0800 Subject: [PATCH] query doco --- dns/query.py | 355 ++++++++++++++++++++++++--------------------- doc/exceptions.rst | 9 +- doc/manual.rst | 2 + doc/query.rst | 33 +++++ doc/resolver.rst | 8 + 5 files changed, 239 insertions(+), 168 deletions(-) create mode 100644 doc/query.rst create mode 100644 doc/resolver.rst diff --git a/dns/query.py b/dns/query.py index 2ae1d89c..49711106 100644 --- a/dns/query.py +++ b/dns/query.py @@ -1,4 +1,4 @@ -# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# Copyright (C) 2003-2017 Nominum, Inc. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose with or without fee is hereby granted, @@ -42,12 +42,10 @@ else: socket_factory = socket.socket class UnexpectedSource(dns.exception.DNSException): - """A DNS query response came from an unexpected address or port.""" class BadResponse(dns.exception.FormError): - """A DNS query response does not respond to the question asked.""" @@ -57,19 +55,14 @@ def _compute_expiration(timeout): else: return time.time() + timeout +# This module can use either poll() or select() as the "polling backend". +# +# A backend function takes an fd, bools for readability, writablity, and +# error detection, and a timeout. def _poll_for(fd, readable, writable, error, timeout): - """Poll polling backend. - @param fd: File descriptor - @type fd: int - @param readable: Whether to wait for readability - @type readable: bool - @param writable: Whether to wait for writability - @type writable: bool - @param timeout: Deadline timeout (expiration time, in seconds) - @type timeout: float - @return True on success, False on timeout - """ + """Poll polling backend.""" + event_mask = 0 if readable: event_mask |= select.POLLIN @@ -90,17 +83,8 @@ def _poll_for(fd, readable, writable, error, timeout): def _select_for(fd, readable, writable, error, timeout): - """Select polling backend. - @param fd: File descriptor - @type fd: int - @param readable: Whether to wait for readability - @type readable: bool - @param writable: Whether to wait for writability - @type writable: bool - @param timeout: Deadline timeout (expiration time, in seconds) - @type timeout: float - @return True on success, False on timeout - """ + """Select polling backend.""" + rset, wset, xset = [], [], [] if readable: @@ -119,6 +103,10 @@ def _select_for(fd, readable, writable, error, timeout): def _wait_for(fd, readable, writable, error, expiration): + # Use the selected polling backend to wait for any of the specified + # events. An "expiration" absolute time is converted into a relative + # timeout. + done = False while not done: if expiration is None: @@ -137,9 +125,8 @@ def _wait_for(fd, readable, writable, error, expiration): def _set_polling_backend(fn): - """ - Internal API. Do not use. - """ + # Internal API. Do not use. + global _polling_backend _polling_backend = fn @@ -165,8 +152,11 @@ def _addresses_equal(af, a1, a2): # Convert the first value of the tuple, which is a textual format # address into binary form, so that we are not confused by different # textual representations of the same address - n1 = dns.inet.inet_pton(af, a1[0]) - n2 = dns.inet.inet_pton(af, a2[0]) + try: + n1 = dns.inet.inet_pton(af, a1[0]) + n2 = dns.inet.inet_pton(af, a2[0]) + except dns.exception.SyntaxError: + return False return n1 == n2 and a1[1:] == a2[1:] @@ -196,17 +186,20 @@ def _destination_and_source(af, where, port, source, source_port): def send_udp(sock, what, destination, expiration=None): """Send a DNS message to the specified UDP socket. - @param sock: the socket - @type sock: socket.socket - @param what: the message to send - @type what: wire format bytes or a dns.message.Message - @param destination: where to send the query - @type destination: tuple appropriate for the address family of the socket - @param expiration: The absolute time at which a timeout exception should - be raised. - @type expiration: float - @rtype: (int, double) tuple of bytes sent and the sent time. + *sock*, a ``socket``. + + *what*, a ``binary`` or ``dns.message.Message``, the message to send. + + *destination*, a destination tuple appropriate for the address family + of the socket, specifying where to send the query. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. + + Returns an ``(int, float)`` tuple of bytes sent and the sent time. """ + if isinstance(what, dns.message.Message): what = what.to_wire() _wait_for_writable(sock, expiration) @@ -215,38 +208,41 @@ def send_udp(sock, what, destination, expiration=None): return (n, sent_time) -def receive_udp(sock, destination, expiration=None, af=None, +def receive_udp(sock, destination, expiration=None, ignore_unexpected=False, one_rr_per_rrset=False, keyring=None, request_mac=b''): """Read a DNS message from a UDP socket. - @param sock: the socket - @type sock: socket.socket - @param af: the address family to use. The default is None, which - causes the address family to use to be inferred from the form of - destination. If the inference attempt fails, AF_INET is used. - @type af: int - @param ignore_unexpected: If True, ignore responses from unexpected - sources. The default is False. - @type ignore_unexpected: bool - @param one_rr_per_rrset: Put each RR into its own RRset - @type one_rr_per_rrset: bool - @param keyring: the keyring to use for TSIG - @type keyring: keyring dict - @param request_mac: the MAC of the request (for TSIG) - @type request_mac: bytes - @rtype: dns.message.Message object + *sock*, a ``socket``. + + *destination*, a destination tuple appropriate for the address family + of the socket, specifying where the associated query was sent. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. + + *ignore_unexpected*, a ``bool``. If ``True``, ignore responses from + unexpected sources. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + *keyring*, a ``dict``, the keyring to use for TSIG. + + *request_mac*, a ``binary``, the MAC of the request (for TSIG). + + Raises if the message is malformed, if network errors occur, of if + there is a timeout. + + Returns a ``dns.message.Message`` object. """ - if af is None: - try: - af = dns.inet.af_for_address(destination[0]) - except Exception: - af = dns.inet.AF_INET + wire = b'' while 1: _wait_for_readable(sock, expiration) (wire, from_address) = sock.recvfrom(65535) - if _addresses_equal(af, from_address, destination) or \ + if _addresses_equal(socket.family, from_address, destination) or \ (dns.inet.is_multicast(destination[0]) and from_address[1:] == destination[1:]): break @@ -263,30 +259,34 @@ def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, ignore_unexpected=False, one_rr_per_rrset=False): """Return the response obtained after sending a query via UDP. - @param q: the query - @type q: dns.message.Message - @param where: where to send the message - @type where: string containing an IPv4 or IPv6 address - @param timeout: The number of seconds to wait before the query times out. - If None, the default, wait forever. - @type timeout: float - @param port: The port to which to send the message. The default is 53. - @type port: int - @param af: the address family to use. The default is None, which - causes the address family to use to be inferred from the form of where. - If the inference attempt fails, AF_INET is used. - @type af: int - @param source: source address. The default is the wildcard address. - @type source: string - @param source_port: The port from which to send the message. + *q*, a ``dns.message.message``, the query to send + + *where*, a ``text`` containing an IPv4 or IPv6 address, where + to send the message. + + *timeout*, a ``float`` or ``None``, the number of seconds to wait before the + query times out. If ``None``, the default, wait forever. + + *port*, an ``int``, the port send the message to. The default is 53. + + *af*, an ``int``, the address family to use. The default is ``None``, + which causes the address family to use to be inferred from the form of + *where*. If the inference attempt fails, AF_INET is used. This + parameter is historical; you need never set it. + + *source*, a ``text`` containing an IPv4 or IPv6 address, specifying + the source address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. The default is 0. - @type source_port: int - @param ignore_unexpected: If True, ignore responses from unexpected - sources. The default is False. - @type ignore_unexpected: bool - @param one_rr_per_rrset: Put each RR into its own RRset - @type one_rr_per_rrset: bool - @rtype: dns.message.Message object + + *ignore_unexpected*, a ``bool``. If ``True``, ignore responses from + unexpected sources. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + Returns a ``dns.message.Message``. """ wire = q.to_wire() @@ -348,15 +348,17 @@ def _net_write(sock, data, expiration): def send_tcp(sock, what, expiration=None): """Send a DNS message to the specified TCP socket. - @param sock: the socket - @type sock: socket.socket - @param what: the message to send - @type what: wire format bytes or a dns.message.Message - @param expiration: The absolute time at which a timeout exception should - be raised. - @type expiration: float - @rtype: (int, double) tuple of bytes sent and the sent time. + *sock*, a ``socket``. + + *what*, a ``binary`` or ``dns.message.Message``, the message to send. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. + + Returns an ``(int, float)`` tuple of bytes sent and the sent time. """ + if isinstance(what, dns.message.Message): what = what.to_wire() l = len(what) @@ -373,16 +375,25 @@ def receive_tcp(sock, expiration=None, one_rr_per_rrset=False, keyring=None, request_mac=b''): """Read a DNS message from a TCP socket. - @param sock: the socket - @type sock: socket.socket - @param one_rr_per_rrset: Put each RR into its own RRset - @type one_rr_per_rrset: bool - @param keyring: the keyring to use for TSIG - @type keyring: keyring dict - @param request_mac: the MAC of the request (for TSIG) - @type request_mac: bytes - @rtype: dns.message.Message object + *sock*, a ``socket``. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + *keyring*, a ``dict``, the keyring to use for TSIG. + + *request_mac*, a ``binary``, the MAC of the request (for TSIG). + + Raises if the message is malformed, if network errors occur, of if + there is a timeout. + + Returns a ``dns.message.Message`` object. """ + ldata = _net_read(sock, 2, expiration) (l,) = struct.unpack("!H", ldata) wire = _net_read(sock, l, expiration) @@ -409,27 +420,31 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, one_rr_per_rrset=False): """Return the response obtained after sending a query via TCP. - @param q: the query - @type q: dns.message.Message object - @param where: where to send the message - @type where: string containing an IPv4 or IPv6 address - @param timeout: The number of seconds to wait before the query times out. - If None, the default, wait forever. - @type timeout: float - @param port: The port to which to send the message. The default is 53. - @type port: int - @param af: the address family to use. The default is None, which - causes the address family to use to be inferred from the form of where. - If the inference attempt fails, AF_INET is used. - @type af: int - @rtype: dns.message.Message object - @param source: source address. The default is the wildcard address. - @type source: string - @param source_port: The port from which to send the message. + *q*, a ``dns.message.message``, the query to send + + *where*, a ``text`` containing an IPv4 or IPv6 address, where + to send the message. + + *timeout*, a ``float`` or ``None``, the number of seconds to wait before the + query times out. If ``None``, the default, wait forever. + + *port*, an ``int``, the port send the message to. The default is 53. + + *af*, an ``int``, the address family to use. The default is ``None``, + which causes the address family to use to be inferred from the form of + *where*. If the inference attempt fails, AF_INET is used. This + parameter is historical; you need never set it. + + *source*, a ``text`` containing an IPv4 or IPv6 address, specifying + the source address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. The default is 0. - @type source_port: int - @param one_rr_per_rrset: Put each RR into its own RRset - @type one_rr_per_rrset: bool + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + Returns a ``dns.message.Message``. """ wire = q.to_wire() @@ -465,51 +480,57 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN, use_udp=False, keyalgorithm=dns.tsig.default_algorithm): """Return a generator for the responses to a zone transfer. - @param where: where to send the message - @type where: string containing an IPv4 or IPv6 address - @param zone: The name of the zone to transfer - @type zone: dns.name.Name object or string - @param rdtype: The type of zone transfer. The default is - dns.rdatatype.AXFR. - @type rdtype: int or string - @param rdclass: The class of the zone transfer. The default is - dns.rdataclass.IN. - @type rdclass: int or string - @param timeout: The number of seconds to wait for each response message. - If None, the default, wait forever. - @type timeout: float - @param port: The port to which to send the message. The default is 53. - @type port: int - @param keyring: The TSIG keyring to use - @type keyring: dict - @param keyname: The name of the TSIG key to use - @type keyname: dns.name.Name object or string - @param relativize: If True, all names in the zone will be relativized to - the zone origin. It is essential that the relativize setting matches - the one specified to dns.zone.from_xfr(). - @type relativize: bool - @param af: the address family to use. The default is None, which - causes the address family to use to be inferred from the form of where. - If the inference attempt fails, AF_INET is used. - @type af: int - @param lifetime: The total number of seconds to spend doing the transfer. - If None, the default, then there is no limit on the time the transfer may - take. - @type lifetime: float - @rtype: generator of dns.message.Message objects. - @param source: source address. The default is the wildcard address. - @type source: string - @param source_port: The port from which to send the message. + *where*. If the inference attempt fails, AF_INET is used. This + parameter is historical; you need never set it. + + *zone*, a ``dns.name.Name`` or ``text``, the name of the zone to transfer. + + *rdtype*, an ``int`` or ``text``, the type of zone transfer. The + default is ``dns.rdatatype.AXFR``. ``dns.rdatatype.IXFR`` can be + used to do an incremental transfer instead. + + *rdclass*, an ``int`` or ``text``, the class of the zone transfer. + The default is ``dns.rdataclass.IN``. + + *timeout*, a ``float``, the number of seconds to wait for each + response message. If None, the default, wait forever. + + *port*, an ``int``, the port send the message to. The default is 53. + + *keyring*, a ``dict``, the keyring to use for TSIG. + + *keyname*, a ``dns.name.Name`` or ``text``, the name of the TSIG + key to use. + + *relativize*, a ``bool``. If ``True``, all names in the zone will be + relativized to the zone origin. It is essential that the + relativize setting matches the one specified to + ``dns.zone.from_xfr()`` if using this generator to make a zone. + + *af*, an ``int``, the address family to use. The default is ``None``, + which causes the address family to use to be inferred from the form of + *where*. If the inference attempt fails, AF_INET is used. This + parameter is historical; you need never set it. + + *lifetime*, a ``float``, the total number of seconds to spend + doing the transfer. If ``None``, the default, then there is no + limit on the time the transfer may take. + + *source*, a ``text`` containing an IPv4 or IPv6 address, specifying + the source address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. The default is 0. - @type source_port: int - @param serial: The SOA serial number to use as the base for an IXFR diff - sequence (only meaningful if rdtype == dns.rdatatype.IXFR). - @type serial: int - @param use_udp: Use UDP (only meaningful for IXFR) - @type use_udp: bool - @param keyalgorithm: The TSIG algorithm to use; defaults to - dns.tsig.default_algorithm - @type keyalgorithm: string + + *serial*, an ``int``, the SOA serial number to use as the base for + an IXFR diff sequence (only meaningful if *rdtype* is + ``dns.rdatatype.IXFR``). + + *use_udp*, a ``bool``. If ``True``, use UDP (only meaningful for IXFR). + + *keyalgorithm*, a ``dns.name.Name`` or ``text``, the TSIG algorithm to use. + + Returns a generator of ``dns.message.Message`` objects. """ if isinstance(zone, string_types): diff --git a/doc/exceptions.rst b/doc/exceptions.rst index 9f5e22cc..cc04afc6 100644 --- a/doc/exceptions.rst +++ b/doc/exceptions.rst @@ -45,7 +45,14 @@ dns.opcode Exceptions --------------------- .. autoexception:: dns.opcode.UnknownOpcode - + +dns.query Exceptions +-------------------- + +.. autoexception:: dns.query.BadResponse +.. autoexception:: dns.query.UnexpectedSource + + dns.rcode Exceptions -------------------- diff --git a/doc/manual.rst b/doc/manual.rst index c032f4f9..f1528943 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -10,5 +10,7 @@ Dnspython Manual rdata message dnssec + query + resolver exceptions utilities diff --git a/doc/query.rst b/doc/query.rst new file mode 100644 index 00000000..77e1c6b7 --- /dev/null +++ b/doc/query.rst @@ -0,0 +1,33 @@ +.. module:: dns.query +.. _query: + +DNS Query Support +================= + +The ``dns.query`` module is for sending messages to DNS servers, and +processing their responses. If you want "stub resolver" behavior, then +you should use the higher level ``dns.resolver`` module; see :ref:`resolver`. + +For UDP and TCP, the module provides a single "do everything" query +function, and also provides the send and receive halves of this function +individually for situations where more sophisticated I/O handling is in +being used by the application. + +UDP +--- + +.. autofunction:: dns.query.udp +.. autofunction:: dns.query.send_udp +.. autofunction:: dns.query.receive_udp + +TCP +--- + +.. autofunction:: dns.query.tcp +.. autofunction:: dns.query.send_tcp +.. autofunction:: dns.query.receive_tcp + +Zone Transfers +-------------- + +.. autofunction:: dns.query.xfr diff --git a/doc/resolver.rst b/doc/resolver.rst new file mode 100644 index 00000000..6fad36d8 --- /dev/null +++ b/doc/resolver.rst @@ -0,0 +1,8 @@ +.. module:: dns.resolver +.. _resolver: + +Stub Resolver +============= + +This is a placeholder. + -- 2.47.3