Merge branch 'ip6mr-no-rtnl-for-rtnl_family_ip6mr-rtnetlink'

Kuniyuki Iwashima says:

====================
ip6mr: No RTNL for RTNL_FAMILY_IP6MR rtnetlink.

This series is the IPv6 version of

  https://lore.kernel.org/netdev/20260228221800.1082070-1-kuniyu@google.com/

and removes RTNL from ip6mr rtnetlink handlers.

After this series, there are a few RTNL left in net/ipv6/ip6mr.c
and such users will be converted to per-netns RTNL in another
series.

Patch 1 extends the ipmr selftest to exercise most of the RTNL
 paths in net/ipv6/ipmr.c

Patch 2 - 6 converts RTM_GETROUTE handlers to RCU.

Patch 7 removes struct fib_dump_filter.rtnl_held.

Patch 8 use RCU for mr_table for CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=n
 for ->exit_rtnl().

Patch 9 move fib_rules_unregister() to ->exit()

Patch 10 - 12 converts ->exit_batch() to ->exit_rtnl() to
 save one RTNL in cleanup_net().

Patch 13 removes unnecessary RTNL during setup_net() failure.

Patch 14 drops RTNL for MRT6_(ADD|DEL)_MFC(_PROXY)?.

Patch 15 misc clean up

v2: https://lore.kernel.org/20260410211726.1668756-1-kuniyu@google.com
v1: https://lore.kernel.org/20260407212001.2368593-1-kuniyu@google.com
====================

Link: https://patch.msgid.link/20260604224712.3209821-1-kuniyu@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-06-08 17:06:26 -07:00
commit b8e40c907b
8 changed files with 312 additions and 212 deletions

View File

@ -269,7 +269,6 @@ struct fib_dump_filter {
bool filter_set;
bool dump_routes;
bool dump_exceptions;
bool rtnl_held;
unsigned char protocol;
unsigned char rt_type;
unsigned int flags;

View File

@ -112,13 +112,14 @@ struct netns_ipv6 {
struct list_head mr6_tables;
struct fib_rules_ops *mr6_rules_ops;
#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;
struct seg6_pernet_data *seg6_data;
struct fib_notifier_ops *notifier_ops;
struct fib_notifier_ops *ip6mr_notifier_ops;
atomic_t ipmr_seq;
int flowlabel_count;
struct {
struct hlist_head head;

View File

@ -946,9 +946,6 @@ int ip_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh,
struct rtmsg *rtm;
int err, i;
if (filter->rtnl_held)
ASSERT_RTNL();
rtm = nlmsg_payload(nlh, sizeof(*rtm));
if (!rtm) {
NL_SET_ERR_MSG(extack, "Invalid header for FIB dump request");
@ -992,10 +989,8 @@ int ip_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh,
break;
case RTA_OIF:
ifindex = nla_get_u32(tb[i]);
if (filter->rtnl_held)
filter->dev = __dev_get_by_index(net, ifindex);
else
filter->dev = dev_get_by_index_rcu(net, ifindex);
filter->dev = dev_get_by_index_rcu(net, ifindex);
if (!filter->dev)
return -ENODEV;
break;
@ -1017,18 +1012,16 @@ EXPORT_SYMBOL_GPL(ip_valid_fib_dump_req);
static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
{
const struct nlmsghdr *nlh = cb->nlh;
struct net *net = sock_net(skb->sk);
struct fib_dump_filter filter = {
.dump_routes = true,
.dump_exceptions = true,
.rtnl_held = false,
};
const struct nlmsghdr *nlh = cb->nlh;
struct net *net = sock_net(skb->sk);
unsigned int h, s_h;
unsigned int e = 0, s_e;
struct fib_table *tb;
unsigned int e = 0, s_e, h, s_h;
struct hlist_head *head;
int dumped = 0, err = 0;
struct fib_table *tb;
rcu_read_lock();
if (cb->strict_check) {

View File

@ -2777,9 +2777,7 @@ static int ipmr_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
static int ipmr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
{
struct fib_dump_filter filter = {
.rtnl_held = false,
};
struct fib_dump_filter filter = {};
int err;
rcu_read_lock();

View File

@ -633,7 +633,6 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
struct rt6_rtnl_dump_arg arg = {
.filter.dump_exceptions = true,
.filter.dump_routes = true,
.filter.rtnl_held = false,
};
const struct nlmsghdr *nlh = cb->nlh;
struct net *net = sock_net(skb->sk);

View File

@ -85,7 +85,8 @@ static DEFINE_SPINLOCK(mfc_unres_lock);
static struct kmem_cache *mrt_cachep __read_mostly;
static struct mr_table *ip6mr_new_table(struct net *net, u32 id);
static void ip6mr_free_table(struct mr_table *mrt);
static void ip6mr_free_table(struct mr_table *mrt,
struct list_head *dev_kill_list);
static void ip6_mr_forward(struct net *net, struct mr_table *mrt,
struct net_device *dev, struct sk_buff *skb,
@ -99,7 +100,8 @@ static int ip6mr_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack);
static int ip6mr_rtm_dumproute(struct sk_buff *skb,
struct netlink_callback *cb);
static void mroute_clean_tables(struct mr_table *mrt, int flags);
static void mroute_clean_tables(struct mr_table *mrt, int flags,
struct list_head *dev_kill_list);
static void ipmr_expire_process(struct timer_list *t);
#ifdef CONFIG_IPV6_MROUTE_MULTIPLE_TABLES
@ -136,16 +138,6 @@ static struct mr_table *__ip6mr_get_table(struct net *net, u32 id)
return NULL;
}
static struct mr_table *ip6mr_get_table(struct net *net, u32 id)
{
struct mr_table *mrt;
rcu_read_lock();
mrt = __ip6mr_get_table(net, id);
rcu_read_unlock();
return mrt;
}
static int ip6mr_fib_lookup(struct net *net, struct flowi6 *flp6,
struct mr_table **mrt)
{
@ -237,6 +229,7 @@ static const struct fib_rules_ops __net_initconst ip6mr_rules_ops_template = {
static int __net_init ip6mr_rules_init(struct net *net)
{
struct fib_rules_ops *ops;
LIST_HEAD(dev_kill_list);
struct mr_table *mrt;
int err;
@ -260,24 +253,26 @@ static int __net_init ip6mr_rules_init(struct net *net)
return 0;
err2:
rtnl_lock();
ip6mr_free_table(mrt);
rtnl_unlock();
ip6mr_free_table(mrt, &dev_kill_list);
err1:
fib_rules_unregister(ops);
return err;
}
static void __net_exit ip6mr_rules_exit(struct net *net)
{
fib_rules_unregister(net->ipv6.mr6_rules_ops);
}
static void __net_exit ip6mr_rules_exit_rtnl(struct net *net,
struct list_head *dev_kill_list)
{
struct mr_table *mrt, *next;
ASSERT_RTNL();
list_for_each_entry_safe(mrt, next, &net->ipv6.mr6_tables, list) {
list_del(&mrt->list);
ip6mr_free_table(mrt);
list_del_rcu(&mrt->list);
ip6mr_free_table(mrt, dev_kill_list);
}
fib_rules_unregister(net->ipv6.mr6_rules_ops);
}
static int ip6mr_rules_dump(struct net *net, struct notifier_block *nb,
@ -298,28 +293,30 @@ bool ip6mr_rule_default(const struct fib_rule *rule)
}
EXPORT_SYMBOL(ip6mr_rule_default);
#else
#define ip6mr_for_each_table(mrt, net) \
for (mrt = net->ipv6.mrt6; mrt; mrt = NULL)
static struct mr_table *ip6mr_mr_table_iter(struct net *net,
struct mr_table *mrt)
{
if (!mrt)
return net->ipv6.mrt6;
return rcu_dereference(net->ipv6.mrt6);
return NULL;
}
static struct mr_table *ip6mr_get_table(struct net *net, u32 id)
static struct mr_table *__ip6mr_get_table(struct net *net, u32 id)
{
return net->ipv6.mrt6;
return rcu_dereference_check(net->ipv6.mrt6,
lockdep_rtnl_is_held() ||
!rcu_access_pointer(net->ipv6.mrt6));
}
#define __ip6mr_get_table ip6mr_get_table
#define ip6mr_for_each_table(mrt, net) \
for (mrt = __ip6mr_get_table(net, 0); mrt; mrt = NULL)
static int ip6mr_fib_lookup(struct net *net, struct flowi6 *flp6,
struct mr_table **mrt)
{
*mrt = net->ipv6.mrt6;
*mrt = rcu_dereference(net->ipv6.mrt6);
if (!*mrt)
return -EAGAIN;
return 0;
}
@ -330,15 +327,22 @@ static int __net_init ip6mr_rules_init(struct net *net)
mrt = ip6mr_new_table(net, RT6_TABLE_DFLT);
if (IS_ERR(mrt))
return PTR_ERR(mrt);
net->ipv6.mrt6 = mrt;
rcu_assign_pointer(net->ipv6.mrt6, mrt);
return 0;
}
static void __net_exit ip6mr_rules_exit(struct net *net)
{
ASSERT_RTNL();
ip6mr_free_table(net->ipv6.mrt6);
net->ipv6.mrt6 = NULL;
}
static void __net_exit ip6mr_rules_exit_rtnl(struct net *net,
struct list_head *dev_kill_list)
{
struct mr_table *mrt = rcu_dereference_protected(net->ipv6.mrt6, 1);
RCU_INIT_POINTER(net->ipv6.mrt6, NULL);
ip6mr_free_table(mrt, dev_kill_list);
}
static int ip6mr_rules_dump(struct net *net, struct notifier_block *nb,
@ -353,6 +357,17 @@ static unsigned int ip6mr_rules_seq_read(const struct net *net)
}
#endif
static struct mr_table *ip6mr_get_table(struct net *net, u32 id)
{
struct mr_table *mrt;
rcu_read_lock();
mrt = __ip6mr_get_table(net, id);
rcu_read_unlock();
return mrt;
}
static int ip6mr_hash_cmp(struct rhashtable_compare_arg *arg,
const void *ptr)
{
@ -402,17 +417,23 @@ static struct mr_table *ip6mr_new_table(struct net *net, u32 id)
ipmr_expire_process, ip6mr_new_table_set);
}
static void ip6mr_free_table(struct mr_table *mrt)
static void ip6mr_free_table(struct mr_table *mrt,
struct list_head *dev_kill_list)
{
struct net *net = read_pnet(&mrt->net);
LIST_HEAD(ip6mr_dev_kill_list);
WARN_ON_ONCE(!mr_can_free_table(net));
timer_shutdown_sync(&mrt->ipmr_expire_timer);
mroute_clean_tables(mrt, MRT6_FLUSH_MIFS | MRT6_FLUSH_MIFS_STATIC |
MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC);
rhltable_destroy(&mrt->mfc_hash);
kfree(mrt);
MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC,
&ip6mr_dev_kill_list);
mr_table_free(mrt);
WARN_ON_ONCE(!net_initialized(net) && !list_empty(&ip6mr_dev_kill_list));
list_splice(&ip6mr_dev_kill_list, dev_kill_list);
}
#ifdef CONFIG_PROC_FS
@ -623,18 +644,22 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb,
if (!pskb_inet_may_pull(skb))
goto tx_err;
rcu_read_lock();
if (ip6mr_fib_lookup(net, &fl6, &mrt) < 0)
goto tx_err;
goto tx_lookup_err;
DEV_STATS_ADD(dev, tx_bytes, skb->len);
DEV_STATS_INC(dev, tx_packets);
rcu_read_lock();
ip6mr_cache_report(mrt, skb, READ_ONCE(mrt->mroute_reg_vif_num),
MRT6MSG_WHOLEPKT);
rcu_read_unlock();
kfree_skb(skb);
return NETDEV_TX_OK;
tx_lookup_err:
rcu_read_unlock();
tx_err:
DEV_STATS_INC(dev, tx_errors);
kfree_skb(skb);
@ -1157,11 +1182,18 @@ static int ip6mr_cache_report(const struct mr_table *mrt, struct sk_buff *pkt,
static int ip6mr_cache_unresolved(struct mr_table *mrt, mifi_t mifi,
struct sk_buff *skb, struct net_device *dev)
{
struct mfc6_cache *c;
struct net *net = read_pnet(&mrt->net);
struct mfc6_cache *c = NULL;
bool found = false;
int err;
spin_lock_bh(&mfc_unres_lock);
if (!check_net(net)) {
err = -EINVAL;
goto err;
}
list_for_each_entry(c, &mrt->mfc_unres_queue, _c.list) {
if (ipv6_addr_equal(&c->mf6c_mcastgrp, &ipv6_hdr(skb)->daddr) &&
ipv6_addr_equal(&c->mf6c_origin, &ipv6_hdr(skb)->saddr)) {
@ -1177,10 +1209,8 @@ static int ip6mr_cache_unresolved(struct mr_table *mrt, mifi_t mifi,
c = ip6mr_cache_alloc_unres();
if (!c) {
spin_unlock_bh(&mfc_unres_lock);
kfree_skb(skb);
return -ENOBUFS;
err = -ENOBUFS;
goto err;
}
/* Fill in the new cache entry */
@ -1192,16 +1222,8 @@ static int ip6mr_cache_unresolved(struct mr_table *mrt, mifi_t mifi,
* Reflect first query at pim6sd
*/
err = ip6mr_cache_report(mrt, skb, mifi, MRT6MSG_NOCACHE);
if (err < 0) {
/* If the report failed throw the cache entry
out - Brad Parker
*/
spin_unlock_bh(&mfc_unres_lock);
ip6mr_cache_free(c);
kfree_skb(skb);
return err;
}
if (err < 0)
goto err;
atomic_inc(&mrt->cache_resolve_queue_len);
list_add(&c->_c.list, &mrt->mfc_unres_queue);
@ -1212,18 +1234,26 @@ static int ip6mr_cache_unresolved(struct mr_table *mrt, mifi_t mifi,
/* See if we can append the packet */
if (c->_c.mfc_un.unres.unresolved.qlen > 3) {
kfree_skb(skb);
c = NULL;
err = -ENOBUFS;
} else {
if (dev) {
skb->dev = dev;
skb->skb_iif = dev->ifindex;
}
skb_queue_tail(&c->_c.mfc_un.unres.unresolved, skb);
err = 0;
goto err;
}
if (dev) {
skb->dev = dev;
skb->skb_iif = dev->ifindex;
}
skb_queue_tail(&c->_c.mfc_un.unres.unresolved, skb);
spin_unlock_bh(&mfc_unres_lock);
return 0;
err:
spin_unlock_bh(&mfc_unres_lock);
if (c)
ip6mr_cache_free(c);
kfree_skb(skb);
return err;
}
@ -1236,7 +1266,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);
@ -1323,8 +1352,13 @@ static void __net_exit ip6mr_notifier_exit(struct net *net)
/* Setup for IP multicast routing */
static int __net_init ip6mr_net_init(struct net *net)
{
#ifdef CONFIG_PROC_FS
LIST_HEAD(dev_kill_list);
#endif
int err;
mutex_init(&net->ipv6.mfc_mutex);
err = ip6mr_notifier_init(net);
if (err)
return err;
@ -1349,9 +1383,8 @@ static int __net_init ip6mr_net_init(struct net *net)
proc_cache_fail:
remove_proc_entry("ip6_mr_vif", net->proc_net);
proc_vif_fail:
rtnl_lock();
ip6mr_rules_exit_rtnl(net, &dev_kill_list);
ip6mr_rules_exit(net);
rtnl_unlock();
#endif
ip6mr_rules_fail:
ip6mr_notifier_exit(net);
@ -1364,29 +1397,27 @@ static void __net_exit ip6mr_net_exit(struct net *net)
remove_proc_entry("ip6_mr_cache", net->proc_net);
remove_proc_entry("ip6_mr_vif", net->proc_net);
#endif
ip6mr_rules_exit(net);
ip6mr_notifier_exit(net);
}
static void __net_exit ip6mr_net_exit_batch(struct list_head *net_list)
static void __net_exit ip6mr_net_exit_rtnl(struct net *net,
struct list_head *dev_kill_list)
{
struct net *net;
rtnl_lock();
list_for_each_entry(net, net_list, exit_list)
ip6mr_rules_exit(net);
rtnl_unlock();
ip6mr_rules_exit_rtnl(net, dev_kill_list);
}
static struct pernet_operations ip6mr_net_ops = {
.init = ip6mr_net_init,
.exit = ip6mr_net_exit,
.exit_batch = ip6mr_net_exit_batch,
.exit_rtnl = ip6mr_net_exit_rtnl,
};
static const struct rtnl_msg_handler ip6mr_rtnl_msg_handlers[] __initconst_or_module = {
{.owner = THIS_MODULE, .protocol = RTNL_FAMILY_IP6MR,
.msgtype = RTM_GETROUTE,
.doit = ip6mr_rtm_getroute, .dumpit = ip6mr_rtm_dumproute},
.doit = ip6mr_rtm_getroute, .dumpit = ip6mr_rtm_dumproute,
.flags = RTNL_FLAG_DOIT_UNLOCKED | RTNL_FLAG_DUMP_UNLOCKED},
};
int __init ip6_mr_init(void)
@ -1456,7 +1487,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);
@ -1531,10 +1561,11 @@ static int ip6mr_mfc_add(struct net *net, struct mr_table *mrt,
* Close the multicast socket, and clear the vif tables etc
*/
static void mroute_clean_tables(struct mr_table *mrt, int flags)
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;
LIST_HEAD(list);
int i;
/* Shut down all active vif entries */
@ -1544,29 +1575,32 @@ static void mroute_clean_tables(struct mr_table *mrt, int flags)
!(flags & MRT6_FLUSH_MIFS_STATIC)) ||
(!(mrt->vif_table[i].flags & VIFF_STATIC) && !(flags & MRT6_FLUSH_MIFS)))
continue;
mif6_delete(mrt, i, 0, &list);
mif6_delete(mrt, i, 0, dev_kill_list);
}
unregister_netdevice_many(&list);
}
/* 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) {
if (atomic_read(&mrt->cache_resolve_queue_len) != 0) {
if (atomic_read(&mrt->cache_resolve_queue_len) != 0 ||
!check_net(net)) {
spin_lock_bh(&mfc_unres_lock);
list_for_each_entry_safe(c, tmp, &mrt->mfc_unres_queue, list) {
list_del(&c->list);
@ -1609,6 +1643,7 @@ int ip6mr_sk_done(struct sock *sk)
{
struct net *net = sock_net(sk);
struct ipv6_devconf *devconf;
LIST_HEAD(dev_kill_list);
struct mr_table *mrt;
int err = -EACCES;
@ -1636,11 +1671,13 @@ int ip6mr_sk_done(struct sock *sk)
NETCONFA_IFINDEX_ALL,
net->ipv6.devconf_all);
mroute_clean_tables(mrt, MRT6_FLUSH_MIFS | MRT6_FLUSH_MFC);
mroute_clean_tables(mrt, MRT6_FLUSH_MIFS | MRT6_FLUSH_MFC,
&dev_kill_list);
err = 0;
break;
}
}
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
return err;
@ -1742,27 +1779,33 @@ 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:
{
LIST_HEAD(dev_kill_list);
int flags;
if (optlen != sizeof(flags))
return -EINVAL;
if (copy_from_sockptr(&flags, optval, sizeof(flags)))
return -EFAULT;
rtnl_lock();
mroute_clean_tables(mrt, flags);
mroute_clean_tables(mrt, flags, &dev_kill_list);
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
return 0;
}
@ -1778,7 +1821,7 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
return -EINVAL;
if (copy_from_sockptr(&v, optval, sizeof(v)))
return -EFAULT;
mrt->mroute_do_assert = v;
WRITE_ONCE(mrt->mroute_do_assert, v);
return 0;
}
@ -1798,9 +1841,9 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
rtnl_lock();
ret = 0;
if (v != mrt->mroute_do_pim) {
mrt->mroute_do_pim = v;
mrt->mroute_do_assert = v;
mrt->mroute_do_wrvifwhole = do_wrmifwhole;
WRITE_ONCE(mrt->mroute_do_pim, v);
WRITE_ONCE(mrt->mroute_do_assert, v);
WRITE_ONCE(mrt->mroute_do_wrvifwhole, do_wrmifwhole);
}
rtnl_unlock();
return ret;
@ -1868,11 +1911,11 @@ int ip6_mroute_getsockopt(struct sock *sk, int optname, sockptr_t optval,
break;
#ifdef CONFIG_IPV6_PIMSM_V2
case MRT6_PIM:
val = mrt->mroute_do_pim;
val = READ_ONCE(mrt->mroute_do_pim);
break;
#endif
case MRT6_ASSERT:
val = mrt->mroute_do_assert;
val = READ_ONCE(mrt->mroute_do_assert);
break;
default:
return -ENOPROTOOPT;
@ -2175,20 +2218,20 @@ static void ip6_mr_forward(struct net *net, struct mr_table *mrt,
if (rcu_access_pointer(mrt->vif_table[vif].dev) != dev) {
atomic_long_inc(&c->_c.mfc_un.res.wrong_if);
if (true_vifi >= 0 && mrt->mroute_do_assert &&
if (true_vifi >= 0 && READ_ONCE(mrt->mroute_do_assert) &&
/* pimsm uses asserts, when switching from RPT to SPT,
so that we cannot check that packet arrived on an oif.
It is bad, but otherwise we would need to move pretty
large chunk of pimd to kernel. Ough... --ANK
*/
(mrt->mroute_do_pim ||
(READ_ONCE(mrt->mroute_do_pim) ||
c->_c.mfc_un.res.ttls[true_vifi] < 255) &&
time_after(jiffies,
c->_c.mfc_un.res.last_assert +
MFC_ASSERT_THRESH)) {
c->_c.mfc_un.res.last_assert = jiffies;
ip6mr_cache_report(mrt, skb, true_vifi, MRT6MSG_WRONGMIF);
if (mrt->mroute_do_wrvifwhole)
if (READ_ONCE(mrt->mroute_do_wrvifwhole))
ip6mr_cache_report(mrt, skb, true_vifi,
MRT6MSG_WRMIFWHOLE);
}
@ -2537,7 +2580,7 @@ static int _ip6mr_fill_mroute(struct mr_table *mrt, struct sk_buff *skb,
cmd, flags);
}
static int mr6_msgsize(bool unresolved, int maxvif)
static int mr6_msgsize(bool unresolved)
{
size_t len =
NLMSG_ALIGN(sizeof(struct rtmsg))
@ -2550,7 +2593,7 @@ static int mr6_msgsize(bool unresolved, int maxvif)
len = len
+ nla_total_size(4) /* RTA_IIF */
+ nla_total_size(0) /* RTA_MULTIPATH */
+ maxvif * NLA_ALIGN(sizeof(struct rtnexthop))
+ MAXMIFS * NLA_ALIGN(sizeof(struct rtnexthop))
/* RTA_MFC_STATS */
+ nla_total_size_64bit(sizeof(struct rta_mfc_stats))
;
@ -2565,8 +2608,7 @@ static void mr6_netlink_event(struct mr_table *mrt, struct mfc6_cache *mfc,
struct sk_buff *skb;
int err = -ENOBUFS;
skb = nlmsg_new(mr6_msgsize(mfc->_c.mfc_parent >= MAXMIFS, mrt->maxvif),
GFP_ATOMIC);
skb = nlmsg_new(mr6_msgsize(mfc->_c.mfc_parent >= MAXMIFS), GFP_ATOMIC);
if (!skb)
goto errout;
@ -2701,54 +2743,60 @@ static int ip6mr_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
if (err < 0)
return err;
skb = nlmsg_new(mr6_msgsize(false), GFP_KERNEL);
if (!skb)
return -ENOBUFS;
if (tb[RTA_SRC])
src = nla_get_in6_addr(tb[RTA_SRC]);
if (tb[RTA_DST])
grp = nla_get_in6_addr(tb[RTA_DST]);
tableid = nla_get_u32_default(tb[RTA_TABLE], 0);
rcu_read_lock();
mrt = __ip6mr_get_table(net, tableid ?: RT_TABLE_DEFAULT);
if (!mrt) {
NL_SET_ERR_MSG_MOD(extack, "MR table does not exist");
return -ENOENT;
err = -ENOENT;
goto err;
}
/* entries are added/deleted only under RTNL */
rcu_read_lock();
cache = ip6mr_cache_find(mrt, &src, &grp);
rcu_read_unlock();
if (!cache) {
NL_SET_ERR_MSG_MOD(extack, "MR cache entry not found");
return -ENOENT;
err = -ENOENT;
goto err;
}
skb = nlmsg_new(mr6_msgsize(false, mrt->maxvif), GFP_KERNEL);
if (!skb)
return -ENOBUFS;
err = ip6mr_fill_mroute(mrt, skb, NETLINK_CB(in_skb).portid,
nlh->nlmsg_seq, cache, RTM_NEWROUTE, 0);
if (err < 0) {
kfree_skb(skb);
return err;
}
if (err < 0)
goto err;
rcu_read_unlock();
return rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
err:
rcu_read_unlock();
kfree_skb(skb);
return err;
}
static int ip6mr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
{
const struct nlmsghdr *nlh = cb->nlh;
struct fib_dump_filter filter = {
.rtnl_held = true,
};
struct fib_dump_filter filter = {};
int err;
rcu_read_lock();
if (cb->strict_check) {
err = ip_valid_fib_dump_req(sock_net(skb->sk), nlh,
&filter, cb);
if (err < 0)
return err;
goto unlock;
}
if (filter.table_id) {
@ -2756,17 +2804,26 @@ static int ip6mr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
mrt = __ip6mr_get_table(sock_net(skb->sk), filter.table_id);
if (!mrt) {
if (rtnl_msg_family(cb->nlh) != RTNL_FAMILY_IP6MR)
return skb->len;
if (rtnl_msg_family(cb->nlh) != RTNL_FAMILY_IP6MR) {
err = skb->len;
goto unlock;
}
NL_SET_ERR_MSG_MOD(cb->extack, "MR table does not exist");
return -ENOENT;
err = -ENOENT;
goto unlock;
}
err = mr_table_dump(mrt, skb, cb, _ip6mr_fill_mroute,
&mfc_unres_lock, &filter);
return skb->len ? : err;
err = skb->len ? : err;
goto unlock;
}
return mr_rtm_dumproute(skb, cb, ip6mr_mr_table_iter,
_ip6mr_fill_mroute, &mfc_unres_lock, &filter);
err = mr_rtm_dumproute(skb, cb, ip6mr_mr_table_iter,
_ip6mr_fill_mroute, &mfc_unres_lock, &filter);
unlock:
rcu_read_unlock();
return err;
}

View File

@ -2221,12 +2221,10 @@ static bool mpls_rt_uses_dev(struct mpls_route *rt,
static int mpls_dump_routes(struct sk_buff *skb, struct netlink_callback *cb)
{
struct mpls_route __rcu **platform_label;
const struct nlmsghdr *nlh = cb->nlh;
struct net *net = sock_net(skb->sk);
struct mpls_route __rcu **platform_label;
struct fib_dump_filter filter = {
.rtnl_held = false,
};
struct fib_dump_filter filter = {};
unsigned int flags = NLM_F_MULTI;
size_t platform_labels;
unsigned int index;

View File

@ -2,7 +2,9 @@
/* Copyright 2026 Google LLC */
#include <linux/if.h>
#include <linux/in6.h>
#include <linux/mroute.h>
#include <linux/mroute6.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/socket.h>
@ -17,6 +19,14 @@ FIXTURE(ipmr)
int netlink_sk;
int raw_sk;
int veth_ifindex;
union {
struct vifctl vif;
struct mif6ctl vif6;
};
union {
struct mfcctl mfc;
struct mf6cctl mfc6;
};
};
FIXTURE_VARIANT(ipmr)
@ -24,7 +34,14 @@ FIXTURE_VARIANT(ipmr)
int family;
int protocol;
int level;
int rtm_family;
int opts[MRT_MAX - MRT_BASE + 1];
int flush_flags;
int vif_size;
char vif_check_cmd_pimreg[64];
char vif_check_cmd_veth[64];
int mfc_size;
char mfc_check_cmd[1024];
};
FIXTURE_VARIANT_ADD(ipmr, ipv4)
@ -32,6 +49,7 @@ FIXTURE_VARIANT_ADD(ipmr, ipv4)
.family = AF_INET,
.protocol = IPPROTO_IGMP,
.level = IPPROTO_IP,
.rtm_family = RTNL_FAMILY_IPMR,
.opts = {
MRT_INIT,
MRT_DONE,
@ -47,6 +65,44 @@ FIXTURE_VARIANT_ADD(ipmr, ipv4)
MRT_DEL_MFC_PROXY,
MRT_FLUSH,
},
.flush_flags = MRT_FLUSH_MFC | MRT_FLUSH_MFC_STATIC |
MRT_FLUSH_VIFS | MRT_FLUSH_VIFS_STATIC,
.vif_size = sizeof(struct vifctl),
.vif_check_cmd_pimreg = "cat /proc/net/ip_mr_vif | grep -q pimreg",
.vif_check_cmd_veth = "cat /proc/net/ip_mr_vif | grep -q veth",
.mfc_size = sizeof(struct mfcctl),
.mfc_check_cmd = "cat /proc/net/ip_mr_cache | grep -q '00000000 00000000'",
};
FIXTURE_VARIANT_ADD(ipmr, ipv6)
{
.family = AF_INET6,
.protocol = IPPROTO_ICMPV6,
.level = IPPROTO_IPV6,
.rtm_family = RTNL_FAMILY_IP6MR,
.opts = {
MRT6_INIT,
MRT6_DONE,
MRT6_ADD_MIF,
MRT6_DEL_MIF,
MRT6_ADD_MFC,
MRT6_DEL_MFC,
MRT6_VERSION,
MRT6_ASSERT,
MRT6_PIM,
MRT6_TABLE,
MRT6_ADD_MFC_PROXY,
MRT6_DEL_MFC_PROXY,
MRT6_FLUSH,
},
.flush_flags = MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC |
MRT6_FLUSH_MIFS | MRT6_FLUSH_MIFS_STATIC,
.vif_size = sizeof(struct mif6ctl),
.vif_check_cmd_pimreg = "cat /proc/net/ip6_mr_vif | grep -q pim6reg",
.vif_check_cmd_veth = "cat /proc/net/ip6_mr_vif | grep -q veth",
.mfc_size = sizeof(struct mf6cctl),
.mfc_check_cmd = "cat /proc/net/ip6_mr_cache | "
"grep -q '0000:0000:0000:0000:0000:0000:0000:0000 0000:0000:0000:0000:0000:0000:0000:0000'",
};
struct mfc_attr {
@ -71,7 +127,9 @@ static struct rtattr *nl_add_rtattr(struct nlmsghdr *nlmsg, struct rtattr *rta,
return RTA_NEXT(rta, unused);
}
static int nl_sendmsg_mfc(struct __test_metadata *_metadata, FIXTURE_DATA(ipmr) *self,
static int nl_sendmsg_mfc(struct __test_metadata *_metadata,
FIXTURE_DATA(ipmr) *self,
const FIXTURE_VARIANT(ipmr) *variant,
__u16 nlmsg_type, struct mfc_attr *mfc_attr)
{
struct {
@ -87,7 +145,7 @@ static int nl_sendmsg_mfc(struct __test_metadata *_metadata, FIXTURE_DATA(ipmr)
},
.rtm = {
/* hard requirements in rtm_to_ipmr_mfcc() */
.rtm_family = RTNL_FAMILY_IPMR,
.rtm_family = variant->rtm_family,
.rtm_dst_len = 32,
.rtm_type = RTN_MULTICAST,
.rtm_scope = RT_SCOPE_UNIVERSE,
@ -144,6 +202,18 @@ FIXTURE_SETUP(ipmr)
ASSERT_EQ(0, err);
self->veth_ifindex = ifr.ifr_ifindex;
if (variant->family == AF_INET) {
self->vif = (struct vifctl){
.vifc_flags = VIFF_USE_IFINDEX,
.vifc_lcl_ifindex = self->veth_ifindex,
};
} else {
self->vif6 = (struct mif6ctl){
.mif6c_flags = 0,
.mif6c_pifi = self->veth_ifindex,
};
}
}
FIXTURE_TEARDOWN(ipmr)
@ -169,41 +239,39 @@ TEST_F(ipmr, mrt_init)
TEST_F(ipmr, mrt_add_vif_register)
{
struct vifctl vif = {
.vifc_vifi = 0,
.vifc_flags = VIFF_REGISTER,
};
int err;
memset(&self->vif, 0, variant->vif_size);
if (variant->family == AF_INET)
self->vif.vifc_flags = VIFF_REGISTER;
else
self->vif6.mif6c_flags = MIFF_REGISTER;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
&vif, sizeof(vif));
&self->vif, variant->vif_size);
ASSERT_EQ(0, err);
err = system("cat /proc/net/ip_mr_vif | grep -q pimreg");
err = system(variant->vif_check_cmd_pimreg);
ASSERT_EQ(0, err);
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_DEL_VIF - MRT_BASE],
&vif, sizeof(vif));
&self->vif, variant->vif_size);
ASSERT_EQ(0, err);
}
TEST_F(ipmr, mrt_del_vif_unreg)
{
struct vifctl vif = {
.vifc_vifi = 0,
.vifc_flags = VIFF_USE_IFINDEX,
.vifc_lcl_ifindex = self->veth_ifindex,
};
int err;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
&vif, sizeof(vif));
&self->vif, variant->vif_size);
ASSERT_EQ(0, err);
err = system("cat /proc/net/ip_mr_vif | grep -q veth0");
err = system(variant->vif_check_cmd_veth);
ASSERT_EQ(0, err);
/* VIF is removed along with its device. */
@ -213,23 +281,18 @@ TEST_F(ipmr, mrt_del_vif_unreg)
/* mrt->vif_table[veth_ifindex]->dev is NULL. */
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_DEL_VIF - MRT_BASE],
&vif, sizeof(vif));
&self->vif, variant->vif_size);
ASSERT_EQ(-1, err);
ASSERT_EQ(EADDRNOTAVAIL, errno);
}
TEST_F(ipmr, mrt_del_vif_netns_dismantle)
{
struct vifctl vif = {
.vifc_vifi = 0,
.vifc_flags = VIFF_USE_IFINDEX,
.vifc_lcl_ifindex = self->veth_ifindex,
};
int err;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
&vif, sizeof(vif));
&self->vif, variant->vif_size);
ASSERT_EQ(0, err);
/* Let cleanup_net() remove veth0 and VIF. */
@ -237,49 +300,42 @@ TEST_F(ipmr, mrt_del_vif_netns_dismantle)
TEST_F(ipmr, mrt_add_mfc)
{
struct mfcctl mfc = {};
int err;
/* MRT_ADD_MFC / MRT_ADD_MFC_PROXY does not need vif to exist (unlike netlink). */
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_MFC - MRT_BASE],
&mfc, sizeof(mfc));
&self->mfc, variant->mfc_size);
ASSERT_EQ(0, err);
/* (0.0.0.0 -> 0.0.0.0) */
err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_DEL_MFC - MRT_BASE],
&mfc, sizeof(mfc));
&self->mfc, variant->mfc_size);
}
TEST_F(ipmr, mrt_add_mfc_proxy)
{
struct mfcctl mfc = {};
int err;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_MFC_PROXY - MRT_BASE],
&mfc, sizeof(mfc));
&self->mfc, variant->mfc_size);
ASSERT_EQ(0, err);
err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_DEL_MFC_PROXY - MRT_BASE],
&mfc, sizeof(mfc));
&self->mfc, variant->mfc_size);
}
TEST_F(ipmr, mrt_add_mfc_netlink)
{
struct vifctl vif = {
.vifc_vifi = 0,
.vifc_flags = VIFF_USE_IFINDEX,
.vifc_lcl_ifindex = self->veth_ifindex,
};
struct mfc_attr mfc_attr = {
.table = RT_TABLE_DEFAULT,
.origin = 0,
@ -291,26 +347,21 @@ TEST_F(ipmr, mrt_add_mfc_netlink)
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
&vif, sizeof(vif));
&self->vif, variant->vif_size);
ASSERT_EQ(0, err);
err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(0, err);
err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
err = nl_sendmsg_mfc(_metadata, self, RTM_DELROUTE, &mfc_attr);
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_DELROUTE, &mfc_attr);
ASSERT_EQ(0, err);
}
TEST_F(ipmr, mrt_add_mfc_netlink_proxy)
{
struct vifctl vif = {
.vifc_vifi = 0,
.vifc_flags = VIFF_USE_IFINDEX,
.vifc_lcl_ifindex = self->veth_ifindex,
};
struct mfc_attr mfc_attr = {
.table = RT_TABLE_DEFAULT,
.origin = 0,
@ -322,16 +373,16 @@ TEST_F(ipmr, mrt_add_mfc_netlink_proxy)
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
&vif, sizeof(vif));
&self->vif, variant->vif_size);
ASSERT_EQ(0, err);
err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(0, err);
err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
err = nl_sendmsg_mfc(_metadata, self, RTM_DELROUTE, &mfc_attr);
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_DELROUTE, &mfc_attr);
ASSERT_EQ(0, err);
}
@ -347,12 +398,12 @@ TEST_F(ipmr, mrt_add_mfc_netlink_no_vif)
/* netlink always requires RTA_IIF of an existing vif. */
mfc_attr.ifindex = 0;
err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(-ENFILE, err);
/* netlink always requires RTA_IIF of an existing vif. */
mfc_attr.ifindex = self->veth_ifindex;
err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(-ENFILE, err);
}
@ -387,10 +438,10 @@ TEST_F(ipmr, mrt_del_mfc_netlink_netns_dismantle)
}
/* Create a MFC for mrt->vif_table[0]. */
err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(0, err);
err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
/* Remove mrt->vif_table[0]. */
@ -398,13 +449,13 @@ TEST_F(ipmr, mrt_del_mfc_netlink_netns_dismantle)
ASSERT_EQ(0, err);
/* MFC entry is NOT removed even if the tied VIF is removed... */
err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
/* ... and netlink is not capable of removing such an entry
* because netlink always requires a valid RTA_IIF ... :/
*/
err = nl_sendmsg_mfc(_metadata, self, RTM_DELROUTE, &mfc_attr);
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_DELROUTE, &mfc_attr);
ASSERT_EQ(-ENODEV, err);
/* It can be removed by setsockopt(), but let cleanup_net() remove this time. */
@ -412,11 +463,6 @@ TEST_F(ipmr, mrt_del_mfc_netlink_netns_dismantle)
TEST_F(ipmr, mrt_table_flush)
{
struct vifctl vif = {
.vifc_vifi = 0,
.vifc_flags = VIFF_USE_IFINDEX,
.vifc_lcl_ifindex = self->veth_ifindex,
};
struct mfc_attr mfc_attr = {
.origin = 0,
.group = 0,
@ -424,7 +470,7 @@ TEST_F(ipmr, mrt_table_flush)
.proxy = false,
};
int table_id = 92;
int err, flags;
int err;
/* Set a random table id rather than RT_TABLE_DEFAULT.
* Note that /proc/net/ip_mr_{vif,cache} only supports RT_TABLE_DEFAULT.
@ -436,20 +482,29 @@ TEST_F(ipmr, mrt_table_flush)
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
&vif, sizeof(vif));
&self->vif, variant->vif_size);
ASSERT_EQ(0, err);
mfc_attr.table = table_id;
err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
if (variant->family == AF_INET) {
mfc_attr.table = table_id;
err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
} else {
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_MFC - MRT_BASE],
&self->mfc, variant->mfc_size);
}
ASSERT_EQ(0, err);
/* Flush mrt->vif_table[] and all caches. */
flags = MRT_FLUSH_VIFS | MRT_FLUSH_VIFS_STATIC |
MRT_FLUSH_MFC | MRT_FLUSH_MFC_STATIC;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_FLUSH - MRT_BASE],
&flags, sizeof(flags));
&variant->flush_flags, sizeof(variant->flush_flags));
ASSERT_EQ(0, err);
}
XFAIL_ADD(ipmr, ipv6, mrt_add_mfc_netlink);
XFAIL_ADD(ipmr, ipv6, mrt_add_mfc_netlink_proxy);
XFAIL_ADD(ipmr, ipv6, mrt_add_mfc_netlink_no_vif);
XFAIL_ADD(ipmr, ipv6, mrt_del_mfc_netlink_netns_dismantle);
TEST_HARNESS_MAIN