def iter_nested_ns(cls, zone): """ Yield NS/A records for nested zones :param zone: :return: """ suffix = ".%s." % zone.name length = len(zone.name) for z in zone.children: nested_nses = set() for ns in z.profile.authoritative_servers: # NS record ns_name = zone.get_ns_name(ns) yield RR( zone=zone.name, name=z.name[:-length - 1], ttl=zone.profile.zone_ttl, type="NS", rdata=ns_name, ) # Zone delegated to NS from the child zone if ns_name.endswith(suffix) and "." in ns_name[:-len(suffix)]: nested_nses.add((ns_name[:-len(suffix)], ns.ip)) # Yield glue A records for nested NSs for name, ip in nested_nses: yield RR(zone=zone.name, name=name, ttl=z.profile.zone_ttl, type="A", rdata=ip)
def __init__(self, data): records = data.get("records", []) if not records: raise ValueError("Zone must contain SOA record") self.zone = data["name"] if RR(zone=self.zone, **records[0]).type != "SOA": raise ValueError("First record must be SOA") self.records = [RR(zone=self.zone, **r) for r in records]
def iter_classless_delegation(cls, zone): """ Yield classless zone delegations :return: """ # Range delegations for r in AddressRange.objects.filter(action="D").extra( where=["from_address << %s", "to_address << %s"], params=[zone.reverse_prefix, zone.reverse_prefix]): nses = [ns.strip() for ns in r.reverse_nses.split(",")] for a in r.addresses: n = a.address.split(".")[-1] yield RR(zone=zone.name, name=n, ttl=zone.profile.zone_ttl, type="CNAME", rdata="%s.%s/32" % (n, n)) for ns in nses: if not ns.endswith("."): ns += "." yield RR(zone=zone.name, name="%s/32" % n, ttl=zone.profile.zone_ttl, type="NS", rdata=ns) # Subnet delegation macro delegations = defaultdict(list) for zr in DNSZoneRecord.objects.filter(zone=zone, type="NS", name__contains="/"): delegations[zr.name] += [zr.content] # Perform classless reverse zone delegation for d in delegations: nses = delegations[d] net, mask = [int(x) for x in d.split("/")] if net < 0 or net > 255 or mask <= 24 or mask > 32: continue # Invalid record for ns in nses: ns = str(ns) if not ns.endswith("."): ns += "." yield RR(zone=zone.name, name=d, ttl=zone.profile.zone_ttl, type="NS", rdata=ns) m = mask - 24 bitmask = ((1 << m) - 1) << (8 - m) if net & bitmask != net: continue # Invalid network for i in range(net, net + (1 << (8 - m))): yield RR(zone=zone.name, name=str(i), ttl=zone.profile.zone_ttl, type="CNAME", rdata="%d.%s" % (i, d))
def iter_soa(cls, zone): """ Yield SOA record :return: """ def dotted(s): if not s.endswith("."): return s + "." return s yield RR( zone=zone.name, name=dotted(zone.to_idna(zone.name)), ttl=zone.profile.zone_ttl, type="SOA", rdata="%s %s %d %d %d %d %d" % ( dotted(zone.profile.zone_soa), dotted(zone.profile.zone_contact), zone.serial, zone.profile.zone_refresh, zone.profile.zone_retry, zone.profile.zone_expire, zone.profile.zone_ttl, ), )
def iter_missed_ns_a(cls, zone): """ Yield missed A record for NS'es :param zone: :return: """ suffix = ".%s." % zone.name # Create missed A records for NSses from zone # Find in-zone NSes in_zone_nses = {} for ns in zone.profile.authoritative_servers: if not ns.ip: continue ns_name = zone.get_ns_name(ns) # NS server from zone if ns_name.endswith(suffix) and "." not in ns_name[:-len(suffix)]: in_zone_nses[ns_name[:-len(suffix)]] = ns.ip # Find missed in-zone NSes for name in in_zone_nses: yield RR( zone=zone.name, name=name, type="A", ttl=zone.profile.zone_ttl, rdata=in_zone_nses[name], )
def iter_ipam_ptr4(cls, zone): """ Yield IPv4 PTR records from IPAM :param zone: :return: """ def ptr(a): """ Convert address to full PTR record """ x = a.split(".") x.reverse() return "%s.in-addr.arpa" % (".".join(x)) length = len(zone.name) + 1 for a in Address.objects.filter(afi="4").extra( where=["address << %s"], params=[zone.reverse_prefix]): if not a.fqdn: continue yield RR( zone=zone.name, name=ptr(a.address)[:-length], ttl=zone.profile.zone_ttl, type="PTR", rdata=a.fqdn + ".", )
def iter_ns(cls, zone): """ Yield NS records :param zone: :return: """ for ns in zone.ns_list: yield RR(zone=zone.name, name="", ttl=zone.profile.zone_ttl, type="NS", rdata=ns)
def iter_ipam_ptr6(cls, zone): """ Yield IPv6 PTR records from IPAM :return: (name, type, content, ttl, prio) :return: """ origin_length = (len(zone.name) - 8 + 1) // 2 for a in Address.objects.filter(afi="6").extra( where=["address << %s"], params=[zone.reverse_prefix]): yield RR(zone=zone.name, name=IPv6(a.address).ptr(origin_length), ttl=zone.profile.zone_ttl, type="PTR", rdata=a.fqdn + ".")
def iter_rr(cls, zone): """ Yield directly set RRs from database :return: """ for zr in DNSZoneRecord.objects.filter(zone=zone): if "/" in zr.name: continue if not zr.type or not zr.content: continue yield RR(zone=zone.name, name=zr.name, type=zr.type, ttl=zr.ttl if zr.ttl else zone.profile.zone_ttl, priority=zr.priority, rdata=zr.content)
def iter_ipam_a(cls, zone): """ Yield A/AAAA records from IPAM :return: (name, type, content, ttl, prio) """ # @todo: Filter by VRF # @todo: Filter by profile # @todo: Get ttl from profile # Build query length = len(zone.name) + 1 q = (Q(fqdn__iexact=zone.name) | Q(fqdn__iendswith=".%s" % zone.name)) for z in DNSZone.objects.filter(name__iendswith=".%s" % zone.name).values_list("name", flat=True): q &= ~(Q(fqdn__iexact=z) | Q(fqdn__iendswith=".%s" % z)) for afi, fqdn, address in Address.objects.filter(q).values_list( "afi", "fqdn", "address"): yield RR(zone=zone.name, name=fqdn[:-length], type="A" if afi == "4" else "AAAA", ttl=zone.profile.zone_ttl, rdata=address)
def iter_bind_zone_rr(self, data): """ Parse bind-style zone and yields RRs :param data: Zone text """ # ttl = None zone = None ttl = None seen_soa = False for l in self.iter_zone_lines(data): if l.startswith("$TTL "): ttl = self.parse_ttl(l[5:]) continue if l.startswith("$ORIGIN "): zone = l[8:].strip() continue if not seen_soa: # Wait for SOA match = self.rx_soa.match(l) if match: z = match.group("zone") if z and z != "@": if z.endswith("."): zone = z else: zone = "%s.%s" % (z, zone) yield RR( zone=zone.strip("."), name="", type="SOA", rdata=" ".join(match.groups()[-7:]), ttl=ttl, ) seen_soa = True else: parts = l.split() if parts[0] == "IN" or parts[0] in self.RR_TYPES: # missed name parts = [""] + parts # Record ttl if is_int(parts[1]): rttl = int(parts.pop(1)) else: rttl = None if parts[1] == "IN": # Remove IN parts = [parts[0]] + parts[2:] # Normalize name name = parts[0] if name == "@" or not name: name = zone elif not name.endswith("."): name = name + "." + zone # Process value t = parts[1] v = parts[2:] if len(v) > 1 and is_int(v[0]): rprio = int(v[0]) v = v[1:] else: rprio = None value = " ".join(v) if t in ("CNAME", "PTR"): value = self.from_idna(value) elif t == "TXT": # Merge multiple values value = self.merge_mq(value) yield RR( zone=zone, name=self.from_idna(name), type=t, rdata=value, ttl=rttl, priority=rprio, )