|
|
|
@ -9,7 +9,14 @@ with lib; |
|
|
|
|
let |
|
|
|
|
cfg = config.networking.nat; |
|
|
|
|
|
|
|
|
|
dest = if cfg.externalIP == null then "-j MASQUERADE" else "-j SNAT --to-source ${cfg.externalIP}"; |
|
|
|
|
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; }; |
|
|
|
|
|
|
|
|
@ -28,63 +35,80 @@ let |
|
|
|
|
${cfg.extraStopCommands} |
|
|
|
|
''; |
|
|
|
|
|
|
|
|
|
setupNat = '' |
|
|
|
|
${helpers} |
|
|
|
|
# Create subchain 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, 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 \ |
|
|
|
|
${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 \ |
|
|
|
|
${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 \ |
|
|
|
|
${iptables} -w -t nat -A nixos-nat-post \ |
|
|
|
|
-s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest} |
|
|
|
|
'') cfg.internalIPs} |
|
|
|
|
'') internalIPs} |
|
|
|
|
|
|
|
|
|
# NAT from external ports to internal ports. |
|
|
|
|
${concatMapStrings (fwd: '' |
|
|
|
|
iptables -w -t nat -A nixos-nat-pre \ |
|
|
|
|
${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 |
|
|
|
|
m = builtins.match "([0-9.]+):([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); |
|
|
|
|
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 \ |
|
|
|
|
${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 \ |
|
|
|
|
${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 \ |
|
|
|
|
${iptables} -w -t nat -A nixos-nat-post \ |
|
|
|
|
-d ${destinationIP} -p ${fwd.proto} \ |
|
|
|
|
--dport ${destinationPorts} \ |
|
|
|
|
-j SNAT --to-source ${loopbackip} |
|
|
|
|
'') fwd.loopbackIPs} |
|
|
|
|
'') cfg.forwardPorts} |
|
|
|
|
'') 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 \ |
|
|
|
@ -117,6 +141,15 @@ in |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
networking.nat.enableIPv6 = mkOption { |
|
|
|
|
type = types.bool; |
|
|
|
|
default = false; |
|
|
|
|
description = |
|
|
|
|
'' |
|
|
|
|
Whether to enable IPv6 NAT. |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
networking.nat.internalInterfaces = mkOption { |
|
|
|
|
type = types.listOf types.str; |
|
|
|
|
default = []; |
|
|
|
@ -141,6 +174,18 @@ in |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
networking.nat.internalIPv6s = mkOption { |
|
|
|
|
type = types.listOf types.str; |
|
|
|
|
default = []; |
|
|
|
|
example = [ "fc00::/64" ]; |
|
|
|
|
description = |
|
|
|
|
'' |
|
|
|
|
The IPv6 address ranges for which to perform NAT. Packets |
|
|
|
|
coming from these addresses (on any interface) and destined |
|
|
|
|
for the external interface will be rewritten. |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
networking.nat.externalInterface = mkOption { |
|
|
|
|
type = types.nullOr types.str; |
|
|
|
|
default = null; |
|
|
|
@ -164,6 +209,19 @@ in |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
networking.nat.externalIPv6 = mkOption { |
|
|
|
|
type = types.nullOr types.str; |
|
|
|
|
default = null; |
|
|
|
|
example = "2001:dc0:2001:11::175"; |
|
|
|
|
description = |
|
|
|
|
'' |
|
|
|
|
The public IPv6 address to which packets from the local |
|
|
|
|
network are to be rewritten. If this is left empty, the |
|
|
|
|
IP address associated with the external interface will be |
|
|
|
|
used. |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
networking.nat.forwardPorts = mkOption { |
|
|
|
|
type = with types; listOf (submodule { |
|
|
|
|
options = { |
|
|
|
@ -176,7 +234,7 @@ in |
|
|
|
|
destination = mkOption { |
|
|
|
|
type = types.str; |
|
|
|
|
example = "10.0.0.1:80"; |
|
|
|
|
description = "Forward connection to destination ip:port; to specify a port range, use ip:start-end"; |
|
|
|
|
description = "Forward connection to destination ip:port (or [ipv6]:port); to specify a port range, use ip:start-end"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
proto = mkOption { |
|
|
|
@ -195,11 +253,15 @@ in |
|
|
|
|
}; |
|
|
|
|
}); |
|
|
|
|
default = []; |
|
|
|
|
example = [ { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; } ]; |
|
|
|
|
example = [ |
|
|
|
|
{ sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; } |
|
|
|
|
{ sourcePort = 8080; destination = "[fc00::2]:80"; proto = "tcp"; } |
|
|
|
|
]; |
|
|
|
|
description = |
|
|
|
|
'' |
|
|
|
|
List of forwarded ports from the external interface to |
|
|
|
|
internal destinations by using DNAT. |
|
|
|
|
internal destinations by using DNAT. Destination can be |
|
|
|
|
IPv6 if IPv6 NAT is enabled. |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -246,6 +308,9 @@ in |
|
|
|
|
(mkIf config.networking.nat.enable { |
|
|
|
|
|
|
|
|
|
assertions = [ |
|
|
|
|
{ assertion = cfg.enableIPv6 -> config.networking.enableIPv6; |
|
|
|
|
message = "networking.nat.enableIPv6 requires networking.enableIPv6"; |
|
|
|
|
} |
|
|
|
|
{ assertion = (cfg.dmzHost != null) -> (cfg.externalInterface != null); |
|
|
|
|
message = "networking.nat.dmzHost requires networking.nat.externalInterface"; |
|
|
|
|
} |
|
|
|
@ -261,6 +326,15 @@ in |
|
|
|
|
kernel.sysctl = { |
|
|
|
|
"net.ipv4.conf.all.forwarding" = mkOverride 99 true; |
|
|
|
|
"net.ipv4.conf.default.forwarding" = mkOverride 99 true; |
|
|
|
|
} // optionalAttrs cfg.enableIPv6 { |
|
|
|
|
# Do not prevent IPv6 autoconfiguration. |
|
|
|
|
# See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>. |
|
|
|
|
"net.ipv6.conf.all.accept_ra" = mkOverride 99 2; |
|
|
|
|
"net.ipv6.conf.default.accept_ra" = mkOverride 99 2; |
|
|
|
|
|
|
|
|
|
# Forward IPv6 packets. |
|
|
|
|
"net.ipv6.conf.all.forwarding" = mkOverride 99 true; |
|
|
|
|
"net.ipv6.conf.default.forwarding" = mkOverride 99 true; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|