From: Lukas Sichert <l.sichert@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Lukas Sichert <l.sichert@proxmox.com>
Subject: [PATCH ifupdown2 v5 01/10] d/patches: add ipv6 force_forwarding for bridges
Date: Wed, 4 Nov 2026 15:55:02 +0100 [thread overview]
Message-ID: <20261104145513.21471-2-l.sichert@proxmox.com> (raw)
In-Reply-To: <20261104145513.21471-1-l.sichert@proxmox.com>
The per-interface ipv6 forwarding sysctl does not make the kernel
forward packets on that interface, but force_forwarding does (since
Linux 6.17). Set it on bridges whenever forwarding is set, and add an
ip6-force-forward option to configure it manually.
Signed-off-by: Lukas Sichert <l.sichert@proxmox.com>
---
...set-ipv6-force_forwarding-on-bridges.patch | 197 ++++++++++++++++++
debian/patches/series | 1 +
2 files changed, 198 insertions(+)
create mode 100644 debian/patches/pve/0017-addons-address-set-ipv6-force_forwarding-on-bridges.patch
diff --git a/debian/patches/pve/0017-addons-address-set-ipv6-force_forwarding-on-bridges.patch b/debian/patches/pve/0017-addons-address-set-ipv6-force_forwarding-on-bridges.patch
new file mode 100644
index 0000000..b483f2d
--- /dev/null
+++ b/debian/patches/pve/0017-addons-address-set-ipv6-force_forwarding-on-bridges.patch
@@ -0,0 +1,197 @@
+From abac8c62aa62eabc0622fc7102d3bc808571f3d6 Mon Sep 17 00:00:00 2001
+From: Lukas Sichert <l.sichert@proxmox.com>
+Date: Fri, 23 Oct 2026 11:22:05 +0200
+Subject: [PATCH] addons: address: set ipv6 force_forwarding on bridges
+
+The per-interface ipv6 "forwarding" sysctl does not make the kernel
+forward packets on that interface. Since Linux 6.17 the per-interface
+force_forwarding sysctl does.
+
+Enable it for bridges with an address, an upper interface or dhcp and
+clear it otherwise. The kernel does not report force_forwarding in its
+netconf messages, so the value is read from /proc. Kernels without the
+sysctl are skipped. Additionally introduce a flag to configure it
+manually.
+
+Signed-off-by: Lukas Sichert <l.sichert@proxmox.com>
+---
+ ifupdown2/addons/address.py | 90 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 90 insertions(+)
+
+diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py
+index 46226a9..b26d610 100644
+--- a/ifupdown2/addons/address.py
++++ b/ifupdown2/addons/address.py
+@@ -167,6 +167,12 @@ class address(AddonWithIpBlackList, moduleBase):
+ 'default': 'off',
+ 'example': ['ip6-forward off']
+ },
++ 'ip6-force-forward': {
++ 'help': 'ipv6 force_forwarding flag',
++ 'validvals': ['on', 'off', 'yes', 'no', '0', '1'],
++ 'default': 'off',
++ 'example': ['ip6-force-forward off']
++ },
+ 'mpls-enable': {
+ 'help': 'mpls enable flag',
+ 'validvals': ['yes', 'no'],
+@@ -217,6 +223,8 @@ class address(AddonWithIpBlackList, moduleBase):
+ self._bridge_fdb_query_cache = {}
+ self.ipforward = policymanager.policymanager_api.get_attr_default(module_name=self.__class__.__name__, attr='ip-forward')
+ self.ip6forward = policymanager.policymanager_api.get_attr_default(module_name=self.__class__.__name__, attr='ip6-forward')
++ self.ip6forceforward = policymanager.policymanager_api.get_attr_default(module_name=self.__class__.__name__, attr='ip6-force-forward')
++ self.ip6forceforward_supported = (self.read_file_oneline('/proc/sys/net/ipv6/conf/default/force_forwarding') is not None)
+ self.ifaces_defaults = policymanager.policymanager_api.get_iface_defaults(module_name=self.__class__.__name__)
+ self.enable_l3_iface_forwarding_checks = utils.get_boolean_from_string(
+ policymanager.policymanager_api.get_module_globals(
+@@ -371,6 +379,12 @@ class address(AddonWithIpBlackList, moduleBase):
+ self.logger.error("%s: misconfiguration: disabling ip6 forwarding on an l3-svi is not allowed" % ifaceobj.name)
+ return False
+
++ ip6_force_forward = ifaceobj.get_attr_value_first("ip6-force-forward")
++
++ if ip6_force_forward and not utils.get_boolean_from_string(ip6_force_forward) and self.ip6forceforward_supported:
++ self.logger.error("%s: misconfiguration: disabling ip6 force_forwarding on an l3-svi is not allowed" % ifaceobj.name)
++ return False
++
+ return True
+
+ def syntax_check_enable_l3_iface_forwardings(self, ifaceobj, ifaceobj_getfunc, syntax_check=False):
+@@ -420,6 +434,11 @@ class address(AddonWithIpBlackList, moduleBase):
+ result = False
+ self.log_error('%s: \'ip6-forward\' is not supported for '
+ 'bridge port' %ifaceobj.name)
++ ip6forceforward = ifaceobj.get_attr_value_first('ip6-force-forward')
++ if bridge_port and ip6forceforward:
++ result = False
++ self.log_error('%s: \'ip6-force-forward\' is not supported for '
++ 'bridge port' %ifaceobj.name)
+ return result
+
+ def syntax_check_mtu(self, ifaceobj, ifaceobj_getfunc):
+@@ -955,15 +974,29 @@ class address(AddonWithIpBlackList, moduleBase):
+ self.sysctl_write_forwarding_value_to_proc(ifname, "ipv4", 0)
+ if netconf_ipv6_forwarding:
+ self.sysctl_write_forwarding_value_to_proc(ifname, "ipv6", 0)
++ self.sysctl_write_force_forwarding_value_to_proc(ifname, 0)
+ else:
+ if not netconf_ipv4_forwarding:
+ self.sysctl_write_forwarding_value_to_proc(ifname, "ipv4", 1)
+ if not netconf_ipv6_forwarding:
+ self.sysctl_write_forwarding_value_to_proc(ifname, "ipv6", 1)
++ self.sysctl_write_force_forwarding_value_to_proc(ifname, 1)
++
++ def get_ip6_force_forwarding(self, ifname):
++ # kernels <6.17 do not support force_forwarding
++ if not self.ip6forceforward_supported:
++ return None
++ value = self.read_file_oneline("/proc/sys/net/ipv6/conf/%s/force_forwarding" % ifname)
++ return None if value is None else int(value)
+
+ def sysctl_write_forwarding_value_to_proc(self, ifname, family, value):
+ self.write_file("/proc/sys/net/%s/conf/%s/forwarding" % (family, ifname), "%s\n" % value)
+
++ def sysctl_write_force_forwarding_value_to_proc(self, ifname, value):
++ current = self.get_ip6_force_forwarding(ifname)
++ if current is not None and current != value:
++ self.write_file("/proc/sys/net/ipv6/conf/%s/force_forwarding" % ifname, "%s\n" % value)
++
+ def _sysctl_slaac(self, ifaceobj):
+ ifacename = self.cache.link_translate_altname(ifaceobj.name)
+ addr_method = ifaceobj.addr_method
+@@ -1037,6 +1070,7 @@ class address(AddonWithIpBlackList, moduleBase):
+
+ ipforward = ifaceobj.get_attr_value_first('ip-forward')
+ ip6forward = ifaceobj.get_attr_value_first('ip6-forward')
++ ip6forceforward = ifaceobj.get_attr_value_first('ip6-force-forward')
+ if ifupdownflags.flags.PERFMODE:
+ if ipforward:
+ self.sysctl_set('net.ipv4.conf.%s.forwarding'
+@@ -1046,7 +1080,17 @@ class address(AddonWithIpBlackList, moduleBase):
+ self.sysctl_set('net.ipv6.conf.%s.forwarding'
+ %('/'.join(ifacename.split("."))),
+ utils.boolean_support_binary(ip6forward))
++ if ip6forceforward and self.ip6forceforward_supported:
++ self.sysctl_set('net.ipv6.conf.%s.force_forwarding'
++ %('/'.join(ifacename.split("."))),
++ utils.boolean_support_binary(ip6forceforward))
+ return
++
++ if ip6forceforward and not self.ip6forceforward_supported:
++ self.log_warn('%s: \'ip6-force-forward\' is not supported by '
++ 'this kernel (needs >= 6.17)' % ifacename)
++ ip6forceforward = None
++
+ bridge_port = ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT
+ if bridge_port:
+ if ipforward:
+@@ -1055,6 +1099,9 @@ class address(AddonWithIpBlackList, moduleBase):
+ if ip6forward:
+ self.log_error('%s: \'ip6-forward\' is not supported for '
+ 'bridge port' %ifacename)
++ if ip6forceforward:
++ self.log_error('%s: \'ip6-force-forward\' is not supported for '
++ 'bridge port' %ifacename)
+ return
+ setting_default_value = False
+ if not ipforward:
+@@ -1096,6 +1143,27 @@ class address(AddonWithIpBlackList, moduleBase):
+ ifaceobj.status = ifaceStatus.ERROR
+ self.logger.error('%s: %s' %(ifacename, str(e)))
+
++ setting_default_value = False
++
++ if not ip6forceforward and self.ip6forceforward_supported:
++ setting_default_value = True
++ ip6forceforward = self.ip6forceforward
++
++ if ip6forceforward:
++ ip6forceforward = int(utils.get_boolean_from_string(ip6forceforward))
++ running_ip6forceforward = self.get_ip6_force_forwarding(ifacename)
++ if ip6forceforward != running_ip6forceforward:
++ try:
++ self.sysctl_set('net.ipv6.conf.%s.force_forwarding'
++ %('/'.join(ifacename.split("."))),
++ ip6forceforward)
++ except Exception as e:
++ # Analogous to ipv6 forwarding ipv6 force_forwarding
++ # may only be configured if ipv6 is enabled
++ if not setting_default_value:
++ ifaceobj.status = ifaceStatus.ERROR
++ self.logger.error('%s: %s' %(ifacename, str(e)))
++
+ self._sysctl_slaac(ifaceobj)
+
+ def process_mtu(self, ifaceobj, ifaceobj_getfunc):
+@@ -1471,6 +1539,28 @@ class address(AddonWithIpBlackList, moduleBase):
+ 'on' if running_ip6forward else 'off',
+ running_ip6forward != config_ip6forward
+ )
++
++ ip6forceforward = ifaceobj.get_attr_value_first('ip6-force-forward')
++ if ip6forceforward:
++ if bridge_port:
++ ifaceobjcurr.status = ifaceStatus.ERROR
++ ifaceobjcurr.status_str = ('\'ip6-force-forward\' not supported ' +
++ 'for bridge port')
++ ifaceobjcurr.update_config_with_status('ip6-force-forward', 1, None)
++ elif not self.ip6forceforward_supported:
++ ifaceobjcurr.status = ifaceStatus.ERROR
++ ifaceobjcurr.status_str = ('\'ip6-force-forward\' is not supported ' +
++ 'by this kernel (needs >= 6.17)')
++ ifaceobjcurr.update_config_with_status('ip6-force-forward', 1, None)
++ else:
++ running_ip6forceforward = self.get_ip6_force_forwarding(ifaceobj.name)
++ config_ip6forceforward = utils.get_boolean_from_string(ip6forceforward)
++ ifaceobjcurr.update_config_with_status(
++ 'ip6-force-forward',
++ 'on' if running_ip6forceforward else 'off',
++ running_ip6forceforward != config_ip6forceforward
++ )
++
+ mpls_enable = ifaceobj.get_attr_value_first('mpls-enable');
+ if mpls_enable:
+ running_mpls_enable = utils.get_yesno_from_onezero(str(self.cache.get_netconf_mpls_input(ifaceobj.name)))
+--
+2.47.3
+
diff --git a/debian/patches/series b/debian/patches/series
index ee2cb46..1eea3d0 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -17,3 +17,4 @@ upstream/0002-vxlan-add-support-for-IPv6-vxlan-local-tunnelip.patch
pve/0014-nlmanager-read-ipv6-devconf-disable_ipv6-attribute-t.patch
pve/0015-revert-addons-bond-warn-if-sub-interface-is-detected-on-bond-slave.patch
pve/0016-addons-vxlan-add-vxlan-gbp-attribute.patch
+pve/0017-addons-address-set-ipv6-force_forwarding-on-bridges.patch
--
2.47.3
next prev parent reply other threads:[~2026-11-04 14:55 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 14:55 [PATCH many v5 00/10] sdn: enable force_forwarding for ipv6 forwarding Lukas Sichert
2026-11-04 14:55 ` Lukas Sichert [this message]
2026-11-04 14:55 ` [PATCH network v5 02/10] sdn: evpn: enable ip forwarding on the vrf bridge interface Lukas Sichert
2026-11-04 14:55 ` [PATCH network v5 03/10] sdn: evpn: enable force_forwarding for ipv6 forwarding to subnets Lukas Sichert
2026-11-04 14:55 ` [PATCH network v5 04/10] sdn: simple: " Lukas Sichert
2026-11-04 14:55 ` [PATCH network v5 05/10] tests: sdn: update fabric expectations for explicit ipv6 forwarding Lukas Sichert
2026-11-04 14:55 ` [PATCH perl-rs v5 06/10] openfabric: set ip6-forward explicitly Lukas Sichert
2026-11-04 14:55 ` [PATCH perl-rs v5 07/10] fabrics: openfabric: enable force_forwarding for ipv6 transit traffic Lukas Sichert
2026-11-04 14:55 ` [PATCH perl-rs v5 08/10] fabrics: bgp: " Lukas Sichert
2026-11-04 14:55 ` [PATCH manager v5 09/10] ui: sdn: remove IPv6 forwarding hint from fabric edit window Lukas Sichert
2026-11-04 14:55 ` [PATCH docs v5 10/10] sdn: drop global ipv6 forwarding workaround from docs Lukas Sichert
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=20261104145513.21471-2-l.sichert@proxmox.com \
--to=l.sichert@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/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