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