Audit LLDP adjacencies
When you need this
Section titled “When you need this”- “We replaced a fiber pair on the sw-edge-7 ↔ sw-core-1 trunk. Is LLDP bidirectional now?”
- “sw-core-1 isn’t showing up in any traceroute. Is the LLDP collection actually working both ways?”
- “We’re running gNMI primary + SNMP backup. Are they seeing the same fabric?”
In the TUI: press Ctrl+U from any screen (or click the AUDIT tile on home). One table with color-coded status per adjacency. Press Ctrl+R to re-run the audit; the as-of header re-fires it on audit-time change.
From the shell:
make audit-adjacenciesOr, with filters:
docker compose run --rm reconciler l2trace audit-adjacencies --source snmpdocker compose run --rm reconciler l2trace audit-adjacencies --show-healthyA green check means every open adjacency has a confirmed reverse observation. Red, yellow, or any non-empty table means investigate.
The three states
Section titled “The three states”For each open adjacency row from A→B, the audit checks whether B→A
exists. Three outcomes:
| Symbol | Meaning | What it usually indicates |
|---|---|---|
| ✓ | both directions seen by the same source | healthy |
| △ | bidirectional, but via DIFFERENT sources | telemetry asymmetry — link is fine, one source is lagging |
| ✗ | NO reverse observation from any source | one-way cable / LLDP disabled on peer / port shutdown |
Both has_reverse_same_source and has_reverse_any_source are
returned per row so operators can distinguish “actual fabric problem”
from “gNMI hasn’t polled yet”.
Reading the output
Section titled “Reading the output” adjacency audit┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━┳━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┓┃ status ┃ local ┃ port ┃ → ┃ peer ┃ peer port ┃ source ┃┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━╇━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━┩│ ✗ no reverse │ sw-edge-7 │ Eth8 │ → │ sw-core-1 │ Eth1 │ snmp ││ △ source asym │ sw-core-1 │ Eth2 │ → │ sw-edge-8 │ Eth8 │ gnmi │└───────────────┴───────────┴──────┴───┴───────────┴───────────┴────────┘1 adjacency rows with NO reverse observation1 adjacency rows with cross-source asymmetry-
The first row is a real problem: sw-edge-7 sees sw-core-1, but sw-core-1’s
adjacencytable has no entry pointing back at sw-edge-7 via SNMP (or any other source). Possible causes:- One-way cable damage (TX from edge works, RX from core broken)
- LLDP disabled on sw-core-1’s Eth1
- sw-core-1’s SNMP poll hasn’t run since the link came up
- sw-core-1 isn’t registered in l2trace at all
-
The second row is fine for connectivity but worth knowing: sw-core-1’s gNMI sees sw-edge-8, and sw-edge-8 sees sw-core-1 — but sw-edge-8’s gNMI hasn’t observed sw-core-1 yet (its SNMP collector did). The cable works; gNMI is behind on one end.
What the query actually does
Section titled “What the query actually does”audit_adjacencies in db/queries.py:
WITH open_adj AS ( SELECT entry_id, local_device_id, local_port_id, remote_chassis_id, remote_device_id, source, ... FROM adjacency WHERE upper_inf(valid_during) AND upper_inf(recorded_during) AND remote_device_id IS NOT NULL -- skip unresolved)SELECT *, EXISTS (SELECT 1 FROM open_adj r WHERE r.local_device_id = o.remote_device_id AND r.remote_device_id = o.local_device_id AND r.source = o.source) AS has_reverse_same_source, EXISTS (SELECT 1 FROM adjacency r ...) AS has_reverse_any_sourceFROM open_adj o;Two important constraints:
-
Only rows with
remote_device_id IS NOT NULLare audited. A row where the peer hasn’t been identified yet is pending, not broken — see How peer resolution works for why this distinction matters. Pending rows get retroactively resolved by the backfill path the next time the peer’s chassis_id is learned. -
The same-source check matches
r.source = o.sourceexactly. The any-source check doesn’t. The difference is what separates “telemetry hasn’t caught up” from “the link is actually broken.”
Recommended cadence
Section titled “Recommended cadence”Run the audit:
- Manually after any cable change to verify the new run.
- On a cron schedule — once an hour is plenty. Pipe failures into
your alerting:
17 * * * * cd ~/l2trace && make audit-adjacencies | grep -E 'no reverse|asym' | mail -s 'l2trace audit' netops@example
- As part of post-deploy validation when new switches come online.
See also
Section titled “See also”- How peer resolution works — the prerequisite that makes this audit possible
- Collect LLDP adjacencies — what
feeds the
adjacencytable - The query source:
src/l2trace/db/queries.py::audit_adjacencies