Skip to content

Your first traceroute

This tutorial gets you from a clean repo to a successful L2 traceroute and a populated TUI in about ten minutes. You’ll need Docker and make. No network gear required — the demo replays events that emulate two hosts moving between two switch ports.

  • Docker (with docker compose v2 — not docker-compose)
  • make
  • ~5 minutes for the initial container image build
  • git clone of the repo
Terminal window
cp .env.example .env
make up

make up starts three containers:

  • postgres (with the Apache AGE extension for graph projections)
  • nats (JetStream-enabled, the event bus)
  • reconciler (the state-machine + bitemporal writer, plus the compactor co-process)

Nothing binds to host ports. Everything inside the stack talks via the compose network; host-side tools (psql, the TUI, demo scripts) go through docker compose exec or docker compose run --rm.

You can verify with:

Terminal window
make ps

You should see all three as Up (healthy). If postgres takes more than 60 seconds to go healthy, check make logs — the AGE extension load is slow on first-boot.

Terminal window
make migrate

This applies all alembic revisions to head: the bitemporal schema (0001), the mac_observation_disagreement view (0002), the event-provenance columns (0003), and the oui_vendor lookup table (0004).

3. Seed the OUI registry (optional but nice)

Section titled “3. Seed the OUI registry (optional but nice)”
Terminal window
make oui-refresh

Pulls the three IEEE manufacturer-prefix registries (~5 MB total) and UPSERTs ~52k vendor rows. With this in place, the TUI will show Cisco Systems, Inc next to MAC 00:1A:A1:… instead of bare hex.

Skipping this is fine — the TUI gracefully degrades to bare-hex display when oui_vendor is empty.

Terminal window
make replay

This script:

  1. Seeds a one-device, two-port topology (sw1 with Eth1 and Eth2).
  2. Publishes three mac_learned events to NATS:
    • aa:bb:cc:11:22:33 learned on Eth1 (host h1 first appears)
    • aa:bb:cc:44:55:66 learned on Eth2 (host h2 first appears)
    • aa:bb:cc:11:22:33 learned on Eth2 (h1 moves to Eth2)

The reconciler picks them up automatically (it’s already running). The move from Eth1Eth2 triggers a forward-time valid_during close on the original row plus a new row at the new port — the bitemporal log records the move as an explicit event.

You should see output like:

[2/3] publishing 3 events to NATS...
→ mac_learned mac=aa:bb:cc:11:22:33 port=Eth1 event_id=019e1432-...
→ mac_learned mac=aa:bb:cc:44:55:66 port=Eth2 event_id=019e1432-...
→ mac_learned mac=aa:bb:cc:11:22:33 port=Eth2 event_id=019e1432-...

The demo’s two-port topology is simple enough that you can verify the pipeline worked with a single SQL query:

Terminal window
make psql

Then in psql:

SELECT mac, port_id, observed_at, upper_inf(valid_during) AS open
FROM mac_observation
WHERE mac IN ('aa:bb:cc:11:22:33', 'aa:bb:cc:44:55:66')
ORDER BY entry_id;

You should see three rows: aa:bb:cc:11:22:33 on port=1 (Eth1) with open=f (closed), then aa:bb:cc:44:55:66 on port=2 (Eth2) open=t, then aa:bb:cc:11:22:33 on port=2 (Eth2) open=t. The first row’s valid_during was closed when the move event landed.

You can also exercise the actual traceroute query from the CLI:

Terminal window
make trace SRC=aa:bb:cc:11:22:33 DST=aa:bb:cc:44:55:66 VLAN=10

For a one-device topology this is admittedly anticlimactic — both hosts are on the same switch, so the path is one hop. The interesting bit is that you can now also do:

Terminal window
make trace SRC=aa:bb:cc:11:22:33 DST=aa:bb:cc:44:55:66 VLAN=10 \
AS_OF=2026-05-10T22:07:00Z

Replace the timestamp with a moment between the second and third demo events. The trace will show aa:bb:cc:11:22:33 on Eth1 — the historical truth — even though the live state has it on Eth2. That’s the bitemporal “as of T” capability working.

After make replay lands data in the DB, the TUI’s HOME screen looks like this (audit time is “live”; press Ctrl+T for TRACE, Ctrl+H for HISTORY, Ctrl+O for OPS):

HOME screen with as-of header set to live

The next tutorial walks through each mode in turn.