diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-01-10 21:26:07 +0100 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-01-10 21:28:16 +0100 |
| commit | a2f54d4bf139fa15e47df9422be7dc5edb8be279 (patch) | |
| tree | 579e01639181582468b37b0875f6a965d81bafb6 | |
| parent | 014ef8bc62a241c2cd278d859dc24a0740e08099 (diff) | |
| download | vyos-1x-a2f54d4bf139fa15e47df9422be7dc5edb8be279.tar.gz vyos-1x-a2f54d4bf139fa15e47df9422be7dc5edb8be279.zip | |
vrf: T8169: prevent deletion if instance referenced in policy based routing
Check if the VRF which is about to be removed is referenced in any PBR rule,
where any is one or more of route, route6, local-route or local-route6.
| -rwxr-xr-x | smoketest/scripts/cli/test_vrf.py | 45 | ||||
| -rwxr-xr-x | src/conf_mode/vrf.py | 26 |
2 files changed, 67 insertions, 4 deletions
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 468b859c3..9349f43c6 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -684,6 +684,51 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.cli_delete(['nat']) + def test_vrf_policy_based_route(self): + vrf_name = 'test-pbr_123' + + self.cli_set(base_path + ['name', vrf_name, 'table', '17563']) + + policy_path = ['policy', 'route', 'pbr_smoke4', 'rule', '10'] + self.cli_set(policy_path + ['action', 'accept']) + self.cli_set(policy_path + ['set', 'vrf', vrf_name]) + self.cli_set(policy_path + ['source', 'address', '192.0.2.1/32']) + + policy6_path = ['policy', 'route6', 'pbr_smoke6', 'rule', '10'] + self.cli_set(policy6_path + ['action', 'accept']) + self.cli_set(policy6_path + ['set', 'vrf', vrf_name]) + self.cli_set(policy6_path + ['source', 'address', '2001:db8::/56']) + + local_policy_path = ['policy', 'local-route', 'rule', '10'] + self.cli_set(local_policy_path + ['set', 'vrf', vrf_name]) + self.cli_set(local_policy_path + ['source', 'address', '192.0.2.1/32']) + + local_policy6_path = ['policy', 'local-route6', 'rule', '10'] + self.cli_set(local_policy6_path + ['set', 'vrf', vrf_name]) + self.cli_set(local_policy6_path + ['source', 'address', '2001:db8::/56']) + + self.cli_commit() + + self.cli_delete(base_path) + # check validate() - VRF referenced in policy based routing + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(['policy', 'route']) + # check validate() - VRF referenced in policy based routing + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(['policy', 'route6']) + # check validate() - VRF referenced in policy based routing + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(['policy', 'local-route']) + # check validate() - VRF referenced in policy based routing + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(['policy', 'local-route6']) + + self.cli_commit() + def test_dhcp_single_pool(self): # Prepare the vrf and options table = '100' diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 1eacba112..1f2e43381 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -29,6 +29,8 @@ from vyos.frrender import get_frrender_dict from vyos.ifconfig import Interface from vyos.template import render from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_set_nested +from vyos.utils.dict import dict_search_recursive from vyos.utils.network import get_vrf_tableid from vyos.utils.network import get_vrf_members from vyos.utils.network import interface_exists @@ -118,6 +120,17 @@ def get_config(config=None): vrf = conf.get_config_dict(base, key_mangling=('-', '_'), no_tag_node_value_mangle=True, get_first_key=True) + # Policy based routing supports referencing VRFs in it's rules - we need to + # prevent VRF deletion if VRF is used in a PBR rule + for policy_type in ['local-route', 'local-route6', 'route', 'route6']: + tmp = conf.get_config_dict(['policy', policy_type], + key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True) + if tmp: + policy_type = policy_type.replace('-', '_') + dict_set_nested(f'policy.{policy_type}', tmp, vrf) + # determine which VRF has been removed for name in node_changed(conf, base + ['name']): if 'vrf_remove' not in vrf: @@ -130,6 +143,10 @@ def get_config(config=None): # get VRF bound routing instances routes = vrf_routing(conf, name) if routes: vrf['vrf_remove'][name]['route'] = routes + # get VRF bound policy routes + if 'policy' in vrf: + for key, _ in dict_search_recursive(vrf['policy'], 'vrf'): + if key == name: vrf['vrf_remove'][name]['policy'] = {} if 'name' in vrf: vrf['conntrack'] = conntrack_required(conf) @@ -143,12 +160,13 @@ def verify(vrf): # ensure VRF is not assigned to any interface if 'vrf_remove' in vrf: for name, config in vrf['vrf_remove'].items(): + err = f'Can not remove VRF "{name}",' if 'interface' in config: - raise ConfigError(f'Can not remove VRF "{name}", it still has '\ - f'member interfaces!') + raise ConfigError(f'{err} it still has member interfaces!') if 'route' in config: - raise ConfigError(f'Can not remove VRF "{name}", it still has '\ - f'static routes installed!') + raise ConfigError(f'{err} it still has static routes installed!') + if 'policy' in config: + raise ConfigError(f'{err} it still has policy routes!') if 'name' in vrf: reserved_names = ['add', 'all', 'broadcast', 'default', 'delete', 'dev', |
