Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pyroute2/ethtool/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,8 @@
LinkModeBit(bit_index=120, name='800000baseVR4/Full', type=LMBTypeMode),
)
LinkModeBits_by_index = {bit.bit_index: bit for bit in LinkModeBits}


def kernel_version_to_int(major: int, minor: int, stable: int):
"""Used by ethtool.git/common.h"""
return ((major) << 16) + ((minor) << 8) + (stable)
52 changes: 47 additions & 5 deletions pyroute2/ethtool/ethtool.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from collections import namedtuple
from ctypes import c_uint16, c_uint32
from dataclasses import dataclass

from pyroute2.ethtool.common import (
LINK_DUPLEX_NAMES,
Expand Down Expand Up @@ -57,14 +58,19 @@ def __init__(self, set, index, name, enable, available):
self.available = available


class EthtoolFeatures(namedtuple('EthtoolFeatures', ('features',))):
@dataclass
class EthtoolFeatures:
features: dict[str, EthtoolFeature]
offload_flags: dict[str, bool]

@classmethod
def from_ioctl(cls, features):
def from_ioctl(cls, features, offload_flags):
return cls(
{
features={
name: EthtoolFeature(set, index, name, enable, available)
for name, enable, available, set, index in features
}
},
offload_flags=offload_flags,
)

@staticmethod
Expand Down Expand Up @@ -523,11 +529,47 @@ def set_rings(self, ifname, with_netlink=None, **kwargs):

self._with_ioctl.set_rings(ioctl_rings)

def get_offload_flags(self, ifname):
self._with_ioctl.change_ifname(ifname)
return self._with_ioctl.get_offload_flags()

def set_offload_flag(self, ifname, long_name, data):
self._with_ioctl.change_ifname(ifname)
return self._with_ioctl.set_offload_flag(long_name, data)

def get_features(self, ifname):
"""Return Device features.

This is the equivalent of the <ethtool -k|--show-features XX> command

Ethtool().get_features("wlan0").features.keys()
Return all features available on the device wlan0
"""
self._with_ioctl.change_ifname(ifname)
return EthtoolFeatures.from_ioctl(self._with_ioctl.get_features())
return EthtoolFeatures.from_ioctl(
features=self._with_ioctl.get_features(),
offload_flags=self._with_ioctl.get_offload_flags(),
)

def set_features(self, ifname, features):
"""Change Device features.

This is the equivalent of the <ethtool -K|--features> command

Disable and enable tx-checksum-ipv4 offload:
>>> ethtool = Ethtool()
>>> features = ethtool.get_features("wlan0")
>>> features.features["tx-checksum-ipv4"].enable
True
>>> features.features["tx-checksum-ipv4"].enable = False
>>> ethtool.set_features("wlan0", features)
>>> ethtool.get_features("wlan0").features["tx-checksum-ipv4"].enable
False
>>> features.features["tx-checksum-ipv4"].enable = True
>>> ethtool.set_features("wlan0", features)
>>> ethtool.get_features("wlan0").features["tx-checksum-ipv4"].enable
True
"""
self._with_ioctl.change_ifname(ifname)
ioctl_features = self._with_ioctl.get_features()
EthtoolFeatures.to_ioctl(ioctl_features, features)
Expand Down
196 changes: 195 additions & 1 deletion pyroute2/ethtool/ioctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import errno
import fcntl
import socket
from dataclasses import dataclass

from pyroute2.ethtool.common import LinkModeBits
from pyroute2.ethtool.common import LinkModeBits, kernel_version_to_int

# ethtool/ethtool-copy.h
IFNAMSIZ = 16
Expand All @@ -15,6 +16,7 @@
ETHTOOL_GWOL = 0x00000005

ETHTOOL_GFLAGS = 0x00000025
ETHTOOL_SFLAGS = 0x00000026
ETHTOOL_GFEATURES = 0x0000003A
ETHTOOL_SFEATURES = 0x0000003B
ETHTOOL_GCHANNELS = 0x0000003C
Expand Down Expand Up @@ -68,6 +70,143 @@
| ETH_FLAG_RXHASH
)


@dataclass
class EthtoolOffFlag:
"""from ethtool.git/common.h"""

short_name: str
long_name: str
kernel_name: str
get_cmd: int
set_cmd: int
value: int

# For features exposed through ETHTOOL_GFLAGS, the oldest
# kernel version for which we can trust the result. Where
# the flag was added at the same time the kernel started
# supporting the feature, this is 0 (to allow for backports).
# Where the feature was supported before the flag was added,
# it is the version that introduced the flag.
min_kernel_ver: int

def __eq__(self, long_name: str):
return self.long_name == long_name


# from ethtool.git/common.c"""
OFF_FLAG_DEF = [
EthtoolOffFlag(
short_name="rx",
long_name="rx-checksumming",
kernel_name="rx-checksum",
get_cmd=ETHTOOL_GRXCSUM,
set_cmd=ETHTOOL_SRXCSUM,
value=ETH_FLAG_RXCSUM,
min_kernel_ver=0,
),
EthtoolOffFlag(
short_name="tx",
long_name="tx-checksumming",
kernel_name="tx-checksum-*",
get_cmd=ETHTOOL_GTXCSUM,
set_cmd=ETHTOOL_STXCSUM,
value=ETH_FLAG_TXCSUM,
min_kernel_ver=0,
),
EthtoolOffFlag(
short_name="sg",
long_name="scatter-gather",
kernel_name="tx-scatter-gather*",
get_cmd=ETHTOOL_GSG,
set_cmd=ETHTOOL_SSG,
value=ETH_FLAG_SG,
min_kernel_ver=0,
),
EthtoolOffFlag(
short_name="tso",
long_name="tcp-segmentation-offload",
kernel_name="tx-tcp*-segmentation",
get_cmd=ETHTOOL_GTSO,
set_cmd=ETHTOOL_STSO,
value=ETH_FLAG_TSO,
min_kernel_ver=0,
),
EthtoolOffFlag(
short_name="ufo",
long_name="udp-fragmentation-offload",
kernel_name="tx-udp-fragmentation",
get_cmd=ETHTOOL_GUFO,
set_cmd=ETHTOOL_SUFO,
value=ETH_FLAG_UFO,
min_kernel_ver=0,
),
EthtoolOffFlag(
short_name="gso",
long_name="generic-segmentation-offload",
kernel_name="tx-generic-segmentation",
get_cmd=ETHTOOL_GGSO,
set_cmd=ETHTOOL_SGSO,
value=ETH_FLAG_GSO,
min_kernel_ver=0,
),
EthtoolOffFlag(
short_name="gro",
long_name="generic-receive-offload",
kernel_name="rx-gro",
get_cmd=ETHTOOL_GGRO,
set_cmd=ETHTOOL_SGRO,
value=ETH_FLAG_GRO,
min_kernel_ver=0,
),
EthtoolOffFlag(
short_name="lro",
long_name="large-receive-offload",
kernel_name="rx-lro",
get_cmd=0,
set_cmd=0,
value=ETH_FLAG_LRO,
min_kernel_ver=kernel_version_to_int(2, 6, 24),
),
EthtoolOffFlag(
short_name="rxvlan",
long_name="rx-vlan-offload",
kernel_name="rx-vlan-hw-parse",
get_cmd=0,
set_cmd=0,
value=ETH_FLAG_RXVLAN,
min_kernel_ver=kernel_version_to_int(2, 6, 37),
),
EthtoolOffFlag(
short_name="txvlan",
long_name="tx-vlan-offload",
kernel_name="tx-vlan-hw-insert",
get_cmd=0,
set_cmd=0,
value=ETH_FLAG_TXVLAN,
min_kernel_ver=kernel_version_to_int(2, 6, 37),
),
EthtoolOffFlag(
short_name="ntuple",
long_name="ntuple-filters",
kernel_name="rx-ntuple-filter",
get_cmd=0,
set_cmd=0,
value=ETH_FLAG_NTUPLE,
min_kernel_ver=0,
),
EthtoolOffFlag(
short_name="rxhash",
long_name="receive-hashing",
kernel_name="rx-hashing",
get_cmd=0,
set_cmd=0,
value=ETH_FLAG_RXHASH,
min_kernel_ver=0,
),
]


SCHAR_MAX = 127
ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32 = SCHAR_MAX

Expand Down Expand Up @@ -574,6 +713,61 @@ def get_stringset(
strings_found.append(buf)
return strings_found

def get_offload_flags(self):
"""old-style offload flags"""
cmd = EthtoolValue()
self.ifreq.value = ctypes.pointer(cmd)

flags = 0
for off_flag in OFF_FLAG_DEF:
if not off_flag.get_cmd:
# Need to call ETHTOOL_GFLAGS to get it
continue
cmd.cmd = off_flag.get_cmd
try:
self.ioctl()
except NotSupportedError:
if off_flag.get_cmd == ETHTOOL_GUFO:
# Mimic the behavior of ethtool,
# see get_features() in ethtool.c
continue
raise
if cmd.data:
flags |= off_flag.value

cmd.cmd = ETHTOOL_GFLAGS
self.ioctl()
flags |= cmd.data & ETH_FLAG_EXT_MASK

return {
off_flag.long_name: bool(flags & off_flag.value)
for off_flag in OFF_FLAG_DEF
}

def set_offload_flag(self, long_name, data):
try:
off_flag = OFF_FLAG_DEF[OFF_FLAG_DEF.index(long_name)]
except ValueError as e:
raise ValueError(f"Unknown offload flag: {long_name}") from e

cmd = EthtoolValue()
self.ifreq.value = ctypes.pointer(cmd)
if off_flag.set_cmd:
cmd.cmd = off_flag.set_cmd
cmd.data = data
else:
cmd.cmd = ETHTOOL_GFLAGS
self.ioctl()
flags = cmd.data & ETH_FLAG_EXT_MASK
if data:
flags |= off_flag.value
elif flags & off_flag.value:
flags ^= off_flag.value
cmd.cmd = ETHTOOL_SFLAGS
cmd.data = flags

self.ioctl()

def get_features(self):
stringsset = self.get_stringset(set_id=ETH_SS_FEATURES)
cmd = EthtoolGfeatures()
Expand Down
11 changes: 11 additions & 0 deletions tests/test_linux/test_ethtool.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,14 @@ def test_module_info_sff8079_copper():
("Vendor SN", "KAHZ4263"),
("Date code", "241129"),
]


def test_features_offload_flags():
flag_name = "tcp-segmentation-offload"
ethtool = Ethtool()

ethtool.set_offload_flag("lo", flag_name, False)
assert ethtool.get_features("lo").offload_flags[flag_name] is False

ethtool.set_offload_flag("lo", flag_name, True)
assert ethtool.get_features("lo").offload_flags[flag_name] is True
Loading