This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / configs / container-bridge.nix
5.9 kB 181 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 # Use --iptables mode so we can control which packets Jool processes 90 # (--netfilter mode intercepts ALL traffic to pool6, breaking upstream NAT64) 91 ExecStart = "${pkgs.jool-cli}/bin/jool instance add --iptables --pool6 64:ff9b::/96"; 92 ExecStop = "${pkgs.jool-cli}/bin/jool instance remove"; 93 }; 94 }; 95 96 # Update Jool pool4 with current IPv4 address (needed for NAT64 on dynamic IPs) 97 systemd.services.jool-pool4-update = { 98 description = "Update Jool NAT64 pool4 with current IPv4"; 99 after = [ 100 "jool.service" 101 "network-online.target" 102 ]; 103 wants = [ "network-online.target" ]; 104 wantedBy = [ "multi-user.target" ]; 105 path = [ 106 pkgs.iproute2 107 pkgs.gawk 108 pkgs.jool-cli 109 ]; 110 serviceConfig = { 111 Type = "oneshot"; 112 RemainAfterExit = true; 113 }; 114 script = '' 115 # Get default route's source IP 116 IP=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '/src/ {print $7}') 117 if [ -n "$IP" ]; then 118 # Flush old pool and add new 119 jool pool4 flush --tcp 2>/dev/null || true 120 jool pool4 flush --udp 2>/dev/null || true 121 jool pool4 flush --icmp 2>/dev/null || true 122 jool pool4 add "$IP" 1-65535 --tcp 123 jool pool4 add "$IP" 1-65535 --udp 124 jool pool4 add "$IP" 1-65535 --icmp 125 echo "Jool pool4 updated to $IP" 126 else 127 echo "No IPv4 address found, skipping pool4 update" 128 fi 129 ''; 130 }; 131 132 # Firewall rules for container bridge: IPv6 from the containers, and the 133 # IPv4 side of NAT64 (packets Jool translated). 134 networking.firewall.extraForwardRules = '' 135 iifname "ctr0" meta nfproto ipv6 accept 136 meta nfproto ipv4 accept 137 ''; 138 139 # Allow containers to reach host's DNS64 resolver (IPv6 only) 140 networking.firewall.extraInputRules = '' 141 ip6 saddr fd00:c700::/64 meta l4proto { tcp, udp } th dport 53 accept 142 ''; 143 144 # NAT66 - masquerade container IPv6 to reach public IPv6 (when host has IPv6) 145 # Note: No IPv4 MASQUERADE needed - jool does SNAT with pool4 address for NAT64 traffic 146 networking.nftables.tables.container-bridge = { 147 family = "ip6"; 148 content = '' 149 chain postrouting { 150 type nat hook postrouting priority srcnat; policy accept; 151 ip6 saddr fd00:c700::/64 ip6 daddr != fd00:c700::/64 masquerade 152 } 153 ''; 154 }; 155 156 # Send only container traffic to Jool NAT64 (--iptables mode) 157 # Host traffic to 64:ff9b::/96 passes through to upstream NAT64 (e.g., Android hotspot) 158 # JOOL is an xtables target, nftables has no equivalent; ip6tables (the 159 # nft backend) loads it through nft_compat into the ip6 mangle table. 160 systemd.services.jool-iptables = 161 let 162 rule = "PREROUTING -i ctr0 -d 64:ff9b::/96 -j JOOL"; 163 in 164 { 165 description = "Send container traffic to Jool NAT64"; 166 after = [ 167 "jool.service" 168 "nftables.service" 169 ]; 170 requires = [ "jool.service" ]; 171 wantedBy = [ "multi-user.target" ]; 172 path = [ pkgs.iptables ]; 173 environment.XTABLES_LIBDIR = "${pkgs.jool-cli}/lib/xtables"; 174 serviceConfig = { 175 Type = "oneshot"; 176 RemainAfterExit = true; 177 }; 178 script = "ip6tables -t mangle -C ${rule} 2>/dev/null || ip6tables -t mangle -A ${rule}"; 179 preStop = "ip6tables -t mangle -D ${rule} || true"; 180 }; 181}