summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2024-12-24 14:23:20 +0100
committerGitHub <noreply@github.com>2024-12-24 14:23:20 +0100
commit77d5b096bb91964b7abc9fc495f574333184edbf (patch)
tree3e0e92fcb66c1b1dadf25a544273769569966829
parentb05cbfa71570bce1f4f470551e5c0217d14ba5a8 (diff)
parent9d53f47a305a39caf15c73cacaf29177cebbaf14 (diff)
downloadvyos-1x-77d5b096bb91964b7abc9fc495f574333184edbf.tar.gz
vyos-1x-77d5b096bb91964b7abc9fc495f574333184edbf.zip
Merge pull request #4235 from nvollmar/T6944
T6944: adds option to enable switchdev mode on ethernet interface
-rw-r--r--interface-definitions/interfaces_ethernet.xml.in6
-rw-r--r--python/vyos/ifconfig/ethernet.py173
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py43
3 files changed, 156 insertions, 66 deletions
diff --git a/interface-definitions/interfaces_ethernet.xml.in b/interface-definitions/interfaces_ethernet.xml.in
index 89f990d41..b3559a626 100644
--- a/interface-definitions/interfaces_ethernet.xml.in
+++ b/interface-definitions/interfaces_ethernet.xml.in
@@ -56,6 +56,12 @@
</properties>
<defaultValue>auto</defaultValue>
</leafNode>
+ <leafNode name="switchdev">
+ <properties>
+ <help>Enables switchdev mode on interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
#include <include/interface/eapol.xml.i>
<node name="evpn">
<properties>
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 50dd0f396..d0c03dbe0 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -26,11 +26,13 @@ from vyos.utils.file import read_file
from vyos.utils.process import run
from vyos.utils.assertion import assert_list
+
@Interface.register
class EthernetIf(Interface):
"""
Abstraction of a Linux Ethernet Interface
"""
+
iftype = 'ethernet'
definition = {
**Interface.definition,
@@ -41,7 +43,7 @@ class EthernetIf(Interface):
'broadcast': True,
'bridgeable': True,
'eternal': '(lan|eth|eno|ens|enp|enx)[0-9]+$',
- }
+ },
}
@staticmethod
@@ -49,32 +51,35 @@ class EthernetIf(Interface):
run(f'ethtool --features {ifname} {option} {value}')
return False
- _command_set = {**Interface._command_set, **{
- 'gro': {
- 'validate': lambda v: assert_list(v, ['on', 'off']),
- 'possible': lambda i, v: EthernetIf.feature(i, 'gro', v),
- },
- 'gso': {
- '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),
- },
- 'sg': {
- 'validate': lambda v: assert_list(v, ['on', 'off']),
- 'possible': lambda i, v: EthernetIf.feature(i, 'sg', v),
- },
- 'tso': {
- 'validate': lambda v: assert_list(v, ['on', 'off']),
- 'possible': lambda i, v: EthernetIf.feature(i, 'tso', v),
+ _command_set = {
+ **Interface._command_set,
+ **{
+ 'gro': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'possible': lambda i, v: EthernetIf.feature(i, 'gro', v),
+ },
+ 'gso': {
+ '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),
+ },
+ 'sg': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'possible': lambda i, v: EthernetIf.feature(i, 'sg', v),
+ },
+ 'tso': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'possible': lambda i, v: EthernetIf.feature(i, 'tso', v),
+ },
},
- }}
+ }
@staticmethod
def get_bond_member_allowed_options() -> list:
@@ -106,7 +111,7 @@ class EthernetIf(Interface):
'ring_buffer.rx',
'ring_buffer.tx',
'speed',
- 'hw_id'
+ 'hw_id',
]
return bond_allowed_sections
@@ -130,7 +135,11 @@ class EthernetIf(Interface):
self.set_admin_state('down')
# Remove all VLAN subinterfaces - filter with the VLAN dot
- for vlan in [x for x in Section.interfaces(self.iftype) if x.startswith(f'{self.ifname}.')]:
+ for vlan in [
+ x
+ for x in Section.interfaces(self.iftype)
+ if x.startswith(f'{self.ifname}.')
+ ]:
Interface(vlan).remove()
super().remove()
@@ -149,10 +158,12 @@ class EthernetIf(Interface):
ifname = self.config['ifname']
if enable not in ['on', 'off']:
- raise ValueError("Value out of range")
+ raise ValueError('Value out of range')
if not self.ethtool.check_flow_control():
- self._debug_msg(f'NIC driver does not support changing flow control settings!')
+ self._debug_msg(
+ 'NIC driver does not support changing flow control settings!'
+ )
return False
current = self.ethtool.get_flow_control()
@@ -180,12 +191,24 @@ class EthernetIf(Interface):
"""
ifname = self.config['ifname']
- if speed not in ['auto', '10', '100', '1000', '2500', '5000', '10000',
- '25000', '40000', '50000', '100000', '400000']:
- raise ValueError("Value out of range (speed)")
+ if speed not in [
+ 'auto',
+ '10',
+ '100',
+ '1000',
+ '2500',
+ '5000',
+ '10000',
+ '25000',
+ '40000',
+ '50000',
+ '100000',
+ '400000',
+ ]:
+ raise ValueError('Value out of range (speed)')
if duplex not in ['auto', 'full', 'half']:
- raise ValueError("Value out of range (duplex)")
+ raise ValueError('Value out of range (duplex)')
if not self.ethtool.check_speed_duplex(speed, duplex):
Warning(f'changing speed/duplex setting on "{ifname}" is unsupported!')
@@ -224,7 +247,9 @@ class EthernetIf(Interface):
# but they do not actually support it either.
# In that case it's probably better to ignore the error
# than end up with a broken config.
- print('Warning: could not set speed/duplex settings: operation not permitted!')
+ print(
+ 'Warning: could not set speed/duplex settings: operation not permitted!'
+ )
def set_gro(self, state):
"""
@@ -243,7 +268,9 @@ class EthernetIf(Interface):
if not fixed:
return self.set_interface('gro', 'on' if state else 'off')
else:
- print('Adapter does not support changing generic-receive-offload settings!')
+ print(
+ 'Adapter does not support changing generic-receive-offload settings!'
+ )
return False
def set_gso(self, state):
@@ -262,7 +289,9 @@ class EthernetIf(Interface):
if not fixed:
return self.set_interface('gso', 'on' if state else 'off')
else:
- print('Adapter does not support changing generic-segmentation-offload settings!')
+ print(
+ 'Adapter does not support changing generic-segmentation-offload settings!'
+ )
return False
def set_hw_tc_offload(self, state):
@@ -300,7 +329,9 @@ class EthernetIf(Interface):
if not fixed:
return self.set_interface('lro', 'on' if state else 'off')
else:
- print('Adapter does not support changing large-receive-offload settings!')
+ print(
+ 'Adapter does not support changing large-receive-offload settings!'
+ )
return False
def set_rps(self, state):
@@ -332,13 +363,15 @@ class EthernetIf(Interface):
for i in range(0, cpu_count, 32):
# Extract the next 32-bit chunk
chunk = (rps_cpus >> i) & 0xFFFFFFFF
- hex_chunks.append(f"{chunk:08x}")
+ hex_chunks.append(f'{chunk:08x}')
# Join the chunks with commas
- rps_cpus = ",".join(hex_chunks)
+ rps_cpus = ','.join(hex_chunks)
for i in range(queues):
- self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus)
+ self._write_sysfs(
+ f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus
+ )
# send bitmask representation as hex string without leading '0x'
return True
@@ -348,10 +381,13 @@ class EthernetIf(Interface):
queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))
if state:
global_rfs_flow = 32768
- rfs_flow = int(global_rfs_flow/queues)
+ rfs_flow = int(global_rfs_flow / queues)
for i in range(0, queues):
- self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', rfs_flow)
+ self._write_sysfs(
+ f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt',
+ rfs_flow,
+ )
return True
@@ -392,7 +428,9 @@ class EthernetIf(Interface):
if not fixed:
return self.set_interface('tso', 'on' if state else 'off')
else:
- print('Adapter does not support changing tcp-segmentation-offload settings!')
+ print(
+ 'Adapter does not support changing tcp-segmentation-offload settings!'
+ )
return False
def set_ring_buffer(self, rx_tx, size):
@@ -417,39 +455,64 @@ class EthernetIf(Interface):
print(f'could not set "{rx_tx}" ring-buffer for {ifname}')
return output
+ def set_switchdev(self, enable):
+ ifname = self.config['ifname']
+ addr, code = self._popen(
+ f"ethtool -i {ifname} | grep bus-info | awk '{{print $2}}'"
+ )
+ if code != 0:
+ print(f'could not resolve PCIe address of {ifname}')
+ return
+
+ enabled = False
+ state, code = self._popen(
+ f"/sbin/devlink dev eswitch show pci/{addr} | awk '{{print $3}}'"
+ )
+ if code == 0 and state == 'switchdev':
+ enabled = True
+
+ if enable and not enabled:
+ output, code = self._popen(
+ f'/sbin/devlink dev eswitch set pci/{addr} mode switchdev'
+ )
+ if code != 0:
+ print(f'{ifname} does not support switchdev mode')
+ elif not enable and enabled:
+ self._cmd(f'/sbin/devlink dev eswitch set pci/{addr} mode legacy')
+
def update(self, config):
- """ General helper function which works on a dictionary retrived by
+ """General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
interface setup code and provide a single point of entry when workin
- on any interface. """
+ on any interface."""
# disable ethernet flow control (pause frames)
value = 'off' if 'disable_flow_control' in config else 'on'
self.set_flow_control(value)
# GRO (generic receive offload)
- self.set_gro(dict_search('offload.gro', config) != None)
+ self.set_gro(dict_search('offload.gro', config) is not None)
# GSO (generic segmentation offload)
- self.set_gso(dict_search('offload.gso', config) != None)
+ self.set_gso(dict_search('offload.gso', config) is not None)
# GSO (generic segmentation offload)
- self.set_hw_tc_offload(dict_search('offload.hw_tc_offload', config) != None)
+ self.set_hw_tc_offload(dict_search('offload.hw_tc_offload', config) is not None)
# LRO (large receive offload)
- self.set_lro(dict_search('offload.lro', config) != None)
+ self.set_lro(dict_search('offload.lro', config) is not None)
# RPS - Receive Packet Steering
- self.set_rps(dict_search('offload.rps', config) != None)
+ self.set_rps(dict_search('offload.rps', config) is not None)
# RFS - Receive Flow Steering
- self.set_rfs(dict_search('offload.rfs', config) != None)
+ self.set_rfs(dict_search('offload.rfs', config) is not None)
# scatter-gather option
- self.set_sg(dict_search('offload.sg', config) != None)
+ self.set_sg(dict_search('offload.sg', config) is not None)
# TSO (TCP segmentation offloading)
- self.set_tso(dict_search('offload.tso', config) != None)
+ self.set_tso(dict_search('offload.tso', config) is not None)
# Set physical interface speed and duplex
if 'speed_duplex_changed' in config:
@@ -463,6 +526,8 @@ class EthernetIf(Interface):
for rx_tx, size in config['ring_buffer'].items():
self.set_ring_buffer(rx_tx, size)
+ self.set_switchdev('switchdev' in config)
+
# call base class last
super().update(config)
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index c02ca613b..183c10250 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -28,10 +28,12 @@ from base_interfaces_test import BasicInterfaceTest
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.frrender import mgmt_daemon
-from vyos.utils.process import cmd
-from vyos.utils.process import popen
from vyos.utils.file import read_file
+from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import is_ipv6_link_local
+from vyos.utils.process import cmd
+from vyos.utils.process import popen
+
class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
@@ -78,14 +80,18 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
continue
self.assertFalse(is_intf_addr_assigned(interface, addr['addr']))
# Ensure no VLAN interfaces are left behind
- tmp = [x for x in Section.interfaces('ethernet') if x.startswith(f'{interface}.')]
+ tmp = [
+ x
+ for x in Section.interfaces('ethernet')
+ if x.startswith(f'{interface}.')
+ ]
self.assertListEqual(tmp, [])
def test_offloading_rps(self):
# enable RPS on all available CPUs, RPS works with a CPU bitmask,
# where each bit represents a CPU (core/thread). The formula below
# expands to rps_cpus = 255 for a 8 core system
- rps_cpus = (1 << os.cpu_count()) -1
+ rps_cpus = (1 << os.cpu_count()) - 1
# XXX: we should probably reserve one core when the system is under
# high preasure so we can still have a core left for housekeeping.
@@ -101,7 +107,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
for interface in self._interfaces:
cpus = read_file(f'/sys/class/net/{interface}/queues/rx-0/rps_cpus')
# remove the nasty ',' separation on larger strings
- cpus = cpus.replace(',','')
+ cpus = cpus.replace(',', '')
cpus = int(cpus, 16)
self.assertEqual(f'{cpus:x}', f'{rps_cpus:x}')
@@ -117,12 +123,14 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
for interface in self._interfaces:
queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*'))
- rfs_flow = int(global_rfs_flow/queues)
+ rfs_flow = int(global_rfs_flow / queues)
for i in range(0, queues):
- tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt')
+ tmp = read_file(
+ f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt'
+ )
self.assertEqual(int(tmp), rfs_flow)
- tmp = read_file(f'/proc/sys/net/core/rps_sock_flow_entries')
+ tmp = read_file('/proc/sys/net/core/rps_sock_flow_entries')
self.assertEqual(int(tmp), global_rfs_flow)
# delete configuration of RFS and check all values returned to default "0"
@@ -133,12 +141,13 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
for interface in self._interfaces:
queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*'))
- rfs_flow = int(global_rfs_flow/queues)
+ rfs_flow = int(global_rfs_flow / queues)
for i in range(0, queues):
- tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt')
+ tmp = read_file(
+ f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt'
+ )
self.assertEqual(int(tmp), 0)
-
def test_non_existing_interface(self):
unknonw_interface = self._base_path + ['eth667']
self.cli_set(unknonw_interface)
@@ -221,7 +230,17 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
for interface in self._interfaces:
frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon)
- self.assertIn(f' evpn mh uplink', frrconfig)
+ self.assertIn(' evpn mh uplink', frrconfig)
+
+ def test_switchdev(self):
+ interface = self._interfaces[0]
+ self.cli_set(self._base_path + [interface, 'switchdev'])
+
+ # check validate() - virtual interfaces do not support switchdev
+ # should print out warning that enabling failed
+
+ self.cli_delete(self._base_path + [interface, 'switchdev'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)