From ae195727b77c94b44b5efbd1f2d4ae1bfefc9366 Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Tue, 16 Sep 2014 16:56:08 -0700 Subject: [PATCH 1/3] nixos/nat: Don't flush tables, create subchains for autogenerated rules --- nixos/modules/services/networking/nat.nix | 27 ++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix index 35e376e7b3a..bdb79ce2a90 100644 --- a/nixos/modules/services/networking/nat.nix +++ b/nixos/modules/services/networking/nat.nix @@ -13,38 +13,49 @@ let dest = if cfg.externalIP == null then "-j MASQUERADE" else "-j SNAT --to-source ${cfg.externalIP}"; flushNat = '' - iptables -w -t nat -F PREROUTING - iptables -w -t nat -F POSTROUTING - iptables -w -t nat -X + iptables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true + iptables -w -t nat -F nixos-nat-pre 2>/dev/null || true + iptables -w -t nat -X nixos-nat-pre 2>/dev/null || true + iptables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true + iptables -w -t nat -F nixos-nat-post 2>/dev/null || true + iptables -w -t nat -X nixos-nat-post 2>/dev/null || true ''; setupNat = '' + # Create subchain where we store rules + iptables -w -t nat -N nixos-nat-pre + iptables -w -t nat -N nixos-nat-post + # We can't match on incoming interface in POSTROUTING, so # mark packets coming from the external interfaces. ${concatMapStrings (iface: '' - iptables -w -t nat -A PREROUTING \ + 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 POSTROUTING -m mark --mark 1 \ + iptables -w -t nat -A nixos-nat-post -m mark --mark 1 \ -o ${cfg.externalInterface} ${dest} ''} # NAT packets coming from the internal IPs. ${concatMapStrings (range: '' - iptables -w -t nat -A POSTROUTING \ + iptables -w -t nat -A nixos-nat-post \ -s '${range}' -o ${cfg.externalInterface} ${dest} '') cfg.internalIPs} # NAT from external ports to internal ports. ${concatMapStrings (fwd: '' - iptables -w -t nat -A PREROUTING \ + iptables -w -t nat -A nixos-nat-pre \ -i ${cfg.externalInterface} -p tcp \ --dport ${builtins.toString fwd.sourcePort} \ -j DNAT --to-destination ${fwd.destination} '') cfg.forwardPorts} + + # Append our chains to the nat tables + iptables -w -t nat -A PREROUTING -j nixos-nat-pre + iptables -w -t nat -A POSTROUTING -j nixos-nat-post ''; in @@ -157,7 +168,7 @@ in extraStopCommands = flushNat; }; - systemd.services = mkIf (!config.networking.firewall.enable) { nat = { + systemd.services = mkIf (!config.networking.firewall.enable) { nat = { description = "Network Address Translation"; wantedBy = [ "network.target" ]; after = [ "network-interfaces.target" "systemd-modules-load.service" ]; From b047f2ddec9bb3c0bcbbd2b3e325c729595b3887 Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Thu, 18 Sep 2014 11:21:35 -0700 Subject: [PATCH 2/3] nixos/tests/nat: Modify test to accomodate for firewall consolidation --- nixos/tests/nat.nix | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix index 87ed974edad..36d34c01377 100644 --- a/nixos/tests/nat.nix +++ b/nixos/tests/nat.nix @@ -48,7 +48,7 @@ import ./make-test.nix { $router->succeed("curl --fail http://server/ >&2"); # The client should be also able to connect via the NAT router. - $router->waitForUnit("nat"); + $router->waitForUnit("firewall"); # Nat leverages the firewall service $client->waitForUnit("network.target"); $client->succeed("curl --fail http://server/ >&2"); $client->succeed("ping -c 1 server >&2"); @@ -66,12 +66,13 @@ import ./make-test.nix { $router->succeed("ping -c 1 client >&2"); # If we turn off NAT, the client shouldn't be able to reach the server. - $router->stopJob("nat"); + $router->succeed("iptables -t nat -D PREROUTING -j nixos-nat-pre"); + $router->succeed("iptables -t nat -D POSTROUTING -j nixos-nat-post"); $client->fail("curl --fail --connect-timeout 5 http://server/ >&2"); $client->fail("ping -c 1 server >&2"); # And make sure that restarting the NAT job works. - $router->succeed("systemctl start nat"); + $router->succeed("systemctl reload firewall"); # Nat leverages the firewall service $client->succeed("curl --fail http://server/ >&2"); $client->succeed("ping -c 1 server >&2"); ''; From 8250059a9f332e363197405e53551f558e838db9 Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Thu, 18 Sep 2014 13:34:29 -0700 Subject: [PATCH 3/3] nixos/tests/nat: Add tests for standalone and firewall based nat --- nixos/release-combined.nix | 3 +- nixos/release.nix | 3 +- nixos/tests/nat.nix | 131 +++++++++++++++++++------------------ 3 files changed, 71 insertions(+), 66 deletions(-) diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix index 3a458f5e860..debdccf7e54 100644 --- a/nixos/release-combined.nix +++ b/nixos/release-combined.nix @@ -61,7 +61,8 @@ in rec { (all nixos.tests.kde4) (all nixos.tests.login) (all nixos.tests.misc) - (all nixos.tests.nat) + (all nixos.tests.nat.firewall) + (all nixos.tests.nat.standalone) (all nixos.tests.nfs3) (all nixos.tests.openssh) (all nixos.tests.printing) diff --git a/nixos/release.nix b/nixos/release.nix index b3039afb18c..14e8549de5e 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -244,7 +244,8 @@ in rec { tests.munin = callTest tests/munin.nix {}; tests.mysql = callTest tests/mysql.nix {}; tests.mysqlReplication = callTest tests/mysql-replication.nix {}; - tests.nat = callTest tests/nat.nix {}; + tests.nat.firewall = callTest tests/nat.nix { withFirewall = true; }; + tests.nat.standalone = callTest tests/nat.nix { withFirewall = false; }; tests.nfs3 = callTest tests/nfs.nix { version = 3; }; tests.nsd = callTest tests/nsd.nix {}; tests.openssh = callTest tests/openssh.nix {}; diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix index 36d34c01377..c4d2614f785 100644 --- a/nixos/tests/nat.nix +++ b/nixos/tests/nat.nix @@ -3,78 +3,81 @@ # client on the inside network, a server on the outside network, and a # router connected to both that performs Network Address Translation # for the client. +import ./make-test.nix ({ withFirewall, ... }: + let + unit = if withFirewall then "firewall" else "nat"; + in + { + name = "nat${if withFirewall then "WithFirewall" else "Standalone"}"; -import ./make-test.nix { - name = "nat"; + nodes = + { client = + { config, pkgs, nodes, ... }: + { virtualisation.vlans = [ 1 ]; + networking.firewall.allowPing = true; + networking.defaultGateway = + (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ip4).address; + }; - nodes = - { client = - { config, pkgs, nodes, ... }: - { virtualisation.vlans = [ 1 ]; - networking.firewall.allowPing = true; - networking.defaultGateway = - (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ip4).address; - }; + router = + { config, pkgs, ... }: + { virtualisation.vlans = [ 2 1 ]; + networking.firewall.enable = withFirewall; + networking.firewall.allowPing = true; + networking.nat.enable = true; + networking.nat.internalIPs = [ "192.168.1.0/24" ]; + networking.nat.externalInterface = "eth1"; + }; - router = - { config, pkgs, ... }: - { virtualisation.vlans = [ 2 1 ]; - networking.firewall.allowPing = true; - networking.nat.enable = true; - networking.nat.internalIPs = [ "192.168.1.0/24" ]; - networking.nat.externalInterface = "eth1"; - }; + server = + { config, pkgs, ... }: + { virtualisation.vlans = [ 2 ]; + networking.firewall.enable = false; + services.httpd.enable = true; + services.httpd.adminAddr = "foo@example.org"; + services.vsftpd.enable = true; + services.vsftpd.anonymousUser = true; + }; + }; - server = - { config, pkgs, ... }: - { virtualisation.vlans = [ 2 ]; - networking.firewall.enable = false; - services.httpd.enable = true; - services.httpd.adminAddr = "foo@example.org"; - services.vsftpd.enable = true; - services.vsftpd.anonymousUser = true; - }; - }; + testScript = + { nodes, ... }: + '' + startAll; - testScript = - { nodes, ... }: - '' - startAll; + # The router should have access to the server. + $server->waitForUnit("network.target"); + $server->waitForUnit("httpd"); + $router->waitForUnit("network.target"); + $router->succeed("curl --fail http://server/ >&2"); - # The router should have access to the server. - $server->waitForUnit("network.target"); - $server->waitForUnit("httpd"); - $router->waitForUnit("network.target"); - $router->succeed("curl --fail http://server/ >&2"); + # The client should be also able to connect via the NAT router. + $router->waitForUnit("${unit}"); + $client->waitForUnit("network.target"); + $client->succeed("curl --fail http://server/ >&2"); + $client->succeed("ping -c 1 server >&2"); - # The client should be also able to connect via the NAT router. - $router->waitForUnit("firewall"); # Nat leverages the firewall service - $client->waitForUnit("network.target"); - $client->succeed("curl --fail http://server/ >&2"); - $client->succeed("ping -c 1 server >&2"); + # Test whether passive FTP works. + $server->waitForUnit("vsftpd"); + $server->succeed("echo Hello World > /home/ftp/foo.txt"); + $client->succeed("curl -v ftp://server/foo.txt >&2"); - # Test whether passive FTP works. - $server->waitForUnit("vsftpd"); - $server->succeed("echo Hello World > /home/ftp/foo.txt"); - $client->succeed("curl -v ftp://server/foo.txt >&2"); + # Test whether active FTP works. + $client->succeed("curl -v -P - ftp://server/foo.txt >&2"); - # Test whether active FTP works. - $client->succeed("curl -v -P - ftp://server/foo.txt >&2"); + # Test ICMP. + $client->succeed("ping -c 1 router >&2"); + $router->succeed("ping -c 1 client >&2"); - # Test ICMP. - $client->succeed("ping -c 1 router >&2"); - $router->succeed("ping -c 1 client >&2"); + # If we turn off NAT, the client shouldn't be able to reach the server. + $router->succeed("iptables -t nat -D PREROUTING -j nixos-nat-pre"); + $router->succeed("iptables -t nat -D POSTROUTING -j nixos-nat-post"); + $client->fail("curl --fail --connect-timeout 5 http://server/ >&2"); + $client->fail("ping -c 1 server >&2"); - # If we turn off NAT, the client shouldn't be able to reach the server. - $router->succeed("iptables -t nat -D PREROUTING -j nixos-nat-pre"); - $router->succeed("iptables -t nat -D POSTROUTING -j nixos-nat-post"); - $client->fail("curl --fail --connect-timeout 5 http://server/ >&2"); - $client->fail("ping -c 1 server >&2"); - - # And make sure that restarting the NAT job works. - $router->succeed("systemctl reload firewall"); # Nat leverages the firewall service - $client->succeed("curl --fail http://server/ >&2"); - $client->succeed("ping -c 1 server >&2"); - ''; - -} + # And make sure that reloading the NAT job works. + $router->succeed("systemctl restart ${unit}"); + $client->succeed("curl --fail http://server/ >&2"); + $client->succeed("ping -c 1 server >&2"); + ''; + })