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): |