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