summary refs log tree commit diff
path: root/nixos/tests/ipv6.nix
blob: 75faa6f602010b4f1f81c34d2237ec445f4daba5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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}'")
    '';
})