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

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package loader
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/vishvananda/netlink"
    14  	"golang.org/x/sys/unix"
    15  
    16  	"github.com/cilium/ebpf"
    17  	"github.com/cilium/ebpf/link"
    18  
    19  	"github.com/cilium/cilium/pkg/bpf"
    20  )
    21  
    22  func parentToAttachType(parent uint32) ebpf.AttachType {
    23  	switch parent {
    24  	case netlink.HANDLE_MIN_INGRESS:
    25  		return ebpf.AttachTCXIngress
    26  	case netlink.HANDLE_MIN_EGRESS:
    27  		return ebpf.AttachTCXEgress
    28  	}
    29  	panic(fmt.Sprintf("invalid tc direction: %d", parent))
    30  }
    31  
    32  // upsertTCXProgram updates or creates a new tcx attachment for prog to device.
    33  // Returns [link.ErrNotSupported] if tcx is not supported on the node.
    34  func upsertTCXProgram(device netlink.Link, prog *ebpf.Program, progName, bpffsDir string, parent uint32) error {
    35  	err := updateTCX(prog, progName, bpffsDir)
    36  	if err == nil {
    37  		// Link was updated, nothing left to do.
    38  		return nil
    39  	}
    40  	if !errors.Is(err, os.ErrNotExist) {
    41  		// Unrecoverable error, surface to the caller.
    42  		return fmt.Errorf("updating tcx program: %w", err)
    43  	}
    44  
    45  	return attachTCX(device, prog, progName, bpffsDir, parentToAttachType(parent))
    46  }
    47  
    48  // attachTCX creates a new tcx attachment for prog to device at the given attach
    49  // type. It pins the resulting link object to progName in bpffsDir.
    50  //
    51  // progName is typically the Program's key in CollectionSpec.Programs.
    52  func attachTCX(device netlink.Link, prog *ebpf.Program, progName, bpffsDir string, attach ebpf.AttachType) error {
    53  	if err := bpf.MkdirBPF(bpffsDir); err != nil {
    54  		return fmt.Errorf("creating bpffs link dir for tcx attachment to device %s: %w", device.Attrs().Name, err)
    55  	}
    56  
    57  	l, err := link.AttachTCX(link.TCXOptions{
    58  		Program:   prog,
    59  		Attach:    attach,
    60  		Interface: device.Attrs().Index,
    61  		Anchor:    link.Tail(),
    62  	})
    63  	if err != nil {
    64  		return fmt.Errorf("attaching tcx: %w", err)
    65  	}
    66  
    67  	defer func() {
    68  		// The program was successfully attached using tcx. Closing a link does not
    69  		// detach the program if the link is pinned.
    70  		if err := l.Close(); err != nil {
    71  			log.Warnf("Failed to close tcx link for program %s", progName)
    72  		}
    73  	}()
    74  
    75  	pin := filepath.Join(bpffsDir, progName)
    76  	if err := l.Pin(pin); err != nil {
    77  		return fmt.Errorf("pinning link at %s for program %s : %w", pin, progName, err)
    78  	}
    79  
    80  	log.Infof("Program %s attached to device %s using tcx", progName, device.Attrs().Name)
    81  
    82  	return nil
    83  }
    84  
    85  // updateTCX attempts to update an existing tcx link called progName in
    86  // bpffsDir. If the link is defunct, the pin is removed.
    87  //
    88  // Returns nil if the update was successful. Returns an error wrapping
    89  // [os.ErrNotExist] if the link is defunct or missing.
    90  func updateTCX(prog *ebpf.Program, progName, bpffsDir string) error {
    91  	// Attempt to open and update an existing link.
    92  	pin := filepath.Join(bpffsDir, progName)
    93  	err := bpf.UpdateLink(pin, prog)
    94  	switch {
    95  	// Link exists, but is defunct, and needs to be recreated. The program
    96  	// no longer gets triggered at this point and the link needs to be removed
    97  	// to proceed.
    98  	case errors.Is(err, unix.ENOLINK):
    99  		if err := os.Remove(pin); err != nil {
   100  			return fmt.Errorf("unpinning defunct link %s: %w", pin, err)
   101  		}
   102  
   103  		log.Infof("Unpinned defunct link %s for program %s", pin, progName)
   104  
   105  		// Wrap in os.ErrNotExist so the caller needs to look for one error.
   106  		return fmt.Errorf("unpinned defunct link: %w", os.ErrNotExist)
   107  
   108  	// No existing link found, continue trying to create one.
   109  	case errors.Is(err, os.ErrNotExist):
   110  		log.Debugf("No existing link found at %s for program %s", pin, progName)
   111  		return err
   112  
   113  	case err != nil:
   114  		return fmt.Errorf("updating link %s for program %s: %w", pin, progName, err)
   115  	}
   116  
   117  	log.Infof("Updated link %s for program %s", pin, progName)
   118  	return nil
   119  }
   120  
   121  // hasCiliumTCXLinks returns true if device has a Cilium-managed tcx program
   122  // with the given attach type.
   123  func hasCiliumTCXLinks(device netlink.Link, attach ebpf.AttachType) (bool, error) {
   124  	result, err := link.QueryPrograms(link.QueryOptions{
   125  		Target: int(device.Attrs().Index),
   126  		Attach: attach,
   127  	})
   128  	if errors.Is(err, unix.EINVAL) {
   129  		// Attach type likely not supported, kernel doesn't support tcx.
   130  		return false, nil
   131  	}
   132  	if err != nil {
   133  		return false, fmt.Errorf("querying %s tcx programs for device %s: %w", attach, device.Attrs().Name, err)
   134  	}
   135  	if result == nil || len(result.Programs) == 0 {
   136  		return false, nil
   137  	}
   138  
   139  	for _, p := range result.Programs {
   140  		prog, err := ebpf.NewProgramFromID(p.ID)
   141  		if err != nil {
   142  			return false, fmt.Errorf("opening program with id %d: %w", p.ID, err)
   143  		}
   144  		defer prog.Close()
   145  
   146  		pi, err := prog.Info()
   147  		if err != nil {
   148  			continue
   149  		}
   150  		if strings.HasPrefix(pi.Name, "cil_") {
   151  			return true, nil
   152  		}
   153  	}
   154  
   155  	return false, nil
   156  }