Find a host by IP
When you need this
Section titled “When you need this”- “User reports their workstation can’t reach the printer. They know the IP, not the MAC.”
- “Address
10.1.2.99was on a server yesterday — what is it bound to now?” - “DHCP rebound an IP overnight. Who had it before?”
All three are IP-shaped questions that need to hop through ARP into the MAC/port world. The HISTORY screen does this in one step.
In the TUI
Section titled “In the TUI”Press Ctrl+H for HISTORY. The form has two address fields:
| Field | When to use |
|---|---|
MAC: | You know the MAC. Existing behaviour. |
IP: | You know the IP. Resolves through arp_observation to a MAC, then runs the same MAC history. |
Filling IP takes precedence — the MAC field is ignored if IP is set. Filling MAC alone keeps the existing pure-MAC search.
When IP is filled, the result has two parts:
-
IP banner at the top showing the current MAC binding plus any PRIOR bindings:
┃ 10.10.1.99 → 00:03:93:44:20:82 (via gnmi on sw-access-1)┃ prior: 00:00:f0:e7:ee:e7 (2026-05-09T08:50:58Z → 2026-05-10T07:50:58Z)The cyan-bordered block is the ARP context — “this IP currently resolves to alice-macbook, but yesterday it was on the Samsung phone.” That’s the DHCP-rebind story the bitemporal log captures.
-
MAC timeline below — same shape as the pure-MAC history.
In SQL
Section titled “In SQL”The lookup is arp_history(ip, audit_at=None) from db/queries.py:
SELECT mac, vrf, device_id, sources, lower(valid_during) AS first_seen, upper(valid_during) AS last_seen, upper_inf(valid_during) AS is_openFROM arp_observationWHERE ip = '10.10.1.99'::inet AND (recorded_during @> :audit_at OR :audit_at IS NULL)GROUP BY mac, vrf, device_id, valid_duringORDER BY first_seen ASC;The arp_no_overlap_per_source EXCLUDE constraint guarantees that
within a single source, no IP ever has two open bindings to different
MACs at the same wire-time — so the query above can’t surface a
“phantom” double-binding for one source.
Cross-source disagreement (gNMI and SNMP say different MACs for the
same IP) IS allowed by the schema. current_mac_for_ip() resolves it
via priority — gnmi > snmp > netconf > ssh.
In Python
Section titled “In Python”from l2trace.db.session import session_scopefrom l2trace.db.queries import arp_history, current_mac_for_ip
async with session_scope() as session: current = await current_mac_for_ip(session, "10.10.1.99") if current: print(f"now bound to {current.mac} on {current.device_hostname}") history = await arp_history(session, "10.10.1.99") for r in history: print(r.mac, r.first_seen, r.is_open)Trying it on the demo data
Section titled “Trying it on the demo data”After make seed-realistic, 10.10.1.99 is set up as a DHCP-rebound
address — held by one host on day 1, reassigned to another on day 2.
The IP search shows both bindings:
make tui# Ctrl+H → IP: 10.10.1.99 → EnterYou’ll see the prior+current binding pair in the IP banner, and the current MAC’s port history below.
See also
Section titled “See also”- Investigate a MAC’s history — the MAC-side equivalent
- Why bitemporal? — the same
valid_during/recorded_duringmodel applies to ARP rows too - The query source:
src/l2trace/db/queries.py::arp_history