feat: add additional devlink port attributes

Add the following devlink port attributes:

PortNumber: the physical port number
PfNumber: the PF number
VfNumber: the VF number (index)
SfNumber: the SF number (index)
ControllerNumber: the controller number
External: if set, indicates external controller

Signed-off-by: adrianc <adrianc@nvidia.com>
This commit is contained in:
adrianc
2025-08-17 18:01:13 +03:00
committed by Vish (Ishaya) Abrams
parent 37e5aafde3
commit b85149843e
5 changed files with 327 additions and 15 deletions

View File

@@ -46,15 +46,21 @@ type DevlinkPortFnSetAttrs struct {
// DevlinkPort represents port and its attributes // DevlinkPort represents port and its attributes
type DevlinkPort struct { type DevlinkPort struct {
BusName string BusName string
DeviceName string DeviceName string
PortIndex uint32 PortIndex uint32
PortType uint16 PortType uint16
NetdeviceName string NetdeviceName string
NetdevIfIndex uint32 NetdevIfIndex uint32
RdmaDeviceName string RdmaDeviceName string
PortFlavour uint16 PortFlavour uint16
Fn *DevlinkPortFn Fn *DevlinkPortFn
PortNumber *uint32
PfNumber *uint16
VfNumber *uint16
SfNumber *uint32
ControllerNumber *uint32
External *bool
} }
type DevLinkPortAddAttrs struct { type DevLinkPortAddAttrs struct {
@@ -629,6 +635,24 @@ func (port *DevlinkPort) parseAttributes(attrs []syscall.NetlinkRouteAttr) error
port.Fn.OpState = uint8(nested.Value[0]) port.Fn.OpState = uint8(nested.Value[0])
} }
} }
case nl.DEVLINK_ATTR_PORT_NUMBER:
val := native.Uint32(a.Value)
port.PortNumber = &val
case nl.DEVLINK_ATTR_PORT_PCI_PF_NUMBER:
val := native.Uint16(a.Value)
port.PfNumber = &val
case nl.DEVLINK_ATTR_PORT_PCI_VF_NUMBER:
val := native.Uint16(a.Value)
port.VfNumber = &val
case nl.DEVLINK_ATTR_PORT_PCI_SF_NUMBER:
val := native.Uint32(a.Value)
port.SfNumber = &val
case nl.DEVLINK_ATTR_PORT_CONTROLLER_NUMBER:
val := native.Uint32(a.Value)
port.ControllerNumber = &val
case nl.DEVLINK_ATTR_PORT_EXTERNAL:
val := uint8(a.Value[0]) != 0
port.External = &val
} }
} }
return nil return nil

View File

@@ -5,12 +5,17 @@ package netlink
import ( import (
"flag" "flag"
"fmt"
"math/rand" "math/rand"
"net" "net"
"os" "os"
"strconv" "strconv"
"strings"
"syscall"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/vishvananda/netlink/nl" "github.com/vishvananda/netlink/nl"
) )
@@ -49,6 +54,61 @@ func TestDevLinkSetEswitchMode(t *testing.T) {
} }
} }
func logPort(t *testing.T, port *DevlinkPort) {
type field struct {
key string
value string
}
fields := []field{}
fields = append(fields, field{key: "bus", value: port.BusName})
fields = append(fields, field{key: "device", value: port.DeviceName})
fields = append(fields, field{key: "port_index", value: strconv.Itoa(int(port.PortIndex))})
fields = append(fields, field{key: "port_type", value: strconv.Itoa(int(port.PortType))})
fields = append(fields, field{key: "port_flavour", value: strconv.Itoa(int(port.PortFlavour))})
fields = append(fields, field{key: "netdev_name", value: port.NetdeviceName})
fields = append(fields, field{key: "netdev_index", value: strconv.Itoa(int(port.NetdevIfIndex))})
fields = append(fields, field{key: "rdma_dev_name", value: port.RdmaDeviceName})
if port.Fn != nil {
fields = append(fields, field{key: "hw_addr", value: port.Fn.HwAddr.String()})
fields = append(fields, field{key: "state", value: strconv.Itoa(int(port.Fn.State))})
fields = append(fields, field{key: "op_state", value: strconv.Itoa(int(port.Fn.OpState))})
}
if port.PortNumber != nil {
fields = append(fields, field{key: "port_number", value: strconv.Itoa(int(*port.PortNumber))})
}
if port.PfNumber != nil {
fields = append(fields, field{key: "pf_number", value: strconv.Itoa(int(*port.PfNumber))})
}
if port.VfNumber != nil {
fields = append(fields, field{key: "vf_number", value: strconv.Itoa(int(*port.VfNumber))})
}
if port.SfNumber != nil {
fields = append(fields, field{key: "sf_number", value: strconv.Itoa(int(*port.SfNumber))})
}
if port.ControllerNumber != nil {
fields = append(fields, field{key: "controller_number", value: strconv.Itoa(int(*port.ControllerNumber))})
}
if port.External != nil {
fields = append(fields, field{key: "external", value: strconv.FormatBool(*port.External)})
}
fieldsStr := []string{}
for _, field := range fields {
fieldsStr = append(fieldsStr, fmt.Sprintf("%s=%s", field.key, field.value))
}
t.Log(strings.Join(fieldsStr, " "))
}
func TestDevLinkGetAllPortList(t *testing.T) { func TestDevLinkGetAllPortList(t *testing.T) {
minKernelRequired(t, 5, 4) minKernelRequired(t, 5, 4)
ports, err := DevLinkGetAllPortList() ports, err := DevLinkGetAllPortList()
@@ -57,7 +117,7 @@ func TestDevLinkGetAllPortList(t *testing.T) {
} }
t.Log("devlink port count = ", len(ports)) t.Log("devlink port count = ", len(ports))
for _, port := range ports { for _, port := range ports {
t.Log(*port) logPort(t, port)
} }
} }
@@ -401,3 +461,211 @@ func validateDeviceParams(t *testing.T, p *DevlinkParam) {
} }
} }
} }
func testGetDevlinkPortCommonAttrs() []*nl.RtAttr {
nlAttrs := []*nl.RtAttr{}
nlAttrs = append(nlAttrs,
nl.NewRtAttr(nl.DEVLINK_ATTR_BUS_NAME, nl.ZeroTerminated("pci")),
nl.NewRtAttr(nl.DEVLINK_ATTR_DEV_NAME, nl.ZeroTerminated("0000:08:00.0")),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_INDEX, nl.Uint32Attr(131071)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_TYPE, nl.Uint16Attr(nl.DEVLINK_PORT_TYPE_ETH)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_NETDEV_NAME, nl.ZeroTerminated("eth0")),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_NETDEV_IFINDEX, nl.Uint32Attr(5)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_IBDEV_NAME, nl.ZeroTerminated("rdma0")),
)
return nlAttrs
}
func testAddDevlinkPortPhysicalAttrs(nlAttrs []*nl.RtAttr) []*nl.RtAttr {
nlAttrs = append(nlAttrs,
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_FLAVOUR, nl.Uint16Attr(nl.DEVLINK_PORT_FLAVOUR_PHYSICAL)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_NUMBER, nl.Uint32Attr(1)),
)
return nlAttrs
}
func testAddDevlinkPortPfAttrs(nlAttrs []*nl.RtAttr) []*nl.RtAttr {
nlAttrs = append(nlAttrs,
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_FLAVOUR, nl.Uint16Attr(nl.DEVLINK_PORT_FLAVOUR_PCI_PF)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_PCI_PF_NUMBER, nl.Uint16Attr(1)),
)
return nlAttrs
}
func testAddDevlinkPortVfAttrs(nlAttrs []*nl.RtAttr) []*nl.RtAttr {
nlAttrs = append(nlAttrs,
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_FLAVOUR, nl.Uint16Attr(nl.DEVLINK_PORT_FLAVOUR_PCI_VF)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_PCI_PF_NUMBER, nl.Uint16Attr(0)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_PCI_VF_NUMBER, nl.Uint16Attr(4)),
)
nlAttrs = testAddDevlinkPortFnAttrs(nlAttrs)
return nlAttrs
}
func testAddDevlinkPortSfAttrs(nlAttrs []*nl.RtAttr) []*nl.RtAttr {
nlAttrs = append(nlAttrs,
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_FLAVOUR, nl.Uint16Attr(nl.DEVLINK_PORT_FLAVOUR_PCI_SF)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_PCI_PF_NUMBER, nl.Uint16Attr(0)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_PCI_SF_NUMBER, nl.Uint32Attr(123)),
)
nlAttrs = testAddDevlinkPortFnAttrs(nlAttrs)
return nlAttrs
}
func testNlAttrsToNetlinkRouteAttrs(nlAttrs []*nl.RtAttr) []syscall.NetlinkRouteAttr {
attrs := []syscall.NetlinkRouteAttr{}
for _, attr := range nlAttrs {
attrs = append(attrs, syscall.NetlinkRouteAttr{Attr: syscall.RtAttr(attr.RtAttr), Value: attr.Data})
}
return attrs
}
func testAddDevlinkPortFnAttrs(nlAttrs []*nl.RtAttr) []*nl.RtAttr {
hwAddr, _ := net.ParseMAC("00:11:22:33:44:55")
hwAddrAttr := nl.NewRtAttr(nl.DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR, []byte(hwAddr))
raw := hwAddrAttr.Serialize()
nlAttr := nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_FUNCTION, raw)
return append(nlAttrs, nlAttr)
}
func testAddDevlinkPortControllerAttrs(nlAttrs []*nl.RtAttr, controllerNumber uint32, external bool) []*nl.RtAttr {
extVal := uint8(0)
if external {
extVal = 1
}
nlAttrs = append(nlAttrs,
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_CONTROLLER_NUMBER, nl.Uint32Attr(controllerNumber)),
nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_EXTERNAL, nl.Uint8Attr(extVal)),
)
return nlAttrs
}
func testAssertCommonAttrs(t *testing.T, port *DevlinkPort) {
assert.Equal(t, "pci", port.BusName)
assert.Equal(t, "0000:08:00.0", port.DeviceName)
assert.Equal(t, uint32(131071), port.PortIndex)
assert.Equal(t, uint16(nl.DEVLINK_PORT_TYPE_ETH), port.PortType)
assert.Equal(t, "eth0", port.NetdeviceName)
assert.Equal(t, uint32(5), port.NetdevIfIndex)
assert.Equal(t, "rdma0", port.RdmaDeviceName)
}
func TestDevlinkPortParseAttributes(t *testing.T) {
t.Run("flavor physical", func(t *testing.T) {
nlAttrs := testGetDevlinkPortCommonAttrs()
nlAttrs = testAddDevlinkPortPhysicalAttrs(nlAttrs)
attrs := testNlAttrsToNetlinkRouteAttrs(nlAttrs)
port := &DevlinkPort{}
err := port.parseAttributes(attrs)
assert.NoError(t, err)
testAssertCommonAttrs(t, port)
assert.Equal(t, uint16(nl.DEVLINK_PORT_FLAVOUR_PHYSICAL), port.PortFlavour)
assert.Equal(t, uint32(1), *port.PortNumber)
assert.Nil(t, port.Fn)
assert.Nil(t, port.PfNumber)
assert.Nil(t, port.VfNumber)
assert.Nil(t, port.SfNumber)
assert.Nil(t, port.ControllerNumber)
assert.Nil(t, port.External)
})
t.Run("flavor pcipf", func(t *testing.T) {
nlAttrs := testGetDevlinkPortCommonAttrs()
nlAttrs = testAddDevlinkPortPfAttrs(nlAttrs)
attrs := testNlAttrsToNetlinkRouteAttrs(nlAttrs)
port := &DevlinkPort{}
err := port.parseAttributes(attrs)
assert.NoError(t, err)
testAssertCommonAttrs(t, port)
assert.Equal(t, uint16(nl.DEVLINK_PORT_FLAVOUR_PCI_PF), port.PortFlavour)
assert.Equal(t, uint16(1), *port.PfNumber)
assert.Nil(t, port.Fn)
assert.Nil(t, port.PortNumber)
assert.Nil(t, port.VfNumber)
assert.Nil(t, port.SfNumber)
assert.Nil(t, port.ControllerNumber)
assert.Nil(t, port.External)
})
t.Run("flavor pcivf", func(t *testing.T) {
nlAttrs := testGetDevlinkPortCommonAttrs()
nlAttrs = testAddDevlinkPortVfAttrs(nlAttrs)
attrs := testNlAttrsToNetlinkRouteAttrs(nlAttrs)
port := &DevlinkPort{}
err := port.parseAttributes(attrs)
assert.NoError(t, err)
testAssertCommonAttrs(t, port)
assert.Equal(t, uint16(nl.DEVLINK_PORT_FLAVOUR_PCI_VF), port.PortFlavour)
assert.Equal(t, uint16(0), *port.PfNumber)
assert.Equal(t, uint16(4), *port.VfNumber)
assert.Equal(t, "00:11:22:33:44:55", port.Fn.HwAddr.String())
assert.Nil(t, port.PortNumber)
assert.Nil(t, port.SfNumber)
assert.Nil(t, port.ControllerNumber)
assert.Nil(t, port.External)
})
t.Run("flavor pcisf", func(t *testing.T) {
nlAttrs := testGetDevlinkPortCommonAttrs()
nlAttrs = testAddDevlinkPortSfAttrs(nlAttrs)
attrs := testNlAttrsToNetlinkRouteAttrs(nlAttrs)
port := &DevlinkPort{}
err := port.parseAttributes(attrs)
assert.NoError(t, err)
testAssertCommonAttrs(t, port)
assert.Equal(t, uint16(nl.DEVLINK_PORT_FLAVOUR_PCI_SF), port.PortFlavour)
assert.Equal(t, uint16(0), *port.PfNumber)
assert.Equal(t, uint32(123), *port.SfNumber)
assert.Equal(t, "00:11:22:33:44:55", port.Fn.HwAddr.String())
assert.Nil(t, port.PortNumber)
assert.Nil(t, port.VfNumber)
assert.Nil(t, port.ControllerNumber)
assert.Nil(t, port.External)
})
t.Run("port with controller - external false", func(t *testing.T) {
nlAttrs := testGetDevlinkPortCommonAttrs()
nlAttrs = testAddDevlinkPortVfAttrs(nlAttrs)
nlAttrs = testAddDevlinkPortControllerAttrs(nlAttrs, 0, false)
attrs := testNlAttrsToNetlinkRouteAttrs(nlAttrs)
port := &DevlinkPort{}
err := port.parseAttributes(attrs)
assert.NoError(t, err)
assert.Equal(t, uint32(0), *port.ControllerNumber)
assert.Equal(t, false, *port.External)
})
t.Run("port with controller - external true", func(t *testing.T) {
nlAttrs := testGetDevlinkPortCommonAttrs()
nlAttrs = testAddDevlinkPortVfAttrs(nlAttrs)
nlAttrs = testAddDevlinkPortControllerAttrs(nlAttrs, 1, true)
attrs := testNlAttrsToNetlinkRouteAttrs(nlAttrs)
port := &DevlinkPort{}
err := port.parseAttributes(attrs)
assert.NoError(t, err)
assert.Equal(t, uint32(1), *port.ControllerNumber)
assert.Equal(t, true, *port.External)
})
}

7
go.mod
View File

@@ -3,6 +3,13 @@ module github.com/vishvananda/netlink
go 1.23 go 1.23
require ( require (
github.com/stretchr/testify v1.10.0
github.com/vishvananda/netns v0.0.5 github.com/vishvananda/netns v0.0.5
golang.org/x/sys v0.10.0 golang.org/x/sys v0.10.0
) )
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

10
go.sum
View File

@@ -1,4 +1,14 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -50,7 +50,8 @@ const (
DEVLINK_ATTR_RESOURCE_OCC = 74 /* u64 */ DEVLINK_ATTR_RESOURCE_OCC = 74 /* u64 */
DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID = 75 /* u64 */ DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID = 75 /* u64 */
DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS = 76 /* u64 */ DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS = 76 /* u64 */
DEVLINK_ATTR_PORT_FLAVOUR = 77 DEVLINK_ATTR_PORT_FLAVOUR = 77 /* u16 */
DEVLINK_ATTR_PORT_NUMBER = 78 /* u32 */
DEVLINK_ATTR_INFO_DRIVER_NAME = 98 DEVLINK_ATTR_INFO_DRIVER_NAME = 98
DEVLINK_ATTR_INFO_SERIAL_NUMBER = 99 DEVLINK_ATTR_INFO_SERIAL_NUMBER = 99
DEVLINK_ATTR_INFO_VERSION_FIXED = 100 DEVLINK_ATTR_INFO_VERSION_FIXED = 100
@@ -58,10 +59,12 @@ const (
DEVLINK_ATTR_INFO_VERSION_STORED = 102 DEVLINK_ATTR_INFO_VERSION_STORED = 102
DEVLINK_ATTR_INFO_VERSION_NAME = 103 DEVLINK_ATTR_INFO_VERSION_NAME = 103
DEVLINK_ATTR_INFO_VERSION_VALUE = 104 DEVLINK_ATTR_INFO_VERSION_VALUE = 104
DEVLINK_ATTR_PORT_PCI_PF_NUMBER = 127 DEVLINK_ATTR_PORT_PCI_PF_NUMBER = 127 /* u16 */
DEVLINK_ATTR_PORT_FUNCTION = 145 DEVLINK_ATTR_PORT_PCI_VF_NUMBER = 128 /* u16 */
DEVLINK_ATTR_PORT_CONTROLLER_NUMBER = 150 DEVLINK_ATTR_PORT_FUNCTION = 145 /* nested */
DEVLINK_ATTR_PORT_PCI_SF_NUMBER = 164 DEVLINK_ATTR_PORT_EXTERNAL = 149 /* u8 */
DEVLINK_ATTR_PORT_CONTROLLER_NUMBER = 150 /* u32 */
DEVLINK_ATTR_PORT_PCI_SF_NUMBER = 164 /* u32 */
) )
const ( const (