New upstream version 0.0~git20200810.c7f1234
Shengjing Zhu
3 years ago
29 | 29 | - env_var: GO_VERSION |
30 | 30 | values: [ "1.13", "1.14" ] |
31 | 31 | - env_var: KERNEL_VERSION |
32 | values: ["5.4.5", "4.19.81", "4.9.198"] | |
32 | values: ["5.4", "4.19", "4.9"] | |
33 | 33 | commands: |
34 | 34 | - sem-version go $GO_VERSION |
35 | - timeout -s KILL 90s ./run-tests.sh $KERNEL_VERSION | |
35 | - timeout -s KILL 600s ./run-tests.sh $KERNEL_VERSION |
2 | 2 | import ( |
3 | 3 | "bufio" |
4 | 4 | "bytes" |
5 | "errors" | |
5 | 6 | "fmt" |
6 | 7 | "io" |
7 | 8 | "os" |
8 | 9 | "syscall" |
9 | 10 | |
10 | 11 | "github.com/cilium/ebpf/internal" |
11 | ||
12 | "golang.org/x/xerrors" | |
13 | 12 | ) |
14 | 13 | |
15 | 14 | // MapABI are the attributes of a Map which are available across all supported kernels. |
34 | 33 | func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) { |
35 | 34 | info, err := bpfGetMapInfoByFD(fd) |
36 | 35 | if err != nil { |
37 | if xerrors.Is(err, syscall.EINVAL) { | |
36 | if errors.Is(err, syscall.EINVAL) { | |
38 | 37 | abi, err := newMapABIFromProc(fd) |
39 | 38 | return "", abi, err |
40 | 39 | } |
97 | 96 | func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) { |
98 | 97 | info, err := bpfGetProgInfoByFD(fd) |
99 | 98 | if err != nil { |
100 | if xerrors.Is(err, syscall.EINVAL) { | |
99 | if errors.Is(err, syscall.EINVAL) { | |
101 | 100 | return newProgramABIFromProc(fd) |
102 | 101 | } |
103 | 102 | |
126 | 125 | "prog_type": &abi.Type, |
127 | 126 | "prog_tag": &name, |
128 | 127 | }) |
129 | if xerrors.Is(err, errMissingFields) { | |
128 | if errors.Is(err, errMissingFields) { | |
130 | 129 | return "", nil, &internal.UnsupportedFeatureError{ |
131 | 130 | Name: "reading ABI from /proc/self/fdinfo", |
132 | 131 | MinimumVersion: internal.Version{4, 11, 0}, |
152 | 151 | defer fh.Close() |
153 | 152 | |
154 | 153 | if err := scanFdInfoReader(fh, fields); err != nil { |
155 | return xerrors.Errorf("%s: %w", fh.Name(), err) | |
154 | return fmt.Errorf("%s: %w", fh.Name(), err) | |
156 | 155 | } |
157 | 156 | return nil |
158 | 157 | } |
159 | 158 | |
160 | var errMissingFields = xerrors.New("missing fields") | |
159 | var errMissingFields = errors.New("missing fields") | |
161 | 160 | |
162 | 161 | func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { |
163 | 162 | var ( |
178 | 177 | } |
179 | 178 | |
180 | 179 | if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 { |
181 | return xerrors.Errorf("can't parse field %s: %v", name, err) | |
180 | return fmt.Errorf("can't parse field %s: %v", name, err) | |
182 | 181 | } |
183 | 182 | |
184 | 183 | scanned++ |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "encoding/binary" |
4 | "errors" | |
4 | 5 | "fmt" |
5 | 6 | "io" |
6 | 7 | "math" |
7 | 8 | "strings" |
8 | ||
9 | "golang.org/x/xerrors" | |
10 | 9 | ) |
11 | 10 | |
12 | 11 | // InstructionSize is the size of a BPF instruction in bytes |
38 | 37 | } |
39 | 38 | |
40 | 39 | ins.OpCode = bi.OpCode |
41 | ins.Dst = bi.Registers.Dst() | |
42 | ins.Src = bi.Registers.Src() | |
43 | 40 | ins.Offset = bi.Offset |
44 | 41 | ins.Constant = int64(bi.Constant) |
42 | ins.Dst, ins.Src, err = bi.Registers.Unmarshal(bo) | |
43 | if err != nil { | |
44 | return 0, fmt.Errorf("can't unmarshal registers: %s", err) | |
45 | } | |
45 | 46 | |
46 | 47 | if !bi.OpCode.isDWordLoad() { |
47 | 48 | return InstructionSize, nil |
50 | 51 | var bi2 bpfInstruction |
51 | 52 | if err := binary.Read(r, bo, &bi2); err != nil { |
52 | 53 | // No Wrap, to avoid io.EOF clash |
53 | return 0, xerrors.New("64bit immediate is missing second half") | |
54 | return 0, errors.New("64bit immediate is missing second half") | |
54 | 55 | } |
55 | 56 | if bi2.OpCode != 0 || bi2.Offset != 0 || bi2.Registers != 0 { |
56 | return 0, xerrors.New("64bit immediate has non-zero fields") | |
57 | return 0, errors.New("64bit immediate has non-zero fields") | |
57 | 58 | } |
58 | 59 | ins.Constant = int64(uint64(uint32(bi2.Constant))<<32 | uint64(uint32(bi.Constant))) |
59 | 60 | |
63 | 64 | // Marshal encodes a BPF instruction. |
64 | 65 | func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) { |
65 | 66 | if ins.OpCode == InvalidOpCode { |
66 | return 0, xerrors.New("invalid opcode") | |
67 | return 0, errors.New("invalid opcode") | |
67 | 68 | } |
68 | 69 | |
69 | 70 | isDWordLoad := ins.OpCode.isDWordLoad() |
74 | 75 | cons = int32(uint32(ins.Constant)) |
75 | 76 | } |
76 | 77 | |
78 | regs, err := newBPFRegisters(ins.Dst, ins.Src, bo) | |
79 | if err != nil { | |
80 | return 0, fmt.Errorf("can't marshal registers: %s", err) | |
81 | } | |
82 | ||
77 | 83 | bpfi := bpfInstruction{ |
78 | 84 | ins.OpCode, |
79 | newBPFRegisters(ins.Dst, ins.Src), | |
85 | regs, | |
80 | 86 | ins.Offset, |
81 | 87 | cons, |
82 | 88 | } |
105 | 111 | // Returns an error if the instruction doesn't load a map. |
106 | 112 | func (ins *Instruction) RewriteMapPtr(fd int) error { |
107 | 113 | if !ins.OpCode.isDWordLoad() { |
108 | return xerrors.Errorf("%s is not a 64 bit load", ins.OpCode) | |
114 | return fmt.Errorf("%s is not a 64 bit load", ins.OpCode) | |
109 | 115 | } |
110 | 116 | |
111 | 117 | if ins.Src != PseudoMapFD && ins.Src != PseudoMapValue { |
112 | return xerrors.New("not a load from a map") | |
118 | return errors.New("not a load from a map") | |
113 | 119 | } |
114 | 120 | |
115 | 121 | // Preserve the offset value for direct map loads. |
128 | 134 | // Returns an error if the instruction is not a direct load. |
129 | 135 | func (ins *Instruction) RewriteMapOffset(offset uint32) error { |
130 | 136 | if !ins.OpCode.isDWordLoad() { |
131 | return xerrors.Errorf("%s is not a 64 bit load", ins.OpCode) | |
137 | return fmt.Errorf("%s is not a 64 bit load", ins.OpCode) | |
132 | 138 | } |
133 | 139 | |
134 | 140 | if ins.Src != PseudoMapValue { |
135 | return xerrors.New("not a direct load from a map") | |
141 | return errors.New("not a direct load from a map") | |
136 | 142 | } |
137 | 143 | |
138 | 144 | fd := uint64(ins.Constant) & math.MaxUint32 |
243 | 249 | // Returns an error if the symbol isn't used, see IsUnreferencedSymbol. |
244 | 250 | func (insns Instructions) RewriteMapPtr(symbol string, fd int) error { |
245 | 251 | if symbol == "" { |
246 | return xerrors.New("empty symbol") | |
252 | return errors.New("empty symbol") | |
247 | 253 | } |
248 | 254 | |
249 | 255 | found := false |
278 | 284 | } |
279 | 285 | |
280 | 286 | if _, ok := offsets[ins.Symbol]; ok { |
281 | return nil, xerrors.Errorf("duplicate symbol %s", ins.Symbol) | |
287 | return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol) | |
282 | 288 | } |
283 | 289 | |
284 | 290 | offsets[ins.Symbol] = i |
316 | 322 | } |
317 | 323 | |
318 | 324 | if _, ok := symbols[ins.Symbol]; ok { |
319 | return nil, xerrors.Errorf("duplicate symbol %s", ins.Symbol) | |
325 | return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol) | |
320 | 326 | } |
321 | 327 | |
322 | 328 | symbols[ins.Symbol] = currentPos |
397 | 403 | // Rewrite bpf to bpf call |
398 | 404 | offset, ok := absoluteOffsets[ins.Reference] |
399 | 405 | if !ok { |
400 | return xerrors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) | |
406 | return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) | |
401 | 407 | } |
402 | 408 | |
403 | 409 | ins.Constant = int64(offset - num - 1) |
406 | 412 | // Rewrite jump to label |
407 | 413 | offset, ok := absoluteOffsets[ins.Reference] |
408 | 414 | if !ok { |
409 | return xerrors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) | |
415 | return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) | |
410 | 416 | } |
411 | 417 | |
412 | 418 | ins.Offset = int16(offset - num - 1) |
414 | 420 | |
415 | 421 | n, err := ins.Marshal(w, bo) |
416 | 422 | if err != nil { |
417 | return xerrors.Errorf("instruction %d: %w", i, err) | |
423 | return fmt.Errorf("instruction %d: %w", i, err) | |
418 | 424 | } |
419 | 425 | |
420 | 426 | num += int(n / InstructionSize) |
431 | 437 | |
432 | 438 | type bpfRegisters uint8 |
433 | 439 | |
434 | func newBPFRegisters(dst, src Register) bpfRegisters { | |
435 | return bpfRegisters((src << 4) | (dst & 0xF)) | |
436 | } | |
437 | ||
438 | func (r bpfRegisters) Dst() Register { | |
439 | return Register(r & 0xF) | |
440 | } | |
441 | ||
442 | func (r bpfRegisters) Src() Register { | |
443 | return Register(r >> 4) | |
440 | func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, error) { | |
441 | switch bo { | |
442 | case binary.LittleEndian: | |
443 | return bpfRegisters((src << 4) | (dst & 0xF)), nil | |
444 | case binary.BigEndian: | |
445 | return bpfRegisters((dst << 4) | (src & 0xF)), nil | |
446 | default: | |
447 | return 0, fmt.Errorf("unrecognized ByteOrder %T", bo) | |
448 | } | |
449 | } | |
450 | ||
451 | func (r bpfRegisters) Unmarshal(bo binary.ByteOrder) (dst, src Register, err error) { | |
452 | switch bo { | |
453 | case binary.LittleEndian: | |
454 | return Register(r & 0xF), Register(r >> 4), nil | |
455 | case binary.BigEndian: | |
456 | return Register(r >> 4), Register(r & 0xf), nil | |
457 | default: | |
458 | return 0, 0, fmt.Errorf("unrecognized ByteOrder %T", bo) | |
459 | } | |
444 | 460 | } |
445 | 461 | |
446 | 462 | type unreferencedSymbolError struct { |
178 | 178 | // 1: LdImmDW dst: r0 imm: 42 |
179 | 179 | // 3: Exit |
180 | 180 | } |
181 | ||
182 | func TestReadSrcDst(t *testing.T) { | |
183 | testSrcDstProg := []byte{ | |
184 | // on little-endian: r0 = r1 | |
185 | // on big-endian: be: r1 = r0 | |
186 | 0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
187 | } | |
188 | ||
189 | testcases := []struct { | |
190 | bo binary.ByteOrder | |
191 | dst, src Register | |
192 | }{ | |
193 | {binary.BigEndian, R1, R0}, | |
194 | {binary.LittleEndian, R0, R1}, | |
195 | } | |
196 | ||
197 | for _, tc := range testcases { | |
198 | t.Run(tc.bo.String(), func(t *testing.T) { | |
199 | var ins Instruction | |
200 | _, err := ins.Unmarshal(bytes.NewReader(testSrcDstProg), tc.bo) | |
201 | if err != nil { | |
202 | t.Fatal(err) | |
203 | } | |
204 | if ins.Dst != tc.dst { | |
205 | t.Errorf("Expected destination to be %v, got %v", tc.dst, ins.Dst) | |
206 | } | |
207 | if ins.Src != tc.src { | |
208 | t.Errorf("Expected source to be %v, got %v", tc.src, ins.Src) | |
209 | } | |
210 | }) | |
211 | } | |
212 | } |
224 | 224 | } |
225 | 225 | |
226 | 226 | default: |
227 | fmt.Fprintf(&f, "%#x", op) | |
227 | fmt.Fprintf(&f, "OpCode(%#x)", uint8(op)) | |
228 | 228 | } |
229 | 229 | |
230 | 230 | return f.String() |
0 | 0 | package ebpf |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
4 | "fmt" | |
3 | 5 | "math" |
6 | "reflect" | |
7 | "strings" | |
4 | 8 | |
5 | 9 | "github.com/cilium/ebpf/asm" |
6 | 10 | "github.com/cilium/ebpf/internal" |
7 | 11 | "github.com/cilium/ebpf/internal/btf" |
8 | "golang.org/x/xerrors" | |
9 | 12 | ) |
10 | 13 | |
11 | 14 | // CollectionOptions control loading a collection into the kernel. |
63 | 66 | // Not all programs need to use the map |
64 | 67 | |
65 | 68 | default: |
66 | return xerrors.Errorf("program %s: %w", progName, err) | |
69 | return fmt.Errorf("program %s: %w", progName, err) | |
67 | 70 | } |
68 | 71 | } |
69 | 72 | |
70 | 73 | if !seen { |
71 | return xerrors.Errorf("map %s not referenced by any programs", symbol) | |
74 | return fmt.Errorf("map %s not referenced by any programs", symbol) | |
72 | 75 | } |
73 | 76 | |
74 | 77 | // Prevent NewCollection from creating rewritten maps |
95 | 98 | func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error { |
96 | 99 | rodata := cs.Maps[".rodata"] |
97 | 100 | if rodata == nil { |
98 | return xerrors.New("missing .rodata section") | |
101 | return errors.New("missing .rodata section") | |
99 | 102 | } |
100 | 103 | |
101 | 104 | if rodata.BTF == nil { |
102 | return xerrors.New(".rodata section has no BTF") | |
105 | return errors.New(".rodata section has no BTF") | |
103 | 106 | } |
104 | 107 | |
105 | 108 | if n := len(rodata.Contents); n != 1 { |
106 | return xerrors.Errorf("expected one key in .rodata, found %d", n) | |
109 | return fmt.Errorf("expected one key in .rodata, found %d", n) | |
107 | 110 | } |
108 | 111 | |
109 | 112 | kv := rodata.Contents[0] |
110 | 113 | value, ok := kv.Value.([]byte) |
111 | 114 | if !ok { |
112 | return xerrors.Errorf("first value in .rodata is %T not []byte", kv.Value) | |
115 | return fmt.Errorf("first value in .rodata is %T not []byte", kv.Value) | |
113 | 116 | } |
114 | 117 | |
115 | 118 | buf := make([]byte, len(value)) |
122 | 125 | |
123 | 126 | rodata.Contents[0] = MapKV{kv.Key, buf} |
124 | 127 | return nil |
128 | } | |
129 | ||
130 | // Assign the contents of a collection spec to a struct. | |
131 | // | |
132 | // This function is a short-cut to manually checking the presence | |
133 | // of maps and programs in a collection spec. | |
134 | // | |
135 | // The argument to must be a pointer to a struct. A field of the | |
136 | // struct is updated with values from Programs or Maps if it | |
137 | // has an `ebpf` tag and its type is *ProgramSpec or *MapSpec. | |
138 | // The tag gives the name of the program or map as found in | |
139 | // the CollectionSpec. | |
140 | // | |
141 | // struct { | |
142 | // Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"` | |
143 | // Bar *ebpf.MapSpec `ebpf:"bar_map"` | |
144 | // Ignored int | |
145 | // } | |
146 | // | |
147 | // Returns an error if any of the fields can't be found, or | |
148 | // if the same map or program is assigned multiple times. | |
149 | func (cs *CollectionSpec) Assign(to interface{}) error { | |
150 | valueOf := func(typ reflect.Type, name string) (reflect.Value, error) { | |
151 | switch typ { | |
152 | case reflect.TypeOf((*ProgramSpec)(nil)): | |
153 | p := cs.Programs[name] | |
154 | if p == nil { | |
155 | return reflect.Value{}, fmt.Errorf("missing program %q", name) | |
156 | } | |
157 | return reflect.ValueOf(p), nil | |
158 | case reflect.TypeOf((*MapSpec)(nil)): | |
159 | m := cs.Maps[name] | |
160 | if m == nil { | |
161 | return reflect.Value{}, fmt.Errorf("missing map %q", name) | |
162 | } | |
163 | return reflect.ValueOf(m), nil | |
164 | default: | |
165 | return reflect.Value{}, fmt.Errorf("unsupported type %s", typ) | |
166 | } | |
167 | } | |
168 | ||
169 | return assignValues(to, valueOf) | |
170 | } | |
171 | ||
172 | // LoadAndAssign creates a collection from a spec, and assigns it to a struct. | |
173 | // | |
174 | // See Collection.Assign for details. | |
175 | func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error { | |
176 | if opts == nil { | |
177 | opts = &CollectionOptions{} | |
178 | } | |
179 | ||
180 | coll, err := NewCollectionWithOptions(cs, *opts) | |
181 | if err != nil { | |
182 | return err | |
183 | } | |
184 | defer coll.Close() | |
185 | ||
186 | return coll.Assign(to) | |
125 | 187 | } |
126 | 188 | |
127 | 189 | // Collection is a collection of Programs and Maps associated |
184 | 246 | var handle *btf.Handle |
185 | 247 | if mapSpec.BTF != nil { |
186 | 248 | handle, err = loadBTF(btf.MapSpec(mapSpec.BTF)) |
187 | if err != nil && !xerrors.Is(err, btf.ErrNotSupported) { | |
249 | if err != nil && !errors.Is(err, btf.ErrNotSupported) { | |
188 | 250 | return nil, err |
189 | 251 | } |
190 | 252 | } |
191 | 253 | |
192 | 254 | m, err := newMapWithBTF(mapSpec, handle) |
193 | 255 | if err != nil { |
194 | return nil, xerrors.Errorf("map %s: %w", mapName, err) | |
256 | return nil, fmt.Errorf("map %s: %w", mapName, err) | |
195 | 257 | } |
196 | 258 | maps[mapName] = m |
197 | 259 | } |
215 | 277 | |
216 | 278 | m := maps[ins.Reference] |
217 | 279 | if m == nil { |
218 | return nil, xerrors.Errorf("program %s: missing map %s", progName, ins.Reference) | |
280 | return nil, fmt.Errorf("program %s: missing map %s", progName, ins.Reference) | |
219 | 281 | } |
220 | 282 | |
221 | 283 | fd := m.FD() |
222 | 284 | if fd < 0 { |
223 | return nil, xerrors.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd) | |
285 | return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd) | |
224 | 286 | } |
225 | 287 | if err := ins.RewriteMapPtr(m.FD()); err != nil { |
226 | return nil, xerrors.Errorf("progam %s: map %s: %w", progName, ins.Reference, err) | |
288 | return nil, fmt.Errorf("progam %s: map %s: %w", progName, ins.Reference, err) | |
227 | 289 | } |
228 | 290 | } |
229 | 291 | |
230 | 292 | var handle *btf.Handle |
231 | 293 | if progSpec.BTF != nil { |
232 | 294 | handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF)) |
233 | if err != nil && !xerrors.Is(err, btf.ErrNotSupported) { | |
295 | if err != nil && !errors.Is(err, btf.ErrNotSupported) { | |
234 | 296 | return nil, err |
235 | 297 | } |
236 | 298 | } |
237 | 299 | |
238 | 300 | prog, err := newProgramWithBTF(progSpec, handle, opts.Programs) |
239 | 301 | if err != nil { |
240 | return nil, xerrors.Errorf("program %s: %w", progName, err) | |
302 | return nil, fmt.Errorf("program %s: %w", progName, err) | |
241 | 303 | } |
242 | 304 | progs[progName] = prog |
243 | 305 | } |
290 | 352 | delete(coll.Programs, name) |
291 | 353 | return p |
292 | 354 | } |
355 | ||
356 | // Assign the contents of a collection to a struct. | |
357 | // | |
358 | // `to` must be a pointer to a struct like the following: | |
359 | // | |
360 | // struct { | |
361 | // Foo *ebpf.Program `ebpf:"xdp_foo"` | |
362 | // Bar *ebpf.Map `ebpf:"bar_map"` | |
363 | // Ignored int | |
364 | // } | |
365 | // | |
366 | // See CollectionSpec.Assign for the semantics of this function. | |
367 | // | |
368 | // DetachMap and DetachProgram is invoked for all assigned elements | |
369 | // if the function is successful. | |
370 | func (coll *Collection) Assign(to interface{}) error { | |
371 | assignedMaps := make(map[string]struct{}) | |
372 | assignedPrograms := make(map[string]struct{}) | |
373 | valueOf := func(typ reflect.Type, name string) (reflect.Value, error) { | |
374 | switch typ { | |
375 | case reflect.TypeOf((*Program)(nil)): | |
376 | p := coll.Programs[name] | |
377 | if p == nil { | |
378 | return reflect.Value{}, fmt.Errorf("missing program %q", name) | |
379 | } | |
380 | assignedPrograms[name] = struct{}{} | |
381 | return reflect.ValueOf(p), nil | |
382 | case reflect.TypeOf((*Map)(nil)): | |
383 | m := coll.Maps[name] | |
384 | if m == nil { | |
385 | return reflect.Value{}, fmt.Errorf("missing map %q", name) | |
386 | } | |
387 | assignedMaps[name] = struct{}{} | |
388 | return reflect.ValueOf(m), nil | |
389 | default: | |
390 | return reflect.Value{}, fmt.Errorf("unsupported type %s", typ) | |
391 | } | |
392 | } | |
393 | ||
394 | if err := assignValues(to, valueOf); err != nil { | |
395 | return err | |
396 | } | |
397 | ||
398 | for name := range assignedPrograms { | |
399 | coll.DetachProgram(name) | |
400 | } | |
401 | ||
402 | for name := range assignedMaps { | |
403 | coll.DetachMap(name) | |
404 | } | |
405 | ||
406 | return nil | |
407 | } | |
408 | ||
409 | func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Value, error)) error { | |
410 | v := reflect.ValueOf(to) | |
411 | if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { | |
412 | return fmt.Errorf("%T is not a pointer to a struct", to) | |
413 | } | |
414 | ||
415 | type elem struct { | |
416 | typ reflect.Type | |
417 | name string | |
418 | } | |
419 | ||
420 | var ( | |
421 | s = v.Elem() | |
422 | sT = s.Type() | |
423 | assignedTo = make(map[elem]string) | |
424 | ) | |
425 | for i := 0; i < sT.NumField(); i++ { | |
426 | field := sT.Field(i) | |
427 | ||
428 | name := field.Tag.Get("ebpf") | |
429 | if name == "" { | |
430 | continue | |
431 | } | |
432 | if strings.Contains(name, ",") { | |
433 | return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name) | |
434 | } | |
435 | ||
436 | e := elem{field.Type, name} | |
437 | if assignedField := assignedTo[e]; assignedField != "" { | |
438 | return fmt.Errorf("field %s: %q was already assigned to %s", field.Name, name, assignedField) | |
439 | } | |
440 | ||
441 | value, err := valueOf(field.Type, name) | |
442 | if err != nil { | |
443 | return fmt.Errorf("field %s: %w", field.Name, err) | |
444 | } | |
445 | ||
446 | fieldValue := s.Field(i) | |
447 | if !fieldValue.CanSet() { | |
448 | return fmt.Errorf("can't set value of field %s", field.Name) | |
449 | } | |
450 | ||
451 | fieldValue.Set(value) | |
452 | assignedTo[e] = field.Name | |
453 | } | |
454 | ||
455 | return nil | |
456 | } |
0 | 0 | package ebpf |
1 | 1 | |
2 | 2 | import ( |
3 | "fmt" | |
3 | 4 | "testing" |
4 | 5 | |
5 | 6 | "github.com/cilium/ebpf/asm" |
152 | 153 | t.Fatal("new / override map not used") |
153 | 154 | } |
154 | 155 | } |
156 | ||
157 | func TestCollectionAssign(t *testing.T) { | |
158 | var specs struct { | |
159 | Program *ProgramSpec `ebpf:"prog1"` | |
160 | Map *MapSpec `ebpf:"map1"` | |
161 | } | |
162 | ||
163 | mapSpec := &MapSpec{ | |
164 | Type: Array, | |
165 | KeySize: 4, | |
166 | ValueSize: 4, | |
167 | MaxEntries: 1, | |
168 | } | |
169 | progSpec := &ProgramSpec{ | |
170 | Type: SocketFilter, | |
171 | Instructions: asm.Instructions{ | |
172 | asm.LoadImm(asm.R0, 0, asm.DWord), | |
173 | asm.Return(), | |
174 | }, | |
175 | License: "MIT", | |
176 | } | |
177 | ||
178 | cs := &CollectionSpec{ | |
179 | Maps: map[string]*MapSpec{ | |
180 | "map1": mapSpec, | |
181 | }, | |
182 | Programs: map[string]*ProgramSpec{ | |
183 | "prog1": progSpec, | |
184 | }, | |
185 | } | |
186 | ||
187 | if err := cs.Assign(&specs); err != nil { | |
188 | t.Fatal("Can't assign spec:", err) | |
189 | } | |
190 | ||
191 | if specs.Program != progSpec { | |
192 | t.Fatalf("Expected Program to be %p, got %p", progSpec, specs.Program) | |
193 | } | |
194 | ||
195 | if specs.Map != mapSpec { | |
196 | t.Fatalf("Expected Map to be %p, got %p", mapSpec, specs.Map) | |
197 | } | |
198 | ||
199 | if err := cs.Assign(new(int)); err == nil { | |
200 | t.Fatal("Assign allows to besides *struct") | |
201 | } | |
202 | ||
203 | if err := cs.Assign(new(struct{ Foo int })); err != nil { | |
204 | t.Fatal("Assign doesn't ignore untagged fields") | |
205 | } | |
206 | ||
207 | unexported := new(struct { | |
208 | foo *MapSpec `ebpf:"map1"` | |
209 | }) | |
210 | ||
211 | if err := cs.Assign(unexported); err == nil { | |
212 | t.Error("Assign should return an error on unexported fields") | |
213 | } | |
214 | ||
215 | coll, err := NewCollection(cs) | |
216 | if err != nil { | |
217 | t.Fatal(err) | |
218 | } | |
219 | defer coll.Close() | |
220 | ||
221 | var objs struct { | |
222 | Program *Program `ebpf:"prog1"` | |
223 | Map *Map `ebpf:"map1"` | |
224 | } | |
225 | ||
226 | if err := coll.Assign(&objs); err != nil { | |
227 | t.Fatal("Can't Assign objects:", err) | |
228 | } | |
229 | objs.Program.Close() | |
230 | objs.Map.Close() | |
231 | ||
232 | if coll.Programs["prog1"] != nil { | |
233 | t.Fatal("Assign doesn't detach Program") | |
234 | } | |
235 | ||
236 | if coll.Maps["map1"] != nil { | |
237 | t.Fatal("Assign doesn't detach Map") | |
238 | } | |
239 | } | |
240 | ||
241 | func ExampleCollectionSpec_Assign() { | |
242 | spec := &CollectionSpec{ | |
243 | Maps: map[string]*MapSpec{ | |
244 | "map1": { | |
245 | Type: Array, | |
246 | KeySize: 4, | |
247 | ValueSize: 4, | |
248 | MaxEntries: 1, | |
249 | }, | |
250 | }, | |
251 | Programs: map[string]*ProgramSpec{ | |
252 | "prog1": { | |
253 | Type: SocketFilter, | |
254 | Instructions: asm.Instructions{ | |
255 | asm.LoadImm(asm.R0, 0, asm.DWord), | |
256 | asm.Return(), | |
257 | }, | |
258 | License: "MIT", | |
259 | }, | |
260 | }, | |
261 | } | |
262 | ||
263 | var specs struct { | |
264 | Program *ProgramSpec `ebpf:"prog1"` | |
265 | Map *MapSpec `ebpf:"map1"` | |
266 | } | |
267 | ||
268 | if err := spec.Assign(&specs); err != nil { | |
269 | panic(err) | |
270 | } | |
271 | ||
272 | fmt.Println(specs.Program.Type) | |
273 | fmt.Println(specs.Map.Type) | |
274 | ||
275 | // Output: SocketFilter | |
276 | // Array | |
277 | } | |
278 | ||
279 | func ExampleCollectionSpec_LoadAndAssign() { | |
280 | spec := &CollectionSpec{ | |
281 | Maps: map[string]*MapSpec{ | |
282 | "map1": { | |
283 | Type: Array, | |
284 | KeySize: 4, | |
285 | ValueSize: 4, | |
286 | MaxEntries: 1, | |
287 | }, | |
288 | }, | |
289 | Programs: map[string]*ProgramSpec{ | |
290 | "prog1": { | |
291 | Type: SocketFilter, | |
292 | Instructions: asm.Instructions{ | |
293 | asm.LoadImm(asm.R0, 0, asm.DWord), | |
294 | asm.Return(), | |
295 | }, | |
296 | License: "MIT", | |
297 | }, | |
298 | }, | |
299 | } | |
300 | ||
301 | var objs struct { | |
302 | Program *Program `ebpf:"prog1"` | |
303 | Map *Map `ebpf:"map1"` | |
304 | } | |
305 | ||
306 | if err := spec.LoadAndAssign(&objs, nil); err != nil { | |
307 | panic(err) | |
308 | } | |
309 | ||
310 | fmt.Println(objs.Program.ABI().Type) | |
311 | fmt.Println(objs.Map.ABI().Type) | |
312 | ||
313 | // Output: SocketFilter | |
314 | // Array | |
315 | } | |
316 | ||
317 | func ExampleCollection_Assign() { | |
318 | coll, err := NewCollection(&CollectionSpec{ | |
319 | Maps: map[string]*MapSpec{ | |
320 | "map1": { | |
321 | Type: Array, | |
322 | KeySize: 4, | |
323 | ValueSize: 4, | |
324 | MaxEntries: 1, | |
325 | }, | |
326 | }, | |
327 | Programs: map[string]*ProgramSpec{ | |
328 | "prog1": { | |
329 | Type: SocketFilter, | |
330 | Instructions: asm.Instructions{ | |
331 | asm.LoadImm(asm.R0, 0, asm.DWord), | |
332 | asm.Return(), | |
333 | }, | |
334 | License: "MIT", | |
335 | }, | |
336 | }, | |
337 | }) | |
338 | if err != nil { | |
339 | panic(err) | |
340 | } | |
341 | ||
342 | var objs struct { | |
343 | Program *Program `ebpf:"prog1"` | |
344 | Map *Map `ebpf:"map1"` | |
345 | } | |
346 | ||
347 | if err := coll.Assign(&objs); err != nil { | |
348 | panic(err) | |
349 | } | |
350 | ||
351 | fmt.Println(objs.Program.ABI().Type) | |
352 | fmt.Println(objs.Map.ABI().Type) | |
353 | ||
354 | // Output: SocketFilter | |
355 | // Array | |
356 | } |
11 | 11 | // eBPF code should be compiled ahead of time using clang, and shipped with |
12 | 12 | // your application as any other resource. |
13 | 13 | // |
14 | // This package doesn't include code required to attach eBPF to Linux | |
15 | // subsystems, since this varies per subsystem. | |
14 | // Use the link subpackage to attach a loaded program to a hook in the kernel. | |
16 | 15 | package ebpf |
3 | 3 | "bytes" |
4 | 4 | "debug/elf" |
5 | 5 | "encoding/binary" |
6 | "errors" | |
7 | "fmt" | |
6 | 8 | "io" |
7 | 9 | "math" |
8 | 10 | "os" |
12 | 14 | "github.com/cilium/ebpf/internal" |
13 | 15 | "github.com/cilium/ebpf/internal/btf" |
14 | 16 | "github.com/cilium/ebpf/internal/unix" |
15 | ||
16 | "golang.org/x/xerrors" | |
17 | 17 | ) |
18 | 18 | |
19 | 19 | type elfCode struct { |
34 | 34 | |
35 | 35 | spec, err := LoadCollectionSpecFromReader(f) |
36 | 36 | if err != nil { |
37 | return nil, xerrors.Errorf("file %s: %w", file, err) | |
37 | return nil, fmt.Errorf("file %s: %w", file, err) | |
38 | 38 | } |
39 | 39 | return spec, nil |
40 | 40 | } |
49 | 49 | |
50 | 50 | symbols, err := f.Symbols() |
51 | 51 | if err != nil { |
52 | return nil, xerrors.Errorf("load symbols: %v", err) | |
52 | return nil, fmt.Errorf("load symbols: %v", err) | |
53 | 53 | } |
54 | 54 | |
55 | 55 | ec := &elfCode{f, symbols, symbolsPerSection(symbols), "", 0} |
78 | 78 | dataSections[elf.SectionIndex(i)] = sec |
79 | 79 | case sec.Type == elf.SHT_REL: |
80 | 80 | if int(sec.Info) >= len(ec.Sections) { |
81 | return nil, xerrors.Errorf("found relocation section %v for missing section %v", i, sec.Info) | |
81 | return nil, fmt.Errorf("found relocation section %v for missing section %v", i, sec.Info) | |
82 | 82 | } |
83 | 83 | |
84 | 84 | // Store relocations under the section index of the target |
85 | 85 | idx := elf.SectionIndex(sec.Info) |
86 | 86 | if relSections[idx] != nil { |
87 | return nil, xerrors.Errorf("section %d has multiple relocation sections", sec.Info) | |
87 | return nil, fmt.Errorf("section %d has multiple relocation sections", sec.Info) | |
88 | 88 | } |
89 | 89 | relSections[idx] = sec |
90 | 90 | case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: |
94 | 94 | |
95 | 95 | ec.license, err = loadLicense(licenseSection) |
96 | 96 | if err != nil { |
97 | return nil, xerrors.Errorf("load license: %w", err) | |
97 | return nil, fmt.Errorf("load license: %w", err) | |
98 | 98 | } |
99 | 99 | |
100 | 100 | ec.version, err = loadVersion(versionSection, ec.ByteOrder) |
101 | 101 | if err != nil { |
102 | return nil, xerrors.Errorf("load version: %w", err) | |
102 | return nil, fmt.Errorf("load version: %w", err) | |
103 | 103 | } |
104 | 104 | |
105 | 105 | btfSpec, err := btf.LoadSpecFromReader(rd) |
106 | 106 | if err != nil { |
107 | return nil, xerrors.Errorf("load BTF: %w", err) | |
107 | return nil, fmt.Errorf("load BTF: %w", err) | |
108 | } | |
109 | ||
110 | relocations, referencedSections, err := ec.loadRelocations(relSections) | |
111 | if err != nil { | |
112 | return nil, fmt.Errorf("load relocations: %w", err) | |
108 | 113 | } |
109 | 114 | |
110 | 115 | maps := make(map[string]*MapSpec) |
111 | 116 | if err := ec.loadMaps(maps, mapSections); err != nil { |
112 | return nil, xerrors.Errorf("load maps: %w", err) | |
117 | return nil, fmt.Errorf("load maps: %w", err) | |
113 | 118 | } |
114 | 119 | |
115 | 120 | if len(btfMaps) > 0 { |
116 | 121 | if err := ec.loadBTFMaps(maps, btfMaps, btfSpec); err != nil { |
117 | return nil, xerrors.Errorf("load BTF maps: %w", err) | |
122 | return nil, fmt.Errorf("load BTF maps: %w", err) | |
118 | 123 | } |
119 | 124 | } |
120 | 125 | |
121 | 126 | if len(dataSections) > 0 { |
127 | for idx := range dataSections { | |
128 | if !referencedSections[idx] { | |
129 | // Prune data sections which are not referenced by any | |
130 | // instructions. | |
131 | delete(dataSections, idx) | |
132 | } | |
133 | } | |
134 | ||
122 | 135 | if err := ec.loadDataSections(maps, dataSections, btfSpec); err != nil { |
123 | return nil, xerrors.Errorf("load data sections: %w", err) | |
124 | } | |
125 | } | |
126 | ||
127 | relocations, err := ec.loadRelocations(relSections) | |
128 | if err != nil { | |
129 | return nil, xerrors.Errorf("load relocations: %w", err) | |
136 | return nil, fmt.Errorf("load data sections: %w", err) | |
137 | } | |
130 | 138 | } |
131 | 139 | |
132 | 140 | progs, err := ec.loadPrograms(progSections, relocations, btfSpec) |
133 | 141 | if err != nil { |
134 | return nil, xerrors.Errorf("load programs: %w", err) | |
142 | return nil, fmt.Errorf("load programs: %w", err) | |
135 | 143 | } |
136 | 144 | |
137 | 145 | return &CollectionSpec{maps, progs}, nil |
139 | 147 | |
140 | 148 | func loadLicense(sec *elf.Section) (string, error) { |
141 | 149 | if sec == nil { |
142 | return "", xerrors.New("missing license section") | |
143 | } | |
150 | return "", nil | |
151 | } | |
152 | ||
144 | 153 | data, err := sec.Data() |
145 | 154 | if err != nil { |
146 | return "", xerrors.Errorf("section %s: %v", sec.Name, err) | |
155 | return "", fmt.Errorf("section %s: %v", sec.Name, err) | |
147 | 156 | } |
148 | 157 | return string(bytes.TrimRight(data, "\000")), nil |
149 | 158 | } |
155 | 164 | |
156 | 165 | var version uint32 |
157 | 166 | if err := binary.Read(sec.Open(), bo, &version); err != nil { |
158 | return 0, xerrors.Errorf("section %s: %v", sec.Name, err) | |
167 | return 0, fmt.Errorf("section %s: %v", sec.Name, err) | |
159 | 168 | } |
160 | 169 | return version, nil |
161 | 170 | } |
162 | 171 | |
163 | func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, relocations map[elf.SectionIndex]map[uint64]elf.Symbol, btf *btf.Spec) (map[string]*ProgramSpec, error) { | |
172 | func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, relocations map[elf.SectionIndex]map[uint64]elf.Symbol, btfSpec *btf.Spec) (map[string]*ProgramSpec, error) { | |
164 | 173 | var ( |
165 | 174 | progs []*ProgramSpec |
166 | 175 | libs []*ProgramSpec |
169 | 178 | for idx, sec := range progSections { |
170 | 179 | syms := ec.symbolsPerSection[idx] |
171 | 180 | if len(syms) == 0 { |
172 | return nil, xerrors.Errorf("section %v: missing symbols", sec.Name) | |
181 | return nil, fmt.Errorf("section %v: missing symbols", sec.Name) | |
173 | 182 | } |
174 | 183 | |
175 | 184 | funcSym, ok := syms[0] |
176 | 185 | if !ok { |
177 | return nil, xerrors.Errorf("section %v: no label at start", sec.Name) | |
186 | return nil, fmt.Errorf("section %v: no label at start", sec.Name) | |
178 | 187 | } |
179 | 188 | |
180 | 189 | insns, length, err := ec.loadInstructions(sec, syms, relocations[idx]) |
181 | 190 | if err != nil { |
182 | return nil, xerrors.Errorf("program %s: can't unmarshal instructions: %w", funcSym.Name, err) | |
183 | } | |
184 | ||
185 | progType, attachType := getProgType(sec.Name) | |
191 | return nil, fmt.Errorf("program %s: can't unmarshal instructions: %w", funcSym.Name, err) | |
192 | } | |
193 | ||
194 | progType, attachType, attachTo := getProgType(sec.Name) | |
186 | 195 | |
187 | 196 | spec := &ProgramSpec{ |
188 | 197 | Name: funcSym.Name, |
189 | 198 | Type: progType, |
190 | 199 | AttachType: attachType, |
200 | AttachTo: attachTo, | |
191 | 201 | License: ec.license, |
192 | 202 | KernelVersion: ec.version, |
193 | 203 | Instructions: insns, |
194 | } | |
195 | ||
196 | if btf != nil { | |
197 | spec.BTF, err = btf.Program(sec.Name, length) | |
198 | if err != nil { | |
199 | return nil, xerrors.Errorf("BTF for section %s (program %s): %w", sec.Name, funcSym.Name, err) | |
204 | ByteOrder: ec.ByteOrder, | |
205 | } | |
206 | ||
207 | if btfSpec != nil { | |
208 | spec.BTF, err = btfSpec.Program(sec.Name, length) | |
209 | if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) { | |
210 | return nil, fmt.Errorf("program %s: %w", funcSym.Name, err) | |
200 | 211 | } |
201 | 212 | } |
202 | 213 | |
214 | 225 | for _, prog := range progs { |
215 | 226 | err := link(prog, libs) |
216 | 227 | if err != nil { |
217 | return nil, xerrors.Errorf("program %s: %w", prog.Name, err) | |
228 | return nil, fmt.Errorf("program %s: %w", prog.Name, err) | |
218 | 229 | } |
219 | 230 | res[prog.Name] = prog |
220 | 231 | } |
235 | 246 | return insns, offset, nil |
236 | 247 | } |
237 | 248 | if err != nil { |
238 | return nil, 0, xerrors.Errorf("offset %d: %w", offset, err) | |
249 | return nil, 0, fmt.Errorf("offset %d: %w", offset, err) | |
239 | 250 | } |
240 | 251 | |
241 | 252 | ins.Symbol = symbols[offset].Name |
242 | 253 | |
243 | 254 | if rel, ok := relocations[offset]; ok { |
244 | 255 | if err = ec.relocateInstruction(&ins, rel); err != nil { |
245 | return nil, 0, xerrors.Errorf("offset %d: can't relocate instruction: %w", offset, err) | |
256 | return nil, 0, fmt.Errorf("offset %d: can't relocate instruction: %w", offset, err) | |
246 | 257 | } |
247 | 258 | } |
248 | 259 | |
263 | 274 | // from the section itself. |
264 | 275 | idx := int(rel.Section) |
265 | 276 | if idx > len(ec.Sections) { |
266 | return xerrors.New("out-of-bounds section index") | |
277 | return errors.New("out-of-bounds section index") | |
267 | 278 | } |
268 | 279 | |
269 | 280 | name = ec.Sections[idx].Name |
283 | 294 | // section. Weirdly, the offset of the real symbol in the |
284 | 295 | // section is encoded in the instruction stream. |
285 | 296 | if bind != elf.STB_LOCAL { |
286 | return xerrors.Errorf("direct load: %s: unsupported relocation %s", name, bind) | |
297 | return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind) | |
287 | 298 | } |
288 | 299 | |
289 | 300 | // For some reason, clang encodes the offset of the symbol its |
305 | 316 | |
306 | 317 | case elf.STT_OBJECT: |
307 | 318 | if bind != elf.STB_GLOBAL { |
308 | return xerrors.Errorf("load: %s: unsupported binding: %s", name, bind) | |
319 | return fmt.Errorf("load: %s: unsupported binding: %s", name, bind) | |
309 | 320 | } |
310 | 321 | |
311 | 322 | ins.Src = asm.PseudoMapFD |
312 | 323 | |
313 | 324 | default: |
314 | return xerrors.Errorf("load: %s: unsupported relocation: %s", name, typ) | |
325 | return fmt.Errorf("load: %s: unsupported relocation: %s", name, typ) | |
315 | 326 | } |
316 | 327 | |
317 | 328 | // Mark the instruction as needing an update when creating the |
322 | 333 | |
323 | 334 | case ins.OpCode.JumpOp() == asm.Call: |
324 | 335 | if ins.Src != asm.PseudoCall { |
325 | return xerrors.Errorf("call: %s: incorrect source register", name) | |
336 | return fmt.Errorf("call: %s: incorrect source register", name) | |
326 | 337 | } |
327 | 338 | |
328 | 339 | switch typ { |
329 | 340 | case elf.STT_NOTYPE, elf.STT_FUNC: |
330 | 341 | if bind != elf.STB_GLOBAL { |
331 | return xerrors.Errorf("call: %s: unsupported binding: %s", name, bind) | |
342 | return fmt.Errorf("call: %s: unsupported binding: %s", name, bind) | |
332 | 343 | } |
333 | 344 | |
334 | 345 | case elf.STT_SECTION: |
335 | 346 | if bind != elf.STB_LOCAL { |
336 | return xerrors.Errorf("call: %s: unsupported binding: %s", name, bind) | |
347 | return fmt.Errorf("call: %s: unsupported binding: %s", name, bind) | |
337 | 348 | } |
338 | 349 | |
339 | 350 | // The function we want to call is in the indicated section, |
342 | 353 | // A value of -1 references the first instruction in the section. |
343 | 354 | offset := int64(int32(ins.Constant)+1) * asm.InstructionSize |
344 | 355 | if offset < 0 { |
345 | return xerrors.Errorf("call: %s: invalid offset %d", name, offset) | |
356 | return fmt.Errorf("call: %s: invalid offset %d", name, offset) | |
346 | 357 | } |
347 | 358 | |
348 | 359 | sym, ok := ec.symbolsPerSection[rel.Section][uint64(offset)] |
349 | 360 | if !ok { |
350 | return xerrors.Errorf("call: %s: no symbol at offset %d", name, offset) | |
361 | return fmt.Errorf("call: %s: no symbol at offset %d", name, offset) | |
351 | 362 | } |
352 | 363 | |
353 | 364 | ins.Constant = -1 |
354 | 365 | name = sym.Name |
355 | 366 | |
356 | 367 | default: |
357 | return xerrors.Errorf("call: %s: invalid symbol type %s", name, typ) | |
368 | return fmt.Errorf("call: %s: invalid symbol type %s", name, typ) | |
358 | 369 | } |
359 | 370 | |
360 | 371 | default: |
361 | return xerrors.Errorf("relocation for unsupported instruction: %s", ins.OpCode) | |
372 | return fmt.Errorf("relocation for unsupported instruction: %s", ins.OpCode) | |
362 | 373 | } |
363 | 374 | |
364 | 375 | ins.Reference = name |
369 | 380 | for idx, sec := range mapSections { |
370 | 381 | syms := ec.symbolsPerSection[idx] |
371 | 382 | if len(syms) == 0 { |
372 | return xerrors.Errorf("section %v: no symbols", sec.Name) | |
383 | return fmt.Errorf("section %v: no symbols", sec.Name) | |
373 | 384 | } |
374 | 385 | |
375 | 386 | if sec.Size%uint64(len(syms)) != 0 { |
376 | return xerrors.Errorf("section %v: map descriptors are not of equal size", sec.Name) | |
387 | return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name) | |
377 | 388 | } |
378 | 389 | |
379 | 390 | var ( |
383 | 394 | for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size { |
384 | 395 | mapSym, ok := syms[offset] |
385 | 396 | if !ok { |
386 | return xerrors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset) | |
397 | return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset) | |
387 | 398 | } |
388 | 399 | |
389 | 400 | if maps[mapSym.Name] != nil { |
390 | return xerrors.Errorf("section %v: map %v already exists", sec.Name, mapSym) | |
401 | return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym) | |
391 | 402 | } |
392 | 403 | |
393 | 404 | lr := io.LimitReader(r, int64(size)) |
397 | 408 | } |
398 | 409 | switch { |
399 | 410 | case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil: |
400 | return xerrors.Errorf("map %v: missing type", mapSym) | |
411 | return fmt.Errorf("map %v: missing type", mapSym) | |
401 | 412 | case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil: |
402 | return xerrors.Errorf("map %v: missing key size", mapSym) | |
413 | return fmt.Errorf("map %v: missing key size", mapSym) | |
403 | 414 | case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil: |
404 | return xerrors.Errorf("map %v: missing value size", mapSym) | |
415 | return fmt.Errorf("map %v: missing value size", mapSym) | |
405 | 416 | case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil: |
406 | return xerrors.Errorf("map %v: missing max entries", mapSym) | |
417 | return fmt.Errorf("map %v: missing max entries", mapSym) | |
407 | 418 | case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil: |
408 | return xerrors.Errorf("map %v: missing flags", mapSym) | |
419 | return fmt.Errorf("map %v: missing flags", mapSym) | |
409 | 420 | } |
410 | 421 | |
411 | 422 | if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil { |
412 | return xerrors.Errorf("map %v: unknown and non-zero fields in definition", mapSym) | |
423 | return fmt.Errorf("map %v: unknown and non-zero fields in definition", mapSym) | |
413 | 424 | } |
414 | 425 | |
415 | 426 | maps[mapSym.Name] = &spec |
421 | 432 | |
422 | 433 | func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error { |
423 | 434 | if spec == nil { |
424 | return xerrors.Errorf("missing BTF") | |
435 | return fmt.Errorf("missing BTF") | |
425 | 436 | } |
426 | 437 | |
427 | 438 | for idx, sec := range mapSections { |
428 | 439 | syms := ec.symbolsPerSection[idx] |
429 | 440 | if len(syms) == 0 { |
430 | return xerrors.Errorf("section %v: no symbols", sec.Name) | |
441 | return fmt.Errorf("section %v: no symbols", sec.Name) | |
431 | 442 | } |
432 | 443 | |
433 | 444 | for _, sym := range syms { |
434 | 445 | name := sym.Name |
435 | 446 | if maps[name] != nil { |
436 | return xerrors.Errorf("section %v: map %v already exists", sec.Name, sym) | |
437 | } | |
438 | ||
439 | btfMap, btfMapMembers, err := spec.Map(name) | |
447 | return fmt.Errorf("section %v: map %v already exists", sec.Name, sym) | |
448 | } | |
449 | ||
450 | mapSpec, err := mapSpecFromBTF(spec, name) | |
440 | 451 | if err != nil { |
441 | return xerrors.Errorf("map %v: can't get BTF: %w", name, err) | |
442 | } | |
443 | ||
444 | spec, err := mapSpecFromBTF(btfMap, btfMapMembers) | |
445 | if err != nil { | |
446 | return xerrors.Errorf("map %v: %w", name, err) | |
447 | } | |
448 | ||
449 | maps[name] = spec | |
452 | return fmt.Errorf("map %v: %w", name, err) | |
453 | } | |
454 | ||
455 | maps[name] = mapSpec | |
450 | 456 | } |
451 | 457 | } |
452 | 458 | |
453 | 459 | return nil |
454 | 460 | } |
455 | 461 | |
456 | func mapSpecFromBTF(btfMap *btf.Map, btfMapMembers []btf.Member) (*MapSpec, error) { | |
462 | func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) { | |
463 | btfMap, btfMapMembers, err := spec.Map(name) | |
464 | if err != nil { | |
465 | return nil, fmt.Errorf("can't get BTF: %w", err) | |
466 | } | |
467 | ||
468 | keyType := btf.MapKey(btfMap) | |
469 | size, err := btf.Sizeof(keyType) | |
470 | if err != nil { | |
471 | return nil, fmt.Errorf("can't get size of BTF key: %w", err) | |
472 | } | |
473 | keySize := uint32(size) | |
474 | ||
475 | valueType := btf.MapValue(btfMap) | |
476 | size, err = btf.Sizeof(valueType) | |
477 | if err != nil { | |
478 | return nil, fmt.Errorf("can't get size of BTF value: %w", err) | |
479 | } | |
480 | valueSize := uint32(size) | |
481 | ||
457 | 482 | var ( |
458 | 483 | mapType, flags, maxEntries uint32 |
459 | err error | |
460 | 484 | ) |
461 | 485 | for _, member := range btfMapMembers { |
462 | 486 | switch member.Name { |
463 | 487 | case "type": |
464 | 488 | mapType, err = uintFromBTF(member.Type) |
465 | 489 | if err != nil { |
466 | return nil, xerrors.Errorf("can't get type: %w", err) | |
490 | return nil, fmt.Errorf("can't get type: %w", err) | |
467 | 491 | } |
468 | 492 | |
469 | 493 | case "map_flags": |
470 | 494 | flags, err = uintFromBTF(member.Type) |
471 | 495 | if err != nil { |
472 | return nil, xerrors.Errorf("can't get BTF map flags: %w", err) | |
496 | return nil, fmt.Errorf("can't get BTF map flags: %w", err) | |
473 | 497 | } |
474 | 498 | |
475 | 499 | case "max_entries": |
476 | 500 | maxEntries, err = uintFromBTF(member.Type) |
477 | 501 | if err != nil { |
478 | return nil, xerrors.Errorf("can't get BTF map max entries: %w", err) | |
479 | } | |
480 | ||
481 | case "key": | |
482 | case "value": | |
502 | return nil, fmt.Errorf("can't get BTF map max entries: %w", err) | |
503 | } | |
504 | ||
505 | case "key_size": | |
506 | if _, isVoid := keyType.(*btf.Void); !isVoid { | |
507 | return nil, errors.New("both key and key_size given") | |
508 | } | |
509 | ||
510 | keySize, err = uintFromBTF(member.Type) | |
511 | if err != nil { | |
512 | return nil, fmt.Errorf("can't get BTF key size: %w", err) | |
513 | } | |
514 | ||
515 | case "value_size": | |
516 | if _, isVoid := valueType.(*btf.Void); !isVoid { | |
517 | return nil, errors.New("both value and value_size given") | |
518 | } | |
519 | ||
520 | valueSize, err = uintFromBTF(member.Type) | |
521 | if err != nil { | |
522 | return nil, fmt.Errorf("can't get BTF value size: %w", err) | |
523 | } | |
524 | ||
525 | case "pinning": | |
526 | pinning, err := uintFromBTF(member.Type) | |
527 | if err != nil { | |
528 | return nil, fmt.Errorf("can't get pinning: %w", err) | |
529 | } | |
530 | ||
531 | if pinning != 0 { | |
532 | return nil, fmt.Errorf("'pinning' attribute not supported: %w", ErrNotSupported) | |
533 | } | |
534 | ||
535 | case "key", "value": | |
483 | 536 | default: |
484 | return nil, xerrors.Errorf("unrecognized field %s in BTF map definition", member.Name) | |
485 | } | |
486 | } | |
487 | ||
488 | keySize, err := btf.Sizeof(btf.MapKey(btfMap)) | |
489 | if err != nil { | |
490 | return nil, xerrors.Errorf("can't get size of BTF key: %w", err) | |
491 | } | |
492 | ||
493 | valueSize, err := btf.Sizeof(btf.MapValue(btfMap)) | |
494 | if err != nil { | |
495 | return nil, xerrors.Errorf("can't get size of BTF value: %w", err) | |
537 | return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name) | |
538 | } | |
496 | 539 | } |
497 | 540 | |
498 | 541 | return &MapSpec{ |
499 | 542 | Type: MapType(mapType), |
500 | KeySize: uint32(keySize), | |
501 | ValueSize: uint32(valueSize), | |
543 | KeySize: keySize, | |
544 | ValueSize: valueSize, | |
502 | 545 | MaxEntries: maxEntries, |
503 | 546 | Flags: flags, |
504 | 547 | BTF: btfMap, |
510 | 553 | func uintFromBTF(typ btf.Type) (uint32, error) { |
511 | 554 | ptr, ok := typ.(*btf.Pointer) |
512 | 555 | if !ok { |
513 | return 0, xerrors.Errorf("not a pointer: %v", typ) | |
556 | return 0, fmt.Errorf("not a pointer: %v", typ) | |
514 | 557 | } |
515 | 558 | |
516 | 559 | arr, ok := ptr.Target.(*btf.Array) |
517 | 560 | if !ok { |
518 | return 0, xerrors.Errorf("not a pointer to array: %v", typ) | |
561 | return 0, fmt.Errorf("not a pointer to array: %v", typ) | |
519 | 562 | } |
520 | 563 | |
521 | 564 | return arr.Nelems, nil |
523 | 566 | |
524 | 567 | func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error { |
525 | 568 | if spec == nil { |
526 | return xerrors.New("data sections require BTF") | |
569 | return errors.New("data sections require BTF, make sure all consts are marked as static") | |
527 | 570 | } |
528 | 571 | |
529 | 572 | for _, sec := range dataSections { |
534 | 577 | |
535 | 578 | data, err := sec.Data() |
536 | 579 | if err != nil { |
537 | return xerrors.Errorf("data section %s: can't get contents: %w", sec.Name, err) | |
580 | return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err) | |
538 | 581 | } |
539 | 582 | |
540 | 583 | if uint64(len(data)) > math.MaxUint32 { |
541 | return xerrors.Errorf("data section %s: contents exceed maximum size", sec.Name) | |
584 | return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name) | |
542 | 585 | } |
543 | 586 | |
544 | 587 | mapSpec := &MapSpec{ |
565 | 608 | return nil |
566 | 609 | } |
567 | 610 | |
568 | func getProgType(v string) (ProgramType, AttachType) { | |
569 | types := map[string]ProgramType{ | |
570 | // From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568 | |
571 | "socket": SocketFilter, | |
572 | "seccomp": SocketFilter, | |
573 | "kprobe/": Kprobe, | |
574 | "uprobe/": Kprobe, | |
575 | "kretprobe/": Kprobe, | |
576 | "uretprobe/": Kprobe, | |
577 | "tracepoint/": TracePoint, | |
578 | "raw_tracepoint/": RawTracepoint, | |
579 | "xdp": XDP, | |
580 | "perf_event": PerfEvent, | |
581 | "lwt_in": LWTIn, | |
582 | "lwt_out": LWTOut, | |
583 | "lwt_xmit": LWTXmit, | |
584 | "lwt_seg6local": LWTSeg6Local, | |
585 | "sockops": SockOps, | |
586 | "sk_skb": SkSKB, | |
587 | "sk_msg": SkMsg, | |
588 | "lirc_mode2": LircMode2, | |
589 | "flow_dissector": FlowDissector, | |
590 | ||
591 | "cgroup_skb/": CGroupSKB, | |
592 | "cgroup/dev": CGroupDevice, | |
593 | "cgroup/skb": CGroupSKB, | |
594 | "cgroup/sock": CGroupSock, | |
595 | "cgroup/post_bind": CGroupSock, | |
596 | "cgroup/bind": CGroupSockAddr, | |
597 | "cgroup/connect": CGroupSockAddr, | |
598 | "cgroup/sendmsg": CGroupSockAddr, | |
599 | "cgroup/recvmsg": CGroupSockAddr, | |
600 | "cgroup/sysctl": CGroupSysctl, | |
601 | "cgroup/getsockopt": CGroupSockopt, | |
602 | "cgroup/setsockopt": CGroupSockopt, | |
603 | "classifier": SchedCLS, | |
604 | "action": SchedACT, | |
605 | } | |
606 | attachTypes := map[string]AttachType{ | |
607 | "cgroup_skb/ingress": AttachCGroupInetIngress, | |
608 | "cgroup_skb/egress": AttachCGroupInetEgress, | |
609 | "cgroup/sock": AttachCGroupInetSockCreate, | |
610 | "cgroup/post_bind4": AttachCGroupInet4PostBind, | |
611 | "cgroup/post_bind6": AttachCGroupInet6PostBind, | |
612 | "cgroup/dev": AttachCGroupDevice, | |
613 | "sockops": AttachCGroupSockOps, | |
614 | "sk_skb/stream_parser": AttachSkSKBStreamParser, | |
615 | "sk_skb/stream_verdict": AttachSkSKBStreamVerdict, | |
616 | "sk_msg": AttachSkSKBStreamVerdict, | |
617 | "lirc_mode2": AttachLircMode2, | |
618 | "flow_dissector": AttachFlowDissector, | |
619 | "cgroup/bind4": AttachCGroupInet4Bind, | |
620 | "cgroup/bind6": AttachCGroupInet6Bind, | |
621 | "cgroup/connect4": AttachCGroupInet4Connect, | |
622 | "cgroup/connect6": AttachCGroupInet6Connect, | |
623 | "cgroup/sendmsg4": AttachCGroupUDP4Sendmsg, | |
624 | "cgroup/sendmsg6": AttachCGroupUDP6Sendmsg, | |
625 | "cgroup/recvmsg4": AttachCGroupUDP4Recvmsg, | |
626 | "cgroup/recvmsg6": AttachCGroupUDP6Recvmsg, | |
627 | "cgroup/sysctl": AttachCGroupSysctl, | |
628 | "cgroup/getsockopt": AttachCGroupGetsockopt, | |
629 | "cgroup/setsockopt": AttachCGroupSetsockopt, | |
630 | } | |
631 | attachType := AttachNone | |
632 | for k, t := range attachTypes { | |
633 | if strings.HasPrefix(v, k) { | |
634 | attachType = t | |
635 | } | |
636 | } | |
637 | ||
638 | for k, t := range types { | |
639 | if strings.HasPrefix(v, k) { | |
640 | return t, attachType | |
641 | } | |
642 | } | |
643 | return UnspecifiedProgram, AttachNone | |
644 | } | |
645 | ||
646 | func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, error) { | |
611 | func getProgType(sectionName string) (ProgramType, AttachType, string) { | |
612 | types := map[string]struct { | |
613 | progType ProgramType | |
614 | attachType AttachType | |
615 | }{ | |
616 | // From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c | |
617 | "socket": {SocketFilter, AttachNone}, | |
618 | "seccomp": {SocketFilter, AttachNone}, | |
619 | "kprobe/": {Kprobe, AttachNone}, | |
620 | "uprobe/": {Kprobe, AttachNone}, | |
621 | "kretprobe/": {Kprobe, AttachNone}, | |
622 | "uretprobe/": {Kprobe, AttachNone}, | |
623 | "tracepoint/": {TracePoint, AttachNone}, | |
624 | "raw_tracepoint/": {RawTracepoint, AttachNone}, | |
625 | "xdp": {XDP, AttachNone}, | |
626 | "perf_event": {PerfEvent, AttachNone}, | |
627 | "lwt_in": {LWTIn, AttachNone}, | |
628 | "lwt_out": {LWTOut, AttachNone}, | |
629 | "lwt_xmit": {LWTXmit, AttachNone}, | |
630 | "lwt_seg6local": {LWTSeg6Local, AttachNone}, | |
631 | "sockops": {SockOps, AttachCGroupSockOps}, | |
632 | "sk_skb/stream_parser": {SkSKB, AttachSkSKBStreamParser}, | |
633 | "sk_skb/stream_verdict": {SkSKB, AttachSkSKBStreamParser}, | |
634 | "sk_msg": {SkMsg, AttachSkSKBStreamVerdict}, | |
635 | "lirc_mode2": {LircMode2, AttachLircMode2}, | |
636 | "flow_dissector": {FlowDissector, AttachFlowDissector}, | |
637 | "iter/": {Tracing, AttachTraceIter}, | |
638 | "sk_lookup/": {SkLookup, AttachSkLookup}, | |
639 | ||
640 | "cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress}, | |
641 | "cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress}, | |
642 | "cgroup/dev": {CGroupDevice, AttachCGroupDevice}, | |
643 | "cgroup/skb": {CGroupSKB, AttachNone}, | |
644 | "cgroup/sock": {CGroupSock, AttachCGroupInetSockCreate}, | |
645 | "cgroup/post_bind4": {CGroupSock, AttachCGroupInet4PostBind}, | |
646 | "cgroup/post_bind6": {CGroupSock, AttachCGroupInet6PostBind}, | |
647 | "cgroup/bind4": {CGroupSockAddr, AttachCGroupInet4Bind}, | |
648 | "cgroup/bind6": {CGroupSockAddr, AttachCGroupInet6Bind}, | |
649 | "cgroup/connect4": {CGroupSockAddr, AttachCGroupInet4Connect}, | |
650 | "cgroup/connect6": {CGroupSockAddr, AttachCGroupInet6Connect}, | |
651 | "cgroup/sendmsg4": {CGroupSockAddr, AttachCGroupUDP4Sendmsg}, | |
652 | "cgroup/sendmsg6": {CGroupSockAddr, AttachCGroupUDP6Sendmsg}, | |
653 | "cgroup/recvmsg4": {CGroupSockAddr, AttachCGroupUDP4Recvmsg}, | |
654 | "cgroup/recvmsg6": {CGroupSockAddr, AttachCGroupUDP6Recvmsg}, | |
655 | "cgroup/sysctl": {CGroupSysctl, AttachCGroupSysctl}, | |
656 | "cgroup/getsockopt": {CGroupSockopt, AttachCGroupGetsockopt}, | |
657 | "cgroup/setsockopt": {CGroupSockopt, AttachCGroupSetsockopt}, | |
658 | "classifier": {SchedCLS, AttachNone}, | |
659 | "action": {SchedACT, AttachNone}, | |
660 | } | |
661 | ||
662 | for prefix, t := range types { | |
663 | if !strings.HasPrefix(sectionName, prefix) { | |
664 | continue | |
665 | } | |
666 | ||
667 | if !strings.HasSuffix(prefix, "/") { | |
668 | return t.progType, t.attachType, "" | |
669 | } | |
670 | ||
671 | return t.progType, t.attachType, sectionName[len(prefix):] | |
672 | } | |
673 | ||
674 | return UnspecifiedProgram, AttachNone, "" | |
675 | } | |
676 | ||
677 | func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, map[elf.SectionIndex]bool, error) { | |
647 | 678 | result := make(map[elf.SectionIndex]map[uint64]elf.Symbol) |
679 | targets := make(map[elf.SectionIndex]bool) | |
648 | 680 | for idx, sec := range sections { |
649 | 681 | rels := make(map[uint64]elf.Symbol) |
650 | 682 | |
651 | 683 | if sec.Entsize < 16 { |
652 | return nil, xerrors.Errorf("section %s: relocations are less than 16 bytes", sec.Name) | |
684 | return nil, nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name) | |
653 | 685 | } |
654 | 686 | |
655 | 687 | r := sec.Open() |
658 | 690 | |
659 | 691 | var rel elf.Rel64 |
660 | 692 | if binary.Read(ent, ec.ByteOrder, &rel) != nil { |
661 | return nil, xerrors.Errorf("can't parse relocation at offset %v", off) | |
693 | return nil, nil, fmt.Errorf("can't parse relocation at offset %v", off) | |
662 | 694 | } |
663 | 695 | |
664 | 696 | symNo := int(elf.R_SYM64(rel.Info) - 1) |
665 | 697 | if symNo >= len(ec.symbols) { |
666 | return nil, xerrors.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo) | |
667 | } | |
668 | ||
698 | return nil, nil, fmt.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo) | |
699 | } | |
700 | ||
701 | symbol := ec.symbols[symNo] | |
702 | targets[symbol.Section] = true | |
669 | 703 | rels[rel.Off] = ec.symbols[symNo] |
670 | 704 | } |
671 | 705 | |
672 | 706 | result[idx] = rels |
673 | 707 | } |
674 | return result, nil | |
708 | return result, targets, nil | |
675 | 709 | } |
676 | 710 | |
677 | 711 | func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]elf.Symbol { |
5 | 5 | "reflect" |
6 | 6 | "testing" |
7 | 7 | |
8 | "github.com/cilium/ebpf/internal" | |
8 | 9 | "github.com/cilium/ebpf/internal/testutils" |
9 | 10 | ) |
10 | 11 | |
11 | 12 | func TestLoadCollectionSpec(t *testing.T) { |
12 | files, err := filepath.Glob("testdata/loader-*.elf") | |
13 | if err != nil { | |
14 | t.Fatal(err) | |
15 | } | |
16 | ||
17 | for _, file := range files { | |
18 | name := filepath.Base(file) | |
19 | t.Run(name, func(t *testing.T) { | |
20 | spec, err := LoadCollectionSpec(file) | |
13 | testutils.TestFiles(t, "testdata/loader-*.elf", func(t *testing.T, file string) { | |
14 | spec, err := LoadCollectionSpec(file) | |
15 | if err != nil { | |
16 | t.Fatal("Can't parse ELF:", err) | |
17 | } | |
18 | ||
19 | hashMapSpec := &MapSpec{ | |
20 | Name: "hash_map", | |
21 | Type: Hash, | |
22 | KeySize: 4, | |
23 | ValueSize: 2, | |
24 | MaxEntries: 1, | |
25 | } | |
26 | checkMapSpec(t, spec.Maps, "hash_map", hashMapSpec) | |
27 | checkMapSpec(t, spec.Maps, "array_of_hash_map", &MapSpec{ | |
28 | Name: "hash_map", | |
29 | Type: ArrayOfMaps, | |
30 | KeySize: 4, | |
31 | MaxEntries: 2, | |
32 | }) | |
33 | spec.Maps["array_of_hash_map"].InnerMap = spec.Maps["hash_map"] | |
34 | ||
35 | hashMap2Spec := &MapSpec{ | |
36 | Name: "", | |
37 | Type: Hash, | |
38 | KeySize: 4, | |
39 | ValueSize: 1, | |
40 | MaxEntries: 2, | |
41 | Flags: 1, | |
42 | } | |
43 | checkMapSpec(t, spec.Maps, "hash_map2", hashMap2Spec) | |
44 | checkMapSpec(t, spec.Maps, "hash_of_hash_map", &MapSpec{ | |
45 | Type: HashOfMaps, | |
46 | KeySize: 4, | |
47 | MaxEntries: 2, | |
48 | }) | |
49 | spec.Maps["hash_of_hash_map"].InnerMap = spec.Maps["hash_map2"] | |
50 | ||
51 | checkProgramSpec(t, spec.Programs, "xdp_prog", &ProgramSpec{ | |
52 | Type: XDP, | |
53 | License: "MIT", | |
54 | KernelVersion: 0, | |
55 | }) | |
56 | checkProgramSpec(t, spec.Programs, "no_relocation", &ProgramSpec{ | |
57 | Type: SocketFilter, | |
58 | License: "MIT", | |
59 | KernelVersion: 0, | |
60 | }) | |
61 | ||
62 | if rodata := spec.Maps[".rodata"]; rodata != nil { | |
63 | err := spec.RewriteConstants(map[string]interface{}{ | |
64 | "arg": uint32(1), | |
65 | }) | |
21 | 66 | if err != nil { |
22 | t.Fatal("Can't parse ELF:", err) | |
67 | t.Fatal("Can't rewrite constant:", err) | |
23 | 68 | } |
24 | 69 | |
25 | hashMapSpec := &MapSpec{ | |
26 | Name: "hash_map", | |
27 | Type: Hash, | |
28 | KeySize: 4, | |
29 | ValueSize: 2, | |
30 | MaxEntries: 1, | |
70 | err = spec.RewriteConstants(map[string]interface{}{ | |
71 | "totallyBogus": uint32(1), | |
72 | }) | |
73 | if err == nil { | |
74 | t.Error("Rewriting a bogus constant doesn't fail") | |
31 | 75 | } |
32 | checkMapSpec(t, spec.Maps, "hash_map", hashMapSpec) | |
33 | checkMapSpec(t, spec.Maps, "array_of_hash_map", &MapSpec{ | |
34 | Name: "hash_map", | |
35 | Type: ArrayOfMaps, | |
36 | KeySize: 4, | |
37 | MaxEntries: 2, | |
38 | }) | |
39 | spec.Maps["array_of_hash_map"].InnerMap = spec.Maps["hash_map"] | |
40 | ||
41 | hashMap2Spec := &MapSpec{ | |
42 | Name: "", | |
43 | Type: Hash, | |
44 | KeySize: 4, | |
45 | ValueSize: 1, | |
46 | MaxEntries: 2, | |
47 | Flags: 1, | |
48 | } | |
49 | checkMapSpec(t, spec.Maps, "hash_map2", hashMap2Spec) | |
50 | checkMapSpec(t, spec.Maps, "hash_of_hash_map", &MapSpec{ | |
51 | Type: HashOfMaps, | |
52 | KeySize: 4, | |
53 | MaxEntries: 2, | |
54 | }) | |
55 | spec.Maps["hash_of_hash_map"].InnerMap = spec.Maps["hash_map2"] | |
56 | ||
57 | checkProgramSpec(t, spec.Programs, "xdp_prog", &ProgramSpec{ | |
58 | Type: XDP, | |
59 | License: "MIT", | |
60 | KernelVersion: 0, | |
61 | }) | |
62 | checkProgramSpec(t, spec.Programs, "no_relocation", &ProgramSpec{ | |
63 | Type: SocketFilter, | |
64 | License: "MIT", | |
65 | KernelVersion: 0, | |
66 | }) | |
67 | ||
68 | if rodata := spec.Maps[".rodata"]; rodata != nil { | |
69 | err := spec.RewriteConstants(map[string]interface{}{ | |
70 | "arg": uint32(1), | |
71 | }) | |
72 | if err != nil { | |
73 | t.Fatal("Can't rewrite constant:", err) | |
74 | } | |
75 | ||
76 | err = spec.RewriteConstants(map[string]interface{}{ | |
77 | "totallyBogus": uint32(1), | |
78 | }) | |
79 | if err == nil { | |
80 | t.Error("Rewriting a bogus constant doesn't fail") | |
81 | } | |
82 | } | |
83 | ||
84 | t.Log(spec.Programs["xdp_prog"].Instructions) | |
85 | ||
86 | coll, err := NewCollectionWithOptions(spec, CollectionOptions{ | |
87 | Programs: ProgramOptions{ | |
88 | LogLevel: 1, | |
89 | }, | |
90 | }) | |
91 | testutils.SkipIfNotSupported(t, err) | |
92 | if err != nil { | |
93 | t.Fatal(err) | |
94 | } | |
95 | defer coll.Close() | |
96 | ||
97 | ret, _, err := coll.Programs["xdp_prog"].Test(make([]byte, 14)) | |
98 | if err != nil { | |
99 | t.Fatal("Can't run program:", err) | |
100 | } | |
101 | ||
102 | if ret != 5 { | |
103 | t.Error("Expected return value to be 5, got", ret) | |
104 | } | |
105 | }) | |
106 | } | |
76 | } | |
77 | ||
78 | t.Log(spec.Programs["xdp_prog"].Instructions) | |
79 | ||
80 | if spec.Programs["xdp_prog"].ByteOrder != internal.NativeEndian { | |
81 | return | |
82 | } | |
83 | ||
84 | coll, err := NewCollectionWithOptions(spec, CollectionOptions{ | |
85 | Programs: ProgramOptions{ | |
86 | LogLevel: 1, | |
87 | }, | |
88 | }) | |
89 | testutils.SkipIfNotSupported(t, err) | |
90 | if err != nil { | |
91 | t.Fatal(err) | |
92 | } | |
93 | defer coll.Close() | |
94 | ||
95 | ret, _, err := coll.Programs["xdp_prog"].Test(make([]byte, 14)) | |
96 | if err != nil { | |
97 | t.Fatal("Can't run program:", err) | |
98 | } | |
99 | ||
100 | if ret != 5 { | |
101 | t.Error("Expected return value to be 5, got", ret) | |
102 | } | |
103 | }) | |
107 | 104 | } |
108 | 105 | |
109 | 106 | func checkMapSpec(t *testing.T, maps map[string]*MapSpec, name string, want *MapSpec) { |
158 | 155 | if !ok { |
159 | 156 | t.Fatalf("Missing program %s", name) |
160 | 157 | return |
158 | } | |
159 | ||
160 | if have.ByteOrder == nil { | |
161 | t.Errorf("%s: nil ByteOrder", name) | |
161 | 162 | } |
162 | 163 | |
163 | 164 | if have.License != want.License { |
207 | 208 | } |
208 | 209 | |
209 | 210 | func TestLoadInvalidMap(t *testing.T) { |
210 | _, err := LoadCollectionSpec("testdata/invalid_map.elf") | |
211 | t.Log(err) | |
212 | if err == nil { | |
213 | t.Fatal("should be fail") | |
214 | } | |
215 | } | |
216 | ||
217 | var elfPattern = flag.String("elfs", "", "`PATTERN` for a path containing libbpf-compatible ELFs") | |
211 | testutils.TestFiles(t, "testdata/invalid_map-*.elf", func(t *testing.T, file string) { | |
212 | _, err := LoadCollectionSpec(file) | |
213 | t.Log(err) | |
214 | if err == nil { | |
215 | t.Fatal("Loading an invalid map should fail") | |
216 | } | |
217 | }) | |
218 | } | |
219 | ||
220 | func TestLoadRawTracepoint(t *testing.T) { | |
221 | testutils.SkipOnOldKernel(t, "4.17", "BPF_RAW_TRACEPOINT API") | |
222 | ||
223 | testutils.TestFiles(t, "testdata/raw_tracepoint-*.elf", func(t *testing.T, file string) { | |
224 | spec, err := LoadCollectionSpec(file) | |
225 | if err != nil { | |
226 | t.Fatal("Can't parse ELF:", err) | |
227 | } | |
228 | ||
229 | if spec.Programs["sched_process_exec"].ByteOrder != internal.NativeEndian { | |
230 | return | |
231 | } | |
232 | ||
233 | coll, err := NewCollectionWithOptions(spec, CollectionOptions{ | |
234 | Programs: ProgramOptions{ | |
235 | LogLevel: 1, | |
236 | }, | |
237 | }) | |
238 | testutils.SkipIfNotSupported(t, err) | |
239 | if err != nil { | |
240 | t.Fatal("Can't create collection:", err) | |
241 | } | |
242 | ||
243 | coll.Close() | |
244 | }) | |
245 | } | |
246 | ||
247 | var ( | |
248 | elfPath = flag.String("elfs", "", "`Path` containing libbpf-compatible ELFs") | |
249 | elfPattern = flag.String("elf-pattern", "*.o", "Glob `pattern` for object files that should be tested") | |
250 | ) | |
218 | 251 | |
219 | 252 | func TestLibBPFCompat(t *testing.T) { |
220 | if *elfPattern == "" { | |
253 | if *elfPath == "" { | |
221 | 254 | // Specify the path to the directory containing the eBPF for |
222 | // the kernel's selftests. | |
223 | // As of 5.2 that is tools/testing/selftests/bpf/. | |
255 | // the kernel's selftests if you want to run this test. | |
256 | // As of 5.2 that is tools/testing/selftests/bpf/ | |
224 | 257 | t.Skip("No path specified") |
225 | 258 | } |
226 | 259 | |
227 | files, err := filepath.Glob(*elfPattern) | |
228 | if err != nil { | |
229 | t.Fatal(err) | |
230 | } | |
231 | ||
232 | for _, file := range files { | |
233 | name := filepath.Base(file) | |
234 | t.Run(name, func(t *testing.T) { | |
235 | t.Parallel() | |
236 | ||
237 | spec, err := LoadCollectionSpec(file) | |
238 | if err != nil { | |
239 | t.Fatalf("Can't read %s: %s", name, err) | |
240 | } | |
241 | ||
242 | coll, err := NewCollection(spec) | |
243 | if err != nil { | |
244 | t.Fatal(err) | |
245 | } | |
246 | coll.Close() | |
247 | }) | |
248 | } | |
249 | } | |
260 | testutils.TestFiles(t, filepath.Join(*elfPath, *elfPattern), func(t *testing.T, path string) { | |
261 | t.Parallel() | |
262 | ||
263 | file := filepath.Base(path) | |
264 | _, err := LoadCollectionSpec(path) | |
265 | testutils.SkipIfNotSupported(t, err) | |
266 | if err != nil { | |
267 | t.Fatalf("Can't read %s: %s", file, err) | |
268 | } | |
269 | }) | |
270 | } | |
271 | ||
272 | func TestGetProgType(t *testing.T) { | |
273 | testcases := []struct { | |
274 | section string | |
275 | pt ProgramType | |
276 | at AttachType | |
277 | to string | |
278 | }{ | |
279 | {"socket/garbage", SocketFilter, AttachNone, ""}, | |
280 | {"kprobe/func", Kprobe, AttachNone, "func"}, | |
281 | {"xdp/foo", XDP, AttachNone, ""}, | |
282 | {"cgroup_skb/ingress", CGroupSKB, AttachCGroupInetIngress, ""}, | |
283 | {"iter/bpf_map", Tracing, AttachTraceIter, "bpf_map"}, | |
284 | } | |
285 | ||
286 | for _, tc := range testcases { | |
287 | pt, at, to := getProgType(tc.section) | |
288 | if pt != tc.pt { | |
289 | t.Errorf("section %s: expected type %s, got %s", tc.section, tc.pt, pt) | |
290 | } | |
291 | ||
292 | if at != tc.at { | |
293 | t.Errorf("section %s: expected attach type %s, got %s", tc.section, tc.at, at) | |
294 | } | |
295 | ||
296 | if to != tc.to { | |
297 | t.Errorf("section %s: expected attachment to be %q, got %q", tc.section, tc.to, to) | |
298 | } | |
299 | } | |
300 | } |
4 | 4 | import ( |
5 | 5 | "context" |
6 | 6 | "fmt" |
7 | "io/ioutil" | |
7 | 8 | "os" |
9 | "strconv" | |
10 | "strings" | |
8 | 11 | "time" |
9 | 12 | |
10 | 13 | "github.com/cilium/ebpf" |
14 | 17 | "golang.org/x/sys/unix" |
15 | 18 | ) |
16 | 19 | |
17 | // Example_program demonstrates how to attach an eBPF program to a tracepoint. | |
20 | // getTracepointID returns the system specific ID for the tracepoint sys_enter_open. | |
21 | func getTracepointID() (uint64, error) { | |
22 | data, err := ioutil.ReadFile("/sys/kernel/debug/tracing/events/syscalls/sys_enter_open/id") | |
23 | if err != nil { | |
24 | return 0, fmt.Errorf("failed to read tracepoint ID for 'sys_enter_open': %v", err) | |
25 | } | |
26 | tid := strings.TrimSuffix(string(data), "\n") | |
27 | return strconv.ParseUint(tid, 10, 64) | |
28 | } | |
29 | ||
30 | // This demonstrates how to attach an eBPF program to a tracepoint. | |
18 | 31 | // The program will be attached to the sys_enter_open syscall and print out the integer |
19 | 32 | // 123 everytime the sycall is used. |
20 | func Example_program() { | |
33 | func Example_tracepoint() { | |
21 | 34 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
22 | 35 | defer cancel() |
23 | 36 | events, err := ebpf.NewMap(&ebpf.MapSpec{ |
84 | 97 | } |
85 | 98 | defer prog.Close() |
86 | 99 | |
87 | // tracepoint id from /sys/kernel/debug/tracing/events/syscalls/sys_enter_open/id | |
88 | tid := uint64(627) | |
100 | tid, err := getTracepointID() | |
101 | if err != nil { | |
102 | panic(fmt.Errorf("could not get tracepoint id: %v", err)) | |
103 | } | |
89 | 104 | |
90 | 105 | attr := unix.PerfEventAttr{ |
91 | 106 | Type: unix.PERF_TYPE_TRACEPOINT, |
99 | 114 | panic(fmt.Errorf("unable to open perf events: %v", err)) |
100 | 115 | } |
101 | 116 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd), unix.PERF_EVENT_IOC_ENABLE, 0); errno != 0 { |
102 | panic(fmt.Errorf("unable to set up perf events: %v", err)) | |
117 | panic(fmt.Errorf("unable to enable perf events: %v", err)) | |
103 | 118 | } |
104 | 119 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd), unix.PERF_EVENT_IOC_SET_BPF, uintptr(prog.FD())); errno != 0 { |
105 | 120 | panic(fmt.Errorf("unable to attach bpf program to perf events: %v", err)) |
106 | 121 | } |
107 | 122 | |
108 | 123 | <-ctx.Done() |
124 | ||
125 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd), unix.PERF_EVENT_IOC_DISABLE, 0); errno != 0 { | |
126 | panic(fmt.Errorf("unable to disable perf events: %v", err)) | |
127 | } | |
109 | 128 | } |
89 | 89 | panic(err) |
90 | 90 | } |
91 | 91 | |
92 | coll, err := ebpf.NewCollection(spec) | |
93 | if err != nil { | |
92 | var objs struct { | |
93 | Prog *ebpf.Program `ebpf:"bpf_prog1"` | |
94 | Stats *ebpf.Map `ebpf:"my_map"` | |
95 | } | |
96 | ||
97 | if err := spec.LoadAndAssign(&objs, nil); err != nil { | |
94 | 98 | panic(err) |
95 | 99 | } |
96 | defer coll.Close() | |
100 | defer objs.Prog.Close() | |
101 | defer objs.Stats.Close() | |
97 | 102 | |
98 | 103 | sock, err := openRawSock(*index) |
99 | 104 | if err != nil { |
101 | 106 | } |
102 | 107 | defer syscall.Close(sock) |
103 | 108 | |
104 | prog := coll.DetachProgram("bpf_prog1") | |
105 | if prog == nil { | |
106 | panic("no program named bpf_prog1 found") | |
107 | } | |
108 | defer prog.Close() | |
109 | ||
110 | if err := syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, SO_ATTACH_BPF, prog.FD()); err != nil { | |
109 | if err := syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, SO_ATTACH_BPF, objs.Prog.FD()); err != nil { | |
111 | 110 | panic(err) |
112 | 111 | } |
113 | 112 | |
114 | 113 | fmt.Printf("Filtering on eth index: %d\n", *index) |
115 | 114 | fmt.Println("Packet stats:") |
116 | ||
117 | protoStats := coll.DetachMap("my_map") | |
118 | if protoStats == nil { | |
119 | panic(fmt.Errorf("no map named my_map found")) | |
120 | } | |
121 | defer protoStats.Close() | |
122 | 115 | |
123 | 116 | for { |
124 | 117 | const ( |
131 | 124 | var icmp uint64 |
132 | 125 | var tcp uint64 |
133 | 126 | var udp uint64 |
134 | err := protoStats.Lookup(uint32(ICMP), &icmp) | |
127 | err := objs.Stats.Lookup(uint32(ICMP), &icmp) | |
135 | 128 | if err != nil { |
136 | 129 | panic(err) |
137 | 130 | } |
138 | err = protoStats.Lookup(uint32(TCP), &tcp) | |
131 | err = objs.Stats.Lookup(uint32(TCP), &tcp) | |
139 | 132 | if err != nil { |
140 | 133 | panic(err) |
141 | 134 | } |
142 | err = protoStats.Lookup(uint32(UDP), &udp) | |
135 | err = objs.Stats.Lookup(uint32(UDP), &udp) | |
143 | 136 | if err != nil { |
144 | 137 | panic(err) |
145 | 138 | } |
0 | 0 | module github.com/cilium/ebpf |
1 | 1 | |
2 | go 1.12 | |
2 | go 1.13 | |
3 | 3 | |
4 | require ( | |
5 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 | |
6 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 | |
7 | ) | |
4 | require golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 |
0 | golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= | |
1 | golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
2 | 0 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= |
3 | 1 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= | |
5 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
3 | 3 | "bytes" |
4 | 4 | "debug/elf" |
5 | 5 | "encoding/binary" |
6 | "errors" | |
7 | "fmt" | |
6 | 8 | "io" |
7 | 9 | "io/ioutil" |
8 | 10 | "math" |
11 | "os" | |
9 | 12 | "reflect" |
13 | "sync" | |
10 | 14 | "unsafe" |
11 | 15 | |
12 | 16 | "github.com/cilium/ebpf/internal" |
13 | 17 | "github.com/cilium/ebpf/internal/unix" |
14 | ||
15 | "golang.org/x/xerrors" | |
16 | 18 | ) |
17 | 19 | |
18 | 20 | const btfMagic = 0xeB9F |
19 | 21 | |
20 | 22 | // Errors returned by BTF functions. |
21 | 23 | var ( |
22 | ErrNotSupported = internal.ErrNotSupported | |
24 | ErrNotSupported = internal.ErrNotSupported | |
25 | ErrNotFound = errors.New("not found") | |
26 | ErrNoExtendedInfo = errors.New("no extended info") | |
23 | 27 | ) |
24 | 28 | |
25 | 29 | // Spec represents decoded BTF. |
29 | 33 | types map[string][]Type |
30 | 34 | funcInfos map[string]extInfo |
31 | 35 | lineInfos map[string]extInfo |
36 | byteOrder binary.ByteOrder | |
32 | 37 | } |
33 | 38 | |
34 | 39 | type btfHeader struct { |
71 | 76 | } |
72 | 77 | |
73 | 78 | if sec.Size > math.MaxUint32 { |
74 | return nil, xerrors.Errorf("section %s exceeds maximum size", sec.Name) | |
79 | return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name) | |
75 | 80 | } |
76 | 81 | |
77 | 82 | sectionSizes[sec.Name] = uint32(sec.Size) |
84 | 89 | |
85 | 90 | symbols, err := file.Symbols() |
86 | 91 | if err != nil { |
87 | return nil, xerrors.Errorf("can't read symbols: %v", err) | |
92 | return nil, fmt.Errorf("can't read symbols: %v", err) | |
88 | 93 | } |
89 | 94 | |
90 | 95 | variableOffsets := make(map[variable]uint32) |
100 | 105 | } |
101 | 106 | |
102 | 107 | if symbol.Value > math.MaxUint32 { |
103 | return nil, xerrors.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name) | |
108 | return nil, fmt.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name) | |
104 | 109 | } |
105 | 110 | |
106 | 111 | variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value) |
107 | 112 | } |
108 | 113 | |
109 | rawTypes, rawStrings, err := parseBTF(btfSection.Open(), file.ByteOrder) | |
114 | spec, err := loadNakedSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets) | |
110 | 115 | if err != nil { |
111 | 116 | return nil, err |
112 | 117 | } |
113 | 118 | |
119 | if btfExtSection == nil { | |
120 | return spec, nil | |
121 | } | |
122 | ||
123 | spec.funcInfos, spec.lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings) | |
124 | if err != nil { | |
125 | return nil, fmt.Errorf("can't read ext info: %w", err) | |
126 | } | |
127 | ||
128 | return spec, nil | |
129 | } | |
130 | ||
131 | func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) { | |
132 | rawTypes, rawStrings, err := parseBTF(btf, bo) | |
133 | if err != nil { | |
134 | return nil, err | |
135 | } | |
136 | ||
114 | 137 | err = fixupDatasec(rawTypes, rawStrings, sectionSizes, variableOffsets) |
115 | 138 | if err != nil { |
116 | 139 | return nil, err |
119 | 142 | types, err := inflateRawTypes(rawTypes, rawStrings) |
120 | 143 | if err != nil { |
121 | 144 | return nil, err |
122 | } | |
123 | ||
124 | var ( | |
125 | funcInfos = make(map[string]extInfo) | |
126 | lineInfos = make(map[string]extInfo) | |
127 | ) | |
128 | if btfExtSection != nil { | |
129 | funcInfos, lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, rawStrings) | |
130 | if err != nil { | |
131 | return nil, xerrors.Errorf("can't read ext info: %w", err) | |
132 | } | |
133 | 145 | } |
134 | 146 | |
135 | 147 | return &Spec{ |
136 | 148 | rawTypes: rawTypes, |
137 | 149 | types: types, |
138 | 150 | strings: rawStrings, |
139 | funcInfos: funcInfos, | |
140 | lineInfos: lineInfos, | |
151 | byteOrder: bo, | |
141 | 152 | }, nil |
153 | } | |
154 | ||
155 | var kernelBTF struct { | |
156 | sync.Mutex | |
157 | *Spec | |
158 | } | |
159 | ||
160 | // LoadKernelSpec returns the current kernel's BTF information. | |
161 | // | |
162 | // Requires a >= 5.5 kernel with CONFIG_DEBUG_INFO_BTF enabled. Returns | |
163 | // ErrNotSupported if BTF is not enabled. | |
164 | func LoadKernelSpec() (*Spec, error) { | |
165 | kernelBTF.Lock() | |
166 | defer kernelBTF.Unlock() | |
167 | ||
168 | if kernelBTF.Spec != nil { | |
169 | return kernelBTF.Spec, nil | |
170 | } | |
171 | ||
172 | var err error | |
173 | kernelBTF.Spec, err = loadKernelSpec() | |
174 | return kernelBTF.Spec, err | |
175 | } | |
176 | ||
177 | func loadKernelSpec() (*Spec, error) { | |
178 | fh, err := os.Open("/sys/kernel/btf/vmlinux") | |
179 | if os.IsNotExist(err) { | |
180 | return nil, fmt.Errorf("can't open kernel BTF at /sys/kernel/btf/vmlinux: %w", ErrNotFound) | |
181 | } | |
182 | if err != nil { | |
183 | return nil, fmt.Errorf("can't read kernel BTF: %s", err) | |
184 | } | |
185 | defer fh.Close() | |
186 | ||
187 | return loadNakedSpec(fh, internal.NativeEndian, nil, nil) | |
142 | 188 | } |
143 | 189 | |
144 | 190 | func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) { |
145 | 191 | rawBTF, err := ioutil.ReadAll(btf) |
146 | 192 | if err != nil { |
147 | return nil, nil, xerrors.Errorf("can't read BTF: %v", err) | |
193 | return nil, nil, fmt.Errorf("can't read BTF: %v", err) | |
148 | 194 | } |
149 | 195 | |
150 | 196 | rd := bytes.NewReader(rawBTF) |
151 | 197 | |
152 | 198 | var header btfHeader |
153 | 199 | if err := binary.Read(rd, bo, &header); err != nil { |
154 | return nil, nil, xerrors.Errorf("can't read header: %v", err) | |
200 | return nil, nil, fmt.Errorf("can't read header: %v", err) | |
155 | 201 | } |
156 | 202 | |
157 | 203 | if header.Magic != btfMagic { |
158 | return nil, nil, xerrors.Errorf("incorrect magic value %v", header.Magic) | |
204 | return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic) | |
159 | 205 | } |
160 | 206 | |
161 | 207 | if header.Version != 1 { |
162 | return nil, nil, xerrors.Errorf("unexpected version %v", header.Version) | |
208 | return nil, nil, fmt.Errorf("unexpected version %v", header.Version) | |
163 | 209 | } |
164 | 210 | |
165 | 211 | if header.Flags != 0 { |
166 | return nil, nil, xerrors.Errorf("unsupported flags %v", header.Flags) | |
212 | return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags) | |
167 | 213 | } |
168 | 214 | |
169 | 215 | remainder := int64(header.HdrLen) - int64(binary.Size(&header)) |
170 | 216 | if remainder < 0 { |
171 | return nil, nil, xerrors.New("header is too short") | |
217 | return nil, nil, errors.New("header is too short") | |
172 | 218 | } |
173 | 219 | |
174 | 220 | if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil { |
175 | return nil, nil, xerrors.Errorf("header padding: %v", err) | |
221 | return nil, nil, fmt.Errorf("header padding: %v", err) | |
176 | 222 | } |
177 | 223 | |
178 | 224 | if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil { |
179 | return nil, nil, xerrors.Errorf("can't seek to start of string section: %v", err) | |
225 | return nil, nil, fmt.Errorf("can't seek to start of string section: %v", err) | |
180 | 226 | } |
181 | 227 | |
182 | 228 | rawStrings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen))) |
183 | 229 | if err != nil { |
184 | return nil, nil, xerrors.Errorf("can't read type names: %w", err) | |
230 | return nil, nil, fmt.Errorf("can't read type names: %w", err) | |
185 | 231 | } |
186 | 232 | |
187 | 233 | if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil { |
188 | return nil, nil, xerrors.Errorf("can't seek to start of type section: %v", err) | |
234 | return nil, nil, fmt.Errorf("can't seek to start of type section: %v", err) | |
189 | 235 | } |
190 | 236 | |
191 | 237 | rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo) |
192 | 238 | if err != nil { |
193 | return nil, nil, xerrors.Errorf("can't read types: %w", err) | |
239 | return nil, nil, fmt.Errorf("can't read types: %w", err) | |
194 | 240 | } |
195 | 241 | |
196 | 242 | return rawTypes, rawStrings, nil |
212 | 258 | return err |
213 | 259 | } |
214 | 260 | |
261 | if name == ".kconfig" || name == ".ksym" { | |
262 | return fmt.Errorf("reference to %s: %w", name, ErrNotSupported) | |
263 | } | |
264 | ||
215 | 265 | size, ok := sectionSizes[name] |
216 | 266 | if !ok { |
217 | return xerrors.Errorf("data section %s: missing size", name) | |
267 | return fmt.Errorf("data section %s: missing size", name) | |
218 | 268 | } |
219 | 269 | |
220 | 270 | rawTypes[i].SizeType = size |
223 | 273 | for j, secInfo := range secinfos { |
224 | 274 | id := int(secInfo.Type - 1) |
225 | 275 | if id >= len(rawTypes) { |
226 | return xerrors.Errorf("data section %s: invalid type id %d for variable %d", name, id, j) | |
276 | return fmt.Errorf("data section %s: invalid type id %d for variable %d", name, id, j) | |
227 | 277 | } |
228 | 278 | |
229 | 279 | varName, err := rawStrings.Lookup(rawTypes[id].NameOff) |
230 | 280 | if err != nil { |
231 | return xerrors.Errorf("data section %s: can't get name for type %d: %w", name, id, err) | |
281 | return fmt.Errorf("data section %s: can't get name for type %d: %w", name, id, err) | |
232 | 282 | } |
233 | 283 | |
234 | 284 | offset, ok := variableOffsets[variable{name, varName}] |
235 | 285 | if !ok { |
236 | return xerrors.Errorf("data section %s: missing offset for variable %s", name, varName) | |
286 | return fmt.Errorf("data section %s: missing offset for variable %s", name, varName) | |
237 | 287 | } |
238 | 288 | |
239 | 289 | secinfos[j].Offset = offset |
243 | 293 | return nil |
244 | 294 | } |
245 | 295 | |
246 | func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) { | |
296 | type marshalOpts struct { | |
297 | ByteOrder binary.ByteOrder | |
298 | StripFuncLinkage bool | |
299 | } | |
300 | ||
301 | func (s *Spec) marshal(opts marshalOpts) ([]byte, error) { | |
247 | 302 | var ( |
248 | 303 | buf bytes.Buffer |
249 | 304 | header = new(btfHeader) |
255 | 310 | _, _ = buf.Write(make([]byte, headerLen)) |
256 | 311 | |
257 | 312 | // Write type section, just after the header. |
258 | for _, typ := range s.rawTypes { | |
259 | if err := typ.Marshal(&buf, bo); err != nil { | |
260 | return nil, xerrors.Errorf("can't marshal BTF: %w", err) | |
313 | for _, raw := range s.rawTypes { | |
314 | switch { | |
315 | case opts.StripFuncLinkage && raw.Kind() == kindFunc: | |
316 | raw.SetLinkage(linkageStatic) | |
317 | } | |
318 | ||
319 | if err := raw.Marshal(&buf, opts.ByteOrder); err != nil { | |
320 | return nil, fmt.Errorf("can't marshal BTF: %w", err) | |
261 | 321 | } |
262 | 322 | } |
263 | 323 | |
279 | 339 | } |
280 | 340 | |
281 | 341 | raw := buf.Bytes() |
282 | err := binary.Write(sliceWriter(raw[:headerLen]), bo, header) | |
283 | if err != nil { | |
284 | return nil, xerrors.Errorf("can't write header: %v", err) | |
342 | err := binary.Write(sliceWriter(raw[:headerLen]), opts.ByteOrder, header) | |
343 | if err != nil { | |
344 | return nil, fmt.Errorf("can't write header: %v", err) | |
285 | 345 | } |
286 | 346 | |
287 | 347 | return raw, nil |
291 | 351 | |
292 | 352 | func (sw sliceWriter) Write(p []byte) (int, error) { |
293 | 353 | if len(p) != len(sw) { |
294 | return 0, xerrors.New("size doesn't match") | |
354 | return 0, errors.New("size doesn't match") | |
295 | 355 | } |
296 | 356 | |
297 | 357 | return copy(sw, p), nil |
301 | 361 | // |
302 | 362 | // Length is the number of bytes in the raw BPF instruction stream. |
303 | 363 | // |
304 | // Returns an error if there is no BTF. | |
364 | // Returns an error which may wrap ErrNoExtendedInfo if the Spec doesn't | |
365 | // contain extended BTF info. | |
305 | 366 | func (s *Spec) Program(name string, length uint64) (*Program, error) { |
306 | 367 | if length == 0 { |
307 | return nil, xerrors.New("length musn't be zero") | |
368 | return nil, errors.New("length musn't be zero") | |
369 | } | |
370 | ||
371 | if s.funcInfos == nil && s.lineInfos == nil { | |
372 | return nil, fmt.Errorf("BTF for section %s: %w", name, ErrNoExtendedInfo) | |
308 | 373 | } |
309 | 374 | |
310 | 375 | funcInfos, funcOK := s.funcInfos[name] |
311 | 376 | lineInfos, lineOK := s.lineInfos[name] |
312 | 377 | |
313 | 378 | if !funcOK && !lineOK { |
314 | return nil, xerrors.Errorf("no BTF for program %s", name) | |
379 | return nil, fmt.Errorf("no extended BTF info for section %s", name) | |
315 | 380 | } |
316 | 381 | |
317 | 382 | return &Program{s, length, funcInfos, lineInfos}, nil |
328 | 393 | |
329 | 394 | mapStruct, ok := mapVar.Type.(*Struct) |
330 | 395 | if !ok { |
331 | return nil, nil, xerrors.Errorf("expected struct, have %s", mapVar.Type) | |
396 | return nil, nil, fmt.Errorf("expected struct, have %s", mapVar.Type) | |
332 | 397 | } |
333 | 398 | |
334 | 399 | var key, value Type |
343 | 408 | } |
344 | 409 | |
345 | 410 | if key == nil { |
346 | return nil, nil, xerrors.Errorf("map %s: missing 'key' in type", name) | |
411 | key = (*Void)(nil) | |
347 | 412 | } |
348 | 413 | |
349 | 414 | if value == nil { |
350 | return nil, nil, xerrors.Errorf("map %s: missing 'value' in type", name) | |
415 | value = (*Void)(nil) | |
351 | 416 | } |
352 | 417 | |
353 | 418 | return &Map{s, key, value}, mapStruct.Members, nil |
357 | 422 | func (s *Spec) Datasec(name string) (*Map, error) { |
358 | 423 | var datasec Datasec |
359 | 424 | if err := s.FindType(name, &datasec); err != nil { |
360 | return nil, xerrors.Errorf("data section %s: can't get BTF: %w", name, err) | |
425 | return nil, fmt.Errorf("data section %s: can't get BTF: %w", name, err) | |
361 | 426 | } |
362 | 427 | |
363 | 428 | return &Map{s, &Void{}, &datasec}, nil |
364 | 429 | } |
365 | 430 | |
366 | var errNotFound = xerrors.New("not found") | |
367 | ||
368 | 431 | // FindType searches for a type with a specific name. |
369 | 432 | // |
370 | 433 | // hint determines the type of the returned Type. |
371 | 434 | // |
372 | // Returns an error if there is no or multiple matches. | |
435 | // Returns an error wrapping ErrNotFound if no matching | |
436 | // type exists in spec. | |
373 | 437 | func (s *Spec) FindType(name string, typ Type) error { |
374 | 438 | var ( |
375 | 439 | wanted = reflect.TypeOf(typ) |
382 | 446 | } |
383 | 447 | |
384 | 448 | if candidate != nil { |
385 | return xerrors.Errorf("type %s: multiple candidates for %T", name, typ) | |
449 | return fmt.Errorf("type %s: multiple candidates for %T", name, typ) | |
386 | 450 | } |
387 | 451 | |
388 | 452 | candidate = typ |
389 | 453 | } |
390 | 454 | |
391 | 455 | if candidate == nil { |
392 | return xerrors.Errorf("type %s: %w", name, errNotFound) | |
456 | return fmt.Errorf("type %s: %w", name, ErrNotFound) | |
393 | 457 | } |
394 | 458 | |
395 | 459 | value := reflect.Indirect(reflect.ValueOf(copyType(candidate))) |
410 | 474 | return nil, err |
411 | 475 | } |
412 | 476 | |
413 | btf, err := spec.marshal(internal.NativeEndian) | |
414 | if err != nil { | |
415 | return nil, xerrors.Errorf("can't marshal BTF: %w", err) | |
477 | if spec.byteOrder != internal.NativeEndian { | |
478 | return nil, fmt.Errorf("can't load %s BTF on %s", spec.byteOrder, internal.NativeEndian) | |
479 | } | |
480 | ||
481 | btf, err := spec.marshal(marshalOpts{ | |
482 | ByteOrder: internal.NativeEndian, | |
483 | StripFuncLinkage: haveFuncLinkage() != nil, | |
484 | }) | |
485 | if err != nil { | |
486 | return nil, fmt.Errorf("can't marshal BTF: %w", err) | |
416 | 487 | } |
417 | 488 | |
418 | 489 | if uint64(len(btf)) > math.MaxUint32 { |
419 | return nil, xerrors.New("BTF exceeds the maximum size") | |
490 | return nil, errors.New("BTF exceeds the maximum size") | |
420 | 491 | } |
421 | 492 | |
422 | 493 | attr := &bpfLoadBTFAttr{ |
500 | 571 | func ProgramAppend(s, other *Program) error { |
501 | 572 | funcInfos, err := s.funcInfos.append(other.funcInfos, s.length) |
502 | 573 | if err != nil { |
503 | return xerrors.Errorf("func infos: %w", err) | |
574 | return fmt.Errorf("func infos: %w", err) | |
504 | 575 | } |
505 | 576 | |
506 | 577 | lineInfos, err := s.lineInfos.append(other.lineInfos, s.length) |
507 | 578 | if err != nil { |
508 | return xerrors.Errorf("line infos: %w", err) | |
579 | return fmt.Errorf("line infos: %w", err) | |
509 | 580 | } |
510 | 581 | |
511 | 582 | s.length += other.length |
559 | 630 | return internal.NewFD(uint32(fd)), nil |
560 | 631 | } |
561 | 632 | |
562 | func minimalBTF(bo binary.ByteOrder) []byte { | |
633 | func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte { | |
563 | 634 | const minHeaderLength = 24 |
564 | 635 | |
636 | typesLen := uint32(binary.Size(types)) | |
637 | header := btfHeader{ | |
638 | Magic: btfMagic, | |
639 | Version: 1, | |
640 | HdrLen: minHeaderLength, | |
641 | TypeOff: 0, | |
642 | TypeLen: typesLen, | |
643 | StringOff: typesLen, | |
644 | StringLen: uint32(len(strings)), | |
645 | } | |
646 | ||
647 | buf := new(bytes.Buffer) | |
648 | _ = binary.Write(buf, bo, &header) | |
649 | _ = binary.Write(buf, bo, types) | |
650 | buf.Write(strings) | |
651 | ||
652 | return buf.Bytes() | |
653 | } | |
654 | ||
655 | var haveBTF = internal.FeatureTest("BTF", "5.1", func() (bool, error) { | |
565 | 656 | var ( |
566 | 657 | types struct { |
567 | 658 | Integer btfType |
568 | 659 | Var btfType |
569 | 660 | btfVar struct{ Linkage uint32 } |
570 | 661 | } |
571 | typLen = uint32(binary.Size(&types)) | |
572 | 662 | strings = []byte{0, 'a', 0} |
573 | header = btfHeader{ | |
574 | Magic: btfMagic, | |
575 | Version: 1, | |
576 | HdrLen: minHeaderLength, | |
577 | TypeOff: 0, | |
578 | TypeLen: typLen, | |
579 | StringOff: typLen, | |
580 | StringLen: uint32(len(strings)), | |
581 | } | |
582 | 663 | ) |
583 | 664 | |
584 | 665 | // We use a BTF_KIND_VAR here, to make sure that |
589 | 670 | types.Var.SetKind(kindVar) |
590 | 671 | types.Var.SizeType = 1 |
591 | 672 | |
592 | buf := new(bytes.Buffer) | |
593 | _ = binary.Write(buf, bo, &header) | |
594 | _ = binary.Write(buf, bo, &types) | |
595 | buf.Write(strings) | |
596 | ||
597 | return buf.Bytes() | |
598 | } | |
599 | ||
600 | var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool { | |
601 | btf := minimalBTF(internal.NativeEndian) | |
673 | btf := marshalBTF(&types, strings, internal.NativeEndian) | |
674 | ||
602 | 675 | fd, err := bpfLoadBTF(&bpfLoadBTFAttr{ |
603 | 676 | btf: internal.NewSlicePointer(btf), |
604 | 677 | btfSize: uint32(len(btf)), |
608 | 681 | } |
609 | 682 | // Check for EINVAL specifically, rather than err != nil since we |
610 | 683 | // otherwise misdetect due to insufficient permissions. |
611 | return !xerrors.Is(err, unix.EINVAL) | |
684 | return !errors.Is(err, unix.EINVAL), nil | |
612 | 685 | }) |
686 | ||
687 | var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() (bool, error) { | |
688 | var ( | |
689 | types struct { | |
690 | FuncProto btfType | |
691 | Func btfType | |
692 | } | |
693 | strings = []byte{0, 'a', 0} | |
694 | ) | |
695 | ||
696 | types.FuncProto.SetKind(kindFuncProto) | |
697 | types.Func.SetKind(kindFunc) | |
698 | types.Func.SizeType = 1 // aka FuncProto | |
699 | types.Func.NameOff = 1 | |
700 | types.Func.SetLinkage(linkageGlobal) | |
701 | ||
702 | btf := marshalBTF(&types, strings, internal.NativeEndian) | |
703 | ||
704 | fd, err := bpfLoadBTF(&bpfLoadBTFAttr{ | |
705 | btf: internal.NewSlicePointer(btf), | |
706 | btfSize: uint32(len(btf)), | |
707 | }) | |
708 | if err == nil { | |
709 | fd.Close() | |
710 | } | |
711 | ||
712 | // Check for EINVAL specifically, rather than err != nil since we | |
713 | // otherwise misdetect due to insufficient permissions. | |
714 | return !errors.Is(err, unix.EINVAL), nil | |
715 | }) |
3 | 3 | "bytes" |
4 | 4 | "compress/gzip" |
5 | 5 | "encoding/binary" |
6 | "errors" | |
6 | 7 | "fmt" |
7 | 8 | "io/ioutil" |
8 | 9 | "os" |
9 | 10 | "testing" |
10 | 11 | |
12 | "github.com/cilium/ebpf/internal" | |
11 | 13 | "github.com/cilium/ebpf/internal/testutils" |
12 | 14 | ) |
13 | 15 | |
28 | 30 | t.Fatal(err) |
29 | 31 | } |
30 | 32 | |
31 | _, _, err = parseBTF(bytes.NewReader(buf), binary.LittleEndian) | |
33 | _, err = loadNakedSpec(bytes.NewReader(buf), binary.LittleEndian, nil, nil) | |
32 | 34 | if err != nil { |
33 | 35 | t.Fatal("Can't load BTF:", err) |
34 | 36 | } |
35 | 37 | } |
36 | 38 | |
37 | 39 | func TestParseCurrentKernelBTF(t *testing.T) { |
38 | if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) { | |
39 | t.Skip("/sys/kernel/btf/vmlinux is not available") | |
40 | spec, err := loadKernelSpec() | |
41 | if errors.Is(err, ErrNotFound) { | |
42 | t.Skip("BTF is not available:", err) | |
43 | } | |
44 | if err != nil { | |
45 | t.Fatal("Can't load BTF:", err) | |
40 | 46 | } |
41 | 47 | |
42 | fh, err := os.Open("/sys/kernel/btf/vmlinux") | |
43 | if err != nil { | |
44 | t.Fatal(err) | |
45 | } | |
46 | defer fh.Close() | |
47 | ||
48 | _, _, err = parseBTF(fh, binary.LittleEndian) | |
49 | if err != nil { | |
50 | t.Fatal("Can't load BTF:", err) | |
48 | if len(spec.types) == 0 { | |
49 | t.Fatal("Empty kernel BTF") | |
51 | 50 | } |
52 | 51 | } |
53 | 52 | |
54 | 53 | func TestLoadSpecFromElf(t *testing.T) { |
55 | fh, err := os.Open("../../testdata/loader-clang-9.elf") | |
56 | if err != nil { | |
57 | t.Fatal(err) | |
58 | } | |
59 | defer fh.Close() | |
54 | testutils.TestFiles(t, "../../testdata/loader-clang-9-*.elf", func(t *testing.T, file string) { | |
55 | fh, err := os.Open(file) | |
56 | if err != nil { | |
57 | t.Fatal(err) | |
58 | } | |
59 | defer fh.Close() | |
60 | 60 | |
61 | spec, err := LoadSpecFromReader(fh) | |
62 | if err != nil { | |
63 | t.Fatal("Can't load BTF:", err) | |
64 | } | |
65 | ||
66 | if spec == nil { | |
67 | t.Error("No BTF found in ELF") | |
68 | } | |
69 | ||
70 | if sec, err := spec.Program("xdp", 1); err != nil { | |
71 | t.Error("Can't get BTF for the xdp section:", err) | |
72 | } else if sec == nil { | |
73 | t.Error("Missing BTF for the xdp section") | |
74 | } | |
75 | ||
76 | if sec, err := spec.Program("socket", 1); err != nil { | |
77 | t.Error("Can't get BTF for the socket section:", err) | |
78 | } else if sec == nil { | |
79 | t.Error("Missing BTF for the socket section") | |
80 | } | |
81 | ||
82 | var bpfMapDef Struct | |
83 | if err := spec.FindType("bpf_map_def", &bpfMapDef); err != nil { | |
84 | t.Fatal("Can't find bpf_map_def:", err) | |
85 | } | |
86 | ||
87 | if name := bpfMapDef.Name; name != "bpf_map_def" { | |
88 | t.Error("struct bpf_map_def has incorrect name:", name) | |
89 | } | |
90 | ||
91 | t.Run("Handle", func(t *testing.T) { | |
92 | btf, err := NewHandle(spec) | |
93 | testutils.SkipIfNotSupported(t, err) | |
61 | spec, err := LoadSpecFromReader(fh) | |
94 | 62 | if err != nil { |
95 | 63 | t.Fatal("Can't load BTF:", err) |
96 | 64 | } |
97 | defer btf.Close() | |
65 | ||
66 | if spec == nil { | |
67 | t.Error("No BTF found in ELF") | |
68 | } | |
69 | ||
70 | if sec, err := spec.Program("xdp", 1); err != nil { | |
71 | t.Error("Can't get BTF for the xdp section:", err) | |
72 | } else if sec == nil { | |
73 | t.Error("Missing BTF for the xdp section") | |
74 | } | |
75 | ||
76 | if sec, err := spec.Program("socket", 1); err != nil { | |
77 | t.Error("Can't get BTF for the socket section:", err) | |
78 | } else if sec == nil { | |
79 | t.Error("Missing BTF for the socket section") | |
80 | } | |
81 | ||
82 | var bpfMapDef Struct | |
83 | if err := spec.FindType("bpf_map_def", &bpfMapDef); err != nil { | |
84 | t.Error("Can't find bpf_map_def:", err) | |
85 | } | |
86 | ||
87 | var tmp Void | |
88 | if err := spec.FindType("totally_bogus_type", &tmp); !errors.Is(err, ErrNotFound) { | |
89 | t.Error("FindType doesn't return ErrNotFound:", err) | |
90 | } | |
91 | ||
92 | if spec.byteOrder != internal.NativeEndian { | |
93 | return | |
94 | } | |
95 | ||
96 | t.Run("Handle", func(t *testing.T) { | |
97 | btf, err := NewHandle(spec) | |
98 | testutils.SkipIfNotSupported(t, err) | |
99 | if err != nil { | |
100 | t.Fatal("Can't load BTF:", err) | |
101 | } | |
102 | defer btf.Close() | |
103 | }) | |
98 | 104 | }) |
99 | 105 | } |
100 | 106 | |
101 | 107 | func TestHaveBTF(t *testing.T) { |
102 | 108 | testutils.CheckFeatureTest(t, haveBTF) |
109 | } | |
110 | ||
111 | func TestHaveFuncLinkage(t *testing.T) { | |
112 | testutils.CheckFeatureTest(t, haveFuncLinkage) | |
103 | 113 | } |
104 | 114 | |
105 | 115 | func ExampleSpec_FindType() { |
3 | 3 | "encoding/binary" |
4 | 4 | "fmt" |
5 | 5 | "io" |
6 | ||
7 | "golang.org/x/xerrors" | |
8 | 6 | ) |
9 | 7 | |
10 | 8 | // btfKind describes a Type. |
32 | 30 | kindDatasec |
33 | 31 | ) |
34 | 32 | |
33 | type btfFuncLinkage uint8 | |
34 | ||
35 | const ( | |
36 | linkageStatic btfFuncLinkage = iota | |
37 | linkageGlobal | |
38 | linkageExtern | |
39 | ) | |
40 | ||
35 | 41 | const ( |
36 | 42 | btfTypeKindShift = 24 |
37 | 43 | btfTypeKindLen = 4 |
43 | 49 | type btfType struct { |
44 | 50 | NameOff uint32 |
45 | 51 | /* "info" bits arrangement |
46 | * bits 0-15: vlen (e.g. # of struct's members) | |
52 | * bits 0-15: vlen (e.g. # of struct's members), linkage | |
47 | 53 | * bits 16-23: unused |
48 | 54 | * bits 24-27: kind (e.g. int, ptr, array...etc) |
49 | 55 | * bits 28-30: unused |
129 | 135 | bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift) |
130 | 136 | } |
131 | 137 | |
138 | func (bt *btfType) Linkage() btfFuncLinkage { | |
139 | return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift)) | |
140 | } | |
141 | ||
142 | func (bt *btfType) SetLinkage(linkage btfFuncLinkage) { | |
143 | bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift) | |
144 | } | |
145 | ||
132 | 146 | func (bt *btfType) Type() TypeID { |
133 | 147 | // TODO: Panic here if wrong kind? |
134 | 148 | return TypeID(bt.SizeType) |
176 | 190 | |
177 | 191 | type btfVariable struct { |
178 | 192 | Linkage uint32 |
193 | } | |
194 | ||
195 | type btfEnum struct { | |
196 | NameOff uint32 | |
197 | Val int32 | |
198 | } | |
199 | ||
200 | type btfParam struct { | |
201 | NameOff uint32 | |
202 | Type TypeID | |
179 | 203 | } |
180 | 204 | |
181 | 205 | func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) { |
188 | 212 | if err := binary.Read(r, bo, &header); err == io.EOF { |
189 | 213 | return types, nil |
190 | 214 | } else if err != nil { |
191 | return nil, xerrors.Errorf("can't read type info for id %v: %v", id, err) | |
215 | return nil, fmt.Errorf("can't read type info for id %v: %v", id, err) | |
192 | 216 | } |
193 | 217 | |
194 | 218 | var data interface{} |
195 | 219 | switch header.Kind() { |
196 | 220 | case kindInt: |
197 | // sizeof(uint32) | |
198 | data = make([]byte, 4) | |
221 | data = new(uint32) | |
199 | 222 | case kindPointer: |
200 | 223 | case kindArray: |
201 | 224 | data = new(btfArray) |
204 | 227 | case kindUnion: |
205 | 228 | data = make([]btfMember, header.Vlen()) |
206 | 229 | case kindEnum: |
207 | // sizeof(struct btf_enum) | |
208 | data = make([]byte, header.Vlen()*4*2) | |
230 | data = make([]btfEnum, header.Vlen()) | |
209 | 231 | case kindForward: |
210 | 232 | case kindTypedef: |
211 | 233 | case kindVolatile: |
213 | 235 | case kindRestrict: |
214 | 236 | case kindFunc: |
215 | 237 | case kindFuncProto: |
216 | // sizeof(struct btf_param) | |
217 | data = make([]byte, header.Vlen()*4*2) | |
238 | data = make([]btfParam, header.Vlen()) | |
218 | 239 | case kindVar: |
219 | 240 | data = new(btfVariable) |
220 | 241 | case kindDatasec: |
221 | 242 | data = make([]btfVarSecinfo, header.Vlen()) |
222 | 243 | default: |
223 | return nil, xerrors.Errorf("type id %v: unknown kind: %v", id, header.Kind()) | |
244 | return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind()) | |
224 | 245 | } |
225 | 246 | |
226 | 247 | if data == nil { |
229 | 250 | } |
230 | 251 | |
231 | 252 | if err := binary.Read(r, bo, data); err != nil { |
232 | return nil, xerrors.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err) | |
253 | return nil, fmt.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err) | |
233 | 254 | } |
234 | 255 | |
235 | 256 | types = append(types, rawType{header, data}) |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | 4 | "encoding/binary" |
5 | "errors" | |
6 | "fmt" | |
5 | 7 | "io" |
6 | 8 | "io/ioutil" |
7 | 9 | |
8 | 10 | "github.com/cilium/ebpf/asm" |
9 | 11 | "github.com/cilium/ebpf/internal" |
10 | ||
11 | "golang.org/x/xerrors" | |
12 | 12 | ) |
13 | 13 | |
14 | 14 | type btfExtHeader struct { |
26 | 26 | func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, err error) { |
27 | 27 | var header btfExtHeader |
28 | 28 | if err := binary.Read(r, bo, &header); err != nil { |
29 | return nil, nil, xerrors.Errorf("can't read header: %v", err) | |
29 | return nil, nil, fmt.Errorf("can't read header: %v", err) | |
30 | 30 | } |
31 | 31 | |
32 | 32 | if header.Magic != btfMagic { |
33 | return nil, nil, xerrors.Errorf("incorrect magic value %v", header.Magic) | |
33 | return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic) | |
34 | 34 | } |
35 | 35 | |
36 | 36 | if header.Version != 1 { |
37 | return nil, nil, xerrors.Errorf("unexpected version %v", header.Version) | |
37 | return nil, nil, fmt.Errorf("unexpected version %v", header.Version) | |
38 | 38 | } |
39 | 39 | |
40 | 40 | if header.Flags != 0 { |
41 | return nil, nil, xerrors.Errorf("unsupported flags %v", header.Flags) | |
41 | return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags) | |
42 | 42 | } |
43 | 43 | |
44 | 44 | remainder := int64(header.HdrLen) - int64(binary.Size(&header)) |
45 | 45 | if remainder < 0 { |
46 | return nil, nil, xerrors.New("header is too short") | |
46 | return nil, nil, errors.New("header is too short") | |
47 | 47 | } |
48 | 48 | |
49 | 49 | // Of course, the .BTF.ext header has different semantics than the |
50 | 50 | // .BTF ext header. We need to ignore non-null values. |
51 | 51 | _, err = io.CopyN(ioutil.Discard, r, remainder) |
52 | 52 | if err != nil { |
53 | return nil, nil, xerrors.Errorf("header padding: %v", err) | |
53 | return nil, nil, fmt.Errorf("header padding: %v", err) | |
54 | 54 | } |
55 | 55 | |
56 | 56 | if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil { |
57 | return nil, nil, xerrors.Errorf("can't seek to function info section: %v", err) | |
57 | return nil, nil, fmt.Errorf("can't seek to function info section: %v", err) | |
58 | 58 | } |
59 | 59 | |
60 | 60 | funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings) |
61 | 61 | if err != nil { |
62 | return nil, nil, xerrors.Errorf("function info: %w", err) | |
62 | return nil, nil, fmt.Errorf("function info: %w", err) | |
63 | 63 | } |
64 | 64 | |
65 | 65 | if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil { |
66 | return nil, nil, xerrors.Errorf("can't seek to line info section: %v", err) | |
66 | return nil, nil, fmt.Errorf("can't seek to line info section: %v", err) | |
67 | 67 | } |
68 | 68 | |
69 | 69 | lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings) |
70 | 70 | if err != nil { |
71 | return nil, nil, xerrors.Errorf("line info: %w", err) | |
71 | return nil, nil, fmt.Errorf("line info: %w", err) | |
72 | 72 | } |
73 | 73 | |
74 | 74 | return funcInfo, lineInfo, nil |
91 | 91 | |
92 | 92 | func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) { |
93 | 93 | if other.recordSize != ei.recordSize { |
94 | return extInfo{}, xerrors.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize) | |
94 | return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize) | |
95 | 95 | } |
96 | 96 | |
97 | 97 | records := make([]extInfoRecord, 0, len(ei.records)+len(other.records)) |
116 | 116 | // while the ELF tracks it in bytes. |
117 | 117 | insnOff := uint32(info.InsnOff / asm.InstructionSize) |
118 | 118 | if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil { |
119 | return nil, xerrors.Errorf("can't write instruction offset: %v", err) | |
119 | return nil, fmt.Errorf("can't write instruction offset: %v", err) | |
120 | 120 | } |
121 | 121 | |
122 | 122 | buf.Write(info.Opaque) |
128 | 128 | func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) { |
129 | 129 | var recordSize uint32 |
130 | 130 | if err := binary.Read(r, bo, &recordSize); err != nil { |
131 | return nil, xerrors.Errorf("can't read record size: %v", err) | |
131 | return nil, fmt.Errorf("can't read record size: %v", err) | |
132 | 132 | } |
133 | 133 | |
134 | 134 | if recordSize < 4 { |
135 | 135 | // Need at least insnOff |
136 | return nil, xerrors.New("record size too short") | |
136 | return nil, errors.New("record size too short") | |
137 | 137 | } |
138 | 138 | |
139 | 139 | result := make(map[string]extInfo) |
142 | 142 | if err := binary.Read(r, bo, &infoHeader); err == io.EOF { |
143 | 143 | return result, nil |
144 | 144 | } else if err != nil { |
145 | return nil, xerrors.Errorf("can't read ext info header: %v", err) | |
145 | return nil, fmt.Errorf("can't read ext info header: %v", err) | |
146 | 146 | } |
147 | 147 | |
148 | 148 | secName, err := strings.Lookup(infoHeader.SecNameOff) |
149 | 149 | if err != nil { |
150 | return nil, xerrors.Errorf("can't get section name: %w", err) | |
150 | return nil, fmt.Errorf("can't get section name: %w", err) | |
151 | 151 | } |
152 | 152 | |
153 | 153 | if infoHeader.NumInfo == 0 { |
154 | return nil, xerrors.Errorf("section %s has invalid number of records", secName) | |
154 | return nil, fmt.Errorf("section %s has invalid number of records", secName) | |
155 | 155 | } |
156 | 156 | |
157 | 157 | var records []extInfoRecord |
158 | 158 | for i := uint32(0); i < infoHeader.NumInfo; i++ { |
159 | 159 | var byteOff uint32 |
160 | 160 | if err := binary.Read(r, bo, &byteOff); err != nil { |
161 | return nil, xerrors.Errorf("section %v: can't read extended info offset: %v", secName, err) | |
161 | return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err) | |
162 | 162 | } |
163 | 163 | |
164 | 164 | buf := make([]byte, int(recordSize-4)) |
165 | 165 | if _, err := io.ReadFull(r, buf); err != nil { |
166 | return nil, xerrors.Errorf("section %v: can't read record: %v", secName, err) | |
166 | return nil, fmt.Errorf("section %v: can't read record: %v", secName, err) | |
167 | 167 | } |
168 | 168 | |
169 | 169 | if byteOff%asm.InstructionSize != 0 { |
170 | return nil, xerrors.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff) | |
170 | return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff) | |
171 | 171 | } |
172 | 172 | |
173 | 173 | records = append(records, extInfoRecord{uint64(byteOff), buf}) |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "errors" | |
5 | "fmt" | |
4 | 6 | "io" |
5 | 7 | "io/ioutil" |
6 | ||
7 | "golang.org/x/xerrors" | |
8 | 8 | ) |
9 | 9 | |
10 | 10 | type stringTable []byte |
12 | 12 | func readStringTable(r io.Reader) (stringTable, error) { |
13 | 13 | contents, err := ioutil.ReadAll(r) |
14 | 14 | if err != nil { |
15 | return nil, xerrors.Errorf("can't read string table: %v", err) | |
15 | return nil, fmt.Errorf("can't read string table: %v", err) | |
16 | 16 | } |
17 | 17 | |
18 | 18 | if len(contents) < 1 { |
19 | return nil, xerrors.New("string table is empty") | |
19 | return nil, errors.New("string table is empty") | |
20 | 20 | } |
21 | 21 | |
22 | 22 | if contents[0] != '\x00' { |
23 | return nil, xerrors.New("first item in string table is non-empty") | |
23 | return nil, errors.New("first item in string table is non-empty") | |
24 | 24 | } |
25 | 25 | |
26 | 26 | if contents[len(contents)-1] != '\x00' { |
27 | return nil, xerrors.New("string table isn't null terminated") | |
27 | return nil, errors.New("string table isn't null terminated") | |
28 | 28 | } |
29 | 29 | |
30 | 30 | return stringTable(contents), nil |
32 | 32 | |
33 | 33 | func (st stringTable) Lookup(offset uint32) (string, error) { |
34 | 34 | if int64(offset) > int64(^uint(0)>>1) { |
35 | return "", xerrors.Errorf("offset %d overflows int", offset) | |
35 | return "", fmt.Errorf("offset %d overflows int", offset) | |
36 | 36 | } |
37 | 37 | |
38 | 38 | pos := int(offset) |
39 | 39 | if pos >= len(st) { |
40 | return "", xerrors.Errorf("offset %d is out of bounds", offset) | |
40 | return "", fmt.Errorf("offset %d is out of bounds", offset) | |
41 | 41 | } |
42 | 42 | |
43 | 43 | if pos > 0 && st[pos-1] != '\x00' { |
44 | return "", xerrors.Errorf("offset %d isn't start of a string", offset) | |
44 | return "", fmt.Errorf("offset %d isn't start of a string", offset) | |
45 | 45 | } |
46 | 46 | |
47 | 47 | str := st[pos:] |
48 | 48 | end := bytes.IndexByte(str, '\x00') |
49 | 49 | if end == -1 { |
50 | return "", xerrors.Errorf("offset %d isn't null terminated", offset) | |
50 | return "", fmt.Errorf("offset %d isn't null terminated", offset) | |
51 | 51 | } |
52 | 52 | |
53 | 53 | return string(str[:end]), nil |
0 | 0 | package btf |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
4 | "fmt" | |
3 | 5 | "math" |
4 | ||
5 | "golang.org/x/xerrors" | |
6 | 6 | ) |
7 | 7 | |
8 | 8 | const maxTypeDepth = 32 |
37 | 37 | // Void is the unit type of BTF. |
38 | 38 | type Void struct{} |
39 | 39 | |
40 | func (v Void) ID() TypeID { return 0 } | |
41 | func (v Void) copy() Type { return Void{} } | |
42 | func (v Void) walk(*copyStack) {} | |
40 | func (v *Void) ID() TypeID { return 0 } | |
41 | func (v *Void) size() uint32 { return 0 } | |
42 | func (v *Void) copy() Type { return (*Void)(nil) } | |
43 | func (v *Void) walk(*copyStack) {} | |
43 | 44 | |
44 | 45 | // Int is an integer of a given length. |
45 | 46 | type Int struct { |
309 | 310 | switch v := typ.(type) { |
310 | 311 | case *Array: |
311 | 312 | if n > 0 && int64(v.Nelems) > math.MaxInt64/n { |
312 | return 0, xerrors.New("overflow") | |
313 | return 0, errors.New("overflow") | |
313 | 314 | } |
314 | 315 | |
315 | 316 | // Arrays may be of zero length, which allows |
335 | 336 | continue |
336 | 337 | |
337 | 338 | default: |
338 | return 0, xerrors.Errorf("unrecognized type %T", typ) | |
339 | return 0, fmt.Errorf("unrecognized type %T", typ) | |
339 | 340 | } |
340 | 341 | |
341 | 342 | if n > 0 && elem > math.MaxInt64/n { |
342 | return 0, xerrors.New("overflow") | |
343 | return 0, errors.New("overflow") | |
343 | 344 | } |
344 | 345 | |
345 | 346 | size := n * elem |
346 | 347 | if int64(int(size)) != size { |
347 | return 0, xerrors.New("overflow") | |
348 | return 0, errors.New("overflow") | |
348 | 349 | } |
349 | 350 | |
350 | 351 | return int(size), nil |
351 | 352 | } |
352 | 353 | |
353 | return 0, xerrors.New("exceeded type depth") | |
354 | return 0, errors.New("exceeded type depth") | |
354 | 355 | } |
355 | 356 | |
356 | 357 | // copy a Type recursively. |
432 | 433 | for i, btfMember := range raw { |
433 | 434 | name, err := rawStrings.LookupName(btfMember.NameOff) |
434 | 435 | if err != nil { |
435 | return nil, xerrors.Errorf("can't get name for member %d: %w", i, err) | |
436 | return nil, fmt.Errorf("can't get name for member %d: %w", i, err) | |
436 | 437 | } |
437 | 438 | members = append(members, Member{ |
438 | 439 | Name: name, |
446 | 447 | } |
447 | 448 | |
448 | 449 | types := make([]Type, 0, len(rawTypes)) |
449 | types = append(types, Void{}) | |
450 | types = append(types, (*Void)(nil)) | |
450 | 451 | namedTypes = make(map[string][]Type) |
451 | 452 | |
452 | 453 | for i, raw := range rawTypes { |
459 | 460 | |
460 | 461 | name, err := rawStrings.LookupName(raw.NameOff) |
461 | 462 | if err != nil { |
462 | return nil, xerrors.Errorf("can't get name for type id %d: %w", id, err) | |
463 | return nil, fmt.Errorf("can't get name for type id %d: %w", id, err) | |
463 | 464 | } |
464 | 465 | |
465 | 466 | switch raw.Kind() { |
483 | 484 | case kindStruct: |
484 | 485 | members, err := convertMembers(raw.data.([]btfMember)) |
485 | 486 | if err != nil { |
486 | return nil, xerrors.Errorf("struct %s (id %d): %w", name, id, err) | |
487 | return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err) | |
487 | 488 | } |
488 | 489 | typ = &Struct{id, name, raw.Size(), members} |
489 | 490 | |
490 | 491 | case kindUnion: |
491 | 492 | members, err := convertMembers(raw.data.([]btfMember)) |
492 | 493 | if err != nil { |
493 | return nil, xerrors.Errorf("union %s (id %d): %w", name, id, err) | |
494 | return nil, fmt.Errorf("union %s (id %d): %w", name, id, err) | |
494 | 495 | } |
495 | 496 | typ = &Union{id, name, raw.Size(), members} |
496 | 497 | |
550 | 551 | typ = &Datasec{id, name, raw.SizeType, vars} |
551 | 552 | |
552 | 553 | default: |
553 | return nil, xerrors.Errorf("type id %d: unknown kind: %v", id, raw.Kind()) | |
554 | return nil, fmt.Errorf("type id %d: unknown kind: %v", id, raw.Kind()) | |
554 | 555 | } |
555 | 556 | |
556 | 557 | types = append(types, typ) |
565 | 566 | for _, fixup := range fixups { |
566 | 567 | i := int(fixup.id) |
567 | 568 | if i >= len(types) { |
568 | return nil, xerrors.Errorf("reference to invalid type id: %d", fixup.id) | |
569 | return nil, fmt.Errorf("reference to invalid type id: %d", fixup.id) | |
569 | 570 | } |
570 | 571 | |
571 | 572 | // Default void (id 0) to unknown |
575 | 576 | } |
576 | 577 | |
577 | 578 | if expected := fixup.expectedKind; expected != kindUnknown && rawKind != expected { |
578 | return nil, xerrors.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind) | |
579 | return nil, fmt.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind) | |
579 | 580 | } |
580 | 581 | |
581 | 582 | *fixup.typ = types[i] |
0 | 0 | package btf |
1 | 1 | |
2 | import "testing" | |
3 | ||
4 | import "fmt" | |
2 | import ( | |
3 | "fmt" | |
4 | "testing" | |
5 | ) | |
5 | 6 | |
6 | 7 | func TestSizeof(t *testing.T) { |
7 | 8 | testcases := []struct { |
8 | 9 | size int |
9 | 10 | typ Type |
10 | 11 | }{ |
12 | {0, (*Void)(nil)}, | |
11 | 13 | {1, &Int{Size: 1}}, |
12 | 14 | {4, &Enum{}}, |
13 | {0, &Array{Type: &Pointer{Target: Void{}}, Nelems: 0}}, | |
15 | {0, &Array{Type: &Pointer{Target: (*Void)(nil)}, Nelems: 0}}, | |
14 | 16 | {12, &Array{Type: &Enum{}, Nelems: 3}}, |
15 | 17 | } |
16 | 18 | |
29 | 31 | } |
30 | 32 | |
31 | 33 | func TestCopyType(t *testing.T) { |
32 | _ = copyType(Void{}) | |
34 | _ = copyType((*Void)(nil)) | |
33 | 35 | |
34 | 36 | in := &Int{Size: 4} |
35 | 37 | out := copyType(in) |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "errors" | |
4 | 5 | "fmt" |
5 | 6 | "strings" |
6 | 7 | |
7 | 8 | "github.com/cilium/ebpf/internal/unix" |
8 | "golang.org/x/xerrors" | |
9 | 9 | ) |
10 | 10 | |
11 | 11 | // ErrorWithLog returns an error that includes logs from the |
15 | 15 | // the log. It is used to check for truncation of the output. |
16 | 16 | func ErrorWithLog(err error, log []byte, logErr error) error { |
17 | 17 | logStr := strings.Trim(CString(log), "\t\r\n ") |
18 | if xerrors.Is(logErr, unix.ENOSPC) { | |
18 | if errors.Is(logErr, unix.ENOSPC) { | |
19 | 19 | logStr += " (truncated...)" |
20 | 20 | } |
21 | 21 |
0 | 0 | package internal |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
4 | "fmt" | |
5 | "os" | |
3 | 6 | "runtime" |
4 | 7 | "strconv" |
5 | 8 | |
6 | 9 | "github.com/cilium/ebpf/internal/unix" |
7 | ||
8 | "golang.org/x/xerrors" | |
9 | 10 | ) |
10 | 11 | |
11 | var ErrClosedFd = xerrors.New("use of closed file descriptor") | |
12 | var ErrClosedFd = errors.New("use of closed file descriptor") | |
12 | 13 | |
13 | 14 | type FD struct { |
14 | 15 | raw int64 |
55 | 56 | |
56 | 57 | dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0) |
57 | 58 | if err != nil { |
58 | return nil, xerrors.Errorf("can't dup fd: %v", err) | |
59 | return nil, fmt.Errorf("can't dup fd: %v", err) | |
59 | 60 | } |
60 | 61 | |
61 | 62 | return NewFD(uint32(dup)), nil |
62 | 63 | } |
64 | ||
65 | func (fd *FD) File(name string) *os.File { | |
66 | fd.Forget() | |
67 | return os.NewFile(uintptr(fd.raw), name) | |
68 | } |
0 | 0 | package internal |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
3 | 4 | "fmt" |
4 | 5 | "sync" |
5 | ||
6 | "golang.org/x/xerrors" | |
7 | 6 | ) |
8 | 7 | |
9 | 8 | // ErrNotSupported indicates that a feature is not supported by the current kernel. |
10 | var ErrNotSupported = xerrors.New("not supported") | |
9 | var ErrNotSupported = errors.New("not supported") | |
11 | 10 | |
12 | 11 | // UnsupportedFeatureError is returned by FeatureTest() functions. |
13 | 12 | type UnsupportedFeatureError struct { |
28 | 27 | return target == ErrNotSupported |
29 | 28 | } |
30 | 29 | |
30 | type featureTest struct { | |
31 | sync.Mutex | |
32 | successful bool | |
33 | result error | |
34 | } | |
35 | ||
36 | // FeatureTestFn is used to determine whether the kernel supports | |
37 | // a certain feature. | |
38 | // | |
39 | // The return values have the following semantics: | |
40 | // | |
41 | // err != nil: the test couldn't be executed | |
42 | // err == nil && available: the feature is available | |
43 | // err == nil && !available: the feature isn't available | |
44 | type FeatureTestFn func() (available bool, err error) | |
45 | ||
31 | 46 | // FeatureTest wraps a function so that it is run at most once. |
32 | 47 | // |
33 | 48 | // name should identify the tested feature, while version must be in the |
34 | 49 | // form Major.Minor[.Patch]. |
35 | 50 | // |
36 | // Returns a descriptive UnsupportedFeatureError if the feature is not available. | |
37 | func FeatureTest(name, version string, fn func() bool) func() error { | |
51 | // Returns an error wrapping ErrNotSupported if the feature is not supported. | |
52 | func FeatureTest(name, version string, fn FeatureTestFn) func() error { | |
38 | 53 | v, err := NewVersion(version) |
39 | 54 | if err != nil { |
40 | 55 | return func() error { return err } |
41 | 56 | } |
42 | 57 | |
43 | var ( | |
44 | once sync.Once | |
45 | result error | |
46 | ) | |
58 | ft := new(featureTest) | |
59 | return func() error { | |
60 | ft.Lock() | |
61 | defer ft.Unlock() | |
47 | 62 | |
48 | return func() error { | |
49 | once.Do(func() { | |
50 | if !fn() { | |
51 | result = &UnsupportedFeatureError{ | |
52 | MinimumVersion: v, | |
53 | Name: name, | |
54 | } | |
63 | if ft.successful { | |
64 | return ft.result | |
65 | } | |
66 | ||
67 | available, err := fn() | |
68 | if errors.Is(err, ErrNotSupported) { | |
69 | // The feature test aborted because a dependent feature | |
70 | // is missing, which we should cache. | |
71 | available = false | |
72 | } else if err != nil { | |
73 | // We couldn't execute the feature test to a point | |
74 | // where it could make a determination. | |
75 | // Don't cache the result, just return it. | |
76 | return fmt.Errorf("can't detect support for %s: %w", name, err) | |
77 | } | |
78 | ||
79 | ft.successful = true | |
80 | if !available { | |
81 | ft.result = &UnsupportedFeatureError{ | |
82 | MinimumVersion: v, | |
83 | Name: name, | |
55 | 84 | } |
56 | }) | |
57 | return result | |
85 | } | |
86 | return ft.result | |
58 | 87 | } |
59 | 88 | } |
60 | 89 | |
68 | 97 | var major, minor, patch uint16 |
69 | 98 | n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch) |
70 | 99 | if n < 2 { |
71 | return Version{}, xerrors.Errorf("invalid version: %s", ver) | |
100 | return Version{}, fmt.Errorf("invalid version: %s", ver) | |
72 | 101 | } |
73 | 102 | return Version{major, minor, patch}, nil |
74 | 103 | } |
0 | 0 | package internal |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
4 | "fmt" | |
3 | 5 | "strings" |
4 | 6 | "testing" |
5 | ||
6 | "golang.org/x/xerrors" | |
7 | 7 | ) |
8 | 8 | |
9 | 9 | func TestFeatureTest(t *testing.T) { |
10 | 10 | var called bool |
11 | 11 | |
12 | fn := FeatureTest("foo", "1.0", func() bool { | |
12 | fn := FeatureTest("foo", "1.0", func() (bool, error) { | |
13 | 13 | called = true |
14 | return true | |
14 | return true, nil | |
15 | 15 | }) |
16 | 16 | |
17 | 17 | if called { |
27 | 27 | t.Error("Unexpected negative result:", err) |
28 | 28 | } |
29 | 29 | |
30 | fn = FeatureTest("bar", "2.1.1", func() bool { | |
31 | return false | |
30 | fn = FeatureTest("bar", "2.1.1", func() (bool, error) { | |
31 | return false, nil | |
32 | 32 | }) |
33 | 33 | |
34 | 34 | err = fn() |
45 | 45 | t.Error("UnsupportedFeatureError.Error doesn't contain version") |
46 | 46 | } |
47 | 47 | |
48 | if !xerrors.Is(err, ErrNotSupported) { | |
48 | if !errors.Is(err, ErrNotSupported) { | |
49 | 49 | t.Error("UnsupportedFeatureError is not ErrNotSupported") |
50 | } | |
51 | ||
52 | fn = FeatureTest("bar", "2.1.1", func() (bool, error) { | |
53 | return false, errors.New("foo") | |
54 | }) | |
55 | ||
56 | err1, err2 := fn(), fn() | |
57 | if err1 == err2 { | |
58 | t.Error("Cached result of unsuccessful execution") | |
59 | } | |
60 | ||
61 | fn = FeatureTest("bar", "2.1.1", func() (bool, error) { | |
62 | return false, fmt.Errorf("bar: %w", ErrNotSupported) | |
63 | }) | |
64 | ||
65 | err1, err2 = fn(), fn() | |
66 | if err1 != err2 { | |
67 | t.Error("Didn't cache an error wrapping ErrNotSupported") | |
50 | 68 | } |
51 | 69 | } |
52 | 70 |
0 | 0 | package internal |
1 | 1 | |
2 | import "golang.org/x/xerrors" | |
2 | import "errors" | |
3 | 3 | |
4 | 4 | // DiscardZeroes makes sure that all written bytes are zero |
5 | 5 | // before discarding them. |
8 | 8 | func (DiscardZeroes) Write(p []byte) (int, error) { |
9 | 9 | for _, b := range p { |
10 | 10 | if b != 0 { |
11 | return 0, xerrors.New("encountered non-zero byte") | |
11 | return 0, errors.New("encountered non-zero byte") | |
12 | 12 | } |
13 | 13 | } |
14 | 14 | return len(p), nil |
0 | 0 | package internal |
1 | 1 | |
2 | 2 | import ( |
3 | "fmt" | |
4 | "path/filepath" | |
3 | 5 | "runtime" |
4 | 6 | "unsafe" |
5 | 7 | |
6 | 8 | "github.com/cilium/ebpf/internal/unix" |
7 | 9 | ) |
8 | 10 | |
11 | //go:generate stringer -output syscall_string.go -type=BPFCmd | |
12 | ||
13 | // BPFCmd identifies a subcommand of the bpf syscall. | |
14 | type BPFCmd int | |
15 | ||
16 | // Well known BPF commands. | |
17 | const ( | |
18 | BPF_MAP_CREATE BPFCmd = iota | |
19 | BPF_MAP_LOOKUP_ELEM | |
20 | BPF_MAP_UPDATE_ELEM | |
21 | BPF_MAP_DELETE_ELEM | |
22 | BPF_MAP_GET_NEXT_KEY | |
23 | BPF_PROG_LOAD | |
24 | BPF_OBJ_PIN | |
25 | BPF_OBJ_GET | |
26 | BPF_PROG_ATTACH | |
27 | BPF_PROG_DETACH | |
28 | BPF_PROG_TEST_RUN | |
29 | BPF_PROG_GET_NEXT_ID | |
30 | BPF_MAP_GET_NEXT_ID | |
31 | BPF_PROG_GET_FD_BY_ID | |
32 | BPF_MAP_GET_FD_BY_ID | |
33 | BPF_OBJ_GET_INFO_BY_FD | |
34 | BPF_PROG_QUERY | |
35 | BPF_RAW_TRACEPOINT_OPEN | |
36 | BPF_BTF_LOAD | |
37 | BPF_BTF_GET_FD_BY_ID | |
38 | BPF_TASK_FD_QUERY | |
39 | BPF_MAP_LOOKUP_AND_DELETE_ELEM | |
40 | BPF_MAP_FREEZE | |
41 | BPF_BTF_GET_NEXT_ID | |
42 | BPF_MAP_LOOKUP_BATCH | |
43 | BPF_MAP_LOOKUP_AND_DELETE_BATCH | |
44 | BPF_MAP_UPDATE_BATCH | |
45 | BPF_MAP_DELETE_BATCH | |
46 | BPF_LINK_CREATE | |
47 | BPF_LINK_UPDATE | |
48 | BPF_LINK_GET_FD_BY_ID | |
49 | BPF_LINK_GET_NEXT_ID | |
50 | BPF_ENABLE_STATS | |
51 | BPF_ITER_CREATE | |
52 | ) | |
53 | ||
9 | 54 | // BPF wraps SYS_BPF. |
10 | 55 | // |
11 | 56 | // Any pointers contained in attr must use the Pointer type from this package. |
12 | func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) { | |
57 | func BPF(cmd BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) { | |
13 | 58 | r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size) |
14 | 59 | runtime.KeepAlive(attr) |
15 | 60 | |
20 | 65 | |
21 | 66 | return r1, err |
22 | 67 | } |
68 | ||
69 | type BPFProgAttachAttr struct { | |
70 | TargetFd uint32 | |
71 | AttachBpfFd uint32 | |
72 | AttachType uint32 | |
73 | AttachFlags uint32 | |
74 | ReplaceBpfFd uint32 | |
75 | } | |
76 | ||
77 | func BPFProgAttach(attr *BPFProgAttachAttr) error { | |
78 | _, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
79 | return err | |
80 | } | |
81 | ||
82 | type BPFProgDetachAttr struct { | |
83 | TargetFd uint32 | |
84 | AttachBpfFd uint32 | |
85 | AttachType uint32 | |
86 | } | |
87 | ||
88 | func BPFProgDetach(attr *BPFProgDetachAttr) error { | |
89 | _, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
90 | return err | |
91 | } | |
92 | ||
93 | type bpfObjAttr struct { | |
94 | fileName Pointer | |
95 | fd uint32 | |
96 | fileFlags uint32 | |
97 | } | |
98 | ||
99 | const bpfFSType = 0xcafe4a11 | |
100 | ||
101 | // BPFObjPin wraps BPF_OBJ_PIN. | |
102 | func BPFObjPin(fileName string, fd *FD) error { | |
103 | dirName := filepath.Dir(fileName) | |
104 | var statfs unix.Statfs_t | |
105 | if err := unix.Statfs(dirName, &statfs); err != nil { | |
106 | return err | |
107 | } | |
108 | if uint64(statfs.Type) != bpfFSType { | |
109 | return fmt.Errorf("%s is not on a bpf filesystem", fileName) | |
110 | } | |
111 | ||
112 | value, err := fd.Value() | |
113 | if err != nil { | |
114 | return err | |
115 | } | |
116 | ||
117 | attr := bpfObjAttr{ | |
118 | fileName: NewStringPointer(fileName), | |
119 | fd: value, | |
120 | } | |
121 | _, err = BPF(BPF_OBJ_PIN, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
122 | if err != nil { | |
123 | return fmt.Errorf("pin object %s: %w", fileName, err) | |
124 | } | |
125 | return nil | |
126 | } | |
127 | ||
128 | // BPFObjGet wraps BPF_OBJ_GET. | |
129 | func BPFObjGet(fileName string) (*FD, error) { | |
130 | attr := bpfObjAttr{ | |
131 | fileName: NewStringPointer(fileName), | |
132 | } | |
133 | ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
134 | if err != nil { | |
135 | return nil, fmt.Errorf("get object %s: %w", fileName, err) | |
136 | } | |
137 | return NewFD(uint32(ptr)), nil | |
138 | } |
0 | // Code generated by "stringer -output syscall_string.go -type=BPFCmd"; DO NOT EDIT. | |
1 | ||
2 | package internal | |
3 | ||
4 | import "strconv" | |
5 | ||
6 | func _() { | |
7 | // An "invalid array index" compiler error signifies that the constant values have changed. | |
8 | // Re-run the stringer command to generate them again. | |
9 | var x [1]struct{} | |
10 | _ = x[BPF_MAP_CREATE-0] | |
11 | _ = x[BPF_MAP_LOOKUP_ELEM-1] | |
12 | _ = x[BPF_MAP_UPDATE_ELEM-2] | |
13 | _ = x[BPF_MAP_DELETE_ELEM-3] | |
14 | _ = x[BPF_MAP_GET_NEXT_KEY-4] | |
15 | _ = x[BPF_PROG_LOAD-5] | |
16 | _ = x[BPF_OBJ_PIN-6] | |
17 | _ = x[BPF_OBJ_GET-7] | |
18 | _ = x[BPF_PROG_ATTACH-8] | |
19 | _ = x[BPF_PROG_DETACH-9] | |
20 | _ = x[BPF_PROG_TEST_RUN-10] | |
21 | _ = x[BPF_PROG_GET_NEXT_ID-11] | |
22 | _ = x[BPF_MAP_GET_NEXT_ID-12] | |
23 | _ = x[BPF_PROG_GET_FD_BY_ID-13] | |
24 | _ = x[BPF_MAP_GET_FD_BY_ID-14] | |
25 | _ = x[BPF_OBJ_GET_INFO_BY_FD-15] | |
26 | _ = x[BPF_PROG_QUERY-16] | |
27 | _ = x[BPF_RAW_TRACEPOINT_OPEN-17] | |
28 | _ = x[BPF_BTF_LOAD-18] | |
29 | _ = x[BPF_BTF_GET_FD_BY_ID-19] | |
30 | _ = x[BPF_TASK_FD_QUERY-20] | |
31 | _ = x[BPF_MAP_LOOKUP_AND_DELETE_ELEM-21] | |
32 | _ = x[BPF_MAP_FREEZE-22] | |
33 | _ = x[BPF_BTF_GET_NEXT_ID-23] | |
34 | _ = x[BPF_MAP_LOOKUP_BATCH-24] | |
35 | _ = x[BPF_MAP_LOOKUP_AND_DELETE_BATCH-25] | |
36 | _ = x[BPF_MAP_UPDATE_BATCH-26] | |
37 | _ = x[BPF_MAP_DELETE_BATCH-27] | |
38 | _ = x[BPF_LINK_CREATE-28] | |
39 | _ = x[BPF_LINK_UPDATE-29] | |
40 | _ = x[BPF_LINK_GET_FD_BY_ID-30] | |
41 | _ = x[BPF_LINK_GET_NEXT_ID-31] | |
42 | _ = x[BPF_ENABLE_STATS-32] | |
43 | _ = x[BPF_ITER_CREATE-33] | |
44 | } | |
45 | ||
46 | const _BPFCmd_name = "BPF_MAP_CREATEBPF_MAP_LOOKUP_ELEMBPF_MAP_UPDATE_ELEMBPF_MAP_DELETE_ELEMBPF_MAP_GET_NEXT_KEYBPF_PROG_LOADBPF_OBJ_PINBPF_OBJ_GETBPF_PROG_ATTACHBPF_PROG_DETACHBPF_PROG_TEST_RUNBPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_PROG_GET_FD_BY_IDBPF_MAP_GET_FD_BY_IDBPF_OBJ_GET_INFO_BY_FDBPF_PROG_QUERYBPF_RAW_TRACEPOINT_OPENBPF_BTF_LOADBPF_BTF_GET_FD_BY_IDBPF_TASK_FD_QUERYBPF_MAP_LOOKUP_AND_DELETE_ELEMBPF_MAP_FREEZEBPF_BTF_GET_NEXT_IDBPF_MAP_LOOKUP_BATCHBPF_MAP_LOOKUP_AND_DELETE_BATCHBPF_MAP_UPDATE_BATCHBPF_MAP_DELETE_BATCHBPF_LINK_CREATEBPF_LINK_UPDATEBPF_LINK_GET_FD_BY_IDBPF_LINK_GET_NEXT_IDBPF_ENABLE_STATSBPF_ITER_CREATE" | |
47 | ||
48 | var _BPFCmd_index = [...]uint16{0, 14, 33, 52, 71, 91, 104, 115, 126, 141, 156, 173, 193, 212, 233, 253, 275, 289, 312, 324, 344, 361, 391, 405, 424, 444, 475, 495, 515, 530, 545, 566, 586, 602, 617} | |
49 | ||
50 | func (i BPFCmd) String() string { | |
51 | if i < 0 || i >= BPFCmd(len(_BPFCmd_index)-1) { | |
52 | return "BPFCmd(" + strconv.FormatInt(int64(i), 10) + ")" | |
53 | } | |
54 | return _BPFCmd_name[_BPFCmd_index[i]:_BPFCmd_index[i+1]] | |
55 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "errors" | |
4 | 5 | "sync" |
5 | 6 | "testing" |
6 | 7 | |
7 | 8 | "github.com/cilium/ebpf/internal" |
8 | 9 | "github.com/cilium/ebpf/internal/unix" |
9 | "golang.org/x/xerrors" | |
10 | 10 | ) |
11 | 11 | |
12 | 12 | var ( |
42 | 42 | return |
43 | 43 | } |
44 | 44 | |
45 | ufe := err.(*internal.UnsupportedFeatureError) | |
46 | checkKernelVersion(t, ufe) | |
45 | var ufe *internal.UnsupportedFeatureError | |
46 | if errors.As(err, &ufe) { | |
47 | checkKernelVersion(t, ufe) | |
48 | } else { | |
49 | t.Error("Feature test failed:", err) | |
50 | } | |
47 | 51 | } |
48 | 52 | |
49 | 53 | func SkipIfNotSupported(tb testing.TB, err error) { |
50 | 54 | var ufe *internal.UnsupportedFeatureError |
51 | if xerrors.As(err, &ufe) { | |
55 | if errors.As(err, &ufe) { | |
52 | 56 | checkKernelVersion(tb, ufe) |
53 | 57 | tb.Skip(ufe.Error()) |
58 | } | |
59 | if errors.Is(err, internal.ErrNotSupported) { | |
60 | tb.Skip(err.Error()) | |
54 | 61 | } |
55 | 62 | } |
56 | 63 |
0 | package testutils | |
1 | ||
2 | import ( | |
3 | "path/filepath" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | // TestFiles calls fn for each file matching pattern. | |
8 | // | |
9 | // The function errors out if the pattern matches no files. | |
10 | func TestFiles(t *testing.T, pattern string, fn func(*testing.T, string)) { | |
11 | t.Helper() | |
12 | ||
13 | files, err := filepath.Glob(pattern) | |
14 | if err != nil { | |
15 | t.Fatal("Can't glob files:", err) | |
16 | } | |
17 | ||
18 | if len(files) == 0 { | |
19 | t.Fatalf("Pattern %s matched no files", pattern) | |
20 | } | |
21 | ||
22 | for _, f := range files { | |
23 | file := f // force copy | |
24 | name := filepath.Base(file) | |
25 | t.Run(name, func(t *testing.T) { | |
26 | fn(t, file) | |
27 | }) | |
28 | } | |
29 | } |
9 | 9 | |
10 | 10 | const ( |
11 | 11 | ENOENT = linux.ENOENT |
12 | EEXIST = linux.EEXIST | |
12 | 13 | EAGAIN = linux.EAGAIN |
13 | 14 | ENOSPC = linux.ENOSPC |
14 | 15 | EINVAL = linux.EINVAL |
15 | 16 | EPOLLIN = linux.EPOLLIN |
16 | 17 | EINTR = linux.EINTR |
18 | EPERM = linux.EPERM | |
17 | 19 | ESRCH = linux.ESRCH |
18 | 20 | ENODEV = linux.ENODEV |
19 | 21 | BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG |
35 | 37 | PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW |
36 | 38 | PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC |
37 | 39 | RLIM_INFINITY = linux.RLIM_INFINITY |
40 | RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK | |
38 | 41 | ) |
39 | 42 | |
40 | 43 | // Statfs_t is a wrapper |
11 | 11 | |
12 | 12 | const ( |
13 | 13 | ENOENT = syscall.ENOENT |
14 | EEXIST = syscall.EEXIST | |
14 | 15 | EAGAIN = syscall.EAGAIN |
15 | 16 | ENOSPC = syscall.ENOSPC |
16 | 17 | EINVAL = syscall.EINVAL |
17 | 18 | EINTR = syscall.EINTR |
19 | EPERM = syscall.EPERM | |
18 | 20 | ESRCH = syscall.ESRCH |
19 | 21 | ENODEV = syscall.ENODEV |
20 | 22 | BPF_F_RDONLY_PROG = 0 |
37 | 39 | PERF_SAMPLE_RAW = 0x400 |
38 | 40 | PERF_FLAG_FD_CLOEXEC = 0x8 |
39 | 41 | RLIM_INFINITY = 0x7fffffffffffffff |
42 | RLIMIT_MEMLOCK = 8 | |
40 | 43 | ) |
41 | 44 | |
42 | 45 | // Statfs_t is a wrapper |
0 | package link | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "fmt" | |
5 | "os" | |
6 | ||
7 | "github.com/cilium/ebpf" | |
8 | ) | |
9 | ||
10 | type cgroupAttachFlags uint32 | |
11 | ||
12 | // cgroup attach flags | |
13 | const ( | |
14 | flagAllowOverride cgroupAttachFlags = 1 << iota | |
15 | flagAllowMulti | |
16 | flagReplace | |
17 | ) | |
18 | ||
19 | type CgroupOptions struct { | |
20 | // Path to a cgroupv2 folder. | |
21 | Path string | |
22 | // One of the AttachCgroup* constants | |
23 | Attach ebpf.AttachType | |
24 | // Program must be of type CGroup*, and the attach type must match Attach. | |
25 | Program *ebpf.Program | |
26 | } | |
27 | ||
28 | // AttachCgroup links a BPF program to a cgroup. | |
29 | func AttachCgroup(opts CgroupOptions) (Link, error) { | |
30 | cgroup, err := os.Open(opts.Path) | |
31 | if err != nil { | |
32 | return nil, fmt.Errorf("can't open cgroup: %s", err) | |
33 | } | |
34 | ||
35 | clone, err := opts.Program.Clone() | |
36 | if err != nil { | |
37 | cgroup.Close() | |
38 | return nil, err | |
39 | } | |
40 | ||
41 | var cg Link | |
42 | cg, err = newLinkCgroup(cgroup, opts.Attach, clone) | |
43 | if errors.Is(err, ErrNotSupported) { | |
44 | cg, err = newProgAttachCgroup(cgroup, opts.Attach, clone, flagAllowMulti) | |
45 | } | |
46 | if errors.Is(err, ErrNotSupported) { | |
47 | cg, err = newProgAttachCgroup(cgroup, opts.Attach, clone, flagAllowOverride) | |
48 | } | |
49 | if err != nil { | |
50 | cgroup.Close() | |
51 | clone.Close() | |
52 | return nil, err | |
53 | } | |
54 | ||
55 | return cg, nil | |
56 | } | |
57 | ||
58 | // LoadPinnedCgroup loads a pinned cgroup from a bpffs. | |
59 | func LoadPinnedCgroup(fileName string) (Link, error) { | |
60 | link, err := LoadPinnedRawLink(fileName) | |
61 | if err != nil { | |
62 | return nil, err | |
63 | } | |
64 | ||
65 | return &linkCgroup{link}, nil | |
66 | } | |
67 | ||
68 | type progAttachCgroup struct { | |
69 | cgroup *os.File | |
70 | current *ebpf.Program | |
71 | attachType ebpf.AttachType | |
72 | flags cgroupAttachFlags | |
73 | } | |
74 | ||
75 | var _ Link = (*progAttachCgroup)(nil) | |
76 | ||
77 | func (cg *progAttachCgroup) isLink() {} | |
78 | ||
79 | func newProgAttachCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program, flags cgroupAttachFlags) (*progAttachCgroup, error) { | |
80 | if flags&flagAllowMulti > 0 { | |
81 | if err := haveProgAttachReplace(); err != nil { | |
82 | return nil, fmt.Errorf("can't support multiple programs: %w", err) | |
83 | } | |
84 | } | |
85 | ||
86 | err := RawAttachProgram(RawAttachProgramOptions{ | |
87 | Target: int(cgroup.Fd()), | |
88 | Program: prog, | |
89 | Flags: uint32(flags), | |
90 | Attach: attach, | |
91 | }) | |
92 | if err != nil { | |
93 | return nil, fmt.Errorf("cgroup: %w", err) | |
94 | } | |
95 | ||
96 | return &progAttachCgroup{cgroup, prog, attach, flags}, nil | |
97 | } | |
98 | ||
99 | func (cg *progAttachCgroup) Close() error { | |
100 | defer cg.cgroup.Close() | |
101 | defer cg.current.Close() | |
102 | ||
103 | err := RawDetachProgram(RawDetachProgramOptions{ | |
104 | Target: int(cg.cgroup.Fd()), | |
105 | Program: cg.current, | |
106 | Attach: cg.attachType, | |
107 | }) | |
108 | if err != nil { | |
109 | return fmt.Errorf("close cgroup: %s", err) | |
110 | } | |
111 | return nil | |
112 | } | |
113 | ||
114 | func (cg *progAttachCgroup) Update(prog *ebpf.Program) error { | |
115 | new, err := prog.Clone() | |
116 | if err != nil { | |
117 | return err | |
118 | } | |
119 | ||
120 | args := RawAttachProgramOptions{ | |
121 | Target: int(cg.cgroup.Fd()), | |
122 | Program: prog, | |
123 | Attach: cg.attachType, | |
124 | Flags: uint32(cg.flags), | |
125 | } | |
126 | ||
127 | if cg.flags&flagAllowMulti > 0 { | |
128 | // Atomically replacing multiple programs requires at least | |
129 | // 5.5 (commit 7dd68b3279f17921 "bpf: Support replacing cgroup-bpf | |
130 | // program in MULTI mode") | |
131 | args.Flags |= uint32(flagReplace) | |
132 | args.Replace = cg.current | |
133 | } | |
134 | ||
135 | if err := RawAttachProgram(args); err != nil { | |
136 | new.Close() | |
137 | return fmt.Errorf("can't update cgroup: %s", err) | |
138 | } | |
139 | ||
140 | cg.current.Close() | |
141 | cg.current = new | |
142 | return nil | |
143 | } | |
144 | ||
145 | func (cg *progAttachCgroup) Pin(string) error { | |
146 | return fmt.Errorf("can't pin cgroup: %w", ErrNotSupported) | |
147 | } | |
148 | ||
149 | type linkCgroup struct { | |
150 | *RawLink | |
151 | } | |
152 | ||
153 | var _ Link = (*linkCgroup)(nil) | |
154 | ||
155 | func (cg *linkCgroup) isLink() {} | |
156 | ||
157 | func newLinkCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program) (*linkCgroup, error) { | |
158 | link, err := AttachRawLink(RawLinkOptions{ | |
159 | Target: int(cgroup.Fd()), | |
160 | Program: prog, | |
161 | Attach: attach, | |
162 | }) | |
163 | if err != nil { | |
164 | return nil, err | |
165 | } | |
166 | ||
167 | return &linkCgroup{link}, err | |
168 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/cilium/ebpf" | |
6 | "github.com/cilium/ebpf/internal/testutils" | |
7 | ) | |
8 | ||
9 | func TestAttachCgroup(t *testing.T) { | |
10 | cgroup, prog, cleanup := mustCgroupFixtures(t) | |
11 | defer cleanup() | |
12 | ||
13 | link, err := AttachCgroup(CgroupOptions{ | |
14 | Path: cgroup.Name(), | |
15 | Attach: ebpf.AttachCGroupInetEgress, | |
16 | Program: prog, | |
17 | }) | |
18 | testutils.SkipIfNotSupported(t, err) | |
19 | if err != nil { | |
20 | t.Fatal(err) | |
21 | } | |
22 | ||
23 | if haveBPFLink() == nil { | |
24 | if _, ok := link.(*linkCgroup); !ok { | |
25 | t.Fatalf("Have support for bpf_link, but got %T instead of linkCgroup", link) | |
26 | } | |
27 | } else { | |
28 | if _, ok := link.(*progAttachCgroup); !ok { | |
29 | t.Fatalf("Expected progAttachCgroup, got %T instead", link) | |
30 | } | |
31 | } | |
32 | } | |
33 | ||
34 | func TestProgAttachCgroup(t *testing.T) { | |
35 | cgroup, prog, cleanup := mustCgroupFixtures(t) | |
36 | defer cleanup() | |
37 | ||
38 | link, err := newProgAttachCgroup(cgroup, ebpf.AttachCGroupInetEgress, prog, 0) | |
39 | if err != nil { | |
40 | t.Fatal("Can't create link:", err) | |
41 | } | |
42 | ||
43 | testLink(t, link, testLinkOptions{ | |
44 | prog: prog, | |
45 | }) | |
46 | } | |
47 | ||
48 | func TestProgAttachCgroupAllowMulti(t *testing.T) { | |
49 | cgroup, prog, cleanup := mustCgroupFixtures(t) | |
50 | defer cleanup() | |
51 | ||
52 | link, err := newProgAttachCgroup(cgroup, ebpf.AttachCGroupInetEgress, prog, flagAllowMulti) | |
53 | testutils.SkipIfNotSupported(t, err) | |
54 | if err != nil { | |
55 | t.Fatal("Can't create link:", err) | |
56 | } | |
57 | ||
58 | // It's currently not possible for a program to replace | |
59 | // itself. | |
60 | prog2 := mustCgroupEgressProgram(t) | |
61 | testLink(t, link, testLinkOptions{ | |
62 | prog: prog2, | |
63 | }) | |
64 | } | |
65 | ||
66 | func TestLinkCgroup(t *testing.T) { | |
67 | cgroup, prog, cleanup := mustCgroupFixtures(t) | |
68 | defer cleanup() | |
69 | ||
70 | link, err := newLinkCgroup(cgroup, ebpf.AttachCGroupInetEgress, prog) | |
71 | testutils.SkipIfNotSupported(t, err) | |
72 | if err != nil { | |
73 | t.Fatal("Can't create link:", err) | |
74 | } | |
75 | ||
76 | testLink(t, link, testLinkOptions{ | |
77 | prog: prog, | |
78 | loadPinned: LoadPinnedCgroup, | |
79 | }) | |
80 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "io" | |
5 | ||
6 | "github.com/cilium/ebpf" | |
7 | ) | |
8 | ||
9 | type IterOptions struct { | |
10 | // Program must be of type Tracing with attach type | |
11 | // AttachTraceIter. The kind of iterator to attach to is | |
12 | // determined at load time via the AttachTo field. | |
13 | // | |
14 | // AttachTo requires the kernel to include BTF of itself, | |
15 | // and it to be compiled with a recent pahole (>= 1.16). | |
16 | Program *ebpf.Program | |
17 | } | |
18 | ||
19 | // AttachIter attaches a BPF seq_file iterator. | |
20 | func AttachIter(opts IterOptions) (*Iter, error) { | |
21 | link, err := AttachRawLink(RawLinkOptions{ | |
22 | Program: opts.Program, | |
23 | Attach: ebpf.AttachTraceIter, | |
24 | }) | |
25 | if err != nil { | |
26 | return nil, fmt.Errorf("can't link iterator: %w", err) | |
27 | } | |
28 | ||
29 | return &Iter{link}, err | |
30 | } | |
31 | ||
32 | // LoadPinnedIter loads a pinned iterator from a bpffs. | |
33 | func LoadPinnedIter(fileName string) (*Iter, error) { | |
34 | link, err := LoadPinnedRawLink(fileName) | |
35 | if err != nil { | |
36 | return nil, err | |
37 | } | |
38 | ||
39 | return &Iter{link}, err | |
40 | } | |
41 | ||
42 | // Iter represents an attached bpf_iter. | |
43 | type Iter struct { | |
44 | link *RawLink | |
45 | } | |
46 | ||
47 | var _ Link = (*Iter)(nil) | |
48 | ||
49 | func (it *Iter) isLink() {} | |
50 | ||
51 | // Close implements Link. | |
52 | func (it *Iter) Close() error { | |
53 | return it.link.Close() | |
54 | } | |
55 | ||
56 | // Pin implements Link. | |
57 | func (it *Iter) Pin(fileName string) error { | |
58 | return it.link.Pin(fileName) | |
59 | } | |
60 | ||
61 | // Update implements Link. | |
62 | func (it *Iter) Update(new *ebpf.Program) error { | |
63 | return it.link.Update(new) | |
64 | } | |
65 | ||
66 | // Open creates a new instance of the iterator. | |
67 | // | |
68 | // Reading from the returned reader triggers the BPF program. | |
69 | func (it *Iter) Open() (io.ReadCloser, error) { | |
70 | linkFd, err := it.link.fd.Value() | |
71 | if err != nil { | |
72 | return nil, err | |
73 | } | |
74 | ||
75 | attr := &bpfIterCreateAttr{ | |
76 | linkFd: linkFd, | |
77 | } | |
78 | ||
79 | fd, err := bpfIterCreate(attr) | |
80 | if err != nil { | |
81 | return nil, fmt.Errorf("can't create iterator: %w", err) | |
82 | } | |
83 | ||
84 | return fd.File("bpf_iter"), nil | |
85 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "io/ioutil" | |
5 | "testing" | |
6 | ||
7 | "github.com/cilium/ebpf" | |
8 | "github.com/cilium/ebpf/asm" | |
9 | "github.com/cilium/ebpf/internal/btf" | |
10 | ) | |
11 | ||
12 | func TestIter(t *testing.T) { | |
13 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ | |
14 | Type: ebpf.Tracing, | |
15 | AttachType: ebpf.AttachTraceIter, | |
16 | AttachTo: "bpf_map", | |
17 | Instructions: asm.Instructions{ | |
18 | asm.Mov.Imm(asm.R0, 0), | |
19 | asm.Return(), | |
20 | }, | |
21 | License: "MIT", | |
22 | }) | |
23 | if errors.Is(err, btf.ErrNotFound) { | |
24 | t.Skip("Kernel doesn't support iter:", err) | |
25 | } | |
26 | if err != nil { | |
27 | t.Fatal("Can't load program:", err) | |
28 | } | |
29 | defer prog.Close() | |
30 | ||
31 | it, err := AttachIter(IterOptions{ | |
32 | Program: prog, | |
33 | }) | |
34 | if err != nil { | |
35 | t.Fatal("Can't create iter:", err) | |
36 | } | |
37 | ||
38 | file, err := it.Open() | |
39 | if err != nil { | |
40 | t.Fatal("Can't open iter instance:", err) | |
41 | } | |
42 | defer file.Close() | |
43 | ||
44 | contents, err := ioutil.ReadAll(file) | |
45 | if err != nil { | |
46 | t.Fatal(err) | |
47 | } | |
48 | ||
49 | if len(contents) != 0 { | |
50 | t.Error("Non-empty output from no-op iterator:", string(contents)) | |
51 | } | |
52 | ||
53 | testLink(t, it, testLinkOptions{ | |
54 | prog: prog, | |
55 | loadPinned: func(s string) (Link, error) { | |
56 | return LoadPinnedIter(s) | |
57 | }, | |
58 | }) | |
59 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ||
5 | "github.com/cilium/ebpf" | |
6 | "github.com/cilium/ebpf/internal" | |
7 | ) | |
8 | ||
9 | var ErrNotSupported = internal.ErrNotSupported | |
10 | ||
11 | // Link represents a Program attached to a BPF hook. | |
12 | type Link interface { | |
13 | // Replace the current program with a new program. | |
14 | // | |
15 | // Passing a nil program is an error. | |
16 | Update(*ebpf.Program) error | |
17 | ||
18 | // Persist a link by pinning it into a bpffs. | |
19 | // | |
20 | // May return an error wrapping ErrNotSupported. | |
21 | Pin(string) error | |
22 | ||
23 | // Close frees resources. | |
24 | // | |
25 | // The link will be broken unless it has been pinned. A link | |
26 | // may continue past the lifetime of the process if Close is | |
27 | // not called. | |
28 | Close() error | |
29 | ||
30 | // Prevent external users from implementing this interface. | |
31 | isLink() | |
32 | } | |
33 | ||
34 | // RawLinkOptions control the creation of a raw link. | |
35 | type RawLinkOptions struct { | |
36 | // File descriptor to attach to. This differs for each attach type. | |
37 | Target int | |
38 | // Program to attach. | |
39 | Program *ebpf.Program | |
40 | // Attach must match the attach type of Program. | |
41 | Attach ebpf.AttachType | |
42 | } | |
43 | ||
44 | // RawLink is the low-level API to bpf_link. | |
45 | // | |
46 | // You should consider using the higher level interfaces in this | |
47 | // package instead. | |
48 | type RawLink struct { | |
49 | fd *internal.FD | |
50 | } | |
51 | ||
52 | // AttachRawLink creates a raw link. | |
53 | func AttachRawLink(opts RawLinkOptions) (*RawLink, error) { | |
54 | if err := haveBPFLink(); err != nil { | |
55 | return nil, err | |
56 | } | |
57 | ||
58 | if opts.Target < 0 { | |
59 | return nil, fmt.Errorf("invalid target: %s", internal.ErrClosedFd) | |
60 | } | |
61 | ||
62 | progFd := opts.Program.FD() | |
63 | if progFd < 0 { | |
64 | return nil, fmt.Errorf("invalid program: %s", internal.ErrClosedFd) | |
65 | } | |
66 | ||
67 | attr := bpfLinkCreateAttr{ | |
68 | targetFd: uint32(opts.Target), | |
69 | progFd: uint32(progFd), | |
70 | attachType: opts.Attach, | |
71 | } | |
72 | fd, err := bpfLinkCreate(&attr) | |
73 | if err != nil { | |
74 | return nil, fmt.Errorf("can't create link: %s", err) | |
75 | } | |
76 | ||
77 | return &RawLink{fd}, nil | |
78 | } | |
79 | ||
80 | // LoadPinnedRawLink loads a persisted link from a bpffs. | |
81 | func LoadPinnedRawLink(fileName string) (*RawLink, error) { | |
82 | fd, err := internal.BPFObjGet(fileName) | |
83 | if err != nil { | |
84 | return nil, fmt.Errorf("can't load pinned link: %s", err) | |
85 | } | |
86 | ||
87 | return &RawLink{fd}, nil | |
88 | } | |
89 | ||
90 | func (l *RawLink) isLink() {} | |
91 | ||
92 | // Close breaks the link. | |
93 | // | |
94 | // Use Pin if you want to make the link persistent. | |
95 | func (l *RawLink) Close() error { | |
96 | return l.fd.Close() | |
97 | } | |
98 | ||
99 | // Pin persists a link past the lifetime of the process. | |
100 | // | |
101 | // Calling Close on a pinned Link will not break the link | |
102 | // until the pin is removed. | |
103 | func (l *RawLink) Pin(fileName string) error { | |
104 | if err := internal.BPFObjPin(fileName, l.fd); err != nil { | |
105 | return fmt.Errorf("can't pin link: %s", err) | |
106 | } | |
107 | return nil | |
108 | } | |
109 | ||
110 | // Update implements Link. | |
111 | func (l *RawLink) Update(new *ebpf.Program) error { | |
112 | return l.UpdateArgs(RawLinkUpdateOptions{ | |
113 | New: new, | |
114 | }) | |
115 | } | |
116 | ||
117 | // RawLinkUpdateOptions control the behaviour of RawLink.UpdateArgs. | |
118 | type RawLinkUpdateOptions struct { | |
119 | New *ebpf.Program | |
120 | Old *ebpf.Program | |
121 | Flags uint32 | |
122 | } | |
123 | ||
124 | // UpdateArgs updates a link based on args. | |
125 | func (l *RawLink) UpdateArgs(opts RawLinkUpdateOptions) error { | |
126 | newFd := opts.New.FD() | |
127 | if newFd < 0 { | |
128 | return fmt.Errorf("invalid program: %s", internal.ErrClosedFd) | |
129 | } | |
130 | ||
131 | var oldFd int | |
132 | if opts.Old != nil { | |
133 | oldFd = opts.Old.FD() | |
134 | if oldFd < 0 { | |
135 | return fmt.Errorf("invalid replacement program: %s", internal.ErrClosedFd) | |
136 | } | |
137 | } | |
138 | ||
139 | linkFd, err := l.fd.Value() | |
140 | if err != nil { | |
141 | return fmt.Errorf("can't update link: %s", err) | |
142 | } | |
143 | ||
144 | attr := bpfLinkUpdateAttr{ | |
145 | linkFd: linkFd, | |
146 | newProgFd: uint32(newFd), | |
147 | oldProgFd: uint32(oldFd), | |
148 | flags: opts.Flags, | |
149 | } | |
150 | return bpfLinkUpdate(&attr) | |
151 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "io/ioutil" | |
5 | "os" | |
6 | "path/filepath" | |
7 | "reflect" | |
8 | "testing" | |
9 | ||
10 | "github.com/cilium/ebpf" | |
11 | "github.com/cilium/ebpf/asm" | |
12 | "github.com/cilium/ebpf/internal/testutils" | |
13 | ) | |
14 | ||
15 | func TestRawLink(t *testing.T) { | |
16 | cgroup, prog, cleanup := mustCgroupFixtures(t) | |
17 | defer cleanup() | |
18 | ||
19 | link, err := AttachRawLink(RawLinkOptions{ | |
20 | Target: int(cgroup.Fd()), | |
21 | Program: prog, | |
22 | Attach: ebpf.AttachCGroupInetEgress, | |
23 | }) | |
24 | testutils.SkipIfNotSupported(t, err) | |
25 | if err != nil { | |
26 | t.Fatal("Can't create raw link:", err) | |
27 | } | |
28 | ||
29 | testLink(t, link, testLinkOptions{ | |
30 | prog: prog, | |
31 | loadPinned: func(f string) (Link, error) { | |
32 | return LoadPinnedRawLink(f) | |
33 | }, | |
34 | }) | |
35 | } | |
36 | ||
37 | func mustCgroupFixtures(t *testing.T) (*os.File, *ebpf.Program, func()) { | |
38 | t.Helper() | |
39 | ||
40 | testutils.SkipIfNotSupported(t, haveProgAttach()) | |
41 | ||
42 | prog := mustCgroupEgressProgram(t) | |
43 | cgdir, err := ioutil.TempDir("/sys/fs/cgroup/unified", "ebpf-link") | |
44 | if err != nil { | |
45 | prog.Close() | |
46 | t.Fatal("Can't create cgroupv2:", err) | |
47 | } | |
48 | ||
49 | cgroup, err := os.Open(cgdir) | |
50 | if err != nil { | |
51 | prog.Close() | |
52 | os.Remove(cgdir) | |
53 | t.Fatal(err) | |
54 | } | |
55 | ||
56 | return cgroup, prog, func() { | |
57 | prog.Close() | |
58 | cgroup.Close() | |
59 | os.Remove(cgdir) | |
60 | } | |
61 | } | |
62 | ||
63 | func mustCgroupEgressProgram(t *testing.T) *ebpf.Program { | |
64 | t.Helper() | |
65 | ||
66 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ | |
67 | Type: ebpf.CGroupSKB, | |
68 | AttachType: ebpf.AttachCGroupInetEgress, | |
69 | License: "MIT", | |
70 | Instructions: asm.Instructions{ | |
71 | asm.Mov.Imm(asm.R0, 0), | |
72 | asm.Return(), | |
73 | }, | |
74 | }) | |
75 | if err != nil { | |
76 | t.Fatal(err) | |
77 | } | |
78 | return prog | |
79 | } | |
80 | ||
81 | type testLinkOptions struct { | |
82 | prog *ebpf.Program | |
83 | loadPinned func(string) (Link, error) | |
84 | } | |
85 | ||
86 | func testLink(t *testing.T, link Link, opts testLinkOptions) { | |
87 | t.Helper() | |
88 | ||
89 | tmp, err := ioutil.TempDir("/sys/fs/bpf", "ebpf-test") | |
90 | if err != nil { | |
91 | t.Fatal(err) | |
92 | } | |
93 | defer os.RemoveAll(tmp) | |
94 | ||
95 | path := filepath.Join(tmp, "link") | |
96 | err = link.Pin(path) | |
97 | if err == ErrNotSupported { | |
98 | t.Errorf("%T.Pin returns unwrapped ErrNotSupported", link) | |
99 | } | |
100 | ||
101 | if opts.loadPinned == nil { | |
102 | if !errors.Is(err, ErrNotSupported) { | |
103 | t.Errorf("%T.Pin doesn't return ErrNotSupported: %s", link, err) | |
104 | } | |
105 | } else { | |
106 | if err != nil { | |
107 | t.Fatalf("Can't pin %T: %s", link, err) | |
108 | } | |
109 | ||
110 | link2, err := opts.loadPinned(path) | |
111 | if err != nil { | |
112 | t.Fatalf("Can't load pinned %T: %s", link, err) | |
113 | } | |
114 | link2.Close() | |
115 | ||
116 | if reflect.TypeOf(link) != reflect.TypeOf(link2) { | |
117 | t.Errorf("Loading a pinned %T returns a %T", link, link2) | |
118 | } | |
119 | } | |
120 | ||
121 | if err := link.Update(opts.prog); err != nil { | |
122 | t.Fatalf("%T.Update returns an error: %s", link, err) | |
123 | } | |
124 | ||
125 | func() { | |
126 | // Panicking is OK | |
127 | defer func() { | |
128 | recover() | |
129 | }() | |
130 | ||
131 | if err := link.Update(nil); err == nil { | |
132 | t.Fatalf("%T.Update accepts nil program", link) | |
133 | } | |
134 | }() | |
135 | ||
136 | if err := link.Close(); err != nil { | |
137 | t.Fatalf("%T.Close returns an error: %s", link, err) | |
138 | } | |
139 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ||
5 | "github.com/cilium/ebpf" | |
6 | "github.com/cilium/ebpf/internal" | |
7 | ) | |
8 | ||
9 | type RawAttachProgramOptions struct { | |
10 | // File descriptor to attach to. This differs for each attach type. | |
11 | Target int | |
12 | // Program to attach. | |
13 | Program *ebpf.Program | |
14 | // Program to replace (cgroups). | |
15 | Replace *ebpf.Program | |
16 | // Attach must match the attach type of Program (and Replace). | |
17 | Attach ebpf.AttachType | |
18 | // Flags control the attach behaviour. This differs for each attach type. | |
19 | Flags uint32 | |
20 | } | |
21 | ||
22 | // RawAttachProgram is a low level wrapper around BPF_PROG_ATTACH. | |
23 | // | |
24 | // You should use one of the higher level abstractions available in this | |
25 | // package if possible. | |
26 | func RawAttachProgram(opts RawAttachProgramOptions) error { | |
27 | if err := haveProgAttach(); err != nil { | |
28 | return err | |
29 | } | |
30 | ||
31 | var replaceFd uint32 | |
32 | if opts.Replace != nil { | |
33 | replaceFd = uint32(opts.Replace.FD()) | |
34 | } | |
35 | ||
36 | attr := internal.BPFProgAttachAttr{ | |
37 | TargetFd: uint32(opts.Target), | |
38 | AttachBpfFd: uint32(opts.Program.FD()), | |
39 | ReplaceBpfFd: replaceFd, | |
40 | AttachType: uint32(opts.Attach), | |
41 | AttachFlags: uint32(opts.Flags), | |
42 | } | |
43 | ||
44 | if err := internal.BPFProgAttach(&attr); err != nil { | |
45 | return fmt.Errorf("can't attach program: %s", err) | |
46 | } | |
47 | return nil | |
48 | } | |
49 | ||
50 | type RawDetachProgramOptions struct { | |
51 | Target int | |
52 | Program *ebpf.Program | |
53 | Attach ebpf.AttachType | |
54 | } | |
55 | ||
56 | // RawDetachProgram is a low level wrapper around BPF_PROG_DETACH. | |
57 | // | |
58 | // You should use one of the higher level abstractions available in this | |
59 | // package if possible. | |
60 | func RawDetachProgram(opts RawDetachProgramOptions) error { | |
61 | if err := haveProgAttach(); err != nil { | |
62 | return err | |
63 | } | |
64 | ||
65 | attr := internal.BPFProgDetachAttr{ | |
66 | TargetFd: uint32(opts.Target), | |
67 | AttachBpfFd: uint32(opts.Program.FD()), | |
68 | AttachType: uint32(opts.Attach), | |
69 | } | |
70 | if err := internal.BPFProgDetach(&attr); err != nil { | |
71 | return fmt.Errorf("can't detach program: %s", err) | |
72 | } | |
73 | ||
74 | return nil | |
75 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/cilium/ebpf" | |
6 | "github.com/cilium/ebpf/asm" | |
7 | "github.com/cilium/ebpf/internal/testutils" | |
8 | ) | |
9 | ||
10 | func TestProgramAlter(t *testing.T) { | |
11 | testutils.SkipOnOldKernel(t, "4.13", "SkSKB type") | |
12 | ||
13 | var err error | |
14 | var prog *ebpf.Program | |
15 | prog, err = ebpf.NewProgram(&ebpf.ProgramSpec{ | |
16 | Type: ebpf.SkSKB, | |
17 | Instructions: asm.Instructions{ | |
18 | asm.LoadImm(asm.R0, 0, asm.DWord), | |
19 | asm.Return(), | |
20 | }, | |
21 | License: "MIT", | |
22 | }) | |
23 | if err != nil { | |
24 | t.Fatal(err) | |
25 | } | |
26 | defer prog.Close() | |
27 | ||
28 | var sockMap *ebpf.Map | |
29 | sockMap, err = ebpf.NewMap(&ebpf.MapSpec{ | |
30 | Type: ebpf.MapType(15), // BPF_MAP_TYPE_SOCKMAP | |
31 | KeySize: 4, | |
32 | ValueSize: 4, | |
33 | MaxEntries: 2, | |
34 | }) | |
35 | if err != nil { | |
36 | t.Fatal(err) | |
37 | } | |
38 | defer sockMap.Close() | |
39 | ||
40 | err = RawAttachProgram(RawAttachProgramOptions{ | |
41 | Target: sockMap.FD(), | |
42 | Program: prog, | |
43 | Attach: ebpf.AttachSkSKBStreamParser, | |
44 | }) | |
45 | if err != nil { | |
46 | t.Fatal(err) | |
47 | } | |
48 | ||
49 | err = RawDetachProgram(RawDetachProgramOptions{ | |
50 | Target: sockMap.FD(), | |
51 | Program: prog, | |
52 | Attach: ebpf.AttachSkSKBStreamParser, | |
53 | }) | |
54 | if err != nil { | |
55 | t.Fatal(err) | |
56 | } | |
57 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ||
5 | "github.com/cilium/ebpf" | |
6 | "github.com/cilium/ebpf/internal" | |
7 | ) | |
8 | ||
9 | type RawTracepointOptions struct { | |
10 | // Tracepoint name. | |
11 | Name string | |
12 | // Program must be of type RawTracepoint* | |
13 | Program *ebpf.Program | |
14 | } | |
15 | ||
16 | // AttachRawTracepoint links a BPF program to a raw_tracepoint. | |
17 | // | |
18 | // Requires at least Linux 4.17. | |
19 | func AttachRawTracepoint(opts RawTracepointOptions) (Link, error) { | |
20 | if opts.Program.FD() < 0 { | |
21 | return nil, fmt.Errorf("invalid program: %w", internal.ErrClosedFd) | |
22 | } | |
23 | ||
24 | fd, err := bpfRawTracepointOpen(&bpfRawTracepointOpenAttr{ | |
25 | name: internal.NewStringPointer(opts.Name), | |
26 | fd: uint32(opts.Program.FD()), | |
27 | }) | |
28 | if err != nil { | |
29 | return nil, err | |
30 | } | |
31 | ||
32 | return &progAttachRawTracepoint{fd: fd}, nil | |
33 | } | |
34 | ||
35 | type progAttachRawTracepoint struct { | |
36 | fd *internal.FD | |
37 | } | |
38 | ||
39 | var _ Link = (*progAttachRawTracepoint)(nil) | |
40 | ||
41 | func (rt *progAttachRawTracepoint) isLink() {} | |
42 | ||
43 | func (rt *progAttachRawTracepoint) Close() error { | |
44 | return rt.fd.Close() | |
45 | } | |
46 | ||
47 | func (rt *progAttachRawTracepoint) Update(_ *ebpf.Program) error { | |
48 | return fmt.Errorf("can't update raw_tracepoint: %w", ErrNotSupported) | |
49 | } | |
50 | ||
51 | func (rt *progAttachRawTracepoint) Pin(_ string) error { | |
52 | return fmt.Errorf("can't pin raw_tracepoint: %w", ErrNotSupported) | |
53 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/cilium/ebpf" | |
6 | "github.com/cilium/ebpf/asm" | |
7 | "github.com/cilium/ebpf/internal/testutils" | |
8 | ) | |
9 | ||
10 | func TestRawTracepoint(t *testing.T) { | |
11 | testutils.SkipOnOldKernel(t, "4.17", "BPF_RAW_TRACEPOINT API") | |
12 | ||
13 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ | |
14 | Type: ebpf.RawTracepoint, | |
15 | AttachType: ebpf.AttachNone, | |
16 | Instructions: asm.Instructions{ | |
17 | asm.LoadImm(asm.R0, 0, asm.DWord), | |
18 | asm.Return(), | |
19 | }, | |
20 | License: "GPL", | |
21 | }) | |
22 | if err != nil { | |
23 | t.Fatal(err) | |
24 | } | |
25 | defer prog.Close() | |
26 | ||
27 | link, err := AttachRawTracepoint(RawTracepointOptions{ | |
28 | Name: "cgroup_mkdir", | |
29 | Program: prog, | |
30 | }) | |
31 | if err != nil { | |
32 | t.Fatal(err) | |
33 | } | |
34 | ||
35 | if link == nil { | |
36 | t.Fatal("no link") | |
37 | } | |
38 | ||
39 | if err := link.Close(); err != nil { | |
40 | t.Fatalf("failed to close link: %v", err) | |
41 | } | |
42 | } | |
43 | ||
44 | func TestRawTracepoint_writable(t *testing.T) { | |
45 | testutils.SkipOnOldKernel(t, "5.2", "BPF_RAW_TRACEPOINT_WRITABLE API") | |
46 | ||
47 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ | |
48 | Type: ebpf.RawTracepointWritable, | |
49 | AttachType: ebpf.AttachNone, | |
50 | Instructions: asm.Instructions{ | |
51 | asm.LoadImm(asm.R0, 0, asm.DWord), | |
52 | asm.Return(), | |
53 | }, | |
54 | License: "GPL", | |
55 | }) | |
56 | if err != nil { | |
57 | t.Fatal(err) | |
58 | } | |
59 | defer prog.Close() | |
60 | ||
61 | link, err := AttachRawTracepoint(RawTracepointOptions{ | |
62 | Name: "cgroup_rmdir", | |
63 | Program: prog, | |
64 | }) | |
65 | if err != nil { | |
66 | t.Fatal(err) | |
67 | } | |
68 | ||
69 | if link == nil { | |
70 | t.Fatal("no link") | |
71 | } | |
72 | ||
73 | if err := link.Close(); err != nil { | |
74 | t.Fatalf("failed to close link: %v", err) | |
75 | } | |
76 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "unsafe" | |
5 | ||
6 | "github.com/cilium/ebpf" | |
7 | "github.com/cilium/ebpf/asm" | |
8 | "github.com/cilium/ebpf/internal" | |
9 | "github.com/cilium/ebpf/internal/unix" | |
10 | ) | |
11 | ||
12 | var haveProgAttach = internal.FeatureTest("BPF_PROG_ATTACH", "4.10", func() (bool, error) { | |
13 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ | |
14 | Type: ebpf.CGroupSKB, | |
15 | AttachType: ebpf.AttachCGroupInetIngress, | |
16 | License: "MIT", | |
17 | Instructions: asm.Instructions{ | |
18 | asm.Mov.Imm(asm.R0, 0), | |
19 | asm.Return(), | |
20 | }, | |
21 | }) | |
22 | if err != nil { | |
23 | return false, nil | |
24 | } | |
25 | ||
26 | // BPF_PROG_ATTACH was introduced at the same time as CGgroupSKB, | |
27 | // so being able to load the program is enough to infer that we | |
28 | // have the syscall. | |
29 | prog.Close() | |
30 | return true, nil | |
31 | }) | |
32 | ||
33 | var haveProgAttachReplace = internal.FeatureTest("BPF_PROG_ATTACH atomic replacement", "5.5", func() (bool, error) { | |
34 | if err := haveProgAttach(); err != nil { | |
35 | return false, err | |
36 | } | |
37 | ||
38 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ | |
39 | Type: ebpf.CGroupSKB, | |
40 | AttachType: ebpf.AttachCGroupInetIngress, | |
41 | License: "MIT", | |
42 | Instructions: asm.Instructions{ | |
43 | asm.Mov.Imm(asm.R0, 0), | |
44 | asm.Return(), | |
45 | }, | |
46 | }) | |
47 | if err != nil { | |
48 | return false, nil | |
49 | } | |
50 | defer prog.Close() | |
51 | ||
52 | // We know that we have BPF_PROG_ATTACH since we can load CGroupSKB programs. | |
53 | // If passing BPF_F_REPLACE gives us EINVAL we know that the feature isn't | |
54 | // present. | |
55 | attr := internal.BPFProgAttachAttr{ | |
56 | // We rely on this being checked after attachFlags. | |
57 | TargetFd: ^uint32(0), | |
58 | AttachBpfFd: uint32(prog.FD()), | |
59 | AttachType: uint32(ebpf.AttachCGroupInetIngress), | |
60 | AttachFlags: uint32(flagReplace), | |
61 | } | |
62 | ||
63 | err = internal.BPFProgAttach(&attr) | |
64 | if errors.Is(err, unix.EPERM) { | |
65 | // We don't have enough permissions, so we never get to the point | |
66 | // where flags are checked. | |
67 | return false, err | |
68 | } | |
69 | return !errors.Is(err, unix.EINVAL), nil | |
70 | }) | |
71 | ||
72 | type bpfLinkCreateAttr struct { | |
73 | progFd uint32 | |
74 | targetFd uint32 | |
75 | attachType ebpf.AttachType | |
76 | flags uint32 | |
77 | } | |
78 | ||
79 | func bpfLinkCreate(attr *bpfLinkCreateAttr) (*internal.FD, error) { | |
80 | ptr, err := internal.BPF(internal.BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
81 | if err != nil { | |
82 | return nil, err | |
83 | } | |
84 | return internal.NewFD(uint32(ptr)), nil | |
85 | } | |
86 | ||
87 | type bpfLinkUpdateAttr struct { | |
88 | linkFd uint32 | |
89 | newProgFd uint32 | |
90 | flags uint32 | |
91 | oldProgFd uint32 | |
92 | } | |
93 | ||
94 | func bpfLinkUpdate(attr *bpfLinkUpdateAttr) error { | |
95 | _, err := internal.BPF(internal.BPF_LINK_UPDATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
96 | return err | |
97 | } | |
98 | ||
99 | var haveBPFLink = internal.FeatureTest("bpf_link", "5.7", func() (bool, error) { | |
100 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ | |
101 | Type: ebpf.CGroupSKB, | |
102 | AttachType: ebpf.AttachCGroupInetIngress, | |
103 | License: "MIT", | |
104 | Instructions: asm.Instructions{ | |
105 | asm.Mov.Imm(asm.R0, 0), | |
106 | asm.Return(), | |
107 | }, | |
108 | }) | |
109 | if err != nil { | |
110 | return false, nil | |
111 | } | |
112 | defer prog.Close() | |
113 | ||
114 | attr := bpfLinkCreateAttr{ | |
115 | // This is a hopefully invalid file descriptor, which triggers EBADF. | |
116 | targetFd: ^uint32(0), | |
117 | progFd: uint32(prog.FD()), | |
118 | attachType: ebpf.AttachCGroupInetIngress, | |
119 | } | |
120 | _, err = bpfLinkCreate(&attr) | |
121 | return !errors.Is(err, unix.EINVAL), nil | |
122 | }) | |
123 | ||
124 | type bpfIterCreateAttr struct { | |
125 | linkFd uint32 | |
126 | flags uint32 | |
127 | } | |
128 | ||
129 | func bpfIterCreate(attr *bpfIterCreateAttr) (*internal.FD, error) { | |
130 | ptr, err := internal.BPF(internal.BPF_ITER_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
131 | if err == nil { | |
132 | return internal.NewFD(uint32(ptr)), nil | |
133 | } | |
134 | return nil, err | |
135 | } | |
136 | ||
137 | type bpfRawTracepointOpenAttr struct { | |
138 | name internal.Pointer | |
139 | fd uint32 | |
140 | _ uint32 | |
141 | } | |
142 | ||
143 | func bpfRawTracepointOpen(attr *bpfRawTracepointOpenAttr) (*internal.FD, error) { | |
144 | ptr, err := internal.BPF(internal.BPF_RAW_TRACEPOINT_OPEN, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
145 | if err == nil { | |
146 | return internal.NewFD(uint32(ptr)), nil | |
147 | } | |
148 | return nil, err | |
149 | } |
0 | package link | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/cilium/ebpf/internal/testutils" | |
6 | ) | |
7 | ||
8 | func TestHaveProgAttach(t *testing.T) { | |
9 | testutils.CheckFeatureTest(t, haveProgAttach) | |
10 | } | |
11 | ||
12 | func TestHaveProgAttachReplace(t *testing.T) { | |
13 | testutils.CheckFeatureTest(t, haveProgAttachReplace) | |
14 | } | |
15 | ||
16 | func TestHaveBPFLink(t *testing.T) { | |
17 | testutils.CheckFeatureTest(t, haveBPFLink) | |
18 | } |
0 | 0 | package ebpf |
1 | 1 | |
2 | 2 | import ( |
3 | "fmt" | |
4 | ||
3 | 5 | "github.com/cilium/ebpf/asm" |
4 | 6 | "github.com/cilium/ebpf/internal/btf" |
5 | ||
6 | "golang.org/x/xerrors" | |
7 | 7 | ) |
8 | 8 | |
9 | 9 | // link resolves bpf-to-bpf calls. |
27 | 27 | |
28 | 28 | needed, err := needSection(insns, lib.Instructions) |
29 | 29 | if err != nil { |
30 | return xerrors.Errorf("linking %s: %w", lib.Name, err) | |
30 | return fmt.Errorf("linking %s: %w", lib.Name, err) | |
31 | 31 | } |
32 | 32 | |
33 | 33 | if !needed { |
40 | 40 | |
41 | 41 | if prog.BTF != nil && lib.BTF != nil { |
42 | 42 | if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil { |
43 | return xerrors.Errorf("linking BTF of %s: %w", lib.Name, err) | |
43 | return fmt.Errorf("linking BTF of %s: %w", lib.Name, err) | |
44 | 44 | } |
45 | 45 | } |
46 | 46 | } |
0 | 0 | package ebpf |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
3 | 4 | "fmt" |
4 | 5 | "strings" |
5 | 6 | |
6 | 7 | "github.com/cilium/ebpf/internal" |
7 | 8 | "github.com/cilium/ebpf/internal/btf" |
8 | 9 | "github.com/cilium/ebpf/internal/unix" |
9 | ||
10 | "golang.org/x/xerrors" | |
11 | 10 | ) |
12 | 11 | |
13 | 12 | // Errors returned by Map and MapIterator methods. |
14 | 13 | var ( |
15 | ErrKeyNotExist = xerrors.New("key does not exist") | |
16 | ErrIterationAborted = xerrors.New("iteration aborted") | |
14 | ErrKeyNotExist = errors.New("key does not exist") | |
15 | ErrKeyExist = errors.New("key already exists") | |
16 | ErrIterationAborted = errors.New("iteration aborted") | |
17 | 17 | ) |
18 | 18 | |
19 | 19 | // MapID represents the unique ID of an eBPF map |
90 | 90 | // You should not use fd after calling this function. |
91 | 91 | func NewMapFromFD(fd int) (*Map, error) { |
92 | 92 | if fd < 0 { |
93 | return nil, xerrors.New("invalid fd") | |
93 | return nil, errors.New("invalid fd") | |
94 | 94 | } |
95 | 95 | bpfFd := internal.NewFD(uint32(fd)) |
96 | 96 | |
106 | 106 | // |
107 | 107 | // Creating a map for the first time will perform feature detection |
108 | 108 | // by creating small, temporary maps. |
109 | // | |
110 | // The caller is responsible for ensuring the process' rlimit is set | |
111 | // sufficiently high for locking memory during map creation. This can be done | |
112 | // by calling unix.Setrlimit with unix.RLIMIT_MEMLOCK prior to calling NewMap. | |
109 | 113 | func NewMap(spec *MapSpec) (*Map, error) { |
110 | 114 | if spec.BTF == nil { |
111 | 115 | return newMapWithBTF(spec, nil) |
112 | 116 | } |
113 | 117 | |
114 | 118 | handle, err := btf.NewHandle(btf.MapSpec(spec.BTF)) |
115 | if err != nil && !xerrors.Is(err, btf.ErrNotSupported) { | |
116 | return nil, xerrors.Errorf("can't load BTF: %w", err) | |
119 | if err != nil && !errors.Is(err, btf.ErrNotSupported) { | |
120 | return nil, fmt.Errorf("can't load BTF: %w", err) | |
117 | 121 | } |
118 | 122 | |
119 | 123 | return newMapWithBTF(spec, handle) |
125 | 129 | } |
126 | 130 | |
127 | 131 | if spec.InnerMap == nil { |
128 | return nil, xerrors.Errorf("%s requires InnerMap", spec.Type) | |
132 | return nil, fmt.Errorf("%s requires InnerMap", spec.Type) | |
129 | 133 | } |
130 | 134 | |
131 | 135 | template, err := createMap(spec.InnerMap, nil, handle) |
149 | 153 | } |
150 | 154 | |
151 | 155 | if abi.ValueSize != 0 && abi.ValueSize != 4 { |
152 | return nil, xerrors.New("ValueSize must be zero or four for map of map") | |
156 | return nil, errors.New("ValueSize must be zero or four for map of map") | |
153 | 157 | } |
154 | 158 | abi.ValueSize = 4 |
155 | 159 | |
156 | 160 | case PerfEventArray: |
157 | 161 | if abi.KeySize != 0 && abi.KeySize != 4 { |
158 | return nil, xerrors.New("KeySize must be zero or four for perf event array") | |
162 | return nil, errors.New("KeySize must be zero or four for perf event array") | |
159 | 163 | } |
160 | 164 | abi.KeySize = 4 |
161 | 165 | |
162 | 166 | if abi.ValueSize != 0 && abi.ValueSize != 4 { |
163 | return nil, xerrors.New("ValueSize must be zero or four for perf event array") | |
167 | return nil, errors.New("ValueSize must be zero or four for perf event array") | |
164 | 168 | } |
165 | 169 | abi.ValueSize = 4 |
166 | 170 | |
167 | 171 | if abi.MaxEntries == 0 { |
168 | 172 | n, err := internal.PossibleCPUs() |
169 | 173 | if err != nil { |
170 | return nil, xerrors.Errorf("perf event array: %w", err) | |
174 | return nil, fmt.Errorf("perf event array: %w", err) | |
171 | 175 | } |
172 | 176 | abi.MaxEntries = uint32(n) |
173 | 177 | } |
175 | 179 | |
176 | 180 | if abi.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze { |
177 | 181 | if err := haveMapMutabilityModifiers(); err != nil { |
178 | return nil, xerrors.Errorf("map create: %w", err) | |
182 | return nil, fmt.Errorf("map create: %w", err) | |
179 | 183 | } |
180 | 184 | } |
181 | 185 | |
191 | 195 | var err error |
192 | 196 | attr.innerMapFd, err = inner.Value() |
193 | 197 | if err != nil { |
194 | return nil, xerrors.Errorf("map create: %w", err) | |
198 | return nil, fmt.Errorf("map create: %w", err) | |
195 | 199 | } |
196 | 200 | } |
197 | 201 | |
207 | 211 | |
208 | 212 | fd, err := bpfMapCreate(&attr) |
209 | 213 | if err != nil { |
210 | return nil, xerrors.Errorf("map create: %w", err) | |
214 | return nil, fmt.Errorf("map create: %w", err) | |
211 | 215 | } |
212 | 216 | |
213 | 217 | m, err := newMap(fd, spec.Name, abi) |
217 | 221 | |
218 | 222 | if err := m.populate(spec.Contents); err != nil { |
219 | 223 | m.Close() |
220 | return nil, xerrors.Errorf("map create: can't set initial contents: %w", err) | |
224 | return nil, fmt.Errorf("map create: can't set initial contents: %w", err) | |
221 | 225 | } |
222 | 226 | |
223 | 227 | if spec.Freeze { |
224 | 228 | if err := m.Freeze(); err != nil { |
225 | 229 | m.Close() |
226 | return nil, xerrors.Errorf("can't freeze map: %w", err) | |
230 | return nil, fmt.Errorf("can't freeze map: %w", err) | |
227 | 231 | } |
228 | 232 | } |
229 | 233 | |
295 | 299 | *value = m |
296 | 300 | return nil |
297 | 301 | case *Map: |
298 | return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil)) | |
302 | return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil)) | |
299 | 303 | case Map: |
300 | return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil)) | |
304 | return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil)) | |
301 | 305 | |
302 | 306 | case **Program: |
303 | 307 | p, err := unmarshalProgram(valueBytes) |
309 | 313 | *value = p |
310 | 314 | return nil |
311 | 315 | case *Program: |
312 | return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil)) | |
316 | return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil)) | |
313 | 317 | case Program: |
314 | return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil)) | |
318 | return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil)) | |
315 | 319 | |
316 | 320 | default: |
317 | 321 | return unmarshalBytes(valueOut, valueBytes) |
326 | 330 | |
327 | 331 | keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) |
328 | 332 | if err != nil { |
329 | return xerrors.Errorf("can't marshal key: %w", err) | |
333 | return fmt.Errorf("can't marshal key: %w", err) | |
330 | 334 | } |
331 | 335 | |
332 | 336 | if err := bpfMapLookupAndDelete(m.fd, keyPtr, valuePtr); err != nil { |
333 | return xerrors.Errorf("lookup and delete failed: %w", err) | |
337 | return fmt.Errorf("lookup and delete failed: %w", err) | |
334 | 338 | } |
335 | 339 | |
336 | 340 | return unmarshalBytes(valueOut, valueBytes) |
344 | 348 | valuePtr := internal.NewSlicePointer(valueBytes) |
345 | 349 | |
346 | 350 | err := m.lookup(key, valuePtr) |
347 | if xerrors.Is(err, ErrKeyNotExist) { | |
351 | if errors.Is(err, ErrKeyNotExist) { | |
348 | 352 | return nil, nil |
349 | 353 | } |
350 | 354 | |
354 | 358 | func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error { |
355 | 359 | keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) |
356 | 360 | if err != nil { |
357 | return xerrors.Errorf("can't marshal key: %w", err) | |
361 | return fmt.Errorf("can't marshal key: %w", err) | |
358 | 362 | } |
359 | 363 | |
360 | 364 | if err = bpfMapLookupElem(m.fd, keyPtr, valueOut); err != nil { |
361 | return xerrors.Errorf("lookup failed: %w", err) | |
365 | return fmt.Errorf("lookup failed: %w", err) | |
362 | 366 | } |
363 | 367 | return nil |
364 | 368 | } |
388 | 392 | func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error { |
389 | 393 | keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) |
390 | 394 | if err != nil { |
391 | return xerrors.Errorf("can't marshal key: %w", err) | |
395 | return fmt.Errorf("can't marshal key: %w", err) | |
392 | 396 | } |
393 | 397 | |
394 | 398 | var valuePtr internal.Pointer |
398 | 402 | valuePtr, err = marshalPtr(value, int(m.abi.ValueSize)) |
399 | 403 | } |
400 | 404 | if err != nil { |
401 | return xerrors.Errorf("can't marshal value: %w", err) | |
405 | return fmt.Errorf("can't marshal value: %w", err) | |
402 | 406 | } |
403 | 407 | |
404 | 408 | if err = bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags)); err != nil { |
405 | return xerrors.Errorf("update failed: %w", err) | |
409 | return fmt.Errorf("update failed: %w", err) | |
406 | 410 | } |
407 | 411 | |
408 | 412 | return nil |
414 | 418 | func (m *Map) Delete(key interface{}) error { |
415 | 419 | keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) |
416 | 420 | if err != nil { |
417 | return xerrors.Errorf("can't marshal key: %w", err) | |
421 | return fmt.Errorf("can't marshal key: %w", err) | |
418 | 422 | } |
419 | 423 | |
420 | 424 | if err = bpfMapDeleteElem(m.fd, keyPtr); err != nil { |
421 | return xerrors.Errorf("delete failed: %w", err) | |
425 | return fmt.Errorf("delete failed: %w", err) | |
422 | 426 | } |
423 | 427 | return nil |
424 | 428 | } |
440 | 444 | } |
441 | 445 | |
442 | 446 | if err := unmarshalBytes(nextKeyOut, nextKeyBytes); err != nil { |
443 | return xerrors.Errorf("can't unmarshal next key: %w", err) | |
447 | return fmt.Errorf("can't unmarshal next key: %w", err) | |
444 | 448 | } |
445 | 449 | return nil |
446 | 450 | } |
457 | 461 | nextKeyPtr := internal.NewSlicePointer(nextKey) |
458 | 462 | |
459 | 463 | err := m.nextKey(key, nextKeyPtr) |
460 | if xerrors.Is(err, ErrKeyNotExist) { | |
464 | if errors.Is(err, ErrKeyNotExist) { | |
461 | 465 | return nil, nil |
462 | 466 | } |
463 | 467 | |
473 | 477 | if key != nil { |
474 | 478 | keyPtr, err = marshalPtr(key, int(m.abi.KeySize)) |
475 | 479 | if err != nil { |
476 | return xerrors.Errorf("can't marshal key: %w", err) | |
480 | return fmt.Errorf("can't marshal key: %w", err) | |
477 | 481 | } |
478 | 482 | } |
479 | 483 | |
480 | 484 | if err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut); err != nil { |
481 | return xerrors.Errorf("next key failed: %w", err) | |
485 | return fmt.Errorf("next key failed: %w", err) | |
482 | 486 | } |
483 | 487 | return nil |
484 | 488 | } |
531 | 535 | |
532 | 536 | dup, err := m.fd.Dup() |
533 | 537 | if err != nil { |
534 | return nil, xerrors.Errorf("can't clone map: %w", err) | |
538 | return nil, fmt.Errorf("can't clone map: %w", err) | |
535 | 539 | } |
536 | 540 | |
537 | 541 | return newMap(dup, m.name, &m.abi) |
541 | 545 | // |
542 | 546 | // This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional |
543 | 547 | func (m *Map) Pin(fileName string) error { |
544 | return bpfPinObject(fileName, m.fd) | |
548 | return internal.BPFObjPin(fileName, m.fd) | |
545 | 549 | } |
546 | 550 | |
547 | 551 | // Freeze prevents a map to be modified from user space. |
549 | 553 | // It makes no changes to kernel-side restrictions. |
550 | 554 | func (m *Map) Freeze() error { |
551 | 555 | if err := haveMapMutabilityModifiers(); err != nil { |
552 | return xerrors.Errorf("can't freeze map: %w", err) | |
556 | return fmt.Errorf("can't freeze map: %w", err) | |
553 | 557 | } |
554 | 558 | |
555 | 559 | if err := bpfMapFreeze(m.fd); err != nil { |
556 | return xerrors.Errorf("can't freeze map: %w", err) | |
560 | return fmt.Errorf("can't freeze map: %w", err) | |
557 | 561 | } |
558 | 562 | return nil |
559 | 563 | } |
561 | 565 | func (m *Map) populate(contents []MapKV) error { |
562 | 566 | for _, kv := range contents { |
563 | 567 | if err := m.Put(kv.Key, kv.Value); err != nil { |
564 | return xerrors.Errorf("key %v: %w", kv.Key, err) | |
568 | return fmt.Errorf("key %v: %w", kv.Key, err) | |
565 | 569 | } |
566 | 570 | } |
567 | 571 | return nil |
572 | 576 | // The function is not compatible with nested maps. |
573 | 577 | // Use LoadPinnedMapExplicit in these situations. |
574 | 578 | func LoadPinnedMap(fileName string) (*Map, error) { |
575 | fd, err := bpfGetObject(fileName) | |
579 | fd, err := internal.BPFObjGet(fileName) | |
576 | 580 | if err != nil { |
577 | 581 | return nil, err |
578 | 582 | } |
586 | 590 | |
587 | 591 | // LoadPinnedMapExplicit loads a map with explicit parameters. |
588 | 592 | func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) { |
589 | fd, err := bpfGetObject(fileName) | |
593 | fd, err := internal.BPFObjGet(fileName) | |
590 | 594 | if err != nil { |
591 | 595 | return nil, err |
592 | 596 | } |
595 | 599 | |
596 | 600 | func unmarshalMap(buf []byte) (*Map, error) { |
597 | 601 | if len(buf) != 4 { |
598 | return nil, xerrors.New("map id requires 4 byte value") | |
602 | return nil, errors.New("map id requires 4 byte value") | |
599 | 603 | } |
600 | 604 | |
601 | 605 | // Looking up an entry in a nested map or prog array returns an id, |
620 | 624 | replaced := make(map[string]bool) |
621 | 625 | replace := func(name string, offset, size int, replacement interface{}) error { |
622 | 626 | if offset+size > len(value) { |
623 | return xerrors.Errorf("%s: offset %d(+%d) is out of bounds", name, offset, size) | |
627 | return fmt.Errorf("%s: offset %d(+%d) is out of bounds", name, offset, size) | |
624 | 628 | } |
625 | 629 | |
626 | 630 | buf, err := marshalBytes(replacement, size) |
627 | 631 | if err != nil { |
628 | return xerrors.Errorf("marshal %s: %w", name, err) | |
632 | return fmt.Errorf("marshal %s: %w", name, err) | |
629 | 633 | } |
630 | 634 | |
631 | 635 | copy(value[offset:offset+size], buf) |
649 | 653 | } |
650 | 654 | |
651 | 655 | default: |
652 | return xerrors.Errorf("patching %T is not supported", typ) | |
656 | return fmt.Errorf("patching %T is not supported", typ) | |
653 | 657 | } |
654 | 658 | |
655 | 659 | if len(replaced) == len(replacements) { |
664 | 668 | } |
665 | 669 | |
666 | 670 | if len(missing) == 1 { |
667 | return xerrors.Errorf("unknown field: %s", missing[0]) | |
668 | } | |
669 | ||
670 | return xerrors.Errorf("unknown fields: %s", strings.Join(missing, ",")) | |
671 | return fmt.Errorf("unknown field: %s", missing[0]) | |
672 | } | |
673 | ||
674 | return fmt.Errorf("unknown fields: %s", strings.Join(missing, ",")) | |
671 | 675 | } |
672 | 676 | |
673 | 677 | // MapIterator iterates a Map. |
725 | 729 | mi.prevKey = mi.prevBytes |
726 | 730 | |
727 | 731 | mi.err = mi.target.Lookup(nextBytes, valueOut) |
728 | if xerrors.Is(mi.err, ErrKeyNotExist) { | |
732 | if errors.Is(mi.err, ErrKeyNotExist) { | |
729 | 733 | // Even though the key should be valid, we couldn't look up |
730 | 734 | // its value. If we're iterating a hash map this is probably |
731 | 735 | // because a concurrent delete removed the value before we |
744 | 748 | return mi.err == nil |
745 | 749 | } |
746 | 750 | |
747 | mi.err = xerrors.Errorf("%w", ErrIterationAborted) | |
751 | mi.err = fmt.Errorf("%w", ErrIterationAborted) | |
748 | 752 | return false |
749 | 753 | } |
750 | 754 | |
761 | 765 | // |
762 | 766 | // Returns ErrNotExist, if there is no next eBPF map. |
763 | 767 | func MapGetNextID(startID MapID) (MapID, error) { |
764 | id, err := objGetNextID(_MapGetNextID, uint32(startID)) | |
768 | id, err := objGetNextID(internal.BPF_MAP_GET_NEXT_ID, uint32(startID)) | |
765 | 769 | return MapID(id), err |
766 | 770 | } |
767 | 771 | |
769 | 773 | // |
770 | 774 | // Returns ErrNotExist, if there is no eBPF map with the given id. |
771 | 775 | func NewMapFromID(id MapID) (*Map, error) { |
772 | fd, err := bpfObjGetFDByID(_MapGetFDByID, uint32(id)) | |
776 | fd, err := bpfObjGetFDByID(internal.BPF_MAP_GET_FD_BY_ID, uint32(id)) | |
773 | 777 | if err != nil { |
774 | 778 | return nil, err |
775 | 779 | } |
0 | 0 | package ebpf |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
3 | 4 | "fmt" |
4 | 5 | "io/ioutil" |
5 | 6 | "math" |
13 | 14 | "github.com/cilium/ebpf/internal" |
14 | 15 | "github.com/cilium/ebpf/internal/testutils" |
15 | 16 | "github.com/cilium/ebpf/internal/unix" |
16 | ||
17 | "golang.org/x/xerrors" | |
18 | 17 | ) |
19 | 18 | |
20 | 19 | func TestMain(m *testing.M) { |
21 | err := unix.Setrlimit(8, &unix.Rlimit{ | |
22 | Cur: math.MaxUint64, | |
23 | Max: math.MaxUint64, | |
20 | err := unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{ | |
21 | Cur: unix.RLIM_INFINITY, | |
22 | Max: unix.RLIM_INFINITY, | |
24 | 23 | }) |
25 | 24 | if err != nil { |
26 | 25 | fmt.Println("WARNING: Failed to adjust rlimit, tests may fail") |
74 | 73 | t.Fatal("Can't close map:", err) |
75 | 74 | } |
76 | 75 | |
77 | if err := m.Put(uint32(0), uint32(42)); !xerrors.Is(err, internal.ErrClosedFd) { | |
76 | if err := m.Put(uint32(0), uint32(42)); !errors.Is(err, internal.ErrClosedFd) { | |
78 | 77 | t.Fatal("Put doesn't check for closed fd", err) |
79 | 78 | } |
80 | 79 | |
81 | if _, err := m.LookupBytes(uint32(0)); !xerrors.Is(err, internal.ErrClosedFd) { | |
80 | if _, err := m.LookupBytes(uint32(0)); !errors.Is(err, internal.ErrClosedFd) { | |
82 | 81 | t.Fatal("Get doesn't check for closed fd", err) |
83 | 82 | } |
84 | 83 | } |
200 | 199 | t.Error("Want value 4242, got", v) |
201 | 200 | } |
202 | 201 | |
203 | if err := m.LookupAndDelete(nil, &v); !xerrors.Is(err, ErrKeyNotExist) { | |
202 | if err := m.LookupAndDelete(nil, &v); !errors.Is(err, ErrKeyNotExist) { | |
204 | 203 | t.Fatal("Lookup and delete on empty Queue:", err) |
205 | 204 | } |
206 | 205 | } |
434 | 433 | |
435 | 434 | var tmp uint32 |
436 | 435 | err := hash.Lookup("hello", &tmp) |
437 | if !xerrors.Is(err, ErrKeyNotExist) { | |
436 | if !errors.Is(err, ErrKeyNotExist) { | |
438 | 437 | t.Error("Lookup doesn't return ErrKeyNotExist") |
439 | 438 | } |
440 | 439 | |
446 | 445 | t.Error("LookupBytes returns non-nil buffer for non-existent key") |
447 | 446 | } |
448 | 447 | |
449 | if err := hash.Delete("hello"); !xerrors.Is(err, ErrKeyNotExist) { | |
448 | if err := hash.Delete("hello"); !errors.Is(err, ErrKeyNotExist) { | |
450 | 449 | t.Error("Deleting unknown key doesn't return ErrKeyNotExist") |
451 | 450 | } |
452 | 451 | |
453 | if err := hash.NextKey(nil, &tmp); !xerrors.Is(err, ErrKeyNotExist) { | |
452 | if err := hash.NextKey(nil, &tmp); !errors.Is(err, ErrKeyNotExist) { | |
454 | 453 | t.Error("Looking up next key in empty map doesn't return a non-existing error") |
454 | } | |
455 | } | |
456 | ||
457 | func TestExist(t *testing.T) { | |
458 | hash := createHash() | |
459 | defer hash.Close() | |
460 | ||
461 | if err := hash.Put("hello", uint32(21)); err != nil { | |
462 | t.Errorf("Failed to put key/value pair into hash: %v", err) | |
463 | } | |
464 | ||
465 | if err := hash.Update("hello", uint32(42), UpdateNoExist); !errors.Is(err, ErrKeyExist) { | |
466 | t.Error("Updating existing key doesn't return ErrKeyExist") | |
455 | 467 | } |
456 | 468 | } |
457 | 469 | |
489 | 501 | } |
490 | 502 | |
491 | 503 | func TestPerCPUMarshaling(t *testing.T) { |
492 | numCPU, err := internal.PossibleCPUs() | |
493 | if err != nil { | |
494 | t.Fatal(err) | |
495 | } | |
496 | if numCPU < 2 { | |
497 | t.Skip("Test requires at least two CPUs") | |
498 | } | |
499 | ||
500 | arr, err := NewMap(&MapSpec{ | |
501 | Type: PerCPUArray, | |
502 | KeySize: 4, | |
503 | ValueSize: 5, | |
504 | MaxEntries: 1, | |
505 | }) | |
506 | if err != nil { | |
507 | t.Fatal(err) | |
508 | } | |
509 | defer arr.Close() | |
510 | ||
511 | values := []*customEncoding{ | |
512 | &customEncoding{"hello"}, | |
513 | &customEncoding{"world"}, | |
514 | } | |
515 | if err := arr.Put(uint32(0), values); err != nil { | |
516 | t.Fatal(err) | |
517 | } | |
518 | ||
519 | // Make sure unmarshaling works on slices containing pointers | |
520 | var retrieved []*customEncoding | |
521 | if err := arr.Lookup(uint32(0), &retrieved); err != nil { | |
522 | t.Fatal("Can't retrieve key 0:", err) | |
523 | } | |
524 | ||
525 | for i, want := range []string{"HELLO", "WORLD"} { | |
526 | if retrieved[i] == nil { | |
527 | t.Error("First item is nil") | |
528 | } else if have := retrieved[i].data; have != want { | |
529 | t.Errorf("Put doesn't use BinaryMarshaler, expected %s but got %s", want, have) | |
530 | } | |
504 | for _, typ := range []MapType{PerCPUHash, PerCPUArray, LRUCPUHash} { | |
505 | t.Run(typ.String(), func(t *testing.T) { | |
506 | numCPU, err := internal.PossibleCPUs() | |
507 | if err != nil { | |
508 | t.Fatal(err) | |
509 | } | |
510 | if numCPU < 2 { | |
511 | t.Skip("Test requires at least two CPUs") | |
512 | } | |
513 | if typ == LRUCPUHash { | |
514 | testutils.SkipOnOldKernel(t, "4.10", "LRU per-CPU hash") | |
515 | } | |
516 | ||
517 | arr, err := NewMap(&MapSpec{ | |
518 | Type: typ, | |
519 | KeySize: 4, | |
520 | ValueSize: 5, | |
521 | MaxEntries: 1, | |
522 | }) | |
523 | if err != nil { | |
524 | t.Fatal(err) | |
525 | } | |
526 | defer arr.Close() | |
527 | ||
528 | values := []*customEncoding{ | |
529 | {"hello"}, | |
530 | {"world"}, | |
531 | } | |
532 | if err := arr.Put(uint32(0), values); err != nil { | |
533 | t.Fatal(err) | |
534 | } | |
535 | ||
536 | // Make sure unmarshaling works on slices containing pointers | |
537 | var retrieved []*customEncoding | |
538 | if err := arr.Lookup(uint32(0), &retrieved); err != nil { | |
539 | t.Fatal("Can't retrieve key 0:", err) | |
540 | } | |
541 | ||
542 | for i, want := range []string{"HELLO", "WORLD"} { | |
543 | if retrieved[i] == nil { | |
544 | t.Error("First item is nil") | |
545 | } else if have := retrieved[i].data; have != want { | |
546 | t.Errorf("Put doesn't use BinaryMarshaler, expected %s but got %s", want, have) | |
547 | } | |
548 | } | |
549 | ||
550 | }) | |
531 | 551 | } |
532 | 552 | } |
533 | 553 | |
709 | 729 | for { |
710 | 730 | last := next |
711 | 731 | if next, err = MapGetNextID(last); err != nil { |
712 | if !xerrors.Is(err, ErrNotExist) { | |
732 | if !errors.Is(err, ErrNotExist) { | |
713 | 733 | t.Fatal("Expected ErrNotExist, got:", err) |
714 | 734 | } |
715 | 735 | break |
739 | 759 | |
740 | 760 | // As there can be multiple maps, we use max(uint32) as MapID to trigger an expected error. |
741 | 761 | _, err = NewMapFromID(MapID(math.MaxUint32)) |
742 | if !xerrors.Is(err, ErrNotExist) { | |
762 | if !errors.Is(err, ErrNotExist) { | |
743 | 763 | t.Fatal("Expected ErrNotExist, got:", err) |
744 | 764 | } |
745 | 765 | } |
898 | 918 | |
899 | 919 | for i := 0; i < b.N; i++ { |
900 | 920 | err := m.Delete(unsafe.Pointer(&key)) |
901 | if err != nil && !xerrors.Is(err, ErrKeyNotExist) { | |
921 | if err != nil && !errors.Is(err, ErrKeyNotExist) { | |
902 | 922 | b.Fatal(err) |
903 | 923 | } |
904 | 924 | } |
3 | 3 | "bytes" |
4 | 4 | "encoding" |
5 | 5 | "encoding/binary" |
6 | "errors" | |
7 | "fmt" | |
6 | 8 | "reflect" |
7 | 9 | "runtime" |
8 | 10 | "unsafe" |
9 | 11 | |
10 | 12 | "github.com/cilium/ebpf/internal" |
11 | ||
12 | "golang.org/x/xerrors" | |
13 | 13 | ) |
14 | 14 | |
15 | 15 | func marshalPtr(data interface{}, length int) (internal.Pointer, error) { |
17 | 17 | if length == 0 { |
18 | 18 | return internal.NewPointer(nil), nil |
19 | 19 | } |
20 | return internal.Pointer{}, xerrors.New("can't use nil as key of map") | |
20 | return internal.Pointer{}, errors.New("can't use nil as key of map") | |
21 | 21 | } |
22 | 22 | |
23 | 23 | if ptr, ok := data.(unsafe.Pointer); ok { |
41 | 41 | case []byte: |
42 | 42 | buf = value |
43 | 43 | case unsafe.Pointer: |
44 | err = xerrors.New("can't marshal from unsafe.Pointer") | |
44 | err = errors.New("can't marshal from unsafe.Pointer") | |
45 | 45 | default: |
46 | 46 | var wr bytes.Buffer |
47 | 47 | err = binary.Write(&wr, internal.NativeEndian, value) |
48 | 48 | if err != nil { |
49 | err = xerrors.Errorf("encoding %T: %v", value, err) | |
49 | err = fmt.Errorf("encoding %T: %v", value, err) | |
50 | 50 | } |
51 | 51 | buf = wr.Bytes() |
52 | 52 | } |
55 | 55 | } |
56 | 56 | |
57 | 57 | if len(buf) != length { |
58 | return nil, xerrors.Errorf("%T doesn't marshal to %d bytes", data, length) | |
58 | return nil, fmt.Errorf("%T doesn't marshal to %d bytes", data, length) | |
59 | 59 | } |
60 | 60 | return buf, nil |
61 | 61 | } |
91 | 91 | *value = buf |
92 | 92 | return nil |
93 | 93 | case string: |
94 | return xerrors.New("require pointer to string") | |
94 | return errors.New("require pointer to string") | |
95 | 95 | case []byte: |
96 | return xerrors.New("require pointer to []byte") | |
96 | return errors.New("require pointer to []byte") | |
97 | 97 | default: |
98 | 98 | rd := bytes.NewReader(buf) |
99 | 99 | if err := binary.Read(rd, internal.NativeEndian, value); err != nil { |
100 | return xerrors.Errorf("decoding %T: %v", value, err) | |
100 | return fmt.Errorf("decoding %T: %v", value, err) | |
101 | 101 | } |
102 | 102 | return nil |
103 | 103 | } |
112 | 112 | func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, error) { |
113 | 113 | sliceType := reflect.TypeOf(slice) |
114 | 114 | if sliceType.Kind() != reflect.Slice { |
115 | return internal.Pointer{}, xerrors.New("per-CPU value requires slice") | |
115 | return internal.Pointer{}, errors.New("per-CPU value requires slice") | |
116 | 116 | } |
117 | 117 | |
118 | 118 | possibleCPUs, err := internal.PossibleCPUs() |
123 | 123 | sliceValue := reflect.ValueOf(slice) |
124 | 124 | sliceLen := sliceValue.Len() |
125 | 125 | if sliceLen > possibleCPUs { |
126 | return internal.Pointer{}, xerrors.Errorf("per-CPU value exceeds number of CPUs") | |
126 | return internal.Pointer{}, fmt.Errorf("per-CPU value exceeds number of CPUs") | |
127 | 127 | } |
128 | 128 | |
129 | 129 | alignedElemLength := align(elemLength, 8) |
150 | 150 | func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) error { |
151 | 151 | slicePtrType := reflect.TypeOf(slicePtr) |
152 | 152 | if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice { |
153 | return xerrors.Errorf("per-cpu value requires pointer to slice") | |
153 | return fmt.Errorf("per-cpu value requires pointer to slice") | |
154 | 154 | } |
155 | 155 | |
156 | 156 | possibleCPUs, err := internal.PossibleCPUs() |
169 | 169 | |
170 | 170 | step := len(buf) / possibleCPUs |
171 | 171 | if step < elemLength { |
172 | return xerrors.Errorf("per-cpu element length is larger than available data") | |
172 | return fmt.Errorf("per-cpu element length is larger than available data") | |
173 | 173 | } |
174 | 174 | for i := 0; i < possibleCPUs; i++ { |
175 | 175 | var elem interface{} |
187 | 187 | |
188 | 188 | err := unmarshalBytes(elem, elemBytes) |
189 | 189 | if err != nil { |
190 | return xerrors.Errorf("cpu %d: %w", i, err) | |
190 | return fmt.Errorf("cpu %d: %w", i, err) | |
191 | 191 | } |
192 | 192 | |
193 | 193 | buf = buf[step:] |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "encoding/binary" |
4 | "errors" | |
4 | 5 | "fmt" |
5 | 6 | "io" |
6 | 7 | "math" |
10 | 11 | "github.com/cilium/ebpf" |
11 | 12 | "github.com/cilium/ebpf/internal" |
12 | 13 | "github.com/cilium/ebpf/internal/unix" |
13 | ||
14 | "golang.org/x/xerrors" | |
15 | 14 | ) |
16 | 15 | |
17 | 16 | var ( |
18 | errClosed = xerrors.New("perf reader was closed") | |
19 | errEOR = xerrors.New("end of ring") | |
17 | errClosed = errors.New("perf reader was closed") | |
18 | errEOR = errors.New("end of ring") | |
20 | 19 | ) |
21 | 20 | |
22 | 21 | // perfEventHeader must match 'struct perf_event_header` in <linux/perf_event.h>. |
28 | 27 | |
29 | 28 | func addToEpoll(epollfd, fd int, cpu int) error { |
30 | 29 | if int64(cpu) > math.MaxInt32 { |
31 | return xerrors.Errorf("unsupported CPU number: %d", cpu) | |
30 | return fmt.Errorf("unsupported CPU number: %d", cpu) | |
32 | 31 | } |
33 | 32 | |
34 | 33 | // The representation of EpollEvent isn't entirely accurate. |
42 | 41 | } |
43 | 42 | |
44 | 43 | if err := unix.EpollCtl(epollfd, unix.EPOLL_CTL_ADD, fd, &event); err != nil { |
45 | return xerrors.Errorf("can't add fd to epoll: %v", err) | |
44 | return fmt.Errorf("can't add fd to epoll: %v", err) | |
46 | 45 | } |
47 | 46 | return nil |
48 | 47 | } |
58 | 57 | CPU int |
59 | 58 | |
60 | 59 | // The data submitted via bpf_perf_event_output. |
61 | // They are padded with 0 to have a 64-bit alignment. | |
62 | // If you are using variable length samples you need to take | |
63 | // this into account. | |
60 | // Due to a kernel bug, this can contain between 0 and 7 bytes of trailing | |
61 | // garbage from the ring depending on the input sample's length. | |
64 | 62 | RawSample []byte |
65 | 63 | |
66 | 64 | // The number of samples which could not be output, since |
87 | 85 | } |
88 | 86 | |
89 | 87 | if err != nil { |
90 | return Record{}, xerrors.Errorf("can't read event header: %v", err) | |
88 | return Record{}, fmt.Errorf("can't read event header: %v", err) | |
91 | 89 | } |
92 | 90 | |
93 | 91 | switch header.Type { |
113 | 111 | |
114 | 112 | err := binary.Read(rd, internal.NativeEndian, &lostHeader) |
115 | 113 | if err != nil { |
116 | return 0, xerrors.Errorf("can't read lost records header: %v", err) | |
114 | return 0, fmt.Errorf("can't read lost records header: %v", err) | |
117 | 115 | } |
118 | 116 | |
119 | 117 | return lostHeader.Lost, nil |
123 | 121 | // This must match 'struct perf_event_sample in kernel sources. |
124 | 122 | var size uint32 |
125 | 123 | if err := binary.Read(rd, internal.NativeEndian, &size); err != nil { |
126 | return nil, xerrors.Errorf("can't read sample size: %v", err) | |
124 | return nil, fmt.Errorf("can't read sample size: %v", err) | |
127 | 125 | } |
128 | 126 | |
129 | 127 | data := make([]byte, int(size)) |
130 | 128 | if _, err := io.ReadFull(rd, data); err != nil { |
131 | return nil, xerrors.Errorf("can't read sample: %v", err) | |
129 | return nil, fmt.Errorf("can't read sample: %v", err) | |
132 | 130 | } |
133 | 131 | return data, nil |
134 | 132 | } |
182 | 180 | // NewReaderWithOptions creates a new reader with the given options. |
183 | 181 | func NewReaderWithOptions(array *ebpf.Map, perCPUBuffer int, opts ReaderOptions) (pr *Reader, err error) { |
184 | 182 | if perCPUBuffer < 1 { |
185 | return nil, xerrors.New("perCPUBuffer must be larger than 0") | |
183 | return nil, errors.New("perCPUBuffer must be larger than 0") | |
186 | 184 | } |
187 | 185 | |
188 | 186 | epollFd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC) |
189 | 187 | if err != nil { |
190 | return nil, xerrors.Errorf("can't create epoll fd: %v", err) | |
188 | return nil, fmt.Errorf("can't create epoll fd: %v", err) | |
191 | 189 | } |
192 | 190 | |
193 | 191 | var ( |
215 | 213 | // Hence we have to create a ring for each CPU. |
216 | 214 | for i := 0; i < nCPU; i++ { |
217 | 215 | ring, err := newPerfEventRing(i, perCPUBuffer, opts.Watermark) |
218 | if xerrors.Is(err, unix.ENODEV) { | |
216 | if errors.Is(err, unix.ENODEV) { | |
219 | 217 | // The requested CPU is currently offline, skip it. |
220 | 218 | rings = append(rings, nil) |
221 | 219 | pauseFds = append(pauseFds, -1) |
223 | 221 | } |
224 | 222 | |
225 | 223 | if err != nil { |
226 | return nil, xerrors.Errorf("failed to create perf ring for CPU %d: %v", i, err) | |
224 | return nil, fmt.Errorf("failed to create perf ring for CPU %d: %v", i, err) | |
227 | 225 | } |
228 | 226 | rings = append(rings, ring) |
229 | 227 | pauseFds = append(pauseFds, ring.fd) |
281 | 279 | internal.NativeEndian.PutUint64(value[:], 1) |
282 | 280 | _, err = unix.Write(pr.closeFd, value[:]) |
283 | 281 | if err != nil { |
284 | err = xerrors.Errorf("can't write event fd: %v", err) | |
282 | err = fmt.Errorf("can't write event fd: %v", err) | |
285 | 283 | return |
286 | 284 | } |
287 | 285 | |
308 | 306 | pr.array.Close() |
309 | 307 | }) |
310 | 308 | if err != nil { |
311 | return xerrors.Errorf("close PerfReader: %w", err) | |
309 | return fmt.Errorf("close PerfReader: %w", err) | |
312 | 310 | } |
313 | 311 | return nil |
314 | 312 | } |
316 | 314 | // Read the next record from the perf ring buffer. |
317 | 315 | // |
318 | 316 | // The function blocks until there are at least Watermark bytes in one |
319 | // of the per CPU buffers. | |
320 | // | |
321 | // Records from buffers below the Watermark are not returned. | |
317 | // of the per CPU buffers. Records from buffers below the Watermark | |
318 | // are not returned. | |
319 | // | |
320 | // Records can contain between 0 and 7 bytes of trailing garbage from the ring | |
321 | // depending on the input sample's length. | |
322 | 322 | // |
323 | 323 | // Calling Close interrupts the function. |
324 | 324 | func (pr *Reader) Read() (Record, error) { |
386 | 386 | } |
387 | 387 | |
388 | 388 | for i := range pr.pauseFds { |
389 | if err := pr.array.Delete(uint32(i)); err != nil && !xerrors.Is(err, ebpf.ErrKeyNotExist) { | |
390 | return xerrors.Errorf("could't delete event fd for CPU %d: %w", i, err) | |
389 | if err := pr.array.Delete(uint32(i)); err != nil && !errors.Is(err, ebpf.ErrKeyNotExist) { | |
390 | return fmt.Errorf("could't delete event fd for CPU %d: %w", i, err) | |
391 | 391 | } |
392 | 392 | } |
393 | 393 | |
411 | 411 | } |
412 | 412 | |
413 | 413 | if err := pr.array.Put(uint32(i), uint32(fd)); err != nil { |
414 | return xerrors.Errorf("couldn't put event fd %d for CPU %d: %w", fd, i, err) | |
414 | return fmt.Errorf("couldn't put event fd %d for CPU %d: %w", fd, i, err) | |
415 | 415 | } |
416 | 416 | } |
417 | 417 | |
425 | 425 | // IsClosed returns true if the error occurred because |
426 | 426 | // a Reader was closed. |
427 | 427 | func IsClosed(err error) bool { |
428 | return xerrors.Is(err, errClosed) | |
428 | return errors.Is(err, errClosed) | |
429 | 429 | } |
430 | 430 | |
431 | 431 | type unknownEventError struct { |
440 | 440 | // because an unknown event was submitted to the perf event ring. |
441 | 441 | func IsUnknownEvent(err error) bool { |
442 | 442 | var uee *unknownEventError |
443 | return xerrors.As(err, &uee) | |
444 | } | |
443 | return errors.As(err, &uee) | |
444 | } |
20 | 20 | ) |
21 | 21 | |
22 | 22 | func TestMain(m *testing.M) { |
23 | err := unix.Setrlimit(8, &unix.Rlimit{ | |
23 | err := unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{ | |
24 | 24 | Cur: unix.RLIM_INFINITY, |
25 | 25 | Max: unix.RLIM_INFINITY, |
26 | 26 | }) |
0 | 0 | package perf |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
4 | "fmt" | |
3 | 5 | "io" |
6 | "math" | |
4 | 7 | "os" |
5 | 8 | "runtime" |
6 | 9 | "sync/atomic" |
7 | 10 | "unsafe" |
8 | 11 | |
9 | 12 | "github.com/cilium/ebpf/internal/unix" |
10 | ||
11 | "golang.org/x/xerrors" | |
12 | 13 | ) |
13 | 14 | |
14 | 15 | // perfEventRing is a page of metadata followed by |
22 | 23 | |
23 | 24 | func newPerfEventRing(cpu, perCPUBuffer, watermark int) (*perfEventRing, error) { |
24 | 25 | if watermark >= perCPUBuffer { |
25 | return nil, xerrors.New("watermark must be smaller than perCPUBuffer") | |
26 | return nil, errors.New("watermark must be smaller than perCPUBuffer") | |
26 | 27 | } |
27 | ||
28 | // Round to nearest page boundary and allocate | |
29 | // an extra page for meta data | |
30 | pageSize := os.Getpagesize() | |
31 | nPages := (perCPUBuffer + pageSize - 1) / pageSize | |
32 | size := (1 + nPages) * pageSize | |
33 | 28 | |
34 | 29 | fd, err := createPerfEvent(cpu, watermark) |
35 | 30 | if err != nil { |
41 | 36 | return nil, err |
42 | 37 | } |
43 | 38 | |
44 | mmap, err := unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) | |
39 | mmap, err := unix.Mmap(fd, 0, perfBufferSize(perCPUBuffer), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) | |
45 | 40 | if err != nil { |
46 | 41 | unix.Close(fd) |
47 | return nil, err | |
42 | return nil, fmt.Errorf("can't mmap: %v", err) | |
48 | 43 | } |
49 | 44 | |
50 | 45 | // This relies on the fact that we allocate an extra metadata page, |
62 | 57 | runtime.SetFinalizer(ring, (*perfEventRing).Close) |
63 | 58 | |
64 | 59 | return ring, nil |
60 | } | |
61 | ||
62 | // mmapBufferSize returns a valid mmap buffer size for use with perf_event_open (1+2^n pages) | |
63 | func perfBufferSize(perCPUBuffer int) int { | |
64 | pageSize := os.Getpagesize() | |
65 | ||
66 | // Smallest whole number of pages | |
67 | nPages := (perCPUBuffer + pageSize - 1) / pageSize | |
68 | ||
69 | // Round up to nearest power of two number of pages | |
70 | nPages = int(math.Pow(2, math.Ceil(math.Log2(float64(nPages))))) | |
71 | ||
72 | // Add one for metadata | |
73 | nPages += 1 | |
74 | ||
75 | return nPages * pageSize | |
65 | 76 | } |
66 | 77 | |
67 | 78 | func (ring *perfEventRing) Close() { |
89 | 100 | attr.Size = uint32(unsafe.Sizeof(attr)) |
90 | 101 | fd, err := unix.PerfEventOpen(&attr, -1, cpu, -1, unix.PERF_FLAG_FD_CLOEXEC) |
91 | 102 | if err != nil { |
92 | return -1, xerrors.Errorf("can't create perf event: %w", err) | |
103 | return -1, fmt.Errorf("can't create perf event: %w", err) | |
93 | 104 | } |
94 | 105 | return fd, nil |
95 | 106 | } |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | 4 | "io" |
5 | "os" | |
5 | 6 | "testing" |
6 | 7 | |
7 | 8 | "github.com/cilium/ebpf/internal/unix" |
61 | 62 | |
62 | 63 | return newRingReader(&meta, ring) |
63 | 64 | } |
65 | ||
66 | func TestPerfEventRing(t *testing.T) { | |
67 | check := func(buffer, watermark int) { | |
68 | ring, err := newPerfEventRing(0, buffer, watermark) | |
69 | if err != nil { | |
70 | t.Fatal(err) | |
71 | } | |
72 | ||
73 | size := len(ring.ringReader.ring) | |
74 | ||
75 | // Ring size should be at least as big as buffer | |
76 | if size < buffer { | |
77 | t.Fatalf("ring size %d smaller than buffer %d", size, buffer) | |
78 | } | |
79 | ||
80 | // Ring size should be of the form 2^n pages (meta page has already been removed) | |
81 | if size%os.Getpagesize() != 0 { | |
82 | t.Fatalf("ring size %d not whole number of pages (pageSize %d)", size, os.Getpagesize()) | |
83 | } | |
84 | nPages := size / os.Getpagesize() | |
85 | if nPages&(nPages-1) != 0 { | |
86 | t.Fatalf("ring size %d (%d pages) not a power of two pages (pageSize %d)", size, nPages, os.Getpagesize()) | |
87 | } | |
88 | } | |
89 | ||
90 | // watermark > buffer | |
91 | _, err := newPerfEventRing(0, 8192, 8193) | |
92 | if err == nil { | |
93 | t.Fatal("watermark > buffer allowed") | |
94 | } | |
95 | ||
96 | // watermark == buffer | |
97 | _, err = newPerfEventRing(0, 8192, 8192) | |
98 | if err == nil { | |
99 | t.Fatal("watermark == buffer allowed") | |
100 | } | |
101 | ||
102 | // buffer not a power of two, watermark < buffer | |
103 | check(8193, 8192) | |
104 | ||
105 | // large buffer not a multiple of page size at all (prime) | |
106 | check(65537, 8192) | |
107 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "encoding/binary" | |
5 | "errors" | |
4 | 6 | "fmt" |
5 | 7 | "math" |
6 | 8 | "strings" |
7 | 9 | "time" |
8 | "unsafe" | |
9 | 10 | |
10 | 11 | "github.com/cilium/ebpf/asm" |
11 | 12 | "github.com/cilium/ebpf/internal" |
12 | 13 | "github.com/cilium/ebpf/internal/btf" |
13 | 14 | "github.com/cilium/ebpf/internal/unix" |
14 | ||
15 | "golang.org/x/xerrors" | |
16 | 15 | ) |
17 | 16 | |
18 | 17 | // ErrNotSupported is returned whenever the kernel doesn't support a feature. |
46 | 45 | type ProgramSpec struct { |
47 | 46 | // Name is passed to the kernel as a debug aid. Must only contain |
48 | 47 | // alpha numeric and '_' characters. |
49 | Name string | |
50 | Type ProgramType | |
51 | AttachType AttachType | |
52 | Instructions asm.Instructions | |
53 | License string | |
48 | Name string | |
49 | // Type determines at which hook in the kernel a program will run. | |
50 | Type ProgramType | |
51 | AttachType AttachType | |
52 | // Name of a kernel data structure to attach to. It's interpretation | |
53 | // depends on Type and AttachType. | |
54 | AttachTo string | |
55 | Instructions asm.Instructions | |
56 | ||
57 | // License of the program. Some helpers are only available if | |
58 | // the license is deemed compatible with the GPL. | |
59 | // | |
60 | // See https://www.kernel.org/doc/html/latest/process/license-rules.html#id1 | |
61 | License string | |
62 | ||
63 | // Version used by tracing programs. | |
64 | // | |
65 | // Deprecated: superseded by BTF. | |
54 | 66 | KernelVersion uint32 |
55 | 67 | |
56 | 68 | // The BTF associated with this program. Changing Instructions |
57 | 69 | // will most likely invalidate the contained data, and may |
58 | 70 | // result in errors when attempting to load it into the kernel. |
59 | 71 | BTF *btf.Program |
72 | ||
73 | // The byte order this program was compiled for, may be nil. | |
74 | ByteOrder binary.ByteOrder | |
60 | 75 | } |
61 | 76 | |
62 | 77 | // Copy returns a copy of the spec. |
79 | 94 | // otherwise it is empty. |
80 | 95 | VerifierLog string |
81 | 96 | |
82 | fd *internal.FD | |
83 | name string | |
84 | abi ProgramABI | |
97 | fd *internal.FD | |
98 | name string | |
99 | abi ProgramABI | |
100 | attachType AttachType | |
85 | 101 | } |
86 | 102 | |
87 | 103 | // NewProgram creates a new Program. |
102 | 118 | } |
103 | 119 | |
104 | 120 | handle, err := btf.NewHandle(btf.ProgramSpec(spec.BTF)) |
105 | if err != nil && !xerrors.Is(err, btf.ErrNotSupported) { | |
106 | return nil, xerrors.Errorf("can't load BTF: %w", err) | |
121 | if err != nil && !errors.Is(err, btf.ErrNotSupported) { | |
122 | return nil, fmt.Errorf("can't load BTF: %w", err) | |
107 | 123 | } |
108 | 124 | |
109 | 125 | return newProgramWithBTF(spec, handle, opts) |
147 | 163 | } |
148 | 164 | |
149 | 165 | err = internal.ErrorWithLog(err, logBuf, logErr) |
150 | return nil, xerrors.Errorf("can't load program: %w", err) | |
166 | return nil, fmt.Errorf("can't load program: %w", err) | |
151 | 167 | } |
152 | 168 | |
153 | 169 | // NewProgramFromFD creates a program from a raw fd. |
157 | 173 | // Requires at least Linux 4.11. |
158 | 174 | func NewProgramFromFD(fd int) (*Program, error) { |
159 | 175 | if fd < 0 { |
160 | return nil, xerrors.New("invalid fd") | |
176 | return nil, errors.New("invalid fd") | |
161 | 177 | } |
162 | 178 | bpfFd := internal.NewFD(uint32(fd)) |
163 | 179 | |
180 | 196 | |
181 | 197 | func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr, error) { |
182 | 198 | if len(spec.Instructions) == 0 { |
183 | return nil, xerrors.New("Instructions cannot be empty") | |
199 | return nil, errors.New("Instructions cannot be empty") | |
184 | 200 | } |
185 | 201 | |
186 | 202 | if len(spec.License) == 0 { |
187 | return nil, xerrors.New("License cannot be empty") | |
203 | return nil, errors.New("License cannot be empty") | |
204 | } | |
205 | ||
206 | if spec.ByteOrder != nil && spec.ByteOrder != internal.NativeEndian { | |
207 | return nil, fmt.Errorf("can't load %s program on %s", spec.ByteOrder, internal.NativeEndian) | |
188 | 208 | } |
189 | 209 | |
190 | 210 | buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize)) |
213 | 233 | |
214 | 234 | recSize, bytes, err := btf.ProgramLineInfos(spec.BTF) |
215 | 235 | if err != nil { |
216 | return nil, xerrors.Errorf("can't get BTF line infos: %w", err) | |
236 | return nil, fmt.Errorf("can't get BTF line infos: %w", err) | |
217 | 237 | } |
218 | 238 | attr.lineInfoRecSize = recSize |
219 | 239 | attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize)) |
221 | 241 | |
222 | 242 | recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF) |
223 | 243 | if err != nil { |
224 | return nil, xerrors.Errorf("can't get BTF function infos: %w", err) | |
244 | return nil, fmt.Errorf("can't get BTF function infos: %w", err) | |
225 | 245 | } |
226 | 246 | attr.funcInfoRecSize = recSize |
227 | 247 | attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize)) |
228 | 248 | attr.funcInfo = internal.NewSlicePointer(bytes) |
249 | } | |
250 | ||
251 | if spec.AttachTo != "" { | |
252 | target, err := resolveBTFType(spec.AttachTo, spec.Type, spec.AttachType) | |
253 | if err != nil { | |
254 | return nil, err | |
255 | } | |
256 | if target != nil { | |
257 | attr.attachBTFID = target.ID() | |
258 | } | |
229 | 259 | } |
230 | 260 | |
231 | 261 | return attr, nil |
269 | 299 | |
270 | 300 | dup, err := p.fd.Dup() |
271 | 301 | if err != nil { |
272 | return nil, xerrors.Errorf("can't clone program: %w", err) | |
302 | return nil, fmt.Errorf("can't clone program: %w", err) | |
273 | 303 | } |
274 | 304 | |
275 | 305 | return newProgram(dup, p.name, &p.abi), nil |
279 | 309 | // |
280 | 310 | // This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional |
281 | 311 | func (p *Program) Pin(fileName string) error { |
282 | if err := bpfPinObject(fileName, p.fd); err != nil { | |
283 | return xerrors.Errorf("can't pin program: %w", err) | |
312 | if err := internal.BPFObjPin(fileName, p.fd); err != nil { | |
313 | return fmt.Errorf("can't pin program: %w", err) | |
284 | 314 | } |
285 | 315 | return nil |
286 | 316 | } |
304 | 334 | func (p *Program) Test(in []byte) (uint32, []byte, error) { |
305 | 335 | ret, out, _, err := p.testRun(in, 1, nil) |
306 | 336 | if err != nil { |
307 | return ret, nil, xerrors.Errorf("can't test program: %w", err) | |
337 | return ret, nil, fmt.Errorf("can't test program: %w", err) | |
308 | 338 | } |
309 | 339 | return ret, out, nil |
310 | 340 | } |
323 | 353 | func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.Duration, error) { |
324 | 354 | ret, _, total, err := p.testRun(in, repeat, reset) |
325 | 355 | if err != nil { |
326 | return ret, total, xerrors.Errorf("can't benchmark program: %w", err) | |
356 | return ret, total, fmt.Errorf("can't benchmark program: %w", err) | |
327 | 357 | } |
328 | 358 | return ret, total, nil |
329 | 359 | } |
330 | 360 | |
331 | var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() bool { | |
361 | var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() (bool, error) { | |
332 | 362 | prog, err := NewProgram(&ProgramSpec{ |
333 | 363 | Type: SocketFilter, |
334 | 364 | Instructions: asm.Instructions{ |
339 | 369 | }) |
340 | 370 | if err != nil { |
341 | 371 | // This may be because we lack sufficient permissions, etc. |
342 | return false | |
372 | return false, err | |
343 | 373 | } |
344 | 374 | defer prog.Close() |
345 | ||
346 | fd, err := prog.fd.Value() | |
347 | if err != nil { | |
348 | return false | |
349 | } | |
350 | 375 | |
351 | 376 | // Programs require at least 14 bytes input |
352 | 377 | in := make([]byte, 14) |
353 | 378 | attr := bpfProgTestRunAttr{ |
354 | fd: fd, | |
379 | fd: uint32(prog.FD()), | |
355 | 380 | dataSizeIn: uint32(len(in)), |
356 | 381 | dataIn: internal.NewSlicePointer(in), |
357 | 382 | } |
358 | 383 | |
359 | _, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
384 | err = bpfProgTestRun(&attr) | |
360 | 385 | |
361 | 386 | // Check for EINVAL specifically, rather than err != nil since we |
362 | 387 | // otherwise misdetect due to insufficient permissions. |
363 | return !xerrors.Is(err, unix.EINVAL) | |
388 | return !errors.Is(err, unix.EINVAL), nil | |
364 | 389 | }) |
365 | 390 | |
366 | 391 | func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte, time.Duration, error) { |
402 | 427 | } |
403 | 428 | |
404 | 429 | for { |
405 | _, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
430 | err = bpfProgTestRun(&attr) | |
406 | 431 | if err == nil { |
407 | 432 | break |
408 | 433 | } |
409 | 434 | |
410 | if xerrors.Is(err, unix.EINTR) { | |
435 | if errors.Is(err, unix.EINTR) { | |
411 | 436 | if reset != nil { |
412 | 437 | reset() |
413 | 438 | } |
414 | 439 | continue |
415 | 440 | } |
416 | 441 | |
417 | return 0, nil, 0, xerrors.Errorf("can't run test: %w", err) | |
442 | return 0, nil, 0, fmt.Errorf("can't run test: %w", err) | |
418 | 443 | } |
419 | 444 | |
420 | 445 | if int(attr.dataSizeOut) > cap(out) { |
430 | 455 | |
431 | 456 | func unmarshalProgram(buf []byte) (*Program, error) { |
432 | 457 | if len(buf) != 4 { |
433 | return nil, xerrors.New("program id requires 4 byte value") | |
458 | return nil, errors.New("program id requires 4 byte value") | |
434 | 459 | } |
435 | 460 | |
436 | 461 | // Looking up an entry in a nested map or prog array returns an id, |
451 | 476 | return buf, nil |
452 | 477 | } |
453 | 478 | |
454 | // Attach a Program to a container object fd | |
479 | // Attach a Program. | |
480 | // | |
481 | // Deprecated: use link.RawAttachProgram instead. | |
455 | 482 | func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error { |
456 | 483 | if fd < 0 { |
457 | return xerrors.New("invalid fd") | |
484 | return errors.New("invalid fd") | |
458 | 485 | } |
459 | 486 | |
460 | 487 | pfd, err := p.fd.Value() |
462 | 489 | return err |
463 | 490 | } |
464 | 491 | |
465 | attr := bpfProgAlterAttr{ | |
466 | targetFd: uint32(fd), | |
467 | attachBpfFd: pfd, | |
468 | attachType: uint32(typ), | |
469 | attachFlags: uint32(flags), | |
470 | } | |
471 | ||
472 | return bpfProgAlter(_ProgAttach, &attr) | |
473 | } | |
474 | ||
475 | // Detach a Program from a container object fd | |
492 | attr := internal.BPFProgAttachAttr{ | |
493 | TargetFd: uint32(fd), | |
494 | AttachBpfFd: pfd, | |
495 | AttachType: uint32(typ), | |
496 | AttachFlags: uint32(flags), | |
497 | } | |
498 | ||
499 | return internal.BPFProgAttach(&attr) | |
500 | } | |
501 | ||
502 | // Detach a Program. | |
503 | // | |
504 | // Deprecated: use link.RawDetachProgram instead. | |
476 | 505 | func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error { |
477 | 506 | if fd < 0 { |
478 | return xerrors.New("invalid fd") | |
507 | return errors.New("invalid fd") | |
508 | } | |
509 | ||
510 | if flags != 0 { | |
511 | return errors.New("flags must be zero") | |
479 | 512 | } |
480 | 513 | |
481 | 514 | pfd, err := p.fd.Value() |
483 | 516 | return err |
484 | 517 | } |
485 | 518 | |
486 | attr := bpfProgAlterAttr{ | |
487 | targetFd: uint32(fd), | |
488 | attachBpfFd: pfd, | |
489 | attachType: uint32(typ), | |
490 | attachFlags: uint32(flags), | |
491 | } | |
492 | ||
493 | return bpfProgAlter(_ProgDetach, &attr) | |
519 | attr := internal.BPFProgDetachAttr{ | |
520 | TargetFd: uint32(fd), | |
521 | AttachBpfFd: pfd, | |
522 | AttachType: uint32(typ), | |
523 | } | |
524 | ||
525 | return internal.BPFProgDetach(&attr) | |
494 | 526 | } |
495 | 527 | |
496 | 528 | // LoadPinnedProgram loads a Program from a BPF file. |
497 | 529 | // |
498 | 530 | // Requires at least Linux 4.11. |
499 | 531 | func LoadPinnedProgram(fileName string) (*Program, error) { |
500 | fd, err := bpfGetObject(fileName) | |
532 | fd, err := internal.BPFObjGet(fileName) | |
501 | 533 | if err != nil { |
502 | 534 | return nil, err |
503 | 535 | } |
505 | 537 | name, abi, err := newProgramABIFromFd(fd) |
506 | 538 | if err != nil { |
507 | 539 | _ = fd.Close() |
508 | return nil, xerrors.Errorf("can't get ABI for %s: %w", fileName, err) | |
540 | return nil, fmt.Errorf("can't get ABI for %s: %w", fileName, err) | |
509 | 541 | } |
510 | 542 | |
511 | 543 | return newProgram(fd, name, abi), nil |
531 | 563 | // |
532 | 564 | // Returns ErrNotExist, if there is no next eBPF program. |
533 | 565 | func ProgramGetNextID(startID ProgramID) (ProgramID, error) { |
534 | id, err := objGetNextID(_ProgGetNextID, uint32(startID)) | |
566 | id, err := objGetNextID(internal.BPF_PROG_GET_NEXT_ID, uint32(startID)) | |
535 | 567 | return ProgramID(id), err |
536 | 568 | } |
537 | 569 | |
539 | 571 | // |
540 | 572 | // Returns ErrNotExist, if there is no eBPF program with the given id. |
541 | 573 | func NewProgramFromID(id ProgramID) (*Program, error) { |
542 | fd, err := bpfObjGetFDByID(_ProgGetFDByID, uint32(id)) | |
574 | fd, err := bpfObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id)) | |
543 | 575 | if err != nil { |
544 | 576 | return nil, err |
545 | 577 | } |
561 | 593 | } |
562 | 594 | return ProgramID(info.id), nil |
563 | 595 | } |
596 | ||
597 | func findKernelType(name string, typ btf.Type) error { | |
598 | kernel, err := btf.LoadKernelSpec() | |
599 | if err != nil { | |
600 | return fmt.Errorf("can't load kernel spec: %w", err) | |
601 | } | |
602 | ||
603 | return kernel.FindType(name, typ) | |
604 | } | |
605 | ||
606 | func resolveBTFType(name string, progType ProgramType, attachType AttachType) (btf.Type, error) { | |
607 | type match struct { | |
608 | p ProgramType | |
609 | a AttachType | |
610 | } | |
611 | ||
612 | target := match{progType, attachType} | |
613 | switch target { | |
614 | case match{Tracing, AttachTraceIter}: | |
615 | var target btf.Func | |
616 | if err := findKernelType("bpf_iter_"+name, &target); err != nil { | |
617 | return nil, fmt.Errorf("can't resolve BTF for iterator %s: %w", name, err) | |
618 | } | |
619 | ||
620 | return &target, nil | |
621 | ||
622 | default: | |
623 | return nil, nil | |
624 | } | |
625 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "encoding/binary" | |
5 | "errors" | |
4 | 6 | "fmt" |
5 | 7 | "io/ioutil" |
6 | 8 | "math" |
16 | 18 | "github.com/cilium/ebpf/internal" |
17 | 19 | "github.com/cilium/ebpf/internal/testutils" |
18 | 20 | "github.com/cilium/ebpf/internal/unix" |
19 | "golang.org/x/xerrors" | |
20 | 21 | ) |
21 | 22 | |
22 | 23 | func TestProgramRun(t *testing.T) { |
269 | 270 | } |
270 | 271 | |
271 | 272 | var ve *internal.VerifierError |
272 | if !xerrors.As(err, &ve) { | |
273 | if !errors.As(err, &ve) { | |
273 | 274 | t.Error("Error is not a VerifierError") |
274 | 275 | } |
275 | 276 | } |
386 | 387 | prog2.Close() |
387 | 388 | } |
388 | 389 | |
389 | func TestProgramAlter(t *testing.T) { | |
390 | testutils.SkipOnOldKernel(t, "4.13", "SkSKB type") | |
391 | ||
392 | var err error | |
393 | var prog *Program | |
394 | prog, err = NewProgram(&ProgramSpec{ | |
395 | Type: SkSKB, | |
396 | Instructions: asm.Instructions{ | |
397 | asm.LoadImm(asm.R0, 0, asm.DWord), | |
398 | asm.Return(), | |
399 | }, | |
400 | License: "MIT", | |
401 | }) | |
402 | if err != nil { | |
403 | t.Fatal(err) | |
404 | } | |
405 | defer prog.Close() | |
406 | ||
407 | var sockMap *Map | |
408 | sockMap, err = NewMap(&MapSpec{ | |
409 | Type: MapType(15), // BPF_MAP_TYPE_SOCKMAP | |
410 | KeySize: 4, | |
411 | ValueSize: 4, | |
412 | MaxEntries: 2, | |
413 | }) | |
414 | if err != nil { | |
415 | t.Fatal(err) | |
416 | } | |
417 | defer sockMap.Close() | |
418 | ||
419 | if err := prog.Attach(sockMap.FD(), AttachSkSKBStreamParser, AttachFlags(0)); err != nil { | |
420 | t.Fatal(err) | |
421 | } | |
422 | if err := prog.Detach(sockMap.FD(), AttachSkSKBStreamParser, AttachFlags(0)); err != nil { | |
423 | t.Fatal(err) | |
424 | } | |
425 | } | |
426 | ||
427 | 390 | func TestHaveProgTestRun(t *testing.T) { |
428 | 391 | testutils.CheckFeatureTest(t, haveProgTestRun) |
429 | 392 | } |
457 | 420 | for { |
458 | 421 | last := next |
459 | 422 | if next, err = ProgramGetNextID(last); err != nil { |
460 | if !xerrors.Is(err, ErrNotExist) { | |
423 | if !errors.Is(err, ErrNotExist) { | |
461 | 424 | t.Fatal("Expected ErrNotExist, got:", err) |
462 | 425 | } |
463 | 426 | break |
496 | 459 | |
497 | 460 | // As there can be multiple programs, we use max(uint32) as ProgramID to trigger an expected error. |
498 | 461 | _, err = NewProgramFromID(ProgramID(math.MaxUint32)) |
499 | if !xerrors.Is(err, ErrNotExist) { | |
462 | if !errors.Is(err, ErrNotExist) { | |
500 | 463 | t.Fatal("Expected ErrNotExist, got:", err) |
464 | } | |
465 | } | |
466 | ||
467 | func TestProgramRejectIncorrectByteOrder(t *testing.T) { | |
468 | spec := socketFilterSpec.Copy() | |
469 | ||
470 | spec.ByteOrder = binary.BigEndian | |
471 | if internal.NativeEndian == binary.BigEndian { | |
472 | spec.ByteOrder = binary.LittleEndian | |
473 | } | |
474 | ||
475 | _, err := NewProgram(spec) | |
476 | if err == nil { | |
477 | t.Error("Incorrect ByteOrder should be rejected at load time") | |
501 | 478 | } |
502 | 479 | } |
503 | 480 |
0 | 0 | eBPF |
1 | 1 | ------- |
2 | [![](https://godoc.org/github.com/cilium/ebpf?status.svg)](https://godoc.org/github.com/cilium/ebpf) | |
2 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/cilium/ebpf)](https://pkg.go.dev/github.com/cilium/ebpf) | |
3 | 3 | |
4 | 4 | eBPF is a pure Go library that provides utilities for loading, compiling, and debugging eBPF programs. It has minimal external dependencies and is intended to be used in long running processes. |
5 | 5 | |
6 | [ebpf/asm](https://godoc.org/github.com/cilium/ebpf/asm) contains a basic assembler. | |
6 | * [ebpf/link](https://pkg.go.dev/github.com/cilium/ebpf/link) allows attaching eBPF to various hooks. | |
7 | * [ebpf/asm](https://pkg.go.dev/github.com/cilium/ebpf/asm) contains a basic assembler. | |
7 | 8 | |
8 | 9 | The library is maintained by [Cloudflare](https://www.cloudflare.com) and [Cilium](https://www.cilium.io). Feel free to [join](https://cilium.herokuapp.com/) the [libbpf-go](https://cilium.slack.com/messages/libbpf-go) channel on Slack. |
9 | 10 |
14 | 14 | export GOPROXY=file:///run/go-root/pkg/mod/cache/download |
15 | 15 | export GOCACHE=/run/go-cache |
16 | 16 | |
17 | elfs="" | |
18 | if [[ -d "/run/input/bpf" ]]; then | |
19 | elfs="/run/input/bpf" | |
20 | fi | |
21 | ||
17 | 22 | echo Running tests... |
18 | /usr/local/bin/go test -coverprofile="$1/coverage.txt" -covermode=atomic -v ./... | |
23 | # TestLibBPFCompat runs separately to pass the "-elfs" flag only for it: https://github.com/cilium/ebpf/pull/119 | |
24 | /usr/local/bin/go test -v -elfs "$elfs" -run TestLibBPFCompat | |
25 | /usr/local/bin/go test -v ./... | |
19 | 26 | touch "$1/success" |
20 | 27 | exit 0 |
21 | 28 | fi |
38 | 45 | fi |
39 | 46 | |
40 | 47 | readonly kernel="linux-${kernel_version}.bz" |
48 | readonly selftests="linux-${kernel_version}-selftests-bpf.bz" | |
49 | readonly input="$(mktemp -d)" | |
41 | 50 | readonly output="$(mktemp -d)" |
42 | readonly tmp_dir="${TMPDIR:-$(mktemp -d)}" | |
51 | readonly tmp_dir="${TMPDIR:-/tmp}" | |
52 | readonly branch="${BRANCH:-master}" | |
43 | 53 | |
44 | test -e "${tmp_dir}/${kernel}" || { | |
45 | echo Fetching "${kernel}" | |
46 | curl --fail -L "https://github.com/newtools/ci-kernels/blob/master/${kernel}?raw=true" -o "${tmp_dir}/${kernel}" | |
54 | fetch() { | |
55 | echo Fetching "${1}" | |
56 | wget -nv -N -P "${tmp_dir}" "https://github.com/cilium/ci-kernels/raw/${branch}/${1}" | |
47 | 57 | } |
58 | ||
59 | fetch "${kernel}" | |
60 | ||
61 | if fetch "${selftests}"; then | |
62 | mkdir "${input}/bpf" | |
63 | tar --strip-components=4 -xjf "${tmp_dir}/${selftests}" -C "${input}/bpf" | |
64 | else | |
65 | echo "No selftests found, disabling" | |
66 | fi | |
48 | 67 | |
49 | 68 | echo Testing on "${kernel_version}" |
50 | 69 | $sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \ |
70 | --rwdir=/run/input="${input}" \ | |
51 | 71 | --rwdir=/run/output="${output}" \ |
52 | 72 | --rodir=/run/go-path="$(go env GOPATH)" \ |
53 | 73 | --rwdir=/run/go-cache="$(go env GOCACHE)" \ |
54 | --script-sh "$(realpath "$0") --in-vm /run/output" | |
74 | --script-sh "$(realpath "$0") --in-vm /run/output" \ | |
75 | --qemu-opts -smp 2 # need at least two CPUs for some tests | |
55 | 76 | |
56 | 77 | if [[ ! -e "${output}/success" ]]; then |
57 | 78 | echo "Test failed on ${kernel_version}" |
58 | 79 | exit 1 |
59 | 80 | else |
60 | 81 | echo "Test successful on ${kernel_version}" |
61 | if [[ -v CODECOV_TOKEN ]]; then | |
62 | curl --fail -s https://codecov.io/bash > "${tmp_dir}/codecov.sh" | |
63 | chmod +x "${tmp_dir}/codecov.sh" | |
64 | "${tmp_dir}/codecov.sh" -f "${output}/coverage.txt" | |
65 | fi | |
82 | # if [[ -v CODECOV_TOKEN ]]; then | |
83 | # curl --fail -s https://codecov.io/bash > "${tmp_dir}/codecov.sh" | |
84 | # chmod +x "${tmp_dir}/codecov.sh" | |
85 | # "${tmp_dir}/codecov.sh" -f "${output}/coverage.txt" | |
86 | # fi | |
66 | 87 | fi |
67 | 88 | |
89 | $sudo rm -r "${input}" | |
68 | 90 | $sudo rm -r "${output}" |
0 | 0 | package ebpf |
1 | 1 | |
2 | 2 | import ( |
3 | "path/filepath" | |
3 | "errors" | |
4 | "fmt" | |
5 | "os" | |
4 | 6 | "unsafe" |
5 | 7 | |
6 | 8 | "github.com/cilium/ebpf/internal" |
7 | 9 | "github.com/cilium/ebpf/internal/btf" |
8 | 10 | "github.com/cilium/ebpf/internal/unix" |
9 | ||
10 | "golang.org/x/xerrors" | |
11 | 11 | ) |
12 | 12 | |
13 | 13 | // Generic errors returned by BPF syscalls. |
14 | 14 | var ( |
15 | ErrNotExist = xerrors.New("requested object does not exit") | |
15 | ErrNotExist = errors.New("requested object does not exist") | |
16 | 16 | ) |
17 | 17 | |
18 | 18 | // bpfObjName is a null-terminated string made up of |
76 | 76 | maxEntries uint32 |
77 | 77 | flags uint32 |
78 | 78 | mapName bpfObjName // since 4.15 ad5b177bd73f |
79 | } | |
80 | ||
81 | type bpfPinObjAttr struct { | |
82 | fileName internal.Pointer | |
83 | fd uint32 | |
84 | padding uint32 | |
85 | 79 | } |
86 | 80 | |
87 | 81 | type bpfProgLoadAttr struct { |
104 | 98 | lineInfoRecSize uint32 |
105 | 99 | lineInfo internal.Pointer |
106 | 100 | lineInfoCnt uint32 |
101 | attachBTFID btf.TypeID | |
102 | attachProgFd uint32 | |
107 | 103 | } |
108 | 104 | |
109 | 105 | type bpfProgInfo struct { |
132 | 128 | duration uint32 |
133 | 129 | } |
134 | 130 | |
135 | type bpfProgAlterAttr struct { | |
136 | targetFd uint32 | |
137 | attachBpfFd uint32 | |
138 | attachType uint32 | |
139 | attachFlags uint32 | |
140 | } | |
141 | ||
142 | 131 | type bpfObjGetInfoByFDAttr struct { |
143 | 132 | fd uint32 |
144 | 133 | infoLen uint32 |
162 | 151 | |
163 | 152 | func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) { |
164 | 153 | for { |
165 | fd, err := internal.BPF(_ProgLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
154 | fd, err := internal.BPF(internal.BPF_PROG_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
166 | 155 | // As of ~4.20 the verifier can be interrupted by a signal, |
167 | 156 | // and returns EAGAIN in that case. |
168 | 157 | if err == unix.EAGAIN { |
177 | 166 | } |
178 | 167 | } |
179 | 168 | |
180 | func bpfProgAlter(cmd int, attr *bpfProgAlterAttr) error { | |
181 | _, err := internal.BPF(cmd, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
169 | func bpfProgTestRun(attr *bpfProgTestRunAttr) error { | |
170 | _, err := internal.BPF(internal.BPF_PROG_TEST_RUN, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
182 | 171 | return err |
183 | 172 | } |
184 | 173 | |
185 | 174 | func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) { |
186 | fd, err := internal.BPF(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
175 | fd, err := internal.BPF(internal.BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) | |
176 | if errors.Is(err, os.ErrPermission) { | |
177 | return nil, errors.New("permission denied or insufficient rlimit to lock memory for map") | |
178 | } | |
179 | ||
187 | 180 | if err != nil { |
188 | 181 | return nil, err |
189 | 182 | } |
191 | 184 | return internal.NewFD(uint32(fd)), nil |
192 | 185 | } |
193 | 186 | |
194 | var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool { | |
187 | var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() (bool, error) { | |
195 | 188 | inner, err := bpfMapCreate(&bpfMapCreateAttr{ |
196 | 189 | mapType: Array, |
197 | 190 | keySize: 4, |
199 | 192 | maxEntries: 1, |
200 | 193 | }) |
201 | 194 | if err != nil { |
202 | return false | |
195 | return false, err | |
203 | 196 | } |
204 | 197 | defer inner.Close() |
205 | 198 | |
212 | 205 | innerMapFd: innerFd, |
213 | 206 | }) |
214 | 207 | if err != nil { |
215 | return false | |
208 | return false, nil | |
216 | 209 | } |
217 | 210 | |
218 | 211 | _ = nested.Close() |
219 | return true | |
212 | return true, nil | |
220 | 213 | }) |
221 | 214 | |
222 | var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", func() bool { | |
215 | var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", func() (bool, error) { | |
223 | 216 | // This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since |
224 | 217 | // BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check. |
225 | 218 | m, err := bpfMapCreate(&bpfMapCreateAttr{ |
230 | 223 | flags: unix.BPF_F_RDONLY_PROG, |
231 | 224 | }) |
232 | 225 | if err != nil { |
233 | return false | |
226 | return false, nil | |
234 | 227 | } |
235 | 228 | _ = m.Close() |
236 | return true | |
229 | return true, nil | |
237 | 230 | }) |
238 | 231 | |
239 | 232 | func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error { |
247 | 240 | key: key, |
248 | 241 | value: valueOut, |
249 | 242 | } |
250 | _, err = internal.BPF(_MapLookupElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
243 | _, err = internal.BPF(internal.BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
251 | 244 | return wrapMapError(err) |
252 | 245 | } |
253 | 246 | |
262 | 255 | key: key, |
263 | 256 | value: valueOut, |
264 | 257 | } |
265 | _, err = internal.BPF(_MapLookupAndDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
258 | _, err = internal.BPF(internal.BPF_MAP_LOOKUP_AND_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
266 | 259 | return wrapMapError(err) |
267 | 260 | } |
268 | 261 | |
278 | 271 | value: valueOut, |
279 | 272 | flags: flags, |
280 | 273 | } |
281 | _, err = internal.BPF(_MapUpdateElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
274 | _, err = internal.BPF(internal.BPF_MAP_UPDATE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
282 | 275 | return wrapMapError(err) |
283 | 276 | } |
284 | 277 | |
292 | 285 | mapFd: fd, |
293 | 286 | key: key, |
294 | 287 | } |
295 | _, err = internal.BPF(_MapDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
288 | _, err = internal.BPF(internal.BPF_MAP_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
296 | 289 | return wrapMapError(err) |
297 | 290 | } |
298 | 291 | |
307 | 300 | key: key, |
308 | 301 | value: nextKeyOut, |
309 | 302 | } |
310 | _, err = internal.BPF(_MapGetNextKey, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
303 | _, err = internal.BPF(internal.BPF_MAP_GET_NEXT_KEY, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
311 | 304 | return wrapMapError(err) |
312 | 305 | } |
313 | 306 | |
314 | func objGetNextID(cmd int, start uint32) (uint32, error) { | |
307 | func objGetNextID(cmd internal.BPFCmd, start uint32) (uint32, error) { | |
315 | 308 | attr := bpfObjGetNextIDAttr{ |
316 | 309 | startID: start, |
317 | 310 | } |
323 | 316 | if err == nil { |
324 | 317 | return nil |
325 | 318 | } |
326 | if xerrors.Is(err, unix.ENOENT) { | |
327 | return xerrors.Errorf("%w", ErrNotExist) | |
328 | } | |
329 | ||
330 | return xerrors.New(err.Error()) | |
319 | if errors.Is(err, unix.ENOENT) { | |
320 | return fmt.Errorf("%w", ErrNotExist) | |
321 | } | |
322 | ||
323 | return errors.New(err.Error()) | |
331 | 324 | } |
332 | 325 | |
333 | 326 | func wrapMapError(err error) error { |
335 | 328 | return nil |
336 | 329 | } |
337 | 330 | |
338 | if xerrors.Is(err, unix.ENOENT) { | |
331 | if errors.Is(err, unix.ENOENT) { | |
339 | 332 | return ErrKeyNotExist |
340 | 333 | } |
341 | 334 | |
342 | return xerrors.New(err.Error()) | |
335 | if errors.Is(err, unix.EEXIST) { | |
336 | return ErrKeyExist | |
337 | } | |
338 | ||
339 | return errors.New(err.Error()) | |
343 | 340 | } |
344 | 341 | |
345 | 342 | func bpfMapFreeze(m *internal.FD) error { |
351 | 348 | attr := bpfMapFreezeAttr{ |
352 | 349 | mapFd: fd, |
353 | 350 | } |
354 | _, err = internal.BPF(_MapFreeze, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
351 | _, err = internal.BPF(internal.BPF_MAP_FREEZE, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
355 | 352 | return err |
356 | } | |
357 | ||
358 | const bpfFSType = 0xcafe4a11 | |
359 | ||
360 | func bpfPinObject(fileName string, fd *internal.FD) error { | |
361 | dirName := filepath.Dir(fileName) | |
362 | var statfs unix.Statfs_t | |
363 | if err := unix.Statfs(dirName, &statfs); err != nil { | |
364 | return err | |
365 | } | |
366 | if uint64(statfs.Type) != bpfFSType { | |
367 | return xerrors.Errorf("%s is not on a bpf filesystem", fileName) | |
368 | } | |
369 | ||
370 | value, err := fd.Value() | |
371 | if err != nil { | |
372 | return err | |
373 | } | |
374 | ||
375 | _, err = internal.BPF(_ObjPin, unsafe.Pointer(&bpfPinObjAttr{ | |
376 | fileName: internal.NewStringPointer(fileName), | |
377 | fd: value, | |
378 | }), 16) | |
379 | if err != nil { | |
380 | return xerrors.Errorf("pin object %s: %w", fileName, err) | |
381 | } | |
382 | return nil | |
383 | } | |
384 | ||
385 | func bpfGetObject(fileName string) (*internal.FD, error) { | |
386 | ptr, err := internal.BPF(_ObjGet, unsafe.Pointer(&bpfPinObjAttr{ | |
387 | fileName: internal.NewStringPointer(fileName), | |
388 | }), 16) | |
389 | if err != nil { | |
390 | return nil, xerrors.Errorf("get object %s: %w", fileName, err) | |
391 | } | |
392 | return internal.NewFD(uint32(ptr)), nil | |
393 | 353 | } |
394 | 354 | |
395 | 355 | func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error { |
404 | 364 | infoLen: uint32(size), |
405 | 365 | info: internal.NewPointer(info), |
406 | 366 | } |
407 | _, err = internal.BPF(_ObjGetInfoByFD, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
408 | if err != nil { | |
409 | return xerrors.Errorf("fd %d: %w", fd, err) | |
367 | _, err = internal.BPF(internal.BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) | |
368 | if err != nil { | |
369 | return fmt.Errorf("fd %d: %w", fd, err) | |
410 | 370 | } |
411 | 371 | return nil |
412 | 372 | } |
414 | 374 | func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) { |
415 | 375 | var info bpfProgInfo |
416 | 376 | if err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil { |
417 | return nil, xerrors.Errorf("can't get program info: %w", err) | |
377 | return nil, fmt.Errorf("can't get program info: %w", err) | |
418 | 378 | } |
419 | 379 | return &info, nil |
420 | 380 | } |
423 | 383 | var info bpfMapInfo |
424 | 384 | err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)) |
425 | 385 | if err != nil { |
426 | return nil, xerrors.Errorf("can't get map info: %w", err) | |
386 | return nil, fmt.Errorf("can't get map info: %w", err) | |
427 | 387 | } |
428 | 388 | return &info, nil |
429 | 389 | } |
430 | 390 | |
431 | var haveObjName = internal.FeatureTest("object names", "4.15", func() bool { | |
391 | var haveObjName = internal.FeatureTest("object names", "4.15", func() (bool, error) { | |
432 | 392 | attr := bpfMapCreateAttr{ |
433 | 393 | mapType: Array, |
434 | 394 | keySize: 4, |
439 | 399 | |
440 | 400 | fd, err := bpfMapCreate(&attr) |
441 | 401 | if err != nil { |
442 | return false | |
402 | return false, nil | |
443 | 403 | } |
444 | 404 | |
445 | 405 | _ = fd.Close() |
446 | return true | |
406 | return true, nil | |
447 | 407 | }) |
448 | 408 | |
449 | var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() bool { | |
409 | var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() (bool, error) { | |
450 | 410 | if err := haveObjName(); err != nil { |
451 | return false | |
411 | return false, err | |
452 | 412 | } |
453 | 413 | |
454 | 414 | attr := bpfMapCreateAttr{ |
461 | 421 | |
462 | 422 | fd, err := bpfMapCreate(&attr) |
463 | 423 | if err != nil { |
464 | return false | |
424 | return false, nil | |
465 | 425 | } |
466 | 426 | |
467 | 427 | _ = fd.Close() |
468 | return true | |
428 | return true, nil | |
469 | 429 | }) |
470 | 430 | |
471 | func bpfObjGetFDByID(cmd int, id uint32) (*internal.FD, error) { | |
431 | func bpfObjGetFDByID(cmd internal.BPFCmd, id uint32) (*internal.FD, error) { | |
472 | 432 | attr := bpfGetFDByIDAttr{ |
473 | 433 | id: id, |
474 | 434 | } |
0 | 0 | LLVM_PREFIX ?= /usr/bin |
1 | 1 | CLANG ?= $(LLVM_PREFIX)/clang |
2 | CFLAGS := -target bpf -O2 -g -Wall -Werror $(CFLAGS) | |
2 | 3 | |
3 | 4 | .PHONY: all clean |
4 | all: loader-clang-6.0.elf loader-clang-7.elf loader-clang-8.elf loader-clang-9.elf rewrite.elf invalid_map.elf | |
5 | all: loader-clang-6.0-el.elf loader-clang-7-el.elf loader-clang-8-el.elf loader-clang-9-el.elf rewrite-el.elf invalid_map-el.elf raw_tracepoint-el.elf \ | |
6 | loader-clang-6.0-eb.elf loader-clang-7-eb.elf loader-clang-8-eb.elf loader-clang-9-eb.elf rewrite-eb.elf invalid_map-eb.elf raw_tracepoint-eb.elf | |
5 | 7 | |
6 | 8 | clean: |
7 | 9 | -$(RM) *.elf |
8 | 10 | |
9 | loader-%.elf: loader.c | |
10 | $* -target bpf -O2 -g \ | |
11 | -Wall -Werror \ | |
12 | -c $< -o $@ | |
11 | loader-%-el.elf: loader.c | |
12 | $* $(CFLAGS) -mlittle-endian -c $< -o $@ | |
13 | 13 | |
14 | %.elf : %.c | |
15 | $(CLANG) -target bpf -O2 -g \ | |
16 | -Wall -Werror \ | |
17 | -c $< -o $@ | |
14 | loader-%-eb.elf: loader.c | |
15 | $* $(CFLAGS) -mbig-endian -c $< -o $@ | |
18 | 16 | |
17 | %-el.elf: %.c | |
18 | $(CLANG) $(CFLAGS) -mlittle-endian -c $< -o $@ | |
19 | ||
20 | %-eb.elf : %.c | |
21 | $(CLANG) $(CFLAGS) -mbig-endian -c $< -o $@ |
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
41 | 41 | __uint(max_entries, 1); |
42 | 42 | __uint(map_flags, BPF_F_NO_PREALLOC); |
43 | 43 | } btf_map __section(".maps"); |
44 | ||
45 | struct { | |
46 | __uint(type, BPF_MAP_TYPE_ARRAY); | |
47 | __uint(key_size, 4); | |
48 | __uint(value_size, 4); | |
49 | __uint(max_entries, 1); | |
50 | } btf_map2 __section(".maps"); | |
44 | 51 | #endif |
45 | 52 | |
46 | 53 | static int __attribute__((noinline)) static_fn(uint32_t arg) { |
Binary diff not shown
Binary diff not shown
0 | /* This file excercises the ELF loader. */ | |
1 | ||
2 | #include "common.h" | |
3 | ||
4 | char __license[] __section("license") = "MIT"; | |
5 | ||
6 | struct bpf_args { | |
7 | uint64_t args[0]; | |
8 | }; | |
9 | ||
10 | __section("raw_tracepoint/sched_process_exec") int sched_process_exec(struct bpf_args *ctx) { | |
11 | return 0; | |
12 | } |
Binary diff not shown
Binary diff not shown
0 | 0 | package ebpf |
1 | 1 | |
2 | //go:generate stringer -output types_string.go -type=MapType,ProgramType | |
2 | //go:generate stringer -output types_string.go -type=MapType,ProgramType,AttachType | |
3 | 3 | |
4 | 4 | // MapType indicates the type map structure |
5 | 5 | // that will be initialized in the kernel. |
84 | 84 | |
85 | 85 | // hasPerCPUValue returns true if the Map stores a value per CPU. |
86 | 86 | func (mt MapType) hasPerCPUValue() bool { |
87 | if mt == PerCPUHash || mt == PerCPUArray { | |
87 | if mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash { | |
88 | 88 | return true |
89 | 89 | } |
90 | 90 | return false |
91 | 91 | } |
92 | ||
93 | const ( | |
94 | _MapCreate = iota | |
95 | _MapLookupElem | |
96 | _MapUpdateElem | |
97 | _MapDeleteElem | |
98 | _MapGetNextKey | |
99 | _ProgLoad | |
100 | _ObjPin | |
101 | _ObjGet | |
102 | _ProgAttach | |
103 | _ProgDetach | |
104 | _ProgTestRun | |
105 | _ProgGetNextID | |
106 | _MapGetNextID | |
107 | _ProgGetFDByID | |
108 | _MapGetFDByID | |
109 | _ObjGetInfoByFD | |
110 | _ProgQuery | |
111 | _RawTracepointOpen | |
112 | _BTFLoad | |
113 | _BTFGetFDByID | |
114 | _TaskFDQuery | |
115 | _MapLookupAndDeleteElem | |
116 | _MapFreeze | |
117 | _BTFGetNextID | |
118 | ) | |
119 | 92 | |
120 | 93 | // ProgramType of the eBPF program |
121 | 94 | type ProgramType uint32 |
122 | 95 | |
123 | 96 | // eBPF program types |
124 | 97 | const ( |
125 | // Unrecognized program type | |
126 | 98 | UnspecifiedProgram ProgramType = iota |
127 | // SocketFilter socket or seccomp filter | |
128 | 99 | SocketFilter |
129 | // Kprobe program | |
130 | 100 | Kprobe |
131 | // SchedCLS traffic control shaper | |
132 | 101 | SchedCLS |
133 | // SchedACT routing control shaper | |
134 | 102 | SchedACT |
135 | // TracePoint program | |
136 | 103 | TracePoint |
137 | // XDP program | |
138 | 104 | XDP |
139 | // PerfEvent program | |
140 | 105 | PerfEvent |
141 | // CGroupSKB program | |
142 | 106 | CGroupSKB |
143 | // CGroupSock program | |
144 | 107 | CGroupSock |
145 | // LWTIn program | |
146 | 108 | LWTIn |
147 | // LWTOut program | |
148 | 109 | LWTOut |
149 | // LWTXmit program | |
150 | 110 | LWTXmit |
151 | // SockOps program | |
152 | 111 | SockOps |
153 | // SkSKB program | |
154 | 112 | SkSKB |
155 | // CGroupDevice program | |
156 | 113 | CGroupDevice |
157 | // SkMsg program | |
158 | 114 | SkMsg |
159 | // RawTracepoint program | |
160 | 115 | RawTracepoint |
161 | // CGroupSockAddr program | |
162 | 116 | CGroupSockAddr |
163 | // LWTSeg6Local program | |
164 | 117 | LWTSeg6Local |
165 | // LircMode2 program | |
166 | 118 | LircMode2 |
167 | // SkReuseport program | |
168 | 119 | SkReuseport |
169 | // FlowDissector program | |
170 | 120 | FlowDissector |
171 | // CGroupSysctl program | |
172 | 121 | CGroupSysctl |
173 | // RawTracepointWritable program | |
174 | 122 | RawTracepointWritable |
175 | // CGroupSockopt program | |
176 | 123 | CGroupSockopt |
177 | // Tracing program | |
178 | 124 | Tracing |
125 | StructOps | |
126 | Extension | |
127 | LSM | |
128 | SkLookup | |
179 | 129 | ) |
180 | 130 | |
181 | 131 | // AttachType of the eBPF program, needed to differentiate allowed context accesses in |
213 | 163 | AttachTraceRawTp |
214 | 164 | AttachTraceFEntry |
215 | 165 | AttachTraceFExit |
166 | AttachModifyReturn | |
167 | AttachLSMMac | |
168 | AttachTraceIter | |
169 | AttachCgroupInet4GetPeername | |
170 | AttachCgroupInet6GetPeername | |
171 | AttachCgroupInet4GetSockname | |
172 | AttachCgroupInet6GetSockname | |
173 | AttachXDPDevMap | |
174 | AttachCgroupInetSockRelease | |
175 | AttachXDPCPUMap | |
176 | AttachSkLookup | |
177 | AttachXDP | |
216 | 178 | ) |
217 | 179 | |
218 | 180 | // AttachFlags of the eBPF program used in BPF_PROG_ATTACH command |
0 | // Code generated by "stringer -output types_string.go -type=MapType,ProgramType"; DO NOT EDIT. | |
0 | // Code generated by "stringer -output types_string.go -type=MapType,ProgramType,AttachType"; DO NOT EDIT. | |
1 | 1 | |
2 | 2 | package ebpf |
3 | 3 | |
76 | 76 | _ = x[RawTracepointWritable-24] |
77 | 77 | _ = x[CGroupSockopt-25] |
78 | 78 | _ = x[Tracing-26] |
79 | _ = x[StructOps-27] | |
80 | _ = x[Extension-28] | |
81 | _ = x[LSM-29] | |
82 | _ = x[SkLookup-30] | |
79 | 83 | } |
80 | 84 | |
81 | const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracing" | |
85 | const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracingStructOpsExtensionLSMSkLookup" | |
82 | 86 | |
83 | var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258, 265} | |
87 | var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258, 265, 274, 283, 286, 294} | |
84 | 88 | |
85 | 89 | func (i ProgramType) String() string { |
86 | 90 | if i >= ProgramType(len(_ProgramType_index)-1) { |
88 | 92 | } |
89 | 93 | return _ProgramType_name[_ProgramType_index[i]:_ProgramType_index[i+1]] |
90 | 94 | } |
95 | func _() { | |
96 | // An "invalid array index" compiler error signifies that the constant values have changed. | |
97 | // Re-run the stringer command to generate them again. | |
98 | var x [1]struct{} | |
99 | _ = x[AttachNone-0] | |
100 | _ = x[AttachCGroupInetIngress-0] | |
101 | _ = x[AttachCGroupInetEgress-1] | |
102 | _ = x[AttachCGroupInetSockCreate-2] | |
103 | _ = x[AttachCGroupSockOps-3] | |
104 | _ = x[AttachSkSKBStreamParser-4] | |
105 | _ = x[AttachSkSKBStreamVerdict-5] | |
106 | _ = x[AttachCGroupDevice-6] | |
107 | _ = x[AttachSkMsgVerdict-7] | |
108 | _ = x[AttachCGroupInet4Bind-8] | |
109 | _ = x[AttachCGroupInet6Bind-9] | |
110 | _ = x[AttachCGroupInet4Connect-10] | |
111 | _ = x[AttachCGroupInet6Connect-11] | |
112 | _ = x[AttachCGroupInet4PostBind-12] | |
113 | _ = x[AttachCGroupInet6PostBind-13] | |
114 | _ = x[AttachCGroupUDP4Sendmsg-14] | |
115 | _ = x[AttachCGroupUDP6Sendmsg-15] | |
116 | _ = x[AttachLircMode2-16] | |
117 | _ = x[AttachFlowDissector-17] | |
118 | _ = x[AttachCGroupSysctl-18] | |
119 | _ = x[AttachCGroupUDP4Recvmsg-19] | |
120 | _ = x[AttachCGroupUDP6Recvmsg-20] | |
121 | _ = x[AttachCGroupGetsockopt-21] | |
122 | _ = x[AttachCGroupSetsockopt-22] | |
123 | _ = x[AttachTraceRawTp-23] | |
124 | _ = x[AttachTraceFEntry-24] | |
125 | _ = x[AttachTraceFExit-25] | |
126 | _ = x[AttachModifyReturn-26] | |
127 | _ = x[AttachLSMMac-27] | |
128 | _ = x[AttachTraceIter-28] | |
129 | _ = x[AttachCgroupInet4GetPeername-29] | |
130 | _ = x[AttachCgroupInet6GetPeername-30] | |
131 | _ = x[AttachCgroupInet4GetSockname-31] | |
132 | _ = x[AttachCgroupInet6GetSockname-32] | |
133 | _ = x[AttachXDPDevMap-33] | |
134 | _ = x[AttachCgroupInetSockRelease-34] | |
135 | _ = x[AttachXDPCPUMap-35] | |
136 | _ = x[AttachSkLookup-36] | |
137 | _ = x[AttachXDP-37] | |
138 | } | |
139 | ||
140 | const _AttachType_name = "AttachNoneAttachCGroupInetEgressAttachCGroupInetSockCreateAttachCGroupSockOpsAttachSkSKBStreamParserAttachSkSKBStreamVerdictAttachCGroupDeviceAttachSkMsgVerdictAttachCGroupInet4BindAttachCGroupInet6BindAttachCGroupInet4ConnectAttachCGroupInet6ConnectAttachCGroupInet4PostBindAttachCGroupInet6PostBindAttachCGroupUDP4SendmsgAttachCGroupUDP6SendmsgAttachLircMode2AttachFlowDissectorAttachCGroupSysctlAttachCGroupUDP4RecvmsgAttachCGroupUDP6RecvmsgAttachCGroupGetsockoptAttachCGroupSetsockoptAttachTraceRawTpAttachTraceFEntryAttachTraceFExitAttachModifyReturnAttachLSMMacAttachTraceIterAttachCgroupInet4GetPeernameAttachCgroupInet6GetPeernameAttachCgroupInet4GetSocknameAttachCgroupInet6GetSocknameAttachXDPDevMapAttachCgroupInetSockReleaseAttachXDPCPUMapAttachSkLookupAttachXDP" | |
141 | ||
142 | var _AttachType_index = [...]uint16{0, 10, 32, 58, 77, 100, 124, 142, 160, 181, 202, 226, 250, 275, 300, 323, 346, 361, 380, 398, 421, 444, 466, 488, 504, 521, 537, 555, 567, 582, 610, 638, 666, 694, 709, 736, 751, 765, 774} | |
143 | ||
144 | func (i AttachType) String() string { | |
145 | if i >= AttachType(len(_AttachType_index)-1) { | |
146 | return "AttachType(" + strconv.FormatInt(int64(i), 10) + ")" | |
147 | } | |
148 | return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]] | |
149 | } |