This is a practical troubleshooting post. Facts first, evidence second, conclusions last.
TL;DR
- Symptom: Cross-VLAN
pingtimes out. The target host showsecho replysent, yet the client never receives it. - Root cause #1 (decisive): PVE intentionally kept OpenWrt (
192.168.22.13) as the default gateway for QoS/traffic handling, but had no explicit routes for other internal VLAN subnets, so return traffic to internal networks followed the default route into OpenWrt and never traversed the primary L3 gateway (classic asymmetric path). - Root cause #2 (amplifier): OpenWrt had
masq=1on the LAN zone, applying SNAT to internal east-west traffic and making paths/sessions unpredictable. - Fix:
- Keep PVE default gateway = OpenWrt (
192.168.22.13) for QoS, but add explicit routes for internal subnets (e.g.,192.168.11.0/24,192.168.183.0/24) via the primary gateway (192.168.22.1) - Disable masquerade on OpenWrt
lanzone
- Keep PVE default gateway = OpenWrt (
- Prevention: Route internally, NAT only on real WAN egress; if a side gateway must be the default route (QoS/proxy), explicitly split internal routing vs Internet default; use
tcpdump+ip route getas a standard SOP.
Background (Topology and Roles)
Multi-VLAN home network:
- Cloud Gateway Fiber (primary L3 gateway)
- Inter-VLAN routing
- Example subnets:
- VLAN11:
192.168.11.0/24(GW192.168.11.1) - VLAN22 (PVE/Lab):
192.168.22.0/24(GW192.168.22.1) - VLAN183:
192.168.183.0/24(GW192.168.183.1)
- VLAN11:
- OpenWrt (secondary / side gateway)
192.168.22.13/24onbr-lanfw3 + iptables, plus proxy tooling (OpenClash/Passwall)
- PVE node:
192.168.22.12/24onvmbr0 - Test hosts:
192.168.183.235(VLAN183)192.168.11.29(VLAN11)
The intended design is simple:
- Inter-VLAN traffic should be routed by the primary gateway.
- NAT should only happen on a true WAN egress, not on internal VLAN-to-VLAN paths.
ASCII topology (printable via shell)
This script does not change any config. It prints the topology and the two key paths (bad vs fixed) so readers can visualize the data flow:
bash#!/usr/bin/env bashset -euo pipefail# Print the topology and the two paths (bad vs fixed).cat <<'EOF'+------------------------------+| Cloud Gateway Fiber (L3) || VLAN11: 192.168.11.1 || VLAN22: 192.168.22.1 || VLAN183: 192.168.183.1 |+---------------+--------------+|VLAN22|+----------------+----------------+| |+----------v-----------+ +----------v-----------+| OpenWrt (side GW) | | PVE || 192.168.22.13 | | 192.168.22.12 || QoS / traffic mgmt | | vmbr0 |+----------------------+ +----------------------+Clients:VLAN183 host: 192.168.183.235VLAN11 host: 192.168.11.29Problem #1 (before):192.168.183.235 -> Fiber -> PVEPVE reply -> (default gw) OpenWrt -> [NAT/proxy/unknown] -> ??? (Fiber never sees the reply)Fix idea:Keep PVE default gw = OpenWrt (for QoS),but route internal subnets via Fiber.Problem #2 (amplifier):OpenWrt LAN masquerade (SNAT) rewrites east-west traffic,making internal routing/session behavior unpredictable.EOF
Incidents and Impact
Incident #1: VLAN183 could not ping PVE
- Client
192.168.183.235→ping 192.168.22.12timed out - On PVE, captures showed:
ICMP echo requestarrivingICMP echo replybeing generated
- On the primary gateway (Fiber), captures on
br22/br0showed requests only, no replies
Meaning: the reply never traversed the primary gateway.
Incident #2: After fixing #1, VLAN11 still could not ping PVE
192.168.11.29could not ping192.168.22.12- But it could ping OpenWrt
192.168.22.13
This pointed to OpenWrt NAT/firewall behavior affecting internal reachability.
Investigation (Evidence-driven)
1) Confirm the primary gateway is fine
On Fiber:
- It could ping both
192.168.183.235and192.168.22.12. ip routeshowed both subnets as directly connected.
Conclusion: core L3 routing on Fiber was not broken.
2) Hop-by-hop capture: find where the reply disappears
- On PVE: request/reply both exist → PVE is replying.
- On Fiber (
br22/br0): request only → reply never reached Fiber.
Conclusion: the return path is not going through the primary gateway.
3) Let ip route get tell the truth
On PVE:
ip routeshowed:default via 192.168.22.13 dev vmbr0ip route get 192.168.183.235showed:via 192.168.22.13
Root cause #1: PVE had no explicit route to internal VLAN subnets, so return traffic to 192.168.183.0/24 naturally followed the default route into OpenWrt. Once that happens, any NAT/proxy/misroute on OpenWrt can prevent the primary gateway from ever seeing the reply.
That creates asymmetric routing:
- Forward:
183 → Fiber → 22 → PVE(OK) - Return:
PVE → OpenWrt → (NAT/proxy/unknown)(uncontrolled)
4) Validate OpenWrt firewall/NAT configuration
After confirming OpenWrt uses fw3 + iptables (no nftables), the key finding was:
lanzone hadmasq='1'
Root cause #2: OpenWrt applied SNAT to internal traffic, rewriting sources to 192.168.22.13 and making internal routing/session behavior unstable, especially with proxy redirection.
Fix (Minimal changes)
Fix #1: Keep default gateway = OpenWrt, but route internal subnets via Fiber
Constraint: PVE default route must remain 192.168.22.13 (OpenWrt) for QoS/traffic handling. In that case, do not touch the default route. Instead, explicitly route internal VLAN subnets via the primary L3 gateway (192.168.22.1).
Temporary test (examples for VLAN11/VLAN183; add others as needed):
bash# Keep default via OpenWrt for QoS / traffic management# default via 192.168.22.13# Route internal VLANs via the primary L3 gateway (Fiber)ip route replace 192.168.183.0/24 via 192.168.22.1 dev vmbr0ip route replace 192.168.11.0/24 via 192.168.22.1 dev vmbr0
Verification tips:
ip route get 192.168.183.235should showvia 192.168.22.1- ICMP replies should be visible on Fiber interfaces (e.g.,
br22/br0)
Persist it in Proxmox networking (commonly /etc/network/interfaces for vmbr0) with post-up:
bashpost-up ip route replace 192.168.183.0/24 via 192.168.22.1 dev vmbr0post-up ip route replace 192.168.11.0/24 via 192.168.22.1 dev vmbr0post-down ip route del 192.168.183.0/24 via 192.168.22.1 dev vmbr0 || truepost-down ip route del 192.168.11.0/24 via 192.168.22.1 dev vmbr0 || true
Verification:
- Fiber sees ICMP replies on
br22/br0. 192.168.183.235can ping192.168.22.12again.
Fix #2: Disable masquerade on OpenWrt LAN zone
bashuci set firewall.@zone[0].masq='0'uci commit firewall/etc/init.d/firewall restart
Verification:
192.168.11.29can ping192.168.22.12.- Internal traffic keeps real source IPs, making debugging and ACLs sane.
Prevention
- Route internally; NAT only on real WAN
lan/internal zones:masq=0wanegress:masq=1
- If a side gateway must be the default route (QoS/proxy), split routing explicitly
- Default route can stay on OpenWrt, but internal subnets must have explicit routes via the primary L3 gateway (or use policy routing by destination).
- Keep east-west traffic on predictable L3 routing. Do not let it fall into NAT/proxy black boxes.
- Standard SOP (Standard Operating Procedure)
- Meaning: for “cross-VLAN ping fails”, do not guess. Follow the same evidence-driven checklist every time.
- Step 1: packet captures answer 3 questions: did the request arrive, did the target reply, and at which hop did the reply disappear?
- On PVE:
tcpdump -ni vmbr0 icmp - On the primary gateway (both VLAN interfaces):
tcpdump -ni br22 icmpandtcpdump -ni br0 icmp
- On PVE:
- Step 2: verify routing decisions (no imagination)
- On PVE:
ip route+ip route get 192.168.183.235 - On OpenWrt/Fiber:
ip route get 192.168.183.235(confirm the actual next hop)
- On PVE:
- Step 3: simplify the data path first
- Restore pure routing with real source IPs and predictable return paths before layering QoS/proxy/policy routing.
- Make proxy behavior explicit
- Exclude internal subnets from transparent proxy/tunnel redirection.
One-line lesson
If ICMP looks like “ghost packets”, you most likely built a black box with the wrong default gateway and the wrong NAT — not a broken protocol stack.
Comments