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 }