Permanent WSL2 dev fix for multi-subnet source-address trap
Mirrored mode re-clones the Windows NIC's addresses each boot, so the kernel keeps picking the wrong source for stacked device subnets (10.0.10.x sourced from 192.168.1.123) — ARP resolves but ping/TCP dies, and every runtime ip-route fix is wiped by wsl --shutdown. deploy/wsl-fix-route-source.sh pins each scope-link route's src to this host's own address in that subnet (no hardcoded IPs, idempotent, preserves metric, non-fatal per route, waits for the route at boot). deploy/parking-net .service reapplies it on every boot. Dev-box only; the appliance is bare-metal Linux with static networkd config. Verified: camera pings with no -I flag; driver pulls a snapshot with no localAddress set.
This commit is contained in:
@@ -56,6 +56,59 @@ Mirrored networking is necessary but **not sufficient** — these still bit us:
|
||||
(`127.0.0.1`). Node's Vite proxy can stall on the v6 attempt before falling back — point the
|
||||
proxy at `127.0.0.1` explicitly. (See [[local-dev-workflow]].)
|
||||
|
||||
## Multi-subnet source-address trap (the "ARP works but ping/TCP dies" bug)
|
||||
|
||||
Field devices arrive **statically configured on assorted `/24`s** by whoever installed them last
|
||||
(e.g. a camera on `10.0.10.121`, a printer on `10.0.10.6`, others on `192.168.1.x`). The host
|
||||
copes by carrying **one IP per device subnet on a single NIC** (this is correct — you do **not**
|
||||
need a NIC per subnet). But stacking subnets on one interface exposes a Linux source-selection
|
||||
trap:
|
||||
|
||||
- Connected routes come up as `proto kernel scope link` **with no preferred source**. With two
|
||||
such subnets on one NIC, the kernel may pick the **wrong source address** — e.g. sourcing
|
||||
traffic to `10.0.10.121` from `192.168.1.123`.
|
||||
- Symptom is baffling: **ARP resolves and the neighbor shows `REACHABLE`** (L2 is fine, source
|
||||
address is irrelevant to ARP) while **every ping and TCP connect times out** (replies have a
|
||||
wrong/unroutable source → dropped, possibly by uRPF). Looks like "the device is down / the whole
|
||||
subnet is unreachable" when nothing is actually broken.
|
||||
- **Diagnose:** `ip route get <device-ip>` shows the chosen `src` — if it's an address on a
|
||||
*different* subnet, that's the bug. Confirm by forcing the right source:
|
||||
`ping -I <correct-src> <device-ip>` (or `curl --interface <correct-src> …`) — instant replies.
|
||||
- **Fix (runtime):** pin the preferred source on the connected route, per subnet:
|
||||
`sudo ip route replace <subnet>/24 dev <nic> proto kernel scope link src <correct-host-ip> metric <m>`
|
||||
(use `replace`, not `change` — `change` errors `RTNETLINK: No such file` if the route isn't up
|
||||
yet). Do **not** delete the other subnet's address unless it's genuinely unwanted — you need all
|
||||
of them to reach all the devices.
|
||||
|
||||
- **Fix (permanent, this box):** `deploy/wsl-fix-route-source.sh` + `deploy/parking-net.service`.
|
||||
The script walks each `proto kernel scope link` route on the NIC and pins `src` to THIS host's own
|
||||
address in that same subnet — **no hardcoded IPs**, so it also covers future device subnets; it's
|
||||
idempotent, preserves the route metric, and tolerates a missing route. The systemd unit (oneshot,
|
||||
`enabled`) reapplies it on every WSL boot — which is the point, since `wsl --shutdown` otherwise
|
||||
wipes the runtime fix (mirrored mode re-clones the Windows addresses fresh each boot, see below).
|
||||
Install once: copy the unit to `/etc/systemd/system/`, `systemctl enable --now parking-net`.
|
||||
Gotchas hit while building it: `network.target` is too early for mirrored-mode addresses (the
|
||||
script waits up to 15s for a route to appear); and it must NOT `set -e` or one failed `ip` call
|
||||
aborts the whole boot fixer.
|
||||
|
||||
> **Root cause is on the Windows side.** Mirrored mode clones the Windows host NIC's addresses into
|
||||
> Linux at every boot, so the stray `192.168.1.x` lives on Windows — the truly permanent fix is to
|
||||
> remove/reconfigure it there (or set `SkipAsSource`/interface metric). The systemd hook is the
|
||||
> self-contained Linux-side answer that needs no Windows changes.
|
||||
|
||||
Verified on hardware (2026-06-15): after the hook, `10.0.10.121` pings and the real [[lpr-camera]]
|
||||
Hikvision driver pulls a snapshot with **no** source-forcing (`localAddress` becomes optional).
|
||||
|
||||
## On the real appliance: multi-subnet is a deployment config, not a WSL hack
|
||||
|
||||
Production is a **dedicated hardened Linux appliance** ([[disk-os-hardening]]), so the WSL story
|
||||
above is dev-only. The device-subnet problem persists, though, and is solved the same way at the
|
||||
OS level: the appliance NIC carries **one address per device subnet**, each connected route with a
|
||||
pinned `src`, made persistent (systemd-networkd / netplan). Per the threat model this still rides
|
||||
on **[[network-isolation]]** — device subnets are isolated segments reachable only by the host.
|
||||
The long-term clean answer is to **re-IP the devices onto one planned parking-system subnet** at
|
||||
install so the host needs only one address; the multi-subnet config is what you run until then.
|
||||
|
||||
## Alternative if you can't use mirrored mode
|
||||
|
||||
Windows 10 / old WSL can't do mirrored mode. Options: run the **backend natively on Windows**
|
||||
|
||||
Reference in New Issue
Block a user