diff options
| author | Viacheslav Hletenko <v.gletenko@vyos.io> | 2026-06-22 14:03:20 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-22 14:03:20 +0300 |
| commit | 02f8c17a56518157e48048fb39b69f373ee9c829 (patch) | |
| tree | 38f3f1f8cd3a9b16a31366dec5b01ab87976ef55 | |
| parent | 6c0fd99099a6251a12bc2287d5ba2ab115cd8015 (diff) | |
| parent | 50919788b8128024341b2af351f969a00a9d3a0a (diff) | |
| download | vyos-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.py | 45 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 66 | ||||
| -rw-r--r-- | src/conf_mode/vpp_acl.py | 29 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_ipip.py | 4 |
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 |
