diff options
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2311.section.md | 2 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/networking/jool.nix | 222 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/jool.nix | 250 | ||||
-rw-r--r-- | pkgs/os-specific/linux/jool/cli.nix | 10 | ||||
-rw-r--r-- | pkgs/os-specific/linux/jool/default.nix | 4 | ||||
-rw-r--r-- | pkgs/os-specific/linux/jool/validate-config.patch | 193 |
8 files changed, 681 insertions, 2 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index 31e90c30cf173..8880c71fa9a63 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -30,6 +30,8 @@ - [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). +- [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). + - [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. - [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler. Available as [services.pgbouncer](#opt-services.pgbouncer.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c7d76fe2eee93..97b8f61e1e70d 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -929,6 +929,7 @@ ./services/networking/jibri/default.nix ./services/networking/jicofo.nix ./services/networking/jitsi-videobridge.nix + ./services/networking/jool.nix ./services/networking/kea.nix ./services/networking/keepalived/default.nix ./services/networking/keybase.nix diff --git a/nixos/modules/services/networking/jool.nix b/nixos/modules/services/networking/jool.nix new file mode 100644 index 0000000000000..3aafbe40967ce --- /dev/null +++ b/nixos/modules/services/networking/jool.nix @@ -0,0 +1,222 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.networking.jool; + + jool = config.boot.kernelPackages.jool; + jool-cli = pkgs.jool-cli; + + hardening = { + # Run as unprivileged user + User = "jool"; + Group = "jool"; + DynamicUser = true; + + # Restrict filesystem to only read the jool module + TemporaryFileSystem = [ "/" ]; + BindReadOnlyPaths = [ + builtins.storeDir + "/run/current-system/kernel-modules" + ]; + + # Give capabilities to load the module and configure it + AmbientCapabilities = [ "CAP_SYS_MODULE" "CAP_NET_ADMIN" ]; + RestrictAddressFamilies = [ "AF_NETLINK" ]; + + # Other restrictions + RestrictNamespaces = [ "net" ]; + SystemCallFilter = [ "@system-service" "@module" ]; + CapabilityBoundingSet = [ "CAP_SYS_MODULE" "CAP_NET_ADMIN" ]; + }; + + configFormat = pkgs.formats.json {}; + + mkDefaultAttrs = lib.mapAttrs (n: v: lib.mkDefault v); + + defaultNat64 = { + instance = "default"; + framework = "netfilter"; + global.pool6 = "64:ff9b::/96"; + }; + defaultSiit = { + instance = "default"; + framework = "netfilter"; + }; + + nat64Conf = configFormat.generate "jool-nat64.conf" cfg.nat64.config; + siitConf = configFormat.generate "jool-siit.conf" cfg.siit.config; + +in + +{ + ###### interface + + options = { + networking.jool.enable = lib.mkOption { + type = lib.types.bool; + default = false; + relatedPackages = [ "linuxPackages.jool" "jool-cli" ]; + description = lib.mdDoc '' + Whether to enable Jool, an Open Source implementation of IPv4/IPv6 + translation on Linux. + + Jool can perform stateless IP/ICMP translation (SIIT) or stateful + NAT64, analogous to the IPv4 NAPT. Refer to the upstream + [documentation](https://nicmx.github.io/Jool/en/intro-xlat.html) for + the supported modes of translation and how to configure them. + ''; + }; + + networking.jool.nat64.enable = lib.mkEnableOption (lib.mdDoc "a NAT64 instance of Jool."); + networking.jool.nat64.config = lib.mkOption { + type = configFormat.type; + default = defaultNat64; + example = lib.literalExpression '' + { + # custom NAT64 prefix + global.pool6 = "2001:db8:64::/96"; + + # Port forwarding + bib = [ + { # SSH 192.0.2.16 → 2001:db8:a::1 + "protocol" = "TCP"; + "ipv4 address" = "192.0.2.16#22"; + "ipv6 address" = "2001:db8:a::1#22"; + } + { # DNS (TCP) 192.0.2.16 → 2001:db8:a::2 + "protocol" = "TCP"; + "ipv4 address" = "192.0.2.16#53"; + "ipv6 address" = "2001:db8:a::2#53"; + } + { # DNS (UDP) 192.0.2.16 → 2001:db8:a::2 + "protocol" = "UDP"; + "ipv4 address" = "192.0.2.16#53"; + "ipv6 address" = "2001:db8:a::2#53"; + } + ]; + + pool4 = [ + # Ports for dynamic translation + { protocol = "TCP"; prefix = "192.0.2.16/32"; "port range" = "40001-65535"; } + { protocol = "UDP"; prefix = "192.0.2.16/32"; "port range" = "40001-65535"; } + { protocol = "ICMP"; prefix = "192.0.2.16/32"; "port range" = "40001-65535"; } + + # Ports for static BIB entries + { protocol = "TCP"; prefix = "192.0.2.16/32"; "port range" = "22"; } + { protocol = "UDP"; prefix = "192.0.2.16/32"; "port range" = "53"; } + ]; + } + ''; + description = lib.mdDoc '' + The configuration of a stateful NAT64 instance of Jool managed through + NixOS. See https://nicmx.github.io/Jool/en/config-atomic.html for the + available options. + + ::: {.note} + Existing or more instances created manually will not interfere with the + NixOS instance, provided the respective `pool4` addresses and port + ranges are not overlapping. + ::: + + ::: {.warning} + Changes to the NixOS instance performed via `jool instance nixos-nat64` + are applied correctly but will be lost after restarting + `jool-nat64.service`. + ::: + ''; + }; + + networking.jool.siit.enable = lib.mkEnableOption (lib.mdDoc "a SIIT instance of Jool."); + networking.jool.siit.config = lib.mkOption { + type = configFormat.type; + default = defaultSiit; + example = lib.literalExpression '' + { + # Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v. + pool6 = "2001:db8::/96"; + + # Explicit address mappings + eamt = [ + # 2001:db8:1:: ←→ 192.0.2.0 + { "ipv6 prefix": "2001:db8:1::/128", "ipv4 prefix": "192.0.2.0" } + # 2001:db8:1::x ←→ 198.51.100.x + { "ipv6 prefix": "2001:db8:2::/120", "ipv4 prefix": "198.51.100.0/24" } + ] + } + ''; + description = lib.mdDoc '' + The configuration of a SIIT instance of Jool managed through + NixOS. See https://nicmx.github.io/Jool/en/config-atomic.html for the + available options. + + ::: {.note} + Existing or more instances created manually will not interfere with the + NixOS instance, provided the respective `EAMT` address mappings are not + overlapping. + ::: + + ::: {.warning} + Changes to the NixOS instance performed via `jool instance nixos-siit` + are applied correctly but will be lost after restarting + `jool-siit.service`. + ::: + ''; + }; + + }; + + ###### implementation + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ jool-cli ]; + boot.extraModulePackages = [ jool ]; + + systemd.services.jool-nat64 = lib.mkIf cfg.nat64.enable { + description = "Jool, NAT64 setup"; + documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ]; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + reloadIfChanged = true; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStartPre = "${pkgs.kmod}/bin/modprobe jool"; + ExecStart = "${jool-cli}/bin/jool file handle ${nat64Conf}"; + ExecStop = "${jool-cli}/bin/jool -f ${nat64Conf} instance remove"; + } // hardening; + }; + + systemd.services.jool-siit = lib.mkIf cfg.siit.enable { + description = "Jool, SIIT setup"; + documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ]; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + reloadIfChanged = true; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStartPre = "${pkgs.kmod}/bin/modprobe jool_siit"; + ExecStart = "${jool-cli}/bin/jool_siit file handle ${siitConf}"; + ExecStop = "${jool-cli}/bin/jool_siit -f ${siitConf} instance remove"; + } // hardening; + }; + + system.checks = lib.singleton (pkgs.runCommand "jool-validated" { + nativeBuildInputs = [ pkgs.buildPackages.jool-cli ]; + preferLocalBuild = true; + } '' + printf 'Validating Jool configuration... ' + ${lib.optionalString cfg.siit.enable "jool_siit file check ${siitConf}"} + ${lib.optionalString cfg.nat64.enable "jool file check ${nat64Conf}"} + printf 'ok\n' + touch "$out" + ''); + + networking.jool.nat64.config = mkDefaultAttrs defaultNat64; + networking.jool.siit.config = mkDefaultAttrs defaultSiit; + + }; + + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 4b338dac69a7d..6f17bd2cdd3b5 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -391,6 +391,7 @@ in { jibri = handleTest ./jibri.nix {}; jirafeau = handleTest ./jirafeau.nix {}; jitsi-meet = handleTest ./jitsi-meet.nix {}; + jool = handleTest ./jool.nix {}; k3s = handleTest ./k3s {}; kafka = handleTest ./kafka.nix {}; kanidm = handleTest ./kanidm.nix {}; diff --git a/nixos/tests/jool.nix b/nixos/tests/jool.nix new file mode 100644 index 0000000000000..6d5ded9b18e07 --- /dev/null +++ b/nixos/tests/jool.nix @@ -0,0 +1,250 @@ +{ system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../.. { inherit system config; } +}: + +with import ../lib/testing-python.nix { inherit system pkgs; }; + +let + inherit (pkgs) lib; + + ipv6Only = { + networking.useDHCP = false; + networking.interfaces.eth1.ipv4.addresses = lib.mkVMOverride [ ]; + }; + + ipv4Only = { + networking.useDHCP = false; + networking.interfaces.eth1.ipv6.addresses = lib.mkVMOverride [ ]; + }; + + webserver = ip: msg: { + systemd.services.webserver = { + description = "Mock webserver"; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig.Restart = "always"; + script = '' + while true; do + { + printf 'HTTP/1.0 200 OK\n' + printf 'Content-Length: ${toString (1 + builtins.stringLength msg)}\n' + printf '\n${msg}\n\n' + } | ${pkgs.libressl.nc}/bin/nc -${toString ip}nvl 80 + done + ''; + }; + networking.firewall.allowedTCPPorts = [ 80 ]; + }; + +in + +{ + siit = makeTest { + # This test simulates the setup described in [1] with two IPv6 and + # IPv4-only devices on different subnets communicating through a border + # relay running Jool in SIIT mode. + # [1]: https://nicmx.github.io/Jool/en/run-vanilla.html + name = "jool-siit"; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + + # Border relay + nodes.relay = { ... }: { + imports = [ ../modules/profiles/minimal.nix ]; + virtualisation.vlans = [ 1 2 ]; + + # Enable packet routing + boot.kernel.sysctl = { + "net.ipv6.conf.all.forwarding" = 1; + "net.ipv4.conf.all.forwarding" = 1; + }; + + networking.useDHCP = false; + networking.interfaces = lib.mkVMOverride { + eth1.ipv6.addresses = [ { address = "fd::198.51.100.1"; prefixLength = 120; } ]; + eth2.ipv4.addresses = [ { address = "192.0.2.1"; prefixLength = 24; } ]; + }; + + networking.jool = { + enable = true; + siit.enable = true; + siit.config.global.pool6 = "fd::/96"; + }; + }; + + # IPv6 only node + nodes.alice = { ... }: { + imports = [ + ../modules/profiles/minimal.nix + ipv6Only + (webserver 6 "Hello, Bob!") + ]; + + virtualisation.vlans = [ 1 ]; + networking.interfaces.eth1.ipv6 = { + addresses = [ { address = "fd::198.51.100.8"; prefixLength = 120; } ]; + routes = [ { address = "fd::192.0.2.0"; prefixLength = 120; + via = "fd::198.51.100.1"; } ]; + }; + }; + + # IPv4 only node + nodes.bob = { ... }: { + imports = [ + ../modules/profiles/minimal.nix + ipv4Only + (webserver 4 "Hello, Alice!") + ]; + + virtualisation.vlans = [ 2 ]; + networking.interfaces.eth1.ipv4 = { + addresses = [ { address = "192.0.2.16"; prefixLength = 24; } ]; + routes = [ { address = "198.51.100.0"; prefixLength = 24; + via = "192.0.2.1"; } ]; + }; + }; + + testScript = '' + start_all() + + relay.wait_for_unit("jool-siit.service") + alice.wait_for_unit("network-addresses-eth1.service") + bob.wait_for_unit("network-addresses-eth1.service") + + with subtest("Alice and Bob can't ping each other"): + relay.systemctl("stop jool-siit.service") + alice.fail("ping -c1 fd::192.0.2.16") + bob.fail("ping -c1 198.51.100.8") + + with subtest("Alice and Bob can ping using the relay"): + relay.systemctl("start jool-siit.service") + alice.wait_until_succeeds("ping -c1 fd::192.0.2.16") + bob.wait_until_succeeds("ping -c1 198.51.100.8") + + with subtest("Alice can connect to Bob's webserver"): + bob.wait_for_open_port(80) + alice.succeed("curl -vvv http://[fd::192.0.2.16] >&2") + alice.succeed("curl --fail -s http://[fd::192.0.2.16] | grep -q Alice") + + with subtest("Bob can connect to Alices's webserver"): + alice.wait_for_open_port(80) + bob.succeed("curl --fail -s http://198.51.100.8 | grep -q Bob") + ''; + }; + + nat64 = makeTest { + # This test simulates the setup described in [1] with two IPv6-only nodes + # (a client and a homeserver) on the LAN subnet and an IPv4 node on the WAN. + # The router runs Jool in stateful NAT64 mode, masquarading the LAN and + # forwarding ports using static BIB entries. + # [1]: https://nicmx.github.io/Jool/en/run-nat64.html + name = "jool-nat64"; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + + # Router + nodes.router = { ... }: { + imports = [ ../modules/profiles/minimal.nix ]; + virtualisation.vlans = [ 1 2 ]; + + # Enable packet routing + boot.kernel.sysctl = { + "net.ipv6.conf.all.forwarding" = 1; + "net.ipv4.conf.all.forwarding" = 1; + }; + + networking.useDHCP = false; + networking.interfaces = lib.mkVMOverride { + eth1.ipv6.addresses = [ { address = "2001:db8::1"; prefixLength = 96; } ]; + eth2.ipv4.addresses = [ { address = "203.0.113.1"; prefixLength = 24; } ]; + }; + + networking.jool = { + enable = true; + nat64.enable = true; + nat64.config = { + bib = [ + { # forward HTTP 203.0.113.1 (router) → 2001:db8::9 (homeserver) + "protocol" = "TCP"; + "ipv4 address" = "203.0.113.1#80"; + "ipv6 address" = "2001:db8::9#80"; + } + ]; + pool4 = [ + # Ports for dynamic translation + { protocol = "TCP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } + { protocol = "UDP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } + { protocol = "ICMP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } + # Ports for static BIB entries + { protocol = "TCP"; prefix = "203.0.113.1/32"; "port range" = "80"; } + ]; + }; + }; + }; + + # LAN client (IPv6 only) + nodes.client = { ... }: { + imports = [ ../modules/profiles/minimal.nix ipv6Only ]; + virtualisation.vlans = [ 1 ]; + + networking.interfaces.eth1.ipv6 = { + addresses = [ { address = "2001:db8::8"; prefixLength = 96; } ]; + routes = [ { address = "64:ff9b::"; prefixLength = 96; + via = "2001:db8::1"; } ]; + }; + }; + + # LAN server (IPv6 only) + nodes.homeserver = { ... }: { + imports = [ + ../modules/profiles/minimal.nix + ipv6Only + (webserver 6 "Hello from IPv6!") + ]; + + virtualisation.vlans = [ 1 ]; + networking.interfaces.eth1.ipv6 = { + addresses = [ { address = "2001:db8::9"; prefixLength = 96; } ]; + routes = [ { address = "64:ff9b::"; prefixLength = 96; + via = "2001:db8::1"; } ]; + }; + }; + + # WAN server (IPv4 only) + nodes.server = { ... }: { + imports = [ + ../modules/profiles/minimal.nix + ipv4Only + (webserver 4 "Hello from IPv4!") + ]; + + virtualisation.vlans = [ 2 ]; + networking.interfaces.eth1.ipv4.addresses = + [ { address = "203.0.113.16"; prefixLength = 24; } ]; + }; + + testScript = '' + start_all() + + for node in [client, homeserver, server]: + node.wait_for_unit("network-addresses-eth1.service") + + with subtest("Client can ping the WAN server"): + router.wait_for_unit("jool-nat64.service") + client.succeed("ping -c1 64:ff9b::203.0.113.16") + + with subtest("Client can connect to the WAN webserver"): + server.wait_for_open_port(80) + client.succeed("curl --fail -s http://[64:ff9b::203.0.113.16] | grep -q IPv4!") + + with subtest("Router BIB entries are correctly populated"): + router.succeed("jool bib display | grep -q 'Dynamic TCP.*2001:db8::8'") + router.succeed("jool bib display | grep -q 'Static TCP.*2001:db8::9'") + + with subtest("WAN server can reach the LAN server"): + homeserver.wait_for_open_port(80) + server.succeed("curl --fail -s http://203.0.113.1 | grep -q IPv6!") + ''; + + }; + +} diff --git a/pkgs/os-specific/linux/jool/cli.nix b/pkgs/os-specific/linux/jool/cli.nix index e6a41ef31ecf8..ee5ee1128a867 100644 --- a/pkgs/os-specific/linux/jool/cli.nix +++ b/pkgs/os-specific/linux/jool/cli.nix @@ -1,4 +1,6 @@ -{ lib, stdenv, fetchFromGitHub, fetchpatch, autoreconfHook, pkg-config, libnl, iptables }: +{ lib, stdenv, fetchFromGitHub, nixosTests +, autoreconfHook, pkg-config, libnl, iptables +}: let sourceAttrs = (import ./source.nix) { inherit fetchFromGitHub; }; @@ -10,6 +12,10 @@ stdenv.mkDerivation { src = sourceAttrs.src; + patches = [ + ./validate-config.patch + ]; + outputs = [ "out" "man" @@ -24,6 +30,8 @@ stdenv.mkDerivation { sed -e 's%^XTABLES_SO_DIR = .*%XTABLES_SO_DIR = '"$out"'/lib/xtables%g' -i src/usr/iptables/Makefile ''; + passthru.tests = { inherit (nixosTests) jool; }; + meta = with lib; { homepage = "https://www.jool.mx/"; description = "Fairly compliant SIIT and Stateful NAT64 for Linux - CLI tools"; diff --git a/pkgs/os-specific/linux/jool/default.nix b/pkgs/os-specific/linux/jool/default.nix index 2ee5f0d6e0784..91276cbc11b17 100644 --- a/pkgs/os-specific/linux/jool/default.nix +++ b/pkgs/os-specific/linux/jool/default.nix @@ -1,4 +1,4 @@ -{ lib, stdenv, fetchFromGitHub, fetchpatch, kernel }: +{ lib, stdenv, fetchFromGitHub, kernel, nixosTests }: let sourceAttrs = (import ./source.nix) { inherit fetchFromGitHub; }; @@ -23,6 +23,8 @@ stdenv.mkDerivation { installTargets = "modules_install"; + passthru.tests = { inherit (nixosTests) jool; }; + meta = with lib; { homepage = "https://www.jool.mx/"; description = "Fairly compliant SIIT and Stateful NAT64 for Linux - kernel modules"; diff --git a/pkgs/os-specific/linux/jool/validate-config.patch b/pkgs/os-specific/linux/jool/validate-config.patch new file mode 100644 index 0000000000000..8841b6fb14f34 --- /dev/null +++ b/pkgs/os-specific/linux/jool/validate-config.patch @@ -0,0 +1,193 @@ +From df0a1cf61188b5b7bb98675d746cb63d9300f148 Mon Sep 17 00:00:00 2001 +From: rnhmjoj <rnhmjoj@inventati.org> +Date: Sat, 1 Jul 2023 18:47:05 +0200 +Subject: [PATCH] Add mode to validate the atomic configuration + +--- + src/usr/argp/main.c | 6 ++++++ + src/usr/argp/wargp/file.c | 26 +++++++++++++++++++++++++- + src/usr/argp/wargp/file.h | 1 + + src/usr/nl/file.c | 32 ++++++++++++++++++++++---------- + src/usr/nl/file.h | 3 ++- + 5 files changed, 56 insertions(+), 12 deletions(-) + +diff --git a/src/usr/argp/main.c b/src/usr/argp/main.c +index 744a6df0..d04917da 100644 +--- a/src/usr/argp/main.c ++++ b/src/usr/argp/main.c +@@ -238,6 +238,12 @@ static struct cmd_option file_ops[] = { + .handler = handle_file_update, + .handle_autocomplete = autocomplete_file_update, + }, ++ { ++ .label = "check", ++ .xt = XT_ANY, ++ .handler = handle_file_check, ++ .handle_autocomplete = autocomplete_file_update, ++ }, + { 0 }, + }; + +diff --git a/src/usr/argp/wargp/file.c b/src/usr/argp/wargp/file.c +index 0951b544..27ee3e64 100644 +--- a/src/usr/argp/wargp/file.c ++++ b/src/usr/argp/wargp/file.c +@@ -26,6 +26,30 @@ static struct wargp_option update_opts[] = { + { 0 }, + }; + ++int handle_file_check(char *iname, int argc, char **argv, void const *arg) ++{ ++ struct update_args uargs = { 0 }; ++ struct joolnl_socket sk = { 0 }; ++ struct jool_result result; ++ ++ result.error = wargp_parse(update_opts, argc, argv, &uargs); ++ if (result.error) ++ return result.error; ++ ++ if (!uargs.file_name.value) { ++ struct requirement reqs[] = { ++ { false, "a file name" }, ++ { 0 } ++ }; ++ return requirement_print(reqs); ++ } ++ ++ result = joolnl_file_parse(&sk, xt_get(), iname, uargs.file_name.value, ++ uargs.force.value, true); ++ ++ return pr_result(&result); ++} ++ + int handle_file_update(char *iname, int argc, char **argv, void const *arg) + { + struct update_args uargs = { 0 }; +@@ -49,7 +73,7 @@ int handle_file_update(char *iname, int argc, char **argv, void const *arg) + return pr_result(&result); + + result = joolnl_file_parse(&sk, xt_get(), iname, uargs.file_name.value, +- uargs.force.value); ++ uargs.force.value, false); + + joolnl_teardown(&sk); + return pr_result(&result); +diff --git a/src/usr/argp/wargp/file.h b/src/usr/argp/wargp/file.h +index ce5de508..8ea4a4d2 100644 +--- a/src/usr/argp/wargp/file.h ++++ b/src/usr/argp/wargp/file.h +@@ -2,6 +2,7 @@ + #define SRC_USR_ARGP_WARGP_FILE_H_ + + int handle_file_update(char *iname, int argc, char **argv, void const *arg); ++int handle_file_check(char *iname, int argc, char **argv, void const *arg); + void autocomplete_file_update(void const *args); + + #endif /* SRC_USR_ARGP_WARGP_FILE_H_ */ +diff --git a/src/usr/nl/file.c b/src/usr/nl/file.c +index f9413236..51a668bd 100644 +--- a/src/usr/nl/file.c ++++ b/src/usr/nl/file.c +@@ -29,6 +29,7 @@ static struct joolnl_socket sk; + static char const *iname; + static xlator_flags flags; + static __u8 force; ++static bool check; + + struct json_meta { + char const *name; /* This being NULL signals the end of the array. */ +@@ -163,9 +164,11 @@ static struct jool_result handle_array(cJSON *json, int attrtype, char *name, + goto too_small; + + nla_nest_end(msg, root); +- result = joolnl_request(&sk, msg, NULL, NULL); +- if (result.error) +- return result; ++ if (!check) { ++ result = joolnl_request(&sk, msg, NULL, NULL); ++ if (result.error) ++ return result; ++ } + + msg = NULL; + json = json->prev; +@@ -179,6 +182,8 @@ static struct jool_result handle_array(cJSON *json, int attrtype, char *name, + return result_success(); + + nla_nest_end(msg, root); ++ if (check) ++ return result_success(); + return joolnl_request(&sk, msg, NULL, NULL); + + too_small: +@@ -244,6 +249,8 @@ static struct jool_result handle_global(cJSON *json) + + nla_nest_end(msg, root); + free(meta); ++ if (check) ++ return result_success(); + return joolnl_request(&sk, msg, NULL, NULL); + + revert_meta: +@@ -654,9 +661,11 @@ static struct jool_result send_ctrl_msg(bool init) + else + NLA_PUT(msg, JNLAR_ATOMIC_END, 0, NULL); + +- result = joolnl_request(&sk, msg, NULL, NULL); +- if (result.error) +- return result; ++ if (!check) { ++ result = joolnl_request(&sk, msg, NULL, NULL); ++ if (result.error) ++ return result; ++ } + + return result_success(); + +@@ -683,9 +692,11 @@ static struct jool_result do_parsing(char const *iname, char *buffer) + if (result.error) + goto fail; + +- result = send_ctrl_msg(true); +- if (result.error) +- goto fail; ++ if (!check) { ++ result = send_ctrl_msg(true); ++ if (result.error) ++ goto fail; ++ } + + switch (xlator_flags2xt(flags)) { + case XT_SIIT: +@@ -718,12 +729,13 @@ fail: + } + + struct jool_result joolnl_file_parse(struct joolnl_socket *_sk, xlator_type xt, +- char const *iname, char const *file_name, bool _force) ++ char const *iname, char const *file_name, bool _force, bool _check) + { + char *buffer; + struct jool_result result; + + sk = *_sk; ++ check = _check; + flags = xt; + force = _force ? JOOLNLHDR_FLAGS_FORCE : 0; + +diff --git a/src/usr/nl/file.h b/src/usr/nl/file.h +index 51802aaf..8b4a66dd 100644 +--- a/src/usr/nl/file.h ++++ b/src/usr/nl/file.h +@@ -9,7 +9,8 @@ struct jool_result joolnl_file_parse( + xlator_type xt, + char const *iname, + char const *file_name, +- bool force ++ bool force, ++ bool check + ); + + struct jool_result joolnl_file_get_iname( +-- +2.40.1 + |