include/linux/netdevice.h | 1 + net/bridge/br.c | 5 +++++ net/bridge/br_multicast.c | 16 ++++++++++++++++ net/bridge/br_private.h | 1 + net/core/dev.c | 10 +++++----- net/ipv6/addrconf.c | 3 +++ 6 files changed, 31 insertions(+), 5 deletions(-)
Trigger the bridge to (re)start sending out Queries to the Host once
IPv6 address becomes valid.
In current implementation, once the bridge (interface) is brought up,
the bridge will start trying to send v4 and v6 Queries to the Host
immediately. However, at that time most likely the IPv6 address of
the bridge interface is not valid yet, and thus the send (actually
the alloc) operation will fail. So the first v6 Startup Query is
always missed.
This caused a ripple effect on the timing of Querier Election. In
current implementation, :: always wins the election. In order for
the "real" election to take place, the bridge would have to first
select itself (this happens when a v6 Query is successfully sent
to the Host), and then do the real address comparison when the next
Query is received. In worst cast scenario, the bridge would have to
wait for [Startup Query Interval] seconds (for the second Query to
be sent to the Host) plus [Query Interval] seconds (for the real
Querier to send the next Query) before it can recognize the real
Querier.
This patch adds a new notification NETDEV_NEWADDR when IPv6 address
becomes valid. When the bridge receives the notification, it will
restart the Startup Queries (much like how the bridge handles port
NETDEV_CHANGE events today).
Signed-off-by: Joseph Huang <Joseph.Huang@garmin.com>
---
include/linux/netdevice.h | 1 +
net/bridge/br.c | 5 +++++
net/bridge/br_multicast.c | 16 ++++++++++++++++
net/bridge/br_private.h | 1 +
net/core/dev.c | 10 +++++-----
net/ipv6/addrconf.c | 3 +++
6 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index f3a3b761abfb..27297e46e064 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3129,6 +3129,7 @@ enum netdev_cmd {
NETDEV_REGISTER,
NETDEV_UNREGISTER,
NETDEV_CHANGEMTU, /* notify after mtu change happened */
+ NETDEV_NEWADDR,
NETDEV_CHANGEADDR, /* notify after the address change */
NETDEV_PRE_CHANGEADDR, /* notify before the address change */
NETDEV_GOING_DOWN,
diff --git a/net/bridge/br.c b/net/bridge/br.c
index c683baa3847f..6f66965e8075 100644
--- a/net/bridge/br.c
+++ b/net/bridge/br.c
@@ -49,6 +49,11 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v
return NOTIFY_DONE;
}
+
+ if (event == NETDEV_NEWADDR) {
+ br_multicast_enable_host(netdev_priv(dev));
+ return NOTIFY_DONE;
+ }
}
if (is_vlan_dev(dev)) {
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 8ce145938b02..5a138c5731f5 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -2076,6 +2076,22 @@ static void br_multicast_enable(struct bridge_mcast_own_query *query)
mod_timer(&query->timer, jiffies);
}
+void br_multicast_enable_host(struct net_bridge *br)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ spin_lock_bh(&br->multicast_lock);
+
+ if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
+ !netif_running(br->dev))
+ goto out;
+
+ br_multicast_enable(&br->multicast_ctx.ip6_own_query);
+
+out:
+ spin_unlock_bh(&br->multicast_lock);
+#endif
+}
+
static void __br_multicast_enable_port_ctx(struct net_bridge_mcast_port *pmctx)
{
struct net_bridge *br = pmctx->port->br;
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 8de0904b9627..16864286dc0d 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -967,6 +967,7 @@ br_mdb_entry_skb_get(struct net_bridge_mcast *brmctx, struct sk_buff *skb,
u16 vid);
int br_multicast_add_port(struct net_bridge_port *port);
void br_multicast_del_port(struct net_bridge_port *port);
+void br_multicast_enable_host(struct net_bridge *br);
void br_multicast_enable_port(struct net_bridge_port *port);
void br_multicast_disable_port(struct net_bridge_port *port);
void br_multicast_init(struct net_bridge *br);
diff --git a/net/core/dev.c b/net/core/dev.c
index 93a25d87b86b..70a9f379f003 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1843,11 +1843,11 @@ const char *netdev_cmd_to_name(enum netdev_cmd cmd)
return "NETDEV_" __stringify(val);
switch (cmd) {
N(UP) N(DOWN) N(REBOOT) N(CHANGE) N(REGISTER) N(UNREGISTER)
- N(CHANGEMTU) N(CHANGEADDR) N(GOING_DOWN) N(CHANGENAME) N(FEAT_CHANGE)
- N(BONDING_FAILOVER) N(PRE_UP) N(PRE_TYPE_CHANGE) N(POST_TYPE_CHANGE)
- N(POST_INIT) N(PRE_UNINIT) N(RELEASE) N(NOTIFY_PEERS) N(JOIN)
- N(CHANGEUPPER) N(RESEND_IGMP) N(PRECHANGEMTU) N(CHANGEINFODATA)
- N(BONDING_INFO) N(PRECHANGEUPPER) N(CHANGELOWERSTATE)
+ N(CHANGEMTU) N(NEWADDR) N(CHANGEADDR) N(GOING_DOWN) N(CHANGENAME)
+ N(FEAT_CHANGE) N(BONDING_FAILOVER) N(PRE_UP) N(PRE_TYPE_CHANGE)
+ N(POST_TYPE_CHANGE) N(POST_INIT) N(PRE_UNINIT) N(RELEASE)
+ N(NOTIFY_PEERS) N(JOIN) N(CHANGEUPPER) N(RESEND_IGMP) N(PRECHANGEMTU)
+ N(CHANGEINFODATA) N(BONDING_INFO) N(PRECHANGEUPPER) N(CHANGELOWERSTATE)
N(UDP_TUNNEL_PUSH_INFO) N(UDP_TUNNEL_DROP_INFO) N(CHANGE_TX_QUEUE_LEN)
N(CVLAN_FILTER_PUSH_INFO) N(CVLAN_FILTER_DROP_INFO)
N(SVLAN_FILTER_PUSH_INFO) N(SVLAN_FILTER_DROP_INFO)
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index f17a5dd4789f..785952377d69 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -6292,6 +6292,9 @@ static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
addrconf_prefix_route(&ifp->peer_addr, 128,
ifp->rt_priority, ifp->idev->dev,
0, 0, GFP_ATOMIC);
+
+ call_netdevice_notifiers(NETDEV_NEWADDR, ifp->idev->dev);
+
break;
case RTM_DELADDR:
if (ifp->idev->cnf.forwarding)
--
2.50.1
________________________________
CONFIDENTIALITY NOTICE: This email and any attachments are for the sole use of the intended recipient(s) and contain information that may be Garmin confidential and/or Garmin legally privileged. If you have received this email in error, please notify the sender by reply email and delete the message. Any disclosure, copying, distribution or use of this communication (including attachments) by someone other than the intended recipient is prohibited. Thank you.
On Fri, Sep 12, 2025 at 06:39:30PM -0400, Joseph Huang wrote: > Trigger the bridge to (re)start sending out Queries to the Host once > IPv6 address becomes valid. > > In current implementation, once the bridge (interface) is brought up, > the bridge will start trying to send v4 and v6 Queries to the Host > immediately. However, at that time most likely the IPv6 address of > the bridge interface is not valid yet, and thus the send (actually > the alloc) operation will fail. So the first v6 Startup Query is > always missed. > > This caused a ripple effect on the timing of Querier Election. In > current implementation, :: always wins the election. In order for > the "real" election to take place, the bridge would have to first > select itself (this happens when a v6 Query is successfully sent > to the Host), and then do the real address comparison when the next > Query is received. In worst cast scenario, the bridge would have to > wait for [Startup Query Interval] seconds (for the second Query to > be sent to the Host) plus [Query Interval] seconds (for the real > Querier to send the next Query) before it can recognize the real > Querier. > > This patch adds a new notification NETDEV_NEWADDR when IPv6 address > becomes valid. When the bridge receives the notification, it will > restart the Startup Queries (much like how the bridge handles port > NETDEV_CHANGE events today). > > Signed-off-by: Joseph Huang <Joseph.Huang@garmin.com> > --- > include/linux/netdevice.h | 1 + > net/bridge/br.c | 5 +++++ > net/bridge/br_multicast.c | 16 ++++++++++++++++ > net/bridge/br_private.h | 1 + > net/core/dev.c | 10 +++++----- > net/ipv6/addrconf.c | 3 +++ > 6 files changed, 31 insertions(+), 5 deletions(-) A few comments: 1. The confidentiality footer needs to be removed. 2. Patches targeted at net need to have a Fixes tag. If you cannot identify a commit before which this worked correctly (i.e., it's not a regression), then target the patch at net-next instead. 3. The commit message needs to describe the user visible changes. My understanding is as follows: When the bridge is brought administratively up it will try to send a General Query which requires an IPv6 link-local address to be configured on the bridge device. Because of DAD, such an address might not exist right away, which means that the first General Query will be sent after "mcast_startup_query_interval" seconds. During this time the bridge will be unaware of multicast listeners that joined before the creation of the bridge. Therefore, the bridge will either unnecessarily flood multicast traffic to all the bridge ports or just to those marked as router ports. The patch aims to reduce this time period and send a General Query as soon as the bridge is assigned an IPv6 link-local address. 4. Use imperative mood: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes 5. There is already a notification chain that notifies about addition / deletion of IPv6 addresses. See register_inet6addr_notifier(). 6. Please extend bridge_mld.sh with a test case in a separate patch. You can look at xstats to see if queries were sent or not. See for example: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aea45363e29dd16050e6ce333ce0d3696ac3b5a9 Thanks
On 9/13/2025 2:23 PM, Ido Schimmel wrote: > On Fri, Sep 12, 2025 at 06:39:30PM -0400, Joseph Huang wrote: >> Trigger the bridge to (re)start sending out Queries to the Host once >> IPv6 address becomes valid. >> >> In current implementation, once the bridge (interface) is brought up, >> the bridge will start trying to send v4 and v6 Queries to the Host >> immediately. However, at that time most likely the IPv6 address of >> the bridge interface is not valid yet, and thus the send (actually >> the alloc) operation will fail. So the first v6 Startup Query is >> always missed. >> >> This caused a ripple effect on the timing of Querier Election. In >> current implementation, :: always wins the election. In order for >> the "real" election to take place, the bridge would have to first >> select itself (this happens when a v6 Query is successfully sent >> to the Host), and then do the real address comparison when the next >> Query is received. In worst cast scenario, the bridge would have to >> wait for [Startup Query Interval] seconds (for the second Query to >> be sent to the Host) plus [Query Interval] seconds (for the real >> Querier to send the next Query) before it can recognize the real >> Querier. >> >> This patch adds a new notification NETDEV_NEWADDR when IPv6 address >> becomes valid. When the bridge receives the notification, it will >> restart the Startup Queries (much like how the bridge handles port >> NETDEV_CHANGE events today). >> >> Signed-off-by: Joseph Huang <Joseph.Huang@garmin.com> >> --- >> include/linux/netdevice.h | 1 + >> net/bridge/br.c | 5 +++++ >> net/bridge/br_multicast.c | 16 ++++++++++++++++ >> net/bridge/br_private.h | 1 + >> net/core/dev.c | 10 +++++----- >> net/ipv6/addrconf.c | 3 +++ >> 6 files changed, 31 insertions(+), 5 deletions(-) > > A few comments: > > 1. The confidentiality footer needs to be removed. > > 2. Patches targeted at net need to have a Fixes tag. If you cannot > identify a commit before which this worked correctly (i.e., it's not a > regression), then target the patch at net-next instead. > > 3. The commit message needs to describe the user visible changes. My > understanding is as follows: When the bridge is brought administratively > up it will try to send a General Query which requires an IPv6 link-local > address to be configured on the bridge device. Because of DAD, such an > address might not exist right away, which means that the first General > Query will be sent after "mcast_startup_query_interval" seconds. > > During this time the bridge will be unaware of multicast listeners that > joined before the creation of the bridge. Therefore, the bridge will > either unnecessarily flood multicast traffic to all the bridge ports or > just to those marked as router ports. > > The patch aims to reduce this time period and send a General Query as > soon as the bridge is assigned an IPv6 link-local address. > > 4. Use imperative mood: > https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes > > 5. There is already a notification chain that notifies about addition / > deletion of IPv6 addresses. See register_inet6addr_notifier(). > It seems that inet6addr_notifier_call_chain() can be called when the address is still tentative, which means br_ip6_multicast_alloc_query() is still going to fail (br_ip6_multicast_alloc_query() calls ipv6_dev_get_saddr(), which calls __ipv6_dev_get_saddr(), which does not consider tentative source addresses). What the bridge needs really is a notification after DAD is completed, but I couldn't find such notification. Or did you mean reusing the same notification inet6addr_notifier_call_chain() but with a new event after DAD is completed? Thanks, Joseph
+ Linus Lüssing Original patch: https://lore.kernel.org/netdev/20250912223937.1363559-1-Joseph.Huang@garmin.com/ On Mon, Sep 15, 2025 at 06:41:19PM -0400, Huang, Joseph wrote: > It seems that inet6addr_notifier_call_chain() can be called when the address > is still tentative, which means br_ip6_multicast_alloc_query() is still > going to fail (br_ip6_multicast_alloc_query() calls ipv6_dev_get_saddr(), > which calls __ipv6_dev_get_saddr(), which does not consider tentative source > addresses). > > What the bridge needs really is a notification after DAD is completed, but I > couldn't find such notification. Or did you mean reusing the same > notification inet6addr_notifier_call_chain() but with a new event after DAD > is completed? Adding a new event to the inet6addr notification chain makes more sense than adding a new event to the netdev notification chain. But before making changes, I want to better understand the problem you are seeing. Is it specific to the offloaded data path? I believe the problem was fixed in the software data path by this commit: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0888d5f3c0f183ea6177355752ada433d370ac89 And Linus is working [1][2] on reflecting it to device drivers so that the hardware data path will act like the software data path and flood unregistered multicast traffic to all the ports as long as no querier was detected. As a temporary workaround, you can either configure an IPv6 link-local address on the bridge before opening it: # ip -6 address add fe80::1/64 dev br0 nodad Or enable optimistic DAD: # sysctl -wq net.ipv6.conf.br0.optimistic_dad=1 [1] https://lore.kernel.org/netdev/20250522195952.29265-1-linus.luessing@c0d3.blue/ [2] https://lore.kernel.org/netdev/20250829085724.24230-1-linus.luessing@c0d3.blue/
© 2016 - 2025 Red Hat, Inc.