131 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| # 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}'")
 | |
|     '';
 | |
| })
 | 
