Passive LAN device discovery #9

Closed
opened 2026-07-12 20:11:03 -06:00 by jhodgkin · 2 comments
Owner

Read the ARP table (no active scanning) to list devices by IP/MAC, cross-referenced against ansible/inventory.yml in the homelab repo to flag known vs. unknown devices. Optional active nmap sweep as a later, opt-in enhancement — off by default.

Read the ARP table (no active scanning) to list devices by IP/MAC, cross-referenced against `ansible/inventory.yml` in the homelab repo to flag known vs. unknown devices. Optional active `nmap` sweep as a later, opt-in enhancement — off by default.
Author
Owner

Starting this one. Investigated the literal 'passive ARP table' approach from the original scope and it would be nearly useless: the api container's ARP cache only fills with devices it has actually talked to, which is almost nothing on its own. Confirmed with the user: doing a lightweight ICMP ping sweep (no port scanning) to populate the cache first, then reading it, matching how tools like NetAlertX do this.

Also: real ARP entries for the LAN live in the network namespace of the CT122 host, not inside an isolated Docker bridge network, so the sweep+read has to run at the CT122 host level (systemd timer), not inside the API container. Going with a small host-level script dropping JSON that the API reads — keeps container isolation intact instead of switching to network_mode: host, and matches the homelab's existing pattern of host-level cron/systemd jobs alongside Dockerized services (e.g. the rclone backups on CT105).

Starting this one. Investigated the literal 'passive ARP table' approach from the original scope and it would be nearly useless: the api container's ARP cache only fills with devices it has actually talked to, which is almost nothing on its own. Confirmed with the user: doing a lightweight ICMP ping sweep (no port scanning) to populate the cache first, then reading it, matching how tools like NetAlertX do this. Also: real ARP entries for the LAN live in the network namespace of the CT122 *host*, not inside an isolated Docker bridge network, so the sweep+read has to run at the CT122 host level (systemd timer), not inside the API container. Going with a small host-level script dropping JSON that the API reads — keeps container isolation intact instead of switching to `network_mode: host`, and matches the homelab's existing pattern of host-level cron/systemd jobs alongside Dockerized services (e.g. the rclone backups on CT105).
Author
Owner

Done and deployed. Systemd timer running on CT122 host every 5 min (ping sweep 192.168.1.1-254, ICMP only, then ip neigh show). Dashboard's new 'Network Devices' section verified live: 63 devices total, 24/26 configured known hosts matched (the 2 misses are expected — the dashboard doesn't ARP-neighbor itself, and CT114/keycloak is mid-decommission), plus 39 genuine unrecognized devices (phones, laptops, IoT, etc). Full writeup including the architecture reasoning (why this runs as a host-level systemd timer, not inside the Docker container) in docs/device-discovery.md.

Done and deployed. Systemd timer running on CT122 host every 5 min (ping sweep 192.168.1.1-254, ICMP only, then `ip neigh show`). Dashboard's new 'Network Devices' section verified live: 63 devices total, 24/26 configured known hosts matched (the 2 misses are expected — the dashboard doesn't ARP-neighbor itself, and CT114/keycloak is mid-decommission), plus 39 genuine unrecognized devices (phones, laptops, IoT, etc). Full writeup including the architecture reasoning (why this runs as a host-level systemd timer, not inside the Docker container) in `docs/device-discovery.md`.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jhodgkin/homelab-monitor#9