nixpkgs/nixos/modules/services/networking/nat-nftables.nix
Silvan Mosberger 4f0dadbf38 treewide: format all inactive Nix files
After final improvements to the official formatter implementation,
this commit now performs the first treewide reformat of Nix files using it.
This is part of the implementation of RFC 166.

Only "inactive" files are reformatted, meaning only files that
aren't being touched by any PR with activity in the past 2 months.
This is to avoid conflicts for PRs that might soon be merged.
Later we can do a full treewide reformat to get the rest,
which should not cause as many conflicts.

A CI check has already been running for some time to ensure that new and
already-formatted files are formatted, so the files being reformatted here
should also stay formatted.

This commit was automatically created and can be verified using

    nix-build a08b3a4d19.tar.gz \
      --argstr baseRev b32a094368
    result/bin/apply-formatting $NIXPKGS_PATH
2024-12-10 20:26:33 +01:00

176 lines
5.5 KiB
Nix

{ config, lib, ... }:
with lib;
let
cfg = config.networking.nat;
mkDest = externalIP: if externalIP == null then "masquerade" else "snat ${externalIP}";
dest = mkDest cfg.externalIP;
destIPv6 = mkDest cfg.externalIPv6;
toNftSet = list: concatStringsSep ", " list;
toNftRange = ports: replaceStrings [ ":" ] [ "-" ] (toString ports);
ifaceSet = toNftSet (map (x: ''"${x}"'') cfg.internalInterfaces);
ipSet = toNftSet cfg.internalIPs;
ipv6Set = toNftSet cfg.internalIPv6s;
oifExpr = optionalString (cfg.externalInterface != null) ''oifname "${cfg.externalInterface}"'';
# Whether given IP (plus optional port) is an IPv6.
isIPv6 = ip: length (lib.splitString ":" ip) > 2;
splitIPPorts =
IPPorts:
let
matchIP = if isIPv6 IPPorts then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
m = builtins.match "${matchIP}:([0-9-]+)" IPPorts;
in
{
IP = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 0;
ports = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 1;
};
mkTable =
{
ipVer,
dest,
ipSet,
forwardPorts,
dmzHost,
externalIP,
}:
let
# nftables maps for port forward
# [daddr .] l4proto . dport : addr . port
fwdMap = toNftSet (
map (
fwd:
with (splitIPPorts fwd.destination);
"${
optionalString (externalIP != null) "${externalIP} . "
}${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
) forwardPorts
);
# nftables maps for port forward loopback dnat
# daddr . l4proto . dport : addr . port
fwdLoopDnatMap = toNftSet (
concatMap (
fwd:
map (
loopbackip:
with (splitIPPorts fwd.destination);
"${loopbackip} . ${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
) fwd.loopbackIPs
) forwardPorts
);
# nftables set for port forward loopback snat
# daddr . l4proto . dport
fwdLoopSnatSet = toNftSet (
map (fwd: with (splitIPPorts fwd.destination); "${IP} . ${fwd.proto} . ${ports}") forwardPorts
);
in
''
chain pre {
type nat hook prerouting priority dstnat;
${optionalString (fwdMap != "") ''
iifname "${cfg.externalInterface}" meta l4proto { tcp, udp } dnat ${
optionalString (externalIP != null) "${ipVer} daddr . "
}meta l4proto . th dport map { ${fwdMap} } comment "port forward"
''}
${optionalString (fwdLoopDnatMap != "") ''
meta l4proto { tcp, udp } dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from other hosts behind NAT"
''}
${optionalString (dmzHost != null) ''
iifname "${cfg.externalInterface}" dnat ${dmzHost} comment "dmz"
''}
}
chain post {
type nat hook postrouting priority srcnat;
${optionalString (ifaceSet != "") ''
iifname { ${ifaceSet} } ${oifExpr} ${dest} comment "from internal interfaces"
''}
${optionalString (ipSet != "") ''
${ipVer} saddr { ${ipSet} } ${oifExpr} ${dest} comment "from internal IPs"
''}
${optionalString (fwdLoopSnatSet != "") ''
iifname != "${cfg.externalInterface}" ${ipVer} daddr . meta l4proto . th dport { ${fwdLoopSnatSet} } masquerade comment "port forward loopback snat"
''}
}
chain out {
type nat hook output priority mangle;
${optionalString (fwdLoopDnatMap != "") ''
meta l4proto { tcp, udp } dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from the host itself"
''}
}
'';
in
{
config = mkIf (config.networking.nftables.enable && cfg.enable) {
assertions = [
{
assertion = cfg.extraCommands == "";
message = "extraCommands is incompatible with the nftables based nat module: ${cfg.extraCommands}";
}
{
assertion = cfg.extraStopCommands == "";
message = "extraStopCommands is incompatible with the nftables based nat module: ${cfg.extraStopCommands}";
}
{
assertion = config.networking.nftables.rulesetFile == null;
message = "networking.nftables.rulesetFile conflicts with the nat module";
}
];
networking.nftables.tables = {
"nixos-nat" = {
family = "ip";
content = mkTable {
ipVer = "ip";
inherit dest ipSet;
forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
inherit (cfg) dmzHost externalIP;
};
};
"nixos-nat6" = mkIf cfg.enableIPv6 {
family = "ip6";
name = "nixos-nat";
content = mkTable {
ipVer = "ip6";
dest = destIPv6;
ipSet = ipv6Set;
forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
dmzHost = null;
externalIP = cfg.externalIPv6;
};
};
};
networking.firewall.extraForwardRules = optionalString config.networking.firewall.filterForward ''
${optionalString (ifaceSet != "") ''
iifname { ${ifaceSet} } ${oifExpr} accept comment "from internal interfaces"
''}
${optionalString (ipSet != "") ''
ip saddr { ${ipSet} } ${oifExpr} accept comment "from internal IPs"
''}
${optionalString (ipv6Set != "") ''
ip6 saddr { ${ipv6Set} } ${oifExpr} accept comment "from internal IPv6s"
''}
'';
};
}