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