...

Source file src/github.com/cilium/ebpf/collection_test.go

Documentation: github.com/cilium/ebpf

     1  package ebpf
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"github.com/cilium/ebpf/asm"
    10  	"github.com/cilium/ebpf/btf"
    11  	"github.com/cilium/ebpf/internal"
    12  	"github.com/cilium/ebpf/internal/testutils"
    13  )
    14  
    15  func TestCollectionSpecNotModified(t *testing.T) {
    16  	cs := CollectionSpec{
    17  		Maps: map[string]*MapSpec{
    18  			"my-map": {
    19  				Type:       Array,
    20  				KeySize:    4,
    21  				ValueSize:  4,
    22  				MaxEntries: 1,
    23  			},
    24  		},
    25  		Programs: map[string]*ProgramSpec{
    26  			"test": {
    27  				Type: SocketFilter,
    28  				Instructions: asm.Instructions{
    29  					asm.LoadImm(asm.R1, 0, asm.DWord).WithReference("my-map"),
    30  					asm.LoadImm(asm.R0, 0, asm.DWord),
    31  					asm.Return(),
    32  				},
    33  				License: "MIT",
    34  			},
    35  		},
    36  	}
    37  
    38  	coll, err := NewCollection(&cs)
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	coll.Close()
    43  
    44  	if cs.Programs["test"].Instructions[0].Constant != 0 {
    45  		t.Error("Creating a collection modifies input spec")
    46  	}
    47  }
    48  
    49  func TestCollectionSpecCopy(t *testing.T) {
    50  	cs := &CollectionSpec{
    51  		Maps: map[string]*MapSpec{
    52  			"my-map": {
    53  				Type:       Array,
    54  				KeySize:    4,
    55  				ValueSize:  4,
    56  				MaxEntries: 1,
    57  			},
    58  		},
    59  		Programs: map[string]*ProgramSpec{
    60  			"test": {
    61  				Type: SocketFilter,
    62  				Instructions: asm.Instructions{
    63  					asm.LoadMapPtr(asm.R1, 0),
    64  					asm.LoadImm(asm.R0, 0, asm.DWord),
    65  					asm.Return(),
    66  				},
    67  				License: "MIT",
    68  			},
    69  		},
    70  		Types: &btf.Spec{},
    71  	}
    72  	cpy := cs.Copy()
    73  
    74  	if cpy == cs {
    75  		t.Error("Copy returned the same pointner")
    76  	}
    77  
    78  	if cpy.Maps["my-map"] == cs.Maps["my-map"] {
    79  		t.Error("Copy returned same Maps")
    80  	}
    81  
    82  	if cpy.Programs["test"] == cs.Programs["test"] {
    83  		t.Error("Copy returned same Programs")
    84  	}
    85  
    86  	if cpy.Types != cs.Types {
    87  		t.Error("Copy returned different Types")
    88  	}
    89  }
    90  
    91  func TestCollectionSpecLoadCopy(t *testing.T) {
    92  	file := fmt.Sprintf("testdata/loader-%s.elf", internal.ClangEndian)
    93  	spec, err := LoadCollectionSpec(file)
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  
    98  	spec2 := spec.Copy()
    99  
   100  	var objs struct {
   101  		Prog *Program `ebpf:"xdp_prog"`
   102  	}
   103  
   104  	err = spec.LoadAndAssign(&objs, nil)
   105  	testutils.SkipIfNotSupported(t, err)
   106  	if err != nil {
   107  		t.Fatal("Loading original spec:", err)
   108  	}
   109  	defer objs.Prog.Close()
   110  
   111  	if err := spec2.LoadAndAssign(&objs, nil); err != nil {
   112  		t.Fatal("Loading copied spec:", err)
   113  	}
   114  	defer objs.Prog.Close()
   115  }
   116  
   117  func TestCollectionSpecRewriteMaps(t *testing.T) {
   118  	insns := asm.Instructions{
   119  		// R1 map
   120  		asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"),
   121  		// R2 key
   122  		asm.Mov.Reg(asm.R2, asm.R10),
   123  		asm.Add.Imm(asm.R2, -4),
   124  		asm.StoreImm(asm.R2, 0, 0, asm.Word),
   125  		// Lookup map[0]
   126  		asm.FnMapLookupElem.Call(),
   127  		asm.JEq.Imm(asm.R0, 0, "ret"),
   128  		asm.LoadMem(asm.R0, asm.R0, 0, asm.Word),
   129  		asm.Return().WithSymbol("ret"),
   130  	}
   131  
   132  	cs := &CollectionSpec{
   133  		Maps: map[string]*MapSpec{
   134  			"test-map": {
   135  				Type:       Array,
   136  				KeySize:    4,
   137  				ValueSize:  4,
   138  				MaxEntries: 1,
   139  			},
   140  		},
   141  		Programs: map[string]*ProgramSpec{
   142  			"test-prog": {
   143  				Type:         SocketFilter,
   144  				Instructions: insns,
   145  				License:      "MIT",
   146  			},
   147  		},
   148  	}
   149  
   150  	// Override the map with another one
   151  	newMap, err := NewMap(cs.Maps["test-map"])
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  	defer newMap.Close()
   156  
   157  	err = newMap.Put(uint32(0), uint32(2))
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  
   162  	err = cs.RewriteMaps(map[string]*Map{
   163  		"test-map": newMap,
   164  	})
   165  	if err != nil {
   166  		t.Fatal(err)
   167  	}
   168  
   169  	if cs.Maps["test-map"] != nil {
   170  		t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps")
   171  	}
   172  
   173  	coll, err := NewCollection(cs)
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	defer coll.Close()
   178  
   179  	ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14))
   180  	testutils.SkipIfNotSupported(t, err)
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  
   185  	if ret != 2 {
   186  		t.Fatal("new / override map not used")
   187  	}
   188  }
   189  
   190  func TestCollectionSpecMapReplacements(t *testing.T) {
   191  	insns := asm.Instructions{
   192  		// R1 map
   193  		asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"),
   194  		// R2 key
   195  		asm.Mov.Reg(asm.R2, asm.R10),
   196  		asm.Add.Imm(asm.R2, -4),
   197  		asm.StoreImm(asm.R2, 0, 0, asm.Word),
   198  		// Lookup map[0]
   199  		asm.FnMapLookupElem.Call(),
   200  		asm.JEq.Imm(asm.R0, 0, "ret"),
   201  		asm.LoadMem(asm.R0, asm.R0, 0, asm.Word),
   202  		asm.Return().WithSymbol("ret"),
   203  	}
   204  
   205  	cs := &CollectionSpec{
   206  		Maps: map[string]*MapSpec{
   207  			"test-map": {
   208  				Type:       Array,
   209  				KeySize:    4,
   210  				ValueSize:  4,
   211  				MaxEntries: 1,
   212  			},
   213  		},
   214  		Programs: map[string]*ProgramSpec{
   215  			"test-prog": {
   216  				Type:         SocketFilter,
   217  				Instructions: insns,
   218  				License:      "MIT",
   219  			},
   220  		},
   221  	}
   222  
   223  	// Replace the map with another one
   224  	newMap, err := NewMap(cs.Maps["test-map"])
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	defer newMap.Close()
   229  
   230  	err = newMap.Put(uint32(0), uint32(2))
   231  	if err != nil {
   232  		t.Fatal(err)
   233  	}
   234  
   235  	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
   236  		MapReplacements: map[string]*Map{
   237  			"test-map": newMap,
   238  		},
   239  	})
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  	defer coll.Close()
   244  
   245  	ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14))
   246  	testutils.SkipIfNotSupported(t, err)
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  
   251  	if ret != 2 {
   252  		t.Fatal("new / override map not used")
   253  	}
   254  
   255  	// Check that newMap isn't closed when the collection is closed
   256  	coll.Close()
   257  	err = newMap.Put(uint32(0), uint32(3))
   258  	if err != nil {
   259  		t.Fatalf("failed to update replaced map: %s", err)
   260  	}
   261  }
   262  func TestCollectionSpecMapReplacements_NonExistingMap(t *testing.T) {
   263  	cs := &CollectionSpec{
   264  		Maps: map[string]*MapSpec{
   265  			"test-map": {
   266  				Type:       Array,
   267  				KeySize:    4,
   268  				ValueSize:  4,
   269  				MaxEntries: 1,
   270  			},
   271  		},
   272  	}
   273  
   274  	// Override non-existing map
   275  	newMap, err := NewMap(cs.Maps["test-map"])
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	defer newMap.Close()
   280  
   281  	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
   282  		MapReplacements: map[string]*Map{
   283  			"non-existing-map": newMap,
   284  		},
   285  	})
   286  	if err == nil {
   287  		coll.Close()
   288  		t.Fatal("Overriding a non existing map did not fail")
   289  	}
   290  }
   291  
   292  func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) {
   293  	cs := &CollectionSpec{
   294  		Maps: map[string]*MapSpec{
   295  			"test-map": {
   296  				Type:       Array,
   297  				KeySize:    4,
   298  				ValueSize:  4,
   299  				MaxEntries: 1,
   300  			},
   301  		},
   302  	}
   303  
   304  	// Override map with mismatching spec
   305  	newMap, err := NewMap(&MapSpec{
   306  		Type:       Array,
   307  		KeySize:    4,
   308  		ValueSize:  8, // this is different
   309  		MaxEntries: 1,
   310  	})
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	// Map fd is duplicated by MapReplacements, this one can be safely closed.
   315  	defer newMap.Close()
   316  
   317  	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
   318  		MapReplacements: map[string]*Map{
   319  			"test-map": newMap,
   320  		},
   321  	})
   322  	if err == nil {
   323  		coll.Close()
   324  		t.Fatal("Overriding a map with a mismatching spec did not fail")
   325  	}
   326  	if !errors.Is(err, ErrMapIncompatible) {
   327  		t.Fatalf("Overriding a map with a mismatching spec failed with the wrong error")
   328  	}
   329  }
   330  
   331  func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {
   332  	spec := &CollectionSpec{
   333  		Maps: map[string]*MapSpec{
   334  			"valid": {
   335  				Type:       Array,
   336  				KeySize:    4,
   337  				ValueSize:  4,
   338  				MaxEntries: 1,
   339  			},
   340  			"bogus": {
   341  				Type:       Array,
   342  				MaxEntries: 0,
   343  			},
   344  		},
   345  		Programs: map[string]*ProgramSpec{
   346  			"valid": {
   347  				Type: SocketFilter,
   348  				Instructions: asm.Instructions{
   349  					asm.LoadImm(asm.R0, 0, asm.DWord),
   350  					asm.Return(),
   351  				},
   352  				License: "MIT",
   353  			},
   354  			"bogus": {
   355  				Type: SocketFilter,
   356  				Instructions: asm.Instructions{
   357  					// Undefined return value is rejected
   358  					asm.Return(),
   359  				},
   360  				License: "MIT",
   361  			},
   362  		},
   363  	}
   364  
   365  	var objs struct {
   366  		Prog *Program `ebpf:"valid"`
   367  		Map  *Map     `ebpf:"valid"`
   368  	}
   369  
   370  	if err := spec.LoadAndAssign(&objs, nil); err != nil {
   371  		t.Fatal("Assign loads a map or program that isn't requested in the struct:", err)
   372  	}
   373  	defer objs.Prog.Close()
   374  	defer objs.Map.Close()
   375  
   376  	if objs.Prog == nil {
   377  		t.Error("Program is nil")
   378  	}
   379  
   380  	if objs.Map == nil {
   381  		t.Error("Map is nil")
   382  	}
   383  }
   384  
   385  func TestCollectionAssign(t *testing.T) {
   386  	var specs struct {
   387  		Program *ProgramSpec `ebpf:"prog1"`
   388  		Map     *MapSpec     `ebpf:"map1"`
   389  	}
   390  
   391  	mapSpec := &MapSpec{
   392  		Type:       Array,
   393  		KeySize:    4,
   394  		ValueSize:  4,
   395  		MaxEntries: 1,
   396  	}
   397  	progSpec := &ProgramSpec{
   398  		Type: SocketFilter,
   399  		Instructions: asm.Instructions{
   400  			asm.LoadImm(asm.R0, 0, asm.DWord),
   401  			asm.Return(),
   402  		},
   403  		License: "MIT",
   404  	}
   405  
   406  	cs := &CollectionSpec{
   407  		Maps: map[string]*MapSpec{
   408  			"map1": mapSpec,
   409  		},
   410  		Programs: map[string]*ProgramSpec{
   411  			"prog1": progSpec,
   412  		},
   413  	}
   414  
   415  	if err := cs.Assign(&specs); err != nil {
   416  		t.Fatal("Can't assign spec:", err)
   417  	}
   418  
   419  	if specs.Program != progSpec {
   420  		t.Fatalf("Expected Program to be %p, got %p", progSpec, specs.Program)
   421  	}
   422  
   423  	if specs.Map != mapSpec {
   424  		t.Fatalf("Expected Map to be %p, got %p", mapSpec, specs.Map)
   425  	}
   426  
   427  	if err := cs.Assign(new(int)); err == nil {
   428  		t.Fatal("Assign allows to besides *struct")
   429  	}
   430  
   431  	if err := cs.Assign(new(struct{ Foo int })); err != nil {
   432  		t.Fatal("Assign doesn't ignore untagged fields")
   433  	}
   434  
   435  	unexported := new(struct {
   436  		foo *MapSpec `ebpf:"map1"`
   437  	})
   438  
   439  	if err := cs.Assign(unexported); err == nil {
   440  		t.Error("Assign should return an error on unexported fields")
   441  	}
   442  }
   443  
   444  func TestAssignValues(t *testing.T) {
   445  	zero := func(t reflect.Type, name string) (interface{}, error) {
   446  		return reflect.Zero(t).Interface(), nil
   447  	}
   448  
   449  	type t1 struct {
   450  		Bar int `ebpf:"bar"`
   451  	}
   452  
   453  	type t2 struct {
   454  		t1
   455  		Foo int `ebpf:"foo"`
   456  	}
   457  
   458  	type t2ptr struct {
   459  		*t1
   460  		Foo int `ebpf:"foo"`
   461  	}
   462  
   463  	invalid := []struct {
   464  		name string
   465  		to   interface{}
   466  	}{
   467  		{"non-struct", 1},
   468  		{"non-pointer struct", t1{}},
   469  		{"pointer to non-struct", new(int)},
   470  		{"embedded nil pointer", &t2ptr{}},
   471  		{"unexported field", new(struct {
   472  			foo int `ebpf:"foo"`
   473  		})},
   474  		{"identical tag", new(struct {
   475  			Foo1 int `ebpf:"foo"`
   476  			Foo2 int `ebpf:"foo"`
   477  		})},
   478  	}
   479  
   480  	for _, testcase := range invalid {
   481  		t.Run(testcase.name, func(t *testing.T) {
   482  			if err := assignValues(testcase.to, zero); err == nil {
   483  				t.Fatal("assignValues didn't return an error")
   484  			} else {
   485  				t.Log(err)
   486  			}
   487  		})
   488  	}
   489  
   490  	valid := []struct {
   491  		name string
   492  		to   interface{}
   493  	}{
   494  		{"pointer to struct", new(t1)},
   495  		{"embedded struct", new(t2)},
   496  		{"embedded struct pointer", &t2ptr{t1: new(t1)}},
   497  		{"untagged field", new(struct{ Foo int })},
   498  	}
   499  
   500  	for _, testcase := range valid {
   501  		t.Run(testcase.name, func(t *testing.T) {
   502  			if err := assignValues(testcase.to, zero); err != nil {
   503  				t.Fatal("assignValues returned", err)
   504  			}
   505  		})
   506  	}
   507  
   508  }
   509  
   510  func TestIncompleteLoadAndAssign(t *testing.T) {
   511  	spec := &CollectionSpec{
   512  		Programs: map[string]*ProgramSpec{
   513  			"valid": {
   514  				Type: SocketFilter,
   515  				Instructions: asm.Instructions{
   516  					asm.LoadImm(asm.R0, 0, asm.DWord),
   517  					asm.Return(),
   518  				},
   519  				License: "MIT",
   520  			},
   521  			"invalid": {
   522  				Type: SocketFilter,
   523  				Instructions: asm.Instructions{
   524  					asm.Return(),
   525  				},
   526  				License: "MIT",
   527  			},
   528  		},
   529  	}
   530  
   531  	s := struct {
   532  		// Assignment to Valid should execute and succeed.
   533  		Valid *Program `ebpf:"valid"`
   534  		// Assignment to Invalid should fail and cause Valid's fd to be closed.
   535  		Invalid *Program `ebpf:"invalid"`
   536  	}{}
   537  
   538  	if err := spec.LoadAndAssign(&s, nil); err == nil {
   539  		t.Fatal("expected error loading invalid ProgramSpec")
   540  	}
   541  
   542  	if fd := s.Valid.FD(); fd != -1 {
   543  		t.Fatal("expected valid prog to have closed fd -1, got:", fd)
   544  	}
   545  
   546  	if s.Invalid != nil {
   547  		t.Fatal("expected invalid prog to be nil due to never being assigned")
   548  	}
   549  }
   550  
   551  func ExampleCollectionSpec_Assign() {
   552  	spec := &CollectionSpec{
   553  		Maps: map[string]*MapSpec{
   554  			"map1": {
   555  				Type:       Array,
   556  				KeySize:    4,
   557  				ValueSize:  4,
   558  				MaxEntries: 1,
   559  			},
   560  		},
   561  		Programs: map[string]*ProgramSpec{
   562  			"prog1": {
   563  				Type: SocketFilter,
   564  				Instructions: asm.Instructions{
   565  					asm.LoadImm(asm.R0, 0, asm.DWord),
   566  					asm.Return(),
   567  				},
   568  				License: "MIT",
   569  			},
   570  		},
   571  	}
   572  
   573  	type maps struct {
   574  		Map *MapSpec `ebpf:"map1"`
   575  	}
   576  
   577  	var specs struct {
   578  		maps
   579  		Program *ProgramSpec `ebpf:"prog1"`
   580  	}
   581  
   582  	if err := spec.Assign(&specs); err != nil {
   583  		panic(err)
   584  	}
   585  
   586  	fmt.Println(specs.Program.Type)
   587  	fmt.Println(specs.Map.Type)
   588  
   589  	// Output: SocketFilter
   590  	// Array
   591  }
   592  
   593  func ExampleCollectionSpec_LoadAndAssign() {
   594  	spec := &CollectionSpec{
   595  		Maps: map[string]*MapSpec{
   596  			"map1": {
   597  				Type:       Array,
   598  				KeySize:    4,
   599  				ValueSize:  4,
   600  				MaxEntries: 1,
   601  			},
   602  		},
   603  		Programs: map[string]*ProgramSpec{
   604  			"prog1": {
   605  				Type: SocketFilter,
   606  				Instructions: asm.Instructions{
   607  					asm.LoadImm(asm.R0, 0, asm.DWord),
   608  					asm.Return(),
   609  				},
   610  				License: "MIT",
   611  			},
   612  		},
   613  	}
   614  
   615  	var objs struct {
   616  		Program *Program `ebpf:"prog1"`
   617  		Map     *Map     `ebpf:"map1"`
   618  	}
   619  
   620  	if err := spec.LoadAndAssign(&objs, nil); err != nil {
   621  		panic(err)
   622  	}
   623  	defer objs.Program.Close()
   624  	defer objs.Map.Close()
   625  
   626  	fmt.Println(objs.Program.Type())
   627  	fmt.Println(objs.Map.Type())
   628  
   629  	// Output: SocketFilter
   630  	// Array
   631  }
   632  

View as plain text