From 9199b75d75ceea3b7d49f0e3d71a19175b7b1326 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:59:10 +0200 Subject: [PATCH] firewall: T3509: Add support for IPv6 return path filtering --- data/templates/firewall/nftables.j2 | 23 ++++++++++++++++ interface-definitions/firewall.xml.in | 26 ++----------------- .../include/firewall/source-validation.xml.i | 26 +++++++++++++++++++ smoketest/scripts/cli/test_firewall.py | 25 ++++++++++++++++++ src/conf_mode/firewall.py | 11 ++++---- 5 files changed, 82 insertions(+), 29 deletions(-) create mode 100644 interface-definitions/include/firewall/source-validation.xml.i diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 9d609f73fd..e5d89bfbae 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -3,6 +3,29 @@ {% import 'firewall/nftables-defines.j2' as group_tmpl %} {% import 'firewall/nftables-zone.j2' as zone_tmpl %} +{% if first_install is not vyos_defined %} +delete table inet vyos_rpfilter +{% endif %} +table inet vyos_rpfilter { + chain PREROUTING { + type filter hook prerouting priority -300; policy accept; +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.source_validation is vyos_defined('loose') %} + iifname {{ ifname }} fib saddr oif 0 counter drop +{% elif ifconf.source_validation is vyos_defined('strict') %} + iifname {{ ifname }} fib saddr . iif oif 0 counter drop +{% endif %} +{% endfor %} +{% endif %} +{% if source_validation is vyos_defined('loose') %} + fib saddr oif 0 counter drop +{% elif source_validation is vyos_defined('strict') %} + fib saddr . iif oif 0 counter drop +{% endif %} + } +} + {% if first_install is not vyos_defined %} delete table ip vyos_filter {% endif %} diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 773e86f003..2050a090b2 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -346,6 +346,7 @@ #include + #include @@ -676,30 +677,7 @@ enable - - - Policy for source validation by reversed path, as specified in RFC3704 - - strict loose disable - - - strict - Enable Strict Reverse Path Forwarding as defined in RFC3704 - - - loose - Enable Loose Reverse Path Forwarding as defined in RFC3704 - - - disable - No source validation - - - (strict|loose|disable) - - - disable - + #include Global firewall state-policy diff --git a/interface-definitions/include/firewall/source-validation.xml.i b/interface-definitions/include/firewall/source-validation.xml.i new file mode 100644 index 0000000000..e79868f458 --- /dev/null +++ b/interface-definitions/include/firewall/source-validation.xml.i @@ -0,0 +1,26 @@ + + + + Policy for source validation by reversed path, as specified in RFC3704 + + strict loose disable + + + strict + Enable Strict Reverse Path Forwarding as defined in RFC3704 + + + loose + Enable Loose Reverse Path Forwarding as defined in RFC3704 + + + disable + No source validation + + + (strict|loose|disable) + + + disable + + \ No newline at end of file diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 821925bcd0..78eeed799f 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -404,6 +404,31 @@ def test_ipv4_state_and_status_rules(self): self.verify_nftables(nftables_search, 'ip vyos_filter') + def test_source_validation(self): + # Strict + self.cli_set(['firewall', 'interface', 'eth0', 'source-validation', 'strict']) + self.cli_set(['firewall', 'source-validation', 'strict']) + self.cli_commit() + + nftables_strict_search = [ + ['iifname "eth0"', 'fib saddr . iif oif 0', 'drop'], + ['fib saddr . iif oif 0', 'drop'] + ] + + self.verify_nftables(nftables_strict_search, 'inet vyos_rpfilter') + + # Loose + self.cli_set(['firewall', 'interface', 'eth0', 'source-validation', 'loose']) + self.cli_set(['firewall', 'source-validation', 'loose']) + self.cli_commit() + + nftables_loose_search = [ + ['iifname "eth0"', 'fib saddr oif 0', 'drop'], + ['fib saddr oif 0', 'drop'] + ] + + self.verify_nftables(nftables_loose_search, 'inet vyos_rpfilter') + def test_sysfs(self): for name, conf in sysfs_config.items(): paths = glob(conf['sysfs']) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index cbd9cbe90f..b53636f53c 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -58,7 +58,6 @@ 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians'}, 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects'}, 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects'}, - 'source_validation': {'sysfs': '/proc/sys/net/ipv4/conf/*/rp_filter', 'disable': '0', 'strict': '1', 'loose': '2'}, 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies'}, 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'} } @@ -134,13 +133,10 @@ def get_config(config=None): # XXX: T2665: we currently have no nice way for defaults under tag # nodes, thus we load the defaults "by hand" default_values = defaults(base) - for tmp in ['name', 'ipv6_name']: + for tmp in ['name', 'ipv6_name', 'interface', 'zone']: if tmp in default_values: del default_values[tmp] - if 'zone' in default_values: - del default_values['zone'] - firewall = dict_merge(default_values, firewall) # Merge in defaults for IPv4 ruleset @@ -157,6 +153,11 @@ def get_config(config=None): firewall['ipv6_name'][ipv6_name] = dict_merge(default_values, firewall['ipv6_name'][ipv6_name]) + if 'interface' in firewall: + default_values = defaults(base + ['interface']) + for ifname in firewall['interface']: + firewall['interface'][ifname] = dict_merge(default_values, firewall['interface'][ifname]) + if 'zone' in firewall: default_values = defaults(base + ['zone']) for zone in firewall['zone']: