Test dynamic DHCP names in the DNS VM test

A client with a hostname is published with its reverse record, a client
claiming a configured name does not take it over, and a name outside the
domain is cut to its first label.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
2026-10-30 00:30:11 +01:00
co-authored by Claude Sonnet 5.5
parent 2eab735f59
commit a01214707a
+52 -1
View File
@@ -9,6 +9,18 @@
let
common = import ./common.nix;
clientMac = "02:00:00:00:00:51";
# Only the settings module's services (router-leases-dns) are used here; the
# router itself is configured below.
settings = pkgs.writeText "settings.json" (
builtins.toJSON {
system = {
hostName = "router";
stateVersion = "26.11";
};
router = { };
}
);
in
pkgs.testers.runNixOSTest {
name = "router-dns";
@@ -55,7 +67,11 @@ pkgs.testers.runNixOSTest {
router =
{ lib, ... }:
{
imports = [ self.nixosModules.router ];
imports = [
self.nixosModules.router
self.nixosModules.settings
];
routerSettings.file = settings;
virtualisation.vlans = [
1
2
@@ -84,6 +100,7 @@ pkgs.testers.runNixOSTest {
dns = {
forwarders = [ "203.0.113.1" ];
domain = "lan.test";
dynamicLeases = true;
hosts = {
nas = [
"192.168.10.20"
@@ -116,6 +133,23 @@ pkgs.testers.runNixOSTest {
matchConfig.Name = "eth1";
linkConfig.MACAddress = "${clientMac}";
networkConfig.DHCP = "ipv4";
# Claims a name the configuration publishes for someone else.
dhcpV4Config.Hostname = "nas";
};
};
laptop =
{ lib, ... }:
{
virtualisation.vlans = [ 2 ];
networking.useNetworkd = true;
networking.useDHCP = false;
networking.firewall.enable = false;
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ ];
systemd.network.networks."10-lan" = {
matchConfig.Name = "eth1";
networkConfig.DHCP = "ipv4";
dhcpV4Config.Hostname = "Laptop.evil.example";
};
};
};
@@ -148,6 +182,23 @@ pkgs.testers.runNixOSTest {
assert q("router") == "192.168.10.1", q("router")
assert q("1.10.168.192.in-addr.arpa", "PTR") == "router.lan.test."
with subtest("DHCP clients are published under the name they gave"):
laptop.wait_until_succeeds("ip -4 addr show dev eth1 | grep -q 192.168.10.1[0-9][0-9]")
addr = laptop.succeed("ip -4 -o addr show dev eth1").split()[3].split("/")[0]
router.wait_until_succeeds(f"dig +short @192.168.10.1 laptop.lan.test | grep -qx {addr}", timeout=60)
assert q("laptop.lan.test") == addr
rev = ".".join(reversed(addr.split("."))) + ".in-addr.arpa"
assert q(rev, "PTR") == "laptop.lan.test."
with subtest("a client cannot take over a name the configuration publishes"):
assert q("nas.lan.test") == "192.168.10.20", q("nas.lan.test")
assert q("20.10.168.192.in-addr.arpa", "PTR") == "nas.lan.test."
with subtest("the status shows what was published and skipped"):
import json
st = json.loads(router.succeed("cat /run/router-leases-dns/status.json"))
router.log(str(st))
with subtest("the local domain is authoritative: unknown names are not forwarded"):
# Positive control: resolving through the router works at all.
assert q("other.example.test") == "203.0.113.8"