[PATCH net-next 0/2] netlink: optional per-netns ROUTE multicast index
From: patrick
Date: Tue Nov 03 2026 - 22:37:09 EST
NETLINK_ROUTE multicast walks the protocol-wide subscriber list before
checking network namespace and group membership. Ordinary subscribers in
unrelated namespaces therefore add work to every notification.
Maintain one ROUTE subscriber list per network namespace and one global
LISTEN_ALL_NSID list. With net.core.netlink_route_mc_index=1, visit the
sender's namespace and the cross-namespace list. Exclude local sockets
from the second walk to avoid duplicate delivery. Preserve do_one_broadcast
and the existing broadcast initialization, locking, completion, error and
skb ownership handling; only select a different candidate iteration.
No additional broadcast helper or forced inlining is needed.
The setting defaults to 0, is visible only in init_net and affects all
network namespaces. Mode 0 retains the original global walk. Other
Netlink protocols retain their original walk. Both auxiliary lists remain
maintained in both modes so switching includes existing subscriptions
immediately. There is no automatic threshold or fallback.
The explicit switch allows administrators to choose the indexed walk
for workloads with many ordinary foreign subscribers. Local and ALL_NSID
subscribers can remove its benefit. Disabling it retains the memory and
subscription-maintenance costs.
U is the unpatched base; P0/P1 use the same newly built patched image with
the switch disabled/enabled. Measurements ran in ARM64 KVM with two vCPUs
and 3072 MiB RAM on a shared Phytium D2000/8 host. Three independent boot
pairs, three rounds and four samples per round yield 36 samples per state
and scene: 63 scenes and 6804 valid samples. The unchanged U image was
hash-verified and reused; every measurement in this comparison is new.
Performance configurations match except LOCALVERSION, with no probes.
Each route batch replaces 2640 routes using existing nexthop IDs, then
drains and validates every actual recipient. Timings include userspace,
syscalls, draining and validation. Thread CPU excludes asynchronous
cleanup workers. The 95% intervals resample paired boots and whole rounds
with 10000 bootstrap replicates. All completed formal samples are retained.
The host CPUs remained at 2300 MHz with performance governors.
Host load and small swap-counter changes were observed;
CPU pinning does not establish exclusive host resources or isolate kernel
layout effects.
Selected median wall times; negative changes mean less time:
Scenario U ms P0 ms P1 ms P0/U % P1/U %
T1-n900-d0 105.112 120.571 18.595 +14.71 -82.31
T1-n900-d2 147.796 152.543 16.378 +3.21 -88.92
T2-field-c0 200.423 197.234 13.228 -1.59 -93.40
T2-field-c1 199.410 207.788 13.254 +4.20 -93.35
T3-monitor1 101.576 120.891 23.994 +19.02 -76.38
C1-n0 17.393 17.784 16.973 +2.24 -2.42
C2-candidate900 86.568 91.119 93.700 +5.26 +8.24
C4-all450 108.084 115.643 77.509 +6.99 -28.29
C4-all900 123.199 137.517 161.098 +11.62 +30.76
E1-n1800-d0 585.290 603.409 769.098 +3.10 +31.40
C7-option-route 201.253 208.166 206.722 +3.43 +2.72
PAR-n900 99.985 102.772 19.941 +2.79 -80.06
T1-n900-d0 holds 900 ordinary foreign sockets in one namespace; d2 puts
two sockets in each foreign namespace. These are sockets, not 900 processes.
The source has two receivers. T2-field-c0/c1 uses a synthetic layout with
450 two-socket namespaces and seven extra subscribers under
nexthop_compat_mode 0/1, producing one recipient per event. T3-monitor1
adds a mapped cross-namespace monitor. C1-n0 has no foreign subscribers;
C2-candidate900 has 900 local candidates with only two matching receivers.
C4-all450/all900 enables ALL_NSID on 450/900 foreign sockets without NSID
mappings; E1-n1800-d0 extends this to 1800 sockets. C7-option-route measures
ROUTE socket-option changes. PAR-n900 has two senders sharing the foreign
socket pool. Workloads differ between scenes; comparisons are within a scene.
Default-off median changes and 95% relative confidence intervals:
T1-n900-d0: wall +14.71% [-5.03%, +37.23%];
thread CPU +15.06% [-6.24%, +37.81%]
T2-field-c0: wall -1.59% [-6.03%, +5.55%];
thread CPU -1.36% [-5.85%, +5.99%]
T3-monitor1: wall +19.02% [+2.68%, +20.75%];
thread CPU +18.98% [+2.51%, +20.75%]
C4-all900: wall +11.62% [+1.38%, +16.63%];
thread CPU +11.98% [+1.30%, +16.93%]
C7-option-route: wall +3.43% [+0.91%, +5.83%];
thread CPU +4.37% [+1.68%, +6.52%]
With the switch enabled, T1-n900-d0 wall change is -82.31% [-85.75%, -80.43%],
and T2-field-c0 is -93.40% [-93.64%, -93.12%].
The high-ALL_NSID cases show the boundary cost: C4-all900 wall change is
+30.76% [+21.83%, +36.14%]; E1-n1800-d0 is
+31.40% [+8.40%, +44.03%].
There is no candidate reduction when almost all foreign listeners use
ALL_NSID, while auxiliary-node access and local-namespace checks remain.
Default-off changes vary by scene and include intervals entirely above
zero, as shown above; the disabled state is not claimed to be cost-free.
These are measured costs and uncertainty, without a fixed-percentage
claim for default-off overhead. This is workload-dependent multicast
behavior, not end-to-end routing convergence or x86 runtime evidence.
On this ARM64 configuration, netlink_sock grows from 1072 to 1104 bytes
(+32) for all Netlink protocols. Both sizes use the 2048-byte kmalloc
bucket here. The per-netns payload is an 8-byte list
head, with allocation rounding and generic-pointer overhead in addition.
Selected new global symbols total 72 bytes, excluding alignment,
strings and dynamic sysctl registration. Both modes retain these costs.
The 21-case semantics test passes on U, both production modes, both debug
modes and as an installed standalone selftest. Six switch cases pass on
production and debug kernels and from the installed standalone copy.
Production and KASAN/PROVE_LOCKING/DEBUG_LIST/DEBUG_NET kernels each pass
fixed-0, fixed-1 and live-switching stress. Every group has two pinned
senders, at least 60 seconds and 100 batches per sender, concurrent socket,
membership, option and namespace churn; switching groups verify at least
1000 writes. Stable receiver notifications and ACKs are checked. No new
KASAN, lock or list error was reported.
Full linked builds pass for ARM64 production and debug, all four x86_64
GCC NET_NS/SYSCTL combinations, and x86_64 Clang 19. All builds ran in the
isolated ARM64 guest. Cross builds do not establish x86 runtime correctness.
KCSAN and skb allocation-failure injection were not run. FRR 10.3 dual-stack
learn/install/ping/withdraw/reinstall passes separately on U/P0/P1.
The tested base is 4dd16e31135ea8437a720222df34e8745f385d6d.
Both patches also apply cleanly to the checked net-next main
7b6ab4dbc156e2f96419715130b3d5738a306c93. Its five additional
commits change no implementation or test-code file touched here; the
shared selftest config adds unrelated devmem options. Runtime evidence
is for the stated tested base.
Patch 1 adds the lists, sysctl and documentation. Patch 2 adds two
standalone networking selftests, including behavior without the switch.
Raw samples, all scene comparisons and benchmark tools are retained and
can be supplied separately; they are not additional patches or attachments.
Please review this series for application to net-next.
AI assistance: An LLM assisted with implementation adaptation and
simplification.
patrick (2):
netlink: add an optional per-netns ROUTE multicast index
selftests: net: cover ROUTE multicast namespace and switch semantics
Documentation/admin-guide/sysctl/net.rst | 27 ++
net/netlink/af_netlink.c | 109 ++++-
net/netlink/af_netlink.h | 4 +
tools/testing/selftests/net/Makefile | 2 +
tools/testing/selftests/net/config | 1 +
.../selftests/net/netlink_mcast_semantics.py | 420 ++++++++++++++++++
.../selftests/net/netlink_mcast_switch.py | 217 +++++++++
7 files changed, 777 insertions(+), 3 deletions(-)
create mode 100755 tools/testing/selftests/net/netlink_mcast_semantics.py
create mode 100755 tools/testing/selftests/net/netlink_mcast_switch.py
base-commit: 4dd16e31135ea8437a720222df34e8745f385d6d