Unverified Commit 355a9fa0 authored by rnhmjoj's avatar rnhmjoj
Browse files

nixos/jool: allow to manage multiple instances

parent 3c15feef
Loading
Loading
Loading
Loading
+186 −127
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ let
    TemporaryFileSystem = [ "/" ];
    BindReadOnlyPaths = [
      builtins.storeDir
      "/run/current-system/kernel-modules"
      "/run/booted-system/kernel-modules"
    ];

    # Give capabilities to load the module and configure it
@@ -31,26 +31,96 @@ let

  configFormat = pkgs.formats.json {};

  mkDefaultAttrs = lib.mapAttrs (n: v: lib.mkDefault v);
  # Generate the config file of instance `name`
  nat64Conf = name:
    configFormat.generate "jool-nat64-${name}.conf"
      (cfg.nat64.${name} // { instance = name; });
  siitConf = name:
    configFormat.generate "jool-siit-${name}.conf"
      (cfg.siit.${name} // { instance = name; });

  # NAT64 config type
  nat64Options = lib.types.submodule {
    # The format is plain JSON
    freeformType = configFormat.type;
    # Some options with a default value
    options.framework = lib.mkOption {
      type = lib.types.enum [ "netfilter" "iptables" ];
      default = "netfilter";
      description = lib.mdDoc ''
        The framework to use for attaching Jool's translation to the exist
        kernel packet processing rules. See the
        [documentation](https://nicmx.github.io/Jool/en/intro-jool.html#design)
        for the differences between the two options.
      '';
    };
    options.global.pool6 = lib.mkOption {
      type = lib.types.strMatching "[[:xdigit:]:]+/[[:digit:]]+"
        // { description = "Network prefix in CIDR notation"; };
      default = "64:ff9b::/96";
      description = lib.mdDoc ''
        The prefix used for embedding IPv4 into IPv6 addresses.
        Defaults to the well-known NAT64 prefix, defined by
        [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
      '';
    };
  };

  # SIIT config type
  siitOptions = lib.types.submodule {
    # The format is, again, plain JSON
    freeformType = configFormat.type;
    # Some options with a default value
    options = { inherit (nat64Options.getSubOptions []) framework; };
  };

  makeNat64Unit = name: opts: {
    "jool-nat64-${name}" = {
      description = "Jool, NAT64 setup of instance ${name}";
      documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStartPre = "${pkgs.kmod}/bin/modprobe jool";
        ExecStart    = "${jool-cli}/bin/jool file handle ${nat64Conf name}";
        ExecStop     = "${jool-cli}/bin/jool -f ${nat64Conf name} instance remove";
      } // hardening;
    };
  };

  defaultNat64 = {
    instance = "default";
    framework = "netfilter";
    global.pool6 = "64:ff9b::/96";
  makeSiitUnit = name: opts: {
    "jool-siit-${name}" = {
      description = "Jool, SIIT setup of instance ${name}";
      documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStartPre = "${pkgs.kmod}/bin/modprobe jool_siit";
        ExecStart    = "${jool-cli}/bin/jool_siit file handle ${siitConf name}";
        ExecStop     = "${jool-cli}/bin/jool_siit -f ${siitConf name} instance remove";
      } // hardening;
    };
  defaultSiit = {
    instance = "default";
    framework = "netfilter";
  };

  nat64Conf = configFormat.generate "jool-nat64.conf" cfg.nat64.config;
  siitConf  = configFormat.generate "jool-siit.conf" cfg.siit.config;
  checkNat64 = name: _: ''
    printf 'Validating Jool configuration for NAT64 instance "${name}"... '
    jool file check ${nat64Conf name}
    printf 'Ok.\n'; touch "$out"
  '';

  checkSiit = name: _: ''
    printf 'Validating Jool configuration for SIIT instance "${name}"... '
    jool_siit file check ${siitConf name}
    printf 'Ok.\n'; touch "$out"
  '';

in

{
  ###### interface

  options = {
    networking.jool.enable = lib.mkOption {
      type = lib.types.bool;
@@ -64,15 +134,18 @@ in
        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.

        Enabling this option will install the Jool kernel module and the
        command line tools for controlling it.
      '';
    };

    networking.jool.nat64.enable = lib.mkEnableOption (lib.mdDoc "a NAT64 instance of Jool.");
    networking.jool.nat64.config = lib.mkOption {
      type = configFormat.type;
      default = defaultNat64;
    networking.jool.nat64 = lib.mkOption {
      type = lib.types.attrsOf nat64Options;
      default = { };
      example = lib.literalExpression ''
        {
          default = {
            # custom NAT64 prefix
            global.pool6 = "2001:db8:64::/96";

@@ -96,7 +169,7 @@ in
            ];

            pool4 = [
            # Ports for dynamic translation
              # Port ranges 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"; }
@@ -105,116 +178,102 @@ in
              { 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.
        Definitions of NAT64 instances of Jool.
        See the
        [documentation](https://nicmx.github.io/Jool/en/config-atomic.html) for
        the available options. Also check out the
        [tutorial](https://nicmx.github.io/Jool/en/run-nat64.html) for an
        introduction to NAT64 and how to troubleshoot the setup.

        The attribute name defines the name of the instance, with the main one
        being `default`: this can be accessed from the command line without
        specifying the name with `-i`.

        ::: {.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.
        Instances created imperatively from the command line will not interfere
        with the NixOS instances, 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`.
        Changes to an instance performed via `jool -i <name>` are applied
        correctly but will be lost after restarting the respective
        `jool-nat64-<name>.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;
    networking.jool.siit = lib.mkOption {
      type = lib.types.attrsOf siitOptions;
      default = { };
      example = lib.literalExpression ''
        {
          default = {
            # Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v.
          pool6 = "2001:db8::/96";
            global.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" }
              { "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" }
          ]
              { "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.
        Definitions of SIIT instances of Jool.
        See the
        [documentation](https://nicmx.github.io/Jool/en/config-atomic.html) for
        the available options. Also check out the
        [tutorial](https://nicmx.github.io/Jool/en/run-vanilla.html) for an
        introduction to SIIT and how to troubleshoot the setup.

        The attribute name defines the name of the instance, with the main one
        being `default`: this can be accessed from the command line without
        specifying the name with `-i`.

        ::: {.note}
        Existing or more instances created manually will not interfere with the
        NixOS instance, provided the respective `EAMT` address mappings are not
        overlapping.
        Instances created imperatively from the command line will not interfere
        with the NixOS instances, provided the respective EAMT addresses and
        port ranges 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`.
        Changes to an instance performed via `jool -i <name>` are applied
        correctly but will be lost after restarting the respective
        `jool-siit-<name>.service`.
        :::
      '';
    };

  };

  ###### implementation

  config = lib.mkIf cfg.enable {
    environment.systemPackages = [ jool-cli ];
    # Install kernel module and cli tools
    boot.extraModulePackages = [ jool ];
    environment.systemPackages = [ jool-cli ];

    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;
    };
    # Install services for each instance
    systemd.services = lib.mkMerge
      (lib.mapAttrsToList makeNat64Unit cfg.nat64 ++
       lib.mapAttrsToList makeSiitUnit cfg.siit);

    system.checks = lib.singleton (pkgs.runCommand "jool-validated" {
      nativeBuildInputs = [ pkgs.buildPackages.jool-cli ];
    # Check the configuration of each instance
    system.checks = lib.optional (cfg.nat64 != {} || cfg.siit != {})
      (pkgs.runCommand "jool-validated"
        {
          nativeBuildInputs = with 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;

        }
        (lib.concatStrings
          (lib.mapAttrsToList checkNat64 cfg.nat64 ++
           lib.mapAttrsToList checkSiit cfg.siit)));
  };

  meta.maintainers = with lib.maintainers; [ rnhmjoj ];