at 23.11-pre 6.6 kB view raw
1# This module enables Network Address Translation (NAT). 2# XXX: todo: support multiple upstream links 3# see http://yesican.chsoft.biz/lartc/MultihomedLinuxNetworking.html 4 5{ config, lib, pkgs, ... }: 6 7with lib; 8 9let 10 cfg = config.networking.nat; 11 12 mkDest = externalIP: 13 if externalIP == null 14 then "-j MASQUERADE" 15 else "-j SNAT --to-source ${externalIP}"; 16 dest = mkDest cfg.externalIP; 17 destIPv6 = mkDest cfg.externalIPv6; 18 19 # Whether given IP (plus optional port) is an IPv6. 20 isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2; 21 22 helpers = import ./helpers.nix { inherit config lib; }; 23 24 flushNat = '' 25 ${helpers} 26 ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true 27 ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true 28 ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true 29 ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true 30 ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true 31 ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true 32 ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true 33 ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true 34 ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true 35 36 ${cfg.extraStopCommands} 37 ''; 38 39 mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: '' 40 # We can't match on incoming interface in POSTROUTING, so 41 # mark packets coming from the internal interfaces. 42 ${concatMapStrings (iface: '' 43 ${iptables} -w -t nat -A nixos-nat-pre \ 44 -i '${iface}' -j MARK --set-mark 1 45 '') cfg.internalInterfaces} 46 47 # NAT the marked packets. 48 ${optionalString (cfg.internalInterfaces != []) '' 49 ${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \ 50 ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest} 51 ''} 52 53 # NAT packets coming from the internal IPs. 54 ${concatMapStrings (range: '' 55 ${iptables} -w -t nat -A nixos-nat-post \ 56 -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest} 57 '') internalIPs} 58 59 # NAT from external ports to internal ports. 60 ${concatMapStrings (fwd: '' 61 ${iptables} -w -t nat -A nixos-nat-pre \ 62 -i ${toString cfg.externalInterface} -p ${fwd.proto} \ 63 --dport ${builtins.toString fwd.sourcePort} \ 64 -j DNAT --to-destination ${fwd.destination} 65 66 ${concatMapStrings (loopbackip: 67 let 68 matchIP = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)"; 69 m = builtins.match "${matchIP}:([0-9-]+)" fwd.destination; 70 destinationIP = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0; 71 destinationPorts = if m == null then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1); 72 in '' 73 # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself 74 ${iptables} -w -t nat -A nixos-nat-out \ 75 -d ${loopbackip} -p ${fwd.proto} \ 76 --dport ${builtins.toString fwd.sourcePort} \ 77 -j DNAT --to-destination ${fwd.destination} 78 79 # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT 80 ${iptables} -w -t nat -A nixos-nat-pre \ 81 -d ${loopbackip} -p ${fwd.proto} \ 82 --dport ${builtins.toString fwd.sourcePort} \ 83 -j DNAT --to-destination ${fwd.destination} 84 85 ${iptables} -w -t nat -A nixos-nat-post \ 86 -d ${destinationIP} -p ${fwd.proto} \ 87 --dport ${destinationPorts} \ 88 -j SNAT --to-source ${loopbackip} 89 '') fwd.loopbackIPs} 90 '') forwardPorts} 91 ''; 92 93 setupNat = '' 94 ${helpers} 95 # Create subchains where we store rules 96 ip46tables -w -t nat -N nixos-nat-pre 97 ip46tables -w -t nat -N nixos-nat-post 98 ip46tables -w -t nat -N nixos-nat-out 99 100 ${mkSetupNat { 101 iptables = "iptables"; 102 inherit dest; 103 inherit (cfg) internalIPs; 104 forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts; 105 }} 106 107 ${optionalString cfg.enableIPv6 (mkSetupNat { 108 iptables = "ip6tables"; 109 dest = destIPv6; 110 internalIPs = cfg.internalIPv6s; 111 forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts; 112 })} 113 114 ${optionalString (cfg.dmzHost != null) '' 115 iptables -w -t nat -A nixos-nat-pre \ 116 -i ${toString cfg.externalInterface} -j DNAT \ 117 --to-destination ${cfg.dmzHost} 118 ''} 119 120 ${cfg.extraCommands} 121 122 # Append our chains to the nat tables 123 ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre 124 ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post 125 ip46tables -w -t nat -A OUTPUT -j nixos-nat-out 126 ''; 127 128in 129 130{ 131 132 options = { 133 134 networking.nat.extraCommands = mkOption { 135 type = types.lines; 136 default = ""; 137 example = "iptables -A INPUT -p icmp -j ACCEPT"; 138 description = lib.mdDoc '' 139 Additional shell commands executed as part of the nat 140 initialisation script. 141 142 This option is incompatible with the nftables based nat module. 143 ''; 144 }; 145 146 networking.nat.extraStopCommands = mkOption { 147 type = types.lines; 148 default = ""; 149 example = "iptables -D INPUT -p icmp -j ACCEPT || true"; 150 description = lib.mdDoc '' 151 Additional shell commands executed as part of the nat 152 teardown script. 153 154 This option is incompatible with the nftables based nat module. 155 ''; 156 }; 157 158 }; 159 160 161 config = mkIf (!config.networking.nftables.enable) 162 (mkMerge [ 163 ({ networking.firewall.extraCommands = mkBefore flushNat; }) 164 (mkIf config.networking.nat.enable { 165 166 networking.firewall = mkIf config.networking.firewall.enable { 167 extraCommands = setupNat; 168 extraStopCommands = flushNat; 169 }; 170 171 systemd.services = mkIf (!config.networking.firewall.enable) { 172 nat = { 173 description = "Network Address Translation"; 174 wantedBy = [ "network.target" ]; 175 after = [ "network-pre.target" "systemd-modules-load.service" ]; 176 path = [ config.networking.firewall.package ]; 177 unitConfig.ConditionCapability = "CAP_NET_ADMIN"; 178 179 serviceConfig = { 180 Type = "oneshot"; 181 RemainAfterExit = true; 182 }; 183 184 script = flushNat + setupNat; 185 186 postStop = flushNat; 187 }; 188 }; 189 }) 190 ]); 191}