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
67 changes: 67 additions & 0 deletions ethtool_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package netlink

import (
"errors"
"fmt"
"syscall"

"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)

func (h *Handle) ethtoolRequest(command uint8, flags int, attrs []*nl.RtAttr) ([][]syscall.NetlinkRouteAttr, error) {
family, err := h.GenlFamilyGet(nl.ETHTOOL_GENL_NAME)
if err != nil {
return nil, err
}

req := h.newNetlinkRequest(int(family.ID), flags)
req.AddData(&nl.Genlmsg{
Command: command,
Version: nl.ETHTOOL_GENL_VERSION,
})
for _, attr := range attrs {
req.AddData(attr)
}

msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0)
if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, executeErr
}

parsed := make([][]syscall.NetlinkRouteAttr, 0, len(msgs))
for _, msg := range msgs {
if len(msg) < nl.SizeofGenlmsg {
return nil, fmt.Errorf("netlink: short ethtool response: got %d bytes, want at least %d", len(msg), nl.SizeofGenlmsg)
}
attrs, err := nl.ParseRouteAttr(msg[nl.SizeofGenlmsg:])
if err != nil {
return nil, err
}
parsed = append(parsed, attrs)
}
return parsed, executeErr
}

func newEthtoolHeader(attrType, ifIndex int) (*nl.RtAttr, error) {
if ifIndex <= 0 || uint64(ifIndex) > uint64(^uint32(0)) {
return nil, fmt.Errorf("netlink: invalid interface index %d", ifIndex)
}
header := nl.NewRtAttr(unix.NLA_F_NESTED|attrType, nil)
header.AddRtAttr(nl.ETHTOOL_A_HEADER_DEV_INDEX, nl.Uint32Attr(uint32(ifIndex)))
return header, nil
}

func readEthtoolUint8(attr syscall.NetlinkRouteAttr) (uint8, error) {
if len(attr.Value) != 1 {
return 0, fmt.Errorf("netlink: ethtool attribute %d has %d bytes, want 1", attr.Attr.Type&nl.NLA_TYPE_MASK, len(attr.Value))
}
return attr.Value[0], nil
}

func readEthtoolUint32(attr syscall.NetlinkRouteAttr) (uint32, error) {
if len(attr.Value) != 4 {
return 0, fmt.Errorf("netlink: ethtool attribute %d has %d bytes, want 4", attr.Attr.Type&nl.NLA_TYPE_MASK, len(attr.Value))
}
return native.Uint32(attr.Value), nil
}
63 changes: 63 additions & 0 deletions ethtool_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package netlink

import (
"syscall"
"testing"

"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)

func routeAttr(attrType int, value []byte) syscall.NetlinkRouteAttr {
return syscall.NetlinkRouteAttr{
Attr: syscall.RtAttr{Type: uint16(attrType)},
Value: value,
}
}

func parseSerializedAttrs(t *testing.T, attrs []*nl.RtAttr) []syscall.NetlinkRouteAttr {
t.Helper()
var data []byte
for _, attr := range attrs {
data = append(data, attr.Serialize()...)
}
parsed, err := nl.ParseRouteAttr(data)
if err != nil {
t.Fatalf("failed to parse serialized attributes: %v", err)
}
return parsed
}

func attrsByType(attrs []syscall.NetlinkRouteAttr) map[uint16]syscall.NetlinkRouteAttr {
byType := make(map[uint16]syscall.NetlinkRouteAttr, len(attrs))
for _, attr := range attrs {
byType[attr.Attr.Type&nl.NLA_TYPE_MASK] = attr
}
return byType
}

func TestNewEthtoolHeader(t *testing.T) {
header, err := newEthtoolHeader(nl.ETHTOOL_A_RINGS_HEADER, 42)
if err != nil {
t.Fatalf("newEthtoolHeader failed: %v", err)
}
if header.Type != unix.NLA_F_NESTED|nl.ETHTOOL_A_RINGS_HEADER {
t.Fatalf("header type = %#x, want %#x", header.Type, unix.NLA_F_NESTED|nl.ETHTOOL_A_RINGS_HEADER)
}

outer := parseSerializedAttrs(t, []*nl.RtAttr{header})
inner, err := nl.ParseRouteAttr(outer[0].Value)
if err != nil {
t.Fatalf("failed to parse ethtool header: %v", err)
}
if len(inner) != 1 || inner[0].Attr.Type != nl.ETHTOOL_A_HEADER_DEV_INDEX {
t.Fatalf("header attributes = %#v, want device index", inner)
}
if got := native.Uint32(inner[0].Value); got != 42 {
t.Fatalf("device index = %d, want 42", got)
}

if _, err := newEthtoolHeader(nl.ETHTOOL_A_RINGS_HEADER, 0); err == nil {
t.Fatal("newEthtoolHeader accepted interface index zero")
}
}
213 changes: 213 additions & 0 deletions ethtool_rings_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package netlink

import (
"fmt"
"syscall"

"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)

// NetDevTCPDataSplit describes whether a device places TCP headers and payload
// data in separate receive buffers.
type NetDevTCPDataSplit uint8

const (
NetDevTCPDataSplitUnknown NetDevTCPDataSplit = iota
NetDevTCPDataSplitDisabled
NetDevTCPDataSplitEnabled
)

// NetDevRings contains the ring parameters reported by a netdevice.
type NetDevRings struct {
RxMax uint32
RxMiniMax uint32
RxJumboMax uint32
TxMax uint32
Rx uint32
RxMini uint32
RxJumbo uint32
Tx uint32

RxBufLen uint32
TCPDataSplit NetDevTCPDataSplit
CQESize uint32
TxPush bool
RxPush bool
TxPushBufLen uint32
TxPushBufLenMax uint32
HDSThreshold uint32
HDSThresholdMax uint32
}

// NetDevRingsConfig describes a partial ring-parameter update. Nil fields are
// left unchanged.
type NetDevRingsConfig struct {
Rx *uint32
RxMini *uint32
RxJumbo *uint32
Tx *uint32
RxBufLen *uint32

TCPDataSplit *NetDevTCPDataSplit
CQESize *uint32
TxPush *bool
RxPush *bool
TxPushBufLen *uint32
HDSThreshold *uint32
}

func parseNetDevRings(attrs []syscall.NetlinkRouteAttr) (*NetDevRings, error) {
rings := &NetDevRings{}
for _, attr := range attrs {
typeID := attr.Attr.Type & nl.NLA_TYPE_MASK
switch typeID {
case nl.ETHTOOL_A_RINGS_RX_MAX,
nl.ETHTOOL_A_RINGS_RX_MINI_MAX,
nl.ETHTOOL_A_RINGS_RX_JUMBO_MAX,
nl.ETHTOOL_A_RINGS_TX_MAX,
nl.ETHTOOL_A_RINGS_RX,
nl.ETHTOOL_A_RINGS_RX_MINI,
nl.ETHTOOL_A_RINGS_RX_JUMBO,
nl.ETHTOOL_A_RINGS_TX,
nl.ETHTOOL_A_RINGS_RX_BUF_LEN,
nl.ETHTOOL_A_RINGS_CQE_SIZE,
nl.ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN,
nl.ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX,
nl.ETHTOOL_A_RINGS_HDS_THRESH,
nl.ETHTOOL_A_RINGS_HDS_THRESH_MAX:
value, err := readEthtoolUint32(attr)
if err != nil {
return nil, err
}
switch typeID {
case nl.ETHTOOL_A_RINGS_RX_MAX:
rings.RxMax = value
case nl.ETHTOOL_A_RINGS_RX_MINI_MAX:
rings.RxMiniMax = value
case nl.ETHTOOL_A_RINGS_RX_JUMBO_MAX:
rings.RxJumboMax = value
case nl.ETHTOOL_A_RINGS_TX_MAX:
rings.TxMax = value
case nl.ETHTOOL_A_RINGS_RX:
rings.Rx = value
case nl.ETHTOOL_A_RINGS_RX_MINI:
rings.RxMini = value
case nl.ETHTOOL_A_RINGS_RX_JUMBO:
rings.RxJumbo = value
case nl.ETHTOOL_A_RINGS_TX:
rings.Tx = value
case nl.ETHTOOL_A_RINGS_RX_BUF_LEN:
rings.RxBufLen = value
case nl.ETHTOOL_A_RINGS_CQE_SIZE:
rings.CQESize = value
case nl.ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN:
rings.TxPushBufLen = value
case nl.ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX:
rings.TxPushBufLenMax = value
case nl.ETHTOOL_A_RINGS_HDS_THRESH:
rings.HDSThreshold = value
case nl.ETHTOOL_A_RINGS_HDS_THRESH_MAX:
rings.HDSThresholdMax = value
}
case nl.ETHTOOL_A_RINGS_TCP_DATA_SPLIT,
nl.ETHTOOL_A_RINGS_TX_PUSH,
nl.ETHTOOL_A_RINGS_RX_PUSH:
value, err := readEthtoolUint8(attr)
if err != nil {
return nil, err
}
switch typeID {
case nl.ETHTOOL_A_RINGS_TCP_DATA_SPLIT:
rings.TCPDataSplit = NetDevTCPDataSplit(value)
case nl.ETHTOOL_A_RINGS_TX_PUSH:
rings.TxPush = value != 0
case nl.ETHTOOL_A_RINGS_RX_PUSH:
rings.RxPush = value != 0
}
}
}
return rings, nil
}

// NetDevRingsGet returns the ring parameters for ifIndex.
func NetDevRingsGet(ifIndex int) (*NetDevRings, error) {
return pkgHandle.NetDevRingsGet(ifIndex)
}

// NetDevRingsGet returns the ring parameters for ifIndex.
func (h *Handle) NetDevRingsGet(ifIndex int) (*NetDevRings, error) {
header, err := newEthtoolHeader(nl.ETHTOOL_A_RINGS_HEADER, ifIndex)
if err != nil {
return nil, err
}
msgs, err := h.ethtoolRequest(nl.ETHTOOL_MSG_RINGS_GET, unix.NLM_F_ACK, []*nl.RtAttr{header})
if err != nil {
return nil, err
}
if len(msgs) != 1 {
return nil, fmt.Errorf("netlink: expected one ethtool rings response, got %d", len(msgs))
}
return parseNetDevRings(msgs[0])
}

func newNetDevRingsSetAttrs(ifIndex int, config NetDevRingsConfig) ([]*nl.RtAttr, error) {
header, err := newEthtoolHeader(nl.ETHTOOL_A_RINGS_HEADER, ifIndex)
if err != nil {
return nil, err
}
attrs := []*nl.RtAttr{header}
addUint32 := func(attrType int, value *uint32) {
if value != nil {
attrs = append(attrs, nl.NewRtAttr(attrType, nl.Uint32Attr(*value)))
}
}
addBool := func(attrType int, value *bool) {
if value != nil {
v := byte(0)
if *value {
v = 1
}
attrs = append(attrs, nl.NewRtAttr(attrType, []byte{v}))
}
}

if config.RxBufLen != nil && *config.RxBufLen == 0 {
return nil, fmt.Errorf("netlink: RX buffer length must not be zero")
}
if config.CQESize != nil && *config.CQESize == 0 {
return nil, fmt.Errorf("netlink: CQE size must not be zero")
}
addUint32(nl.ETHTOOL_A_RINGS_RX, config.Rx)
addUint32(nl.ETHTOOL_A_RINGS_RX_MINI, config.RxMini)
addUint32(nl.ETHTOOL_A_RINGS_RX_JUMBO, config.RxJumbo)
addUint32(nl.ETHTOOL_A_RINGS_TX, config.Tx)
addUint32(nl.ETHTOOL_A_RINGS_RX_BUF_LEN, config.RxBufLen)
if config.TCPDataSplit != nil {
if *config.TCPDataSplit > NetDevTCPDataSplitEnabled {
return nil, fmt.Errorf("netlink: invalid TCP data split value %d", *config.TCPDataSplit)
}
attrs = append(attrs, nl.NewRtAttr(nl.ETHTOOL_A_RINGS_TCP_DATA_SPLIT, []byte{byte(*config.TCPDataSplit)}))
}
addUint32(nl.ETHTOOL_A_RINGS_CQE_SIZE, config.CQESize)
addBool(nl.ETHTOOL_A_RINGS_TX_PUSH, config.TxPush)
addBool(nl.ETHTOOL_A_RINGS_RX_PUSH, config.RxPush)
addUint32(nl.ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN, config.TxPushBufLen)
addUint32(nl.ETHTOOL_A_RINGS_HDS_THRESH, config.HDSThreshold)
return attrs, nil
}

// NetDevRingsSet applies a partial ring-parameter update to ifIndex.
func NetDevRingsSet(ifIndex int, config NetDevRingsConfig) error {
return pkgHandle.NetDevRingsSet(ifIndex, config)
}

// NetDevRingsSet applies a partial ring-parameter update to ifIndex.
func (h *Handle) NetDevRingsSet(ifIndex int, config NetDevRingsConfig) error {
attrs, err := newNetDevRingsSetAttrs(ifIndex, config)
if err != nil {
return err
}
_, err = h.ethtoolRequest(nl.ETHTOOL_MSG_RINGS_SET, unix.NLM_F_ACK, attrs)
return err
}
Loading
Loading