def delete_rdataset(self, name, rdtype, covers=rdatatype.NONE): """Delete the rdataset matching I{rdtype} and I{covers}, if it exists at the node specified by I{name}. The I{name}, I{rdtype}, and I{covers} parameters may be strings, in which case they will be converted to their proper type. It is not an error if the node does not exist, or if there is no matching rdataset at the node. If the node has no rdatasets after the deletion, it will itself be deleted. @param name: the owner name to look for @type name: DNS.name.Name object or string @param rdtype: the rdata type desired @type rdtype: int or string @param covers: the covered type (defaults to None) @type covers: int or string """ name = self._validate_name(name) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) if isinstance(covers, (str, unicode)): covers = rdatatype.from_text(covers) node = self.get_node(name) if not node is None: node.delete_rdataset(self.rdclass, rdtype, covers) if len(node) == 0: self.delete_node(name)
def find_rdataset(self, name, rdtype, covers=rdatatype.NONE, create=False): """Look for rdata with the specified name and type in the zone, and return an rdataset encapsulating it. The I{name}, I{rdtype}, and I{covers} parameters may be strings, in which case they will be converted to their proper type. The rdataset returned is not a copy; changes to it will change the zone. KeyError is raised if the name or type are not found. Use L{get_rdataset} if you want to have None returned instead. @param name: the owner name to look for @type name: DNS.name.Name object or string @param rdtype: the rdata type desired @type rdtype: int or string @param covers: the covered type (defaults to None) @type covers: int or string @param create: should the node and rdataset be created if they do not exist? @type create: bool @raises KeyError: the node or rdata could not be found @rtype: rrset.RRset object """ name = self._validate_name(name) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) if isinstance(covers, (str, unicode)): covers = rdatatype.from_text(covers) node = self.find_node(name, create) return node.find_rdataset(self.rdclass, rdtype, covers, create)
def _question_line(self, section): """Process one line from the text format question section.""" token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = mname.from_text(token.value, None) name = self.last_name token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: rdclass = rdataclass.IN # Type rdtype = rdatatype.from_text(token.value) self.message.find_rrset(self.message.question, name, rdclass, rdtype, create=True, force_unique=True) if self.updating: self.zone_rdclass = rdclass self.tok.get_eol()
def _question_line(self, section): """Process one line from the text format question section.""" token = self.tok.get(want_leading = True) if not token.is_whitespace(): self.last_name = mname.from_text(token.value, None) name = self.last_name token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: rdclass = rdataclass.IN # Type rdtype = rdatatype.from_text(token.value) self.message.find_rrset(self.message.question, name, rdclass, rdtype, create=True, force_unique=True) if self.updating: self.zone_rdclass = rdclass self.tok.get_eol()
def make_query(qname, rdtype, rdclass=rdataclass.IN, use_edns=None, want_dnssec=False, ednsflags=0, payload=1280, request_payload=None, options=None): """Make a query message. The query name, type, and class may all be specified either as objects of the appropriate type, or as strings. The query will have a randomly choosen query id, and its DNS flags will be set to flags.RD. @param qname: The query name. @type qname: mname.Name object or string @param rdtype: The desired rdata type. @type rdtype: int @param rdclass: The desired rdata class; the default is class IN. @type rdclass: int @param use_edns: The EDNS level to use; the default is None (no EDNS). See the description of message.Message.use_edns() for the possible values for use_edns and their meanings. @type use_edns: int or bool or None @param want_dnssec: Should the query indicate that DNSSEC is desired? @type want_dnssec: bool @param ednsflags: EDNS flag values. @type ednsflags: int @param payload: The EDNS sender's payload field, which is the maximum size of UDP datagram the sender can handle. @type payload: int @param request_payload: The EDNS payload size to use when sending this message. If not specified, defaults to the value of payload. @type request_payload: int or None @param options: The EDNS options @type options: None or list of edns.Option objects @see: RFC 2671 @rtype: message.Message object""" if isinstance(qname, (str, unicode)): qname = mname.from_text(qname) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) if isinstance(rdclass, (str, unicode)): rdclass = rdataclass.from_text(rdclass) m = Message() m.flags |= flags.RD m.find_rrset(m.question, qname, rdclass, rdtype, create=True, force_unique=True) m.use_edns(use_edns, ednsflags, payload, request_payload, options) m.want_dnssec(want_dnssec) return m
def iterate_rdatasets(self, rdtype=rdatatype.ANY, covers=rdatatype.NONE): """Return a generator which yields (name, rdataset) tuples for all rdatasets in the zone which have the specified I{rdtype} and I{covers}. If I{rdtype} is rdatatype.ANY, the default, then all rdatasets will be matched. @param rdtype: int or string @type rdtype: int or string @param covers: the covered type (defaults to None) @type covers: int or string """ if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) if isinstance(covers, (str, unicode)): covers = rdatatype.from_text(covers) for (name, node) in self.iteritems(): for rds in node: if rdtype == rdatatype.ANY or \ (rds.rdtype == rdtype and rds.covers == covers): yield (name, rds)
def _rr_line(self, section): """Process one line from the text format answer, authority, or additional data sections. """ deleting = None # Name token = self.tok.get(want_leading = True) if not token.is_whitespace(): self.last_name = mname.from_text(token.value, None) name = self.last_name token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError # TTL try: ttl = int(token.value, 0) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: ttl = 0 # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError if rdclass == rdataclass.ANY or rdclass == rdataclass.NONE: deleting = rdclass rdclass = self.zone_rdclass except exception.SyntaxError: raise exception.SyntaxError except: rdclass = rdataclass.IN # Type rdtype = rdatatype.from_text(token.value) token = self.tok.get() if not token.is_eol_or_eof(): self.tok.unget(token) rd = rdata.from_text(rdclass, rdtype, self.tok, None) covers = rd.covers() else: rd = None covers = rdatatype.NONE rrset = self.message.find_rrset(section, name, rdclass, rdtype, covers, deleting, True, self.updating) if not rd is None: rrset.add(rd, ttl)
def find_rrset(self, name, rdtype, covers=rdatatype.NONE): """Look for rdata with the specified name and type in the zone, and return an RRset encapsulating it. The I{name}, I{rdtype}, and I{covers} parameters may be strings, in which case they will be converted to their proper type. This method is less efficient than the similar L{find_rdataset} because it creates an RRset instead of returning the matching rdataset. It may be more convenient for some uses since it returns an object which binds the owner name to the rdata. This method may not be used to create new nodes or rdatasets; use L{find_rdataset} instead. KeyError is raised if the name or type are not found. Use L{get_rrset} if you want to have None returned instead. @param name: the owner name to look for @type name: DNS.name.Name object or string @param rdtype: the rdata type desired @type rdtype: int or string @param covers: the covered type (defaults to None) @type covers: int or string @raises KeyError: the node or rdata could not be found @rtype: rrset.RRset object """ name = self._validate_name(name) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) if isinstance(covers, (str, unicode)): covers = rdatatype.from_text(covers) rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers) rrset = rrset.RRset(name, self.rdclass, rdtype, covers) rrset.update(rdataset) return rrset
def _rr_line(self, section): """Process one line from the text format answer, authority, or additional data sections. """ deleting = None # Name token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = mname.from_text(token.value, None) name = self.last_name token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError # TTL try: ttl = int(token.value, 0) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: ttl = 0 # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError if rdclass == rdataclass.ANY or rdclass == rdataclass.NONE: deleting = rdclass rdclass = self.zone_rdclass except exception.SyntaxError: raise exception.SyntaxError except: rdclass = rdataclass.IN # Type rdtype = rdatatype.from_text(token.value) token = self.tok.get() if not token.is_eol_or_eof(): self.tok.unget(token) rd = rdata.from_text(rdclass, rdtype, self.tok, None) covers = rd.covers() else: rd = None covers = rdatatype.NONE rrset = self.message.find_rrset(section, name, rdclass, rdtype, covers, deleting, True, self.updating) if not rd is None: rrset.add(rd, ttl)
def make_query(qname, rdtype, rdclass = rdataclass.IN, use_edns=None, want_dnssec=False, ednsflags=0, payload=1280, request_payload=None, options=None): """Make a query message. The query name, type, and class may all be specified either as objects of the appropriate type, or as strings. The query will have a randomly choosen query id, and its DNS flags will be set to flags.RD. @param qname: The query name. @type qname: mname.Name object or string @param rdtype: The desired rdata type. @type rdtype: int @param rdclass: The desired rdata class; the default is class IN. @type rdclass: int @param use_edns: The EDNS level to use; the default is None (no EDNS). See the description of message.Message.use_edns() for the possible values for use_edns and their meanings. @type use_edns: int or bool or None @param want_dnssec: Should the query indicate that DNSSEC is desired? @type want_dnssec: bool @param ednsflags: EDNS flag values. @type ednsflags: int @param payload: The EDNS sender's payload field, which is the maximum size of UDP datagram the sender can handle. @type payload: int @param request_payload: The EDNS payload size to use when sending this message. If not specified, defaults to the value of payload. @type request_payload: int or None @param options: The EDNS options @type options: None or list of edns.Option objects @see: RFC 2671 @rtype: message.Message object""" if isinstance(qname, (str, unicode)): qname = mname.from_text(qname) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) if isinstance(rdclass, (str, unicode)): rdclass = rdataclass.from_text(rdclass) m = Message() m.flags |= flags.RD m.find_rrset(m.question, qname, rdclass, rdtype, create=True, force_unique=True) m.use_edns(use_edns, ednsflags, payload, request_payload, options) m.want_dnssec(want_dnssec) return m
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): algorithm = tok.get_uint8() flags = tok.get_uint8() iterations = tok.get_uint16() salt = tok.get_string() if salt == '-': salt = '' else: salt = salt.decode('hex-codec') next = tok.get_string().upper().translate(b32_hex_to_normal) next = base64.b32decode(next) rdtypes = [] while 1: token = tok.get().unescape() if token.is_eol_or_eof(): break nrdtype = rdatatype.from_text(token.value) if nrdtype == 0: raise exception.SyntaxError("NSEC3 with bit 0") if nrdtype > 65535: raise exception.SyntaxError("NSEC3 with bit > 65535") rdtypes.append(nrdtype) rdtypes.sort() window = 0 octets = 0 prior_rdtype = 0 bitmap = ['\0'] * 32 windows = [] for nrdtype in rdtypes: if nrdtype == prior_rdtype: continue prior_rdtype = nrdtype new_window = nrdtype // 256 if new_window != window: if octets != 0: windows.append((window, ''.join(bitmap[0:octets]))) bitmap = ['\0'] * 32 window = new_window offset = nrdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) if octets != 0: windows.append((window, ''.join(bitmap[0:octets]))) return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, windows)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): algorithm = tok.get_uint8() flags = tok.get_uint8() iterations = tok.get_uint16() salt = tok.get_string() if salt == '-': salt = '' else: salt = salt.decode('hex-codec') next = tok.get_string().upper().translate(b32_hex_to_normal) next = base64.b32decode(next) rdtypes = [] while 1: token = tok.get().unescape() if token.is_eol_or_eof(): break nrdtype = rdatatype.from_text(token.value) if nrdtype == 0: raise exception.SyntaxError("NSEC3 with bit 0") if nrdtype > 65535: raise exception.SyntaxError("NSEC3 with bit > 65535") rdtypes.append(nrdtype) rdtypes.sort() window = 0 octets = 0 prior_rdtype = 0 bitmap = ['\0'] * 32 windows = [] for nrdtype in rdtypes: if nrdtype == prior_rdtype: continue prior_rdtype = nrdtype new_window = nrdtype // 256 if new_window != window: if octets != 0: windows.append((window, ''.join(bitmap[0:octets]))) bitmap = ['\0'] * 32 window = new_window offset = nrdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) if octets != 0: windows.append((window, ''.join(bitmap[0:octets]))) return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, windows)
def from_text_list(rdclass, rdtype, ttl, text_rdatas): """Create an rdataset with the specified class, type, and TTL, and with the specified list of rdatas in text format. @rtype: rdataset.Rdataset object """ if isinstance(rdclass, (str, unicode)): rdclass = rdataclass.from_text(rdclass) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) r = Rdataset(rdclass, rdtype) r.update_ttl(ttl) for t in text_rdatas: rd = rdata.from_text(r.rdclass, r.rdtype, t) r.add(rd) return r
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): type_covered = rdatatype.from_text(tok.get_string()) algorithm = dnssec.algorithm_from_text(tok.get_string()) labels = tok.get_int() original_ttl = tok.get_ttl() expiration = sigtime_to_posixtime(tok.get_string()) inception = sigtime_to_posixtime(tok.get_string()) key_tag = tok.get_int() signer = tok.get_name() signer = signer.choose_relativity(origin, relativize) chunks = [] while 1: t = tok.get().unescape() if t.is_eol_or_eof(): break if not t.is_identifier(): raise exception.SyntaxError chunks.append(t.value) b64 = ''.join(chunks) signature = b64.decode('base64_codec') return cls(rdclass, rdtype, type_covered, algorithm, labels, original_ttl, expiration, inception, key_tag, signer, signature)
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): next = tok.get_name() next = next.choose_relativity(origin, relativize) rdtypes = [] while 1: token = tok.get().unescape() if token.is_eol_or_eof(): break nrdtype = rdatatype.from_text(token.value) if nrdtype == 0: raise exception.SyntaxError("NSEC with bit 0") if nrdtype > 65535: raise exception.SyntaxError("NSEC with bit > 65535") rdtypes.append(nrdtype) rdtypes.sort() window = 0 octets = 0 prior_rdtype = 0 bitmap = ["\0"] * 32 windows = [] for nrdtype in rdtypes: if nrdtype == prior_rdtype: continue prior_rdtype = nrdtype new_window = nrdtype // 256 if new_window != window: windows.append((window, "".join(bitmap[0:octets]))) bitmap = ["\0"] * 32 window = new_window offset = nrdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) windows.append((window, "".join(bitmap[0:octets]))) return cls(rdclass, rdtype, next, windows)
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): next = tok.get_name() next = next.choose_relativity(origin, relativize) rdtypes = [] while 1: token = tok.get().unescape() if token.is_eol_or_eof(): break nrdtype = rdatatype.from_text(token.value) if nrdtype == 0: raise exception.SyntaxError("NSEC with bit 0") if nrdtype > 65535: raise exception.SyntaxError("NSEC with bit > 65535") rdtypes.append(nrdtype) rdtypes.sort() window = 0 octets = 0 prior_rdtype = 0 bitmap = ['\0'] * 32 windows = [] for nrdtype in rdtypes: if nrdtype == prior_rdtype: continue prior_rdtype = nrdtype new_window = nrdtype // 256 if new_window != window: windows.append((window, ''.join(bitmap[0:octets]))) bitmap = ['\0'] * 32 window = new_window offset = nrdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) windows.append((window, ''.join(bitmap[0:octets]))) return cls(rdclass, rdtype, next, windows)
def xfr(where, zone, rdtype=rdatatype.AXFR, rdclass=rdataclass.IN, timeout=None, port=53, keyring=None, keyname=None, relativize=True, af=None, lifetime=None, source=None, source_port=0, serial=0, use_udp=False, keyalgorithm=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: name.Name object or string @param rdtype: The type of zone transfer. The default is rdatatype.AXFR. @type rdtype: int or string @param rdclass: The class of the zone transfer. The default is 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: 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 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 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 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. 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 == 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 tsig.default_algorithm @type keyalgorithm: string """ if isinstance(zone, (str, unicode)): zone = name.from_text(zone) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) q = message.make_query(zone, rdtype, rdclass) if rdtype == rdatatype.IXFR: rrset = rrset.from_text(zone, 0, 'IN', 'SOA', '. . %u 0 0 0 0' % serial) q.authority.append(rrset) if not keyring is None: q.use_tsig(keyring, keyname, algorithm=keyalgorithm) wire = q.to_wire() (af, destination, source) = _destination_and_source(af, where, port, source, source_port) if use_udp: if rdtype != rdatatype.IXFR: raise ValueError('cannot do a UDP AXFR') s = socket.socket(af, socket.SOCK_DGRAM, 0) else: s = socket.socket(af, socket.SOCK_STREAM, 0) s.setblocking(0) if source is not None: s.bind(source) expiration = _compute_expiration(lifetime) _connect(s, destination) l = len(wire) if use_udp: _wait_for_writable(s, expiration) s.send(wire) else: tcpmsg = struct.pack("!H", l) + wire _net_write(s, tcpmsg, expiration) done = False delete_mode = True expecting_SOA = False soa_rrset = None soa_count = 0 if relativize: origin = zone oname = name.empty else: origin = None oname = zone tsig_ctx = None first = True while not done: mexpiration = _compute_expiration(timeout) if mexpiration is None or mexpiration > expiration: mexpiration = expiration if use_udp: _wait_for_readable(s, expiration) (wire, from_address) = s.recvfrom(65535) else: ldata = _net_read(s, 2, mexpiration) (l,) = struct.unpack("!H", ldata) wire = _net_read(s, l, mexpiration) r = message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, xfr=True, origin=origin, tsig_ctx=tsig_ctx, multi=True, first=first, one_rr_per_rrset=(rdtype==rdatatype.IXFR)) tsig_ctx = r.tsig_ctx first = False answer_index = 0 if soa_rrset is None: if not r.answer or r.answer[0].name != oname: raise exception.FormError("No answer or RRset not for qname") rrset = r.answer[0] if rrset.rdtype != rdatatype.SOA: raise exception.FormError("first RRset is not an SOA") answer_index = 1 soa_rrset = rrset.copy() if rdtype == rdatatype.IXFR: if soa_rrset[0].serial <= serial: # # We're already up-to-date. # done = True else: expecting_SOA = True # # Process SOAs in the answer section (other than the initial # SOA in the first message). # for rrset in r.answer[answer_index:]: if done: raise exception.FormError("answers after final SOA") if rrset.rdtype == rdatatype.SOA and rrset.name == oname: if expecting_SOA: if rrset[0].serial != serial: raise exception.FormError("IXFR base serial mismatch") expecting_SOA = False elif rdtype == rdatatype.IXFR: delete_mode = not delete_mode # # If this SOA RRset is equal to the first we saw then we're # finished. If this is an IXFR we also check that we're seeing # the record in the expected part of the response. # if rrset == soa_rrset and \ (rdtype == rdatatype.AXFR or \ (rdtype == rdatatype.IXFR and delete_mode)): done = True elif expecting_SOA: # # We made an IXFR request and are expecting another # SOA RR, but saw something else, so this must be an # AXFR response. # rdtype = rdatatype.AXFR expecting_SOA = False if done and q.keyring and not r.had_tsig: raise exception.FormError("missing TSIG") yield r s.close()
def query(self, qname, rdtype=rdatatype.A, rdclass=rdataclass.IN, tcp=False, source=None, raise_on_no_answer=True, source_port=0): """Query nameservers to find the answer to the question. The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects of the appropriate type, or strings that can be converted into objects of the appropriate type. E.g. For I{rdtype} the integer 2 and the the string 'NS' both mean to query for records with DNS rdata type NS. @param qname: the query name @type qname: name.Name object or string @param rdtype: the query type @type rdtype: int or string @param rdclass: the query class @type rdclass: int or string @param tcp: use TCP to make the query (default is False). @type tcp: bool @param source: bind to this IP address (defaults to machine default IP). @type source: IP address in dotted quad notation @param raise_on_no_answer: raise NoAnswer if there's no answer (defaults is True). @type raise_on_no_answer: bool @param source_port: The port from which to send the message. The default is 0. @type source_port: int @rtype: resolver.Answer instance @raises Timeout: no answers could be found in the specified lifetime @raises NXDOMAIN: the query name does not exist @raises YXDOMAIN: the query name is too long after DNAME substitution @raises NoAnswer: the response did not contain an answer and raise_on_no_answer is True. @raises NoNameservers: no non-broken nameservers are available to answer the question.""" #import pdb #pdb.set_trace() if isinstance(qname, (str, unicode)): qname = name.from_text(qname, None) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) if rdatatype.is_metatype(rdtype): raise NoMetaqueries if isinstance(rdclass, (str, unicode)): rdclass = rdataclass.from_text(rdclass) if rdataclass.is_metaclass(rdclass): raise NoMetaqueries qnames_to_try = [] if qname.is_absolute(): qnames_to_try.append(qname) else: if len(qname) > 1: qnames_to_try.append(qname.concatenate(name.root)) if self.search: for suffix in self.search: qnames_to_try.append(qname.concatenate(suffix)) else: qnames_to_try.append(qname.concatenate(self.domain)) all_nxdomain = True start = time.time() for qname in qnames_to_try: if self.cache: answer = self.cache.get((qname, rdtype, rdclass)) if not answer is None: if answer.rrset is None and raise_on_no_answer: raise NoAnswer else: return answer request = message.make_query(qname, rdtype, rdclass) if not self.keyname is None: request.use_tsig(self.keyring, self.keyname, algorithm=self.keyalgorithm) request.use_edns(self.edns, self.ednsflags, self.payload) if self.flags is not None: request.flags = self.flags response = None # # make a copy of the servers list so we can alter it later. # nameservers = self.nameservers[:] if self.rotate: random.shuffle(nameservers) backoff = 0.10 while response is None: if len(nameservers) == 0: raise NoNameservers for nameserver in nameservers[:]: timeout = self._compute_timeout(start) try: if tcp: response = mquery.tcp(request, nameserver, timeout, self.port, source=source, source_port=source_port) else: response = mquery.udp(request, nameserver, timeout, self.port, source=source, source_port=source_port) if response.flags & flags.TC: # Response truncated; retry with TCP. timeout = self._compute_timeout(start) response = mquery.tcp(request, nameserver, timeout, self.port, source=source, source_port=source_port) except (socket.error, exception.Timeout): # # Communication failure or timeout. Go to the # next server # response = None continue except mquery.UnexpectedSource: # # Who knows? Keep going. # response = None continue except exception.FormError: # # We don't understand what this server is # saying. Take it out of the mix and # continue. # nameservers.remove(nameserver) response = None continue except EOFError: # # We're using TCP and they hung up on us. # Probably they don't support TCP (though # they're supposed to!). Take it out of the # mix and continue. # nameservers.remove(nameserver) response = None continue rcode = response.rcode() if rcode == mrcode.YXDOMAIN: raise YXDOMAIN if rcode == mrcode.NOERROR or \ rcode == mrcode.NXDOMAIN: break # # We got a response, but we're not happy with the # rcode in it. Remove the server from the mix if # the rcode isn't SERVFAIL. # if rcode != mrcode.SERVFAIL or not self.retry_servfail: nameservers.remove(nameserver) response = None if not response is None: break # # All nameservers failed! # if len(nameservers) > 0: # # But we still have servers to try. Sleep a bit # so we don't pound them! # timeout = self._compute_timeout(start) sleep_time = min(timeout, backoff) backoff *= 2 time.sleep(sleep_time) if response.rcode() == mrcode.NXDOMAIN: continue all_nxdomain = False break if all_nxdomain: raise NXDOMAIN answer = Answer(qname, rdtype, rdclass, response, raise_on_no_answer) if self.cache: self.cache.put((qname, rdtype, rdclass), answer) return answer
def _rr_line(self): """Process one line from a DNS master file.""" # Name if self.current_origin is None: raise UnknownOrigin token = self.tok.get(want_leading = True) if not token.is_whitespace(): self.last_name = name.from_text(token.value, self.current_origin) else: token = self.tok.get() if token.is_eol_or_eof(): # treat leading WS followed by EOL/EOF as if they were EOL/EOF. return self.tok.unget(token) name = self.last_name if not name.is_subdomain(self.zone.origin): self._eat_line() return if self.relativize: name = name.relativize(self.zone.origin) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError # TTL try: ttl = ttl.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except ttl.BadTTL: ttl = self.ttl # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: rdclass = self.zone.rdclass if rdclass != self.zone.rdclass: raise exception.SyntaxError("RR class is not zone's class") # Type try: rdtype = rdatatype.from_text(token.value) except: raise exception.SyntaxError("unknown rdatatype '%s'" % token.value) n = self.zone.nodes.get(name) if n is None: n = self.zone.node_factory() self.zone.nodes[name] = n try: rd = rdata.from_text(rdclass, rdtype, self.tok, self.current_origin, False) except exception.SyntaxError: # Catch and reraise. (ty, va) = sys.exc_info()[:2] raise va except: # All exceptions that occur in the processing of rdata # are treated as syntax errors. This is not strictly # correct, but it is correct almost all of the time. # We convert them to syntax errors so that we can emit # helpful filename:line info. (ty, va) = sys.exc_info()[:2] raise exception.SyntaxError("caught exception %s: %s" % (str(ty), str(va))) rd.choose_relativity(self.zone.origin, self.relativize) covers = rd.covers() rds = n.find_rdataset(rdclass, rdtype, covers, True) rds.add(rd, ttl)
def _generate_line(self): # range lhs [ttl] [class] type rhs [ comment ] """Process one line containing the GENERATE statement from a DNS master file.""" if self.current_origin is None: raise UnknownOrigin token = self.tok.get() # Range (required) try: start, stop, step = grange.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except: raise exception.SyntaxError # lhs (required) try: lhs = token.value token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except: raise exception.SyntaxError # TTL try: ttl = ttl.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except ttl.BadTTL: ttl = self.ttl # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: rdclass = self.zone.rdclass if rdclass != self.zone.rdclass: raise exception.SyntaxError("RR class is not zone's class") # Type try: rdtype = rdatatype.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except: raise exception.SyntaxError("unknown rdatatype '%s'" % token.value) # lhs (required) try: rhs = token.value except: raise exception.SyntaxError lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs) rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs) for i in range(start, stop + 1, step): # +1 because bind is inclusive and python is exclusive if lsign == '+': lindex = i + int(loffset) elif lsign == '-': lindex = i - int(loffset) if rsign == '-': rindex = i - int(roffset) elif rsign == '+': rindex = i + int(roffset) lzfindex = str(lindex).zfill(int(lwidth)) rzfindex = str(rindex).zfill(int(rwidth)) name = lhs.replace('$%s' % (lmod), lzfindex) rdata = rhs.replace('$%s' % (rmod), rzfindex) self.last_name = name.from_text(name, self.current_origin) name = self.last_name if not name.is_subdomain(self.zone.origin): self._eat_line() return if self.relativize: name = name.relativize(self.zone.origin) n = self.zone.nodes.get(name) if n is None: n = self.zone.node_factory() self.zone.nodes[name] = n try: rd = rdata.from_text(rdclass, rdtype, rdata, self.current_origin, False) except exception.SyntaxError: # Catch and reraise. (ty, va) = sys.exc_info()[:2] raise va except: # All exceptions that occur in the processing of rdata # are treated as syntax errors. This is not strictly # correct, but it is correct almost all of the time. # We convert them to syntax errors so that we can emit # helpful filename:line info. (ty, va) = sys.exc_info()[:2] raise exception.SyntaxError("caught exception %s: %s" % (str(ty), str(va))) rd.choose_relativity(self.zone.origin, self.relativize) covers = rd.covers() rds = n.find_rdataset(rdclass, rdtype, covers, True) rds.add(rd, ttl)
def _rr_line(self): """Process one line from a DNS master file.""" # Name if self.current_origin is None: raise UnknownOrigin token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = name.from_text(token.value, self.current_origin) else: token = self.tok.get() if token.is_eol_or_eof(): # treat leading WS followed by EOL/EOF as if they were EOL/EOF. return self.tok.unget(token) name = self.last_name if not name.is_subdomain(self.zone.origin): self._eat_line() return if self.relativize: name = name.relativize(self.zone.origin) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError # TTL try: ttl = ttl.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except ttl.BadTTL: ttl = self.ttl # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: rdclass = self.zone.rdclass if rdclass != self.zone.rdclass: raise exception.SyntaxError("RR class is not zone's class") # Type try: rdtype = rdatatype.from_text(token.value) except: raise exception.SyntaxError("unknown rdatatype '%s'" % token.value) n = self.zone.nodes.get(name) if n is None: n = self.zone.node_factory() self.zone.nodes[name] = n try: rd = rdata.from_text(rdclass, rdtype, self.tok, self.current_origin, False) except exception.SyntaxError: # Catch and reraise. (ty, va) = sys.exc_info()[:2] raise va except: # All exceptions that occur in the processing of rdata # are treated as syntax errors. This is not strictly # correct, but it is correct almost all of the time. # We convert them to syntax errors so that we can emit # helpful filename:line info. (ty, va) = sys.exc_info()[:2] raise exception.SyntaxError("caught exception %s: %s" % (str(ty), str(va))) rd.choose_relativity(self.zone.origin, self.relativize) covers = rd.covers() rds = n.find_rdataset(rdclass, rdtype, covers, True) rds.add(rd, ttl)
def xfr(where, zone, rdtype=rdatatype.AXFR, rdclass=rdataclass.IN, timeout=None, port=53, keyring=None, keyname=None, relativize=True, af=None, lifetime=None, source=None, source_port=0, serial=0, use_udp=False, keyalgorithm=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: name.Name object or string @param rdtype: The type of zone transfer. The default is rdatatype.AXFR. @type rdtype: int or string @param rdclass: The class of the zone transfer. The default is 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: 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 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 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 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. 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 == 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 tsig.default_algorithm @type keyalgorithm: string """ if isinstance(zone, (str, unicode)): zone = name.from_text(zone) if isinstance(rdtype, (str, unicode)): rdtype = rdatatype.from_text(rdtype) q = message.make_query(zone, rdtype, rdclass) if rdtype == rdatatype.IXFR: rrset = rrset.from_text(zone, 0, 'IN', 'SOA', '. . %u 0 0 0 0' % serial) q.authority.append(rrset) if not keyring is None: q.use_tsig(keyring, keyname, algorithm=keyalgorithm) wire = q.to_wire() (af, destination, source) = _destination_and_source(af, where, port, source, source_port) if use_udp: if rdtype != rdatatype.IXFR: raise ValueError('cannot do a UDP AXFR') s = socket.socket(af, socket.SOCK_DGRAM, 0) else: s = socket.socket(af, socket.SOCK_STREAM, 0) s.setblocking(0) if source is not None: s.bind(source) expiration = _compute_expiration(lifetime) _connect(s, destination) l = len(wire) if use_udp: _wait_for_writable(s, expiration) s.send(wire) else: tcpmsg = struct.pack("!H", l) + wire _net_write(s, tcpmsg, expiration) done = False delete_mode = True expecting_SOA = False soa_rrset = None soa_count = 0 if relativize: origin = zone oname = name.empty else: origin = None oname = zone tsig_ctx = None first = True while not done: mexpiration = _compute_expiration(timeout) if mexpiration is None or mexpiration > expiration: mexpiration = expiration if use_udp: _wait_for_readable(s, expiration) (wire, from_address) = s.recvfrom(65535) else: ldata = _net_read(s, 2, mexpiration) (l, ) = struct.unpack("!H", ldata) wire = _net_read(s, l, mexpiration) r = message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, xfr=True, origin=origin, tsig_ctx=tsig_ctx, multi=True, first=first, one_rr_per_rrset=(rdtype == rdatatype.IXFR)) tsig_ctx = r.tsig_ctx first = False answer_index = 0 if soa_rrset is None: if not r.answer or r.answer[0].name != oname: raise exception.FormError("No answer or RRset not for qname") rrset = r.answer[0] if rrset.rdtype != rdatatype.SOA: raise exception.FormError("first RRset is not an SOA") answer_index = 1 soa_rrset = rrset.copy() if rdtype == rdatatype.IXFR: if soa_rrset[0].serial <= serial: # # We're already up-to-date. # done = True else: expecting_SOA = True # # Process SOAs in the answer section (other than the initial # SOA in the first message). # for rrset in r.answer[answer_index:]: if done: raise exception.FormError("answers after final SOA") if rrset.rdtype == rdatatype.SOA and rrset.name == oname: if expecting_SOA: if rrset[0].serial != serial: raise exception.FormError("IXFR base serial mismatch") expecting_SOA = False elif rdtype == rdatatype.IXFR: delete_mode = not delete_mode # # If this SOA RRset is equal to the first we saw then we're # finished. If this is an IXFR we also check that we're seeing # the record in the expected part of the response. # if rrset == soa_rrset and \ (rdtype == rdatatype.AXFR or \ (rdtype == rdatatype.IXFR and delete_mode)): done = True elif expecting_SOA: # # We made an IXFR request and are expecting another # SOA RR, but saw something else, so this must be an # AXFR response. # rdtype = rdatatype.AXFR expecting_SOA = False if done and q.keyring and not r.had_tsig: raise exception.FormError("missing TSIG") yield r s.close()