rtnetlink/link.go
Anthony Harivel b3f5422a3b
link: add support for IFLA_VFINFO_LIST and IFLA_VF_STATS (#295)
Add support for decoding Virtual Function (VF) information from netlink
messages. This enables users to retrieve SR-IOV VF details when querying
network interfaces.

The implementation adds:
- VFStats struct containing per-VF traffic statistics (packets, bytes,
  broadcast, multicast, dropped counters for both RX and TX)
- VFInfo struct containing VF configuration (MAC address, VLAN, QoS,
  TX rate limits, spoof check, link state, RSS query, trust settings)
- VFLinkState type with Auto/Enable/Disable constants
- NumVF and VFInfoList fields to LinkAttributes
- Decoding logic for IFLA_NUM_VF and IFLA_VFINFO_LIST nested attributes

The VF information follows the kernel's nested attribute structure.

This is useful for monitoring and managing SR-IOV enabled network
interfaces where the Physical Function (PF) exposes multiple Virtual
Functions to guest VMs or containers.

Signed-off-by: Anthony Harivel <aharivel@redhat.com>
2026-02-08 12:50:40 +01:00

1107 lines
32 KiB
Go

package rtnetlink
import (
"errors"
"fmt"
"net"
"github.com/jsimonetti/rtnetlink/v2/internal/unix"
"github.com/mdlayher/netlink"
)
var (
// errInvalidLinkMessage is returned when a LinkMessage is malformed.
errInvalidLinkMessage = errors.New("rtnetlink LinkMessage is invalid or too short")
// errInvalidLinkMessageAttr is returned when link attributes are malformed.
errInvalidLinkMessageAttr = errors.New("rtnetlink LinkMessage has a wrong attribute data length")
)
var _ Message = &LinkMessage{}
// A LinkMessage is a route netlink link message.
type LinkMessage struct {
// Always set to AF_UNSPEC (0)
Family uint16
// Device Type
Type uint16
// Unique interface index, using a nonzero value with
// NewLink will instruct the kernel to create a
// device with the given index (kernel 3.7+ required)
Index uint32
// Contains device flags, see netdevice(7)
Flags uint32
// Change Flags, specifies which flags will be affected by the Flags field
Change uint32
// Attributes List
Attributes *LinkAttributes
// A response was filtered as requested, see NLM_F_DUMP_FILTERED
filtered bool
}
// MarshalBinary marshals a LinkMessage into a byte slice.
func (m *LinkMessage) MarshalBinary() ([]byte, error) {
b := make([]byte, unix.SizeofIfInfomsg)
b[0] = 0 // Family
b[1] = 0 // reserved
nativeEndian.PutUint16(b[2:4], m.Type)
nativeEndian.PutUint32(b[4:8], m.Index)
nativeEndian.PutUint32(b[8:12], m.Flags)
nativeEndian.PutUint32(b[12:16], m.Change)
if m.Attributes != nil {
if m.Attributes.Info != nil && m.Attributes.Info.Data != nil {
if verifier, ok := m.Attributes.Info.Data.(LinkDriverVerifier); ok {
if err := verifier.Verify(m); err != nil {
return nil, err
}
}
}
ae := netlink.NewAttributeEncoder()
ae.ByteOrder = nativeEndian
err := m.Attributes.encode(ae)
if err != nil {
return nil, err
}
a, err := ae.Encode()
if err != nil {
return nil, err
}
return append(b, a...), nil
}
return b, nil
}
// UnmarshalBinary unmarshals the contents of a byte slice into a LinkMessage.
func (m *LinkMessage) UnmarshalBinary(b []byte) error {
l := len(b)
if l < unix.SizeofIfInfomsg {
return errInvalidLinkMessage
}
m.Family = nativeEndian.Uint16(b[0:2])
m.Type = nativeEndian.Uint16(b[2:4])
m.Index = nativeEndian.Uint32(b[4:8])
m.Flags = nativeEndian.Uint32(b[8:12])
m.Change = nativeEndian.Uint32(b[12:16])
if l > unix.SizeofIfInfomsg {
m.Attributes = &LinkAttributes{}
ad, err := netlink.NewAttributeDecoder(b[16:])
if err != nil {
return err
}
ad.ByteOrder = nativeEndian
err = m.Attributes.decode(ad)
if err != nil {
return err
}
}
return nil
}
// rtMessage is an empty method to sattisfy the Message interface.
func (*LinkMessage) rtMessage() {}
// LinkService is used to retrieve rtnetlink family information.
type LinkService struct {
c *Conn
}
// execute executes the request and returns the messages as a LinkMessage slice
func (l *LinkService) execute(m Message, family uint16, flags netlink.HeaderFlags) ([]LinkMessage, error) {
msgs, err := l.c.Execute(m, family, flags)
links := make([]LinkMessage, len(msgs))
for i := range msgs {
links[i] = *msgs[i].(*LinkMessage)
}
return links, err
}
// New creates a new interface using the LinkMessage information.
func (l *LinkService) New(req *LinkMessage) error {
flags := netlink.Request | netlink.Create | netlink.Acknowledge | netlink.Excl
_, err := l.execute(req, unix.RTM_NEWLINK, flags)
return err
}
// Delete removes an interface by index.
func (l *LinkService) Delete(index uint32) error {
req := &LinkMessage{
Index: index,
}
flags := netlink.Request | netlink.Acknowledge
_, err := l.c.Execute(req, unix.RTM_DELLINK, flags)
return err
}
// Get retrieves interface information by index.
func (l *LinkService) Get(index uint32) (LinkMessage, error) {
req := &LinkMessage{
Index: index,
}
flags := netlink.Request | netlink.DumpFiltered
links, err := l.execute(req, unix.RTM_GETLINK, flags)
if len(links) != 1 {
return LinkMessage{}, fmt.Errorf("too many/little matches, expected 1, actual %d", len(links))
}
return links[0], err
}
// Set sets interface attributes according to the LinkMessage information.
//
// ref: https://lwn.net/Articles/236919/
// We explicitly use RTM_NEWLINK to set link attributes instead of
// RTM_SETLINK because:
// - using RTM_SETLINK is actually an old rtnetlink API, not supporting most
// attributes common today
// - using RTM_NEWLINK is the preferred way to create AND update links
// - RTM_NEWLINK is backward compatible to RTM_SETLINK
func (l *LinkService) Set(req *LinkMessage) error {
flags := netlink.Request | netlink.Acknowledge
_, err := l.c.Execute(req, unix.RTM_NEWLINK, flags)
return err
}
// SetMaster enslaves an interface to a master device (such as a bridge or bond).
// The optional slaveConfig parameter allows configuring slave-specific settings
// (such as BridgePort configuration when enslaving to a bridge).
//
// Example usage with a bridge:
//
// bridgePort := &driver.BridgePort{
// Learning: &driver.BridgeEnableEnabled,
// UnicastFlood: &driver.BridgeEnableEnabled,
// }
// err := conn.Link.SetMaster(ifaceIndex, bridgeIndex, bridgePort)
//
// To remove an interface from its master, use RemoveMaster instead.
func (l *LinkService) SetMaster(ifaceIndex, masterIndex uint32, slaveConfig LinkSlaveDriver) error {
// Get current interface state
rx, err := l.Get(ifaceIndex)
if err != nil {
return err
}
// Prepare link attributes with master set
master := masterIndex
attrs := &LinkAttributes{
Master: &master,
Name: rx.Attributes.Name,
MTU: rx.Attributes.MTU,
Type: rx.Attributes.Type,
QueueDisc: rx.Attributes.QueueDisc,
}
// If slave configuration is provided, set the slave info
if slaveConfig != nil {
attrs.Info = &LinkInfo{
SlaveKind: slaveConfig.Kind(),
SlaveData: slaveConfig,
}
}
// Apply the changes
tx := &LinkMessage{
Family: unix.AF_UNSPEC,
Type: rx.Type,
Index: ifaceIndex,
Flags: rx.Flags,
Change: 0,
Attributes: attrs,
}
return l.Set(tx)
}
// RemoveMaster un-enslaves an interface from its master device.
// This sets the Master field to 0, removing the interface from any bridge, bond, or other master.
//
// Example usage:
//
// err := conn.Link.RemoveMaster(ifaceIndex)
func (l *LinkService) RemoveMaster(ifaceIndex uint32) error {
return l.SetMaster(ifaceIndex, 0, nil)
}
func (l *LinkService) list(kind string) ([]LinkMessage, error) {
req := &LinkMessage{}
flags := netlink.Request | netlink.Dump
if kind == "" {
return l.execute(req, unix.RTM_GETLINK, flags)
}
req.Attributes = &LinkAttributes{
Info: &LinkInfo{Kind: kind},
}
msgs, err := l.execute(req, unix.RTM_GETLINK, flags)
// All filtered links are marked by a NLM_F_DUMP_FILTERED flag
// no other links present in a response, so just check the first one
if err == nil && len(msgs) > 0 && !msgs[0].filtered {
msgs = []LinkMessage{}
}
return msgs, err
}
// ListByKind retrieves all interfaces of a specific kind.
func (l *LinkService) ListByKind(kind string) ([]LinkMessage, error) {
return l.list(kind)
}
// List retrieves all interfaces.
func (l *LinkService) List() ([]LinkMessage, error) {
return l.list("")
}
// ListWithVFInfo retrieves all interfaces including SR-IOV VF information.
// This sets the RTEXT_FILTER_VF extended filter mask to request VF details.
func (l *LinkService) ListWithVFInfo() ([]LinkMessage, error) {
extMask := uint32(unix.RTEXT_FILTER_VF)
req := &LinkMessage{
Attributes: &LinkAttributes{
ExtMask: &extMask,
},
}
flags := netlink.Request | netlink.Dump
return l.execute(req, unix.RTM_GETLINK, flags)
}
// LinkAttributes contains all attributes for an interface.
type LinkAttributes struct {
Address net.HardwareAddr // Interface L2 address
Alias *string // Interface alias name
AltNames []string // Alternative interface names
Broadcast net.HardwareAddr // L2 broadcast address
Carrier *uint8 // Current physical link state of the interface.
CarrierChanges *uint32 // Number of times the link has seen a change from UP to DOWN and vice versa
CarrierUpCount *uint32 // Number of times the link has been up
CarrierDownCount *uint32 // Number of times the link has been down
ExtMask *uint32 // Extended info mask for queries (RTEXT_FILTER_*)
Index *uint32 // System-wide interface unique index identifier
Info *LinkInfo // Detailed Interface Information
LinkMode *uint8 // Interface link mode
MTU uint32 // MTU of the device
Name string // Device name
NetDevGroup *uint32 // Interface network device group
NumVF *uint32 // Number of Virtual Functions (SR-IOV)
OperationalState OperationalState // Interface operation state
PhysPortID *string // Interface unique physical port identifier within the NIC
PhysPortName *string // Interface physical port name within the NIC
PhysSwitchID *string // Unique physical switch identifier of a switch this port belongs to
QueueDisc string // Queueing discipline
Master *uint32 // Master device index (0 value un-enslaves)
Stats *LinkStats // Interface Statistics
Stats64 *LinkStats64 // Interface Statistics (64 bits version)
TxQueueLen *uint32 // Interface transmit queue len in number of packets
Type uint32 // Link type
VFInfoList []VFInfo // Virtual Function information list (SR-IOV)
XDP *LinkXDP // Express Data Patch Information
NetNS *NetNS // Interface network namespace
}
// OperationalState represents an interface's operational state.
type OperationalState uint8
// Constants that represent operational state of an interface
//
// Adapted from https://elixir.bootlin.com/linux/v4.19.2/source/include/uapi/linux/if.h#L166
const (
OperStateUnknown OperationalState = iota // status could not be determined
OperStateNotPresent // down, due to some missing component (typically hardware)
OperStateDown // down, either administratively or due to a fault
OperStateLowerLayerDown // down, due to lower-layer interfaces
OperStateTesting // operationally down, in some test mode
OperStateDormant // down, waiting for some external event
OperStateUp // interface is in a state to send and receive packets
)
// unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage.
func (a *LinkAttributes) decode(ad *netlink.AttributeDecoder) error {
for ad.Next() {
switch ad.Type() {
case unix.IFLA_UNSPEC:
// unused attribute
case unix.IFLA_ADDRESS:
l := len(ad.Bytes())
if l < 4 || l > 32 {
return errInvalidLinkMessageAttr
}
a.Address = ad.Bytes()
case unix.IFLA_IFALIAS:
v := ad.String()
a.Alias = &v
case unix.IFLA_BROADCAST:
l := len(ad.Bytes())
if l < 4 || l > 32 {
return errInvalidLinkMessageAttr
}
a.Broadcast = ad.Bytes()
case unix.IFLA_CARRIER:
v := ad.Uint8()
a.Carrier = &v
case unix.IFLA_CARRIER_CHANGES:
v := ad.Uint32()
a.CarrierChanges = &v
case unix.IFLA_CARRIER_UP_COUNT:
v := ad.Uint32()
a.CarrierUpCount = &v
case unix.IFLA_CARRIER_DOWN_COUNT:
v := ad.Uint32()
a.CarrierDownCount = &v
case unix.IFLA_GROUP:
v := ad.Uint32()
a.NetDevGroup = &v
case unix.IFLA_MTU:
a.MTU = ad.Uint32()
case unix.IFLA_IFNAME:
a.Name = ad.String()
case unix.IFLA_LINK:
a.Type = ad.Uint32()
case unix.IFLA_LINKINFO:
a.Info = &LinkInfo{}
ad.Nested(a.Info.decode)
case unix.IFLA_LINKMODE:
v := ad.Uint8()
a.LinkMode = &v
case unix.IFLA_MASTER:
v := ad.Uint32()
a.Master = &v
case unix.IFLA_OPERSTATE:
a.OperationalState = OperationalState(ad.Uint8())
case unix.IFLA_PHYS_PORT_ID:
v := ad.String()
a.PhysPortID = &v
case unix.IFLA_PHYS_SWITCH_ID:
v := ad.String()
a.PhysSwitchID = &v
case unix.IFLA_PHYS_PORT_NAME:
v := ad.String()
a.PhysPortName = &v
case unix.IFLA_QDISC:
a.QueueDisc = ad.String()
case unix.IFLA_STATS:
a.Stats = &LinkStats{}
err := a.Stats.unmarshalBinary(ad.Bytes())
if err != nil {
return err
}
case unix.IFLA_STATS64:
a.Stats64 = &LinkStats64{}
err := a.Stats64.unmarshalBinary(ad.Bytes())
if err != nil {
return err
}
case unix.IFLA_TXQLEN:
v := ad.Uint32()
a.TxQueueLen = &v
case unix.IFLA_XDP:
a.XDP = &LinkXDP{}
ad.Nested(a.XDP.decode)
case unix.IFLA_PROP_LIST:
// read nested encoded property list
nad, err := netlink.NewAttributeDecoder(ad.Bytes())
if err != nil {
return err
}
for nad.Next() {
if nad.Type() == unix.IFLA_ALT_IFNAME {
a.AltNames = append(a.AltNames, nad.String())
}
}
case unix.IFLA_NUM_VF:
v := ad.Uint32()
a.NumVF = &v
case unix.IFLA_VFINFO_LIST:
nad, err := netlink.NewAttributeDecoder(ad.Bytes())
if err != nil {
return err
}
nad.ByteOrder = nativeEndian
vfs, err := decodeVFInfoList(nad)
if err != nil {
return err
}
a.VFInfoList = vfs
}
}
return nil
}
// MarshalBinary marshals a LinkAttributes into a byte slice.
func (a *LinkAttributes) encode(ae *netlink.AttributeEncoder) error {
if a.Name != "" {
ae.String(unix.IFLA_IFNAME, a.Name)
}
if a.Alias != nil {
ae.String(unix.IFLA_IFALIAS, *a.Alias)
}
if a.Type != 0 {
ae.Uint32(unix.IFLA_LINK, a.Type)
}
if a.QueueDisc != "" {
ae.String(unix.IFLA_QDISC, a.QueueDisc)
}
if a.MTU != 0 {
ae.Uint32(unix.IFLA_MTU, a.MTU)
}
if len(a.Address) != 0 {
ae.Bytes(unix.IFLA_ADDRESS, a.Address)
}
if len(a.Broadcast) != 0 {
ae.Bytes(unix.IFLA_BROADCAST, a.Broadcast)
}
if a.OperationalState != OperStateUnknown {
ae.Uint8(unix.IFLA_OPERSTATE, uint8(a.OperationalState))
}
if a.Info != nil {
nae := netlink.NewAttributeEncoder()
nae.ByteOrder = ae.ByteOrder
err := a.Info.encode(nae)
if err != nil {
return err
}
b, err := nae.Encode()
if err != nil {
return err
}
ae.Bytes(unix.IFLA_LINKINFO, b)
}
if a.XDP != nil {
nae := netlink.NewAttributeEncoder()
nae.ByteOrder = ae.ByteOrder
err := a.XDP.encode(nae)
if err != nil {
return err
}
b, err := nae.Encode()
if err != nil {
return err
}
ae.Bytes(unix.IFLA_XDP, b)
}
if a.Master != nil {
ae.Uint32(unix.IFLA_MASTER, *a.Master)
}
if a.NetNS != nil {
ae.Uint32(a.NetNS.value())
}
if a.ExtMask != nil {
ae.Uint32(unix.IFLA_EXT_MASK, *a.ExtMask)
}
return nil
}
// LinkStats contains packet statistics
type LinkStats struct {
RXPackets uint32 // total packets received
TXPackets uint32 // total packets transmitted
RXBytes uint32 // total bytes received
TXBytes uint32 // total bytes transmitted
RXErrors uint32 // bad packets received
TXErrors uint32 // packet transmit problems
RXDropped uint32 // no space in linux buffers
TXDropped uint32 // no space available in linux
Multicast uint32 // multicast packets received
Collisions uint32
// detailed rx_errors:
RXLengthErrors uint32
RXOverErrors uint32 // receiver ring buff overflow
RXCRCErrors uint32 // recved pkt with crc error
RXFrameErrors uint32 // recv'd frame alignment error
RXFIFOErrors uint32 // recv'r fifo overrun
RXMissedErrors uint32 // receiver missed packet
// detailed tx_errors
TXAbortedErrors uint32
TXCarrierErrors uint32
TXFIFOErrors uint32
TXHeartbeatErrors uint32
TXWindowErrors uint32
// for cslip etc
RXCompressed uint32
TXCompressed uint32
RXNoHandler uint32 // dropped, no handler found
}
// unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage.
func (a *LinkStats) unmarshalBinary(b []byte) error {
l := len(b)
if l != 92 && l != 96 {
return fmt.Errorf("incorrect LinkMessage size, want: 92 or 96, got: %d", len(b))
}
a.RXPackets = nativeEndian.Uint32(b[0:4])
a.TXPackets = nativeEndian.Uint32(b[4:8])
a.RXBytes = nativeEndian.Uint32(b[8:12])
a.TXBytes = nativeEndian.Uint32(b[12:16])
a.RXErrors = nativeEndian.Uint32(b[16:20])
a.TXErrors = nativeEndian.Uint32(b[20:24])
a.RXDropped = nativeEndian.Uint32(b[24:28])
a.TXDropped = nativeEndian.Uint32(b[28:32])
a.Multicast = nativeEndian.Uint32(b[32:36])
a.Collisions = nativeEndian.Uint32(b[36:40])
a.RXLengthErrors = nativeEndian.Uint32(b[40:44])
a.RXOverErrors = nativeEndian.Uint32(b[44:48])
a.RXCRCErrors = nativeEndian.Uint32(b[48:52])
a.RXFrameErrors = nativeEndian.Uint32(b[52:56])
a.RXFIFOErrors = nativeEndian.Uint32(b[56:60])
a.RXMissedErrors = nativeEndian.Uint32(b[60:64])
a.TXAbortedErrors = nativeEndian.Uint32(b[64:68])
a.TXCarrierErrors = nativeEndian.Uint32(b[68:72])
a.TXFIFOErrors = nativeEndian.Uint32(b[72:76])
a.TXHeartbeatErrors = nativeEndian.Uint32(b[76:80])
a.TXWindowErrors = nativeEndian.Uint32(b[80:84])
a.RXCompressed = nativeEndian.Uint32(b[84:88])
a.TXCompressed = nativeEndian.Uint32(b[88:92])
if l == 96 { // kernel 4.6+
a.RXNoHandler = nativeEndian.Uint32(b[92:96])
}
return nil
}
// LinkStats64 contains packet statistics
type LinkStats64 struct {
RXPackets uint64 // total packets received
TXPackets uint64 // total packets transmitted
RXBytes uint64 // total bytes received
TXBytes uint64 // total bytes transmitted
RXErrors uint64 // bad packets received
TXErrors uint64 // packet transmit problems
RXDropped uint64 // no space in linux buffers
TXDropped uint64 // no space available in linux
Multicast uint64 // multicast packets received
Collisions uint64
// detailed rx_errors:
RXLengthErrors uint64
RXOverErrors uint64 // receiver ring buff overflow
RXCRCErrors uint64 // recved pkt with crc error
RXFrameErrors uint64 // recv'd frame alignment error
RXFIFOErrors uint64 // recv'r fifo overrun
RXMissedErrors uint64 // receiver missed packet
// detailed tx_errors
TXAbortedErrors uint64
TXCarrierErrors uint64
TXFIFOErrors uint64
TXHeartbeatErrors uint64
TXWindowErrors uint64
// for cslip etc
RXCompressed uint64
TXCompressed uint64
RXNoHandler uint64 // dropped, no handler found
RXOtherhostDropped uint64 // Number of packets dropped due to mismatch in destination MAC address.
}
// unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage.
func (a *LinkStats64) unmarshalBinary(b []byte) error {
l := len(b)
if l != 184 && l != 192 && l != 200 {
return fmt.Errorf("incorrect size, want: 184 or 192 or 200")
}
a.RXPackets = nativeEndian.Uint64(b[0:8])
a.TXPackets = nativeEndian.Uint64(b[8:16])
a.RXBytes = nativeEndian.Uint64(b[16:24])
a.TXBytes = nativeEndian.Uint64(b[24:32])
a.RXErrors = nativeEndian.Uint64(b[32:40])
a.TXErrors = nativeEndian.Uint64(b[40:48])
a.RXDropped = nativeEndian.Uint64(b[48:56])
a.TXDropped = nativeEndian.Uint64(b[56:64])
a.Multicast = nativeEndian.Uint64(b[64:72])
a.Collisions = nativeEndian.Uint64(b[72:80])
a.RXLengthErrors = nativeEndian.Uint64(b[80:88])
a.RXOverErrors = nativeEndian.Uint64(b[88:96])
a.RXCRCErrors = nativeEndian.Uint64(b[96:104])
a.RXFrameErrors = nativeEndian.Uint64(b[104:112])
a.RXFIFOErrors = nativeEndian.Uint64(b[112:120])
a.RXMissedErrors = nativeEndian.Uint64(b[120:128])
a.TXAbortedErrors = nativeEndian.Uint64(b[128:136])
a.TXCarrierErrors = nativeEndian.Uint64(b[136:144])
a.TXFIFOErrors = nativeEndian.Uint64(b[144:152])
a.TXHeartbeatErrors = nativeEndian.Uint64(b[152:160])
a.TXWindowErrors = nativeEndian.Uint64(b[160:168])
a.RXCompressed = nativeEndian.Uint64(b[168:176])
a.TXCompressed = nativeEndian.Uint64(b[176:184])
if l > 191 { // kernel 4.6+
a.RXNoHandler = nativeEndian.Uint64(b[184:192])
}
if l > 199 { // kernel 5.19+
a.RXOtherhostDropped = nativeEndian.Uint64(b[192:200])
}
return nil
}
// VFLinkState represents the link state of a VF
type VFLinkState uint32
// Constants for VF link state
const (
VFLinkStateAuto VFLinkState = iota // link state of the uplink
VFLinkStateEnable // link always up
VFLinkStateDisable // link always down
)
// VFStats contains statistics for a Virtual Function
type VFStats struct {
RxPackets uint64
TxPackets uint64
RxBytes uint64
TxBytes uint64
Broadcast uint64
Multicast uint64
RxDropped uint64
TxDropped uint64
}
func (s *VFStats) decode(ad *netlink.AttributeDecoder) error {
for ad.Next() {
switch ad.Type() {
case unix.IFLA_VF_STATS_RX_PACKETS:
s.RxPackets = ad.Uint64()
case unix.IFLA_VF_STATS_TX_PACKETS:
s.TxPackets = ad.Uint64()
case unix.IFLA_VF_STATS_RX_BYTES:
s.RxBytes = ad.Uint64()
case unix.IFLA_VF_STATS_TX_BYTES:
s.TxBytes = ad.Uint64()
case unix.IFLA_VF_STATS_BROADCAST:
s.Broadcast = ad.Uint64()
case unix.IFLA_VF_STATS_MULTICAST:
s.Multicast = ad.Uint64()
case unix.IFLA_VF_STATS_RX_DROPPED:
s.RxDropped = ad.Uint64()
case unix.IFLA_VF_STATS_TX_DROPPED:
s.TxDropped = ad.Uint64()
}
}
return nil
}
// VFInfo contains information about a Virtual Function
type VFInfo struct {
ID uint32 // VF index
MAC net.HardwareAddr // VF MAC address
Broadcast net.HardwareAddr // VF broadcast address
Vlan uint32 // VLAN ID
Qos uint32 // VLAN QoS
TxRate uint32 // Max TX bandwidth (deprecated, use MaxTxRate)
MinTxRate uint32 // Min TX bandwidth in Mbps
MaxTxRate uint32 // Max TX bandwidth in Mbps
SpoofCheck bool // Spoof checking enabled
LinkState VFLinkState // Link state
RssQuery bool // RSS query enabled
Trust bool // VF trust setting
Stats *VFStats // VF statistics
}
func (vf *VFInfo) decode(ad *netlink.AttributeDecoder) error {
var firstVFID *uint32
for ad.Next() {
switch ad.Type() {
case unix.IFLA_VF_MAC:
b := ad.Bytes()
if len(b) < 36 { // struct ifla_vf_mac: 4 bytes vf + 32 bytes mac
return errInvalidLinkMessageAttr
}
vfID := nativeEndian.Uint32(b[0:4])
if firstVFID == nil {
firstVFID = &vfID
vf.ID = vfID
} else if *firstVFID != vfID {
return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
}
// MAC is typically 6 bytes, but kernel struct has 32 bytes
vf.MAC = make(net.HardwareAddr, 6)
copy(vf.MAC, b[4:10])
case unix.IFLA_VF_BROADCAST:
b := ad.Bytes()
if len(b) < 32 { // struct ifla_vf_broadcast: 32 bytes
return errInvalidLinkMessageAttr
}
vf.Broadcast = make(net.HardwareAddr, 6)
copy(vf.Broadcast, b[0:6])
case unix.IFLA_VF_VLAN:
b := ad.Bytes()
if len(b) < 12 { // struct ifla_vf_vlan: 4 bytes vf + 4 bytes vlan + 4 bytes qos
return errInvalidLinkMessageAttr
}
vfID := nativeEndian.Uint32(b[0:4])
if firstVFID == nil {
firstVFID = &vfID
vf.ID = vfID
} else if *firstVFID != vfID {
return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
}
vf.Vlan = nativeEndian.Uint32(b[4:8])
vf.Qos = nativeEndian.Uint32(b[8:12])
case unix.IFLA_VF_TX_RATE:
b := ad.Bytes()
if len(b) < 8 { // struct ifla_vf_tx_rate: 4 bytes vf + 4 bytes rate
return errInvalidLinkMessageAttr
}
vfID := nativeEndian.Uint32(b[0:4])
if firstVFID == nil {
firstVFID = &vfID
vf.ID = vfID
} else if *firstVFID != vfID {
return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
}
vf.TxRate = nativeEndian.Uint32(b[4:8])
case unix.IFLA_VF_RATE:
b := ad.Bytes()
if len(b) < 12 { // struct ifla_vf_rate: 4 bytes vf + 4 bytes min + 4 bytes max
return errInvalidLinkMessageAttr
}
vfID := nativeEndian.Uint32(b[0:4])
if firstVFID == nil {
firstVFID = &vfID
vf.ID = vfID
} else if *firstVFID != vfID {
return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
}
vf.MinTxRate = nativeEndian.Uint32(b[4:8])
vf.MaxTxRate = nativeEndian.Uint32(b[8:12])
case unix.IFLA_VF_SPOOFCHK:
b := ad.Bytes()
if len(b) < 8 { // struct ifla_vf_spoofchk: 4 bytes vf + 4 bytes setting
return errInvalidLinkMessageAttr
}
vfID := nativeEndian.Uint32(b[0:4])
if firstVFID == nil {
firstVFID = &vfID
vf.ID = vfID
} else if *firstVFID != vfID {
return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
}
vf.SpoofCheck = nativeEndian.Uint32(b[4:8]) != 0
case unix.IFLA_VF_LINK_STATE:
b := ad.Bytes()
if len(b) < 8 { // struct ifla_vf_link_state: 4 bytes vf + 4 bytes link_state
return errInvalidLinkMessageAttr
}
vfID := nativeEndian.Uint32(b[0:4])
if firstVFID == nil {
firstVFID = &vfID
vf.ID = vfID
} else if *firstVFID != vfID {
return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
}
vf.LinkState = VFLinkState(nativeEndian.Uint32(b[4:8]))
case unix.IFLA_VF_RSS_QUERY_EN:
b := ad.Bytes()
if len(b) < 8 { // struct ifla_vf_rss_query_en: 4 bytes vf + 4 bytes setting
return errInvalidLinkMessageAttr
}
vfID := nativeEndian.Uint32(b[0:4])
if firstVFID == nil {
firstVFID = &vfID
vf.ID = vfID
} else if *firstVFID != vfID {
return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
}
vf.RssQuery = nativeEndian.Uint32(b[4:8]) != 0
case unix.IFLA_VF_TRUST:
b := ad.Bytes()
if len(b) < 8 { // struct ifla_vf_trust: 4 bytes vf + 4 bytes setting
return errInvalidLinkMessageAttr
}
vfID := nativeEndian.Uint32(b[0:4])
if firstVFID == nil {
firstVFID = &vfID
vf.ID = vfID
} else if *firstVFID != vfID {
return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
}
vf.Trust = nativeEndian.Uint32(b[4:8]) != 0
case unix.IFLA_VF_STATS:
vf.Stats = &VFStats{}
ad.Nested(vf.Stats.decode)
}
}
return nil
}
func decodeVFInfoList(ad *netlink.AttributeDecoder) ([]VFInfo, error) {
var vfs []VFInfo
for ad.Next() {
if ad.Type() == unix.IFLA_VF_INFO {
vf := VFInfo{}
ad.Nested(vf.decode)
vfs = append(vfs, vf)
}
}
return vfs, ad.Err()
}
var (
// registeredDrivers is the global map of registered drivers
registeredDrivers = make(map[string]LinkDriver)
// registeredSlaveDrivers is the global map of registered slave drivers
registeredSlaveDrivers = make(map[string]LinkDriver)
)
// RegisterDriver registers a driver with the link service
// This allows the driver to be used to encode/decode the link data
//
// This function is not threadsafe. This should not be used after Dial
func RegisterDriver(d LinkDriver) error {
if _, ok := d.(LinkSlaveDriver); ok {
if _, ok := registeredSlaveDrivers[d.Kind()]; ok {
return fmt.Errorf("driver %s already registered", d.Kind())
}
registeredSlaveDrivers[d.Kind()] = d
return nil
}
if _, ok := registeredDrivers[d.Kind()]; ok {
return fmt.Errorf("driver %s already registered", d.Kind())
}
registeredDrivers[d.Kind()] = d
return nil
}
// getDriver returns the driver instance for the given kind, and true if the driver is registered
// it returns the default (LinkData) driver, and false if the driver is not registered
func getDriver(kind string, slave bool) (LinkDriver, bool) {
if slave {
if t, ok := registeredSlaveDrivers[kind]; ok {
return t.New(), true
}
return &LinkData{Name: kind, Slave: true}, false
}
if t, ok := registeredDrivers[kind]; ok {
return t.New(), true
}
return &LinkData{Name: kind}, false
}
// LinkDriver is the interface that wraps link-specific Encode, Decode, and Kind methods
type LinkDriver interface {
// New returns a new instance of the LinkDriver
New() LinkDriver
// Encode the driver data into the netlink message attribute
Encode(*netlink.AttributeEncoder) error
// Decode the driver data from the netlink message attribute
Decode(*netlink.AttributeDecoder) error
// Return the driver kind as string, this will be matched with the LinkInfo.Kind to find a driver to decode the data
Kind() string
}
// LinkSlaveDriver defines a LinkDriver with Slave method
type LinkSlaveDriver interface {
LinkDriver
// Slave method specifies driver is a slave link info
Slave()
}
// LinkDriverVerifier defines a LinkDriver with Verify method
type LinkDriverVerifier interface {
LinkDriver
// Verify function run before Encode function to check for correctness and
// pass related values that otherwise unavailable to the driver
Verify(*LinkMessage) error
}
// LinkData implements the default LinkDriver interface for not registered drivers
type LinkData struct {
Name string
Data []byte
Slave bool
}
var _ LinkDriver = &LinkData{}
func (d *LinkData) New() LinkDriver {
return &LinkData{}
}
func (d *LinkData) Decode(ad *netlink.AttributeDecoder) error {
d.Data = ad.Bytes()
return nil
}
func (d *LinkData) Encode(ae *netlink.AttributeEncoder) error {
if len(d.Data) == 0 {
return nil
}
if d.Slave {
ae.Bytes(unix.IFLA_INFO_SLAVE_DATA, d.Data)
} else {
ae.Bytes(unix.IFLA_INFO_DATA, d.Data)
}
return nil
}
func (d *LinkData) Kind() string {
return d.Name
}
// LinkInfo contains data for specific network types
type LinkInfo struct {
Kind string // Driver name
Data LinkDriver // Driver specific configuration stored as nested Netlink messages
SlaveKind string // Slave driver name
SlaveData LinkDriver // Slave driver specific configuration
}
func (i *LinkInfo) decode(ad *netlink.AttributeDecoder) error {
for ad.Next() {
switch ad.Type() {
case unix.IFLA_INFO_KIND:
i.Kind = ad.String()
case unix.IFLA_INFO_SLAVE_KIND:
i.SlaveKind = ad.String()
case unix.IFLA_INFO_DATA:
driver, found := getDriver(i.Kind, false)
i.Data = driver
if found {
ad.Nested(i.Data.Decode)
continue
}
_ = i.Data.Decode(ad)
case unix.IFLA_INFO_SLAVE_DATA:
driver, found := getDriver(i.SlaveKind, true)
i.SlaveData = driver
if found {
ad.Nested(i.SlaveData.Decode)
continue
}
_ = i.SlaveData.Decode(ad)
}
}
return nil
}
func (i *LinkInfo) encode(ae *netlink.AttributeEncoder) error {
ae.String(unix.IFLA_INFO_KIND, i.Kind)
if i.Data != nil {
if i.Kind != i.Data.Kind() {
return fmt.Errorf("driver kind %s is not equal to info kind %s", i.Data.Kind(), i.Kind)
}
if _, ok := i.Data.(*LinkData); ok {
_ = i.Data.Encode(ae)
} else {
ae.Nested(unix.IFLA_INFO_DATA, i.Data.Encode)
}
}
if i.SlaveData != nil {
if i.SlaveKind != i.SlaveData.Kind() {
return fmt.Errorf("slave driver kind %s is not equal to slave info kind %s", i.SlaveData.Kind(), i.SlaveKind)
}
ae.String(unix.IFLA_INFO_SLAVE_KIND, i.SlaveKind)
if _, ok := i.SlaveData.(*LinkData); ok {
_ = i.SlaveData.Encode(ae)
} else {
ae.Nested(unix.IFLA_INFO_SLAVE_DATA, i.SlaveData.Encode)
}
}
return nil
}
// LinkXDP holds Express Data Path specific information
type LinkXDP struct {
FD int32
ExpectedFD int32
Attached uint8
Flags uint32
ProgID uint32
}
func (xdp *LinkXDP) decode(ad *netlink.AttributeDecoder) error {
for ad.Next() {
switch ad.Type() {
case unix.IFLA_XDP_FD:
xdp.FD = ad.Int32()
case unix.IFLA_XDP_EXPECTED_FD:
xdp.ExpectedFD = ad.Int32()
case unix.IFLA_XDP_ATTACHED:
xdp.Attached = ad.Uint8()
case unix.IFLA_XDP_FLAGS:
xdp.Flags = ad.Uint32()
case unix.IFLA_XDP_PROG_ID:
xdp.ProgID = ad.Uint32()
}
}
return nil
}
func (xdp *LinkXDP) encode(ae *netlink.AttributeEncoder) error {
ae.Int32(unix.IFLA_XDP_FD, xdp.FD)
ae.Int32(unix.IFLA_XDP_EXPECTED_FD, xdp.ExpectedFD)
ae.Uint32(unix.IFLA_XDP_FLAGS, xdp.Flags)
// XDP_ATTACHED and XDP_PROG_ID are things that can only be returned by the
// kernel, so we don't encode them. source:
// https://elixir.bootlin.com/linux/v5.10.15/source/net/core/rtnetlink.c#L2894
return nil
}