diff options
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | data/templates/conntrack/sysctl.conf.j2 | 1 | ||||
| -rw-r--r-- | data/templates/firewall/nftables-offload.j2 | 11 | ||||
| -rw-r--r-- | data/templates/firewall/nftables.j2 | 24 | ||||
| -rw-r--r-- | data/templates/frr/daemons.frr.tmpl | 31 | ||||
| -rw-r--r-- | data/templates/openvpn/server.conf.j2 | 11 | ||||
| -rw-r--r-- | interface-definitions/include/firewall/flow-offload.xml.i | 47 | ||||
| -rw-r--r-- | interface-definitions/include/firewall/global-options.xml.i | 1 | ||||
| -rw-r--r-- | interface-definitions/system-conntrack.xml.in | 6 | ||||
| -rw-r--r-- | op-mode-definitions/show-techsupport_report.xml.in | 3 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_firewall.py | 12 | ||||
| -rwxr-xr-x | src/conf_mode/firewall.py | 37 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 7 | 
13 files changed, 154 insertions, 38 deletions
| @@ -62,7 +62,6 @@ op_mode_definitions: $(op_xml_obj)  	rm -f $(OP_TMPL_DIR)/delete/node.def  	rm -f $(OP_TMPL_DIR)/generate/node.def  	rm -f $(OP_TMPL_DIR)/set/node.def -	rm -f $(OP_TMPL_DIR)/show/tech-support/node.def  	# XXX: ping and traceroute must be able to recursivly call itself as the  	# options are provided from the script itself diff --git a/data/templates/conntrack/sysctl.conf.j2 b/data/templates/conntrack/sysctl.conf.j2 index 075402c04..3d6fc43f2 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/firewall/nftables-offload.j2 b/data/templates/firewall/nftables-offload.j2 new file mode 100644 index 000000000..6afcd79f7 --- /dev/null +++ b/data/templates/firewall/nftables-offload.j2 @@ -0,0 +1,11 @@ +{% macro render_flowtable(name, devices, priority='filter', hardware_offload=false, with_counter=true) %} +flowtable {{ name }} { +    hook ingress priority {{ priority }}; devices = { {{ devices | join(', ') }} }; +{% if hardware_offload %} +    flags offload; +{% endif %} +{% if with_counter %} +    counter +{% endif %} +} +{% endmacro %} diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 87630940b..1b764c9da 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -2,6 +2,7 @@  {% import 'firewall/nftables-defines.j2' as group_tmpl %}  {% import 'firewall/nftables-bridge.j2' as bridge_tmpl %} +{% import 'firewall/nftables-offload.j2' as offload %}  flush chain raw FW_CONNTRACK  flush chain ip6 raw FW_CONNTRACK @@ -271,3 +272,26 @@ table bridge vyos_filter {  {{ group_tmpl.groups(group, False, False) }}  }  {% endif %} +{{ group_tmpl.groups(group, True) }} +} + +table inet vyos_offload +delete table inet vyos_offload +table inet vyos_offload { +{% if flowtable_enabled %} +{%     if global_options.flow_offload.hardware.interface is vyos_defined %} +    {{- offload.render_flowtable('VYOS_FLOWTABLE_hardware', global_options.flow_offload.hardware.interface | list, priority='filter - 2', hardware_offload=true) }} +    chain VYOS_OFFLOAD_hardware { +        type filter hook forward priority filter - 2; policy accept; +        ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_hardware +    } +{%     endif %} +{%     if global_options.flow_offload.software.interface is vyos_defined %} +    {{- offload.render_flowtable('VYOS_FLOWTABLE_software', global_options.flow_offload.software.interface | list, priority='filter - 1') }} +    chain VYOS_OFFLOAD_software { +        type filter hook forward priority filter - 1; policy accept; +        ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_software +    } +{%     endif %} +{% endif %} +} diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl index e09c7d1d2..fe2610724 100644 --- a/data/templates/frr/daemons.frr.tmpl +++ b/data/templates/frr/daemons.frr.tmpl @@ -17,40 +17,41 @@ bfdd=yes  staticd=yes  vtysh_enable=yes -zebra_options="  -s 90000000 --daemon -A 127.0.0.1 +zebra_options="   --daemon -A 127.0.0.1 -s 90000000  {%- if irdp is defined %} -M irdp{% endif -%}  {%- if snmp is defined and snmp.zebra is defined %} -M snmp{% endif -%}  " -bgpd_options="   --daemon -A 127.0.0.1 +bgpd_options="    --daemon -A 127.0.0.1 -M rpki  {%- if bmp is defined %} -M bmp{% endif -%}  {%- if snmp is defined and snmp.bgpd is defined %} -M snmp{% endif -%}  " -ospfd_options="  --daemon -A 127.0.0.1 +ospfd_options="   --daemon -A 127.0.0.1  {%- if snmp is defined and snmp.ospfd is defined %} -M snmp{% endif -%}  " -ospf6d_options=" --daemon -A ::1 +ospf6d_options="  --daemon -A ::1  {%- if snmp is defined and snmp.ospf6d is defined %} -M snmp{% endif -%}  " -ripd_options="   --daemon -A 127.0.0.1 +ripd_options="    --daemon -A 127.0.0.1  {%- if snmp is defined and snmp.ripd is defined %} -M snmp{% endif -%}  " -ripngd_options=" --daemon -A ::1" -isisd_options="  --daemon -A 127.0.0.1 +ripngd_options="  --daemon -A ::1" +isisd_options="   --daemon -A 127.0.0.1  {%- if snmp is defined and snmp.isisd is defined %} -M snmp{% endif -%}  " -pimd_options="  --daemon -A 127.0.0.1" -pim6d_options=" --daemon -A ::1" -ldpd_options="  --daemon -A 127.0.0.1 +pimd_options="    --daemon -A 127.0.0.1" +pim6d_options="   --daemon -A ::1" +ldpd_options="    --daemon -A 127.0.0.1  {%- if snmp is defined and snmp.ldpd is defined %} -M snmp{% endif -%}  " -mgmtd_options=" --daemon -A 127.0.0.1" -nhrpd_options="  --daemon -A 127.0.0.1" +mgmtd_options="   --daemon -A 127.0.0.1" +nhrpd_options="   --daemon -A 127.0.0.1"  eigrpd_options="  --daemon -A 127.0.0.1"  babeld_options="  --daemon -A 127.0.0.1"  sharpd_options="  --daemon -A 127.0.0.1" -pbrd_options="  --daemon -A 127.0.0.1" -staticd_options="  --daemon -A 127.0.0.1" -bfdd_options="  --daemon -A 127.0.0.1" +pbrd_options="    --daemon -A 127.0.0.1" +staticd_options=" --daemon -A 127.0.0.1" +bfdd_options="    --daemon -A 127.0.0.1"  watchfrr_enable=no  valgrind_enable=no + diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2 index f76fbbe79..2eb9416fe 100644 --- a/data/templates/openvpn/server.conf.j2 +++ b/data/templates/openvpn/server.conf.j2 @@ -74,7 +74,7 @@ topology {{ server.topology }}  {%             endif %}  {%             for subnet in server.subnet %}  {%                 if subnet | is_ipv4 %} -server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} nopool +server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} {{ 'nopool' if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined else '' }}  {# First ip address is used as gateway. It's allows to use metrics #}  {%                     if server.push_route is vyos_defined %}  {%                         for route, route_config in server.push_route.items() %} @@ -85,15 +85,6 @@ push "route-ipv6 {{ route }}"  {%                             endif %}  {%                         endfor %}  {%                     endif %} -{# OpenVPN assigns the first IP address to its local interface so the pool used #} -{# in net30 topology - where each client receives a /30 must start from the second subnet #} -{%                     if server.topology is vyos_defined('net30') %} -ifconfig-pool {{ subnet | inc_ip('4') }} {{ subnet | last_host_address | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tap' else '' }} -{%                     else %} -{# OpenVPN assigns the first IP address to its local interface so the pool must #} -{# start from the second address and end on the last address #} -ifconfig-pool {{ subnet | first_host_address | inc_ip('1') }} {{ subnet | last_host_address | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tun' else '' }} -{%                     endif %}  {%                 elif subnet | is_ipv6 %}  server-ipv6 {{ subnet }}  {%                 endif %} diff --git a/interface-definitions/include/firewall/flow-offload.xml.i b/interface-definitions/include/firewall/flow-offload.xml.i new file mode 100644 index 000000000..706836362 --- /dev/null +++ b/interface-definitions/include/firewall/flow-offload.xml.i @@ -0,0 +1,47 @@ +<!-- include start from firewall/flow-offload.xml.i --> +<node name="flow-offload"> +  <properties> +    <help>Configurable flow offload options</help> +  </properties> +  <children> +    <leafNode name="disable"> +      <properties> +        <help>Disable flow offload</help> +        <valueless/> +      </properties> +    </leafNode> +    <node name="software"> +      <properties> +        <help>Software offload</help> +      </properties> +      <children> +        <leafNode name="interface"> +          <properties> +            <help>Interfaces to enable</help> +            <completionHelp> +              <script>${vyos_completion_dir}/list_interfaces</script> +            </completionHelp> +            <multi/> +          </properties> +        </leafNode> +      </children> +    </node> +    <node name="hardware"> +      <properties> +        <help>Hardware offload</help> +      </properties> +      <children> +        <leafNode name="interface"> +          <properties> +            <help>Interfaces to enable</help> +            <completionHelp> +              <script>${vyos_completion_dir}/list_interfaces</script> +            </completionHelp> +            <multi/> +          </properties> +        </leafNode> +      </children> +    </node> +  </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i index e655cd6ac..03c07e657 100644 --- a/interface-definitions/include/firewall/global-options.xml.i +++ b/interface-definitions/include/firewall/global-options.xml.i @@ -271,6 +271,7 @@        </properties>        <defaultValue>disable</defaultValue>      </leafNode> +    #include <include/firewall/flow-offload.xml.i>    </children>  </node>  <!-- include end --> diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index 3abf9bbf0..78d19090c 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> diff --git a/op-mode-definitions/show-techsupport_report.xml.in b/op-mode-definitions/show-techsupport_report.xml.in index aa51eacd9..ef051e940 100644 --- a/op-mode-definitions/show-techsupport_report.xml.in +++ b/op-mode-definitions/show-techsupport_report.xml.in @@ -3,6 +3,9 @@    <node name="show">      <children>        <node name="tech-support"> +        <properties> +          <help>Show tech-support report</help> +        </properties>          <children>            <node name="report">              <properties> diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index f74ce4b72..391ef03ff 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -603,5 +603,17 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):                  with open(path, 'r') as f:                      self.assertNotEqual(f.read().strip(), conf['default'], msg=path) +    def test_flow_offload_software(self): +        self.cli_set(['firewall', 'global-options', 'flow-offload', 'software', 'interface', 'eth0']) +        self.cli_commit() +        nftables_search = [ +            ['flowtable VYOS_FLOWTABLE_software'], +            ['hook ingress priority filter - 1'], +            ['devices = { eth0 }'], +            ['flow add @VYOS_FLOWTABLE_software'], +        ] +        self.verify_nftables(nftables_search, 'inet vyos_offload') + +  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index c3b1ee015..769cc598f 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -26,7 +26,7 @@ from vyos.config import Config  from vyos.configdict import node_changed  from vyos.configdiff import get_config_diff, Diff  from vyos.configdep import set_dependents, call_dependents -# from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_interface_exists  from vyos.firewall import fqdn_config_parse  from vyos.firewall import geoip_update  from vyos.template import render @@ -38,6 +38,7 @@ from vyos.utils.process import process_named_running  from vyos.utils.process import rc_cmd  from vyos import ConfigError  from vyos import airbag +  airbag.enable()  nat_conf_script = 'nat.py' @@ -100,7 +101,7 @@ def geoip_updated(conf, firewall):          elif (path[0] == 'ipv6'):              set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'              out['ipv6_name'].append(set_name) -             +          updated = True      if 'delete' in node_diff: @@ -140,6 +141,14 @@ def get_config(config=None):      fqdn_config_parse(firewall) +    firewall['flowtable_enabled'] = False +    flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload') +    if flow_offload and 'disable' not in flow_offload: +        for offload_type in ('software', 'hardware'): +            if dict_search_args(flow_offload, offload_type, 'interface'): +                firewall['flowtable_enabled'] = True +                break +      return firewall  def verify_rule(firewall, rule_conf, ipv6): @@ -327,6 +336,14 @@ def verify(firewall):                          for rule_id, rule_conf in name_conf['rule'].items():                              verify_rule(firewall, rule_conf, True) +    # Verify flow offload options +    flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload') +    for offload_type in ('software', 'hardware'): +        interfaces = dict_search_args(flow_offload, offload_type, 'interface') or [] +        for interface in interfaces: +            # nft will raise an error when adding a non-existent interface to a flowtable +            verify_interface_exists(interface) +      return None  def generate(firewall): @@ -336,13 +353,15 @@ def generate(firewall):      # Determine if conntrack is needed      firewall['ipv4_conntrack_action'] = 'return'      firewall['ipv6_conntrack_action'] = 'return' - -    for rules, path in dict_search_recursive(firewall, 'rule'): -        if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): -            if path[0] == 'ipv4': -                firewall['ipv4_conntrack_action'] = 'accept' -            elif path[0] == 'ipv6': -                firewall['ipv6_conntrack_action'] = 'accept' +    if firewall['flowtable_enabled']:  # Netfilter's flowtable offload requires conntrack +        firewall['ipv4_conntrack_action'] = 'accept' +        firewall['ipv6_conntrack_action'] = 'accept' +    else:  # Check if conntrack is needed by firewall rules +        for proto in ('ipv4', 'ipv6'): +            for rules, _ in dict_search_recursive(firewall.get(proto, {}), 'rule'): +                if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): +                    firewall[f'{proto}_conntrack_action'] = 'accept' +                    break      render(nftables_conf, 'firewall/nftables.j2', firewall)      return None diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 1d0feb56f..9f4de990c 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -344,9 +344,6 @@ def verify(openvpn):              if v6_subnets > 1:                  raise ConfigError('Cannot specify more than 1 IPv6 server subnet') -            if v6_subnets > 0 and v4_subnets == 0: -                raise ConfigError('IPv6 server requires an IPv4 server subnet') -              for subnet in tmp:                  if is_ipv4(subnet):                      subnet = IPv4Network(subnet) @@ -388,6 +385,10 @@ def verify(openvpn):                          for v4PoolNet in v4PoolNets:                              if IPv4Address(client['ip'][0]) in v4PoolNet:                                  print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.') +            # configuring a client_ip_pool will set 'server ... nopool' which is currently incompatible with 'server-ipv6' (probably to be fixed upstream) +            for subnet in (dict_search('server.subnet', openvpn) or []): +                if is_ipv6(subnet): +                    raise ConfigError(f'Setting client-ip-pool is incompatible having an IPv6 server subnet.')          for subnet in (dict_search('server.subnet', openvpn) or []):              if is_ipv6(subnet): | 
