]> git.baikalelectronics.ru Git - kernel.git/commitdiff
net: bridge: multicast: add EHT source set handling functions
authorNikolay Aleksandrov <nikolay@nvidia.com>
Wed, 20 Jan 2021 14:51:56 +0000 (16:51 +0200)
committerJakub Kicinski <kuba@kernel.org>
Sat, 23 Jan 2021 03:39:56 +0000 (19:39 -0800)
Add EHT source set and set-entry create, delete and lookup functions.
These allow to manipulate source sets which contain their own host sets
with entries which joined that S,G. We're limiting the maximum number of
tracked S,G entries per host to PG_SRC_ENT_LIMIT (currently 32) which is
the current maximum of S,G entries for a group. There's a per-set timer
which will be used to destroy the whole set later.

Signed-off-by: Nikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/bridge/br_multicast.c
net/bridge/br_multicast_eht.c
net/bridge/br_private_mcast_eht.h

index dc6e879dc84074bc0e27d87505d7db3236b6cb76..ac363b10423979b22a7cdbae946f78efd0e1cfc5 100644 (file)
@@ -594,6 +594,7 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp,
 
        rcu_assign_pointer(*pp, pg->next);
        hlist_del_init(&pg->mglist);
+       br_multicast_eht_clean_sets(pg);
        hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node)
                br_multicast_del_group_src(ent);
        br_mdb_notify(br->dev, mp, pg, RTM_DELMDB);
@@ -1174,6 +1175,7 @@ struct net_bridge_port_group *br_multicast_new_port_group(
        p->filter_mode = filter_mode;
        p->rt_protocol = rt_protocol;
        p->eht_host_tree = RB_ROOT;
+       p->eht_set_tree = RB_ROOT;
        p->mcast_gc.destroy = br_multicast_destroy_port_group;
        INIT_HLIST_HEAD(&p->src_list);
 
index 5cebca45e72c1f27de0399da571510967896d7f6..f4bbf2dc9fc84c7f19a41a080556f925298d04b1 100644 (file)
 #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(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)
@@ -66,6 +75,54 @@ static int br_multicast_eht_host_filter_mode(struct net_bridge_port_group *pg,
        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));
@@ -75,6 +132,107 @@ static void __eht_destroy_host(struct net_bridge_group_eht_host *eht_host)
        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,
@@ -113,3 +271,166 @@ __eht_lookup_create_host(struct net_bridge_port_group *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_create_eht_set_entry(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(br));
+       mod_timer(&eht_set->timer, jiffies + br_multicast_gmi(br));
+
+       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;
+}
index 0c9c4267969dc1d79dce1a890c0fdfffa212133e..bba507c9acb09d1fe10e3477fbf096e57dbdab04 100644 (file)
@@ -47,4 +47,6 @@ struct net_bridge_group_eht_set {
        struct net_bridge_mcast_gc      mcast_gc;
 };
 
+void br_multicast_eht_clean_sets(struct net_bridge_port_group *pg);
+
 #endif /* _BR_PRIVATE_MCAST_EHT_H_ */