File: cgroup.go

package info (click to toggle)
golang-github-cilium-ebpf 0.17.3%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 4,684 kB
  • sloc: ansic: 1,259; makefile: 127; python: 113; awk: 29; sh: 24
file content (208 lines) | stat: -rw-r--r-- 5,240 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package link

import (
	"errors"
	"fmt"
	"os"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/internal/sys"
)

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.Anchor = ReplaceProgram(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
}

func (cg *linkCgroup) Info() (*Info, error) {
	var info sys.CgroupLinkInfo
	if err := sys.ObjInfo(cg.fd, &info); err != nil {
		return nil, fmt.Errorf("cgroup link info: %s", err)
	}
	extra := &CgroupInfo{
		CgroupId:   info.CgroupId,
		AttachType: info.AttachType,
	}

	return &Info{
		info.Type,
		info.Id,
		ebpf.ProgramID(info.ProgId),
		extra,
	}, nil
}