package ebpf import ( "bytes" "encoding/binary" "errors" "fmt" "math" "os" "path/filepath" "runtime" "strings" "syscall" "testing" "time" qt "github.com/frankban/quicktest" "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/testutils" "github.com/cilium/ebpf/internal/unix" ) func TestProgramRun(t *testing.T) { testutils.SkipOnOldKernel(t, "4.8", "XDP program") pat := []byte{0xDE, 0xAD, 0xBE, 0xEF} buf := internal.EmptyBPFContext // r1 : ctx_start // r1+4: ctx_end ins := asm.Instructions{ // r2 = *(r1+4) asm.LoadMem(asm.R2, asm.R1, 4, asm.Word), // r1 = *(r1+0) asm.LoadMem(asm.R1, asm.R1, 0, asm.Word), // r3 = r1 asm.Mov.Reg(asm.R3, asm.R1), // r3 += len(buf) asm.Add.Imm(asm.R3, int32(len(buf))), // if r3 > r2 goto +len(pat) asm.JGT.Reg(asm.R3, asm.R2, "out"), } for i, b := range pat { ins = append(ins, asm.StoreImm(asm.R1, int16(i), int64(b), asm.Byte)) } ins = append(ins, // return 42 asm.LoadImm(asm.R0, 42, asm.DWord).WithSymbol("out"), asm.Return(), ) t.Log(ins) prog, err := NewProgram(&ProgramSpec{ Name: "test", Type: XDP, Instructions: ins, License: "MIT", }) if err != nil { t.Fatal(err) } defer prog.Close() p2, err := prog.Clone() if err != nil { t.Fatal("Can't clone program") } defer p2.Close() prog.Close() prog = p2 ret, out, err := prog.Test(buf) testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal(err) } if ret != 42 { t.Error("Expected return value to be 42, got", ret) } if !bytes.Equal(out[:len(pat)], pat) { t.Errorf("Expected %v, got %v", pat, out) } } func TestProgramRunWithOptions(t *testing.T) { testutils.SkipOnOldKernel(t, "5.15", "XDP ctx_in/ctx_out") ins := asm.Instructions{ // Return XDP_ABORTED asm.LoadImm(asm.R0, 0, asm.DWord), asm.Return(), } prog, err := NewProgram(&ProgramSpec{ Name: "test", Type: XDP, Instructions: ins, License: "MIT", }) if err != nil { t.Fatal(err) } defer prog.Close() buf := internal.EmptyBPFContext xdp := sys.XdpMd{ Data: 0, DataEnd: uint32(len(buf)), } xdpOut := sys.XdpMd{} opts := RunOptions{ Data: buf, Context: xdp, ContextOut: &xdpOut, } ret, err := prog.Run(&opts) testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal(err) } if ret != 0 { t.Error("Expected return value to be 0, got", ret) } if xdp != xdpOut { t.Errorf("Expect xdp (%+v) == xdpOut (%+v)", xdp, xdpOut) } } func TestProgramRunEmptyData(t *testing.T) { testutils.SkipOnOldKernel(t, "5.13", "sk_lookup BPF_PROG_RUN") ins := asm.Instructions{ // Return SK_DROP asm.LoadImm(asm.R0, 0, asm.DWord), asm.Return(), } prog, err := NewProgram(&ProgramSpec{ Name: "test", Type: SkLookup, AttachType: AttachSkLookup, Instructions: ins, License: "MIT", }) if err != nil { t.Fatal(err) } defer prog.Close() opts := RunOptions{ Context: sys.SkLookup{ Family: syscall.AF_INET, }, } ret, err := prog.Run(&opts) testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal(err) } if ret != 0 { t.Error("Expected return value to be 0, got", ret) } } func TestProgramBenchmark(t *testing.T) { prog := mustSocketFilter(t) ret, duration, err := prog.Benchmark(internal.EmptyBPFContext, 1, nil) testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal("Error from Benchmark:", err) } if ret != 2 { t.Error("Expected return value 2, got", ret) } if duration == 0 { t.Error("Expected non-zero duration") } } func TestProgramTestRunInterrupt(t *testing.T) { testutils.SkipOnOldKernel(t, "5.0", "EINTR from BPF_PROG_TEST_RUN") prog := mustSocketFilter(t) var ( tgid = unix.Getpid() tidChan = make(chan int, 1) exit = make(chan struct{}) errs = make(chan error, 1) timeout = time.After(5 * time.Second) ) defer close(exit) go func() { runtime.LockOSThread() defer func() { // Wait for the test to allow us to unlock the OS thread, to // ensure that we don't send SIGUSR1 to the wrong thread. <-exit runtime.UnlockOSThread() }() tidChan <- unix.Gettid() // Block this thread in the BPF syscall, so that we can // trigger EINTR by sending a signal. opts := RunOptions{ Data: internal.EmptyBPFContext, Repeat: math.MaxInt32, Reset: func() { // We don't know how long finishing the // test run would take, so flag that we've seen // an interruption and abort the goroutine. close(errs) runtime.Goexit() }, } _, _, err := prog.run(&opts) errs <- err }() tid := <-tidChan for { err := unix.Tgkill(tgid, tid, syscall.SIGUSR1) if err != nil { t.Fatal("Can't send signal to goroutine thread:", err) } select { case err, ok := <-errs: if !ok { return } testutils.SkipIfNotSupported(t, err) if err == nil { t.Fatal("testRun wasn't interrupted") } t.Fatal("testRun returned an error:", err) case <-timeout: t.Fatal("Timed out trying to interrupt the goroutine") default: } } } func TestProgramClose(t *testing.T) { prog := mustSocketFilter(t) if err := prog.Close(); err != nil { t.Fatal("Can't close program:", err) } } func TestProgramPin(t *testing.T) { prog := mustSocketFilter(t) c := qt.New(t) tmp := testutils.TempBPFFS(t) path := filepath.Join(tmp, "program") if err := prog.Pin(path); err != nil { t.Fatal(err) } pinned := prog.IsPinned() c.Assert(pinned, qt.IsTrue) prog.Close() prog, err := LoadPinnedProgram(path, nil) testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal(err) } defer prog.Close() if prog.Type() != SocketFilter { t.Error("Expected pinned program to have type SocketFilter, but got", prog.Type()) } if haveObjName() == nil { if prog.name != "test" { t.Errorf("Expected program to have object name 'test', got '%s'", prog.name) } } else { if prog.name != "program" { t.Errorf("Expected program to have file name 'program', got '%s'", prog.name) } } if !prog.IsPinned() { t.Error("Expected IsPinned to be true") } } func TestProgramUnpin(t *testing.T) { prog := mustSocketFilter(t) c := qt.New(t) tmp := testutils.TempBPFFS(t) path := filepath.Join(tmp, "program") if err := prog.Pin(path); err != nil { t.Fatal(err) } pinned := prog.IsPinned() c.Assert(pinned, qt.IsTrue) if err := prog.Unpin(); err != nil { t.Fatal("Failed to unpin program:", err) } if _, err := os.Stat(path); err == nil { t.Fatal("Pinned program path still exists after unpinning:", err) } } func TestProgramLoadPinnedWithFlags(t *testing.T) { // Introduced in commit 6e71b04a8224. testutils.SkipOnOldKernel(t, "4.14", "file_flags in BPF_OBJ_GET") prog := mustSocketFilter(t) tmp := testutils.TempBPFFS(t) path := filepath.Join(tmp, "program") if err := prog.Pin(path); err != nil { t.Fatal(err) } prog.Close() _, err := LoadPinnedProgram(path, &LoadPinOptions{ Flags: math.MaxUint32, }) testutils.SkipIfNotSupported(t, err) if !errors.Is(err, unix.EINVAL) { t.Fatal("Invalid flags don't trigger an error:", err) } } func TestProgramVerifierOutputOnError(t *testing.T) { _, err := NewProgram(&ProgramSpec{ Type: SocketFilter, Instructions: asm.Instructions{ asm.Return(), }, License: "MIT", }) if err == nil { t.Fatal("Expected program to be invalid") } ve, ok := err.(*VerifierError) if !ok { t.Fatal("NewProgram does return an unwrapped VerifierError") } if !strings.Contains(ve.Error(), "R0 !read_ok") { t.Logf("%+v", ve) t.Error("Missing verifier log in error summary") } } func TestProgramKernelVersion(t *testing.T) { testutils.SkipOnOldKernel(t, "4.20", "KernelVersion") prog, err := NewProgram(&ProgramSpec{ Type: Kprobe, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, 0, asm.DWord), asm.Return(), }, KernelVersion: 42, License: "MIT", }) if err != nil { t.Fatal("Could not load Kprobe program") } defer prog.Close() } func TestProgramVerifierOutput(t *testing.T) { prog, err := NewProgramWithOptions(socketFilterSpec, ProgramOptions{ LogLevel: LogLevelInstruction, }) if err != nil { t.Fatal(err) } defer prog.Close() if prog.VerifierLog == "" { t.Error("Expected VerifierLog to be present") } // Issue 64 _, err = NewProgramWithOptions(&ProgramSpec{ Type: SocketFilter, Instructions: asm.Instructions{ asm.Mov.Reg(asm.R0, asm.R1), }, License: "MIT", }, ProgramOptions{ LogLevel: LogLevelInstruction, }) if err == nil { t.Fatal("Expected an error from invalid program") } var ve *internal.VerifierError if !errors.As(err, &ve) { t.Error("Error is not a VerifierError") } } // Test all scenarios where the VerifierError.Truncated flag is expected to be // true, marked with an x. LL means ProgramOption.LogLevel. // // | | Valid | Invalid | // |------|-------|---------| // | LL=0 | | x | // | LL>0 | x | x | func TestProgramVerifierLogTruncated(t *testing.T) { // Make the buffer intentionally small to coerce ENOSPC. // 128 bytes is the smallest the kernel will accept. logSize := 128 check := func(t *testing.T, err error) { if err == nil { t.Fatal("Expected an error") } var ve *internal.VerifierError if !errors.As(err, &ve) { t.Fatal("Error is not a VerifierError") } if !ve.Truncated { t.Errorf("VerifierError is not truncated: %+v", ve) } } // Generate a base program of sufficient size whose verifier log does not fit // a 128-byte buffer. This should always result in ENOSPC, setting the // VerifierError.Truncated flag. base := func() (out asm.Instructions) { for i := 0; i < 32; i++ { out = append(out, asm.Mov.Reg(asm.R0, asm.R1)) } return }() invalid := func() (out asm.Instructions) { out = base // Touch R10 (read-only frame pointer) to reliably force a verifier error. out = append(out, asm.Mov.Reg(asm.R10, asm.R0)) out = append(out, asm.Return()) return }() valid := func() (out asm.Instructions) { out = base out = append(out, asm.Return()) return }() // Start out with testing against the invalid program. spec := &ProgramSpec{ Type: SocketFilter, License: "MIT", Instructions: invalid, } // Set an undersized log buffer without explicitly requesting a verifier log // for an invalid program. _, err := NewProgramWithOptions(spec, ProgramOptions{LogSize: logSize}) check(t, err) // Explicitly request a verifier log for an invalid program. _, err = NewProgramWithOptions(spec, ProgramOptions{ LogSize: logSize, LogLevel: LogLevelInstruction, }) check(t, err) // Run tests against a valid program from here on out. spec.Instructions = valid // Don't request a verifier log, only set LogSize. Expect the valid program to // be created without errors. prog, err := NewProgramWithOptions(spec, ProgramOptions{ LogSize: logSize, }) if err != nil { t.Fatal(err) } prog.Close() // Explicitly request verifier log for a valid program. If a log is requested // and the buffer is too small, ENOSPC occurs even for valid programs. _, err = NewProgramWithOptions(spec, ProgramOptions{ LogSize: logSize, LogLevel: LogLevelInstruction, }) check(t, err) } func TestProgramWithUnsatisfiedMap(t *testing.T) { coll, err := LoadCollectionSpec("testdata/loader-el.elf") if err != nil { t.Fatal(err) } // The program will have at least one map reference. progSpec := coll.Programs["xdp_prog"] progSpec.ByteOrder = nil _, err = NewProgram(progSpec) testutils.SkipIfNotSupported(t, err) if !errors.Is(err, asm.ErrUnsatisfiedMapReference) { t.Fatal("Expected an error wrapping asm.ErrUnsatisfiedMapReference, got", err) } t.Log(err) } func TestProgramName(t *testing.T) { if err := haveObjName(); err != nil { t.Skip(err) } prog := mustSocketFilter(t) var info sys.ProgInfo if err := sys.ObjInfo(prog.fd, &info); err != nil { t.Fatal(err) } if name := unix.ByteSliceToString(info.Name[:]); name != "test" { t.Errorf("Name is not test, got '%s'", name) } } func TestSanitizeName(t *testing.T) { for input, want := range map[string]string{ "test": "test", "t-est": "test", "t_est": "t_est", "hörnchen": "hrnchen", } { if have := SanitizeName(input, -1); have != want { t.Errorf("Wanted '%s' got '%s'", want, have) } } } func TestProgramCloneNil(t *testing.T) { p, err := (*Program)(nil).Clone() if err != nil { t.Fatal(err) } if p != nil { t.Fatal("Cloning a nil Program doesn't return nil") } } func TestProgramMarshaling(t *testing.T) { const idx = uint32(0) arr := createProgramArray(t) defer arr.Close() prog := mustSocketFilter(t) if err := arr.Put(idx, prog); err != nil { t.Fatal("Can't put program:", err) } if err := arr.Lookup(idx, Program{}); err == nil { t.Fatal("Lookup accepts non-pointer Program") } var prog2 *Program defer prog2.Close() if err := arr.Lookup(idx, prog2); err == nil { t.Fatal("Get accepts *Program") } testutils.SkipOnOldKernel(t, "4.12", "lookup for ProgramArray") if err := arr.Lookup(idx, &prog2); err != nil { t.Fatal("Can't unmarshal program:", err) } defer prog2.Close() if prog2 == nil { t.Fatal("Unmarshalling set program to nil") } } func TestProgramFromFD(t *testing.T) { prog := mustSocketFilter(t) // If you're thinking about copying this, don't. Use // Clone() instead. prog2, err := NewProgramFromFD(dupFD(t, prog.FD())) testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal(err) } defer prog2.Close() // Name and type are supposed to be copied from program info. if haveObjName() == nil && prog2.name != "test" { t.Errorf("Expected program to have name test, got '%s'", prog2.name) } if prog2.typ != SocketFilter { t.Errorf("Expected program to have type SocketFilter, got '%s'", prog2.typ) } } func TestHaveProgTestRun(t *testing.T) { testutils.CheckFeatureTest(t, haveProgRun) } func TestProgramGetNextID(t *testing.T) { testutils.SkipOnOldKernel(t, "4.13", "bpf_prog_get_next_id") // Ensure there is at least one program loaded _ = mustSocketFilter(t) // As there can be multiple eBPF programs, we loop over all of them and // make sure, the IDs increase and the last call will return ErrNotExist last := ProgramID(0) for { next, err := ProgramGetNextID(last) if errors.Is(err, os.ErrNotExist) { if last == 0 { t.Fatal("Got ErrNotExist on the first iteration") } break } if err != nil { t.Fatal("Unexpected error:", err) } if next <= last { t.Fatalf("Expected next ID (%d) to be higher than the last ID (%d)", next, last) } last = next } } func TestNewProgramFromID(t *testing.T) { prog := mustSocketFilter(t) info, err := prog.Info() testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal("Could not get program info:", err) } id, ok := info.ID() if !ok { t.Skip("Program ID not supported") } prog2, err := NewProgramFromID(id) if err != nil { t.Fatalf("Can't get FD for program ID %d: %v", id, err) } prog2.Close() // As there can be multiple programs, we use max(uint32) as ProgramID to trigger an expected error. _, err = NewProgramFromID(ProgramID(math.MaxUint32)) if !errors.Is(err, os.ErrNotExist) { t.Fatal("Expected ErrNotExist, got:", err) } } func TestProgramRejectIncorrectByteOrder(t *testing.T) { spec := socketFilterSpec.Copy() spec.ByteOrder = binary.BigEndian if internal.NativeEndian == binary.BigEndian { spec.ByteOrder = binary.LittleEndian } _, err := NewProgram(spec) if err == nil { t.Error("Incorrect ByteOrder should be rejected at load time") } } func TestProgramSpecTag(t *testing.T) { arr := createArray(t) defer arr.Close() spec := &ProgramSpec{ Type: SocketFilter, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, -1, asm.DWord), asm.LoadMapPtr(asm.R1, arr.FD()), asm.Mov.Imm32(asm.R0, 0), asm.Return(), }, License: "MIT", } prog, err := NewProgram(spec) if err != nil { t.Fatal(err) } defer prog.Close() info, err := prog.Info() testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal(err) } tag, err := spec.Tag() if err != nil { t.Fatal("Can't calculate tag:", err) } if tag != info.Tag { t.Errorf("Calculated tag %s doesn't match kernel tag %s", tag, info.Tag) } } func TestProgramAttachToKernel(t *testing.T) { // See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744 testutils.SkipOnOldKernel(t, "5.5", "attach_btf_id") haveTestmod := haveTestmod(t) tests := []struct { attachTo string programType ProgramType attachType AttachType flags uint32 }{ { attachTo: "task_getpgid", programType: LSM, attachType: AttachLSMMac, }, { attachTo: "inet_dgram_connect", programType: Tracing, attachType: AttachTraceFEntry, }, { attachTo: "inet_dgram_connect", programType: Tracing, attachType: AttachTraceFExit, }, { attachTo: "bpf_modify_return_test", programType: Tracing, attachType: AttachModifyReturn, }, { attachTo: "kfree_skb", programType: Tracing, attachType: AttachTraceRawTp, }, { attachTo: "bpf_testmod_test_read", programType: Tracing, attachType: AttachTraceFEntry, }, { attachTo: "bpf_testmod_test_read", programType: Tracing, attachType: AttachTraceFExit, }, { attachTo: "bpf_testmod_test_read", programType: Tracing, attachType: AttachModifyReturn, }, { attachTo: "bpf_testmod_test_read", programType: Tracing, attachType: AttachTraceRawTp, }, } for _, test := range tests { name := fmt.Sprintf("%s:%s", test.attachType, test.attachTo) t.Run(name, func(t *testing.T) { if strings.HasPrefix(test.attachTo, "bpf_testmod_") && !haveTestmod { t.Skip("bpf_testmod not loaded") } prog, err := NewProgram(&ProgramSpec{ AttachTo: test.attachTo, AttachType: test.attachType, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, 0, asm.DWord), asm.Return(), }, License: "GPL", Type: test.programType, Flags: test.flags, }) if err != nil { t.Fatal("Can't load program:", err) } prog.Close() }) } } func TestProgramKernelTypes(t *testing.T) { if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) { t.Skip("/sys/kernel/btf/vmlinux not present") } btfSpec, err := btf.LoadSpec("/sys/kernel/btf/vmlinux") if err != nil { t.Fatal(err) } prog, err := NewProgramWithOptions(&ProgramSpec{ Type: Tracing, AttachType: AttachTraceIter, AttachTo: "bpf_map", Instructions: asm.Instructions{ asm.Mov.Imm(asm.R0, 0), asm.Return(), }, License: "MIT", }, ProgramOptions{ KernelTypes: btfSpec, }) testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal("NewProgram with Target:", err) } prog.Close() } func TestProgramBindMap(t *testing.T) { testutils.SkipOnOldKernel(t, "5.10", "BPF_PROG_BIND_MAP") arr, err := NewMap(&MapSpec{ Type: Array, KeySize: 4, ValueSize: 4, MaxEntries: 1, }) if err != nil { t.Errorf("Failed to load map: %v", err) } defer arr.Close() prog := mustSocketFilter(t) // The attached map does not contain BTF information. So // the metadata part of the program will be empty. This // test just makes sure that we can bind a map to a program. if err := prog.BindMap(arr); err != nil { t.Errorf("Failed to bind map to program: %v", err) } } func TestProgramInstructions(t *testing.T) { name := "test_prog" spec := &ProgramSpec{ Type: SocketFilter, Name: name, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, -1, asm.DWord).WithSymbol(name), asm.Return(), }, License: "MIT", } prog, err := NewProgram(spec) if err != nil { t.Fatal(err) } defer prog.Close() pi, err := prog.Info() testutils.SkipIfNotSupported(t, err) if err != nil { t.Fatal(err) } insns, err := pi.Instructions() if err != nil { t.Fatal(err) } tag, err := spec.Tag() if err != nil { t.Fatal(err) } tagXlated, err := insns.Tag(internal.NativeEndian) if err != nil { t.Fatal(err) } if tag != tagXlated { t.Fatalf("tag %s differs from xlated instructions tag %s", tag, tagXlated) } } func createProgramArray(t *testing.T) *Map { t.Helper() arr, err := NewMap(&MapSpec{ Type: ProgramArray, KeySize: 4, ValueSize: 4, MaxEntries: 1, }) if err != nil { t.Fatal(err) } return arr } var socketFilterSpec = &ProgramSpec{ Name: "test", Type: SocketFilter, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, 2, asm.DWord), asm.Return(), }, License: "MIT", } func mustSocketFilter(tb testing.TB) *Program { tb.Helper() prog, err := NewProgram(socketFilterSpec) if err != nil { tb.Fatal(err) } tb.Cleanup(func() { prog.Close() }) return prog } // Print the full verifier log when loading a program fails. func ExampleVerifierError_retrieveFullLog() { _, err := NewProgram(&ProgramSpec{ Type: SocketFilter, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, 0, asm.DWord), // Missing Return }, License: "MIT", }) var ve *VerifierError if errors.As(err, &ve) { // Using %+v will print the whole verifier error, not just the last // few lines. fmt.Printf("Verifier error: %+v\n", ve) } } // VerifierLog understands a variety of formatting flags. func ExampleVerifierError() { err := internal.ErrorWithLog( "catastrophe", syscall.ENOSPC, []byte("first\nsecond\nthird"), false, ) fmt.Printf("With %%s: %s\n", err) err.Truncated = true fmt.Printf("With %%v and a truncated log: %v\n", err) fmt.Printf("All log lines: %+v\n", err) fmt.Printf("First line: %+1v\n", err) fmt.Printf("Last two lines: %-2v\n", err) // Output: With %s: catastrophe: no space left on device: third (2 line(s) omitted) // With %v and a truncated log: catastrophe: no space left on device: second: third (truncated, 1 line(s) omitted) // All log lines: catastrophe: no space left on device: // first // second // third // (truncated) // First line: catastrophe: no space left on device: // first // (2 line(s) omitted) // (truncated) // Last two lines: catastrophe: no space left on device: // (1 line(s) omitted) // second // third // (truncated) } // Use NewProgramWithOptions if you'd like to get the verifier output // for a program, or if you want to change the buffer size used when // generating error messages. func ExampleProgram_retrieveVerifierLog() { spec := &ProgramSpec{ Type: SocketFilter, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, 0, asm.DWord), asm.Return(), }, License: "MIT", } prog, err := NewProgramWithOptions(spec, ProgramOptions{ LogLevel: LogLevelInstruction, LogSize: 1024, }) if err != nil { panic(err) } defer prog.Close() fmt.Println("The verifier output is:") fmt.Println(prog.VerifierLog) } // It's possible to read a program directly from a ProgramArray. func ExampleProgram_unmarshalFromMap() { progArray, err := LoadPinnedMap("/path/to/map", nil) if err != nil { panic(err) } defer progArray.Close() // Load a single program var prog *Program if err := progArray.Lookup(uint32(0), &prog); err != nil { panic(err) } defer prog.Close() fmt.Println("first prog:", prog) // Iterate all programs var ( key uint32 entries = progArray.Iterate() ) for entries.Next(&key, &prog) { fmt.Println(key, "is", prog) } if err := entries.Err(); err != nil { panic(err) } } func ExampleProgramSpec_Tag() { spec := &ProgramSpec{ Type: SocketFilter, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, 0, asm.DWord), asm.Return(), }, License: "MIT", } prog, _ := NewProgram(spec) info, _ := prog.Info() tag, _ := spec.Tag() if info.Tag != tag { fmt.Printf("The tags don't match: %s != %s\n", info.Tag, tag) } else { fmt.Println("The programs are identical, tag is", tag) } } func dupFD(tb testing.TB, fd int) int { tb.Helper() dup, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 1) if err != nil { tb.Fatal("Can't dup fd:", err) } return dup }