The park-buzi printer is a K200L (Xprinter/ICS XP-K200L; its LAN board and USB descriptor call it "POS-80"). Its board serves the Rongta's five-row status table under /prt_status.htm — but the reply carries no HTTP status line or headers, which node:http rejects, so the Rongta driver could never read it and the unit was filed in July as "no status page → generic driver" (reachability only). New `k200l` driver (printer-k200l.ts): prints through the generic ESC/POS device (same bytes, TCP 9100 or usblp) and reads the page over a raw socket, tolerant of both the headerless and a proper HTTP reply. Mapping mirrors the Rongta: board unreachable → offline; page not understood → degraded, never ready; any fault → degraded naming it; USB → reachability floor. The Rongta driver is untouched. Tests replay the captured headerless page (devices suite 76). Live against the lab unit: ready; with the cover open the board reports cover open, paper out, off-line. Wiki: new k200l-printer entity (names, network setup from factory 192.168.123.100, board quirks, status page, what it means for park-buzi — over USB the app never saw cover/paper state at all), cross-links on the Rongta, status-monitoring, USB-transport and WSL-networking pages (parking-net pinned to eth1 while the LAN NIC is eth0), index. Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
8.4 KiB
type, tags, sources, updated
| type | tags | sources | updated | |||||
|---|---|---|---|---|---|---|---|---|
| reference |
|
2026-09-09 |
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).
It bit again, 2026-09-09 — and the fix was pinned to the wrong NIC. Configuring the k200l-printer meant adding
192.168.123.101beside10.0.10.203on the mirrored LAN NIC; WSL then sourced 10.0.10.x traffic from the 192.168.123 address:pingfine, every HTTP connect timing out,ip route get 10.0.10.7showingsrc 192.168.123.101.parking-net.servicewas active but pinseth1, and the mirrored LAN NIC iseth0on this box now — so the boot fixer was a no-op. Rundeploy/wsl-fix-route-source.sh eth0(and fix the unit's argument), orcurl --interface 10.0.10.203 …as a one-off. Interface names are not stable across WSL reboots/NIC changes; the script accepts the NIC as an argument for exactly this reason.
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).