Skip to content

Investigate a MAC's full history

  • “This printer keeps disappearing from the network for a few minutes at a time — what’s its actual port history?”
  • “Did host X really move between switches three times in an hour, or is one of our collectors lying?”
  • “Show me everything we know about MAC aa:bb:cc:11:22:33.”

Press Ctrl+H for HISTORY. Enter the MAC. Optionally filter by VLAN. Press Enter or click Search.

HISTORY screen with a MAC searched and a timeline of bitemporal entries rendered

Each timeline entry is one logical “this MAC was here for this window”. Cross-source observations get collapsed — if gNMI and SNMP both saw the MAC on the same port at the same time, they appear as one entry with sources: gnmi, snmp.

What each field means:

FieldDescription
Titleport @ device plus VLAN
sourcesWhich collectors observed this row
validWire-time range — when it was on the network. open = current, closed = aged out / moved
observedEarliest device-clock observation
believedEarliest time the system recorded this belief
entry_idsThe actual primary keys — useful when joining manually

The gap between observed and believed is the latency for that row. A big gap (~seconds OK; minutes is bad) signals either device-clock skew or queue-dwell. The quarantine pane tells you which.

If the OUI registry is populated, the summary at the bottom shows the vendor: Cisco Systems, Inc · 3 timeline entries · 1 open · 2 closed.

The query backing the HISTORY screen lives in db/queries.mac_history(). It does the cross-source collapse for you:

WITH q AS (
SELECT
mo.entry_id,
mo.device_id, d.hostname AS device_hostname,
mo.port_id, p.name AS port_name,
mo.vlan,
mo.source,
mo.observed_at,
mo.collector_emitted_at,
lower(mo.valid_during) AS valid_from,
upper(mo.valid_during) AS valid_to,
lower(mo.recorded_during) AS recorded_from
FROM mac_observation mo
JOIN device d ON d.id = mo.device_id
JOIN port p ON p.id = mo.port_id
WHERE mo.mac = :mac
AND (:vlan::int IS NULL OR mo.vlan = :vlan)
AND mo.recorded_during @> COALESCE(:audit_at, now())
ORDER BY mo.valid_during, mo.source
)
SELECT * FROM q;

audit_at is the belief-time. Pass NULL or now() for “current belief”.

from l2trace.db.session import session_scope
from l2trace.db.queries import mac_history
async with session_scope() as session:
rows = await mac_history(session, mac="aa:bb:cc:11:22:33", vlan=10)
for row in rows:
print(row.port_name, row.device_hostname, row.sources, row.is_open)