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 := &current.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  }