github.com/cilium/ebpf@v0.10.0/prog_test.go (about) 1 package ebpf 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "math" 9 "os" 10 "path/filepath" 11 "runtime" 12 "strings" 13 "syscall" 14 "testing" 15 "time" 16 17 qt "github.com/frankban/quicktest" 18 19 "github.com/cilium/ebpf/asm" 20 "github.com/cilium/ebpf/btf" 21 "github.com/cilium/ebpf/internal" 22 "github.com/cilium/ebpf/internal/sys" 23 "github.com/cilium/ebpf/internal/testutils" 24 "github.com/cilium/ebpf/internal/unix" 25 ) 26 27 func TestProgramRun(t *testing.T) { 28 testutils.SkipOnOldKernel(t, "4.8", "XDP program") 29 30 pat := []byte{0xDE, 0xAD, 0xBE, 0xEF} 31 buf := internal.EmptyBPFContext 32 33 // r1 : ctx_start 34 // r1+4: ctx_end 35 ins := asm.Instructions{ 36 // r2 = *(r1+4) 37 asm.LoadMem(asm.R2, asm.R1, 4, asm.Word), 38 // r1 = *(r1+0) 39 asm.LoadMem(asm.R1, asm.R1, 0, asm.Word), 40 // r3 = r1 41 asm.Mov.Reg(asm.R3, asm.R1), 42 // r3 += len(buf) 43 asm.Add.Imm(asm.R3, int32(len(buf))), 44 // if r3 > r2 goto +len(pat) 45 asm.JGT.Reg(asm.R3, asm.R2, "out"), 46 } 47 for i, b := range pat { 48 ins = append(ins, asm.StoreImm(asm.R1, int16(i), int64(b), asm.Byte)) 49 } 50 ins = append(ins, 51 // return 42 52 asm.LoadImm(asm.R0, 42, asm.DWord).WithSymbol("out"), 53 asm.Return(), 54 ) 55 56 t.Log(ins) 57 58 prog, err := NewProgram(&ProgramSpec{ 59 Name: "test", 60 Type: XDP, 61 Instructions: ins, 62 License: "MIT", 63 }) 64 if err != nil { 65 t.Fatal(err) 66 } 67 defer prog.Close() 68 69 p2, err := prog.Clone() 70 if err != nil { 71 t.Fatal("Can't clone program") 72 } 73 defer p2.Close() 74 75 prog.Close() 76 prog = p2 77 78 ret, out, err := prog.Test(buf) 79 testutils.SkipIfNotSupported(t, err) 80 if err != nil { 81 t.Fatal(err) 82 } 83 84 if ret != 42 { 85 t.Error("Expected return value to be 42, got", ret) 86 } 87 88 if !bytes.Equal(out[:len(pat)], pat) { 89 t.Errorf("Expected %v, got %v", pat, out) 90 } 91 } 92 93 func TestProgramRunWithOptions(t *testing.T) { 94 testutils.SkipOnOldKernel(t, "5.15", "XDP ctx_in/ctx_out") 95 96 ins := asm.Instructions{ 97 // Return XDP_ABORTED 98 asm.LoadImm(asm.R0, 0, asm.DWord), 99 asm.Return(), 100 } 101 102 prog, err := NewProgram(&ProgramSpec{ 103 Name: "test", 104 Type: XDP, 105 Instructions: ins, 106 License: "MIT", 107 }) 108 if err != nil { 109 t.Fatal(err) 110 } 111 defer prog.Close() 112 113 buf := internal.EmptyBPFContext 114 xdp := sys.XdpMd{ 115 Data: 0, 116 DataEnd: uint32(len(buf)), 117 } 118 xdpOut := sys.XdpMd{} 119 opts := RunOptions{ 120 Data: buf, 121 Context: xdp, 122 ContextOut: &xdpOut, 123 } 124 ret, err := prog.Run(&opts) 125 testutils.SkipIfNotSupported(t, err) 126 if err != nil { 127 t.Fatal(err) 128 } 129 130 if ret != 0 { 131 t.Error("Expected return value to be 0, got", ret) 132 } 133 134 if xdp != xdpOut { 135 t.Errorf("Expect xdp (%+v) == xdpOut (%+v)", xdp, xdpOut) 136 } 137 } 138 139 func TestProgramRunEmptyData(t *testing.T) { 140 testutils.SkipOnOldKernel(t, "5.13", "sk_lookup BPF_PROG_RUN") 141 142 ins := asm.Instructions{ 143 // Return SK_DROP 144 asm.LoadImm(asm.R0, 0, asm.DWord), 145 asm.Return(), 146 } 147 148 prog, err := NewProgram(&ProgramSpec{ 149 Name: "test", 150 Type: SkLookup, 151 AttachType: AttachSkLookup, 152 Instructions: ins, 153 License: "MIT", 154 }) 155 if err != nil { 156 t.Fatal(err) 157 } 158 defer prog.Close() 159 160 opts := RunOptions{ 161 Context: sys.SkLookup{ 162 Family: syscall.AF_INET, 163 }, 164 } 165 ret, err := prog.Run(&opts) 166 testutils.SkipIfNotSupported(t, err) 167 if err != nil { 168 t.Fatal(err) 169 } 170 171 if ret != 0 { 172 t.Error("Expected return value to be 0, got", ret) 173 } 174 } 175 176 func TestProgramBenchmark(t *testing.T) { 177 prog := mustSocketFilter(t) 178 179 ret, duration, err := prog.Benchmark(internal.EmptyBPFContext, 1, nil) 180 testutils.SkipIfNotSupported(t, err) 181 if err != nil { 182 t.Fatal("Error from Benchmark:", err) 183 } 184 185 if ret != 2 { 186 t.Error("Expected return value 2, got", ret) 187 } 188 189 if duration == 0 { 190 t.Error("Expected non-zero duration") 191 } 192 } 193 194 func TestProgramTestRunInterrupt(t *testing.T) { 195 testutils.SkipOnOldKernel(t, "5.0", "EINTR from BPF_PROG_TEST_RUN") 196 197 prog := mustSocketFilter(t) 198 199 var ( 200 tgid = unix.Getpid() 201 tidChan = make(chan int, 1) 202 exit = make(chan struct{}) 203 errs = make(chan error, 1) 204 timeout = time.After(5 * time.Second) 205 ) 206 207 defer close(exit) 208 209 go func() { 210 runtime.LockOSThread() 211 defer func() { 212 // Wait for the test to allow us to unlock the OS thread, to 213 // ensure that we don't send SIGUSR1 to the wrong thread. 214 <-exit 215 runtime.UnlockOSThread() 216 }() 217 218 tidChan <- unix.Gettid() 219 220 // Block this thread in the BPF syscall, so that we can 221 // trigger EINTR by sending a signal. 222 opts := RunOptions{ 223 Data: internal.EmptyBPFContext, 224 Repeat: math.MaxInt32, 225 Reset: func() { 226 // We don't know how long finishing the 227 // test run would take, so flag that we've seen 228 // an interruption and abort the goroutine. 229 close(errs) 230 runtime.Goexit() 231 }, 232 } 233 _, _, err := prog.run(&opts) 234 235 errs <- err 236 }() 237 238 tid := <-tidChan 239 for { 240 err := unix.Tgkill(tgid, tid, syscall.SIGUSR1) 241 if err != nil { 242 t.Fatal("Can't send signal to goroutine thread:", err) 243 } 244 245 select { 246 case err, ok := <-errs: 247 if !ok { 248 return 249 } 250 251 testutils.SkipIfNotSupported(t, err) 252 if err == nil { 253 t.Fatal("testRun wasn't interrupted") 254 } 255 256 t.Fatal("testRun returned an error:", err) 257 258 case <-timeout: 259 t.Fatal("Timed out trying to interrupt the goroutine") 260 261 default: 262 } 263 } 264 } 265 266 func TestProgramClose(t *testing.T) { 267 prog := mustSocketFilter(t) 268 269 if err := prog.Close(); err != nil { 270 t.Fatal("Can't close program:", err) 271 } 272 } 273 274 func TestProgramPin(t *testing.T) { 275 prog := mustSocketFilter(t) 276 c := qt.New(t) 277 278 tmp := testutils.TempBPFFS(t) 279 280 path := filepath.Join(tmp, "program") 281 if err := prog.Pin(path); err != nil { 282 t.Fatal(err) 283 } 284 285 pinned := prog.IsPinned() 286 c.Assert(pinned, qt.IsTrue) 287 288 prog.Close() 289 290 prog, err := LoadPinnedProgram(path, nil) 291 testutils.SkipIfNotSupported(t, err) 292 if err != nil { 293 t.Fatal(err) 294 } 295 defer prog.Close() 296 297 if prog.Type() != SocketFilter { 298 t.Error("Expected pinned program to have type SocketFilter, but got", prog.Type()) 299 } 300 301 if !prog.IsPinned() { 302 t.Error("Expected IsPinned to be true") 303 } 304 } 305 306 func TestProgramUnpin(t *testing.T) { 307 prog := mustSocketFilter(t) 308 c := qt.New(t) 309 310 tmp := testutils.TempBPFFS(t) 311 312 path := filepath.Join(tmp, "program") 313 if err := prog.Pin(path); err != nil { 314 t.Fatal(err) 315 } 316 317 pinned := prog.IsPinned() 318 c.Assert(pinned, qt.IsTrue) 319 320 if err := prog.Unpin(); err != nil { 321 t.Fatal("Failed to unpin program:", err) 322 } 323 if _, err := os.Stat(path); err == nil { 324 t.Fatal("Pinned program path still exists after unpinning:", err) 325 } 326 } 327 328 func TestProgramLoadPinnedWithFlags(t *testing.T) { 329 // Introduced in commit 6e71b04a8224. 330 testutils.SkipOnOldKernel(t, "4.14", "file_flags in BPF_OBJ_GET") 331 332 prog := mustSocketFilter(t) 333 334 tmp := testutils.TempBPFFS(t) 335 336 path := filepath.Join(tmp, "program") 337 if err := prog.Pin(path); err != nil { 338 t.Fatal(err) 339 } 340 341 prog.Close() 342 343 _, err := LoadPinnedProgram(path, &LoadPinOptions{ 344 Flags: math.MaxUint32, 345 }) 346 testutils.SkipIfNotSupported(t, err) 347 if !errors.Is(err, unix.EINVAL) { 348 t.Fatal("Invalid flags don't trigger an error:", err) 349 } 350 } 351 352 func TestProgramVerifierOutputOnError(t *testing.T) { 353 _, err := NewProgram(&ProgramSpec{ 354 Type: SocketFilter, 355 Instructions: asm.Instructions{ 356 asm.Return(), 357 }, 358 License: "MIT", 359 }) 360 if err == nil { 361 t.Fatal("Expected program to be invalid") 362 } 363 364 ve, ok := err.(*VerifierError) 365 if !ok { 366 t.Fatal("NewProgram does return an unwrapped VerifierError") 367 } 368 369 if !strings.Contains(ve.Error(), "R0 !read_ok") { 370 t.Logf("%+v", ve) 371 t.Error("Missing verifier log in error summary") 372 } 373 } 374 375 func TestProgramKernelVersion(t *testing.T) { 376 testutils.SkipOnOldKernel(t, "4.20", "KernelVersion") 377 prog, err := NewProgram(&ProgramSpec{ 378 Type: Kprobe, 379 Instructions: asm.Instructions{ 380 asm.LoadImm(asm.R0, 0, asm.DWord), 381 asm.Return(), 382 }, 383 KernelVersion: 42, 384 License: "MIT", 385 }) 386 if err != nil { 387 t.Fatal("Could not load Kprobe program") 388 } 389 defer prog.Close() 390 } 391 392 func TestProgramVerifierOutput(t *testing.T) { 393 prog, err := NewProgramWithOptions(socketFilterSpec, ProgramOptions{ 394 LogLevel: LogLevelInstruction, 395 }) 396 if err != nil { 397 t.Fatal(err) 398 } 399 defer prog.Close() 400 401 if prog.VerifierLog == "" { 402 t.Error("Expected VerifierLog to be present") 403 } 404 405 // Issue 64 406 _, err = NewProgramWithOptions(&ProgramSpec{ 407 Type: SocketFilter, 408 Instructions: asm.Instructions{ 409 asm.Mov.Reg(asm.R0, asm.R1), 410 }, 411 License: "MIT", 412 }, ProgramOptions{ 413 LogLevel: LogLevelInstruction, 414 }) 415 416 if err == nil { 417 t.Fatal("Expected an error from invalid program") 418 } 419 420 var ve *internal.VerifierError 421 if !errors.As(err, &ve) { 422 t.Error("Error is not a VerifierError") 423 } 424 } 425 426 // Test all scenarios where the VerifierError.Truncated flag is expected to be 427 // true, marked with an x. LL means ProgramOption.LogLevel. 428 // 429 // | | Valid | Invalid | 430 // |------|-------|---------| 431 // | LL=0 | | x | 432 // | LL>0 | x | x | 433 func TestProgramVerifierLogTruncated(t *testing.T) { 434 // Make the buffer intentionally small to coerce ENOSPC. 435 // 128 bytes is the smallest the kernel will accept. 436 logSize := 128 437 438 check := func(t *testing.T, err error) { 439 if err == nil { 440 t.Fatal("Expected an error") 441 } 442 var ve *internal.VerifierError 443 if !errors.As(err, &ve) { 444 t.Error("Error is not a VerifierError") 445 } 446 if !ve.Truncated { 447 t.Errorf("VerifierError is not truncated: %+v", ve) 448 } 449 } 450 451 // Generate a base program of sufficient size whose verifier log does not fit 452 // a 128-byte buffer. This should always result in ENOSPC, setting the 453 // VerifierError.Truncated flag. 454 base := func() (out asm.Instructions) { 455 for i := 0; i < 32; i++ { 456 out = append(out, asm.Mov.Reg(asm.R0, asm.R1)) 457 } 458 return 459 }() 460 461 invalid := func() (out asm.Instructions) { 462 out = base 463 // Touch R10 (read-only frame pointer) to reliably force a verifier error. 464 out = append(out, asm.Mov.Reg(asm.R10, asm.R0)) 465 out = append(out, asm.Return()) 466 return 467 }() 468 469 valid := func() (out asm.Instructions) { 470 out = base 471 out = append(out, asm.Return()) 472 return 473 }() 474 475 // Start out with testing against the invalid program. 476 spec := &ProgramSpec{ 477 Type: SocketFilter, 478 License: "MIT", 479 Instructions: invalid, 480 } 481 482 // Set an undersized log buffer without explicitly requesting a verifier log 483 // for an invalid program. 484 _, err := NewProgramWithOptions(spec, ProgramOptions{LogSize: logSize}) 485 check(t, err) 486 487 // Explicitly request a verifier log for an invalid program. 488 _, err = NewProgramWithOptions(spec, ProgramOptions{ 489 LogSize: logSize, 490 LogLevel: LogLevelInstruction, 491 }) 492 check(t, err) 493 494 // Run tests against a valid program from here on out. 495 spec.Instructions = valid 496 497 // Don't request a verifier log, only set LogSize. Expect the valid program to 498 // be created without errors. 499 prog, err := NewProgramWithOptions(spec, ProgramOptions{ 500 LogSize: logSize, 501 }) 502 if err != nil { 503 t.Fatal(err) 504 } 505 prog.Close() 506 507 // Explicitly request verifier log for a valid program. If a log is requested 508 // and the buffer is too small, ENOSPC occurs even for valid programs. 509 _, err = NewProgramWithOptions(spec, ProgramOptions{ 510 LogSize: logSize, 511 LogLevel: LogLevelInstruction, 512 }) 513 check(t, err) 514 } 515 516 func TestProgramWithUnsatisfiedMap(t *testing.T) { 517 coll, err := LoadCollectionSpec("testdata/loader-el.elf") 518 if err != nil { 519 t.Fatal(err) 520 } 521 522 // The program will have at least one map reference. 523 progSpec := coll.Programs["xdp_prog"] 524 progSpec.ByteOrder = nil 525 526 _, err = NewProgram(progSpec) 527 testutils.SkipIfNotSupported(t, err) 528 if !errors.Is(err, asm.ErrUnsatisfiedMapReference) { 529 t.Fatal("Expected an error wrapping asm.ErrUnsatisfiedMapReference, got", err) 530 } 531 t.Log(err) 532 } 533 534 func TestProgramName(t *testing.T) { 535 if err := haveObjName(); err != nil { 536 t.Skip(err) 537 } 538 539 prog := mustSocketFilter(t) 540 541 var info sys.ProgInfo 542 if err := sys.ObjInfo(prog.fd, &info); err != nil { 543 t.Fatal(err) 544 } 545 546 if name := unix.ByteSliceToString(info.Name[:]); name != "test" { 547 t.Errorf("Name is not test, got '%s'", name) 548 } 549 } 550 551 func TestSanitizeName(t *testing.T) { 552 for input, want := range map[string]string{ 553 "test": "test", 554 "t-est": "test", 555 "t_est": "t_est", 556 "hörnchen": "hrnchen", 557 } { 558 if have := SanitizeName(input, -1); have != want { 559 t.Errorf("Wanted '%s' got '%s'", want, have) 560 } 561 } 562 } 563 564 func TestProgramCloneNil(t *testing.T) { 565 p, err := (*Program)(nil).Clone() 566 if err != nil { 567 t.Fatal(err) 568 } 569 570 if p != nil { 571 t.Fatal("Cloning a nil Program doesn't return nil") 572 } 573 } 574 575 func TestProgramMarshaling(t *testing.T) { 576 const idx = uint32(0) 577 578 arr := createProgramArray(t) 579 defer arr.Close() 580 581 prog := mustSocketFilter(t) 582 583 if err := arr.Put(idx, prog); err != nil { 584 t.Fatal("Can't put program:", err) 585 } 586 587 if err := arr.Lookup(idx, Program{}); err == nil { 588 t.Fatal("Lookup accepts non-pointer Program") 589 } 590 591 var prog2 *Program 592 defer prog2.Close() 593 594 if err := arr.Lookup(idx, prog2); err == nil { 595 t.Fatal("Get accepts *Program") 596 } 597 598 testutils.SkipOnOldKernel(t, "4.12", "lookup for ProgramArray") 599 600 if err := arr.Lookup(idx, &prog2); err != nil { 601 t.Fatal("Can't unmarshal program:", err) 602 } 603 defer prog2.Close() 604 605 if prog2 == nil { 606 t.Fatal("Unmarshalling set program to nil") 607 } 608 } 609 610 func TestProgramFromFD(t *testing.T) { 611 prog := mustSocketFilter(t) 612 613 // If you're thinking about copying this, don't. Use 614 // Clone() instead. 615 prog2, err := NewProgramFromFD(prog.FD()) 616 testutils.SkipIfNotSupported(t, err) 617 if err != nil { 618 t.Fatal(err) 619 } 620 621 // Name and type are supposed to be copied from program info. 622 if haveObjName() == nil && prog2.name != "test" { 623 t.Errorf("Expected program to have name test, got '%s'", prog2.name) 624 } 625 626 if prog2.typ != SocketFilter { 627 t.Errorf("Expected program to have type SocketFilter, got '%s'", prog2.typ) 628 } 629 630 // Both programs refer to the same fd now. Closing either of them will 631 // release the fd to the OS, which then might re-use that fd for another 632 // test. Once we close the second map we might close the re-used fd 633 // inadvertently, leading to spurious test failures. 634 // To avoid this we have to "leak" one of the programs. 635 prog2.fd.Forget() 636 } 637 638 func TestHaveProgTestRun(t *testing.T) { 639 testutils.CheckFeatureTest(t, haveProgRun) 640 } 641 642 func TestProgramGetNextID(t *testing.T) { 643 testutils.SkipOnOldKernel(t, "4.13", "bpf_prog_get_next_id") 644 645 // Ensure there is at least one program loaded 646 _ = mustSocketFilter(t) 647 648 // As there can be multiple eBPF programs, we loop over all of them and 649 // make sure, the IDs increase and the last call will return ErrNotExist 650 last := ProgramID(0) 651 for { 652 next, err := ProgramGetNextID(last) 653 if errors.Is(err, os.ErrNotExist) { 654 if last == 0 { 655 t.Fatal("Got ErrNotExist on the first iteration") 656 } 657 break 658 } 659 if err != nil { 660 t.Fatal("Unexpected error:", err) 661 } 662 if next <= last { 663 t.Fatalf("Expected next ID (%d) to be higher than the last ID (%d)", next, last) 664 } 665 last = next 666 } 667 } 668 669 func TestNewProgramFromID(t *testing.T) { 670 prog := mustSocketFilter(t) 671 672 info, err := prog.Info() 673 testutils.SkipIfNotSupported(t, err) 674 if err != nil { 675 t.Fatal("Could not get program info:", err) 676 } 677 678 id, ok := info.ID() 679 if !ok { 680 t.Skip("Program ID not supported") 681 } 682 683 prog2, err := NewProgramFromID(id) 684 if err != nil { 685 t.Fatalf("Can't get FD for program ID %d: %v", id, err) 686 } 687 prog2.Close() 688 689 // As there can be multiple programs, we use max(uint32) as ProgramID to trigger an expected error. 690 _, err = NewProgramFromID(ProgramID(math.MaxUint32)) 691 if !errors.Is(err, os.ErrNotExist) { 692 t.Fatal("Expected ErrNotExist, got:", err) 693 } 694 } 695 696 func TestProgramRejectIncorrectByteOrder(t *testing.T) { 697 spec := socketFilterSpec.Copy() 698 699 spec.ByteOrder = binary.BigEndian 700 if internal.NativeEndian == binary.BigEndian { 701 spec.ByteOrder = binary.LittleEndian 702 } 703 704 _, err := NewProgram(spec) 705 if err == nil { 706 t.Error("Incorrect ByteOrder should be rejected at load time") 707 } 708 } 709 710 func TestProgramSpecTag(t *testing.T) { 711 arr := createArray(t) 712 defer arr.Close() 713 714 spec := &ProgramSpec{ 715 Type: SocketFilter, 716 Instructions: asm.Instructions{ 717 asm.LoadImm(asm.R0, -1, asm.DWord), 718 asm.LoadMapPtr(asm.R1, arr.FD()), 719 asm.Mov.Imm32(asm.R0, 0), 720 asm.Return(), 721 }, 722 License: "MIT", 723 } 724 725 prog, err := NewProgram(spec) 726 if err != nil { 727 t.Fatal(err) 728 } 729 defer prog.Close() 730 731 info, err := prog.Info() 732 testutils.SkipIfNotSupported(t, err) 733 if err != nil { 734 t.Fatal(err) 735 } 736 737 tag, err := spec.Tag() 738 if err != nil { 739 t.Fatal("Can't calculate tag:", err) 740 } 741 742 if tag != info.Tag { 743 t.Errorf("Calculated tag %s doesn't match kernel tag %s", tag, info.Tag) 744 } 745 } 746 747 func TestProgramAttachToKernel(t *testing.T) { 748 // See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744 749 testutils.SkipOnOldKernel(t, "5.5", "attach_btf_id") 750 751 haveTestmod := false 752 if !testutils.MustKernelVersion().Less(internal.Version{5, 11}) { 753 // See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744 754 testmod, err := btf.FindHandle(func(info *btf.HandleInfo) bool { 755 return info.IsModule() && info.Name == "bpf_testmod" 756 }) 757 if err != nil && !errors.Is(err, btf.ErrNotFound) { 758 t.Fatal(err) 759 } 760 haveTestmod = testmod != nil 761 testmod.Close() 762 } 763 764 tests := []struct { 765 attachTo string 766 programType ProgramType 767 attachType AttachType 768 flags uint32 769 }{ 770 { 771 attachTo: "task_getpgid", 772 programType: LSM, 773 attachType: AttachLSMMac, 774 }, 775 { 776 attachTo: "inet_dgram_connect", 777 programType: Tracing, 778 attachType: AttachTraceFEntry, 779 }, 780 { 781 attachTo: "inet_dgram_connect", 782 programType: Tracing, 783 attachType: AttachTraceFExit, 784 }, 785 { 786 attachTo: "bpf_modify_return_test", 787 programType: Tracing, 788 attachType: AttachModifyReturn, 789 }, 790 { 791 attachTo: "kfree_skb", 792 programType: Tracing, 793 attachType: AttachTraceRawTp, 794 }, 795 { 796 attachTo: "bpf_testmod_test_read", 797 programType: Tracing, 798 attachType: AttachTraceFEntry, 799 }, 800 { 801 attachTo: "bpf_testmod_test_read", 802 programType: Tracing, 803 attachType: AttachTraceFExit, 804 }, 805 { 806 attachTo: "bpf_testmod_test_read", 807 programType: Tracing, 808 attachType: AttachModifyReturn, 809 }, 810 { 811 attachTo: "bpf_testmod_test_read", 812 programType: Tracing, 813 attachType: AttachTraceRawTp, 814 }, 815 } 816 for _, test := range tests { 817 name := fmt.Sprintf("%s:%s", test.attachType, test.attachTo) 818 t.Run(name, func(t *testing.T) { 819 if strings.HasPrefix(test.attachTo, "bpf_testmod_") && !haveTestmod { 820 t.Skip("bpf_testmod not loaded") 821 } 822 823 prog, err := NewProgram(&ProgramSpec{ 824 AttachTo: test.attachTo, 825 AttachType: test.attachType, 826 Instructions: asm.Instructions{ 827 asm.LoadImm(asm.R0, 0, asm.DWord), 828 asm.Return(), 829 }, 830 License: "GPL", 831 Type: test.programType, 832 Flags: test.flags, 833 }) 834 if err != nil { 835 t.Fatal("Can't load program:", err) 836 } 837 prog.Close() 838 }) 839 } 840 } 841 842 func TestProgramKernelTypes(t *testing.T) { 843 if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) { 844 t.Skip("/sys/kernel/btf/vmlinux not present") 845 } 846 847 btfSpec, err := btf.LoadSpec("/sys/kernel/btf/vmlinux") 848 if err != nil { 849 t.Fatal(err) 850 } 851 852 prog, err := NewProgramWithOptions(&ProgramSpec{ 853 Type: Tracing, 854 AttachType: AttachTraceIter, 855 AttachTo: "bpf_map", 856 Instructions: asm.Instructions{ 857 asm.Mov.Imm(asm.R0, 0), 858 asm.Return(), 859 }, 860 License: "MIT", 861 }, ProgramOptions{ 862 KernelTypes: btfSpec, 863 }) 864 testutils.SkipIfNotSupported(t, err) 865 if err != nil { 866 t.Fatal("NewProgram with Target:", err) 867 } 868 prog.Close() 869 } 870 871 func TestProgramBindMap(t *testing.T) { 872 testutils.SkipOnOldKernel(t, "5.10", "BPF_PROG_BIND_MAP") 873 874 arr, err := NewMap(&MapSpec{ 875 Type: Array, 876 KeySize: 4, 877 ValueSize: 4, 878 MaxEntries: 1, 879 }) 880 if err != nil { 881 t.Errorf("Failed to load map: %v", err) 882 } 883 defer arr.Close() 884 885 prog := mustSocketFilter(t) 886 887 // The attached map does not contain BTF information. So 888 // the metadata part of the program will be empty. This 889 // test just makes sure that we can bind a map to a program. 890 if err := prog.BindMap(arr); err != nil { 891 t.Errorf("Failed to bind map to program: %v", err) 892 } 893 } 894 895 func TestProgramInstructions(t *testing.T) { 896 name := "test_prog" 897 spec := &ProgramSpec{ 898 Type: SocketFilter, 899 Name: name, 900 Instructions: asm.Instructions{ 901 asm.LoadImm(asm.R0, -1, asm.DWord).WithSymbol(name), 902 asm.Return(), 903 }, 904 License: "MIT", 905 } 906 907 prog, err := NewProgram(spec) 908 if err != nil { 909 t.Fatal(err) 910 } 911 defer prog.Close() 912 913 pi, err := prog.Info() 914 testutils.SkipIfNotSupported(t, err) 915 if err != nil { 916 t.Fatal(err) 917 } 918 919 insns, err := pi.Instructions() 920 if err != nil { 921 t.Fatal(err) 922 } 923 924 tag, err := spec.Tag() 925 if err != nil { 926 t.Fatal(err) 927 } 928 929 tagXlated, err := insns.Tag(internal.NativeEndian) 930 if err != nil { 931 t.Fatal(err) 932 } 933 934 if tag != tagXlated { 935 t.Fatalf("tag %s differs from xlated instructions tag %s", tag, tagXlated) 936 } 937 } 938 939 func createProgramArray(t *testing.T) *Map { 940 t.Helper() 941 942 arr, err := NewMap(&MapSpec{ 943 Type: ProgramArray, 944 KeySize: 4, 945 ValueSize: 4, 946 MaxEntries: 1, 947 }) 948 if err != nil { 949 t.Fatal(err) 950 } 951 return arr 952 } 953 954 var socketFilterSpec = &ProgramSpec{ 955 Name: "test", 956 Type: SocketFilter, 957 Instructions: asm.Instructions{ 958 asm.LoadImm(asm.R0, 2, asm.DWord), 959 asm.Return(), 960 }, 961 License: "MIT", 962 } 963 964 func mustSocketFilter(tb testing.TB) *Program { 965 tb.Helper() 966 967 prog, err := NewProgram(socketFilterSpec) 968 if err != nil { 969 tb.Fatal(err) 970 } 971 tb.Cleanup(func() { prog.Close() }) 972 973 return prog 974 } 975 976 // Print the full verifier log when loading a program fails. 977 func ExampleProgram_verboseVerifierError() { 978 _, err := NewProgram(&ProgramSpec{ 979 Type: SocketFilter, 980 Instructions: asm.Instructions{ 981 asm.LoadImm(asm.R0, 0, asm.DWord), 982 // Missing Return 983 }, 984 License: "MIT", 985 }) 986 987 var ve *VerifierError 988 if errors.As(err, &ve) { 989 // Using %+v will print the whole verifier error, not just the last 990 // few lines. 991 fmt.Printf("Verifier error: %+v\n", ve) 992 } 993 } 994 995 // Use NewProgramWithOptions if you'd like to get the verifier output 996 // for a program, or if you want to change the buffer size used when 997 // generating error messages. 998 func ExampleProgram_retrieveVerifierLog() { 999 spec := &ProgramSpec{ 1000 Type: SocketFilter, 1001 Instructions: asm.Instructions{ 1002 asm.LoadImm(asm.R0, 0, asm.DWord), 1003 asm.Return(), 1004 }, 1005 License: "MIT", 1006 } 1007 1008 prog, err := NewProgramWithOptions(spec, ProgramOptions{ 1009 LogLevel: LogLevelInstruction, 1010 LogSize: 1024, 1011 }) 1012 if err != nil { 1013 panic(err) 1014 } 1015 defer prog.Close() 1016 1017 fmt.Println("The verifier output is:") 1018 fmt.Println(prog.VerifierLog) 1019 } 1020 1021 // It's possible to read a program directly from a ProgramArray. 1022 func ExampleProgram_unmarshalFromMap() { 1023 progArray, err := LoadPinnedMap("/path/to/map", nil) 1024 if err != nil { 1025 panic(err) 1026 } 1027 defer progArray.Close() 1028 1029 // Load a single program 1030 var prog *Program 1031 if err := progArray.Lookup(uint32(0), &prog); err != nil { 1032 panic(err) 1033 } 1034 defer prog.Close() 1035 1036 fmt.Println("first prog:", prog) 1037 1038 // Iterate all programs 1039 var ( 1040 key uint32 1041 entries = progArray.Iterate() 1042 ) 1043 1044 for entries.Next(&key, &prog) { 1045 fmt.Println(key, "is", prog) 1046 } 1047 1048 if err := entries.Err(); err != nil { 1049 panic(err) 1050 } 1051 } 1052 1053 func ExampleProgramSpec_Tag() { 1054 spec := &ProgramSpec{ 1055 Type: SocketFilter, 1056 Instructions: asm.Instructions{ 1057 asm.LoadImm(asm.R0, 0, asm.DWord), 1058 asm.Return(), 1059 }, 1060 License: "MIT", 1061 } 1062 1063 prog, _ := NewProgram(spec) 1064 info, _ := prog.Info() 1065 tag, _ := spec.Tag() 1066 1067 if info.Tag != tag { 1068 fmt.Printf("The tags don't match: %s != %s\n", info.Tag, tag) 1069 } else { 1070 fmt.Println("The programs are identical, tag is", tag) 1071 } 1072 }