diff options
24 files changed, 288 insertions, 369 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bd9aa7bbf..933894447 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,6 +21,9 @@ the box, please use [x] <!-- All submitted PRs must be linked to a Task on Phabricator. --> * https://vyos.dev/Txxxx +## Related PR(s) +<!-- Link here any PRs in other repositories that are required by this PR --> + ## Component(s) name <!-- A rather incomplete list of components: ethernet, wireguard, bgp, mpls, ldp, l2tp, dhcp ... --> diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json index 08732bd4c..a433c2522 100644 --- a/data/config-mode-dependencies/vyos-1x.json +++ b/data/config-mode-dependencies/vyos-1x.json @@ -1,6 +1,9 @@ { - "firewall": {"group_resync": ["conntrack", "nat", "policy-route"]}, + "firewall": {"conntrack": ["conntrack"], "group_resync": ["conntrack", "nat", "policy-route"]}, "http_api": {"https": ["https"]}, + "load_balancing_wan": {"conntrack": ["conntrack"]}, + "nat": {"conntrack": ["conntrack"]}, + "nat66": {"conntrack": ["conntrack"]}, "pki": { "ethernet": ["interfaces-ethernet"], "openvpn": ["interfaces-openvpn"], diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2 index 3a5b5a87c..895f61a55 100644 --- a/data/templates/conntrack/nftables-ct.j2 +++ b/data/templates/conntrack/nftables-ct.j2 @@ -2,16 +2,11 @@ {% import 'firewall/nftables-defines.j2' as group_tmpl %} -{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %} -{% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %} - -# we first flush all chains and render the content from scratch - this makes -# any delta check obsolete -flush chain raw {{ nft_ct_ignore_name }} -flush chain raw {{ nft_ct_timeout_name }} - -table raw { - chain {{ nft_ct_ignore_name }} { +{% if first_install is not vyos_defined %} +delete table ip vyos_conntrack +{% endif %} +table ip vyos_conntrack { + chain VYOS_CT_IGNORE { {% if ignore.ipv4.rule is vyos_defined %} {% for rule, rule_config in ignore.ipv4.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} @@ -20,7 +15,7 @@ table raw { {% endif %} return } - chain {{ nft_ct_timeout_name }} { + chain VYOS_CT_TIMEOUT { {% if timeout.custom.rule is vyos_defined %} {% for rule, rule_config in timeout.custom.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} @@ -29,14 +24,81 @@ table raw { return } -{{ group_tmpl.groups(firewall_group, False, True) }} -} + chain PREROUTING { + type filter hook prerouting priority -300; policy accept; +{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %} + counter jump VYOS_CT_HELPER +{% endif %} + counter jump VYOS_CT_IGNORE + counter jump VYOS_CT_TIMEOUT + counter jump FW_CONNTRACK + counter jump NAT_CONNTRACK + counter jump WLB_CONNTRACK + notrack + } + + chain OUTPUT { + type filter hook output priority -300; policy accept; +{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %} + counter jump VYOS_CT_HELPER +{% endif %} + counter jump VYOS_CT_IGNORE + counter jump VYOS_CT_TIMEOUT + counter jump FW_CONNTRACK + counter jump NAT_CONNTRACK +{% if wlb_local_action %} + counter jump WLB_CONNTRACK +{% endif %} + notrack + } + + ct helper rpc_tcp { + type "rpc" protocol tcp; + } + + ct helper rpc_udp { + type "rpc" protocol udp; + } + + ct helper tns_tcp { + type "tns" protocol tcp; + } + + chain VYOS_CT_HELPER { +{% for module, module_conf in module_map.items() %} +{% if modules[module] is vyos_defined %} +{% if 'nftables' in module_conf %} +{% for rule in module_conf.nftables %} + {{ rule }} +{% endfor %} +{% endif %} +{% endif %} +{% endfor %} + return + } + + chain FW_CONNTRACK { + {{ ipv4_firewall_action }} + } + + chain NAT_CONNTRACK { + {{ ipv4_nat_action }} + } + + chain WLB_CONNTRACK { + {{ wlb_action }} + } -flush chain ip6 raw {{ nft_ct_ignore_name }} -flush chain ip6 raw {{ nft_ct_timeout_name }} +{% if firewall.group is vyos_defined %} +{{ group_tmpl.groups(firewall.group, False, True) }} +{% endif %} +} -table ip6 raw { - chain {{ nft_ct_ignore_name }} { +{% if first_install is not vyos_defined %} +delete table ip6 vyos_conntrack +{% endif %} +table ip6 vyos_conntrack { + chain VYOS_CT_IGNORE { {% if ignore.ipv6.rule is vyos_defined %} {% for rule, rule_config in ignore.ipv6.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} @@ -45,7 +107,7 @@ table ip6 raw { {% endif %} return } - chain {{ nft_ct_timeout_name }} { + chain VYOS_CT_TIMEOUT { {% if timeout.custom.rule is vyos_defined %} {% for rule, rule_config in timeout.custom.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} @@ -54,5 +116,64 @@ table ip6 raw { return } -{{ group_tmpl.groups(firewall_group, True, True) }} + chain PREROUTING { + type filter hook prerouting priority -300; policy accept; +{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %} + counter jump VYOS_CT_HELPER +{% endif %} + counter jump VYOS_CT_IGNORE + counter jump VYOS_CT_TIMEOUT + counter jump FW_CONNTRACK + counter jump NAT_CONNTRACK + notrack + } + + chain OUTPUT { + type filter hook output priority -300; policy accept; +{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %} + counter jump VYOS_CT_HELPER +{% endif %} + counter jump VYOS_CT_IGNORE + counter jump VYOS_CT_TIMEOUT + counter jump FW_CONNTRACK + counter jump NAT_CONNTRACK + notrack + } + + ct helper rpc_tcp { + type "rpc" protocol tcp; + } + + ct helper rpc_udp { + type "rpc" protocol udp; + } + + ct helper tns_tcp { + type "tns" protocol tcp; + } + + chain VYOS_CT_HELPER { +{% for module, module_conf in module_map.items() %} +{% if modules[module] is vyos_defined %} +{% if 'nftables' in module_conf %} +{% for rule in module_conf.nftables %} + {{ rule }} +{% endfor %} +{% endif %} +{% endif %} +{% endfor %} + return + } + + chain FW_CONNTRACK { + {{ ipv6_firewall_action }} + } + + chain NAT_CONNTRACK { + {{ ipv6_nat_action }} + } + +{% if firewall.group is vyos_defined %} +{{ group_tmpl.groups(firewall.group, True, True) }} +{% endif %} } diff --git a/data/templates/firewall/nftables-nat.j2 b/data/templates/firewall/nftables-nat.j2 index dcf28da88..4254f6a0e 100644 --- a/data/templates/firewall/nftables-nat.j2 +++ b/data/templates/firewall/nftables-nat.j2 @@ -2,27 +2,6 @@ {% import 'firewall/nftables-defines.j2' as group_tmpl %} -{% if helper_functions is vyos_defined('remove') %} -{# NAT if going to be disabled - remove rules and targets from nftables #} -{% set base_command = 'delete rule ip raw' %} -{{ base_command }} PREROUTING handle {{ pre_ct_ignore }} -{{ base_command }} OUTPUT handle {{ out_ct_ignore }} -{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }} -{{ base_command }} OUTPUT handle {{ out_ct_conntrack }} - -delete chain ip raw NAT_CONNTRACK - -{% elif helper_functions is vyos_defined('add') %} -{# NAT if enabled - add targets to nftables #} -add chain ip raw NAT_CONNTRACK -add rule ip raw NAT_CONNTRACK counter accept -{% set base_command = 'add rule ip raw' %} -{{ base_command }} PREROUTING position {{ pre_ct_ignore }} counter jump VYOS_CT_HELPER -{{ base_command }} OUTPUT position {{ out_ct_ignore }} counter jump VYOS_CT_HELPER -{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK -{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK -{% endif %} - {% if first_install is not vyos_defined %} delete table ip vyos_nat {% endif %} diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2 index 27b3eec88..67eb2c109 100644 --- a/data/templates/firewall/nftables-nat66.j2 +++ b/data/templates/firewall/nftables-nat66.j2 @@ -1,22 +1,5 @@ #!/usr/sbin/nft -f -{% if helper_functions is vyos_defined('remove') %} -{# NAT if going to be disabled - remove rules and targets from nftables #} -{% set base_command = 'delete rule ip6 raw' %} -{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }} -{{ base_command }} OUTPUT handle {{ out_ct_conntrack }} - -delete chain ip6 raw NAT_CONNTRACK - -{% elif helper_functions is vyos_defined('add') %} -{# NAT if enabled - add targets to nftables #} -add chain ip6 raw NAT_CONNTRACK -add rule ip6 raw NAT_CONNTRACK counter accept -{% set base_command = 'add rule ip6 raw' %} -{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK -{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK -{% endif %} - {% if first_install is not vyos_defined %} delete table ip6 vyos_nat {% endif %} diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index db010257d..1564b3ef8 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -4,17 +4,10 @@ {% import 'firewall/nftables-bridge.j2' as bridge_tmpl %} {% import 'firewall/nftables-offload.j2' as offload %} -flush chain raw FW_CONNTRACK -flush chain ip6 raw FW_CONNTRACK - flush chain raw vyos_global_rpfilter flush chain ip6 raw vyos_global_rpfilter table raw { - chain FW_CONNTRACK { - {{ ipv4_conntrack_action }} - } - chain vyos_global_rpfilter { {% if global_options.source_validation is vyos_defined('loose') %} fib saddr oif 0 counter drop @@ -26,10 +19,6 @@ table raw { } table ip6 raw { - chain FW_CONNTRACK { - {{ ipv6_conntrack_action }} - } - chain vyos_global_rpfilter { {% if global_options.ipv6_source_validation is vyos_defined('loose') %} fib saddr oif 0 counter drop @@ -273,23 +262,22 @@ table bridge vyos_filter { } {% endif %} -table inet vyos_offload +{% if first_install is not vyos_defined %} delete table inet vyos_offload +{% endif %} table inet vyos_offload { -{% if flowtable_enabled %} -{% if global_options.flow_offload.hardware.interface is vyos_defined %} +{% if global_options.flow_offload.hardware.interface is vyos_defined %} {{- offload.render_flowtable('VYOS_FLOWTABLE_hardware', global_options.flow_offload.hardware.interface | list, priority='filter - 2', hardware_offload=true) }} chain VYOS_OFFLOAD_hardware { type filter hook forward priority filter - 2; policy accept; ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_hardware } -{% endif %} -{% if global_options.flow_offload.software.interface is vyos_defined %} +{% endif %} +{% if global_options.flow_offload.software.interface is vyos_defined %} {{- offload.render_flowtable('VYOS_FLOWTABLE_software', global_options.flow_offload.software.interface | list, priority='filter - 1') }} chain VYOS_OFFLOAD_software { type filter hook forward priority filter - 1; policy accept; ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_software } -{% endif %} {% endif %} } diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf index 7e258e6f1..cd7d5011f 100644 --- a/data/vyos-firewall-init.conf +++ b/data/vyos-firewall-init.conf @@ -9,6 +9,7 @@ table ip nat { } table inet mangle { + # Used by system flow-accounting chain FORWARD { type filter hook forward priority -150; policy accept; } @@ -28,61 +29,9 @@ table raw { counter jump vyos_global_rpfilter } - chain PREROUTING { + # Used by system flow-accounting + chain VYOS_PREROUTING_HOOK { type filter hook prerouting priority -300; policy accept; - counter jump VYOS_CT_IGNORE - counter jump VYOS_CT_TIMEOUT - counter jump VYOS_CT_PREROUTING_HOOK - counter jump FW_CONNTRACK - notrack - } - - chain OUTPUT { - type filter hook output priority -300; policy accept; - counter jump VYOS_CT_IGNORE - counter jump VYOS_CT_TIMEOUT - counter jump VYOS_CT_OUTPUT_HOOK - counter jump FW_CONNTRACK - notrack - } - - ct helper rpc_tcp { - type "rpc" protocol tcp; - } - - ct helper rpc_udp { - type "rpc" protocol udp; - } - - ct helper tns_tcp { - type "tns" protocol tcp; - } - - chain VYOS_CT_HELPER { - ct helper set "rpc_tcp" tcp dport {111} return - ct helper set "rpc_udp" udp dport {111} return - ct helper set "tns_tcp" tcp dport {1521,1525,1536} return - return - } - - chain VYOS_CT_IGNORE { - return - } - - chain VYOS_CT_TIMEOUT { - return - } - - chain VYOS_CT_PREROUTING_HOOK { - return - } - - chain VYOS_CT_OUTPUT_HOOK { - return - } - - chain FW_CONNTRACK { - return } } @@ -100,60 +49,8 @@ table ip6 raw { counter jump vyos_global_rpfilter } - chain PREROUTING { + # Used by system flow-accounting + chain VYOS_PREROUTING_HOOK { type filter hook prerouting priority -300; policy accept; - counter jump VYOS_CT_IGNORE - counter jump VYOS_CT_TIMEOUT - counter jump VYOS_CT_PREROUTING_HOOK - counter jump FW_CONNTRACK - notrack - } - - chain OUTPUT { - type filter hook output priority -300; policy accept; - counter jump VYOS_CT_IGNORE - counter jump VYOS_CT_TIMEOUT - counter jump VYOS_CT_OUTPUT_HOOK - counter jump FW_CONNTRACK - notrack - } - - ct helper rpc_tcp { - type "rpc" protocol tcp; - } - - ct helper rpc_udp { - type "rpc" protocol udp; - } - - ct helper tns_tcp { - type "tns" protocol tcp; - } - - chain VYOS_CT_HELPER { - ct helper set "rpc_tcp" tcp dport {111} return - ct helper set "rpc_udp" udp dport {111} return - ct helper set "tns_tcp" tcp dport {1521,1525,1536} return - return - } - - chain VYOS_CT_IGNORE { - return - } - - chain VYOS_CT_TIMEOUT { - return - } - - chain VYOS_CT_PREROUTING_HOOK { - return - } - - chain VYOS_CT_OUTPUT_HOOK { - return - } - - chain FW_CONNTRACK { - return } } diff --git a/interface-definitions/include/firewall/common-rule-inet.xml.i b/interface-definitions/include/firewall/common-rule-inet.xml.i index 7a2eb86d4..e51dd0056 100644 --- a/interface-definitions/include/firewall/common-rule-inet.xml.i +++ b/interface-definitions/include/firewall/common-rule-inet.xml.i @@ -303,6 +303,7 @@ </children> </node> #include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> <node name="time"> <properties> <help>Time to match rule</help> diff --git a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i index a1071a09a..e040c9b13 100644 --- a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i +++ b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i @@ -260,6 +260,7 @@ </children> </node> #include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> <node name="time"> <properties> <help>Time to match rule</help> diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 7417a3c58..c62bf2c5f 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -315,6 +315,7 @@ </children> </node> #include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> <node name="time"> <properties> <help>Time to match rule</help> diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i index e2ce7b9fd..36546c2e4 100644 --- a/interface-definitions/include/firewall/tcp-flags.xml.i +++ b/interface-definitions/include/firewall/tcp-flags.xml.i @@ -1,7 +1,7 @@ <!-- include start from firewall/tcp-flags.xml.i --> <node name="tcp"> <properties> - <help>TCP flags to match</help> + <help>TCP options to match</help> </properties> <children> <node name="flags"> @@ -114,22 +114,6 @@ </node> </children> </node> - <leafNode name="mss"> - <properties> - <help>Maximum segment size (MSS)</help> - <valueHelp> - <format>u32:1-16384</format> - <description>Maximum segment size</description> - </valueHelp> - <valueHelp> - <format><min>-<max></format> - <description>TCP MSS range (use '-' as delimiter)</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--allow-range --range 1-16384"/> - </constraint> - </properties> - </leafNode> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/firewall/tcp-mss.xml.i b/interface-definitions/include/firewall/tcp-mss.xml.i new file mode 100644 index 000000000..dc49b4272 --- /dev/null +++ b/interface-definitions/include/firewall/tcp-mss.xml.i @@ -0,0 +1,25 @@ +<!-- include start from firewall/tcp-mss.xml.i --> +<node name="tcp"> + <properties> + <help>TCP options to match</help> + </properties> + <children> + <leafNode name="mss"> + <properties> + <help>Maximum segment size (MSS)</help> + <valueHelp> + <format>u32:1-16384</format> + <description>Maximum segment size</description> + </valueHelp> + <valueHelp> + <format><min>-<max></format> + <description>TCP MSS range (use '-' as delimiter)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 1-16384"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i index 216ec9bea..6551d23ab 100644 --- a/interface-definitions/include/policy/route-common.xml.i +++ b/interface-definitions/include/policy/route-common.xml.i @@ -314,6 +314,7 @@ </children>
</node>
#include <include/firewall/tcp-flags.xml.i>
+#include <include/firewall/tcp-mss.xml.i>
<node name="time">
<properties>
<help>Time to match rule</help>
diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index 78d19090c..4452f1a74 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -127,6 +127,7 @@ #include <include/nat-port.xml.i> </children> </node> + #include <include/firewall/tcp-flags.xml.i> </children> </tagNode> </children> @@ -212,6 +213,7 @@ #include <include/nat-port.xml.i> </children> </node> + #include <include/firewall/tcp-flags.xml.i> </children> </tagNode> </children> diff --git a/python/vyos/template.py b/python/vyos/template.py index add4d3ce5..3be486cc4 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -678,6 +678,11 @@ def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False): proto = rule_conf['protocol'] output.append(f'meta l4proto {proto}') + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if tcp_flags: + from vyos.firewall import parse_tcp_flags + output.append(parse_tcp_flags(tcp_flags)) + for side in ['source', 'destination']: if side in rule_conf: side_conf = rule_conf[side] diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 391ef03ff..75d6e9bb1 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -523,8 +523,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_filter') # Check conntrack - self.verify_nftables_chain([['accept']], 'raw', 'FW_CONNTRACK') - self.verify_nftables_chain([['return']], 'ip6 raw', 'FW_CONNTRACK') + self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') + self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') def test_bridge_basic_rules(self): name = 'smoketest' diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index ea304783d..c9f184558 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -200,7 +200,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): self.assertTrue(os.path.isdir(f'/sys/module/{driver}')) if 'nftables' in module_options: for rule in module_options['nftables']: - self.assertTrue(find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) != None) + self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) != None) # unload modules for module in modules: @@ -216,7 +216,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): self.assertFalse(os.path.isdir(f'/sys/module/{driver}')) if 'nftables' in module_options: for rule in module_options['nftables']: - self.assertTrue(find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) == None) + self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) == None) def test_conntrack_hash_size(self): hash_size = '65536' @@ -256,6 +256,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'port', '22']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'protocol', 'tcp']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'tcp', 'flags', 'syn']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group]) @@ -274,7 +275,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'notrack'], + ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'tcp flags & syn == syn', 'notrack'], ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack'] ] @@ -284,8 +285,8 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack'] ] - self.verify_nftables(nftables_search, 'raw') - self.verify_nftables(nftables6_search, 'ip6 raw') + self.verify_nftables(nftables_search, 'ip vyos_conntrack') + self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack') self.cli_delete(['firewall']) diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py index d55ea616e..6c761579b 100755 --- a/smoketest/scripts/cli/test_system_flow-accounting.py +++ b/smoketest/scripts/cli/test_system_flow-accounting.py @@ -67,7 +67,7 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): self.cli_commit() # verify configuration - nftables_output = cmd('sudo nft list chain raw VYOS_CT_PREROUTING_HOOK').splitlines() + nftables_output = cmd('sudo nft list chain raw VYOS_PREROUTING_HOOK').splitlines() for interface in Section.interfaces('ethernet'): rule_found = False ifname_search = f'iifname "{interface}"' diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index a0de914bc..21a20ea8d 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -20,11 +20,10 @@ import re from sys import exit from vyos.config import Config -from vyos.firewall import find_nftables_rule -from vyos.firewall import remove_nftables_rule from vyos.utils.process import process_named_running from vyos.utils.dict import dict_search from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive from vyos.utils.process import cmd from vyos.utils.process import rc_cmd from vyos.utils.process import run @@ -47,8 +46,8 @@ module_map = { 'ko' : ['nf_nat_h323', 'nf_conntrack_h323'], }, 'nfs' : { - 'nftables' : ['ct helper set "rpc_tcp" tcp dport "{111}" return', - 'ct helper set "rpc_udp" udp dport "{111}" return'] + 'nftables' : ['ct helper set "rpc_tcp" tcp dport {111} return', + 'ct helper set "rpc_udp" udp dport {111} return'] }, 'pptp' : { 'ko' : ['nf_nat_pptp', 'nf_conntrack_pptp'], @@ -57,7 +56,7 @@ module_map = { 'ko' : ['nf_nat_sip', 'nf_conntrack_sip'], }, 'sqlnet' : { - 'nftables' : ['ct helper set "tns_tcp" tcp dport "{1521,1525,1536}" return'] + 'nftables' : ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return'] }, 'tftp' : { 'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'], @@ -87,10 +86,25 @@ def get_config(config=None): get_first_key=True, with_recursive_defaults=True) - conntrack['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), + conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + conntrack['flowtable_enabled'] = False + flow_offload = dict_search_args(conntrack['firewall'], 'global_options', 'flow_offload') + if flow_offload and 'disable' not in flow_offload: + for offload_type in ('software', 'hardware'): + if dict_search_args(flow_offload, offload_type, 'interface'): + conntrack['flowtable_enabled'] = True + break + + conntrack['ipv4_nat_action'] = 'accept' if conf.exists(['nat']) else 'return' + conntrack['ipv6_nat_action'] = 'accept' if conf.exists(['nat66']) else 'return' + conntrack['wlb_action'] = 'accept' if conf.exists(['load-balancing', 'wan']) else 'return' + conntrack['wlb_local_action'] = conf.exists(['load-balancing', 'wan', 'enable-local-traffic']) + + conntrack['module_map'] = module_map + return conntrack def verify(conntrack): @@ -104,6 +118,17 @@ def verify(conntrack): if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']: raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') + tcp_flags = dict_search_args(rule_config, 'tcp', 'flags') + if tcp_flags: + if dict_search_args(rule_config, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + + not_flags = dict_search_args(rule_config, 'tcp', 'flags', 'not') + if not_flags: + duplicates = [flag for flag in tcp_flags if flag in not_flags] + if duplicates: + raise ConfigError(f'Cannot match a tcp flag as set and not set') + for side in ['destination', 'source']: if side in rule_config: side_conf = rule_config[side] @@ -127,7 +152,7 @@ def verify(conntrack): if inet == 'ipv6': group = f'ipv6_{group}' - group_obj = dict_search_args(conntrack['firewall_group'], group, group_name) + group_obj = dict_search_args(conntrack['firewall'], 'group', group, group_name) if group_obj is None: raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule') @@ -138,22 +163,29 @@ def verify(conntrack): return None def generate(conntrack): + if not os.path.exists(nftables_ct_file): + conntrack['first_install'] = True + + # Determine if conntrack is needed + conntrack['ipv4_firewall_action'] = 'return' + conntrack['ipv6_firewall_action'] = 'return' + + if conntrack['flowtable_enabled']: + conntrack['ipv4_firewall_action'] = 'accept' + conntrack['ipv6_firewall_action'] = 'accept' + else: + for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'): + if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): + if path[0] == 'ipv4': + conntrack['ipv4_firewall_action'] = 'accept' + elif path[0] == 'ipv6': + conntrack['ipv6_firewall_action'] = 'accept' + render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack) render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack) render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack) return None -def find_nftables_ct_rule(table, chain, rule): - helper_search = re.search('ct helper set "(\w+)"', rule) - if helper_search: - rule = helper_search[1] - return find_nftables_rule(table, chain, [rule]) - -def find_remove_rule(table, chain, rule): - handle = find_nftables_ct_rule(table, chain, rule) - if handle: - remove_nftables_rule(table, chain, handle) - def apply(conntrack): # Depending on the enable/disable state of the ALG (Application Layer Gateway) # modules we need to either insmod or rmmod the helpers. @@ -164,21 +196,10 @@ def apply(conntrack): # Only remove the module if it's loaded if os.path.exists(f'/sys/module/{mod}'): cmd(f'rmmod {mod}') - if 'nftables' in module_config: - for rule in module_config['nftables']: - find_remove_rule('raw', 'VYOS_CT_HELPER', rule) - find_remove_rule('ip6 raw', 'VYOS_CT_HELPER', rule) else: if 'ko' in module_config: for mod in module_config['ko']: cmd(f'modprobe {mod}') - if 'nftables' in module_config: - for rule in module_config['nftables']: - if not find_nftables_ct_rule('raw', 'VYOS_CT_HELPER', rule): - cmd(f'nft insert rule raw VYOS_CT_HELPER {rule}') - - if not find_nftables_ct_rule('ip6 raw', 'VYOS_CT_HELPER', rule): - cmd(f'nft insert rule ip6 raw VYOS_CT_HELPER {rule}') # Load new nftables ruleset install_result, output = rc_cmd(f'nft -f {nftables_ct_file}') diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 769cc598f..d999b2a64 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -141,13 +141,7 @@ def get_config(config=None): fqdn_config_parse(firewall) - firewall['flowtable_enabled'] = False - flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload') - if flow_offload and 'disable' not in flow_offload: - for offload_type in ('software', 'hardware'): - if dict_search_args(flow_offload, offload_type, 'interface'): - firewall['flowtable_enabled'] = True - break + set_dependents('conntrack', conf) return firewall @@ -350,19 +344,6 @@ def generate(firewall): if not os.path.exists(nftables_conf): firewall['first_install'] = True - # Determine if conntrack is needed - firewall['ipv4_conntrack_action'] = 'return' - firewall['ipv6_conntrack_action'] = 'return' - if firewall['flowtable_enabled']: # Netfilter's flowtable offload requires conntrack - firewall['ipv4_conntrack_action'] = 'accept' - firewall['ipv6_conntrack_action'] = 'accept' - else: # Check if conntrack is needed by firewall rules - for proto in ('ipv4', 'ipv6'): - for rules, _ in dict_search_recursive(firewall.get(proto, {}), 'rule'): - if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): - firewall[f'{proto}_conntrack_action'] = 'accept' - break - render(nftables_conf, 'firewall/nftables.j2', firewall) return None @@ -392,8 +373,7 @@ def apply(firewall): apply_sysfs(firewall) - if firewall['group_resync']: - call_dependents() + call_dependents() # T970 Enable a resolver (systemd daemon) that checks # domain-group/fqdn addresses and update entries for domains by timeout diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 71acd69fa..81ee39df1 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -37,7 +37,7 @@ uacctd_conf_path = '/run/pmacct/uacctd.conf' systemd_service = 'uacctd.service' systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf' nftables_nflog_table = 'raw' -nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK' +nftables_nflog_chain = 'VYOS_PREROUTING_HOOK' egress_nftables_nflog_table = 'inet mangle' egress_nftables_nflog_chain = 'FORWARD' diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py index ad9c80d72..5da0b906b 100755 --- a/src/conf_mode/load-balancing-wan.py +++ b/src/conf_mode/load-balancing-wan.py @@ -21,6 +21,7 @@ from shutil import rmtree from vyos.base import Warning from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents from vyos.utils.process import cmd from vyos.template import render from vyos import ConfigError @@ -49,6 +50,8 @@ def get_config(config=None): if lb.from_defaults(['rule', rule, 'limit']): del lb['rule'][rule]['limit'] + set_dependents('conntrack', conf) + return lb @@ -132,6 +135,8 @@ def apply(lb): cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1') cmd(f'systemctl restart {systemd_service}') + call_dependents() + return None diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index e37a7011c..52a7a71fd 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -18,13 +18,12 @@ import jmespath import json import os -from distutils.version import LooseVersion -from platform import release as kernel_version from sys import exit from netifaces import interfaces from vyos.base import Warning from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents from vyos.template import render from vyos.template import is_ip_network from vyos.utils.kernel import check_kmod @@ -38,10 +37,7 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -if LooseVersion(kernel_version()) > LooseVersion('5.1'): - k_mod = ['nft_nat', 'nft_chain_nat'] -else: - k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] +k_mod = ['nft_nat', 'nft_chain_nat'] nftables_nat_config = '/run/nftables_nat.conf' nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft' @@ -53,18 +49,27 @@ valid_groups = [ 'port_group' ] -def get_handler(json, chain, target): - """ Get nftable rule handler number of given chain/target combination. - Handler is required when adding NAT/Conntrack helper targets """ - for x in json: - if x['chain'] != chain: - continue - if x['target'] != target: - continue - return x['handle'] +def get_config(config=None): + if config: + conf = config + else: + conf = Config() - return None + base = ['nat'] + nat = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + set_dependents('conntrack', conf) + + if not conf.exists(base): + nat['deleted'] = '' + return nat + + nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + return nat def verify_rule(config, err_msg, groups_dict): """ Common verify steps used for both source and destination NAT """ @@ -136,62 +141,11 @@ def verify_rule(config, err_msg, groups_dict): if count != 100: Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour') -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['nat'] - nat = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - # read in current nftable (once) for further processing - tmp = cmd('nft -j list table raw') - nftable_json = json.loads(tmp) - - # condense the full JSON table into a list with only relevand informations - pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}' - condensed_json = jmespath.search(pattern, nftable_json) - - if not conf.exists(base): - if get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER'): - nat['helper_functions'] = 'remove' - - # Retrieve current table handler positions - nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER') - nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') - nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER') - nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') - nat['deleted'] = '' - return nat - - nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - # check if NAT connection tracking helpers need to be set up - this has to - # be done only once - if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): - nat['helper_functions'] = 'add' - - # Retrieve current table handler positions - nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE') - nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK') - nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE') - nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK') - - return nat - def verify(nat): if not nat or 'deleted' in nat: # no need to verify the CLI as NAT is going to be deactivated return None - if 'helper_functions' in nat: - if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): - raise Exception('could not determine nftable ruleset handlers') - if dict_search('source.rule', nat): for rule, config in dict_search('source.rule', nat).items(): err_msg = f'Source NAT configuration error in rule {rule}:' @@ -267,6 +221,8 @@ def apply(nat): os.unlink(nftables_nat_config) os.unlink(nftables_static_nat_conf) + call_dependents() + return None if __name__ == '__main__': diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 4c12618bc..46d796bc8 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -23,6 +23,7 @@ from netifaces import interfaces from vyos.base import Warning from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents from vyos.template import render from vyos.utils.process import cmd from vyos.utils.kernel import check_kmod @@ -37,18 +38,6 @@ k_mod = ['nft_nat', 'nft_chain_nat'] nftables_nat66_config = '/run/nftables_nat66.nft' ndppd_config = '/run/ndppd/ndppd.conf' -def get_handler(json, chain, target): - """ Get nftable rule handler number of given chain/target combination. - Handler is required when adding NAT66/Conntrack helper targets """ - for x in json: - if x['chain'] != chain: - continue - if x['target'] != target: - continue - return x['handle'] - - return None - def get_config(config=None): if config: conf = config @@ -58,35 +47,10 @@ def get_config(config=None): base = ['nat66'] nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # read in current nftable (once) for further processing - tmp = cmd('nft -j list table ip6 raw') - nftable_json = json.loads(tmp) - - # condense the full JSON table into a list with only relevand informations - pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}' - condensed_json = jmespath.search(pattern, nftable_json) + set_dependents('conntrack', conf) if not conf.exists(base): - nat['helper_functions'] = 'remove' - nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER') - nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') - nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER') - nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') nat['deleted'] = '' - return nat - - # check if NAT66 connection tracking helpers need to be set up - this has to - # be done only once - if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): - nat['helper_functions'] = 'add' - - # Retrieve current table handler positions - nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE') - nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK') - nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE') - nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK') - else: - nat['helper_functions'] = 'has' return nat @@ -95,10 +59,6 @@ def verify(nat): # no need to verify the CLI as NAT66 is going to be deactivated return None - if 'helper_functions' in nat and nat['helper_functions'] != 'has': - if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']): - raise Exception('could not determine nftable ruleset handlers') - if dict_search('source.rule', nat): for rule, config in dict_search('source.rule', nat).items(): err_msg = f'Source NAT66 configuration error in rule {rule}:' @@ -155,6 +115,8 @@ def apply(nat): else: cmd('systemctl restart ndppd') + call_dependents() + return None if __name__ == '__main__': |