summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2023-09-19 18:48:09 +0200
committerGitHub <noreply@github.com>2023-09-19 18:48:09 +0200
commite570044ff8a81cbcfc340df5c8a33763a6962af3 (patch)
tree1ba3123b08fdb08dfc45295091a461cc39046dc0
parent38cab26959ded78a737db2272fe25106a2de47b0 (diff)
parent0984a36f6d6427bb6cb665002be3e67c4a72ff9d (diff)
downloadvyos-1x-e570044ff8a81cbcfc340df5c8a33763a6962af3.tar.gz
vyos-1x-e570044ff8a81cbcfc340df5c8a33763a6962af3.zip
Merge pull request #2288 from sarthurdev/flowtable
firewall: T4502: Update to flowtable CLI
-rw-r--r--data/templates/firewall/nftables-offload.j216
-rw-r--r--data/templates/firewall/nftables.j236
-rw-r--r--interface-definitions/firewall.xml.in40
-rw-r--r--interface-definitions/include/firewall/action-forward.xml.i45
-rw-r--r--interface-definitions/include/firewall/flow-offload.xml.i47
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i1
-rw-r--r--interface-definitions/include/firewall/ipv4-hook-forward.xml.i2
-rw-r--r--interface-definitions/include/firewall/ipv6-hook-forward.xml.i2
-rw-r--r--interface-definitions/include/firewall/offload-target.xml.i10
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in6
-rw-r--r--python/vyos/ethtool.py3
-rw-r--r--python/vyos/firewall.py26
-rw-r--r--python/vyos/ifconfig/ethernet.py26
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py34
-rwxr-xr-xsrc/conf_mode/firewall.py42
15 files changed, 231 insertions, 105 deletions
diff --git a/data/templates/firewall/nftables-offload.j2 b/data/templates/firewall/nftables-offload.j2
index 6afcd79f7..a893e05b2 100644
--- a/data/templates/firewall/nftables-offload.j2
+++ b/data/templates/firewall/nftables-offload.j2
@@ -1,11 +1,9 @@
-{% 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;
+{% macro flowtable(name, config) %}
+ flowtable VYOS_FLOWTABLE_{{ name }} {
+ hook ingress priority 0; devices = { {{ config.interface | join(', ') }} };
+{% if config.offload is vyos_defined('hardware') %}
+ flags offload;
{% endif %}
-{% if with_counter %}
- counter
-{% endif %}
-}
+ counter
+ }
{% endmacro %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 1564b3ef8..75800ee3d 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -2,7 +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 %}
+{% import 'firewall/nftables-offload.j2' as offload_tmpl %}
flush chain raw vyos_global_rpfilter
flush chain ip6 raw vyos_global_rpfilter
@@ -34,6 +34,12 @@ delete table ip vyos_filter
{% endif %}
table ip vyos_filter {
{% if ipv4 is vyos_defined %}
+{% if flowtable is vyos_defined %}
+{% for name, flowtable_conf in flowtable.items() %}
+{{ offload_tmpl.flowtable(name, flowtable_conf) }}
+{% endfor %}
+{% endif %}
+
{% set ns = namespace(sets=[]) %}
{% if ipv4.forward is vyos_defined %}
{% for prior, conf in ipv4.forward.items() %}
@@ -153,6 +159,12 @@ delete table ip6 vyos_filter
{% endif %}
table ip6 vyos_filter {
{% if ipv6 is vyos_defined %}
+{% if flowtable is vyos_defined %}
+{% for name, flowtable_conf in flowtable.items() %}
+{{ offload_tmpl.flowtable(name, flowtable_conf) }}
+{% endfor %}
+{% endif %}
+
{% set ns = namespace(sets=[]) %}
{% if ipv6.forward is vyos_defined %}
{% for prior, conf in ipv6.forward.items() %}
@@ -255,29 +267,7 @@ table ip6 vyos_filter {
{% if first_install is not vyos_defined %}
delete table bridge vyos_filter
{% endif %}
-{% if bridge is vyos_defined %}
table bridge vyos_filter {
{{ bridge_tmpl.bridge(bridge) }}
{{ group_tmpl.groups(group, False, False) }}
}
-{% endif %}
-
-{% if first_install is not vyos_defined %}
-delete table inet vyos_offload
-{% endif %}
-table inet vyos_offload {
-{% 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 %}
-}
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 8e462f3eb..81e6b89ea 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -7,6 +7,46 @@
</properties>
<children>
#include <include/firewall/global-options.xml.i>
+ <tagNode name="flowtable">
+ <properties>
+ <help>Flowtable</help>
+ <constraint>
+ <regex>[a-zA-Z0-9][\w\-\.]*</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ <leafNode name="interface">
+ <properties>
+ <help>Interfaces to use this flowtable</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="offload">
+ <properties>
+ <help>Offloading method</help>
+ <completionHelp>
+ <list>hardware software</list>
+ </completionHelp>
+ <valueHelp>
+ <format>hardware</format>
+ <description>Hardware offload</description>
+ </valueHelp>
+ <valueHelp>
+ <format>software</format>
+ <description>Software offload</description>
+ </valueHelp>
+ <constraint>
+ <regex>(hardware|software)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>software</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
<node name="group">
<properties>
<help>Firewall group</help>
diff --git a/interface-definitions/include/firewall/action-forward.xml.i b/interface-definitions/include/firewall/action-forward.xml.i
new file mode 100644
index 000000000..f61e51887
--- /dev/null
+++ b/interface-definitions/include/firewall/action-forward.xml.i
@@ -0,0 +1,45 @@
+<!-- include start from firewall/action-forward.xml.i -->
+<leafNode name="action">
+ <properties>
+ <help>Rule action</help>
+ <completionHelp>
+ <list>accept continue jump reject return drop queue offload</list>
+ </completionHelp>
+ <valueHelp>
+ <format>accept</format>
+ <description>Accept matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>continue</format>
+ <description>Continue parsing next rule</description>
+ </valueHelp>
+ <valueHelp>
+ <format>jump</format>
+ <description>Jump to another chain</description>
+ </valueHelp>
+ <valueHelp>
+ <format>reject</format>
+ <description>Reject matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>return</format>
+ <description>Return from the current chain and continue at the next rule of the last chain</description>
+ </valueHelp>
+ <valueHelp>
+ <format>drop</format>
+ <description>Drop matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>queue</format>
+ <description>Enqueue packet to userspace</description>
+ </valueHelp>
+ <valueHelp>
+ <format>offload</format>
+ <description>Offload packet via flowtable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(accept|continue|jump|reject|return|drop|queue|offload)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/flow-offload.xml.i b/interface-definitions/include/firewall/flow-offload.xml.i
deleted file mode 100644
index 706836362..000000000
--- a/interface-definitions/include/firewall/flow-offload.xml.i
+++ /dev/null
@@ -1,47 +0,0 @@
-<!-- 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 03c07e657..e655cd6ac 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -271,7 +271,6 @@
</properties>
<defaultValue>disable</defaultValue>
</leafNode>
- #include <include/firewall/flow-offload.xml.i>
</children>
</node>
<!-- include end -->
diff --git a/interface-definitions/include/firewall/ipv4-hook-forward.xml.i b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i
index 08ee96419..70c0adb77 100644
--- a/interface-definitions/include/firewall/ipv4-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i
@@ -24,8 +24,10 @@
<constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
+ #include <include/firewall/action-forward.xml.i>
#include <include/firewall/common-rule-ipv4.xml.i>
#include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/offload-target.xml.i>
#include <include/firewall/outbound-interface.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/include/firewall/ipv6-hook-forward.xml.i b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i
index 20ab8dbe8..d83827161 100644
--- a/interface-definitions/include/firewall/ipv6-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i
@@ -24,8 +24,10 @@
<constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
+ #include <include/firewall/action-forward.xml.i>
#include <include/firewall/common-rule-ipv6.xml.i>
#include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/offload-target.xml.i>
#include <include/firewall/outbound-interface.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/include/firewall/offload-target.xml.i b/interface-definitions/include/firewall/offload-target.xml.i
new file mode 100644
index 000000000..940ed8091
--- /dev/null
+++ b/interface-definitions/include/firewall/offload-target.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from firewall/offload-target.xml.i -->
+<leafNode name="offload-target">
+ <properties>
+ <help>Set flowtable offload target. Action offload must be defined to use this setting</help>
+ <completionHelp>
+ <path>firewall flowtable</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index 3669336fd..5aaa7095c 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -80,6 +80,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="hw-tc-offload">
+ <properties>
+ <help>Enable Hardware Flow Offload</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="lro">
<properties>
<help>Enable Large Receive Offload</help>
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index ca3bcfc3d..f19632719 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -172,6 +172,9 @@ class Ethtool:
def get_generic_segmentation_offload(self):
return self._get_generic('generic-segmentation-offload')
+ def get_hw_tc_offload(self):
+ return self._get_generic('hw-tc-offload')
+
def get_large_receive_offload(self):
return self._get_generic('large-receive-offload')
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 69ad11d1d..3ca7a25b9 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -401,20 +401,24 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if 'action' in rule_conf:
# Change action=return to action=action
# #output.append(nft_action(rule_conf['action']))
- output.append(f'{rule_conf["action"]}')
- if 'jump' in rule_conf['action']:
- target = rule_conf['jump_target']
- output.append(f'NAME{def_suffix}_{target}')
+ if rule_conf['action'] == 'offload':
+ offload_target = rule_conf['offload_target']
+ output.append(f'flow add @VYOS_FLOWTABLE_{offload_target}')
+ else:
+ output.append(f'{rule_conf["action"]}')
- if 'queue' in rule_conf['action']:
- if 'queue' in rule_conf:
- target = rule_conf['queue']
- output.append(f'num {target}')
+ if 'jump' in rule_conf['action']:
+ target = rule_conf['jump_target']
+ output.append(f'NAME{def_suffix}_{target}')
- if 'queue_options' in rule_conf:
- queue_opts = ','.join(rule_conf['queue_options'])
- output.append(f'{queue_opts}')
+ if 'queue' in rule_conf['action']:
+ if 'queue' in rule_conf:
+ target = rule_conf['queue']
+ output.append(f'num {target}')
+ if 'queue_options' in rule_conf:
+ queue_opts = ','.join(rule_conf['queue_options'])
+ output.append(f'{queue_opts}')
else:
output.append('return')
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 24ce3a803..285542057 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -57,6 +57,10 @@ class EthernetIf(Interface):
'validate': lambda v: assert_list(v, ['on', 'off']),
'possible': lambda i, v: EthernetIf.feature(i, 'gso', v),
},
+ 'hw-tc-offload': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'possible': lambda i, v: EthernetIf.feature(i, 'hw-tc-offload', v),
+ },
'lro': {
'validate': lambda v: assert_list(v, ['on', 'off']),
'possible': lambda i, v: EthernetIf.feature(i, 'lro', v),
@@ -222,6 +226,25 @@ class EthernetIf(Interface):
print('Adapter does not support changing generic-segmentation-offload settings!')
return False
+ def set_hw_tc_offload(self, state):
+ """
+ Enable hardware TC flow offload. State can be either True or False.
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_hw_tc_offload(True)
+ """
+ if not isinstance(state, bool):
+ raise ValueError('Value out of range')
+
+ enabled, fixed = self.ethtool.get_hw_tc_offload()
+ if enabled != state:
+ if not fixed:
+ return self.set_interface('hw-tc-offload', 'on' if state else 'off')
+ else:
+ print('Adapter does not support changing hw-tc-offload settings!')
+ return False
+
def set_lro(self, state):
"""
Enable Large Receive offload. State can be either True or False.
@@ -358,6 +381,9 @@ class EthernetIf(Interface):
# GSO (generic segmentation offload)
self.set_gso(dict_search('offload.gso', config) != None)
+ # GSO (generic segmentation offload)
+ self.set_hw_tc_offload(dict_search('offload.hw-tc-offload', config) != None)
+
# LRO (large receive offload)
self.set_lro(dict_search('offload.lro', config) != None)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 75d6e9bb1..72e04847a 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -603,17 +603,39 @@ 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'])
+ def test_flow_offload(self):
+ self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0'])
+ self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware'])
+
+ # QEMU virtual NIC does not support hw-tc-offload
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software'])
+
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'offload'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'established', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'related', 'enable'])
+
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'action', 'offload'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'established', 'enable'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'related', 'enable'])
+
self.cli_commit()
+
nftables_search = [
- ['flowtable VYOS_FLOWTABLE_software'],
- ['hook ingress priority filter - 1'],
+ ['flowtable VYOS_FLOWTABLE_smoketest'],
+ ['hook ingress priority filter'],
['devices = { eth0 }'],
- ['flow add @VYOS_FLOWTABLE_software'],
+ ['ct state { established, related }', 'meta l4proto { tcp, udp }', 'flow add @VYOS_FLOWTABLE_smoketest'],
]
- self.verify_nftables(nftables_search, 'inet vyos_offload')
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
+ self.verify_nftables(nftables_search, 'ip6 vyos_filter')
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index d999b2a64..3d799318e 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -27,6 +27,7 @@ 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.ethtool import Ethtool
from vyos.firewall import fqdn_config_parse
from vyos.firewall import geoip_update
from vyos.template import render
@@ -163,6 +164,15 @@ def verify_rule(firewall, rule_conf, ipv6):
if target not in dict_search_args(firewall, 'ipv6', 'name'):
raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system')
+ if rule_conf['action'] == 'offload':
+ if 'offload_target' not in rule_conf:
+ raise ConfigError('Action set to offload, but no offload-target specified')
+
+ offload_target = rule_conf['offload_target']
+
+ if not dict_search_args(firewall, 'flowtable', offload_target):
+ raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system')
+
if 'queue_options' in rule_conf:
if 'queue' not in rule_conf['action']:
raise ConfigError('queue-options defined, but action queue needed and it is not defined')
@@ -282,7 +292,31 @@ def verify_nested_group(group_name, group, groups, seen):
if 'include' in groups[g]:
verify_nested_group(g, groups[g], groups, seen)
+def verify_hardware_offload(ifname):
+ ethtool = Ethtool(ifname)
+ enabled, fixed = ethtool.get_hw_tc_offload()
+
+ if not enabled and fixed:
+ raise ConfigError(f'Interface "{ifname}" does not support hardware offload')
+
+ if not enabled:
+ raise ConfigError(f'Interface "{ifname}" requires "offload hw-tc-offload"')
+
def verify(firewall):
+ if 'flowtable' in firewall:
+ for flowtable, flowtable_conf in firewall['flowtable'].items():
+ if 'interface' not in flowtable_conf:
+ raise ConfigError(f'Flowtable "{flowtable}" requires at least one interface')
+
+ for ifname in flowtable_conf['interface']:
+ verify_interface_exists(ifname)
+
+ if dict_search_args(flowtable_conf, 'offload') == 'hardware':
+ interfaces = flowtable_conf['interface']
+
+ for ifname in interfaces:
+ verify_hardware_offload(ifname)
+
if 'group' in firewall:
for group_type in nested_group_types:
if group_type in firewall['group']:
@@ -330,14 +364,6 @@ 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):