From a7af7360e3f2d4635d7bf0340d183f01e3846aa9 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 19 Dec 2025 21:10:14 +0100 Subject: bond: T8084: remove dynamic interface type detection via vyos.ifconfig.Section Aggregated / bonded interfaces do only work with ethernet interfaces as underlaying link. There is no need to "dynamically" detect that eth0, eth1 or any other interface starting with eth is an ethernet interface. Remove calls to vyos.ifconfig.Section(). --- src/conf_mode/interfaces_bonding.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index f844d0a21..3f6450076 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -34,7 +34,6 @@ from vyos.frrender import FRRender from vyos.frrender import get_frrender_dict from vyos.ifconfig import BondIf from vyos.ifconfig.ethernet import EthernetIf -from vyos.ifconfig import Section from vyos.utils.assertion import assert_mac from vyos.utils.dict import dict_search from vyos.utils.dict import dict_to_paths_values @@ -114,8 +113,7 @@ def get_config(config=None): # ethernet commit again in apply function # to apply options under ethernet section set_dependents('ethernet', conf, interface) - section = Section.section(interface) # this will be 'ethernet' for 'eth0' - if conf.exists([section, interface, 'disable']): + if conf.exists(['ethernet', interface, 'disable']): tmp[interface] = {'disable': ''} else: tmp[interface] = {} @@ -144,14 +142,8 @@ def get_config(config=None): bond['shutdown_required'] = {} bond['member']['interface'][interface].update({'new_added' : {}}) - # Check if member interface is disabled - conf.set_level(['interfaces']) - - section = Section.section(interface) # this will be 'ethernet' for 'eth0' - if conf.exists([section, interface, 'disable']): - if tmp: bond['member']['interface'][interface].update({'disable': ''}) - - conf.set_level(old_level) + if 'disable' in interface_ethernet_config: + bond['member']['interface'][interface].update({'disable': ''}) # Check if member interface is already member of another bridge tmp = is_member(conf, interface, 'bridge') -- cgit v1.2.3 From e7b65794f82026bb8f02e3ac364c1b5589c00a22 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 19 Dec 2025 21:13:06 +0100 Subject: bond: T8084: refactor verify() to remove duplication Consolidate repeated helper function calls used for both bonded and non-bonded Ethernet interfaces, resulting in cleaner and more maintainable code. --- src/conf_mode/interfaces_ethernet.py | 39 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index edcc6563c..8e490884f 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -371,51 +371,44 @@ def verify(ethernet): if 'deleted' in ethernet: return None + + ethtool = Ethtool(ethernet['ifname']) + verify_interface_exists(ethernet, ethernet['ifname']) + verify_eapol(ethernet) + verify_mirror_redirect(ethernet) + # No need to check speed and duplex keys as both have default values + verify_speed_duplex(ethernet, ethtool) + verify_flow_control(ethernet, ethtool) + verify_ring_buffer(ethernet, ethtool) + verify_offload(ethernet, ethtool) + if 'is_bond_member' in ethernet: - verify_bond_member(ethernet) + verify_bond_member(ethernet, ethtool) else: - verify_ethernet(ethernet) + verify_ethernet(ethernet, ethtool) -def verify_bond_member(ethernet): +def verify_bond_member(ethernet: dict, ethtool: Ethtool) -> None: """ Verification function for ethernet interface which is in bonding :param ethernet: dictionary which is received from get_interface_dict :type ethernet: dict """ - ifname = ethernet['ifname'] - verify_interface_exists(ethernet, ifname) - verify_eapol(ethernet) - verify_mirror_redirect(ethernet) - ethtool = Ethtool(ifname) - verify_speed_duplex(ethernet, ethtool) - verify_flow_control(ethernet, ethtool) - verify_ring_buffer(ethernet, ethtool) - verify_offload(ethernet, ethtool) verify_allowedbond_changes(ethernet) + return None -def verify_ethernet(ethernet): +def verify_ethernet(ethernet: dict, ethtool: Ethtool) -> None: """ Verification function for simple ethernet interface :param ethernet: dictionary which is received from get_interface_dict :type ethernet: dict """ - ifname = ethernet['ifname'] - verify_interface_exists(ethernet, ifname) verify_mtu(ethernet) verify_mtu_ipv6(ethernet) verify_dhcpv6(ethernet) verify_address(ethernet) verify_vrf(ethernet) verify_bond_bridge_member(ethernet) - verify_eapol(ethernet) - verify_mirror_redirect(ethernet) - ethtool = Ethtool(ifname) - # No need to check speed and duplex keys as both have default values. - verify_speed_duplex(ethernet, ethtool) - verify_flow_control(ethernet, ethtool) - verify_ring_buffer(ethernet, ethtool) - verify_offload(ethernet, ethtool) # use common function to verify VLAN configuration verify_vlan_config(ethernet) return None -- cgit v1.2.3 From ba60266ab18969d588179b3acff3bccc078b2ee8 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 19 Dec 2025 21:16:22 +0100 Subject: ethernet: T8084: prevent MAC changes on ENA interfaces (AWS EC2) Add a safeguard to block MAC address changes when the underlying driver is ENA (used on AWS EC2), and display a clear error message instead of raising "OSError: [Errno 95] Operation not supported." --- python/vyos/ethtool.py | 8 ++++++++ src/conf_mode/interfaces_ethernet.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+) (limited to 'src') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 22e066def..76c5b29f0 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -25,6 +25,10 @@ _drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront', 'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth', 'ixgbevf', 'tun', 'vif'] +_drivers_without_mac_change = ['ena'] +# enable interface bonding will change the interface MAC address, thus all drivers +# not supporting MAC address change, also do not support bonding + class Ethtool: """ Class is used to retrive and cache information about an ethernet adapter @@ -222,3 +226,7 @@ class Ethtool: matches = re.findall(rf'{rx_tx_comb}:\s+(\d+)', self._channels) return [int(value) for value in matches] + + def check_mac_change(self) -> bool: + """ Check if ethernet drivers supports changing MAC address """ + return bool(self.get_driver_name() not in _drivers_without_mac_change) diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index 8e490884f..d926c458c 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -273,6 +273,20 @@ def verify_offload(ethernet: dict, ethtool: Ethtool): raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ 'for MTU size larger then 1500 bytes') +def verify_mac_change(ethernet: dict, ethtool: Ethtool): + """ + Verify if ethernet card driver supports changing the interface MAC address. + AWS ENA driver has no support for MAC address changes. + + :param ethernet: dictionary which is received from get_interface_dict + :type ethernet: dict + :param ethtool: Ethernet object + :type ethtool: Ethtool + """ + if 'mac' not in ethernet: + return None + if not ethtool.check_mac_change(): + raise ConfigError(f'Driver does not suport changing MAC address!') def verify_allowedbond_changes(ethernet: dict): """ @@ -381,6 +395,7 @@ def verify(ethernet): verify_flow_control(ethernet, ethtool) verify_ring_buffer(ethernet, ethtool) verify_offload(ethernet, ethtool) + verify_mac_change(ethernet, ethtool) if 'is_bond_member' in ethernet: verify_bond_member(ethernet, ethtool) -- cgit v1.2.3 From 066d2be8b3d57b2171aa2056c7d953810f50dcb8 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 19 Dec 2025 21:19:11 +0100 Subject: bond: T8084: disallow bond members that do not support MAC changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building on commit ba60266ab1896 (“ethernet: T8084: prevent MAC changes on ENA interfaces (AWS EC2)”), add safeguards to prevent interfaces from being used as bond members if they: * do not support MAC address changes, or * appear on a denylist of interfaces invalid for bonding (currently empty) --- python/vyos/ethtool.py | 5 +++++ src/conf_mode/interfaces_bonding.py | 5 +++++ 2 files changed, 10 insertions(+) (limited to 'src') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 76c5b29f0..d0495df43 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -28,6 +28,7 @@ _drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront', _drivers_without_mac_change = ['ena'] # enable interface bonding will change the interface MAC address, thus all drivers # not supporting MAC address change, also do not support bonding +_drivers_without_bonding_support = _drivers_without_mac_change + [] class Ethtool: """ @@ -230,3 +231,7 @@ class Ethtool: def check_mac_change(self) -> bool: """ Check if ethernet drivers supports changing MAC address """ return bool(self.get_driver_name() not in _drivers_without_mac_change) + + def check_bonding(self) -> bool: + """ Check if ethernet drivers supports bonding """ + return bool(self.get_driver_name() not in _drivers_without_bonding_support) diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index 3f6450076..e6aaa3440 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -30,6 +30,7 @@ from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf +from vyos.ethtool import Ethtool from vyos.frrender import FRRender from vyos.frrender import get_frrender_dict from vyos.ifconfig import BondIf @@ -251,6 +252,10 @@ def verify(bond): raise ConfigError('Configured MTU is less then member '\ f'interface "{interface}" minimum of {min_mtu}!') + # not all ethernet drivers support interface bonding + if not Ethtool(interface).check_bonding(): + raise ConfigError(error_msg + 'driver is not supported!') + if 'primary' in bond: if bond['primary'] not in bond['member']['interface']: raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface') -- cgit v1.2.3