summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorViacheslav Hletenko <v.gletenko@vyos.io>2026-06-22 14:03:20 +0300
committerGitHub <noreply@github.com>2026-06-22 14:03:20 +0300
commit02f8c17a56518157e48048fb39b69f373ee9c829 (patch)
tree38f3f1f8cd3a9b16a31366dec5b01ab87976ef55
parent6c0fd99099a6251a12bc2287d5ba2ab115cd8015 (diff)
parent50919788b8128024341b2af351f969a00a9d3a0a (diff)
downloadvyos-1x-02f8c17a56518157e48048fb39b69f373ee9c829.tar.gz
vyos-1x-02f8c17a56518157e48048fb39b69f373ee9c829.zip
Merge pull request #5280 from natali-rs1985/T8603
vpp: T8603: Expand ACL support to logical interfaces
-rw-r--r--python/vyos/vpp/utils.py45
-rwxr-xr-xsmoketest/scripts/cli/test_vpp.py66
-rw-r--r--src/conf_mode/vpp_acl.py29
-rw-r--r--src/conf_mode/vpp_interfaces_ipip.py4
4 files changed, 117 insertions, 27 deletions
diff --git a/python/vyos/vpp/utils.py b/python/vyos/vpp/utils.py
index 0badfdb19..90859c325 100644
--- a/python/vyos/vpp/utils.py
+++ b/python/vyos/vpp/utils.py
@@ -22,7 +22,6 @@ from fcntl import ioctl
from pathlib import Path
from struct import pack
-
mem_shift = {'K': 10, 'KB': 10, 'M': 20, 'MB': 20, 'G': 30, 'GB': 30}
@@ -54,29 +53,37 @@ def vpp_iface_name_transform(iface: str) -> str:
"""Convert a CLI interface name to its corresponding VPP interface name format
Args:
- iface (str): Interface name as used in VyOS configuration (e.g., "bond0").
+ iface (str): Interface name as used in VyOS configuration (e.g., "vppbond0").
Returns:
str: Interface name formatted as recognized by VPP (e.g., "BondEthernet0").
"""
- vpp_iface_name = iface
- if vpp_iface_name.startswith('vppbond'):
+ vpp_iface_name = iface.removeprefix('vpp')
+ if vpp_iface_name.startswith('bond'):
# interface name in VPP is BondEthernetX
- vpp_iface_name = vpp_iface_name.replace('vppbond', 'BondEthernet')
+ return vpp_iface_name.replace('bond', 'BondEthernet')
+ if vpp_iface_name.startswith('lo'):
+ # interface name in VPP is loopX
+ return vpp_iface_name.replace('lo', 'loop')
+ if vpp_iface_name.startswith('vxlan'):
+ # VXLAN interface names in VPP are vxlan_tunnelN
+ return iftunnel_transform(iface)
return vpp_iface_name
-def cli_ifaces_list(config_instance, mode: str = 'candidate') -> list[str]:
+def cli_ifaces_list(
+ config_instance, mode: str = 'candidate', include_vifs: bool = False
+) -> list[str]:
"""List of all VPP interfaces (CLI names)
Args:
config_instance (VyOS Config): VyOS Config instance
mode (str, optional): `candidate` or `running`. Defaults to 'candidate'.
+ include_vifs (bool, optional): Include VIF subinterfaces. Defaults to False.
Returns:
list[str]: list of interfaces
"""
-
effective_mode: bool = True if mode == 'running' else False
# Read a config
@@ -98,27 +105,32 @@ def cli_ifaces_list(config_instance, mode: str = 'candidate') -> list[str]:
with_recursive_defaults=True,
)
- vpp_ifaces: list[str] = []
+ ethernet_ifaces = list(config.get('settings', {}).get('interface', {}).keys())
- # Get a list of Ethernet interfaces
- for iface in config.get('settings', {}).get('interface', {}).keys():
- vpp_ifaces.append(iface)
+ if include_vifs:
+ vpp_ifaces = cli_ethernet_with_vifs_ifaces(config_instance, mode=mode)
+ else:
+ vpp_ifaces = ethernet_ifaces
- # Get a list of interfaces VPP
- for iface_type in interfaces_config.keys():
- for iface in interfaces_config.get(iface_type, {}).keys():
+ for _, iface_type_config in interfaces_config.items():
+ for iface, iface_config in iface_type_config.items():
vpp_ifaces.append(iface)
+ if include_vifs:
+ vpp_ifaces.extend(
+ [f'{iface}.{vif}' for vif in iface_config.get('vif', {})]
+ )
return vpp_ifaces
def cli_ethernet_with_vifs_ifaces(
- config_instance, include_nested_vifs=False
+ config_instance, mode: str = 'candidate', include_nested_vifs=False
) -> list[str]:
"""List of all VPP Ethernet interfaces with VIFs
Args:
config_instance (VyOS Config): VyOS Config instance
+ mode (str, optional): `candidate` or `running`. Defaults to 'candidate'.
include_nested_vifs (bool): Include Q-in-Q/customer VIFs if True
Returns:
@@ -126,10 +138,13 @@ def cli_ethernet_with_vifs_ifaces(
"""
from vyos.configdict import get_interface_dict
+ effective_mode: bool = True if mode == 'running' else False
+
# Read a config
config = config_instance.get_config_dict(
['vpp'],
key_mangling=('-', '_'),
+ effective=effective_mode,
get_first_key=True,
no_tag_node_value_mangle=True,
with_recursive_defaults=True,
diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py
index 7c52dd10c..7813dfdf5 100755
--- a/smoketest/scripts/cli/test_vpp.py
+++ b/smoketest/scripts/cli/test_vpp.py
@@ -1474,7 +1474,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
# Ensure that VPP process is active
self.assertTrue(process_named_running(PROCESS_NAME))
- def test_23_vpp_acl_subinterface(self):
+ def test_23_1_vpp_acl_subinterface(self):
base_acl = base_path + ['acl', 'ip']
vlan = '200'
subif = f'{interface}.{vlan}'
@@ -1514,6 +1514,70 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
list(acl_interfaces[0].acls)[: acl_interfaces[0].count], [acl_index]
)
+ def test_23_2_vpp_acl_bond_with_vif(self):
+ base_acl = base_path + ['acl', 'ip']
+ base_bond = interfaces_path + ['bonding']
+ bond = 'vppbond0'
+ acl_name = 'TEST_ACL'
+ vif = '111'
+ bond_vif = f'{bond}.{vif}'
+ bond_vif_vpp = vpp_iface_name_transform(bond_vif)
+
+ self.cli_set(base_bond + [bond, 'member', 'interface', interface])
+ self.cli_set(base_bond + [bond, 'vif', vif])
+
+ self.cli_set(
+ base_acl + ['tag-name', acl_name, 'rule', '10', 'action', 'permit']
+ )
+ self.cli_set(
+ base_acl
+ + ['interface', bond_vif, 'input', 'acl-tag', '10', 'tag-name', acl_name]
+ )
+ self.cli_commit()
+
+ # Verify the VIF interface exists in VPP and the ACL was created
+ vpp = VPPControl()
+ iface_index = vpp.get_sw_if_index(bond_vif_vpp)
+ self.assertIsNotNone(iface_index)
+
+ acl_index = None
+ for acl in vpp.api.acl_dump(acl_index=0xFFFFFFFF):
+ if acl.tag == acl_name:
+ acl_index = acl.acl_index
+ break
+ self.assertIsNotNone(acl_index)
+
+ # Verify the ACL is assigned to the VIF interface
+ acl_interfaces = [
+ entry
+ for entry in vpp.api.acl_interface_list_dump()
+ if entry.sw_if_index == iface_index and entry.count != 0
+ ]
+ self.assertEqual(len(acl_interfaces), 1)
+ self.assertEqual(
+ list(acl_interfaces[0].acls)[: acl_interfaces[0].count], [acl_index]
+ )
+
+ # Change bond mode — this recreates the bond interface and must re-trigger
+ # the ACL dependency so the ACL is reapplied to the VIF
+ self.cli_set(base_bond + [bond, 'mode', '802.3ad'])
+ self.cli_commit()
+
+ # Verify the ACL is still correctly assigned after bond reconfiguration
+ vpp = VPPControl()
+ iface_index = vpp.get_sw_if_index(bond_vif_vpp)
+ self.assertIsNotNone(iface_index)
+
+ acl_interfaces = [
+ entry
+ for entry in vpp.api.acl_interface_list_dump()
+ if entry.sw_if_index == iface_index and entry.count != 0
+ ]
+ self.assertEqual(len(acl_interfaces), 1)
+ self.assertEqual(
+ list(acl_interfaces[0].acls)[: acl_interfaces[0].count], [acl_index]
+ )
+
def test_24_vpp_lcp_vrf_table_sync(self):
vlan = '20'
subif = f'{interface}.{vlan}'
diff --git a/src/conf_mode/vpp_acl.py b/src/conf_mode/vpp_acl.py
index 5b282dfdc..9f972ea0a 100644
--- a/src/conf_mode/vpp_acl.py
+++ b/src/conf_mode/vpp_acl.py
@@ -25,12 +25,11 @@ from vyos.configdict import node_changed
from vyos.config import Config
from vyos.utils.network import get_protocol_by_name
-from vyos.vpp.utils import cli_ethernet_with_vifs_ifaces
from vyos.vpp.utils import cli_ifaces_list
+from vyos.vpp.utils import vpp_iface_name_transform
from vyos.vpp.acl import Acl
from vyos.vpp.config_verify import verify_vpp_interface_not_a_member
-
# TCP flag names to bit values
TCP_FLAGS = {
'FIN': 0x01,
@@ -185,11 +184,7 @@ def get_config(config=None) -> dict:
{
'changed_ip_ifaces': changed_ip_ifaces,
'changed_mac_ifaces': changed_mac_ifaces,
- 'vpp_ifaces': list(
- dict.fromkeys(
- cli_ifaces_list(conf) + cli_ethernet_with_vifs_ifaces(conf)
- )
- ),
+ 'vpp_ifaces': cli_ifaces_list(conf, include_vifs=True),
}
)
@@ -226,7 +221,12 @@ def verify(config):
if 'action' not in rule_config:
raise ConfigError(f'{err_msg} action must be defined')
+ not_allowed_prefixes = ('vppbr', 'vppxcon')
for iface, iface_config in acl.get('interface', {}).items():
+ if iface.startswith(not_allowed_prefixes):
+ raise ConfigError(
+ f'Interface {iface} is not allowed in ACL {acl_type} configuration'
+ )
if iface not in config.get('vpp_ifaces'):
raise ConfigError(
f'{iface} must be a VPP interface for ACL interface'
@@ -332,6 +332,8 @@ def verify(config):
f'ACL with tag-name {name} does not exist. Cannot use it for interface {iface}'
)
+ return None
+
def generate(config):
pass
@@ -350,7 +352,7 @@ def apply(config):
# Delete ACL interfaces
for interface in config.get('changed_ip_ifaces'):
- acl.delete_acl_interface(interface)
+ acl.delete_acl_interface(vpp_iface_name_transform(interface))
# Delete ACLs
for acl_name in remove_config_ip.get('tag_name'):
@@ -363,7 +365,7 @@ def apply(config):
# Delete ACL interfaces
for interface in config.get('changed_mac_ifaces'):
- acl.delete_acl_mac_interface(interface)
+ acl.delete_acl_mac_interface(vpp_iface_name_transform(interface))
# Delete ACL mac
for acl_name in remove_config_mac.get('tag_name'):
@@ -390,7 +392,7 @@ def apply(config):
v['tag_name']
for v in iface_config.get('output', {}).get('acl_tag', {}).values()
]
- acl.add_acl_interface(iface, input_tags, output_tags)
+ acl.add_acl_interface(vpp_iface_name_transform(iface), input_tags, output_tags)
# Add or replace ACL mac
config_mac = config.get('mac', {})
@@ -401,7 +403,12 @@ def apply(config):
acl.add_replace_acl_mac(acl_name, rules)
for iface, iface_config in config_mac.get('interface', {}).items():
- acl.add_acl_mac_interface(iface, iface_config.get('tag_name'))
+ acl.add_acl_mac_interface(
+ vpp_iface_name_transform(iface),
+ iface_config.get('tag_name'),
+ )
+
+ return None
if __name__ == '__main__':
diff --git a/src/conf_mode/vpp_interfaces_ipip.py b/src/conf_mode/vpp_interfaces_ipip.py
index dd4a19e36..10c728e02 100644
--- a/src/conf_mode/vpp_interfaces_ipip.py
+++ b/src/conf_mode/vpp_interfaces_ipip.py
@@ -71,6 +71,10 @@ def get_config(config=None) -> dict:
no_tag_node_value_mangle=True,
)
+ # ACL dependency
+ if conf.exists(['vpp', 'acl']):
+ set_dependents('vpp_acl', conf)
+
return config