This repository has no description
5.5 kB
192 lines
1{ config, pkgs, ... }:
2{
3 # Local DNS64 resolver for containers
4 # Synthesizes AAAA records with 64:ff9b::/96 prefix for IPv4-only hosts
5 services.unbound = {
6 enable = true;
7 settings = {
8 server = {
9 interface = [
10 "fd00:c700::1"
11 "127.0.0.1"
12 ];
13 access-control = [
14 "fd00:c700::/64 allow"
15 "127.0.0.0/8 allow"
16 ];
17 # DNS64: synthesize AAAA with NAT64 prefix
18 module-config = ''"dns64 validator iterator"'';
19 dns64-prefix = "64:ff9b::/96";
20 # Always synthesize, even if real AAAA exists (needed when host has no IPv6)
21 dns64-synthall = "yes";
22 # Handle reverse DNS for NAT64 prefix locally (return NXDOMAIN immediately)
23 # Without this, PTR lookups for 64:ff9b::* timeout waiting for upstream
24 local-zone = ''"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa." static'';
25 };
26 forward-zone = [
27 {
28 name = ".";
29 forward-addr = [
30 "1.1.1.1" # Cloudflare (IPv4 - always works)
31 "8.8.8.8" # Google (IPv4)
32 "2606:4700:4700::1111" # Cloudflare (IPv6 - when available)
33 ];
34 }
35 ];
36 };
37 };
38
39 # Bridge interface for containers
40 systemd.network.netdevs."10-ctr0" = {
41 netdevConfig = {
42 Kind = "bridge";
43 Name = "ctr0";
44 };
45 };
46
47 systemd.network.networks."10-ctr0" = {
48 matchConfig.Name = "ctr0";
49 address = [ "fd00:c700::1/64" ];
50 networkConfig = {
51 ConfigureWithoutCarrier = true;
52 IPv6SendRA = true; # Enable Router Advertisements
53 };
54
55 # Router Advertisement configuration
56 ipv6SendRAConfig = {
57 Managed = false; # No DHCPv6 for addresses (use SLAAC)
58 OtherInformation = false; # No DHCPv6 for DNS (use RDNSS)
59 RouterLifetimeSec = 1800;
60 DNS = [ "fd00:c700::1" ]; # RDNSS: advertise DNS64 resolver
61 };
62 ipv6Prefixes = [
63 { Prefix = "fd00:c700::/64"; }
64 ];
65 # Advertise NAT64 prefix via PREF64 option (RFC 8781)
66 ipv6PREF64Prefixes = [
67 { Prefix = "64:ff9b::/96"; }
68 ];
69 };
70
71 # Exclude from NetworkManager
72 networking.networkmanager.unmanaged = [ "ctr0" ];
73
74 # IP forwarding
75 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
76 boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
77
78 # Jool NAT64 kernel module
79 boot.extraModulePackages = [ config.boot.kernelPackages.jool ];
80 boot.kernelModules = [ "jool" ];
81
82 systemd.services.jool = {
83 description = "Jool NAT64";
84 after = [ "network.target" ];
85 wantedBy = [ "multi-user.target" ];
86 serviceConfig = {
87 Type = "oneshot";
88 RemainAfterExit = true;
89 ExecStart = "${pkgs.jool-cli}/bin/jool instance add --netfilter --pool6 64:ff9b::/96";
90 ExecStop = "${pkgs.jool-cli}/bin/jool instance remove";
91 };
92 };
93
94 # Update Jool pool4 with current IPv4 address (needed for NAT64 on dynamic IPs)
95 systemd.services.jool-pool4-update = {
96 description = "Update Jool NAT64 pool4 with current IPv4";
97 after = [
98 "jool.service"
99 "network-online.target"
100 ];
101 wants = [ "network-online.target" ];
102 wantedBy = [ "multi-user.target" ];
103 path = [
104 pkgs.iproute2
105 pkgs.gawk
106 pkgs.jool-cli
107 ];
108 serviceConfig = {
109 Type = "oneshot";
110 RemainAfterExit = true;
111 };
112 script = ''
113 # Get default route's source IP
114 IP=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '/src/ {print $7}')
115 if [ -n "$IP" ]; then
116 # Flush old pool and add new
117 jool pool4 flush --tcp 2>/dev/null || true
118 jool pool4 flush --udp 2>/dev/null || true
119 jool pool4 flush --icmp 2>/dev/null || true
120 jool pool4 add "$IP" 1-65535 --tcp
121 jool pool4 add "$IP" 1-65535 --udp
122 jool pool4 add "$IP" 1-65535 --icmp
123 echo "Jool pool4 updated to $IP"
124 else
125 echo "No IPv4 address found, skipping pool4 update"
126 fi
127 '';
128 };
129
130 # Firewall rules for container bridge
131 krebs.iptables.tables.filter.FORWARD.rules = [
132 # IPv6 forwarding for containers
133 {
134 v4 = false;
135 v6 = true;
136 predicate = "-i ctr0";
137 target = "ACCEPT";
138 }
139 {
140 v4 = false;
141 v6 = true;
142 predicate = "-o ctr0 -m conntrack --ctstate RELATED,ESTABLISHED";
143 target = "ACCEPT";
144 }
145 # IPv4 forwarding for NAT64 translated packets
146 {
147 v4 = true;
148 v6 = false;
149 predicate = "-m conntrack --ctstate RELATED,ESTABLISHED";
150 target = "ACCEPT";
151 }
152 {
153 v4 = true;
154 v6 = false;
155 predicate = "";
156 target = "ACCEPT";
157 }
158 ];
159
160 # NAT66 - masquerade container IPv6 to reach public IPv6 (when host has IPv6)
161 # NAT44 - masquerade NAT64 translated IPv4 packets (exclude localhost!)
162 krebs.iptables.tables.nat.POSTROUTING.rules = [
163 {
164 v4 = false;
165 v6 = true;
166 predicate = "-s fd00:c700::/64 ! -d fd00:c700::/64";
167 target = "MASQUERADE";
168 }
169 {
170 v4 = true;
171 v6 = false;
172 predicate = "! -d 127.0.0.0/8 ! -o lo";
173 target = "MASQUERADE";
174 } # Don't masquerade localhost
175 ];
176
177 # Allow containers to reach host's DNS64 resolver (IPv6 only)
178 krebs.iptables.tables.filter.INPUT.rules = [
179 {
180 v4 = false;
181 v6 = true;
182 predicate = "-s fd00:c700::/64 -p udp --dport 53";
183 target = "ACCEPT";
184 }
185 {
186 v4 = false;
187 v6 = true;
188 predicate = "-s fd00:c700::/64 -p tcp --dport 53";
189 target = "ACCEPT";
190 }
191 ];
192}