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  }