summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/interface.py138
1 files changed, 103 insertions, 35 deletions
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 270e3db08..692875491 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'
@@ -423,6 +424,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()
@@ -887,13 +894,58 @@ 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")
- 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', 'ip', 'raw', 'vyos_rpfilter', 'handle', handle_search[1]])
+ 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]))
+ elems = dict_search('set.elem', tmp['nftables'][1], []) or []
+ # nft may return a bare value for a single element instead of a list
+ if not isinstance(elems, list):
+ elems = [elems]
+ return elems
+
+ def _get_nft_rule_handles(self, family, table, chain, set_name):
+ """Return handles of all rules referencing set_name."""
+ handles = []
+ tmp = json.loads(self._cmdl(['nft', '-j', 'list', 'chain', family, table, chain]))
+ for entry in tmp.get('nftables', []):
+ rule = entry.get('rule')
+ if not rule:
+ continue
+ if set_name in json.dumps(rule.get('expr', [])):
+ handles.append(str(rule['handle']))
+ return handles
+
+ 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:
+ handles = self._get_nft_rule_handles(family, 'raw', 'vyos_rpfilter', 'rpfilter_strict_ifaces')
+ if handles:
+ rpf_dict['strict_handles'] = handles
+ if loose_needs_rule:
+ handles = self._get_nft_rule_handles(family, 'raw', 'vyos_rpfilter', 'rpfilter_loose_ifaces')
+ if handles:
+ rpf_dict['loose_handles'] = handles
+
+ def _apply_rpf_nft_config(self, rpf_dict):
+ from subprocess import STDOUT
+
+ rpf_template = '/run/nftables-source-validation.conf'
+ render(rpf_template, 'firewall/nftables-source-validation.j2', rpf_dict)
+
+ cmdl(
+ ['nft', '-c', '--file', rpf_template],
+ stderr=STDOUT,
+ raising=ConfigError,
+ message='Source validation nftables check failed',
+ )
+ cmdl(
+ ['nft', '-f', rpf_template],
+ stderr=STDOUT,
+ raising=ConfigError,
+ message='Failed to apply source validation nftables config',
+ )
def set_ipv4_source_validation(self, mode):
"""
@@ -907,23 +959,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):
"""
@@ -937,15 +993,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):
"""
@@ -1948,12 +2016,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