From: Cedric Jehasse <[email protected]> For a static L2 multicast group that has both a host entry and a port entry, deleting the port entry also removes the host entry, and the whole group then disappears from "bridge mdb show".
To reproduce: bridge mdb add dev br0 port br0 grp 01:02:03:04:05:06 permanent bridge mdb add dev br0 port swp1 grp 01:02:03:04:05:06 permanent bridge mdb del dev br0 port swp1 grp 01:02:03:04:05:06 permanent bridge mdb show # the "port br0" host entry is gone, too br_multicast_del_pg() processes every non-(*,G) entry through the S,G path, which removes the port group from br->sg_port_tbl and then calls br_multicast_sg_del_exclude_ports(). L2 entries are stored in sg_port_tbl as well, so they take this path too. When the last port is removed in br_multicast_sg_del_exclude_ports it sets "sgmp->host_joined = false", clearing the host membership directly and bypassing br_multicast_host_leave(). With host_joined now false and no ports left, br_multicast_del_pg() arms the group timer and br_multicast_group_expired() tears down the whole mdb entry -- even though the host membership was explicitly and permanently configured from user space. Keep removing L2 port groups from sg_port_tbl, but skip the S,G EXCLUDE-mode handling for them. The host membership of an L2 group is managed solely via br_multicast_host_join() / br_multicast_host_leave(). Signed-off-by: Cedric Jehasse <[email protected]> --- For a static L2 multicast group that has both a host entry and a port entry, deleting the port entry also removes the host entry, and the whole group then disappears from "bridge mdb show". To reproduce: bridge mdb add dev br0 port br0 grp 01:02:03:04:05:06 permanent bridge mdb add dev br0 port swp1 grp 01:02:03:04:05:06 permanent bridge mdb del dev br0 port swp1 grp 01:02:03:04:05:06 permanent bridge mdb show # the "port br0" host entry is gone, too br_multicast_del_pg() processes every non-(*,G) entry through the S,G path, which removes the port group from br->sg_port_tbl and then calls br_multicast_sg_del_exclude_ports(). L2 entries are stored in sg_port_tbl as well, so they take this path too. When the last port is removed in br_multicast_sg_del_exclude_ports it sets "sgmp->host_joined = false", clearing the host membership directly and bypassing br_multicast_host_leave(). With host_joined now false and no ports left, br_multicast_del_pg() arms the group timer and br_multicast_group_expired() tears down the whole mdb entry -- even though the host membership was explicitly and permanently configured from user space. Keep removing L2 port groups from sg_port_tbl, but skip the S,G EXCLUDE-mode handling for them. The host membership of an L2 group is managed solely via br_multicast_host_join() / br_multicast_host_leave(). This fixes the issue, but i'd like a second opinion on if this is the correct way to fix it. --- net/bridge/br_multicast.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 881d866d687a..d718a6d1ddb1 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -816,7 +816,13 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, if (!br_multicast_is_star_g(&mp->addr)) { rhashtable_remove_fast(&br->sg_port_tbl, &pg->rhnode, br_sg_port_rht_params); - br_multicast_sg_del_exclude_ports(mp); + /* L2 entries share sg_port_tbl with S,G entries but have no + * *,G/S,G EXCLUDE-mode semantics; their host membership is + * managed explicitly via br_multicast_host_join()/leave() and + * must not be cleared here when the last port group is removed. + */ + if (!br_group_is_l2(&mp->addr)) + br_multicast_sg_del_exclude_ports(mp); } else { br_multicast_star_g_handle_mode(pg, MCAST_INCLUDE); } --- base-commit: 022bdd9c0d036863c4bacd1688b73c6be3001cee change-id: 20260609-mdb_l2_host_joined_fix-fb2de21580c7 Best regards, -- Cedric Jehasse <[email protected]>

