diff options
| author | Ahmed Abdelhalim <[email protected]> | 2026-07-09 22:57:11 +0200 |
|---|---|---|
| committer | Ahmed Abdelhalim <[email protected]> | 2026-07-09 22:57:11 +0200 |
| commit | 1cbb9df950175e698e1edc87953e8f4bfb22b88a (patch) | |
| tree | 1f303b78561e725cf65c9bd1e62a9e22273e078d /roles/gateway/templates | |
| parent | d295dd0edcd61e094b7bd7ffe8ca3187d0a6da00 (diff) | |
Fix IPv6 first-request failure caused by ICMPv6 Redirects
Root cause: Pi, the ISP router (FritzBox), and LAN clients share the same
L2 segment. When Pi forwards a client's IPv6 packet to an external GUA
destination, the Linux kernel detects that the ISP router is a "better"
next-hop on the same link and sends an ICMPv6 Redirect (type 137) to the
client. The client obeys the redirect and sends its first SYN to the ISP
router directly — Pi never forwards the original packet. The ISP router
applies per-device content filtering to the unmasqueraded client GUA and
issues a RST. This produced the symptom of the first IPv6 request failing
with "Connection reset by peer" every ~30-60 seconds (matching the FritzBox
RA interval, after which clients re-resolve their default gateway).
Diagnosis method: tcpdump with MAC addresses (-e flag) on Pi's end0 caught
the ICMPv6 Redirect being sent immediately after the first SYN arrived.
Inserting a DROP rule for icmpv6-type redirect at position 1 in ip6tables
OUTPUT confirmed the fix — 0 failures across extended testing.
Fix:
- Drop ICMPv6 Redirect (type 137) in ip6tables OUTPUT as the first rule,
before the ACCEPT rule, in both direct and VPN modes
- Flush ip6tables OUTPUT chain on clear_rules() instead of per-rule -D
deletion, which was fragile and left stale rules accumulating across
mode switches (previously caused duplicate/conflicting OUTPUT rules)
- Flush ip6tables nat POSTROUTING table instead of per-rule -D deletion
for the same reason
Also documents the ICMPv6 Redirect issue and IPv6 masquerade rationale
in roles/gateway/README.md for future reference.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Diffstat (limited to 'roles/gateway/templates')
| -rw-r--r-- | roles/gateway/templates/gateway-apply-rules.sh.j2 | 5 |
1 files changed, 3 insertions, 2 deletions
diff --git a/roles/gateway/templates/gateway-apply-rules.sh.j2 b/roles/gateway/templates/gateway-apply-rules.sh.j2 index 0cbdd67a..59a960cf 100644 --- a/roles/gateway/templates/gateway-apply-rules.sh.j2 +++ b/roles/gateway/templates/gateway-apply-rules.sh.j2 @@ -27,9 +27,8 @@ clear_rules() { iptables -D OUTPUT -o "$GATEWAY_INTERFACE" -j ACCEPT 2>/dev/null || true if [ -n "$GATEWAY_SUBNET_V6" ]; then ip6tables -F FORWARD 2>/dev/null || true + ip6tables -F OUTPUT 2>/dev/null || true ip6tables -t nat -F POSTROUTING 2>/dev/null || true - ip6tables -D OUTPUT -o wg+ -j ACCEPT 2>/dev/null || true - ip6tables -D OUTPUT -o "$GATEWAY_INTERFACE" -j ACCEPT 2>/dev/null || true fi } @@ -42,6 +41,7 @@ apply_vpn_mode() { iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT if [ -n "$GATEWAY_SUBNET_V6" ]; then ip6tables -t nat -A POSTROUTING -o wg+ -j MASQUERADE + ip6tables -I OUTPUT 1 -p ipv6-icmp --icmpv6-type redirect -j DROP ip6tables -A OUTPUT -o wg+ -j ACCEPT ip6tables -A FORWARD -i "$GATEWAY_INTERFACE" -o wg+ -j ACCEPT ip6tables -A FORWARD -i wg+ -o "$GATEWAY_INTERFACE" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT @@ -57,6 +57,7 @@ apply_direct_rules() { iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT if [ -n "$GATEWAY_SUBNET_V6" ]; then ip6tables -t nat -A POSTROUTING -o "$GATEWAY_INTERFACE" -j MASQUERADE + ip6tables -I OUTPUT 1 -p ipv6-icmp --icmpv6-type redirect -j DROP ip6tables -A OUTPUT -o "$GATEWAY_INTERFACE" -j ACCEPT ip6tables -A FORWARD -s "$GATEWAY_SUBNET_V6" -i "$GATEWAY_INTERFACE" -o "$GATEWAY_INTERFACE" -j ACCEPT ip6tables -A FORWARD -i "$GATEWAY_INTERFACE" -o "$GATEWAY_INTERFACE" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT |
