all lists on lists.proxmox.com
 help / color / mirror / Atom feed
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





  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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal