From: John Crispin Date: Thu, 12 Mar 2026 13:50:05 +0000 (+0100) Subject: lldpd: resolve bridge VLAN sub-interfaces to member ports X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c9945c203accce9dad4799ae4fddecefc49ea814;p=thirdparty%2Fopenwrt.git lldpd: resolve bridge VLAN sub-interfaces to member ports When bridge VLAN filtering is active, network_get_physdev resolves UCI interface names to the VLAN sub-interface (e.g. br-lanv0) rather than the bridge master. LLDP frames use reserved multicast (01:80:c2:00:00:0e) which bridges trap to member ports, so lldpd must listen on the physical ports to receive them. Detect when the resolved device is a sub-interface of a bridge and expand it to the list of bridge member ports instead. Signed-off-by: John Crispin --- diff --git a/package/network/services/lldpd/files/lldpd.init b/package/network/services/lldpd/files/lldpd.init index e9891389c26..50063a01bcc 100644 --- a/package/network/services/lldpd/files/lldpd.init +++ b/package/network/services/lldpd/files/lldpd.init @@ -123,6 +123,23 @@ get_config_restart_hash() { export -n "$var=$_hash" } +resolve_bridge_member_ports() { + local _lower _bridge _port _ports="" + + _lower=$(ls "/sys/class/net/$1/" 2>/dev/null | grep "^lower_" | head -1) + [ -n "$_lower" ] || return 1 + + _bridge="${_lower#lower_}" + [ -d "/sys/class/net/$_bridge/bridge" ] || return 1 + + for _port in $(ls "/sys/class/net/$_bridge/" 2>/dev/null | grep "^lower_"); do + append _ports "${_port#lower_}" "," + done + + [ -n "$_ports" ] || return 1 + echo "$_ports" +} + get_interface_csv() { local _ifaces config_get _ifaces "$2" "$3" @@ -134,7 +151,15 @@ get_interface_csv() { local _l2device="" if network_get_physdev _l2device "$_iface" || [ -e "/sys/class/net/$_iface" ]; then - append _ifnames "${_l2device:-$_iface}" "," + local _resolved="${_l2device:-$_iface}" + local _ports + # LLDP uses reserved multicast (01:80:c2:00:00:0e) that bridges + # trap rather than forward; trapped frames arrive on the member + # ports, not on bridge VLAN sub-interfaces, so resolve down to + # the individual bridge member ports + _ports=$(resolve_bridge_member_ports "$_resolved") + [ -n "$_ports" ] && _resolved="$_ports" + append _ifnames "$_resolved" "," else # Glob case (interface is e.g. '!eth1' or 'eth*' or '*') append _ifnames "$_iface" ","