package link import ( "errors" "fmt" "os" "github.com/cilium/ebpf" ) type cgroupAttachFlags uint32 const ( // Allow programs attached to sub-cgroups to override the verdict of this // program. flagAllowOverride cgroupAttachFlags = 1 << iota // Allow attaching multiple programs to the cgroup. Only works if the cgroup // has zero or more programs attached using the Multi flag. Implies override. flagAllowMulti // Set automatically by progAttachCgroup.Update(). Used for updating a // specific given program attached in multi-mode. flagReplace ) type CgroupOptions struct { // Path to a cgroupv2 folder. Path string // One of the AttachCgroup* constants Attach ebpf.AttachType // Program must be of type CGroup*, and the attach type must match Attach. Program *ebpf.Program } // AttachCgroup links a BPF program to a cgroup. // // If the running kernel doesn't support bpf_link, attempts to emulate its // semantics using the legacy PROG_ATTACH mechanism. If bpf_link is not // available, the returned [Link] will not support pinning to bpffs. // // If you need more control over attachment flags or the attachment mechanism // used, look at [RawAttachProgram] and [AttachRawLink] instead. func AttachCgroup(opts CgroupOptions) (cg Link, err error) { cgroup, err := os.Open(opts.Path) if err != nil { return nil, fmt.Errorf("can't open cgroup: %s", err) } defer func() { if _, ok := cg.(*progAttachCgroup); ok { // Skip closing the cgroup handle if we return a valid progAttachCgroup, // where the handle is retained to implement Update(). return } cgroup.Close() }() cg, err = newLinkCgroup(cgroup, opts.Attach, opts.Program) if err == nil { return cg, nil } if errors.Is(err, ErrNotSupported) { cg, err = newProgAttachCgroup(cgroup, opts.Attach, opts.Program, flagAllowMulti) } if errors.Is(err, ErrNotSupported) { cg, err = newProgAttachCgroup(cgroup, opts.Attach, opts.Program, flagAllowOverride) } if err != nil { return nil, err } return cg, nil } type progAttachCgroup struct { cgroup *os.File current *ebpf.Program attachType ebpf.AttachType flags cgroupAttachFlags } var _ Link = (*progAttachCgroup)(nil) func (cg *progAttachCgroup) isLink() {} // newProgAttachCgroup attaches prog to cgroup using BPF_PROG_ATTACH. // cgroup and prog are retained by [progAttachCgroup]. func newProgAttachCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program, flags cgroupAttachFlags) (*progAttachCgroup, error) { if flags&flagAllowMulti > 0 { if err := haveProgAttachReplace(); err != nil { return nil, fmt.Errorf("can't support multiple programs: %w", err) } } // Use a program handle that cannot be closed by the caller. clone, err := prog.Clone() if err != nil { return nil, err } err = RawAttachProgram(RawAttachProgramOptions{ Target: int(cgroup.Fd()), Program: clone, Flags: uint32(flags), Attach: attach, }) if err != nil { clone.Close() return nil, fmt.Errorf("cgroup: %w", err) } return &progAttachCgroup{cgroup, clone, attach, flags}, nil } func (cg *progAttachCgroup) Close() error { defer cg.cgroup.Close() defer cg.current.Close() err := RawDetachProgram(RawDetachProgramOptions{ Target: int(cg.cgroup.Fd()), Program: cg.current, Attach: cg.attachType, }) if err != nil { return fmt.Errorf("close cgroup: %s", err) } return nil } func (cg *progAttachCgroup) Update(prog *ebpf.Program) error { new, err := prog.Clone() if err != nil { return err } args := RawAttachProgramOptions{ Target: int(cg.cgroup.Fd()), Program: prog, Attach: cg.attachType, Flags: uint32(cg.flags), } if cg.flags&flagAllowMulti > 0 { // Atomically replacing multiple programs requires at least // 5.5 (commit 7dd68b3279f17921 "bpf: Support replacing cgroup-bpf // program in MULTI mode") args.Flags |= uint32(flagReplace) args.Replace = cg.current } if err := RawAttachProgram(args); err != nil { new.Close() return fmt.Errorf("can't update cgroup: %s", err) } cg.current.Close() cg.current = new return nil } func (cg *progAttachCgroup) Pin(string) error { return fmt.Errorf("can't pin cgroup: %w", ErrNotSupported) } func (cg *progAttachCgroup) Unpin() error { return fmt.Errorf("can't unpin cgroup: %w", ErrNotSupported) } func (cg *progAttachCgroup) Info() (*Info, error) { return nil, fmt.Errorf("can't get cgroup info: %w", ErrNotSupported) } type linkCgroup struct { RawLink } var _ Link = (*linkCgroup)(nil) // newLinkCgroup attaches prog to cgroup using BPF_LINK_CREATE. func newLinkCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program) (*linkCgroup, error) { link, err := AttachRawLink(RawLinkOptions{ Target: int(cgroup.Fd()), Program: prog, Attach: attach, }) if err != nil { return nil, err } return &linkCgroup{*link}, err }