github.com/k8snetworkplumbingwg/sriov-network-operator@v1.2.1-0.20240408194816-2d2e5a45d453/pkg/host/internal/network/network.go (about)

     1  package network
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/cenkalti/backoff"
    12  	"github.com/vishvananda/netlink/nl"
    13  	"sigs.k8s.io/controller-runtime/pkg/log"
    14  
    15  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
    16  	dputilsPkg "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/lib/dputils"
    17  	ethtoolPkg "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/lib/ethtool"
    18  	netlinkPkg "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/lib/netlink"
    19  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/types"
    20  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/utils"
    21  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars"
    22  )
    23  
    24  type network struct {
    25  	utilsHelper utils.CmdInterface
    26  	dputilsLib  dputilsPkg.DPUtilsLib
    27  	netlinkLib  netlinkPkg.NetlinkLib
    28  	ethtoolLib  ethtoolPkg.EthtoolLib
    29  }
    30  
    31  func New(utilsHelper utils.CmdInterface, dputilsLib dputilsPkg.DPUtilsLib, netlinkLib netlinkPkg.NetlinkLib, ethtoolLib ethtoolPkg.EthtoolLib) types.NetworkInterface {
    32  	return &network{
    33  		utilsHelper: utilsHelper,
    34  		dputilsLib:  dputilsLib,
    35  		netlinkLib:  netlinkLib,
    36  		ethtoolLib:  ethtoolLib,
    37  	}
    38  }
    39  
    40  // TryToGetVirtualInterfaceName get the interface name of a virtio interface
    41  func (n *network) TryToGetVirtualInterfaceName(pciAddr string) string {
    42  	log.Log.Info("TryToGetVirtualInterfaceName() get interface name for device", "device", pciAddr)
    43  
    44  	// To support different driver that is not virtio-pci like mlx
    45  	name := n.TryGetInterfaceName(pciAddr)
    46  	if name != "" {
    47  		return name
    48  	}
    49  
    50  	netDir, err := filepath.Glob(filepath.Join(vars.FilesystemRoot, consts.SysBusPciDevices, pciAddr, "virtio*", "net"))
    51  	if err != nil || len(netDir) < 1 {
    52  		return ""
    53  	}
    54  
    55  	fInfos, err := os.ReadDir(netDir[0])
    56  	if err != nil {
    57  		log.Log.Error(err, "TryToGetVirtualInterfaceName(): failed to read net directory", "dir", netDir[0])
    58  		return ""
    59  	}
    60  
    61  	names := make([]string, 0)
    62  	for _, f := range fInfos {
    63  		names = append(names, f.Name())
    64  	}
    65  
    66  	if len(names) < 1 {
    67  		return ""
    68  	}
    69  
    70  	return names[0]
    71  }
    72  
    73  func (n *network) TryGetInterfaceName(pciAddr string) string {
    74  	names, err := n.dputilsLib.GetNetNames(pciAddr)
    75  	if err != nil || len(names) < 1 {
    76  		return ""
    77  	}
    78  	netDevName := names[0]
    79  
    80  	// Switchdev PF and their VFs representors are existing under the same PCI address since kernel 5.8
    81  	// if device is switchdev then return PF name
    82  	for _, name := range names {
    83  		if !n.IsSwitchdev(name) {
    84  			continue
    85  		}
    86  		// Try to get the phys port name, if not exists then fallback to check without it
    87  		// phys_port_name should be in formant p<port-num> e.g p0,p1,p2 ...etc.
    88  		if physPortName, err := n.GetPhysPortName(name); err == nil {
    89  			if !vars.PfPhysPortNameRe.MatchString(physPortName) {
    90  				continue
    91  			}
    92  		}
    93  		return name
    94  	}
    95  
    96  	log.Log.V(2).Info("tryGetInterfaceName()", "name", netDevName)
    97  	return netDevName
    98  }
    99  
   100  func (n *network) GetPhysSwitchID(name string) (string, error) {
   101  	swIDFile := filepath.Join(vars.FilesystemRoot, consts.SysClassNet, name, "phys_switch_id")
   102  	physSwitchID, err := os.ReadFile(swIDFile)
   103  	if err != nil {
   104  		return "", err
   105  	}
   106  	if physSwitchID != nil {
   107  		return strings.TrimSpace(string(physSwitchID)), nil
   108  	}
   109  	return "", nil
   110  }
   111  
   112  func (n *network) GetPhysPortName(name string) (string, error) {
   113  	devicePortNameFile := filepath.Join(vars.FilesystemRoot, consts.SysClassNet, name, "phys_port_name")
   114  	physPortName, err := os.ReadFile(devicePortNameFile)
   115  	if err != nil {
   116  		return "", err
   117  	}
   118  	if physPortName != nil {
   119  		return strings.TrimSpace(string(physPortName)), nil
   120  	}
   121  	return "", nil
   122  }
   123  
   124  func (n *network) IsSwitchdev(name string) bool {
   125  	switchID, err := n.GetPhysSwitchID(name)
   126  	if err != nil || switchID == "" {
   127  		return false
   128  	}
   129  
   130  	return true
   131  }
   132  
   133  func (n *network) GetNetdevMTU(pciAddr string) int {
   134  	log.Log.V(2).Info("GetNetdevMTU(): get MTU", "device", pciAddr)
   135  	ifaceName := n.TryGetInterfaceName(pciAddr)
   136  	if ifaceName == "" {
   137  		return 0
   138  	}
   139  
   140  	link, err := n.netlinkLib.LinkByName(ifaceName)
   141  	if err != nil {
   142  		log.Log.Error(err, "GetNetdevMTU(): fail to get Link ", "device", ifaceName)
   143  		return 0
   144  	}
   145  
   146  	return link.Attrs().MTU
   147  }
   148  
   149  func (n *network) SetNetdevMTU(pciAddr string, mtu int) error {
   150  	log.Log.V(2).Info("SetNetdevMTU(): set MTU", "device", pciAddr, "mtu", mtu)
   151  	if mtu <= 0 {
   152  		log.Log.V(2).Info("SetNetdevMTU(): refusing to set MTU", "mtu", mtu)
   153  		return nil
   154  	}
   155  	b := backoff.NewConstantBackOff(1 * time.Second)
   156  	err := backoff.Retry(func() error {
   157  		ifaceName := n.TryGetInterfaceName(pciAddr)
   158  		if ifaceName == "" {
   159  			log.Log.Error(nil, "SetNetdevMTU(): fail to get interface name", "device", pciAddr)
   160  			return fmt.Errorf("failed to get netdevice for device %s", pciAddr)
   161  		}
   162  
   163  		link, err := n.netlinkLib.LinkByName(ifaceName)
   164  		if err != nil {
   165  			log.Log.Error(err, "SetNetdevMTU(): fail to get Link ", "device", ifaceName)
   166  			return err
   167  		}
   168  		return n.netlinkLib.LinkSetMTU(link, mtu)
   169  	}, backoff.WithMaxRetries(b, 10))
   170  
   171  	if err != nil {
   172  		log.Log.Error(err, "SetNetdevMTU(): fail to set mtu after retrying")
   173  		return err
   174  	}
   175  	return nil
   176  }
   177  
   178  // GetNetDevMac returns network device MAC address or empty string if address cannot be
   179  // retrieved.
   180  func (n *network) GetNetDevMac(ifaceName string) string {
   181  	log.Log.V(2).Info("GetNetDevMac(): get Mac", "device", ifaceName)
   182  	link, err := n.netlinkLib.LinkByName(ifaceName)
   183  	if err != nil {
   184  		log.Log.Error(err, "GetNetDevMac(): failed to get Link", "device", ifaceName)
   185  		return ""
   186  	}
   187  	return link.Attrs().HardwareAddr.String()
   188  }
   189  
   190  func (n *network) GetNetDevLinkSpeed(ifaceName string) string {
   191  	log.Log.V(2).Info("GetNetDevLinkSpeed(): get LinkSpeed", "device", ifaceName)
   192  	speedFilePath := filepath.Join(vars.FilesystemRoot, consts.SysClassNet, ifaceName, "speed")
   193  	data, err := os.ReadFile(speedFilePath)
   194  	if err != nil {
   195  		log.Log.Error(err, "GetNetDevLinkSpeed(): fail to read Link Speed file", "path", speedFilePath)
   196  		return ""
   197  	}
   198  
   199  	return fmt.Sprintf("%s Mb/s", strings.TrimSpace(string(data)))
   200  }
   201  
   202  // GetDevlinkDeviceParam returns devlink parameter for the device as a string, if the parameter has multiple values
   203  // then the function will return only first one from the list.
   204  func (n *network) GetDevlinkDeviceParam(pciAddr, paramName string) (string, error) {
   205  	funcLog := log.Log.WithValues("device", pciAddr, "param", paramName)
   206  	funcLog.V(2).Info("GetDevlinkDeviceParam(): get device parameter")
   207  	param, err := n.netlinkLib.DevlinkGetDeviceParamByName(consts.BusPci, pciAddr, paramName)
   208  	if err != nil {
   209  		funcLog.Error(err, "GetDevlinkDeviceParam(): fail to get devlink device param")
   210  		return "", err
   211  	}
   212  	if len(param.Values) == 0 {
   213  		err = fmt.Errorf("param %s has no value", paramName)
   214  		funcLog.Error(err, "GetDevlinkDeviceParam(): error")
   215  		return "", err
   216  	}
   217  	var value string
   218  	switch param.Type {
   219  	case nl.DEVLINK_PARAM_TYPE_U8, nl.DEVLINK_PARAM_TYPE_U16, nl.DEVLINK_PARAM_TYPE_U32:
   220  		var valData uint64
   221  		switch v := param.Values[0].Data.(type) {
   222  		case uint8:
   223  			valData = uint64(v)
   224  		case uint16:
   225  			valData = uint64(v)
   226  		case uint32:
   227  			valData = uint64(v)
   228  		default:
   229  			return "", fmt.Errorf("unexpected uint type type")
   230  		}
   231  		value = strconv.FormatUint(valData, 10)
   232  
   233  	case nl.DEVLINK_PARAM_TYPE_STRING:
   234  		value = param.Values[0].Data.(string)
   235  	case nl.DEVLINK_PARAM_TYPE_BOOL:
   236  		value = strconv.FormatBool(param.Values[0].Data.(bool))
   237  	default:
   238  		return "", fmt.Errorf("unknown value type: %d", param.Type)
   239  	}
   240  	funcLog.V(2).Info("GetDevlinkDeviceParam(): result", "value", value)
   241  	return value, nil
   242  }
   243  
   244  // SetDevlinkDeviceParam set devlink parameter for the device, accepts paramName and value
   245  // as a string. Automatically set CMODE for the parameter and converts the value to the right
   246  // type before submitting it.
   247  func (n *network) SetDevlinkDeviceParam(pciAddr, paramName, value string) error {
   248  	funcLog := log.Log.WithValues("device", pciAddr, "param", paramName, "value", value)
   249  	funcLog.V(2).Info("SetDevlinkDeviceParam(): set device parameter")
   250  	param, err := n.netlinkLib.DevlinkGetDeviceParamByName(consts.BusPci, pciAddr, paramName)
   251  	if err != nil {
   252  		funcLog.Error(err, "SetDevlinkDeviceParam(): can't get existing param data")
   253  		return err
   254  	}
   255  	if len(param.Values) == 0 {
   256  		err = fmt.Errorf("param %s has no value", paramName)
   257  		funcLog.Error(err, "SetDevlinkDeviceParam(): error")
   258  		return err
   259  	}
   260  	targetCMOD := param.Values[0].CMODE
   261  	var typedValue interface{}
   262  	var v uint64
   263  	switch param.Type {
   264  	case nl.DEVLINK_PARAM_TYPE_U8:
   265  		v, err = strconv.ParseUint(value, 10, 8)
   266  		typedValue = uint8(v)
   267  	case nl.DEVLINK_PARAM_TYPE_U16:
   268  		v, err = strconv.ParseUint(value, 10, 16)
   269  		typedValue = uint16(v)
   270  	case nl.DEVLINK_PARAM_TYPE_U32:
   271  		v, err = strconv.ParseUint(value, 10, 32)
   272  		typedValue = uint32(v)
   273  	case nl.DEVLINK_PARAM_TYPE_STRING:
   274  		err = nil
   275  		typedValue = value
   276  	case nl.DEVLINK_PARAM_TYPE_BOOL:
   277  		typedValue, err = strconv.ParseBool(value)
   278  	default:
   279  		return fmt.Errorf("parameter has unknown value type: %d", param.Type)
   280  	}
   281  	if err != nil {
   282  		err = fmt.Errorf("failed to convert value %s to the required type: %T, devlink paramType is: %d", value, typedValue, param.Type)
   283  		funcLog.Error(err, "SetDevlinkDeviceParam(): error")
   284  		return err
   285  	}
   286  	if err := n.netlinkLib.DevlinkSetDeviceParam(consts.BusPci, pciAddr, paramName, targetCMOD, typedValue); err != nil {
   287  		funcLog.Error(err, "SetDevlinkDeviceParam(): failed to set parameter")
   288  		return err
   289  	}
   290  	return nil
   291  }
   292  
   293  // EnableHwTcOffload makes sure that hw-tc-offload feature is enabled if device supports it
   294  func (n *network) EnableHwTcOffload(ifaceName string) error {
   295  	log.Log.V(2).Info("EnableHwTcOffload(): enable offloading", "device", ifaceName)
   296  	hwTcOffloadFeatureName := "hw-tc-offload"
   297  
   298  	knownFeatures, err := n.ethtoolLib.FeatureNames(ifaceName)
   299  	if err != nil {
   300  		log.Log.Error(err, "EnableHwTcOffload(): can't list supported features", "device", ifaceName)
   301  		return err
   302  	}
   303  	if _, isKnown := knownFeatures[hwTcOffloadFeatureName]; !isKnown {
   304  		log.Log.V(0).Info("EnableHwTcOffload(): can't enable feature, feature is not supported", "device", ifaceName)
   305  		return nil
   306  	}
   307  	currentFeaturesState, err := n.ethtoolLib.Features(ifaceName)
   308  	if err != nil {
   309  		log.Log.Error(err, "EnableHwTcOffload(): can't read features state for device", "device", ifaceName)
   310  		return err
   311  	}
   312  	if currentFeaturesState[hwTcOffloadFeatureName] {
   313  		log.Log.V(2).Info("EnableHwTcOffload(): already enabled", "device", ifaceName)
   314  		return nil
   315  	}
   316  	if err := n.ethtoolLib.Change(ifaceName, map[string]bool{hwTcOffloadFeatureName: true}); err != nil {
   317  		log.Log.Error(err, "EnableHwTcOffload(): can't set feature for device", "device", ifaceName)
   318  		return err
   319  	}
   320  	updatedFeaturesState, err := n.ethtoolLib.Features(ifaceName)
   321  	if err != nil {
   322  		log.Log.Error(err, "EnableHwTcOffload(): can't read features state for device", "device", ifaceName)
   323  		return err
   324  	}
   325  	if updatedFeaturesState[hwTcOffloadFeatureName] {
   326  		log.Log.V(2).Info("EnableHwTcOffload(): feature enabled", "device", ifaceName)
   327  		return nil
   328  	}
   329  	log.Log.V(0).Info("EnableHwTcOffload(): feature is still disabled, not supported by device", "device", ifaceName)
   330  	return nil
   331  }