diff --git a/README.md b/README.md
index 26c66ce..ef8a1fe 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# Netbox DNS Bridge
-(Formerly Netbox Bind Provisioner)
The NetBox DNS Bridge plugin extends [NetBox DNS](https://github.com/sys4/netbox-plugin-dns) by
embedding a lightweight DNS server directly within NetBox. It acts as a bridge between NetBox DNS
and your existing DNS infrastructure, leveraging native DNS mechanisms for seamless integration.
-These include zone transfers (RFC 5936), catalog zones (RFC 9432), and dynamic updates (RFC 2136).
+These include zone transfers (RFC 5936), catalog zones (RFC 9432), and notifying clients about zone
+changes (RFC 1996).
@@ -18,12 +18,6 @@ These include zone transfers (RFC 5936), catalog zones (RFC 9432), and dynamic u
-
-
-
-
-DISCLAIMER: The dynamic update endpoint isn't here yet. Its planned - without a deadline.
-
## Plugin configuration
While providing Zone transfers via AXFR, the Server also exposes specialized catalog zones that BIND
and other RFC9432 compliant DNS Servers use to automatically discover newly created zones and remove
@@ -52,44 +46,55 @@ Parameter | Description
--address | IP of interface to bind to (defaults to 0.0.0.0)
### Plugin settings
+Plugin settings should be configured under `PLUGINS_CONFIG` in `netbox_dns_bridge`:
+```json
+PLUGINS_CONFIG = {
+ 'netbox_dns_bridge': {
+ ...
+ ...
+ }
+}
+```
+
+#### TSIG Authentication
+Following sets the TSIG key that allows clients to query the transfer endpoint and also they key to
+be used to sign NOTIFY messages to clients on zone changes. Each view should have its own unique
+key to allow the plugin to identify the view the client is trying to access. Re-using the key for
+multiple views yields unpredicted behavior.
+```json
+'tsig_keys': {
+ 'the-view-name-here': {
+ 'keyname': 'the-tsig-key-name-here',
+ 'algorithm': 'hmac-sha256',
+ 'secret': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
+ }
+}
+```
+
+#### Client notification on zone change
+To enable the NOTIFY feature, set following:
+```
+'notify_clients': True
+```
+This causes the plugin to schedule background jobs which send out NOTIFY messages to all clients
+that have previously successfullly requested a zone transfer from the transfer endpoint.
+
+After how many hours that are client last checked in on a zone with a SOA or transfer request,
+should the client be considered dead and no longer be notified. Default is 24 hours.
+```
+notify_client_dead_after_hours: 24
+```
+
Setting | Description
--------------------| ---------------------------------------------------------
tsig_keys | Maps a TSIG Key to be used for each view.
+notify_clients | When set to true, turns on the notify mechanism to inform downsteams
## Plugin compatibility
-This plugin is an extension to the netbox-plugin-dns plugin. As such the versioning of this plugin
-was changed to match the one of the netbox-plugin-dns plugin closely. To guarantee compatability,
-ensure that the major and minor version match between both plugins.
-For example, when using netbox-plugin-dns `v1.5.5` install netbox-plugin-dns-bridge `v1.5.x`.
-
-## Post 1.0.7 Upgrade Guide
-Applies only if you have a <= 1.0.7 installation. If you are freshly installing this plugin,
-go on to [Installation Guide](#installation-guide).
-
-After version 1.0.7, the project was restructured and renamed. Until that point version updates
-happened more or less automatically. However, this shift is more of a move from one plugin to
-another as the package name has changed.
-
-This should not be an issue since the slave DNS Servers connected to Netbox can operate
-independently while this plugin is being upgraded.
-
-Make sure to note down the catalog zone serial number before going further.
-
-1. Remove Netbox Bind Provisioner package
- - Remove `netbox-plugin-bind-provisioner` from **local_dependencies.txt**
- - Uninstall the package: `pip uninstall netbox-plugin-bind-provisioner`
-2. Install Netbox DNS Bridge package
- - Install the package `pip install netbox-plugin-dns-bridge`
- - Put `netbox-plugin-dns-bridge` in your **local_dependencies.txt** so it will be installed on
- next upgrade.
-3. Adjust your `configuration.py`
- - Change the plugin name from `netbox-plugin-bind-provisioner` to `netbox-plugin-dns-bridge`
- - Change the key in `PLUGIN_CONFIG` from `netbox_plugin_bind_provisioner` to `netbox_dns_bridge`
-4. Run migrations: `manage.py migrate`
-5. Restore the catalog zone serial you noted down previously so that your slave dns servers continue
- to pull the changes: `manage.py dns-settings set catalog-zone-soa-serial yourserialnumber`
-6. Start the `dns-transfer-endpoint` service.
-7. Mission accomplished.
+This plugin extends the netbox-plugin-dns plugin. As such the versioning was changed to match the
+one of netbox-plugin-dns more closely. To guarantee compatability, ensure that the major and minor
+version match between both plugins. For example, when using netbox-plugin-dns `v1.5.5` install
+netbox-plugin-dns-bridge `v1.5.x`.
## Installation guide
This setup provisions a BIND9 server directly with DNS data from NetBox. BIND9 can optionally run on
@@ -222,6 +227,7 @@ This guide assumes:
########## ZONES ##########
view "public" {
match-clients { public; };
+ allow-notify { key "public_view_key"; };
catalog-zones {
zone "catz"
@@ -240,6 +246,7 @@ This guide assumes:
view "private" {
match-clients { private; };
+ allow-notify { key "private_view_key"; };
catalog-zones {
zone "catz"
diff --git a/netbox_dns_bridge/notify_handler.py b/netbox_dns_bridge/notify_handler.py
index 7e57123..f1f27a5 100644
--- a/netbox_dns_bridge/notify_handler.py
+++ b/netbox_dns_bridge/notify_handler.py
@@ -12,6 +12,7 @@ from .logger import get_logger
from .models import SeenTransferClients
logger = get_logger(__name__)
+SETTINGS = settings.PLUGINS_CONFIG["netbox_dns_bridge"]
class SendDNSNotify(JobRunner):
@@ -19,7 +20,7 @@ class SendDNSNotify(JobRunner):
name = "Send DNS NOTIFY"
def _load_tsig_key(self, view_name: str) -> dns.tsig.Key:
- tsig_keys = settings.PLUGINS_CONFIG["netbox_dns_bridge"].get("tsig_keys", {})
+ tsig_keys = SETTINGS.get("tsig_keys", {})
if view_name not in tsig_keys:
raise RuntimeError(f"No TSIG key configured for view '{view_name}'")
@@ -50,7 +51,8 @@ class SendDNSNotify(JobRunner):
view = View.objects.get(name=view_name)
tsig_key = self._load_tsig_key(view_name)
- seen_cutoff = timezone.now() - timezone.timedelta(hours=1)
+ cutoff_hours = SETTINGS.get("notify_client_dead_after_hours", 24)
+ seen_cutoff = timezone.now() - timezone.timedelta(hours=cutoff_hours)
clients = SeenTransferClients.objects.filter(
view=view, last_seen__gte=seen_cutoff
)
diff --git a/netbox_dns_bridge/request_handler.py b/netbox_dns_bridge/request_handler.py
index 9995b52..2b098ba 100644
--- a/netbox_dns_bridge/request_handler.py
+++ b/netbox_dns_bridge/request_handler.py
@@ -12,7 +12,7 @@ import dns.exception
import dns.renderer
from .logger import get_logger
from django.utils import timezone
-from netbox_dns.models import Zone, Record
+from netbox_dns.models import Zone, Record, View
from netbox_dns_bridge import catalog_zone_manager as catzm
from .models import SeenTransferClients
@@ -103,7 +103,8 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
# Ensure rdataset has the same rdclass as the zone
if rdataset.rdclass != zone.rdclass:
raise ValueError(
- f"rdataset rdclass {rdataset.rdclass} does not match zone rdclass {zone.rdclass}"
+ f"rdataset rdclass {rdataset.rdclass} does not match "
+ f"zone rdclass {zone.rdclass}"
)
# Check if the rdataset has any rdata before creating an RRset
@@ -143,6 +144,13 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
wire = response.to_wire(multi=False)
self._send_response(wire)
+ def _track_seen_client(source_ip: str, view: View) -> None:
+ SeenTransferClients.objects.update_or_create(
+ source_ip=source_ip,
+ view=view,
+ defaults={"last_seen": timezone.now()},
+ )
+
def _handle_soa_request(self, query, soa_rrset, zone, peer, nb_view, dname) -> None:
# We assume that the SOA rdataset has at least one record (it usually does).
soa_rdata = soa_rrset[0] # Get the first SOA record
@@ -274,13 +282,6 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
)
wire = r.get_wire()
self._send_response(wire)
-
- SeenTransferClients.objects.update_or_create(
- source_ip=peer,
- view=nb_view,
- defaults={"last_seen": timezone.now()},
- )
-
logger.info(f"{peer} AXFR {nb_view.name}/{dname}")
def _handle_dns_query(self, wire) -> None:
@@ -361,8 +362,10 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
if qtype == dns.rdatatype.SOA:
self._handle_soa_request(query, soa_rrset, zone, peer, nb_view, dname)
+ self._track_seen_client(peer, nb_view)
elif qtype == dns.rdatatype.AXFR:
self._handle_axfr_request(query, zone, peer, nb_view, dname)
+ self._track_seen_client(peer, nb_view)
class UDPRequestHandler(DNSBaseRequestHandler):
diff --git a/netbox_dns_bridge/signals/netbox_dns_zones.py b/netbox_dns_bridge/signals/netbox_dns_zones.py
index e23c21b..ccc5326 100644
--- a/netbox_dns_bridge/signals/netbox_dns_zones.py
+++ b/netbox_dns_bridge/signals/netbox_dns_zones.py
@@ -1,11 +1,14 @@
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.db import transaction
+from django.conf import settings
from netbox.context import current_request
from netbox_dns.models import Zone
from netbox_dns_bridge import catalog_zone_manager as catzm
from netbox_dns_bridge import notify_handler
+SETTINGS = settings.PLUGINS_CONFIG["netbox_dns_bridge"]
+
@receiver(pre_save, sender=Zone)
def zone_pre_save(sender, instance, **kwargs):
@@ -31,7 +34,9 @@ def zone_post_save(sender, instance, created, **kwargs):
def _on_commit():
catzm.increment_soa_serial()
- notify_handler.schedule(zone)
+
+ if SETTINGS.get("notify_clients", False):
+ notify_handler.schedule(zone)
transaction.on_commit(_on_commit)