1let
2 aliceIp6 = "202:b70:9b0b:cf34:f93c:8f18:bbfd:7034";
3 aliceKeys = {
4 PublicKey = "3e91ec9e861960d86e1ce88051f97c435bdf2859640ab681dfa906eb45ad5182";
5 PrivateKey = "a867f9e078e4ce58d310cf5acd4622d759e2a21df07e1d6fc380a2a26489480d3e91ec9e861960d86e1ce88051f97c435bdf2859640ab681dfa906eb45ad5182";
6 };
7 bobIp6 = "202:a483:73a4:9f2d:a559:4a19:bc9:8458";
8 bobPrefix = "302:a483:73a4:9f2d";
9 bobConfig = {
10 InterfacePeers = {
11 eth1 = [ "tcp://192.168.1.200:12345" ];
12 };
13 MulticastInterfaces = [ "eth1" ];
14 LinkLocalTCPPort = 54321;
15 PublicKey = "2b6f918b6c1a4b54d6bcde86cf74e074fb32ead4ee439b7930df2aa60c825186";
16 PrivateKey = "0c4a24acd3402722ce9277ed179f4a04b895b49586493c25fbaed60653d857d62b6f918b6c1a4b54d6bcde86cf74e074fb32ead4ee439b7930df2aa60c825186";
17 };
18 danIp6 = bobPrefix + "::2";
19
20in import ./make-test-python.nix ({ pkgs, ...} : {
21 name = "yggdrasil";
22 meta = with pkgs.lib.maintainers; {
23 maintainers = [ gazally ];
24 };
25
26 nodes = rec {
27 # Alice is listening for peerings on a specified port,
28 # but has multicast peering disabled. Alice has part of her
29 # yggdrasil config in Nix and part of it in a file.
30 alice =
31 { ... }:
32 {
33 networking = {
34 interfaces.eth1.ipv4.addresses = [{
35 address = "192.168.1.200";
36 prefixLength = 24;
37 }];
38 firewall.allowedTCPPorts = [ 80 12345 ];
39 };
40 services.httpd.enable = true;
41 services.httpd.adminAddr = "foo@example.org";
42
43 services.yggdrasil = {
44 enable = true;
45 settings = {
46 Listen = ["tcp://0.0.0.0:12345"];
47 MulticastInterfaces = [ ];
48 };
49 configFile = toString (pkgs.writeTextFile {
50 name = "yggdrasil-alice-conf";
51 text = builtins.toJSON aliceKeys;
52 });
53 };
54 };
55
56 # Bob is set up to peer with Alice, and also to do local multicast
57 # peering. Bob's yggdrasil config is in a file.
58 bob =
59 { ... }:
60 {
61 networking.firewall.allowedTCPPorts = [ 54321 ];
62 services.yggdrasil = {
63 enable = true;
64 openMulticastPort = true;
65 configFile = toString (pkgs.writeTextFile {
66 name = "yggdrasil-bob-conf";
67 text = builtins.toJSON bobConfig;
68 });
69 };
70
71 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
72
73 networking = {
74 bridges.br0.interfaces = [ ];
75 interfaces.br0 = {
76 ipv6.addresses = [{
77 address = bobPrefix + "::1";
78 prefixLength = 64;
79 }];
80 };
81 };
82
83 # dan is a node inside a container running on bob's host.
84 containers.dan = {
85 autoStart = true;
86 privateNetwork = true;
87 hostBridge = "br0";
88 config = { config, pkgs, ... }: {
89 networking.interfaces.eth0.ipv6 = {
90 addresses = [{
91 address = bobPrefix + "::2";
92 prefixLength = 64;
93 }];
94 routes = [{
95 address = "200::";
96 prefixLength = 7;
97 via = bobPrefix + "::1";
98 }];
99 };
100 services.httpd.enable = true;
101 services.httpd.adminAddr = "foo@example.org";
102 networking.firewall.allowedTCPPorts = [ 80 ];
103 };
104 };
105 };
106
107 # Carol only does local peering. Carol's yggdrasil config is all Nix.
108 carol =
109 { ... }:
110 {
111 networking.firewall.allowedTCPPorts = [ 43210 ];
112 services.yggdrasil = {
113 enable = true;
114 denyDhcpcdInterfaces = [ "ygg0" ];
115 settings = {
116 IfTAPMode = true;
117 IfName = "ygg0";
118 MulticastInterfaces = [ "eth1" ];
119 LinkLocalTCPPort = 43210;
120 };
121 persistentKeys = true;
122 };
123 };
124 };
125
126 testScript =
127 ''
128 import re
129
130 # Give Alice a head start so she is ready when Bob calls.
131 alice.start()
132 alice.wait_for_unit("yggdrasil.service")
133
134 bob.start()
135 carol.start()
136 bob.wait_for_unit("default.target")
137 carol.wait_for_unit("yggdrasil.service")
138
139 ip_addr_show = "ip -o -6 addr show dev ygg0 scope global"
140 carol.wait_until_succeeds(f"[ `{ip_addr_show} | grep -v tentative | wc -l` -ge 1 ]")
141 carol_ip6 = re.split(" +|/", carol.succeed(ip_addr_show))[3]
142
143 # If Alice can talk to Carol, then Bob's outbound peering and Carol's
144 # local peering have succeeded and everybody is connected.
145 alice.wait_until_succeeds(f"ping -c 1 {carol_ip6}")
146 alice.succeed("ping -c 1 ${bobIp6}")
147
148 bob.succeed("ping -c 1 ${aliceIp6}")
149 bob.succeed(f"ping -c 1 {carol_ip6}")
150
151 carol.succeed("ping -c 1 ${aliceIp6}")
152 carol.succeed("ping -c 1 ${bobIp6}")
153 carol.succeed("ping -c 1 ${bobPrefix}::1")
154 carol.succeed("ping -c 8 ${danIp6}")
155
156 carol.fail("journalctl -u dhcpcd | grep ygg0")
157
158 alice.wait_for_unit("httpd.service")
159 carol.succeed("curl --fail -g http://[${aliceIp6}]")
160 carol.succeed("curl --fail -g http://[${danIp6}]")
161 '';
162})