From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 5A6901FF0C0 for ; Wed, 04 Nov 2026 15:55:48 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id A7B912161B; Wed, 04 Nov 2026 15:55:25 +0100 (CET) From: Lukas Sichert To: pve-devel@lists.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 Message-ID: <20261104145513.21471-2-l.sichert@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261104145513.21471-1-l.sichert@proxmox.com> References: <20261104145513.21471-1-l.sichert@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793804120256 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.526 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: VZYZ54VC7ILGKNA2NNPEAM4JN6P6CWDL X-Message-ID-Hash: VZYZ54VC7ILGKNA2NNPEAM4JN6P6CWDL X-MailFrom: l.sichert@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Lukas Sichert X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 --- ...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 +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 +--- + 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