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