github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/link/cgroup.go (about)

     1  package link
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  
     8  	"github.com/cilium/ebpf"
     9  )
    10  
    11  type cgroupAttachFlags uint32
    12  
    13  const (
    14  	// Allow programs attached to sub-cgroups to override the verdict of this
    15  	// program.
    16  	flagAllowOverride cgroupAttachFlags = 1 << iota
    17  	// Allow attaching multiple programs to the cgroup. Only works if the cgroup
    18  	// has zero or more programs attached using the Multi flag. Implies override.
    19  	flagAllowMulti
    20  	// Set automatically by progAttachCgroup.Update(). Used for updating a
    21  	// specific given program attached in multi-mode.
    22  	flagReplace
    23  )
    24  
    25  type CgroupOptions struct {
    26  	// Path to a cgroupv2 folder.
    27  	Path string
    28  	// One of the AttachCgroup* constants
    29  	Attach ebpf.AttachType
    30  	// Program must be of type CGroup*, and the attach type must match Attach.
    31  	Program *ebpf.Program
    32  }
    33  
    34  // AttachCgroup links a BPF program to a cgroup.
    35  //
    36  // If the running kernel doesn't support bpf_link, attempts to emulate its
    37  // semantics using the legacy PROG_ATTACH mechanism. If bpf_link is not
    38  // available, the returned [Link] will not support pinning to bpffs.
    39  //
    40  // If you need more control over attachment flags or the attachment mechanism
    41  // used, look at [RawAttachProgram] and [AttachRawLink] instead.
    42  func AttachCgroup(opts CgroupOptions) (cg Link, err error) {
    43  	cgroup, err := os.Open(opts.Path)
    44  	if err != nil {
    45  		return nil, fmt.Errorf("can't open cgroup: %s", err)
    46  	}
    47  	defer func() {
    48  		if _, ok := cg.(*progAttachCgroup); ok {
    49  			// Skip closing the cgroup handle if we return a valid progAttachCgroup,
    50  			// where the handle is retained to implement Update().
    51  			return
    52  		}
    53  		cgroup.Close()
    54  	}()
    55  
    56  	cg, err = newLinkCgroup(cgroup, opts.Attach, opts.Program)
    57  	if err == nil {
    58  		return cg, nil
    59  	}
    60  
    61  	if errors.Is(err, ErrNotSupported) {
    62  		cg, err = newProgAttachCgroup(cgroup, opts.Attach, opts.Program, flagAllowMulti)
    63  	}
    64  	if errors.Is(err, ErrNotSupported) {
    65  		cg, err = newProgAttachCgroup(cgroup, opts.Attach, opts.Program, flagAllowOverride)
    66  	}
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	return cg, nil
    72  }
    73  
    74  type progAttachCgroup struct {
    75  	cgroup     *os.File
    76  	current    *ebpf.Program
    77  	attachType ebpf.AttachType
    78  	flags      cgroupAttachFlags
    79  }
    80  
    81  var _ Link = (*progAttachCgroup)(nil)
    82  
    83  func (cg *progAttachCgroup) isLink() {}
    84  
    85  // newProgAttachCgroup attaches prog to cgroup using BPF_PROG_ATTACH.
    86  // cgroup and prog are retained by [progAttachCgroup].
    87  func newProgAttachCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program, flags cgroupAttachFlags) (*progAttachCgroup, error) {
    88  	if flags&flagAllowMulti > 0 {
    89  		if err := haveProgAttachReplace(); err != nil {
    90  			return nil, fmt.Errorf("can't support multiple programs: %w", err)
    91  		}
    92  	}
    93  
    94  	// Use a program handle that cannot be closed by the caller.
    95  	clone, err := prog.Clone()
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	err = RawAttachProgram(RawAttachProgramOptions{
   101  		Target:  int(cgroup.Fd()),
   102  		Program: clone,
   103  		Flags:   uint32(flags),
   104  		Attach:  attach,
   105  	})
   106  	if err != nil {
   107  		clone.Close()
   108  		return nil, fmt.Errorf("cgroup: %w", err)
   109  	}
   110  
   111  	return &progAttachCgroup{cgroup, clone, attach, flags}, nil
   112  }
   113  
   114  func (cg *progAttachCgroup) Close() error {
   115  	defer cg.cgroup.Close()
   116  	defer cg.current.Close()
   117  
   118  	err := RawDetachProgram(RawDetachProgramOptions{
   119  		Target:  int(cg.cgroup.Fd()),
   120  		Program: cg.current,
   121  		Attach:  cg.attachType,
   122  	})
   123  	if err != nil {
   124  		return fmt.Errorf("close cgroup: %s", err)
   125  	}
   126  	return nil
   127  }
   128  
   129  func (cg *progAttachCgroup) Update(prog *ebpf.Program) error {
   130  	new, err := prog.Clone()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	args := RawAttachProgramOptions{
   136  		Target:  int(cg.cgroup.Fd()),
   137  		Program: prog,
   138  		Attach:  cg.attachType,
   139  		Flags:   uint32(cg.flags),
   140  	}
   141  
   142  	if cg.flags&flagAllowMulti > 0 {
   143  		// Atomically replacing multiple programs requires at least
   144  		// 5.5 (commit 7dd68b3279f17921 "bpf: Support replacing cgroup-bpf
   145  		// program in MULTI mode")
   146  		args.Anchor = ReplaceProgram(cg.current)
   147  	}
   148  
   149  	if err := RawAttachProgram(args); err != nil {
   150  		new.Close()
   151  		return fmt.Errorf("can't update cgroup: %s", err)
   152  	}
   153  
   154  	cg.current.Close()
   155  	cg.current = new
   156  	return nil
   157  }
   158  
   159  func (cg *progAttachCgroup) Pin(string) error {
   160  	return fmt.Errorf("can't pin cgroup: %w", ErrNotSupported)
   161  }
   162  
   163  func (cg *progAttachCgroup) Unpin() error {
   164  	return fmt.Errorf("can't unpin cgroup: %w", ErrNotSupported)
   165  }
   166  
   167  func (cg *progAttachCgroup) Info() (*Info, error) {
   168  	return nil, fmt.Errorf("can't get cgroup info: %w", ErrNotSupported)
   169  }
   170  
   171  type linkCgroup struct {
   172  	RawLink
   173  }
   174  
   175  var _ Link = (*linkCgroup)(nil)
   176  
   177  // newLinkCgroup attaches prog to cgroup using BPF_LINK_CREATE.
   178  func newLinkCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program) (*linkCgroup, error) {
   179  	link, err := AttachRawLink(RawLinkOptions{
   180  		Target:  int(cgroup.Fd()),
   181  		Program: prog,
   182  		Attach:  attach,
   183  	})
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	return &linkCgroup{*link}, err
   189  }