Skip to content

Find a host by IP

  • “User reports their workstation can’t reach the printer. They know the IP, not the MAC.”
  • “Address 10.1.2.99 was 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.

Press Ctrl+H for HISTORY. The form has two address fields:

FieldWhen 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:

  1. 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.

  2. MAC timeline below — same shape as the pure-MAC history.

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_open
FROM arp_observation
WHERE ip = '10.10.1.99'::inet
AND (recorded_during @> :audit_at OR :audit_at IS NULL)
GROUP BY mac, vrf, device_id, valid_during
ORDER 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.

from l2trace.db.session import session_scope
from 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)

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:

Terminal window
make tui
# Ctrl+H → IP: 10.10.1.99 → Enter

You’ll see the prior+current binding pair in the IP banner, and the current MAC’s port history below.