diff options
| author | Nataliia Solomko <natalirs1985@gmail.com> | 2026-04-02 13:57:19 +0300 |
|---|---|---|
| committer | Nataliia Solomko <natalirs1985@gmail.com> | 2026-04-02 14:27:22 +0300 |
| commit | 3139ba759c21800386bc1aa127dd95bc63229362 (patch) | |
| tree | 01ba37b32bd77866cdc908e1e7eedf6b0a3abcab | |
| parent | 2ad37c4a8a963651bbc183d24662f2571c4682c6 (diff) | |
| download | vyos-1x-3139ba759c21800386bc1aa127dd95bc63229362.tar.gz vyos-1x-3139ba759c21800386bc1aa127dd95bc63229362.zip | |
vpp: T8438: Add bidirectional interface-in-use validation
Add bidirectional VPP interface reference validation: prevent assigning an interface used by a VPP feature (NAT/ACL/IPFIX/sFlow, etc.)
as a VPP member (bond/bridge/xconnect) and prevent using a VPP member interface in VPP features.
Block interface deletion when it is still referenced by any VPP feature/member.
Fix VLAN subinterface removal checks broken by the recent VPP config tree restructuring.
| -rw-r--r-- | python/vyos/vpp/config_verify.py | 116 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 14 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces_ethernet.py | 57 | ||||
| -rwxr-xr-x | src/conf_mode/vpp.py | 67 | ||||
| -rw-r--r-- | src/conf_mode/vpp_acl.py | 10 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_bonding.py | 12 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_bridge.py | 10 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_xconnect.py | 10 | ||||
| -rw-r--r-- | src/conf_mode/vpp_ipfix.py | 10 | ||||
| -rw-r--r-- | src/conf_mode/vpp_nat_cgnat.py | 10 | ||||
| -rw-r--r-- | src/conf_mode/vpp_nat_nat44.py | 10 | ||||
| -rw-r--r-- | src/conf_mode/vpp_sflow.py | 10 |
12 files changed, 235 insertions, 101 deletions
diff --git a/python/vyos/vpp/config_verify.py b/python/vyos/vpp/config_verify.py index b32544a40..da8e3d24b 100644 --- a/python/vyos/vpp/config_verify.py +++ b/python/vyos/vpp/config_verify.py @@ -21,11 +21,127 @@ import psutil from vyos import ConfigError from vyos.base import Warning from vyos.utils.cpu import get_core_count as total_core_count, get_cpus +from vyos.utils.dict import dict_search 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.utils import human_memory_to_bytes, bytes_to_human_memory +# VPP feature paths that reference interfaces +_VPP_FEATURE_INTERFACE_REFS = [ + ('nat.cgnat.interface.inside', None, 'VPP CGNAT inside'), + ('nat.cgnat.interface.outside', None, 'VPP CGNAT outside'), + ('nat.nat44.interface.inside', None, 'VPP NAT44 inside'), + ('nat.nat44.interface.outside', None, 'VPP NAT44 outside'), + ( + 'nat.nat44.address_pool.translation.interface', + None, + 'VPP NAT44 translation pool', + ), + ('nat.nat44.address_pool.twice_nat.interface', None, 'VPP NAT44 twice-NAT pool'), + ('nat.nat44.exclude.rule', 'external_interface', 'VPP NAT44 exclude rule external'), + ('acl.ip.interface', None, 'VPP IP ACL'), + ('acl.mac.interface', None, 'VPP MAC ACL'), + ('ipfix.interface', None, 'IPFIX monitoring'), + ('sflow.interface', None, 'VPP sFlow'), +] +# VPP member configuration paths that reference interfaces +_VPP_MEMBER_INTERFACE_REFS = [ + ('interfaces_vpp.bonding', 'member.interface', 'VPP bonding member'), + ('interfaces_vpp.bridge', 'member.interface', 'VPP bridge member'), + ('interfaces_vpp.xconnect', 'member.interface', 'VPP xconnect member'), +] + +_VPP_INTERFACE_REFS = _VPP_FEATURE_INTERFACE_REFS + _VPP_MEMBER_INTERFACE_REFS + + +def vpp_interface_in_use( + iface: str, config: dict, match_vlans: bool = False, refs: list = None +): + """Check if an interface is referenced in VPP config. + + Args: + iface: interface name to check (e.g. 'eth0' or 'eth0.100') + config: config dict + match_vlans: if True, also match VLAN subinterfaces (e.g. 'eth0.100') + refs: list of (path, inner_path, feature_name) tuples to scan. + Defaults to _VPP_INTERFACE_REFS (all refs). + Use _VPP_FEATURE_INTERFACE_REFS or _VPP_MEMBER_INTERFACE_REFS to narrow scope. + + Returns: + feature_name (str) if found, None otherwise + """ + if refs is None: + refs = _VPP_INTERFACE_REFS + + def _matches(candidate): + """ + Return True if 'candidate' matches 'iface' (optionally match subinterfaces). + 'candidate' can be a string or a list/iterable of strings. + """ + values = [candidate] if isinstance(candidate, str) else list(candidate) + for name in values: + if name == iface: + return True + if match_vlans and name.startswith(f'{iface}.'): + return True + return False + + for path, inner_path, usage in refs: + data = dict_search(path, config) + if not data: + continue + + if inner_path is not None: + for item_key, item_conf in data.items(): + value = dict_search(inner_path, item_conf) + if value is not None and _matches(value): + return usage + else: + if _matches(data): + return usage + + return None + + +def verify_vpp_remove_interface(iface: str, config: dict, match_vlans: bool = False): + """ + Check that an interface is not referenced by any VPP feature. + Raises ConfigError if the interface is still referenced. + """ + feature = vpp_interface_in_use(iface, config, match_vlans) + if feature: + raise ConfigError( + f'Cannot remove interface "{iface}", ' + f'{"it or its VLAN " if match_vlans else "it "}is still configured as {feature} 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.). + + Called from VPP interfaces scripts (bonding/bridge/xconnect) before adding a member. + """ + feature = vpp_interface_in_use(iface, config, refs=_VPP_FEATURE_INTERFACE_REFS) + if feature: + raise ConfigError( + f'Interface {iface} is already used as {feature} interface ' + f'and cannot be added as a member' + ) + + +def verify_vpp_interface_not_a_member(iface: str, config: dict): + """Raise ConfigError if interface is a member of bonding/bridge/xconnect. + + Called from feature scripts (NAT, ACL, sFlow, etc.) before adding an interface. + """ + member = vpp_interface_in_use(iface, config, refs=_VPP_MEMBER_INTERFACE_REFS) + if member: + raise ConfigError( + f'Interface {iface} is already used as {member} interface ' + f'and cannot be added to a VPP feature' + ) + def verify_vpp_remove_xconnect_interface(config: dict): if not 'deleted' in config: diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py index d4238f68c..8fcd541f0 100755 --- a/smoketest/scripts/cli/test_vpp.py +++ b/smoketest/scripts/cli/test_vpp.py @@ -873,6 +873,13 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): lines = out.split('\n') self.assertTrue(len(lines) == 3) + # Cannot remove inside/outside interface from vpp while it is used in the feature + # expect raise ConfigError + self.cli_delete(base_bond + [iface_bond, 'vif', vif_1]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_discard() + def test_14_vpp_nat44(self): base_nat = base_path + ['nat', 'nat44'] exclude_local_addr = '100.64.0.52' @@ -1018,6 +1025,13 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): for expected_entry in expected_entries: self.assertIn(expected_entry, out) + # Cannot remove interface from vpp while it is used in the feature + # expect raise ConfigError + self.cli_delete(base_path + ['settings', 'interface', iface_2]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_discard() + # cannot delete system sFlow configuration if VPP sFlow is configured # expect raise ConfigError self.cli_delete(base_sflow) diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index 2eb36a231..886f9b7b7 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -15,7 +15,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import re from sys import exit @@ -47,6 +46,7 @@ from vyos.utils.dict import dict_to_paths_values from vyos.utils.dict import dict_set from vyos.utils.dict import dict_delete from vyos.utils.process import is_systemd_service_running +from vyos.vpp.config_verify import verify_vpp_remove_interface from vyos.vpp.control_vpp import VPPControl from vyos import ConfigError from vyos import airbag @@ -176,12 +176,20 @@ def get_config(config=None): ethernet['flowtable_interfaces'] = get_flowtable_interfaces(conf) - ethernet['vpp'] = conf.get_config_dict( + vpp_config = conf.get_config_dict( ['vpp'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True, ) + if vpp_config: + ethernet['vpp'] = vpp_config + ethernet['vpp']['interfaces_vpp'] = conf.get_config_dict( + ['interfaces', 'vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) # Protocols static arp dependency if 'static_arp' in ethernet: @@ -353,23 +361,11 @@ def verify_flowtable(ethernet: dict): def verify_vpp_remove_vif(ethernet: dict): """Ensure that VIF interfaces being removed are not used by VPP features""" - vpp_paths_pattern = re.compile( - # Known paths that already use VLAN interfaces - r'(nat\.cgnat\.interface\.inside)|' - r'(nat\.cgnat\.interface\.outside)|' - r'(nat\.nat44\.interface\.inside)|' - r'(nat\.nat44\.interface\.outside)|' - # Potential paths for VLAN interfaces - r'(nat\.nat44\.address_pool\.translation\.interface)|' - r'(nat\.nat44\.address_pool\.twice_nat\.interface)|' - r'(nat\.nat44\.exclude\.rule\.(\d)+\.external_interface)|' - r'(interfaces\.bonding\.bond(\d)+\.member\.interface)|' - r'(interfaces\.bridge\.br(\d)+\.member\.interface)|' - r'(interfaces\.xconnect\.xcon(\d)+\.member\.interface)|' - r'(acl\.ip\.interface)|' - r'(acl\.mac\.interface)' - ) ifname = ethernet['ifname'] + vpp_config = ethernet.get('vpp') + + if not vpp_config: + return vlan_names = [ f'{ifname}.{vif_id}' @@ -377,29 +373,8 @@ def verify_vpp_remove_vif(ethernet: dict): for vif_id in ethernet.get(vif_group, []) ] - if not vlan_names: - return - - vpp_flat = dict_to_paths_values(ethernet.get('vpp', {})) - - candidate_keys = [] - for key, value in vpp_flat.items(): - # Normalize values to list for consistent processing - values = value if isinstance(value, list) else [value] - if any(vlan in values for vlan in vlan_names): - candidate_keys.append((key, values)) - - if not candidate_keys: - return - - for key, values in candidate_keys: - if vpp_paths_pattern.fullmatch(key): - used_vlans = [v for v in vlan_names if v in values] - if used_vlans: - raise ConfigError( - f'Cannot delete interface "{used_vlans[0]}", ' - f'it is still in use by "vpp {key.replace(".", " ")}"' - ) + for vlan in vlan_names: + verify_vpp_remove_interface(vlan, vpp_config) def verify(ethernet): verify_flowtable(ethernet) diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index 7b41c54fe..dc9ba9b7c 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -49,10 +49,8 @@ from vyos.utils.process import is_systemd_service_active from vyos.vpp import VPPControl from vyos.vpp import control_host from vyos.vpp import VppNotRunningError -from vyos.vpp.config_deps import deps_bond_dict -from vyos.vpp.config_deps import deps_bridge_dict -from vyos.vpp.config_deps import deps_xconnect_dict from vyos.vpp.config_verify import ( + verify_vpp_remove_interface, verify_vpp_minimum_cpus, verify_vpp_minimum_memory, verify_vpp_cpu_cores, @@ -218,33 +216,6 @@ def _get_max_xdp_rx_queues(config: dict): return 1 -def _check_removed_interfaces(config: dict, feature_name: str, interfaces_config: dict): - """ - Check if removed interfaces are used in any feature configuration - - Args: - config: The main configuration dictionary - feature_name: Human-readable feature name for error messages - interfaces_config: The interfaces dictionary from the feature config - Example: - _check_removed_interfaces(config, 'IPFIX monitoring', config.get('ipfix', {}).get('interface', {})) - """ - if ( - 'removed_ifaces' not in config - or not config['removed_ifaces'] - or not interfaces_config - ): - return - - for removed_iface in config['removed_ifaces']: - iface_name = removed_iface.get('iface_name') - if iface_name and iface_name in interfaces_config: - raise ConfigError( - f'Cannot remove interface {iface_name} - it is currently configured for {feature_name}. ' - f'Remove it from {feature_name} configuration first.' - ) - - def _is_device_allowed(config: dict, iface: str): """ Determines if a network interface device is allowed to be used @@ -295,10 +266,6 @@ def get_config(config=None): no_tag_node_value_mangle=True, ) - xconn_members = deps_xconnect_dict(conf) - bridge_members = deps_bridge_dict(conf) - bond_members = deps_bond_dict(conf) - removed_ifaces = [] tmp = node_changed(conf, base_settings + ['interface']) if tmp: @@ -337,9 +304,6 @@ def get_config(config=None): set_dependents('pppoe_server', conf) return { 'removed_ifaces': removed_ifaces, - 'xconn_members': xconn_members, - 'bridge_members': bridge_members, - 'bond_members': bond_members, 'persist_config': eth_ifaces_persist, 'interfaces_vpp': interfaces_config, 'pppoe_ifaces': pppoe_ifaces, @@ -503,9 +467,6 @@ def get_config(config=None): if removed_ifaces: config['removed_ifaces'] = removed_ifaces - config['xconn_members'] = xconn_members - config['bridge_members'] = bridge_members - config['bond_members'] = bond_members config['interfaces_vpp'] = interfaces_config @@ -557,11 +518,6 @@ def get_config(config=None): def verify(config): - # Check remove VPP interface that used in IPFIX - _check_removed_interfaces( - config, 'IPFIX monitoring', config.get('ipfix', {}).get('interface', {}) - ) - if config.get('interfaces_vpp') and 'remove' in config: raise ConfigError( 'VPP cannot be removed while VPP interfaces exist. Remove all "interfaces vpp" first!' @@ -580,6 +536,12 @@ def verify(config): if not config or 'remove' in config: return None + # Check removed interfaces (and their VLANs) against all VPP features + for removed_iface in config.get('removed_ifaces', []): + verify_vpp_remove_interface( + removed_iface['iface_name'], config, match_vlans=True + ) + if 'settings' not in config: raise ConfigError('"settings interface" is required but not set!') @@ -685,21 +647,6 @@ def verify(config): f'RX mode {rx_mode} is not supported for interface {iface}' ) - # Check if deleted interfaces are not xconnect/bridge/bond members - for iface_config in config.get('removed_ifaces', []): - if iface_config['iface_name'] in config.get('xconn_members', {}): - raise ConfigError( - f'Interface {iface_config["iface_name"]} is an xconnect member and cannot be removed' - ) - if iface_config['iface_name'] in config.get('bridge_members', {}): - raise ConfigError( - f'Interface {iface_config["iface_name"]} is a bridge member and cannot be removed' - ) - if iface_config['iface_name'] in config.get('bond_members', {}): - raise ConfigError( - f'Interface {iface_config["iface_name"]} is a bond member and cannot be removed' - ) - verify_routes_count(config['settings']) for pppoe_iface in config.get('changed_pppoe_ifaces', []): diff --git a/src/conf_mode/vpp_acl.py b/src/conf_mode/vpp_acl.py index 4d9ada4a1..ccbe11763 100644 --- a/src/conf_mode/vpp_acl.py +++ b/src/conf_mode/vpp_acl.py @@ -27,6 +27,7 @@ from vyos.utils.network import get_protocol_by_name from vyos.vpp.utils import cli_ifaces_list from vyos.vpp.acl import Acl +from vyos.vpp.config_verify import verify_vpp_interface_not_a_member # TCP flag names to bit values @@ -190,6 +191,14 @@ def get_config(config=None) -> dict: if effective_config: config.update({'effective': effective_config}) + # VPP interface membership data for member-conflict checks + config['interfaces_vpp'] = conf.get_config_dict( + ['interfaces', 'vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + return config @@ -217,6 +226,7 @@ def verify(config): raise ConfigError( f'{iface} must be a VPP interface for ACL interface' ) + verify_vpp_interface_not_a_member(iface, config) if 'ip' in config: acl = config.get('ip') diff --git a/src/conf_mode/vpp_interfaces_bonding.py b/src/conf_mode/vpp_interfaces_bonding.py index 51272c087..7dba9fb3b 100644 --- a/src/conf_mode/vpp_interfaces_bonding.py +++ b/src/conf_mode/vpp_interfaces_bonding.py @@ -32,6 +32,8 @@ from vyos.vpp.config_deps import deps_xconnect_dict from vyos.vpp.config_verify import verify_member_conflicts from vyos.vpp.config_verify import verify_vpp_remove_bridge_interface from vyos.vpp.config_verify import verify_vpp_remove_xconnect_interface +from vyos.vpp.config_verify import verify_vpp_remove_interface +from vyos.vpp.config_verify import verify_vpp_interface_not_in_feature from vyos.vpp.utils import cli_ifaces_list @@ -98,6 +100,13 @@ def get_config(config=None) -> dict: get_first_key=True, no_tag_node_value_mangle=True, ) + # VPP config for member-in-feature checks + config['vpp'] = conf.get_config_dict( + ['vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) config['bond_members'] = deps_bond_dict(conf) @@ -150,6 +159,7 @@ def verify(config): verify_vpp_remove_bridge_interface(config) if 'deleted' in config: + verify_vpp_remove_interface(ifname, config.get('vpp'), match_vlans=True) return None if not is_systemd_service_active('vpp.service'): @@ -170,6 +180,7 @@ def verify(config): ) verify_member_conflicts(iface, config, 'bond') + verify_vpp_interface_not_in_feature(iface, config.get('vpp')) if mtu := config.get('mtu'): mtu = int(mtu) @@ -199,6 +210,7 @@ def verify(config): raise ConfigError( f'Cannot remove interface {vif_iface}: it is still in use by the PPPoE server' ) + verify_vpp_remove_interface(vif_iface, config.get('vpp')) verify_mtu_ipv6(config) diff --git a/src/conf_mode/vpp_interfaces_bridge.py b/src/conf_mode/vpp_interfaces_bridge.py index 25fad5e5b..4d65690ce 100644 --- a/src/conf_mode/vpp_interfaces_bridge.py +++ b/src/conf_mode/vpp_interfaces_bridge.py @@ -26,6 +26,7 @@ from vyos.vpp.config_deps import deps_bond_dict from vyos.vpp.config_deps import deps_bridge_dict from vyos.vpp.config_deps import deps_xconnect_dict from vyos.vpp.config_verify import verify_member_conflicts +from vyos.vpp.config_verify import verify_vpp_interface_not_in_feature def get_config(config=None) -> dict: @@ -71,6 +72,14 @@ def get_config(config=None) -> dict: config['bridge_members'] = deps_bridge_dict(conf) config['xconn_members'] = deps_xconnect_dict(conf) + # VPP config for member-in-feature checks + config['vpp'] = conf.get_config_dict( + ['vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + return config @@ -108,6 +117,7 @@ def verify(config): ) verify_member_conflicts(member, config, 'bridge') + verify_vpp_interface_not_in_feature(member, config.get('vpp')) # Check if BVI is already defined, only one BVI per bridge domain is allowed if 'bvi' in member_config: diff --git a/src/conf_mode/vpp_interfaces_xconnect.py b/src/conf_mode/vpp_interfaces_xconnect.py index 91fa27c1b..29f2da520 100644 --- a/src/conf_mode/vpp_interfaces_xconnect.py +++ b/src/conf_mode/vpp_interfaces_xconnect.py @@ -26,6 +26,7 @@ from vyos.vpp.config_deps import deps_bond_dict from vyos.vpp.config_deps import deps_bridge_dict from vyos.vpp.config_deps import deps_xconnect_dict from vyos.vpp.config_verify import verify_member_conflicts +from vyos.vpp.config_verify import verify_vpp_interface_not_in_feature from vyos.vpp.utils import cli_ifaces_list @@ -67,6 +68,14 @@ def get_config(config=None) -> dict: config['xconn_members'] = deps_xconnect_dict(conf) config['vpp_ifaces'] = cli_ifaces_list(conf, 'candidate') + # VPP config for member-in-feature checks + config['vpp'] = conf.get_config_dict( + ['vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + return config @@ -100,6 +109,7 @@ def verify(config): ) verify_member_conflicts(iface, config, 'xconn') + verify_vpp_interface_not_in_feature(iface, config.get('vpp')) def generate(config): diff --git a/src/conf_mode/vpp_ipfix.py b/src/conf_mode/vpp_ipfix.py index b69651ee4..8a7633389 100644 --- a/src/conf_mode/vpp_ipfix.py +++ b/src/conf_mode/vpp_ipfix.py @@ -21,6 +21,7 @@ from vyos.vpp.ipfix import IPFIX from vyos.vpp.utils import cli_ifaces_list from vyos.vpp.utils import vpp_iface_name_transform from vyos.vpp.control_vpp import VPPControl +from vyos.vpp.config_verify import verify_vpp_interface_not_a_member def get_config(config=None) -> dict: @@ -60,6 +61,14 @@ def get_config(config=None) -> dict: # Add list of VPP interfaces to the config config.update({'vpp_ifaces': cli_ifaces_list(conf)}) + # VPP interface membership data for member-conflict checks + config['interfaces_vpp'] = conf.get_config_dict( + ['interfaces', 'vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + return config @@ -81,6 +90,7 @@ def verify(config): raise ConfigError( f'{interface} must be a VPP interface for IPFIX monitoring' ) + verify_vpp_interface_not_a_member(interface, config) # Verify that at least one collector is configured if 'collector' not in config: diff --git a/src/conf_mode/vpp_nat_cgnat.py b/src/conf_mode/vpp_nat_cgnat.py index 2044a07b9..838d13239 100644 --- a/src/conf_mode/vpp_nat_cgnat.py +++ b/src/conf_mode/vpp_nat_cgnat.py @@ -26,6 +26,7 @@ from vyos.vpp.utils import vpp_iface_name_transform from vyos.vpp.nat.det44 import Det44 from vyos.vpp.control_vpp import VPPControl from vyos.vpp.config_verify import verify_nat_interfaces +from vyos.vpp.config_verify import verify_vpp_interface_not_a_member protocol_map = { 'all': 0, @@ -118,6 +119,14 @@ def get_config(config=None) -> dict: if effective_config: config.update({'effective': effective_config}) + # VPP interface membership data for member-conflict checks + config['interfaces_vpp'] = conf.get_config_dict( + ['interfaces', 'vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + return config @@ -157,6 +166,7 @@ def verify(config): raise ConfigError( f'{interface} must be a VPP interface for {direction} CGNAT interface' ) + verify_vpp_interface_not_a_member(interface, config) required_keys = {'outside_prefix', 'inside_prefix'} for rule in config['rule']: diff --git a/src/conf_mode/vpp_nat_nat44.py b/src/conf_mode/vpp_nat_nat44.py index 7f0b27541..8d69ee786 100644 --- a/src/conf_mode/vpp_nat_nat44.py +++ b/src/conf_mode/vpp_nat_nat44.py @@ -30,6 +30,7 @@ from vyos.vpp.utils import vpp_iface_name_transform from vyos.vpp.nat.nat44 import Nat44 from vyos.vpp.control_vpp import VPPControl from vyos.vpp.config_verify import verify_nat_interfaces +from vyos.vpp.config_verify import verify_vpp_interface_not_a_member protocol_map = { @@ -124,6 +125,14 @@ def get_config(config=None) -> dict: if effective_config: config.update({'effective': effective_config}) + # VPP interface membership data for member-conflict checks + config['interfaces_vpp'] = conf.get_config_dict( + ['interfaces', 'vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + return config @@ -171,6 +180,7 @@ def verify(config): raise ConfigError( f'{interface} must be a VPP interface for {direction} NAT interface' ) + verify_vpp_interface_not_a_member(interface, config) if not config.get('address_pool', {}).get('translation') and not config.get( 'static', {} diff --git a/src/conf_mode/vpp_sflow.py b/src/conf_mode/vpp_sflow.py index df9d0365f..de592f514 100644 --- a/src/conf_mode/vpp_sflow.py +++ b/src/conf_mode/vpp_sflow.py @@ -19,6 +19,7 @@ from vyos import ConfigError from vyos.config import Config from vyos.vpp.utils import cli_ifaces_list from vyos.vpp.sflow import SFlow +from vyos.vpp.config_verify import verify_vpp_interface_not_a_member def get_config(config=None) -> dict: @@ -70,6 +71,14 @@ def get_config(config=None) -> dict: # Add list of VPP interfaces to the config config.update({'vpp_ifaces': cli_ifaces_list(conf)}) + # VPP interface membership data for member-conflict checks + config['interfaces_vpp'] = conf.get_config_dict( + ['interfaces', 'vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + return config @@ -87,6 +96,7 @@ def verify(config): raise ConfigError( f'{interface} must be a VPP interface for sFlow monitoring' ) + verify_vpp_interface_not_a_member(interface, config) # Verify that system sflow has enable-vpp defined if 'system_sflow' not in config or 'vpp' not in config.get('system_sflow', {}): |
