|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | // Copyright (c) 2020, Nikolay Aleksandrov <nikolay@nvidia.com> | 
|  | #include <linux/err.h> | 
|  | #include <linux/export.h> | 
|  | #include <linux/if_ether.h> | 
|  | #include <linux/igmp.h> | 
|  | #include <linux/in.h> | 
|  | #include <linux/jhash.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/log2.h> | 
|  | #include <linux/netdevice.h> | 
|  | #include <linux/netfilter_bridge.h> | 
|  | #include <linux/random.h> | 
|  | #include <linux/rculist.h> | 
|  | #include <linux/skbuff.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/timer.h> | 
|  | #include <linux/inetdevice.h> | 
|  | #include <linux/mroute.h> | 
|  | #include <net/ip.h> | 
|  | #include <net/switchdev.h> | 
|  | #if IS_ENABLED(CONFIG_IPV6) | 
|  | #include <linux/icmpv6.h> | 
|  | #include <net/ipv6.h> | 
|  | #include <net/mld.h> | 
|  | #include <net/ip6_checksum.h> | 
|  | #include <net/addrconf.h> | 
|  | #endif | 
|  |  | 
|  | #include "br_private.h" | 
|  | #include "br_private_mcast_eht.h" | 
|  |  | 
|  | static bool br_multicast_del_eht_set_entry(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *src_addr, | 
|  | union net_bridge_eht_addr *h_addr); | 
|  | static void br_multicast_create_eht_set_entry(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *src_addr, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | int filter_mode, | 
|  | bool allow_zero_src); | 
|  |  | 
|  | static struct net_bridge_group_eht_host * | 
|  | br_multicast_eht_host_lookup(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr) | 
|  | { | 
|  | struct rb_node *node = pg->eht_host_tree.rb_node; | 
|  |  | 
|  | while (node) { | 
|  | struct net_bridge_group_eht_host *this; | 
|  | int result; | 
|  |  | 
|  | this = rb_entry(node, struct net_bridge_group_eht_host, | 
|  | rb_node); | 
|  | result = memcmp(h_addr, &this->h_addr, sizeof(*h_addr)); | 
|  | if (result < 0) | 
|  | node = node->rb_left; | 
|  | else if (result > 0) | 
|  | node = node->rb_right; | 
|  | else | 
|  | return this; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static int br_multicast_eht_host_filter_mode(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr) | 
|  | { | 
|  | struct net_bridge_group_eht_host *eht_host; | 
|  |  | 
|  | eht_host = br_multicast_eht_host_lookup(pg, h_addr); | 
|  | if (!eht_host) | 
|  | return MCAST_INCLUDE; | 
|  |  | 
|  | return eht_host->filter_mode; | 
|  | } | 
|  |  | 
|  | static struct net_bridge_group_eht_set_entry * | 
|  | br_multicast_eht_set_entry_lookup(struct net_bridge_group_eht_set *eht_set, | 
|  | union net_bridge_eht_addr *h_addr) | 
|  | { | 
|  | struct rb_node *node = eht_set->entry_tree.rb_node; | 
|  |  | 
|  | while (node) { | 
|  | struct net_bridge_group_eht_set_entry *this; | 
|  | int result; | 
|  |  | 
|  | this = rb_entry(node, struct net_bridge_group_eht_set_entry, | 
|  | rb_node); | 
|  | result = memcmp(h_addr, &this->h_addr, sizeof(*h_addr)); | 
|  | if (result < 0) | 
|  | node = node->rb_left; | 
|  | else if (result > 0) | 
|  | node = node->rb_right; | 
|  | else | 
|  | return this; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static struct net_bridge_group_eht_set * | 
|  | br_multicast_eht_set_lookup(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *src_addr) | 
|  | { | 
|  | struct rb_node *node = pg->eht_set_tree.rb_node; | 
|  |  | 
|  | while (node) { | 
|  | struct net_bridge_group_eht_set *this; | 
|  | int result; | 
|  |  | 
|  | this = rb_entry(node, struct net_bridge_group_eht_set, | 
|  | rb_node); | 
|  | result = memcmp(src_addr, &this->src_addr, sizeof(*src_addr)); | 
|  | if (result < 0) | 
|  | node = node->rb_left; | 
|  | else if (result > 0) | 
|  | node = node->rb_right; | 
|  | else | 
|  | return this; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void __eht_destroy_host(struct net_bridge_group_eht_host *eht_host) | 
|  | { | 
|  | WARN_ON(!hlist_empty(&eht_host->set_entries)); | 
|  |  | 
|  | br_multicast_eht_hosts_dec(eht_host->pg); | 
|  |  | 
|  | rb_erase(&eht_host->rb_node, &eht_host->pg->eht_host_tree); | 
|  | RB_CLEAR_NODE(&eht_host->rb_node); | 
|  | kfree(eht_host); | 
|  | } | 
|  |  | 
|  | static void br_multicast_destroy_eht_set_entry(struct net_bridge_mcast_gc *gc) | 
|  | { | 
|  | struct net_bridge_group_eht_set_entry *set_h; | 
|  |  | 
|  | set_h = container_of(gc, struct net_bridge_group_eht_set_entry, mcast_gc); | 
|  | WARN_ON(!RB_EMPTY_NODE(&set_h->rb_node)); | 
|  |  | 
|  | del_timer_sync(&set_h->timer); | 
|  | kfree(set_h); | 
|  | } | 
|  |  | 
|  | static void br_multicast_destroy_eht_set(struct net_bridge_mcast_gc *gc) | 
|  | { | 
|  | struct net_bridge_group_eht_set *eht_set; | 
|  |  | 
|  | eht_set = container_of(gc, struct net_bridge_group_eht_set, mcast_gc); | 
|  | WARN_ON(!RB_EMPTY_NODE(&eht_set->rb_node)); | 
|  | WARN_ON(!RB_EMPTY_ROOT(&eht_set->entry_tree)); | 
|  |  | 
|  | del_timer_sync(&eht_set->timer); | 
|  | kfree(eht_set); | 
|  | } | 
|  |  | 
|  | static void __eht_del_set_entry(struct net_bridge_group_eht_set_entry *set_h) | 
|  | { | 
|  | struct net_bridge_group_eht_host *eht_host = set_h->h_parent; | 
|  | union net_bridge_eht_addr zero_addr; | 
|  |  | 
|  | rb_erase(&set_h->rb_node, &set_h->eht_set->entry_tree); | 
|  | RB_CLEAR_NODE(&set_h->rb_node); | 
|  | hlist_del_init(&set_h->host_list); | 
|  | memset(&zero_addr, 0, sizeof(zero_addr)); | 
|  | if (memcmp(&set_h->h_addr, &zero_addr, sizeof(zero_addr))) | 
|  | eht_host->num_entries--; | 
|  | hlist_add_head(&set_h->mcast_gc.gc_node, &set_h->br->mcast_gc_list); | 
|  | queue_work(system_long_wq, &set_h->br->mcast_gc_work); | 
|  |  | 
|  | if (hlist_empty(&eht_host->set_entries)) | 
|  | __eht_destroy_host(eht_host); | 
|  | } | 
|  |  | 
|  | static void br_multicast_del_eht_set(struct net_bridge_group_eht_set *eht_set) | 
|  | { | 
|  | struct net_bridge_group_eht_set_entry *set_h; | 
|  | struct rb_node *node; | 
|  |  | 
|  | while ((node = rb_first(&eht_set->entry_tree))) { | 
|  | set_h = rb_entry(node, struct net_bridge_group_eht_set_entry, | 
|  | rb_node); | 
|  | __eht_del_set_entry(set_h); | 
|  | } | 
|  |  | 
|  | rb_erase(&eht_set->rb_node, &eht_set->pg->eht_set_tree); | 
|  | RB_CLEAR_NODE(&eht_set->rb_node); | 
|  | hlist_add_head(&eht_set->mcast_gc.gc_node, &eht_set->br->mcast_gc_list); | 
|  | queue_work(system_long_wq, &eht_set->br->mcast_gc_work); | 
|  | } | 
|  |  | 
|  | void br_multicast_eht_clean_sets(struct net_bridge_port_group *pg) | 
|  | { | 
|  | struct net_bridge_group_eht_set *eht_set; | 
|  | struct rb_node *node; | 
|  |  | 
|  | while ((node = rb_first(&pg->eht_set_tree))) { | 
|  | eht_set = rb_entry(node, struct net_bridge_group_eht_set, | 
|  | rb_node); | 
|  | br_multicast_del_eht_set(eht_set); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void br_multicast_eht_set_entry_expired(struct timer_list *t) | 
|  | { | 
|  | struct net_bridge_group_eht_set_entry *set_h = from_timer(set_h, t, timer); | 
|  | struct net_bridge *br = set_h->br; | 
|  |  | 
|  | spin_lock(&br->multicast_lock); | 
|  | if (RB_EMPTY_NODE(&set_h->rb_node) || timer_pending(&set_h->timer)) | 
|  | goto out; | 
|  |  | 
|  | br_multicast_del_eht_set_entry(set_h->eht_set->pg, | 
|  | &set_h->eht_set->src_addr, | 
|  | &set_h->h_addr); | 
|  | out: | 
|  | spin_unlock(&br->multicast_lock); | 
|  | } | 
|  |  | 
|  | static void br_multicast_eht_set_expired(struct timer_list *t) | 
|  | { | 
|  | struct net_bridge_group_eht_set *eht_set = from_timer(eht_set, t, | 
|  | timer); | 
|  | struct net_bridge *br = eht_set->br; | 
|  |  | 
|  | spin_lock(&br->multicast_lock); | 
|  | if (RB_EMPTY_NODE(&eht_set->rb_node) || timer_pending(&eht_set->timer)) | 
|  | goto out; | 
|  |  | 
|  | br_multicast_del_eht_set(eht_set); | 
|  | out: | 
|  | spin_unlock(&br->multicast_lock); | 
|  | } | 
|  |  | 
|  | static struct net_bridge_group_eht_host * | 
|  | __eht_lookup_create_host(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | unsigned char filter_mode) | 
|  | { | 
|  | struct rb_node **link = &pg->eht_host_tree.rb_node, *parent = NULL; | 
|  | struct net_bridge_group_eht_host *eht_host; | 
|  |  | 
|  | while (*link) { | 
|  | struct net_bridge_group_eht_host *this; | 
|  | int result; | 
|  |  | 
|  | this = rb_entry(*link, struct net_bridge_group_eht_host, | 
|  | rb_node); | 
|  | result = memcmp(h_addr, &this->h_addr, sizeof(*h_addr)); | 
|  | parent = *link; | 
|  | if (result < 0) | 
|  | link = &((*link)->rb_left); | 
|  | else if (result > 0) | 
|  | link = &((*link)->rb_right); | 
|  | else | 
|  | return this; | 
|  | } | 
|  |  | 
|  | if (br_multicast_eht_hosts_over_limit(pg)) | 
|  | return NULL; | 
|  |  | 
|  | eht_host = kzalloc(sizeof(*eht_host), GFP_ATOMIC); | 
|  | if (!eht_host) | 
|  | return NULL; | 
|  |  | 
|  | memcpy(&eht_host->h_addr, h_addr, sizeof(*h_addr)); | 
|  | INIT_HLIST_HEAD(&eht_host->set_entries); | 
|  | eht_host->pg = pg; | 
|  | eht_host->filter_mode = filter_mode; | 
|  |  | 
|  | rb_link_node(&eht_host->rb_node, parent, link); | 
|  | rb_insert_color(&eht_host->rb_node, &pg->eht_host_tree); | 
|  |  | 
|  | br_multicast_eht_hosts_inc(pg); | 
|  |  | 
|  | return eht_host; | 
|  | } | 
|  |  | 
|  | static struct net_bridge_group_eht_set_entry * | 
|  | __eht_lookup_create_set_entry(struct net_bridge *br, | 
|  | struct net_bridge_group_eht_set *eht_set, | 
|  | struct net_bridge_group_eht_host *eht_host, | 
|  | bool allow_zero_src) | 
|  | { | 
|  | struct rb_node **link = &eht_set->entry_tree.rb_node, *parent = NULL; | 
|  | struct net_bridge_group_eht_set_entry *set_h; | 
|  |  | 
|  | while (*link) { | 
|  | struct net_bridge_group_eht_set_entry *this; | 
|  | int result; | 
|  |  | 
|  | this = rb_entry(*link, struct net_bridge_group_eht_set_entry, | 
|  | rb_node); | 
|  | result = memcmp(&eht_host->h_addr, &this->h_addr, | 
|  | sizeof(union net_bridge_eht_addr)); | 
|  | parent = *link; | 
|  | if (result < 0) | 
|  | link = &((*link)->rb_left); | 
|  | else if (result > 0) | 
|  | link = &((*link)->rb_right); | 
|  | else | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /* always allow auto-created zero entry */ | 
|  | if (!allow_zero_src && eht_host->num_entries >= PG_SRC_ENT_LIMIT) | 
|  | return NULL; | 
|  |  | 
|  | set_h = kzalloc(sizeof(*set_h), GFP_ATOMIC); | 
|  | if (!set_h) | 
|  | return NULL; | 
|  |  | 
|  | memcpy(&set_h->h_addr, &eht_host->h_addr, | 
|  | sizeof(union net_bridge_eht_addr)); | 
|  | set_h->mcast_gc.destroy = br_multicast_destroy_eht_set_entry; | 
|  | set_h->eht_set = eht_set; | 
|  | set_h->h_parent = eht_host; | 
|  | set_h->br = br; | 
|  | timer_setup(&set_h->timer, br_multicast_eht_set_entry_expired, 0); | 
|  |  | 
|  | hlist_add_head(&set_h->host_list, &eht_host->set_entries); | 
|  | rb_link_node(&set_h->rb_node, parent, link); | 
|  | rb_insert_color(&set_h->rb_node, &eht_set->entry_tree); | 
|  | /* we must not count the auto-created zero entry otherwise we won't be | 
|  | * able to track the full list of PG_SRC_ENT_LIMIT entries | 
|  | */ | 
|  | if (!allow_zero_src) | 
|  | eht_host->num_entries++; | 
|  |  | 
|  | return set_h; | 
|  | } | 
|  |  | 
|  | static struct net_bridge_group_eht_set * | 
|  | __eht_lookup_create_set(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *src_addr) | 
|  | { | 
|  | struct rb_node **link = &pg->eht_set_tree.rb_node, *parent = NULL; | 
|  | struct net_bridge_group_eht_set *eht_set; | 
|  |  | 
|  | while (*link) { | 
|  | struct net_bridge_group_eht_set *this; | 
|  | int result; | 
|  |  | 
|  | this = rb_entry(*link, struct net_bridge_group_eht_set, | 
|  | rb_node); | 
|  | result = memcmp(src_addr, &this->src_addr, sizeof(*src_addr)); | 
|  | parent = *link; | 
|  | if (result < 0) | 
|  | link = &((*link)->rb_left); | 
|  | else if (result > 0) | 
|  | link = &((*link)->rb_right); | 
|  | else | 
|  | return this; | 
|  | } | 
|  |  | 
|  | eht_set = kzalloc(sizeof(*eht_set), GFP_ATOMIC); | 
|  | if (!eht_set) | 
|  | return NULL; | 
|  |  | 
|  | memcpy(&eht_set->src_addr, src_addr, sizeof(*src_addr)); | 
|  | eht_set->mcast_gc.destroy = br_multicast_destroy_eht_set; | 
|  | eht_set->pg = pg; | 
|  | eht_set->br = pg->key.port->br; | 
|  | eht_set->entry_tree = RB_ROOT; | 
|  | timer_setup(&eht_set->timer, br_multicast_eht_set_expired, 0); | 
|  |  | 
|  | rb_link_node(&eht_set->rb_node, parent, link); | 
|  | rb_insert_color(&eht_set->rb_node, &pg->eht_set_tree); | 
|  |  | 
|  | return eht_set; | 
|  | } | 
|  |  | 
|  | static void br_multicast_ip_src_to_eht_addr(const struct br_ip *src, | 
|  | union net_bridge_eht_addr *dest) | 
|  | { | 
|  | switch (src->proto) { | 
|  | case htons(ETH_P_IP): | 
|  | dest->ip4 = src->src.ip4; | 
|  | break; | 
|  | #if IS_ENABLED(CONFIG_IPV6) | 
|  | case htons(ETH_P_IPV6): | 
|  | memcpy(&dest->ip6, &src->src.ip6, sizeof(struct in6_addr)); | 
|  | break; | 
|  | #endif | 
|  | } | 
|  | } | 
|  |  | 
|  | static void br_eht_convert_host_filter_mode(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | int filter_mode) | 
|  | { | 
|  | struct net_bridge_group_eht_host *eht_host; | 
|  | union net_bridge_eht_addr zero_addr; | 
|  |  | 
|  | eht_host = br_multicast_eht_host_lookup(pg, h_addr); | 
|  | if (eht_host) | 
|  | eht_host->filter_mode = filter_mode; | 
|  |  | 
|  | memset(&zero_addr, 0, sizeof(zero_addr)); | 
|  | switch (filter_mode) { | 
|  | case MCAST_INCLUDE: | 
|  | br_multicast_del_eht_set_entry(pg, &zero_addr, h_addr); | 
|  | break; | 
|  | case MCAST_EXCLUDE: | 
|  | br_multicast_create_eht_set_entry(brmctx, pg, &zero_addr, | 
|  | h_addr, MCAST_EXCLUDE, | 
|  | true); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void br_multicast_create_eht_set_entry(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *src_addr, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | int filter_mode, | 
|  | bool allow_zero_src) | 
|  | { | 
|  | struct net_bridge_group_eht_set_entry *set_h; | 
|  | struct net_bridge_group_eht_host *eht_host; | 
|  | struct net_bridge *br = pg->key.port->br; | 
|  | struct net_bridge_group_eht_set *eht_set; | 
|  | union net_bridge_eht_addr zero_addr; | 
|  |  | 
|  | memset(&zero_addr, 0, sizeof(zero_addr)); | 
|  | if (!allow_zero_src && !memcmp(src_addr, &zero_addr, sizeof(zero_addr))) | 
|  | return; | 
|  |  | 
|  | eht_set = __eht_lookup_create_set(pg, src_addr); | 
|  | if (!eht_set) | 
|  | return; | 
|  |  | 
|  | eht_host = __eht_lookup_create_host(pg, h_addr, filter_mode); | 
|  | if (!eht_host) | 
|  | goto fail_host; | 
|  |  | 
|  | set_h = __eht_lookup_create_set_entry(br, eht_set, eht_host, | 
|  | allow_zero_src); | 
|  | if (!set_h) | 
|  | goto fail_set_entry; | 
|  |  | 
|  | mod_timer(&set_h->timer, jiffies + br_multicast_gmi(brmctx)); | 
|  | mod_timer(&eht_set->timer, jiffies + br_multicast_gmi(brmctx)); | 
|  |  | 
|  | return; | 
|  |  | 
|  | fail_set_entry: | 
|  | if (hlist_empty(&eht_host->set_entries)) | 
|  | __eht_destroy_host(eht_host); | 
|  | fail_host: | 
|  | if (RB_EMPTY_ROOT(&eht_set->entry_tree)) | 
|  | br_multicast_del_eht_set(eht_set); | 
|  | } | 
|  |  | 
|  | static bool br_multicast_del_eht_set_entry(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *src_addr, | 
|  | union net_bridge_eht_addr *h_addr) | 
|  | { | 
|  | struct net_bridge_group_eht_set_entry *set_h; | 
|  | struct net_bridge_group_eht_set *eht_set; | 
|  | bool set_deleted = false; | 
|  |  | 
|  | eht_set = br_multicast_eht_set_lookup(pg, src_addr); | 
|  | if (!eht_set) | 
|  | goto out; | 
|  |  | 
|  | set_h = br_multicast_eht_set_entry_lookup(eht_set, h_addr); | 
|  | if (!set_h) | 
|  | goto out; | 
|  |  | 
|  | __eht_del_set_entry(set_h); | 
|  |  | 
|  | if (RB_EMPTY_ROOT(&eht_set->entry_tree)) { | 
|  | br_multicast_del_eht_set(eht_set); | 
|  | set_deleted = true; | 
|  | } | 
|  |  | 
|  | out: | 
|  | return set_deleted; | 
|  | } | 
|  |  | 
|  | static void br_multicast_del_eht_host(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr) | 
|  | { | 
|  | struct net_bridge_group_eht_set_entry *set_h; | 
|  | struct net_bridge_group_eht_host *eht_host; | 
|  | struct hlist_node *tmp; | 
|  |  | 
|  | eht_host = br_multicast_eht_host_lookup(pg, h_addr); | 
|  | if (!eht_host) | 
|  | return; | 
|  |  | 
|  | hlist_for_each_entry_safe(set_h, tmp, &eht_host->set_entries, host_list) | 
|  | br_multicast_del_eht_set_entry(set_h->eht_set->pg, | 
|  | &set_h->eht_set->src_addr, | 
|  | &set_h->h_addr); | 
|  | } | 
|  |  | 
|  | /* create new set entries from reports */ | 
|  | static void __eht_create_set_entries(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | size_t addr_size, | 
|  | int filter_mode) | 
|  | { | 
|  | union net_bridge_eht_addr eht_src_addr; | 
|  | u32 src_idx; | 
|  |  | 
|  | memset(&eht_src_addr, 0, sizeof(eht_src_addr)); | 
|  | for (src_idx = 0; src_idx < nsrcs; src_idx++) { | 
|  | memcpy(&eht_src_addr, srcs + (src_idx * addr_size), addr_size); | 
|  | br_multicast_create_eht_set_entry(brmctx, pg, &eht_src_addr, | 
|  | h_addr, filter_mode, | 
|  | false); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* delete existing set entries and their (S,G) entries if they were the last */ | 
|  | static bool __eht_del_set_entries(struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | size_t addr_size) | 
|  | { | 
|  | union net_bridge_eht_addr eht_src_addr; | 
|  | struct net_bridge_group_src *src_ent; | 
|  | bool changed = false; | 
|  | struct br_ip src_ip; | 
|  | u32 src_idx; | 
|  |  | 
|  | memset(&eht_src_addr, 0, sizeof(eht_src_addr)); | 
|  | memset(&src_ip, 0, sizeof(src_ip)); | 
|  | src_ip.proto = pg->key.addr.proto; | 
|  | for (src_idx = 0; src_idx < nsrcs; src_idx++) { | 
|  | memcpy(&eht_src_addr, srcs + (src_idx * addr_size), addr_size); | 
|  | if (!br_multicast_del_eht_set_entry(pg, &eht_src_addr, h_addr)) | 
|  | continue; | 
|  | memcpy(&src_ip, srcs + (src_idx * addr_size), addr_size); | 
|  | src_ent = br_multicast_find_group_src(pg, &src_ip); | 
|  | if (!src_ent) | 
|  | continue; | 
|  | br_multicast_del_group_src(src_ent, true); | 
|  | changed = true; | 
|  | } | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | static bool br_multicast_eht_allow(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | size_t addr_size) | 
|  | { | 
|  | bool changed = false; | 
|  |  | 
|  | switch (br_multicast_eht_host_filter_mode(pg, h_addr)) { | 
|  | case MCAST_INCLUDE: | 
|  | __eht_create_set_entries(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | addr_size, MCAST_INCLUDE); | 
|  | break; | 
|  | case MCAST_EXCLUDE: | 
|  | changed = __eht_del_set_entries(pg, h_addr, srcs, nsrcs, | 
|  | addr_size); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | static bool br_multicast_eht_block(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | size_t addr_size) | 
|  | { | 
|  | bool changed = false; | 
|  |  | 
|  | switch (br_multicast_eht_host_filter_mode(pg, h_addr)) { | 
|  | case MCAST_INCLUDE: | 
|  | changed = __eht_del_set_entries(pg, h_addr, srcs, nsrcs, | 
|  | addr_size); | 
|  | break; | 
|  | case MCAST_EXCLUDE: | 
|  | __eht_create_set_entries(brmctx, pg, h_addr, srcs, nsrcs, addr_size, | 
|  | MCAST_EXCLUDE); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | /* flush_entries is true when changing mode */ | 
|  | static bool __eht_inc_exc(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | size_t addr_size, | 
|  | unsigned char filter_mode, | 
|  | bool to_report) | 
|  | { | 
|  | bool changed = false, flush_entries = to_report; | 
|  | union net_bridge_eht_addr eht_src_addr; | 
|  |  | 
|  | if (br_multicast_eht_host_filter_mode(pg, h_addr) != filter_mode) | 
|  | flush_entries = true; | 
|  |  | 
|  | memset(&eht_src_addr, 0, sizeof(eht_src_addr)); | 
|  | /* if we're changing mode del host and its entries */ | 
|  | if (flush_entries) | 
|  | br_multicast_del_eht_host(pg, h_addr); | 
|  | __eht_create_set_entries(brmctx, pg, h_addr, srcs, nsrcs, addr_size, | 
|  | filter_mode); | 
|  | /* we can be missing sets only if we've deleted some entries */ | 
|  | if (flush_entries) { | 
|  | struct net_bridge_group_eht_set *eht_set; | 
|  | struct net_bridge_group_src *src_ent; | 
|  | struct hlist_node *tmp; | 
|  |  | 
|  | hlist_for_each_entry_safe(src_ent, tmp, &pg->src_list, node) { | 
|  | br_multicast_ip_src_to_eht_addr(&src_ent->addr, | 
|  | &eht_src_addr); | 
|  | if (!br_multicast_eht_set_lookup(pg, &eht_src_addr)) { | 
|  | br_multicast_del_group_src(src_ent, true); | 
|  | changed = true; | 
|  | continue; | 
|  | } | 
|  | /* this is an optimization for TO_INCLUDE where we lower | 
|  | * the set's timeout to LMQT to catch timeout hosts: | 
|  | * - host A (timing out): set entries X, Y | 
|  | * - host B: set entry Z (new from current TO_INCLUDE) | 
|  | *           sends BLOCK Z after LMQT but host A's EHT | 
|  | *           entries still exist (unless lowered to LMQT | 
|  | *           so they can timeout with the S,Gs) | 
|  | * => we wait another LMQT, when we can just delete the | 
|  | *    group immediately | 
|  | */ | 
|  | if (!(src_ent->flags & BR_SGRP_F_SEND) || | 
|  | filter_mode != MCAST_INCLUDE || | 
|  | !to_report) | 
|  | continue; | 
|  | eht_set = br_multicast_eht_set_lookup(pg, | 
|  | &eht_src_addr); | 
|  | if (!eht_set) | 
|  | continue; | 
|  | mod_timer(&eht_set->timer, jiffies + br_multicast_lmqt(brmctx)); | 
|  | } | 
|  | } | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | static bool br_multicast_eht_inc(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | size_t addr_size, | 
|  | bool to_report) | 
|  | { | 
|  | bool changed; | 
|  |  | 
|  | changed = __eht_inc_exc(brmctx, pg, h_addr, srcs, nsrcs, addr_size, | 
|  | MCAST_INCLUDE, to_report); | 
|  | br_eht_convert_host_filter_mode(brmctx, pg, h_addr, MCAST_INCLUDE); | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | static bool br_multicast_eht_exc(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | size_t addr_size, | 
|  | bool to_report) | 
|  | { | 
|  | bool changed; | 
|  |  | 
|  | changed = __eht_inc_exc(brmctx, pg, h_addr, srcs, nsrcs, addr_size, | 
|  | MCAST_EXCLUDE, to_report); | 
|  | br_eht_convert_host_filter_mode(brmctx, pg, h_addr, MCAST_EXCLUDE); | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | static bool __eht_ip4_handle(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | int grec_type) | 
|  | { | 
|  | bool changed = false, to_report = false; | 
|  |  | 
|  | switch (grec_type) { | 
|  | case IGMPV3_ALLOW_NEW_SOURCES: | 
|  | br_multicast_eht_allow(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | sizeof(__be32)); | 
|  | break; | 
|  | case IGMPV3_BLOCK_OLD_SOURCES: | 
|  | changed = br_multicast_eht_block(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | sizeof(__be32)); | 
|  | break; | 
|  | case IGMPV3_CHANGE_TO_INCLUDE: | 
|  | to_report = true; | 
|  | fallthrough; | 
|  | case IGMPV3_MODE_IS_INCLUDE: | 
|  | changed = br_multicast_eht_inc(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | sizeof(__be32), to_report); | 
|  | break; | 
|  | case IGMPV3_CHANGE_TO_EXCLUDE: | 
|  | to_report = true; | 
|  | fallthrough; | 
|  | case IGMPV3_MODE_IS_EXCLUDE: | 
|  | changed = br_multicast_eht_exc(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | sizeof(__be32), to_report); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | #if IS_ENABLED(CONFIG_IPV6) | 
|  | static bool __eht_ip6_handle(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | union net_bridge_eht_addr *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | int grec_type) | 
|  | { | 
|  | bool changed = false, to_report = false; | 
|  |  | 
|  | switch (grec_type) { | 
|  | case MLD2_ALLOW_NEW_SOURCES: | 
|  | br_multicast_eht_allow(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | sizeof(struct in6_addr)); | 
|  | break; | 
|  | case MLD2_BLOCK_OLD_SOURCES: | 
|  | changed = br_multicast_eht_block(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | sizeof(struct in6_addr)); | 
|  | break; | 
|  | case MLD2_CHANGE_TO_INCLUDE: | 
|  | to_report = true; | 
|  | fallthrough; | 
|  | case MLD2_MODE_IS_INCLUDE: | 
|  | changed = br_multicast_eht_inc(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | sizeof(struct in6_addr), | 
|  | to_report); | 
|  | break; | 
|  | case MLD2_CHANGE_TO_EXCLUDE: | 
|  | to_report = true; | 
|  | fallthrough; | 
|  | case MLD2_MODE_IS_EXCLUDE: | 
|  | changed = br_multicast_eht_exc(brmctx, pg, h_addr, srcs, nsrcs, | 
|  | sizeof(struct in6_addr), | 
|  | to_report); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return changed; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* true means an entry was deleted */ | 
|  | bool br_multicast_eht_handle(const struct net_bridge_mcast *brmctx, | 
|  | struct net_bridge_port_group *pg, | 
|  | void *h_addr, | 
|  | void *srcs, | 
|  | u32 nsrcs, | 
|  | size_t addr_size, | 
|  | int grec_type) | 
|  | { | 
|  | bool eht_enabled = !!(pg->key.port->flags & BR_MULTICAST_FAST_LEAVE); | 
|  | union net_bridge_eht_addr eht_host_addr; | 
|  | bool changed = false; | 
|  |  | 
|  | if (!eht_enabled) | 
|  | goto out; | 
|  |  | 
|  | memset(&eht_host_addr, 0, sizeof(eht_host_addr)); | 
|  | memcpy(&eht_host_addr, h_addr, addr_size); | 
|  | if (addr_size == sizeof(__be32)) | 
|  | changed = __eht_ip4_handle(brmctx, pg, &eht_host_addr, srcs, | 
|  | nsrcs, grec_type); | 
|  | #if IS_ENABLED(CONFIG_IPV6) | 
|  | else | 
|  | changed = __eht_ip6_handle(brmctx, pg, &eht_host_addr, srcs, | 
|  | nsrcs, grec_type); | 
|  | #endif | 
|  |  | 
|  | out: | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | int br_multicast_eht_set_hosts_limit(struct net_bridge_port *p, | 
|  | u32 eht_hosts_limit) | 
|  | { | 
|  | struct net_bridge *br = p->br; | 
|  |  | 
|  | if (!eht_hosts_limit) | 
|  | return -EINVAL; | 
|  |  | 
|  | spin_lock_bh(&br->multicast_lock); | 
|  | p->multicast_eht_hosts_limit = eht_hosts_limit; | 
|  | spin_unlock_bh(&br->multicast_lock); | 
|  |  | 
|  | return 0; | 
|  | } |