github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/link/link_test.go (about)

     1  package link
     2  
     3  import (
     4  	"errors"
     5  	"math"
     6  	"os"
     7  	"path/filepath"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/cilium/ebpf"
    12  	"github.com/cilium/ebpf/asm"
    13  	"github.com/cilium/ebpf/internal/sys"
    14  	"github.com/cilium/ebpf/internal/testutils"
    15  	"github.com/cilium/ebpf/internal/testutils/fdtrace"
    16  	"github.com/cilium/ebpf/internal/unix"
    17  
    18  	"github.com/go-quicktest/qt"
    19  )
    20  
    21  func TestMain(m *testing.M) {
    22  	fdtrace.TestMain(m)
    23  }
    24  
    25  func TestRawLink(t *testing.T) {
    26  	cgroup, prog := mustCgroupFixtures(t)
    27  
    28  	link, err := AttachRawLink(RawLinkOptions{
    29  		Target:  int(cgroup.Fd()),
    30  		Program: prog,
    31  		Attach:  ebpf.AttachCGroupInetEgress,
    32  	})
    33  	testutils.SkipIfNotSupported(t, err)
    34  	if err != nil {
    35  		t.Fatal("Can't create raw link:", err)
    36  	}
    37  
    38  	info, err := link.Info()
    39  	if err != nil {
    40  		t.Fatal("Can't get link info:", err)
    41  	}
    42  
    43  	pi, err := prog.Info()
    44  	if err != nil {
    45  		t.Fatal("Can't get program info:", err)
    46  	}
    47  
    48  	progID, ok := pi.ID()
    49  	if !ok {
    50  		t.Fatal("Program ID not available in program info")
    51  	}
    52  
    53  	if info.Program != progID {
    54  		t.Error("Link program ID doesn't match program ID")
    55  	}
    56  
    57  	testLink(t, &linkCgroup{*link}, prog)
    58  }
    59  
    60  func TestUnpinRawLink(t *testing.T) {
    61  	cgroup, prog := mustCgroupFixtures(t)
    62  	link, _ := newPinnedRawLink(t, cgroup, prog)
    63  	defer link.Close()
    64  
    65  	qt.Assert(t, qt.IsTrue(link.IsPinned()))
    66  
    67  	if err := link.Unpin(); err != nil {
    68  		t.Fatal(err)
    69  	}
    70  
    71  	qt.Assert(t, qt.IsFalse(link.IsPinned()))
    72  }
    73  
    74  func TestRawLinkLoadPinnedWithOptions(t *testing.T) {
    75  	cgroup, prog := mustCgroupFixtures(t)
    76  	link, path := newPinnedRawLink(t, cgroup, prog)
    77  	defer link.Close()
    78  
    79  	qt.Assert(t, qt.IsTrue(link.IsPinned()))
    80  
    81  	// It seems like the kernel ignores BPF_F_RDONLY when updating a link,
    82  	// so we can't test this.
    83  	_, err := loadPinnedRawLink(path, &ebpf.LoadPinOptions{
    84  		Flags: math.MaxUint32,
    85  	})
    86  	if !errors.Is(err, unix.EINVAL) {
    87  		t.Fatal("Invalid flags don't trigger an error:", err)
    88  	}
    89  }
    90  
    91  func TestIterator(t *testing.T) {
    92  	cgroup, prog := mustCgroupFixtures(t)
    93  
    94  	tLink, err := AttachRawLink(RawLinkOptions{
    95  		Target:  int(cgroup.Fd()),
    96  		Program: prog,
    97  		Attach:  ebpf.AttachCGroupInetEgress,
    98  	})
    99  	testutils.SkipIfNotSupported(t, err)
   100  	if err != nil {
   101  		t.Fatal("Can't create original raw link:", err)
   102  	}
   103  	defer tLink.Close()
   104  	tLinkInfo, err := tLink.Info()
   105  	testutils.SkipIfNotSupported(t, err)
   106  	if err != nil {
   107  		t.Fatal("Can't get original link info:", err)
   108  	}
   109  
   110  	it := new(Iterator)
   111  	defer it.Close()
   112  
   113  	prev := it.ID
   114  	var foundLink Link
   115  	for it.Next() {
   116  		// Iterate all loaded links.
   117  		if it.Link == nil {
   118  			t.Fatal("Next doesn't assign link")
   119  		}
   120  		if it.ID == prev {
   121  			t.Fatal("Iterator doesn't advance ID")
   122  		}
   123  		prev = it.ID
   124  		if it.ID == tLinkInfo.ID {
   125  			foundLink = it.Take()
   126  		}
   127  	}
   128  	if err := it.Err(); err != nil {
   129  		t.Fatal("Iteration returned an error:", err)
   130  	}
   131  	if it.Link != nil {
   132  		t.Fatal("Next doesn't clean up link on last iteration")
   133  	}
   134  	if prev != it.ID {
   135  		t.Fatal("Next changes ID on last iteration")
   136  	}
   137  	if foundLink == nil {
   138  		t.Fatal("Original link not found")
   139  	}
   140  	defer foundLink.Close()
   141  	// Confirm that we found the original link.
   142  	info, err := foundLink.Info()
   143  	if err != nil {
   144  		t.Fatal("Can't get link info:", err)
   145  	}
   146  	if info.ID != tLinkInfo.ID {
   147  		t.Fatal("Found link has wrong ID")
   148  	}
   149  
   150  }
   151  
   152  func newPinnedRawLink(t *testing.T, cgroup *os.File, prog *ebpf.Program) (*RawLink, string) {
   153  	t.Helper()
   154  
   155  	link, err := AttachRawLink(RawLinkOptions{
   156  		Target:  int(cgroup.Fd()),
   157  		Program: prog,
   158  		Attach:  ebpf.AttachCGroupInetEgress,
   159  	})
   160  	testutils.SkipIfNotSupported(t, err)
   161  	if err != nil {
   162  		t.Fatal("Can't create raw link:", err)
   163  	}
   164  
   165  	path := filepath.Join(testutils.TempBPFFS(t), "link")
   166  	err = link.Pin(path)
   167  	testutils.SkipIfNotSupported(t, err)
   168  	if err != nil {
   169  		t.Fatal(err)
   170  	}
   171  
   172  	return link, path
   173  }
   174  
   175  func mustCgroupFixtures(t *testing.T) (*os.File, *ebpf.Program) {
   176  	t.Helper()
   177  
   178  	testutils.SkipIfNotSupported(t, haveProgAttach())
   179  
   180  	return testutils.CreateCgroup(t), mustLoadProgram(t, ebpf.CGroupSKB, 0, "")
   181  }
   182  
   183  func testLink(t *testing.T, link Link, prog *ebpf.Program) {
   184  	t.Helper()
   185  
   186  	tmp, err := os.MkdirTemp("/sys/fs/bpf", "ebpf-test")
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	defer os.RemoveAll(tmp)
   191  
   192  	t.Run("link/pinning", func(t *testing.T) {
   193  		path := filepath.Join(tmp, "link")
   194  		err = link.Pin(path)
   195  		testutils.SkipIfNotSupported(t, err)
   196  		if err != nil {
   197  			t.Fatalf("Can't pin %T: %s", link, err)
   198  		}
   199  
   200  		link2, err := LoadPinnedLink(path, nil)
   201  		if err != nil {
   202  			t.Fatalf("Can't load pinned %T: %s", link, err)
   203  		}
   204  		link2.Close()
   205  
   206  		if reflect.TypeOf(link) != reflect.TypeOf(link2) {
   207  			t.Errorf("Loading a pinned %T returns a %T", link, link2)
   208  		}
   209  
   210  		_, err = LoadPinnedLink(path, &ebpf.LoadPinOptions{
   211  			Flags: math.MaxUint32,
   212  		})
   213  		if !errors.Is(err, unix.EINVAL) {
   214  			t.Errorf("Loading a pinned %T doesn't respect flags", link)
   215  		}
   216  	})
   217  
   218  	t.Run("link/update", func(t *testing.T) {
   219  		err := link.Update(prog)
   220  		testutils.SkipIfNotSupported(t, err)
   221  		if err != nil {
   222  			t.Fatal("Update returns an error:", err)
   223  		}
   224  
   225  		func() {
   226  			// Panicking is OK
   227  			defer func() {
   228  				_ = recover()
   229  			}()
   230  
   231  			if err := link.Update(nil); err == nil {
   232  				t.Fatalf("%T.Update accepts nil program", link)
   233  			}
   234  		}()
   235  	})
   236  
   237  	t.Run("link/info", func(t *testing.T) {
   238  		info, err := link.Info()
   239  		testutils.SkipIfNotSupported(t, err)
   240  		if err != nil {
   241  			t.Fatal("Link info returns an error:", err)
   242  		}
   243  
   244  		if info.Type == 0 {
   245  			t.Fatal("Failed to get link info type")
   246  		}
   247  
   248  		switch info.Type {
   249  		case sys.BPF_LINK_TYPE_TRACING:
   250  			if info.Tracing() == nil {
   251  				t.Fatalf("Failed to get link tracing extra info")
   252  			}
   253  		case sys.BPF_LINK_TYPE_CGROUP:
   254  			cg := info.Cgroup()
   255  			if cg.CgroupId == 0 {
   256  				t.Fatalf("Failed to get link Cgroup extra info")
   257  			}
   258  		case sys.BPF_LINK_TYPE_NETNS:
   259  			netns := info.NetNs()
   260  			if netns.AttachType == 0 {
   261  				t.Fatalf("Failed to get link NetNs extra info")
   262  			}
   263  		case sys.BPF_LINK_TYPE_XDP:
   264  			xdp := info.XDP()
   265  			if xdp.Ifindex == 0 {
   266  				t.Fatalf("Failed to get link XDP extra info")
   267  			}
   268  		case sys.BPF_LINK_TYPE_TCX:
   269  			tcx := info.TCX()
   270  			if tcx.Ifindex == 0 {
   271  				t.Fatalf("Failed to get link TCX extra info")
   272  			}
   273  		case sys.BPF_LINK_TYPE_NETFILTER:
   274  			nf := info.Netfilter()
   275  			if nf.Priority == 0 {
   276  				t.Fatalf("Failed to get link Netfilter extra info")
   277  			}
   278  		case sys.BPF_LINK_TYPE_KPROBE_MULTI:
   279  			// test default Info data
   280  			kmulti := info.KprobeMulti()
   281  			if count, ok := kmulti.AddressCount(); ok {
   282  				qt.Assert(t, qt.Not(qt.Equals(count, 0)))
   283  
   284  				_, ok = kmulti.Missed()
   285  				qt.Assert(t, qt.IsTrue(ok))
   286  				// NB: We don't check that missed is actually correct
   287  				// since it's not easy to trigger from tests.
   288  			}
   289  		case sys.BPF_LINK_TYPE_PERF_EVENT:
   290  			// test default Info data
   291  			pevent := info.PerfEvent()
   292  			switch pevent.Type {
   293  			case sys.BPF_PERF_EVENT_KPROBE, sys.BPF_PERF_EVENT_KRETPROBE:
   294  				kp := pevent.Kprobe()
   295  				if addr, ok := kp.Address(); ok {
   296  					qt.Assert(t, qt.Not(qt.Equals(addr, 0)))
   297  
   298  					_, ok := kp.Missed()
   299  					qt.Assert(t, qt.IsTrue(ok))
   300  					// NB: We don't check that missed is actually correct
   301  					// since it's not easy to trigger from tests.
   302  				}
   303  			}
   304  		}
   305  	})
   306  
   307  	type FDer interface {
   308  		FD() int
   309  	}
   310  
   311  	t.Run("from fd", func(t *testing.T) {
   312  		fder, ok := link.(FDer)
   313  		if !ok {
   314  			t.Skip("Link doesn't allow retrieving FD")
   315  		}
   316  
   317  		// We need to dup the FD since NewLinkFromFD takes
   318  		// ownership.
   319  		dupFD, err := unix.FcntlInt(uintptr(fder.FD()), unix.F_DUPFD_CLOEXEC, 1)
   320  		if err != nil {
   321  			t.Fatal("Can't dup link FD:", err)
   322  		}
   323  		defer unix.Close(dupFD)
   324  
   325  		newLink, err := NewFromFD(dupFD)
   326  		testutils.SkipIfNotSupported(t, err)
   327  		if err != nil {
   328  			t.Fatal("Can't create new link from dup link FD:", err)
   329  		}
   330  		defer newLink.Close()
   331  
   332  		if reflect.TypeOf(newLink) != reflect.TypeOf(link) {
   333  			t.Fatalf("Expected type %T, got %T", link, newLink)
   334  		}
   335  	})
   336  
   337  	if err := link.Close(); err != nil {
   338  		t.Fatalf("%T.Close returns an error: %s", link, err)
   339  	}
   340  }
   341  
   342  func mustLoadProgram(tb testing.TB, typ ebpf.ProgramType, attachType ebpf.AttachType, attachTo string) *ebpf.Program {
   343  	tb.Helper()
   344  
   345  	license := "MIT"
   346  	switch typ {
   347  	case ebpf.RawTracepoint, ebpf.LSM:
   348  		license = "GPL"
   349  	}
   350  
   351  	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
   352  		Type:       typ,
   353  		AttachType: attachType,
   354  		AttachTo:   attachTo,
   355  		License:    license,
   356  		Instructions: asm.Instructions{
   357  			asm.Mov.Imm(asm.R0, 0),
   358  			asm.Return(),
   359  		},
   360  	})
   361  	if err != nil {
   362  		tb.Fatal(err)
   363  	}
   364  
   365  	tb.Cleanup(func() {
   366  		prog.Close()
   367  	})
   368  
   369  	return prog
   370  }