github.com/openshift/dpu-operator@v0.0.0-20240502153209-3af840d137c2/dpu-cni/pkgs/sriovutils/sriovutils.go (about) 1 package sriovutils 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net" 8 "os" 9 "path/filepath" 10 "strconv" 11 "strings" 12 "time" 13 ) 14 15 var ( 16 sriovConfigured = "/sriov_numvfs" 17 // NetDirectory sysfs net directory 18 NetDirectory = "/sys/class/net" 19 // SysBusPci is sysfs pci device directory 20 SysBusPci = "/sys/bus/pci/devices" 21 // SysV4ArpNotify is the sysfs IPv4 ARP Notify directory 22 SysV4ArpNotify = "/proc/sys/net/ipv4/conf/" 23 // SysV6NdiscNotify is the sysfs IPv6 Neighbor Discovery Notify directory 24 SysV6NdiscNotify = "/proc/sys/net/ipv6/conf/" 25 // UserspaceDrivers is a list of driver names that don't have netlink representation for their devices 26 UserspaceDrivers = []string{"vfio-pci", "uio_pci_generic", "igb_uio"} 27 ) 28 29 // EnableArpAndNdiscNotify enables IPv4 arp_notify and IPv6 ndisc_notify for netdev 30 func EnableArpAndNdiscNotify(ifName string) error { 31 /* For arp_notify, when a value of "1" is set then a Gratuitous ARP request will be sent 32 * when the network device is brought up or if the link-layer address changes. 33 * For ndsic_notify, when a value of "1" is set then a Unsolicited Neighbor Advertisement 34 * will be sent when the network device is brought up or if the link-layer address changes. 35 * Both of these being enabled would be useful in the case when an application reenables 36 * an interface or if the MAC address configuration is changed. The kernel is responsible 37 * for sending of these packets when the conditions are met. 38 */ 39 v4ArpNotifyPath := filepath.Join(SysV4ArpNotify, ifName, "arp_notify") 40 err := os.WriteFile(v4ArpNotifyPath, []byte("1"), os.ModeAppend) 41 if err != nil { 42 return fmt.Errorf("failed to write arp_notify=1 for interface %s: %v", ifName, err) 43 } 44 v6NdiscNotifyPath := filepath.Join(SysV6NdiscNotify, ifName, "ndisc_notify") 45 err = os.WriteFile(v6NdiscNotifyPath, []byte("1"), os.ModeAppend) 46 if err != nil { 47 return fmt.Errorf("failed to write ndisc_notify=1 for interface %s: %v", ifName, err) 48 } 49 return nil 50 } 51 52 // GetSriovNumVfs takes in a PF name(ifName) as string and returns number of VF configured as int 53 func GetSriovNumVfs(ifName string) (int, error) { 54 var vfTotal int 55 56 sriovFile := filepath.Join(NetDirectory, ifName, "device", sriovConfigured) 57 if _, err := os.Lstat(sriovFile); err != nil { 58 return vfTotal, fmt.Errorf("failed to open the sriov_numfs of device %q: %v", ifName, err) 59 } 60 61 data, err := os.ReadFile(sriovFile) 62 if err != nil { 63 return vfTotal, fmt.Errorf("failed to read the sriov_numfs of device %q: %v", ifName, err) 64 } 65 66 if len(data) == 0 { 67 return vfTotal, fmt.Errorf("no data in the file %q", sriovFile) 68 } 69 70 sriovNumfs := strings.TrimSpace(string(data)) 71 vfTotal, err = strconv.Atoi(sriovNumfs) 72 if err != nil { 73 return vfTotal, fmt.Errorf("failed to convert sriov_numfs(byte value) to int of device %q: %v", ifName, err) 74 } 75 76 return vfTotal, nil 77 } 78 79 // GetVfid takes in VF's PCI address(addr) and pfName as string and returns VF's ID as int 80 func GetVfid(addr string, pfName string) (int, error) { 81 var id int 82 vfTotal, err := GetSriovNumVfs(pfName) 83 if err != nil { 84 return id, err 85 } 86 for vf := 0; vf < vfTotal; vf++ { 87 vfDir := filepath.Join(NetDirectory, pfName, "device", fmt.Sprintf("virtfn%d", vf)) 88 _, err := os.Lstat(vfDir) 89 if err != nil { 90 continue 91 } 92 pciinfo, err := os.Readlink(vfDir) 93 if err != nil { 94 continue 95 } 96 pciaddr := filepath.Base(pciinfo) 97 if pciaddr == addr { 98 return vf, nil 99 } 100 } 101 return id, fmt.Errorf("unable to get VF ID with PF: %s and VF pci address %v", pfName, addr) 102 } 103 104 // GetPfName returns PF net device name of a given VF pci address 105 func GetPfName(vf string) (string, error) { 106 pfSymLink := filepath.Join(SysBusPci, vf, "physfn", "net") 107 _, err := os.Lstat(pfSymLink) 108 if err != nil { 109 return "", err 110 } 111 112 files, err := os.ReadDir(pfSymLink) 113 if err != nil { 114 return "", err 115 } 116 117 if len(files) < 1 { 118 return "", fmt.Errorf("PF network device not found") 119 } 120 121 return strings.TrimSpace(files[0].Name()), nil 122 } 123 124 // GetPciAddress takes in a interface(ifName) and VF id and returns its pci addr as string 125 func GetPciAddress(ifName string, vf int) (string, error) { 126 var pciaddr string 127 vfDir := filepath.Join(NetDirectory, ifName, "device", fmt.Sprintf("virtfn%d", vf)) 128 dirInfo, err := os.Lstat(vfDir) 129 if err != nil { 130 return pciaddr, fmt.Errorf("can't get the symbolic link of virtfn%d dir of the device %q: %v", vf, ifName, err) 131 } 132 133 if (dirInfo.Mode() & os.ModeSymlink) == 0 { 134 return pciaddr, fmt.Errorf("no symbolic link for the virtfn%d dir of the device %q", vf, ifName) 135 } 136 137 pciinfo, err := os.Readlink(vfDir) 138 if err != nil { 139 return pciaddr, fmt.Errorf("can't read the symbolic link of virtfn%d dir of the device %q: %v", vf, ifName, err) 140 } 141 142 pciaddr = filepath.Base(pciinfo) 143 return pciaddr, nil 144 } 145 146 // GetSharedPF takes in VF name(ifName) as string and returns the other VF name that shares same PCI address as string 147 func GetSharedPF(ifName string) (string, error) { 148 pfName := "" 149 pfDir := filepath.Join(NetDirectory, ifName) 150 dirInfo, err := os.Lstat(pfDir) 151 if err != nil { 152 return pfName, fmt.Errorf("can't get the symbolic link of the device %q: %v", ifName, err) 153 } 154 155 if (dirInfo.Mode() & os.ModeSymlink) == 0 { 156 return pfName, fmt.Errorf("no symbolic link for dir of the device %q", ifName) 157 } 158 159 fullpath, _ := filepath.EvalSymlinks(pfDir) 160 parentDir := fullpath[:len(fullpath)-len(ifName)] 161 dirList, _ := os.ReadDir(parentDir) 162 163 for _, file := range dirList { 164 if file.Name() != ifName { 165 pfName = file.Name() 166 return pfName, nil 167 } 168 } 169 170 return pfName, fmt.Errorf("shared PF not found") 171 } 172 173 // GetVFLinkName returns VF's network interface name given it's PCI addr 174 func GetVFLinkName(pciAddr string) (string, error) { 175 var names []string 176 vfDir := filepath.Join(SysBusPci, pciAddr, "net") 177 if _, err := os.Lstat(vfDir); err != nil { 178 return "", err 179 } 180 181 fInfos, err := os.ReadDir(vfDir) 182 if err != nil { 183 return "", fmt.Errorf("failed to read net dir of the device %s: %v", pciAddr, err) 184 } 185 186 if len(fInfos) == 0 { 187 return "", fmt.Errorf("VF device %s sysfs path (%s) has no entries", pciAddr, vfDir) 188 } 189 190 names = make([]string, 0) 191 for _, f := range fInfos { 192 names = append(names, f.Name()) 193 } 194 195 return names[0], nil 196 } 197 198 // GetVFLinkNamesFromVFID returns VF's network interface name given it's PF name as string and VF id as int 199 func GetVFLinkNamesFromVFID(pfName string, vfID int) ([]string, error) { 200 var names []string 201 vfDir := filepath.Join(NetDirectory, pfName, "device", fmt.Sprintf("virtfn%d", vfID), "net") 202 if _, err := os.Lstat(vfDir); err != nil { 203 return nil, err 204 } 205 206 fInfos, err := os.ReadDir(vfDir) 207 if err != nil { 208 return nil, fmt.Errorf("failed to read the virtfn%d dir of the device %q: %v", vfID, pfName, err) 209 } 210 211 names = make([]string, 0) 212 for _, f := range fInfos { 213 names = append(names, f.Name()) 214 } 215 216 return names, nil 217 } 218 219 // HasDpdkDriver checks if a device is attached to dpdk supported driver 220 func HasDpdkDriver(pciAddr string) (bool, error) { 221 driverLink := filepath.Join(SysBusPci, pciAddr, "driver") 222 driverPath, err := filepath.EvalSymlinks(driverLink) 223 if err != nil { 224 return false, err 225 } 226 driverStat, err := os.Stat(driverPath) 227 if err != nil { 228 return false, err 229 } 230 driverName := driverStat.Name() 231 for _, drv := range UserspaceDrivers { 232 if driverName == drv { 233 return true, nil 234 } 235 } 236 return false, nil 237 } 238 239 // SaveNetConf takes in container ID, data dir and Pod interface name as string and a json encoded struct Conf 240 // and save this Conf in data dir 241 func SaveNetConf(cid, dataDir, podIfName string, conf interface{}) error { 242 netConfBytes, err := json.Marshal(conf) 243 if err != nil { 244 return fmt.Errorf("error serializing delegate netconf: %v", err) 245 } 246 247 s := []string{cid, podIfName} 248 cRef := strings.Join(s, "-") 249 250 // save the rendered netconf for cmdDel 251 return saveScratchNetConf(cRef, dataDir, netConfBytes) 252 } 253 254 func saveScratchNetConf(containerID, dataDir string, netconf []byte) error { 255 if err := os.MkdirAll(dataDir, 0700); err != nil { 256 return fmt.Errorf("failed to create the sriov data directory(%q): %v", dataDir, err) 257 } 258 259 path := filepath.Join(dataDir, containerID) 260 261 err := os.WriteFile(path, netconf, 0600) 262 if err != nil { 263 return fmt.Errorf("failed to write container data in the path(%q): %v", path, err) 264 } 265 266 return err 267 } 268 269 // ReadScratchNetConf takes in container ID, Pod interface name and data dir as string and returns a pointer to Conf 270 func ReadScratchNetConf(cRefPath string) ([]byte, error) { 271 data, err := os.ReadFile(cRefPath) 272 if err != nil { 273 return nil, fmt.Errorf("failed to read container data in the path(%q): %v", cRefPath, err) 274 } 275 276 return data, err 277 } 278 279 // CleanCachedNetConf removed cached NetConf from disk 280 func CleanCachedNetConf(cRefPath string) error { 281 if err := os.Remove(cRefPath); err != nil { 282 return fmt.Errorf("error removing NetConf file %s: %v", cRefPath, err) 283 } 284 return nil 285 } 286 287 // SetVFEffectiveMAC will try to set the mac address on a specific VF interface 288 // 289 // the function will also validate that the mac address was configured as expect 290 // it will return an error if it didn't manage to configure the vf mac address 291 // or the mac is not equal to the expect one 292 // retries 20 times and wait 100 milliseconds 293 // 294 // Some NIC drivers (i.e. i40e/iavf) set VF MAC address asynchronously 295 // via PF. This means that while the PF could already show the VF with 296 // the desired MAC address, the netdev VF may still have the original 297 // one. If in this window we issue a netdev VF MAC address set, the driver 298 // will return an error and the pod will fail to create. 299 // Other NICs (Mellanox) require explicit netdev VF MAC address so we 300 // cannot skip this part. 301 // Retry up to 5 times; wait 200 milliseconds between retries 302 func SetVFEffectiveMAC(netLinkManager NetlinkManager, netDeviceName string, macAddress string) error { 303 hwaddr, err := net.ParseMAC(macAddress) 304 if err != nil { 305 return fmt.Errorf("failed to parse MAC address %s: %v", macAddress, err) 306 } 307 308 orgLinkObj, err := netLinkManager.LinkByName(netDeviceName) 309 if err != nil { 310 return err 311 } 312 313 return Retry(20, 100*time.Millisecond, func() error { 314 if err := netLinkManager.LinkSetHardwareAddr(orgLinkObj, hwaddr); err != nil { 315 return err 316 } 317 318 linkObj, err := netLinkManager.LinkByName(netDeviceName) 319 if err != nil { 320 return fmt.Errorf("failed to get netlink device with name %s: %q", orgLinkObj.Attrs().Name, err) 321 } 322 if linkObj.Attrs().HardwareAddr.String() != macAddress { 323 return fmt.Errorf("effective mac address is different from requested one") 324 } 325 326 return nil 327 }) 328 } 329 330 // SetVFHardwareMAC will try to set the hardware mac address on a specific VF ID under a requested PF 331 332 // the function will also validate that the mac address was configured as expect 333 // it will return an error if it didn't manage to configure the vf mac address 334 // or the mac is not equal to the expect one 335 // retries 20 times and wait 100 milliseconds 336 func SetVFHardwareMAC(netLinkManager NetlinkManager, pfDevice string, vfID int, macAddress string) error { 337 hwaddr, err := net.ParseMAC(macAddress) 338 if err != nil { 339 return fmt.Errorf("failed to parse MAC address %s: %v", macAddress, err) 340 } 341 342 orgLinkObj, err := netLinkManager.LinkByName(pfDevice) 343 if err != nil { 344 return err 345 } 346 347 return Retry(20, 100*time.Millisecond, func() error { 348 if err := netLinkManager.LinkSetVfHardwareAddr(orgLinkObj, vfID, hwaddr); err != nil { 349 return err 350 } 351 352 linkObj, err := netLinkManager.LinkByName(pfDevice) 353 if err != nil { 354 return fmt.Errorf("failed to get netlink device with name %s: %q", orgLinkObj.Attrs().Name, err) 355 } 356 if linkObj.Attrs().Vfs[vfID].Mac.String() != macAddress { 357 return fmt.Errorf("hardware mac address is different from requested one") 358 } 359 360 return nil 361 }) 362 } 363 364 // IsValidMACAddress checks if net.HardwareAddr is a valid MAC address. 365 func IsValidMACAddress(addr net.HardwareAddr) bool { 366 invalidMACAddresses := [][]byte{ 367 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 368 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 369 } 370 valid := false 371 if len(addr) == 6 { 372 valid = true 373 for _, invalidMACAddress := range invalidMACAddresses { 374 if bytes.Equal(addr, invalidMACAddress) { 375 valid = false 376 break 377 } 378 } 379 } 380 return valid 381 } 382 383 // IsIPv4 checks if a net.IP is an IPv4 address. 384 func IsIPv4(ip net.IP) bool { 385 return ip.To4() != nil 386 } 387 388 // IsIPv6 checks if a net.IP is an IPv6 address. 389 func IsIPv6(ip net.IP) bool { 390 return ip.To4() == nil && ip.To16() != nil 391 } 392 393 // Retry retries a given function until no return error; times out after retries*sleep 394 func Retry(retries int, sleep time.Duration, f func() error) error { 395 err := error(nil) 396 for retry := 0; retry < retries; retry++ { 397 err = f() 398 if err == nil { 399 return nil 400 } 401 time.Sleep(sleep) 402 } 403 return err 404 }