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 }