Take the TUI for a spin
The TUI is l2trace’s primary interface. Four modes accessed from a home
screen, with an audit-time picker at the top that’s always live: type
5m ago into it and every visible row on every screen re-queries for
“what we believed five minutes ago.”
Launch it after running through the first-traceroute tutorial:
make tuiThe home screen
Section titled “The home screen”Four tiles — TRACE, HISTORY, OPS, AUDIT. Keyboard shortcuts:
- Ctrl+T → TRACE mode
- Ctrl+H → HISTORY mode
- Ctrl+O → OPS mode
- Ctrl+U → AUDIT mode
- F1 or ? → per-screen help overlay
- Escape or Q → back to home
- j / k → vim-style cursor on lists and tables
The bar at the top of every screen is the as-of header. It accepts:
now(the default — live mode, dot is green)5m ago,2h ago,1d ago(relative)2026-05-10T14:42:00Z(ISO-8601)
Press Enter to commit. The status flips to ⏱ 2026-05-10T14:42:00+00:00
in amber, and every screen that watches App.audit_time re-queries.
TRACE — hop-by-hop path
Section titled “TRACE — hop-by-hop path”Fill in source MAC, destination MAC, and VLAN. The MAC inputs validate as
you type, and the manufacturer pops up next to each field the moment
your MAC matches a known OUI prefix — Apple, Inc. next to a 00:03:93:…
laptop, Cisco Systems, Inc next to a 00:1a:a1:… IP phone, etc.
(Requires make oui-refresh — see Refresh the OUI registry.)
A malformed MAC turns the field red:
When valid input is in place, the form looks like this:
Press Ctrl+R or click Run to execute. A successful trace
(alice-macbook → bob-macbook across sw-access-1 → sw-core-1 → sw-access-2):
Each row is one hop: the device, the ingress port (where the frame entered),
the egress port (where it leaves toward the next hop), the port role (access
vs trunk), and the source that vouched for that CAM entry. The termination
line below tells you why the trace stopped — reached, floods, dead-end,
loop, or max-hops.
Press Ctrl+R or click Run. The hop table populates with one row per
device on the path, showing in-port, out-port, port role (access / trunk),
and the source that vouched for that hop (gnmi, snmp, etc.).
Termination tells you why the trace stopped: reached (good), floods
(dst MAC not in CAM at some intermediate switch — frame would be flooded
there), dead-end (no adjacency to follow), loop (visited-set guard
fired), or max-hops (limit reached).
Change the as-of header while looking at a result — the trace re-runs at the new audit time without re-typing anything.
HISTORY — one MAC’s full timeline
Section titled “HISTORY — one MAC’s full timeline”Search by MAC (and optionally VLAN). The timeline shows every row from
mac_observation for that MAC, collapsed across sources (so a MAC seen
simultaneously by gNMI and SNMP at the same port appears as one row with
sources: gnmi, snmp).
Each timeline entry shows:
port @ device(with vendor label ifoui_vendoris populated)sources— which collectors saw itvalid:— the wire-time range, plusopen/closedstateobserved:— earliest device-clock observationbelieved:— earliest time the system recorded the beliefentry_ids:— the actual primary keys, useful when joining manually
The gap between observed: and believed: is the latency for that
row. Big gap = either a clock-skew event or a queue-dwell — see
quarantine for the difference.
OPS — live dashboard
Section titled “OPS — live dashboard”Three panes:
- Live FDB (left): tree-view of
device → port → MACsfor the current as-of time. Refresh withRor change the audit-time picker. - Disagreements (middle): rows where multiple sources point at different ports for the same (mac, vlan, device). Auto-refreshes every 10 seconds. This is the “what’s broken right now” pane — empty = good.
- Quarantine (right): live tail of NATS quarantine events.
Color-coded by reason: yellow for
device-skew, magenta forqueue-dwell, red forintegrity-error.
The disagreement view answers questions like “is gNMI lying to us about where MAC X is?” without needing to know which CLI to type at which switch. How to interpret the disagreements pane.
AUDIT — bidirectional LLDP
Section titled “AUDIT — bidirectional LLDP”One table. For every open adjacency row from A→B, checks whether B→A
exists too:
- ✗ no reverse — no peer observation back from any source. Real problem: one-way cable, LLDP disabled on peer, or port shutdown.
- △ source asym — link is bidirectional, but the same source only sees one direction. Telemetry asymmetry, not a cable issue — gNMI on one end hasn’t polled yet while SNMP on the other already has.
- ✓ — both directions confirmed by the same source.
Press Ctrl+R to re-run the audit; changing the as-of header re-fires
it automatically. This is the same query as l2trace audit-adjacencies
on the CLI — see Audit LLDP adjacencies for
the operator playbook, and How peer resolution works
for why pending unresolved rows are excluded.
Themes
Section titled “Themes”Three themes ship out of the box. Pick one via TUI_THEME in .env:
- default — terminal palette, green-on-near-black
- hacker — pure green-on-black, more aggressively monochrome
- amber — amber-on-black, “old vt100” feel
TUI_THEME=hackerSame TRACE screen, three themes (note this is the same SVG dimensions — Textual renders each theme deterministically from its CSS layer):
| default | hacker | amber |
|---|---|---|
And the OPS dashboard across themes — useful for showing what each one looks like with denser content:
| default | hacker | amber |
|---|---|---|
What’s next?
Section titled “What’s next?”- Investigate a real MAC: How to investigate a MAC’s history
- Spot collector disagreements: How to spot disagreements
- Try the audit-time picker: Querying past beliefs