From 85041c13bbeee274aa89d0a16b95a9236d6aa5f8 Mon Sep 17 00:00:00 2001 From: Christian Manivong Date: Wed, 10 Jun 2026 00:23:33 +0200 Subject: [PATCH] fix: configure lldpd on bridge interface, not VLAN subinterface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added _lldpd_fix_interface() helper that detects the management interface via the default route and strips any .VID suffix (e.g. br-ap.10 → br-ap). LLDP is L2 and must run on the bridge itself — sending on a VLAN subinterface produces tagged frames the switch won't recognize as LLDP. The helper runs every poll so existing wrong configs (e.g. eth0 from the original install action) are corrected automatically on the next poll. Co-Authored-By: Claude Sonnet 4.6 --- napalm_openwrt/openwrt.py | 41 +++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/napalm_openwrt/openwrt.py b/napalm_openwrt/openwrt.py index 2079aa6..77fab83 100644 --- a/napalm_openwrt/openwrt.py +++ b/napalm_openwrt/openwrt.py @@ -493,6 +493,35 @@ class OpenWrtDriver(AccessPointDriver): ) return neighbors + def _lldpd_fix_interface(self) -> None: + """Ensure lldpd UCI config uses the actual management interface. + + Detects the interface carrying the default route (e.g. br-lan.10 on a + VLAN trunk) and replaces any stale/wrong interface entry in the lldpd + UCI config. Idempotent — only writes if the config needs updating. + """ + mgmt_iface = self._send_command( + "ip route show default 2>/dev/null | head -1 | grep -oE 'dev [^ ]+' | awk '{print $2}'" + ).strip() + if not mgmt_iface: + mgmt_iface = "br-lan" + # LLDP is a L2 protocol — it must run on the bridge, not a VLAN subinterface. + # Strip .VID suffix so br-ap.10 → br-ap. + if "." in mgmt_iface: + mgmt_iface = mgmt_iface.rsplit(".", 1)[0] + + current = self._send_command("uci get lldpd.config.interface 2>/dev/null").strip() + if mgmt_iface in current: + return # already correct + + # Reset the interface list to just the management interface + self._send_command( + "uci delete lldpd.config.interface 2>/dev/null; " + f"uci add_list lldpd.config.interface='{mgmt_iface}' 2>/dev/null; " + "uci commit lldpd 2>/dev/null; " + "/etc/init.d/lldpd restart 2>/dev/null" + ) + def _get_lldp_table(self) -> List[Dict]: """Parse ``lldpctl -f keyvalue`` into a list of row dicts. @@ -500,11 +529,12 @@ class OpenWrtDriver(AccessPointDriver): not already running the daemon needs time to discover neighbors so the first call after a fresh install will return an empty list. """ - # Enable + start lldpd if not already running (idempotent / silent) + # Ensure lldpd is running on the correct management interface self._send_command( "pgrep lldpd >/dev/null 2>&1 || " "(/etc/init.d/lldpd enable 2>/dev/null; /etc/init.d/lldpd start 2>/dev/null)" ) + self._lldpd_fix_interface() output = self._send_command("lldpctl -f keyvalue") rows: List[Dict] = [] @@ -1949,13 +1979,8 @@ class OpenWrtDriver(AccessPointDriver): raw = self._send_command("opkg install lldpd 2>&1") out = self._clean_pkg_output(raw) - # Add eth0 to lldpd UCI interface list (idempotent) - current_ifaces = self._send_command("uci get lldpd.config.interface 2>/dev/null").strip() - if "eth0" not in current_ifaces: - self._send_command( - "uci add_list lldpd.config.interface='eth0' 2>/dev/null; " - "uci commit lldpd 2>/dev/null" - ) + # Configure the correct management interface and enable the service + self._lldpd_fix_interface() # Enable and start the service self._send_command(