* [PATCH net-next] ipv6: always apply all.force_forwarding to every interface
@ 2026-11-12 13:41 Gabriel Goller
2026-11-12 16:26 ` Nicolas Dichtel
0 siblings, 1 reply; 2+ messages in thread
From: Gabriel Goller @ 2026-11-12 13:41 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Randy Dunlap,
David Ahern, Ido Schimmel, Nicolas Dichtel
Cc: netdev, linux-doc, linux-kernel, linux-kselftest, Gabriel Goller
Currently, writing to all.force_forwarding updates the interfaces
only if the value stored in all.force_forwarding changes. Setting an
interface to a different value and then writing the same value to all
leaves that interface unchanged:
# sysctl -wq net.ipv6.conf.all.force_forwarding=1
# sysctl -wq net.ipv6.conf.dummy1.force_forwarding=0
# sysctl -wq net.ipv6.conf.all.force_forwarding=1
# sysctl net.ipv6.conf.dummy1.force_forwarding
net.ipv6.conf.dummy1.force_forwarding = 0
Apply every write to all.force_forwarding to all interfaces, even when
its stored value is unchanged, matching all.forwarding. In the example,
the final write to all now sets dummy1.force_forwarding back to 1.
Also match forwarding's locking: take RTNL for every write and compare
the old and new values under the lock, rather than returning early based
on an unlocked comparison. Send netconf notifications only for actual
value changes, including for default and per-interface writes.
Note that IPv4's forwarding does not behave like this, but we won't
change that one and align force_forward to the IPv6 forwarding.
Fixes: f24987ef6959 ("ipv6: add `force_forwarding` sysctl to enable per-interface forwarding")
Suggested-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
Documentation/networking/ip-sysctl.rst | 2 +
net/ipv6/addrconf.c | 38 +++++++++-------
.../testing/selftests/net/ipv6_force_forwarding.sh | 51 ++++++++++++++++++++++
3 files changed, 75 insertions(+), 16 deletions(-)
diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 0b7bfbd98a92..3cc80d9b024b 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -2683,6 +2683,8 @@ force_forwarding - BOOLEAN
Enable forwarding on this interface only -- regardless of the setting on
``conf/all/forwarding``. When setting ``conf.all.forwarding`` to 0,
the ``force_forwarding`` flag will be reset on all interfaces.
+ Writing to ``conf/all/force_forwarding`` applies the value to all
+ interfaces, even if the value did not change.
fwmark_reflect - BOOLEAN
Controls the fwmark of kernel-generated IPv6 reply packets that are not
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 6572bb22b2c0..10642c68571b 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -6841,7 +6841,7 @@ static int addrconf_sysctl_force_forwarding(const struct ctl_table *ctl, int wri
struct net *net = ctl->extra2;
int *valp = ctl->data;
int new_val = *valp;
- int old_val = *valp;
+ int old_val;
int ret;
tmp_ctl.extra1 = SYSCTL_ZERO;
@@ -6849,35 +6849,41 @@ static int addrconf_sysctl_force_forwarding(const struct ctl_table *ctl, int wri
tmp_ctl.data = &new_val;
ret = proc_douintvec_minmax(&tmp_ctl, write, buffer, lenp, ppos);
+ if (ret || !write)
+ return ret;
- if (write && old_val != new_val) {
- if (!rtnl_net_trylock(net))
- return restart_syscall();
+ if (!rtnl_net_trylock(net))
+ return restart_syscall();
- WRITE_ONCE(*valp, new_val);
+ old_val = *valp;
+ WRITE_ONCE(*valp, new_val);
- if (valp == &net->ipv6.devconf_dflt->force_forwarding) {
+ if (valp == &net->ipv6.devconf_dflt->force_forwarding) {
+ if (old_val != new_val)
inet6_netconf_notify_devconf(net, RTM_NEWNETCONF,
NETCONFA_FORCE_FORWARDING,
NETCONFA_IFINDEX_DEFAULT,
net->ipv6.devconf_dflt);
- } else if (valp == &net->ipv6.devconf_all->force_forwarding) {
+ } else if (valp == &net->ipv6.devconf_all->force_forwarding) {
+ if (old_val != new_val)
inet6_netconf_notify_devconf(net, RTM_NEWNETCONF,
NETCONFA_FORCE_FORWARDING,
NETCONFA_IFINDEX_ALL,
net->ipv6.devconf_all);
- addrconf_force_forward_change(net, new_val);
- } else {
- inet6_netconf_notify_devconf(net, RTM_NEWNETCONF,
- NETCONFA_FORCE_FORWARDING,
- idev->dev->ifindex,
- &idev->cnf);
- }
- rtnl_net_unlock(net);
+ /* Like all.forwarding, apply to every interface even if
+ * all.force_forwarding did not change.
+ */
+ addrconf_force_forward_change(net, new_val);
+ } else if (old_val != new_val) {
+ inet6_netconf_notify_devconf(net, RTM_NEWNETCONF,
+ NETCONFA_FORCE_FORWARDING,
+ idev->dev->ifindex,
+ &idev->cnf);
}
+ rtnl_net_unlock(net);
- return ret;
+ return 0;
}
static int minus_one = -1;
diff --git a/tools/testing/selftests/net/ipv6_force_forwarding.sh b/tools/testing/selftests/net/ipv6_force_forwarding.sh
index bf0243366caa..383d1ec64f1c 100755
--- a/tools/testing/selftests/net/ipv6_force_forwarding.sh
+++ b/tools/testing/selftests/net/ipv6_force_forwarding.sh
@@ -86,12 +86,63 @@ test_force_forwarding() {
return $ret
}
+check_force_forwarding() {
+ local dev=$1
+ local expected=$2
+ local msg=$3
+ local val
+
+ val=$(ip netns exec $ns2 sysctl -n net.ipv6.conf.$dev.force_forwarding) || return 1
+ if [ "$val" = "$expected" ]; then
+ echo "PASS: $msg"
+ return 0
+ fi
+
+ echo "FAIL: $msg (got $val, expected $expected)"
+ return 1
+}
+
+test_all_force_forwarding() {
+ local ret=0
+
+ echo "TEST: all.force_forwarding"
+
+ ip netns exec $ns2 sysctl -qw net.ipv6.conf.veth21.force_forwarding=0 || return 1
+ ip netns exec $ns2 sysctl -qw net.ipv6.conf.all.force_forwarding=1 || return 1
+ check_force_forwarding veth21 1 "all.force_forwarding applied to interfaces" || ret=1
+
+ # Rewriting the same value to all re-applies it to every interface
+ ip netns exec $ns2 sysctl -qw net.ipv6.conf.veth21.force_forwarding=0 || return 1
+ check_force_forwarding veth21 0 "interface force_forwarding overridden" || return 1
+ ip netns exec $ns2 sysctl -qw net.ipv6.conf.all.force_forwarding=1 || return 1
+ check_force_forwarding veth21 1 "unchanged all.force_forwarding re-applied" || ret=1
+
+ if ip netns exec $ns1 ping -6 -c 1 -W 2 2001:db8:2::2 &>/dev/null; then
+ echo "PASS: forwarding after re-applying all.force_forwarding"
+ else
+ echo "FAIL: ping failed after re-applying all.force_forwarding"
+ ret=1
+ fi
+
+ # Disabling all.forwarding resets force_forwarding, including on all
+ ip netns exec $ns2 sysctl -qw net.ipv6.conf.all.forwarding=1 || return 1
+ ip netns exec $ns2 sysctl -qw net.ipv6.conf.all.forwarding=0 || return 1
+ check_force_forwarding all 0 "all.forwarding=0 resets all.force_forwarding" || ret=1
+ check_force_forwarding veth21 0 "all.forwarding=0 resets interface force_forwarding" || ret=1
+
+ return $ret
+}
+
echo "IPv6 force_forwarding test"
echo "=========================="
setup_test
test_force_forwarding
ret=$?
+if [ $ret -eq 0 ]; then
+ test_all_force_forwarding
+ ret=$?
+fi
if [ $ret -eq 0 ]; then
echo "OK"
---
base-commit: 676f3aace662fbbc180b3898b190a372fa2a516b
base-tree: git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git main
change-id: 20261112-ipv6_always_apply-afc485227167
Best regards,
--
Gabriel Goller <g.goller@proxmox.com>
^ permalink raw reply [flat|nested] 2+ messages in thread* Re: [PATCH net-next] ipv6: always apply all.force_forwarding to every interface
2026-11-12 13:41 [PATCH net-next] ipv6: always apply all.force_forwarding to every interface Gabriel Goller
@ 2026-11-12 16:26 ` Nicolas Dichtel
0 siblings, 0 replies; 2+ messages in thread
From: Nicolas Dichtel @ 2026-11-12 16:26 UTC (permalink / raw)
To: Gabriel Goller, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, David Ahern, Ido Schimmel
Cc: netdev, linux-doc, linux-kernel, linux-kselftest
Le 12/11/2026 à 14:41, Gabriel Goller a écrit :
> Currently, writing to all.force_forwarding updates the interfaces
> only if the value stored in all.force_forwarding changes. Setting an
> interface to a different value and then writing the same value to all
> leaves that interface unchanged:
>
> # sysctl -wq net.ipv6.conf.all.force_forwarding=1
> # sysctl -wq net.ipv6.conf.dummy1.force_forwarding=0
> # sysctl -wq net.ipv6.conf.all.force_forwarding=1
> # sysctl net.ipv6.conf.dummy1.force_forwarding
> net.ipv6.conf.dummy1.force_forwarding = 0
>
> Apply every write to all.force_forwarding to all interfaces, even when
> its stored value is unchanged, matching all.forwarding. In the example,
> the final write to all now sets dummy1.force_forwarding back to 1.
>
> Also match forwarding's locking: take RTNL for every write and compare
> the old and new values under the lock, rather than returning early based
> on an unlocked comparison. Send netconf notifications only for actual
> value changes, including for default and per-interface writes.
>
> Note that IPv4's forwarding does not behave like this, but we won't
> change that one and align force_forward to the IPv6 forwarding.
>
> Fixes: f24987ef6959 ("ipv6: add `force_forwarding` sysctl to enable per-interface forwarding")
> Suggested-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
Reviewed-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-12 16:26 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-12 13:41 [PATCH net-next] ipv6: always apply all.force_forwarding to every interface Gabriel Goller
2026-11-12 16:26 ` Nicolas Dichtel
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®