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  }