summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-01-10 21:26:07 +0100
committerChristian Breunig <christian@breunig.cc>2026-01-10 21:28:16 +0100
commita2f54d4bf139fa15e47df9422be7dc5edb8be279 (patch)
tree579e01639181582468b37b0875f6a965d81bafb6
parent014ef8bc62a241c2cd278d859dc24a0740e08099 (diff)
downloadvyos-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-xsmoketest/scripts/cli/test_vrf.py45
-rwxr-xr-xsrc/conf_mode/vrf.py26
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',