1 # This module automatically discovers zones in BIND and NSD NixOS
2 # configurations and creates zones for all definitions of networking.extraHosts
3 # (except those that point to 127.0.0.1 or ::1) within the current test network
4 # and delegates these zones using a fake root zone served by a BIND recursive
15 options.test-support.resolver.enable = lib.mkOption {
16 type = lib.types.bool;
20 Whether to enable the resolver that automatically discovers zone in the
23 This option is `true` by default, because the module
24 defining this option needs to be explicitly imported.
26 The reason this option exists is for the
27 {file}`nixos/tests/common/acme/server` module, which
28 needs that option to disable the resolver once the user has set its own
33 config = lib.mkIf config.test-support.resolver.enable {
34 networking.firewall.enable = false;
35 services.bind.enable = true;
36 services.bind.cacheNetworks = lib.mkForce [ "any" ];
37 services.bind.forwarders = lib.mkForce [ ];
38 services.bind.zones = lib.singleton {
43 addDot = zone: zone + lib.optionalString (!lib.hasSuffix "." zone) ".";
44 mkNsdZoneNames = zones: map addDot (lib.attrNames zones);
45 mkBindZoneNames = zones: map addDot (lib.attrNames zones);
46 getZones = cfg: mkNsdZoneNames cfg.services.nsd.zones ++ mkBindZoneNames cfg.services.bind.zones;
48 getZonesForNode = attrs: {
49 ip = attrs.config.networking.primaryIPAddress;
50 zones = lib.filter (zone: zone != ".") (getZones attrs.config);
53 zoneInfo = lib.mapAttrsToList (lib.const getZonesForNode) nodes;
55 # A and AAAA resource records for all the definitions of
56 # networking.extraHosts except those for 127.0.0.1 or ::1.
58 # The result is an attribute set with keys being the host name and the
59 # values are either { ipv4 = ADDR; } or { ipv6 = ADDR; } where ADDR is
60 # the IP address for the corresponding key.
61 recordsFromExtraHosts =
63 getHostsForNode = lib.const (n: n.config.networking.extraHosts);
64 allHostsList = lib.mapAttrsToList getHostsForNode nodes;
65 allHosts = lib.concatStringsSep "\n" allHostsList;
67 reIp = "[a-fA-F0-9.:]+";
68 reHost = "[a-zA-Z0-9.-]+";
73 matched = builtins.match "[ \t]+(${reHost})(.*)" str;
74 continue = lib.singleton (lib.head matched) ++ matchAliases (lib.last matched);
76 lib.optional (matched != null) continue;
81 result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str;
83 if result == null then
87 ipAddr = lib.head result;
88 hosts = lib.singleton (lib.elemAt result 1) ++ matchAliases (lib.last result);
94 rest = builtins.match "[^\n]*\n(.*)" str;
96 if rest == null then "" else lib.head rest;
101 result = matchLine str;
102 next = getEntries (skipLine str);
103 newEntry = acc ++ lib.singleton result;
104 continue = if result == null then next acc else next newEntry;
106 if str == "" then acc else continue;
108 isIPv6 = str: builtins.match ".*:.*" str != null;
113 filterLoopback = lib.filter (e: !lib.elem e.ipAddr loopbackIps);
115 allEntries = lib.concatMap (
119 ${if isIPv6 entry.ipAddr then "ipv6" else "ipv4"} = entry.ipAddr;
121 ) (filterLoopback (getEntries (allHosts + "\n") [ ]));
127 lib.optional (entry ? ipv6) "AAAA ${entry.ipv6}"
128 ++ lib.optional (entry ? ipv4) "A ${entry.ipv4}";
129 mkRecord = typeAndData: "${entry.host}. IN ${typeAndData}";
131 lib.concatMapStringsSep "\n" mkRecord records;
134 lib.concatMapStringsSep "\n" mkRecords allEntries;
136 # All of the zones that are subdomains of existing zones.
137 # For example if there is only "example.com" the following zones would
143 # While the following would *not* be 'subZones':
150 allZones = lib.concatMap (zi: zi.zones) zoneInfo;
151 isSubZoneOf = z1: z2: lib.hasSuffix z2 z1 && z1 != z2;
153 lib.filter (z: lib.any (isSubZoneOf z) allZones) allZones;
155 # All the zones without 'subZones'.
156 filteredZoneInfo = map (
160 zones = lib.filter (x: !lib.elem x subZones) zi.zones;
165 pkgs.writeText "fake-root.zone" ''
167 . IN SOA ns.fakedns. admin.fakedns. ( 1 3h 1h 1w 1d )
168 ns.fakedns. IN A ${config.networking.primaryIPAddress}
170 ${lib.concatImapStrings (
174 ns${toString num}.fakedns. IN A ${ip}
175 ${lib.concatMapStrings (zone: ''
176 ${zone} IN NS ns${toString num}.fakedns.
179 ) (lib.filter (zi: zi.zones != [ ]) filteredZoneInfo)}
180 ${recordsFromExtraHosts}