From d5f88ddc4940d740a8430c917d4dcccc2c0e0182 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Sun, 17 Aug 2025 16:12:56 -0400 Subject: [PATCH] Fix VPN traffic routing issue with iptables NAT rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MASQUERADE rules had policy matching (-m policy --pol none --dir out) which was preventing both WireGuard AND IPsec traffic from being NAT'd properly. This policy match was incorrect and broke internet routing for all VPN clients. The confusion arose because: - IPsec FORWARD rules check for --pol ipsec (encrypted traffic) - But POSTROUTING happens AFTER decryption, so packets no longer have policy - The --pol none match was blocking these decrypted packets from NAT Changes: - Removed policy matching from both IPsec and WireGuard NAT rules - Both VPN types now use simple source-based NAT rules - Applied to both IPv4 and IPv6 rule templates This fixes the issue where VPN clients (both WireGuard and IPsec) could connect but not route traffic to the internet. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- roles/common/templates/rules.v4.j2 | 9 ++++++++- roles/common/templates/rules.v6.j2 | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/roles/common/templates/rules.v4.j2 b/roles/common/templates/rules.v4.j2 index bf3cd047..b456e0d3 100644 --- a/roles/common/templates/rules.v4.j2 +++ b/roles/common/templates/rules.v4.j2 @@ -36,7 +36,14 @@ COMMIT -A PREROUTING --in-interface {{ ansible_default_ipv4['interface'] }} -p udp --dport {{ wireguard_port_avoid }} -j REDIRECT --to-port {{ wireguard_port_actual }} {% endif %} # Allow traffic from the VPN network to the outside world, and replies --A POSTROUTING -s {{ subnets | join(',') }} -m policy --pol none --dir out {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} +{% if ipsec_enabled %} +# For IPsec traffic - NAT the decrypted packets from the VPN subnet +-A POSTROUTING -s {{ strongswan_network }} {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} +{% endif %} +{% if wireguard_enabled %} +# For WireGuard traffic - NAT packets from the VPN subnet +-A POSTROUTING -s {{ wireguard_network_ipv4 }} {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} +{% endif %} COMMIT diff --git a/roles/common/templates/rules.v6.j2 b/roles/common/templates/rules.v6.j2 index 9515b685..07043331 100644 --- a/roles/common/templates/rules.v6.j2 +++ b/roles/common/templates/rules.v6.j2 @@ -35,7 +35,14 @@ COMMIT -A PREROUTING --in-interface {{ ansible_default_ipv6['interface'] }} -p udp --dport {{ wireguard_port_avoid }} -j REDIRECT --to-port {{ wireguard_port_actual }} {% endif %} # Allow traffic from the VPN network to the outside world, and replies --A POSTROUTING -s {{ subnets | join(',') }} -m policy --pol none --dir out {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} +{% if ipsec_enabled %} +# For IPsec traffic - NAT the decrypted packets from the VPN subnet +-A POSTROUTING -s {{ strongswan_network_ipv6 }} {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} +{% endif %} +{% if wireguard_enabled %} +# For WireGuard traffic - NAT packets from the VPN subnet +-A POSTROUTING -s {{ wireguard_network_ipv6 }} {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} +{% endif %} COMMIT