github.com/openshift/dpu-operator@v0.0.0-20240502153209-3af840d137c2/dpu-cni/pkgs/networkfn/networkfn.go (about) 1 package networkfn 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 8 current "github.com/containernetworking/cni/pkg/types/100" 9 "github.com/containernetworking/plugins/pkg/ipam" 10 "github.com/containernetworking/plugins/pkg/ns" 11 "github.com/openshift/dpu-operator/dpu-cni/pkgs/cnitypes" 12 "github.com/vishvananda/netlink" 13 "k8s.io/klog/v2" 14 ) 15 16 func setDevTempName(dev netlink.Link) (netlink.Link, error) { 17 // Generate a temp name with the interface index 18 tempName := fmt.Sprintf("%s%d", "temp_", dev.Attrs().Index) 19 20 // Rename to tempName 21 if err := netlink.LinkSetName(dev, tempName); err != nil { 22 return nil, fmt.Errorf("failed to rename device %q to %q: %v", dev.Attrs().Name, tempName, err) 23 } 24 25 // Get updated Link obj 26 tempDev, err := netlink.LinkByName(tempName) 27 if err != nil { 28 return nil, fmt.Errorf("failed to find %q after rename to %q: %v", dev.Attrs().Name, tempName, err) 29 } 30 31 klog.Infof("setDevTempName: Set temp name %+v %q", tempDev, tempName) 32 33 return tempDev, nil 34 } 35 36 func moveLinkInNetNamespace(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netlink.Link, error) { 37 origLinkFlags := hostDev.Attrs().Flags 38 origHostDevName := hostDev.Attrs().Name 39 40 // Get the default namespace from the host 41 defaultHostNs, err := ns.GetCurrentNS() 42 if err != nil { 43 return nil, fmt.Errorf("failed to get host namespace: %v", err) 44 } 45 46 // Devices can be renamed only when down 47 if err = netlink.LinkSetDown(hostDev); err != nil { 48 return nil, fmt.Errorf("failed to set %q down: %v", hostDev.Attrs().Name, err) 49 } 50 klog.Infof("moveLinkInNetNamespace: Set Link Down name %q", hostDev.Attrs().Name) 51 52 // Restore original link state in case of error 53 defer func() { 54 if err != nil { 55 // If the device is originally up, make sure to bring it up 56 if origLinkFlags&net.FlagUp == net.FlagUp && hostDev != nil { 57 _ = netlink.LinkSetUp(hostDev) 58 klog.Infof("Error: moveLinkInNetNamespace: Set Link Up name %q", hostDev.Attrs().Name) 59 } 60 } 61 }() 62 63 hostDev, err = setDevTempName(hostDev) 64 if err != nil { 65 return nil, fmt.Errorf("failed to rename device %q to temporary name: %v", origHostDevName, err) 66 } 67 68 // Restore original netdev name in case of error 69 defer func() { 70 if err != nil && hostDev != nil { 71 _ = netlink.LinkSetName(hostDev, origHostDevName) 72 klog.Infof("Error: moveLinkInNetNamespace: Set Link Up on interface %q", hostDev.Attrs().Name) 73 } 74 }() 75 76 // Move interface to the container network namespace 77 if err = netlink.LinkSetNsFd(hostDev, int(containerNs.Fd())); err != nil { 78 return nil, fmt.Errorf("failed to move %q to container ns: %v", hostDev.Attrs().Name, err) 79 } 80 klog.Infof("moveLinkInNetNamespace: Move interface %q to %q", hostDev.Attrs().Name, containerNs.Path()) 81 82 var contDev netlink.Link 83 tempDevName := hostDev.Attrs().Name 84 if err = containerNs.Do(func(_ ns.NetNS) error { 85 var err error 86 contDev, err = netlink.LinkByName(tempDevName) 87 if err != nil { 88 return fmt.Errorf("failed to find %q: %v", tempDevName, err) 89 } 90 91 // Move netdev back to host namespace in case of error 92 defer func() { 93 if err != nil { 94 _ = netlink.LinkSetNsFd(contDev, int(defaultHostNs.Fd())) 95 klog.Infof("Error: moveLinkInNetNamespace: Move interface %q to %q", contDev.Attrs().Name, defaultHostNs.Path()) 96 // we need to get updated link object as link was moved back to host namespace 97 _ = defaultHostNs.Do(func(_ ns.NetNS) error { 98 hostDev, _ = netlink.LinkByName(tempDevName) 99 return nil 100 }) 101 } 102 }() 103 104 // Save host device name into the container device's alias property 105 if err = netlink.LinkSetAlias(contDev, origHostDevName); err != nil { 106 return fmt.Errorf("failed to set alias to %q: %v", tempDevName, err) 107 } 108 klog.Infof("moveLinkInNetNamespace: Save original host name %q on %q", origHostDevName, contDev.Attrs().Name) 109 110 // Rename container device to respect ifName coming from CNI netconf 111 if err = netlink.LinkSetName(contDev, ifName); err != nil { 112 return fmt.Errorf("failed to rename device %q to %q: %v", tempDevName, ifName, err) 113 } 114 klog.Infof("moveLinkInNetNamespace: Rename interface %q to %q", contDev.Attrs().Name, ifName) 115 116 // Restore tempDevName in case of error 117 defer func() { 118 if err != nil { 119 _ = netlink.LinkSetName(contDev, tempDevName) 120 klog.Infof("Error: moveLinkInNetNamespace: Rename interface %q to %q", contDev.Attrs().Name, tempDevName) 121 } 122 }() 123 124 // Bring container device up 125 if err = netlink.LinkSetUp(contDev); err != nil { 126 return fmt.Errorf("failed to set %q up: %v", ifName, err) 127 } 128 klog.Infof("moveLinkInNetNamespace: Set Link Up on interface %q", contDev.Attrs().Name) 129 130 // Bring device down in case of error 131 defer func() { 132 if err != nil { 133 _ = netlink.LinkSetDown(contDev) 134 klog.Infof("Error: moveLinkInNetNamespace: Set Link Down on interface %q", contDev.Attrs().Name) 135 } 136 }() 137 138 // Retrieve link again to get up-to-date name and attributes 139 contDev, err = netlink.LinkByName(ifName) 140 if err != nil { 141 return fmt.Errorf("failed to find %q: %v", ifName, err) 142 } 143 return nil 144 }); err != nil { 145 return nil, err 146 } 147 148 return contDev, nil 149 } 150 151 func moveLinkOutToHost(containerNs ns.NetNS, ifName string) error { 152 // Get the default namespace from the host 153 defaultHostNs, err := ns.GetCurrentNS() 154 if err != nil { 155 return err 156 } 157 defer defaultHostNs.Close() 158 159 var tempName string 160 var origDev netlink.Link 161 err = containerNs.Do(func(_ ns.NetNS) error { 162 dev, err := netlink.LinkByName(ifName) 163 if err != nil { 164 return fmt.Errorf("failed to find %q: %v", ifName, err) 165 } 166 origDev = dev 167 168 // Devices can be renamed only when down 169 if err = netlink.LinkSetDown(dev); err != nil { 170 return fmt.Errorf("failed to set %q down: %v", ifName, err) 171 } 172 klog.Infof("moveLinkOutToHost: Set Link Down on interface %q", ifName) 173 174 defer func() { 175 // If moving the device to the host namespace fails, set its name back to ifName so that this 176 // function can be retried. Also bring the device back up, unless it was already down before. 177 if err != nil { 178 _ = netlink.LinkSetName(dev, ifName) 179 if dev.Attrs().Flags&net.FlagUp == net.FlagUp { 180 _ = netlink.LinkSetUp(dev) 181 klog.Infof("Error: moveLinkOutToHost: Set Link Up on interface %q", dev.Attrs().Name) 182 } 183 } 184 }() 185 186 newLink, err := setDevTempName(dev) 187 if err != nil { 188 return fmt.Errorf("failed to rename device %q to temporary name: %v", ifName, err) 189 } 190 dev = newLink 191 tempName = dev.Attrs().Name 192 193 if err = netlink.LinkSetNsFd(dev, int(defaultHostNs.Fd())); err != nil { 194 return fmt.Errorf("failed to move %q to host netns: %v", tempName, err) 195 } 196 klog.Infof("moveLinkOutToHost: Move interface %q to %q", tempName, defaultHostNs.Path()) 197 return nil 198 }) 199 200 if err != nil { 201 return err 202 } 203 204 // Rename the device to its original name from the host namespace 205 tempDev, err := netlink.LinkByName(tempName) 206 if err != nil { 207 return fmt.Errorf("failed to find %q in host namespace: %v", tempName, err) 208 } 209 210 // Use the device's alias to do this. 211 if err = netlink.LinkSetName(tempDev, tempDev.Attrs().Alias); err != nil { 212 // Move device back to container ns so it may be retired 213 defer func() { 214 _ = netlink.LinkSetNsFd(tempDev, int(containerNs.Fd())) 215 _ = containerNs.Do(func(_ ns.NetNS) error { 216 lnk, err := netlink.LinkByName(tempName) 217 if err != nil { 218 return err 219 } 220 _ = netlink.LinkSetName(lnk, ifName) 221 if origDev.Attrs().Flags&net.FlagUp == net.FlagUp { 222 _ = netlink.LinkSetUp(lnk) 223 } 224 return nil 225 }) 226 }() 227 return fmt.Errorf("failed to restore %q to original name %q: %v", tempName, tempDev.Attrs().Alias, err) 228 } 229 230 return nil 231 } 232 233 func CmdAdd(req *cnitypes.PodRequest) (*current.Result, error) { 234 klog.Info("CmdAdd called for networkfn") 235 236 conf := req.CNIConf 237 238 klog.Infof("CmdAdd: conf %+v", conf) 239 240 containerNs, err := ns.GetNS(req.Netns) 241 if err != nil { 242 return nil, fmt.Errorf("failed to open netns %+v: %v", containerNs, err) 243 } 244 defer containerNs.Close() 245 246 klog.Infof("CmdAdd: Netns: %q", req.Netns) 247 248 result := ¤t.Result{} 249 var contDev netlink.Link 250 251 // TODO: In the future we may want to support the following formats coming from the device plugin 252 // pciAddr: For Netdev and DPDK use cases 253 // auxDevices: Device plugins may allocate network device on a bus different than PCI 254 // Also this code would not work for DPDK interfaces. 255 hostDev, err := netlink.LinkByName(conf.DeviceID) 256 if err != nil { 257 return nil, fmt.Errorf("failed to find host device: %v", err) 258 } 259 260 contDev, err = moveLinkInNetNamespace(hostDev, containerNs, req.IfName) 261 if err != nil { 262 return nil, fmt.Errorf("failed to move link %v", err) 263 } 264 265 result.Interfaces = []*current.Interface{{ 266 Name: contDev.Attrs().Name, 267 Mac: contDev.Attrs().HardwareAddr.String(), 268 Sandbox: containerNs.Path(), 269 }} 270 271 if conf.IPAM.Type == "" { 272 return result, nil 273 } 274 275 klog.Infof("CmdAdd: Running IPAM %q", conf.IPAM.Type) 276 // Run the IPAM plugin and get back the config to apply 277 r, err := ipam.ExecAdd(conf.IPAM.Type, req.CNIReq.Config) 278 if err != nil { 279 return nil, err 280 } 281 282 // Invoke ipam del if err to avoid ip leak 283 defer func() { 284 if err != nil { 285 ipam.ExecDel(conf.IPAM.Type, req.CNIReq.Config) 286 } 287 }() 288 289 // Convert the IPAM result was into the current Result type 290 newResult, err := current.NewResultFromResult(r) 291 if err != nil { 292 return nil, err 293 } 294 295 if len(newResult.IPs) == 0 { 296 return nil, errors.New("IPAM plugin returned missing IP config") 297 } 298 299 for _, ipc := range newResult.IPs { 300 // All addresses apply to the container interface 301 ipc.Interface = current.Int(0) 302 } 303 304 newResult.Interfaces = result.Interfaces 305 306 err = containerNs.Do(func(_ ns.NetNS) error { 307 return ipam.ConfigureIface(req.IfName, newResult) 308 }) 309 if err != nil { 310 return nil, err 311 } 312 313 newResult.DNS = conf.DNS 314 315 return newResult, nil 316 } 317 318 func CmdDel(req *cnitypes.PodRequest) error { 319 klog.Info("CmdDel called for networkfn") 320 321 conf := req.CNIConf 322 323 if req.Netns == "" { 324 return nil 325 } 326 327 containerNs, err := ns.GetNS(req.Netns) 328 if err != nil { 329 return fmt.Errorf("failed to open netns %q: %v", req.Netns, err) 330 } 331 defer containerNs.Close() 332 333 klog.Infof("CmdDel: Netns: %q", req.Netns) 334 335 if conf.IPAM.Type != "" { 336 if err := ipam.ExecDel(conf.IPAM.Type, req.CNIReq.Config); err != nil { 337 return err 338 } 339 } 340 341 klog.Infof("CmdDel: Running IPAM %q", conf.IPAM.Type) 342 343 if err := moveLinkOutToHost(containerNs, req.IfName); err != nil { 344 return err 345 } 346 347 return nil 348 }