github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/nettools/sriov.go (about)

     1  /*
     2  Copyright 2018 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Some of the code is based on CNI's plugins/main/bridge/bridge.go, pkg/ip/link.go
    18  // Original copyright notice:
    19  //
    20  // Copyright 2014 CNI authors
    21  //
    22  // Licensed under the Apache License, Version 2.0 (the "License");
    23  // you may not use this file except in compliance with the License.
    24  // You may obtain a copy of the License at
    25  //
    26  //     http://www.apache.org/licenses/LICENSE-2.0
    27  //
    28  // Unless required by applicable law or agreed to in writing, software
    29  // distributed under the License is distributed on an "AS IS" BASIS,
    30  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    31  // See the License for the specific language governing permissions and
    32  // limitations under the License.
    33  
    34  package nettools
    35  
    36  import (
    37  	"fmt"
    38  	"io/ioutil"
    39  	"net"
    40  	"os"
    41  	"os/exec"
    42  	"path/filepath"
    43  
    44  	"github.com/containernetworking/cni/pkg/ns"
    45  	"github.com/golang/glog"
    46  	"github.com/vishvananda/netlink"
    47  
    48  	"github.com/Mirantis/virtlet/pkg/network"
    49  	"github.com/Mirantis/virtlet/pkg/utils"
    50  )
    51  
    52  // verify if device is pci virtual function (in the same way as does
    53  // that libvirt (src/util/virpci.c:virPCIIsVirtualFunction)
    54  func isSriovVf(link netlink.Link) bool {
    55  	_, err := os.Stat(filepath.Join("/sys/class/net", link.Attrs().Name, "device/physfn"))
    56  	return err == nil
    57  }
    58  
    59  func getPCIAddressOfVF(devName string) (string, error) {
    60  	linkDestination, err := os.Readlink(filepath.Join("/sys/class/net", devName, "device"))
    61  	if err != nil {
    62  		return "", err
    63  	}
    64  	if linkDestination[:13] != "../../../0000" {
    65  		return "", fmt.Errorf("unknown address as device symlink: %q", linkDestination)
    66  	}
    67  	// we need pci address without leading "../../../"
    68  	return linkDestination[9:], nil
    69  }
    70  
    71  func getDevNameByPCIAddress(address string) (string, error) {
    72  	desiredLinkLocation := "../../../" + address
    73  	devices, err := ioutil.ReadDir("/sys/class/net")
    74  	if err != nil {
    75  		return "", err
    76  	}
    77  	for _, fi := range devices {
    78  		// skip entries in /sys/class/net which are not directories
    79  		// with "device" entry (example: bonding_masters)
    80  		devPath := filepath.Join("/sys/class/net", fi.Name(), "device")
    81  		if _, err := os.Stat(devPath); err != nil {
    82  			continue
    83  		}
    84  
    85  		linkDestination, err := os.Readlink(devPath)
    86  		if err != nil {
    87  			return "", err
    88  		}
    89  		if linkDestination == desiredLinkLocation {
    90  			return fi.Name(), nil
    91  		}
    92  	}
    93  	return "", fmt.Errorf("can't find network device with pci address %q", address)
    94  }
    95  
    96  func unbindDriverFromDevice(pciAddress string) error {
    97  	return ioutil.WriteFile(
    98  		filepath.Join("/sys/bus/pci/devices", pciAddress, "driver/unbind"),
    99  		[]byte(pciAddress),
   100  		0200,
   101  	)
   102  }
   103  
   104  func getDeviceIdentifier(pciAddress string) (string, error) {
   105  	devDir := filepath.Join("/sys/bus/pci/devices", pciAddress)
   106  
   107  	vendor, err := ioutil.ReadFile(filepath.Join(devDir, "vendor"))
   108  	if err != nil {
   109  		return "", err
   110  	}
   111  
   112  	devID, err := ioutil.ReadFile(filepath.Join(devDir, "device"))
   113  	if err != nil {
   114  		return "", err
   115  	}
   116  
   117  	return fmt.Sprintf("%s %s", vendor, devID), nil
   118  }
   119  
   120  func rebindDriverToDevice(pciAddress string) error {
   121  	return ioutil.WriteFile(
   122  		"/sys/bus/pci/drivers_probe",
   123  		[]byte(pciAddress),
   124  		0200,
   125  	)
   126  }
   127  
   128  func bindDeviceToVFIO(devIdentifier string) error {
   129  	return ioutil.WriteFile(
   130  		"/sys/bus/pci/drivers/vfio-pci/new_id",
   131  		[]byte(devIdentifier),
   132  		0200,
   133  	)
   134  }
   135  
   136  func getVirtFNNo(pciAddress string) (int, error) {
   137  	for i := 0; ; i++ {
   138  		dest, err := os.Readlink(
   139  			filepath.Join("/sys/bus/pci/devices", pciAddress, "physfn",
   140  				fmt.Sprintf("virtfn%d", i),
   141  			),
   142  		)
   143  		if err != nil {
   144  			return 0, err
   145  		}
   146  		if dest[3:] == pciAddress {
   147  			return i, nil
   148  		}
   149  	}
   150  }
   151  
   152  func getMasterLinkOfVf(pciAddress string) (netlink.Link, error) {
   153  	dest, err := os.Readlink(filepath.Join("/sys/bus/pci/devices", pciAddress, "physfn"))
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	masterDev, err := getDevNameByPCIAddress(dest[3:])
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	masterLink, err := netlink.LinkByName(masterDev)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	return masterLink, nil
   167  }
   168  
   169  // setMacAndVlanOnVf uses VF pci address to locate its parent device and uses
   170  // it to set mac address and VLAN id on VF.  It needs to be called from the host netns.
   171  func setMacAndVlanOnVf(pciAddress string, mac net.HardwareAddr, vlanID int) error {
   172  	virtFNNo, err := getVirtFNNo(pciAddress)
   173  	if err != nil {
   174  		return fmt.Errorf("cannot find VF number for device with pci address %q: %v", pciAddress, err)
   175  	}
   176  	masterLink, err := getMasterLinkOfVf(pciAddress)
   177  	if err != nil {
   178  		return fmt.Errorf("cannot get link for PF of VF with pci address %q: %v", pciAddress, err)
   179  	}
   180  	if err := netlink.LinkSetVfHardwareAddr(masterLink, virtFNNo, mac); err != nil {
   181  		return fmt.Errorf("cannot set mac address of VF with pci address %q: %v", pciAddress, err)
   182  	}
   183  	err = netlink.LinkSetVfVlan(masterLink, virtFNNo, vlanID)
   184  	if err != nil {
   185  		return fmt.Errorf("cannot set vlan of VF with pci address %q: %v", pciAddress, err)
   186  	}
   187  	return nil
   188  }
   189  
   190  func getVfVlanID(pciAddress string) (int, error) {
   191  	virtFNNo, err := getVirtFNNo(pciAddress)
   192  	if err != nil {
   193  		return 0, err
   194  	}
   195  	masterLink, err := getMasterLinkOfVf(pciAddress)
   196  	if err != nil {
   197  		return 0, err
   198  	}
   199  
   200  	// vfinfos are gathered using `ip link show` because of failure in vishvananda/netlink
   201  	// which is occuring for bigger netlink queries like one asking for list ov VFs of an interface.
   202  	iplinkOutput, err := exec.Command("ip", "link", "show", masterLink.Attrs().Name).CombinedOutput()
   203  	if err != nil {
   204  		return 0, fmt.Errorf("error during execution of ip link show: %q\nOutput:%s", err, iplinkOutput)
   205  	}
   206  	vfinfos, err := utils.ParseIPLinkOutput(iplinkOutput)
   207  	if err != nil {
   208  		return 0, fmt.Errorf("error during parsing ip link output for device %q: %v",
   209  			masterLink.Attrs().Name, err)
   210  	}
   211  
   212  	for _, vfInfo := range vfinfos {
   213  		if vfInfo.ID == virtFNNo {
   214  			return int(vfInfo.VLanID), nil
   215  		}
   216  	}
   217  	return 0, fmt.Errorf("vlan info for %d vf on %s not found", virtFNNo, masterLink.Attrs().Name)
   218  }
   219  
   220  func setupSriovAndGetInterfaceDescription(link netlink.Link, hostNS ns.NetNS) (*network.InterfaceDescription, error) {
   221  	hwAddr := link.Attrs().HardwareAddr
   222  	ifaceName := link.Attrs().Name
   223  	mtu := link.Attrs().MTU
   224  	vlanID := 0
   225  
   226  	pciAddress, err := getPCIAddressOfVF(ifaceName)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	// tapmanager protocol needs a file descriptor in Fo field
   232  	// but SR-IOV part is not using it at all, so set it to
   233  	// new file descriptor with /dev/null opened
   234  	fo, err := os.Open("/dev/null")
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	// Switch to the host netns to get VLAN ID of the VF using its master device.
   240  	if err := utils.CallInNetNSWithSysfsRemounted(hostNS, func(ns.NetNS) error {
   241  		var err error
   242  		vlanID, err = getVfVlanID(pciAddress)
   243  		return err
   244  	}); err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	if err := unbindDriverFromDevice(pciAddress); err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	devIdentifier, err := getDeviceIdentifier(pciAddress)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	if err := bindDeviceToVFIO(devIdentifier); err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	// Switch to the host netns to set mac address and VLAN id
   262  	// of VF using its master device.
   263  	if err := utils.CallInNetNSWithSysfsRemounted(hostNS, func(ns.NetNS) error {
   264  		return setMacAndVlanOnVf(pciAddress, hwAddr, vlanID)
   265  	}); err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	glog.V(3).Infof("Adding interface %q as VF on %s address", ifaceName, pciAddress)
   270  
   271  	return &network.InterfaceDescription{
   272  		Type:         network.InterfaceTypeVF,
   273  		Name:         ifaceName,
   274  		Fo:           fo,
   275  		HardwareAddr: hwAddr,
   276  		PCIAddress:   pciAddress,
   277  		MTU:          uint16(mtu),
   278  		VlanID:       vlanID,
   279  	}, nil
   280  }
   281  
   282  // ReconstructVFs iterates over stored PCI addresses, rebinding each
   283  // corresponding interface to its host driver, changing its MAC address and name
   284  // to the values stored in csn and then moving it into the container namespace
   285  func ReconstructVFs(csn *network.ContainerSideNetwork, netns ns.NetNS, ignoreUnbind bool) error {
   286  	for _, iface := range csn.Interfaces {
   287  		if iface.Type != network.InterfaceTypeVF {
   288  			continue
   289  		}
   290  		if err := unbindDriverFromDevice(iface.PCIAddress); err != nil {
   291  			if ignoreUnbind != true {
   292  				return err
   293  			}
   294  		}
   295  		if err := rebindDriverToDevice(iface.PCIAddress); err != nil {
   296  			return err
   297  		}
   298  		devName, err := getDevNameByPCIAddress(iface.PCIAddress)
   299  		if err != nil {
   300  			return err
   301  		}
   302  		if err := setMacAndVlanOnVf(iface.PCIAddress, iface.HardwareAddr, iface.VlanID); err != nil {
   303  			return err
   304  		}
   305  		link, err := netlink.LinkByName(devName)
   306  		if err != nil {
   307  			return fmt.Errorf("can't find link with name %q: %v", devName, err)
   308  		}
   309  		tmpName, err := RandomVethName()
   310  		if err != nil {
   311  			return err
   312  		}
   313  		if err := netlink.LinkSetName(link, tmpName); err != nil {
   314  			return fmt.Errorf("can't set random name %q on interface %q: %v", tmpName, iface.Name, err)
   315  		}
   316  		if link, err = netlink.LinkByName(tmpName); err != nil {
   317  			return fmt.Errorf("can't reread link info: %v", err)
   318  		}
   319  		if err := netlink.LinkSetNsFd(link, int(netns.Fd())); err != nil {
   320  			return fmt.Errorf("can't move link %q to netns %q: %v", iface.Name, netns.Path(), err)
   321  		}
   322  		if err := netns.Do(func(ns.NetNS) error {
   323  			if err := netlink.LinkSetName(link, iface.Name); err != nil {
   324  				return fmt.Errorf("can't rename device %q to %q: %v", devName, iface.Name, err)
   325  			}
   326  			return nil
   327  		}); err != nil {
   328  			return err
   329  		}
   330  	}
   331  
   332  	return nil
   333  }