-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Fix VPN traffic routing issue with iptables NAT rules #14825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Claude finished @dguido's task —— View job
Comprehensive PR Review
I've conducted a thorough review of this critical iptables NAT routing fix. Here's my detailed analysis:
Review Checklist:
- Read and understand the problem described in PR description
- Analyze the iptables template changes in rules.v4.j2
- Analyze the iptables template changes in rules.v6.j2
- Review the new test file for coverage and quality
- Evaluate security implications of the changes
- Check for potential performance impacts
- Look for edge cases or potential regressions
- Provide comprehensive feedback and recommendations
Review Summary: ✅ EXCELLENT - Strongly Recommend Merge
This PR addresses a critical VPN functionality bug with a well-researched solution, comprehensive testing, and excellent technical execution.
Key Strengths
🎯 Problem Analysis & Solution
- Root cause correctly identified: The
--pol none
policy matching was indeed blocking decrypted VPN traffic from being NAT'd - Solution is architecturally sound: Separates NAT rules by VPN type and removes unnecessary policy matching
- Maintains security: Keeps appropriate policy matching for IPsec FORWARD rules where it belongs
🧪 Exceptional Test Coverage
The new test file (tests/unit/test_iptables_rules.py
) is exemplary:
- 188 lines of comprehensive test coverage
- Tests all critical scenarios: WireGuard-only, IPsec-only, both VPNs, SNAT vs MASQUERADE
- Regression-focused: Explicitly tests that the problematic
--pol none
patterns are absent - Clear test names that document expected behavior
- Good mutation testing approach: Tests verify what should NOT be present
🔒 Security Assessment: SAFE
- No security regressions: IPsec FORWARD rules correctly retain
--pol ipsec --dir in
matching - Principle of least complexity: Removes unnecessary policy matching where not needed
- Maintains VPN isolation: Client-to-client blocking rules unchanged
- No privilege escalation risks: Changes are within established firewall patterns
⚡ Performance Assessment: NEUTRAL TO POSITIVE
- Removes unnecessary checks: Eliminating
--pol none
matching reduces iptables processing overhead - Separates rule logic: More explicit rule generation may slightly improve rule evaluation
- No additional rule complexity: Rule count remains the same
Technical Implementation Review
Template Changes (roles/common/templates/rules.{v4,v6}.j2
)
Before (Problematic):
# Single rule for all VPN traffic with policy matching
-A POSTROUTING -s 10.48.0.0/16,10.49.0.0/16 -m policy --pol none --dir out -j MASQUERADE
After (Fixed):
# Separate, cleaner rules per VPN type {% if ipsec_enabled %} -A POSTROUTING -s {{ strongswan_network }} -j MASQUERADE {% endif %} {% if wireguard_enabled %} -A POSTROUTING -s {{ wireguard_network_ipv4 }} -j MASQUERADE {% endif %}
Why this is correct:
- NAT sees decrypted packets: Post-decryption, packets from VPN subnets need simple source-based NAT
- WireGuard has no IPsec policies: Policy matching is meaningless for WireGuard traffic
- Cleaner template logic: Separate rules per VPN type improve maintainability
FORWARD Rules Logic ✅
- IPsec FORWARD: Correctly keeps
--pol ipsec --dir in
(identifies encrypted traffic) - WireGuard FORWARD: Correctly removes policy matching (not applicable)
- Maintains proper traffic flow control
Edge Cases & Regression Analysis
Scenarios Tested ✅
- Single VPN deployments (WireGuard-only, IPsec-only)
- Dual VPN deployments (both enabled)
- Alternative ingress IP (SNAT vs MASQUERADE)
- Policy matching presence/absence verification
Potential Edge Cases (None Critical)
- Site-to-site VPN: If Algo ever supports this, NAT rules may need policy matching again
- Multiple subnets: Current logic handles single subnet per VPN type
- IPv6 NAT: Uses same logic as IPv4 (appropriate)
Code Quality Assessment
Template Quality ⭐⭐⭐⭐⭐
- Clear separation of concerns: Each VPN type has dedicated rule generation
- Consistent with existing patterns: Follows established Jinja2 conventions
- Good comments: Explains the purpose of each rule section
- DRY principle maintained: Reuses common SNAT/MASQUERADE logic
Test Quality ⭐⭐⭐⭐⭐
- Comprehensive coverage: Tests all NAT and FORWARD rule combinations
- Negative testing: Verifies problematic patterns are absent
- Real-world scenarios: Tests actual variable combinations used in deployment
- Self-documenting: Test names clearly explain what they verify
Minor Suggestions
- Consider adding IPv6 test coverage: Current tests focus on IPv4 (though changes are symmetric)
- Documentation: Consider updating any network troubleshooting docs to reference this fix
Conclusion
This is security-critical infrastructure code executed flawlessly:
- ✅ Fixes a critical bug affecting all VPN users
- ✅ Maintains security boundaries
- ✅ Comprehensive test coverage prevents regression
- ✅ Clean, maintainable implementation
- ✅ No breaking changes
- ✅ Excellent technical documentation in PR
Recommendation: Immediate merge - this fixes broken VPN functionality with no downsides.
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 <noreply@anthropic.com>
4b0f3b1
to
d5f88dd
Compare
The policy matching (-m policy --pol none) was causing routing issues for both WireGuard and IPsec VPN traffic. This was based on a misunderstanding of how iptables processes VPN traffic: 1. FORWARD chain: IPsec needs --pol ipsec to identify encrypted traffic, but WireGuard doesn't need any policy match (it's not IPsec) 2. POSTROUTING NAT: Both VPN types see decrypted packets here, so policy matching is unnecessary and was blocking NAT Changes: - Removed policy matching from all NAT rules (both VPN types) - Removed policy matching from WireGuard FORWARD rules - Kept policy matching only for IPsec FORWARD (where it's needed) - Added comprehensive unit tests to prevent regression This fully fixes VPN routing for both WireGuard and IPsec clients. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Fixed all ruff linting issues: - Removed unused yaml import - Fixed import sorting (pathlib before third-party imports) - Removed trailing whitespace from blank lines - Added newline at end of file All tests still pass after formatting fixes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Summary
Fixes critical issue where both WireGuard AND IPsec VPN clients could connect but not route traffic to the internet.
Root Cause Analysis
The NAT and FORWARD rules incorrectly used policy matching (
-m policy --pol none
) which broke routing:NAT Issue: The POSTROUTING chain sees decrypted packets from VPN clients. The
--pol none
match was blocking these packets from being NAT'd, preventing internet access.FORWARD Issue: WireGuard packets don't use IPsec policies at all. The
--pol none
match in FORWARD was unnecessary and potentially problematic.Why it existed: The policy matching was likely added to handle edge cases (site-to-site VPNs, preventing double NAT) that don't apply to Algo's road warrior VPN design.
Solution
Removed all unnecessary policy matching:
--pol ipsec
(correctly identifies encrypted traffic)Changes
Fixed iptables templates:
roles/common/templates/rules.v4.j2
roles/common/templates/rules.v6.j2
Added comprehensive tests (
tests/unit/test_iptables_rules.py
):Testing
Impact
🤖 Generated with Claude Code