Skip to content

MAC rotation

Modern phones and laptops rotate their MAC address — per network, and often per reconnection. Without handling that, a phone becomes a new device every few days, the inventory fills with ghosts, and no device ever finishes its learning phase.

The identity model exists to solve exactly this. It runs as an hourly job and is behind a feature flag, off by default, so an existing installation keeps its behaviour until an operator turns it on.

Confidence over the shared, temporally plausible anchors, combined as 1 - product(1 - weight):

Confidence Result
≥ 0.90 Merge. The two identities are one device.
0.60 – 0.89 Candidate. Reported for review, never executed.
< 0.60 Stays separate.

Two additions to that rule are deliberate:

An exemplar-unique class resolves directly. If both identities carry the same agent, dot1x, duid or mac anchor, that is not a probability question — it is the same device, and it resolves without asking for a merge confidence.

Repeated agreement on separate days counts. The same anchors agreeing again on a different day contributes a confirming recurrence term, at most twice. In practice that means a host name plus a DHCP fingerprint reaches the threshold after three days of consistent agreement — evidence accumulated over time, rather than a single coincidence.

Unconditional, regardless of confidence:

  • Never across tenants.
  • Never across separated VLAN classes. An IoT device and a workstation are not the same device just because their anchors look alike.
  • Never when two different anchors of an exemplar-unique class are active at the same time. Two distinct MAC addresses in use simultaneously means two devices, whatever else agrees.

The earlier first-seen timestamp. This is the whole point: a rotating device must not restart its learning phase every few days.

An immutable event. The rule that fired, the confidence, the anchors, and who or what triggered it.

The dissolved identifier, as an alias. Old links keep working; nothing is deleted.

If a merge was wrong, it can be reverted — exactly one merge at a time:

  • The dissolved identity returns with the anchors and addresses that actually came from it. An anchor that both identities had observed independently stays with both, because it was genuinely theirs.
  • The reverting event is recorded.
  • A block stops that same anchor combination from merging these two identities again.

That block is scoped to the pair on purpose. Blocking the anchor value alone would strip a rotating device of its only founding anchor, and it would then start a brand-new identity on every single observation — a worse failure than the bad merge it was meant to fix.

Nothing is deleted by a split.

An address is only followed to the device that held it while the identity was observed. So a reassigned address never inherits another device’s history, and the same address at two different times resolves to the two different devices — rather than to whichever row the database happened to return first.

Each identity keeps at most 64 anchors per class, so a daily-rotating address cannot grow one identity without limit. The complete history stays in the append-only anchor table, which is also what makes the whole process replayable.

One job run processes at most 200,000 records and 50,000 names. Reaching the record budget holds the watermark at the last processed observation rather than skipping the tail, and both limits are logged as capacity lines — a backlog is visible, not silently dropped.

The job reads DHCP events from the firewall syslog feed — MAC, DHCP host name, a client identifier, and the parameter and class fingerprint — plus .local names from the DNS view, because mDNS names survive rotation.

Layer-2 addresses from the packet source are extracted and tested but deliberately not read yet, for the reason given in Zeek: in a routed or mirrored segment, the layer-2 address paired with an address from another subnet is the gateway’s, and every internal peer would resolve to the gateway’s identity. That needs a same-segment guard first.

Controller data is likewise not yet wired into the job, although the UniFi connector already produces the anchors for it.