github.com/cilium/ebpf@v0.16.0/collection_test.go (about) 1 package ebpf 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "reflect" 10 "testing" 11 12 "github.com/go-quicktest/qt" 13 14 "github.com/cilium/ebpf/asm" 15 "github.com/cilium/ebpf/btf" 16 "github.com/cilium/ebpf/internal" 17 "github.com/cilium/ebpf/internal/testutils" 18 "github.com/cilium/ebpf/internal/testutils/fdtrace" 19 ) 20 21 func TestMain(m *testing.M) { 22 fdtrace.TestMain(m) 23 } 24 25 func TestCollectionSpecNotModified(t *testing.T) { 26 cs := CollectionSpec{ 27 Maps: map[string]*MapSpec{ 28 "my-map": { 29 Type: Array, 30 KeySize: 4, 31 ValueSize: 4, 32 MaxEntries: 1, 33 }, 34 }, 35 Programs: map[string]*ProgramSpec{ 36 "test": { 37 Type: SocketFilter, 38 Instructions: asm.Instructions{ 39 asm.LoadImm(asm.R1, 0, asm.DWord).WithReference("my-map"), 40 asm.LoadImm(asm.R0, 0, asm.DWord), 41 asm.Return(), 42 }, 43 License: "MIT", 44 }, 45 }, 46 } 47 48 coll, err := NewCollection(&cs) 49 if err != nil { 50 t.Fatal(err) 51 } 52 coll.Close() 53 54 if cs.Programs["test"].Instructions[0].Constant != 0 { 55 t.Error("Creating a collection modifies input spec") 56 } 57 } 58 59 func TestCollectionSpecCopy(t *testing.T) { 60 cs := &CollectionSpec{ 61 map[string]*MapSpec{ 62 "my-map": { 63 Type: Array, 64 KeySize: 4, 65 ValueSize: 4, 66 MaxEntries: 1, 67 }, 68 }, 69 map[string]*ProgramSpec{ 70 "test": { 71 Type: SocketFilter, 72 Instructions: asm.Instructions{ 73 asm.LoadMapPtr(asm.R1, 0), 74 asm.LoadImm(asm.R0, 0, asm.DWord), 75 asm.Return(), 76 }, 77 License: "MIT", 78 }, 79 }, 80 &btf.Spec{}, 81 binary.LittleEndian, 82 } 83 84 qt.Check(t, qt.IsNil((*CollectionSpec)(nil).Copy())) 85 qt.Assert(t, testutils.IsDeepCopy(cs.Copy(), cs)) 86 } 87 88 func TestCollectionSpecLoadCopy(t *testing.T) { 89 file := testutils.NativeFile(t, "testdata/loader-%s.elf") 90 spec, err := LoadCollectionSpec(file) 91 if err != nil { 92 t.Fatal(err) 93 } 94 95 spec2 := spec.Copy() 96 97 var objs struct { 98 Prog *Program `ebpf:"xdp_prog"` 99 } 100 101 err = spec.LoadAndAssign(&objs, nil) 102 testutils.SkipIfNotSupported(t, err) 103 if err != nil { 104 t.Fatal("Loading original spec:", err) 105 } 106 defer objs.Prog.Close() 107 108 if err := spec2.LoadAndAssign(&objs, nil); err != nil { 109 t.Fatal("Loading copied spec:", err) 110 } 111 defer objs.Prog.Close() 112 } 113 114 func TestCollectionSpecRewriteMaps(t *testing.T) { 115 insns := asm.Instructions{ 116 // R1 map 117 asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"), 118 // R2 key 119 asm.Mov.Reg(asm.R2, asm.R10), 120 asm.Add.Imm(asm.R2, -4), 121 asm.StoreImm(asm.R2, 0, 0, asm.Word), 122 // Lookup map[0] 123 asm.FnMapLookupElem.Call(), 124 asm.JEq.Imm(asm.R0, 0, "ret"), 125 asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), 126 asm.Return().WithSymbol("ret"), 127 } 128 129 cs := &CollectionSpec{ 130 Maps: map[string]*MapSpec{ 131 "test-map": { 132 Type: Array, 133 KeySize: 4, 134 ValueSize: 4, 135 MaxEntries: 1, 136 }, 137 }, 138 Programs: map[string]*ProgramSpec{ 139 "test-prog": { 140 Type: SocketFilter, 141 Instructions: insns, 142 License: "MIT", 143 }, 144 }, 145 } 146 147 // Override the map with another one 148 newMap, err := NewMap(cs.Maps["test-map"]) 149 if err != nil { 150 t.Fatal(err) 151 } 152 defer newMap.Close() 153 154 err = newMap.Put(uint32(0), uint32(2)) 155 if err != nil { 156 t.Fatal(err) 157 } 158 159 err = cs.RewriteMaps(map[string]*Map{ 160 "test-map": newMap, 161 }) 162 if err != nil { 163 t.Fatal(err) 164 } 165 166 if cs.Maps["test-map"] != nil { 167 t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps") 168 } 169 170 coll, err := NewCollection(cs) 171 if err != nil { 172 t.Fatal(err) 173 } 174 defer coll.Close() 175 176 ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext) 177 testutils.SkipIfNotSupported(t, err) 178 if err != nil { 179 t.Fatal(err) 180 } 181 182 if ret != 2 { 183 t.Fatal("new / override map not used") 184 } 185 } 186 187 func TestCollectionSpecMapReplacements(t *testing.T) { 188 insns := asm.Instructions{ 189 // R1 map 190 asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"), 191 // R2 key 192 asm.Mov.Reg(asm.R2, asm.R10), 193 asm.Add.Imm(asm.R2, -4), 194 asm.StoreImm(asm.R2, 0, 0, asm.Word), 195 // Lookup map[0] 196 asm.FnMapLookupElem.Call(), 197 asm.JEq.Imm(asm.R0, 0, "ret"), 198 asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), 199 asm.Return().WithSymbol("ret"), 200 } 201 202 cs := &CollectionSpec{ 203 Maps: map[string]*MapSpec{ 204 "test-map": { 205 Type: Array, 206 KeySize: 4, 207 ValueSize: 4, 208 MaxEntries: 1, 209 }, 210 }, 211 Programs: map[string]*ProgramSpec{ 212 "test-prog": { 213 Type: SocketFilter, 214 Instructions: insns, 215 License: "MIT", 216 }, 217 }, 218 } 219 220 // Replace the map with another one 221 newMap, err := NewMap(cs.Maps["test-map"]) 222 if err != nil { 223 t.Fatal(err) 224 } 225 defer newMap.Close() 226 227 err = newMap.Put(uint32(0), uint32(2)) 228 if err != nil { 229 t.Fatal(err) 230 } 231 232 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 233 MapReplacements: map[string]*Map{ 234 "test-map": newMap, 235 }, 236 }) 237 if err != nil { 238 t.Fatal(err) 239 } 240 defer coll.Close() 241 242 ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext) 243 testutils.SkipIfNotSupported(t, err) 244 if err != nil { 245 t.Fatal(err) 246 } 247 248 if ret != 2 { 249 t.Fatal("new / override map not used") 250 } 251 252 // Check that newMap isn't closed when the collection is closed 253 coll.Close() 254 err = newMap.Put(uint32(0), uint32(3)) 255 if err != nil { 256 t.Fatalf("failed to update replaced map: %s", err) 257 } 258 } 259 func TestCollectionSpecMapReplacements_NonExistingMap(t *testing.T) { 260 cs := &CollectionSpec{ 261 Maps: map[string]*MapSpec{ 262 "test-map": { 263 Type: Array, 264 KeySize: 4, 265 ValueSize: 4, 266 MaxEntries: 1, 267 }, 268 }, 269 } 270 271 // Override non-existing map 272 newMap, err := NewMap(cs.Maps["test-map"]) 273 if err != nil { 274 t.Fatal(err) 275 } 276 defer newMap.Close() 277 278 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 279 MapReplacements: map[string]*Map{ 280 "non-existing-map": newMap, 281 }, 282 }) 283 if err == nil { 284 coll.Close() 285 t.Fatal("Overriding a non existing map did not fail") 286 } 287 } 288 289 func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) { 290 cs := &CollectionSpec{ 291 Maps: map[string]*MapSpec{ 292 "test-map": { 293 Type: Array, 294 KeySize: 4, 295 ValueSize: 4, 296 MaxEntries: 1, 297 }, 298 }, 299 } 300 301 // Override map with mismatching spec 302 newMap, err := NewMap(&MapSpec{ 303 Type: Array, 304 KeySize: 4, 305 ValueSize: 8, // this is different 306 MaxEntries: 1, 307 }) 308 if err != nil { 309 t.Fatal(err) 310 } 311 // Map fd is duplicated by MapReplacements, this one can be safely closed. 312 defer newMap.Close() 313 314 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 315 MapReplacements: map[string]*Map{ 316 "test-map": newMap, 317 }, 318 }) 319 if err == nil { 320 coll.Close() 321 t.Fatal("Overriding a map with a mismatching spec did not fail") 322 } 323 if !errors.Is(err, ErrMapIncompatible) { 324 t.Fatalf("Overriding a map with a mismatching spec failed with the wrong error") 325 } 326 } 327 328 func TestCollectionRewriteConstants(t *testing.T) { 329 cs := &CollectionSpec{ 330 Maps: map[string]*MapSpec{ 331 ".rodata": { 332 Type: Array, 333 KeySize: 4, 334 ValueSize: 4, 335 MaxEntries: 1, 336 Value: &btf.Datasec{ 337 Vars: []btf.VarSecinfo{ 338 { 339 Type: &btf.Var{ 340 Name: "the_constant", 341 Type: &btf.Int{Size: 4}, 342 }, 343 Offset: 0, 344 Size: 4, 345 }, 346 }, 347 }, 348 Contents: []MapKV{ 349 {Key: uint32(0), Value: []byte{1, 1, 1, 1}}, 350 }, 351 }, 352 }, 353 } 354 355 err := cs.RewriteConstants(map[string]interface{}{ 356 "fake_constant_one": uint32(1), 357 "fake_constant_two": uint32(2), 358 }) 359 qt.Assert(t, qt.IsNotNil(err), qt.Commentf("RewriteConstants did not fail")) 360 361 var mErr *MissingConstantsError 362 if !errors.As(err, &mErr) { 363 t.Fatal("Error doesn't wrap MissingConstantsError:", err) 364 } 365 qt.Assert(t, qt.ContentEquals(mErr.Constants, []string{"fake_constant_one", "fake_constant_two"})) 366 367 err = cs.RewriteConstants(map[string]interface{}{ 368 "the_constant": uint32(0x42424242), 369 }) 370 qt.Assert(t, qt.IsNil(err)) 371 qt.Assert(t, qt.ContentEquals(cs.Maps[".rodata"].Contents[0].Value.([]byte), []byte{0x42, 0x42, 0x42, 0x42})) 372 } 373 374 func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) { 375 spec := &CollectionSpec{ 376 Maps: map[string]*MapSpec{ 377 "valid": { 378 Type: Array, 379 KeySize: 4, 380 ValueSize: 4, 381 MaxEntries: 1, 382 }, 383 "bogus": { 384 Type: Array, 385 MaxEntries: 0, 386 }, 387 }, 388 Programs: map[string]*ProgramSpec{ 389 "valid": { 390 Type: SocketFilter, 391 Instructions: asm.Instructions{ 392 asm.LoadImm(asm.R0, 0, asm.DWord), 393 asm.Return(), 394 }, 395 License: "MIT", 396 }, 397 "bogus": { 398 Type: SocketFilter, 399 Instructions: asm.Instructions{ 400 // Undefined return value is rejected 401 asm.Return(), 402 }, 403 License: "MIT", 404 }, 405 }, 406 } 407 408 var objs struct { 409 Prog *Program `ebpf:"valid"` 410 Map *Map `ebpf:"valid"` 411 } 412 413 if err := spec.LoadAndAssign(&objs, nil); err != nil { 414 t.Fatal("Assign loads a map or program that isn't requested in the struct:", err) 415 } 416 defer objs.Prog.Close() 417 defer objs.Map.Close() 418 419 if objs.Prog == nil { 420 t.Error("Program is nil") 421 } 422 423 if objs.Map == nil { 424 t.Error("Map is nil") 425 } 426 } 427 428 func TestCollectionSpecAssign(t *testing.T) { 429 var specs struct { 430 Program *ProgramSpec `ebpf:"prog1"` 431 Map *MapSpec `ebpf:"map1"` 432 } 433 434 mapSpec := &MapSpec{ 435 Type: Array, 436 KeySize: 4, 437 ValueSize: 4, 438 MaxEntries: 1, 439 } 440 progSpec := &ProgramSpec{ 441 Type: SocketFilter, 442 Instructions: asm.Instructions{ 443 asm.LoadImm(asm.R0, 0, asm.DWord), 444 asm.Return(), 445 }, 446 License: "MIT", 447 } 448 449 cs := &CollectionSpec{ 450 Maps: map[string]*MapSpec{ 451 "map1": mapSpec, 452 }, 453 Programs: map[string]*ProgramSpec{ 454 "prog1": progSpec, 455 }, 456 } 457 458 if err := cs.Assign(&specs); err != nil { 459 t.Fatal("Can't assign spec:", err) 460 } 461 462 if specs.Program != progSpec { 463 t.Fatalf("Expected Program to be %p, got %p", progSpec, specs.Program) 464 } 465 466 if specs.Map != mapSpec { 467 t.Fatalf("Expected Map to be %p, got %p", mapSpec, specs.Map) 468 } 469 470 if err := cs.Assign(new(int)); err == nil { 471 t.Fatal("Assign allows to besides *struct") 472 } 473 474 if err := cs.Assign(new(struct{ Foo int })); err != nil { 475 t.Fatal("Assign doesn't ignore untagged fields") 476 } 477 478 unexported := new(struct { 479 foo *MapSpec `ebpf:"map1"` 480 }) 481 482 if err := cs.Assign(unexported); err == nil { 483 t.Error("Assign should return an error on unexported fields") 484 } 485 } 486 487 func TestAssignValues(t *testing.T) { 488 zero := func(t reflect.Type, name string) (interface{}, error) { 489 return reflect.Zero(t).Interface(), nil 490 } 491 492 type t1 struct { 493 Bar int `ebpf:"bar"` 494 } 495 496 type t2 struct { 497 t1 498 Foo int `ebpf:"foo"` 499 } 500 501 type t2ptr struct { 502 *t1 503 Foo int `ebpf:"foo"` 504 } 505 506 invalid := []struct { 507 name string 508 to interface{} 509 }{ 510 {"non-struct", 1}, 511 {"non-pointer struct", t1{}}, 512 {"pointer to non-struct", new(int)}, 513 {"embedded nil pointer", &t2ptr{}}, 514 {"unexported field", new(struct { 515 foo int `ebpf:"foo"` 516 })}, 517 {"identical tag", new(struct { 518 Foo1 int `ebpf:"foo"` 519 Foo2 int `ebpf:"foo"` 520 })}, 521 } 522 523 for _, testcase := range invalid { 524 t.Run(testcase.name, func(t *testing.T) { 525 if err := assignValues(testcase.to, zero); err == nil { 526 t.Fatal("assignValues didn't return an error") 527 } else { 528 t.Log(err) 529 } 530 }) 531 } 532 533 valid := []struct { 534 name string 535 to interface{} 536 }{ 537 {"pointer to struct", new(t1)}, 538 {"embedded struct", new(t2)}, 539 {"embedded struct pointer", &t2ptr{t1: new(t1)}}, 540 {"untagged field", new(struct{ Foo int })}, 541 } 542 543 for _, testcase := range valid { 544 t.Run(testcase.name, func(t *testing.T) { 545 if err := assignValues(testcase.to, zero); err != nil { 546 t.Fatal("assignValues returned", err) 547 } 548 }) 549 } 550 551 } 552 553 func TestCollectionAssign(t *testing.T) { 554 var objs struct { 555 Program *Program `ebpf:"prog1"` 556 Map *Map `ebpf:"map1"` 557 } 558 559 cs := &CollectionSpec{ 560 Maps: map[string]*MapSpec{ 561 "map1": { 562 Type: Array, 563 KeySize: 4, 564 ValueSize: 4, 565 MaxEntries: 1, 566 }, 567 }, 568 Programs: map[string]*ProgramSpec{ 569 "prog1": { 570 Type: SocketFilter, 571 Instructions: asm.Instructions{ 572 asm.LoadImm(asm.R0, 0, asm.DWord), 573 asm.Return(), 574 }, 575 License: "MIT", 576 }, 577 }, 578 } 579 580 coll, err := NewCollection(cs) 581 qt.Assert(t, qt.IsNil(err)) 582 defer coll.Close() 583 584 qt.Assert(t, qt.IsNil(coll.Assign(&objs))) 585 defer objs.Program.Close() 586 defer objs.Map.Close() 587 588 // Check that objs has received ownership of map and prog 589 qt.Assert(t, qt.IsTrue(objs.Program.FD() >= 0)) 590 qt.Assert(t, qt.IsTrue(objs.Map.FD() >= 0)) 591 592 // Check that the collection has lost ownership 593 qt.Assert(t, qt.IsNil(coll.Programs["prog1"])) 594 qt.Assert(t, qt.IsNil(coll.Maps["map1"])) 595 } 596 597 func TestCollectionAssignFail(t *testing.T) { 598 // `map2` does not exist 599 var objs struct { 600 Program *Program `ebpf:"prog1"` 601 Map *Map `ebpf:"map2"` 602 } 603 604 cs := &CollectionSpec{ 605 Maps: map[string]*MapSpec{ 606 "map1": { 607 Type: Array, 608 KeySize: 4, 609 ValueSize: 4, 610 MaxEntries: 1, 611 }, 612 }, 613 Programs: map[string]*ProgramSpec{ 614 "prog1": { 615 Type: SocketFilter, 616 Instructions: asm.Instructions{ 617 asm.LoadImm(asm.R0, 0, asm.DWord), 618 asm.Return(), 619 }, 620 License: "MIT", 621 }, 622 }, 623 } 624 625 coll, err := NewCollection(cs) 626 qt.Assert(t, qt.IsNil(err)) 627 defer coll.Close() 628 629 qt.Assert(t, qt.IsNotNil(coll.Assign(&objs))) 630 631 // Check that the collection has retained ownership 632 qt.Assert(t, qt.IsNotNil(coll.Programs["prog1"])) 633 qt.Assert(t, qt.IsNotNil(coll.Maps["map1"])) 634 } 635 636 func TestIncompleteLoadAndAssign(t *testing.T) { 637 spec := &CollectionSpec{ 638 Programs: map[string]*ProgramSpec{ 639 "valid": { 640 Type: SocketFilter, 641 Instructions: asm.Instructions{ 642 asm.LoadImm(asm.R0, 0, asm.DWord), 643 asm.Return(), 644 }, 645 License: "MIT", 646 }, 647 "invalid": { 648 Type: SocketFilter, 649 Instructions: asm.Instructions{ 650 asm.Return(), 651 }, 652 License: "MIT", 653 }, 654 }, 655 } 656 657 s := struct { 658 // Assignment to Valid should execute and succeed. 659 Valid *Program `ebpf:"valid"` 660 // Assignment to Invalid should fail and cause Valid's fd to be closed. 661 Invalid *Program `ebpf:"invalid"` 662 }{} 663 664 if err := spec.LoadAndAssign(&s, nil); err == nil { 665 t.Fatal("expected error loading invalid ProgramSpec") 666 } 667 668 if s.Valid == nil { 669 t.Fatal("expected valid prog to be non-nil") 670 } 671 672 if fd := s.Valid.FD(); fd != -1 { 673 t.Fatal("expected valid prog to have closed fd -1, got:", fd) 674 } 675 676 if s.Invalid != nil { 677 t.Fatal("expected invalid prog to be nil due to never being assigned") 678 } 679 } 680 681 func BenchmarkNewCollection(b *testing.B) { 682 file := testutils.NativeFile(b, "testdata/loader-%s.elf") 683 spec, err := LoadCollectionSpec(file) 684 if err != nil { 685 b.Fatal(err) 686 } 687 688 spec.Maps["array_of_hash_map"].InnerMap = spec.Maps["hash_map"] 689 for _, m := range spec.Maps { 690 m.Pinning = PinNone 691 } 692 693 b.ReportAllocs() 694 b.ResetTimer() 695 696 for i := 0; i < b.N; i++ { 697 coll, err := NewCollection(spec) 698 if err != nil { 699 b.Fatal(err) 700 } 701 coll.Close() 702 } 703 } 704 705 func BenchmarkNewCollectionManyProgs(b *testing.B) { 706 file := testutils.NativeFile(b, "testdata/manyprogs-%s.elf") 707 spec, err := LoadCollectionSpec(file) 708 if err != nil { 709 b.Fatal(err) 710 } 711 712 b.ReportAllocs() 713 b.ResetTimer() 714 715 for i := 0; i < b.N; i++ { 716 coll, err := NewCollection(spec) 717 if err != nil { 718 b.Fatal(err) 719 } 720 coll.Close() 721 } 722 } 723 724 func BenchmarkLoadCollectionManyProgs(b *testing.B) { 725 file, err := os.Open(testutils.NativeFile(b, "testdata/manyprogs-%s.elf")) 726 qt.Assert(b, qt.IsNil(err)) 727 defer file.Close() 728 729 b.ReportAllocs() 730 b.ResetTimer() 731 732 for i := 0; i < b.N; i++ { 733 _, err := file.Seek(0, io.SeekStart) 734 if err != nil { 735 b.Fatal(err) 736 } 737 738 _, err = LoadCollectionSpecFromReader(file) 739 if err != nil { 740 b.Fatal(err) 741 } 742 } 743 } 744 745 func ExampleCollectionSpec_Assign() { 746 spec := &CollectionSpec{ 747 Maps: map[string]*MapSpec{ 748 "map1": { 749 Type: Array, 750 KeySize: 4, 751 ValueSize: 4, 752 MaxEntries: 1, 753 }, 754 }, 755 Programs: map[string]*ProgramSpec{ 756 "prog1": { 757 Type: SocketFilter, 758 Instructions: asm.Instructions{ 759 asm.LoadImm(asm.R0, 0, asm.DWord), 760 asm.Return(), 761 }, 762 License: "MIT", 763 }, 764 }, 765 } 766 767 type maps struct { 768 Map *MapSpec `ebpf:"map1"` 769 } 770 771 var specs struct { 772 maps 773 Program *ProgramSpec `ebpf:"prog1"` 774 } 775 776 if err := spec.Assign(&specs); err != nil { 777 panic(err) 778 } 779 780 fmt.Println(specs.Program.Type) 781 fmt.Println(specs.Map.Type) 782 783 // Output: SocketFilter 784 // Array 785 } 786 787 func ExampleCollectionSpec_LoadAndAssign() { 788 spec := &CollectionSpec{ 789 Maps: map[string]*MapSpec{ 790 "map1": { 791 Type: Array, 792 KeySize: 4, 793 ValueSize: 4, 794 MaxEntries: 1, 795 }, 796 }, 797 Programs: map[string]*ProgramSpec{ 798 "prog1": { 799 Type: SocketFilter, 800 Instructions: asm.Instructions{ 801 asm.LoadImm(asm.R0, 0, asm.DWord), 802 asm.Return(), 803 }, 804 License: "MIT", 805 }, 806 }, 807 } 808 809 var objs struct { 810 Program *Program `ebpf:"prog1"` 811 Map *Map `ebpf:"map1"` 812 } 813 814 if err := spec.LoadAndAssign(&objs, nil); err != nil { 815 panic(err) 816 } 817 defer objs.Program.Close() 818 defer objs.Map.Close() 819 820 fmt.Println(objs.Program.Type()) 821 fmt.Println(objs.Map.Type()) 822 823 // Output: SocketFilter 824 // Array 825 }