This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+31 -10
View File
@@ -1,14 +1,35 @@
version: "2"
linters:
disable:
- gosimple
- unused
enable:
- gocritic
- misspell
- staticcheck
- errcheck
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- misspell
linters-settings:
gci:
sections:
- standard
- default
- prefix(github.com/safchain/ethtool)
- goimports
settings:
gci:
sections:
- standard
- default
- prefix(github.com/safchain/ethtool)
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+8
View File
@@ -0,0 +1,8 @@
---
extends: default
rules:
document-start: disable
line-length: disable
truthy:
check-keys: false
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Copyright (c) 2015 The Ethtool Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+1
View File
@@ -2,3 +2,4 @@ all: build
build:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build
+620 -76
View File
@@ -29,7 +29,8 @@ import (
"bytes"
"encoding/hex"
"fmt"
"strings"
"sync"
"time"
"unsafe"
"golang.org/x/sys/unix"
@@ -47,33 +48,81 @@ const (
// ethtool stats related constants.
const (
ETH_GSTRING_LEN = 32
ETH_SS_STATS = 1
ETH_SS_FEATURES = 4
ETH_GSTRING_LEN = 32
ETH_SS_STATS = 1
ETH_SS_PRIV_FLAGS = 2
ETH_SS_FEATURES = 4
// CMD supported
ETHTOOL_GDRVINFO = 0x00000003 /* Get driver info. */
ETHTOOL_GSTRINGS = 0x0000001b /* get specified string set */
ETHTOOL_GSTATS = 0x0000001d /* get NIC-specific statistics */
// other CMDs from ethtool-copy.h of ethtool-3.5 package
ETHTOOL_GSET = 0x00000001 /* Get settings. */
ETHTOOL_SSET = 0x00000002 /* Set settings. */
ETHTOOL_GMSGLVL = 0x00000007 /* Get driver message level */
ETHTOOL_SMSGLVL = 0x00000008 /* Set driver msg level. */
ETHTOOL_GCHANNELS = 0x0000003c /* Get no of channels */
ETHTOOL_SCHANNELS = 0x0000003d /* Set no of channels */
ETHTOOL_GCOALESCE = 0x0000000e /* Get coalesce config */
/* Get link status for host, i.e. whether the interface *and* the
* physical port (if there is one) are up (ethtool_value). */
ETHTOOL_GLINK = 0x0000000a
ETHTOOL_GMODULEINFO = 0x00000042 /* Get plug-in module information */
ETHTOOL_GMODULEEEPROM = 0x00000043 /* Get plug-in module eeprom */
ETHTOOL_GPERMADDR = 0x00000020 /* Get permanent hardware address */
ETHTOOL_GFEATURES = 0x0000003a /* Get device offload settings */
ETHTOOL_SFEATURES = 0x0000003b /* Change device offload settings */
ETHTOOL_GFLAGS = 0x00000025 /* Get flags bitmap(ethtool_value) */
ETHTOOL_GSSET_INFO = 0x00000037 /* Get string set info */
ETHTOOL_GET_TS_INFO = 0x00000041 /* Get time stamping and PHC info */
ETHTOOL_GSET = 0x00000001 /* Get settings. */
ETHTOOL_SSET = 0x00000002 /* Set settings. */
ETHTOOL_GWOL = 0x00000005 /* Get wake-on-lan options. */
ETHTOOL_SWOL = 0x00000006 /* Set wake-on-lan options. */
ETHTOOL_GDRVINFO = 0x00000003 /* Get driver info. */
ETHTOOL_GMSGLVL = 0x00000007 /* Get driver message level */
ETHTOOL_SMSGLVL = 0x00000008 /* Set driver msg level. */
ETHTOOL_GLINKSETTINGS = unix.ETHTOOL_GLINKSETTINGS // 0x4c
ETHTOOL_SLINKSETTINGS = unix.ETHTOOL_SLINKSETTINGS // 0x4d
// Get link status for host, i.e. whether the interface *and* the
// physical port (if there is one) are up (ethtool_value).
ETHTOOL_GLINK = 0x0000000a
ETHTOOL_GCOALESCE = 0x0000000e /* Get coalesce config */
ETHTOOL_SCOALESCE = 0x0000000f /* Set coalesce config */
ETHTOOL_GRINGPARAM = 0x00000010 /* Get ring parameters */
ETHTOOL_SRINGPARAM = 0x00000011 /* Set ring parameters. */
ETHTOOL_GPAUSEPARAM = 0x00000012 /* Get pause parameters */
ETHTOOL_SPAUSEPARAM = 0x00000013 /* Set pause parameters. */
ETHTOOL_GSTRINGS = 0x0000001b /* Get specified string set */
ETHTOOL_PHYS_ID = 0x0000001c /* Identify the NIC */
ETHTOOL_GSTATS = 0x0000001d /* Get NIC-specific statistics */
ETHTOOL_GPERMADDR = 0x00000020 /* Get permanent hardware address */
ETHTOOL_GFLAGS = 0x00000025 /* Get flags bitmap(ethtool_value) */
ETHTOOL_GPFLAGS = 0x00000027 /* Get driver-private flags bitmap */
ETHTOOL_SPFLAGS = 0x00000028 /* Set driver-private flags bitmap */
ETHTOOL_GSSET_INFO = 0x00000037 /* Get string set info */
ETHTOOL_GFEATURES = 0x0000003a /* Get device offload settings */
ETHTOOL_SFEATURES = 0x0000003b /* Change device offload settings */
ETHTOOL_GCHANNELS = 0x0000003c /* Get no of channels */
ETHTOOL_SCHANNELS = 0x0000003d /* Set no of channels */
ETHTOOL_GET_TS_INFO = 0x00000041 /* Get time stamping and PHC info */
ETHTOOL_GMODULEINFO = 0x00000042 /* Get plug-in module information */
ETHTOOL_GMODULEEEPROM = 0x00000043 /* Get plug-in module eeprom */
ETHTOOL_GRXFHINDIR = 0x00000038 /* Get RX flow hash indir'n table */
ETHTOOL_SRXFHINDIR = 0x00000039 /* Set RX flow hash indir'n table */
ETH_RXFH_INDIR_NO_CHANGE = 0xFFFFFFFF
// Speed and Duplex unknowns/constants (Manually defined based on <linux/ethtool.h>)
SPEED_UNKNOWN = 0xffffffff // ((__u32)-1) SPEED_UNKNOWN
DUPLEX_HALF = 0x00 // DUPLEX_HALF
DUPLEX_FULL = 0x01 // DUPLEX_FULL
DUPLEX_UNKNOWN = 0xff // DUPLEX_UNKNOWN
// Port types (Manually defined based on <linux/ethtool.h>)
PORT_TP = 0x00 // PORT_TP
PORT_AUI = 0x01 // PORT_AUI
PORT_MII = 0x02 // PORT_MII
PORT_FIBRE = 0x03 // PORT_FIBRE
PORT_BNC = 0x04 // PORT_BNC
PORT_DA = 0x05 // PORT_DA
PORT_NONE = 0xef // PORT_NONE
PORT_OTHER = 0xff // PORT_OTHER
// Autoneg settings (Manually defined based on <linux/ethtool.h>)
AUTONEG_DISABLE = 0x00 // AUTONEG_DISABLE
AUTONEG_ENABLE = 0x01 // AUTONEG_ENABLE
// MDIX states (Manually defined based on <linux/ethtool.h>)
ETH_TP_MDI_INVALID = 0x00 // ETH_TP_MDI_INVALID
ETH_TP_MDI = 0x01 // ETH_TP_MDI
ETH_TP_MDI_X = 0x02 // ETH_TP_MDI_X
ETH_TP_MDI_AUTO = 0x03 // Control value ETH_TP_MDI_AUTO
// Link mode mask bits count (Manually defined based on ethtool.h)
ETHTOOL_LINK_MODE_MASK_NBITS = 92 // __ETHTOOL_LINK_MODE_MASK_NBITS
// Calculate max nwords based on NBITS using the manually defined constant
MAX_LINK_MODE_MASK_NWORDS = (ETHTOOL_LINK_MODE_MASK_NBITS + 31) / 32 // = 3
)
// MAX_GSTRINGS maximum number of stats entries that ethtool can
@@ -85,6 +134,32 @@ const (
PERMADDR_LEN = 32
)
// ethtool sset_info related constants
const (
MAX_SSET_INFO = 64
)
const (
DEFAULT_BLINK_DURATION = 60 * time.Second
)
var (
gstringsPool = sync.Pool{
New: func() interface{} {
// new() will allocate and zero-initialize the struct.
// The large data array within ethtoolGStrings will be zeroed.
return new(EthtoolGStrings)
},
}
statsPool = sync.Pool{
New: func() interface{} {
// new() will allocate and zero-initialize the struct.
// The large data array within ethtoolStats will be zeroed.
return new(EthtoolStats)
},
}
)
type ifreq struct {
ifr_name [IFNAMSIZ]byte
ifr_data uintptr
@@ -94,8 +169,8 @@ type ifreq struct {
type ethtoolSsetInfo struct {
cmd uint32
reserved uint32
sset_mask uint32
data uintptr
sset_mask uint64
data [MAX_SSET_INFO]uint32
}
type ethtoolGetFeaturesBlock struct {
@@ -194,23 +269,61 @@ type Coalesce struct {
RateSampleInterval uint32
}
// IdentityConf is an identity config for an interface
type IdentityConf struct {
Cmd uint32
Duration uint32
}
// WoL options
const (
SOF_TIMESTAMPING_TX_HARDWARE = (1 << 0)
SOF_TIMESTAMPING_TX_SOFTWARE = (1 << 1)
SOF_TIMESTAMPING_RX_HARDWARE = (1 << 2)
SOF_TIMESTAMPING_RX_SOFTWARE = (1 << 3)
SOF_TIMESTAMPING_SOFTWARE = (1 << 4)
SOF_TIMESTAMPING_SYS_HARDWARE = (1 << 5)
SOF_TIMESTAMPING_RAW_HARDWARE = (1 << 6)
SOF_TIMESTAMPING_OPT_ID = (1 << 7)
SOF_TIMESTAMPING_TX_SCHED = (1 << 8)
SOF_TIMESTAMPING_TX_ACK = (1 << 9)
SOF_TIMESTAMPING_OPT_CMSG = (1 << 10)
SOF_TIMESTAMPING_OPT_TSONLY = (1 << 11)
SOF_TIMESTAMPING_OPT_STATS = (1 << 12)
SOF_TIMESTAMPING_OPT_PKTINFO = (1 << 13)
SOF_TIMESTAMPING_OPT_TX_SWHW = (1 << 14)
SOF_TIMESTAMPING_BIND_PHC = (1 << 15)
WAKE_PHY = 1 << 0
WAKE_UCAST = 1 << 1
WAKE_MCAST = 1 << 2
WAKE_BCAST = 1 << 3
WAKE_ARP = 1 << 4
WAKE_MAGIC = 1 << 5
WAKE_MAGICSECURE = 1 << 6 // only meaningful if WAKE_MAGIC
)
var WoLMap = map[uint32]string{
WAKE_PHY: "p", // Wake on PHY activity
WAKE_UCAST: "u", // Wake on unicast messages
WAKE_MCAST: "m", // Wake on multicast messages
WAKE_BCAST: "b", // Wake on broadcast messages
WAKE_ARP: "a", // Wake on ARP
WAKE_MAGIC: "g", // Wake on MagicPacket™
WAKE_MAGICSECURE: "s", // Enable SecureOn™ password for MagicPacket™
// f Wake on filter(s)
// d Disable (wake on nothing). This option clears all previous options.
}
// WakeOnLan contains WoL config for an interface
type WakeOnLan struct {
Cmd uint32 // ETHTOOL_GWOL or ETHTOOL_SWOL
Supported uint32 // r/o bitmask of WAKE_* flags for supported WoL modes
Opts uint32 // Bitmask of WAKE_* flags for enabled WoL modes
}
// Timestamping options
// see: https://www.kernel.org/doc/Documentation/networking/timestamping.txt
const (
SOF_TIMESTAMPING_TX_HARDWARE = (1 << 0) /* Request tx timestamps generated by the network adapter. */
SOF_TIMESTAMPING_TX_SOFTWARE = (1 << 1) /* Request tx timestamps when data leaves the kernel. */
SOF_TIMESTAMPING_RX_HARDWARE = (1 << 2) /* Request rx timestamps generated by the network adapter. */
SOF_TIMESTAMPING_RX_SOFTWARE = (1 << 3) /* Request rx timestamps when data enters the kernel. */
SOF_TIMESTAMPING_SOFTWARE = (1 << 4) /* Report any software timestamps when available. */
SOF_TIMESTAMPING_SYS_HARDWARE = (1 << 5) /* This option is deprecated and ignored. */
SOF_TIMESTAMPING_RAW_HARDWARE = (1 << 6) /* Report hardware timestamps. */
SOF_TIMESTAMPING_OPT_ID = (1 << 7) /* Generate a unique identifier along with each packet. */
SOF_TIMESTAMPING_TX_SCHED = (1 << 8) /* Request tx timestamps prior to entering the packet scheduler. */
SOF_TIMESTAMPING_TX_ACK = (1 << 9) /* Request tx timestamps when all data in the send buffer has been acknowledged. */
SOF_TIMESTAMPING_OPT_CMSG = (1 << 10) /* Support recv() cmsg for all timestamped packets. */
SOF_TIMESTAMPING_OPT_TSONLY = (1 << 11) /* Applies to transmit timestamps only. */
SOF_TIMESTAMPING_OPT_STATS = (1 << 12) /* Optional stats that are obtained along with the transmit timestamps. */
SOF_TIMESTAMPING_OPT_PKTINFO = (1 << 13) /* Enable the SCM_TIMESTAMPING_PKTINFO control message for incoming packets with hardware timestamps. */
SOF_TIMESTAMPING_OPT_TX_SWHW = (1 << 14) /* Request both hardware and software timestamps for outgoing packets when SOF_TIMESTAMPING_TX_HARDWARE and SOF_TIMESTAMPING_TX_SOFTWARE are enabled at the same time. */
SOF_TIMESTAMPING_BIND_PHC = (1 << 15) /* Bind the socket to a specific PTP Hardware Clock. */
)
const (
@@ -266,6 +379,7 @@ const (
HWTSTAMP_FILTER_NTP_ALL /* NTP, UDP, all versions and packet modes */
)
// TimestampingInformation contains PTP timetstapming information
type TimestampingInformation struct {
Cmd uint32
SoTimestamping uint32 /* SOF_TIMESTAMPING_* bitmask */
@@ -276,14 +390,14 @@ type TimestampingInformation struct {
rxReserved [3]uint32
}
type ethtoolGStrings struct {
type EthtoolGStrings struct {
cmd uint32
string_set uint32
len uint32
data [MAX_GSTRINGS * ETH_GSTRING_LEN]byte
}
type ethtoolStats struct {
type EthtoolStats struct {
cmd uint32
n_stats uint32
data [MAX_GSTRINGS]uint64
@@ -315,15 +429,53 @@ type ethtoolPermAddr struct {
data [PERMADDR_LEN]byte
}
// Ring is a ring config for an interface
type Ring struct {
Cmd uint32
RxMaxPending uint32
RxMiniMaxPending uint32
RxJumboMaxPending uint32
TxMaxPending uint32
RxPending uint32
RxMiniPending uint32
RxJumboPending uint32
TxPending uint32
}
// Pause is a pause config for an interface
type Pause struct {
Cmd uint32
Autoneg uint32
RxPause uint32
TxPause uint32
}
// Ethtool is a struct that contains the file descriptor for the ethtool
type Ethtool struct {
fd int
}
// max values for my setup dont know how to make this dynamic
const MAX_INDIR_SIZE = 256
const MAX_CORES = 32
type Indir struct {
Cmd uint32
Size uint32
RingIndex [MAX_INDIR_SIZE]uint32 // statically definded otherwise crash
}
type SetIndir struct {
Equal uint8 // used to set number of cores
Weight []uint32 // used to select cores
}
// Convert zero-terminated array of chars (string in C) to a Go string.
func goString(s []byte) string {
strEnd := bytes.IndexByte(s, 0)
if strEnd == -1 {
return string(s[:])
return string(s)
}
return string(s[:strEnd])
}
@@ -356,7 +508,7 @@ func (e *Ethtool) ModuleEeprom(intf string) ([]byte, error) {
return eeprom.data[:eeprom.len], nil
}
// ModuleEeprom returns Eeprom information of the given interface name.
// ModuleEepromHex returns Eeprom information as hexadecimal string
func (e *Ethtool) ModuleEepromHex(intf string) (string, error) {
eeprom, _, err := e.getModuleEeprom(intf)
if err != nil {
@@ -391,6 +543,36 @@ func (e *Ethtool) DriverInfo(intf string) (DrvInfo, error) {
return drvInfo, nil
}
// GetIndir retrieves the indirection table of the given interface name.
func (e *Ethtool) GetIndir(intf string) (Indir, error) {
indir, err := e.getIndir(intf)
if err != nil {
return Indir{}, err
}
return indir, nil
}
// SetIndir sets the indirection table of the given interface from the SetIndir struct
func (e *Ethtool) SetIndir(intf string, setIndir SetIndir) (Indir, error) {
if setIndir.Equal != 0 && setIndir.Weight != nil {
return Indir{}, fmt.Errorf("equal and weight options are mutually exclusive")
}
indir, err := e.GetIndir(intf)
if err != nil {
return Indir{}, err
}
newindir, err := e.setIndir(intf, indir, setIndir)
if err != nil {
return Indir{}, err
}
return newindir, nil
}
// GetChannels returns the number of channels for the given interface name.
func (e *Ethtool) GetChannels(intf string) (Channels, error) {
channels, err := e.getChannels(intf)
@@ -421,6 +603,15 @@ func (e *Ethtool) GetCoalesce(intf string) (Coalesce, error) {
return coalesce, nil
}
// SetCoalesce sets the coalesce config for the given interface name.
func (e *Ethtool) SetCoalesce(intf string, coalesce Coalesce) (Coalesce, error) {
coalesce, err := e.setCoalesce(intf, coalesce)
if err != nil {
return Coalesce{}, err
}
return coalesce, nil
}
// GetTimestampingInformation returns the PTP timestamping information for the given interface name.
func (e *Ethtool) GetTimestampingInformation(intf string) (TimestampingInformation, error) {
ts, err := e.getTimestampingInformation(intf)
@@ -453,6 +644,31 @@ func (e *Ethtool) PermAddr(intf string) (string, error) {
), nil
}
// GetWakeOnLan returns the WoL config for the given interface name.
func (e *Ethtool) GetWakeOnLan(intf string) (WakeOnLan, error) {
wol := WakeOnLan{
Cmd: ETHTOOL_GWOL,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&wol))); err != nil {
return WakeOnLan{}, err
}
return wol, nil
}
// SetWakeOnLan sets the WoL config for the given interface name and
// returns the new WoL config.
func (e *Ethtool) SetWakeOnLan(intf string, wol WakeOnLan) (WakeOnLan, error) {
wol.Cmd = ETHTOOL_SWOL
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&wol))); err != nil {
return WakeOnLan{}, err
}
return wol, nil
}
func (e *Ethtool) ioctl(intf string, data uintptr) error {
var name [IFNAMSIZ]byte
copy(name[:], []byte(intf))
@@ -482,6 +698,92 @@ func (e *Ethtool) getDriverInfo(intf string) (ethtoolDrvInfo, error) {
return drvinfo, nil
}
// parsing of do_grxfhindir from ethtool.c
func (e *Ethtool) getIndir(intf string) (Indir, error) {
indir_head := Indir{
Cmd: ETHTOOL_GRXFHINDIR,
Size: 0,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&indir_head))); err != nil {
return Indir{}, err
}
indir := Indir{
Cmd: ETHTOOL_GRXFHINDIR,
Size: indir_head.Size,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&indir))); err != nil {
return Indir{}, err
}
return indir, nil
}
// parsing of do_srxfhindir from ethtool.c
func (e *Ethtool) setIndir(intf string, indir Indir, setIndir SetIndir) (Indir, error) {
err := fillIndirTable(&indir.Size, indir.RingIndex[:], 0, 0, int(setIndir.Equal), setIndir.Weight, uint32(len(setIndir.Weight)))
if err != nil {
return Indir{}, err
}
if indir.Size == ETH_RXFH_INDIR_NO_CHANGE {
indir.Size = MAX_INDIR_SIZE
return indir, nil
}
indir.Cmd = ETHTOOL_SRXFHINDIR
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&indir))); err != nil {
return Indir{}, err
}
return indir, nil
}
func fillIndirTable(indirSize *uint32, indir []uint32, rxfhindirDefault int,
rxfhindirStart int, rxfhindirEqual int, rxfhindirWeight []uint32,
numWeights uint32) error {
switch {
case rxfhindirEqual != 0:
for i := uint32(0); i < *indirSize; i++ {
indir[i] = uint32(rxfhindirStart) + (i % uint32(rxfhindirEqual))
}
case rxfhindirWeight != nil:
var sum, partial uint32 = 0, 0
var j, weight uint32
for j = range numWeights {
weight = rxfhindirWeight[j]
sum += weight
}
if sum == 0 {
return fmt.Errorf("at least one weight must be non-zero")
}
if sum > *indirSize {
return fmt.Errorf("total weight exceeds the size of the indirection table")
}
j = ^uint32(0) // equivalent to -1 for unsigned
for i := uint32(0); i < *indirSize; i++ {
for i >= (*indirSize*partial)/sum {
j++
weight = rxfhindirWeight[j]
partial += weight
}
indir[i] = uint32(rxfhindirStart) + j
}
case rxfhindirDefault != 0:
*indirSize = 0
default:
*indirSize = ETH_RXFH_INDIR_NO_CHANGE
}
return nil
}
func (e *Ethtool) getChannels(intf string) (Channels, error) {
channels := Channels{
Cmd: ETHTOOL_GCHANNELS,
@@ -516,6 +818,16 @@ func (e *Ethtool) getCoalesce(intf string) (Coalesce, error) {
return coalesce, nil
}
func (e *Ethtool) setCoalesce(intf string, coalesce Coalesce) (Coalesce, error) {
coalesce.Cmd = ETHTOOL_SCOALESCE
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&coalesce))); err != nil {
return Coalesce{}, err
}
return coalesce, nil
}
func (e *Ethtool) getTimestampingInformation(intf string) (TimestampingInformation, error) {
ts := TimestampingInformation{
Cmd: ETHTOOL_GET_TS_INFO,
@@ -567,10 +879,75 @@ func (e *Ethtool) getModuleEeprom(intf string) (ethtoolEeprom, ethtoolModInfo, e
return eeprom, modInfo, nil
}
// GetRing retrieves ring parameters of the given interface name.
func (e *Ethtool) GetRing(intf string) (Ring, error) {
ring := Ring{
Cmd: ETHTOOL_GRINGPARAM,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&ring))); err != nil {
return Ring{}, err
}
return ring, nil
}
// SetRing sets ring parameters of the given interface name.
func (e *Ethtool) SetRing(intf string, ring Ring) (Ring, error) {
ring.Cmd = ETHTOOL_SRINGPARAM
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&ring))); err != nil {
return Ring{}, err
}
return ring, nil
}
// GetPause retrieves pause parameters of the given interface name.
func (e *Ethtool) GetPause(intf string) (Pause, error) {
pause := Pause{
Cmd: ETHTOOL_GPAUSEPARAM,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&pause))); err != nil {
return Pause{}, err
}
return pause, nil
}
// SetPause sets pause parameters of the given interface name.
func (e *Ethtool) SetPause(intf string, pause Pause) (Pause, error) {
pause.Cmd = ETHTOOL_SPAUSEPARAM
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&pause))); err != nil {
return Pause{}, err
}
return pause, nil
}
func isFeatureBitSet(blocks [MAX_FEATURE_BLOCKS]ethtoolGetFeaturesBlock, index uint) bool {
return (blocks)[index/32].active&(1<<(index%32)) != 0
}
// FeatureState contains the state of a feature.
type FeatureState struct {
Available bool
Requested bool
Active bool
NeverChanged bool
}
func getFeatureStateBits(blocks [MAX_FEATURE_BLOCKS]ethtoolGetFeaturesBlock, index uint) FeatureState {
return FeatureState{
Available: (blocks)[index/32].available&(1<<(index%32)) != 0,
Requested: (blocks)[index/32].requested&(1<<(index%32)) != 0,
Active: (blocks)[index/32].active&(1<<(index%32)) != 0,
NeverChanged: (blocks)[index/32].never_changed&(1<<(index%32)) != 0,
}
}
func setFeatureBit(blocks *[MAX_FEATURE_BLOCKS]ethtoolSetFeaturesBlock, index uint, value bool) {
blockIndex, bitIndex := index/32, index%32
@@ -583,27 +960,28 @@ func setFeatureBit(blocks *[MAX_FEATURE_BLOCKS]ethtoolSetFeaturesBlock, index ui
}
}
// FeatureNames shows supported features by their name.
func (e *Ethtool) FeatureNames(intf string) (map[string]uint, error) {
func (e *Ethtool) getNames(intf string, mask int) (map[string]uint, error) {
ssetInfo := ethtoolSsetInfo{
cmd: ETHTOOL_GSSET_INFO,
sset_mask: 1 << ETH_SS_FEATURES,
sset_mask: 1 << mask,
data: [MAX_SSET_INFO]uint32{},
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&ssetInfo))); err != nil {
return nil, err
}
length := uint32(ssetInfo.data)
/* we only read data on first index because single bit was set in sset_mask(0x10) */
length := ssetInfo.data[0]
if length == 0 {
return map[string]uint{}, nil
} else if length > MAX_GSTRINGS {
return nil, fmt.Errorf("ethtool currently doesn't support more than %d entries, received %d", MAX_GSTRINGS, length)
}
gstrings := ethtoolGStrings{
gstrings := EthtoolGStrings{
cmd: ETHTOOL_GSTRINGS,
string_set: ETH_SS_FEATURES,
string_set: uint32(mask),
len: length,
data: [MAX_GSTRINGS * ETH_GSTRING_LEN]byte{},
}
@@ -612,7 +990,7 @@ func (e *Ethtool) FeatureNames(intf string) (map[string]uint, error) {
return nil, err
}
var result = make(map[string]uint)
result := make(map[string]uint)
for i := 0; i != int(length); i++ {
b := gstrings.data[i*ETH_GSTRING_LEN : i*ETH_GSTRING_LEN+ETH_GSTRING_LEN]
key := goString(b)
@@ -624,6 +1002,11 @@ func (e *Ethtool) FeatureNames(intf string) (map[string]uint, error) {
return result, nil
}
// FeatureNames shows supported features by their name.
func (e *Ethtool) FeatureNames(intf string) (map[string]uint, error) {
return e.getNames(intf, ETH_SS_FEATURES)
}
// Features retrieves features of the given interface name.
func (e *Ethtool) Features(intf string) (map[string]bool, error) {
names, err := e.FeatureNames(intf)
@@ -645,7 +1028,7 @@ func (e *Ethtool) Features(intf string) (map[string]bool, error) {
return nil, err
}
var result = make(map[string]bool, length)
result := make(map[string]bool, length)
for key, index := range names {
result[key] = isFeatureBitSet(features.blocks, index)
}
@@ -653,6 +1036,36 @@ func (e *Ethtool) Features(intf string) (map[string]bool, error) {
return result, nil
}
// FeaturesWithState retrieves features of the given interface name,
// with extra flags to explain if they can be enabled
func (e *Ethtool) FeaturesWithState(intf string) (map[string]FeatureState, error) {
names, err := e.FeatureNames(intf)
if err != nil {
return nil, err
}
length := uint32(len(names))
if length == 0 {
return map[string]FeatureState{}, nil
}
features := ethtoolGfeatures{
cmd: ETHTOOL_GFEATURES,
size: (length + 32 - 1) / 32,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&features))); err != nil {
return nil, err
}
var result = make(map[string]FeatureState, length)
for key, index := range names {
result[key] = getFeatureStateBits(features.blocks, index)
}
return result, nil
}
// Change requests a change in the given device's features.
func (e *Ethtool) Change(intf string, config map[string]bool) error {
names, err := e.FeatureNames(intf)
@@ -678,7 +1091,69 @@ func (e *Ethtool) Change(intf string, config map[string]bool) error {
return e.ioctl(intf, uintptr(unsafe.Pointer(&features)))
}
// Get state of a link.
// PrivFlagsNames shows supported private flags by their name.
func (e *Ethtool) PrivFlagsNames(intf string) (map[string]uint, error) {
return e.getNames(intf, ETH_SS_PRIV_FLAGS)
}
// PrivFlags retrieves private flags of the given interface name.
func (e *Ethtool) PrivFlags(intf string) (map[string]bool, error) {
names, err := e.PrivFlagsNames(intf)
if err != nil {
return nil, err
}
length := uint32(len(names))
if length == 0 {
return map[string]bool{}, nil
}
var val ethtoolLink
val.cmd = ETHTOOL_GPFLAGS
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&val))); err != nil {
return nil, err
}
result := make(map[string]bool, length)
for name, mask := range names {
result[name] = val.data&(1<<mask) != 0
}
return result, nil
}
// UpdatePrivFlags requests a change in the given device's private flags.
func (e *Ethtool) UpdatePrivFlags(intf string, config map[string]bool) error {
names, err := e.PrivFlagsNames(intf)
if err != nil {
return err
}
var curr ethtoolLink
curr.cmd = ETHTOOL_GPFLAGS
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&curr))); err != nil {
return err
}
var update ethtoolLink
update.cmd = ETHTOOL_SPFLAGS
update.data = curr.data
for name, value := range config {
if index, ok := names[name]; ok {
if value {
update.data |= 1 << index
} else {
update.data &= ^(1 << index)
}
} else {
return fmt.Errorf("unsupported priv flag %q", name)
}
}
return e.ioctl(intf, uintptr(unsafe.Pointer(&update)))
}
// LinkState get the state of a link.
func (e *Ethtool) LinkState(intf string) (uint32, error) {
x := ethtoolLink{
cmd: ETHTOOL_GLINK,
@@ -692,7 +1167,23 @@ func (e *Ethtool) LinkState(intf string) (uint32, error) {
}
// Stats retrieves stats of the given interface name.
// This maintains backward compatibility with existing code.
func (e *Ethtool) Stats(intf string) (map[string]uint64, error) {
// Create temporary buffers and delegate to StatsWithBuffer
gstrings := gstringsPool.Get().(*EthtoolGStrings)
stats := statsPool.Get().(*EthtoolStats)
defer func() {
gstringsPool.Put(gstrings)
statsPool.Put(stats)
}()
return e.StatsWithBuffer(intf, gstrings, stats)
}
// StatsWithBuffer retrieves stats of the given interface name using pre-allocated buffers.
// This allows the caller to control where the large structures are allocated,
// which can be useful to avoid heap allocations in Go 1.24+.
func (e *Ethtool) StatsWithBuffer(intf string, gstringsPtr *EthtoolGStrings, statsPtr *EthtoolStats) (map[string]uint64, error) {
drvinfo := ethtoolDrvInfo{
cmd: ETHTOOL_GDRVINFO,
}
@@ -701,41 +1192,37 @@ func (e *Ethtool) Stats(intf string) (map[string]uint64, error) {
return nil, err
}
if drvinfo.n_stats*ETH_GSTRING_LEN > MAX_GSTRINGS*ETH_GSTRING_LEN {
if drvinfo.n_stats > MAX_GSTRINGS {
return nil, fmt.Errorf("ethtool currently doesn't support more than %d entries, received %d", MAX_GSTRINGS, drvinfo.n_stats)
}
gstrings := ethtoolGStrings{
cmd: ETHTOOL_GSTRINGS,
string_set: ETH_SS_STATS,
len: drvinfo.n_stats,
data: [MAX_GSTRINGS * ETH_GSTRING_LEN]byte{},
}
gstringsPtr.cmd = ETHTOOL_GSTRINGS
gstringsPtr.string_set = ETH_SS_STATS
gstringsPtr.len = drvinfo.n_stats
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&gstrings))); err != nil {
if err := e.ioctl(intf, uintptr(unsafe.Pointer(gstringsPtr))); err != nil {
return nil, err
}
stats := ethtoolStats{
cmd: ETHTOOL_GSTATS,
n_stats: drvinfo.n_stats,
data: [MAX_GSTRINGS]uint64{},
}
statsPtr.cmd = ETHTOOL_GSTATS
statsPtr.n_stats = drvinfo.n_stats
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&stats))); err != nil {
if err := e.ioctl(intf, uintptr(unsafe.Pointer(statsPtr))); err != nil {
return nil, err
}
var result = make(map[string]uint64)
result := make(map[string]uint64, drvinfo.n_stats)
for i := 0; i != int(drvinfo.n_stats); i++ {
b := gstrings.data[i*ETH_GSTRING_LEN : i*ETH_GSTRING_LEN+ETH_GSTRING_LEN]
strEnd := strings.Index(string(b), "\x00")
b := gstringsPtr.data[i*ETH_GSTRING_LEN : (i+1)*ETH_GSTRING_LEN]
strEnd := bytes.IndexByte(b, 0)
if strEnd == -1 {
strEnd = ETH_GSTRING_LEN
}
key := string(b[:strEnd])
if len(key) != 0 {
result[key] = stats.data[i]
result[key] = statsPtr.data[i]
}
}
@@ -747,9 +1234,23 @@ func (e *Ethtool) Close() {
unix.Close(e.fd)
}
// Identity the nic with blink duration, if not specify blink for 60 seconds
func (e *Ethtool) Identity(intf string, duration *time.Duration) error {
dur := uint32(DEFAULT_BLINK_DURATION.Seconds())
if duration != nil {
dur = uint32(duration.Seconds())
}
return e.identity(intf, IdentityConf{Duration: dur})
}
func (e *Ethtool) identity(intf string, identity IdentityConf) error {
identity.Cmd = ETHTOOL_PHYS_ID
return e.ioctl(intf, uintptr(unsafe.Pointer(&identity)))
}
// NewEthtool returns a new ethtool handler
func NewEthtool() (*Ethtool, error) {
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, unix.IPPROTO_IP)
if err != nil {
return nil, err
}
@@ -798,3 +1299,46 @@ func PermAddr(intf string) (string, error) {
defer e.Close()
return e.PermAddr(intf)
}
// Identity the nic with blink duration, if not specify blink infinity
func Identity(intf string, duration *time.Duration) error {
e, err := NewEthtool()
if err != nil {
return err
}
defer e.Close()
return e.Identity(intf, duration)
}
func supportedSpeeds(mask uint64) (ret []struct {
name string
mask uint64
speed uint64
}) {
for _, mode := range supportedCapabilities {
if mode.speed > 0 && ((1<<mode.mask)&mask) != 0 {
ret = append(ret, mode)
}
}
return ret
}
// SupportedLinkModes returns the names of the link modes supported by the interface.
func SupportedLinkModes(mask uint64) []string {
var ret []string
for _, mode := range supportedSpeeds(mask) {
ret = append(ret, mode.name)
}
return ret
}
// SupportedSpeed returns the maximum capacity of this interface.
func SupportedSpeed(mask uint64) uint64 {
var ret uint64
for _, mode := range supportedSpeeds(mask) {
if mode.speed > ret {
ret = mode.speed
}
}
return ret
}
+18 -22
View File
@@ -33,7 +33,9 @@ import (
"golang.org/x/sys/unix"
)
type EthtoolCmd struct { /* ethtool.c: struct ethtool_cmd */
// EthtoolCmd is the Go version of the Linux kerne ethtool_cmd struct
// see ethtool.c
type EthtoolCmd struct {
Cmd uint32
Supported uint32
Advertising uint32
@@ -75,7 +77,7 @@ func (ecmd *EthtoolCmd) CmdSet(intf string) (uint32, error) {
return e.CmdSet(ecmd, intf)
}
func (f *EthtoolCmd) reflect(retv *map[string]uint64) {
func (f *EthtoolCmd) reflect(retv map[string]uint64) {
val := reflect.ValueOf(f).Elem()
for i := 0; i < val.NumField(); i++ {
@@ -83,29 +85,22 @@ func (f *EthtoolCmd) reflect(retv *map[string]uint64) {
typeField := val.Type().Field(i)
t := valueField.Interface()
//tt := reflect.TypeOf(t)
//fmt.Printf(" t %T %v tt %T %v\n", t, t, tt, tt)
switch t.(type) {
switch tt := t.(type) {
case uint32:
//fmt.Printf(" t is uint32\n")
(*retv)[typeField.Name] = uint64(t.(uint32))
retv[typeField.Name] = uint64(tt)
case uint16:
(*retv)[typeField.Name] = uint64(t.(uint16))
retv[typeField.Name] = uint64(tt)
case uint8:
(*retv)[typeField.Name] = uint64(t.(uint8))
retv[typeField.Name] = uint64(tt)
case int32:
(*retv)[typeField.Name] = uint64(t.(int32))
retv[typeField.Name] = uint64(tt)
case int16:
(*retv)[typeField.Name] = uint64(t.(int16))
retv[typeField.Name] = uint64(tt)
case int8:
(*retv)[typeField.Name] = uint64(t.(int8))
retv[typeField.Name] = uint64(tt)
default:
(*retv)[typeField.Name+"_unknown_type"] = 0
retv[typeField.Name+"_unknown_type"] = 0
}
//tag := typeField.Tag
//fmt.Printf("Field Name: %s,\t Field Value: %v,\t Tag Value: %s\n",
// typeField.Name, valueField.Interface(), tag.Get("tag_name"))
}
}
@@ -128,7 +123,7 @@ func (e *Ethtool) CmdGet(ecmd *EthtoolCmd, intf string) (uint32, error) {
return 0, ep
}
var speedval uint32 = (uint32(ecmd.Speed_hi) << 16) |
var speedval = (uint32(ecmd.Speed_hi) << 16) |
(uint32(ecmd.Speed) & 0xffff)
if speedval == math.MaxUint16 {
speedval = math.MaxUint32
@@ -156,7 +151,7 @@ func (e *Ethtool) CmdSet(ecmd *EthtoolCmd, intf string) (uint32, error) {
return 0, unix.Errno(ep)
}
var speedval uint32 = (uint32(ecmd.Speed_hi) << 16) |
var speedval = (uint32(ecmd.Speed_hi) << 16) |
(uint32(ecmd.Speed) & 0xffff)
if speedval == math.MaxUint16 {
speedval = math.MaxUint32
@@ -185,19 +180,20 @@ func (e *Ethtool) CmdGetMapped(intf string) (map[string]uint64, error) {
return nil, ep
}
var result = make(map[string]uint64)
result := make(map[string]uint64)
// ref https://gist.github.com/drewolson/4771479
// Golang Reflection Example
ecmd.reflect(&result)
ecmd.reflect(result)
var speedval uint32 = (uint32(ecmd.Speed_hi) << 16) |
var speedval = (uint32(ecmd.Speed_hi) << 16) |
(uint32(ecmd.Speed) & 0xffff)
result["speed"] = uint64(speedval)
return result, nil
}
// CmdGetMapped returns the interface settings in a map
func CmdGetMapped(intf string) (map[string]uint64, error) {
e, err := NewEthtool()
if err != nil {
+30
View File
@@ -0,0 +1,30 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package ethtool
var supportedCapabilities = []struct {
name string
mask uint64
speed uint64
}{
// no supported capabilities on darwin
}
+324
View File
@@ -0,0 +1,324 @@
package ethtool
import (
"errors"
"fmt"
"math"
"strings"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
type LinkSettingSource string
// Constants defining the source of the LinkSettings data
const (
SourceGLinkSettings LinkSettingSource = "GLINKSETTINGS"
SourceGSet LinkSettingSource = "GSET"
)
// EthtoolLinkSettingsFixed corresponds to struct ethtool_link_settings fixed part
type EthtoolLinkSettingsFixed struct {
Cmd uint32
Speed uint32
Duplex uint8
Port uint8
PhyAddress uint8
Autoneg uint8
MdixSupport uint8 // Renamed from mdio_support
EthTpMdix uint8
EthTpMdixCtrl uint8
LinkModeMasksNwords int8
Transceiver uint8
MasterSlaveCfg uint8
MasterSlaveState uint8
Reserved1 [1]byte
Reserved [7]uint32
// Flexible array member link_mode_masks[0] starts here implicitly
}
// ethtoolLinkSettingsRequest includes space for the flexible array members
type ethtoolLinkSettingsRequest struct {
Settings EthtoolLinkSettingsFixed
Masks [3 * MAX_LINK_MODE_MASK_NWORDS]uint32 // Uses MAX_LINK_MODE_MASK_NWORDS constant from ethtool.go
}
// LinkSettings is the user-friendly representation returned by GetLinkSettings
type LinkSettings struct {
Speed uint32
Duplex uint8
Port uint8
PhyAddress uint8
Autoneg uint8
MdixSupport uint8
EthTpMdix uint8
EthTpMdixCtrl uint8
Transceiver uint8
MasterSlaveCfg uint8
MasterSlaveState uint8
SupportedLinkModes []string
AdvertisingLinkModes []string
LpAdvertisingModes []string
Source LinkSettingSource // "GSET" or "GLINKSETTINGS"
}
// GetLinkSettings retrieves link settings, preferring ETHTOOL_GLINKSETTINGS and falling back to ETHTOOL_GSET.
// Uses a single ioctl call with the maximum expected buffer size.
func (e *Ethtool) GetLinkSettings(intf string) (*LinkSettings, error) {
// 1. Attempt ETHTOOL_GLINKSETTINGS with max buffer size
var req ethtoolLinkSettingsRequest
req.Settings.Cmd = ETHTOOL_GLINKSETTINGS
// Provide the maximum expected nwords based on our constant
req.Settings.LinkModeMasksNwords = int8(MAX_LINK_MODE_MASK_NWORDS)
err := e.ioctl(intf, uintptr(unsafe.Pointer(&req)))
fallbackReason := ""
var errno syscall.Errno
switch {
case errors.As(err, &errno) && errors.Is(errno, unix.EOPNOTSUPP):
// Condition 1: ioctl returned EOPNOTSUPP
fallbackReason = "EOPNOTSUPP"
case err == nil:
// Condition 2: ioctl succeeded, but nwords might be invalid or buffer too small
nwords := int(req.Settings.LinkModeMasksNwords)
switch {
case nwords <= 0 || nwords > MAX_LINK_MODE_MASK_NWORDS:
// Sub-case 2a: Invalid nwords -> fallback
fmt.Printf("Warning: GLINKSETTINGS succeeded but returned invalid nwords (%d), attempting fallback to GSET\n", nwords)
fallbackReason = "invalid nwords from GLINKSETTINGS"
case 3*nwords > len(req.Masks):
// Sub-case 2b: Buffer too small -> error
return nil, fmt.Errorf("kernel requires %d words for GLINKSETTINGS, buffer only has space for %d (max %d)", nwords, len(req.Masks)/3, MAX_LINK_MODE_MASK_NWORDS)
default:
// Sub-case 2c: Success (nwords valid and buffer sufficient)
results := &LinkSettings{
Speed: req.Settings.Speed,
Duplex: req.Settings.Duplex,
Port: req.Settings.Port,
PhyAddress: req.Settings.PhyAddress,
Autoneg: req.Settings.Autoneg,
MdixSupport: req.Settings.MdixSupport,
EthTpMdix: req.Settings.EthTpMdix,
EthTpMdixCtrl: req.Settings.EthTpMdixCtrl,
Transceiver: req.Settings.Transceiver,
MasterSlaveCfg: req.Settings.MasterSlaveCfg,
MasterSlaveState: req.Settings.MasterSlaveState,
SupportedLinkModes: parseLinkModeMasks(req.Masks[0*nwords : 1*nwords]),
AdvertisingLinkModes: parseLinkModeMasks(req.Masks[1*nwords : 2*nwords]),
LpAdvertisingModes: parseLinkModeMasks(req.Masks[2*nwords : 3*nwords]),
Source: SourceGLinkSettings,
}
return results, nil
}
default:
// Condition 3: ioctl failed with an error other than EOPNOTSUPP
// No fallback in this case.
return nil, fmt.Errorf("ETHTOOL_GLINKSETTINGS ioctl failed: %w", err)
}
// Fallback to ETHTOOL_GSET using e.CmdGet
var cmd EthtoolCmd
_, errGet := e.CmdGet(&cmd, intf)
if errGet != nil {
return nil, fmt.Errorf("ETHTOOL_GLINKSETTINGS failed (%s), fallback ETHTOOL_GSET (CmdGet) also failed: %w", fallbackReason, errGet)
}
results := convertCmdToLinkSettings(&cmd)
results.Source = SourceGSet
return results, nil
}
// SetLinkSettings applies link settings, determining whether to use ETHTOOL_SLINKSETTINGS or ETHTOOL_SSET.
func (e *Ethtool) SetLinkSettings(intf string, settings *LinkSettings) error {
var checkReq ethtoolLinkSettingsRequest
checkReq.Settings.Cmd = ETHTOOL_GLINKSETTINGS
checkReq.Settings.LinkModeMasksNwords = int8(MAX_LINK_MODE_MASK_NWORDS)
errGLinkSettings := e.ioctl(intf, uintptr(unsafe.Pointer(&checkReq)))
canUseGLinkSettings := false
nwords := 0
if errGLinkSettings == nil {
nwords = int(checkReq.Settings.LinkModeMasksNwords)
if nwords <= 0 || nwords > MAX_LINK_MODE_MASK_NWORDS {
return fmt.Errorf("ETHTOOL_GLINKSETTINGS check succeeded but returned invalid nwords: %d", nwords)
}
canUseGLinkSettings = true
} else {
var errno syscall.Errno
if !errors.As(errGLinkSettings, &errno) || !errors.Is(errno, unix.EOPNOTSUPP) {
return fmt.Errorf("checking support via ETHTOOL_GLINKSETTINGS failed: %w", errGLinkSettings)
}
}
if canUseGLinkSettings {
var setReq ethtoolLinkSettingsRequest
if 3*nwords > len(setReq.Masks) {
return fmt.Errorf("internal error: required nwords (%d) exceeds allocated buffer (%d)", nwords, MAX_LINK_MODE_MASK_NWORDS)
}
setReq.Settings.Cmd = ETHTOOL_SLINKSETTINGS
setReq.Settings.Speed = settings.Speed
setReq.Settings.Duplex = settings.Duplex
setReq.Settings.Port = settings.Port
setReq.Settings.PhyAddress = settings.PhyAddress
setReq.Settings.Autoneg = settings.Autoneg
setReq.Settings.EthTpMdixCtrl = settings.EthTpMdixCtrl
setReq.Settings.MasterSlaveCfg = settings.MasterSlaveCfg
setReq.Settings.LinkModeMasksNwords = int8(nwords)
advertisingMask := buildLinkModeMask(settings.AdvertisingLinkModes, nwords)
if len(advertisingMask) != nwords {
return fmt.Errorf("failed to build advertising mask with correct size (%d != %d)", len(advertisingMask), nwords)
}
copy(setReq.Masks[nwords:2*nwords], advertisingMask)
zeroMaskSupported := make([]uint32, nwords)
zeroMaskLp := make([]uint32, nwords)
copy(setReq.Masks[0*nwords:1*nwords], zeroMaskSupported)
copy(setReq.Masks[2*nwords:3*nwords], zeroMaskLp)
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&setReq))); err != nil {
return fmt.Errorf("ETHTOOL_SLINKSETTINGS ioctl failed: %w", err)
}
return nil
}
// Check if trying to set high bits when only SSET is available
advertisingMaskCheck := buildLinkModeMask(settings.AdvertisingLinkModes, MAX_LINK_MODE_MASK_NWORDS)
for i := 1; i < len(advertisingMaskCheck); i++ {
if advertisingMaskCheck[i] != 0 {
return fmt.Errorf("cannot set link modes beyond 32 bits using legacy ETHTOOL_SSET; device does not support ETHTOOL_SLINKSETTINGS")
}
}
// Fallback to SSET
cmd := convertLinkSettingsToCmd(settings)
_, errSet := e.CmdSet(cmd, intf)
if errSet != nil {
return fmt.Errorf("ETHTOOL_SLINKSETTINGS not supported, fallback ETHTOOL_SSET (CmdSet) failed: %w", errSet)
}
return nil
}
// parseLinkModeMasks converts a slice of uint32 bitmasks to a list of mode names.
// It filters out non-speed/duplex modes (like TP, Autoneg, Pause).
func parseLinkModeMasks(mask []uint32) []string {
modes := make([]string, 0, 8)
for _, capability := range supportedCapabilities {
// Only include capabilities that represent a speed/duplex mode
if capability.speed > 0 {
bitIndex := int(capability.mask)
wordIndex := bitIndex / 32
bitInWord := uint(bitIndex % 32)
if wordIndex < len(mask) && (mask[wordIndex]>>(bitInWord))&1 != 0 {
modes = append(modes, capability.name)
}
}
}
return modes
}
// buildLinkModeMask converts a list of mode names back into a uint32 bitmask slice.
// It filters out non-speed/duplex modes.
func buildLinkModeMask(modes []string, nwords int) []uint32 {
if nwords <= 0 || nwords > MAX_LINK_MODE_MASK_NWORDS {
return make([]uint32, 0)
}
mask := make([]uint32, nwords)
modeMap := make(map[string]struct {
bitIndex int
speed uint64
})
for _, capability := range supportedCapabilities {
// Only consider capabilities that represent a speed/duplex mode
if capability.speed > 0 {
modeMap[capability.name] = struct {
bitIndex int
speed uint64
}{bitIndex: int(capability.mask), speed: capability.speed}
}
}
for _, modeName := range modes {
if info, ok := modeMap[strings.TrimSpace(modeName)]; ok {
wordIndex := info.bitIndex / 32
bitInWord := uint(info.bitIndex % 32)
if wordIndex < nwords {
mask[wordIndex] |= 1 << bitInWord
} else {
fmt.Printf("Warning: Link mode '%s' (bit %d) exceeds device's mask size (%d words)\n", modeName, info.bitIndex, nwords)
}
} else {
// Check if the user provided a non-speed mode name - ignore it for the mask, maybe warn?
isKnownNonSpeed := false
for _, capability := range supportedCapabilities {
if capability.speed == 0 && capability.name == strings.TrimSpace(modeName) {
isKnownNonSpeed = true
break
}
}
if !isKnownNonSpeed {
fmt.Printf("Warning: Unknown link mode '%s' specified for mask building\n", modeName)
} // Silently ignore known non-speed modes like Autoneg, TP, Pause for the mask
}
}
return mask
}
// convertCmdToLinkSettings converts data from the legacy EthtoolCmd to the new LinkSettings format.
func convertCmdToLinkSettings(cmd *EthtoolCmd) *LinkSettings {
ls := &LinkSettings{
Speed: (uint32(cmd.Speed_hi) << 16) | uint32(cmd.Speed),
Duplex: cmd.Duplex,
Port: cmd.Port,
PhyAddress: cmd.Phy_address,
Autoneg: cmd.Autoneg,
MdixSupport: cmd.Mdio_support,
EthTpMdix: cmd.Eth_tp_mdix,
EthTpMdixCtrl: ETH_TP_MDI_INVALID,
Transceiver: cmd.Transceiver,
MasterSlaveCfg: 0, // No equivalent in EthtoolCmd
MasterSlaveState: 0, // No equivalent in EthtoolCmd
SupportedLinkModes: parseLegacyLinkModeMask(cmd.Supported),
AdvertisingLinkModes: parseLegacyLinkModeMask(cmd.Advertising),
LpAdvertisingModes: parseLegacyLinkModeMask(cmd.Lp_advertising),
}
if cmd.Speed == math.MaxUint16 && cmd.Speed_hi == math.MaxUint16 {
ls.Speed = SPEED_UNKNOWN // GSET uses 0xFFFF/0xFFFF for unknown/auto
}
return ls
}
// parseLegacyLinkModeMask helper for converting single uint32 mask.
func parseLegacyLinkModeMask(mask uint32) []string {
return parseLinkModeMasks([]uint32{mask})
}
// convertLinkSettingsToCmd converts new LinkSettings data back to the legacy EthtoolCmd format for SSET fallback.
func convertLinkSettingsToCmd(ls *LinkSettings) *EthtoolCmd {
cmd := &EthtoolCmd{}
if ls.Speed == 0 || ls.Speed == SPEED_UNKNOWN {
cmd.Speed = math.MaxUint16
cmd.Speed_hi = math.MaxUint16
} else {
cmd.Speed = uint16(ls.Speed & 0xFFFF)
cmd.Speed_hi = uint16((ls.Speed >> 16) & 0xFFFF)
}
cmd.Duplex = ls.Duplex
cmd.Port = ls.Port
cmd.Phy_address = ls.PhyAddress
cmd.Autoneg = ls.Autoneg
// Cannot set EthTpMdixCtrl via EthtoolCmd
cmd.Transceiver = ls.Transceiver
cmd.Advertising = buildLegacyLinkModeMask(ls.AdvertisingLinkModes)
return cmd
}
// buildLegacyLinkModeMask helper for building single uint32 mask from names.
func buildLegacyLinkModeMask(modes []string) uint32 {
maskSlice := buildLinkModeMask(modes, 1)
if len(maskSlice) > 0 {
return maskSlice[0]
}
return 0
}
+130
View File
@@ -0,0 +1,130 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package ethtool
import (
"golang.org/x/sys/unix"
)
// Updated supportedCapabilities including modes from ethtool.h enum ethtool_link_mode_bit_indices
var supportedCapabilities = []struct {
name string
mask uint64 // Use uint64 to accommodate indices > 31
speed uint64 // Speed in bps, 0 for non-speed modes
}{
// Existing entries (reordered slightly by bit index for clarity)
{"10baseT_Half", unix.ETHTOOL_LINK_MODE_10baseT_Half_BIT, 10_000_000}, // 0
{"10baseT_Full", unix.ETHTOOL_LINK_MODE_10baseT_Full_BIT, 10_000_000}, // 1
{"100baseT_Half", unix.ETHTOOL_LINK_MODE_100baseT_Half_BIT, 100_000_000}, // 2
{"100baseT_Full", unix.ETHTOOL_LINK_MODE_100baseT_Full_BIT, 100_000_000}, // 3
{"1000baseT_Half", unix.ETHTOOL_LINK_MODE_1000baseT_Half_BIT, 1_000_000_000}, // 4
{"1000baseT_Full", unix.ETHTOOL_LINK_MODE_1000baseT_Full_BIT, 1_000_000_000}, // 5
// Newly added or re-confirmed based on full enum
{"Autoneg", unix.ETHTOOL_LINK_MODE_Autoneg_BIT, 0}, // 6
{"TP", unix.ETHTOOL_LINK_MODE_TP_BIT, 0}, // 7 (Twisted Pair port)
{"AUI", unix.ETHTOOL_LINK_MODE_AUI_BIT, 0}, // 8 (AUI port)
{"MII", unix.ETHTOOL_LINK_MODE_MII_BIT, 0}, // 9 (MII port)
{"FIBRE", unix.ETHTOOL_LINK_MODE_FIBRE_BIT, 0}, // 10 (FIBRE port)
{"BNC", unix.ETHTOOL_LINK_MODE_BNC_BIT, 0}, // 11 (BNC port)
{"10000baseT_Full", unix.ETHTOOL_LINK_MODE_10000baseT_Full_BIT, 10_000_000_000}, // 12
{"Pause", unix.ETHTOOL_LINK_MODE_Pause_BIT, 0}, // 13
{"Asym_Pause", unix.ETHTOOL_LINK_MODE_Asym_Pause_BIT, 0}, // 14
{"2500baseX_Full", unix.ETHTOOL_LINK_MODE_2500baseX_Full_BIT, 2_500_000_000}, // 15
{"Backplane", unix.ETHTOOL_LINK_MODE_Backplane_BIT, 0}, // 16 (Backplane port)
{"1000baseKX_Full", unix.ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, 1_000_000_000}, // 17
{"10000baseKX4_Full", unix.ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, 10_000_000_000}, // 18
{"10000baseKR_Full", unix.ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, 10_000_000_000}, // 19
{"10000baseR_FEC", unix.ETHTOOL_LINK_MODE_10000baseR_FEC_BIT, 10_000_000_000}, // 20
{"20000baseMLD2_Full", unix.ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT, 20_000_000_000}, // 21
{"20000baseKR2_Full", unix.ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT, 20_000_000_000}, // 22
{"40000baseKR4_Full", unix.ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, 40_000_000_000}, // 23
{"40000baseCR4_Full", unix.ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, 40_000_000_000}, // 24
{"40000baseSR4_Full", unix.ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, 40_000_000_000}, // 25
{"40000baseLR4_Full", unix.ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, 40_000_000_000}, // 26
{"56000baseKR4_Full", unix.ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT, 56_000_000_000}, // 27
{"56000baseCR4_Full", unix.ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT, 56_000_000_000}, // 28
{"56000baseSR4_Full", unix.ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT, 56_000_000_000}, // 29
{"56000baseLR4_Full", unix.ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT, 56_000_000_000}, // 30
{"25000baseCR_Full", unix.ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, 25_000_000_000}, // 31
// Modes beyond bit 31 (require GLINKSETTINGS)
{"25000baseKR_Full", unix.ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, 25_000_000_000}, // 32
{"25000baseSR_Full", unix.ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, 25_000_000_000}, // 33
{"50000baseCR2_Full", unix.ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, 50_000_000_000}, // 34
{"50000baseKR2_Full", unix.ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, 50_000_000_000}, // 35
{"100000baseKR4_Full", unix.ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, 100_000_000_000}, // 36
{"100000baseSR4_Full", unix.ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, 100_000_000_000}, // 37
{"100000baseCR4_Full", unix.ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, 100_000_000_000}, // 38
{"100000baseLR4_ER4_Full", unix.ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT, 100_000_000_000}, // 39
{"50000baseSR2_Full", unix.ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, 50_000_000_000}, // 40
{"1000baseX_Full", unix.ETHTOOL_LINK_MODE_1000baseX_Full_BIT, 1_000_000_000}, // 41
{"10000baseCR_Full", unix.ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, 10_000_000_000}, // 42
{"10000baseSR_Full", unix.ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, 10_000_000_000}, // 43
{"10000baseLR_Full", unix.ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, 10_000_000_000}, // 44
{"10000baseLRM_Full", unix.ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, 10_000_000_000}, // 45
{"10000baseER_Full", unix.ETHTOOL_LINK_MODE_10000baseER_Full_BIT, 10_000_000_000}, // 46
{"2500baseT_Full", unix.ETHTOOL_LINK_MODE_2500baseT_Full_BIT, 2_500_000_000}, // 47 (already present but reconfirmed)
{"5000baseT_Full", unix.ETHTOOL_LINK_MODE_5000baseT_Full_BIT, 5_000_000_000}, // 48
{"FEC_NONE", unix.ETHTOOL_LINK_MODE_FEC_NONE_BIT, 0}, // 49
{"FEC_RS", unix.ETHTOOL_LINK_MODE_FEC_RS_BIT, 0}, // 50 (Reed-Solomon FEC)
{"FEC_BASER", unix.ETHTOOL_LINK_MODE_FEC_BASER_BIT, 0}, // 51 (BaseR FEC)
{"50000baseKR_Full", unix.ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, 50_000_000_000}, // 52
{"50000baseSR_Full", unix.ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, 50_000_000_000}, // 53
{"50000baseCR_Full", unix.ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, 50_000_000_000}, // 54
{"50000baseLR_ER_FR_Full", unix.ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT, 50_000_000_000}, // 55
{"50000baseDR_Full", unix.ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, 50_000_000_000}, // 56
{"100000baseKR2_Full", unix.ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, 100_000_000_000}, // 57
{"100000baseSR2_Full", unix.ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, 100_000_000_000}, // 58
{"100000baseCR2_Full", unix.ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, 100_000_000_000}, // 59
{"100000baseLR2_ER2_FR2_Full", unix.ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT, 100_000_000_000}, // 60
{"100000baseDR2_Full", unix.ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, 100_000_000_000}, // 61
{"200000baseKR4_Full", unix.ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT, 200_000_000_000}, // 62
{"200000baseSR4_Full", unix.ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT, 200_000_000_000}, // 63
{"200000baseLR4_ER4_FR4_Full", unix.ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT, 200_000_000_000}, // 64
{"200000baseDR4_Full", unix.ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT, 200_000_000_000}, // 65
{"200000baseCR4_Full", unix.ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT, 200_000_000_000}, // 66
{"100baseT1_Full", unix.ETHTOOL_LINK_MODE_100baseT1_Full_BIT, 100_000_000}, // 67 (Automotive/SPE)
{"1000baseT1_Full", unix.ETHTOOL_LINK_MODE_1000baseT1_Full_BIT, 1_000_000_000}, // 68 (Automotive/SPE)
{"400000baseKR8_Full", unix.ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT, 400_000_000_000}, // 69
{"400000baseSR8_Full", unix.ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT, 400_000_000_000}, // 70
{"400000baseLR8_ER8_FR8_Full", unix.ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT, 400_000_000_000}, // 71
{"400000baseDR8_Full", unix.ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT, 400_000_000_000}, // 72
{"400000baseCR8_Full", unix.ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT, 400_000_000_000}, // 73
{"FEC_LLRS", unix.ETHTOOL_LINK_MODE_FEC_LLRS_BIT, 0}, // 74 (Low Latency Reed-Solomon FEC)
// PAM4 modes start here? Often indicated by lack of KR/CR/SR/LR or different naming
{"100000baseKR_Full", unix.ETHTOOL_LINK_MODE_100000baseKR_Full_BIT, 100_000_000_000}, // 75 (Likely 100GBASE-KR1)
{"100000baseSR_Full", unix.ETHTOOL_LINK_MODE_100000baseSR_Full_BIT, 100_000_000_000}, // 76 (Likely 100GBASE-SR1)
{"100000baseLR_ER_FR_Full", unix.ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT, 100_000_000_000}, // 77 (Likely 100GBASE-LR1/ER1/FR1)
{"100000baseCR_Full", unix.ETHTOOL_LINK_MODE_100000baseCR_Full_BIT, 100_000_000_000}, // 78 (Likely 100GBASE-CR1)
{"100000baseDR_Full", unix.ETHTOOL_LINK_MODE_100000baseDR_Full_BIT, 100_000_000_000}, // 79
{"200000baseKR2_Full", unix.ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT, 200_000_000_000}, // 80 (Likely 200GBASE-KR2)
{"200000baseSR2_Full", unix.ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT, 200_000_000_000}, // 81 (Likely 200GBASE-SR2)
{"200000baseLR2_ER2_FR2_Full", unix.ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT, 200_000_000_000}, // 82 (Likely 200GBASE-LR2/etc)
{"200000baseDR2_Full", unix.ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT, 200_000_000_000}, // 83
{"200000baseCR2_Full", unix.ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT, 200_000_000_000}, // 84 (Likely 200GBASE-CR2)
{"400000baseKR4_Full", unix.ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT, 400_000_000_000}, // 85 (Likely 400GBASE-KR4)
{"400000baseSR4_Full", unix.ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT, 400_000_000_000}, // 86 (Likely 400GBASE-SR4)
{"400000baseLR4_ER4_FR4_Full", unix.ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT, 400_000_000_000}, // 87 (Likely 400GBASE-LR4/etc)
{"400000baseDR4_Full", unix.ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT, 400_000_000_000}, // 88
{"400000baseCR4_Full", unix.ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT, 400_000_000_000}, // 89 (Likely 400GBASE-CR4)
{"100baseFX_Half", unix.ETHTOOL_LINK_MODE_100baseFX_Half_BIT, 100_000_000}, // 90
{"100baseFX_Full", unix.ETHTOOL_LINK_MODE_100baseFX_Full_BIT, 100_000_000}, // 91
}