diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index d45c9c272..80bb56fa2 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -1,200 +1,204 @@ # Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. import re from json import loads +from vyos.utils.network import interface_exists from vyos.utils.process import popen # These drivers do not support using ethtool to change the speed, duplex, or # flow control settings _drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront', 'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth', 'ixgbevf', 'tun'] class Ethtool: """ Class is used to retrive and cache information about an ethernet adapter """ # dictionary containing driver featurs, it will be populated on demand and # the content will look like: # [{'esp-hw-offload': {'active': False, 'fixed': True, 'requested': False}, # 'esp-tx-csum-hw-offload': {'active': False, # 'fixed': True, # 'requested': False}, # 'fcoe-mtu': {'active': False, 'fixed': True, 'requested': False}, # 'generic-receive-offload': {'active': True, # 'fixed': False, # 'requested': True}, # 'generic-segmentation-offload': {'active': True, # 'fixed': False, # 'requested': True}, # 'highdma': {'active': True, 'fixed': False, 'requested': True}, # 'ifname': 'eth0', # 'l2-fwd-offload': {'active': False, 'fixed': True, 'requested': False}, # 'large-receive-offload': {'active': False, # 'fixed': False, # 'requested': False}, # ... _features = { } # dictionary containing available interface speed and duplex settings # { # '10' : {'full': '', 'half': ''}, # '100' : {'full': '', 'half': ''}, # '1000': {'full': ''} # } _speed_duplex = {'auto': {'auto': ''}} _ring_buffer = None _driver_name = None _auto_negotiation = False _auto_negotiation_supported = None _flow_control = None def __init__(self, ifname): # Get driver used for interface + if not interface_exists(ifname): + raise ValueError(f'Interface "{ifname}" does not exist!') + out, _ = popen(f'ethtool --driver {ifname}') driver = re.search(r'driver:\s(\w+)', out) if driver: self._driver_name = driver.group(1) # Build a dictinary of supported link-speed and dupley settings. out, _ = popen(f'ethtool {ifname}') reading = False pattern = re.compile(r'\d+base.*') for line in out.splitlines()[1:]: line = line.lstrip() if 'Supported link modes:' in line: reading = True if 'Supported pause frame use:' in line: reading = False if reading: for block in line.split(): if pattern.search(block): speed = block.split('base')[0] duplex = block.split('/')[-1].lower() if speed not in self._speed_duplex: self._speed_duplex.update({ speed : {}}) if duplex not in self._speed_duplex[speed]: self._speed_duplex[speed].update({ duplex : ''}) if 'Supports auto-negotiation:' in line: # Split the following string: Auto-negotiation: off # we are only interested in off or on tmp = line.split()[-1] self._auto_negotiation_supported = bool(tmp == 'Yes') # Only read in if Auto-negotiation is supported if self._auto_negotiation_supported and 'Auto-negotiation:' in line: # Split the following string: Auto-negotiation: off # we are only interested in off or on tmp = line.split()[-1] self._auto_negotiation = bool(tmp == 'on') # Now populate driver features out, _ = popen(f'ethtool --json --show-features {ifname}') self._features = loads(out) # Get information about NIC ring buffers out, _ = popen(f'ethtool --json --show-ring {ifname}') self._ring_buffer = loads(out) # Get current flow control settings, but this is not supported by # all NICs (e.g. vmxnet3 does not support is) out, err = popen(f'ethtool --json --show-pause {ifname}') if not bool(err): self._flow_control = loads(out) def check_auto_negotiation_supported(self): """ Check if the NIC supports changing auto-negotiation """ return self._auto_negotiation_supported def get_auto_negotiation(self): return self._auto_negotiation_supported and self._auto_negotiation def get_driver_name(self): return self._driver_name def _get_generic(self, feature): """ Generic method to read self._features and return a tuple for feature enabled and feature is fixed. In case of a missing key, return "fixed = True and enabled = False" """ active = False fixed = True if feature in self._features[0]: active = bool(self._features[0][feature]['active']) fixed = bool(self._features[0][feature]['fixed']) return active, fixed def get_generic_receive_offload(self): return self._get_generic('generic-receive-offload') 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') def get_scatter_gather(self): return self._get_generic('scatter-gather') def get_tcp_segmentation_offload(self): return self._get_generic('tcp-segmentation-offload') def get_ring_buffer_max(self, rx_tx): # Configuration of RX/TX ring-buffers is not supported on every device, # thus when it's impossible return None if rx_tx not in ['rx', 'tx']: ValueError('Ring-buffer type must be either "rx" or "tx"') return str(self._ring_buffer[0].get(f'{rx_tx}-max', None)) def get_ring_buffer(self, rx_tx): # Configuration of RX/TX ring-buffers is not supported on every device, # thus when it's impossible return None if rx_tx not in ['rx', 'tx']: ValueError('Ring-buffer type must be either "rx" or "tx"') return str(self._ring_buffer[0].get(rx_tx, None)) def check_speed_duplex(self, speed, duplex): """ Check if the passed speed and duplex combination is supported by the underlaying network adapter. """ if isinstance(speed, int): speed = str(speed) if speed != 'auto' and not speed.isdigit(): raise ValueError(f'Value "{speed}" for speed is invalid!') if duplex not in ['auto', 'full', 'half']: raise ValueError(f'Value "{duplex}" for duplex is invalid!') if self.get_driver_name() in _drivers_without_speed_duplex_flow: return False if speed in self._speed_duplex: if duplex in self._speed_duplex[speed]: return True return False def check_flow_control(self): """ Check if the NIC supports flow-control """ return bool(self._flow_control) def get_flow_control(self): if self._flow_control == None: raise ValueError('Interface does not support changing '\ 'flow-control settings!') return 'on' if bool(self._flow_control[0]['autonegotiate']) else 'off' diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index 14ad0fe4d..05a0c7237 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -1,120 +1,125 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2024 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/>. # T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS # CLI. See https://vyos.dev/T3619#102254 for all the details. # T3787: Remove deprecated UDP fragmentation offloading option from sys import argv from vyos.ethtool import Ethtool from vyos.configtree import ConfigTree +from vyos.utils.network import interface_exists 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 = ['interfaces', 'ethernet'] config = ConfigTree(config_file) if not config.exists(base): exit(0) for ifname in config.list_nodes(base): + # Bail out early if interface vanished from system + if not interface_exists(ifname): + continue + eth = Ethtool(ifname) # If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is # enabled via CLI but not supported by the NIC - we remove it from the CLI configured = config.exists(base + [ifname, 'offload', 'gro']) enabled, fixed = eth.get_generic_receive_offload() if configured and fixed: config.delete(base + [ifname, 'offload', 'gro']) elif enabled and not fixed: config.set(base + [ifname, 'offload', 'gro']) # If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is # enabled via CLI but not supported by the NIC - we remove it from the CLI configured = config.exists(base + [ifname, 'offload', 'gso']) enabled, fixed = eth.get_generic_segmentation_offload() if configured and fixed: config.delete(base + [ifname, 'offload', 'gso']) elif enabled and not fixed: config.set(base + [ifname, 'offload', 'gso']) # If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is # enabled via CLI but not supported by the NIC - we remove it from the CLI configured = config.exists(base + [ifname, 'offload', 'lro']) enabled, fixed = eth.get_large_receive_offload() if configured and fixed: config.delete(base + [ifname, 'offload', 'lro']) elif enabled and not fixed: config.set(base + [ifname, 'offload', 'lro']) # If SG is enabled by the Kernel - we reflect this on the CLI. If SG is # enabled via CLI but not supported by the NIC - we remove it from the CLI configured = config.exists(base + [ifname, 'offload', 'sg']) enabled, fixed = eth.get_scatter_gather() if configured and fixed: config.delete(base + [ifname, 'offload', 'sg']) elif enabled and not fixed: config.set(base + [ifname, 'offload', 'sg']) # If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is # enabled via CLI but not supported by the NIC - we remove it from the CLI configured = config.exists(base + [ifname, 'offload', 'tso']) enabled, fixed = eth.get_tcp_segmentation_offload() if configured and fixed: config.delete(base + [ifname, 'offload', 'tso']) elif enabled and not fixed: config.set(base + [ifname, 'offload', 'tso']) # Remove deprecated UDP fragmentation offloading option if config.exists(base + [ifname, 'offload', 'ufo']): config.delete(base + [ifname, 'offload', 'ufo']) # Also while processing the interface configuration, not all adapters support # changing the speed and duplex settings. If the desired speed and duplex # values do not work for the NIC driver, we change them back to the default # value of "auto" - which will be applied if the CLI node is deleted. speed_path = base + [ifname, 'speed'] duplex_path = base + [ifname, 'duplex'] # speed and duplex must always be set at the same time if not set to "auto" if config.exists(speed_path) and config.exists(duplex_path): speed = config.return_value(speed_path) duplex = config.return_value(duplex_path) if speed != 'auto' and duplex != 'auto': if not eth.check_speed_duplex(speed, duplex): config.delete(speed_path) config.delete(duplex_path) # Also while processing the interface configuration, not all adapters support # changing disabling flow-control - or change this setting. If disabling # flow-control is not supported by the NIC, we remove the setting from CLI flow_control_path = base + [ifname, 'disable-flow-control'] if config.exists(flow_control_path): if not eth.check_flow_control(): config.delete(flow_control_path) 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)