validation-discounts: driving cases → the settled validation-only model (all money/paper at the booth) → setup UX/storage/RBAC → full as-built record. DECIDED: merchant stations scan with a USB/HID barcode scanner on the web/desktop app (hand-keying + Luhn as fallback); POSTPONED with analysis: web getUserMedia scanning (secure-context TLS prerequisite on the LAN + Code128-via-camera weakness → QR-on-ticket first) and a Tauri v2 Android merchant app (native ML Kit scanning; Android build/sideload overhead + configurable-server-URL prerequisite). Also: wsl-dev-networking gains the mirrored-mode gotcha where a Windows-side listener makes a port EADDRINUSE inside WSL while invisible to ss — Vite auto-increments and tauri dev's fixed devUrl waits on the wrong port. Claude-Session: https://claude.ai/code/session_01YYkpEsLmoQPaize5ec3oUm
7.7 KiB
type, tags, sources, updated
| type | tags | sources | updated | |||||
|---|---|---|---|---|---|---|---|---|
| reference |
|
2026-06-15 |
WSL2 Dev Networking (for device testing)
Dev-environment note, not product architecture. Recorded because reaching real hardware (the uhppote-controller) from a dev box running under WSL2 took significant debugging. If you test devices from WSL, read this first.
The problem
By default WSL2 uses NAT networking: the Linux VM sits on its own virtual subnet
(e.g. 172.x), not the Windows host's LAN. Consequences for device work:
- UDP broadcast (UHPPOTE discovery) cannot leave the VM — a
get-devicesbroadcast getsEACCES/ never reaches a controller on the physical LAN. The device is reachable from Windows but not from inside WSL. - Even unicast to a LAN device may not route, depending on setup.
The fix: mirrored networking
Switch WSL to mirrored mode so it shares the Windows host's interfaces (and thus the real LAN). Requires Windows 11 22H2+ and WSL ≥ 2.0.
%UserProfile%\.wslconfig (create it; it doesn't exist by default):
[wsl2]
networkingMode=mirrored
firewall=false # Windows Firewall otherwise filters WSL traffic (can drop UDP replies)
[experimental]
hostAddressLoopback=true # host <-> WSL over the host's IP
Apply: in PowerShell wsl --shutdown, wait ~10 s, reopen WSL. Verify with ip -4 addr —
interfaces should now show the real LAN subnet (e.g. 10.0.10.x) instead of 172.x.
(Microsoft recommends editing via the WSL Settings GUI rather than the file by hand.)
wsl --shutdownkills the dev servers — restartpnpm devafterward.
After mirrored mode: app-level gotchas that remained
Mirrored networking is necessary but not sufficient — these still bit us:
- Multiple interfaces. Mirrored WSL exposes all host NICs (LAN, Tailscale/CGNAT
100.x, docker bridges). UHPPOTE discovery must broadcast on every subnet, not the first one — see device-discovery. - Subnet-directed broadcast (
10.0.10.255, not255.255.255.255) — the lib won't enableSO_BROADCASTotherwise. See device-discovery. localhost→ IPv6 first.localhostresolves to::1, but the backend binds IPv4 (127.0.0.1). Node's Vite proxy can stall on the v6 attempt before falling back — point the proxy at127.0.0.1explicitly. (See local-dev-workflow.)- Windows-side listeners collide with WSL binds — INVISIBLY (2026-07-13). Under mirrored
mode, a process listening on the WINDOWS side makes the same port
EADDRINUSEinside WSL, but it never appears in Linuxss/lsof— the port looks free yet won't bind. Bit us as "tauri dev: Could not connect to http://localhost:5173 after 180s": a DIFFERENT React app's dev server running on the Windows side held::1:5173, so the WSL Vite silently auto-incremented to 5174 while Tauri'sdevUrlis the FIXED stringhttp://localhost:5173intauri.conf.json(it cannot follow the auto-increment). Diagnose from WSL withpowershell.exe -NoProfile -Command "Get-NetTCPConnection -LocalPort 5173 -State Listen"(thenGet-Process -Id <OwningProcess>); kill withtaskkill.exe /PID <pid> /F. Guard:strictPort: truein the webvite.configso the mismatch fails in a second with a clear error instead of a 3-minute hang on the wrong port.
Multi-subnet source-address trap (the "ARP works but ping/TCP dies" bug)
Field devices arrive statically configured on assorted /24s 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 linkwith no preferred source. With two such subnets on one NIC, the kernel may pick the wrong source address — e.g. sourcing traffic to10.0.10.121from192.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 chosensrc— 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>(orcurl --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>(usereplace, notchange—changeerrorsRTNETLINK: No such fileif 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 eachproto kernel scope linkroute on the NIC and pinssrcto 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, sincewsl --shutdownotherwise 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.targetis too early for mirrored-mode addresses (the script waits up to 15s for a route to appear); and it must NOTset -eor one failedipcall 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.xlives on Windows — the truly permanent fix is to remove/reconfigure it there (or setSkipAsSource/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 (shares the LAN), or use unicast by IP instead of broadcast discovery (target the controller's known IP — the driver supports an explicit host). On the real appliance (a dedicated hardened Linux box, disk-os-hardening) none of this applies — it's bare-metal on the device VLAN (network-isolation).