lol

nixos/jool: allow to manage multiple instances

rnhmjoj 355a9fa0 3c15feef

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