diff options
| author | Nataliia Solomko <natalirs1985@gmail.com> | 2025-10-07 18:21:06 +0300 |
|---|---|---|
| committer | Nataliia Solomko <natalirs1985@gmail.com> | 2025-10-14 15:41:37 +0300 |
| commit | 888eec975ff765216e4c65ba15ed9176d7dee7ea (patch) | |
| tree | 650d0f2ed8edd656e00bb8c8c4d583b5c9e90213 | |
| parent | fc178aaf00f7a9dc4589567ae3e17cb9718d9ad3 (diff) | |
| download | vyos-1x-888eec975ff765216e4c65ba15ed9176d7dee7ea.tar.gz vyos-1x-888eec975ff765216e4c65ba15ed9176d7dee7ea.zip | |
T7750: VPP: CGNAT commit failure with vpptunX interface
Allow using bond interfaces in CGNAT. Improve interface cleanup to rely
on VPP API queries, as VPP config changes may modify interface indexes
| -rw-r--r-- | data/config-mode-dependencies/vyos-vpp.json | 3 | ||||
| -rw-r--r-- | python/vyos/vpp/nat/det44.py | 22 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 46 | ||||
| -rwxr-xr-x | src/conf_mode/vpp.py | 5 | ||||
| -rw-r--r-- | src/conf_mode/vpp_kernel-interfaces.py | 6 | ||||
| -rw-r--r-- | src/conf_mode/vpp_nat_cgnat.py | 43 |
6 files changed, 102 insertions, 23 deletions
diff --git a/data/config-mode-dependencies/vyos-vpp.json b/data/config-mode-dependencies/vyos-vpp.json index 4729ae95c..eac0f56d6 100644 --- a/data/config-mode-dependencies/vyos-vpp.json +++ b/data/config-mode-dependencies/vyos-vpp.json @@ -65,6 +65,9 @@ "vpp_nat": ["vpp_nat"], "vpp_nat_cgnat": ["vpp_nat_cgnat"], "vpp_kernel_interface": ["vpp_kernel-interfaces"] + }, + "vpp_kernel_interfaces": { + "vpp_nat_cgnat": ["vpp_nat_cgnat"] } } diff --git a/python/vyos/vpp/nat/det44.py b/python/vyos/vpp/nat/det44.py index 70f903086..51ad41dc8 100644 --- a/python/vyos/vpp/nat/det44.py +++ b/python/vyos/vpp/nat/det44.py @@ -44,10 +44,10 @@ class Det44: is_add=True, ) - def delete_det44_interface_outside(self, interface_out): + def delete_det44_interface_outside(self, interface_out_index): """Delete DET44 outside interface""" self.vpp.api.det44_interface_add_del_feature( - sw_if_index=self.vpp.get_sw_if_index(interface_out), + sw_if_index=interface_out_index, is_inside=False, is_add=False, ) @@ -60,10 +60,10 @@ class Det44: is_add=True, ) - def delete_det44_interface_inside(self, interface_in): + def delete_det44_interface_inside(self, interface_in_index): """Delete DET44 inside interface""" self.vpp.api.det44_interface_add_del_feature( - sw_if_index=self.vpp.get_sw_if_index(interface_in), + sw_if_index=interface_in_index, is_inside=True, is_add=False, ) @@ -104,3 +104,17 @@ class Det44: tcp_established=tcp_established, tcp_transitory=tcp_transitory, ) + + def get_det44_interfaces_outside(self): + ifaces_outside = [] + for iface in self.vpp.api.det44_interface_dump(): + if iface.is_outside: + ifaces_outside.append(iface.sw_if_index) + return ifaces_outside + + def get_det44_interfaces_inside(self): + ifaces_inside = [] + for iface in self.vpp.api.det44_interface_dump(): + if iface.is_inside: + ifaces_inside.append(iface.sw_if_index) + return ifaces_inside diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py index 37485bd0d..5a42538c6 100755 --- a/smoketest/scripts/cli/test_vpp.py +++ b/smoketest/scripts/cli/test_vpp.py @@ -1256,7 +1256,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): config = read_file(VPP_CONF) self.assertIn('interface ipip', config) - def test_15_vpp_cgnat(self): + def test_15_1_vpp_cgnat(self): base_cgnat = base_path + ['nat', 'cgnat'] iface_out = 'eth0' iface_inside = 'eth1' @@ -1295,6 +1295,50 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'tcp transitory timeout: {timeout_tcp_trans}sec', out) self.assertIn(f'icmp timeout: {timeout_icmp}sec', out) + def test_15_2_vpp_cgnat_bond_with_vifs(self): + base_cgnat = base_path + ['nat', 'cgnat'] + base_kernel = base_path + ['kernel-interfaces'] + base_bond = base_path + ['interfaces', 'bonding'] + iface_kernel = 'vpptun0' + iface_bond = 'bond0' + vif_1 = '23' + vif_2 = '24' + iface_out = f'{iface_bond}.{vif_1}' + iface_inside = f'{iface_bond}.{vif_2}' + address_1 = '100.64.0.23/32' + address_2 = '192.0.2.1/32' + + self.cli_set(base_bond + [iface_bond, 'kernel-interface', iface_kernel]) + self.cli_set(base_bond + [iface_bond, 'member', 'interface', interface]) + + self.cli_set(base_kernel + [iface_kernel, 'vif', vif_1, 'address', address_1]) + self.cli_set(base_kernel + [iface_kernel, 'vif', vif_2, 'address', address_2]) + + self.cli_set(base_cgnat + ['interface', 'inside', iface_inside]) + self.cli_set(base_cgnat + ['interface', 'outside', iface_out]) + self.cli_set(base_cgnat + ['rule', '100', 'inside-prefix', address_1]) + self.cli_set(base_cgnat + ['rule', '100', 'outside-prefix', address_2]) + self.cli_commit() + + # Check interfaces + _, out = rc_cmd('sudo vppctl show det44 interfaces') + self.assertIn(f'BondEthernet0.{vif_2} in', out) + self.assertIn(f'BondEthernet0.{vif_1} out', out) + + # Change bonding interface configuration + self.cli_set(base_bond + [iface_bond, 'mode', '802.3ad']) + self.cli_commit() + + # Check interfaces + _, out = rc_cmd('sudo vppctl show det44 interfaces') + self.assertIn(f'BondEthernet0.{vif_2} in', out) + self.assertIn(f'BondEthernet0.{vif_1} out', out) + + # Verify only expected interfaces are shown: + # header + inside + outside = 3 lines total + lines = out.split('\n') + self.assertTrue(len(lines) == 3) + def test_16_vpp_nat(self): base_nat = base_path + ['nat44'] base_nat_settings = base_path + ['settings', 'nat44'] diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index 81502c0b4..2d8c17e4f 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -373,6 +373,11 @@ def get_config(config=None): eth_ifaces_persist[iface]['bus_id'] = control_host.get_bus_name(iface) eth_ifaces_persist[iface]['dev_id'] = control_host.get_dev_id(iface) + # kernel-interfaces dependency + if effective_config.get('kernel_interfaces'): + for iface in config.get('kernel_interfaces', {}): + set_dependents('vpp_kernel_interface', conf, iface) + # NAT dependency if conf.exists(['vpp', 'nat44']): set_dependents('vpp_nat', conf) diff --git a/src/conf_mode/vpp_kernel-interfaces.py b/src/conf_mode/vpp_kernel-interfaces.py index 85706094e..d10449eec 100644 --- a/src/conf_mode/vpp_kernel-interfaces.py +++ b/src/conf_mode/vpp_kernel-interfaces.py @@ -19,6 +19,7 @@ import os from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents from vyos.configdict import leaf_node_changed from vyos.configdict import node_changed from vyos import ConfigError @@ -79,6 +80,9 @@ def get_config(config=None) -> dict: if vlans_removed: config['vlans_removed'] = vlans_removed + if conf.exists(['vpp', 'nat', 'cgnat']): + set_dependents('vpp_nat_cgnat', conf) + config['ifname'] = ifname return config @@ -185,6 +189,8 @@ def apply(config): lcp_name = vpp_control.lcp_pair_find(kernel_name=ifname).get('vpp_name_kernel') vpp_control.iface_rxmode(lcp_name, rx_mode) + call_dependents() + return None diff --git a/src/conf_mode/vpp_nat_cgnat.py b/src/conf_mode/vpp_nat_cgnat.py index be2386ba2..56d11b5a1 100644 --- a/src/conf_mode/vpp_nat_cgnat.py +++ b/src/conf_mode/vpp_nat_cgnat.py @@ -23,6 +23,15 @@ from vyos.configdiff import Diff from vyos.vpp.utils import cli_ifaces_list from vyos.vpp.nat.det44 import Det44 +from vyos.vpp.control_vpp import VPPControl + + +def _vpp_iface_name_transform(iface_name): + vpp_iface_name = iface_name + if vpp_iface_name.startswith('bond'): + # interface name in VPP is BondEthernetX + vpp_iface_name = vpp_iface_name.replace('bond', 'BondEthernet') + return vpp_iface_name def get_config(config=None) -> dict: @@ -120,16 +129,14 @@ def verify(config): f'Please choose a side for: {", ".join(conflict_ifaces)} ' ) - for interface in config['interface']['inside']: - if interface not in config['vpp_ifaces']: - raise ConfigError( - f'{interface} must be a VPP interface for inside CGNAT interface' - ) - for interface in config['interface']['outside']: - if interface not in config['vpp_ifaces']: - raise ConfigError( - f'{interface} must be a VPP interface for outside CGNAT interface' - ) + vpp = VPPControl() + for direction in ['inside', 'outside']: + for interface in config['interface'][direction]: + vpp_iface_name = _vpp_iface_name_transform(interface) + if vpp.get_sw_if_index(vpp_iface_name) is None: + raise ConfigError( + f'{interface} must be a VPP interface for {direction} CGNAT interface' + ) required_keys = {'outside_prefix', 'inside_prefix'} for rule in config['rule']: @@ -158,13 +165,11 @@ def apply(config): if 'effective' in config: remove_config = config.get('effective') # Delete inside interfaces - for interface in remove_config['interface']['inside']: - if interface not in config.get('interface', {}).get('inside', []): - cgnat.delete_det44_interface_inside(interface) + for interface in cgnat.get_det44_interfaces_inside(): + cgnat.delete_det44_interface_inside(interface) # Delete outside interfaces - for interface in remove_config['interface']['outside']: - if interface not in config.get('interface', {}).get('outside', []): - cgnat.delete_det44_interface_outside(interface) + for interface in cgnat.get_det44_interfaces_outside(): + cgnat.delete_det44_interface_outside(interface) # Delete CGNAT rules for rule in config['changed_rules']: if rule in remove_config.get('rule', {}): @@ -182,10 +187,12 @@ def apply(config): cgnat.enable_det44_plugin() # Add inside interfaces for interface in config['interface']['inside']: - cgnat.add_det44_interface_inside(interface) + vpp_iface_name = _vpp_iface_name_transform(interface) + cgnat.add_det44_interface_inside(vpp_iface_name) # Add outside interfaces for interface in config['interface']['outside']: - cgnat.add_det44_interface_outside(interface) + vpp_iface_name = _vpp_iface_name_transform(interface) + cgnat.add_det44_interface_outside(vpp_iface_name) # Add CGNAT rules for rule in config['changed_rules']: if rule in config.get('rule', {}): |
