netbox-ddns 1.5.0__tar.gz → 1.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {netbox_ddns-1.5.0/netbox_ddns.egg-info → netbox_ddns-1.7.0}/PKG-INFO +7 -5
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/__init__.py +3 -3
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/api/serializers.py +1 -1
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/background_tasks.py +28 -5
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/forms.py +2 -2
- netbox_ddns-1.7.0/netbox_ddns/migrations/0014_alter_extradnsname_name.py +19 -0
- netbox_ddns-1.7.0/netbox_ddns/migrations/0015_server_protocol.py +18 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/models.py +15 -3
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/navigation.py +4 -0
- netbox_ddns-1.7.0/netbox_ddns/tables.py +96 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/template_content.py +9 -0
- netbox_ddns-1.7.0/netbox_ddns/templates/netbox_ddns/managed_dns_names.html +17 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/server.html +7 -1
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/urls.py +8 -2
- netbox_ddns-1.7.0/netbox_ddns/utils.py +123 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/validators.py +6 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/views.py +21 -3
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0/netbox_ddns.egg-info}/PKG-INFO +7 -5
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/SOURCES.txt +3 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/setup.cfg +4 -3
- netbox_ddns-1.5.0/netbox_ddns/tables.py +0 -51
- netbox_ddns-1.5.0/netbox_ddns/utils.py +0 -28
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/LICENSE.txt +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/MANIFEST.in +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/README.md +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/admin.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/api/__init__.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/api/urls.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/api/views.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/filtersets.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0001_initial.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0002_add_ttl.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0003_dnsstatus.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0004_ensure_trailing_dot.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0005_extradnsname.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0006_extradns_cname.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0007_zone_meta.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0008_server_server_port.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0009_alter_dnsstatus_id_alter_extradnsname_id_and_more.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0010_extradnsname_created_extradnsname_custom_field_data_and_more.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0012_zone_created_zone_custom_field_data_and_more.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0013_reversezone_created_reversezone_custom_field_data_and_more.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/__init__.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/search.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/signals.py +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/extradnsname.html +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/ipaddress/dns_extra.html +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/ipaddress/dns_info.html +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/ipaddress/dns_refresh_button.html +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/reversezone.html +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/update_reverse_zone.html +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/update_zone.html +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/zone.html +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/dependency_links.txt +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/not-zip-safe +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/requires.txt +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/top_level.txt +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/pyproject.toml +0 -0
- {netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/setup.py +0 -0
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-ddns
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Dynamic DNS Connector for NetBox
|
|
5
5
|
Home-page: https://github.com/sjm-steffann/netbox-ddns
|
|
6
6
|
Author: Sander Steffann
|
|
7
7
|
Author-email: sander@steffann.nl
|
|
8
8
|
License: Apache 2.0
|
|
9
|
-
Classifier: Development Status ::
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
10
|
Classifier: Framework :: Django
|
|
11
11
|
Classifier: Framework :: Django :: 3.0
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.
|
|
15
|
-
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.11
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
17
18
|
License-File: LICENSE.txt
|
|
18
19
|
Requires-Dist: setuptools
|
|
19
20
|
Requires-Dist: dnspython
|
|
21
|
+
Dynamic: license-file
|
|
20
22
|
|
|
21
23
|
# Dynamic DNS Connector for NetBox
|
|
22
24
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
VERSION = '1.
|
|
1
|
+
VERSION = '1.7.0'
|
|
2
2
|
|
|
3
3
|
try:
|
|
4
4
|
from netbox.plugins import PluginConfig
|
|
@@ -12,8 +12,8 @@ class NetBoxDDNSConfig(PluginConfig):
|
|
|
12
12
|
name = 'netbox_ddns'
|
|
13
13
|
verbose_name = 'Dynamic DNS'
|
|
14
14
|
version = VERSION
|
|
15
|
-
min_version = '4.
|
|
16
|
-
max_version = '4.
|
|
15
|
+
min_version = '4.4.0'
|
|
16
|
+
max_version = '4.5.999'
|
|
17
17
|
author = 'Sander Steffann'
|
|
18
18
|
author_email = 'sander@steffann.nl'
|
|
19
19
|
description = 'Dynamic DNS Connector for NetBox'
|
|
@@ -26,7 +26,7 @@ class ServerSerializer(NetBoxModelSerializer):
|
|
|
26
26
|
class ZoneSerializer(NetBoxModelSerializer):
|
|
27
27
|
class Meta:
|
|
28
28
|
model = Zone
|
|
29
|
-
fields = ('name', 'ttl', 'server')
|
|
29
|
+
fields = ('name', 'ttl', 'server', 'protocol')
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class ReverseZoneSerializer(NetBoxModelSerializer):
|
|
@@ -9,7 +9,7 @@ from django_rq import job
|
|
|
9
9
|
from dns import rcode
|
|
10
10
|
from netaddr import ip
|
|
11
11
|
|
|
12
|
-
from netbox_ddns.models import ACTION_CREATE, ACTION_DELETE, DNSStatus, RCODE_NO_ZONE, ReverseZone, Zone
|
|
12
|
+
from netbox_ddns.models import ACTION_CREATE, ACTION_DELETE, DNSStatus, RCODE_NO_ZONE, ReverseZone, Zone, Protocol
|
|
13
13
|
from netbox_ddns.utils import get_soa
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger('netbox_ddns')
|
|
@@ -28,6 +28,17 @@ def status_update(output: List[str], operation: str, response) -> None:
|
|
|
28
28
|
output.append(message)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def send_dns_update(update, server, protocol):
|
|
32
|
+
"""Send DNS update via TCP or UDP. Returns response or None on unknown protocol."""
|
|
33
|
+
if protocol == Protocol.TCP:
|
|
34
|
+
return dns.query.tcp(update, server.address, port=server.server_port)
|
|
35
|
+
elif protocol == Protocol.UDP:
|
|
36
|
+
return dns.query.udp(update, server.address, port=server.server_port)
|
|
37
|
+
else:
|
|
38
|
+
logger.error(f"Unknown protocol {protocol} for server {server}")
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
31
42
|
def create_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSStatus], output: List[str]):
|
|
32
43
|
if status:
|
|
33
44
|
status.forward_action = ACTION_CREATE
|
|
@@ -38,6 +49,7 @@ def create_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
|
|
|
38
49
|
|
|
39
50
|
# Check the SOA, we don't want to write to a parent zone if it has delegated authority
|
|
40
51
|
soa = get_soa(zone.name)
|
|
52
|
+
protocol = zone.server.protocol
|
|
41
53
|
if soa == zone.name:
|
|
42
54
|
record_type = 'A' if address.version == 4 else 'AAAA'
|
|
43
55
|
update = zone.server.create_update(zone.name)
|
|
@@ -47,7 +59,9 @@ def create_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
|
|
|
47
59
|
record_type,
|
|
48
60
|
str(address)
|
|
49
61
|
)
|
|
50
|
-
response =
|
|
62
|
+
response = send_dns_update(update, zone.server, protocol)
|
|
63
|
+
if response is None:
|
|
64
|
+
return
|
|
51
65
|
status_update(output, f'Adding {dns_name} {record_type} {address}', response)
|
|
52
66
|
if status:
|
|
53
67
|
status.forward_rcode = response.rcode()
|
|
@@ -72,6 +86,7 @@ def delete_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
|
|
|
72
86
|
|
|
73
87
|
# Check the SOA, we don't want to write to a parent zone if it has delegated authority
|
|
74
88
|
soa = get_soa(zone.name)
|
|
89
|
+
protocol = zone.server.protocol
|
|
75
90
|
if soa == zone.name:
|
|
76
91
|
record_type = 'A' if address.version == 4 else 'AAAA'
|
|
77
92
|
update = zone.server.create_update(zone.name)
|
|
@@ -80,7 +95,9 @@ def delete_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
|
|
|
80
95
|
record_type,
|
|
81
96
|
str(address)
|
|
82
97
|
)
|
|
83
|
-
response =
|
|
98
|
+
response = send_dns_update(update, zone.server, protocol)
|
|
99
|
+
if response is None:
|
|
100
|
+
return
|
|
84
101
|
status_update(output, f'Deleting {dns_name} {record_type} {address}', response)
|
|
85
102
|
if status:
|
|
86
103
|
status.forward_rcode = response.rcode()
|
|
@@ -106,6 +123,7 @@ def create_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
|
|
|
106
123
|
|
|
107
124
|
# Check the SOA, we don't want to write to a parent zone if it has delegated authority
|
|
108
125
|
soa = get_soa(record_name)
|
|
126
|
+
protocol = zone.server.protocol
|
|
109
127
|
if soa == zone.name:
|
|
110
128
|
update = zone.server.create_update(zone.name)
|
|
111
129
|
update.add(
|
|
@@ -114,7 +132,9 @@ def create_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
|
|
|
114
132
|
'ptr',
|
|
115
133
|
dns_name
|
|
116
134
|
)
|
|
117
|
-
response =
|
|
135
|
+
response = send_dns_update(update, zone.server, protocol)
|
|
136
|
+
if response is None:
|
|
137
|
+
return
|
|
118
138
|
status_update(output, f'Adding {record_name} PTR {dns_name}', response)
|
|
119
139
|
if status:
|
|
120
140
|
status.reverse_rcode = response.rcode()
|
|
@@ -140,6 +160,7 @@ def delete_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
|
|
|
140
160
|
|
|
141
161
|
# Check the SOA, we don't want to write to a parent zone if it has delegated authority
|
|
142
162
|
soa = get_soa(record_name)
|
|
163
|
+
protocol = zone.server.protocol
|
|
143
164
|
if soa == zone.name:
|
|
144
165
|
update = zone.server.create_update(zone.name)
|
|
145
166
|
update.delete(
|
|
@@ -147,7 +168,9 @@ def delete_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
|
|
|
147
168
|
'ptr',
|
|
148
169
|
dns_name
|
|
149
170
|
)
|
|
150
|
-
response =
|
|
171
|
+
response = send_dns_update(update, zone.server, protocol)
|
|
172
|
+
if response is None:
|
|
173
|
+
return
|
|
151
174
|
status_update(output, f'Deleting {record_name} PTR {dns_name}', response)
|
|
152
175
|
if status:
|
|
153
176
|
status.reverse_rcode = response.rcode()
|
|
@@ -34,13 +34,13 @@ class ZoneBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
34
34
|
|
|
35
35
|
class ServerForm(NetBoxModelForm):
|
|
36
36
|
fieldsets = (
|
|
37
|
-
FieldSet('server', 'server_port', name='Server'),
|
|
37
|
+
FieldSet('server', 'server_port', 'protocol', name='Server'),
|
|
38
38
|
FieldSet('tsig_key_name', 'tsig_algorithm', "tsig_key", name='Authentication'),
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
class Meta:
|
|
42
42
|
model = Server
|
|
43
|
-
fields = ('server', 'server_port', 'tsig_key_name', 'tsig_algorithm', "tsig_key")
|
|
43
|
+
fields = ('server', 'server_port', 'protocol', 'tsig_key_name', 'tsig_algorithm', "tsig_key")
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class ExtraDNSNameIPAddressForm(NetBoxModelForm):
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Generated by Django 5.2.5 on 2025-09-01 09:17
|
|
2
|
+
|
|
3
|
+
import netbox_ddns.validators
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('netbox_ddns', '0013_reversezone_created_reversezone_custom_field_data_and_more'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name='extradnsname',
|
|
16
|
+
name='name',
|
|
17
|
+
field=models.CharField(max_length=255, validators=[netbox_ddns.validators.DNSNameValidator()]),
|
|
18
|
+
),
|
|
19
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 5.2.6 on 2025-10-01 14:44
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('netbox_ddns', '0014_alter_extradnsname_name'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name='server',
|
|
15
|
+
name='protocol',
|
|
16
|
+
field=models.CharField(default='udp', max_length=3),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -18,7 +18,7 @@ from ipam.fields import IPNetworkField
|
|
|
18
18
|
from ipam.models import IPAddress
|
|
19
19
|
from utilities.querysets import RestrictedQuerySet
|
|
20
20
|
from .utils import normalize_fqdn
|
|
21
|
-
from .validators import HostnameAddressValidator, HostnameValidator, validate_base64, MinValueValidator, \
|
|
21
|
+
from .validators import DNSNameValidator, HostnameAddressValidator, HostnameValidator, validate_base64, MinValueValidator, \
|
|
22
22
|
MaxValueValidator
|
|
23
23
|
|
|
24
24
|
logger = logging.getLogger('netbox_ddns')
|
|
@@ -64,6 +64,9 @@ def get_rcode_display(code):
|
|
|
64
64
|
else:
|
|
65
65
|
return _('Unknown response: {}').format(code)
|
|
66
66
|
|
|
67
|
+
class Protocol(models.TextChoices):
|
|
68
|
+
UDP = 'udp', _('UDP')
|
|
69
|
+
TCP = 'tcp', _('TCP')
|
|
67
70
|
|
|
68
71
|
class Server(NetBoxModel):
|
|
69
72
|
server = models.CharField(
|
|
@@ -95,6 +98,12 @@ class Server(NetBoxModel):
|
|
|
95
98
|
validators=[validate_base64],
|
|
96
99
|
help_text=_('in base64 notation'),
|
|
97
100
|
)
|
|
101
|
+
protocol = models.CharField(
|
|
102
|
+
verbose_name=_('Protocol'),
|
|
103
|
+
max_length=3,
|
|
104
|
+
choices=Protocol.choices,
|
|
105
|
+
default=Protocol.UDP,
|
|
106
|
+
)
|
|
98
107
|
|
|
99
108
|
class Meta:
|
|
100
109
|
unique_together = (
|
|
@@ -119,7 +128,10 @@ class Server(NetBoxModel):
|
|
|
119
128
|
|
|
120
129
|
@property
|
|
121
130
|
def address(self) -> Optional[str]:
|
|
122
|
-
|
|
131
|
+
addrinfo = socket.getaddrinfo(self.server, self.server_port, proto=socket.IPPROTO_UDP)
|
|
132
|
+
for family, _, _, _, sockaddr in addrinfo:
|
|
133
|
+
if family in (socket.AF_INET, socket.AF_INET6) and sockaddr[0]:
|
|
134
|
+
return sockaddr[0]
|
|
123
135
|
|
|
124
136
|
def create_update(self, zone: str) -> dns.update.Update:
|
|
125
137
|
return dns.update.Update(
|
|
@@ -374,7 +386,7 @@ class ExtraDNSName(NetBoxModel):
|
|
|
374
386
|
name = models.CharField(
|
|
375
387
|
verbose_name=_('DNS name'),
|
|
376
388
|
max_length=255,
|
|
377
|
-
validators=[
|
|
389
|
+
validators=[DNSNameValidator()],
|
|
378
390
|
)
|
|
379
391
|
|
|
380
392
|
last_update = models.DateTimeField(
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import django_tables2 as tables
|
|
2
|
+
from django_tables2 import LinkColumn, RelatedLinkColumn
|
|
3
|
+
|
|
4
|
+
from netbox_ddns.models import ExtraDNSName, Server, ReverseZone, Zone
|
|
5
|
+
|
|
6
|
+
from netbox.tables import NetBoxTable
|
|
7
|
+
|
|
8
|
+
MANAGED_DNS_ACTIONS = """
|
|
9
|
+
<a href="{{ record.view_url }}" class="btn btn-sm btn-outline-primary" title="View">
|
|
10
|
+
<i class="mdi mdi-eye"></i>
|
|
11
|
+
</a>
|
|
12
|
+
<a href="{{ record.edit_url }}" class="btn btn-sm btn-outline-warning" title="Edit">
|
|
13
|
+
<i class="mdi mdi-pencil"></i>
|
|
14
|
+
</a>
|
|
15
|
+
{% if record.delete_url %}
|
|
16
|
+
<a href="{{ record.delete_url }}" class="btn btn-sm btn-outline-danger" title="Delete">
|
|
17
|
+
<i class="mdi mdi-delete"></i>
|
|
18
|
+
</a>
|
|
19
|
+
{% endif %}
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
FORWARD_DNS = """
|
|
23
|
+
{% if record.forward_action is not None %}
|
|
24
|
+
{{ record.get_forward_action_display }}:
|
|
25
|
+
{{ record.get_forward_rcode_html_display }}
|
|
26
|
+
{% else %}
|
|
27
|
+
<span class="text-muted">Not created</span>
|
|
28
|
+
{% endif %}
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ReverseZoneTable(NetBoxTable):
|
|
33
|
+
name = LinkColumn()
|
|
34
|
+
server = RelatedLinkColumn()
|
|
35
|
+
|
|
36
|
+
class Meta(NetBoxTable.Meta):
|
|
37
|
+
model = ReverseZone
|
|
38
|
+
fields = ("id", "name", "prefix", "server", "ttl")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ZoneTable(NetBoxTable):
|
|
42
|
+
name = LinkColumn()
|
|
43
|
+
server = RelatedLinkColumn()
|
|
44
|
+
|
|
45
|
+
class Meta(NetBoxTable.Meta):
|
|
46
|
+
model = Zone
|
|
47
|
+
fields = ("id", "name", "server", "ttl")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ServerTable(NetBoxTable):
|
|
51
|
+
server = LinkColumn()
|
|
52
|
+
|
|
53
|
+
class Meta(NetBoxTable.Meta):
|
|
54
|
+
model = Server
|
|
55
|
+
fields = ("id", "server", "server_port", "protocol", "tsig_key_name", "tsig_algorithm")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ExtraDNSNameTable(NetBoxTable):
|
|
59
|
+
ip_address = RelatedLinkColumn()
|
|
60
|
+
name = LinkColumn()
|
|
61
|
+
forward_dns = tables.TemplateColumn(template_code=FORWARD_DNS)
|
|
62
|
+
|
|
63
|
+
class Meta(NetBoxTable.Meta):
|
|
64
|
+
model = ExtraDNSName
|
|
65
|
+
fields = ('id', 'name', 'ip_address', 'last_update', 'forward_dns')
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ManagedDNSNameTable(tables.Table):
|
|
69
|
+
"""Table for primary DNS names (from IPAddress.dns_name)."""
|
|
70
|
+
dns_name = tables.Column(
|
|
71
|
+
accessor='dns_name',
|
|
72
|
+
linkify=lambda record: record.view_url,
|
|
73
|
+
)
|
|
74
|
+
ip_address = tables.Column(
|
|
75
|
+
accessor='ip_address',
|
|
76
|
+
linkify=lambda record: record.ip_address.get_absolute_url(),
|
|
77
|
+
)
|
|
78
|
+
zone = tables.Column(
|
|
79
|
+
accessor='zone',
|
|
80
|
+
linkify=lambda record: record.zone.get_absolute_url() if record.zone else None,
|
|
81
|
+
)
|
|
82
|
+
forward_status = tables.TemplateColumn(
|
|
83
|
+
template_code='{{ record.forward_status_html|safe }}',
|
|
84
|
+
verbose_name='Forward DNS',
|
|
85
|
+
)
|
|
86
|
+
actions = tables.TemplateColumn(
|
|
87
|
+
template_code=MANAGED_DNS_ACTIONS,
|
|
88
|
+
verbose_name='Actions',
|
|
89
|
+
orderable=False,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def render_zone(self, value):
|
|
93
|
+
return value if value else '—'
|
|
94
|
+
|
|
95
|
+
class Meta:
|
|
96
|
+
attrs = {'class': 'table table-hover object-list'}
|
|
@@ -6,7 +6,10 @@ from . import tables
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ReverseZoneRecreate(PluginTemplateExtension):
|
|
9
|
+
# NetBox up to 4.1
|
|
9
10
|
model = 'netbox_ddns.reversezone'
|
|
11
|
+
# NetBox from 4.2, required to be present as of NetBox 4.3
|
|
12
|
+
models = [model]
|
|
10
13
|
|
|
11
14
|
def buttons(self):
|
|
12
15
|
"""
|
|
@@ -20,7 +23,10 @@ class ReverseZoneRecreate(PluginTemplateExtension):
|
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class ZoneRecreate(PluginTemplateExtension):
|
|
26
|
+
# NetBox up to 4.1
|
|
23
27
|
model = 'netbox_ddns.zone'
|
|
28
|
+
# NetBox from 4.2, required to be present as of NetBox 4.3
|
|
29
|
+
models = [model]
|
|
24
30
|
|
|
25
31
|
def buttons(self):
|
|
26
32
|
"""
|
|
@@ -34,7 +40,10 @@ class ZoneRecreate(PluginTemplateExtension):
|
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
class DNSInfo(PluginTemplateExtension):
|
|
43
|
+
# NetBox up to 4.1
|
|
37
44
|
model = 'ipam.ipaddress'
|
|
45
|
+
# NetBox from 4.2, required to be present as of NetBox 4.3
|
|
46
|
+
models = [model]
|
|
38
47
|
|
|
39
48
|
def buttons(self):
|
|
40
49
|
"""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{% extends 'base/layout.html' %}
|
|
2
|
+
{% load render_table from django_tables2 %}
|
|
3
|
+
|
|
4
|
+
{% block title %}DNS Names{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<div class="row">
|
|
8
|
+
<div class="col col-12">
|
|
9
|
+
<div class="card">
|
|
10
|
+
<h5 class="card-header">DNS Names</h5>
|
|
11
|
+
<div class="card-body table-responsive">
|
|
12
|
+
{% render_table table 'inc/table.html' %}
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
{% endblock content %}
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
{{ object.server_port }}
|
|
20
20
|
</td>
|
|
21
21
|
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<th scope="row">Protocol</th>
|
|
24
|
+
<td>
|
|
25
|
+
{{ object.protocol }}
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
22
28
|
</table>
|
|
23
29
|
</div>
|
|
24
30
|
<div class="card">
|
|
@@ -65,4 +71,4 @@
|
|
|
65
71
|
</div>
|
|
66
72
|
</div>
|
|
67
73
|
|
|
68
|
-
{% endblock content %}
|
|
74
|
+
{% endblock content %}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
from django.urls import path, include
|
|
2
2
|
|
|
3
3
|
from utilities.urls import get_model_urls
|
|
4
|
-
from .views import
|
|
5
|
-
|
|
4
|
+
from .views import (
|
|
5
|
+
ExtraDNSNameCreateView,
|
|
6
|
+
IPAddressDNSNameRecreateView,
|
|
7
|
+
ManagedDNSNameListView,
|
|
8
|
+
UpdateForwardZone,
|
|
9
|
+
UpdateReverseZone,
|
|
10
|
+
)
|
|
6
11
|
|
|
7
12
|
urlpatterns = [
|
|
13
|
+
path('managed-dns-names/', ManagedDNSNameListView.as_view(), name='managed_dns_name_list'),
|
|
8
14
|
|
|
9
15
|
path('extra-dns-names/', include(get_model_urls('netbox_ddns', 'extradnsname', detail=False))),
|
|
10
16
|
path('extra-dns-names/<int:pk>/', include(get_model_urls('netbox_ddns', 'extradnsname'))),
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
3
|
+
|
|
4
|
+
import dns.rdatatype
|
|
5
|
+
import dns.resolver
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ipam.models import IPAddress
|
|
9
|
+
from netbox_ddns.models import Zone
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ManagedDNSNameRow:
|
|
14
|
+
"""Primary DNS name from IPAddress, with optional zone."""
|
|
15
|
+
dns_name: str
|
|
16
|
+
ip_address: 'IPAddress'
|
|
17
|
+
zone: Optional['Zone']
|
|
18
|
+
obj: 'IPAddress'
|
|
19
|
+
view_url: str = ''
|
|
20
|
+
edit_url: str = ''
|
|
21
|
+
delete_url: Optional[str] = None
|
|
22
|
+
forward_status_html: str = ''
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _find_zone_for_dns_name(dns_name: str, zones_by_name: dict) -> Optional['Zone']:
|
|
26
|
+
"""In-memory zone lookup."""
|
|
27
|
+
parts = dns_name.lower().rstrip('.').split('.')
|
|
28
|
+
for i in range(len(parts)):
|
|
29
|
+
candidate = '.'.join(parts[i:])
|
|
30
|
+
for suffix in (candidate + '.', candidate):
|
|
31
|
+
if suffix in zones_by_name:
|
|
32
|
+
return zones_by_name[suffix]
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_managed_dns_names(user) -> List[ManagedDNSNameRow]:
|
|
37
|
+
"""
|
|
38
|
+
Return all primary DNS names (from IPAddress.dns_name), with or without a zone.
|
|
39
|
+
Extra DNS names are shown in the separate Extra DNS Names view.
|
|
40
|
+
"""
|
|
41
|
+
from django.urls import reverse
|
|
42
|
+
|
|
43
|
+
from dns import rcode
|
|
44
|
+
|
|
45
|
+
from ipam.models import IPAddress
|
|
46
|
+
from netbox_ddns.models import ACTION_CHOICES, DNSStatus, Zone, get_rcode_display
|
|
47
|
+
|
|
48
|
+
rows: List[ManagedDNSNameRow] = []
|
|
49
|
+
no_zone_status = '<span class="text-muted">No zone configured</span>'
|
|
50
|
+
|
|
51
|
+
zones = list(Zone.objects.all().restrict(user, 'view'))
|
|
52
|
+
zones_by_name = {z.name: z for z in zones}
|
|
53
|
+
for z in zones:
|
|
54
|
+
n = z.name.rstrip('.')
|
|
55
|
+
if n != z.name:
|
|
56
|
+
zones_by_name[n] = z
|
|
57
|
+
|
|
58
|
+
ip_addresses = (
|
|
59
|
+
IPAddress.objects.filter(dns_name__isnull=False)
|
|
60
|
+
.exclude(dns_name='')
|
|
61
|
+
.restrict(user, 'view')
|
|
62
|
+
.prefetch_related('dnsstatus')
|
|
63
|
+
.order_by('dns_name')
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
for ip_address in ip_addresses:
|
|
67
|
+
zone = _find_zone_for_dns_name(ip_address.dns_name, zones_by_name)
|
|
68
|
+
if zone is None:
|
|
69
|
+
forward_status = no_zone_status
|
|
70
|
+
else:
|
|
71
|
+
try:
|
|
72
|
+
status = ip_address.dnsstatus
|
|
73
|
+
if status.forward_action is not None:
|
|
74
|
+
output = next(
|
|
75
|
+
label for value, label in ACTION_CHOICES if value == status.forward_action
|
|
76
|
+
)
|
|
77
|
+
output += ': '
|
|
78
|
+
output += get_rcode_display(status.forward_rcode) or ''
|
|
79
|
+
colour = 'green' if status.forward_rcode == rcode.NOERROR else 'red'
|
|
80
|
+
forward_status = f'<span style="color:{colour}">{output}</span>'
|
|
81
|
+
else:
|
|
82
|
+
forward_status = '<span class="text-muted">Not created</span>'
|
|
83
|
+
except DNSStatus.DoesNotExist:
|
|
84
|
+
forward_status = '<span class="text-muted">Not created</span>'
|
|
85
|
+
|
|
86
|
+
rows.append(ManagedDNSNameRow(
|
|
87
|
+
dns_name=ip_address.dns_name,
|
|
88
|
+
ip_address=ip_address,
|
|
89
|
+
zone=zone,
|
|
90
|
+
obj=ip_address,
|
|
91
|
+
view_url=ip_address.get_absolute_url(),
|
|
92
|
+
edit_url=reverse('ipam:ipaddress_edit', args=[ip_address.pk]),
|
|
93
|
+
delete_url=None,
|
|
94
|
+
forward_status_html=forward_status,
|
|
95
|
+
))
|
|
96
|
+
|
|
97
|
+
return rows
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def normalize_fqdn(dns_name: str) -> str:
|
|
101
|
+
if not dns_name:
|
|
102
|
+
return ''
|
|
103
|
+
|
|
104
|
+
return dns_name.lower().rstrip('.') + '.'
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_soa(dns_name: str) -> str:
|
|
108
|
+
parts = dns_name.rstrip('.').split('.')
|
|
109
|
+
for i in range(len(parts)):
|
|
110
|
+
zone_name = normalize_fqdn('.'.join(parts[i:]))
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
dns.resolver.query(zone_name, dns.rdatatype.SOA)
|
|
114
|
+
return zone_name
|
|
115
|
+
except dns.resolver.NoAnswer:
|
|
116
|
+
# The name exists, but has no SOA. Continue one level further up
|
|
117
|
+
continue
|
|
118
|
+
except dns.resolver.NXDOMAIN as e:
|
|
119
|
+
# Look for a SOA record in the authority section
|
|
120
|
+
for query, response in e.responses().items():
|
|
121
|
+
for rrset in response.authority:
|
|
122
|
+
if rrset.rdtype == dns.rdatatype.SOA:
|
|
123
|
+
return rrset.name.to_text()
|
|
@@ -21,3 +21,9 @@ def validate_base64(value):
|
|
|
21
21
|
base64.b64decode(value, validate=True)
|
|
22
22
|
except binascii.Error:
|
|
23
23
|
raise ValidationError('Invalid base64 string')
|
|
24
|
+
|
|
25
|
+
class DNSNameValidator(RegexValidator):
|
|
26
|
+
regex = r"^([0-9A-Za-z_-]+|\*)(\.[0-9A-Za-z_-]+)*\.?$"
|
|
27
|
+
message = _(
|
|
28
|
+
"Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names"
|
|
29
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from django.contrib import messages
|
|
2
2
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
3
|
-
from django.shortcuts import get_object_or_404, redirect
|
|
3
|
+
from django.shortcuts import get_object_or_404, redirect, render
|
|
4
4
|
from django.utils.translation import gettext as _
|
|
5
5
|
|
|
6
6
|
from ipam.models import IPAddress
|
|
@@ -9,16 +9,34 @@ from netbox_ddns.background_tasks import dns_create
|
|
|
9
9
|
from netbox_ddns.filtersets import ServerFilterSet, ZoneFilterSet, ReverseZoneFilterSet, ExtraDNSNameFilterSet
|
|
10
10
|
from netbox_ddns.forms import ServerForm, ZoneForm, ReverseZoneForm, ExtraDNSNameIPAddressForm, ExtraDNSNameForm
|
|
11
11
|
from netbox_ddns.models import DNSStatus, ExtraDNSName, Server, Zone, ReverseZone
|
|
12
|
-
from netbox_ddns.tables import ServerTable, ZoneTable, ReverseZoneTable, ExtraDNSNameTable
|
|
13
|
-
from netbox_ddns.utils import normalize_fqdn
|
|
12
|
+
from netbox_ddns.tables import ManagedDNSNameTable, ServerTable, ZoneTable, ReverseZoneTable, ExtraDNSNameTable
|
|
13
|
+
from netbox_ddns.utils import get_managed_dns_names, normalize_fqdn
|
|
14
14
|
|
|
15
15
|
from netbox.views.generic import ObjectDeleteView, ObjectEditView, ObjectView, ObjectListView, BulkDeleteView
|
|
16
16
|
|
|
17
|
+
from django_tables2 import RequestConfig
|
|
17
18
|
from django.views.generic import View
|
|
18
19
|
|
|
19
20
|
from utilities.views import register_model_view, GetRelatedModelsMixin
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
# Managed DNS Names (primary DNS names from IP addresses)
|
|
24
|
+
class ManagedDNSNameListView(PermissionRequiredMixin, View):
|
|
25
|
+
"""List of all primary DNS names (from IPAddress.dns_name). Extra DNS names have a separate view."""
|
|
26
|
+
permission_required = 'netbox_ddns.view_zone'
|
|
27
|
+
|
|
28
|
+
def get(self, request):
|
|
29
|
+
rows = get_managed_dns_names(request.user)
|
|
30
|
+
table = ManagedDNSNameTable(rows)
|
|
31
|
+
RequestConfig(request).configure(table)
|
|
32
|
+
|
|
33
|
+
return render(
|
|
34
|
+
request,
|
|
35
|
+
'netbox_ddns/managed_dns_names.html',
|
|
36
|
+
{'table': table},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
22
40
|
# ReverseZone
|
|
23
41
|
@register_model_view(ReverseZone)
|
|
24
42
|
class ReverseZoneView(ObjectView):
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-ddns
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Dynamic DNS Connector for NetBox
|
|
5
5
|
Home-page: https://github.com/sjm-steffann/netbox-ddns
|
|
6
6
|
Author: Sander Steffann
|
|
7
7
|
Author-email: sander@steffann.nl
|
|
8
8
|
License: Apache 2.0
|
|
9
|
-
Classifier: Development Status ::
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
10
|
Classifier: Framework :: Django
|
|
11
11
|
Classifier: Framework :: Django :: 3.0
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.
|
|
15
|
-
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.11
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
17
18
|
License-File: LICENSE.txt
|
|
18
19
|
Requires-Dist: setuptools
|
|
19
20
|
Requires-Dist: dnspython
|
|
21
|
+
Dynamic: license-file
|
|
20
22
|
|
|
21
23
|
# Dynamic DNS Connector for NetBox
|
|
22
24
|
|
|
@@ -42,8 +42,11 @@ netbox_ddns/migrations/0010_extradnsname_created_extradnsname_custom_field_data_
|
|
|
42
42
|
netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py
|
|
43
43
|
netbox_ddns/migrations/0012_zone_created_zone_custom_field_data_and_more.py
|
|
44
44
|
netbox_ddns/migrations/0013_reversezone_created_reversezone_custom_field_data_and_more.py
|
|
45
|
+
netbox_ddns/migrations/0014_alter_extradnsname_name.py
|
|
46
|
+
netbox_ddns/migrations/0015_server_protocol.py
|
|
45
47
|
netbox_ddns/migrations/__init__.py
|
|
46
48
|
netbox_ddns/templates/netbox_ddns/extradnsname.html
|
|
49
|
+
netbox_ddns/templates/netbox_ddns/managed_dns_names.html
|
|
47
50
|
netbox_ddns/templates/netbox_ddns/reversezone.html
|
|
48
51
|
netbox_ddns/templates/netbox_ddns/server.html
|
|
49
52
|
netbox_ddns/templates/netbox_ddns/update_reverse_zone.html
|
|
@@ -10,18 +10,19 @@ url = https://github.com/sjm-steffann/netbox-ddns
|
|
|
10
10
|
license = Apache 2.0
|
|
11
11
|
license_file = LICENSE.txt
|
|
12
12
|
classifiers =
|
|
13
|
-
Development Status ::
|
|
13
|
+
Development Status :: 5 - Production/Stable
|
|
14
14
|
Framework :: Django
|
|
15
15
|
Framework :: Django :: 3.0
|
|
16
16
|
License :: OSI Approved :: Apache Software License
|
|
17
17
|
Programming Language :: Python :: 3
|
|
18
|
-
Programming Language :: Python :: 3.
|
|
18
|
+
Programming Language :: Python :: 3.11
|
|
19
|
+
Programming Language :: Python :: 3.12
|
|
19
20
|
|
|
20
21
|
[options]
|
|
21
22
|
zip_safe = False
|
|
22
23
|
include_package_data = True
|
|
23
24
|
packages = find:
|
|
24
|
-
python_requires = >=3.
|
|
25
|
+
python_requires = >=3.11
|
|
25
26
|
install_requires =
|
|
26
27
|
setuptools
|
|
27
28
|
dnspython
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import django_tables2 as tables
|
|
2
|
-
from django_tables2 import LinkColumn, RelatedLinkColumn
|
|
3
|
-
|
|
4
|
-
from netbox_ddns.models import ExtraDNSName, Server, ReverseZone, Zone
|
|
5
|
-
|
|
6
|
-
from netbox.tables import NetBoxTable
|
|
7
|
-
|
|
8
|
-
FORWARD_DNS = """
|
|
9
|
-
{% if record.forward_action is not None %}
|
|
10
|
-
{{ record.get_forward_action_display }}:
|
|
11
|
-
{{ record.get_forward_rcode_html_display }}
|
|
12
|
-
{% else %}
|
|
13
|
-
<span class="text-muted">Not created</span>
|
|
14
|
-
{% endif %}
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ReverseZoneTable(NetBoxTable):
|
|
19
|
-
name = LinkColumn()
|
|
20
|
-
server = RelatedLinkColumn()
|
|
21
|
-
|
|
22
|
-
class Meta(NetBoxTable.Meta):
|
|
23
|
-
model = ReverseZone
|
|
24
|
-
fields = ("id", "name", "prefix", "server", "ttl")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class ZoneTable(NetBoxTable):
|
|
28
|
-
name = LinkColumn()
|
|
29
|
-
server = RelatedLinkColumn()
|
|
30
|
-
|
|
31
|
-
class Meta(NetBoxTable.Meta):
|
|
32
|
-
model = Zone
|
|
33
|
-
fields = ("id", "name", "server", "ttl")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class ServerTable(NetBoxTable):
|
|
37
|
-
server = LinkColumn()
|
|
38
|
-
|
|
39
|
-
class Meta(NetBoxTable.Meta):
|
|
40
|
-
model = Server
|
|
41
|
-
fields = ("id", "server", "server_port", "tsig_key_name", "tsig_algorithm")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class ExtraDNSNameTable(NetBoxTable):
|
|
45
|
-
ip_address = RelatedLinkColumn()
|
|
46
|
-
name = LinkColumn()
|
|
47
|
-
forward_dns = tables.TemplateColumn(template_code=FORWARD_DNS)
|
|
48
|
-
|
|
49
|
-
class Meta(NetBoxTable.Meta):
|
|
50
|
-
model = ExtraDNSName
|
|
51
|
-
fields = ('id', 'name', 'ip_address', 'last_update', 'forward_dns')
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import dns.rdatatype
|
|
2
|
-
import dns.resolver
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def normalize_fqdn(dns_name: str) -> str:
|
|
6
|
-
if not dns_name:
|
|
7
|
-
return ''
|
|
8
|
-
|
|
9
|
-
return dns_name.lower().rstrip('.') + '.'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def get_soa(dns_name: str) -> str:
|
|
13
|
-
parts = dns_name.rstrip('.').split('.')
|
|
14
|
-
for i in range(len(parts)):
|
|
15
|
-
zone_name = normalize_fqdn('.'.join(parts[i:]))
|
|
16
|
-
|
|
17
|
-
try:
|
|
18
|
-
dns.resolver.query(zone_name, dns.rdatatype.SOA)
|
|
19
|
-
return zone_name
|
|
20
|
-
except dns.resolver.NoAnswer:
|
|
21
|
-
# The name exists, but has no SOA. Continue one level further up
|
|
22
|
-
continue
|
|
23
|
-
except dns.resolver.NXDOMAIN as e:
|
|
24
|
-
# Look for a SOA record in the authority section
|
|
25
|
-
for query, response in e.responses().items():
|
|
26
|
-
for rrset in response.authority:
|
|
27
|
-
if rrset.rdtype == dns.rdatatype.SOA:
|
|
28
|
-
return rrset.name.to_text()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/ipaddress/dns_extra.html
RENAMED
|
File without changes
|
{netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/ipaddress/dns_info.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_ddns-1.5.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/update_reverse_zone.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|