Xfce updates 2024-12-27 (#368555)
[NixPkgs.git] / nixos / tests / common / resolver.nix
blob76d720f1fb85977b6792eb92c4e398b62b3f6f6a
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
5 # name server.
7   config,
8   nodes,
9   pkgs,
10   lib,
11   ...
15   options.test-support.resolver.enable = lib.mkOption {
16     type = lib.types.bool;
17     default = true;
18     internal = true;
19     description = ''
20       Whether to enable the resolver that automatically discovers zone in the
21       test network.
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
29       resolver.
30     '';
31   };
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 {
39       name = ".";
40       master = true;
41       file =
42         let
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);
51           };
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.
57           #
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 =
62             let
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.-]+";
70               matchAliases =
71                 str:
72                 let
73                   matched = builtins.match "[ \t]+(${reHost})(.*)" str;
74                   continue = lib.singleton (lib.head matched) ++ matchAliases (lib.last matched);
75                 in
76                 lib.optional (matched != null) continue;
78               matchLine =
79                 str:
80                 let
81                   result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str;
82                 in
83                 if result == null then
84                   null
85                 else
86                   {
87                     ipAddr = lib.head result;
88                     hosts = lib.singleton (lib.elemAt result 1) ++ matchAliases (lib.last result);
89                   };
91               skipLine =
92                 str:
93                 let
94                   rest = builtins.match "[^\n]*\n(.*)" str;
95                 in
96                 if rest == null then "" else lib.head rest;
98               getEntries =
99                 str: acc:
100                 let
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;
105                 in
106                 if str == "" then acc else continue;
108               isIPv6 = str: builtins.match ".*:.*" str != null;
109               loopbackIps = [
110                 "127.0.0.1"
111                 "::1"
112               ];
113               filterLoopback = lib.filter (e: !lib.elem e.ipAddr loopbackIps);
115               allEntries = lib.concatMap (
116                 entry:
117                 map (host: {
118                   inherit host;
119                   ${if isIPv6 entry.ipAddr then "ipv6" else "ipv4"} = entry.ipAddr;
120                 }) entry.hosts
121               ) (filterLoopback (getEntries (allHosts + "\n") [ ]));
123               mkRecords =
124                 entry:
125                 let
126                   records =
127                     lib.optional (entry ? ipv6) "AAAA ${entry.ipv6}"
128                     ++ lib.optional (entry ? ipv4) "A ${entry.ipv4}";
129                   mkRecord = typeAndData: "${entry.host}. IN ${typeAndData}";
130                 in
131                 lib.concatMapStringsSep "\n" mkRecord records;
133             in
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
138           # be 'subZones':
139           #
140           #  * foo.example.com.
141           #  * bar.example.com.
142           #
143           # While the following would *not* be 'subZones':
144           #
145           #  * example.com.
146           #  * com.
147           #
148           subZones =
149             let
150               allZones = lib.concatMap (zi: zi.zones) zoneInfo;
151               isSubZoneOf = z1: z2: lib.hasSuffix z2 z1 && z1 != z2;
152             in
153             lib.filter (z: lib.any (isSubZoneOf z) allZones) allZones;
155           # All the zones without 'subZones'.
156           filteredZoneInfo = map (
157             zi:
158             zi
159             // {
160               zones = lib.filter (x: !lib.elem x subZones) zi.zones;
161             }
162           ) zoneInfo;
164         in
165         pkgs.writeText "fake-root.zone" ''
166           $TTL 3600
167           . IN SOA ns.fakedns. admin.fakedns. ( 1 3h 1h 1w 1d )
168           ns.fakedns. IN A ${config.networking.primaryIPAddress}
169           . IN NS ns.fakedns.
170           ${lib.concatImapStrings (
171             num:
172             { ip, zones }:
173             ''
174               ns${toString num}.fakedns. IN A ${ip}
175               ${lib.concatMapStrings (zone: ''
176                 ${zone} IN NS ns${toString num}.fakedns.
177               '') zones}
178             ''
179           ) (lib.filter (zi: zi.zones != [ ]) filteredZoneInfo)}
180           ${recordsFromExtraHosts}
181         '';
182     };
183   };