github.com/cilium/cilium@v1.16.2/pkg/datapath/loader/xdp.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package loader
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/cilium/ebpf"
    15  	"github.com/cilium/ebpf/link"
    16  	"github.com/vishvananda/netlink"
    17  	"golang.org/x/sys/unix"
    18  
    19  	"github.com/cilium/cilium/pkg/bpf"
    20  	"github.com/cilium/cilium/pkg/identity"
    21  	"github.com/cilium/cilium/pkg/mac"
    22  	"github.com/cilium/cilium/pkg/option"
    23  )
    24  
    25  func xdpConfigModeToFlag(xdpMode string) link.XDPAttachFlags {
    26  	switch xdpMode {
    27  	case option.XDPModeNative, option.XDPModeLinkDriver, option.XDPModeBestEffort:
    28  		return link.XDPDriverMode
    29  	case option.XDPModeGeneric, option.XDPModeLinkGeneric:
    30  		return link.XDPGenericMode
    31  	}
    32  	return 0
    33  }
    34  
    35  // These constant values are returned by the kernel when querying the XDP program attach mode.
    36  // Important: they differ from constants that are used when attaching an XDP program to a netlink device.
    37  const (
    38  	xdpAttachedNone uint32 = iota
    39  	xdpAttachedDriver
    40  	xdpAttachedGeneric
    41  )
    42  
    43  // xdpAttachedModeToFlag maps the attach mode that is returned in the metadata when
    44  // querying netlink devices to the attach flags that were used to configure the
    45  // xdp program attachement.
    46  func xdpAttachedModeToFlag(mode uint32) link.XDPAttachFlags {
    47  	switch mode {
    48  	case xdpAttachedDriver:
    49  		return link.XDPDriverMode
    50  	case xdpAttachedGeneric:
    51  		return link.XDPGenericMode
    52  	}
    53  	return 0
    54  }
    55  
    56  // maybeUnloadObsoleteXDPPrograms removes bpf_xdp.o from previously used
    57  // devices.
    58  //
    59  // bpffsBase is typically set to /sys/fs/bpf/cilium, but can be a temp directory
    60  // during tests.
    61  func (l *loader) maybeUnloadObsoleteXDPPrograms(xdpDevs []string, xdpMode, bpffsBase string) {
    62  	links, err := netlink.LinkList()
    63  	if err != nil {
    64  		log.WithError(err).Warn("Failed to list links for XDP unload")
    65  	}
    66  
    67  	for _, link := range links {
    68  		linkxdp := link.Attrs().Xdp
    69  		if linkxdp == nil || !linkxdp.Attached {
    70  			// No XDP program is attached
    71  			continue
    72  		}
    73  		if strings.Contains(link.Attrs().Name, "cilium") {
    74  			// Ignore devices created by cilium-agent
    75  			continue
    76  		}
    77  
    78  		used := false
    79  		for _, xdpDev := range xdpDevs {
    80  			if link.Attrs().Name == xdpDev &&
    81  				xdpAttachedModeToFlag(linkxdp.AttachMode) == xdpConfigModeToFlag(xdpMode) {
    82  				// XDP mode matches; don't unload, otherwise we might introduce
    83  				// intermittent connectivity problems
    84  				used = true
    85  				break
    86  			}
    87  		}
    88  		if !used {
    89  			if err := l.DetachXDP(link.Attrs().Name, bpffsBase, symbolFromHostNetdevXDP); err != nil {
    90  				log.WithError(err).Warn("Failed to detach obsolete XDP program")
    91  			}
    92  		}
    93  	}
    94  }
    95  
    96  // xdpCompileArgs derives compile arguments for bpf_xdp.c.
    97  func xdpCompileArgs(xdpDev string, extraCArgs []string) ([]string, error) {
    98  	link, err := netlink.LinkByName(xdpDev)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	args := []string{
   104  		fmt.Sprintf("-DSECLABEL=%d", identity.ReservedIdentityWorld),
   105  		fmt.Sprintf("-DTHIS_INTERFACE_MAC={.addr=%s}", mac.CArrayString(link.Attrs().HardwareAddr)),
   106  		fmt.Sprintf("-DCALLS_MAP=cilium_calls_xdp_%d", link.Attrs().Index),
   107  	}
   108  	args = append(args, extraCArgs...)
   109  	if option.Config.EnableNodePort {
   110  		args = append(args, []string{
   111  			fmt.Sprintf("-DTHIS_MTU=%d", link.Attrs().MTU),
   112  			fmt.Sprintf("-DNATIVE_DEV_IFINDEX=%d", link.Attrs().Index),
   113  			"-DDISABLE_LOOPBACK_LB",
   114  		}...)
   115  	}
   116  	if option.Config.IsDualStack() {
   117  		args = append(args, fmt.Sprintf("-DSECLABEL_IPV4=%d", identity.ReservedIdentityWorldIPv4))
   118  		args = append(args, fmt.Sprintf("-DSECLABEL_IPV6=%d", identity.ReservedIdentityWorldIPv6))
   119  	} else {
   120  		args = append(args, fmt.Sprintf("-DSECLABEL_IPV4=%d", identity.ReservedIdentityWorld))
   121  		args = append(args, fmt.Sprintf("-DSECLABEL_IPV6=%d", identity.ReservedIdentityWorld))
   122  	}
   123  
   124  	return args, nil
   125  }
   126  
   127  // compileAndLoadXDPProg compiles bpf_xdp.c for the given XDP device and loads it.
   128  func compileAndLoadXDPProg(ctx context.Context, xdpDev, xdpMode string, extraCArgs []string) error {
   129  	args, err := xdpCompileArgs(xdpDev, extraCArgs)
   130  	if err != nil {
   131  		return fmt.Errorf("failed to derive XDP compile extra args: %w", err)
   132  	}
   133  
   134  	dirs := &directoryInfo{
   135  		Library: option.Config.BpfDir,
   136  		Runtime: option.Config.StateDir,
   137  		Output:  option.Config.StateDir,
   138  		State:   option.Config.StateDir,
   139  	}
   140  	prog := &progInfo{
   141  		Source:     xdpProg,
   142  		Output:     xdpObj,
   143  		OutputType: outputObject,
   144  		Options:    args,
   145  	}
   146  
   147  	objPath, err := compile(ctx, prog, dirs)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	if err := ctx.Err(); err != nil {
   152  		return err
   153  	}
   154  
   155  	iface, err := netlink.LinkByName(xdpDev)
   156  	if err != nil {
   157  		return fmt.Errorf("retrieving device %s: %w", xdpDev, err)
   158  	}
   159  
   160  	spec, err := bpf.LoadCollectionSpec(objPath)
   161  	if err != nil {
   162  		return fmt.Errorf("loading eBPF ELF %s: %w", objPath, err)
   163  	}
   164  
   165  	coll, commit, err := loadDatapath(spec, nil, nil)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	defer coll.Close()
   170  
   171  	if err := attachXDPProgram(iface, coll.Programs[symbolFromHostNetdevXDP], symbolFromHostNetdevXDP,
   172  		bpffsDeviceLinksDir(bpf.CiliumPath(), iface), xdpConfigModeToFlag(xdpMode)); err != nil {
   173  		return fmt.Errorf("interface %s: %w", xdpDev, err)
   174  	}
   175  
   176  	if err := commit(); err != nil {
   177  		return fmt.Errorf("committing bpf pins: %w", err)
   178  	}
   179  
   180  	return nil
   181  }
   182  
   183  // attachXDPProgram attaches prog with the given progName to link.
   184  //
   185  // bpffsDir should exist and point to the links/ subdirectory in the per-device
   186  // bpffs directory.
   187  func attachXDPProgram(iface netlink.Link, prog *ebpf.Program, progName, bpffsDir string, flags link.XDPAttachFlags) error {
   188  	if prog == nil {
   189  		return fmt.Errorf("program %s is nil", progName)
   190  	}
   191  
   192  	// Attempt to open and update an existing link.
   193  	pin := filepath.Join(bpffsDir, progName)
   194  	err := bpf.UpdateLink(pin, prog)
   195  	switch {
   196  	// Update successful, nothing left to do.
   197  	case err == nil:
   198  		log.Infof("Updated link %s for program %s", pin, progName)
   199  
   200  		return nil
   201  
   202  	// Link exists, but is defunct, and needs to be recreated. The program
   203  	// no longer gets triggered at this point and the link needs to be removed
   204  	// to proceed.
   205  	case errors.Is(err, unix.ENOLINK):
   206  		if err := os.Remove(pin); err != nil {
   207  			return fmt.Errorf("unpinning defunct link %s: %w", pin, err)
   208  		}
   209  
   210  		log.Infof("Unpinned defunct link %s for program %s", pin, progName)
   211  
   212  	// No existing link found, continue trying to create one.
   213  	case errors.Is(err, os.ErrNotExist):
   214  		log.Infof("No existing link found at %s for program %s", pin, progName)
   215  
   216  	default:
   217  		return fmt.Errorf("updating link %s for program %s: %w", pin, progName, err)
   218  	}
   219  
   220  	if err := bpf.MkdirBPF(bpffsDir); err != nil {
   221  		return fmt.Errorf("creating bpffs link dir for xdp attachment to device %s: %w", iface.Attrs().Name, err)
   222  	}
   223  
   224  	// Create a new link. This will only succeed on nodes that support bpf_link
   225  	// and don't have any XDP programs attached through netlink.
   226  	l, err := link.AttachXDP(link.XDPOptions{
   227  		Program:   prog,
   228  		Interface: iface.Attrs().Index,
   229  		Flags:     flags,
   230  	})
   231  	if err == nil {
   232  		defer func() {
   233  			// The program was successfully attached using bpf_link. Closing a link
   234  			// does not detach the program if the link is pinned.
   235  			if err := l.Close(); err != nil {
   236  				log.Warnf("Failed to close bpf_link for program %s", progName)
   237  			}
   238  		}()
   239  
   240  		if err := l.Pin(pin); err != nil {
   241  			return fmt.Errorf("pinning link at %s for program %s : %w", pin, progName, err)
   242  		}
   243  
   244  		// Successfully created and pinned bpf_link.
   245  		log.Infof("Program %s attached using bpf_link", progName)
   246  
   247  		return nil
   248  	}
   249  
   250  	// Kernels before 5.7 don't support bpf_link. In that case link.AttachXDP
   251  	// returns ErrNotSupported.
   252  	//
   253  	// If the kernel supports bpf_link, but an older version of Cilium attached a
   254  	// XDP program, link.AttachXDP will return EBUSY.
   255  	if !errors.Is(err, unix.EBUSY) && !errors.Is(err, link.ErrNotSupported) {
   256  		// Unrecoverable error from AttachRawLink.
   257  		return fmt.Errorf("attaching program %s using bpf_link: %w", progName, err)
   258  	}
   259  
   260  	log.Debugf("Performing netlink attach for program %s", progName)
   261  
   262  	// Omitting XDP_FLAGS_UPDATE_IF_NOEXIST equals running 'ip' with -force,
   263  	// and will clobber any existing XDP attachment to the interface, including
   264  	// bpf_link attachments created by a different process.
   265  	if err := netlink.LinkSetXdpFdWithFlags(iface, prog.FD(), int(flags)); err != nil {
   266  		return fmt.Errorf("attaching XDP program %s to interface %s using netlink: %w", progName, iface.Attrs().Name, err)
   267  	}
   268  
   269  	// Nothing left to do, the netlink device now holds a reference to the prog
   270  	// the program stays active.
   271  	log.Infof("Program %s was attached using netlink", progName)
   272  
   273  	return nil
   274  }
   275  
   276  // DetachXDP removes an XDP program from a network interface. On kernels before
   277  // 4.15, always removes the XDP program regardless of progName.
   278  //
   279  // bpffsBase is typically /sys/fs/bpf/cilium, but can be overridden to a tempdir
   280  // during tests.
   281  func (l *loader) DetachXDP(ifaceName string, bpffsBase, progName string) error {
   282  	iface, err := netlink.LinkByName(ifaceName)
   283  	if err != nil {
   284  		return fmt.Errorf("getting link '%s' by name: %w", ifaceName, err)
   285  	}
   286  
   287  	pin := filepath.Join(bpffsDeviceLinksDir(bpffsBase, iface), progName)
   288  	err = bpf.UnpinLink(pin)
   289  	if err == nil {
   290  		return nil
   291  	}
   292  	if !errors.Is(err, os.ErrNotExist) {
   293  		// The pinned link exists, something went wrong unpinning it.
   294  		return fmt.Errorf("unpinning XDP program using bpf_link: %w", err)
   295  	}
   296  
   297  	xdp := iface.Attrs().Xdp
   298  	if xdp == nil || !xdp.Attached {
   299  		return nil
   300  	}
   301  
   302  	// Inspect the attached program to only remove the intended XDP program.
   303  	id := xdp.ProgId
   304  	prog, err := ebpf.NewProgramFromID(ebpf.ProgramID(id))
   305  	if err != nil {
   306  		return fmt.Errorf("opening XDP program id %d: %w", id, err)
   307  	}
   308  	info, err := prog.Info()
   309  	if err != nil {
   310  		return fmt.Errorf("getting XDP program info %d: %w", id, err)
   311  	}
   312  	// The program name returned by BPF_PROG_INFO is limited to 20 characters.
   313  	// Treat the kernel-provided program name as a prefix that needs to match
   314  	// against progName. Empty program names (on kernels before 4.15) will always
   315  	// match and be removed.
   316  	if !strings.HasPrefix(progName, info.Name) {
   317  		return nil
   318  	}
   319  
   320  	// Pin doesn't exist, fall through to detaching using netlink.
   321  	if err := netlink.LinkSetXdpFdWithFlags(iface, -1, int(link.XDPGenericMode)); err != nil {
   322  		return fmt.Errorf("detaching generic-mode XDP program using netlink: %w", err)
   323  	}
   324  
   325  	if err := netlink.LinkSetXdpFdWithFlags(iface, -1, int(link.XDPDriverMode)); err != nil {
   326  		return fmt.Errorf("detaching driver-mode XDP program using netlink: %w", err)
   327  	}
   328  
   329  	return nil
   330  }