[PATCH v3 net-next 15/15] ip6mr: Replace RTNL with a dedicated mutex for MFC.

Kuniyuki Iwashima
2026-04-07T21:20:28Z

ip6mr does not have rtnetlink interface for MFC unlike ipmr,
which uses dev_get_by_index_rcu() to set struct mfcctl.mfcc_parent.

ip6mr_mfc_add() and ip6mr_mfc_delete() are called under RTNL
from ip6_mroute_setsockopt() only.

There are no RTNL dependant, but ip6_mroute_setsockopt() reuses
RTNL just for mrt->mfc_hash and mrt->mfc_cache_list.

Let's replace RTNL with a new per-netns mutex.

Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/net/netns/ipv6.h | 1 +
net/ipv6/ip6mr.c | 21 ++++++++++++++-------
2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h
index df00567374f4..6453d70d5946 100644
--- a/include/net/netns/ipv6.h
+++ b/include/net/netns/ipv6.h
@@ -114,6 +114,7 @@ struct netns_ipv6 {
#endif
struct fib_notifier_ops *ip6mr_notifier_ops;
atomic_t ipmr_seq;
+ struct mutex mfc_mutex;
#endif
atomic_t dev_addr_genid;
atomic_t fib6_sernum;
diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c
index b1443fb65b40..e4c31d05744b 100644
--- a/net/ipv6/ip6mr.c
+++ b/net/ipv6/ip6mr.c
@@ -1256,7 +1256,6 @@ static int ip6mr_mfc_delete(struct mr_table *mrt, struct mf6cctl *mfc,
{
struct mfc6_cache *c;

- /* The entries are added/deleted only under RTNL */
rcu_read_lock();
c = ip6mr_cache_find_parent(mrt, &mfc->mf6cc_origin.sin6_addr,
&mfc->mf6cc_mcastgrp.sin6_addr, parent);
@@ -1346,6 +1345,8 @@ static int __net_init ip6mr_net_init(struct net *net)
LIST_HEAD(dev_kill_list);
int err;

+ mutex_init(&net->ipv6.mfc_mutex);
+
err = ip6mr_notifier_init(net);
if (err)
return err;
@@ -1474,7 +1475,6 @@ static int ip6mr_mfc_add(struct net *net, struct mr_table *mrt,
ttls[i] = 1;
}

- /* The entries are added/deleted only under RTNL */
rcu_read_lock();
c = ip6mr_cache_find_parent(mrt, &mfc->mf6cc_origin.sin6_addr,
&mfc->mf6cc_mcastgrp.sin6_addr, parent);
@@ -1553,6 +1553,7 @@ static int ip6mr_mfc_add(struct net *net, struct mr_table *mrt,
static void mroute_clean_tables(struct mr_table *mrt, int flags,
struct list_head *dev_kill_list)
{
+ struct net *net = read_pnet(&mrt->net);
struct mr_mfc *c, *tmp;
int i;

@@ -1569,18 +1570,21 @@ static void mroute_clean_tables(struct mr_table *mrt, int flags,

/* Wipe the cache */
if (flags & (MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC)) {
+ mutex_lock(&net->ipv6.mfc_mutex);
+
list_for_each_entry_safe(c, tmp, &mrt->mfc_cache_list, list) {
if (((c->mfc_flags & MFC_STATIC) && !(flags & MRT6_FLUSH_MFC_STATIC)) ||
(!(c->mfc_flags & MFC_STATIC) && !(flags & MRT6_FLUSH_MFC)))
continue;
rhltable_remove(&mrt->mfc_hash, &c->mnode, ip6mr_rht_params);
list_del_rcu(&c->list);
- call_ip6mr_mfc_entry_notifiers(read_pnet(&mrt->net),
- FIB_EVENT_ENTRY_DEL,
+ call_ip6mr_mfc_entry_notifiers(net, FIB_EVENT_ENTRY_DEL,
(struct mfc6_cache *)c, mrt->id);
mr6_netlink_event(mrt, (struct mfc6_cache *)c, RTM_DELROUTE);
mr_cache_put(c);
}
+
+ mutex_unlock(&net->ipv6.mfc_mutex);
}

if (flags & MRT6_FLUSH_MFC) {
@@ -1763,15 +1767,18 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
return -EFAULT;
if (parent == 0)
parent = mfc.mf6cc_parent;
- rtnl_lock();
+
+ mutex_lock(&net->ipv6.mfc_mutex);
+
if (optname == MRT6_DEL_MFC || optname == MRT6_DEL_MFC_PROXY)
ret = ip6mr_mfc_delete(mrt, &mfc, parent);
else
ret = ip6mr_mfc_add(net, mrt, &mfc,
sk ==
- rtnl_dereference(mrt->mroute_sk),
+ rcu_access_pointer(mrt->mroute_sk),
parent);
- rtnl_unlock();
+
+ mutex_unlock(&net->ipv6.mfc_mutex);
return ret;

case MRT6_FLUSH:
--
2.53.0.1213.gd9a14994de-goog