diff options
| author | l0crian1 <ryan.claridge13@gmail.com> | 2026-08-01 17:56:37 -0400 |
|---|---|---|
| committer | sarthurdev <965089+sarthurdev@users.noreply.github.com> | 2026-09-24 17:22:59 +0200 |
| commit | 3aed4c9c216375ff4f5ad3b0a000b6fd30836846 (patch) | |
| tree | ce7a249bb6faafe7d0704e734963f6e7fb977d83 /python | |
| parent | ed218c254d964775ddf24158ac6520972d575faf (diff) | |
| download | vyos-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.py | 119 |
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 |
