From 4839fbdc5e48aa8a32bf08ca20c4b922aa26cb32 Mon Sep 17 00:00:00 2001 From: Nataliia Solomko Date: Mon, 27 Jul 2026 16:35:37 +0300 Subject: vpp: T8468: Apply MAC address changes on VPP interfaces The interface config filter stripped the "mac" node, so a MAC address configured on a VPP interface never reached the dataplane. Allow "mac" through the filter; VPP applies it to the hardware interface via lcp-sync. Some DPDK drivers (e.g. vmxnet3) cannot change the MAC and would fail to bring the interface up. Reject such a change at verify time - both when setting the MAC and when adding an interface that already has one to VPP. --- python/vyos/vpp/config_filter.py | 1 + python/vyos/vpp/config_verify.py | 42 ++++++++++++++++++++++++++++++++++++ smoketest/scripts/cli/test_vpp.py | 21 +++++++++++++++++- src/conf_mode/interfaces_ethernet.py | 6 ++++++ src/conf_mode/vpp.py | 13 +++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/python/vyos/vpp/config_filter.py b/python/vyos/vpp/config_filter.py index d0c982f7c..f6b4aaff2 100644 --- a/python/vyos/vpp/config_filter.py +++ b/python/vyos/vpp/config_filter.py @@ -36,6 +36,7 @@ def iface_filter_eth(config: Config, iface: str) -> None: 'hw-id', 'ip', 'ipv6', + 'mac', 'mtu', 'redirect', 'vif', diff --git a/python/vyos/vpp/config_verify.py b/python/vyos/vpp/config_verify.py index 228fcbc09..fb5d82443 100644 --- a/python/vyos/vpp/config_verify.py +++ b/python/vyos/vpp/config_verify.py @@ -24,10 +24,19 @@ from vyos.utils.convert import range_str_to_list from vyos.utils.cpu import get_core_count as total_core_count, get_cpus from vyos.utils.dict import dict_search from vyos.utils.file import read_file +from vyos.utils.file import read_json from vyos.vpp.config_resource_checks import memory as mem_checks from vyos.vpp.config_resource_checks.resource_defaults import default_resource_map +from vyos.vpp.configdb import STORAGE_LOCATION from vyos.vpp.utils import human_memory_to_bytes, bytes_to_human_memory +from vyos.vpp.utils import EthtoolGDrvinfo + +# DPDK drivers whose PMD cannot change the interface MAC address. Their PMD does +# not implement the secondary unicast-MAC filter callback (mac_addr_add/remove), +# so a MAC change makes VPP fail to bring the interface up (dpdk_add_del_mac_address +# returns -95/ENOTSUP). Reject the change up front instead (T8468). +VPP_MAC_CHANGE_UNSUPPORTED_DRIVERS = frozenset({'vmxnet3'}) # VPP feature paths that reference interfaces _VPP_FEATURE_INTERFACE_REFS = [ @@ -122,6 +131,39 @@ def verify_vpp_remove_interface(iface: str, config: dict, match_vlans: bool = Fa ) +def _vpp_iface_driver(iface: str) -> str | None: + """Resolve the physical NIC driver of a VPP interface. + + Once the interface is bound to VPP its live kernel driver is the LCP tap, + so use the original driver captured at bind time from the persisted config. + Before it is bound (interface being added to VPP now) there is no persisted + entry yet, so fall back to the live kernel driver. + """ + persist = read_json(f'{STORAGE_LOCATION}/vpp_conf.json', {}) + driver = dict_search(f'eth_ifaces.{iface}.original_driver', persist) + if driver: + return driver + try: + return EthtoolGDrvinfo(iface).driver + except FileNotFoundError: + return None + + +def verify_vpp_mac_change_supported(iface: str): + """Reject a custom MAC address on a VPP interface whose DPDK driver cannot + apply it. Applying it makes VPP fail to bring the interface up, leaving it + down - so reject it whether the MAC is being set or the interface is being + added to VPP with a MAC already configured. + """ + driver = _vpp_iface_driver(iface) + if driver in VPP_MAC_CHANGE_UNSUPPORTED_DRIVERS: + raise ConfigError( + f'Interface "{iface}" has a custom MAC address configured, but its ' + f'VPP dataplane driver ("{driver}") does not support changing the ' + 'MAC address. Remove the "mac" setting from this interface.' + ) + + def verify_vpp_interface_not_in_feature(iface: str, config: dict): """Raise ConfigError if interface is used by a VPP feature (NAT, ACL, etc.). diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py index bda2da0f9..16ce29f8b 100755 --- a/smoketest/scripts/cli/test_vpp.py +++ b/smoketest/scripts/cli/test_vpp.py @@ -103,6 +103,9 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): # out the current configuration :) cls.cli_delete(cls, base_path) cls.cli_delete(cls, interfaces_path) + # drop any pre-existing custom MAC so the MAC test baseline is the + # interface hardware address (hw-id) + cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'mac']) def setUp(self): # always forward to base class @@ -121,8 +124,9 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.cli_delete(interfaces_path) self.cli_commit() - # delete address for Ethernet interface + # delete address and any custom MAC for the Ethernet interface self.cli_delete(['interfaces', 'ethernet', interface, 'address']) + self.cli_delete(['interfaces', 'ethernet', interface, 'mac']) self.cli_commit() self.assertFalse(os.path.exists(VPP_CONF)) @@ -183,6 +187,21 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.cli_delete(['interfaces', 'ethernet', interface, 'mtu']) self.cli_commit() + # A custom MAC address must reach the VPP dataplane, and removing it must + # restore the hardware address (hw-id). Rejection of drivers that cannot + # change the MAC (e.g. vmxnet3) is not covered here, as the CI dataplane + # NIC uses a supported driver. + hw_mac = VPPControl().get_mac(interface) + mac = '02:00:de:ad:be:01' + self.cli_set(['interfaces', 'ethernet', interface, 'mac', mac]) + self.cli_commit() + self.assertEqual(VPPControl().get_mac(interface), mac) + + # removing the custom MAC reverts to the hardware address (hw-id) + self.cli_delete(['interfaces', 'ethernet', interface, 'mac']) + self.cli_commit() + self.assertEqual(VPPControl().get_mac(interface), hw_mac) + # set interface address as dhcp self.cli_set(['interfaces', 'ethernet', interface, 'address', 'dhcp']) self.cli_commit() diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index 9063bcd7c..16ff90300 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -50,6 +50,7 @@ from vyos.utils.network import get_vrf_tableid from vyos.utils.process import is_systemd_service_running from vyos.vpp.config_deps import deps_bond_dict from vyos.vpp.config_verify import verify_vpp_remove_interface +from vyos.vpp.config_verify import verify_vpp_mac_change_supported from vyos.vpp.control_vpp import VPPControl from vyos import ConfigError from vyos import airbag @@ -402,6 +403,11 @@ def verify(ethernet): verify_ring_buffer(ethernet, ethtool) verify_offload(ethernet, ethtool) verify_mac_change(ethernet, ethtool) + if ( + 'mac' in ethernet + and dict_search(f'vpp.settings.interface.{ifname}', ethernet) is not None + ): + verify_vpp_mac_change_supported(ifname) verify_coalesce(ethernet, ethtool) if 'is_bond_member' in ethernet: diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index c0e30d1f1..a831ded02 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -62,6 +62,7 @@ from vyos.vpp.config_verify import ( verify_routes_count, verify_vpp_main_heap_size, verify_vpp_buffers, + verify_vpp_mac_change_supported, ) from vyos.vpp.config_resource_checks import memory from vyos.vpp.config_filter import iface_filter_eth @@ -394,6 +395,12 @@ def get_config(config=None): EthtoolGDrvinfo(iface).driver ) + # Record whether a custom MAC is configured, so verify() can + # reject it up front on drivers that cannot apply it. + config['settings']['interface'][iface]['mac_configured'] = conf.exists( + ['interfaces', 'ethernet', iface, 'mac'] + ) + # filter unsupported config nodes iface_filter_eth(conf, iface) set_dependents('ethernet', conf, iface) @@ -582,6 +589,12 @@ def verify(config): if iface not in ethernet_ifaces: raise ConfigError(f'Interface {iface} does not exist or is not Ethernet!') + # An interface added to VPP with a custom MAC its driver cannot apply would + # fail to come up in the dataplane - reject it here instead. + for iface, iface_config in config['settings']['interface'].items(): + if iface_config.get('mac_configured'): + verify_vpp_mac_change_supported(iface) + # Resource usage checks cpu_cores = int(config['settings']['resource_allocation']['cpu_cores']) verify_vpp_minimum_cpus() -- cgit v1.2.3