Add reverse DNS

This commit is contained in:
rdujardin 2016-07-22 16:01:19 +02:00
parent 02e142e1de
commit 90ea56489d
4 changed files with 230 additions and 3 deletions

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-22 09:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ipam', '0006_auto_20160720_0941'),
]
operations = [
migrations.AddField(
model_name='prefix',
name='soa_contact',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='prefix',
name='soa_expire',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='prefix',
name='soa_minimum',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='prefix',
name='soa_name',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='prefix',
name='soa_refresh',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='prefix',
name='soa_retry',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='prefix',
name='soa_serial',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='prefix',
name='ttl',
field=models.PositiveIntegerField(blank=True, null=True),
),
]

View File

@ -8,9 +8,12 @@ from django.db import models
from dcim.models import Interface from dcim.models import Interface
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
import dns.models
from .fields import IPNetworkField, IPAddressField from .fields import IPNetworkField, IPAddressField
import time, ipaddress
AF_CHOICES = ( AF_CHOICES = (
(4, 'IPv4'), (4, 'IPv4'),
@ -233,10 +236,21 @@ class Prefix(CreatedUpdatedModel):
verbose_name='VLAN') verbose_name='VLAN')
status = models.PositiveSmallIntegerField('Status', choices=PREFIX_STATUS_CHOICES, default=1) status = models.PositiveSmallIntegerField('Status', choices=PREFIX_STATUS_CHOICES, default=1)
role = models.ForeignKey('Role', related_name='prefixes', on_delete=models.SET_NULL, blank=True, null=True) role = models.ForeignKey('Role', related_name='prefixes', on_delete=models.SET_NULL, blank=True, null=True)
description = models.CharField(max_length=100, blank=True)
objects = PrefixQuerySet.as_manager() objects = PrefixQuerySet.as_manager()
#Reverse DNS
ttl = models.PositiveIntegerField(blank=True, null=True)
soa_name = models.CharField(max_length=100, blank=True)
soa_contact = models.CharField(max_length=100, blank=True)
soa_serial = models.CharField(max_length=100, blank=True)
soa_refresh = models.PositiveIntegerField(blank=True, null=True)
soa_retry = models.PositiveIntegerField(blank=True, null=True)
soa_expire = models.PositiveIntegerField(blank=True, null=True)
soa_minimum = models.PositiveIntegerField(blank=True, null=True)
description = models.CharField(max_length=100, blank=True)
class Meta: class Meta:
ordering = ['family', 'prefix'] ordering = ['family', 'prefix']
verbose_name_plural = 'prefixes' verbose_name_plural = 'prefixes'
@ -257,6 +271,7 @@ class Prefix(CreatedUpdatedModel):
"instead.") "instead.")
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.update_serial()
if self.prefix: if self.prefix:
# Clear host bits from prefix # Clear host bits from prefix
self.prefix = self.prefix.cidr self.prefix = self.prefix.cidr
@ -264,6 +279,24 @@ class Prefix(CreatedUpdatedModel):
self.family = self.prefix.version self.family = self.prefix.version
super(Prefix, self).save(*args, **kwargs) super(Prefix, self).save(*args, **kwargs)
def update_serial(self):
"""
Each time a record or the zone is modified, the serial is incremented.
"""
current_date = time.strftime('%Y%m%d',time.localtime())
if not self.soa_serial:
self.soa_serial = current_date+'1'
else:
serial_date = self.soa_serial[:8]
serial_num = self.soa_serial[8:]
if serial_date!=current_date:
self.soa_serial = current_date+'1'
else:
serial_num = int(serial_num)
serial_num += 1
self.soa_serial = current_date + str(serial_num)
def to_csv(self): def to_csv(self):
return ','.join([ return ','.join([
str(self.prefix), str(self.prefix),
@ -288,6 +321,92 @@ class Prefix(CreatedUpdatedModel):
def get_status_class(self): def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status] return STATUS_CHOICE_CLASSES[self.status]
def to_bind(self,ipaddresses):
zones = {}
def header (zone_id): return '\n'.join([
'; '+zone_id,
'; gen from prefix '+str(self.prefix)+' ('+(self.description if self.description else '')+') by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) ',
'',
'$TTL '+str(self.ttl),
self.soa_name.ljust(30)+' IN '+'SOA '+self.soa_contact+' (',
' '+self.soa_serial.ljust(30)+' ; serial',
' '+str(self.soa_refresh).ljust(30)+' ; refresh',
' '+str(self.soa_retry).ljust(30)+' ; retry',
' '+str(self.soa_expire).ljust(30)+' ; expire',
' '+str(self.soa_minimum).ljust(29)+') ; minimum',
'',
'',
'',
'$ORIGIN '+zone_id,
'',
'',
])
if self.prefix.version == 4:
pbytes = str(self.prefix).split('/')[0].split('.')
pslash = int(str(self.prefix).split('/')[1])
if pslash > 16:
# create /24 zones
ztype = 24
else:
# create /16 zones
ztype = 16
for ip in ipaddresses:
ibytes = str(ip.address).split('/')[0].split('.')
islash = str(ip.address).split('/')[1]
if ztype == 24:
zone_id = ibytes[2]+'.'+ibytes[1]+'.'+ibytes[0]+'.in-addr.arpa.'
if not zone_id in zones:
zones[zone_id] = header(zone_id)
zones[zone_id] += ibytes[3].ljust(3) + ' IN PTR ' + ip.hostname.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) \n'
else:
zone_id = ibytes[1]+'.'+ibytes[0]+'.in-addr.arpa.'
if not zone_id in zones:
zones[zone_id] = header(zone_id)
zones[zone_id] += (ibytes[3]+'.'+ibytes[2]).ljust(7) + ' IN PTR ' + ip.hostname.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) \n'
else:
pfull = str(ipaddress.IPv6Address(str(self.prefix).split('/')[0]).exploded)
pnibbles = pfull.split(':')
pdigits = pfull.replace(':','')
pslash = int(str(self.prefix).split('/')[1])
ztype = pslash if pslash % 16 == 0 else pslash/16+16
for ip in ipaddresses:
ifull = str(ipaddress.IPv6Address(str(ip.address).split('/')[0]).exploded)
inibbles = ifull.split(':')
idigits = ifull.replace(':','').reverse
islash = int(str(ip.address).split('/')[1])
pdigitszone = pdigits[:ztype/4].reverse
zone_id = '.'.join(pdigitszone)+'.ip6.arpa.'
if not zone_id in zones:
zones[zone_id] = header(zone_id)
zones[zone_id] += ('.'.join(idigits[:32-ztype/4])).ljust(30)+' IN PTR ' + ip.hostname.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) \n'
for z in zones:
z += '\n\n; end '
ret = []
for zid,zc in zones.items():
ret.append({
'num': len(ret),
'id': zid,
'content': zc,
})
return ret
class IPAddress(CreatedUpdatedModel): class IPAddress(CreatedUpdatedModel):
""" """
@ -341,6 +460,9 @@ class IPAddress(CreatedUpdatedModel):
if self.address: if self.address:
# Infer address family from IPAddress object # Infer address family from IPAddress object
self.family = self.address.version self.family = self.address.version
dns_records = dns.models.Record.objects.filter(address=self)
for r in dns_records:
r.save()
super(IPAddress, self).save(*args, **kwargs) super(IPAddress, self).save(*args, **kwargs)
def to_csv(self): def to_csv(self):

View File

@ -276,8 +276,14 @@ def prefix(request, pk):
except Aggregate.DoesNotExist: except Aggregate.DoesNotExist:
aggregate = None aggregate = None
child_ip = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix))
# Count child IP addresses # Count child IP addresses
ipaddress_count = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix)).count() ipaddress_count = child_ip.count()
# BIND reverse export
bind_export = prefix.to_bind(child_ip)
bind_export_count = len(bind_export)
# Parent prefixes table # Parent prefixes table
parent_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix__net_contains=str(prefix.prefix))\ parent_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix__net_contains=str(prefix.prefix))\
@ -307,6 +313,8 @@ def prefix(request, pk):
'parent_prefix_table': parent_prefix_table, 'parent_prefix_table': parent_prefix_table,
'child_prefix_table': child_prefix_table, 'child_prefix_table': child_prefix_table,
'duplicate_prefix_table': duplicate_prefix_table, 'duplicate_prefix_table': duplicate_prefix_table,
'bind_export': bind_export,
'bind_export_count': bind_export_count,
}) })
@ -481,7 +489,10 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]: if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field] fields_to_update[field] = form.cleaned_data[field]
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update) iplist = self.cls.objects.filter(pk__in=pk_list)
for ip in iplist:
ip.save() # in order to update dns zones serials
return iplist.update(**fields_to_update)
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):

View File

@ -113,4 +113,43 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="row" style="margin-top: 30px;">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong class="text-md-left">BIND Reverse Exports</strong>
</div>
<table class="table table-hover panel-body">
{% for z in bind_export %}
<tr><td>
<strong class="text-md-left">{{ z.id }}</strong>
<a class="pull-right" id="bind_export_select_{{ z.num }}" href="#">Select</a>
</td></tr>
<tr><td>
<p style="text-overflow: scroll;"><pre id="bind_export_{{ z.num }}">{{ z.content }}</pre></p>
</td></tr>
{% endfor %}
</table>
</div>
</div>
</div>
{% endblock %}
{% block javascript %}
<script>
for(var i=0;i<{{ bind_export_count }};i++) {
$('#bind_export_select_'+i).click(function(e){
e.preventDefault();
if(document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(document.getElementById('bind_export_'+i));
range.select();
}
else if(window.getSelection) {
var range = document.createRange();
range.selectNode(document.getElementById('bind_export'+i));
window.getSelection().addRange(range);
}
});
}
</script>
{% endblock %} {% endblock %}