Publish DHCP clients' names in DNS
router-webui leases-dns reads Kea's leases and publishes <name>.<domain> with reverse records in the running Unbound (router.dns.dynamicLeases). Names the configuration publishes are never taken over; a client's name is cut to its first label and kept inside the local domain; of two clients the newer lease wins. The DNS page switches it on and shows what is published and what was skipped; router-setup enables it with a domain. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
@@ -391,7 +391,7 @@ let
|
||||
+ (if $v6 == "y" then { ipv6: { pd: true } } else {} end))
|
||||
}
|
||||
}
|
||||
+ (if $domain == "" then {} else { dns: { domain: $domain } } end))
|
||||
+ (if $domain == "" then {} else { dns: { domain: $domain, dynamicLeases: true } } end))
|
||||
}' > "$config/settings.json"
|
||||
|
||||
if [ "$wan_type" = pppoe ]; then
|
||||
|
||||
+20
-2
@@ -43,12 +43,23 @@ let
|
||||
lib.nameValuePair selfName selfAddrs
|
||||
);
|
||||
|
||||
hostRecords = lib.mapAttrsToList (n: a: lib.nameValuePair (fqdn n) a) dns.hosts;
|
||||
reservationRecords = map (r: lib.nameValuePair (fqdn r.name) r.value) reservations;
|
||||
records =
|
||||
selfRecord
|
||||
++ lib.mapAttrsToList (n: a: lib.nameValuePair (fqdn n) a) dns.hosts
|
||||
++ map (r: lib.nameValuePair (fqdn r.name) r.value) reservations
|
||||
++ hostRecords
|
||||
++ reservationRecords
|
||||
++ lib.mapAttrsToList (n: a: lib.nameValuePair n a) dns.overrides;
|
||||
|
||||
# What the configuration publishes itself: dynamic names (the clients' host
|
||||
# names, see dns.dynamicLeases) must never take one of these over.
|
||||
staticNames = builtins.toJSON {
|
||||
inherit domain;
|
||||
names = map (r: r.name) (selfRecord ++ hostRecords ++ reservationRecords) ++ map (r: r.name) selfBare;
|
||||
overrides = lib.attrNames dns.overrides;
|
||||
forwardZones = lib.attrNames dns.forwardZones;
|
||||
};
|
||||
|
||||
# Unbound reads these as one quoted argument.
|
||||
localData = lib.concatMap (r: map (a: ''"${r.name}. ${rrOf a} ${a}"'') r.value) (records ++ selfBare);
|
||||
# Reverse records for hosts and reservations (not for wildcard overrides).
|
||||
@@ -67,8 +78,15 @@ let
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
environment.etc."router/dns-static.json".text = staticNames;
|
||||
warnings = lib.optional (dns.dynamicLeases && domain == null) ''
|
||||
router.dns.dynamicLeases has no effect without router.dns.domain: the clients' names need a domain to live in.
|
||||
'';
|
||||
|
||||
services.unbound = {
|
||||
enable = true;
|
||||
# What router-webui leases-dns publishes the clients' names through.
|
||||
localControlSocketPath = lib.mkIf dns.dynamicLeases "/run/unbound/unbound.ctl";
|
||||
settings = {
|
||||
server = {
|
||||
interface = [
|
||||
|
||||
@@ -528,6 +528,21 @@ in
|
||||
description = "Like `dns.hosts`, but the name and everything below it answer with these addresses (wildcard).";
|
||||
};
|
||||
|
||||
dns.dynamicLeases = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Publish the host names of DHCP clients as `<name>.<dns.domain>` (with
|
||||
reverse records), updated as leases come and go. Needs `dns.domain`.
|
||||
|
||||
A client picks its own name, so only its first label is used, only
|
||||
under the local domain, and a name that is configured here (`dns.hosts`,
|
||||
a reservation, the router itself, a wildcard or a forward zone it falls
|
||||
under) is never taken over. Of two clients claiming one name the newer
|
||||
lease wins. Run by `router-webui leases-dns` (the settings module).
|
||||
'';
|
||||
};
|
||||
|
||||
dns.forwardZones = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
default = { };
|
||||
|
||||
@@ -190,6 +190,37 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
# The DHCP clients' names in DNS (router.dns.dynamicLeases): reads Kea's
|
||||
# leases and publishes them in the running Unbound.
|
||||
systemd.services.router-leases-dns = lib.mkIf (config.router.enable && config.router.dns.dynamicLeases) {
|
||||
description = "Publish DHCP clients' names in DNS";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"unbound.service"
|
||||
"kea-dhcp4-server.service"
|
||||
];
|
||||
path = [ config.services.unbound.package ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${lib.getExe webui} leases-dns -leases /var/lib/kea/dhcp4.leases -static /etc/router/dns-static.json -socket ${config.services.unbound.localControlSocketPath} -run /run/router-leases-dns";
|
||||
RuntimeDirectory = "router-leases-dns";
|
||||
Restart = "always";
|
||||
RestartSec = 5;
|
||||
# It reads leases and talks to Unbound's socket; nothing else.
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateNetwork = true;
|
||||
PrivateDevices = true;
|
||||
NoNewPrivileges = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Rebuilding on the device must work without internet (the uplink may be
|
||||
# what is being configured). What a changed configuration rebuilds is all
|
||||
# generated by NixOS, but those builds need these packages; found by
|
||||
|
||||
Reference in New Issue
Block a user