summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2024-01-19 18:29:07 +0100
committerGitHub <noreply@github.com>2024-01-19 18:29:07 +0100
commit9c6aed53c68b8b5d62223717b71f8e41b70b78cc (patch)
tree2c3566becc1e93b70df5fb2f61b4f1028a04dd8f
parent4b3ef473c3acfedeb70a023a9ca46df5437fc5a2 (diff)
parent80068c8ce453a385981999c25e4ff5aeaa6bf030 (diff)
downloadvyos-1x-9c6aed53c68b8b5d62223717b71f8e41b70b78cc.tar.gz
vyos-1x-9c6aed53c68b8b5d62223717b71f8e41b70b78cc.zip
Merge pull request #2802 from vyos/mergify/bp/sagitta/pr-2574
T5779: conntrack: Apply fixes to <set system conntrack timeout custom> (backport #2574)
-rw-r--r--data/config-mode-dependencies/vyos-1x.json3
-rw-r--r--data/templates/conntrack/nftables-ct.j2182
-rw-r--r--data/templates/conntrack/nftables-helpers.j270
-rw-r--r--data/templates/conntrack/sysctl.conf.j21
-rw-r--r--data/templates/conntrack/vyos_nf_conntrack.conf.j22
-rw-r--r--interface-definitions/include/conntrack/timeout-custom-protocols.xml.i136
-rw-r--r--interface-definitions/include/firewall/source-destination-group-ipv4.xml.i41
-rw-r--r--interface-definitions/include/version/conntrack-version.xml.i2
-rw-r--r--interface-definitions/system_conntrack.xml.in367
-rw-r--r--python/vyos/template.py101
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py143
-rwxr-xr-xsrc/conf_mode/system_conntrack.py212
-rwxr-xr-xsrc/init/vyos-router3
-rwxr-xr-xsrc/migration-scripts/conntrack/3-to-450
-rwxr-xr-xsrc/migration-scripts/conntrack/4-to-559
15 files changed, 1152 insertions, 220 deletions
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index 50473a6a1..81d86cf7e 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -1,6 +1,7 @@
{
"firewall": {
- "group_resync": ["nat", "policy_route"]
+ "conntrack": ["system_conntrack"],
+ "group_resync": ["system_conntrack", "nat", "policy_route"]
},
"interfaces_bonding": {
"ethernet": ["interfaces_ethernet"]
diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2
index 16a03fc6e..762a6f693 100644
--- a/data/templates/conntrack/nftables-ct.j2
+++ b/data/templates/conntrack/nftables-ct.j2
@@ -1,48 +1,160 @@
#!/usr/sbin/nft -f
-{% 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 ignore.rule is vyos_defined %}
-{% for rule, rule_config in ignore.rule.items() %}
+{% import 'conntrack/nftables-helpers.j2' as helper_tmpl %}
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
+
+{% 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 }}
-{% set nft_command = '' %}
-{% if rule_config.inbound_interface is vyos_defined %}
-{% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %}
-{% endif %}
-{% if rule_config.protocol is vyos_defined %}
-{% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %}
-{% endif %}
-{% if rule_config.destination.address is vyos_defined %}
-{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %}
-{% endif %}
-{% if rule_config.destination.port is vyos_defined %}
-{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %}
-{% endif %}
-{% if rule_config.source.address is vyos_defined %}
-{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %}
-{% endif %}
-{% if rule_config.source.port is vyos_defined %}
-{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %}
-{% endif %}
- {{ nft_command }} counter notrack comment ignore-{{ rule }}
+ {{ rule_config | conntrack_rule(rule, 'ignore', ipv6=False) }}
+{% endfor %}
+{% endif %}
+ return
+ }
+ chain VYOS_CT_TIMEOUT {
+{% 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
}
- chain {{ nft_ct_timeout_name }} {
-{% 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() %}
+ 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' %}
+ 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
+ }
+
+{{ helper_tmpl.conntrack_helpers(module_map, modules, ipv4=True) }}
+
+ chain FW_CONNTRACK {
+ {{ ipv4_firewall_action }}
+ }
+
+ chain NAT_CONNTRACK {
+ {{ ipv4_nat_action }}
+ }
+
+ chain WLB_CONNTRACK {
+ {{ wlb_action }}
+ }
+
+{% if firewall.group is vyos_defined %}
+{{ group_tmpl.groups(firewall.group, False, True) }}
+{% endif %}
+}
+
+{% 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 }}
+ {{ rule_config | conntrack_rule(rule, 'ignore', ipv6=True) }}
{% endfor %}
{% endif %}
return
}
+ chain VYOS_CT_TIMEOUT {
+{% 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' %}
+ 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
+ }
+
+{{ helper_tmpl.conntrack_helpers(module_map, modules, ipv4=False) }}
+
+ 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/conntrack/nftables-helpers.j2 b/data/templates/conntrack/nftables-helpers.j2
new file mode 100644
index 000000000..433931162
--- /dev/null
+++ b/data/templates/conntrack/nftables-helpers.j2
@@ -0,0 +1,70 @@
+{% macro conntrack_helpers(module_map, modules, ipv4=True) %}
+{% if modules.ftp is vyos_defined %}
+ ct helper ftp_tcp {
+ type "ftp" protocol tcp;
+ }
+{% endif %}
+
+{% if modules.h323 is vyos_defined %}
+ ct helper ras_udp {
+ type "RAS" protocol udp;
+ }
+
+ ct helper q931_tcp {
+ type "Q.931" protocol tcp;
+ }
+{% endif %}
+
+{% if modules.pptp is vyos_defined and ipv4 %}
+ ct helper pptp_tcp {
+ type "pptp" protocol tcp;
+ }
+{% endif %}
+
+{% if modules.nfs is vyos_defined %}
+ ct helper rpc_tcp {
+ type "rpc" protocol tcp;
+ }
+
+ ct helper rpc_udp {
+ type "rpc" protocol udp;
+ }
+{% endif %}
+
+{% if modules.sip is vyos_defined %}
+ ct helper sip_tcp {
+ type "sip" protocol tcp;
+ }
+
+ ct helper sip_udp {
+ type "sip" protocol udp;
+ }
+{% endif %}
+
+{% if modules.tftp is vyos_defined %}
+ ct helper tftp_udp {
+ type "tftp" protocol udp;
+ }
+{% endif %}
+
+{% if modules.sqlnet is vyos_defined %}
+ ct helper tns_tcp {
+ type "tns" protocol tcp;
+ }
+{% endif %}
+
+ chain VYOS_CT_HELPER {
+{% for module, module_conf in module_map.items() %}
+{% if modules[module] is vyos_defined %}
+{% if 'nftables' in module_conf %}
+{% if module_conf.ipv4 is not vyos_defined or module_conf.ipv4 == ipv4 %}
+{% for rule in module_conf.nftables %}
+ {{ rule }}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% endif %}
+{% endfor %}
+ return
+ }
+{% endmacro %}
diff --git a/data/templates/conntrack/sysctl.conf.j2 b/data/templates/conntrack/sysctl.conf.j2
index 9ea1ed2d8..986f75c61 100644
--- a/data/templates/conntrack/sysctl.conf.j2
+++ b/data/templates/conntrack/sysctl.conf.j2
@@ -24,3 +24,4 @@ net.netfilter.nf_conntrack_tcp_timeout_time_wait = {{ timeout.tcp.time_wait }}
net.netfilter.nf_conntrack_udp_timeout = {{ timeout.udp.other }}
net.netfilter.nf_conntrack_udp_timeout_stream = {{ timeout.udp.stream }}
+net.netfilter.nf_conntrack_acct = {{ '1' if flow_accounting is vyos_defined else '0' }}
diff --git a/data/templates/conntrack/vyos_nf_conntrack.conf.j2 b/data/templates/conntrack/vyos_nf_conntrack.conf.j2
index 290607662..1b12fec5f 100644
--- a/data/templates/conntrack/vyos_nf_conntrack.conf.j2
+++ b/data/templates/conntrack/vyos_nf_conntrack.conf.j2
@@ -1,2 +1,2 @@
# Autogenerated by system_conntrack.py
-options nf_conntrack hashsize={{ hash_size }} nf_conntrack_helper=1
+options nf_conntrack hashsize={{ hash_size }}
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/include/firewall/source-destination-group-ipv4.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i
new file mode 100644
index 000000000..8c34fb933
--- /dev/null
+++ b/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i
@@ -0,0 +1,41 @@
+<!-- include start from firewall/source-destination-group-ipv4.xml.i -->
+<node name="group">
+ <properties>
+ <help>Group</help>
+ </properties>
+ <children>
+ <leafNode name="address-group">
+ <properties>
+ <help>Group of addresses</help>
+ <completionHelp>
+ <path>firewall group address-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="domain-group">
+ <properties>
+ <help>Group of domains</help>
+ <completionHelp>
+ <path>firewall group domain-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="network-group">
+ <properties>
+ <help>Group of networks</help>
+ <completionHelp>
+ <path>firewall group network-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="port-group">
+ <properties>
+ <help>Group of ports</help>
+ <completionHelp>
+ <path>firewall group port-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/version/conntrack-version.xml.i b/interface-definitions/include/version/conntrack-version.xml.i
index 696f76362..6995ce119 100644
--- a/interface-definitions/include/version/conntrack-version.xml.i
+++ b/interface-definitions/include/version/conntrack-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/conntrack-version.xml.i -->
-<syntaxVersion component='conntrack' version='3'></syntaxVersion>
+<syntaxVersion component='conntrack' version='5'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/system_conntrack.xml.in b/interface-definitions/system_conntrack.xml.in
index ed5b7e8e0..a348097cc 100644
--- a/interface-definitions/system_conntrack.xml.in
+++ b/interface-definitions/system_conntrack.xml.in
@@ -9,6 +9,12 @@
<priority>218</priority>
</properties>
<children>
+ <leafNode name="flow-accounting">
+ <properties>
+ <help>Enable connection tracking flow accounting</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="expect-table-size">
<properties>
<help>Size of connection tracking expect table</help>
@@ -40,82 +46,179 @@
<help>Customized rules to ignore selective connection tracking</help>
</properties>
<children>
- <tagNode name="rule">
+ <node name="ipv4">
<properties>
- <help>Rule number</help>
- <valueHelp>
- <format>u32:1-999999</format>
- <description>Number of conntrack ignore 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">
+ <tagNode name="rule">
<properties>
- <help>Destination parameters</help>
+ <help>Rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of conntrack ignore 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/source-destination-group-ipv4.xml.i>
+ #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>
+ <leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/source-destination-group-ipv4.xml.i>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ #include <include/firewall/tcp-flags.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>
- <leafNode name="protocol">
+ </tagNode>
+ </children>
+ </node>
+ <node name="ipv6">
+ <properties>
+ <help>IPv6 rules</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
<properties>
- <help>Protocol to match (protocol name, number, or "all")</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_protocols.sh</script>
- <list>all tcp_udp</list>
- </completionHelp>
- <valueHelp>
- <format>all</format>
- <description>All IP protocols</description>
- </valueHelp>
- <valueHelp>
- <format>tcp_udp</format>
- <description>Both TCP and UDP</description>
- </valueHelp>
- <valueHelp>
- <format>u32:0-255</format>
- <description>IP protocol number</description>
- </valueHelp>
- <valueHelp>
- <format>&lt;protocol&gt;</format>
- <description>IP protocol name</description>
- </valueHelp>
+ <help>Rule number</help>
<valueHelp>
- <format>!&lt;protocol&gt;</format>
- <description>IP protocol name</description>
+ <format>u32:1-999999</format>
+ <description>Number of conntrack ignore rule</description>
</valueHelp>
<constraint>
- <validator name="ip-protocol"/>
+ <validator name="numeric" argument="--range 1-999999"/>
</constraint>
- </properties>
- </leafNode>
- <node name="source">
- <properties>
- <help>Source parameters</help>
+ <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/firewall/source-destination-group-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>
+ #include <include/ip-protocol.xml.i>
+ <leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ #include <include/firewall/tcp-flags.xml.i>
</children>
- </node>
+ </tagNode>
</children>
- </tagNode>
+ </node>
+
</children>
</node>
<node name="log">
@@ -282,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">
+ <tagNode name="rule">
<properties>
- <help>Destination 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/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>
- <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">
- <properties>
- <help>Customize protocol specific timers, one protocol configuration per rule</help>
- </properties>
- <children>
- #include <include/conntrack/timeout-common-protocols.xml.i>
- </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 b65386654..4b6dca254 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -659,6 +659,107 @@ 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_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 = []
+
+ if 'inbound_interface' in rule_conf:
+ ifname = rule_conf['inbound_interface']
+ if ifname != 'any':
+ output.append(f'iifname {ifname}')
+
+ if 'protocol' in rule_conf:
+ 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 and action != 'timeout':
+ 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]
+ prefix = side[0]
+
+ if 'address' in side_conf:
+ address = side_conf['address']
+ operator = ''
+ if address[0] == '!':
+ operator = '!='
+ address = address[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} {address}')
+
+ if 'port' in side_conf:
+ port = side_conf['port']
+ operator = ''
+ if port[0] == '!':
+ operator = '!='
+ port = port[1:]
+ output.append(f'th {prefix}port {operator} {port}')
+
+ if 'group' in side_conf:
+ group = side_conf['group']
+
+ if 'address_group' in group:
+ group_name = group['address_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @A{def_suffix}_{group_name}')
+ # Generate firewall group domain-group
+ elif 'domain_group' in group:
+ group_name = group['domain_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}')
+ elif 'network_group' in group:
+ group_name = group['network_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @N{def_suffix}_{group_name}')
+ if 'port_group' in group:
+ group_name = group['port_group']
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+
+ output.append(f'{proto} {prefix}port {operator} @P_{group_name}')
+
+ 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 2a89aa98b..0dbc97d49 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -35,6 +35,17 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ def verify_nftables(self, nftables_search, table, inverse=False, args=''):
+ nftables_output = cmd(f'sudo nft {args} list table {table}')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
def test_conntrack_options(self):
conntrack_config = {
'net.netfilter.nf_conntrack_expect_max' : {
@@ -151,27 +162,34 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
def test_conntrack_module_enable(self):
# conntrack helper modules are disabled by default
modules = {
- 'ftp' : {
- 'driver' : ['nf_nat_ftp', 'nf_conntrack_ftp'],
+ 'ftp': {
+ 'driver': ['nf_nat_ftp', 'nf_conntrack_ftp'],
+ 'nftables': ['ct helper set "ftp_tcp"']
},
- 'h323' : {
- 'driver' : ['nf_nat_h323', 'nf_conntrack_h323'],
+ 'h323': {
+ 'driver': ['nf_nat_h323', 'nf_conntrack_h323'],
+ 'nftables': ['ct helper set "ras_udp"',
+ 'ct helper set "q931_tcp"']
},
- 'nfs' : {
- 'nftables' : ['ct helper set "rpc_tcp"',
- 'ct helper set "rpc_udp"']
+ 'nfs': {
+ 'nftables': ['ct helper set "rpc_tcp"',
+ 'ct helper set "rpc_udp"']
},
- 'pptp' : {
- 'driver' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
+ 'pptp': {
+ 'driver': ['nf_nat_pptp', 'nf_conntrack_pptp'],
+ 'nftables': ['ct helper set "pptp_tcp"']
},
- 'sip' : {
- 'driver' : ['nf_nat_sip', 'nf_conntrack_sip'],
+ 'sip': {
+ 'driver': ['nf_nat_sip', 'nf_conntrack_sip'],
+ 'nftables': ['ct helper set "sip_tcp"',
+ 'ct helper set "sip_udp"']
},
- 'sqlnet' : {
- 'nftables' : ['ct helper set "tns_tcp"']
+ 'sqlnet': {
+ 'nftables': ['ct helper set "tns_tcp"']
},
- 'tftp' : {
- 'driver' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ 'tftp': {
+ 'driver': ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ 'nftables': ['ct helper set "tftp_udp"']
},
}
@@ -189,7 +207,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:
@@ -205,7 +223,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'
@@ -232,5 +250,96 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf')
self.assertIn(hash_size_default, tmp)
+ def test_conntrack_ignore(self):
+ address_group = 'conntracktest'
+ address_group_member = '192.168.0.1'
+ ipv6_address_group = 'conntracktest6'
+ ipv6_address_group_member = 'dead:beef::1'
+
+ self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member])
+ self.cli_set(['firewall', 'group', 'ipv6-address-group', ipv6_address_group, 'address', ipv6_address_group_member])
+
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1'])
+ 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])
+
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'port', '22'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'protocol', 'tcp'])
+
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'source', 'address', 'fe80::1'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'destination', 'group', 'address-group', ipv6_address_group])
+
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'source', 'address', 'fe80::1'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'destination', 'address', '!fe80::3'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['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']
+ ]
+
+ nftables6_search = [
+ ['ip6 saddr fe80::1', 'ip6 daddr fe80::2', 'tcp dport 22', 'notrack'],
+ ['ip6 saddr fe80::1', 'ip6 daddr @A6_conntracktest6', 'notrack'],
+ ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_conntrack')
+ self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack')
+
+ 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/system_conntrack.py b/src/conf_mode/system_conntrack.py
index 9c43640a9..7f6c71440 100755
--- a/src/conf_mode/system_conntrack.py
+++ b/src/conf_mode/system_conntrack.py
@@ -20,11 +20,13 @@ 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.configdep import set_dependents, call_dependents
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
from vyos.template import render
from vyos import ConfigError
@@ -38,34 +40,44 @@ nftables_ct_file = r'/run/nftables-ct.conf'
# Every ALG (Application Layer Gateway) consists of either a Kernel Object
# also called a Kernel Module/Driver or some rules present in iptables
module_map = {
- 'ftp' : {
- 'ko' : ['nf_nat_ftp', 'nf_conntrack_ftp'],
+ 'ftp': {
+ 'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'],
+ 'nftables': ['ct helper set "ftp_tcp" tcp dport {21} return']
},
- 'h323' : {
- 'ko' : ['nf_nat_h323', 'nf_conntrack_h323'],
+ 'h323': {
+ 'ko': ['nf_nat_h323', 'nf_conntrack_h323'],
+ 'nftables': ['ct helper set "ras_udp" udp dport {1719} return',
+ 'ct helper set "q931_tcp" tcp dport {1720} return']
},
- 'nfs' : {
- 'nftables' : ['ct helper set "rpc_tcp" tcp dport "{111}" return',
- 'ct helper set "rpc_udp" udp dport "{111}" return']
+ 'nfs': {
+ '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'],
+ 'pptp': {
+ 'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'],
+ 'nftables': ['ct helper set "pptp_tcp" tcp dport {1723} return'],
+ 'ipv4': True
},
- 'sip' : {
- 'ko' : ['nf_nat_sip', 'nf_conntrack_sip'],
+ 'sip': {
+ 'ko': ['nf_nat_sip', 'nf_conntrack_sip'],
+ 'nftables': ['ct helper set "sip_tcp" tcp dport {5060,5061} return',
+ 'ct helper set "sip_udp" udp dport {5060,5061} return']
},
- 'sqlnet' : {
- 'nftables' : ['ct helper set "tns_tcp" tcp dport "{1521,1525,1536}" return']
+ 'sqlnet': {
+ 'nftables': ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return']
},
- 'tftp' : {
- 'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ 'tftp': {
+ 'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ 'nftables': ['ct helper set "tftp_udp" udp dport {69} return']
},
}
-def resync_conntrackd():
- tmp = run('/usr/libexec/vyos/conf_mode/conntrack_sync.py')
- if tmp > 0:
- print('ERROR: error restarting conntrackd!')
+valid_groups = [
+ 'address_group',
+ 'domain_group',
+ 'network_group',
+ 'port_group'
+]
def get_config(config=None):
if config:
@@ -78,71 +90,141 @@ def get_config(config=None):
get_first_key=True,
with_recursive_defaults=True)
+ conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ 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
+
+ if conf.exists(['service', 'conntrack-sync']):
+ set_dependents('conntrack_sync', conf)
+
return conntrack
def verify(conntrack):
- if dict_search('ignore.rule', conntrack) != None:
- for rule, rule_config in conntrack['ignore']['rule'].items():
- if dict_search('destination.port', rule_config) or \
- dict_search('source.port', rule_config):
- 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}')
-
+ for inet in ['ipv4', 'ipv6']:
+ if dict_search_args(conntrack, 'ignore', inet, 'rule') != None:
+ for rule, rule_config in conntrack['ignore'][inet]['rule'].items():
+ if dict_search('destination.port', rule_config) or \
+ dict_search('destination.group.port_group', rule_config) or \
+ dict_search('source.port', rule_config) or \
+ dict_search('source.group.port_group', rule_config):
+ 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]
+
+ if 'group' in side_conf:
+ if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
+ raise ConfigError('Only one address-group, network-group or domain-group can be specified')
+
+ for group in valid_groups:
+ if group in side_conf['group']:
+ group_name = side_conf['group'][group]
+ error_group = group.replace("_", "-")
+
+ if group in ['address_group', 'network_group', 'domain_group']:
+ if 'address' in side_conf:
+ raise ConfigError(f'{error_group} and address cannot both be defined')
+
+ if group_name and group_name[0] == '!':
+ group_name = group_name[1:]
+
+ if inet == 'ipv6':
+ group = f'ipv6_{group}'
+
+ 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')
+
+ 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):
+ 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'
+
+ for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'):
+ if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' 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)
-
- # dry-run newly generated configuration
- tmp = run(f'nft -c -f {nftables_ct_file}')
- if tmp > 0:
- if os.path.exists(nftables_ct_file):
- os.unlink(nftables_ct_file)
- raise ConfigError('Configuration file errors encountered!')
-
return None
-def find_nftables_ct_rule(rule):
- helper_search = re.search('ct helper set "(\w+)"', rule)
- if helper_search:
- rule = helper_search[1]
- return find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule])
-
-def find_remove_rule(rule):
- handle = find_nftables_ct_rule(rule)
- if handle:
- remove_nftables_rule('raw', 'VYOS_CT_HELPER', 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.
+
+ add_modules = []
+ rm_modules = []
+
for module, module_config in module_map.items():
- if dict_search(f'modules.{module}', conntrack) is None:
+ if dict_search_args(conntrack, 'modules', module) is None:
if 'ko' in module_config:
- for mod in module_config['ko']:
- # 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(rule)
+ unloaded = [mod for mod in module_config['ko'] if os.path.exists(f'/sys/module/{mod}')]
+ rm_modules.extend(unloaded)
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(rule):
- cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}')
+ add_modules.extend(module_config['ko'])
+
+ # Add modules before nftables uses them
+ if add_modules:
+ module_str = ' '.join(add_modules)
+ cmd(f'modprobe -a {module_str}')
# Load new nftables ruleset
- cmd(f'nft -f {nftables_ct_file}')
+ install_result, output = rc_cmd(f'nft -f {nftables_ct_file}')
+ if install_result == 1:
+ raise ConfigError(f'Failed to apply configuration: {output}')
- if process_named_running('conntrackd'):
- # Reload conntrack-sync daemon to fetch new sysctl values
- resync_conntrackd()
+ # Remove modules after nftables stops using them
+ if rm_modules:
+ module_str = ' '.join(rm_modules)
+ cmd(f'rmmod {module_str}')
+
+ try:
+ call_dependents()
+ except ConfigError:
+ # Ignore config errors on dependent due to being called too early. Example:
+ # ConfigError("ConfigError('Interface ethN requires an IP address!')")
+ pass
# We silently ignore all errors
# See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080
diff --git a/src/init/vyos-router b/src/init/vyos-router
index ff95be994..aaecbf2a1 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -364,6 +364,9 @@ start ()
nfct helper add rpc inet tcp
nfct helper add rpc inet udp
nfct helper add tns inet tcp
+ nfct helper add rpc inet6 tcp
+ nfct helper add rpc inet6 udp
+ nfct helper add tns inet6 tcp
nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules"
# As VyOS does not execute commands that are not present in the CLI we call
diff --git a/src/migration-scripts/conntrack/3-to-4 b/src/migration-scripts/conntrack/3-to-4
new file mode 100755
index 000000000..e90c383af
--- /dev/null
+++ b/src/migration-scripts/conntrack/3-to-4
@@ -0,0 +1,50 @@
+#!/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/>.
+
+# Add support for IPv6 conntrack ignore, move existing nodes to `system conntrack ignore ipv4`
+
+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 + ['ignore', 'rule']):
+ config.set(base + ['ignore', 'ipv4'])
+ config.copy(base + ['ignore', 'rule'], base + ['ignore', 'ipv4', 'rule'])
+ config.delete(base + ['ignore', '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)
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)