Network Discovery (Sonar)
Live network map with active scanning and admin tools — 75+ devices across Milky Way, Andromeda (via Tailscale), mesh nodes, and external hosts. Powered by Sonar container.
Network Discovery (Sonar)
The network discovery module provides a real-time device inventory across the entire homelab infrastructure, powered by the Sonar scanner container on Altair-Link. It combines passive API queries with active scanning (nmap, arp-scan) and includes on-demand network admin tools — all accessible from /admin/network-map.
Network Map (/admin/network-map)
The main page renders a grid of glass-style device cards, each showing:
- Status dot — green (online), red (offline), or amber (unknown)
- Device name — friendly name from known_devices.json, DHCP hostname, or raw IP
- IP address — local network IP or Tailscale IP
- MAC address — with OUI vendor identification
- Type badge — router, switch, wap, workstation, hypervisor, nas, drone, vps, etc.
- Connection type — Wired, 2.4GHz, 5GHz, 6GHz (ASUS clients only)
- Subnet router badge — for Tailscale nodes advertising routes
- Last seen — relative timestamp
- Action buttons — Port Scan, Traceroute, DNS Lookup (pre-fills tools drawer)
Filter Tabs
Five tabs across the top filter the device grid by network:
- All — every device from every source
- Milky Way — 10.42.0.0/24 devices (cyan accent)
- Andromeda — 192.168.20.0/24 devices via Tailscale routing (purple accent)
- Tailscale — VPN mesh nodes (violet accent)
- External — Sentinel VPS and other external probes (orange accent)
Search
The search bar filters across hostname, IP, MAC, vendor, and device type. Filtering is instant (client-side, no API call).
Stats Bar
A stats row at the top shows: total device count, online count, offline count, and per-network device counts. These update on every poll cycle.
Scanner Status
The header shows a connection indicator (green dot = connected, red = disconnected), the timestamp of the last successful scan, and the scan duration in milliseconds. A refresh button triggers an immediate rescan via POST /api/net-scanner/trigger.
Auto-Refresh
The page polls GET /api/net-scanner/devices every 30 seconds using setInterval. The poll initializes on astro:page-load (View Transitions compatible) and clears on astro:before-swap. If the scanner is unreachable, the page falls back to cached data and shows a disconnected indicator.
Tools Drawer
A slide-in panel on the right side provides 5 network admin tools. Opened via the “Tools” button in the header, or by clicking action buttons on device cards.
Available Tools
| Tool | Endpoint | Description |
|---|---|---|
| Ping Sweep | POST /tools/ping-sweep | Sweep a subnet for live hosts (nmap -sn) |
| Port Scan | POST /tools/port-scan | Scan ports on a host (SYN or service detection) |
| Traceroute | POST /tools/traceroute | Route trace or MTR to a host |
| DNS Lookup | POST /tools/dns | DNS query (A, AAAA, MX, NS, CNAME, TXT, PTR, SOA, SRV) |
| Nmap Scan | POST /tools/nmap | Full nmap scan with configurable type and ports |
Tool Results
Results render as structured tables:
- Nmap/Port Scan — port, protocol, state (color-coded), service, version
- Traceroute — hop number, IP, latency or loss percentage
- DNS — record list
- Ping Sweep — host list with state
Device Card Actions
Each device card has three action buttons that pre-fill the tools drawer:
- Port Scan — opens tools with target IP and port-scan selected
- Traceroute — opens tools with target IP and traceroute selected
- DNS — opens tools with target IP/hostname and DNS selected
Sonar Scanner Service (Backend)
Sonar is a Docker container (Arcturus-Prime-sonar) running on Altair-Link (10.42.0.199) with host networking. It has full network visibility via the host’s LAN interface and Tailscale mesh.
- Image: Alpine 3.21 + Python 3.13 + network tools
- Container:
Arcturus-Prime-sonar,network_mode: host,CAP_NET_RAW+CAP_NET_ADMIN - Port: 8096 (bound on host)
- Tunnel:
playground-switch.Arcturus-Prime.com(Cloudflare tunnel on Altair-Link)
Data Sources (6 Scanners)
| Source | Network | Method | Interval | Data Provided |
|---|---|---|---|---|
| OPNsense | Milky Way (10.42.0.0/24) | REST API — ARP table + DHCP leases | 60s | IP, MAC, hostname, vendor, lease type |
| ARP scan | Milky Way (10.42.0.0/24) | arp-scan --localnet | 120s | IP, MAC, vendor |
| Nmap | Milky Way + Andromeda | nmap -sn ping sweep | 300s | IP, hostname, MAC (local only) |
| Tailscale | VPN mesh | tailscale status --json subprocess | 60s | Tailscale IP, hostname, OS, relay, subnet routes |
| VPS Probe | External | TCP connect to configured ports | 300s | Reachability, open ports |
| ASUS Router | Andromeda (192.168.20.0/24) | asusrouter library — client list | 120s | IP, MAC, hostname, WiFi band, signal |
Note: Nmap reaches the Andromeda network (192.168.20.0/24) via Tailscale subnet routing through Tarn-Host. This provides host discovery even without ASUS credentials.
Device Resolution
The scanner deduplicates devices by IP address, preferring the entry with more data. It then enriches results with known_devices.json — a manually maintained file mapping IPs and MACs to friendly names and device types. The resolver:
- Flattens results from all scanners
- Deduplicates by IP (keeps most complete entry)
- Enriches from known_devices.json (MAC match first, then IP)
- Sorts: online first, then by IP numerically
Network Classification
The nmap scanner classifies discovered hosts by IP range:
10.42.0.0/24→ Milky Way192.168.20.0/24→ Andromeda100.64.0.0/10→ Tailscale
Tools Security
All network tools use safe execution:
asyncio.create_subprocess_exec(never shell=True)- Strict regex validation for IPs, CIDRs, hostnames, ports
asyncio.Semaphore(3)concurrency limit- Hard timeouts (5-300s per tool)
- All behind API key authentication
Installed Tools
nmap (+ NSE scripts), masscan, arp-scan, mtr, traceroute, dig, whois, ping, iperf3, iproute2.
API Routes
The Arcturus-Prime frontend communicates through a proxy layer that injects the scanner API key server-side.
Proxy (Dev)
In development, the Vite dev server proxy in astro.config.mjs handles:
/api/net-scanner/* → 10.42.0.199:8096/api/scan/*
The proxy rewrites the path and injects X-Api-Key from NET_SCANNER_API_KEY env var.
SSR Route (Production)
In production, src/pages/api/net-scanner/[...path].ts handles the proxy:
- Checks admin auth via
resolveAuthState - Forwards to
NET_SCANNER_URL(defaulthttps://playground-switch.Arcturus-Prime.com) - Injects
X-Api-Keyfrom env - 120s timeout (tools can take time), no caching
- Wildcards all sub-paths including
/tools/*
Discovery Endpoints
| Method | Frontend Path | Scanner Path | Auth | Purpose |
|---|---|---|---|---|
| GET | /api/net-scanner/health | /api/scan/health | None | Scanner status + per-source health |
| GET | /api/net-scanner/devices | /api/scan/devices | API Key | Full device list, optional filters |
| GET | /api/net-scanner/summary | /api/scan/summary | API Key | Device counts by network |
| POST | /api/net-scanner/trigger | /api/scan/trigger | API Key | Force immediate rescan |
Tools Endpoints
| Method | Frontend Path | Scanner Path | Auth | Purpose |
|---|---|---|---|---|
| POST | /api/net-scanner/tools/nmap | /api/scan/tools/nmap | API Key | On-demand nmap scan |
| POST | /api/net-scanner/tools/traceroute | /api/scan/tools/traceroute | API Key | Traceroute or MTR |
| POST | /api/net-scanner/tools/dns | /api/scan/tools/dns | API Key | DNS lookup |
| POST | /api/net-scanner/tools/port-scan | /api/scan/tools/port-scan | API Key | Port scan |
| POST | /api/net-scanner/tools/ping-sweep | /api/scan/tools/ping-sweep | API Key | Ping sweep |
| GET | /api/net-scanner/tools/interfaces | /api/scan/tools/interfaces | API Key | Network interfaces |
Query parameters for /devices: ?network=milky-way|andromeda|tailscale|external and ?status=online|offline.
Module Configuration
The module manifest at src/config/modules/network-discovery.ts (v2.0.0) registers:
- Nav item: id
network-discovery, href/admin/network-map, iconfa-satellite-dish, groupinfra - Required env vars:
NET_SCANNER_URL,NET_SCANNER_API_KEY - Required role: admin
Environment Variables
Arcturus-Prime (CF Pages)
| Variable | Purpose | Default |
|---|---|---|
NET_SCANNER_URL | Sonar service URL | https://playground-switch.Arcturus-Prime.com |
NET_SCANNER_API_KEY | API key for scanner auth | (none) |
Sonar Container (.env)
| Variable | Purpose | Default |
|---|---|---|
OPNSENSE_HOST | OPNsense IP:port | 10.42.0.1 |
OPNSENSE_API_KEY | OPNsense API key | (disabled if empty) |
OPNSENSE_API_SECRET | OPNsense API secret | (disabled if empty) |
ASUS_HOST | ASUS router IP | 192.168.20.1 |
ASUS_USERNAME | Router admin user | (disabled if empty) |
ASUS_PASSWORD | Router admin password | (disabled if empty) |
VPS_HOST | Sentinel VPS IP | 178.156.247.186 |
VPS_PORTS | Ports to probe | 22,3001 |
API_KEY | Service auth token | changeme |
NMAP_ENABLED | Enable nmap scanner | true |
NMAP_SUBNETS | Local subnets to scan | 10.42.0.0/24 |
NMAP_TAILSCALE_SUBNETS | Remote subnets via Tailscale | 192.168.20.0/24 |
NMAP_INTERVAL | Nmap scan interval (seconds) | 300 |
ARP_ENABLED | Enable ARP scanner | true |
Deployment
Sonar runs as a Docker container on Altair-Link. To update:
cd ~/Development/Arcturus-Prime-net-scanner
scp Dockerfile docker-compose.yml requirements.txt known_devices.json [email protected]:/opt/Arcturus-Prime-sonar/
scp -r app/ [email protected]:/opt/Arcturus-Prime-sonar/app/
ssh [email protected] "cd /opt/Arcturus-Prime-sonar && sudo docker compose up -d --build"
Key Files
| File | Purpose |
|---|---|
src/pages/admin/network-map.astro | Admin page with device grid + tools drawer |
src/pages/api/net-scanner/[...path].ts | SSR proxy route |
src/config/modules/network-discovery.ts | Module manifest (v2.0.0) |
~/Development/Arcturus-Prime-net-scanner/ | Sonar service source |
known_devices.json | Device name/type overrides |
Known Issues
- ASUS Andromeda — GT-AXE16000 credentials not yet configured. Nmap provides IP-only discovery as a fallback; ASUS would add MAC addresses, hostnames, and WiFi band info for ~30-35 devices.
- Tailscale last_seen — Some nodes report
0001-01-01T00:00:00when they haven’t been seen yet. - OPNsense ARP format — Returns a plain list, not
{"rows": [...]}. Scanner handles both formats.