diff options
-rw-r--r-- | include/linux/if_bridge.h | 8 | ||||
-rw-r--r-- | include/uapi/linux/if_bridge.h | 2 | ||||
-rw-r--r-- | net/bridge/br_forward.c | 5 | ||||
-rw-r--r-- | net/bridge/br_input.c | 2 | ||||
-rw-r--r-- | net/bridge/br_mdb.c | 57 | ||||
-rw-r--r-- | net/bridge/br_multicast.c | 445 | ||||
-rw-r--r-- | net/bridge/br_private.h | 67 |
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 |