nixos/nat: support IPv6 NAT
This commit is contained in:
parent
4e721164a8
commit
b93a5a1746
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user