From 5a8e3089b35e2eca2f896a01410fcdf6ac928278 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Sun, 3 Sep 2023 13:08:00 +0200
Subject: conntrack: T4309: T4903: Refactor `system conntrack ignore` rule
generation, add IPv6 support and firewall groups
---
data/config-mode-dependencies.json | 2 +-
data/templates/conntrack/nftables-ct.j2 | 54 +++---
data/vyos-firewall-init.conf | 31 +++
.../firewall/source-destination-group-ipv4.xml.i | 41 ++++
.../include/version/conntrack-version.xml.i | 2 +-
interface-definitions/system-conntrack.xml.in | 215 +++++++++++++++------
python/vyos/template.py | 78 ++++++++
smoketest/configs/basic-vyos | 12 ++
src/conf_mode/conntrack.py | 91 ++++++---
src/helpers/vyos-domain-resolver.py | 6 +-
src/init/vyos-router | 3 +
src/migration-scripts/conntrack/3-to-4 | 50 +++++
12 files changed, 476 insertions(+), 109 deletions(-)
create mode 100644 interface-definitions/include/firewall/source-destination-group-ipv4.xml.i
create mode 100755 src/migration-scripts/conntrack/3-to-4
diff --git a/data/config-mode-dependencies.json b/data/config-mode-dependencies.json
index 91a757c16..08732bd4c 100644
--- a/data/config-mode-dependencies.json
+++ b/data/config-mode-dependencies.json
@@ -1,5 +1,5 @@
{
- "firewall": {"group_resync": ["nat", "policy-route"]},
+ "firewall": {"group_resync": ["conntrack", "nat", "policy-route"]},
"http_api": {"https": ["https"]},
"pki": {
"ethernet": ["interfaces-ethernet"],
diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2
index 16a03fc6e..970869043 100644
--- a/data/templates/conntrack/nftables-ct.j2
+++ b/data/templates/conntrack/nftables-ct.j2
@@ -1,5 +1,7 @@
#!/usr/sbin/nft -f
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
+
{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %}
{% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %}
@@ -10,29 +12,35 @@ 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() %}
+{% 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) }}
+{% endfor %}
+{% endif %}
+ return
+ }
+ chain {{ nft_ct_timeout_name }} {
+{% 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 }}
+{% endfor %}
+{% endif %}
+ return
+ }
+
+{{ group_tmpl.groups(firewall_group, False) }}
+}
+
+flush chain ip6 raw {{ nft_ct_ignore_name }}
+flush chain ip6 raw {{ nft_ct_timeout_name }}
+
+table ip6 raw {
+ chain {{ nft_ct_ignore_name }} {
+{% 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 }}
-{% 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_ignore_rule(rule, ipv6=True) }}
{% endfor %}
{% endif %}
return
@@ -45,4 +53,6 @@ table raw {
{% endif %}
return
}
+
+{{ group_tmpl.groups(firewall_group, True) }}
}
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 41e7627f5..768031c83 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -88,6 +88,8 @@ table ip6 raw {
chain PREROUTING {
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
@@ -95,11 +97,40 @@ table ip6 raw {
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
}
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 @@
+
+
+
+ Group
+
+
+
+
+ Group of addresses
+
+ firewall group address-group
+
+
+
+
+
+ Group of domains
+
+ firewall group domain-group
+
+
+
+
+
+ Group of networks
+
+ firewall group network-group
+
+
+
+
+
+ Group of ports
+
+ firewall group port-group
+
+
+
+
+
+
diff --git a/interface-definitions/include/version/conntrack-version.xml.i b/interface-definitions/include/version/conntrack-version.xml.i
index 696f76362..c0f632c70 100644
--- a/interface-definitions/include/version/conntrack-version.xml.i
+++ b/interface-definitions/include/version/conntrack-version.xml.i
@@ -1,3 +1,3 @@
-
+
diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in
index 8dad048b8..3abf9bbf0 100644
--- a/interface-definitions/system-conntrack.xml.in
+++ b/interface-definitions/system-conntrack.xml.in
@@ -40,82 +40,177 @@
Customized rules to ignore selective connection tracking
-
+
- Rule number
-
- u32:1-999999
- Number of conntrack ignore rule
-
-
-
-
- Ignore rule number must be between 1 and 999999
+ IPv4 rules
- #include
-
+
- Destination parameters
+ Rule number
+
+ u32:1-999999
+ Number of conntrack ignore rule
+
+
+
+
+ Ignore rule number must be between 1 and 999999
- #include
- #include
+ #include
+
+
+ Destination parameters
+
+
+ #include
+ #include
+ #include
+
+
+
+
+ Interface to ignore connections tracking on
+
+ any
+
+
+
+
+ #include
+
+
+ Protocol to match (protocol name, number, or "all")
+
+
+ all tcp_udp
+
+
+ all
+ All IP protocols
+
+
+ tcp_udp
+ Both TCP and UDP
+
+
+ u32:0-255
+ IP protocol number
+
+
+ <protocol>
+ IP protocol name
+
+
+ !<protocol>
+ IP protocol name
+
+
+
+
+
+
+
+
+ Source parameters
+
+
+ #include
+ #include
+ #include
+
+
-
-
-
- Interface to ignore connections tracking on
-
- any
-
-
-
-
- #include
-
+
+
+
+
+
+ IPv6 rules
+
+
+
- Protocol to match (protocol name, number, or "all")
-
-
- all tcp_udp
-
-
- all
- All IP protocols
-
-
- tcp_udp
- Both TCP and UDP
-
-
- u32:0-255
- IP protocol number
-
-
- <protocol>
- IP protocol name
-
+ Rule number
- !<protocol>
- IP protocol name
+ u32:1-999999
+ Number of conntrack ignore rule
-
+
-
-
-
-
- Source parameters
+ Ignore rule number must be between 1 and 999999
- #include
- #include
+ #include
+
+
+ Destination parameters
+
+
+ #include
+ #include
+ #include
+
+
+
+
+ Interface to ignore connections tracking on
+
+ any
+
+
+
+
+ #include
+
+
+ Protocol to match (protocol name, number, or "all")
+
+
+ all tcp_udp
+
+
+ all
+ All IP protocols
+
+
+ tcp_udp
+ Both TCP and UDP
+
+
+ u32:0-255
+ IP protocol number
+
+
+ <protocol>
+ IP protocol name
+
+
+ !<protocol>
+ IP protocol name
+
+
+
+
+
+
+
+
+ Source parameters
+
+
+ #include
+ #include
+ #include
+
+
-
+
-
+
+
diff --git a/python/vyos/template.py b/python/vyos/template.py
index e167488c6..c1b57b883 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -663,6 +663,84 @@ 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):
+ 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']
+ output.append(f'iifname {ifname}')
+
+ if 'protocol' in rule_conf:
+ proto = rule_conf['protocol']
+ output.append(f'meta l4proto {proto}')
+
+ 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}')
+
+ output.append('counter notrack')
+ output.append(f'comment "ignore-{rule_id}"')
+
+ 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/configs/basic-vyos b/smoketest/configs/basic-vyos
index 033c1a518..78dba3ee2 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -116,6 +116,18 @@ system {
speed 115200
}
}
+ conntrack {
+ ignore {
+ rule 1 {
+ destination {
+ address 192.0.2.2
+ }
+ source {
+ address 192.0.2.1
+ }
+ }
+ }
+ }
host-name vyos
login {
user vyos {
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index 9c43640a9..a0de914bc 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -24,7 +24,9 @@ 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.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
@@ -62,6 +64,13 @@ module_map = {
},
}
+valid_groups = [
+ 'address_group',
+ 'domain_group',
+ 'network_group',
+ 'port_group'
+]
+
def resync_conntrackd():
tmp = run('/usr/libexec/vyos/conf_mode/conntrack_sync.py')
if tmp > 0:
@@ -78,15 +87,53 @@ def get_config(config=None):
get_first_key=True,
with_recursive_defaults=True)
+ conntrack['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
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}')
+
+ 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!')
return None
@@ -94,26 +141,18 @@ def generate(conntrack):
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):
+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('raw', 'VYOS_CT_HELPER', [rule])
+ return find_nftables_rule(table, chain, [rule])
-def find_remove_rule(rule):
- handle = find_nftables_ct_rule(rule)
+def find_remove_rule(table, chain, rule):
+ handle = find_nftables_ct_rule(table, chain, rule)
if handle:
- remove_nftables_rule('raw', 'VYOS_CT_HELPER', handle)
+ remove_nftables_rule(table, chain, handle)
def apply(conntrack):
# Depending on the enable/disable state of the ALG (Application Layer Gateway)
@@ -127,18 +166,24 @@ def apply(conntrack):
cmd(f'rmmod {mod}')
if 'nftables' in module_config:
for rule in module_config['nftables']:
- find_remove_rule(rule)
+ 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(rule):
- cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}')
+ 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
- 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
diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py
index 7e2fe2462..eac3d37af 100755
--- a/src/helpers/vyos-domain-resolver.py
+++ b/src/helpers/vyos-domain-resolver.py
@@ -37,12 +37,14 @@ domain_state = {}
ipv4_tables = {
'ip vyos_mangle',
'ip vyos_filter',
- 'ip vyos_nat'
+ 'ip vyos_nat',
+ 'ip raw'
}
ipv6_tables = {
'ip6 vyos_mangle',
- 'ip6 vyos_filter'
+ 'ip6 vyos_filter',
+ 'ip6 raw'
}
def get_config(conf):
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 96f163213..a5d1a31fa 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -335,6 +335,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"
rm -f /etc/hostname
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 .
+
+# 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)
--
cgit v1.2.3