...

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

Documentation: github.com/cilium/ebpf

     1  package ebpf
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"strings"
     9  
    10  	"github.com/cilium/ebpf/asm"
    11  	"github.com/cilium/ebpf/btf"
    12  )
    13  
    14  // CollectionOptions control loading a collection into the kernel.
    15  //
    16  // Maps and Programs are passed to NewMapWithOptions and NewProgramsWithOptions.
    17  type CollectionOptions struct {
    18  	Maps     MapOptions
    19  	Programs ProgramOptions
    20  
    21  	// MapReplacements takes a set of Maps that will be used instead of
    22  	// creating new ones when loading the CollectionSpec.
    23  	//
    24  	// For each given Map, there must be a corresponding MapSpec in
    25  	// CollectionSpec.Maps, and its type, key/value size, max entries and flags
    26  	// must match the values of the MapSpec.
    27  	//
    28  	// The given Maps are Clone()d before being used in the Collection, so the
    29  	// caller can Close() them freely when they are no longer needed.
    30  	MapReplacements map[string]*Map
    31  }
    32  
    33  // CollectionSpec describes a collection.
    34  type CollectionSpec struct {
    35  	Maps     map[string]*MapSpec
    36  	Programs map[string]*ProgramSpec
    37  
    38  	// Types holds type information about Maps and Programs.
    39  	// Modifications to Types are currently undefined behaviour.
    40  	Types *btf.Spec
    41  
    42  	// ByteOrder specifies whether the ELF was compiled for
    43  	// big-endian or little-endian architectures.
    44  	ByteOrder binary.ByteOrder
    45  }
    46  
    47  // Copy returns a recursive copy of the spec.
    48  func (cs *CollectionSpec) Copy() *CollectionSpec {
    49  	if cs == nil {
    50  		return nil
    51  	}
    52  
    53  	cpy := CollectionSpec{
    54  		Maps:      make(map[string]*MapSpec, len(cs.Maps)),
    55  		Programs:  make(map[string]*ProgramSpec, len(cs.Programs)),
    56  		ByteOrder: cs.ByteOrder,
    57  		Types:     cs.Types,
    58  	}
    59  
    60  	for name, spec := range cs.Maps {
    61  		cpy.Maps[name] = spec.Copy()
    62  	}
    63  
    64  	for name, spec := range cs.Programs {
    65  		cpy.Programs[name] = spec.Copy()
    66  	}
    67  
    68  	return &cpy
    69  }
    70  
    71  // RewriteMaps replaces all references to specific maps.
    72  //
    73  // Use this function to use pre-existing maps instead of creating new ones
    74  // when calling NewCollection. Any named maps are removed from CollectionSpec.Maps.
    75  //
    76  // Returns an error if a named map isn't used in at least one program.
    77  //
    78  // Deprecated: Pass CollectionOptions.MapReplacements when loading the Collection
    79  // instead.
    80  func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
    81  	for symbol, m := range maps {
    82  		// have we seen a program that uses this symbol / map
    83  		seen := false
    84  		for progName, progSpec := range cs.Programs {
    85  			err := progSpec.Instructions.AssociateMap(symbol, m)
    86  
    87  			switch {
    88  			case err == nil:
    89  				seen = true
    90  
    91  			case errors.Is(err, asm.ErrUnreferencedSymbol):
    92  				// Not all programs need to use the map
    93  
    94  			default:
    95  				return fmt.Errorf("program %s: %w", progName, err)
    96  			}
    97  		}
    98  
    99  		if !seen {
   100  			return fmt.Errorf("map %s not referenced by any programs", symbol)
   101  		}
   102  
   103  		// Prevent NewCollection from creating rewritten maps
   104  		delete(cs.Maps, symbol)
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  // RewriteConstants replaces the value of multiple constants.
   111  //
   112  // The constant must be defined like so in the C program:
   113  //
   114  //    volatile const type foobar;
   115  //    volatile const type foobar = default;
   116  //
   117  // Replacement values must be of the same length as the C sizeof(type).
   118  // If necessary, they are marshalled according to the same rules as
   119  // map values.
   120  //
   121  // From Linux 5.5 the verifier will use constants to eliminate dead code.
   122  //
   123  // Returns an error if a constant doesn't exist.
   124  func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
   125  	replaced := make(map[string]bool)
   126  
   127  	for name, spec := range cs.Maps {
   128  		if !strings.HasPrefix(name, ".rodata") {
   129  			continue
   130  		}
   131  
   132  		b, ds, err := spec.dataSection()
   133  		if errors.Is(err, errMapNoBTFValue) {
   134  			// Data sections without a BTF Datasec are valid, but don't support
   135  			// constant replacements.
   136  			continue
   137  		}
   138  		if err != nil {
   139  			return fmt.Errorf("map %s: %w", name, err)
   140  		}
   141  
   142  		// MapSpec.Copy() performs a shallow copy. Fully copy the byte slice
   143  		// to avoid any changes affecting other copies of the MapSpec.
   144  		cpy := make([]byte, len(b))
   145  		copy(cpy, b)
   146  
   147  		for _, v := range ds.Vars {
   148  			vname := v.Type.TypeName()
   149  			replacement, ok := consts[vname]
   150  			if !ok {
   151  				continue
   152  			}
   153  
   154  			if replaced[vname] {
   155  				return fmt.Errorf("section %s: duplicate variable %s", name, vname)
   156  			}
   157  
   158  			if int(v.Offset+v.Size) > len(cpy) {
   159  				return fmt.Errorf("section %s: offset %d(+%d) for variable %s is out of bounds", name, v.Offset, v.Size, vname)
   160  			}
   161  
   162  			b, err := marshalBytes(replacement, int(v.Size))
   163  			if err != nil {
   164  				return fmt.Errorf("marshaling constant replacement %s: %w", vname, err)
   165  			}
   166  
   167  			copy(cpy[v.Offset:v.Offset+v.Size], b)
   168  
   169  			replaced[vname] = true
   170  		}
   171  
   172  		spec.Contents[0] = MapKV{Key: uint32(0), Value: cpy}
   173  	}
   174  
   175  	var missing []string
   176  	for c := range consts {
   177  		if !replaced[c] {
   178  			missing = append(missing, c)
   179  		}
   180  	}
   181  
   182  	if len(missing) != 0 {
   183  		return fmt.Errorf("spec is missing one or more constants: %s", strings.Join(missing, ","))
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  // Assign the contents of a CollectionSpec to a struct.
   190  //
   191  // This function is a shortcut to manually checking the presence
   192  // of maps and programs in a CollectionSpec. Consider using bpf2go
   193  // if this sounds useful.
   194  //
   195  // 'to' must be a pointer to a struct. A field of the
   196  // struct is updated with values from Programs or Maps if it
   197  // has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
   198  // The tag's value specifies the name of the program or map as
   199  // found in the CollectionSpec.
   200  //
   201  //    struct {
   202  //        Foo     *ebpf.ProgramSpec `ebpf:"xdp_foo"`
   203  //        Bar     *ebpf.MapSpec     `ebpf:"bar_map"`
   204  //        Ignored int
   205  //    }
   206  //
   207  // Returns an error if any of the eBPF objects can't be found, or
   208  // if the same MapSpec or ProgramSpec is assigned multiple times.
   209  func (cs *CollectionSpec) Assign(to interface{}) error {
   210  	// Assign() only supports assigning ProgramSpecs and MapSpecs,
   211  	// so doesn't load any resources into the kernel.
   212  	getValue := func(typ reflect.Type, name string) (interface{}, error) {
   213  		switch typ {
   214  
   215  		case reflect.TypeOf((*ProgramSpec)(nil)):
   216  			if p := cs.Programs[name]; p != nil {
   217  				return p, nil
   218  			}
   219  			return nil, fmt.Errorf("missing program %q", name)
   220  
   221  		case reflect.TypeOf((*MapSpec)(nil)):
   222  			if m := cs.Maps[name]; m != nil {
   223  				return m, nil
   224  			}
   225  			return nil, fmt.Errorf("missing map %q", name)
   226  
   227  		default:
   228  			return nil, fmt.Errorf("unsupported type %s", typ)
   229  		}
   230  	}
   231  
   232  	return assignValues(to, getValue)
   233  }
   234  
   235  // LoadAndAssign loads Maps and Programs into the kernel and assigns them
   236  // to a struct.
   237  //
   238  // Omitting Map/Program.Close() during application shutdown is an error.
   239  // See the package documentation for details around Map and Program lifecycle.
   240  //
   241  // This function is a shortcut to manually checking the presence
   242  // of maps and programs in a CollectionSpec. Consider using bpf2go
   243  // if this sounds useful.
   244  //
   245  // 'to' must be a pointer to a struct. A field of the struct is updated with
   246  // a Program or Map if it has an `ebpf` tag and its type is *Program or *Map.
   247  // The tag's value specifies the name of the program or map as found in the
   248  // CollectionSpec. Before updating the struct, the requested objects and their
   249  // dependent resources are loaded into the kernel and populated with values if
   250  // specified.
   251  //
   252  //    struct {
   253  //        Foo     *ebpf.Program `ebpf:"xdp_foo"`
   254  //        Bar     *ebpf.Map     `ebpf:"bar_map"`
   255  //        Ignored int
   256  //    }
   257  //
   258  // opts may be nil.
   259  //
   260  // Returns an error if any of the fields can't be found, or
   261  // if the same Map or Program is assigned multiple times.
   262  func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error {
   263  	loader, err := newCollectionLoader(cs, opts)
   264  	if err != nil {
   265  		return err
   266  	}
   267  	defer loader.close()
   268  
   269  	// Support assigning Programs and Maps, lazy-loading the required objects.
   270  	assignedMaps := make(map[string]bool)
   271  	assignedProgs := make(map[string]bool)
   272  
   273  	getValue := func(typ reflect.Type, name string) (interface{}, error) {
   274  		switch typ {
   275  
   276  		case reflect.TypeOf((*Program)(nil)):
   277  			assignedProgs[name] = true
   278  			return loader.loadProgram(name)
   279  
   280  		case reflect.TypeOf((*Map)(nil)):
   281  			assignedMaps[name] = true
   282  			return loader.loadMap(name)
   283  
   284  		default:
   285  			return nil, fmt.Errorf("unsupported type %s", typ)
   286  		}
   287  	}
   288  
   289  	// Load the Maps and Programs requested by the annotated struct.
   290  	if err := assignValues(to, getValue); err != nil {
   291  		return err
   292  	}
   293  
   294  	// Populate the requested maps. Has a chance of lazy-loading other dependent maps.
   295  	if err := loader.populateMaps(); err != nil {
   296  		return err
   297  	}
   298  
   299  	// Evaluate the loader's objects after all (lazy)loading has taken place.
   300  	for n, m := range loader.maps {
   301  		switch m.typ {
   302  		case ProgramArray:
   303  			// Require all lazy-loaded ProgramArrays to be assigned to the given object.
   304  			// The kernel empties a ProgramArray once the last user space reference
   305  			// to it closes, which leads to failed tail calls. Combined with the library
   306  			// closing map fds via GC finalizers this can lead to surprising behaviour.
   307  			// Only allow unassigned ProgramArrays when the library hasn't pre-populated
   308  			// any entries from static value declarations. At this point, we know the map
   309  			// is empty and there's no way for the caller to interact with the map going
   310  			// forward.
   311  			if !assignedMaps[n] && len(cs.Maps[n].Contents) > 0 {
   312  				return fmt.Errorf("ProgramArray %s must be assigned to prevent missed tail calls", n)
   313  			}
   314  		}
   315  	}
   316  
   317  	// Prevent loader.cleanup() from closing assigned Maps and Programs.
   318  	for m := range assignedMaps {
   319  		delete(loader.maps, m)
   320  	}
   321  	for p := range assignedProgs {
   322  		delete(loader.programs, p)
   323  	}
   324  
   325  	return nil
   326  }
   327  
   328  // Collection is a collection of Programs and Maps associated
   329  // with their symbols
   330  type Collection struct {
   331  	Programs map[string]*Program
   332  	Maps     map[string]*Map
   333  }
   334  
   335  // NewCollection creates a Collection from the given spec, creating and
   336  // loading its declared resources into the kernel.
   337  //
   338  // Omitting Collection.Close() during application shutdown is an error.
   339  // See the package documentation for details around Map and Program lifecycle.
   340  func NewCollection(spec *CollectionSpec) (*Collection, error) {
   341  	return NewCollectionWithOptions(spec, CollectionOptions{})
   342  }
   343  
   344  // NewCollectionWithOptions creates a Collection from the given spec using
   345  // options, creating and loading its declared resources into the kernel.
   346  //
   347  // Omitting Collection.Close() during application shutdown is an error.
   348  // See the package documentation for details around Map and Program lifecycle.
   349  func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) {
   350  	loader, err := newCollectionLoader(spec, &opts)
   351  	if err != nil {
   352  		return nil, err
   353  	}
   354  	defer loader.close()
   355  
   356  	// Create maps first, as their fds need to be linked into programs.
   357  	for mapName := range spec.Maps {
   358  		if _, err := loader.loadMap(mapName); err != nil {
   359  			return nil, err
   360  		}
   361  	}
   362  
   363  	for progName, prog := range spec.Programs {
   364  		if prog.Type == UnspecifiedProgram {
   365  			continue
   366  		}
   367  
   368  		if _, err := loader.loadProgram(progName); err != nil {
   369  			return nil, err
   370  		}
   371  	}
   372  
   373  	// Maps can contain Program and Map stubs, so populate them after
   374  	// all Maps and Programs have been successfully loaded.
   375  	if err := loader.populateMaps(); err != nil {
   376  		return nil, err
   377  	}
   378  
   379  	// Prevent loader.cleanup from closing maps and programs.
   380  	maps, progs := loader.maps, loader.programs
   381  	loader.maps, loader.programs = nil, nil
   382  
   383  	return &Collection{
   384  		progs,
   385  		maps,
   386  	}, nil
   387  }
   388  
   389  type handleCache struct {
   390  	btfHandles map[*btf.Spec]*btf.Handle
   391  }
   392  
   393  func newHandleCache() *handleCache {
   394  	return &handleCache{
   395  		btfHandles: make(map[*btf.Spec]*btf.Handle),
   396  	}
   397  }
   398  
   399  func (hc handleCache) btfHandle(spec *btf.Spec) (*btf.Handle, error) {
   400  	if hc.btfHandles[spec] != nil {
   401  		return hc.btfHandles[spec], nil
   402  	}
   403  
   404  	handle, err := btf.NewHandle(spec)
   405  	if err != nil {
   406  		return nil, err
   407  	}
   408  
   409  	hc.btfHandles[spec] = handle
   410  	return handle, nil
   411  }
   412  
   413  func (hc handleCache) close() {
   414  	for _, handle := range hc.btfHandles {
   415  		handle.Close()
   416  	}
   417  }
   418  
   419  type collectionLoader struct {
   420  	coll     *CollectionSpec
   421  	opts     *CollectionOptions
   422  	maps     map[string]*Map
   423  	programs map[string]*Program
   424  	handles  *handleCache
   425  }
   426  
   427  func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collectionLoader, error) {
   428  	if opts == nil {
   429  		opts = &CollectionOptions{}
   430  	}
   431  
   432  	// Check for existing MapSpecs in the CollectionSpec for all provided replacement maps.
   433  	for name, m := range opts.MapReplacements {
   434  		spec, ok := coll.Maps[name]
   435  		if !ok {
   436  			return nil, fmt.Errorf("replacement map %s not found in CollectionSpec", name)
   437  		}
   438  
   439  		if err := spec.checkCompatibility(m); err != nil {
   440  			return nil, fmt.Errorf("using replacement map %s: %w", spec.Name, err)
   441  		}
   442  	}
   443  
   444  	return &collectionLoader{
   445  		coll,
   446  		opts,
   447  		make(map[string]*Map),
   448  		make(map[string]*Program),
   449  		newHandleCache(),
   450  	}, nil
   451  }
   452  
   453  // close all resources left over in the collectionLoader.
   454  func (cl *collectionLoader) close() {
   455  	cl.handles.close()
   456  	for _, m := range cl.maps {
   457  		m.Close()
   458  	}
   459  	for _, p := range cl.programs {
   460  		p.Close()
   461  	}
   462  }
   463  
   464  func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
   465  	if m := cl.maps[mapName]; m != nil {
   466  		return m, nil
   467  	}
   468  
   469  	mapSpec := cl.coll.Maps[mapName]
   470  	if mapSpec == nil {
   471  		return nil, fmt.Errorf("missing map %s", mapName)
   472  	}
   473  
   474  	if mapSpec.BTF != nil && cl.coll.Types != mapSpec.BTF {
   475  		return nil, fmt.Errorf("map %s: BTF doesn't match collection", mapName)
   476  	}
   477  
   478  	if replaceMap, ok := cl.opts.MapReplacements[mapName]; ok {
   479  		// Clone the map to avoid closing user's map later on.
   480  		m, err := replaceMap.Clone()
   481  		if err != nil {
   482  			return nil, err
   483  		}
   484  
   485  		cl.maps[mapName] = m
   486  		return m, nil
   487  	}
   488  
   489  	m, err := newMapWithOptions(mapSpec, cl.opts.Maps, cl.handles)
   490  	if err != nil {
   491  		return nil, fmt.Errorf("map %s: %w", mapName, err)
   492  	}
   493  
   494  	cl.maps[mapName] = m
   495  	return m, nil
   496  }
   497  
   498  func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
   499  	if prog := cl.programs[progName]; prog != nil {
   500  		return prog, nil
   501  	}
   502  
   503  	progSpec := cl.coll.Programs[progName]
   504  	if progSpec == nil {
   505  		return nil, fmt.Errorf("unknown program %s", progName)
   506  	}
   507  
   508  	// Bail out early if we know the kernel is going to reject the program.
   509  	// This skips loading map dependencies, saving some cleanup work later.
   510  	if progSpec.Type == UnspecifiedProgram {
   511  		return nil, fmt.Errorf("cannot load program %s: program type is unspecified", progName)
   512  	}
   513  
   514  	if progSpec.BTF != nil && cl.coll.Types != progSpec.BTF {
   515  		return nil, fmt.Errorf("program %s: BTF doesn't match collection", progName)
   516  	}
   517  
   518  	progSpec = progSpec.Copy()
   519  
   520  	// Rewrite any reference to a valid map in the program's instructions,
   521  	// which includes all of its dependencies.
   522  	for i := range progSpec.Instructions {
   523  		ins := &progSpec.Instructions[i]
   524  
   525  		if !ins.IsLoadFromMap() || ins.Reference() == "" {
   526  			continue
   527  		}
   528  
   529  		// Don't overwrite map loads containing non-zero map fd's,
   530  		// they can be manually included by the caller.
   531  		// Map FDs/IDs are placed in the lower 32 bits of Constant.
   532  		if int32(ins.Constant) > 0 {
   533  			continue
   534  		}
   535  
   536  		m, err := cl.loadMap(ins.Reference())
   537  		if err != nil {
   538  			return nil, fmt.Errorf("program %s: %w", progName, err)
   539  		}
   540  
   541  		if err := ins.AssociateMap(m); err != nil {
   542  			return nil, fmt.Errorf("program %s: map %s: %w", progName, ins.Reference(), err)
   543  		}
   544  	}
   545  
   546  	prog, err := newProgramWithOptions(progSpec, cl.opts.Programs, cl.handles)
   547  	if err != nil {
   548  		return nil, fmt.Errorf("program %s: %w", progName, err)
   549  	}
   550  
   551  	cl.programs[progName] = prog
   552  	return prog, nil
   553  }
   554  
   555  func (cl *collectionLoader) populateMaps() error {
   556  	for mapName, m := range cl.maps {
   557  		mapSpec, ok := cl.coll.Maps[mapName]
   558  		if !ok {
   559  			return fmt.Errorf("missing map spec %s", mapName)
   560  		}
   561  
   562  		mapSpec = mapSpec.Copy()
   563  
   564  		// MapSpecs that refer to inner maps or programs within the same
   565  		// CollectionSpec do so using strings. These strings are used as the key
   566  		// to look up the respective object in the Maps or Programs fields.
   567  		// Resolve those references to actual Map or Program resources that
   568  		// have been loaded into the kernel.
   569  		for i, kv := range mapSpec.Contents {
   570  			if objName, ok := kv.Value.(string); ok {
   571  				switch mapSpec.Type {
   572  				case ProgramArray:
   573  					// loadProgram is idempotent and could return an existing Program.
   574  					prog, err := cl.loadProgram(objName)
   575  					if err != nil {
   576  						return fmt.Errorf("loading program %s, for map %s: %w", objName, mapName, err)
   577  					}
   578  					mapSpec.Contents[i] = MapKV{kv.Key, prog}
   579  
   580  				case ArrayOfMaps, HashOfMaps:
   581  					// loadMap is idempotent and could return an existing Map.
   582  					innerMap, err := cl.loadMap(objName)
   583  					if err != nil {
   584  						return fmt.Errorf("loading inner map %s, for map %s: %w", objName, mapName, err)
   585  					}
   586  					mapSpec.Contents[i] = MapKV{kv.Key, innerMap}
   587  				}
   588  			}
   589  		}
   590  
   591  		// Populate and freeze the map if specified.
   592  		if err := m.finalize(mapSpec); err != nil {
   593  			return fmt.Errorf("populating map %s: %w", mapName, err)
   594  		}
   595  	}
   596  
   597  	return nil
   598  }
   599  
   600  // LoadCollection reads an object file and creates and loads its declared
   601  // resources into the kernel.
   602  //
   603  // Omitting Collection.Close() during application shutdown is an error.
   604  // See the package documentation for details around Map and Program lifecycle.
   605  func LoadCollection(file string) (*Collection, error) {
   606  	spec, err := LoadCollectionSpec(file)
   607  	if err != nil {
   608  		return nil, err
   609  	}
   610  	return NewCollection(spec)
   611  }
   612  
   613  // Close frees all maps and programs associated with the collection.
   614  //
   615  // The collection mustn't be used afterwards.
   616  func (coll *Collection) Close() {
   617  	for _, prog := range coll.Programs {
   618  		prog.Close()
   619  	}
   620  	for _, m := range coll.Maps {
   621  		m.Close()
   622  	}
   623  }
   624  
   625  // DetachMap removes the named map from the Collection.
   626  //
   627  // This means that a later call to Close() will not affect this map.
   628  //
   629  // Returns nil if no map of that name exists.
   630  func (coll *Collection) DetachMap(name string) *Map {
   631  	m := coll.Maps[name]
   632  	delete(coll.Maps, name)
   633  	return m
   634  }
   635  
   636  // DetachProgram removes the named program from the Collection.
   637  //
   638  // This means that a later call to Close() will not affect this program.
   639  //
   640  // Returns nil if no program of that name exists.
   641  func (coll *Collection) DetachProgram(name string) *Program {
   642  	p := coll.Programs[name]
   643  	delete(coll.Programs, name)
   644  	return p
   645  }
   646  
   647  // structField represents a struct field containing the ebpf struct tag.
   648  type structField struct {
   649  	reflect.StructField
   650  	value reflect.Value
   651  }
   652  
   653  // ebpfFields extracts field names tagged with 'ebpf' from a struct type.
   654  // Keep track of visited types to avoid infinite recursion.
   655  func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]structField, error) {
   656  	if visited == nil {
   657  		visited = make(map[reflect.Type]bool)
   658  	}
   659  
   660  	structType := structVal.Type()
   661  	if structType.Kind() != reflect.Struct {
   662  		return nil, fmt.Errorf("%s is not a struct", structType)
   663  	}
   664  
   665  	if visited[structType] {
   666  		return nil, fmt.Errorf("recursion on type %s", structType)
   667  	}
   668  
   669  	fields := make([]structField, 0, structType.NumField())
   670  	for i := 0; i < structType.NumField(); i++ {
   671  		field := structField{structType.Field(i), structVal.Field(i)}
   672  
   673  		// If the field is tagged, gather it and move on.
   674  		name := field.Tag.Get("ebpf")
   675  		if name != "" {
   676  			fields = append(fields, field)
   677  			continue
   678  		}
   679  
   680  		// If the field does not have an ebpf tag, but is a struct or a pointer
   681  		// to a struct, attempt to gather its fields as well.
   682  		var v reflect.Value
   683  		switch field.Type.Kind() {
   684  		case reflect.Ptr:
   685  			if field.Type.Elem().Kind() != reflect.Struct {
   686  				continue
   687  			}
   688  
   689  			if field.value.IsNil() {
   690  				return nil, fmt.Errorf("nil pointer to %s", structType)
   691  			}
   692  
   693  			// Obtain the destination type of the pointer.
   694  			v = field.value.Elem()
   695  
   696  		case reflect.Struct:
   697  			// Reference the value's type directly.
   698  			v = field.value
   699  
   700  		default:
   701  			continue
   702  		}
   703  
   704  		inner, err := ebpfFields(v, visited)
   705  		if err != nil {
   706  			return nil, fmt.Errorf("field %s: %w", field.Name, err)
   707  		}
   708  
   709  		fields = append(fields, inner...)
   710  	}
   711  
   712  	return fields, nil
   713  }
   714  
   715  // assignValues attempts to populate all fields of 'to' tagged with 'ebpf'.
   716  //
   717  // getValue is called for every tagged field of 'to' and must return the value
   718  // to be assigned to the field with the given typ and name.
   719  func assignValues(to interface{},
   720  	getValue func(typ reflect.Type, name string) (interface{}, error)) error {
   721  
   722  	toValue := reflect.ValueOf(to)
   723  	if toValue.Type().Kind() != reflect.Ptr {
   724  		return fmt.Errorf("%T is not a pointer to struct", to)
   725  	}
   726  
   727  	if toValue.IsNil() {
   728  		return fmt.Errorf("nil pointer to %T", to)
   729  	}
   730  
   731  	fields, err := ebpfFields(toValue.Elem(), nil)
   732  	if err != nil {
   733  		return err
   734  	}
   735  
   736  	type elem struct {
   737  		// Either *Map or *Program
   738  		typ  reflect.Type
   739  		name string
   740  	}
   741  
   742  	assigned := make(map[elem]string)
   743  	for _, field := range fields {
   744  		// Get string value the field is tagged with.
   745  		tag := field.Tag.Get("ebpf")
   746  		if strings.Contains(tag, ",") {
   747  			return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name)
   748  		}
   749  
   750  		// Check if the eBPF object with the requested
   751  		// type and tag was already assigned elsewhere.
   752  		e := elem{field.Type, tag}
   753  		if af := assigned[e]; af != "" {
   754  			return fmt.Errorf("field %s: object %q was already assigned to %s", field.Name, tag, af)
   755  		}
   756  
   757  		// Get the eBPF object referred to by the tag.
   758  		value, err := getValue(field.Type, tag)
   759  		if err != nil {
   760  			return fmt.Errorf("field %s: %w", field.Name, err)
   761  		}
   762  
   763  		if !field.value.CanSet() {
   764  			return fmt.Errorf("field %s: can't set value", field.Name)
   765  		}
   766  		field.value.Set(reflect.ValueOf(value))
   767  
   768  		assigned[e] = field.Name
   769  	}
   770  
   771  	return nil
   772  }
   773  

View as plain text