Page MenuHomeVyOS Platform

ethernet.py
No OneTemporary

Size
17 KB
Referenced Files
None
Subscribers
None

ethernet.py

# Copyright 2019-2023 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 os
from glob import glob
from vyos.base import Warning
from vyos.ethtool import Ethtool
from vyos.ifconfig.interface import Interface
from vyos.utils.dict import dict_search
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,
**{
'section': 'ethernet',
'prefixes': ['lan', 'eth', 'eno', 'ens', 'enp', 'enx'],
'bondable': True,
'broadcast': True,
'bridgeable': True,
'eternal': '(lan|eth|eno|ens|enp|enx)[0-9]+$',
}
}
@staticmethod
def feature(ifname, option, value):
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),
},
}}
@staticmethod
def get_bond_member_allowed_options() -> list:
"""
Return list of options which are allowed for changing,
when interface is a bond member
:return: List of interface options
:rtype: list
"""
bond_allowed_sections = [
'description',
'disable',
'disable_flow_control',
'disable_link_detect',
'duplex',
'eapol.ca_certificate',
'eapol.certificate',
'eapol.passphrase',
'mirror.egress',
'mirror.ingress',
'offload.gro',
'offload.gso',
'offload.lro',
'offload.rfs',
'offload.rps',
'offload.sg',
'offload.tso',
'redirect',
'ring_buffer.rx',
'ring_buffer.tx',
'speed',
'hw_id'
]
return bond_allowed_sections
def __init__(self, ifname, **kargs):
super().__init__(ifname, **kargs)
self.ethtool = Ethtool(ifname)
def remove(self):
"""
Remove interface from config. Removing the interface deconfigures all
assigned IP addresses.
Example:
>>> from vyos.ifconfig import WWANIf
>>> i = EthernetIf('eth0')
>>> i.remove()
"""
if self.exists(self.ifname):
# interface is placed in A/D state when removed from config! It
# will remain visible for the operating system.
self.set_admin_state('down')
super().remove()
def set_flow_control(self, enable):
"""
Changes the pause parameters of the specified Ethernet device.
@param enable: true -> enable pause frames, false -> disable pause frames
Example:
>>> from vyos.ifconfig import EthernetIf
>>> i = EthernetIf('eth0')
>>> i.set_flow_control(True)
"""
ifname = self.config['ifname']
if enable not in ['on', 'off']:
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!')
return False
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:
Warning(f'could not change "{ifname}" flow control setting!')
return output
return None
def set_speed_duplex(self, speed, duplex):
"""
Set link speed in Mbit/s and duplex.
@speed can be any link speed in MBit/s, e.g. 10, 100, 1000 auto
@duplex can be half, full, auto
Example:
>>> from vyos.ifconfig import EthernetIf
>>> i = EthernetIf('eth0')
>>> i.set_speed_duplex('auto', 'auto')
"""
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 duplex not in ['auto', 'full', 'half']:
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!')
return
if not self.ethtool.check_auto_negotiation_supported():
Warning(f'changing auto-negotiation setting on "{ifname}" is unsupported!')
return
# Get current speed and duplex settings:
ifname = self.config['ifname']
if self.ethtool.get_auto_negotiation():
if speed == 'auto' and duplex == 'auto':
# bail out early as nothing is to change
return
else:
# XXX: read in current speed and duplex settings
# There are some "nice" NICs like AX88179 which do not support
# reading the speed thus we simply fallback to the supplied speed
# to not cause any change here and raise an exception.
cur_speed = read_file(f'/sys/class/net/{ifname}/speed', speed)
cur_duplex = read_file(f'/sys/class/net/{ifname}/duplex', duplex)
if (cur_speed == speed) and (cur_duplex == duplex):
# bail out early as nothing is to change
return
cmd = f'ethtool --change {ifname}'
try:
if speed == 'auto' or duplex == 'auto':
cmd += ' autoneg on'
else:
cmd += f' speed {speed} duplex {duplex} autoneg off'
return self._cmd(cmd)
except PermissionError:
# Some NICs do not tell that they don't suppport settings speed/duplex,
# 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!')
def set_gro(self, state):
"""
Enable Generic Receive Offload. State can be either True or False.
Example:
>>> from vyos.ifconfig import EthernetIf
>>> i = EthernetIf('eth0')
>>> i.set_gro(True)
"""
if not isinstance(state, bool):
raise ValueError('Value out of range')
enabled, fixed = self.ethtool.get_generic_receive_offload()
if enabled != state:
if not fixed:
return self.set_interface('gro', 'on' if state else 'off')
else:
print('Adapter does not support changing generic-receive-offload settings!')
return False
def set_gso(self, state):
"""
Enable Generic Segmentation offload. State can be either True or False.
Example:
>>> from vyos.ifconfig import EthernetIf
>>> i = EthernetIf('eth0')
>>> i.set_gso(True)
"""
if not isinstance(state, bool):
raise ValueError('Value out of range')
enabled, fixed = self.ethtool.get_generic_segmentation_offload()
if enabled != state:
if not fixed:
return self.set_interface('gso', 'on' if state else 'off')
else:
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.
Example:
>>> from vyos.ifconfig import EthernetIf
>>> i = EthernetIf('eth0')
>>> i.set_lro(True)
"""
if not isinstance(state, bool):
raise ValueError('Value out of range')
enabled, fixed = self.ethtool.get_large_receive_offload()
if enabled != state:
if not fixed:
return self.set_interface('lro', 'on' if state else 'off')
else:
print('Adapter does not support changing large-receive-offload settings!')
return False
def set_rps(self, state):
if not isinstance(state, bool):
raise ValueError('Value out of range')
rps_cpus = 0
queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))
if state:
# Enable RPS on all available CPUs except CPU0 which we will not
# utilize so the system has one spare core when it's under high
# preasure to server other means. Linux sysfs excepts a bitmask
# representation of the CPUs which should participate on RPS, we
# can enable more CPUs that are physically present on the system,
# Linux will clip that internally!
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.
# This is done by masking out the lowst bit so CPU0 is spared from
# receive packet steering.
rps_cpus &= ~1
for i in range(0, queues):
self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', f'{rps_cpus:x}')
# send bitmask representation as hex string without leading '0x'
return True
def set_rfs(self, state):
rfs_flow = 0
queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))
if state:
global_rfs_flow = 32768
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)
return True
def set_sg(self, state):
"""
Enable Scatter-Gather support. State can be either True or False.
Example:
>>> from vyos.ifconfig import EthernetIf
>>> i = EthernetIf('eth0')
>>> i.set_sg(True)
"""
if not isinstance(state, bool):
raise ValueError('Value out of range')
enabled, fixed = self.ethtool.get_scatter_gather()
if enabled != state:
if not fixed:
return self.set_interface('sg', 'on' if state else 'off')
else:
print('Adapter does not support changing scatter-gather settings!')
return False
def set_tso(self, state):
"""
Enable TCP segmentation offloading. State can be either True or False.
Example:
>>> from vyos.ifconfig import EthernetIf
>>> i = EthernetIf('eth0')
>>> i.set_tso(False)
"""
if not isinstance(state, bool):
raise ValueError('Value out of range')
enabled, fixed = self.ethtool.get_tcp_segmentation_offload()
if enabled != state:
if not fixed:
return self.set_interface('tso', 'on' if state else 'off')
else:
print('Adapter does not support changing tcp-segmentation-offload settings!')
return False
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 --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 "{rx_tx}" ring-buffer for {ifname}')
return output
def set_eee(self, enable):
"""
Enable/Disable Energy Efficient Ethernet (EEE) settings
Example:
>>> from vyos.ifconfig import EthernetIf
>>> i = EthernetIf('eth0')
>>> i.set_eee(enable=False)
"""
if not isinstance(enable, bool):
raise ValueError('Value out of range')
if not self.ethtool.check_eee():
self._debug_msg(f'NIC driver does not support changing EEE settings!')
return False
current = self.ethtool.get_eee()
if current != enable:
# Assemble command executed on system. Unfortunately there is no way
# to change this setting via sysfs
cmd = f'ethtool --set-eee {self.ifname} eee '
cmd += 'on' if enable else 'off'
output, code = self._popen(cmd)
if code:
Warning(f'could not change "{self.ifname}" EEE setting!')
return output
return None
def update(self, config):
""" 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. """
# disable ethernet flow control (pause frames)
value = 'off' if 'disable_flow_control' in config else 'on'
self.set_flow_control(value)
# Always disable Energy Efficient Ethernet
self.set_eee(False)
# GRO (generic receive offload)
self.set_gro(dict_search('offload.gro', config) != None)
# 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)
# RPS - Receive Packet Steering
self.set_rps(dict_search('offload.rps', config) != None)
# RFS - Receive Flow Steering
self.set_rfs(dict_search('offload.rfs', config) != None)
# scatter-gather option
self.set_sg(dict_search('offload.sg', config) != None)
# TSO (TCP segmentation offloading)
self.set_tso(dict_search('offload.tso', config) != None)
# Set physical interface speed and duplex
if 'speed_duplex_changed' in config:
if {'speed', 'duplex'} <= set(config):
speed = config.get('speed')
duplex = config.get('duplex')
self.set_speed_duplex(speed, duplex)
# Set interface ring buffer
if 'ring_buffer' in config:
for rx_tx, size in config['ring_buffer'].items():
self.set_ring_buffer(rx_tx, size)
# call base class last
super().update(config)

File Metadata

Mime Type
text/x-script.python
Expires
Sat, Sep 26, 1:42 PM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4285304
Default Alt Text
ethernet.py (17 KB)

Event Timeline