code wiki / _hdl_build / nx_lan_scan.nx

nx_lan_scan.nx

buildroot/runtime/_hdl_build/nx_lan_scan.nx

11580 B284 linesdepth 5pulls 5 transitivereach 6 importersview sourcekind librarytopic lan
docsdependenciesstructsconstsfunctions

about

nx_lan_scan.nx -- shared sovereign LAN sweep primitive (no main). WHY THIS EXISTS (rule 15, DRY through shared libraries): two organs now ask the SAME question -- "is host H open on port P?" -- nx_printer_ctl (printer discovery/survey) and nx_iot_ctl (home-electronics inventory). If each keeps its own copy of the sweep, one gets fixed and the other silently does not, and the two tools then DISAGREE about the same LAN while both look healthy. The attribution rule they must agree on is the reason to share, not the line count. The implementation is lifted VERBATIM from the proven, gate-green pctl_discover_scan / pctl_probe_host (nx_printer_ctl_lib), so behaviour is identical BY CONSTRUCTION rather than by re-derivation. SCALE LAW (learned the hard way, banked in the iot-printer-fabric memory): any organ that fans out network I/O over MCP must bound total latency to the tools-daemon fork-capture window, or the call returns status=0 with no output while working fine on the CLI. Three rules, all encoded below: 1. BATCHED -- fire all 254 non-blocking connects first, then poll. 2. POLL-UNTIL-RESOLVED -- a single poll() returns as soon as ANY fd is ready (the ~250 instantly-refused hosts) BEFORE a real host finishes its handshake, so it finds NOTHING. Mark each fd done as its revents fire (write 0xff over the pollfd fd bytes so poll ignores it next round) and keep polling the still-connecting ones. 3. TIGHT BUDGET -- responsive devices answer in <50ms; unreachable hosts must not be waited on. Open iff POLLOUT && !POLLERR && !POLLHUP. THE BATCH IS THE BOUND (2026-08-18, lane F box health -- MEASURED, not read). A later edit replaced the sweep's raw non-blocking connect with nx_connect_bounded, whose contract is to POLL THAT ONE FD for up to NX_CONN_DEFAULT_MS (6,000 ms) before returning. Inside the fire-all-254 loop that turned rule 1 into its opposite: 254 serial 6 s waits (~25 min per port, ~100 min per `monitor` run) with every socket held open until the final close loop. Live evidence: ~20 `nx_printer_ctl monitor` beat instances piled up at RSS 4 kB with FDSize 512 and fd counts rising 14-26 per minute (nx_leak_check fleet, full population), each one a sweep still stuck in its OPEN loop. Rules 1-3 above were correct; the primitive underneath them had been swapped for one whose OWN bound was the thing being batched away. FIX, by construction: the sweep issues the raw non-blocking connect (EINPROGRESS is

dependencies 2 imports · 3 importers

nx_syscalls.nx nx_connect.nx nx_lan_scan.nx nx_iot_ctl_gate.nx nx_iot_ctl_lib.nx nx_printer_ctl_lib.nx

imports: nx_syscalls.nxnx_connect.nx

imported by: nx_iot_ctl_gate.nxnx_iot_ctl_lib.nxnx_printer_ctl_lib.nx

structs

none

consts

49const LSCAN_MAGIC_65535: i64 = 65535
51const LSCAN_SOCK_NONBLOCK: i64 = 2048 // SOCK_NONBLOCK
52const LSCAN_POLLOUT: i64 = 4
53const LSCAN_POLLERR: i64 = 8
54const LSCAN_POLLHUP: i64 = 16
55const LSCAN_HOSTS: i64 = 254 // .1 .. .254 of a /24
56const LSCAN_ROUNDS: i64 = 12 // poll-until-resolved rounds
57const LSCAN_MIN_PER_MS: i64 = 5 // floor on the per-round poll slice
71const LSCAN_BUDGET_MS: i64 = 120

functions

75func lscan_parse_prefix(s: *u8) -> i64
114func lscan_parse_ip(s: *u8) -> i64
called by 1: main
152func lscan_parse_port(s: *u8) -> i64
called by 1: main
175func lscan_fill_sockaddr(addr: *u8, ipv4: i64, port: i64) -> i64
195func lscan_probe_host(ipv4: i64, port: i64, timeout_ms: i64) -> i64
212func lscan_sweep(base24: i64, port: i64, timeout_ms: i64, out_ips: *i64, max: i64) -> i64