From a84c09d9cd25ce811377a0d0f986dfd5da8c5730 Mon Sep 17 00:00:00 2001 From: dcstephenson Date: Fri, 18 Sep 2026 11:50:57 -0500 Subject: [PATCH] Trust directly-scanned mgmt IP over neighbor-reported LLDP value Some vendors (e.g. HP/Aruba ProCurve) advertise a per-port Management Address TLV that reflects whatever VLAN is routable out that link, not the switch's real management IP. A neighbor's LLDP report could therefore overwrite a switch's correct mgmt_ip with a wrong one. upsert_switch() now takes a trusted flag: a direct scan's mgmt_ip always wins, while a neighbor-reported one only fills in a blank value. Co-Authored-By: Claude Sonnet 5 --- db.py | 22 ++++++++++++++++++---- scanner.py | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/db.py b/db.py index 040fb12..17cdecb 100644 --- a/db.py +++ b/db.py @@ -82,21 +82,35 @@ def init_db(): conn.close() -def upsert_switch(chassis_id, hostname, mgmt_ip, description, firmware='', vendor='', serial=''): +def upsert_switch(chassis_id, hostname, mgmt_ip, description, firmware='', vendor='', serial='', trusted=False): + """ + trusted=True means mgmt_ip came from directly scanning this switch (its own + SSH session / local LLDP info) — always authoritative. trusted=False means + it came from another switch's LLDP neighbor report about this one: some + switches (e.g. HP/Aruba ProCurve) advertise a per-port Management Address + TLV that reflects whatever VLAN happens to be routable out that specific + link, not the switch's real management IP — so an untrusted mgmt_ip is + only used to fill in a still-unknown value, never to overwrite one already + established by a trusted (direct) scan. + """ conn = get_conn() conn.execute(""" INSERT INTO switches (chassis_id, hostname, mgmt_ip, description, firmware, vendor, serial, last_seen) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now')) ON CONFLICT(chassis_id) DO UPDATE SET hostname = excluded.hostname, - mgmt_ip = CASE WHEN excluded.mgmt_ip != '' AND excluded.mgmt_ip LIKE '%.%.%.%' - THEN excluded.mgmt_ip ELSE mgmt_ip END, + mgmt_ip = CASE + WHEN excluded.mgmt_ip = '' OR excluded.mgmt_ip NOT LIKE '%.%.%.%' THEN mgmt_ip + WHEN ? THEN excluded.mgmt_ip + WHEN mgmt_ip = '' OR mgmt_ip IS NULL THEN excluded.mgmt_ip + ELSE mgmt_ip + END, description = CASE WHEN excluded.description != '' THEN excluded.description ELSE description END, firmware = CASE WHEN excluded.firmware != '' THEN excluded.firmware ELSE firmware END, vendor = CASE WHEN excluded.vendor != '' THEN excluded.vendor ELSE vendor END, serial = CASE WHEN excluded.serial != '' THEN excluded.serial ELSE serial END, last_seen = excluded.last_seen - """, (chassis_id, hostname, mgmt_ip, description, firmware, vendor, serial)) + """, (chassis_id, hostname, mgmt_ip, description, firmware, vendor, serial, bool(trusted))) conn.commit() conn.close() diff --git a/scanner.py b/scanner.py index 821ad6f..40e6107 100644 --- a/scanner.py +++ b/scanner.py @@ -105,6 +105,7 @@ def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3): firmware=result.get("firmware", ""), vendor=result.get("vendor", ""), serial=result.get("serial", ""), + trusted=True, ) for neighbor in result["neighbors"]: