Skip to content
17 changes: 17 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ func (c *Client) Ports() ([]*Port, error) {
return c.c.Ports()
}

// DPIPETables retrieves all devlink DPIPE tables attached to specified device on this system.
func (c *Client) DPIPETables(dev *Device) ([]*DPIPETable, error) {
return c.c.DPIPETables(dev)
}

// An osClient is the operating system-specific implementation of Client.
type osClient interface {
io.Closer
Devices() ([]*Device, error)
Ports() ([]*Port, error)
DPIPETables(*Device) ([]*DPIPETable, error)
}

// A Device is a devlink device.
Expand Down Expand Up @@ -73,3 +79,14 @@ type Port struct {
Type PortType
Name string
}

// A DPIPETable is a devlink pipeline debugging (DPIPE) table.
// For more information on DPIPE, see:
// https://github.com/Mellanox/mlxsw/wiki/Pipeline-Debugging-(DPIPE)
type DPIPETable struct {
Bus string
Device string
Name string
Size uint64
CountersEnabled bool
}
97 changes: 94 additions & 3 deletions client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
package devlink

import (
"fmt"

"github.com/mdlayher/genetlink"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -45,7 +47,7 @@ func (c *client) Close() error { return c.c.Close() }

// Devices implements osClient.
func (c *client) Devices() ([]*Device, error) {
msgs, err := c.execute(unix.DEVLINK_CMD_GET, netlink.Dump)
msgs, err := c.execute(unix.DEVLINK_CMD_GET, netlink.Dump, nil)
if err != nil {
return nil, err
}
Expand All @@ -55,23 +57,49 @@ func (c *client) Devices() ([]*Device, error) {

// Ports implements osClient.
func (c *client) Ports() ([]*Port, error) {
msgs, err := c.execute(unix.DEVLINK_CMD_PORT_GET, netlink.Dump)
msgs, err := c.execute(unix.DEVLINK_CMD_PORT_GET, netlink.Dump, nil)
if err != nil {
return nil, err
}

return parsePorts(msgs)
}

// DPIPETables implements osClient.
func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) {
if dev == nil {
return nil, fmt.Errorf("invalid argument")
}
ae := netlink.NewAttributeEncoder()
ae.String(unix.DEVLINK_ATTR_BUS_NAME, dev.Bus)
ae.String(unix.DEVLINK_ATTR_DEV_NAME, dev.Device)

msgs, err := c.execute(unix.DEVLINK_CMD_DPIPE_TABLE_GET, netlink.Acknowledge, ae)
if err != nil {
return nil, err
}

return parseDPIPETables(msgs)
}

// execute executes the specified command with additional header flags. The
// netlink.Request header flag is automatically set.
func (c *client) execute(cmd uint8, flags netlink.HeaderFlags) ([]genetlink.Message, error) {
func (c *client) execute(cmd uint8, flags netlink.HeaderFlags, ae *netlink.AttributeEncoder) ([]genetlink.Message, error) {
var data []byte
var err error
if ae != nil {
data, err = ae.Encode()
if err != nil {
return nil, err
}
}
return c.c.Execute(
genetlink.Message{
Header: genetlink.Header{
Command: cmd,
Version: unix.DEVLINK_GENL_VERSION,
},
Data: data,
},
// Always pass the genetlink family ID and request flag.
c.family.ID,
Expand Down Expand Up @@ -139,3 +167,66 @@ func parsePorts(msgs []genetlink.Message) ([]*Port, error) {

return ps, nil
}

// parseDPIPETables parses DPIPE tables from a slice of generic netlink messages.
func parseDPIPETables(msgs []genetlink.Message) ([]*DPIPETable, error) {
var bus, dev string
if len(msgs) == 0 {
// No devlink response found.
return nil, nil
}

ts := make([]*DPIPETable, 0, len(msgs))
for _, m := range msgs {
ad, err := netlink.NewAttributeDecoder(m.Data)
if err != nil {
return nil, err
}

for ad.Next() {
switch ad.Type() {
case unix.DEVLINK_ATTR_BUS_NAME:
bus = ad.String()
case unix.DEVLINK_ATTR_DEV_NAME:
dev = ad.String()
case unix.DEVLINK_ATTR_DPIPE_TABLES:
// Netlink array of DPIPE tables.
// Errors while parsing are propagated up to top-level ad.Err check.
ad.Nested(func(nad *netlink.AttributeDecoder) error {
for nad.Next() {
t := parseDPIPETable(nad)
t.Bus = bus
t.Device = dev
ts = append(ts, t)
}
return nil
})
}

}
if err := ad.Err(); err != nil {
return nil, err
}
}
return ts, nil
}

// parseDPIPETable parses a single DPIPE table from a netlink attribute payload.
func parseDPIPETable(ad *netlink.AttributeDecoder) *DPIPETable {
var t DPIPETable
ad.Nested(func(nad *netlink.AttributeDecoder) error {
// Netlink entry for a single DPIPE table.
for nad.Next() {
switch nad.Type() {
case unix.DEVLINK_ATTR_DPIPE_TABLE_NAME:
t.Name = nad.String()
case unix.DEVLINK_ATTR_DPIPE_TABLE_SIZE:
t.Size = nad.Uint64()
case unix.DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED:
t.CountersEnabled = nad.Uint8() != 0
}
}
return nil
})
return &t
}
21 changes: 21 additions & 0 deletions client_linux_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,25 @@ func TestLinuxClientIntegration(t *testing.T) {
t.Logf("port: %+v", p)
}
})

t.Run("dpipe_tables", func(t *testing.T) {
var tables []*devlink.DPIPETable
devices, err := c.Devices()
if err != nil {
t.Fatalf("failed to get devices: %v", err)
}

for _, d := range devices {
tt, err := c.DPIPETables(d)
if err != nil {
t.Errorf("failed to get DPIPE table from device %v: %v", d, err)
}
tables = append(tables, tt...)
}

// Just print all DPIPE tables that are available.
for _, table := range tables {
t.Logf("dpipe_table: %+v", table)
}
})
}
18 changes: 18 additions & 0 deletions client_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import (
)

func TestLinuxClientEmptyResponse(t *testing.T) {
const (
bus = "pci"
device = "0000:01:00.0"
)
tests := []struct {
name string
fn func(t *testing.T, c *client)
Expand Down Expand Up @@ -46,6 +50,20 @@ func TestLinuxClientEmptyResponse(t *testing.T) {
}
},
},
{
name: "dpipe_tables",
fn: func(t *testing.T, c *client) {
dev := Device{bus, device}
tables, err := c.DPIPETables(&dev)
if err != nil {
t.Fatalf("failed to get DPIPE tables: %v", err)
}

if diff := cmp.Diff(0, len(tables)); diff != "" {
t.Fatalf("unexpected number of DPIPE tables (-want +got):\n%s", diff)
}
},
},
}

for _, tt := range tests {
Expand Down
5 changes: 5 additions & 0 deletions client_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ func (c *client) Devices() ([]*Device, error) {
func (c *client) Ports() ([]*Port, error) {
return nil, errUnimplemented
}

// PID implements osClient.
func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) {
return nil, errUnimplemented
}