summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2023-08-26 14:59:10 +0200
committerGitHub <noreply@github.com>2023-08-26 14:59:10 +0200
commit75aa90cf2b234a34565d165697196ac9a304bb66 (patch)
tree8a1c07e17cec0333b71ed32c12f403953a762afe
parent3144b67f1b04e88f5ba928db2eee6f198be04a3a (diff)
parent2509a1ab84cdb6d9389b547f93b0904cf329e78a (diff)
downloadvyos-1x-75aa90cf2b234a34565d165697196ac9a304bb66.tar.gz
vyos-1x-75aa90cf2b234a34565d165697196ac9a304bb66.zip
Merge pull request #2163 from sarthurdev/firewall_rpfilter
firewall: T3509: Add support for IPv6 reverse path filtering
-rw-r--r--data/templates/firewall/nftables-zone.j278
-rw-r--r--data/templates/firewall/nftables.j214
-rw-r--r--data/vyos-firewall-init.conf4
-rw-r--r--interface-definitions/include/interface/ipv6-options.xml.i1
-rw-r--r--python/vyos/ifconfig/interface.py29
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py12
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py21
-rwxr-xr-xsrc/conf_mode/firewall.py1
8 files changed, 81 insertions, 79 deletions
diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2
deleted file mode 100644
index 17ef5101d..000000000
--- a/data/templates/firewall/nftables-zone.j2
+++ /dev/null
@@ -1,78 +0,0 @@
-
-{% macro zone_chains(zone, state_policy=False, ipv6=False) %}
-{% set fw_name = 'ipv6_name' if ipv6 else 'name' %}
-{% set suffix = '6' if ipv6 else '' %}
- chain VYOS_ZONE_FORWARD {
- type filter hook forward priority 1; policy accept;
-{% if state_policy %}
- jump VYOS_STATE_POLICY{{ suffix }}
-{% endif %}
-{% for zone_name, zone_conf in zone.items() %}
-{% if 'local_zone' not in zone_conf %}
- oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
-{% endif %}
-{% endfor %}
- }
- chain VYOS_ZONE_LOCAL {
- type filter hook input priority 1; policy accept;
-{% if state_policy %}
- jump VYOS_STATE_POLICY{{ suffix }}
-{% endif %}
-{% for zone_name, zone_conf in zone.items() %}
-{% if 'local_zone' in zone_conf %}
- counter jump VZONE_{{ zone_name }}_IN
-{% endif %}
-{% endfor %}
- }
- chain VYOS_ZONE_OUTPUT {
- type filter hook output priority 1; policy accept;
-{% if state_policy %}
- jump VYOS_STATE_POLICY{{ suffix }}
-{% endif %}
-{% for zone_name, zone_conf in zone.items() %}
-{% if 'local_zone' in zone_conf %}
- counter jump VZONE_{{ zone_name }}_OUT
-{% endif %}
-{% endfor %}
- }
-{% for zone_name, zone_conf in zone.items() %}
-{% if zone_conf.local_zone is vyos_defined %}
- chain VZONE_{{ zone_name }}_IN {
- iifname lo counter return
-{% if zone_conf.from is vyos_defined %}
-{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endfor %}
-{% endif %}
- {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
- }
- chain VZONE_{{ zone_name }}_OUT {
- oifname lo counter return
-{% if zone_conf.from_local is vyos_defined %}
-{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall[fw_name] is vyos_defined %}
- oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
- oifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endfor %}
-{% endif %}
- {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
- }
-{% else %}
- chain VZONE_{{ zone_name }} {
- iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
-{% if zone_conf.intra_zone_filtering is vyos_defined %}
- iifname { {{ zone_conf.interface | join(",") }} } counter return
-{% endif %}
-{% if zone_conf.from is vyos_defined %}
-{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
-{% if zone[from_zone].local_zone is not defined %}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endif %}
-{% endfor %}
-{% endif %}
- {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
- }
-{% endif %}
-{% endfor %}
-{% endmacro %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 10cbc68cb..84af0449a 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -3,6 +3,20 @@
{% import 'firewall/nftables-defines.j2' as group_tmpl %}
{% if first_install is not vyos_defined %}
+delete table inet vyos_global_rpfilter
+{% endif %}
+table inet vyos_global_rpfilter {
+ chain PREROUTING {
+ type filter hook prerouting priority -300; policy accept;
+{% if global_options.source_validation is vyos_defined('loose') %}
+ fib saddr oif 0 counter drop
+{% elif global_options.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 %}
table ip vyos_filter {
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 36d92fe93..ab25ab4bd 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -82,6 +82,10 @@ table ip6 raw {
type filter hook forward priority -300; policy accept;
}
+ chain vyos_rpfilter {
+ type filter hook prerouting priority -300; policy accept;
+ }
+
chain PREROUTING {
type filter hook prerouting priority -300; policy accept;
counter jump VYOS_CT_PREROUTING_HOOK
diff --git a/interface-definitions/include/interface/ipv6-options.xml.i b/interface-definitions/include/interface/ipv6-options.xml.i
index d2e47de91..edb4a74f9 100644
--- a/interface-definitions/include/interface/ipv6-options.xml.i
+++ b/interface-definitions/include/interface/ipv6-options.xml.i
@@ -9,6 +9,7 @@
#include <include/interface/ipv6-accept-dad.xml.i>
#include <include/interface/ipv6-address.xml.i>
#include <include/interface/ipv6-dup-addr-detect-transmits.xml.i>
+ #include <include/interface/source-validation.xml.i>
</children>
</node>
<!-- include end -->
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index ddac387e7..41ce352ad 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -777,6 +777,30 @@ class Interface(Control):
return None
return self.set_interface('rp_filter', value)
+ def _cleanup_ipv6_source_validation_rules(self, ifname):
+ commands = []
+ results = self._cmd(f'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._cmd(f'nft delete rule ip6 raw vyos_rpfilter handle {handle_search[1]}')
+
+ def set_ipv6_source_validation(self, mode):
+ """
+ Set IPv6 reverse path validation
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_ipv6_source_validation('strict')
+ """
+ self._cleanup_ipv6_source_validation_rules(self.ifname)
+ nft_prefix = f'nft add rule ip6 raw vyos_rpfilter iifname "{self.ifname}"'
+ if mode == 'strict':
+ self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop")
+ elif mode == 'loose':
+ self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop")
+
def set_ipv6_accept_ra(self, accept_ra):
"""
Accept Router Advertisements; autoconfigure using them.
@@ -1568,6 +1592,11 @@ class Interface(Control):
value = tmp if (tmp != None) else '0'
self.set_ipv4_source_validation(value)
+ # IPv6 source-validation
+ tmp = dict_search('ipv6.source_validation', config)
+ value = tmp if (tmp != None) else '0'
+ self.set_ipv6_source_validation(value)
+
# MTU - Maximum Transfer Unit has a default value. It must ALWAYS be set
# before mangling any IPv6 option. If MTU is less then 1280 IPv6 will be
# automatically disabled by the kernel. Also MTU must be increased before
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index b5b65e253..820024dc9 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -844,6 +844,7 @@ class BasicInterfaceTest:
mss = '1400'
dad_transmits = '10'
accept_dad = '0'
+ source_validation = 'strict'
for interface in self._interfaces:
path = self._base_path + [interface]
@@ -863,6 +864,9 @@ class BasicInterfaceTest:
if cli_defined(self._base_path + ['ipv6'], 'disable-forwarding'):
self.cli_set(path + ['ipv6', 'disable-forwarding'])
+ if cli_defined(self._base_path + ['ipv6'], 'source-validation'):
+ self.cli_set(path + ['ipv6', 'source-validation', source_validation])
+
self.cli_commit()
for interface in self._interfaces:
@@ -886,6 +890,14 @@ class BasicInterfaceTest:
tmp = read_file(f'{proc_base}/forwarding')
self.assertEqual('0', tmp)
+ if cli_defined(self._base_path + ['ipv6'], 'source-validation'):
+ base_options = f'iifname "{interface}"'
+ out = cmd('sudo nft list chain ip6 raw vyos_rpfilter')
+ for line in out.splitlines():
+ if line.startswith(base_options):
+ self.assertIn('fib saddr . iif oif 0', line)
+ self.assertIn('drop', line)
+
def test_dhcpv6_client_options(self):
if not self._test_ipv6_dhcpc6:
self.skipTest('not supported')
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index b2076c077..c6514210b 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -511,6 +511,27 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip vyos_filter')
+ def test_source_validation(self):
+ # Strict
+ self.cli_set(['firewall', 'global-options', 'source-validation', 'strict'])
+ self.cli_commit()
+
+ nftables_strict_search = [
+ ['fib saddr . iif oif 0', 'drop']
+ ]
+
+ self.verify_nftables(nftables_strict_search, 'inet vyos_global_rpfilter')
+
+ # Loose
+ self.cli_set(['firewall', 'global-options', 'source-validation', 'loose'])
+ self.cli_commit()
+
+ nftables_loose_search = [
+ ['fib saddr oif 0', 'drop']
+ ]
+
+ self.verify_nftables(nftables_loose_search, 'inet vyos_global_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 d32ae497a..c86d1b555 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -54,7 +54,6 @@ sysfs_config = {
'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'}
}