2022-12-23 00:23:23 +08:00
|
|
|
# This module enables Network Address Translation (NAT).
|
|
|
|
# XXX: todo: support multiple upstream links
|
|
|
|
# see http://yesican.chsoft.biz/lartc/MultihomedLinuxNetworking.html
|
|
|
|
|
|
|
|
{
|
|
|
|
config,
|
|
|
|
lib,
|
|
|
|
pkgs,
|
|
|
|
...
|
|
|
|
}:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.networking.nat;
|
|
|
|
|
|
|
|
mkDest =
|
|
|
|
externalIP: if externalIP == null then "-j MASQUERADE" else "-j SNAT --to-source ${externalIP}";
|
|
|
|
dest = mkDest cfg.externalIP;
|
|
|
|
destIPv6 = mkDest cfg.externalIPv6;
|
|
|
|
|
|
|
|
# Whether given IP (plus optional port) is an IPv6.
|
|
|
|
isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2;
|
|
|
|
|
|
|
|
helpers = import ./helpers.nix { inherit config lib; };
|
|
|
|
|
|
|
|
flushNat = ''
|
|
|
|
${helpers}
|
|
|
|
ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
|
|
|
|
ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true
|
|
|
|
ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true
|
|
|
|
ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
|
|
|
|
ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true
|
|
|
|
ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true
|
|
|
|
ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true
|
|
|
|
ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true
|
|
|
|
ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true
|
2024-07-21 07:22:52 +02:00
|
|
|
ip46tables -w -t filter -D FORWARD -j nixos-filter-forward 2>/dev/null || true
|
|
|
|
ip46tables -w -t filter -F nixos-filter-forward 2>/dev/null || true
|
|
|
|
ip46tables -w -t filter -X nixos-filter-forward 2>/dev/null || true
|
2022-12-23 00:23:23 +08:00
|
|
|
|
|
|
|
${cfg.extraStopCommands}
|
|
|
|
'';
|
|
|
|
|
2024-07-21 06:58:20 +02:00
|
|
|
mkSetupNat =
|
|
|
|
{
|
|
|
|
iptables,
|
|
|
|
dest,
|
|
|
|
internalIPs,
|
|
|
|
forwardPorts,
|
|
|
|
externalIp,
|
|
|
|
}:
|
|
|
|
''
|
2022-12-23 00:23:23 +08:00
|
|
|
# We can't match on incoming interface in POSTROUTING, so
|
|
|
|
# mark packets coming from the internal interfaces.
|
|
|
|
${concatMapStrings (iface: ''
|
|
|
|
${iptables} -w -t nat -A nixos-nat-pre \
|
|
|
|
-i '${iface}' -j MARK --set-mark 1
|
2024-07-21 07:22:52 +02:00
|
|
|
${iptables} -w -t filter -A nixos-filter-forward \
|
|
|
|
-i '${iface}' ${
|
|
|
|
optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"
|
|
|
|
} -j ACCEPT
|
2022-12-23 00:23:23 +08:00
|
|
|
'') cfg.internalInterfaces}
|
2024-12-10 20:26:33 +01:00
|
|
|
|
2022-12-23 00:23:23 +08:00
|
|
|
# NAT the marked packets.
|
|
|
|
${optionalString (cfg.internalInterfaces != [ ]) ''
|
|
|
|
${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \
|
|
|
|
${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
|
|
|
|
''}
|
2024-12-10 20:26:33 +01:00
|
|
|
|
2022-12-23 00:23:23 +08:00
|
|
|
# NAT packets coming from the internal IPs.
|
|
|
|
${concatMapStrings (range: ''
|
|
|
|
${iptables} -w -t nat -A nixos-nat-post \
|
2024-07-21 07:22:52 +02:00
|
|
|
-s '${range}' ${
|
|
|
|
optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"
|
2022-12-23 00:23:23 +08:00
|
|
|
} ${dest}
|
2024-07-21 07:22:52 +02:00
|
|
|
${iptables} -w -t filter -A nixos-filter-forward \
|
2022-12-23 00:23:23 +08:00
|
|
|
-s '${range}' ${
|
|
|
|
optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"
|
2024-07-21 07:22:52 +02:00
|
|
|
} -j ACCEPT
|
2022-12-23 00:23:23 +08:00
|
|
|
'') internalIPs}
|
2024-12-10 20:26:33 +01:00
|
|
|
|
2024-07-21 07:22:52 +02:00
|
|
|
# Related connections are allowed
|
|
|
|
${iptables} -w -t filter -A nixos-filter-forward \
|
|
|
|
-m state --state ESTABLISHED,RELATED -j ACCEPT
|
2024-12-10 20:26:33 +01:00
|
|
|
|
2022-12-23 00:23:23 +08:00
|
|
|
# NAT from external ports to internal ports.
|
|
|
|
${concatMapStrings (fwd: ''
|
|
|
|
${iptables} -w -t nat -A nixos-nat-pre \
|
|
|
|
-i ${toString cfg.externalInterface} -p ${fwd.proto} \
|
2024-07-21 06:58:20 +02:00
|
|
|
${
|
|
|
|
optionalString (externalIp != null) "-d ${externalIp}"
|
|
|
|
} --dport ${builtins.toString fwd.sourcePort} \
|
2022-12-23 00:23:23 +08:00
|
|
|
-j DNAT --to-destination ${fwd.destination}
|
2024-07-21 07:22:52 +02:00
|
|
|
${iptables} -w -t filter -A nixos-filter-forward \
|
|
|
|
-i ${toString cfg.externalInterface} -p ${fwd.proto} \
|
|
|
|
--dport ${builtins.toString fwd.sourcePort} -j ACCEPT
|
2024-12-10 20:26:33 +01:00
|
|
|
|
2022-12-23 00:23:23 +08:00
|
|
|
${concatMapStrings (
|
|
|
|
loopbackip:
|
|
|
|
let
|
|
|
|
matchIP = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
|
|
|
|
m = builtins.match "${matchIP}:([0-9-]+)" fwd.destination;
|
|
|
|
destinationIP = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
|
|
|
|
destinationPorts =
|
|
|
|
if m == null then
|
|
|
|
throw "bad ip:ports `${fwd.destination}'"
|
2024-12-10 20:26:33 +01:00
|
|
|
else
|
2022-12-23 00:23:23 +08:00
|
|
|
builtins.replaceStrings [ "-" ] [ ":" ] (elemAt m 1);
|
2024-07-21 07:20:31 +02:00
|
|
|
in
|
|
|
|
''
|
|
|
|
# Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
|
|
|
|
${iptables} -w -t nat -A nixos-nat-out \
|
|
|
|
-d ${loopbackip} -p ${fwd.proto} \
|
|
|
|
--dport ${builtins.toString fwd.sourcePort} \
|
|
|
|
-j DNAT --to-destination ${fwd.destination}
|
2024-12-10 20:26:33 +01:00
|
|
|
|
2022-12-23 00:23:23 +08:00
|
|
|
# Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
|
2024-07-21 07:20:31 +02:00
|
|
|
${concatMapStrings (range: ''
|
|
|
|
${iptables} -w -t nat -A nixos-nat-pre \
|
|
|
|
-d ${loopbackip} -p ${fwd.proto} -s '${range}' \
|
|
|
|
--dport ${builtins.toString fwd.sourcePort} \
|
|
|
|
-j DNAT --to-destination ${fwd.destination}
|
|
|
|
${iptables} -w -t nat -A nixos-nat-post \
|
|
|
|
-d ${destinationIP} -p ${fwd.proto} \
|
|
|
|
-s '${range}' --dport ${destinationPorts} \
|
|
|
|
-j SNAT --to-source ${loopbackip}
|
2024-07-21 07:22:52 +02:00
|
|
|
${iptables} -w -t filter -A nixos-filter-forward \
|
|
|
|
-d ${destinationIP} -p ${fwd.proto} \
|
|
|
|
-s '${range}' --dport ${destinationPorts} -j ACCEPT
|
2024-07-21 07:20:31 +02:00
|
|
|
'') internalIPs}
|
|
|
|
${concatMapStrings (iface: ''
|
|
|
|
${iptables} -w -t nat -A nixos-nat-pre \
|
|
|
|
-d ${loopbackip} -p ${fwd.proto} -i '${iface}' \
|
|
|
|
--dport ${builtins.toString fwd.sourcePort} \
|
|
|
|
-j DNAT --to-destination ${fwd.destination}
|
|
|
|
${iptables} -w -t nat -A nixos-nat-post \
|
2024-07-21 07:22:52 +02:00
|
|
|
-d ${destinationIP} -p ${fwd.proto} \
|
|
|
|
-i '${iface}' --dport ${destinationPorts} \
|
2024-07-21 07:20:31 +02:00
|
|
|
-j SNAT --to-source ${loopbackip}
|
2024-07-21 07:22:52 +02:00
|
|
|
${iptables} -w -t filter -A nixos-filter-forward \
|
|
|
|
-d ${destinationIP} -p ${fwd.proto} \
|
|
|
|
-i '${iface}' --dport ${destinationPorts} -j ACCEPT
|
|
|
|
'') cfg.internalInterfaces}
|
2022-12-23 00:23:23 +08:00
|
|
|
''
|
|
|
|
) fwd.loopbackIPs}
|
|
|
|
'') forwardPorts}
|
|
|
|
'';
|
|
|
|
|
|
|
|
setupNat = ''
|
|
|
|
${helpers}
|
|
|
|
# Create subchains where we store rules
|
|
|
|
ip46tables -w -t nat -N nixos-nat-pre
|
|
|
|
ip46tables -w -t nat -N nixos-nat-post
|
|
|
|
ip46tables -w -t nat -N nixos-nat-out
|
2024-07-21 07:22:52 +02:00
|
|
|
ip46tables -w -t filter -N nixos-filter-forward
|
2022-12-23 00:23:23 +08:00
|
|
|
|
|
|
|
${mkSetupNat {
|
|
|
|
iptables = "iptables";
|
|
|
|
inherit dest;
|
|
|
|
inherit (cfg) internalIPs;
|
|
|
|
forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
|
2024-07-21 06:58:20 +02:00
|
|
|
externalIp = cfg.externalIP;
|
2022-12-23 00:23:23 +08:00
|
|
|
}}
|
|
|
|
|
|
|
|
${optionalString cfg.enableIPv6 (mkSetupNat {
|
|
|
|
iptables = "ip6tables";
|
|
|
|
dest = destIPv6;
|
|
|
|
internalIPs = cfg.internalIPv6s;
|
|
|
|
forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
|
2024-07-21 06:58:20 +02:00
|
|
|
externalIp = cfg.externalIPv6;
|
2022-12-23 00:23:23 +08:00
|
|
|
})}
|
|
|
|
|
|
|
|
${optionalString (cfg.dmzHost != null) ''
|
|
|
|
iptables -w -t nat -A nixos-nat-pre \
|
|
|
|
-i ${toString cfg.externalInterface} -j DNAT \
|
|
|
|
--to-destination ${cfg.dmzHost}
|
|
|
|
''}
|
|
|
|
|
|
|
|
${cfg.extraCommands}
|
|
|
|
|
|
|
|
# Append our chains to the nat tables
|
|
|
|
ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre
|
|
|
|
ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post
|
|
|
|
ip46tables -w -t nat -A OUTPUT -j nixos-nat-out
|
2024-07-21 07:22:52 +02:00
|
|
|
ip46tables -w -t filter -A FORWARD -j nixos-filter-forward
|
2022-12-23 00:23:23 +08:00
|
|
|
'';
|
|
|
|
|
|
|
|
in
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
networking.nat.extraCommands = mkOption {
|
|
|
|
type = types.lines;
|
|
|
|
default = "";
|
|
|
|
example = "iptables -A INPUT -p icmp -j ACCEPT";
|
|
|
|
description = ''
|
|
|
|
Additional shell commands executed as part of the nat
|
|
|
|
initialisation script.
|
|
|
|
|
|
|
|
This option is incompatible with the nftables based nat module.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
networking.nat.extraStopCommands = mkOption {
|
|
|
|
type = types.lines;
|
|
|
|
default = "";
|
|
|
|
example = "iptables -D INPUT -p icmp -j ACCEPT || true";
|
|
|
|
description = ''
|
|
|
|
Additional shell commands executed as part of the nat
|
|
|
|
teardown script.
|
|
|
|
|
|
|
|
This option is incompatible with the nftables based nat module.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
config = mkIf (!config.networking.nftables.enable) (mkMerge [
|
|
|
|
({ networking.firewall.extraCommands = mkBefore flushNat; })
|
|
|
|
(mkIf config.networking.nat.enable {
|
2024-12-10 20:26:33 +01:00
|
|
|
|
2022-12-23 00:23:23 +08:00
|
|
|
networking.firewall = mkIf config.networking.firewall.enable {
|
|
|
|
extraCommands = setupNat;
|
|
|
|
extraStopCommands = flushNat;
|
2024-12-10 20:26:33 +01:00
|
|
|
};
|
|
|
|
|
2022-12-23 00:23:23 +08:00
|
|
|
systemd.services = mkIf (!config.networking.firewall.enable) {
|
2024-12-10 20:26:33 +01:00
|
|
|
nat = {
|
2022-12-23 00:23:23 +08:00
|
|
|
description = "Network Address Translation";
|
|
|
|
wantedBy = [ "network.target" ];
|
|
|
|
after = [
|
|
|
|
"network-pre.target"
|
|
|
|
"systemd-modules-load.service"
|
2024-12-10 20:26:33 +01:00
|
|
|
];
|
2022-12-23 00:23:23 +08:00
|
|
|
path = [ config.networking.firewall.package ];
|
|
|
|
unitConfig.ConditionCapability = "CAP_NET_ADMIN";
|
2024-12-10 20:26:33 +01:00
|
|
|
|
2022-12-23 00:23:23 +08:00
|
|
|
serviceConfig = {
|
|
|
|
Type = "oneshot";
|
|
|
|
RemainAfterExit = true;
|
2024-12-10 20:26:33 +01:00
|
|
|
};
|
2022-12-23 00:23:23 +08:00
|
|
|
|
|
|
|
script = flushNat + setupNat;
|
|
|
|
|
|
|
|
postStop = flushNat;
|
|
|
|
};
|
2024-12-10 20:26:33 +01:00
|
|
|
};
|
2022-12-23 00:23:23 +08:00
|
|
|
})
|
|
|
|
]);
|
|
|
|
}
|