gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

collection.go (21502B)


      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 }