summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNataliia Solomko <natalirs1985@gmail.com>2026-07-27 16:35:37 +0300
committerNataliia Solomko <natalirs1985@gmail.com>2026-07-29 13:45:21 +0300
commit4839fbdc5e48aa8a32bf08ca20c4b922aa26cb32 (patch)
tree9ed8a75655e90e5bf40cfbd12a03bd786988e1db
parentb81e435210f499b225b5d27fd16c370c0095da3a (diff)
downloadvyos-1x-4839fbdc5e48aa8a32bf08ca20c4b922aa26cb32.tar.gz
vyos-1x-4839fbdc5e48aa8a32bf08ca20c4b922aa26cb32.zip
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.
-rw-r--r--python/vyos/vpp/config_filter.py1
-rw-r--r--python/vyos/vpp/config_verify.py42
-rwxr-xr-xsmoketest/scripts/cli/test_vpp.py21
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py6
-rwxr-xr-xsrc/conf_mode/vpp.py13
5 files changed, 82 insertions, 1 deletions
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()