From 24a1a70596fafdd35d88506159e6cb9cd94e7a66 Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Tue, 5 Dec 2023 10:36:14 +0000 Subject: T5779: conntrack: Apply fixes to . Remove what was not working on 1.3, migrate what was working to new syntax and extend feature for ipv6. --- data/templates/conntrack/nftables-ct.j2 | 40 +++++- .../conntrack/timeout-custom-protocols.xml.i | 136 +++++++++++++++++++ interface-definitions/system-conntrack.xml.in | 144 +++++++++++++++------ python/vyos/template.py | 29 ++++- smoketest/scripts/cli/test_system_conntrack.py | 44 +++++++ src/conf_mode/conntrack.py | 7 + src/migration-scripts/conntrack/4-to-5 | 59 +++++++++ 7 files changed, 406 insertions(+), 53 deletions(-) create mode 100644 interface-definitions/include/conntrack/timeout-custom-protocols.xml.i create mode 100755 src/migration-scripts/conntrack/4-to-5 diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2 index 1e0fc8065..762a6f693 100644 --- a/data/templates/conntrack/nftables-ct.j2 +++ b/data/templates/conntrack/nftables-ct.j2 @@ -11,20 +11,33 @@ table ip vyos_conntrack { {% 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 }} - {{ rule_config | conntrack_ignore_rule(rule, ipv6=False) }} + {{ rule_config | conntrack_rule(rule, 'ignore', ipv6=False) }} {% endfor %} {% endif %} - return + return } chain VYOS_CT_TIMEOUT { -{% if timeout.custom.rule is vyos_defined %} -{% for rule, rule_config in timeout.custom.rule.items() %} +{% if timeout.custom.ipv4.rule is vyos_defined %} +{% for rule, rule_config in timeout.custom.ipv4.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} + {{ rule_config | conntrack_rule(rule, 'timeout', ipv6=False) }} {% endfor %} {% endif %} return } +{% if timeout.custom.ipv4.rule is vyos_defined %} +{% for rule, rule_config in timeout.custom.ipv4.rule.items() %} + ct timeout ct-timeout-{{ rule }} { + l3proto ip; +{% for protocol, protocol_config in rule_config.protocol.items() %} + protocol {{ protocol }}; + policy = { {{ protocol_config | conntrack_ct_policy() }} } +{% endfor %} + } +{% endfor %} +{% endif %} + chain PREROUTING { type filter hook prerouting priority -300; policy accept; {% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %} @@ -80,20 +93,33 @@ table ip6 vyos_conntrack { {% 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 }} - {{ rule_config | conntrack_ignore_rule(rule, ipv6=True) }} + {{ rule_config | conntrack_rule(rule, 'ignore', ipv6=True) }} {% endfor %} {% endif %} return } chain VYOS_CT_TIMEOUT { -{% if timeout.custom.rule is vyos_defined %} -{% for rule, rule_config in timeout.custom.rule.items() %} +{% if timeout.custom.ipv6.rule is vyos_defined %} +{% for rule, rule_config in timeout.custom.ipv6.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} + {{ rule_config | conntrack_rule(rule, 'timeout', ipv6=True) }} {% endfor %} {% endif %} return } +{% if timeout.custom.ipv6.rule is vyos_defined %} +{% for rule, rule_config in timeout.custom.ipv6.rule.items() %} + ct timeout ct-timeout-{{ rule }} { + l3proto ip; +{% for protocol, protocol_config in rule_config.protocol.items() %} + protocol {{ protocol }}; + policy = { {{ protocol_config | conntrack_ct_policy() }} } +{% endfor %} + } +{% endfor %} +{% endif %} + chain PREROUTING { type filter hook prerouting priority -300; policy accept; {% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %} diff --git a/interface-definitions/include/conntrack/timeout-custom-protocols.xml.i b/interface-definitions/include/conntrack/timeout-custom-protocols.xml.i new file mode 100644 index 000000000..e6bff7e4d --- /dev/null +++ b/interface-definitions/include/conntrack/timeout-custom-protocols.xml.i @@ -0,0 +1,136 @@ + + + + TCP connection timeout options + + + + + TCP CLOSE-WAIT timeout in seconds + + u32:1-21474836 + TCP CLOSE-WAIT timeout in seconds + + + + + + + + + TCP CLOSE timeout in seconds + + u32:1-21474836 + TCP CLOSE timeout in seconds + + + + + + + + + TCP ESTABLISHED timeout in seconds + + u32:1-21474836 + TCP ESTABLISHED timeout in seconds + + + + + + + + + TCP FIN-WAIT timeout in seconds + + u32:1-21474836 + TCP FIN-WAIT timeout in seconds + + + + + + + + + TCP LAST-ACK timeout in seconds + + u32:1-21474836 + TCP LAST-ACK timeout in seconds + + + + + + + + + TCP SYN-RECEIVED timeout in seconds + + u32:1-21474836 + TCP SYN-RECEIVED timeout in seconds + + + + + + + + + TCP SYN-SENT timeout in seconds + + u32:1-21474836 + TCP SYN-SENT timeout in seconds + + + + + + + + + TCP TIME-WAIT timeout in seconds + + u32:1-21474836 + TCP TIME-WAIT timeout in seconds + + + + + + + + + + + UDP timeout options + + + + + Timeout for UDP connection seen in both directions + + u32:1-21474836 + Timeout for UDP connection seen in both directions + + + + + + + + + Timeout for unreplied UDP + + u32:1-21474836 + Timeout for unreplied UDP + + + + + + + + + diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index 4452f1a74..d9504544d 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -385,58 +385,122 @@ Define custom timeouts per connection - + - Rule number - - u32:1-999999 - Number of conntrack rule - - - - - Ignore rule number must be between 1 and 999999 + IPv4 rules - #include - - - Destination parameters - - - #include - #include - - - - - Interface to ignore connections tracking on - - any - - - - - #include - + - Customize protocol specific timers, one protocol configuration per rule + Rule number + + u32:1-999999 + Number of conntrack rule + + + + + Ignore rule number must be between 1 and 999999 - #include + #include + + + Destination parameters + + + #include + #include + + + + + Interface to ignore connections tracking on + + any + + + + + + + Customize protocol specific timers, one protocol configuration per rule + + + #include + + + + + Source parameters + + + #include + #include + + - - + + + + + + IPv6 rules + + + - Source parameters + Rule number + + u32:1-999999 + Number of conntrack rule + + + + + Ignore rule number must be between 1 and 999999 - #include - #include + #include + + + Destination parameters + + + #include + #include + + + + + Interface to ignore connections tracking on + + any + + + + + + + Customize protocol specific timers, one protocol configuration per rule + + + #include + + + + + Source parameters + + + #include + #include + + - + - + #include diff --git a/python/vyos/template.py b/python/vyos/template.py index 1e683b605..0e2663258 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -664,8 +664,8 @@ def nat_static_rule(rule_conf, rule_id, nat_type): from vyos.nat import parse_nat_static_rule return parse_nat_static_rule(rule_conf, rule_id, nat_type) -@register_filter('conntrack_ignore_rule') -def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False): +@register_filter('conntrack_rule') +def conntrack_rule(rule_conf, rule_id, action, ipv6=False): ip_prefix = 'ip6' if ipv6 else 'ip' def_suffix = '6' if ipv6 else '' output = [] @@ -676,11 +676,15 @@ def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False): output.append(f'iifname {ifname}') if 'protocol' in rule_conf: - proto = rule_conf['protocol'] + if action != 'timeout': + proto = rule_conf['protocol'] + else: + for protocol, protocol_config in rule_conf['protocol'].items(): + proto = protocol output.append(f'meta l4proto {proto}') tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') - if tcp_flags: + if tcp_flags and action != 'timeout': from vyos.firewall import parse_tcp_flags output.append(parse_tcp_flags(tcp_flags)) @@ -743,11 +747,24 @@ def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False): output.append(f'{proto} {prefix}port {operator} @P_{group_name}') - output.append('counter notrack') - output.append(f'comment "ignore-{rule_id}"') + if action == 'ignore': + output.append('counter notrack') + output.append(f'comment "ignore-{rule_id}"') + else: + output.append(f'counter ct timeout set ct-timeout-{rule_id}') + output.append(f'comment "timeout-{rule_id}"') return " ".join(output) +@register_filter('conntrack_ct_policy') +def conntrack_ct_policy(protocol_conf): + output = [] + for item in protocol_conf: + item_value = protocol_conf[item] + output.append(f'{item}: {item_value}') + + return ", ".join(output) + @register_filter('range_to_regex') def range_to_regex(num_range): """Convert range of numbers or list of ranges diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index 7657ab724..0dbc97d49 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -297,5 +297,49 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): self.cli_delete(['firewall']) + def test_conntrack_timeout_custom(self): + + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'destination', 'port', '22']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'syn-sent', '77']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'close', '88']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'established', '99']) + + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'inbound-interface', 'eth1']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'source', 'address', '198.51.100.1']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'protocol', 'udp', 'unreplied', '55']) + + self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'source', 'address', '2001:db8::1']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'inbound-interface', 'eth2']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'protocol', 'tcp', 'time-wait', '22']) + self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'protocol', 'tcp', 'last-ack', '33']) + + self.cli_commit() + + nftables_search = [ + ['ct timeout ct-timeout-1 {'], + ['protocol tcp'], + ['policy = { syn_sent : 77, established : 99, close : 88 }'], + ['ct timeout ct-timeout-2 {'], + ['protocol udp'], + ['policy = { unreplied : 55 }'], + ['chain VYOS_CT_TIMEOUT {'], + ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'ct timeout set "ct-timeout-1"'], + ['iifname "eth1"', 'meta l4proto udp', 'ip saddr 198.51.100.1', 'ct timeout set "ct-timeout-2"'] + ] + + nftables6_search = [ + ['ct timeout ct-timeout-1 {'], + ['protocol tcp'], + ['policy = { last_ack : 33, time_wait : 22 }'], + ['chain VYOS_CT_TIMEOUT {'], + ['iifname "eth2"', 'meta l4proto tcp', 'ip6 saddr 2001:db8::1', 'ct timeout set "ct-timeout-1"'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_conntrack') + self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack') + + self.cli_delete(['firewall']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 4cece6921..7f6c71440 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -159,6 +159,13 @@ def verify(conntrack): if not group_obj: Warning(f'{error_group} "{group_name}" has no members!') + if dict_search_args(conntrack, 'timeout', 'custom', inet, 'rule') != None: + for rule, rule_config in conntrack['timeout']['custom'][inet]['rule'].items(): + if 'protocol' not in rule_config: + raise ConfigError(f'Conntrack custom timeout rule {rule} requires protocol tcp or udp') + else: + if 'tcp' in rule_config['protocol'] and 'udp' in rule_config['protocol']: + raise ConfigError(f'conntrack custom timeout rule {rule} - Cant use both tcp and udp protocol') return None def generate(conntrack): diff --git a/src/migration-scripts/conntrack/4-to-5 b/src/migration-scripts/conntrack/4-to-5 new file mode 100755 index 000000000..d2e5fc5fa --- /dev/null +++ b/src/migration-scripts/conntrack/4-to-5 @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# T5779: system conntrack timeout custom +# Before: +# Protocols tcp, udp and icmp allowed. When using udp it did not work +# Only ipv4 custom timeout rules +# Now: +# Valid protocols are only tcp or udp. +# Extend functionality to ipv6 and move ipv4 custom rules to new node: +# set system conntrack timeout custom [ipv4 | ipv6] rule ... + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['system', 'conntrack'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base + ['timeout', 'custom', 'rule']): + for rule in config.list_nodes(base + ['timeout', 'custom', 'rule']): + if config.exists(base + ['timeout', 'custom', 'rule', rule, 'protocol', 'tcp']): + config.set(base + ['timeout', 'custom', 'ipv4', 'rule']) + config.copy(base + ['timeout', 'custom', 'rule', rule], base + ['timeout', 'custom', 'ipv4', 'rule', rule]) + config.delete(base + ['timeout', 'custom', 'rule']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3