diff options
-rw-r--r-- | interface-definitions/system-login.xml.in | 5 | ||||
-rw-r--r-- | op-mode-definitions/show-interfaces-ethernet.xml.in | 2 | ||||
-rw-r--r-- | python/vyos/ethtool.py | 82 | ||||
-rw-r--r-- | python/vyos/ifconfig/ethernet.py | 79 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-ethernet.py | 11 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/20-to-21 | 23 |
6 files changed, 134 insertions, 68 deletions
diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index 3c2c7dfa5..f4613b8a2 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -12,7 +12,7 @@ <properties> <help>Local user account information</help> <constraint> - <regex>[a-zA-Z0-9\-_\.]{1,100}</regex> + <regex>^[-_a-zA-Z0-9.]{1,100}</regex> </constraint> <constraintErrorMessage>Username contains illegal characters or\nexceeds 100 character limitation.</constraintErrorMessage> </properties> @@ -44,6 +44,9 @@ <tagNode name="public-keys"> <properties> <help>Remote access public keys</help> + <constraint> + <regex>^[-_a-zA-Z0-9@]+$</regex> + </constraint> <valueHelp> <format>txt</format> <description>Key identifier used by ssh-keygen (usually of form user@host)</description> diff --git a/op-mode-definitions/show-interfaces-ethernet.xml.in b/op-mode-definitions/show-interfaces-ethernet.xml.in index 6d50d6e90..c42efd21f 100644 --- a/op-mode-definitions/show-interfaces-ethernet.xml.in +++ b/op-mode-definitions/show-interfaces-ethernet.xml.in @@ -29,7 +29,7 @@ <properties> <help>Show physical device information for specified ethernet interface</help> </properties> - <command>ethtool "$4"; ethtool --driver "$4"</command> + <command>ethtool "$4"; ethtool --show-ring "$4"; ethtool --driver "$4"</command> <children> <leafNode name="offload"> <properties> diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 7dcb68346..609d83b5e 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -13,7 +13,9 @@ # 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 os import re + from vyos.util import popen class Ethtool: @@ -41,8 +43,22 @@ class Ethtool: # } _speed_duplex = { } _ring_buffers = { } + _ring_buffers_max = { } + _driver_name = None + _auto_negotiation = None + _flow_control = None + _flow_control_enabled = None def __init__(self, ifname): + # Get driver used for interface + sysfs_file = f'/sys/class/net/{ifname}/device/driver/module' + if os.path.exists(sysfs_file): + link = os.readlink(sysfs_file) + self._driver_name = os.path.basename(link) + + if not self._driver_name: + raise ValueError(f'Could not determine driver for interface {ifname}!') + # Build a dictinary of supported link-speed and dupley settings. out, err = popen(f'ethtool {ifname}') reading = False @@ -53,7 +69,6 @@ class Ethtool: reading = True if 'Supported pause frame use:' in line: reading = False - break if reading: for block in line.split(): if pattern.search(block): @@ -63,6 +78,15 @@ class Ethtool: self._speed_duplex.update({ speed : {}}) if duplex not in self._speed_duplex[speed]: self._speed_duplex[speed].update({ duplex : ''}) + if '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') + + if self._auto_negotiation == None: + raise ValueError(f'Could not determine auto-negotiation settings '\ + f'for interface {ifname}!') # Now populate features dictionaty out, err = popen(f'ethtool --show-features {ifname}') @@ -78,7 +102,7 @@ class Ethtool: 'fixed' : fixed } - out, err = popen(f'ethtool -g {ifname}') + out, err = popen(f'ethtool --show-ring {ifname}') # We are only interested in line 2-5 which contains the device maximum # ringbuffers for line in out.splitlines()[2:6]: @@ -89,7 +113,26 @@ class Ethtool: # output format from 0 -> n/a. As we are only interested in the # tx/rx keys we do not care about RX Mini/Jumbo. if value.isdigit(): - self._ring_buffers[key] = int(value) + self._ring_buffers_max[key] = value + # Now we wan't to get the current RX/TX ringbuffer values - used for + for line in out.splitlines()[7:11]: + if ':' in line: + key, value = [s.strip() for s in line.strip().split(":", 1)] + key = key.lower().replace(' ', '_') + # T3645: ethtool version used on Debian Bullseye changed the + # output format from 0 -> n/a. As we are only interested in the + # tx/rx keys we do not care about RX Mini/Jumbo. + if value.isdigit(): + self._ring_buffers[key] = value + + # 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 --show-pause {ifname}') + if len(out.splitlines()) > 1: + self._flow_control = True + # read current flow control setting, this returns: + # ['Autonegotiate:', 'on'] + self._flow_control_enabled = out.splitlines()[1].split()[-1] def _get_generic(self, feature): """ @@ -122,15 +165,19 @@ class Ethtool: def get_tcp_segmentation_offload(self): return self._get_generic('tcp-segmentation-offload') - def get_rx_buffer(self): - # Configuration of RX ring-buffers is not supported on every device, + 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 - return self._ring_buffers.get('rx', None) + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return self._ring_buffers_max.get(rx_tx, None) - def get_tx_buffer(self): - # Configuration of TX ring-buffers is not supported on every device, + 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 - return self._ring_buffers.get('tx', None) + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return str(self._ring_buffers.get(rx_tx, None)) def check_speed_duplex(self, speed, duplex): """ Check if the passed speed and duplex combination is supported by @@ -142,8 +189,25 @@ class Ethtool: if duplex not in ['full', 'half']: raise ValueError(f'Value "{duplex}" for duplex is invalid!') + if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + 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 """ + if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + return False + return self._flow_control + + def get_flow_control(self): + if self._flow_control_enabled == None: + raise ValueError('Interface does not support changing '\ + 'flow-control settings!') + return self._flow_control_enabled + + def get_auto_negotiation(self): + return self._auto_negotiation diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 76ed3fd92..7bd269491 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -20,6 +20,7 @@ from vyos.ethtool import Ethtool from vyos.ifconfig.interface import Interface from vyos.util import run from vyos.util import dict_search +from vyos.util import read_file from vyos.validate import assert_list @Interface.register @@ -120,38 +121,16 @@ class EthernetIf(Interface): 'flow control settings!') return - # Get current flow control settings: - cmd = f'ethtool --show-pause {ifname}' - output, code = self._popen(cmd) - if code == 76: - # the interface does not support it - return '' - if code: - # never fail here as it prevent vyos to boot - print(f'unexpected return code {code} from {cmd}') - return '' - - # The above command returns - with tabs: - # - # Pause parameters for eth0: - # Autonegotiate: on - # RX: off - # TX: off - if re.search("Autonegotiate:\ton", output): - if enable == "on": - # flowcontrol is already enabled - no need to re-enable it again - # this will prevent the interface from flapping as applying the - # flow-control settings will take the interface down and bring - # it back up every time. - return '' - - # Assemble command executed on system. Unfortunately there is no way - # to change this setting via sysfs - cmd = f'ethtool --pause {ifname} autoneg {enable} tx {enable} rx {enable}' - output, code = self._popen(cmd) - if code: - print(f'could not set flowcontrol for {ifname}') - return output + current = self.ethtool.get_flow_control() + if current != enable: + # Assemble command executed on system. Unfortunately there is no way + # to change this setting via sysfs + cmd = f'ethtool --pause {ifname} autoneg {enable} tx {enable} rx {enable}' + output, code = self._popen(cmd) + if code: + print(f'Could not set flowcontrol for {ifname}') + return output + return None def set_speed_duplex(self, speed, duplex): """ @@ -181,32 +160,19 @@ class EthernetIf(Interface): # Get current speed and duplex settings: ifname = self.config['ifname'] - cmd = f'ethtool {ifname}' - tmp = self._cmd(cmd) - - if re.search("\tAuto-negotiation: on", tmp): + if self.ethtool.get_auto_negotiation(): if speed == 'auto' and duplex == 'auto': # bail out early as nothing is to change return else: # read in current speed and duplex settings - cur_speed = 0 - cur_duplex = '' - for line in tmp.splitlines(): - if line.lstrip().startswith("Speed:"): - non_decimal = re.compile(r'[^\d.]+') - cur_speed = non_decimal.sub('', line) - continue - - if line.lstrip().startswith("Duplex:"): - cur_duplex = line.split()[-1].lower() - break - + cur_speed = read_file(f'/sys/class/net/{ifname}/speed') + cur_duplex = read_file(f'/sys/class/net/{ifname}/duplex') if (cur_speed == speed) and (cur_duplex == duplex): # bail out early as nothing is to change return - cmd = f'ethtool -s {ifname}' + cmd = f'ethtool --change {ifname}' if speed == 'auto' or duplex == 'auto': cmd += ' autoneg on' else: @@ -328,21 +294,26 @@ class EthernetIf(Interface): print('Adapter does not support changing tcp-segmentation-offload settings!') return False - def set_ring_buffer(self, b_type, b_size): + def set_ring_buffer(self, rx_tx, size): """ Example: >>> from vyos.ifconfig import EthernetIf >>> i = EthernetIf('eth0') >>> i.set_ring_buffer('rx', '4096') """ + current_size = self.ethtool.get_ring_buffer(rx_tx) + if current_size == size: + # bail out early if nothing is about to change + return None + ifname = self.config['ifname'] - cmd = f'ethtool -G {ifname} {b_type} {b_size}' + cmd = f'ethtool --set-ring {ifname} {rx_tx} {size}' output, code = self._popen(cmd) # ethtool error codes: # 80 - value already setted # 81 - does not possible to set value if code and code != 80: - print(f'could not set "{b_type}" ring-buffer for {ifname}') + print(f'could not set "{rx_tx}" ring-buffer for {ifname}') return output def update(self, config): @@ -381,8 +352,8 @@ class EthernetIf(Interface): # Set interface ring buffer if 'ring_buffer' in config: - for b_type in config['ring_buffer']: - self.set_ring_buffer(b_type, config['ring_buffer'][b_type]) + for rx_tx, size in config['ring_buffer'].items(): + self.set_ring_buffer(rx_tx, size) # call base class first super().update(config) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 27c4a7c38..81ed36bf2 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -85,14 +85,19 @@ def verify(ethernet): speed = ethernet['speed'] duplex = ethernet['duplex'] if not ethtool.check_speed_duplex(speed, duplex): - raise ConfigError(f'Adapter does not support speed "{speed}" and duplex "{duplex}"!') + raise ConfigError(f'Adapter does not support changing speed and duplex '\ + f'settings to: {speed}/{duplex}!') + + if 'disable_flow_control' in ethernet: + if not ethtool.check_flow_control(): + raise ConfigError('Adapter does not support changing flow-control settings!') if 'ring_buffer' in ethernet: - max_rx = ethtool.get_rx_buffer() + max_rx = ethtool.get_ring_buffer_max('rx') if not max_rx: raise ConfigError('Driver does not support RX ring-buffer configuration!') - max_tx = ethtool.get_tx_buffer() + max_tx = ethtool.get_ring_buffer_max('tx') if not max_tx: raise ConfigError('Driver does not support TX ring-buffer configuration!') diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index 4b0e70d35..0bd858760 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -89,6 +89,29 @@ for ifname in config.list_nodes(base): 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()) |