nixos/jool: allow to manage multiple instances

rnhmjoj 355a9fa0 3c15feef

Changed files
+182 -123
nixos
modules
services
networking
+182 -123
nixos/modules/services/networking/jool.nix
···
TemporaryFileSystem = [ "/" ];
BindReadOnlyPaths = [
builtins.storeDir
-
"/run/current-system/kernel-modules"
+
"/run/booted-system/kernel-modules"
];
# Give capabilities to load the module and configure it
···
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; };
+
};
-
defaultNat64 = {
-
instance = "default";
-
framework = "netfilter";
-
global.pool6 = "64:ff9b::/96";
+
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;
+
};
};
-
defaultSiit = {
-
instance = "default";
-
framework = "netfilter";
+
+
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;
+
};
};
-
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;
···
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 ''
{
-
# custom NAT64 prefix
-
global.pool6 = "2001:db8:64::/96";
+
default = {
+
# 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";
-
}
-
];
+
# 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"; }
+
pool4 = [
+
# 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"; }
-
# 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"; }
-
];
+
# 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.
+
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 ''
{
-
# Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v.
-
pool6 = "2001:db8::/96";
+
default = {
+
# Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v.
+
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" }
-
# 2001:db8:1::x ←→ 198.51.100.x
-
{ "ipv6 prefix": "2001:db8:2::/120", "ipv4 prefix": "198.51.100.0/24" }
-
]
+
# 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.
+
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 ];
-
-
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"
-
'');
+
environment.systemPackages = [ jool-cli ];
-
networking.jool.nat64.config = mkDefaultAttrs defaultNat64;
-
networking.jool.siit.config = mkDefaultAttrs defaultSiit;
+
# Install services for each instance
+
systemd.services = lib.mkMerge
+
(lib.mapAttrsToList makeNat64Unit cfg.nat64 ++
+
lib.mapAttrsToList makeSiitUnit cfg.siit);
+
# 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;
+
}
+
(lib.concatStrings
+
(lib.mapAttrsToList checkNat64 cfg.nat64 ++
+
lib.mapAttrsToList checkSiit cfg.siit)));
};
meta.maintainers = with lib.maintainers; [ rnhmjoj ];