1 package ebpf
2
3 import (
4 "errors"
5 "fmt"
6 "reflect"
7 "testing"
8
9 "github.com/cilium/ebpf/asm"
10 "github.com/cilium/ebpf/btf"
11 "github.com/cilium/ebpf/internal"
12 "github.com/cilium/ebpf/internal/testutils"
13 )
14
15 func TestCollectionSpecNotModified(t *testing.T) {
16 cs := CollectionSpec{
17 Maps: map[string]*MapSpec{
18 "my-map": {
19 Type: Array,
20 KeySize: 4,
21 ValueSize: 4,
22 MaxEntries: 1,
23 },
24 },
25 Programs: map[string]*ProgramSpec{
26 "test": {
27 Type: SocketFilter,
28 Instructions: asm.Instructions{
29 asm.LoadImm(asm.R1, 0, asm.DWord).WithReference("my-map"),
30 asm.LoadImm(asm.R0, 0, asm.DWord),
31 asm.Return(),
32 },
33 License: "MIT",
34 },
35 },
36 }
37
38 coll, err := NewCollection(&cs)
39 if err != nil {
40 t.Fatal(err)
41 }
42 coll.Close()
43
44 if cs.Programs["test"].Instructions[0].Constant != 0 {
45 t.Error("Creating a collection modifies input spec")
46 }
47 }
48
49 func TestCollectionSpecCopy(t *testing.T) {
50 cs := &CollectionSpec{
51 Maps: map[string]*MapSpec{
52 "my-map": {
53 Type: Array,
54 KeySize: 4,
55 ValueSize: 4,
56 MaxEntries: 1,
57 },
58 },
59 Programs: map[string]*ProgramSpec{
60 "test": {
61 Type: SocketFilter,
62 Instructions: asm.Instructions{
63 asm.LoadMapPtr(asm.R1, 0),
64 asm.LoadImm(asm.R0, 0, asm.DWord),
65 asm.Return(),
66 },
67 License: "MIT",
68 },
69 },
70 Types: &btf.Spec{},
71 }
72 cpy := cs.Copy()
73
74 if cpy == cs {
75 t.Error("Copy returned the same pointner")
76 }
77
78 if cpy.Maps["my-map"] == cs.Maps["my-map"] {
79 t.Error("Copy returned same Maps")
80 }
81
82 if cpy.Programs["test"] == cs.Programs["test"] {
83 t.Error("Copy returned same Programs")
84 }
85
86 if cpy.Types != cs.Types {
87 t.Error("Copy returned different Types")
88 }
89 }
90
91 func TestCollectionSpecLoadCopy(t *testing.T) {
92 file := fmt.Sprintf("testdata/loader-%s.elf", internal.ClangEndian)
93 spec, err := LoadCollectionSpec(file)
94 if err != nil {
95 t.Fatal(err)
96 }
97
98 spec2 := spec.Copy()
99
100 var objs struct {
101 Prog *Program `ebpf:"xdp_prog"`
102 }
103
104 err = spec.LoadAndAssign(&objs, nil)
105 testutils.SkipIfNotSupported(t, err)
106 if err != nil {
107 t.Fatal("Loading original spec:", err)
108 }
109 defer objs.Prog.Close()
110
111 if err := spec2.LoadAndAssign(&objs, nil); err != nil {
112 t.Fatal("Loading copied spec:", err)
113 }
114 defer objs.Prog.Close()
115 }
116
117 func TestCollectionSpecRewriteMaps(t *testing.T) {
118 insns := asm.Instructions{
119
120 asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"),
121
122 asm.Mov.Reg(asm.R2, asm.R10),
123 asm.Add.Imm(asm.R2, -4),
124 asm.StoreImm(asm.R2, 0, 0, asm.Word),
125
126 asm.FnMapLookupElem.Call(),
127 asm.JEq.Imm(asm.R0, 0, "ret"),
128 asm.LoadMem(asm.R0, asm.R0, 0, asm.Word),
129 asm.Return().WithSymbol("ret"),
130 }
131
132 cs := &CollectionSpec{
133 Maps: map[string]*MapSpec{
134 "test-map": {
135 Type: Array,
136 KeySize: 4,
137 ValueSize: 4,
138 MaxEntries: 1,
139 },
140 },
141 Programs: map[string]*ProgramSpec{
142 "test-prog": {
143 Type: SocketFilter,
144 Instructions: insns,
145 License: "MIT",
146 },
147 },
148 }
149
150
151 newMap, err := NewMap(cs.Maps["test-map"])
152 if err != nil {
153 t.Fatal(err)
154 }
155 defer newMap.Close()
156
157 err = newMap.Put(uint32(0), uint32(2))
158 if err != nil {
159 t.Fatal(err)
160 }
161
162 err = cs.RewriteMaps(map[string]*Map{
163 "test-map": newMap,
164 })
165 if err != nil {
166 t.Fatal(err)
167 }
168
169 if cs.Maps["test-map"] != nil {
170 t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps")
171 }
172
173 coll, err := NewCollection(cs)
174 if err != nil {
175 t.Fatal(err)
176 }
177 defer coll.Close()
178
179 ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14))
180 testutils.SkipIfNotSupported(t, err)
181 if err != nil {
182 t.Fatal(err)
183 }
184
185 if ret != 2 {
186 t.Fatal("new / override map not used")
187 }
188 }
189
190 func TestCollectionSpecMapReplacements(t *testing.T) {
191 insns := asm.Instructions{
192
193 asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"),
194
195 asm.Mov.Reg(asm.R2, asm.R10),
196 asm.Add.Imm(asm.R2, -4),
197 asm.StoreImm(asm.R2, 0, 0, asm.Word),
198
199 asm.FnMapLookupElem.Call(),
200 asm.JEq.Imm(asm.R0, 0, "ret"),
201 asm.LoadMem(asm.R0, asm.R0, 0, asm.Word),
202 asm.Return().WithSymbol("ret"),
203 }
204
205 cs := &CollectionSpec{
206 Maps: map[string]*MapSpec{
207 "test-map": {
208 Type: Array,
209 KeySize: 4,
210 ValueSize: 4,
211 MaxEntries: 1,
212 },
213 },
214 Programs: map[string]*ProgramSpec{
215 "test-prog": {
216 Type: SocketFilter,
217 Instructions: insns,
218 License: "MIT",
219 },
220 },
221 }
222
223
224 newMap, err := NewMap(cs.Maps["test-map"])
225 if err != nil {
226 t.Fatal(err)
227 }
228 defer newMap.Close()
229
230 err = newMap.Put(uint32(0), uint32(2))
231 if err != nil {
232 t.Fatal(err)
233 }
234
235 coll, err := NewCollectionWithOptions(cs, CollectionOptions{
236 MapReplacements: map[string]*Map{
237 "test-map": newMap,
238 },
239 })
240 if err != nil {
241 t.Fatal(err)
242 }
243 defer coll.Close()
244
245 ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14))
246 testutils.SkipIfNotSupported(t, err)
247 if err != nil {
248 t.Fatal(err)
249 }
250
251 if ret != 2 {
252 t.Fatal("new / override map not used")
253 }
254
255
256 coll.Close()
257 err = newMap.Put(uint32(0), uint32(3))
258 if err != nil {
259 t.Fatalf("failed to update replaced map: %s", err)
260 }
261 }
262 func TestCollectionSpecMapReplacements_NonExistingMap(t *testing.T) {
263 cs := &CollectionSpec{
264 Maps: map[string]*MapSpec{
265 "test-map": {
266 Type: Array,
267 KeySize: 4,
268 ValueSize: 4,
269 MaxEntries: 1,
270 },
271 },
272 }
273
274
275 newMap, err := NewMap(cs.Maps["test-map"])
276 if err != nil {
277 t.Fatal(err)
278 }
279 defer newMap.Close()
280
281 coll, err := NewCollectionWithOptions(cs, CollectionOptions{
282 MapReplacements: map[string]*Map{
283 "non-existing-map": newMap,
284 },
285 })
286 if err == nil {
287 coll.Close()
288 t.Fatal("Overriding a non existing map did not fail")
289 }
290 }
291
292 func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) {
293 cs := &CollectionSpec{
294 Maps: map[string]*MapSpec{
295 "test-map": {
296 Type: Array,
297 KeySize: 4,
298 ValueSize: 4,
299 MaxEntries: 1,
300 },
301 },
302 }
303
304
305 newMap, err := NewMap(&MapSpec{
306 Type: Array,
307 KeySize: 4,
308 ValueSize: 8,
309 MaxEntries: 1,
310 })
311 if err != nil {
312 t.Fatal(err)
313 }
314
315 defer newMap.Close()
316
317 coll, err := NewCollectionWithOptions(cs, CollectionOptions{
318 MapReplacements: map[string]*Map{
319 "test-map": newMap,
320 },
321 })
322 if err == nil {
323 coll.Close()
324 t.Fatal("Overriding a map with a mismatching spec did not fail")
325 }
326 if !errors.Is(err, ErrMapIncompatible) {
327 t.Fatalf("Overriding a map with a mismatching spec failed with the wrong error")
328 }
329 }
330
331 func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {
332 spec := &CollectionSpec{
333 Maps: map[string]*MapSpec{
334 "valid": {
335 Type: Array,
336 KeySize: 4,
337 ValueSize: 4,
338 MaxEntries: 1,
339 },
340 "bogus": {
341 Type: Array,
342 MaxEntries: 0,
343 },
344 },
345 Programs: map[string]*ProgramSpec{
346 "valid": {
347 Type: SocketFilter,
348 Instructions: asm.Instructions{
349 asm.LoadImm(asm.R0, 0, asm.DWord),
350 asm.Return(),
351 },
352 License: "MIT",
353 },
354 "bogus": {
355 Type: SocketFilter,
356 Instructions: asm.Instructions{
357
358 asm.Return(),
359 },
360 License: "MIT",
361 },
362 },
363 }
364
365 var objs struct {
366 Prog *Program `ebpf:"valid"`
367 Map *Map `ebpf:"valid"`
368 }
369
370 if err := spec.LoadAndAssign(&objs, nil); err != nil {
371 t.Fatal("Assign loads a map or program that isn't requested in the struct:", err)
372 }
373 defer objs.Prog.Close()
374 defer objs.Map.Close()
375
376 if objs.Prog == nil {
377 t.Error("Program is nil")
378 }
379
380 if objs.Map == nil {
381 t.Error("Map is nil")
382 }
383 }
384
385 func TestCollectionAssign(t *testing.T) {
386 var specs struct {
387 Program *ProgramSpec `ebpf:"prog1"`
388 Map *MapSpec `ebpf:"map1"`
389 }
390
391 mapSpec := &MapSpec{
392 Type: Array,
393 KeySize: 4,
394 ValueSize: 4,
395 MaxEntries: 1,
396 }
397 progSpec := &ProgramSpec{
398 Type: SocketFilter,
399 Instructions: asm.Instructions{
400 asm.LoadImm(asm.R0, 0, asm.DWord),
401 asm.Return(),
402 },
403 License: "MIT",
404 }
405
406 cs := &CollectionSpec{
407 Maps: map[string]*MapSpec{
408 "map1": mapSpec,
409 },
410 Programs: map[string]*ProgramSpec{
411 "prog1": progSpec,
412 },
413 }
414
415 if err := cs.Assign(&specs); err != nil {
416 t.Fatal("Can't assign spec:", err)
417 }
418
419 if specs.Program != progSpec {
420 t.Fatalf("Expected Program to be %p, got %p", progSpec, specs.Program)
421 }
422
423 if specs.Map != mapSpec {
424 t.Fatalf("Expected Map to be %p, got %p", mapSpec, specs.Map)
425 }
426
427 if err := cs.Assign(new(int)); err == nil {
428 t.Fatal("Assign allows to besides *struct")
429 }
430
431 if err := cs.Assign(new(struct{ Foo int })); err != nil {
432 t.Fatal("Assign doesn't ignore untagged fields")
433 }
434
435 unexported := new(struct {
436 foo *MapSpec `ebpf:"map1"`
437 })
438
439 if err := cs.Assign(unexported); err == nil {
440 t.Error("Assign should return an error on unexported fields")
441 }
442 }
443
444 func TestAssignValues(t *testing.T) {
445 zero := func(t reflect.Type, name string) (interface{}, error) {
446 return reflect.Zero(t).Interface(), nil
447 }
448
449 type t1 struct {
450 Bar int `ebpf:"bar"`
451 }
452
453 type t2 struct {
454 t1
455 Foo int `ebpf:"foo"`
456 }
457
458 type t2ptr struct {
459 *t1
460 Foo int `ebpf:"foo"`
461 }
462
463 invalid := []struct {
464 name string
465 to interface{}
466 }{
467 {"non-struct", 1},
468 {"non-pointer struct", t1{}},
469 {"pointer to non-struct", new(int)},
470 {"embedded nil pointer", &t2ptr{}},
471 {"unexported field", new(struct {
472 foo int `ebpf:"foo"`
473 })},
474 {"identical tag", new(struct {
475 Foo1 int `ebpf:"foo"`
476 Foo2 int `ebpf:"foo"`
477 })},
478 }
479
480 for _, testcase := range invalid {
481 t.Run(testcase.name, func(t *testing.T) {
482 if err := assignValues(testcase.to, zero); err == nil {
483 t.Fatal("assignValues didn't return an error")
484 } else {
485 t.Log(err)
486 }
487 })
488 }
489
490 valid := []struct {
491 name string
492 to interface{}
493 }{
494 {"pointer to struct", new(t1)},
495 {"embedded struct", new(t2)},
496 {"embedded struct pointer", &t2ptr{t1: new(t1)}},
497 {"untagged field", new(struct{ Foo int })},
498 }
499
500 for _, testcase := range valid {
501 t.Run(testcase.name, func(t *testing.T) {
502 if err := assignValues(testcase.to, zero); err != nil {
503 t.Fatal("assignValues returned", err)
504 }
505 })
506 }
507
508 }
509
510 func TestIncompleteLoadAndAssign(t *testing.T) {
511 spec := &CollectionSpec{
512 Programs: map[string]*ProgramSpec{
513 "valid": {
514 Type: SocketFilter,
515 Instructions: asm.Instructions{
516 asm.LoadImm(asm.R0, 0, asm.DWord),
517 asm.Return(),
518 },
519 License: "MIT",
520 },
521 "invalid": {
522 Type: SocketFilter,
523 Instructions: asm.Instructions{
524 asm.Return(),
525 },
526 License: "MIT",
527 },
528 },
529 }
530
531 s := struct {
532
533 Valid *Program `ebpf:"valid"`
534
535 Invalid *Program `ebpf:"invalid"`
536 }{}
537
538 if err := spec.LoadAndAssign(&s, nil); err == nil {
539 t.Fatal("expected error loading invalid ProgramSpec")
540 }
541
542 if fd := s.Valid.FD(); fd != -1 {
543 t.Fatal("expected valid prog to have closed fd -1, got:", fd)
544 }
545
546 if s.Invalid != nil {
547 t.Fatal("expected invalid prog to be nil due to never being assigned")
548 }
549 }
550
551 func ExampleCollectionSpec_Assign() {
552 spec := &CollectionSpec{
553 Maps: map[string]*MapSpec{
554 "map1": {
555 Type: Array,
556 KeySize: 4,
557 ValueSize: 4,
558 MaxEntries: 1,
559 },
560 },
561 Programs: map[string]*ProgramSpec{
562 "prog1": {
563 Type: SocketFilter,
564 Instructions: asm.Instructions{
565 asm.LoadImm(asm.R0, 0, asm.DWord),
566 asm.Return(),
567 },
568 License: "MIT",
569 },
570 },
571 }
572
573 type maps struct {
574 Map *MapSpec `ebpf:"map1"`
575 }
576
577 var specs struct {
578 maps
579 Program *ProgramSpec `ebpf:"prog1"`
580 }
581
582 if err := spec.Assign(&specs); err != nil {
583 panic(err)
584 }
585
586 fmt.Println(specs.Program.Type)
587 fmt.Println(specs.Map.Type)
588
589
590
591 }
592
593 func ExampleCollectionSpec_LoadAndAssign() {
594 spec := &CollectionSpec{
595 Maps: map[string]*MapSpec{
596 "map1": {
597 Type: Array,
598 KeySize: 4,
599 ValueSize: 4,
600 MaxEntries: 1,
601 },
602 },
603 Programs: map[string]*ProgramSpec{
604 "prog1": {
605 Type: SocketFilter,
606 Instructions: asm.Instructions{
607 asm.LoadImm(asm.R0, 0, asm.DWord),
608 asm.Return(),
609 },
610 License: "MIT",
611 },
612 },
613 }
614
615 var objs struct {
616 Program *Program `ebpf:"prog1"`
617 Map *Map `ebpf:"map1"`
618 }
619
620 if err := spec.LoadAndAssign(&objs, nil); err != nil {
621 panic(err)
622 }
623 defer objs.Program.Close()
624 defer objs.Map.Close()
625
626 fmt.Println(objs.Program.Type())
627 fmt.Println(objs.Map.Type())
628
629
630
631 }
632
View as plain text