diff options
| author | Christian Breunig <christian@breunig.cc> | 2023-09-06 20:26:06 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-06 20:26:06 +0200 | 
| commit | 50f3e9f66abfd7e0cad344c4c0fac0df7bc322df (patch) | |
| tree | 8d23d4fbc9763801f2a0259792509349b559b0a3 | |
| parent | c37f78087ba985e9c53c73ce51169dfbdbd5e553 (diff) | |
| parent | 2c88d01697eefbcd0188ec91dcbc589dee529db7 (diff) | |
| download | vyos-1x-50f3e9f66abfd7e0cad344c4c0fac0df7bc322df.tar.gz vyos-1x-50f3e9f66abfd7e0cad344c4c0fac0df7bc322df.zip | |
Merge pull request #2199 from sarthurdev/T4309
conntrack: T4309: T4903: Refactor `system conntrack ignore`, add IPv6 support and firewall groups
| -rw-r--r-- | data/config-mode-dependencies.json | 2 | ||||
| -rw-r--r-- | data/templates/conntrack/nftables-ct.j2 | 54 | ||||
| -rw-r--r-- | data/vyos-firewall-init.conf | 31 | ||||
| -rw-r--r-- | interface-definitions/include/firewall/source-destination-group-ipv4.xml.i | 41 | ||||
| -rw-r--r-- | interface-definitions/include/version/conntrack-version.xml.i | 2 | ||||
| -rw-r--r-- | interface-definitions/system-conntrack.xml.in | 215 | ||||
| -rw-r--r-- | python/vyos/template.py | 78 | ||||
| -rw-r--r-- | smoketest/configs/basic-vyos | 12 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_system_conntrack.py | 57 | ||||
| -rwxr-xr-x | src/conf_mode/conntrack.py | 91 | ||||
| -rwxr-xr-x | src/conf_mode/nat.py | 2 | ||||
| -rwxr-xr-x | src/helpers/vyos-domain-resolver.py | 6 | ||||
| -rwxr-xr-x | src/init/vyos-router | 3 | ||||
| -rwxr-xr-x | src/migration-scripts/conntrack/3-to-4 | 50 | 
14 files changed, 534 insertions, 110 deletions
| 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 b0026fdf3..7e258e6f1 100644 --- a/data/vyos-firewall-init.conf +++ b/data/vyos-firewall-init.conf @@ -102,6 +102,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 @@ -109,11 +111,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 @@ +<!-- 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..c0f632c70 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='4'></syntaxVersion>  <!-- include end --> 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 @@                <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><protocol></format> +                            <description>IP protocol name</description> +                          </valueHelp> +                          <valueHelp> +                            <format>!<protocol></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>                      </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><protocol></format> -                        <description>IP protocol name</description> -                      </valueHelp> +                      <help>Rule number</help>                        <valueHelp> -                        <format>!<protocol></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><protocol></format> +                            <description>IP protocol name</description> +                          </valueHelp> +                          <valueHelp> +                            <format>!<protocol></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>                      </children> -                  </node> +                  </tagNode>                  </children> -              </tagNode> +              </node> +                            </children>            </node>            <node name="log"> 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/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index 2a89aa98b..ea304783d 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' : { @@ -232,5 +243,51 @@ 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', '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', '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, 'raw') +        self.verify_nftables(nftables6_search, 'ip6 raw') + +        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 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/conf_mode/nat.py b/src/conf_mode/nat.py index 9da7fbe80..08e96f10b 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -112,7 +112,7 @@ def verify_rule(config, err_msg, groups_dict):                          group_obj = dict_search_args(groups_dict, group, group_name)                          if group_obj is None: -                            raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') +                            raise ConfigError(f'Invalid {error_group} "{group_name}" on nat rule')                          if not group_obj:                              Warning(f'{error_group} "{group_name}" has no members!') 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 <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) | 
