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