diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/networking/nat-iptables.nix')
-rw-r--r-- | nixpkgs/nixos/modules/services/networking/nat-iptables.nix | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/networking/nat-iptables.nix b/nixpkgs/nixos/modules/services/networking/nat-iptables.nix new file mode 100644 index 000000000000..d1bed401feeb --- /dev/null +++ b/nixpkgs/nixos/modules/services/networking/nat-iptables.nix @@ -0,0 +1,191 @@ +# 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; + }; + }; + }) + ]); +} |