From: patrick <work_patrick@qq.com>
To: netdev@vger.kernel.org
Cc: davem@davemloft.net, edumazet@kernel.org, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org, corbet@lwn.net,
rdunlap@infradead.org, linux-doc@vger.kernel.org,
skhan@linuxfoundation.org, linux-kselftest@vger.kernel.org,
linux-api@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH net-next 1/2] netlink: add an optional per-netns ROUTE multicast index
Date: Wed, 4 Nov 2026 11:35:25 +0800 [thread overview]
Message-ID: <tencent_1E208C03035DF063166FD46C1DA1978F6306@qq.com> (raw)
In-Reply-To: <20261104033526.2917590-1-work_patrick@qq.com>
NETLINK_ROUTE broadcasts visit subscribers in unrelated network namespaces
before checking whether they can receive a notification.
Maintain one ROUTE subscriber list per network namespace and one global
LISTEN_ALL_NSID list. Select those candidate lists when the new
net.core.netlink_route_mc_index sysctl is enabled. Exclude local sockets
from the second list to avoid duplicate delivery.
Preserve do_one_broadcast and the existing broadcast initialization,
locking, completion and error handling. Change only candidate selection
inside netlink_broadcast_filtered; do not force function inlining or add
separate broadcast setup, completion or indexed-walk helpers.
Use the existing table-grab protection for index maintenance at
subscription, option and removal transitions. Keep both indexes populated
while disabled so switching modes includes existing sockets immediately.
The sysctl defaults to zero, is visible only in init_net and affects all
network namespaces. Other Netlink protocols retain the original walk.
Disabling the switch retains index maintenance and memory costs.
AI assistance: An LLM assisted with implementation adaptation and
simplification.
Signed-off-by: patrick <work_patrick@qq.com>
---
Documentation/admin-guide/sysctl/net.rst | 27 ++++++
net/netlink/af_netlink.c | 109 ++++++++++++++++++++++-
net/netlink/af_netlink.h | 4 +
3 files changed, 137 insertions(+), 3 deletions(-)
diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index fe43e8595..0cda347ed 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -280,6 +280,33 @@ checksums.
These messages are now emitted at KERN_DEBUG and can generally be enabled
and controlled by the dynamic_debug facility.
+netlink_route_mc_index
+---------------------
+
+Selects the multicast candidate lists used for NETLINK_ROUTE notifications.
+This setting is available only in the initial network namespace and applies
+to notifications originating in all network namespaces.
+
+Values:
+
+ - 0 - walk the original global subscription list (default).
+ - 1 - walk the sender's network namespace subscription list and the
+ global NETLINK_LISTEN_ALL_NSID subscription list, regardless of the
+ number of subscribers.
+
+Both auxiliary indexes are maintained in either mode.
+Existing sockets are therefore included immediately when the index is enabled.
+Each broadcast reads the setting once and completes using its selected path;
+changing the setting does not restart broadcasts already in progress.
+Group membership, namespace, permission and socket filter checks still apply
+in both modes. Other Netlink protocols continue to use their original lists.
+
+The indexed path can reduce notification costs when there are many ordinary
+subscribers in other network namespaces. It may add overhead when most
+subscribers are local or listen across network namespaces. Disabling the
+indexed path does not remove its memory or subscription maintenance costs.
+Without CONFIG_SYSCTL, the setting remains at its default value of 0.
+
netdev_budget
-------------
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c
index 1281c42a3..fa21290be 100644
--- a/net/netlink/af_netlink.c
+++ b/net/netlink/af_netlink.c
@@ -31,6 +31,7 @@
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/socket.h>
+#include <linux/sysctl.h>
#include <linux/un.h>
#include <linux/fcntl.h>
#include <linux/termios.h>
@@ -93,6 +94,69 @@ EXPORT_SYMBOL_GPL(nl_table);
static DECLARE_WAIT_QUEUE_HEAD(nl_table_wait);
+/*
+ * Auxiliary NETLINK_ROUTE candidate indexes. Writers hold the table grabbed;
+ * broadcasts pin the same table with netlink_lock_table(). Socket/netns
+ * lifetime therefore follows the original mc_list, without another RCU domain.
+ */
+struct netlink_route_net {
+ struct hlist_head mc_list;
+};
+
+static unsigned int netlink_route_net_id;
+static HLIST_HEAD(netlink_route_all_nsid);
+
+/* Indexes stay populated in both modes; each broadcast samples this once. */
+static int netlink_route_mc_index __read_mostly;
+
+#ifdef CONFIG_SYSCTL
+static const struct ctl_table netlink_route_sysctls[] = {
+ {
+ .procname = "netlink_route_mc_index",
+ .data = &netlink_route_mc_index,
+ .maxlen = sizeof(netlink_route_mc_index),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = SYSCTL_ZERO,
+ .extra2 = SYSCTL_ONE,
+ },
+};
+
+static int __init netlink_route_sysctl_init(void)
+{
+ if (!register_net_sysctl(&init_net, "net/core", netlink_route_sysctls))
+ return -ENOMEM;
+ return 0;
+}
+fs_initcall(netlink_route_sysctl_init);
+#endif
+
+static void netlink_update_route_mc(struct sock *sk, unsigned int subscriptions)
+{
+ struct netlink_sock *nlk = nlk_sk(sk);
+ struct netlink_route_net *nn;
+
+ if (sk->sk_protocol != NETLINK_ROUTE)
+ return;
+
+ lockdep_assert_held(&nl_table_lock);
+ nn = net_generic(sock_net(sk), netlink_route_net_id);
+ if (subscriptions) {
+ if (hlist_unhashed(&nlk->mc_net_node))
+ hlist_add_head(&nlk->mc_net_node, &nn->mc_list);
+ } else if (!hlist_unhashed(&nlk->mc_net_node)) {
+ hlist_del_init(&nlk->mc_net_node);
+ }
+
+ if (subscriptions && nlk_test_bit(LISTEN_ALL_NSID, sk)) {
+ if (hlist_unhashed(&nlk->mc_all_nsid_node))
+ hlist_add_head(&nlk->mc_all_nsid_node,
+ &netlink_route_all_nsid);
+ } else if (!hlist_unhashed(&nlk->mc_all_nsid_node)) {
+ hlist_del_init(&nlk->mc_all_nsid_node);
+ }
+}
+
static struct lock_class_key nlk_cb_mutex_keys[MAX_LINKS];
static const char *const nlk_cb_mutex_key_strings[MAX_LINKS + 1] = {
@@ -598,6 +662,7 @@ static void netlink_remove(struct sock *sk)
__sock_put(sk);
netlink_table_grab();
+ netlink_update_route_mc(sk, 0);
if (nlk_sk(sk)->subscriptions) {
__sk_del_bind_node(sk);
netlink_update_listeners(sk);
@@ -628,6 +693,8 @@ static int __netlink_create(struct net *net, struct socket *sock,
sock_init_data(sock, sk);
nlk = nlk_sk(sk);
+ INIT_HLIST_NODE(&nlk->mc_net_node);
+ INIT_HLIST_NODE(&nlk->mc_all_nsid_node);
mutex_init(&nlk->nl_cb_mutex);
lockdep_set_class_and_name(&nlk->nl_cb_mutex,
nlk_cb_mutex_keys + protocol,
@@ -915,6 +982,7 @@ netlink_update_subscriptions(struct sock *sk, unsigned int subscriptions)
else if (!nlk->subscriptions && subscriptions)
sk_add_bind_node(sk, &nl_table[sk->sk_protocol].mc_list);
nlk->subscriptions = subscriptions;
+ netlink_update_route_mc(sk, subscriptions);
}
static int netlink_realloc_groups(struct sock *sk)
@@ -1555,8 +1623,25 @@ int netlink_broadcast_filtered(struct sock *ssk, struct sk_buff *skb,
netlink_lock_table();
- sk_for_each_bound(sk, &nl_table[ssk->sk_protocol].mc_list)
- do_one_broadcast(sk, &info);
+ if (ssk->sk_protocol == NETLINK_ROUTE &&
+ READ_ONCE(netlink_route_mc_index)) {
+ struct netlink_route_net *nn;
+ struct netlink_sock *nlk;
+
+ nn = net_generic(net, netlink_route_net_id);
+ hlist_for_each_entry(nlk, &nn->mc_list, mc_net_node)
+ do_one_broadcast(&nlk->sk, &info);
+
+ /* Local all-nsid listeners were visited above. */
+ hlist_for_each_entry(nlk, &netlink_route_all_nsid,
+ mc_all_nsid_node) {
+ if (!net_eq(sock_net(&nlk->sk), net))
+ do_one_broadcast(&nlk->sk, &info);
+ }
+ } else {
+ sk_for_each_bound(sk, &nl_table[ssk->sk_protocol].mc_list)
+ do_one_broadcast(sk, &info);
+ }
consume_skb(skb);
@@ -1722,6 +1807,13 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname,
case NETLINK_LISTEN_ALL_NSID:
if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_BROADCAST))
return -EPERM;
+ if (sk->sk_protocol == NETLINK_ROUTE) {
+ netlink_table_grab();
+ assign_bit(NETLINK_F_LISTEN_ALL_NSID, &nlk->flags, val);
+ netlink_update_route_mc(sk, nlk->subscriptions);
+ netlink_table_ungrab();
+ return 0;
+ }
nr = NETLINK_F_LISTEN_ALL_NSID;
break;
case NETLINK_CAP_ACK:
@@ -2867,6 +2959,9 @@ static const struct net_proto_family netlink_family_ops = {
static int __net_init netlink_net_init(struct net *net)
{
+ struct netlink_route_net *nn = net_generic(net, netlink_route_net_id);
+
+ INIT_HLIST_HEAD(&nn->mc_list);
#ifdef CONFIG_PROC_FS
if (!proc_create_net("netlink", 0, net->proc_net, &netlink_seq_ops,
sizeof(struct nl_seq_iter)))
@@ -2877,6 +2972,11 @@ static int __net_init netlink_net_init(struct net *net)
static void __net_exit netlink_net_exit(struct net *net)
{
+ struct netlink_route_net *nn = net_generic(net, netlink_route_net_id);
+
+ netlink_table_grab();
+ WARN_ON_ONCE(!hlist_empty(&nn->mc_list));
+ netlink_table_ungrab();
#ifdef CONFIG_PROC_FS
remove_proc_entry("netlink", net->proc_net);
#endif
@@ -2905,6 +3005,8 @@ static void __init netlink_add_usersock_entry(void)
static struct pernet_operations __net_initdata netlink_net_ops = {
.init = netlink_net_init,
.exit = netlink_net_exit,
+ .id = &netlink_route_net_id,
+ .size = sizeof(struct netlink_route_net),
};
static inline u32 netlink_hash(const void *data, u32 len, u32 seed)
@@ -2980,7 +3082,8 @@ static int __init netlink_proto_init(void)
netlink_add_usersock_entry();
sock_register(&netlink_family_ops);
- register_pernet_subsys(&netlink_net_ops);
+ if (register_pernet_subsys(&netlink_net_ops))
+ goto panic;
register_pernet_subsys(&netlink_tap_net_ops);
/* The netlink device handler may be needed early. */
rtnetlink_init();
diff --git a/net/netlink/af_netlink.h b/net/netlink/af_netlink.h
index 778a38093..bc7b38d4f 100644
--- a/net/netlink/af_netlink.h
+++ b/net/netlink/af_netlink.h
@@ -47,6 +47,10 @@ struct netlink_sock {
unsigned long *groups);
struct module *module;
+ /* NETLINK_ROUTE auxiliary multicast indexes; nl_table_lock protected. */
+ struct hlist_node mc_net_node;
+ struct hlist_node mc_all_nsid_node;
+
struct rhash_head node;
struct rcu_head rcu;
};
next parent reply other threads:[~2026-11-04 3:36 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20261104033526.2917590-1-work_patrick@qq.com>
2026-11-04 3:35 ` patrick [this message]
2026-11-04 7:31 ` Paolo Abeni
2026-11-04 3:35 ` [PATCH net-next 2/2] selftests: net: cover ROUTE multicast namespace and switch semantics patrick
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=tencent_1E208C03035DF063166FD46C1DA1978F6306@qq.com \
--to=work_patrick@qq.com \
--cc=corbet@lwn.net \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-api@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=rdunlap@infradead.org \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®