Skip to content

Architecture

gsjonio edited this page Jul 13, 2026 · 3 revisions

Architecture

netwp is hexagonal (Ports & Adapters). The point of the design is that the domain logic never touches the operating system directly, so it stays fast to test and the same code runs on Windows and Linux.

The layers

cmd/netwp         composition root: CLI dispatch + adapter wiring
internal/core     pure domain: use cases + port interfaces (no OS/net imports)
internal/adapter  adapters that touch the OS/network (arpscan, icmpping,
                  netinfo, oui, tcpprobe, namelookup, wifi, ...)
internal/tui      terminal rendering: scan table, monitor, dashboard

internal/core holds the use cases (device discovery, classification, scan diffing, the connectivity doctor, Wake-on-LAN packet building) and one small port interface per capability (Scanner, Pinger, Prober, HostResolver, VendorLookup, ServiceScanner, and so on). It imports no net, os/exec, or syscall, only plain data types. That is what lets the whole domain run against fakes in tests without a real network card.

internal/adapter/* implements those ports against the real system. Each adapter is a small package; a few are platform-specific and selected at build time by Go build tags, never at runtime:

  • arpscan on Windows uses SendARP; on Linux a raw AF_PACKET socket.
  • icmpping on Windows uses IcmpSendEcho; on other platforms the ping tool.
  • ifacestat, netinfo, wifi similarly have per-OS implementations.

The core cannot tell which implementation it is talking to.

cmd/netwp is the composition root: it wires the concrete adapters into the core use cases and parses CLI arguments. It is deliberately thin.

internal/tui renders core types to the terminal, both the plain scan table and the bubbletea/lipgloss live views (monitor, dashboard).

How a scan flows

cmd builds a core.Discovery from the adapters
  -> Discovery.Run: ARP sweep, then per-host enrichment in parallel
       (hostname, vendor, open ports, ICMP RTT/TTL, mDNS services)
  -> Classify each host (services > ports > vendor; a manual pin wins)
  -> internal/tui renders the result

Dependencies point one way: cmd and tui depend on core; core depends on nobody. Adapters depend on core only for its port interfaces and data types.

Why two table renderers

internal/tui has two ways to render a device table on purpose. The plain scan output uses raw ANSI escape codes, because lipgloss disables color outside a real TTY (which would make color assertions vacuous under go test). The live monitor/dashboard use lipgloss styles. Any new colored cell needs both variants.

Persistence

Small state lives as plain files under <user-config-dir>/netwp/, each behind a tiny store adapter that shares one generic MAC-keyed JSON map (macstore): aliases.json, classoverride.json, watchlist.json, plus the lastscan.json cache and the events.jsonl log. See the Command Reference.

For the contributor-facing version and package-boundary rules, see CONTRIBUTING.md.

Clone this wiki locally