From 5daafafd954d6daf286efddbf0e84afeeaae6321 Mon Sep 17 00:00:00 2001 From: Yury Kulazhenkov Date: Thu, 8 Feb 2024 16:07:51 +0200 Subject: [PATCH] Add functions to work with devlink device parameters Functions added: DevlinkGetDeviceParams - get all parameters for device DevlinkGetDeviceParamByName - get specific parameter for device DevlinkSetDeviceParam - set parameter for device Signed-off-by: Yury Kulazhenkov --- devlink_linux.go | 239 ++++++++++++++++++++++++++++++++++++++++++++ devlink_test.go | 116 +++++++++++++++++++++ nl/devlink_linux.go | 27 +++++ 3 files changed, 382 insertions(+) diff --git a/devlink_linux.go b/devlink_linux.go index 10687a5..d98801d 100644 --- a/devlink_linux.go +++ b/devlink_linux.go @@ -247,6 +247,107 @@ func (dlrs *DevlinkResources) parseAttributes(attrs map[uint16]syscall.NetlinkRo return nil } +// DevlinkParam represents parameter of the device +type DevlinkParam struct { + Name string + IsGeneric bool + Type uint8 // possible values are in nl.DEVLINK_PARAM_TYPE_* constants + Values []DevlinkParamValue +} + +// DevlinkParamValue contains values of the parameter +// Data field contains specific type which can be casted by unsing info from the DevlinkParam.Type field +type DevlinkParamValue struct { + rawData []byte + Data interface{} + CMODE uint8 // possible values are in nl.DEVLINK_PARAM_CMODE_* constants +} + +// parseAttributes parses provided Netlink Attributes and populates DevlinkParam, returns error if occured +func (dlp *DevlinkParam) parseAttributes(attrs []syscall.NetlinkRouteAttr) error { + var valuesList [][]syscall.NetlinkRouteAttr + for _, attr := range attrs { + switch attr.Attr.Type { + case nl.DEVLINK_ATTR_PARAM: + nattrs, err := nl.ParseRouteAttr(attr.Value) + if err != nil { + return err + } + for _, nattr := range nattrs { + switch nattr.Attr.Type { + case nl.DEVLINK_ATTR_PARAM_NAME: + dlp.Name = nl.BytesToString(nattr.Value) + case nl.DEVLINK_ATTR_PARAM_GENERIC: + dlp.IsGeneric = true + case nl.DEVLINK_ATTR_PARAM_TYPE: + if len(nattr.Value) == 1 { + dlp.Type = nattr.Value[0] + } + case nl.DEVLINK_ATTR_PARAM_VALUES_LIST: + nnattrs, err := nl.ParseRouteAttr(nattr.Value) + if err != nil { + return err + } + valuesList = append(valuesList, nnattrs) + } + } + } + } + for _, valAttr := range valuesList { + v := DevlinkParamValue{} + if err := v.parseAttributes(valAttr, dlp.Type); err != nil { + return err + } + dlp.Values = append(dlp.Values, v) + } + return nil +} + +func (dlpv *DevlinkParamValue) parseAttributes(attrs []syscall.NetlinkRouteAttr, paramType uint8) error { + for _, attr := range attrs { + nattrs, err := nl.ParseRouteAttr(attr.Value) + if err != nil { + return err + } + var rawData []byte + for _, nattr := range nattrs { + switch nattr.Attr.Type { + case nl.DEVLINK_ATTR_PARAM_VALUE_DATA: + rawData = nattr.Value + case nl.DEVLINK_ATTR_PARAM_VALUE_CMODE: + if len(nattr.Value) == 1 { + dlpv.CMODE = nattr.Value[0] + } + } + } + switch paramType { + case nl.DEVLINK_PARAM_TYPE_U8: + dlpv.Data = uint8(0) + if rawData != nil && len(rawData) == 1 { + dlpv.Data = uint8(rawData[0]) + } + case nl.DEVLINK_PARAM_TYPE_U16: + dlpv.Data = uint16(0) + if rawData != nil { + dlpv.Data = native.Uint16(rawData) + } + case nl.DEVLINK_PARAM_TYPE_U32: + dlpv.Data = uint32(0) + if rawData != nil { + dlpv.Data = native.Uint32(rawData) + } + case nl.DEVLINK_PARAM_TYPE_STRING: + dlpv.Data = "" + if rawData != nil { + dlpv.Data = nl.BytesToString(rawData) + } + case nl.DEVLINK_PARAM_TYPE_BOOL: + dlpv.Data = rawData != nil + } + } + return nil +} + func parseDevLinkDeviceList(msgs [][]byte) ([]*DevlinkDevice, error) { devices := make([]*DevlinkDevice, 0, len(msgs)) for _, m := range msgs { @@ -635,6 +736,144 @@ func (h *Handle) DevlinkGetDeviceResources(bus string, device string) (*DevlinkR return &resources, nil } +// DevlinkGetDeviceParams returns parameters for devlink device +// Equivalent to: `devlink dev param show /` +func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) { + _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device) + if err != nil { + return nil, err + } + req.Flags |= unix.NLM_F_DUMP + respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) + if err != nil { + return nil, err + } + var params []*DevlinkParam + for _, m := range respmsg { + attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) + if err != nil { + return nil, err + } + p := &DevlinkParam{} + if err := p.parseAttributes(attrs); err != nil { + return nil, err + } + params = append(params, p) + } + + return params, nil +} + +// DevlinkGetDeviceParams returns parameters for devlink device +// Equivalent to: `devlink dev param show /` +func DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) { + return pkgHandle.DevlinkGetDeviceParams(bus, device) +} + +// DevlinkGetDeviceParamByName returns specific parameter for devlink device +// Equivalent to: `devlink dev param show / name ` +func (h *Handle) DevlinkGetDeviceParamByName(bus string, device string, param string) (*DevlinkParam, error) { + _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device) + if err != nil { + return nil, err + } + req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_NAME, nl.ZeroTerminated(param))) + respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) + if err != nil { + return nil, err + } + if len(respmsg) == 0 { + return nil, fmt.Errorf("unexpected response") + } + attrs, err := nl.ParseRouteAttr(respmsg[0][nl.SizeofGenlmsg:]) + if err != nil { + return nil, err + } + p := &DevlinkParam{} + if err := p.parseAttributes(attrs); err != nil { + return nil, err + } + return p, nil +} + +// DevlinkGetDeviceParamByName returns specific parameter for devlink device +// Equivalent to: `devlink dev param show / name ` +func DevlinkGetDeviceParamByName(bus string, device string, param string) (*DevlinkParam, error) { + return pkgHandle.DevlinkGetDeviceParamByName(bus, device, param) +} + +// DevlinkSetDeviceParam set specific parameter for devlink device +// Equivalent to: `devlink dev param set / name cmode value ` +// cmode argument should contain valid cmode value as uint8, modes are define in nl.DEVLINK_PARAM_CMODE_* constants +// value argument should have one of the following types: uint8, uint16, uint32, string, bool +func (h *Handle) DevlinkSetDeviceParam(bus string, device string, param string, cmode uint8, value interface{}) error { + // retrive the param type + p, err := h.DevlinkGetDeviceParamByName(bus, device, param) + if err != nil { + return fmt.Errorf("failed to get device param: %v", err) + } + paramType := p.Type + + _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_SET, bus, device) + if err != nil { + return err + } + req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_TYPE, nl.Uint8Attr(paramType))) + req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_NAME, nl.ZeroTerminated(param))) + req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_VALUE_CMODE, nl.Uint8Attr(cmode))) + + var valueAsBytes []byte + switch paramType { + case nl.DEVLINK_PARAM_TYPE_U8: + v, ok := value.(uint8) + if !ok { + return fmt.Errorf("unepected value type required: uint8, actual: %T", value) + } + valueAsBytes = nl.Uint8Attr(v) + case nl.DEVLINK_PARAM_TYPE_U16: + v, ok := value.(uint16) + if !ok { + return fmt.Errorf("unepected value type required: uint16, actual: %T", value) + } + valueAsBytes = nl.Uint16Attr(v) + case nl.DEVLINK_PARAM_TYPE_U32: + v, ok := value.(uint32) + if !ok { + return fmt.Errorf("unepected value type required: uint32, actual: %T", value) + } + valueAsBytes = nl.Uint32Attr(v) + case nl.DEVLINK_PARAM_TYPE_STRING: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unepected value type required: string, actual: %T", value) + } + valueAsBytes = nl.ZeroTerminated(v) + case nl.DEVLINK_PARAM_TYPE_BOOL: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unepected value type required: bool, actual: %T", value) + } + if v { + valueAsBytes = []byte{} + } + default: + return fmt.Errorf("unsupported parameter type: %d", paramType) + } + if valueAsBytes != nil { + req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_VALUE_DATA, valueAsBytes)) + } + _, err = req.Execute(unix.NETLINK_GENERIC, 0) + return err +} + +// DevlinkSetDeviceParam set specific parameter for devlink device +// Equivalent to: `devlink dev param set / name cmode value ` +// cmode argument should contain valid cmode value as uint8, modes are define in nl.DEVLINK_PARAM_CMODE_* constants +// value argument should have one of the following types: uint8, uint16, uint32, string, bool +func DevlinkSetDeviceParam(bus string, device string, param string, cmode uint8, value interface{}) error { + return pkgHandle.DevlinkSetDeviceParam(bus, device, param, cmode, value) +} + // DevLinkGetPortByIndex provides a pointer to devlink portand nil error, // otherwise returns an error code. func DevLinkGetPortByIndex(Bus string, Device string, PortIndex uint32) (*DevlinkPort, error) { diff --git a/devlink_test.go b/devlink_test.go index 5449a5e..cf6961c 100644 --- a/devlink_test.go +++ b/devlink_test.go @@ -5,8 +5,13 @@ package netlink import ( "flag" + "math/rand" "net" + "os" + "strconv" "testing" + + "github.com/vishvananda/netlink/nl" ) func TestDevLinkGetDeviceList(t *testing.T) { @@ -286,3 +291,114 @@ func TestDevlinkGetDeviceResources(t *testing.T) { t.Logf("Resources: %+v", res) } + +// devlink device parameters can be tested with netdevsim +// function will create netdevsim/netdevsim virtual device that can be used for testing +// netdevsim module should be loaded to run devlink param tests +func setupDevlinkDeviceParamTest(t *testing.T) (string, string, func()) { + t.Helper() + skipUnlessRoot(t) + skipUnlessKModuleLoaded(t, "netdevsim") + testDevID := strconv.Itoa(1000 + rand.Intn(1000)) + err := os.WriteFile("/sys/bus/netdevsim/new_device", []byte(testDevID), 0755) + if err != nil { + t.Fatalf("can't create netdevsim test device %s: %v", testDevID, err) + } + + return "netdevsim", "netdevsim" + testDevID, func() { + _ = os.WriteFile("/sys/bus/netdevsim/del_device", []byte(testDevID), 0755) + } +} + +func TestDevlinkGetDeviceParams(t *testing.T) { + busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t) + defer cleanupFunc() + params, err := DevlinkGetDeviceParams(busName, deviceName) + if err != nil { + t.Fatalf("failed to get device(%s/%s) parameters. %s", busName, deviceName, err) + } + if len(params) == 0 { + t.Fatal("parameters list is empty") + } + for _, p := range params { + validateDeviceParams(t, p) + } +} + +func TestDevlinkGetDeviceParamByName(t *testing.T) { + busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t) + defer cleanupFunc() + param, err := DevlinkGetDeviceParamByName(busName, deviceName, "max_macs") + if err != nil { + t.Fatalf("failed to get device(%s/%s) parameter max_macs. %s", busName, deviceName, err) + } + validateDeviceParams(t, param) +} + +func TestDevlinkSetDeviceParam(t *testing.T) { + busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t) + defer cleanupFunc() + err := DevlinkSetDeviceParam(busName, deviceName, "max_macs", nl.DEVLINK_PARAM_CMODE_DRIVERINIT, uint32(8)) + if err != nil { + t.Fatalf("failed to set max_macs for device(%s/%s): %s", busName, deviceName, err) + } + param, err := DevlinkGetDeviceParamByName(busName, deviceName, "max_macs") + if err != nil { + t.Fatalf("failed to get device(%s/%s) parameter max_macs. %s", busName, deviceName, err) + } + validateDeviceParams(t, param) + v, ok := param.Values[0].Data.(uint32) + if !ok { + t.Fatalf("unexpected value") + } + if v != uint32(8) { + t.Fatalf("value not set") + } +} + +func validateDeviceParams(t *testing.T, p *DevlinkParam) { + if p.Name == "" { + t.Fatal("Name field not set") + } + if p.Name == "max_macs" && !p.IsGeneric { + t.Fatal("IsGeneric should be true for generic parameter") + } + // test1 is a driver-specific parameter in netdevsim device, check should + // also path on HW devices + if p.Name == "test1" && p.IsGeneric { + t.Fatal("IsGeneric should be false for driver-specific parameter") + } + switch p.Type { + case nl.DEVLINK_PARAM_TYPE_U8, + nl.DEVLINK_PARAM_TYPE_U16, + nl.DEVLINK_PARAM_TYPE_U32, + nl.DEVLINK_PARAM_TYPE_STRING, + nl.DEVLINK_PARAM_TYPE_BOOL: + default: + t.Fatal("Type has unexpected value") + } + if len(p.Values) == 0 { + t.Fatal("Values are not set") + } + for _, v := range p.Values { + switch v.CMODE { + case nl.DEVLINK_PARAM_CMODE_RUNTIME, + nl.DEVLINK_PARAM_CMODE_DRIVERINIT, + nl.DEVLINK_PARAM_CMODE_PERMANENT: + default: + t.Fatal("CMODE has unexpected value") + } + if p.Name == "max_macs" { + _, ok := v.Data.(uint32) + if !ok { + t.Fatalf("value max_macs has wrong type: %T, expected: uint32", v.Data) + } + } + if p.Name == "test1" { + _, ok := v.Data.(bool) + if !ok { + t.Fatalf("value test1 has wrong type: %T, expected: bool", v.Data) + } + } + } +} diff --git a/nl/devlink_linux.go b/nl/devlink_linux.go index 4ab19d0..956367b 100644 --- a/nl/devlink_linux.go +++ b/nl/devlink_linux.go @@ -17,6 +17,8 @@ const ( DEVLINK_CMD_ESWITCH_GET = 29 DEVLINK_CMD_ESWITCH_SET = 30 DEVLINK_CMD_RESOURCE_DUMP = 36 + DEVLINK_CMD_PARAM_GET = 38 + DEVLINK_CMD_PARAM_SET = 39 DEVLINK_CMD_INFO_GET = 51 ) @@ -113,3 +115,28 @@ const ( const ( DEVLINK_RESOURCE_UNIT_ENTRY uint8 = 0 ) + +const ( + DEVLINK_ATTR_PARAM = iota + 80 /* nested */ + DEVLINK_ATTR_PARAM_NAME /* string */ + DEVLINK_ATTR_PARAM_GENERIC /* flag */ + DEVLINK_ATTR_PARAM_TYPE /* u8 */ + DEVLINK_ATTR_PARAM_VALUES_LIST /* nested */ + DEVLINK_ATTR_PARAM_VALUE /* nested */ + DEVLINK_ATTR_PARAM_VALUE_DATA /* dynamic */ + DEVLINK_ATTR_PARAM_VALUE_CMODE /* u8 */ +) + +const ( + DEVLINK_PARAM_TYPE_U8 = 1 + DEVLINK_PARAM_TYPE_U16 = 2 + DEVLINK_PARAM_TYPE_U32 = 3 + DEVLINK_PARAM_TYPE_STRING = 5 + DEVLINK_PARAM_TYPE_BOOL = 6 +) + +const ( + DEVLINK_PARAM_CMODE_RUNTIME = iota + DEVLINK_PARAM_CMODE_DRIVERINIT + DEVLINK_PARAM_CMODE_PERMANENT +)