/* VPN Confinement Integration Test This test validates that Nixarr services are properly confined to a VPN namespace and cannot leak traffic when the VPN connection fails. It uses a 3-VM topology to simulate real-world network conditions. Network Topology: ┌──────────────┐ VLAN 2 ┌─────────────┐ VLAN 1 ┌─────────────┐ │internetClient│ ◄──────────── │ gateway │ ◄──────────── │ nixarrHost │ │ 10.0.1.2 │ │ 10.0.1.1 │ │192.168.1.2 │ │ fd00:2::2 │ │192.168.1.1 │ │ fd00:1::2 │ └──────────────┘ │ fd00:2::1 │ └─────────────┘ │ fd00:1::1 │ │ └─────────────┘ │ │ │ WireGuard tunnel │ 10.100.0.1 ◄────────────────────────┘ fd00:100::1 VPN namespace (10.100.0.2, fd00:100::2) Test Coverage: - VPN namespace isolation (transmission confined to wg namespace) - IPv4 and IPv6 traffic routing through VPN tunnel - Traffic leak prevention when VPN is down - Port forwarding from external clients through gateway to VPN services - DNS configuration in VPN namespace - Service recovery after VPN reconnection The test ensures that: 1. All transmission traffic goes through the VPN tunnel 2. Source IP is preserved (shows VPN client IP: 10.100.0.2/fd00:100::2) 3. No traffic leaks to host network when VPN fails 4. External port forwarding works correctly 5. Both IPv4 and IPv6 work identically through the tunnel */ { pkgs, nixosModules, lib ? pkgs.lib, }: let # WireGuard configuration for the VPN gateway wgGatewayPort = 51820; # Generate real WireGuard keys wgGatewayPrivateKey = pkgs.runCommand "wg-gateway-private" {buildInputs = [pkgs.wireguard-tools];} '' wg genkey > $out ''; wgGatewayPublicKey = pkgs.runCommand "wg-gateway-public" {buildInputs = [pkgs.wireguard-tools];} '' cat ${wgGatewayPrivateKey} | wg pubkey > $out ''; wgClientPrivateKey = pkgs.runCommand "wg-client-private" {buildInputs = [pkgs.wireguard-tools];} '' wg genkey > $out ''; wgClientPublicKey = pkgs.runCommand "wg-client-public" {buildInputs = [pkgs.wireguard-tools];} '' cat ${wgClientPrivateKey} | wg pubkey > $out ''; # Network configuration wgGatewayAddr = "10.100.0.1"; wgClientAddr = "10.100.0.2"; wgSubnet = "10.100.0.0/24"; # Fixed VM IPs gatewayIP = "192.168.1.1"; nixarrHostIP = "192.168.1.2"; # Internet client IPs internetClientIP = "10.0.1.2"; internetGatewayIP = "10.0.1.1"; # IPv6 addresses gatewayIPv6 = "fd00:1::1"; nixarrHostIPv6 = "fd00:1::2"; internetClientIPv6 = "fd00:2::2"; internetGatewayIPv6 = "fd00:2::1"; wgGatewayAddrV6 = "fd00:100::1"; wgClientAddrV6 = "fd00:100::2"; # Generate WireGuard config file for client wgClientConfig = pkgs.writeText "wg-client.conf" '' [Interface] PrivateKey = ${builtins.readFile wgClientPrivateKey} Address = ${wgClientAddr}/24, ${wgClientAddrV6}/64 DNS = ${wgGatewayAddr} [Peer] PublicKey = ${builtins.readFile wgGatewayPublicKey} Endpoint = ${gatewayIP}:${toString wgGatewayPort} AllowedIPs = 0.0.0.0/0, ::/0 PersistentKeepalive = 25 ''; in pkgs.testers.nixosTest { name = "nixarr-vpn-confinement-test"; # Disable interactive mode to avoid hanging interactive = false; nodes = { # Internet client VM - Simulates external services and clients internetClient = { config, pkgs, ... }: { virtualisation.vlans = [2]; # Connect to VLAN 2 (Internet) networking = { firewall.enable = false; }; # Add route to VPN subnet boot.kernel.sysctl."net.ipv4.ip_forward" = 0; # internetClient doesn't forward # Enable systemd-networkd for proper route management systemd.network.enable = true; networking.useNetworkd = true; # Configure static routes to VPN subnet using systemd-networkd systemd.network.networks."40-eth1" = { matchConfig.Name = "eth1"; networkConfig = { DHCP = "no"; }; address = [ "${internetClientIP}/24" "${internetClientIPv6}/64" ]; gateway = [ "${internetGatewayIP}" "${internetGatewayIPv6}" ]; routes = [ { Destination = "${wgSubnet}"; Gateway = "${internetGatewayIP}"; } { Destination = "fd00:100::/64"; Gateway = "${internetGatewayIPv6}"; } ]; }; # Web server that returns source IP for testing systemd.services.source-ip-server = { enable = true; wantedBy = ["multi-user.target"]; after = ["network.target"]; serviceConfig = { Type = "exec"; ExecStart = let server = pkgs.writeText "server.py" '' import http.server import socketserver import socket class DualStackHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): address_family = socket.AF_INET6 def server_bind(self): # Enable dual-stack support self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) super().server_bind() class MyHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.end_headers() self.wfile.write(f"Source: {self.client_address[0]}".encode()) # Listen on all interfaces with DualStackHTTPServer(("::", 8080), MyHandler) as httpd: httpd.serve_forever() ''; in "${pkgs.python3}/bin/python3 ${server}"; Restart = "always"; }; }; environment.systemPackages = with pkgs; [ netcat-gnu curl python3 ]; }; # VPN Gateway VM - Acts as WireGuard server and internet gateway gateway = { config, pkgs, ... }: { virtualisation.vlans = [ 1 2 ]; # VLAN 1 for LAN, VLAN 2 for Internet networking = { interfaces.eth1 = { ipv4.addresses = [ { address = gatewayIP; prefixLength = 24; } ]; ipv6.addresses = [ { address = gatewayIPv6; prefixLength = 64; } ]; }; interfaces.eth2 = { ipv4.addresses = [ { address = internetGatewayIP; prefixLength = 24; } ]; ipv6.addresses = [ { address = internetGatewayIPv6; prefixLength = 64; } ]; }; firewall = { enable = true; allowedUDPPorts = [ wgGatewayPort 51413 ]; allowedTCPPorts = [51413]; }; wireguard.interfaces.wg0 = { ips = [ "${wgGatewayAddr}/24" "${wgGatewayAddrV6}/64" ]; listenPort = wgGatewayPort; privateKeyFile = "${wgGatewayPrivateKey}"; peers = [ { publicKey = builtins.readFile wgClientPublicKey; allowedIPs = [ "${wgClientAddr}/32" "${wgClientAddrV6}/128" ]; } ]; }; }; # Enable IP forwarding boot.kernel.sysctl = { "net.ipv4.ip_forward" = 1; "net.ipv6.conf.all.forwarding" = 1; }; # Port forwarding and firewall rules networking.firewall.extraCommands = '' # Allow WireGuard and testing traffic (IPv4) iptables -A INPUT -i eth1 -j ACCEPT iptables -A INPUT -i eth2 -j ACCEPT iptables -A INPUT -i wg0 -j ACCEPT # Allow WireGuard and testing traffic (IPv6) ip6tables -A INPUT -i eth1 -j ACCEPT ip6tables -A INPUT -i eth2 -j ACCEPT ip6tables -A INPUT -i wg0 -j ACCEPT # IPv6 forwarding rules - Allow forwarding between interfaces ip6tables -A FORWARD -i wg0 -o eth2 -j ACCEPT ip6tables -A FORWARD -i eth2 -o wg0 -j ACCEPT ip6tables -A FORWARD -i wg0 -o eth1 -j ACCEPT ip6tables -A FORWARD -i eth1 -o wg0 -j ACCEPT ip6tables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT # Note: No masquerading - we want to preserve source IPs for testing # Forward transmission peer port from internet to VPN client (this is the key test) iptables -t nat -A PREROUTING -i eth2 -p tcp --dport 51413 -j DNAT --to-destination ${wgClientAddr}:51413 iptables -t nat -A PREROUTING -i eth2 -p udp --dport 51413 -j DNAT --to-destination ${wgClientAddr}:51413 # Allow forwarded traffic iptables -A FORWARD -p tcp --dport 51413 -d ${wgClientAddr} -j ACCEPT iptables -A FORWARD -p udp --dport 51413 -d ${wgClientAddr} -j ACCEPT # Accept return traffic for established connections iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT ''; # Simple DNS server for testing services.dnsmasq = { enable = true; settings = { interface = "wg0"; bind-interfaces = true; listen-address = wgGatewayAddr; # Log DNS queries for leak detection log-queries = true; log-facility = "/var/log/dnsmasq-queries.log"; # Static DNS entries for testing address = [ "/test.vpn.local/${wgGatewayAddr}" "/leak.test.local/1.2.3.4" "/transmission.local/${wgClientAddr}" ]; }; }; # Ensure dnsmasq starts after WireGuard systemd.services.dnsmasq = { after = ["wireguard-wg0.service"]; wants = ["wireguard-wg0.service"]; }; # No additional routing needed on gateway - it has direct interfaces to both networks # Install test utilities environment.systemPackages = with pkgs; [ iptables netcat-gnu python3 iproute2 # for ss command tcpdump ]; }; # Nixarr host with VPN-confined transmission nixarrHost = { config, pkgs, ... }: { imports = [nixosModules.default]; virtualisation.vlans = [1]; # Connect to VLAN 1 networking = { interfaces.eth1 = { ipv4.addresses = [ { address = nixarrHostIP; prefixLength = 24; } ]; ipv6.addresses = [ { address = nixarrHostIPv6; prefixLength = 64; } ]; }; # Disable firewall for testing firewall.enable = false; # Add route to gateway defaultGateway = { address = gatewayIP; interface = "eth1"; }; defaultGateway6 = { address = gatewayIPv6; interface = "eth1"; }; }; # Copy WireGuard config to the expected location system.activationScripts.setupWgConfig = '' mkdir -p /etc/wireguard cp ${wgClientConfig} /etc/wireguard/wg0.conf chmod 600 /etc/wireguard/wg0.conf ''; # Minimal nixarr configuration with VPN nixarr = { enable = true; # Required directories mediaDir = "/data/media"; stateDir = "/data/.state/nixarr"; # Enable VPN vpn = { enable = true; wgConf = "/etc/wireguard/wg0.conf"; }; # Enable transmission with VPN transmission = { enable = true; vpn.enable = true; # Use specific peer port for testing peerPort = 51413; # Disable firewall opening since we're in VPN openFirewall = false; }; # Disable all other services sonarr.enable = false; radarr.enable = false; lidarr.enable = false; readarr.enable = false; bazarr.enable = false; prowlarr.enable = false; jellyfin.enable = false; plex.enable = false; sabnzbd.enable = false; autobrr.enable = false; recyclarr.enable = false; jellyseerr.enable = false; }; # Add IPv6 route for VPN namespace to reach internetClient via WireGuard systemd.network.networks."10-eth1" = { matchConfig.Name = "eth1"; routes = [ { routeConfig = { Destination = "fd00:2::/64"; # Route to internetClient network Gateway = gatewayIPv6; # Via gateway IPv6 }; } ]; }; # Install test utilities environment.systemPackages = with pkgs; [ wireguard-tools dig curl iproute2 iptables netcat-gnu tcpdump ]; }; }; testScript = '' start_all() print("=== Waiting for VMs to boot ===") # Wait for all VMs to be ready internetClient.wait_for_unit("multi-user.target", timeout=60) gateway.wait_for_unit("multi-user.target", timeout=60) nixarrHost.wait_for_unit("multi-user.target", timeout=60) # Wait for web server on internetClient internetClient.wait_for_unit("source-ip-server.service") internetClient.wait_for_open_port(8080) # Wait for systemd-networkd to set up routes internetClient.wait_for_unit("systemd-networkd.service") print("=== Test 1: Basic connectivity between VMs ===") # First verify that nixarrHost can reach the gateway nixarrHost.succeed("ping -c 1 ${gatewayIP}") gateway.succeed("ping -c 1 ${nixarrHostIP}") print("=== Test 2: Check VPN namespace setup ===") # Check that wg namespace exists nixarrHost.succeed("ip netns list | grep -q wg") # The VPN namespace service should be running nixarrHost.wait_for_unit("wg.service") # Check if transmission is running nixarrHost.wait_for_unit("transmission.service", timeout=30) print("=== Test 3: Check WireGuard connectivity ===") # Check if WireGuard interface exists in namespace nixarrHost.succeed("ip netns exec wg ip link show wg0") # Check WireGuard status nixarrHost.succeed("ip netns exec wg wg show") # Test VPN tunnel connectivity nixarrHost.succeed("ip netns exec wg ping -c 3 ${wgGatewayAddr}") print("=== Test 4: Verify traffic routing through VPN ===") # Debug: See what the web server actually returns response = nixarrHost.succeed("ip netns exec wg curl -s http://${internetClientIP}:8080") print(f"Web server response: {response}") # Test traffic through VPN tunnel to internetClient - should show VPN client IP nixarrHost.succeed("ip netns exec wg curl -s http://${internetClientIP}:8080 | grep -q '${wgClientAddr}'") print("=== Test 5: Verify traffic routing through VPN ===") # IPv4 traffic to host network should be blocked (specific route handling) nixarrHost.fail("ip netns exec wg curl -s --max-time 2 http://${gatewayIP}:8080") # Debug IPv6 connectivity before main test print("=== Debug IPv6 connectivity ===") # Check IPv6 addresses in VPN namespace ipv6_addrs = nixarrHost.succeed("ip netns exec wg ip -6 addr show") print(f"VPN namespace IPv6 addresses:\n{ipv6_addrs}") # Check if VPN namespace can ping gateway IPv6 try: nixarrHost.succeed("ip netns exec wg ping -6 -c 1 -W 3 ${wgGatewayAddrV6}") print("✓ VPN namespace can ping WireGuard gateway IPv6") except Exception as e: print(f"✗ VPN namespace cannot ping WireGuard gateway IPv6: {e}") # Check if VPN namespace can reach internetClient IPv6 (simple connectivity) try: result = nixarrHost.succeed("ip netns exec wg curl -6 -s --max-time 5 http://[${internetClientIPv6}]:8080") print(f"✓ VPN namespace can reach internetClient IPv6: {result}") except Exception as e: print(f"✗ VPN namespace cannot reach internetClient IPv6: {e}") # Check gateway IPv6 routes gw_routes = gateway.succeed("ip -6 route show") print(f"Gateway IPv6 routes:\n{gw_routes}") # Check internetClient IPv6 routes client_routes = internetClient.succeed("ip -6 route show") print(f"InternetClient IPv6 routes:\n{client_routes}") # IPv6 traffic should go through VPN tunnel (shows VPN client source) nixarrHost.succeed("ip netns exec wg curl -6 -s --max-time 2 http://[${internetClientIPv6}]:8080 | grep -q 'Source: fd00:100::2'") print("=== Test 6: Verify transmission is confined ===") # Check transmission is running and confined to VPN namespace nixarrHost.succeed("systemctl status transmission.service | grep -q 'Active: active'") print("=== Test 7: Interrupt VPN - Verify no connectivity ===") # Block WireGuard traffic on gateway using iptables gateway.succeed("iptables -I INPUT -p udp --dport ${toString wgGatewayPort} -j DROP") gateway.succeed("iptables -I OUTPUT -p udp --sport ${toString wgGatewayPort} -j DROP") # All connectivity should fail - no leaks! nixarrHost.fail("ip netns exec wg ping -c 1 -W 2 ${wgGatewayAddr}") nixarrHost.fail("ip netns exec wg curl -s --max-time 2 http://${internetClientIP}:8080") # DNS should also fail completely when VPN is down nixarrHost.fail("ip netns exec wg dig @${wgGatewayAddr} test.vpn.local +short +timeout=2") nixarrHost.fail("ip netns exec wg dig leak.test.local +short +timeout=2") print("✓ DNS queries fail when VPN is down - no fallback to host DNS") # Verify no traffic leaks to host network nixarrHost.fail("ip netns exec wg curl -s --max-time 2 http://${gatewayIP}:8080") print("=== Test 8: Restore VPN - Verify recovery ===") # Remove iptables blocks gateway.succeed("iptables -D INPUT -p udp --dport ${toString wgGatewayPort} -j DROP") gateway.succeed("iptables -D OUTPUT -p udp --sport ${toString wgGatewayPort} -j DROP") # Restart the wg namespace service to force reconnection nixarrHost.succeed("systemctl restart wg.service") nixarrHost.wait_for_unit("wg.service") # Verify VPN connectivity is restored nixarrHost.succeed("ip netns exec wg ping -c 3 ${wgGatewayAddr}") # Verify source IP is correct again - use internetClient since gateway has no web server nixarrHost.succeed("ip netns exec wg curl -s http://${internetClientIP}:8080 | grep -q '${wgClientAddr}'") print("=== Test 9: Verify DNS configuration ===") # Check that resolv.conf in namespace uses VPN DNS nixarrHost.succeed("ip netns exec wg cat /etc/resolv.conf | grep -q 'nameserver ${wgGatewayAddr}'") # Verify no host DNS servers are present nixarrHost.fail("ip netns exec wg cat /etc/resolv.conf | grep -q 'nameserver 10.0.2.3'") print("=== Test 9b: DNS leak test ===") # Debug: Check if dnsmasq is running on gateway gateway.succeed("systemctl status dnsmasq") gateway.succeed("ss -unpl | grep :53 || echo 'No DNS listener found'") # Debug: Check connectivity to DNS server nixarrHost.succeed("ip netns exec wg ping -c 1 ${wgGatewayAddr}") # Start tcpdump on host interface to detect DNS leaks nixarrHost.succeed("nohup tcpdump -i eth1 -n 'port 53' -w /tmp/dns-leak.pcap > /tmp/tcpdump-dns.log 2>&1 &") # Clear dnsmasq query log gateway.succeed("echo > /var/log/dnsmasq-queries.log || true") # Use dig instead of nslookup for more reliable DNS queries nixarrHost.succeed("ip netns exec wg dig @${wgGatewayAddr} test.vpn.local +short | grep -q ${wgGatewayAddr}") nixarrHost.succeed("ip netns exec wg dig @${wgGatewayAddr} leak.test.local +short | grep -q '1.2.3.4'") nixarrHost.succeed("ip netns exec wg dig @${wgGatewayAddr} transmission.local +short | grep -q ${wgClientAddr}") # Also test without specifying server (uses resolv.conf) nixarrHost.succeed("ip netns exec wg dig test.vpn.local +short | grep -q ${wgGatewayAddr}") # Wait for any potential leaked packets nixarrHost.succeed("pkill tcpdump || true") # Check if any DNS packets were captured on host interface dns_packets = nixarrHost.succeed("tcpdump -r /tmp/dns-leak.pcap -nn 2>/dev/null | wc -l").strip() if int(dns_packets) > 0: # Show what was captured for debugging captured = nixarrHost.succeed("tcpdump -r /tmp/dns-leak.pcap -nn 2>/dev/null || echo 'No packets'") print("DNS leak detected! Captured " + dns_packets + " packets:") print(captured) nixarrHost.fail("DNS queries leaked to host network") # Verify queries went through VPN by checking gateway's dnsmasq log gateway.succeed("grep -q 'test.vpn.local' /var/log/dnsmasq-queries.log") gateway.succeed("grep -q 'leak.test.local' /var/log/dnsmasq-queries.log") print("✓ No DNS leaks detected - all queries confined to VPN") # Clean up nixarrHost.succeed("rm -f /tmp/dns-leak.pcap /tmp/tcpdump-dns.log") print("=== Test 10: Port forwarding test ===") # Wait for transmission to be ready and listening nixarrHost.wait_for_open_port(9091) # Web UI port # Check that transmission peer port is listening in namespace nixarrHost.succeed("ip netns exec wg ss -tlnp | grep -q ':51413'") nixarrHost.succeed("ip netns exec wg ss -ulnp | grep -q ':51413'") # Ensure WireGuard tunnel is active nixarrHost.succeed("ip netns exec wg ping -c 1 ${wgGatewayAddr}") # Debug: Print iptables rules and routing info print("=== Gateway NAT OUTPUT rules ===") output_rules = gateway.succeed("iptables -t nat -L OUTPUT -n -v") print(output_rules) print("=== Gateway NAT PREROUTING rules ===") prerouting_rules = gateway.succeed("iptables -t nat -L PREROUTING -n -v") print(prerouting_rules) print("=== Gateway routing table ===") routes = gateway.succeed("ip route show") print(routes) print("=== WireGuard status on gateway ===") wg_gateway = gateway.succeed("wg show") print(wg_gateway) print("=== WireGuard status on client ===") wg_client = nixarrHost.succeed("ip netns exec wg wg show") print(wg_client) # Test port forwarding through gateway print("=== Testing port forwarding ===") # First verify the NAT rules were actually applied output_nat = gateway.succeed("iptables -t nat -L OUTPUT | grep 51413 || echo 'No OUTPUT NAT rules found'") prerouting_nat = gateway.succeed("iptables -t nat -L PREROUTING | grep 51413 || echo 'No PREROUTING NAT rules found'") print(f"OUTPUT NAT check: {output_nat}") print(f"PREROUTING NAT check: {prerouting_nat}") # Debug connectivity and routing print("=== Testing connectivity from nixarrHost to gateway ===") nixarrHost.succeed("ping -c 1 ${gatewayIP}") # Debug FORWARD chain forward_rules = gateway.succeed("iptables -L FORWARD -n -v") print(f"Gateway FORWARD rules:\n{forward_rules}") # Check if gateway can reach VPN client (after handshake) gateway.succeed("wg show | grep -q 'latest handshake:'") # Debug port forwarding connectivity print("=== Debugging port forwarding ===") # First, ensure WireGuard tunnel is fully established gateway.succeed("wg") # Force handshake if needed nixarrHost.succeed("ip netns exec wg wg") # Skip direct gateway->client test after restart (WireGuard asymmetry) print("=== Testing port forwarding ===") # Test from nixarrHost (outside VPN) through gateway DNAT print("Test: External client -> Gateway -> VPN Client (via DNAT)") # First, ensure client initiates some traffic to establish WireGuard state nixarrHost.succeed("ip netns exec wg ping -c 1 ${wgGatewayAddr}") # Start tcpdump in background using nohup to properly detach it gateway.succeed("nohup tcpdump -i any -n 'port 51413 or host ${wgClientAddr}' -w /tmp/capture.pcap > /tmp/tcpdump.log 2>&1 &") # Verify tcpdump is running gateway.succeed("pgrep tcpdump") print("Tcpdump started, now testing connection...") # Now test the connection - this should succeed through DNAT! # Test from internetClient to gateway's internet IP - this simulates external traffic # The connection should be forwarded through the VPN to nixarrHost's transmission internetClient.succeed("timeout 5 nc -z -v ${internetGatewayIP} 51413") print("Success: Port forwarding works!") # Stop tcpdump and analyze what happened gateway.succeed("pkill tcpdump") tcpdump_output = gateway.succeed("tcpdump -r /tmp/capture.pcap -nn 2>/dev/null || echo 'No packets captured'") print(f"Tcpdump results:\n{tcpdump_output}") # Verify transmission can reach external services through VPN tunnel nixarrHost.succeed("ip netns exec wg curl -s http://${internetClientIP}:8080 | grep -q '${wgClientAddr}'") # Verify port is NOT accessible from host network (outside VPN) nixarrHost.fail("timeout 2 nc -z -v localhost 51413") print("=== Test 11: IPv6 leak test ===") # Verify IPv6 connectivity between VMs nixarrHost.succeed("ping -6 -c 1 ${gatewayIPv6}") gateway.succeed("ping -6 -c 1 ${nixarrHostIPv6}") # Check if IPv6 is enabled in VPN namespace nixarrHost.succeed("ip netns exec wg ip -6 addr show") # Test IPv6 through VPN tunnel nixarrHost.succeed("ip netns exec wg ping -6 -c 1 ${wgGatewayAddrV6}") # Test IPv6 traffic routing - should go through VPN tunnel to internetClient nixarrHost.succeed("ip netns exec wg curl -6 -s --max-time 2 http://[${internetClientIPv6}]:8080") nixarrHost.succeed("ip netns exec wg curl -6 -s http://[${internetClientIPv6}]:8080 | grep -q '${wgClientAddrV6}'") print("=== Test 12: IPv6 traffic test with VPN interruption ===") # Since WireGuard tunnel uses IPv4, blocking it affects both IPv4 and IPv6 traffic # The IPv6 traffic inside the tunnel should fail when we block the IPv4 WireGuard connection # This test verifies IPv6 behavior is tied to the VPN tunnel # Verify WireGuard is listening (debug what ports are actually open) print("=== Gateway listening ports ===") listening_ports = gateway.succeed("ss -unp") print(listening_ports) print("=== Looking for WireGuard port ${toString wgGatewayPort} ===") wg_port_check = gateway.succeed("ss -unp | grep :${toString wgGatewayPort} || echo 'WireGuard port not found'") print(wg_port_check) # The previous test (Test 7) already blocked IPv4 WireGuard and verified it works # So IPv6 through the tunnel should also be blocked after IPv4 VPN disruption # Let's verify IPv6 still works before disruption nixarrHost.succeed("ip netns exec wg ping -6 -c 1 ${wgGatewayAddrV6}") # Now use the same IPv4 blocking as Test 7 gateway.succeed("iptables -I INPUT -p udp --dport ${toString wgGatewayPort} -j DROP") gateway.succeed("iptables -I OUTPUT -p udp --sport ${toString wgGatewayPort} -j DROP") # Both IPv4 and IPv6 connectivity through VPN should fail nixarrHost.fail("ip netns exec wg ping -c 1 -W 2 ${wgGatewayAddr}") nixarrHost.fail("ip netns exec wg ping -6 -c 1 -W 2 ${wgGatewayAddrV6}") nixarrHost.fail("ip netns exec wg curl -6 -s --max-time 2 http://[${internetClientIPv6}]:8080") print("=== Test 13: IPv6 VPN recovery ===") # Remove iptables blocks (IPv4, since that's what WireGuard uses) gateway.succeed("iptables -D INPUT -p udp --dport ${toString wgGatewayPort} -j DROP") gateway.succeed("iptables -D OUTPUT -p udp --sport ${toString wgGatewayPort} -j DROP") # Verify IPv6 VPN connectivity is restored nixarrHost.succeed("ip netns exec wg ping -6 -c 3 ${wgGatewayAddrV6}") # Verify source IPv6 is correct again nixarrHost.succeed("ip netns exec wg curl -6 -s http://[${internetClientIPv6}]:8080 | grep -q '${wgClientAddrV6}'") print("=== All tests passed! ===") ''; }