Loading nixos/modules/system/boot/resolved.nix +37 −3 Original line number Diff line number Diff line { config, lib, pkgs, utils, ... }: Loading @@ -14,12 +13,15 @@ let elem isList literalExpression mapAttrs' mapAttrsToList mkIf mkMerge mkOption mkOrder mkRenamedOptionModule mkRemovedOptionModule nameValuePair optionalAttrs types ; Loading Loading @@ -132,6 +134,27 @@ in }; }; dnsDelegates = mkOption { description = '' dns-delegate files to be created. See {manpage}`systemd.dns-delegate(5)` for more info. ''; default = { }; type = types.attrsOf ( types.submodule { options.Delegate = mkOption { description = '' Settings option for systemd dns-delegate files. See {manpage}`systemd.dns-delegate(5)` for all available options. ''; type = types.submodule { freeformType = types.attrsOf unitOption; }; }; } ); }; }; boot.initrd.services.resolved.enable = mkOption { Loading Loading @@ -167,7 +190,12 @@ in systemd.services.systemd-resolved = { wantedBy = [ "sysinit.target" ]; aliases = [ "dbus-org.freedesktop.resolve1.service" ]; reloadTriggers = [ config.environment.etc."systemd/resolved.conf".source ]; reloadTriggers = [ config.environment.etc."systemd/resolved.conf".source ] ++ mapAttrsToList ( name: _: config.environment.etc."systemd/dns-delegate.d/${name}.dns-delegate".source ) cfg.dnsDelegates; stopIfChanged = false; }; Loading @@ -180,7 +208,13 @@ in } // optionalAttrs dnsmasqResolve { "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf"; }; } // mapAttrs' ( name: value: nameValuePair "systemd/dns-delegate.d/${name}.dns-delegate" { text = settingsToSections (transformSettings value); } ) cfg.dnsDelegates; # If networkmanager is enabled, ask it to interface with resolved. networking.networkmanager.dns = "systemd-resolved"; Loading nixos/tests/systemd-resolved.nix +65 −12 Original line number Diff line number Diff line Loading @@ -36,13 +36,48 @@ }; }; nodes.delegate_server = { lib, config, ... }: let delegateZone = pkgs.writeTextDir "delegated.example.org.zone" '' @ SOA ns.delegated.example.org. noc.delegated.example.org. 2019031301 86400 7200 3600000 172800 test A ${(lib.head config.networking.interfaces.eth1.ipv4.addresses).address} test AAAA ${(lib.head config.networking.interfaces.eth1.ipv6.addresses).address} ''; in { networking.firewall.enable = false; networking.useDHCP = false; networking.interfaces.eth1.ipv6.addresses = lib.mkForce [ { address = "fd00::3"; prefixLength = 64; } ]; services.knot = { enable = true; settings = { server.listen = [ "0.0.0.0@53" "::@53" ]; template.default.storage = delegateZone; zone."delegated.example.org".file = "delegated.example.org.zone"; }; }; }; nodes.client = { nodes, ... }: let inherit (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses) address; serverAddress = (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address; delegateAddress = (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv4.addresses).address; in { networking.nameservers = [ address ]; networking.nameservers = [ serverAddress ]; networking.interfaces.eth1.ipv6.addresses = lib.mkForce [ { address = "fd00::2"; Loading @@ -51,6 +86,12 @@ ]; services.resolved.enable = true; services.resolved.settings.Resolve.FallbackDNS = [ ]; services.resolved.dnsDelegates.example-org = { Delegate = { DNS = delegateAddress; Domains = [ "delegated.example.org" ]; }; }; networking.useNetworkd = true; networking.useDHCP = false; systemd.network.networks."40-eth0".enable = false; Loading @@ -69,23 +110,35 @@ let address4 = (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address; address6 = (lib.head nodes.server.networking.interfaces.eth1.ipv6.addresses).address; delegateAddress4 = (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv4.addresses).address; delegateAddress6 = (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv6.addresses).address; in #python '' start_all() server.wait_for_unit("multi-user.target") delegate_server.wait_for_unit("multi-user.target") def test_client(): query = client.succeed("resolvectl query example.com") assert "${address4}" in query assert "${address6}" in query client.succeed("ping -4 -c 1 example.com") client.succeed("ping -6 -c 1 example.com") def test_resolve(domain, expected_addrs): query = client.succeed(f"resolvectl query {domain}") for addr in expected_addrs: assert addr in query, f"Expected {addr} in: {query}" client.succeed(f"ping -4 -c 1 {domain}") client.succeed(f"ping -6 -c 1 {domain}") with subtest("resolve in initrd"): client.wait_for_unit("initrd.target") test_client() test_resolve("example.com", ["${address4}", "${address6}"]) client.switch_root() with subtest("resolve after switch-root"): client.wait_for_unit("multi-user.target") test_client() test_resolve("example.com", ["${address4}", "${address6}"]) with subtest("dns-delegate resolves delegated subdomain"): test_resolve("test.delegated.example.org", ["${delegateAddress4}", "${delegateAddress6}"]) ''; } Loading
nixos/modules/system/boot/resolved.nix +37 −3 Original line number Diff line number Diff line { config, lib, pkgs, utils, ... }: Loading @@ -14,12 +13,15 @@ let elem isList literalExpression mapAttrs' mapAttrsToList mkIf mkMerge mkOption mkOrder mkRenamedOptionModule mkRemovedOptionModule nameValuePair optionalAttrs types ; Loading Loading @@ -132,6 +134,27 @@ in }; }; dnsDelegates = mkOption { description = '' dns-delegate files to be created. See {manpage}`systemd.dns-delegate(5)` for more info. ''; default = { }; type = types.attrsOf ( types.submodule { options.Delegate = mkOption { description = '' Settings option for systemd dns-delegate files. See {manpage}`systemd.dns-delegate(5)` for all available options. ''; type = types.submodule { freeformType = types.attrsOf unitOption; }; }; } ); }; }; boot.initrd.services.resolved.enable = mkOption { Loading Loading @@ -167,7 +190,12 @@ in systemd.services.systemd-resolved = { wantedBy = [ "sysinit.target" ]; aliases = [ "dbus-org.freedesktop.resolve1.service" ]; reloadTriggers = [ config.environment.etc."systemd/resolved.conf".source ]; reloadTriggers = [ config.environment.etc."systemd/resolved.conf".source ] ++ mapAttrsToList ( name: _: config.environment.etc."systemd/dns-delegate.d/${name}.dns-delegate".source ) cfg.dnsDelegates; stopIfChanged = false; }; Loading @@ -180,7 +208,13 @@ in } // optionalAttrs dnsmasqResolve { "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf"; }; } // mapAttrs' ( name: value: nameValuePair "systemd/dns-delegate.d/${name}.dns-delegate" { text = settingsToSections (transformSettings value); } ) cfg.dnsDelegates; # If networkmanager is enabled, ask it to interface with resolved. networking.networkmanager.dns = "systemd-resolved"; Loading
nixos/tests/systemd-resolved.nix +65 −12 Original line number Diff line number Diff line Loading @@ -36,13 +36,48 @@ }; }; nodes.delegate_server = { lib, config, ... }: let delegateZone = pkgs.writeTextDir "delegated.example.org.zone" '' @ SOA ns.delegated.example.org. noc.delegated.example.org. 2019031301 86400 7200 3600000 172800 test A ${(lib.head config.networking.interfaces.eth1.ipv4.addresses).address} test AAAA ${(lib.head config.networking.interfaces.eth1.ipv6.addresses).address} ''; in { networking.firewall.enable = false; networking.useDHCP = false; networking.interfaces.eth1.ipv6.addresses = lib.mkForce [ { address = "fd00::3"; prefixLength = 64; } ]; services.knot = { enable = true; settings = { server.listen = [ "0.0.0.0@53" "::@53" ]; template.default.storage = delegateZone; zone."delegated.example.org".file = "delegated.example.org.zone"; }; }; }; nodes.client = { nodes, ... }: let inherit (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses) address; serverAddress = (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address; delegateAddress = (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv4.addresses).address; in { networking.nameservers = [ address ]; networking.nameservers = [ serverAddress ]; networking.interfaces.eth1.ipv6.addresses = lib.mkForce [ { address = "fd00::2"; Loading @@ -51,6 +86,12 @@ ]; services.resolved.enable = true; services.resolved.settings.Resolve.FallbackDNS = [ ]; services.resolved.dnsDelegates.example-org = { Delegate = { DNS = delegateAddress; Domains = [ "delegated.example.org" ]; }; }; networking.useNetworkd = true; networking.useDHCP = false; systemd.network.networks."40-eth0".enable = false; Loading @@ -69,23 +110,35 @@ let address4 = (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address; address6 = (lib.head nodes.server.networking.interfaces.eth1.ipv6.addresses).address; delegateAddress4 = (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv4.addresses).address; delegateAddress6 = (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv6.addresses).address; in #python '' start_all() server.wait_for_unit("multi-user.target") delegate_server.wait_for_unit("multi-user.target") def test_client(): query = client.succeed("resolvectl query example.com") assert "${address4}" in query assert "${address6}" in query client.succeed("ping -4 -c 1 example.com") client.succeed("ping -6 -c 1 example.com") def test_resolve(domain, expected_addrs): query = client.succeed(f"resolvectl query {domain}") for addr in expected_addrs: assert addr in query, f"Expected {addr} in: {query}" client.succeed(f"ping -4 -c 1 {domain}") client.succeed(f"ping -6 -c 1 {domain}") with subtest("resolve in initrd"): client.wait_for_unit("initrd.target") test_client() test_resolve("example.com", ["${address4}", "${address6}"]) client.switch_root() with subtest("resolve after switch-root"): client.wait_for_unit("multi-user.target") test_client() test_resolve("example.com", ["${address4}", "${address6}"]) with subtest("dns-delegate resolves delegated subdomain"): test_resolve("test.delegated.example.org", ["${delegateAddress4}", "${delegateAddress6}"]) ''; }