lol

Merge pull request #240982 from rnhmjoj/pr-jool

nixos/jool: add service for setting up SIIT/NAT64

authored by

Martin Weinelt and committed by
GitHub
7c75694d af569e48

+681 -2
+2
nixos/doc/manual/release-notes/rl-2311.section.md
··· 30 30 31 31 - [sitespeed-io](https://sitespeed.io), a tool that can generate metrics (timings, diagnostics) for websites. Available as [services.sitespeed-io](#opt-services.sitespeed-io.enable). 32 32 33 + - [Jool](https://nicmx.github.io/Jool/en/index.html), an Open Source implementation of IPv4/IPv6 translation on Linux. Available as [networking.jool.enable](#opt-networking.jool.enable). 34 + 33 35 - [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services. 34 36 35 37 - [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler. Available as [services.pgbouncer](#opt-services.pgbouncer.enable).
+1
nixos/modules/module-list.nix
··· 929 929 ./services/networking/jibri/default.nix 930 930 ./services/networking/jicofo.nix 931 931 ./services/networking/jitsi-videobridge.nix 932 + ./services/networking/jool.nix 932 933 ./services/networking/kea.nix 933 934 ./services/networking/keepalived/default.nix 934 935 ./services/networking/keybase.nix
+222
nixos/modules/services/networking/jool.nix
··· 1 + { config, pkgs, lib, ... }: 2 + 3 + let 4 + cfg = config.networking.jool; 5 + 6 + jool = config.boot.kernelPackages.jool; 7 + jool-cli = pkgs.jool-cli; 8 + 9 + hardening = { 10 + # Run as unprivileged user 11 + User = "jool"; 12 + Group = "jool"; 13 + DynamicUser = true; 14 + 15 + # Restrict filesystem to only read the jool module 16 + TemporaryFileSystem = [ "/" ]; 17 + BindReadOnlyPaths = [ 18 + builtins.storeDir 19 + "/run/current-system/kernel-modules" 20 + ]; 21 + 22 + # Give capabilities to load the module and configure it 23 + AmbientCapabilities = [ "CAP_SYS_MODULE" "CAP_NET_ADMIN" ]; 24 + RestrictAddressFamilies = [ "AF_NETLINK" ]; 25 + 26 + # Other restrictions 27 + RestrictNamespaces = [ "net" ]; 28 + SystemCallFilter = [ "@system-service" "@module" ]; 29 + CapabilityBoundingSet = [ "CAP_SYS_MODULE" "CAP_NET_ADMIN" ]; 30 + }; 31 + 32 + configFormat = pkgs.formats.json {}; 33 + 34 + mkDefaultAttrs = lib.mapAttrs (n: v: lib.mkDefault v); 35 + 36 + defaultNat64 = { 37 + instance = "default"; 38 + framework = "netfilter"; 39 + global.pool6 = "64:ff9b::/96"; 40 + }; 41 + defaultSiit = { 42 + instance = "default"; 43 + framework = "netfilter"; 44 + }; 45 + 46 + nat64Conf = configFormat.generate "jool-nat64.conf" cfg.nat64.config; 47 + siitConf = configFormat.generate "jool-siit.conf" cfg.siit.config; 48 + 49 + in 50 + 51 + { 52 + ###### interface 53 + 54 + options = { 55 + networking.jool.enable = lib.mkOption { 56 + type = lib.types.bool; 57 + default = false; 58 + relatedPackages = [ "linuxPackages.jool" "jool-cli" ]; 59 + description = lib.mdDoc '' 60 + Whether to enable Jool, an Open Source implementation of IPv4/IPv6 61 + translation on Linux. 62 + 63 + Jool can perform stateless IP/ICMP translation (SIIT) or stateful 64 + NAT64, analogous to the IPv4 NAPT. Refer to the upstream 65 + [documentation](https://nicmx.github.io/Jool/en/intro-xlat.html) for 66 + the supported modes of translation and how to configure them. 67 + ''; 68 + }; 69 + 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; 74 + example = lib.literalExpression '' 75 + { 76 + # custom NAT64 prefix 77 + global.pool6 = "2001:db8:64::/96"; 78 + 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 + ]; 97 + 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"; } 103 + 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 + ]; 108 + } 109 + ''; 110 + 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. 114 + 115 + ::: {.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. 119 + ::: 120 + 121 + ::: {.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`. 125 + ::: 126 + ''; 127 + }; 128 + 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; 133 + example = lib.literalExpression '' 134 + { 135 + # Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v. 136 + pool6 = "2001:db8::/96"; 137 + 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 + ] 145 + } 146 + ''; 147 + 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. 151 + 152 + ::: {.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. 156 + ::: 157 + 158 + ::: {.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`. 162 + ::: 163 + ''; 164 + }; 165 + 166 + }; 167 + 168 + ###### implementation 169 + 170 + config = lib.mkIf cfg.enable { 171 + environment.systemPackages = [ jool-cli ]; 172 + 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 + ''); 214 + 215 + networking.jool.nat64.config = mkDefaultAttrs defaultNat64; 216 + networking.jool.siit.config = mkDefaultAttrs defaultSiit; 217 + 218 + }; 219 + 220 + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; 221 + 222 + }
+1
nixos/tests/all-tests.nix
··· 391 391 jibri = handleTest ./jibri.nix {}; 392 392 jirafeau = handleTest ./jirafeau.nix {}; 393 393 jitsi-meet = handleTest ./jitsi-meet.nix {}; 394 + jool = handleTest ./jool.nix {}; 394 395 k3s = handleTest ./k3s {}; 395 396 kafka = handleTest ./kafka.nix {}; 396 397 kanidm = handleTest ./kanidm.nix {};
+250
nixos/tests/jool.nix
··· 1 + { system ? builtins.currentSystem, 2 + config ? {}, 3 + pkgs ? import ../.. { inherit system config; } 4 + }: 5 + 6 + with import ../lib/testing-python.nix { inherit system pkgs; }; 7 + 8 + let 9 + inherit (pkgs) lib; 10 + 11 + ipv6Only = { 12 + networking.useDHCP = false; 13 + networking.interfaces.eth1.ipv4.addresses = lib.mkVMOverride [ ]; 14 + }; 15 + 16 + ipv4Only = { 17 + networking.useDHCP = false; 18 + networking.interfaces.eth1.ipv6.addresses = lib.mkVMOverride [ ]; 19 + }; 20 + 21 + webserver = ip: msg: { 22 + systemd.services.webserver = { 23 + description = "Mock webserver"; 24 + wants = [ "network-online.target" ]; 25 + wantedBy = [ "multi-user.target" ]; 26 + serviceConfig.Restart = "always"; 27 + script = '' 28 + while true; do 29 + { 30 + printf 'HTTP/1.0 200 OK\n' 31 + printf 'Content-Length: ${toString (1 + builtins.stringLength msg)}\n' 32 + printf '\n${msg}\n\n' 33 + } | ${pkgs.libressl.nc}/bin/nc -${toString ip}nvl 80 34 + done 35 + ''; 36 + }; 37 + networking.firewall.allowedTCPPorts = [ 80 ]; 38 + }; 39 + 40 + in 41 + 42 + { 43 + siit = makeTest { 44 + # This test simulates the setup described in [1] with two IPv6 and 45 + # IPv4-only devices on different subnets communicating through a border 46 + # relay running Jool in SIIT mode. 47 + # [1]: https://nicmx.github.io/Jool/en/run-vanilla.html 48 + name = "jool-siit"; 49 + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; 50 + 51 + # Border relay 52 + nodes.relay = { ... }: { 53 + imports = [ ../modules/profiles/minimal.nix ]; 54 + virtualisation.vlans = [ 1 2 ]; 55 + 56 + # Enable packet routing 57 + boot.kernel.sysctl = { 58 + "net.ipv6.conf.all.forwarding" = 1; 59 + "net.ipv4.conf.all.forwarding" = 1; 60 + }; 61 + 62 + networking.useDHCP = false; 63 + networking.interfaces = lib.mkVMOverride { 64 + eth1.ipv6.addresses = [ { address = "fd::198.51.100.1"; prefixLength = 120; } ]; 65 + eth2.ipv4.addresses = [ { address = "192.0.2.1"; prefixLength = 24; } ]; 66 + }; 67 + 68 + networking.jool = { 69 + enable = true; 70 + siit.enable = true; 71 + siit.config.global.pool6 = "fd::/96"; 72 + }; 73 + }; 74 + 75 + # IPv6 only node 76 + nodes.alice = { ... }: { 77 + imports = [ 78 + ../modules/profiles/minimal.nix 79 + ipv6Only 80 + (webserver 6 "Hello, Bob!") 81 + ]; 82 + 83 + virtualisation.vlans = [ 1 ]; 84 + networking.interfaces.eth1.ipv6 = { 85 + addresses = [ { address = "fd::198.51.100.8"; prefixLength = 120; } ]; 86 + routes = [ { address = "fd::192.0.2.0"; prefixLength = 120; 87 + via = "fd::198.51.100.1"; } ]; 88 + }; 89 + }; 90 + 91 + # IPv4 only node 92 + nodes.bob = { ... }: { 93 + imports = [ 94 + ../modules/profiles/minimal.nix 95 + ipv4Only 96 + (webserver 4 "Hello, Alice!") 97 + ]; 98 + 99 + virtualisation.vlans = [ 2 ]; 100 + networking.interfaces.eth1.ipv4 = { 101 + addresses = [ { address = "192.0.2.16"; prefixLength = 24; } ]; 102 + routes = [ { address = "198.51.100.0"; prefixLength = 24; 103 + via = "192.0.2.1"; } ]; 104 + }; 105 + }; 106 + 107 + testScript = '' 108 + start_all() 109 + 110 + relay.wait_for_unit("jool-siit.service") 111 + alice.wait_for_unit("network-addresses-eth1.service") 112 + bob.wait_for_unit("network-addresses-eth1.service") 113 + 114 + with subtest("Alice and Bob can't ping each other"): 115 + relay.systemctl("stop jool-siit.service") 116 + alice.fail("ping -c1 fd::192.0.2.16") 117 + bob.fail("ping -c1 198.51.100.8") 118 + 119 + with subtest("Alice and Bob can ping using the relay"): 120 + relay.systemctl("start jool-siit.service") 121 + alice.wait_until_succeeds("ping -c1 fd::192.0.2.16") 122 + bob.wait_until_succeeds("ping -c1 198.51.100.8") 123 + 124 + with subtest("Alice can connect to Bob's webserver"): 125 + bob.wait_for_open_port(80) 126 + alice.succeed("curl -vvv http://[fd::192.0.2.16] >&2") 127 + alice.succeed("curl --fail -s http://[fd::192.0.2.16] | grep -q Alice") 128 + 129 + with subtest("Bob can connect to Alices's webserver"): 130 + alice.wait_for_open_port(80) 131 + bob.succeed("curl --fail -s http://198.51.100.8 | grep -q Bob") 132 + ''; 133 + }; 134 + 135 + nat64 = makeTest { 136 + # This test simulates the setup described in [1] with two IPv6-only nodes 137 + # (a client and a homeserver) on the LAN subnet and an IPv4 node on the WAN. 138 + # The router runs Jool in stateful NAT64 mode, masquarading the LAN and 139 + # forwarding ports using static BIB entries. 140 + # [1]: https://nicmx.github.io/Jool/en/run-nat64.html 141 + name = "jool-nat64"; 142 + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; 143 + 144 + # Router 145 + nodes.router = { ... }: { 146 + imports = [ ../modules/profiles/minimal.nix ]; 147 + virtualisation.vlans = [ 1 2 ]; 148 + 149 + # Enable packet routing 150 + boot.kernel.sysctl = { 151 + "net.ipv6.conf.all.forwarding" = 1; 152 + "net.ipv4.conf.all.forwarding" = 1; 153 + }; 154 + 155 + networking.useDHCP = false; 156 + networking.interfaces = lib.mkVMOverride { 157 + eth1.ipv6.addresses = [ { address = "2001:db8::1"; prefixLength = 96; } ]; 158 + eth2.ipv4.addresses = [ { address = "203.0.113.1"; prefixLength = 24; } ]; 159 + }; 160 + 161 + networking.jool = { 162 + enable = true; 163 + nat64.enable = true; 164 + nat64.config = { 165 + bib = [ 166 + { # forward HTTP 203.0.113.1 (router) → 2001:db8::9 (homeserver) 167 + "protocol" = "TCP"; 168 + "ipv4 address" = "203.0.113.1#80"; 169 + "ipv6 address" = "2001:db8::9#80"; 170 + } 171 + ]; 172 + pool4 = [ 173 + # Ports for dynamic translation 174 + { protocol = "TCP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } 175 + { protocol = "UDP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } 176 + { protocol = "ICMP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } 177 + # Ports for static BIB entries 178 + { protocol = "TCP"; prefix = "203.0.113.1/32"; "port range" = "80"; } 179 + ]; 180 + }; 181 + }; 182 + }; 183 + 184 + # LAN client (IPv6 only) 185 + nodes.client = { ... }: { 186 + imports = [ ../modules/profiles/minimal.nix ipv6Only ]; 187 + virtualisation.vlans = [ 1 ]; 188 + 189 + networking.interfaces.eth1.ipv6 = { 190 + addresses = [ { address = "2001:db8::8"; prefixLength = 96; } ]; 191 + routes = [ { address = "64:ff9b::"; prefixLength = 96; 192 + via = "2001:db8::1"; } ]; 193 + }; 194 + }; 195 + 196 + # LAN server (IPv6 only) 197 + nodes.homeserver = { ... }: { 198 + imports = [ 199 + ../modules/profiles/minimal.nix 200 + ipv6Only 201 + (webserver 6 "Hello from IPv6!") 202 + ]; 203 + 204 + virtualisation.vlans = [ 1 ]; 205 + networking.interfaces.eth1.ipv6 = { 206 + addresses = [ { address = "2001:db8::9"; prefixLength = 96; } ]; 207 + routes = [ { address = "64:ff9b::"; prefixLength = 96; 208 + via = "2001:db8::1"; } ]; 209 + }; 210 + }; 211 + 212 + # WAN server (IPv4 only) 213 + nodes.server = { ... }: { 214 + imports = [ 215 + ../modules/profiles/minimal.nix 216 + ipv4Only 217 + (webserver 4 "Hello from IPv4!") 218 + ]; 219 + 220 + virtualisation.vlans = [ 2 ]; 221 + networking.interfaces.eth1.ipv4.addresses = 222 + [ { address = "203.0.113.16"; prefixLength = 24; } ]; 223 + }; 224 + 225 + testScript = '' 226 + start_all() 227 + 228 + for node in [client, homeserver, server]: 229 + node.wait_for_unit("network-addresses-eth1.service") 230 + 231 + with subtest("Client can ping the WAN server"): 232 + router.wait_for_unit("jool-nat64.service") 233 + client.succeed("ping -c1 64:ff9b::203.0.113.16") 234 + 235 + with subtest("Client can connect to the WAN webserver"): 236 + server.wait_for_open_port(80) 237 + client.succeed("curl --fail -s http://[64:ff9b::203.0.113.16] | grep -q IPv4!") 238 + 239 + with subtest("Router BIB entries are correctly populated"): 240 + router.succeed("jool bib display | grep -q 'Dynamic TCP.*2001:db8::8'") 241 + router.succeed("jool bib display | grep -q 'Static TCP.*2001:db8::9'") 242 + 243 + with subtest("WAN server can reach the LAN server"): 244 + homeserver.wait_for_open_port(80) 245 + server.succeed("curl --fail -s http://203.0.113.1 | grep -q IPv6!") 246 + ''; 247 + 248 + }; 249 + 250 + }
+9 -1
pkgs/os-specific/linux/jool/cli.nix
··· 1 - { lib, stdenv, fetchFromGitHub, fetchpatch, autoreconfHook, pkg-config, libnl, iptables }: 1 + { lib, stdenv, fetchFromGitHub, nixosTests 2 + , autoreconfHook, pkg-config, libnl, iptables 3 + }: 2 4 3 5 let 4 6 sourceAttrs = (import ./source.nix) { inherit fetchFromGitHub; }; ··· 10 12 11 13 src = sourceAttrs.src; 12 14 15 + patches = [ 16 + ./validate-config.patch 17 + ]; 18 + 13 19 outputs = [ 14 20 "out" 15 21 "man" ··· 23 29 prePatch = '' 24 30 sed -e 's%^XTABLES_SO_DIR = .*%XTABLES_SO_DIR = '"$out"'/lib/xtables%g' -i src/usr/iptables/Makefile 25 31 ''; 32 + 33 + passthru.tests = { inherit (nixosTests) jool; }; 26 34 27 35 meta = with lib; { 28 36 homepage = "https://www.jool.mx/";
+3 -1
pkgs/os-specific/linux/jool/default.nix
··· 1 - { lib, stdenv, fetchFromGitHub, fetchpatch, kernel }: 1 + { lib, stdenv, fetchFromGitHub, kernel, nixosTests }: 2 2 3 3 let 4 4 sourceAttrs = (import ./source.nix) { inherit fetchFromGitHub; }; ··· 22 22 ]; 23 23 24 24 installTargets = "modules_install"; 25 + 26 + passthru.tests = { inherit (nixosTests) jool; }; 25 27 26 28 meta = with lib; { 27 29 homepage = "https://www.jool.mx/";
+193
pkgs/os-specific/linux/jool/validate-config.patch
··· 1 + From df0a1cf61188b5b7bb98675d746cb63d9300f148 Mon Sep 17 00:00:00 2001 2 + From: rnhmjoj <rnhmjoj@inventati.org> 3 + Date: Sat, 1 Jul 2023 18:47:05 +0200 4 + Subject: [PATCH] Add mode to validate the atomic configuration 5 + 6 + --- 7 + src/usr/argp/main.c | 6 ++++++ 8 + src/usr/argp/wargp/file.c | 26 +++++++++++++++++++++++++- 9 + src/usr/argp/wargp/file.h | 1 + 10 + src/usr/nl/file.c | 32 ++++++++++++++++++++++---------- 11 + src/usr/nl/file.h | 3 ++- 12 + 5 files changed, 56 insertions(+), 12 deletions(-) 13 + 14 + diff --git a/src/usr/argp/main.c b/src/usr/argp/main.c 15 + index 744a6df0..d04917da 100644 16 + --- a/src/usr/argp/main.c 17 + +++ b/src/usr/argp/main.c 18 + @@ -238,6 +238,12 @@ static struct cmd_option file_ops[] = { 19 + .handler = handle_file_update, 20 + .handle_autocomplete = autocomplete_file_update, 21 + }, 22 + + { 23 + + .label = "check", 24 + + .xt = XT_ANY, 25 + + .handler = handle_file_check, 26 + + .handle_autocomplete = autocomplete_file_update, 27 + + }, 28 + { 0 }, 29 + }; 30 + 31 + diff --git a/src/usr/argp/wargp/file.c b/src/usr/argp/wargp/file.c 32 + index 0951b544..27ee3e64 100644 33 + --- a/src/usr/argp/wargp/file.c 34 + +++ b/src/usr/argp/wargp/file.c 35 + @@ -26,6 +26,30 @@ static struct wargp_option update_opts[] = { 36 + { 0 }, 37 + }; 38 + 39 + +int handle_file_check(char *iname, int argc, char **argv, void const *arg) 40 + +{ 41 + + struct update_args uargs = { 0 }; 42 + + struct joolnl_socket sk = { 0 }; 43 + + struct jool_result result; 44 + + 45 + + result.error = wargp_parse(update_opts, argc, argv, &uargs); 46 + + if (result.error) 47 + + return result.error; 48 + + 49 + + if (!uargs.file_name.value) { 50 + + struct requirement reqs[] = { 51 + + { false, "a file name" }, 52 + + { 0 } 53 + + }; 54 + + return requirement_print(reqs); 55 + + } 56 + + 57 + + result = joolnl_file_parse(&sk, xt_get(), iname, uargs.file_name.value, 58 + + uargs.force.value, true); 59 + + 60 + + return pr_result(&result); 61 + +} 62 + + 63 + int handle_file_update(char *iname, int argc, char **argv, void const *arg) 64 + { 65 + struct update_args uargs = { 0 }; 66 + @@ -49,7 +73,7 @@ int handle_file_update(char *iname, int argc, char **argv, void const *arg) 67 + return pr_result(&result); 68 + 69 + result = joolnl_file_parse(&sk, xt_get(), iname, uargs.file_name.value, 70 + - uargs.force.value); 71 + + uargs.force.value, false); 72 + 73 + joolnl_teardown(&sk); 74 + return pr_result(&result); 75 + diff --git a/src/usr/argp/wargp/file.h b/src/usr/argp/wargp/file.h 76 + index ce5de508..8ea4a4d2 100644 77 + --- a/src/usr/argp/wargp/file.h 78 + +++ b/src/usr/argp/wargp/file.h 79 + @@ -2,6 +2,7 @@ 80 + #define SRC_USR_ARGP_WARGP_FILE_H_ 81 + 82 + int handle_file_update(char *iname, int argc, char **argv, void const *arg); 83 + +int handle_file_check(char *iname, int argc, char **argv, void const *arg); 84 + void autocomplete_file_update(void const *args); 85 + 86 + #endif /* SRC_USR_ARGP_WARGP_FILE_H_ */ 87 + diff --git a/src/usr/nl/file.c b/src/usr/nl/file.c 88 + index f9413236..51a668bd 100644 89 + --- a/src/usr/nl/file.c 90 + +++ b/src/usr/nl/file.c 91 + @@ -29,6 +29,7 @@ static struct joolnl_socket sk; 92 + static char const *iname; 93 + static xlator_flags flags; 94 + static __u8 force; 95 + +static bool check; 96 + 97 + struct json_meta { 98 + char const *name; /* This being NULL signals the end of the array. */ 99 + @@ -163,9 +164,11 @@ static struct jool_result handle_array(cJSON *json, int attrtype, char *name, 100 + goto too_small; 101 + 102 + nla_nest_end(msg, root); 103 + - result = joolnl_request(&sk, msg, NULL, NULL); 104 + - if (result.error) 105 + - return result; 106 + + if (!check) { 107 + + result = joolnl_request(&sk, msg, NULL, NULL); 108 + + if (result.error) 109 + + return result; 110 + + } 111 + 112 + msg = NULL; 113 + json = json->prev; 114 + @@ -179,6 +182,8 @@ static struct jool_result handle_array(cJSON *json, int attrtype, char *name, 115 + return result_success(); 116 + 117 + nla_nest_end(msg, root); 118 + + if (check) 119 + + return result_success(); 120 + return joolnl_request(&sk, msg, NULL, NULL); 121 + 122 + too_small: 123 + @@ -244,6 +249,8 @@ static struct jool_result handle_global(cJSON *json) 124 + 125 + nla_nest_end(msg, root); 126 + free(meta); 127 + + if (check) 128 + + return result_success(); 129 + return joolnl_request(&sk, msg, NULL, NULL); 130 + 131 + revert_meta: 132 + @@ -654,9 +661,11 @@ static struct jool_result send_ctrl_msg(bool init) 133 + else 134 + NLA_PUT(msg, JNLAR_ATOMIC_END, 0, NULL); 135 + 136 + - result = joolnl_request(&sk, msg, NULL, NULL); 137 + - if (result.error) 138 + - return result; 139 + + if (!check) { 140 + + result = joolnl_request(&sk, msg, NULL, NULL); 141 + + if (result.error) 142 + + return result; 143 + + } 144 + 145 + return result_success(); 146 + 147 + @@ -683,9 +692,11 @@ static struct jool_result do_parsing(char const *iname, char *buffer) 148 + if (result.error) 149 + goto fail; 150 + 151 + - result = send_ctrl_msg(true); 152 + - if (result.error) 153 + - goto fail; 154 + + if (!check) { 155 + + result = send_ctrl_msg(true); 156 + + if (result.error) 157 + + goto fail; 158 + + } 159 + 160 + switch (xlator_flags2xt(flags)) { 161 + case XT_SIIT: 162 + @@ -718,12 +729,13 @@ fail: 163 + } 164 + 165 + struct jool_result joolnl_file_parse(struct joolnl_socket *_sk, xlator_type xt, 166 + - char const *iname, char const *file_name, bool _force) 167 + + char const *iname, char const *file_name, bool _force, bool _check) 168 + { 169 + char *buffer; 170 + struct jool_result result; 171 + 172 + sk = *_sk; 173 + + check = _check; 174 + flags = xt; 175 + force = _force ? JOOLNLHDR_FLAGS_FORCE : 0; 176 + 177 + diff --git a/src/usr/nl/file.h b/src/usr/nl/file.h 178 + index 51802aaf..8b4a66dd 100644 179 + --- a/src/usr/nl/file.h 180 + +++ b/src/usr/nl/file.h 181 + @@ -9,7 +9,8 @@ struct jool_result joolnl_file_parse( 182 + xlator_type xt, 183 + char const *iname, 184 + char const *file_name, 185 + - bool force 186 + + bool force, 187 + + bool check 188 + ); 189 + 190 + struct jool_result joolnl_file_get_iname( 191 + -- 192 + 2.40.1 193 +