Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Merged
dguido merged 3 commits into master from fix/wireguard-iptables-nat
Aug 17, 2025

Conversation

Copy link
Member

@dguido dguido commented Aug 17, 2025
edited
Loading

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:

  1. 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.

  2. FORWARD Issue: WireGuard packets don't use IPsec policies at all. The --pol none match in FORWARD was unnecessary and potentially problematic.

  3. 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:

  • ✅ NAT rules: Simple source-based NAT for both VPN types
  • ✅ WireGuard FORWARD: No policy match needed (it's not IPsec)
  • ✅ IPsec FORWARD: Kept --pol ipsec (correctly identifies encrypted traffic)

Changes

  1. Fixed iptables templates:

    • roles/common/templates/rules.v4.j2
    • roles/common/templates/rules.v6.j2
  2. Added comprehensive tests (tests/unit/test_iptables_rules.py):

    • Verifies NAT rules have no policy matching
    • Verifies WireGuard FORWARD has no policy matching
    • Verifies IPsec FORWARD keeps required policy matching
    • Tests alternative ingress IP (SNAT) functionality
    • Prevents regression of this issue

Testing

  • ✅ Manually tested on live server - routing now works
  • ✅ All unit tests pass (65/65 including 6 new tests)
  • ✅ ansible-lint passes

Impact

  • Fixes VPN connectivity for both WireGuard and IPsec
  • No breaking changes for existing functionality
  • Road warrior VPN (Algo's use case) works correctly
  • Would need adjustment if Algo ever adds site-to-site VPN support

🤖 Generated with Claude Code

Copy link
Contributor

claude bot commented Aug 17, 2025
edited
Loading

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:

  1. NAT sees decrypted packets: Post-decryption, packets from VPN subnets need simple source-based NAT
  2. WireGuard has no IPsec policies: Policy matching is meaningless for WireGuard traffic
  3. 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)

  1. Site-to-site VPN: If Algo ever supports this, NAT rules may need policy matching again
  2. Multiple subnets: Current logic handles single subnet per VPN type
  3. 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

  1. Consider adding IPv6 test coverage: Current tests focus on IPv4 (though changes are symmetric)
  2. 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>
@dguido dguido force-pushed the fix/wireguard-iptables-nat branch from 4b0f3b1 to d5f88dd Compare August 17, 2025 20:17
@dguido dguido changed the title (削除) Fix WireGuard traffic routing issue with iptables NAT rules (削除ここまで) (追記) Fix VPN traffic routing issue with iptables NAT rules (追記ここまで) Aug 17, 2025
dguido and others added 2 commits August 17, 2025 16:22
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>
@dguido dguido merged commit 9cc0b02 into master Aug 17, 2025
24 checks passed
@dguido dguido deleted the fix/wireguard-iptables-nat branch August 17, 2025 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@jackivanov jackivanov Awaiting requested review from jackivanov jackivanov is a code owner

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /