1{ pkgs, runTest }:
2
3let
4 inherit (pkgs) lib;
5
6 ipv6Only = {
7 networking.useDHCP = false;
8 networking.interfaces.eth1.ipv4.addresses = lib.mkVMOverride [ ];
9 };
10
11 ipv4Only = {
12 networking.useDHCP = false;
13 networking.interfaces.eth1.ipv6.addresses = lib.mkVMOverride [ ];
14 };
15
16 webserver = ip: msg: {
17 systemd.services.webserver = {
18 description = "Mock webserver";
19 wants = [ "network-online.target" ];
20 wantedBy = [ "multi-user.target" ];
21 script = ''
22 while true; do
23 {
24 printf 'HTTP/1.0 200 OK\n'
25 printf 'Content-Length: ${toString (1 + builtins.stringLength msg)}\n'
26 printf '\n${msg}\n\n'
27 } | ${pkgs.libressl.nc}/bin/nc -${toString ip}nvl 80
28 done
29 '';
30 };
31 networking.firewall.allowedTCPPorts = [ 80 ];
32 };
33
34in
35
36{
37 siit = runTest {
38 # This test simulates the setup described in [1] with two IPv6 and
39 # IPv4-only devices on different subnets communicating through a border
40 # relay running Jool in SIIT mode.
41 # [1]: https://nicmx.github.io/Jool/en/run-vanilla.html
42 name = "jool-siit";
43 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
44
45 # Border relay
46 nodes.relay = {
47 virtualisation.vlans = [
48 1
49 2
50 ];
51
52 # Enable packet routing
53 boot.kernel.sysctl = {
54 "net.ipv6.conf.all.forwarding" = 1;
55 "net.ipv4.conf.all.forwarding" = 1;
56 };
57
58 networking.useDHCP = false;
59 networking.interfaces = lib.mkVMOverride {
60 eth1.ipv6.addresses = [
61 {
62 address = "fd::198.51.100.1";
63 prefixLength = 120;
64 }
65 ];
66 eth2.ipv4.addresses = [
67 {
68 address = "192.0.2.1";
69 prefixLength = 24;
70 }
71 ];
72 };
73
74 networking.jool.enable = true;
75 networking.jool.siit.default.global.pool6 = "fd::/96";
76 };
77
78 # IPv6 only node
79 nodes.alice = {
80 imports = [
81 ipv6Only
82 (webserver 6 "Hello, Bob!")
83 ];
84
85 virtualisation.vlans = [ 1 ];
86 networking.interfaces.eth1.ipv6 = {
87 addresses = [
88 {
89 address = "fd::198.51.100.8";
90 prefixLength = 120;
91 }
92 ];
93 routes = [
94 {
95 address = "fd::192.0.2.0";
96 prefixLength = 120;
97 via = "fd::198.51.100.1";
98 }
99 ];
100 };
101 };
102
103 # IPv4 only node
104 nodes.bob = {
105 imports = [
106 ipv4Only
107 (webserver 4 "Hello, Alice!")
108 ];
109
110 virtualisation.vlans = [ 2 ];
111 networking.interfaces.eth1.ipv4 = {
112 addresses = [
113 {
114 address = "192.0.2.16";
115 prefixLength = 24;
116 }
117 ];
118 routes = [
119 {
120 address = "198.51.100.0";
121 prefixLength = 24;
122 via = "192.0.2.1";
123 }
124 ];
125 };
126 };
127
128 testScript = ''
129 start_all()
130
131 relay.wait_for_unit("jool-siit-default.service")
132 alice.wait_for_unit("network-addresses-eth1.service")
133 bob.wait_for_unit("network-addresses-eth1.service")
134
135 with subtest("Alice and Bob can't ping each other"):
136 relay.systemctl("stop jool-siit-default.service")
137 alice.fail("ping -c1 fd::192.0.2.16")
138 bob.fail("ping -c1 198.51.100.8")
139
140 with subtest("Alice and Bob can ping using the relay"):
141 relay.systemctl("start jool-siit-default.service")
142 alice.wait_until_succeeds("ping -c1 fd::192.0.2.16")
143 bob.wait_until_succeeds("ping -c1 198.51.100.8")
144
145 with subtest("Alice can connect to Bob's webserver"):
146 bob.wait_for_open_port(80)
147 alice.succeed("curl -vvv http://[fd::192.0.2.16] >&2")
148 alice.succeed("curl --fail -s http://[fd::192.0.2.16] | grep -q Alice")
149
150 with subtest("Bob can connect to Alices's webserver"):
151 alice.wait_for_open_port(80)
152 bob.succeed("curl --fail -s http://198.51.100.8 | grep -q Bob")
153 '';
154 };
155
156 nat64 = runTest {
157 # This test simulates the setup described in [1] with two IPv6-only nodes
158 # (a client and a homeserver) on the LAN subnet and an IPv4 node on the WAN.
159 # The router runs Jool in stateful NAT64 mode, masquarading the LAN and
160 # forwarding ports using static BIB entries.
161 # [1]: https://nicmx.github.io/Jool/en/run-nat64.html
162 name = "jool-nat64";
163 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
164
165 # Router
166 nodes.router = {
167 virtualisation.vlans = [
168 1
169 2
170 ];
171
172 # Enable packet routing
173 boot.kernel.sysctl = {
174 "net.ipv6.conf.all.forwarding" = 1;
175 "net.ipv4.conf.all.forwarding" = 1;
176 };
177
178 networking.useDHCP = false;
179 networking.interfaces = lib.mkVMOverride {
180 eth1.ipv6.addresses = [
181 {
182 address = "2001:db8::1";
183 prefixLength = 96;
184 }
185 ];
186 eth2.ipv4.addresses = [
187 {
188 address = "203.0.113.1";
189 prefixLength = 24;
190 }
191 ];
192 };
193
194 networking.jool.enable = true;
195 networking.jool.nat64.default = {
196 bib = [
197 {
198 # forward HTTP 203.0.113.1 (router) → 2001:db8::9 (homeserver)
199 "protocol" = "TCP";
200 "ipv4 address" = "203.0.113.1#80";
201 "ipv6 address" = "2001:db8::9#80";
202 }
203 ];
204 pool4 = [
205 # Ports for dynamic translation
206 {
207 protocol = "TCP";
208 prefix = "203.0.113.1/32";
209 "port range" = "40001-65535";
210 }
211 {
212 protocol = "UDP";
213 prefix = "203.0.113.1/32";
214 "port range" = "40001-65535";
215 }
216 {
217 protocol = "ICMP";
218 prefix = "203.0.113.1/32";
219 "port range" = "40001-65535";
220 }
221 # Ports for static BIB entries
222 {
223 protocol = "TCP";
224 prefix = "203.0.113.1/32";
225 "port range" = "80";
226 }
227 ];
228 };
229 };
230
231 # LAN client (IPv6 only)
232 nodes.client = {
233 imports = [ ipv6Only ];
234 virtualisation.vlans = [ 1 ];
235
236 networking.interfaces.eth1.ipv6 = {
237 addresses = lib.mkForce [
238 {
239 address = "2001:db8::8";
240 prefixLength = 96;
241 }
242 ];
243 routes = lib.mkForce [
244 {
245 address = "64:ff9b::";
246 prefixLength = 96;
247 via = "2001:db8::1";
248 }
249 ];
250 };
251 };
252
253 # LAN server (IPv6 only)
254 nodes.homeserver = {
255 imports = [
256 ipv6Only
257 (webserver 6 "Hello from IPv6!")
258 ];
259
260 virtualisation.vlans = [ 1 ];
261 networking.interfaces.eth1.ipv6 = {
262 addresses = lib.mkForce [
263 {
264 address = "2001:db8::9";
265 prefixLength = 96;
266 }
267 ];
268 routes = lib.mkForce [
269 {
270 address = "64:ff9b::";
271 prefixLength = 96;
272 via = "2001:db8::1";
273 }
274 ];
275 };
276 };
277
278 # WAN server (IPv4 only)
279 nodes.server = {
280 imports = [
281 ipv4Only
282 (webserver 4 "Hello from IPv4!")
283 ];
284
285 virtualisation.vlans = [ 2 ];
286 networking.interfaces.eth1.ipv4.addresses = [
287 {
288 address = "203.0.113.16";
289 prefixLength = 24;
290 }
291 ];
292 };
293
294 testScript = ''
295 start_all()
296
297 for node in [client, homeserver, server]:
298 node.wait_for_unit("network-addresses-eth1.service")
299
300 with subtest("Client can ping the WAN server"):
301 router.wait_for_unit("jool-nat64-default.service")
302 client.succeed("ping -c1 64:ff9b::203.0.113.16")
303
304 with subtest("Client can connect to the WAN webserver"):
305 server.wait_for_open_port(80)
306 client.succeed("curl --fail -s http://[64:ff9b::203.0.113.16] | grep -q IPv4!")
307
308 with subtest("Router BIB entries are correctly populated"):
309 router.succeed("jool bib display | grep -q 'Dynamic TCP.*2001:db8::8'")
310 router.succeed("jool bib display | grep -q 'Static TCP.*2001:db8::9'")
311
312 with subtest("WAN server can reach the LAN server"):
313 homeserver.wait_for_open_port(80)
314 server.succeed("curl --fail -s http://203.0.113.1 | grep -q IPv6!")
315 '';
316
317 };
318
319}