netbox-ddns 1.4.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.4.0/netbox_ddns.egg-info → netbox_ddns-1.7.0}/PKG-INFO +7 -5
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/__init__.py +3 -3
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/api/serializers.py +20 -2
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/api/views.py +1 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/background_tasks.py +28 -5
- netbox_ddns-1.7.0/netbox_ddns/filtersets.py +30 -0
- netbox_ddns-1.7.0/netbox_ddns/forms.py +60 -0
- netbox_ddns-1.7.0/netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py +36 -0
- netbox_ddns-1.7.0/netbox_ddns/migrations/0012_zone_created_zone_custom_field_data_and_more.py +36 -0
- netbox_ddns-1.7.0/netbox_ddns/migrations/0013_reversezone_created_reversezone_custom_field_data_and_more.py +36 -0
- 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.7.0/netbox_ddns/migrations/__init__.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/models.py +61 -9
- netbox_ddns-1.7.0/netbox_ddns/navigation.py +62 -0
- netbox_ddns-1.7.0/netbox_ddns/tables.py +96 -0
- netbox_ddns-1.7.0/netbox_ddns/template_content.py +74 -0
- netbox_ddns-1.7.0/netbox_ddns/templates/netbox_ddns/extradnsname.html +61 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/ipaddress/dns_extra.html +1 -1
- netbox_ddns-1.7.0/netbox_ddns/templates/netbox_ddns/managed_dns_names.html +17 -0
- netbox_ddns-1.7.0/netbox_ddns/templates/netbox_ddns/reversezone.html +51 -0
- netbox_ddns-1.7.0/netbox_ddns/templates/netbox_ddns/server.html +74 -0
- netbox_ddns-1.7.0/netbox_ddns/templates/netbox_ddns/update_reverse_zone.html +11 -0
- netbox_ddns-1.7.0/netbox_ddns/templates/netbox_ddns/update_zone.html +11 -0
- netbox_ddns-1.7.0/netbox_ddns/templates/netbox_ddns/zone.html +53 -0
- netbox_ddns-1.7.0/netbox_ddns/urls.py +40 -0
- netbox_ddns-1.7.0/netbox_ddns/utils.py +123 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/validators.py +6 -0
- netbox_ddns-1.7.0/netbox_ddns/views.py +339 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0/netbox_ddns.egg-info}/PKG-INFO +7 -5
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/SOURCES.txt +12 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/setup.cfg +4 -3
- netbox_ddns-1.4.0/netbox_ddns/admin.py +0 -156
- netbox_ddns-1.4.0/netbox_ddns/filtersets.py +0 -8
- netbox_ddns-1.4.0/netbox_ddns/forms.py +0 -9
- netbox_ddns-1.4.0/netbox_ddns/tables.py +0 -50
- netbox_ddns-1.4.0/netbox_ddns/template_content.py +0 -37
- netbox_ddns-1.4.0/netbox_ddns/templates/netbox_ddns/extradnsname.html +0 -169
- netbox_ddns-1.4.0/netbox_ddns/urls.py +0 -22
- netbox_ddns-1.4.0/netbox_ddns/utils.py +0 -28
- netbox_ddns-1.4.0/netbox_ddns/views.py +0 -155
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/LICENSE.txt +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/MANIFEST.in +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/README.md +0 -0
- /netbox_ddns-1.4.0/netbox_ddns/api/__init__.py → /netbox_ddns-1.7.0/netbox_ddns/admin.py +0 -0
- {netbox_ddns-1.4.0/netbox_ddns/migrations → netbox_ddns-1.7.0/netbox_ddns/api}/__init__.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/api/urls.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0001_initial.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0002_add_ttl.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0003_dnsstatus.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0004_ensure_trailing_dot.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0005_extradnsname.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0006_extradns_cname.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0007_zone_meta.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0008_server_server_port.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0009_alter_dnsstatus_id_alter_extradnsname_id_and_more.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/migrations/0010_extradnsname_created_extradnsname_custom_field_data_and_more.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/search.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/signals.py +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/ipaddress/dns_info.html +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns/templates/netbox_ddns/ipaddress/dns_refresh_button.html +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/dependency_links.txt +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/not-zip-safe +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/requires.txt +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/netbox_ddns.egg-info/top_level.txt +0 -0
- {netbox_ddns-1.4.0 → netbox_ddns-1.7.0}/pyproject.toml +0 -0
- {netbox_ddns-1.4.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'
|
|
@@ -2,16 +2,34 @@ from rest_framework import serializers
|
|
|
2
2
|
from rest_framework.relations import PrimaryKeyRelatedField
|
|
3
3
|
from ipam.models import IPAddress
|
|
4
4
|
from netbox.api.serializers import NetBoxModelSerializer
|
|
5
|
-
from ..models import ExtraDNSName
|
|
5
|
+
from ..models import ExtraDNSName, Server, Zone, ReverseZone
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ExtraDNSNameSerializer(NetBoxModelSerializer):
|
|
9
9
|
url = serializers.HyperlinkedIdentityField(
|
|
10
10
|
view_name='plugins-api:netbox_ddns-api:extradnsname-detail'
|
|
11
11
|
)
|
|
12
|
-
ip_address = PrimaryKeyRelatedField(queryset=IPAddress.objects.all()
|
|
12
|
+
ip_address = PrimaryKeyRelatedField(queryset=IPAddress.objects.all())
|
|
13
13
|
|
|
14
14
|
class Meta:
|
|
15
15
|
model = ExtraDNSName
|
|
16
16
|
fields = ('id', 'ip_address', 'name', 'url')
|
|
17
17
|
read_only_fields = ('id', 'url')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ServerSerializer(NetBoxModelSerializer):
|
|
21
|
+
class Meta:
|
|
22
|
+
model = Server
|
|
23
|
+
fields = ('server', 'server_port', 'tsig_key_name', 'tsig_algorithm', "tsig_key")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ZoneSerializer(NetBoxModelSerializer):
|
|
27
|
+
class Meta:
|
|
28
|
+
model = Zone
|
|
29
|
+
fields = ('name', 'ttl', 'server', 'protocol')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ReverseZoneSerializer(NetBoxModelSerializer):
|
|
33
|
+
class Meta:
|
|
34
|
+
model = ReverseZone
|
|
35
|
+
fields = ('name', 'prefix', 'ttl', 'server')
|
|
@@ -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()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import django_filters
|
|
2
|
+
|
|
3
|
+
from netbox.filtersets import NetBoxModelFilterSet
|
|
4
|
+
from .models import ExtraDNSName, Server, Zone, ReverseZone
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExtraDNSNameFilterSet(NetBoxModelFilterSet):
|
|
8
|
+
class Meta:
|
|
9
|
+
model = ExtraDNSName
|
|
10
|
+
fields = ('id', 'name', 'ip_address', 'forward_rcode')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ServerFilterSet(NetBoxModelFilterSet):
|
|
14
|
+
class Meta:
|
|
15
|
+
model = Server
|
|
16
|
+
fields = ('id', 'server', 'server_port', 'tsig_key_name', 'tsig_algorithm', "tsig_key")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ZoneFilterSet(NetBoxModelFilterSet):
|
|
20
|
+
class Meta:
|
|
21
|
+
model = Zone
|
|
22
|
+
fields = ('id', 'name', 'ttl', 'server')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ReverseZoneFilterSet(NetBoxModelFilterSet):
|
|
26
|
+
prefix = django_filters.CharFilter(lookup_expr='icontains')
|
|
27
|
+
|
|
28
|
+
class Meta:
|
|
29
|
+
model = ReverseZone
|
|
30
|
+
fields = ('id', 'name', 'prefix', 'ttl', 'server')
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from django.forms import IntegerField
|
|
2
|
+
|
|
3
|
+
from ipam.models import IPAddress
|
|
4
|
+
from netbox.forms import NetBoxModelForm, NetBoxModelBulkEditForm
|
|
5
|
+
from netbox_ddns.models import ExtraDNSName, Server, Zone, ReverseZone
|
|
6
|
+
from utilities.forms.fields import DynamicModelChoiceField
|
|
7
|
+
from utilities.forms.rendering import FieldSet
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ReverseZoneForm(NetBoxModelForm):
|
|
11
|
+
class Meta:
|
|
12
|
+
model = ReverseZone
|
|
13
|
+
fields = ('prefix', 'name', 'ttl', 'server')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ZoneForm(NetBoxModelForm):
|
|
17
|
+
class Meta:
|
|
18
|
+
model = Zone
|
|
19
|
+
fields = ('name', 'ttl', 'server')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ZoneBulkEditForm(NetBoxModelBulkEditForm):
|
|
23
|
+
model = Zone
|
|
24
|
+
|
|
25
|
+
ttl = IntegerField(
|
|
26
|
+
min_value=1,
|
|
27
|
+
required=False
|
|
28
|
+
)
|
|
29
|
+
server = DynamicModelChoiceField(
|
|
30
|
+
queryset=Server.objects.all(),
|
|
31
|
+
required=False
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ServerForm(NetBoxModelForm):
|
|
36
|
+
fieldsets = (
|
|
37
|
+
FieldSet('server', 'server_port', 'protocol', name='Server'),
|
|
38
|
+
FieldSet('tsig_key_name', 'tsig_algorithm', "tsig_key", name='Authentication'),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
class Meta:
|
|
42
|
+
model = Server
|
|
43
|
+
fields = ('server', 'server_port', 'protocol', 'tsig_key_name', 'tsig_algorithm', "tsig_key")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ExtraDNSNameIPAddressForm(NetBoxModelForm):
|
|
47
|
+
class Meta:
|
|
48
|
+
model = ExtraDNSName
|
|
49
|
+
fields = ['name']
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ExtraDNSNameForm(NetBoxModelForm):
|
|
53
|
+
ip_address = DynamicModelChoiceField(
|
|
54
|
+
queryset=IPAddress.objects.all(),
|
|
55
|
+
required=True
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
class Meta:
|
|
59
|
+
model = ExtraDNSName
|
|
60
|
+
fields = ['name', "ip_address"]
|
netbox_ddns-1.7.0/netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated by Django 5.0.9 on 2025-01-08 11:03
|
|
2
|
+
|
|
3
|
+
import taggit.managers
|
|
4
|
+
import utilities.json
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
('extras', '0122_charfield_null_choices'),
|
|
12
|
+
('netbox_ddns', '0010_extradnsname_created_extradnsname_custom_field_data_and_more'),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AddField(
|
|
17
|
+
model_name='server',
|
|
18
|
+
name='created',
|
|
19
|
+
field=models.DateTimeField(auto_now_add=True, null=True),
|
|
20
|
+
),
|
|
21
|
+
migrations.AddField(
|
|
22
|
+
model_name='server',
|
|
23
|
+
name='custom_field_data',
|
|
24
|
+
field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
|
|
25
|
+
),
|
|
26
|
+
migrations.AddField(
|
|
27
|
+
model_name='server',
|
|
28
|
+
name='last_updated',
|
|
29
|
+
field=models.DateTimeField(auto_now=True, null=True),
|
|
30
|
+
),
|
|
31
|
+
migrations.AddField(
|
|
32
|
+
model_name='server',
|
|
33
|
+
name='tags',
|
|
34
|
+
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
|
35
|
+
),
|
|
36
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated by Django 5.0.9 on 2025-01-08 18:46
|
|
2
|
+
|
|
3
|
+
import taggit.managers
|
|
4
|
+
import utilities.json
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
('extras', '0122_charfield_null_choices'),
|
|
12
|
+
('netbox_ddns', '0011_server_created_server_custom_field_data_and_more'),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AddField(
|
|
17
|
+
model_name='zone',
|
|
18
|
+
name='created',
|
|
19
|
+
field=models.DateTimeField(auto_now_add=True, null=True),
|
|
20
|
+
),
|
|
21
|
+
migrations.AddField(
|
|
22
|
+
model_name='zone',
|
|
23
|
+
name='custom_field_data',
|
|
24
|
+
field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
|
|
25
|
+
),
|
|
26
|
+
migrations.AddField(
|
|
27
|
+
model_name='zone',
|
|
28
|
+
name='last_updated',
|
|
29
|
+
field=models.DateTimeField(auto_now=True, null=True),
|
|
30
|
+
),
|
|
31
|
+
migrations.AddField(
|
|
32
|
+
model_name='zone',
|
|
33
|
+
name='tags',
|
|
34
|
+
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
|
35
|
+
),
|
|
36
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated by Django 5.0.9 on 2025-01-08 19:51
|
|
2
|
+
|
|
3
|
+
import taggit.managers
|
|
4
|
+
import utilities.json
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
('extras', '0122_charfield_null_choices'),
|
|
12
|
+
('netbox_ddns', '0012_zone_created_zone_custom_field_data_and_more'),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AddField(
|
|
17
|
+
model_name='reversezone',
|
|
18
|
+
name='created',
|
|
19
|
+
field=models.DateTimeField(auto_now_add=True, null=True),
|
|
20
|
+
),
|
|
21
|
+
migrations.AddField(
|
|
22
|
+
model_name='reversezone',
|
|
23
|
+
name='custom_field_data',
|
|
24
|
+
field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
|
|
25
|
+
),
|
|
26
|
+
migrations.AddField(
|
|
27
|
+
model_name='reversezone',
|
|
28
|
+
name='last_updated',
|
|
29
|
+
field=models.DateTimeField(auto_now=True, null=True),
|
|
30
|
+
),
|
|
31
|
+
migrations.AddField(
|
|
32
|
+
model_name='reversezone',
|
|
33
|
+
name='tags',
|
|
34
|
+
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
|
35
|
+
),
|
|
36
|
+
]
|
|
@@ -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
|
+
]
|
|
File without changes
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import socket
|
|
5
5
|
from django.core.exceptions import ValidationError
|
|
6
6
|
from django.db import models
|
|
7
|
+
from django.db.models import Q
|
|
7
8
|
from django.db.models.functions import Length
|
|
8
9
|
from django.utils.html import format_html
|
|
9
10
|
from django.utils.translation import gettext_lazy as _
|
|
@@ -15,8 +16,10 @@ from typing import Optional
|
|
|
15
16
|
from netbox.models import NetBoxModel
|
|
16
17
|
from ipam.fields import IPNetworkField
|
|
17
18
|
from ipam.models import IPAddress
|
|
19
|
+
from utilities.querysets import RestrictedQuerySet
|
|
18
20
|
from .utils import normalize_fqdn
|
|
19
|
-
from .validators import HostnameAddressValidator, HostnameValidator, validate_base64, MinValueValidator,
|
|
21
|
+
from .validators import DNSNameValidator, HostnameAddressValidator, HostnameValidator, validate_base64, MinValueValidator, \
|
|
22
|
+
MaxValueValidator
|
|
20
23
|
|
|
21
24
|
logger = logging.getLogger('netbox_ddns')
|
|
22
25
|
|
|
@@ -61,8 +64,11 @@ def get_rcode_display(code):
|
|
|
61
64
|
else:
|
|
62
65
|
return _('Unknown response: {}').format(code)
|
|
63
66
|
|
|
67
|
+
class Protocol(models.TextChoices):
|
|
68
|
+
UDP = 'udp', _('UDP')
|
|
69
|
+
TCP = 'tcp', _('TCP')
|
|
64
70
|
|
|
65
|
-
class Server(
|
|
71
|
+
class Server(NetBoxModel):
|
|
66
72
|
server = models.CharField(
|
|
67
73
|
verbose_name=_('DDNS Server'),
|
|
68
74
|
max_length=255,
|
|
@@ -92,6 +98,12 @@ class Server(models.Model):
|
|
|
92
98
|
validators=[validate_base64],
|
|
93
99
|
help_text=_('in base64 notation'),
|
|
94
100
|
)
|
|
101
|
+
protocol = models.CharField(
|
|
102
|
+
verbose_name=_('Protocol'),
|
|
103
|
+
max_length=3,
|
|
104
|
+
choices=Protocol.choices,
|
|
105
|
+
default=Protocol.UDP,
|
|
106
|
+
)
|
|
95
107
|
|
|
96
108
|
class Meta:
|
|
97
109
|
unique_together = (
|
|
@@ -111,6 +123,9 @@ class Server(models.Model):
|
|
|
111
123
|
# Ensure trailing dots from domain-style fields
|
|
112
124
|
self.tsig_key_name = normalize_fqdn(self.tsig_key_name.lower().rstrip('.'))
|
|
113
125
|
|
|
126
|
+
def get_absolute_url(self):
|
|
127
|
+
return reverse('plugins:netbox_ddns:server', args=[self.pk])
|
|
128
|
+
|
|
114
129
|
@property
|
|
115
130
|
def address(self) -> Optional[str]:
|
|
116
131
|
addrinfo = socket.getaddrinfo(self.server, self.server_port, proto=socket.IPPROTO_UDP)
|
|
@@ -129,7 +144,7 @@ class Server(models.Model):
|
|
|
129
144
|
)
|
|
130
145
|
|
|
131
146
|
|
|
132
|
-
class ZoneQuerySet(
|
|
147
|
+
class ZoneQuerySet(RestrictedQuerySet):
|
|
133
148
|
def find_for_dns_name(self, dns_name: str) -> Optional['Zone']:
|
|
134
149
|
# Generate all possible zones
|
|
135
150
|
zones = []
|
|
@@ -141,7 +156,7 @@ class ZoneQuerySet(models.QuerySet):
|
|
|
141
156
|
return self.filter(name__in=zones).order_by(Length('name').desc()).first()
|
|
142
157
|
|
|
143
158
|
|
|
144
|
-
class Zone(
|
|
159
|
+
class Zone(NetBoxModel):
|
|
145
160
|
name = models.CharField(
|
|
146
161
|
verbose_name=_('zone name'),
|
|
147
162
|
max_length=255,
|
|
@@ -167,6 +182,28 @@ class Zone(models.Model):
|
|
|
167
182
|
def __str__(self):
|
|
168
183
|
return self.name
|
|
169
184
|
|
|
185
|
+
def get_managed_ip_address(self):
|
|
186
|
+
# Find all more-specific zones
|
|
187
|
+
more_specifics = Zone.objects.filter(name__endswith=self.name).exclude(pk=self.pk)
|
|
188
|
+
|
|
189
|
+
# Find all IPAddress objects in this self but not in the more-specifics
|
|
190
|
+
ip_addresses = IPAddress.objects.filter(Q(dns_name__endswith=self.name) |
|
|
191
|
+
Q(dns_name__endswith=self.name.rstrip('.')))
|
|
192
|
+
for more_specific in more_specifics:
|
|
193
|
+
ip_addresses = ip_addresses.exclude(Q(dns_name__endswith=more_specific.name) |
|
|
194
|
+
Q(dns_name__endswith=more_specific.name.rstrip('.')))
|
|
195
|
+
return ip_addresses
|
|
196
|
+
|
|
197
|
+
def get_managed_extra_dns_name(self):
|
|
198
|
+
# Find all more-specific zones
|
|
199
|
+
more_specifics = Zone.objects.filter(name__endswith=self.name).exclude(pk=self.pk)
|
|
200
|
+
|
|
201
|
+
# Find all ExtraDNSName objects in this zone but not in the more-specifics
|
|
202
|
+
extra_names = ExtraDNSName.objects.filter(name__endswith=self.name)
|
|
203
|
+
for more_specific in more_specifics:
|
|
204
|
+
extra_names = extra_names.exclude(name__endswith=more_specific.name)
|
|
205
|
+
return extra_names
|
|
206
|
+
|
|
170
207
|
def clean(self):
|
|
171
208
|
# Ensure trailing dots from domain-style fields
|
|
172
209
|
self.name = normalize_fqdn(self.name)
|
|
@@ -174,8 +211,11 @@ class Zone(models.Model):
|
|
|
174
211
|
def get_updater(self):
|
|
175
212
|
return self.server.create_update(self.name)
|
|
176
213
|
|
|
214
|
+
def get_absolute_url(self):
|
|
215
|
+
return reverse('plugins:netbox_ddns:zone', args=[self.pk])
|
|
216
|
+
|
|
177
217
|
|
|
178
|
-
class ReverseZoneQuerySet(
|
|
218
|
+
class ReverseZoneQuerySet(RestrictedQuerySet):
|
|
179
219
|
def find_for_address(self, address: ip.IPAddress) -> Optional['ReverseZone']:
|
|
180
220
|
# Find the zone, if any
|
|
181
221
|
zones = list(ReverseZone.objects.filter(prefix__net_contains=address))
|
|
@@ -186,7 +226,7 @@ class ReverseZoneQuerySet(models.QuerySet):
|
|
|
186
226
|
return zones[-1]
|
|
187
227
|
|
|
188
228
|
|
|
189
|
-
class ReverseZone(
|
|
229
|
+
class ReverseZone(NetBoxModel):
|
|
190
230
|
prefix = IPNetworkField(
|
|
191
231
|
verbose_name=_('prefix'),
|
|
192
232
|
unique=True,
|
|
@@ -214,7 +254,19 @@ class ReverseZone(models.Model):
|
|
|
214
254
|
verbose_name_plural = _('reverse zones')
|
|
215
255
|
|
|
216
256
|
def __str__(self):
|
|
217
|
-
return f'
|
|
257
|
+
return f'{self.prefix}'
|
|
258
|
+
|
|
259
|
+
def get_managed_ip_address(self):
|
|
260
|
+
# Find all more-specific zones
|
|
261
|
+
more_specifics = ReverseZone.objects.filter(prefix__net_contained=self.prefix).exclude(pk=self.pk)
|
|
262
|
+
# Find all IPAddress objects in this zone but not in the more-specifics
|
|
263
|
+
ip_addresses = IPAddress.objects.filter(address__net_contained_or_equal=self.prefix)
|
|
264
|
+
for more_specific in more_specifics:
|
|
265
|
+
ip_addresses = ip_addresses.exclude(address__net_contained_or_equal=more_specific.prefix)
|
|
266
|
+
return ip_addresses
|
|
267
|
+
|
|
268
|
+
def get_absolute_url(self):
|
|
269
|
+
return reverse('plugins:netbox_ddns:reversezone', args=[self.pk])
|
|
218
270
|
|
|
219
271
|
def record_name(self, address: ip.IPAddress):
|
|
220
272
|
record_name = self.name
|
|
@@ -334,7 +386,7 @@ class ExtraDNSName(NetBoxModel):
|
|
|
334
386
|
name = models.CharField(
|
|
335
387
|
verbose_name=_('DNS name'),
|
|
336
388
|
max_length=255,
|
|
337
|
-
validators=[
|
|
389
|
+
validators=[DNSNameValidator()],
|
|
338
390
|
)
|
|
339
391
|
|
|
340
392
|
last_update = models.DateTimeField(
|
|
@@ -367,7 +419,7 @@ class ExtraDNSName(NetBoxModel):
|
|
|
367
419
|
return self.name
|
|
368
420
|
|
|
369
421
|
def get_absolute_url(self):
|
|
370
|
-
return reverse('plugins:netbox_ddns:extradnsname', args=[self.
|
|
422
|
+
return reverse('plugins:netbox_ddns:extradnsname', args=[self.pk])
|
|
371
423
|
|
|
372
424
|
def clean(self):
|
|
373
425
|
# Ensure trailing dots from domain-style fields
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from netbox.plugins import PluginMenuItem, PluginMenuButton, PluginMenu
|
|
2
|
+
|
|
3
|
+
menu = PluginMenu(
|
|
4
|
+
label='DDNS',
|
|
5
|
+
groups=(
|
|
6
|
+
('Configuration', (
|
|
7
|
+
PluginMenuItem(
|
|
8
|
+
link='plugins:netbox_ddns:managed_dns_name_list',
|
|
9
|
+
link_text='DNS Names',
|
|
10
|
+
),
|
|
11
|
+
PluginMenuItem(
|
|
12
|
+
link='plugins:netbox_ddns:server_list',
|
|
13
|
+
link_text='DDNS Servers',
|
|
14
|
+
buttons=[
|
|
15
|
+
PluginMenuButton(
|
|
16
|
+
link='plugins:netbox_ddns:server_add',
|
|
17
|
+
title='Add',
|
|
18
|
+
icon_class='mdi mdi-plus-thick',
|
|
19
|
+
)
|
|
20
|
+
]
|
|
21
|
+
),
|
|
22
|
+
PluginMenuItem(
|
|
23
|
+
link='plugins:netbox_ddns:zone_list',
|
|
24
|
+
link_text='Forward Zones',
|
|
25
|
+
buttons=[
|
|
26
|
+
PluginMenuButton(
|
|
27
|
+
link='plugins:netbox_ddns:zone_add',
|
|
28
|
+
title='Add',
|
|
29
|
+
icon_class='mdi mdi-plus-thick',
|
|
30
|
+
)
|
|
31
|
+
]
|
|
32
|
+
),
|
|
33
|
+
PluginMenuItem(
|
|
34
|
+
link='plugins:netbox_ddns:reversezone_list',
|
|
35
|
+
link_text='Reverse Zones',
|
|
36
|
+
buttons=[
|
|
37
|
+
PluginMenuButton(
|
|
38
|
+
link='plugins:netbox_ddns:reversezone_add',
|
|
39
|
+
title='Add',
|
|
40
|
+
icon_class='mdi mdi-plus-thick',
|
|
41
|
+
)
|
|
42
|
+
]
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
('Extra DNS Names', (
|
|
47
|
+
PluginMenuItem(
|
|
48
|
+
link='plugins:netbox_ddns:extradnsname_list',
|
|
49
|
+
link_text='Extra DNS names',
|
|
50
|
+
buttons=[
|
|
51
|
+
PluginMenuButton(
|
|
52
|
+
link='plugins:netbox_ddns:extradnsname_add',
|
|
53
|
+
title='Add',
|
|
54
|
+
icon_class='mdi mdi-plus-thick',
|
|
55
|
+
)
|
|
56
|
+
]
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
icon_class='mdi mdi-router'
|
|
62
|
+
)
|