about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests/ipv6.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/tests/ipv6.nix')
-rw-r--r--nixpkgs/nixos/tests/ipv6.nix130
1 files changed, 130 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/ipv6.nix b/nixpkgs/nixos/tests/ipv6.nix
new file mode 100644
index 000000000000..75faa6f60201
--- /dev/null
+++ b/nixpkgs/nixos/tests/ipv6.nix
@@ -0,0 +1,130 @@
+# Test of IPv6 functionality in NixOS, including whether router
+# solicication/advertisement using radvd works.
+
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "ipv6";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes =
+    {
+      # We use lib.mkForce here to remove the interface configuration
+      # provided by makeTest, so that the interfaces are all configured
+      # implicitly.
+
+      # This client should use privacy extensions fully, having a
+      # completely-default network configuration.
+      client_defaults.networking.interfaces = lib.mkForce {};
+
+      # Both of these clients should obtain temporary addresses, but
+      # not use them as the default source IP. We thus run the same
+      # checks against them — but the configuration resulting in this
+      # behaviour is different.
+
+      # Here, by using an altered default value for the global setting...
+      client_global_setting = {
+        networking.interfaces = lib.mkForce {};
+        networking.tempAddresses = "enabled";
+      };
+      # and here, by setting this on the interface explicitly.
+      client_interface_setting = {
+        networking.tempAddresses = "disabled";
+        networking.interfaces = lib.mkForce {
+          eth1.tempAddress = "enabled";
+        };
+      };
+
+      server =
+        { services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          networking.firewall.allowedTCPPorts = [ 80 ];
+        };
+
+      router =
+        { ... }:
+        { services.radvd.enable = true;
+          services.radvd.config =
+            ''
+              interface eth1 {
+                AdvSendAdvert on;
+                # ULA prefix (RFC 4193).
+                prefix fd60:cc69:b537:1::/64 { };
+              };
+            '';
+        };
+    };
+
+  testScript =
+    ''
+      import re
+
+      # Start the router first so that it respond to router solicitations.
+      router.wait_for_unit("radvd")
+
+      clients = [client_defaults, client_global_setting, client_interface_setting]
+
+      start_all()
+
+      for client in clients:
+          client.wait_for_unit("network.target")
+      server.wait_for_unit("network.target")
+      server.wait_for_unit("httpd.service")
+
+      # Wait until the given interface has a non-tentative address of
+      # the desired scope (i.e. has completed Duplicate Address
+      # Detection).
+      def wait_for_address(machine, iface, scope, temporary=False):
+          temporary_flag = "temporary" if temporary else "-temporary"
+          cmd = f"ip -o -6 addr show dev {iface} scope {scope} -tentative {temporary_flag}"
+
+          machine.wait_until_succeeds(f"[ `{cmd} | wc -l` -eq 1 ]")
+          output = machine.succeed(cmd)
+          ip = re.search(r"inet6 ([0-9a-f:]{2,})/", output).group(1)
+
+          if temporary:
+              scope = scope + " temporary"
+          machine.log(f"{scope} address on {iface} is {ip}")
+          return ip
+
+
+      with subtest("Loopback address can be pinged"):
+          client_defaults.succeed("ping -c 1 ::1 >&2")
+          client_defaults.fail("ping -c 1 2001:db8:: >&2")
+
+      with subtest("Local link addresses can be obtained and pinged"):
+          for client in clients:
+              client_ip = wait_for_address(client, "eth1", "link")
+              server_ip = wait_for_address(server, "eth1", "link")
+              client.succeed(f"ping -c 1 {client_ip}%eth1 >&2")
+              client.succeed(f"ping -c 1 {server_ip}%eth1 >&2")
+
+      with subtest("Global addresses can be obtained, pinged, and reached via http"):
+          for client in clients:
+              client_ip = wait_for_address(client, "eth1", "global")
+              server_ip = wait_for_address(server, "eth1", "global")
+              client.succeed(f"ping -c 1 {client_ip} >&2")
+              client.succeed(f"ping -c 1 {server_ip} >&2")
+              client.succeed(f"curl --fail -g http://[{server_ip}]")
+              client.fail(f"curl --fail -g http://[{client_ip}]")
+
+      with subtest(
+          "Privacy extensions: Global temporary address is used as default source address"
+      ):
+          ip = wait_for_address(client_defaults, "eth1", "global", temporary=True)
+          # Default route should have "src <temporary address>" in it
+          client_defaults.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
+
+      for client, setting_desc in (
+          (client_global_setting, "global"),
+          (client_interface_setting, "interface"),
+      ):
+          with subtest(f'Privacy extensions: "enabled" through {setting_desc} setting)'):
+              # We should be obtaining both a temporary address and an EUI-64 address...
+              ip = wait_for_address(client, "eth1", "global")
+              assert "ff:fe" in ip
+              ip_temp = wait_for_address(client, "eth1", "global", temporary=True)
+              # But using the EUI-64 one.
+              client.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
+    '';
+})