App Dev · · 4 min read
NetLay: The IP is not the device
Pinging an address works until DHCP hands it to somebody else, at which point a laptop looks down and a stranger's phone is wearing your printer's name.
The most persistent lie in network documentation#
Every device on the map has an IP address, and the whole application has quietly assumed that the address is the device.
It is not. The address is where the device happens to be living this week. On anything with DHCP in it, that lease expires, the address moves, and your map now pings something that is either nothing at all, in which case the laptop shows as down, or somebody else's phone, in which case the map tells you your printer is fine while it is in fact a phone.
So NetLay can now identify a device by its MAC address and look the current IP up, rather than the other way round.
Three kinds of address.#
Each device says which it is. Static, which means the address is the address and never moves. A DHCP reservation, which means it should always get the same address from the server. Or plain DHCP, where the address follows the MAC and is expected to wander.

The MAC comes from whatever scan placed the device, or you type it, or it gets learned from whatever answers on the device's address today.
Five places a MAC turns up.#
None of these need anything installed, which is the running theme of this project.
The local ARP table, which the machine already has, topped up with a ping sweep of its own subnets when a tracked MAC has gone missing, no more than twice an hour.
The ARP caches of routers and core switches over SNMP, which is the only source that sees across every routed VLAN rather than just the one the server is sitting in.
The results of any discovery scan from the last week.
Imported DHCP leases, which can be a Windows DHCP export, a dnsmasq file, an ISC lease file, a router's client list, or frankly any CSV with an address column and a MAC column. One function sniffs out which it is.
And DNS, last and least trusted, because DNS lags behind reality more often than anything else here.
Anything seen right now beats anything stored, the newest stored sighting wins, and DNS only gets a say when nothing else has an opinion.
What it does about it.#
A DHCP device that turns up on a new address gets moved, and the change is recorded in its history as the old address going to the new one.
A reservation never gets moved. If it is seen somewhere else, that is a warning, because the device is behaving correctly and the DHCP server is not doing what you told it.

And if a device's address answers with a MAC belonging to somebody else entirely, that is flagged, with the vendor of the unexpected MAC, because knowing an address has been taken over by something made by Hewlett Packard is a decent clue about what did it.
Events are only written when they differ from the last one of their kind, so the five minute loop does not fill your history with the same observation three hundred times a day.
The router that broke the first version.#
Testing against my own ARP table at home found the flaw immediately.
The router's MAC answered on its LAN address and on a second address on the other side of it. The first version of this code saw the MAC somewhere new and cheerfully moved the router there, which was wrong and extremely confusing on the map.
The rule now is that if a device still answers on its current address, it stays. A MAC appearing somewhere else as well is interesting, but it is not a move.
Duplicates, and why VLANs matter.#
Once you are tracking addresses, duplicates fall out for free. Two devices on a layout with the same address get a warning if they are in the same VLAN, and a gentler note if they are in different ones, because the same address in two genuinely separate networks is fine and extremely common.

Making that VLAN aware meant tagging SNMP ARP entries with the interface they were learned on, so an entry from Vlan10 is known to belong to VLAN 10 and a device is only ever compared against sightings from its own VLAN.
The first build of this blanked the entire page, which readers of post three will find familiar. Another state selector returning a fresh object every time it was read, another infinite render. The reason I caught it instantly this time is that the check runs in a real browser during development now, which is a habit this series has gradually taught me.
Next time, keeping the manual's pictures honest once the features have moved on.