mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 10:58:37 -06:00
Add reverse DNS
This commit is contained in:
parent
02e142e1de
commit
90ea56489d
55
netbox/ipam/migrations/0007_auto_20160722_0958.py
Normal file
55
netbox/ipam/migrations/0007_auto_20160722_0958.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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 %}
|
||||||
|
Loading…
Reference in New Issue
Block a user