fix(lldp): parse the v7 neighbour table, which uses a different command

get_lldp_neighbors() existed but spoke to the older CLI generation: it ran
`show lldp remote-device all` and expected FASTPATH-style `0/1` ports in
whitespace-separated columns. v7 firmware answers `Unknown command` — as it
does for `show lldp remote-device`, a bare `show lldp`, and the plural
`show lldp neighbors`. Only the singular works, though `show ?` lists `lldp`
plainly enough:

     Port |   Device ID       |     Port ID      |      SysName      | ...
     ---- + ----------------- + ---------------- + ----------------- + ...
       g9 | 10:01:02:44:37:26 |10:01:02:44:37:28 | pve-eze.eze.local | ...

Ports are named g9, and the columns are separated by `|` with irregular
padding, so splitting on whitespace tears the values apart. The port ID is
not always a port name either — a neighbour may identify its port by MAC.

_get_lldp_table() now branches on _detect_cli_v7() into a second parser, the
same shape get_mac_address_table() already uses for this divergence.
get_lldp_neighbors() itself is untouched.

Confirmed against a GS110TPv3 on 7.1.1.17: _detect_cli_v7() returns True
there, and the switch reports two neighbours it had never surfaced. Without
them NetOrk's map had nothing to draw for the site this switch serves, so
four APs and a firewall behind it stood unconnected.
This commit is contained in:
Christian Manivong
2026-08-18 15:18:56 +07:00
parent 5317af0be1
commit efe004a295
2 changed files with 126 additions and 1 deletions
+47 -1
View File
@@ -1052,7 +1052,9 @@ class NetgearSmartDriver(ConfigLifecycleMixin, SwitchDriver):
return neighbors
def _get_lldp_table(self) -> List[Dict]:
"""Parse ``show lldp remote-device all`` into a list of row dicts."""
"""Parse the LLDP neighbour table into a list of row dicts."""
if self._detect_cli_v7():
return self._get_lldp_table_v7()
output = self._send_command("show lldp remote-device all")
rows: List[Dict] = []
in_table = False
@@ -1086,6 +1088,50 @@ class NetgearSmartDriver(ConfigLifecycleMixin, SwitchDriver):
return rows
def _get_lldp_table_v7(self) -> List[Dict]:
"""Parse ``show lldp neighbor`` on v7 firmware.
The v7 CLI has neither ``show lldp remote-device all`` nor a bare
``show lldp`` — both answer "Unknown command". Only the singular
``neighbor`` works, and it lays the table out differently::
Port | Device ID | Port ID | SysName | Capabilities | TTL
---- + ----------------- + ---------------- + ----------------- + -------------- + -----
g9 | 10:01:02:44:37:26 |10:01:02:44:37:28 | pve-eze.eze.local | Bridge | 97
Ports are named ``g9`` rather than ``0/1``, and the columns are
separated by ``|`` with irregular padding — splitting on whitespace
would tear the values apart. The port ID is not always a port name; a
neighbour may identify its port by MAC.
"""
try:
output = self._send_paged_command("show lldp neighbor")
except Exception: # pragma: no cover - transport failure
return []
rows: List[Dict] = []
for line in output.splitlines():
if "|" not in line:
continue
cols = [c.strip() for c in line.split("|")]
if len(cols) < 4:
continue
port, chassis_id, remote_port, sysname = cols[0], cols[1], cols[2], cols[3]
if not port or port.lower() == "port" or set(port) <= set("-+ "):
continue
if not sysname and not remote_port:
continue
rows.append(
{
"local_port": port,
"remote_id": "",
"remote_chassis_id": chassis_id,
"port_id": remote_port,
"system_name": sysname,
}
)
return rows
def get_lldp_neighbors_detail(self, interface: str = "") -> Dict[str, List[Dict]]:
"""Return detailed LLDP neighbor information.