diff options
-rw-r--r-- | data/templates/conntrack/nftables-ct.j2 | 40 | ||||
-rw-r--r-- | interface-definitions/include/conntrack/timeout-custom-protocols.xml.i | 136 | ||||
-rw-r--r-- | interface-definitions/system-conntrack.xml.in | 144 | ||||
-rw-r--r-- | python/vyos/template.py | 29 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_system_conntrack.py | 44 | ||||
-rwxr-xr-x | src/conf_mode/conntrack.py | 7 | ||||
-rwxr-xr-x | src/migration-scripts/conntrack/4-to-5 | 59 |
7 files changed, 406 insertions, 53 deletions
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 @@ +<!-- include start from conntrack/timeout-custom-protocols.xml.i --> +<node name="tcp"> + <properties> + <help>TCP connection timeout options</help> + </properties> + <children> + <leafNode name="close-wait"> + <properties> + <help>TCP CLOSE-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP CLOSE-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="close"> + <properties> + <help>TCP CLOSE timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP CLOSE timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="established"> + <properties> + <help>TCP ESTABLISHED timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP ESTABLISHED timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="fin-wait"> + <properties> + <help>TCP FIN-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP FIN-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="last-ack"> + <properties> + <help>TCP LAST-ACK timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP LAST-ACK timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="syn-recv"> + <properties> + <help>TCP SYN-RECEIVED timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP SYN-RECEIVED timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="syn-sent"> + <properties> + <help>TCP SYN-SENT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP SYN-SENT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time-wait"> + <properties> + <help>TCP TIME-WAIT timeout in seconds</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>TCP TIME-WAIT timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="udp"> + <properties> + <help>UDP timeout options</help> + </properties> + <children> + <leafNode name="replied"> + <properties> + <help>Timeout for UDP connection seen in both directions</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>Timeout for UDP connection seen in both directions</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + <leafNode name="unreplied"> + <properties> + <help>Timeout for unreplied UDP</help> + <valueHelp> + <format>u32:1-21474836</format> + <description>Timeout for unreplied UDP</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-21474836"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> 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 @@ <help>Define custom timeouts per connection</help> </properties> <children> - <tagNode name="rule"> + <node name="ipv4"> <properties> - <help>Rule number</help> - <valueHelp> - <format>u32:1-999999</format> - <description>Number of conntrack rule</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-999999"/> - </constraint> - <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> + <help>IPv4 rules</help> </properties> <children> - #include <include/generic-description.xml.i> - <node name="destination"> - <properties> - <help>Destination parameters</help> - </properties> - <children> - #include <include/nat-address.xml.i> - #include <include/nat-port.xml.i> - </children> - </node> - <leafNode name="inbound-interface"> - <properties> - <help>Interface to ignore connections tracking on</help> - <completionHelp> - <list>any</list> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - </properties> - </leafNode> - #include <include/ip-protocol.xml.i> - <node name="protocol"> + <tagNode name="rule"> <properties> - <help>Customize protocol specific timers, one protocol configuration per rule</help> + <help>Rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of conntrack rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> - #include <include/conntrack/timeout-common-protocols.xml.i> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="inbound-interface"> + <properties> + <help>Interface to ignore connections tracking on</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + <node name="protocol"> + <properties> + <help>Customize protocol specific timers, one protocol configuration per rule</help> + </properties> + <children> + #include <include/conntrack/timeout-custom-protocols.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> </children> - </node> - <node name="source"> + </tagNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 rules</help> + </properties> + <children> + <tagNode name="rule"> <properties> - <help>Source parameters</help> + <help>Rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number of conntrack rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> - #include <include/nat-address.xml.i> - #include <include/nat-port.xml.i> + #include <include/generic-description.xml.i> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="inbound-interface"> + <properties> + <help>Interface to ignore connections tracking on</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + </leafNode> + <node name="protocol"> + <properties> + <help>Customize protocol specific timers, one protocol configuration per rule</help> + </properties> + <children> + #include <include/conntrack/timeout-custom-protocols.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> </children> - </node> + </tagNode> </children> - </tagNode> + </node> </children> </node> #include <include/conntrack/timeout-common-protocols.xml.i> 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 <http://www.gnu.org/licenses/>. + +# 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 <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) |