diff --git a/modules/module-list.nix b/modules/module-list.nix index 5c1f3deab4ca..453d09653fb3 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -99,6 +99,7 @@ ./services/networking/gw6c.nix ./services/networking/ifplugd.nix ./services/networking/ircd-hybrid.nix + ./services/networking/nat.nix ./services/networking/ntpd.nix ./services/networking/openfire.nix ./services/networking/openvpn.nix diff --git a/modules/services/networking/nat.nix b/modules/services/networking/nat.nix new file mode 100644 index 000000000000..2f9715e9fab9 --- /dev/null +++ b/modules/services/networking/nat.nix @@ -0,0 +1,95 @@ +# This module enables Network Address Translation (NAT). + +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.networking.nat; + +in + +{ + + ###### interface + + options = { + + networking.nat.enable = mkOption { + default = false; + description = + '' + Whether to enable Network Address Translation (NAT). + ''; + }; + + networking.nat.internalIPs = mkOption { + example = "192.168.1.0/24"; + description = + '' + The IP address range for which to perform NAT. Packets + coming from these addresses and destined for the external + interface will be rewritten. + ''; + }; + + networking.nat.externalInterface = mkOption { + example = "eth1"; + description = + '' + The name of the external network interface. + ''; + }; + + networking.nat.externalIP = mkOption { + default = ""; + example = "203.0.113.123"; + description = + '' + The public IP 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. + ''; + }; + + }; + + + ###### implementation + + config = mkIf config.networking.nat.enable { + + environment.systemPackages = [ pkgs.iptables ]; + + jobs.nat = + { description = "Network Address Translation"; + + startOn = "started network-interfaces"; + + path = [ pkgs.iptables ]; + + preStart = + '' + iptables -t nat -F + iptables -t nat -X + + iptables -t nat -A POSTROUTING \ + -s ${cfg.internalIPs} -o ${cfg.externalInterface} \ + ${if cfg.externalIP == "" + then "-j MASQUERADE" + else "-j SNAT --to-source ${cfg.externalIP}"} + + echo 1 > /proc/sys/net/ipv4/ip_forward + ''; + + postStop = + '' + iptables -t nat -F + ''; + }; + + }; + +} diff --git a/tests/nat.nix b/tests/nat.nix index 88aa609774f3..6b0c7306bd9e 100644 --- a/tests/nat.nix +++ b/tests/nat.nix @@ -19,7 +19,9 @@ router = { config, pkgs, ... }: { virtualisation.vlans = [ 2 1 ]; - environment.systemPackages = [ pkgs.iptables ]; + networking.nat.enable = true; + networking.nat.internalIPs = "192.168.1.0/24"; + networking.nat.externalInterface = "eth1"; }; server = @@ -37,22 +39,25 @@ # The router should have access to the server. $server->waitForJob("httpd"); - $router->mustSucceed("curl --fail http://server/ >&2"); + $router->succeed("curl --fail http://server/ >&2"); - # But the client shouldn't be able to reach the server. - $client->mustFail("curl --fail --connect-timeout 5 http://server/ >&2"); + # The client should be also able to connect via the NAT router. + $router->waitForJob("nat"); + $client->succeed("curl --fail http://server/ >&2"); + $client->succeed("ping -c 1 server >&2"); + + # If we turn off NAT, the client shouldn't be able to reach the server. + $router->succeed("stop nat"); + $client->fail("curl --fail --connect-timeout 5 http://server/ >&2"); + $client->fail("ping -c 1 server >&2"); - # Enable NAT on the router. - $router->mustSucceed( - "iptables -t nat -F", - "iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.1.0/24 -j ACCEPT", - "iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j SNAT " - . "--to-source ${nodes.router.config.networking.ifaces.eth1.ipAddress}", - "echo 1 > /proc/sys/net/ipv4/ip_forward" - ); + # And make sure that restarting the NAT job works. + $router->succeed("start nat"); + $client->succeed("curl --fail http://server/ >&2"); + $client->succeed("ping -c 1 server >&2"); - # Now the client should be able to connect. - $client->mustSucceed("curl --fail http://server/ >&2"); + $client->succeed("ping -c 1 router >&2"); + $router->succeed("ping -c 1 client >&2"); ''; }