summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/linux/if_bridge.h8
-rw-r--r--include/uapi/linux/if_bridge.h2
-rw-r--r--net/bridge/br_forward.c5
-rw-r--r--net/bridge/br_input.c2
-rw-r--r--net/bridge/br_mdb.c57
-rw-r--r--net/bridge/br_multicast.c445
-rw-r--r--net/bridge/br_private.h67
7 files changed, 480 insertions, 106 deletions
diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
index 2cc35038a8ca..12e9a32dbca0 100644
--- a/include/linux/if_bridge.h
+++ b/include/linux/if_bridge.h
@@ -67,6 +67,7 @@ int br_multicast_list_adjacent(struct net_device *dev,
struct list_head *br_ip_list);
bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto);
bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto);
+bool br_multicast_has_router_adjacent(struct net_device *dev, int proto);
bool br_multicast_enabled(const struct net_device *dev);
bool br_multicast_router(const struct net_device *dev);
int br_mdb_replay(struct net_device *br_dev, struct net_device *dev,
@@ -87,6 +88,13 @@ static inline bool br_multicast_has_querier_adjacent(struct net_device *dev,
{
return false;
}
+
+static inline bool br_multicast_has_router_adjacent(struct net_device *dev,
+ int proto)
+{
+ return true;
+}
+
static inline bool br_multicast_enabled(const struct net_device *dev)
{
return false;
diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
index 13d59c51ef5b..6b56a7549531 100644
--- a/include/uapi/linux/if_bridge.h
+++ b/include/uapi/linux/if_bridge.h
@@ -627,6 +627,8 @@ enum {
MDBA_ROUTER_PATTR_UNSPEC,
MDBA_ROUTER_PATTR_TIMER,
MDBA_ROUTER_PATTR_TYPE,
+ MDBA_ROUTER_PATTR_INET_TIMER,
+ MDBA_ROUTER_PATTR_INET6_TIMER,
__MDBA_ROUTER_PATTR_MAX
};
#define MDBA_ROUTER_PATTR_MAX (__MDBA_ROUTER_PATTR_MAX - 1)
diff --git a/net/bridge/br_forward.c b/net/bridge/br_forward.c
index 6e9b049ae521..07856362538f 100644
--- a/net/bridge/br_forward.c
+++ b/net/bridge/br_forward.c
@@ -276,7 +276,8 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
bool allow_mode_include = true;
struct hlist_node *rp;
- rp = rcu_dereference(hlist_first_rcu(&br->router_list));
+ rp = br_multicast_get_first_rport_node(br, skb);
+
if (mdst) {
p = rcu_dereference(mdst->ports);
if (br_multicast_should_handle_mode(br, mdst->addr.proto) &&
@@ -290,7 +291,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
struct net_bridge_port *port, *lport, *rport;
lport = p ? p->key.port : NULL;
- rport = hlist_entry_safe(rp, struct net_bridge_port, rlist);
+ rport = br_multicast_rport_from_node_skb(rp, skb);
if ((unsigned long)lport > (unsigned long)rport) {
port = lport;
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 8875e953ac53..1f506309efa8 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -132,7 +132,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
br_multicast_querier_exists(br, eth_hdr(skb), mdst)) {
if ((mdst && mdst->host_joined) ||
- br_multicast_is_router(br)) {
+ br_multicast_is_router(br, skb)) {
local_rcv = true;
br->dev->stats.multicast++;
}
diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c
index 95fa4af0e8dd..3f839a8cc9fb 100644
--- a/net/bridge/br_mdb.c
+++ b/net/bridge/br_mdb.c
@@ -16,31 +16,76 @@
#include "br_private.h"
+static bool br_rports_have_mc_router(struct net_bridge *br)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ return !hlist_empty(&br->ip4_mc_router_list) ||
+ !hlist_empty(&br->ip6_mc_router_list);
+#else
+ return !hlist_empty(&br->ip4_mc_router_list);
+#endif
+}
+
+static bool
+br_ip4_rports_get_timer(struct net_bridge_port *port, unsigned long *timer)
+{
+ *timer = br_timer_value(&port->ip4_mc_router_timer);
+ return !hlist_unhashed(&port->ip4_rlist);
+}
+
+static bool
+br_ip6_rports_get_timer(struct net_bridge_port *port, unsigned long *timer)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ *timer = br_timer_value(&port->ip6_mc_router_timer);
+ return !hlist_unhashed(&port->ip6_rlist);
+#else
+ *timer = 0;
+ return false;
+#endif
+}
+
static int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
struct net_device *dev)
{
struct net_bridge *br = netdev_priv(dev);
- struct net_bridge_port *p;
+ bool have_ip4_mc_rtr, have_ip6_mc_rtr;
+ unsigned long ip4_timer, ip6_timer;
struct nlattr *nest, *port_nest;
+ struct net_bridge_port *p;
- if (!br->multicast_router || hlist_empty(&br->router_list))
+ if (!br->multicast_router)
+ return 0;
+
+ if (!br_rports_have_mc_router(br))
return 0;
nest = nla_nest_start_noflag(skb, MDBA_ROUTER);
if (nest == NULL)
return -EMSGSIZE;
- hlist_for_each_entry_rcu(p, &br->router_list, rlist) {
- if (!p)
+ list_for_each_entry_rcu(p, &br->port_list, list) {
+ have_ip4_mc_rtr = br_ip4_rports_get_timer(p, &ip4_timer);
+ have_ip6_mc_rtr = br_ip6_rports_get_timer(p, &ip6_timer);
+
+ if (!have_ip4_mc_rtr && !have_ip6_mc_rtr)
continue;
+
port_nest = nla_nest_start_noflag(skb, MDBA_ROUTER_PORT);
if (!port_nest)
goto fail;
+
if (nla_put_nohdr(skb, sizeof(u32), &p->dev->ifindex) ||
nla_put_u32(skb, MDBA_ROUTER_PATTR_TIMER,
- br_timer_value(&p->multicast_router_timer)) ||
+ max(ip4_timer, ip6_timer)) ||
nla_put_u8(skb, MDBA_ROUTER_PATTR_TYPE,
- p->multicast_router)) {
+ p->multicast_router) ||
+ (have_ip4_mc_rtr &&
+ nla_put_u32(skb, MDBA_ROUTER_PATTR_INET_TIMER,
+ ip4_timer)) ||
+ (have_ip6_mc_rtr &&
+ nla_put_u32(skb, MDBA_ROUTER_PATTR_INET6_TIMER,
+ ip6_timer))) {
nla_nest_cancel(skb, port_nest);
goto fail;
}
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 226bb05c3b42..0703725527b3 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -51,8 +51,8 @@ static const struct rhashtable_params br_sg_port_rht_params = {
static void br_multicast_start_querier(struct net_bridge *br,
struct bridge_mcast_own_query *query);
-static void br_multicast_add_router(struct net_bridge *br,
- struct net_bridge_port *port);
+static void br_ip4_multicast_add_router(struct net_bridge *br,
+ struct net_bridge_port *port);
static void br_ip4_multicast_leave_group(struct net_bridge *br,
struct net_bridge_port *port,
__be32 group,
@@ -60,8 +60,11 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
const unsigned char *src);
static void br_multicast_port_group_rexmit(struct timer_list *t);
-static void __del_port_router(struct net_bridge_port *p);
+static void
+br_multicast_rport_del_notify(struct net_bridge_port *p, bool deleted);
#if IS_ENABLED(CONFIG_IPV6)
+static void br_ip6_multicast_add_router(struct net_bridge *br,
+ struct net_bridge_port *port);
static void br_ip6_multicast_leave_group(struct net_bridge *br,
struct net_bridge_port *port,
const struct in6_addr *group,
@@ -1354,23 +1357,64 @@ static int br_ip6_multicast_add_group(struct net_bridge *br,
}
#endif
-static void br_multicast_router_expired(struct timer_list *t)
+static bool br_multicast_rport_del(struct hlist_node *rlist)
+{
+ if (hlist_unhashed(rlist))
+ return false;
+
+ hlist_del_init_rcu(rlist);
+ return true;
+}
+
+static bool br_ip4_multicast_rport_del(struct net_bridge_port *p)
+{
+ return br_multicast_rport_del(&p->ip4_rlist);
+}
+
+static bool br_ip6_multicast_rport_del(struct net_bridge_port *p)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ return br_multicast_rport_del(&p->ip6_rlist);
+#else
+ return false;
+#endif
+}
+
+static void br_multicast_router_expired(struct net_bridge_port *port,
+ struct timer_list *t,
+ struct hlist_node *rlist)
{
- struct net_bridge_port *port =
- from_timer(port, t, multicast_router_timer);
struct net_bridge *br = port->br;
+ bool del;
spin_lock(&br->multicast_lock);
if (port->multicast_router == MDB_RTR_TYPE_DISABLED ||
port->multicast_router == MDB_RTR_TYPE_PERM ||
- timer_pending(&port->multicast_router_timer))
+ timer_pending(t))
goto out;
- __del_port_router(port);
+ del = br_multicast_rport_del(rlist);
+ br_multicast_rport_del_notify(port, del);
out:
spin_unlock(&br->multicast_lock);
}
+static void br_ip4_multicast_router_expired(struct timer_list *t)
+{
+ struct net_bridge_port *port = from_timer(port, t, ip4_mc_router_timer);
+
+ br_multicast_router_expired(port, t, &port->ip4_rlist);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static void br_ip6_multicast_router_expired(struct timer_list *t)
+{
+ struct net_bridge_port *port = from_timer(port, t, ip6_mc_router_timer);
+
+ br_multicast_router_expired(port, t, &port->ip6_rlist);
+}
+#endif
+
static void br_mc_router_state_change(struct net_bridge *p,
bool is_mc_router)
{
@@ -1384,14 +1428,14 @@ static void br_mc_router_state_change(struct net_bridge *p,
switchdev_port_attr_set(p->dev, &attr, NULL);
}
-static void br_multicast_local_router_expired(struct timer_list *t)
+static void br_multicast_local_router_expired(struct net_bridge *br,
+ struct timer_list *timer)
{
- struct net_bridge *br = from_timer(br, t, multicast_router_timer);
-
spin_lock(&br->multicast_lock);
if (br->multicast_router == MDB_RTR_TYPE_DISABLED ||
br->multicast_router == MDB_RTR_TYPE_PERM ||
- timer_pending(&br->multicast_router_timer))
+ br_ip4_multicast_is_router(br) ||
+ br_ip6_multicast_is_router(br))
goto out;
br_mc_router_state_change(br, false);
@@ -1399,6 +1443,22 @@ out:
spin_unlock(&br->multicast_lock);
}
+static void br_ip4_multicast_local_router_expired(struct timer_list *t)
+{
+ struct net_bridge *br = from_timer(br, t, ip4_mc_router_timer);
+
+ br_multicast_local_router_expired(br, t);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static void br_ip6_multicast_local_router_expired(struct timer_list *t)
+{
+ struct net_bridge *br = from_timer(br, t, ip6_mc_router_timer);
+
+ br_multicast_local_router_expired(br, t);
+}
+#endif
+
static void br_multicast_querier_expired(struct net_bridge *br,
struct bridge_mcast_own_query *query)
{
@@ -1613,11 +1673,13 @@ int br_multicast_add_port(struct net_bridge_port *port)
port->multicast_router = MDB_RTR_TYPE_TEMP_QUERY;
port->multicast_eht_hosts_limit = BR_MCAST_DEFAULT_EHT_HOSTS_LIMIT;
- timer_setup(&port->multicast_router_timer,
- br_multicast_router_expired, 0);
+ timer_setup(&port->ip4_mc_router_timer,
+ br_ip4_multicast_router_expired, 0);
timer_setup(&port->ip4_own_query.timer,
br_ip4_multicast_port_query_expired, 0);
#if IS_ENABLED(CONFIG_IPV6)
+ timer_setup(&port->ip6_mc_router_timer,
+ br_ip6_multicast_router_expired, 0);
timer_setup(&port->ip6_own_query.timer,
br_ip6_multicast_port_query_expired, 0);
#endif
@@ -1649,7 +1711,10 @@ void br_multicast_del_port(struct net_bridge_port *port)
hlist_move_list(&br->mcast_gc_list, &deleted_head);
spin_unlock_bh(&br->multicast_lock);
br_multicast_gc(&deleted_head);
- del_timer_sync(&port->multicast_router_timer);
+ del_timer_sync(&port->ip4_mc_router_timer);
+#if IS_ENABLED(CONFIG_IPV6)
+ del_timer_sync(&port->ip6_mc_router_timer);
+#endif
free_percpu(port->mcast_stats);
}
@@ -1673,9 +1738,10 @@ static void __br_multicast_enable_port(struct net_bridge_port *port)
#if IS_ENABLED(CONFIG_IPV6)
br_multicast_enable(&port->ip6_own_query);
#endif
- if (port->multicast_router == MDB_RTR_TYPE_PERM &&
- hlist_unhashed(&port->rlist))
- br_multicast_add_router(br, port);
+ if (port->multicast_router == MDB_RTR_TYPE_PERM) {
+ br_ip4_multicast_add_router(br, port);
+ br_ip6_multicast_add_router(br, port);
+ }
}
void br_multicast_enable_port(struct net_bridge_port *port)
@@ -1692,19 +1758,22 @@ void br_multicast_disable_port(struct net_bridge_port *port)
struct net_bridge *br = port->br;
struct net_bridge_port_group *pg;
struct hlist_node *n;
+ bool del = false;
spin_lock(&br->multicast_lock);
hlist_for_each_entry_safe(pg, n, &port->mglist, mglist)
if (!(pg->flags & MDB_PG_FLAGS_PERMANENT))
br_multicast_find_del_pg(br, pg);
- __del_port_router(port);
-
- del_timer(&port->multicast_router_timer);
+ del |= br_ip4_multicast_rport_del(port);
+ del_timer(&port->ip4_mc_router_timer);
del_timer(&port->ip4_own_query.timer);
+ del |= br_ip6_multicast_rport_del(port);
#if IS_ENABLED(CONFIG_IPV6)
+ del_timer(&port->ip6_mc_router_timer);
del_timer(&port->ip6_own_query.timer);
#endif
+ br_multicast_rport_del_notify(port, del);
spin_unlock(&br->multicast_lock);
}
@@ -2615,22 +2684,6 @@ update:
}
#endif
-static bool br_multicast_select_querier(struct net_bridge *br,
- struct net_bridge_port *port,
- struct br_ip *saddr)
-{
- switch (saddr->proto) {
- case htons(ETH_P_IP):
- return br_ip4_multicast_select_querier(br, port, saddr->src.ip4);
-#if IS_ENABLED(CONFIG_IPV6)
- case htons(ETH_P_IPV6):
- return br_ip6_multicast_select_querier(br, port, &saddr->src.ip6);
-#endif
- }
-
- return false;
-}
-
static void
br_multicast_update_query_timer(struct net_bridge *br,
struct bridge_mcast_other_query *query,
@@ -2655,45 +2708,122 @@ static void br_port_mc_router_state_change(struct net_bridge_port *p,
switchdev_port_attr_set(p->dev, &attr, NULL);
}
-/*
- * Add port to router_list
+static struct net_bridge_port *
+br_multicast_rport_from_node(struct net_bridge *br,
+ struct hlist_head *mc_router_list,
+ struct hlist_node *rlist)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ if (mc_router_list == &br->ip6_mc_router_list)
+ return hlist_entry(rlist, struct net_bridge_port, ip6_rlist);
+#endif
+ return hlist_entry(rlist, struct net_bridge_port, ip4_rlist);
+}
+
+static struct hlist_node *
+br_multicast_get_rport_slot(struct net_bridge *br,
+ struct net_bridge_port *port,
+ struct hlist_head *mc_router_list)
+
+{
+ struct hlist_node *slot = NULL;
+ struct net_bridge_port *p;
+ struct hlist_node *rlist;
+
+ hlist_for_each(rlist, mc_router_list) {
+ p = br_multicast_rport_from_node(br, mc_router_list, rlist);
+
+ if ((unsigned long)port >= (unsigned long)p)
+ break;
+
+ slot = rlist;
+ }
+
+ return slot;
+}
+
+static bool br_multicast_no_router_otherpf(struct net_bridge_port *port,
+ struct hlist_node *rnode)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ if (rnode != &port->ip6_rlist)
+ return hlist_unhashed(&port->ip6_rlist);
+ else
+ return hlist_unhashed(&port->ip4_rlist);
+#else
+ return true;
+#endif
+}
+
+/* Add port to router_list
* list is maintained ordered by pointer value
* and locked by br->multicast_lock and RCU
*/
static void br_multicast_add_router(struct net_bridge *br,
- struct net_bridge_port *port)
+ struct net_bridge_port *port,
+ struct hlist_node *rlist,
+ struct hlist_head *mc_router_list)
{
- struct net_bridge_port *p;
- struct hlist_node *slot = NULL;
+ struct hlist_node *slot;
- if (!hlist_unhashed(&port->rlist))
+ if (!hlist_unhashed(rlist))
return;
- hlist_for_each_entry(p, &br->router_list, rlist) {
- if ((unsigned long) port >= (unsigned long) p)
- break;
- slot = &p->rlist;
- }
+ slot = br_multicast_get_rport_slot(br, port, mc_router_list);
if (slot)
- hlist_add_behind_rcu(&port->rlist, slot);
+ hlist_add_behind_rcu(rlist, slot);
else
- hlist_add_head_rcu(&port->rlist, &br->router_list);
- br_rtr_notify(br->dev, port, RTM_NEWMDB);
- br_port_mc_router_state_change(port, true);
+ hlist_add_head_rcu(rlist, mc_router_list);
+
+ /* For backwards compatibility for now, only notify if we
+ * switched from no IPv4/IPv6 multicast router to a new
+ * IPv4 or IPv6 multicast router.
+ */
+ if (br_multicast_no_router_otherpf(port, rlist)) {
+ br_rtr_notify(br->dev, port, RTM_NEWMDB);
+ br_port_mc_router_state_change(port, true);
+ }
+}
+
+/* Add port to router_list
+ * list is maintained ordered by pointer value
+ * and locked by br->multicast_lock and RCU
+ */
+static void br_ip4_multicast_add_router(struct net_bridge *br,
+ struct net_bridge_port *port)
+{
+ br_multicast_add_router(br, port, &port->ip4_rlist,
+ &br->ip4_mc_router_list);
+}
+
+/* Add port to router_list
+ * list is maintained ordered by pointer value
+ * and locked by br->multicast_lock and RCU
+ */
+static void br_ip6_multicast_add_router(struct net_bridge *br,
+ struct net_bridge_port *port)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ br_multicast_add_router(br, port, &port->ip6_rlist,
+ &br->ip6_mc_router_list);
+#endif
}
static void br_multicast_mark_router(struct net_bridge *br,
- struct net_bridge_port *port)
+ struct net_bridge_port *port,
+ struct timer_list *timer,
+ struct hlist_node *rlist,
+ struct hlist_head *mc_router_list)
{
unsigned long now = jiffies;
if (!port) {
if (br->multicast_router == MDB_RTR_TYPE_TEMP_QUERY) {
- if (!timer_pending(&br->multicast_router_timer))
+ if (!br_ip4_multicast_is_router(br) &&
+ !br_ip6_multicast_is_router(br))
br_mc_router_state_change(br, true);
- mod_timer(&br->multicast_router_timer,
- now + br->multicast_querier_interval);
+ mod_timer(timer, now + br->multicast_querier_interval);
}
return;
}
@@ -2702,24 +2832,71 @@ static void br_multicast_mark_router(struct net_bridge *br,
port->multicast_router == MDB_RTR_TYPE_PERM)
return;
- br_multicast_add_router(br, port);
+ br_multicast_add_router(br, port, rlist, mc_router_list);
+ mod_timer(timer, now + br->multicast_querier_interval);
+}
+
+static void br_ip4_multicast_mark_router(struct net_bridge *br,
+ struct net_bridge_port *port)
+{
+ struct timer_list *timer = &br->ip4_mc_router_timer;
+ struct hlist_node *rlist = NULL;
+
+ if (port) {
+ timer = &port->ip4_mc_router_timer;
+ rlist = &port->ip4_rlist;
+ }
- mod_timer(&port->multicast_router_timer,
- now + br->multicast_querier_interval);
+ br_multicast_mark_router(br, port, timer, rlist,
+ &br->ip4_mc_router_list);
}
-static void br_multicast_query_received(struct net_bridge *br,
- struct net_bridge_port *port,
- struct bridge_mcast_other_query *query,
- struct br_ip *saddr,
- unsigned long max_delay)
+static void br_ip6_multicast_mark_router(struct net_bridge *br,
+ struct net_bridge_port *port)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ struct timer_list *timer = &br->ip6_mc_router_timer;
+ struct hlist_node *rlist = NULL;
+
+ if (port) {
+ timer = &port->ip6_mc_router_timer;
+ rlist = &port->ip6_rlist;
+ }
+
+ br_multicast_mark_router(br, port, timer, rlist,
+ &br->ip6_mc_router_list);
+#endif
+}
+
+static void
+br_ip4_multicast_query_received(struct net_bridge *br,
+ struct net_bridge_port *port,
+ struct bridge_mcast_other_query *query,
+ struct br_ip *saddr,
+ unsigned long max_delay)
+{
+ if (!br_ip4_multicast_select_querier(br, port, saddr->src.ip4))
+ return;
+
+ br_multicast_update_query_timer(br, query, max_delay);
+ br_ip4_multicast_mark_router(br, port);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static void
+br_ip6_multicast_query_received(struct net_bridge *br,
+ struct net_bridge_port *port,
+ struct bridge_mcast_other_query *query,
+ struct br_ip *saddr,
+ unsigned long max_delay)
{
- if (!br_multicast_select_querier(br, port, saddr))
+ if (!br_ip6_multicast_select_querier(br, port, &saddr->src.ip6))
return;
br_multicast_update_query_timer(br, query, max_delay);
- br_multicast_mark_router(br, port);
+ br_ip6_multicast_mark_router(br, port);
}
+#endif
static void br_ip4_multicast_query(struct net_bridge *br,
struct net_bridge_port *port,
@@ -2768,8 +2945,8 @@ static void br_ip4_multicast_query(struct net_bridge *br,
saddr.proto = htons(ETH_P_IP);
saddr.src.ip4 = iph->saddr;
- br_multicast_query_received(br, port, &br->ip4_other_query,
- &saddr, max_delay);
+ br_ip4_multicast_query_received(br, port, &br->ip4_other_query,
+ &saddr, max_delay);
goto out;
}
@@ -2856,8 +3033,8 @@ static int br_ip6_multicast_query(struct net_bridge *br,
saddr.proto = htons(ETH_P_IPV6);
saddr.src.ip6 = ipv6_hdr(skb)->saddr;
- br_multicast_query_received(br, port, &br->ip6_other_query,
- &saddr, max_delay);
+ br_ip6_multicast_query_received(br, port, &br->ip6_other_query,
+ &saddr, max_delay);
goto out;
} else if (!group) {
goto out;
@@ -3087,7 +3264,7 @@ static void br_multicast_pim(struct net_bridge *br,
pim_hdr_type(pimhdr) != PIM_TYPE_HELLO)
return;
- br_multicast_mark_router(br, port);
+ br_ip4_multicast_mark_router(br, port);
}
static int br_ip4_multicast_mrd_rcv(struct net_bridge *br,
@@ -3098,7 +3275,7 @@ static int br_ip4_multicast_mrd_rcv(struct net_bridge *br,
igmp_hdr(skb)->type != IGMP_MRDISC_ADV)
return -ENOMSG;
- br_multicast_mark_router(br, port);
+ br_ip4_multicast_mark_router(br, port);
return 0;
}
@@ -3166,7 +3343,7 @@ static void br_ip6_multicast_mrd_rcv(struct net_bridge *br,
if (icmp6_hdr(skb)->icmp6_type != ICMPV6_MRDISC_ADV)
return;
- br_multicast_mark_router(br, port);
+ br_ip6_multicast_mark_router(br, port);
}
static int br_multicast_ipv6_rcv(struct net_bridge *br,
@@ -3316,13 +3493,15 @@ void br_multicast_init(struct net_bridge *br)
br_opt_toggle(br, BROPT_HAS_IPV6_ADDR, true);
spin_lock_init(&br->multicast_lock);
- timer_setup(&br->multicast_router_timer,
- br_multicast_local_router_expired, 0);
+ timer_setup(&br->ip4_mc_router_timer,
+ br_ip4_multicast_local_router_expired, 0);
timer_setup(&br->ip4_other_query.timer,
br_ip4_multicast_querier_expired, 0);
timer_setup(&br->ip4_own_query.timer,
br_ip4_multicast_query_expired, 0);
#if IS_ENABLED(CONFIG_IPV6)
+ timer_setup(&br->ip6_mc_router_timer,
+ br_ip6_multicast_local_router_expired, 0);
timer_setup(&br->ip6_other_query.timer,
br_ip6_multicast_querier_expired, 0);
timer_setup(&br->ip6_own_query.timer,
@@ -3416,10 +3595,11 @@ void br_multicast_open(struct net_bridge *br)
void br_multicast_stop(struct net_bridge *br)
{
- del_timer_sync(&br->multicast_router_timer);
+ del_timer_sync(&br->ip4_mc_router_timer);
del_timer_sync(&br->ip4_other_query.timer);
del_timer_sync(&br->ip4_own_query.timer);
#if IS_ENABLED(CONFIG_IPV6)
+ del_timer_sync(&br->ip6_mc_router_timer);
del_timer_sync(&br->ip6_other_query.timer);
del_timer_sync(&br->ip6_own_query.timer);
#endif
@@ -3453,7 +3633,10 @@ int br_multicast_set_router(struct net_bridge *br, unsigned long val)
case MDB_RTR_TYPE_DISABLED:
case MDB_RTR_TYPE_PERM:
br_mc_router_state_change(br, val == MDB_RTR_TYPE_PERM);
- del_timer(&br->multicast_router_timer);
+ del_timer(&br->ip4_mc_router_timer);
+#if IS_ENABLED(CONFIG_IPV6)
+ del_timer(&br->ip6_mc_router_timer);
+#endif
br->multicast_router = val;
err = 0;
break;
@@ -3470,11 +3653,22 @@ int br_multicast_set_router(struct net_bridge *br, unsigned long val)
return err;
}
-static void __del_port_router(struct net_bridge_port *p)
+static void
+br_multicast_rport_del_notify(struct net_bridge_port *p, bool deleted)
{
- if (hlist_unhashed(&p->rlist))
+ if (!deleted)
+ return;
+
+ /* For backwards compatibility for now, only notify if there is
+ * no multicast router anymore for both IPv4 and IPv6.
+ */
+ if (!hlist_unhashed(&p->ip4_rlist))
return;
- hlist_del_init_rcu(&p->rlist);
+#if IS_ENABLED(CONFIG_IPV6)
+ if (!hlist_unhashed(&p->ip6_rlist))
+ return;
+#endif
+
br_rtr_notify(p->br->dev, p, RTM_DELMDB);
br_port_mc_router_state_change(p, false);
@@ -3488,34 +3682,52 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
struct net_bridge *br = p->br;
unsigned long now = jiffies;
int err = -EINVAL;
+ bool del = false;
spin_lock(&br->multicast_lock);
if (p->multicast_router == val) {
/* Refresh the temp router port timer */
- if (p->multicast_router == MDB_RTR_TYPE_TEMP)
- mod_timer(&p->multicast_router_timer,
+ if (p->multicast_router == MDB_RTR_TYPE_TEMP) {
+ mod_timer(&p->ip4_mc_router_timer,
now + br->multicast_querier_interval);
+#if IS_ENABLED(CONFIG_IPV6)
+ mod_timer(&p->ip6_mc_router_timer,
+ now + br->multicast_querier_interval);
+#endif
+ }
err = 0;
goto unlock;
}
switch (val) {
case MDB_RTR_TYPE_DISABLED:
p->multicast_router = MDB_RTR_TYPE_DISABLED;
- __del_port_router(p);
- del_timer(&p->multicast_router_timer);
+ del |= br_ip4_multicast_rport_del(p);
+ del_timer(&p->ip4_mc_router_timer);
+ del |= br_ip6_multicast_rport_del(p);
+#if IS_ENABLED(CONFIG_IPV6)
+ del_timer(&p->ip6_mc_router_timer);
+#endif
+ br_multicast_rport_del_notify(p, del);
break;
case MDB_RTR_TYPE_TEMP_QUERY:
p->multicast_router = MDB_RTR_TYPE_TEMP_QUERY;
- __del_port_router(p);
+ del |= br_ip4_multicast_rport_del(p);
+ del |= br_ip6_multicast_rport_del(p);
+ br_multicast_rport_del_notify(p, del);
break;
case MDB_RTR_TYPE_PERM:
p->multicast_router = MDB_RTR_TYPE_PERM;
- del_timer(&p->multicast_router_timer);
- br_multicast_add_router(br, p);
+ del_timer(&p->ip4_mc_router_timer);
+ br_ip4_multicast_add_router(br, p);
+#if IS_ENABLED(CONFIG_IPV6)
+ del_timer(&p->ip6_mc_router_timer);
+#endif
+ br_ip6_multicast_add_router(br, p);
break;
case MDB_RTR_TYPE_TEMP:
p->multicast_router = MDB_RTR_TYPE_TEMP;
- br_multicast_mark_router(br, p);
+ br_ip4_multicast_mark_router(br, p);
+ br_ip6_multicast_mark_router(br, p);
break;
default:
goto unlock;
@@ -3621,7 +3833,7 @@ bool br_multicast_router(const struct net_device *dev)
bool is_router;
spin_lock_bh(&br->multicast_lock);
- is_router = br_multicast_is_router(br);
+ is_router = br_multicast_is_router(br, NULL);
spin_unlock_bh(&br->multicast_lock);
return is_router;
}
@@ -3842,6 +4054,61 @@ unlock:
}
EXPORT_SYMBOL_GPL(br_multicast_has_querier_adjacent);
+/**
+ * br_multicast_has_router_adjacent - Checks for a router behind a bridge port
+ * @dev: The bridge port adjacent to which to check for a multicast router
+ * @proto: The protocol family to check for: IGMP -> ETH_P_IP, MLD -> ETH_P_IPV6
+ *
+ * Checks whether the given interface has a bridge on top and if so returns
+ * true if a multicast router is behind one of the other ports of this
+ * bridge. Otherwise returns false.
+ */
+bool br_multicast_has_router_adjacent(struct net_device *dev, int proto)
+{
+ struct net_bridge_port *port, *p;
+ bool ret = false;
+
+ rcu_read_lock();
+ port = br_port_get_check_rcu(dev);
+ if (!port)
+ goto unlock;
+
+ switch (proto) {
+ case ETH_P_IP:
+ hlist_for_each_entry_rcu(p, &port->br->ip4_mc_router_list,
+ ip4_rlist) {
+ if (p == port)
+ continue;
+
+ ret = true;
+ goto unlock;
+ }
+ break;
+#if IS_ENABLED(CONFIG_IPV6)
+ case ETH_P_IPV6:
+ hlist_for_each_entry_rcu(p, &port->br->ip6_mc_router_list,
+ ip6_rlist) {
+ if (p == port)
+ continue;
+
+ ret = true;
+ goto unlock;
+ }
+ break;
+#endif
+ default:
+ /* when compiled without IPv6 support, be conservative and
+ * always assume presence of an IPv6 multicast router
+ */
+ ret = true;
+ }
+
+unlock:
+ rcu_read_unlock();
+ return ret;
+}
+EXPORT_SYMBOL_GPL(br_multicast_has_router_adjacent);
+
static void br_mcast_stats_add(struct bridge_mcast_stats __percpu *stats,
const struct sk_buff *skb, u8 type, u8 dir)
{
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 7ce8a77cc6b6..03197ab4af76 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -307,16 +307,18 @@ struct net_bridge_port {
#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
struct bridge_mcast_own_query ip4_own_query;
+ struct timer_list ip4_mc_router_timer;
+ struct hlist_node ip4_rlist;
#if IS_ENABLED(CONFIG_IPV6)
struct bridge_mcast_own_query ip6_own_query;
+ struct timer_list ip6_mc_router_timer;
+ struct hlist_node ip6_rlist;
#endif /* IS_ENABLED(CONFIG_IPV6) */
u32 multicast_eht_hosts_limit;
u32 multicast_eht_hosts_cnt;
unsigned char multicast_router;
struct bridge_mcast_stats __percpu *mcast_stats;
- struct timer_list multicast_router_timer;
struct hlist_head mglist;
- struct hlist_node rlist;
#endif
#ifdef CONFIG_SYSFS
@@ -449,14 +451,16 @@ struct net_bridge {
struct hlist_head mcast_gc_list;
struct hlist_head mdb_list;
- struct hlist_head router_list;
- struct timer_list multicast_router_timer;
+ struct hlist_head ip4_mc_router_list;
+ struct timer_list ip4_mc_router_timer;
struct bridge_mcast_other_query ip4_other_query;
struct bridge_mcast_own_query ip4_own_query;
struct bridge_mcast_querier ip4_querier;
struct bridge_mcast_stats __percpu *mcast_stats;
#if IS_ENABLED(CONFIG_IPV6)
+ struct hlist_head ip6_mc_router_list;
+ struct timer_list ip6_mc_router_timer;
struct bridge_mcast_other_query ip6_other_query;
struct bridge_mcast_own_query ip6_own_query;
struct bridge_mcast_querier ip6_querier;
@@ -864,11 +868,58 @@ static inline bool br_group_is_l2(const struct br_ip *group)
#define mlock_dereference(X, br) \
rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock))
-static inline bool br_multicast_is_router(struct net_bridge *br)
+static inline struct hlist_node *
+br_multicast_get_first_rport_node(struct net_bridge *b, struct sk_buff *skb) {
+#if IS_ENABLED(CONFIG_IPV6)
+ if (skb->protocol == htons(ETH_P_IPV6))
+ return rcu_dereference(hlist_first_rcu(&b->ip6_mc_router_list));
+#endif
+ return rcu_dereference(hlist_first_rcu(&b->ip4_mc_router_list));
+}
+
+static inline struct net_bridge_port *
+br_multicast_rport_from_node_skb(struct hlist_node *rp, struct sk_buff *skb) {
+#if IS_ENABLED(CONFIG_IPV6)
+ if (skb->protocol == htons(ETH_P_IPV6))
+ return hlist_entry_safe(rp, struct net_bridge_port, ip6_rlist);
+#endif
+ return hlist_entry_safe(rp, struct net_bridge_port, ip4_rlist);
+}
+
+static inline bool br_ip4_multicast_is_router(struct net_bridge *br)
+{
+ return timer_pending(&br->ip4_mc_router_timer);
+}
+
+static inline bool br_ip6_multicast_is_router(struct net_bridge *br)
{
- return br->multicast_router == 2 ||
- (br->multicast_router == 1 &&
- timer_pending(&br->multicast_router_timer));
+#if IS_ENABLED(CONFIG_IPV6)
+ return timer_pending(&br->ip6_mc_router_timer);
+#else
+ return false;
+#endif
+}
+
+static inline bool
+br_multicast_is_router(struct net_bridge *br, struct sk_buff *skb)
+{
+ switch (br->multicast_router) {
+ case MDB_RTR_TYPE_PERM:
+ return true;
+ case MDB_RTR_TYPE_TEMP_QUERY:
+ if (skb) {
+ if (skb->protocol == htons(ETH_P_IP))
+ return br_ip4_multicast_is_router(br);
+ else if (skb->protocol == htons(ETH_P_IPV6))
+ return br_ip6_multicast_is_router(br);
+ } else {
+ return br_ip4_multicast_is_router(br) ||
+ br_ip6_multicast_is_router(br);
+ }
+ fallthrough;
+ default:
+ return false;
+ }
}
static inline bool