diff options
Diffstat (limited to 'net/bridge/br_multicast.c')
-rw-r--r-- | net/bridge/br_multicast.c | 445 |
1 files changed, 356 insertions, 89 deletions
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 226bb05c3b42..53c3a9d80d9c 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,7 +60,10 @@ 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); +static void br_ip6_multicast_add_router(struct net_bridge *br, + struct net_bridge_port *port); #if IS_ENABLED(CONFIG_IPV6) static void br_ip6_multicast_leave_group(struct net_bridge *br, struct net_bridge_port *port, @@ -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) { |