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