Skip to content

Device identity and naming

A device in Nyxtrace is not a MAC address and not an IP address. It is an identity cluster: a set of weighted anchors observed over time, with the evidence for each one kept.

That sounds abstract until you try the alternatives. Identify by IP and a DHCP lease change becomes a new device. Identify by MAC and every modern phone becomes a new device every few days. Either way the inventory fills with ghosts and every device is permanently inside its learning phase.

An anchor is one observation that says something about who a device is.

Class From Role
mac DHCP, controller, layer 2 Establishes
mac_random A MAC with the locally administered bit set Confirms only
mac_oui The vendor prefix of an administered MAC Confirms only
dhcp_hostname The host name in a DHCP request Establishes
dhcp_fingerprint The DHCP parameter and class fingerprint Confirms only
mdns A .local name Establishes
authoritative_map A name a person assigned in a controller Establishes
dot1x A RADIUS identity Establishes
duid, agent A DHCPv6 identifier, an endpoint agent Establishes
ja4 A TLS client fingerprint Confirms only
behavior Behavioural similarity Confirms only
recurrence The same anchors agreeing again on a separate day Confirms only

The split in that last column is the important one. A confirming class can raise confidence but can never establish an identity on its own. Vendor prefix, DHCP fingerprint and TLS fingerprint are shared by thousands of identical devices; treating one as an identity would merge every phone of the same model into one.

An address is a location, not a name. An hourly job resolves the name to show from every source that knows one, in a fixed priority, and the interface says which source it came from (name_source):

Priority Source name_source Confidence Where it comes from
1 Operator manual 1.00 A rename in the interface — PATCH /api/v1/devices/{id}
2 NetBox netbox 0.95 The NetBox connector: the device or virtual-machine name, else the host label of the address’s dns_name
3 Hypervisor hypervisor 0.90 The Proxmox VE connector’s guest record of the address: the guest name reduced to its host label
4 Authoritative inventory authoritative_map 0.90 The client name of a UniFi controller
5 DHCP host name dhcp 0.75 The DHCP leases connector’s lease record of the address, a lease the Pi-hole or AdGuard Home connector holds, and DHCP option 12 from the retained IDS events
6 mDNS name mdns 0.70 A .local name from the DNS events
7 Resolver resolver 0.60 The client name a Pi-hole or AdGuard Home holds for the address — its hostname resolution, its hosts file, or a client entry an operator typed there
8 Reverse DNS reverse_dns 0.55 A PTR record from the appliance’s own resolver
9 Address address 0.00 Nothing knew a name

The order deliberately puts the DHCP host name above the mDNS name, unlike the anchor weights above: identity asks which name survives a MAC rotation, naming asks which name an operator recognises, and the name a device asks to be called by is the one that was configured on the device itself.

NetBox ranks directly after the operator because a CMDB entry is a decision somebody made, not an observation; it still loses to a manual name, because the operator at the console knows things the CMDB may not yet. The interface labels such a name via NetBox.

The hypervisor’s guest name ranks next. The Proxmox VE connector writes every guest it inventories into the same record store NetBox uses, and the job turns the guest name into a candidate for each address the hypervisor proves for it. It ranks below NetBox because it is a decision about one machine rather than the estate, and above the controller map because the hypervisor holds the guest’s virtual NIC and knows the address is that guest’s — a controller only sees the MAC arrive. A guest name is a host label, not a typed name, so it becomes a DNS label (lower case, no spaces): Web-01 in Proxmox and a DHCP host name web-01 are one name. A guest the connector has not re-emitted for three full snapshots (72 h) — it was destroyed, or the instance stopped polling — stops naming the device, and the name stays as a former name. The interface labels such a name via hypervisor.

Every name goes through one normalisation, so a manual name and a DHCP host name cannot differ in what they are allowed to be: a host-name source yields a DNS label (lower case, no spaces, .local removed, so the same name from two sources is one name), while an operator-assigned name — manual, controller map, NetBox device name — keeps its case and its spaces, because “Jannik’s iPhone” is exactly the shape those sources produce. The one NetBox value that is a host name rather than a typed name, dns_name, becomes a label first (lower case, trailing dot and domain removed), so Kamera-01.home.example. in NetBox and a DHCP host name kamera-01 are one name. An address, a generic placeholder (localhost, unknown, android, …) and anything over 200 characters are never a name.

A device still named after its address gets upgraded the moment a proven host name appears. A name a human confirmed is never downgraded — it is an overlay that no later resolution overwrites, and renaming a device says nothing about what kind of device it is.

A resolver name ranks after mDNS and before reverse DNS. The Pi-hole and AdGuard Home connectors write every client they know into the same record store NetBox uses, and the job turns each into a candidate by where the resolver got the name: dhcp when the resolver is the DHCP server and holds the lease — that is the name the device asked for — and resolver for everything else. mDNS wins over it because the device said that about itself; it beats reverse DNS because the appliance’s own PTR lookup asks the same resolver one hop later and with less context. A hostname-shaped value becomes its label, so kamera-01.lan and a DHCP host name kamera-01 are one name; a label with spaces such as Kid's tablet is an operator’s label and keeps its shape. A client the connector has not re-listed for three days is a former name — the client list is re-emitted on every change and once an hour anyway, so three days of silence means the resolver forgot the client. The interface labels such a name via resolver.

A DHCP host name expires with its lease. The DHCP leases connector reads the server’s lease store — Kea memfile, ISC dhcpd.leases or the Kea Control Agent — and gives each active lease’s host name the lease end as its validity; a lease without an end is valid for 48 hours from its last observation, which the connector’s daily re-emission keeps renewing while the lease holds. Before the connector, and alongside it, the job also reads the DHCP exchanges the IDS saw cross the mirror, taking the last host name an address was announced with and the announced lease time as its validity, or 48 hours — two renewal cycles — when none was logged. The two dhcp sources merge into one alias per name with the wider window. A name from a finished lease stops being shown and stays listed as a former name, instead of quietly describing whoever holds that address now.

It is the only naming source that talks to the network, so it is limited in every direction: only addresses inside the tenant’s own VLAN ranges are asked about at all, a device a higher-priority source already named is not asked about, the timeout is 300 ms against the appliance’s own resolver over loopback, a token bucket allows 20 queries per second with a burst of 40 and at most 2,000 addresses per run, and answers are cached for an hour (absences for 15 minutes). A query the rate limit stopped is not cached as “no name”: a query that was never sent proves nothing. NXDOMAIN, an empty answer, a name that is itself an address and a generic name all mean the same thing — no name. The first label of the answer becomes the name (camera-01 from camera-01.home.example).

The job runs hourly, is read-only against everything that already exists, and writes only its own name overlay. It is on by default and NDR_NAMING_ENABLED=false switches it off.

A controller also reports which access point a client is on, which SSID, which switch port, which VLAN, the signal strength and whether it is wired. Those are facts about the device, recorded with their source and their age — not claims about its identity.

Keeping that line clean is what stops “two devices were on the same access point” from becoming “two devices are the same device”.

Every fact carries its source and its recency, and a contradiction is shown as a contradiction rather than resolved silently by whichever source wrote last. If DHCP says one host name and the controller says another, you see both, with who said what and when.

An unavailable source leaves its facts stale, never deleted. A controller outage does not erase what it told you yesterday, and no identity decision is made on stale data alone.

The device detail lists every anchor with its class, value, source, weight, first and last seen, the number of separate days it was observed, and two markers: randomized for a privacy address, and confirming for a class that cannot establish identity. Below that: every merge event with its rule, confidence and anchors, and any split that reverted one.

A confidence below 0.90 means the identity is derived, and the interface says so instead of presenting it as fact.