neighbour: add RCU protection to neigh_tables[]
authorEric Dumazet <edumazet@google.com>
Thu, 18 Apr 2024 09:51:04 +0000 (09:51 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 19 Apr 2024 11:39:20 +0000 (12:39 +0100)
In order to remove RTNL protection from neightbl_dump_info()
and neigh_dump_info() later, we need to add
RCU protection to neigh_tables[].

Signed-off-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/core/neighbour.c

index 47d07b122f7abf36c529e125b87633049bfad0f6..ccb770539f1a8ef52c751cf4bfec3c5b6c2c3496 100644 (file)
@@ -1771,7 +1771,7 @@ static void neigh_parms_destroy(struct neigh_parms *parms)
 
 static struct lock_class_key neigh_table_proxy_queue_class;
 
-static struct neigh_table *neigh_tables[NEIGH_NR_TABLES] __read_mostly;
+static struct neigh_table __rcu *neigh_tables[NEIGH_NR_TABLES] __read_mostly;
 
 void neigh_table_init(int index, struct neigh_table *tbl)
 {
@@ -1828,13 +1828,19 @@ void neigh_table_init(int index, struct neigh_table *tbl)
        tbl->last_flush = now;
        tbl->last_rand  = now + tbl->parms.reachable_time * 20;
 
-       neigh_tables[index] = tbl;
+       rcu_assign_pointer(neigh_tables[index], tbl);
 }
 EXPORT_SYMBOL(neigh_table_init);
 
+/*
+ * Only called from ndisc_cleanup(), which means this is dead code
+ * because we no longer can unload IPv6 module.
+ */
 int neigh_table_clear(int index, struct neigh_table *tbl)
 {
-       neigh_tables[index] = NULL;
+       RCU_INIT_POINTER(neigh_tables[index], NULL);
+       synchronize_rcu();
+
        /* It is not clean... Fix it to unload IPv6 module safely */
        cancel_delayed_work_sync(&tbl->managed_work);
        cancel_delayed_work_sync(&tbl->gc_work);
@@ -1866,10 +1872,10 @@ static struct neigh_table *neigh_find_table(int family)
 
        switch (family) {
        case AF_INET:
-               tbl = neigh_tables[NEIGH_ARP_TABLE];
+               tbl = rcu_dereference_rtnl(neigh_tables[NEIGH_ARP_TABLE]);
                break;
        case AF_INET6:
-               tbl = neigh_tables[NEIGH_ND_TABLE];
+               tbl = rcu_dereference_rtnl(neigh_tables[NEIGH_ND_TABLE]);
                break;
        }
 
@@ -2333,7 +2339,7 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
        ndtmsg = nlmsg_data(nlh);
 
        for (tidx = 0; tidx < NEIGH_NR_TABLES; tidx++) {
-               tbl = neigh_tables[tidx];
+               tbl = rcu_dereference_rtnl(neigh_tables[tidx]);
                if (!tbl)
                        continue;
                if (ndtmsg->ndtm_family && tbl->family != ndtmsg->ndtm_family)
@@ -2521,7 +2527,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
        for (tidx = 0; tidx < NEIGH_NR_TABLES; tidx++) {
                struct neigh_parms *p;
 
-               tbl = neigh_tables[tidx];
+               tbl = rcu_dereference_rtnl(neigh_tables[tidx]);
                if (!tbl)
                        continue;
 
@@ -2881,7 +2887,7 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
        s_t = cb->args[0];
 
        for (t = 0; t < NEIGH_NR_TABLES; t++) {
-               tbl = neigh_tables[t];
+               tbl = rcu_dereference_rtnl(neigh_tables[t]);
 
                if (!tbl)
                        continue;
@@ -3145,14 +3151,15 @@ int neigh_xmit(int index, struct net_device *dev,
               const void *addr, struct sk_buff *skb)
 {
        int err = -EAFNOSUPPORT;
+
        if (likely(index < NEIGH_NR_TABLES)) {
                struct neigh_table *tbl;
                struct neighbour *neigh;
 
-               tbl = neigh_tables[index];
-               if (!tbl)
-                       goto out;
                rcu_read_lock();
+               tbl = rcu_dereference(neigh_tables[index]);
+               if (!tbl)
+                       goto out_unlock;
                if (index == NEIGH_ARP_TABLE) {
                        u32 key = *((u32 *)addr);
 
@@ -3168,6 +3175,7 @@ int neigh_xmit(int index, struct net_device *dev,
                        goto out_kfree_skb;
                }
                err = READ_ONCE(neigh->output)(neigh, skb);
+out_unlock:
                rcu_read_unlock();
        }
        else if (index == NEIGH_LINK_TABLE) {