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