arpdvark

Experimental project — built to learn Go, TUI development, and raw networking. Intended for use on home networks only. Not hardened for production or adversarial environments.
A minimal, fast terminal-based network inventory tool. Scans your local network using ARP, identifies connected devices, resolves hostnames and MAC vendors, and displays results in a full-screen auto-refreshing TUI.
arpdvark • interface: eth0 • subnet: 192.168.1.0/24
IP Address MAC Address Hostname Label Vendor
192.168.1.1 30:1f:48:10:f3:04 router.home router zte corp.
192.168.1.2 a8:1d:16:31:a6:4f laptop.home AzureWave
192.168.1.5 98:e2:55:7f:8a:48 switch Nintendo
192.168.1.112 34:af:b3:82:16:95 echo.home echo Amazon Tech.
192.168.1.136 ea:03:65:53:c9:62 Local/Random.
5 device(s) • last scan: 2s ago • e: label r: rescan q: quit
Supported platforms: Linux (amd64, arm64)
Features
-
ARP scanning — discovers all active hosts on the local subnet using raw ARP packets
-
Hostname resolution — tries three methods in order: system DNS, gateway DNS (dnsmasq/home routers), mDNS unicast (Bonjour/Avahi)
-
MAC vendor lookup — identifies hardware vendors from the embedded IEEE OUI registry (~39k entries)
-
Locally administered MAC detection — flags randomized, VM-assigned, or manually set MACs as Local/Randomized (these have no OUI entry by design)
-
Full-screen TUI — fills the terminal, resizes dynamically, auto-refreshes on a configurable interval
-
Persistent device table — devices seen in previous scans remain visible; LastSeen, MAC, and vendor are updated on each round
-
Device persistence — scan results are saved to ~/.config/arpdvark/state.json across runs, tracking first-seen/last-seen timestamps and online/offline status; the TUI shows previously known devices immediately on startup
-
Host labels — assign custom names to any device; labels are keyed by MAC address and persist across restarts in ~/.config/arpdvark/tags.json
-
Multi-round first scan — the first ARP sweep sends 3 rounds of requests (100 ms apart) to catch slow responders such as Wi-Fi clients in power-save mode; subsequent scans send a single round since the device table accumulates across sweeps
-
Column sorting — use ←/→ to cycle sort column (IP, MAC, Hostname, Label, Vendor, Last Seen); press s to toggle ascending/descending
-
Device filtering — press / to filter the device table by any field; matches IP, MAC, hostname, label, and vendor; press / again to clear
-
New device alerts — devices seen for the first time (not in the state file from previous runs) are highlighted in green in the TUI, with a count in the status bar
-
Activity heatmap — the detail view shows a weekly activity pattern (7 days x 24 hours) built from scan history, using block characters to visualize when a device is typically connected; activity is recorded in all modes (TUI, --json, --once) and persisted to ~/.config/arpdvark/activity.json
-
Rate-limited scanning — ARP requests are rate-limited (1000 pkt/s for /24 and smaller, 5000 pkt/s for larger subnets) to avoid overwhelming switches or triggering IDS alerts
Installation
From source:
git clone https://github.com/fabioconcina/arpdvark
cd arpdvark
make build-all # produces dist/arpdvark-linux-amd64 and dist/arpdvark-linux-arm64
Copy the appropriate binary to your Linux machine and grant it network access:
sudo setcap cap_net_raw+ep /path/to/arpdvark
This only needs to be run once after each build or deploy. After that, arpdvark runs without sudo.
Usage
Usage: arpdvark [options]
arpdvark forget [--older-than N] [MAC ...]
Options:
-i <interface> Network interface to scan (default: auto-detect)
-t <seconds> Refresh interval in seconds (default: 10)
--large Allow scanning subnets larger than /16
--json Run one scan and output JSON to stdout
--once Run one scan and print a plain-text table to stdout
--all Include offline devices (--json and --once only)
--mcp Run as MCP server (stdio transport)
--notify-url URL POST to URL when new devices are detected (e.g. ntfy.sh topic)
--version Print version and exit
-h Show help
Interactive TUI (default)
# Auto-detect interface, refresh every 10s
arpdvark
# Scan a specific interface every 5 seconds
arpdvark -i eth0 -t 5
# Scan a large subnet (up to /8)
arpdvark -i eth0 --large
Key bindings:
| Key |
Action |
q / ctrl+c |
Quit |
r |
Force immediate rescan |
↑ / ↓ |
Navigate table rows |
e |
Edit label for selected row |
d |
Forget selected device (removes from state, tags, and activity) |
← / → |
Cycle sort column (IP, MAC, Hostname, Label, Vendor, Last Seen) |
s |
Toggle sort direction (ascending / descending) |
/ |
Open filter (Enter to apply, Esc to clear, / again to clear) |
o |
Toggle show/hide offline devices |
Enter |
Open device detail view |
Esc / Enter |
Close detail view / close filter / cancel label edit |
Detail view (Enter on a row): shows all device fields untruncated — IP, MAC, hostname, label, vendor, status, first seen, last seen — plus a weekly activity heatmap. Navigate fields with ↑/↓.
JSON output (--json)
arpdvark --json
arpdvark --json -i eth0
Runs a single scan, prints a JSON array to stdout, and exits. Suitable for piping to jq, scripts, or other tools.
[
{
"ip": "192.168.1.1",
"mac": "aa:bb:cc:dd:ee:ff",
"vendor": "Cisco Systems",
"hostname": "router.local",
"label": "main-router",
"first_seen": "2024-01-01T00:00:00Z",
"last_seen": "2024-01-01T00:00:00Z"
}
]
Use --all to include offline (previously seen) devices:
arpdvark --json --all
With --all, each device includes an "online" field (true/false).
Plain-text table (--once)
arpdvark --once
Runs a single scan, prints a tab-aligned text table to stdout, and exits. Parseable with awk/cut.
MCP server (--mcp)
arpdvark --mcp
Runs an MCP (Model Context Protocol) server on stdio, exposing a scan_network tool. This allows AI agents (e.g. Claude Desktop) to scan your network programmatically.
Claude Desktop configuration:
{
"mcpServers": {
"arpdvark": {
"command": "/path/to/arpdvark",
"args": ["--mcp"]
}
}
}
Forget devices
Remove specific devices from the state file, or prune devices not seen in a given number of days:
arpdvark forget aa:bb:cc:dd:ee:ff # remove one device by MAC
arpdvark forget --older-than 30 # remove devices unseen for 30+ days
Activity tracking
Every scan (TUI, --json, --once) records which devices are online, building a weekly activity heatmap visible in the detail view (Enter on a device). To get useful data, run periodic scans in the background with a cron job:
# Add to your crontab (crontab -e):
*/5 * * * * /path/to/arpdvark --once > /dev/null 2>&1
Activity data is stored in ~/.config/arpdvark/activity.json. The forget subcommand also removes activity data and labels for forgotten devices.
Notifications
Use --notify-url to get notified when new devices appear on the network. A POST request with a plain-text body is sent to the URL for each batch of new devices. Works with ntfy, Slack webhooks, or any endpoint that accepts POST requests.
arpdvark --notify-url https://ntfy.sh/my-network
arpdvark --once --notify-url https://ntfy.sh/my-network
Combine with a cron job for continuous monitoring:
*/5 * * * * /path/to/arpdvark --once --notify-url https://ntfy.sh/my-network > /dev/null 2>&1
Exit codes
Exit codes apply to --json and --once modes:
| Code |
Meaning |
| 0 |
Success (devices found) |
| 1 |
Error (permissions, interface not found, scan failure) |
| 2 |
Scan completed but no devices found |
Hostname resolution
Hostnames are resolved concurrently for all discovered devices after each ARP sweep. Three methods are tried in order:
- System resolver — uses
/etc/resolv.conf. Works if your DNS server (e.g. Pi-hole with DHCP enabled) maintains PTR records.
- Gateway DNS — queries the default gateway (read from
/proc/net/route, falling back to the first host in the subnet) directly on port 53. Home routers running dnsmasq create PTR records for DHCP leases automatically.
- mDNS unicast — sends a PTR query to the device's port 5353. Works for Apple devices (Bonjour) and Linux hosts running Avahi.
If all three fail (e.g. the device has a randomized MAC and no mDNS), the hostname column is left empty.
Vendor database
The bundled OUI database is embedded at build time from vendor_db/oui.txt. To refresh it with the latest IEEE data:
make update-oui
make build-all
MACs with the locally administered bit set (second least-significant bit of the first octet) are shown as Local/Randomized regardless of the OUI table — these addresses are self-assigned and have no registered manufacturer.
License
MIT