summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--python/vyos/vpp/control_vpp.py106
-rw-r--r--python/vyos/vpp/utils.py2
-rwxr-xr-xsmoketest/scripts/cli/test_vpp.py88
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py29
4 files changed, 224 insertions, 1 deletions
diff --git a/python/vyos/vpp/control_vpp.py b/python/vyos/vpp/control_vpp.py
index 1ad098b71..0746282de 100644
--- a/python/vyos/vpp/control_vpp.py
+++ b/python/vyos/vpp/control_vpp.py
@@ -26,6 +26,8 @@ from typing import TypeVar, ParamSpec, Literal
from vpp_papi import VPPApiClient
from vpp_papi import VPPIOError, VPPValueError
+from vyos.vpp.utils import vpp_ip_addresses_by_index
+
# define types for static type checkers
AnyType = TypeVar('AnyType')
AnyParam = ParamSpec('AnyParam')
@@ -301,6 +303,110 @@ class VPPControl:
"""
return self.__vpp_api_client.api.lcp_nl_resync()
+ @_Decorators.api_call
+ def get_interface_ip_table(self, ifname: str, is_ipv6: bool = False) -> int | None:
+ """Return the IP FIB table ID assigned to an interface.
+
+ Args:
+ ifname (str): Interface name in VPP.
+ is_ipv6 (bool, optional): Return IPv6 table ID if True. Defaults to False.
+
+ Returns:
+ int | None: FIB table ID or None if the interface is not found.
+ """
+ iface_index = self.get_sw_if_index(ifname)
+ if iface_index is None:
+ return None
+
+ response = self.__vpp_api_client.api.sw_interface_get_table(
+ sw_if_index=iface_index,
+ is_ipv6=is_ipv6,
+ )
+ if response.retval != 0:
+ return None
+
+ return response.vrf_id
+
+ @_Decorators.api_call
+ def set_interface_ip_table(
+ self, ifname: str, table_id: int, is_ipv6: bool = False
+ ) -> None:
+ """Assign an interface to an IP FIB table.
+
+ Args:
+ ifname (str): Interface name in VPP.
+ table_id (int): FIB table ID.
+ is_ipv6 (bool, optional): Set IPv6 table if True. Defaults to False.
+ """
+ iface_index = self.get_sw_if_index(ifname)
+ if iface_index is None:
+ return None
+
+ self.__vpp_api_client.api.sw_interface_set_table(
+ sw_if_index=iface_index,
+ is_ipv6=is_ipv6,
+ vrf_id=table_id,
+ )
+
+ @_Decorators.api_call
+ def add_del_interface_ip_address(
+ self, ifname: str, address: str, is_add: bool = True
+ ) -> None:
+ """Add or delete an interface IP address in VPP.
+
+ Args:
+ ifname (str): Interface name in VPP.
+ address (str): Interface prefix, for example "192.0.2.1/24".
+ is_add (bool, optional): Add address if True, delete if False.
+ Defaults to True.
+ """
+ iface_index = self.get_sw_if_index(ifname)
+ if iface_index is None:
+ return None
+
+ self.__vpp_api_client.api.sw_interface_add_del_address(
+ sw_if_index=iface_index,
+ is_add=is_add,
+ del_all=False,
+ prefix=address,
+ )
+
+ def move_interface_to_ip_table_preserve_addresses(
+ self, ifname: str, table_id: int
+ ) -> None:
+ """Move an interface to an IP FIB table while preserving its addresses.
+
+ VPP keeps connected routes in the interface IP table that was active when
+ addresses were added. Re-adding the same addresses after the table move
+ recreates connected routes in the target table.
+
+ Args:
+ ifname (str): Interface name in VPP.
+ table_id (int): Target FIB table ID.
+ """
+
+ iface_index = self.get_sw_if_index(ifname)
+ if iface_index is None:
+ return None
+
+ for is_ipv6 in [False, True]:
+ current_table = self.get_interface_ip_table(ifname, is_ipv6=is_ipv6)
+ if current_table is None or current_table == table_id:
+ continue
+
+ addresses = vpp_ip_addresses_by_index(
+ self.__vpp_api_client.api,
+ iface_index,
+ is_ipv6=is_ipv6,
+ )
+ for address in addresses:
+ self.add_del_interface_ip_address(ifname, address, is_add=False)
+
+ self.set_interface_ip_table(ifname, table_id, is_ipv6=is_ipv6)
+
+ for address in addresses:
+ self.add_del_interface_ip_address(ifname, address)
+
@_Decorators.check_retval
@_Decorators.api_call
def iface_rxmode(self, iface_name: str, rx_mode: str) -> None:
diff --git a/python/vyos/vpp/utils.py b/python/vyos/vpp/utils.py
index 10940b5a4..0badfdb19 100644
--- a/python/vyos/vpp/utils.py
+++ b/python/vyos/vpp/utils.py
@@ -194,7 +194,7 @@ def vpp_ip_addresses_by_index(vpp_api, index: int, is_ipv6: bool = False) -> lis
ip_address_dump = vpp_api.ip_address_dump(sw_if_index=index, is_ipv6=is_ipv6)
while ip_address_dump:
ip_address_details = ip_address_dump.pop()
- ip_addresses_list.append(str(ip_address_details._asdict().get('prefix')))
+ ip_addresses_list.append(str(ip_address_details.prefix))
return ip_addresses_list
diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py
index a506ad4f0..7c52dd10c 100755
--- a/smoketest/scripts/cli/test_vpp.py
+++ b/smoketest/scripts/cli/test_vpp.py
@@ -35,6 +35,7 @@ from vyos.utils.system import sysctl_read
from vyos.utils.network import interface_exists
from vyos.system import image
from vyos.vpp import VPPControl
+from vyos.vpp.utils import vpp_ip_addresses_by_index
from vyos.vpp.utils import vpp_iface_name_transform
from vyos.vpp.config_resource_checks.resource_defaults import default_resource_map
@@ -1513,6 +1514,93 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
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}'
+ address = '100.100.100.1/24'
+ fib_route = '100.100.100.1/32'
+ mgmt_vrf = 'mgmt'
+ test_vrf = 'test1'
+
+ def assert_vpp_lcp_table(table_id):
+ vpp = VPPControl()
+ subif_index = vpp.get_sw_if_index(subif)
+ self.assertIsNotNone(subif_index)
+ self.assertEqual(vpp.get_interface_ip_table(subif), table_id)
+ self.assertIn(address, vpp_ip_addresses_by_index(vpp.api, subif_index))
+
+ def assert_fib_route(table_id, expected=True):
+ _, out = rc_cmd(f'sudo vppctl show ip fib table {table_id}')
+ if expected:
+ self.assertIn(fib_route, out)
+ else:
+ self.assertNotIn(fib_route, out)
+
+ self.cli_set(['vrf', 'name', mgmt_vrf, 'table', '1000'])
+ self.cli_set(
+ ['interfaces', 'ethernet', interface, 'vif', vlan, 'address', address]
+ )
+ self.cli_set(
+ ['interfaces', 'ethernet', interface, 'vif', vlan, 'vrf', mgmt_vrf]
+ )
+ self.cli_commit()
+
+ assert_vpp_lcp_table(1000)
+ assert_fib_route(0, expected=False)
+ assert_fib_route(1000)
+
+ self.cli_delete(['interfaces', 'ethernet', interface, 'vif', vlan, 'address'])
+ self.cli_commit()
+
+ vpp = VPPControl()
+ subif_index = vpp.get_sw_if_index(subif)
+ self.assertIsNotNone(subif_index)
+ self.assertEqual(vpp.get_interface_ip_table(subif), 1000)
+ self.assertNotIn(address, vpp_ip_addresses_by_index(vpp.api, subif_index))
+ assert_fib_route(0, expected=False)
+ assert_fib_route(1000, expected=False)
+
+ self.cli_set(
+ ['interfaces', 'ethernet', interface, 'vif', vlan, 'address', address]
+ )
+ self.cli_commit()
+
+ assert_vpp_lcp_table(1000)
+ assert_fib_route(0, expected=False)
+ assert_fib_route(1000)
+
+ self.cli_delete(['interfaces', 'ethernet', interface, 'vif', vlan, 'vrf'])
+ self.cli_commit()
+
+ assert_vpp_lcp_table(0)
+ assert_fib_route(0)
+ assert_fib_route(1000, expected=False)
+
+ self.cli_set(
+ ['interfaces', 'ethernet', interface, 'vif', vlan, 'vrf', mgmt_vrf]
+ )
+ self.cli_commit()
+
+ assert_vpp_lcp_table(1000)
+ assert_fib_route(0, expected=False)
+ assert_fib_route(1000)
+
+ self.cli_set(['vrf', 'name', test_vrf, 'table', '2000'])
+ self.cli_set(
+ ['interfaces', 'ethernet', interface, 'vif', vlan, 'vrf', test_vrf]
+ )
+ self.cli_commit()
+
+ assert_vpp_lcp_table(2000)
+ assert_fib_route(0, expected=False)
+ assert_fib_route(1000, expected=False)
+ assert_fib_route(2000)
+
+ self.cli_delete(['interfaces', 'ethernet', interface, 'vif', vlan])
+ self.cli_delete(['vrf', 'name', test_vrf])
+ self.cli_delete(['vrf', 'name', mgmt_vrf])
+ self.cli_commit()
+
if __name__ == '__main__':
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index 10b778eea..3b8453780 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -45,6 +45,7 @@ from vyos.utils.dict import dict_search
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.network import get_vrf_tableid
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
@@ -473,8 +474,36 @@ def apply(ethernet):
mtu = e.get_mtu()
vpp_api.set_iface_mtu(lcp_name, mtu)
+ sync_vpp_lcp_vrf_tables(ethernet, vpp_api)
+
return None
+
+def sync_vpp_lcp_vrf_tables(ethernet: dict, vpp_api: VPPControl) -> None:
+ """Synchronize VyOS VRF assignment to VPP IP FIB table binding."""
+
+ def iter_vpp_lcp_vrf_targets() -> list[tuple[str, int]]:
+ interfaces = [ethernet['ifname']]
+ for vif in ethernet.get('vif', {}).values():
+ interfaces.append(vif['ifname'])
+ for vif_s in ethernet.get('vif_s', {}).values():
+ interfaces.append(vif_s['ifname'])
+ for vif_c in vif_s.get('vif_c', {}).values():
+ interfaces.append(vif_c['ifname'])
+ return [(iface, get_vrf_tableid(iface) or 0) for iface in interfaces]
+
+ lcp_vpp_ifaces = {
+ pair.get('vpp_name_hw')
+ for pair in vpp_api.lcp_pairs_list()
+ if pair.get('vpp_name_hw')
+ }
+
+ for ifname, table_id in iter_vpp_lcp_vrf_targets():
+ if ifname not in lcp_vpp_ifaces:
+ continue
+
+ vpp_api.move_interface_to_ip_table_preserve_addresses(ifname, table_id)
+
if __name__ == '__main__':
try:
c = get_config()