Skip to content

Commit b980713

Browse files
committed
tests/nat64-pref64: init
1 parent 3c71f7b commit b980713

5 files changed

Lines changed: 276 additions & 0 deletions

File tree

tests/nat64-pref64/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# NAT64 with PREF64
2+
3+
This configuration extends the [nat64-dns64](../nat64-dns64/) test by replacing DNS64
4+
functionality with PREF64. Instead of using a DNS64-enabled DNS server, the NAT64 gateway
5+
now uses radvd (Router Advertisement Daemon) to advertise IPv6 prefixes that embed IPv4 addresses,
6+
allowing the client to generate IPv6 addresses for IPv4 destinations automatically using clatd.
7+
8+
```mermaid
9+
flowchart LR
10+
server["**Server**
11+
nginx HTTP server
12+
bind DNS server"]
13+
14+
nat64gw["**NAT64 Gateway**
15+
jool NAT64
16+
dnsmasq DNS server
17+
radvd"]
18+
19+
client["**Client**
20+
clatd"]
21+
22+
server <-- ipv4 only --> nat64gw <-- ipv6 only --> client
23+
```

tests/nat64-pref64/client.nix

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{ lib, pkgs, ... }:
2+
{
3+
virtualisation.interfaces.eth1 = {
4+
vlan = 2;
5+
assignIP = false;
6+
};
7+
8+
networking = {
9+
interfaces.eth1 = { };
10+
nameservers = [ "2001:db8::1" ];
11+
};
12+
13+
services.clatd = {
14+
enable = true;
15+
settings = {
16+
plat-prefix = "64:ff9b::/96";
17+
debug = 1;
18+
};
19+
};
20+
21+
systemd.services.clatd.preStart = let
22+
ip = lib.getExe' pkgs.iproute2 "ip";
23+
jq = lib.getExe pkgs.jq;
24+
in ''
25+
while [ $(${ip} -j -6 address show eth1 | ${jq} '.[] | .addr_info | map(select((.scope == "global") and (.tentative == true))) | length') -ne 0 ]
26+
do
27+
sleep 0.1
28+
done
29+
'';
30+
}

tests/nat64-pref64/default.nix

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
{
2+
lib,
3+
pkgs,
4+
...
5+
}:
6+
{
7+
name = "nat64-pref64";
8+
9+
defaults = {
10+
networking = {
11+
useDHCP = false;
12+
firewall.enable = false;
13+
};
14+
};
15+
16+
nodes = {
17+
server = import ./server.nix { inherit pkgs; };
18+
nat64gw = import ./nat64gw.nix;
19+
client = import ./client.nix { inherit lib pkgs; };
20+
};
21+
22+
interactive.nodes = lib.listToAttrs (
23+
map
24+
(name: {
25+
inherit name;
26+
value.environment.systemPackages = with pkgs; [
27+
curl
28+
dig
29+
tcpdump
30+
pwru
31+
];
32+
})
33+
[
34+
"server"
35+
"client"
36+
"nat64gw"
37+
]
38+
);
39+
40+
testScript =
41+
let
42+
curl = lib.getExe pkgs.curl;
43+
dig = lib.getExe pkgs.dig;
44+
jq = lib.getExe pkgs.jq;
45+
in
46+
''
47+
start_all()
48+
49+
for m in [server, nat64gw, client]:
50+
m.wait_for_unit("network.target")
51+
52+
server.wait_for_unit("nginx.service")
53+
server.wait_for_unit("bind.service")
54+
nat64gw.wait_for_unit("dnsmasq.service")
55+
nat64gw.wait_for_unit("jool-nat64-eth2.service")
56+
nat64gw.wait_for_unit("radvd.service")
57+
58+
with subtest('ensure "public" dns server is working properly'):
59+
assert server.succeed("${dig} +short A example.com").strip() == "192.0.2.2"
60+
assert server.succeed("${dig} +short AAAA example.com").strip() == ""
61+
62+
with subtest('ensure http server is online'):
63+
server.succeed("${curl} -sI http://example.com")
64+
65+
with subtest('ensure ipv4 network between server and gateway is working'):
66+
server.succeed("ping -c 1 192.0.2.1")
67+
nat64gw.succeed("ping -c 1 192.0.2.2")
68+
69+
# first dig fails for whatever reason
70+
nat64gw.succeed("${dig} A example.com @2001:db8::1")
71+
72+
with subtest('ensure dns server on nat64gw works as expected'):
73+
assert nat64gw.succeed("${dig} +short A example.com @2001:db8::1").strip() == "192.0.2.2"
74+
assert nat64gw.succeed("${dig} +short AAAA example.com @2001:db8::1").strip() == ""
75+
76+
with subtest('ensure ipv6 network between gateway and client is working'):
77+
client.wait_until_succeeds("""
78+
ip -j -6 a sh eth1 | \
79+
${jq} -r '.[] | .addr_info | .[] | select((.family == "inet6") and .dynamic == true) | .local' | \
80+
grep 2001:db8
81+
""")
82+
client.succeed("ping -6 -c 1 2001:db8::1")
83+
84+
with subtest('ensure dns works as expected from client, and there is no aaaa record'):
85+
assert client.succeed("${dig} +short A example.com @2001:db8::1").strip() == "192.0.2.2"
86+
assert client.succeed("${dig} +short AAAA example.com @2001:db8::1").strip() == ""
87+
88+
with subtest('ensure nat64 works as expected'):
89+
client.wait_until_succeeds("ping -6 -c 1 64:ff9b::192.0.2.2")
90+
client.wait_until_succeeds("${curl} -sI http://[64:ff9b::192.0.2.2] | grep 'HTTP/1.1 200 OK'")
91+
92+
# clat needs to be started after slaac, restarting it now should be sufficient
93+
client.succeed("systemctl restart clatd")
94+
client.wait_for_unit("clatd.service")
95+
client.wait_until_succeeds("ip link show clat")
96+
97+
with subtest('ensure clat uses correct address from ipv4 service continuity prefix'):
98+
client.wait_until_succeeds("""
99+
ip -j -4 address sh clat | ${jq} -e -r '.[].addr_info.[].local == "192.0.0.1"'
100+
""")
101+
102+
with subtest('ensure clat created a ipv4 default route to attract ipv4 traffic'):
103+
client.wait_until_succeeds("""
104+
ip -4 -j route show default | ${jq} -e -r '.[].dev == "clat"'
105+
""")
106+
107+
# for debugging purpose:
108+
print(client.succeed("${curl} -v http://example.com"))
109+
# connect to 192.0.2.2 port 80 from 192.0.0.1 port 37636 failed: No route to host
110+
111+
with subtest('ensure clat works as expected'):
112+
client.succeed("${curl} -sI http://example.com | grep 'HTTP/1.1 200 OK'")
113+
'';
114+
}

tests/nat64-pref64/nat64gw.nix

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
virtualisation.interfaces.eth1 = {
3+
vlan = 1;
4+
assignIP = false;
5+
};
6+
virtualisation.interfaces.eth2 = {
7+
vlan = 2;
8+
assignIP = false;
9+
};
10+
11+
networking.interfaces.eth1 = {
12+
ipv4.addresses = [
13+
{
14+
address = "192.0.2.1";
15+
prefixLength = 24;
16+
}
17+
];
18+
};
19+
20+
networking.interfaces.eth2 = {
21+
ipv6.addresses = [
22+
{
23+
address = "2001:db8::1";
24+
prefixLength = 64;
25+
}
26+
];
27+
};
28+
29+
# nat64
30+
boot.kernelModules = [ "jool" ];
31+
networking.jool = {
32+
enable = true;
33+
nat64.eth2.framework = "netfilter";
34+
};
35+
36+
services.dnsmasq = {
37+
enable = true;
38+
alwaysKeepRunning = true;
39+
settings = {
40+
listen-address = "2001:db8::1";
41+
no-hosts = true;
42+
no-resolv = true;
43+
server = [
44+
"192.0.2.2"
45+
];
46+
};
47+
};
48+
49+
services.radvd = {
50+
enable = true;
51+
config = ''
52+
interface eth2 {
53+
AdvSendAdvert on;
54+
prefix 2001:db8::/64 { };
55+
nat64prefix 64:ff9b::/96 { };
56+
};
57+
'';
58+
};
59+
}

tests/nat64-pref64/server.nix

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{ pkgs, ... }:
2+
{
3+
virtualisation.interfaces.eth1 = {
4+
vlan = 1;
5+
assignIP = false;
6+
};
7+
networking.interfaces.eth1 = {
8+
ipv4.addresses = [
9+
{
10+
address = "192.0.2.2";
11+
prefixLength = 24;
12+
}
13+
];
14+
};
15+
services.nginx = {
16+
enable = true;
17+
virtualHosts."example.com".locations."/".return = "200";
18+
};
19+
20+
# public dns server
21+
services.resolved.enable = false;
22+
services.bind = {
23+
enable = true;
24+
cacheNetworks = [ "0.0.0.0/0" ];
25+
# first line (comment) is to align config
26+
extraOptions = ''
27+
#
28+
recursion yes;
29+
auth-nxdomain no;
30+
dnssec-validation no;
31+
'';
32+
zones."example.com" = {
33+
master = true;
34+
file = pkgs.writeText "zone-example.com.conf" ''
35+
$TTL 1
36+
@ IN SOA example.com. zonemaster.example.com. (
37+
2023013100 ; serial number
38+
86400 ; refresh: 1d
39+
900 ; update retry: 15m
40+
604800 ; expiry: 1w
41+
3600 ) ; negative caching 1h
42+
43+
@ IN NS example.com.
44+
@ IN A 192.0.2.2
45+
46+
'';
47+
};
48+
};
49+
networking.nameservers = [ "127.0.0.1" ];
50+
}

0 commit comments

Comments
 (0)