diff --git a/main.py b/main.py index e4acf5d..90a4fe7 100755 --- a/main.py +++ b/main.py @@ -152,52 +152,28 @@ def write_dns_files(api_client): with open(filename, 'w+') as f: f.write(zone_file_content) -def network_to_arpanets(nets): - """En ipv4, on ne pouvait définir - que des plages reverse en /24, /16 ou /8. Cette fonction vise à retourner - une liste des plages en tenant compte de ce critère (donc de taille - 32/24/16/8) - - Ne touche à rien pour l'IPv6. - """ - subnets = [] - for net in nets: - if not isinstance(net, netaddr.IPNetwork): - net = netaddr.IPNetwork(net) - # on fragmente les subnets - # dans les tailles qui vont bien. - if net.prefixlen > 24: - subnets.extend(net.subnet(32)) - elif net.prefixlen > 16: - subnets.extend(net.subnet(24)) - elif net.prefixlen > 8: - subnets.extend(net.subnet(16)) - else: - subnets.extend(net.subnet(8)) - return subnets - - -def get_arpa(cidr): - """ - Renvoie la zone DNS inverse correspondant au réseau donné. - """ - net = netaddr.IPNetwork(cidr) - byte_size = 8 if net.version == 4 else 4 - addr_bytes = 4 if net.version == 4 else 32 - net_class = max(((net.prefixlen - 1) // byte_size) + 1, 1) - return ".".join( - netaddr.IPAddress(net.first).reverse_dns.split('.')[addr_bytes - net_class:] - ) def get_ip_reverse(ip, prefix_length): + """ Truncate an ip address given a prefix length """ ip = netaddr.IPAddress(ip) return '.'.join(ip.reverse_dns.split('.')[:prefix_length]) def write_dns_reverse_file(api_client): - for zone in api_client.list("dns/reverse-zones"): + """ Generate the reverve file for each reverse zone (= IpType) + For each IpType, we generate both an Ipv4 reverse and a v6. + The main issue is that we have to aggregat some IpTypes together, + because a reverse file can only describe a /24,/16 or /8. + """ + # We need to rember which zone file we already created for the v6 + # because some iptype may share the same prefix + # in which case we must append to the file zone already created + zone_v6 = [] + + for zone in api_client.list("dns/reverse-zones"): + # We start by defining the soa, ns, mx which are comon to v4/v6 now = datetime.datetime.now(datetime.timezone.utc) serial = now.strftime("%Y%m%d") + str(int(100*(now.hour*3600 + now.minute*60 + now.second)/86400)) @@ -215,47 +191,70 @@ def write_dns_reverse_file(api_client): target=x['target']) for x in zone['mx_records'] ) - - ### Start with ipv4 reverse - net = network_to_arpanets(zone['cidrs'])[0] - # Prend la première adresse ip de la plage, sauf si une est fournie - _address = netaddr.IPAddress(net.first) - # retourne le reverse splitté. (un reverse ressemble à 0.136.231.138.in-addr.arpa.) - rev_dns_a = _address.reverse_dns.split('.')[:-1] - # En v4, le reverse étant de la forme 0.136.231.138.in-addr.arpa., soit - # on a un /8, soit un /16, soit un /24. - if net.prefixlen == 8: - zone_name,prefix_length = ('.'.join(rev_dns_a[3:]), 3) - elif net.prefixlen == 16: - zone_name,prefix_length = ('.'.join(rev_dns_a[2:]), 2) - elif net.prefixlen == 24: - zone_name,prefix_length = ('.'.join(rev_dns_a[1:]), 1) - soa = template_soa.format(zone=zone_name, - mail=soa_mail, - serial=serial, - refresh=zone['soa']['refresh'], - retry=zone['soa']['retry'], - expire=zone['soa']['expire'], - ttl=zone['soa']['ttl']) - ptr_records = "\n".join( - template_ptr.format(hostname=host['hostname']+extension, - target=get_ip_reverse(host['ipv4'],prefix_length)) - for host in zone['ptr_records'] if host['ipv4'] - ) - zone_file_content = template_reverse.format(soa=soa, - ns_records=ns_records, - mx_records=mx_records, - ptr_records = ptr_records) + ### We start with the v4 + # We setup the network from the cidrs of the IpType + + # For the ipv4, we need to agregate the subnets together, because + # we can only have reverse for /24, /16 and /8. + subnets = [] + for net in zone['cidrs']: + net = netaddr.IPNetwork(net) + # on fragmente les subnets + # dans les tailles qui vont bien. + if net.prefixlen > 24: + subnets.extend(net.subnet(32)) + elif net.prefixlen > 16: + subnets.extend(net.subnet(24)) + elif net.prefixlen > 8: + subnets.extend(net.subnet(16)) + else: + subnets.extend(net.subnet(8)) + + for subnet in subnets: + # Then, using the first ip address of the subnet and the + # prefix length, we can obtain the name of the reverse zone + _address = netaddr.IPAddress(subnet.first) + rev_dns_a = _address.reverse_dns.split('.')[:-1] + if subnet.prefixlen == 8: + zone_name,prefix_length = ('.'.join(rev_dns_a[3:]), 3) + elif subnet.prefixlen == 16: + zone_name,prefix_length = ('.'.join(rev_dns_a[2:]), 2) + elif subnet.prefixlen == 24: + zone_name,prefix_length = ('.'.join(rev_dns_a[1:]), 1) + + soa = template_soa.format(zone=zone_name, + mail=soa_mail, + serial=serial, + refresh=zone['soa']['refresh'], + retry=zone['soa']['retry'], + expire=zone['soa']['expire'], + ttl=zone['soa']['ttl']) + ptr_records = "\n".join( + template_ptr.format(hostname=host['hostname']+extension, + target=get_ip_reverse(host['ipv4'],prefix_length)) + for host in zone['ptr_records'] if host['ipv4'] in subnet + ) + zone_file_content = template_reverse.format(soa=soa, + ns_records=ns_records, + mx_records=mx_records, + ptr_records = ptr_records) + + filename = 'dns.{zone}zone'.format(zone=zone_name) + with open(filename, 'w+') as f: + f.write(zone_file_content) - filename = 'dns.{zone}zone'.format(zone=zone_name) - with open(filename, 'w+') as f: - f.write(zone_file_content) ### Continue with the ipv6 reverse # hack because we do not have the /64 info - zone6_name = get_arpa(zone['prefix_v6']+"/64") + net = netaddr.IPNetwork(zone['prefix_v6']+"/64") + net_class = max(((net.prefixlen - 1) // 4) + 1, 1) + zone6_name = ".".join( + netaddr.IPAddress(net.first).reverse_dns.split('.')[32 - net_class:] + ) + + soa = template_soa.format(zone=zone6_name, mail=soa_mail, serial=serial, @@ -271,16 +270,29 @@ def write_dns_reverse_file(api_client): target=get_ip_reverse(ip['ipv6'],prefix_length)) for host in zone['ptr_v6_records'] for ip in host['ipv6'] ) - - zone_file_content = template_reverse.format(soa=soa, - ns_records=ns_records, - mx_records=mx_records, - ptr_records = ptr_records) + if zone6_name in zone_v6: + # we already created the file, we ignore the soa + zone_file_content = template_reverse.format(soa="", + ns_records=ns_records, + mx_records=mx_records, + ptr_records = ptr_records) - filename = 'dns.{zone}zone'.format(zone=zone6_name) - with open(filename, 'w+') as f: - f.write(zone_file_content) + filename = 'dns.{zone}zone'.format(zone=zone6_name) + with open(filename, 'a') as f: + f.write(zone_file_content) + else: + # we create the file from scratch + zone_file_content = template_reverse.format(soa=soa, + ns_records=ns_records, + mx_records=mx_records, + ptr_records = ptr_records) + + + filename = 'dns.{zone}zone'.format(zone=zone6_name) + with open(filename, 'w+') as f: + f.write(zone_file_content) + zone_v6.append(zone6_name) api_client = Re2oAPIClient(api_hostname, api_username, api_password)