nixpkgs/nixos/modules/services/networking/nat-iptables.nix

191 lines
6.4 KiB
Nix

# 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
${cfg.extraStopCommands}
'';
mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: ''
# 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
'') cfg.internalInterfaces}
# 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}
''}
# NAT packets coming from the internal IPs.
${concatMapStrings (range: ''
${iptables} -w -t nat -A nixos-nat-post \
-s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
'') internalIPs}
# NAT from external ports to internal ports.
${concatMapStrings (fwd: ''
${iptables} -w -t nat -A nixos-nat-pre \
-i ${toString cfg.externalInterface} -p ${fwd.proto} \
--dport ${builtins.toString fwd.sourcePort} \
-j DNAT --to-destination ${fwd.destination}
${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}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1);
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}
# Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
${iptables} -w -t nat -A nixos-nat-pre \
-d ${loopbackip} -p ${fwd.proto} \
--dport ${builtins.toString fwd.sourcePort} \
-j DNAT --to-destination ${fwd.destination}
${iptables} -w -t nat -A nixos-nat-post \
-d ${destinationIP} -p ${fwd.proto} \
--dport ${destinationPorts} \
-j SNAT --to-source ${loopbackip}
'') 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
${mkSetupNat {
iptables = "iptables";
inherit dest;
inherit (cfg) internalIPs;
forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
}}
${optionalString cfg.enableIPv6 (mkSetupNat {
iptables = "ip6tables";
dest = destIPv6;
internalIPs = cfg.internalIPv6s;
forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
})}
${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
'';
in
{
options = {
networking.nat.extraCommands = mkOption {
type = types.lines;
default = "";
example = "iptables -A INPUT -p icmp -j ACCEPT";
description = lib.mdDoc ''
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 = lib.mdDoc ''
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 {
networking.firewall = mkIf config.networking.firewall.enable {
extraCommands = setupNat;
extraStopCommands = flushNat;
};
systemd.services = mkIf (!config.networking.firewall.enable) {
nat = {
description = "Network Address Translation";
wantedBy = [ "network.target" ];
after = [ "network-pre.target" "systemd-modules-load.service" ];
path = [ config.networking.firewall.package ];
unitConfig.ConditionCapability = "CAP_NET_ADMIN";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = flushNat + setupNat;
postStop = flushNat;
};
};
})
]);
}