1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7
8let
9 cfg = config.networking.jool;
10
11 jool = config.boot.kernelPackages.jool;
12 jool-cli = pkgs.jool-cli;
13
14 hardening = {
15 # Run as unprivileged user
16 User = "jool";
17 Group = "jool";
18 DynamicUser = true;
19
20 # Restrict filesystem to only read the jool module
21 TemporaryFileSystem = [ "/" ];
22 BindReadOnlyPaths = [
23 builtins.storeDir
24 "/run/booted-system/kernel-modules"
25 ];
26
27 # Give capabilities to load the module and configure it
28 AmbientCapabilities = [
29 "CAP_SYS_MODULE"
30 "CAP_NET_ADMIN"
31 ];
32 RestrictAddressFamilies = [ "AF_NETLINK" ];
33
34 # Other restrictions
35 RestrictNamespaces = [ "net" ];
36 SystemCallFilter = [
37 "@system-service"
38 "@module"
39 ];
40 CapabilityBoundingSet = [
41 "CAP_SYS_MODULE"
42 "CAP_NET_ADMIN"
43 ];
44 };
45
46 configFormat = pkgs.formats.json { };
47
48 # Generate the config file of instance `name`
49 nat64Conf =
50 name: configFormat.generate "jool-nat64-${name}.conf" (cfg.nat64.${name} // { instance = name; });
51 siitConf =
52 name: configFormat.generate "jool-siit-${name}.conf" (cfg.siit.${name} // { instance = name; });
53
54 # NAT64 config type
55 nat64Options = lib.types.submodule {
56 # The format is plain JSON
57 freeformType = configFormat.type;
58 # Some options with a default value
59 options.framework = lib.mkOption {
60 type = lib.types.enum [
61 "netfilter"
62 "iptables"
63 ];
64 default = "netfilter";
65 description = ''
66 The framework to use for attaching Jool's translation to the exist
67 kernel packet processing rules. See the
68 [documentation](https://nicmx.github.io/Jool/en/intro-jool.html#design)
69 for the differences between the two options.
70 '';
71 };
72 options.global.pool6 = lib.mkOption {
73 type = lib.types.strMatching "[[:xdigit:]:]+/[[:digit:]]+" // {
74 description = "Network prefix in CIDR notation";
75 };
76 default = "64:ff9b::/96";
77 description = ''
78 The prefix used for embedding IPv4 into IPv6 addresses.
79 Defaults to the well-known NAT64 prefix, defined by
80 [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
81 '';
82 };
83 };
84
85 # SIIT config type
86 siitOptions = lib.types.submodule {
87 # The format is, again, plain JSON
88 freeformType = configFormat.type;
89 # Some options with a default value
90 options = { inherit (nat64Options.getSubOptions [ ]) framework; };
91 };
92
93 makeNat64Unit = name: opts: {
94 "jool-nat64-${name}" = {
95 description = "Jool, NAT64 setup of instance ${name}";
96 documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
97 after = [ "network.target" ];
98 wantedBy = [ "multi-user.target" ];
99 serviceConfig = {
100 Type = "oneshot";
101 RemainAfterExit = true;
102 ExecStartPre = "${pkgs.kmod}/bin/modprobe jool";
103 ExecStart = "${jool-cli}/bin/jool file handle ${nat64Conf name}";
104 ExecStop = "${jool-cli}/bin/jool -f ${nat64Conf name} instance remove";
105 }
106 // hardening;
107 };
108 };
109
110 makeSiitUnit = name: opts: {
111 "jool-siit-${name}" = {
112 description = "Jool, SIIT setup of instance ${name}";
113 documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
114 after = [ "network.target" ];
115 wantedBy = [ "multi-user.target" ];
116 serviceConfig = {
117 Type = "oneshot";
118 RemainAfterExit = true;
119 ExecStartPre = "${pkgs.kmod}/bin/modprobe jool_siit";
120 ExecStart = "${jool-cli}/bin/jool_siit file handle ${siitConf name}";
121 ExecStop = "${jool-cli}/bin/jool_siit -f ${siitConf name} instance remove";
122 }
123 // hardening;
124 };
125 };
126
127 checkNat64 = name: _: ''
128 printf 'Validating Jool configuration for NAT64 instance "${name}"... '
129 jool file check ${nat64Conf name}
130 printf 'Ok.\n'; touch "$out"
131 '';
132
133 checkSiit = name: _: ''
134 printf 'Validating Jool configuration for SIIT instance "${name}"... '
135 jool_siit file check ${siitConf name}
136 printf 'Ok.\n'; touch "$out"
137 '';
138
139in
140
141{
142 options = {
143 networking.jool.enable = lib.mkOption {
144 type = lib.types.bool;
145 default = false;
146 relatedPackages = [
147 "linuxPackages.jool"
148 "jool-cli"
149 ];
150 description = ''
151 Whether to enable Jool, an Open Source implementation of IPv4/IPv6
152 translation on Linux.
153
154 Jool can perform stateless IP/ICMP translation (SIIT) or stateful
155 NAT64, analogous to the IPv4 NAPT. Refer to the upstream
156 [documentation](https://nicmx.github.io/Jool/en/intro-xlat.html) for
157 the supported modes of translation and how to configure them.
158
159 Enabling this option will install the Jool kernel module and the
160 command line tools for controlling it.
161 '';
162 };
163
164 networking.jool.nat64 = lib.mkOption {
165 type = lib.types.attrsOf nat64Options;
166 default = { };
167 example = lib.literalExpression ''
168 {
169 default = {
170 # custom NAT64 prefix
171 global.pool6 = "2001:db8:64::/96";
172
173 # Port forwarding
174 bib = [
175 { # SSH 192.0.2.16 → 2001:db8:a::1
176 "protocol" = "TCP";
177 "ipv4 address" = "192.0.2.16#22";
178 "ipv6 address" = "2001:db8:a::1#22";
179 }
180 { # DNS (TCP) 192.0.2.16 → 2001:db8:a::2
181 "protocol" = "TCP";
182 "ipv4 address" = "192.0.2.16#53";
183 "ipv6 address" = "2001:db8:a::2#53";
184 }
185 { # DNS (UDP) 192.0.2.16 → 2001:db8:a::2
186 "protocol" = "UDP";
187 "ipv4 address" = "192.0.2.16#53";
188 "ipv6 address" = "2001:db8:a::2#53";
189 }
190 ];
191
192 pool4 = [
193 # Port ranges for dynamic translation
194 { protocol = "TCP"; prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
195 { protocol = "UDP"; prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
196 { protocol = "ICMP"; prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
197
198 # Ports for static BIB entries
199 { protocol = "TCP"; prefix = "192.0.2.16/32"; "port range" = "22"; }
200 { protocol = "UDP"; prefix = "192.0.2.16/32"; "port range" = "53"; }
201 ];
202 };
203 }
204 '';
205 description = ''
206 Definitions of NAT64 instances of Jool.
207 See the
208 [documentation](https://nicmx.github.io/Jool/en/config-atomic.html) for
209 the available options. Also check out the
210 [tutorial](https://nicmx.github.io/Jool/en/run-nat64.html) for an
211 introduction to NAT64 and how to troubleshoot the setup.
212
213 The attribute name defines the name of the instance, with the main one
214 being `default`: this can be accessed from the command line without
215 specifying the name with `-i`.
216
217 ::: {.note}
218 Instances created imperatively from the command line will not interfere
219 with the NixOS instances, provided the respective `pool4` addresses and
220 port ranges are not overlapping.
221 :::
222
223 ::: {.warning}
224 Changes to an instance performed via `jool -i <name>` are applied
225 correctly but will be lost after restarting the respective
226 `jool-nat64-<name>.service`.
227 :::
228 '';
229 };
230
231 networking.jool.siit = lib.mkOption {
232 type = lib.types.attrsOf siitOptions;
233 default = { };
234 example = lib.literalExpression ''
235 {
236 default = {
237 # Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v.
238 global.pool6 = "2001:db8::/96";
239
240 # Explicit address mappings
241 eamt = [
242 # 2001:db8:1:: ←→ 192.0.2.0
243 { "ipv6 prefix" = "2001:db8:1::/128"; "ipv4 prefix" = "192.0.2.0"; }
244 # 2001:db8:1::x ←→ 198.51.100.x
245 { "ipv6 prefix" = "2001:db8:2::/120"; "ipv4 prefix" = "198.51.100.0/24"; }
246 ];
247 };
248 }
249 '';
250 description = ''
251 Definitions of SIIT instances of Jool.
252 See the
253 [documentation](https://nicmx.github.io/Jool/en/config-atomic.html) for
254 the available options. Also check out the
255 [tutorial](https://nicmx.github.io/Jool/en/run-vanilla.html) for an
256 introduction to SIIT and how to troubleshoot the setup.
257
258 The attribute name defines the name of the instance, with the main one
259 being `default`: this can be accessed from the command line without
260 specifying the name with `-i`.
261
262 ::: {.note}
263 Instances created imperatively from the command line will not interfere
264 with the NixOS instances, provided the respective EAMT addresses and
265 port ranges are not overlapping.
266 :::
267
268 ::: {.warning}
269 Changes to an instance performed via `jool -i <name>` are applied
270 correctly but will be lost after restarting the respective
271 `jool-siit-<name>.service`.
272 :::
273 '';
274 };
275
276 };
277
278 config = lib.mkIf cfg.enable {
279 # Install kernel module and cli tools
280 boot.extraModulePackages = [ jool ];
281 environment.systemPackages = [ jool-cli ];
282
283 # Install services for each instance
284 systemd.services = lib.mkMerge (
285 lib.mapAttrsToList makeNat64Unit cfg.nat64 ++ lib.mapAttrsToList makeSiitUnit cfg.siit
286 );
287
288 # Check the configuration of each instance
289 system.checks = lib.optional (cfg.nat64 != { } || cfg.siit != { }) (
290 pkgs.runCommand "jool-validated"
291 {
292 nativeBuildInputs = with pkgs.buildPackages; [ jool-cli ];
293 preferLocalBuild = true;
294 }
295 (
296 lib.concatStrings (lib.mapAttrsToList checkNat64 cfg.nat64 ++ lib.mapAttrsToList checkSiit cfg.siit)
297 )
298 );
299 };
300
301 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
302
303}