summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorl0crian1 <ryan.claridge13@gmail.com>2026-08-01 17:56:37 -0400
committersarthurdev <965089+sarthurdev@users.noreply.github.com>2026-09-24 17:22:59 +0200
commit3aed4c9c216375ff4f5ad3b0a000b6fd30836846 (patch)
treece7a249bb6faafe7d0704e734963f6e7fb977d83 /python
parented218c254d964775ddf24158ac6520972d575faf (diff)
downloadvyos-1x-3aed4c9c216375ff4f5ad3b0a000b6fd30836846.tar.gz
vyos-1x-3aed4c9c216375ff4f5ad3b0a000b6fd30836846.zip
T9152: Refactor source-validation to use vmaps
- Refactor source-validation to use vmaps - Update broken source-validation tests - Fix source-validation failing to remove rules when interface is removed (T9153)
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/interface.py119
1 files changed, 86 insertions, 33 deletions
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 73f7544dd..b58eccd43 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -58,6 +58,7 @@ from vyos.ifconfig.control import Control
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
from vyos.ifconfig import Section
+from vyos import ConfigError
link_local_prefix = 'fe80::/64'
@@ -418,6 +419,12 @@ class Interface(Control):
# This will internally stop DHCP(v6) if running
self.flush_addrs()
+ # Remove reverse-path filter entries before the interface is deleted.
+ # Map keys are interface indexes resolved from ifname, so this must
+ # happen while the interface still exists.
+ self.set_ipv4_source_validation('disable')
+ self.set_ipv6_source_validation('disable')
+
# remove interface from conntrack VRF interface map
self._del_interface_from_ct_iface_map()
@@ -882,13 +889,43 @@ class Interface(Control):
return None
return self.set_interface('ipv4_directed_broadcast', forwarding)
- def _cleanup_ipv4_source_validation_rules(self, ifname):
- results = self._cmdl(['nft', '-a', 'list', 'chain', 'ip', 'raw', 'vyos_rpfilter']).split("\n")
+ def _get_nft_set_elements(self, family, table, set_name):
+ """Return elements of an nftables set, or [] if none are present."""
+ tmp = json.loads(self._cmdl(['nft', '-j', 'list', 'set', family, table, set_name]))
+ return dict_search('set.elem', tmp['nftables'][1], [])
+
+ def _get_nft_rule_handle(self, family, table, chain, match):
+ """Return handle of first rule matching match, or None if not found."""
+ results = self._cmdl(['nft', '-a', 'list', 'chain', family, table, chain]).split('\n')
for line in results:
- if f'iifname "{ifname}"' in line:
- handle_search = re.search('handle (\d+)', line)
+ if match in line:
+ handle_search = re.search(r'handle (\d+)', line)
if handle_search:
- self._cmdl(['nft', 'delete', 'rule', 'ip', 'raw', 'vyos_rpfilter', 'handle', handle_search[1]])
+ return handle_search[1]
+ return None
+
+ def _get_rpf_interface_rules(self, family):
+ return self._get_nft_set_elements(family, 'raw', 'rpfilter_strict_ifaces'), self._get_nft_set_elements(family, 'raw', 'rpfilter_loose_ifaces')
+
+ def _set_rpf_rule_deletes(self, family, rpf_dict, strict_needs_rule, loose_needs_rule):
+ if strict_needs_rule:
+ handle = self._get_nft_rule_handle(family, 'raw', 'vyos_rpfilter', '@rpfilter_strict_ifaces')
+ if handle:
+ rpf_dict['strict_delete'] = True
+ rpf_dict['strict_handle'] = handle
+ if loose_needs_rule:
+ handle = self._get_nft_rule_handle(family, 'raw', 'vyos_rpfilter', '@rpfilter_loose_ifaces')
+ if handle:
+ rpf_dict['loose_delete'] = True
+ rpf_dict['loose_handle'] = handle
+
+ def _apply_rpf_nft_config(self, rpf_dict):
+ rpf_template = '/run/nftables-source-validation.conf'
+ render(rpf_template, 'firewall/nftables-source-validation.j2', rpf_dict)
+ tmp = run(['nft', '-c', '-f', rpf_template])
+ if tmp > 0:
+ raise ConfigError('Source validation configuration file errors encountered!')
+ run(['nft', '-f', rpf_template])
def set_ipv4_source_validation(self, mode):
"""
@@ -902,23 +939,27 @@ class Interface(Control):
if 'netns' in self.config:
return None
- self._cleanup_ipv4_source_validation_rules(self.ifname)
- nft_prefix = ['nft', 'insert', 'rule', 'ip', 'raw', 'vyos_rpfilter',
- 'iifname', f'"{self.ifname}"']
- if mode in ['strict', 'loose']:
- self._cmdl(nft_prefix + ['counter', 'return'])
- if mode == 'strict':
- self._cmdl(nft_prefix + ['fib', 'saddr', '.', 'iif', 'oif', '0', 'counter', 'drop'])
- elif mode == 'loose':
- self._cmdl(nft_prefix + ['fib', 'saddr', 'oif', '0', 'counter', 'drop'])
-
- def _cleanup_ipv6_source_validation_rules(self, ifname):
- results = self._cmdl(['nft', '-a', 'list', 'chain', 'ip6', 'raw', 'vyos_rpfilter']).split("\n")
- for line in results:
- if f'iifname "{ifname}"' in line:
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- self._cmdl(['nft', 'delete', 'rule', 'ip6', 'raw', 'vyos_rpfilter', 'handle', handle_search[1]])
+ strict_ifaces, loose_ifaces = self._get_rpf_interface_rules('ip')
+
+ rpf_dict = {
+ 'family': 'ip',
+ 'iface': self.ifname,
+ 'mode': mode
+ }
+
+ strict_needs_rule = (len(strict_ifaces) == 0 or
+ (len(strict_ifaces) == 1 and self.ifname in strict_ifaces))
+ loose_needs_rule = (len(loose_ifaces) == 0 or
+ (len(loose_ifaces) == 1 and self.ifname in loose_ifaces))
+
+ self._set_rpf_rule_deletes('ip', rpf_dict, strict_needs_rule, loose_needs_rule)
+
+ if mode == 'strict' and strict_needs_rule:
+ rpf_dict['strict_add'] = True
+ if mode == 'loose' and loose_needs_rule:
+ rpf_dict['loose_add'] = True
+
+ self._apply_rpf_nft_config(rpf_dict)
def set_ipv6_source_validation(self, mode):
"""
@@ -932,15 +973,27 @@ class Interface(Control):
if 'netns' in self.config:
return None
- self._cleanup_ipv6_source_validation_rules(self.ifname)
- nft_prefix = ['nft', 'insert', 'rule', 'ip6', 'raw', 'vyos_rpfilter',
- 'iifname', f'"{self.ifname}"']
- if mode in ['strict', 'loose']:
- self._cmdl(nft_prefix + ['counter', 'return'])
- if mode == 'strict':
- self._cmdl(nft_prefix + ['fib', 'saddr', '.', 'iif', 'oif', '0', 'counter', 'drop'])
- elif mode == 'loose':
- self._cmdl(nft_prefix + ['fib', 'saddr', 'oif', '0', 'counter', 'drop'])
+ strict_ifaces, loose_ifaces = self._get_rpf_interface_rules('ip6')
+
+ rpf_dict = {
+ 'family': 'ip6',
+ 'iface': self.ifname,
+ 'mode': mode
+ }
+
+ strict_needs_rule = (len(strict_ifaces) == 0 or
+ (len(strict_ifaces) == 1 and self.ifname in strict_ifaces))
+ loose_needs_rule = (len(loose_ifaces) == 0 or
+ (len(loose_ifaces) == 1 and self.ifname in loose_ifaces))
+
+ self._set_rpf_rule_deletes('ip6', rpf_dict, strict_needs_rule, loose_needs_rule)
+
+ if mode == 'strict' and strict_needs_rule:
+ rpf_dict['strict_add'] = True
+ if mode == 'loose' and loose_needs_rule:
+ rpf_dict['loose_add'] = True
+
+ self._apply_rpf_nft_config(rpf_dict)
def set_ipv6_accept_ra(self, accept_ra):
"""
@@ -1942,12 +1995,12 @@ class Interface(Control):
# IPv4 source-validation
tmp = dict_search('ip.source_validation', config)
- value = tmp if (tmp != None) else '0'
+ value = tmp if (tmp != None) else 'disable'
self.set_ipv4_source_validation(value)
# IPv6 source-validation
tmp = dict_search('ipv6.source_validation', config)
- value = tmp if (tmp != None) else '0'
+ value = tmp if (tmp != None) else 'disable'
self.set_ipv6_source_validation(value)
# MTU - Maximum Transfer Unit has a default value. It must ALWAYS be set