|
@@ -1,6 +1,7 @@
|
|
package ebpf
|
|
package ebpf
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
+ "encoding/binary"
|
|
"errors"
|
|
"errors"
|
|
"fmt"
|
|
"fmt"
|
|
"io"
|
|
"io"
|
|
@@ -25,6 +26,10 @@ type CollectionOptions struct {
|
|
type CollectionSpec struct {
|
|
type CollectionSpec struct {
|
|
Maps map[string]*MapSpec
|
|
Maps map[string]*MapSpec
|
|
Programs map[string]*ProgramSpec
|
|
Programs map[string]*ProgramSpec
|
|
|
|
+
|
|
|
|
+ // ByteOrder specifies whether the ELF was compiled for
|
|
|
|
+ // big-endian or little-endian architectures.
|
|
|
|
+ ByteOrder binary.ByteOrder
|
|
}
|
|
}
|
|
|
|
|
|
// Copy returns a recursive copy of the spec.
|
|
// Copy returns a recursive copy of the spec.
|
|
@@ -34,8 +39,9 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
|
|
}
|
|
}
|
|
|
|
|
|
cpy := CollectionSpec{
|
|
cpy := CollectionSpec{
|
|
- Maps: make(map[string]*MapSpec, len(cs.Maps)),
|
|
|
|
- Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
|
|
|
|
|
|
+ Maps: make(map[string]*MapSpec, len(cs.Maps)),
|
|
|
|
+ Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
|
|
|
|
+ ByteOrder: cs.ByteOrder,
|
|
}
|
|
}
|
|
|
|
|
|
for name, spec := range cs.Maps {
|
|
for name, spec := range cs.Maps {
|
|
@@ -123,7 +129,7 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
|
|
buf := make([]byte, len(value))
|
|
buf := make([]byte, len(value))
|
|
copy(buf, value)
|
|
copy(buf, value)
|
|
|
|
|
|
- err := patchValue(buf, btf.MapValue(rodata.BTF), consts)
|
|
|
|
|
|
+ err := patchValue(buf, rodata.BTF.Value, consts)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -134,15 +140,15 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
|
|
|
|
|
|
// Assign the contents of a CollectionSpec to a struct.
|
|
// Assign the contents of a CollectionSpec to a struct.
|
|
//
|
|
//
|
|
-// This function is a short-cut to manually checking the presence
|
|
|
|
-// of maps and programs in a collection spec. Consider using bpf2go if this
|
|
|
|
-// sounds useful.
|
|
|
|
|
|
+// This function is a shortcut to manually checking the presence
|
|
|
|
+// of maps and programs in a CollectionSpec. Consider using bpf2go
|
|
|
|
+// if this sounds useful.
|
|
//
|
|
//
|
|
-// The argument to must be a pointer to a struct. A field of the
|
|
|
|
|
|
+// 'to' must be a pointer to a struct. A field of the
|
|
// struct is updated with values from Programs or Maps if it
|
|
// struct is updated with values from Programs or Maps if it
|
|
// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
|
|
// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
|
|
-// The tag gives the name of the program or map as found in
|
|
|
|
-// the CollectionSpec.
|
|
|
|
|
|
+// The tag's value specifies the name of the program or map as
|
|
|
|
+// found in the CollectionSpec.
|
|
//
|
|
//
|
|
// struct {
|
|
// struct {
|
|
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
|
|
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
|
|
@@ -150,42 +156,47 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
|
|
// Ignored int
|
|
// Ignored int
|
|
// }
|
|
// }
|
|
//
|
|
//
|
|
-// Returns an error if any of the fields can't be found, or
|
|
|
|
-// if the same map or program is assigned multiple times.
|
|
|
|
|
|
+// Returns an error if any of the eBPF objects can't be found, or
|
|
|
|
+// if the same MapSpec or ProgramSpec is assigned multiple times.
|
|
func (cs *CollectionSpec) Assign(to interface{}) error {
|
|
func (cs *CollectionSpec) Assign(to interface{}) error {
|
|
- valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
|
|
|
|
|
|
+ // Assign() only supports assigning ProgramSpecs and MapSpecs,
|
|
|
|
+ // so doesn't load any resources into the kernel.
|
|
|
|
+ getValue := func(typ reflect.Type, name string) (interface{}, error) {
|
|
switch typ {
|
|
switch typ {
|
|
|
|
+
|
|
case reflect.TypeOf((*ProgramSpec)(nil)):
|
|
case reflect.TypeOf((*ProgramSpec)(nil)):
|
|
- p := cs.Programs[name]
|
|
|
|
- if p == nil {
|
|
|
|
- return reflect.Value{}, fmt.Errorf("missing program %q", name)
|
|
|
|
|
|
+ if p := cs.Programs[name]; p != nil {
|
|
|
|
+ return p, nil
|
|
}
|
|
}
|
|
- return reflect.ValueOf(p), nil
|
|
|
|
|
|
+ return nil, fmt.Errorf("missing program %q", name)
|
|
|
|
+
|
|
case reflect.TypeOf((*MapSpec)(nil)):
|
|
case reflect.TypeOf((*MapSpec)(nil)):
|
|
- m := cs.Maps[name]
|
|
|
|
- if m == nil {
|
|
|
|
- return reflect.Value{}, fmt.Errorf("missing map %q", name)
|
|
|
|
|
|
+ if m := cs.Maps[name]; m != nil {
|
|
|
|
+ return m, nil
|
|
}
|
|
}
|
|
- return reflect.ValueOf(m), nil
|
|
|
|
|
|
+ return nil, fmt.Errorf("missing map %q", name)
|
|
|
|
+
|
|
default:
|
|
default:
|
|
- return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
|
|
|
|
|
|
+ return nil, fmt.Errorf("unsupported type %s", typ)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- return assignValues(to, valueOf)
|
|
|
|
|
|
+ return assignValues(to, getValue)
|
|
}
|
|
}
|
|
|
|
|
|
-// LoadAndAssign maps and programs into the kernel and assign them to a struct.
|
|
|
|
|
|
+// LoadAndAssign loads Maps and Programs into the kernel and assigns them
|
|
|
|
+// to a struct.
|
|
//
|
|
//
|
|
-// This function is a short-cut to manually checking the presence
|
|
|
|
-// of maps and programs in a collection spec. Consider using bpf2go if this
|
|
|
|
-// sounds useful.
|
|
|
|
|
|
+// This function is a shortcut to manually checking the presence
|
|
|
|
+// of maps and programs in a CollectionSpec. Consider using bpf2go
|
|
|
|
+// if this sounds useful.
|
|
//
|
|
//
|
|
-// The argument to must be a pointer to a struct. A field of the
|
|
|
|
-// struct is updated with values from Programs or Maps if it
|
|
|
|
-// has an `ebpf` tag and its type is *Program or *Map.
|
|
|
|
-// The tag gives the name of the program or map as found in
|
|
|
|
-// the CollectionSpec.
|
|
|
|
|
|
+// 'to' must be a pointer to a struct. A field of the struct is updated with
|
|
|
|
+// a Program or Map if it has an `ebpf` tag and its type is *Program or *Map.
|
|
|
|
+// The tag's value specifies the name of the program or map as found in the
|
|
|
|
+// CollectionSpec. Before updating the struct, the requested objects and their
|
|
|
|
+// dependent resources are loaded into the kernel and populated with values if
|
|
|
|
+// specified.
|
|
//
|
|
//
|
|
// struct {
|
|
// struct {
|
|
// Foo *ebpf.Program `ebpf:"xdp_foo"`
|
|
// Foo *ebpf.Program `ebpf:"xdp_foo"`
|
|
@@ -196,39 +207,53 @@ func (cs *CollectionSpec) Assign(to interface{}) error {
|
|
// opts may be nil.
|
|
// opts may be nil.
|
|
//
|
|
//
|
|
// Returns an error if any of the fields can't be found, or
|
|
// Returns an error if any of the fields can't be found, or
|
|
-// if the same map or program is assigned multiple times.
|
|
|
|
|
|
+// if the same Map or Program is assigned multiple times.
|
|
func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error {
|
|
func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error {
|
|
- if opts == nil {
|
|
|
|
- opts = &CollectionOptions{}
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- loadMap, loadProgram, done, cleanup := lazyLoadCollection(cs, opts)
|
|
|
|
- defer cleanup()
|
|
|
|
|
|
+ loader := newCollectionLoader(cs, opts)
|
|
|
|
+ defer loader.cleanup()
|
|
|
|
|
|
- valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
|
|
|
|
|
|
+ // Support assigning Programs and Maps, lazy-loading the required objects.
|
|
|
|
+ assignedMaps := make(map[string]bool)
|
|
|
|
+ getValue := func(typ reflect.Type, name string) (interface{}, error) {
|
|
switch typ {
|
|
switch typ {
|
|
|
|
+
|
|
case reflect.TypeOf((*Program)(nil)):
|
|
case reflect.TypeOf((*Program)(nil)):
|
|
- p, err := loadProgram(name)
|
|
|
|
- if err != nil {
|
|
|
|
- return reflect.Value{}, err
|
|
|
|
- }
|
|
|
|
- return reflect.ValueOf(p), nil
|
|
|
|
|
|
+ return loader.loadProgram(name)
|
|
|
|
+
|
|
case reflect.TypeOf((*Map)(nil)):
|
|
case reflect.TypeOf((*Map)(nil)):
|
|
- m, err := loadMap(name)
|
|
|
|
- if err != nil {
|
|
|
|
- return reflect.Value{}, err
|
|
|
|
- }
|
|
|
|
- return reflect.ValueOf(m), nil
|
|
|
|
|
|
+ assignedMaps[name] = true
|
|
|
|
+ return loader.loadMap(name)
|
|
|
|
+
|
|
default:
|
|
default:
|
|
- return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
|
|
|
|
|
|
+ return nil, fmt.Errorf("unsupported type %s", typ)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if err := assignValues(to, valueOf); err != nil {
|
|
|
|
|
|
+ // Load the Maps and Programs requested by the annotated struct.
|
|
|
|
+ if err := assignValues(to, getValue); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- done()
|
|
|
|
|
|
+ // Populate the requested maps. Has a chance of lazy-loading other dependent maps.
|
|
|
|
+ if err := loader.populateMaps(); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Evaluate the loader's objects after all (lazy)loading has taken place.
|
|
|
|
+ for n, m := range loader.maps {
|
|
|
|
+ switch m.typ {
|
|
|
|
+ case ProgramArray:
|
|
|
|
+ // Require all lazy-loaded ProgramArrays to be assigned to the given object.
|
|
|
|
+ // Without any references, they will be closed on the first GC and all tail
|
|
|
|
+ // calls into them will miss.
|
|
|
|
+ if !assignedMaps[n] {
|
|
|
|
+ return fmt.Errorf("ProgramArray %s must be assigned to prevent missed tail calls", n)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ loader.finalize()
|
|
|
|
+
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
@@ -246,24 +271,32 @@ func NewCollection(spec *CollectionSpec) (*Collection, error) {
|
|
|
|
|
|
// NewCollectionWithOptions creates a Collection from a specification.
|
|
// NewCollectionWithOptions creates a Collection from a specification.
|
|
func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) {
|
|
func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) {
|
|
- loadMap, loadProgram, done, cleanup := lazyLoadCollection(spec, &opts)
|
|
|
|
- defer cleanup()
|
|
|
|
|
|
+ loader := newCollectionLoader(spec, &opts)
|
|
|
|
+ defer loader.cleanup()
|
|
|
|
|
|
|
|
+ // Create maps first, as their fds need to be linked into programs.
|
|
for mapName := range spec.Maps {
|
|
for mapName := range spec.Maps {
|
|
- _, err := loadMap(mapName)
|
|
|
|
- if err != nil {
|
|
|
|
|
|
+ if _, err := loader.loadMap(mapName); err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for progName := range spec.Programs {
|
|
for progName := range spec.Programs {
|
|
- _, err := loadProgram(progName)
|
|
|
|
- if err != nil {
|
|
|
|
|
|
+ if _, err := loader.loadProgram(progName); err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- maps, progs := done()
|
|
|
|
|
|
+ // Maps can contain Program and Map stubs, so populate them after
|
|
|
|
+ // all Maps and Programs have been successfully loaded.
|
|
|
|
+ if err := loader.populateMaps(); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ maps, progs := loader.maps, loader.programs
|
|
|
|
+
|
|
|
|
+ loader.finalize()
|
|
|
|
+
|
|
return &Collection{
|
|
return &Collection{
|
|
progs,
|
|
progs,
|
|
maps,
|
|
maps,
|
|
@@ -314,113 +347,154 @@ func (hc handleCache) close() {
|
|
for _, handle := range hc.btfHandles {
|
|
for _, handle := range hc.btfHandles {
|
|
handle.Close()
|
|
handle.Close()
|
|
}
|
|
}
|
|
- hc.btfHandles = nil
|
|
|
|
- hc.btfSpecs = nil
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-func lazyLoadCollection(coll *CollectionSpec, opts *CollectionOptions) (
|
|
|
|
- loadMap func(string) (*Map, error),
|
|
|
|
- loadProgram func(string) (*Program, error),
|
|
|
|
- done func() (map[string]*Map, map[string]*Program),
|
|
|
|
- cleanup func(),
|
|
|
|
-) {
|
|
|
|
- var (
|
|
|
|
- maps = make(map[string]*Map)
|
|
|
|
- progs = make(map[string]*Program)
|
|
|
|
- handles = newHandleCache()
|
|
|
|
- skipMapsAndProgs = false
|
|
|
|
- )
|
|
|
|
-
|
|
|
|
- cleanup = func() {
|
|
|
|
- handles.close()
|
|
|
|
-
|
|
|
|
- if skipMapsAndProgs {
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
|
|
+type collectionLoader struct {
|
|
|
|
+ coll *CollectionSpec
|
|
|
|
+ opts *CollectionOptions
|
|
|
|
+ maps map[string]*Map
|
|
|
|
+ programs map[string]*Program
|
|
|
|
+ handles *handleCache
|
|
|
|
+}
|
|
|
|
|
|
- for _, m := range maps {
|
|
|
|
- m.Close()
|
|
|
|
- }
|
|
|
|
|
|
+func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) *collectionLoader {
|
|
|
|
+ if opts == nil {
|
|
|
|
+ opts = &CollectionOptions{}
|
|
|
|
+ }
|
|
|
|
|
|
- for _, p := range progs {
|
|
|
|
- p.Close()
|
|
|
|
- }
|
|
|
|
|
|
+ return &collectionLoader{
|
|
|
|
+ coll,
|
|
|
|
+ opts,
|
|
|
|
+ make(map[string]*Map),
|
|
|
|
+ make(map[string]*Program),
|
|
|
|
+ newHandleCache(),
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// finalize should be called when all the collectionLoader's resources
|
|
|
|
+// have been successfully loaded into the kernel and populated with values.
|
|
|
|
+func (cl *collectionLoader) finalize() {
|
|
|
|
+ cl.maps, cl.programs = nil, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// cleanup cleans up all resources left over in the collectionLoader.
|
|
|
|
+// Call finalize() when Map and Program creation/population is successful
|
|
|
|
+// to prevent them from getting closed.
|
|
|
|
+func (cl *collectionLoader) cleanup() {
|
|
|
|
+ cl.handles.close()
|
|
|
|
+ for _, m := range cl.maps {
|
|
|
|
+ m.Close()
|
|
|
|
+ }
|
|
|
|
+ for _, p := range cl.programs {
|
|
|
|
+ p.Close()
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
|
|
|
|
+ if m := cl.maps[mapName]; m != nil {
|
|
|
|
+ return m, nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mapSpec := cl.coll.Maps[mapName]
|
|
|
|
+ if mapSpec == nil {
|
|
|
|
+ return nil, fmt.Errorf("missing map %s", mapName)
|
|
}
|
|
}
|
|
|
|
|
|
- done = func() (map[string]*Map, map[string]*Program) {
|
|
|
|
- skipMapsAndProgs = true
|
|
|
|
- return maps, progs
|
|
|
|
|
|
+ m, err := newMapWithOptions(mapSpec, cl.opts.Maps, cl.handles)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, fmt.Errorf("map %s: %w", mapName, err)
|
|
}
|
|
}
|
|
|
|
|
|
- loadMap = func(mapName string) (*Map, error) {
|
|
|
|
- if m := maps[mapName]; m != nil {
|
|
|
|
- return m, nil
|
|
|
|
|
|
+ cl.maps[mapName] = m
|
|
|
|
+ return m, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
|
|
|
|
+ if prog := cl.programs[progName]; prog != nil {
|
|
|
|
+ return prog, nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ progSpec := cl.coll.Programs[progName]
|
|
|
|
+ if progSpec == nil {
|
|
|
|
+ return nil, fmt.Errorf("unknown program %s", progName)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ progSpec = progSpec.Copy()
|
|
|
|
+
|
|
|
|
+ // Rewrite any reference to a valid map.
|
|
|
|
+ for i := range progSpec.Instructions {
|
|
|
|
+ ins := &progSpec.Instructions[i]
|
|
|
|
+
|
|
|
|
+ if !ins.IsLoadFromMap() || ins.Reference == "" {
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
|
|
|
|
- mapSpec := coll.Maps[mapName]
|
|
|
|
- if mapSpec == nil {
|
|
|
|
- return nil, fmt.Errorf("missing map %s", mapName)
|
|
|
|
|
|
+ if uint32(ins.Constant) != math.MaxUint32 {
|
|
|
|
+ // Don't overwrite maps already rewritten, users can
|
|
|
|
+ // rewrite programs in the spec themselves
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
|
|
|
|
- m, err := newMapWithOptions(mapSpec, opts.Maps, handles)
|
|
|
|
|
|
+ m, err := cl.loadMap(ins.Reference)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, fmt.Errorf("map %s: %w", mapName, err)
|
|
|
|
|
|
+ return nil, fmt.Errorf("program %s: %w", progName, err)
|
|
}
|
|
}
|
|
|
|
|
|
- maps[mapName] = m
|
|
|
|
- return m, nil
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- loadProgram = func(progName string) (*Program, error) {
|
|
|
|
- if prog := progs[progName]; prog != nil {
|
|
|
|
- return prog, nil
|
|
|
|
|
|
+ fd := m.FD()
|
|
|
|
+ if fd < 0 {
|
|
|
|
+ return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
|
|
}
|
|
}
|
|
-
|
|
|
|
- progSpec := coll.Programs[progName]
|
|
|
|
- if progSpec == nil {
|
|
|
|
- return nil, fmt.Errorf("unknown program %s", progName)
|
|
|
|
|
|
+ if err := ins.RewriteMapPtr(m.FD()); err != nil {
|
|
|
|
+ return nil, fmt.Errorf("program %s: map %s: %w", progName, ins.Reference, err)
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- progSpec = progSpec.Copy()
|
|
|
|
|
|
+ prog, err := newProgramWithOptions(progSpec, cl.opts.Programs, cl.handles)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, fmt.Errorf("program %s: %w", progName, err)
|
|
|
|
+ }
|
|
|
|
|
|
- // Rewrite any reference to a valid map.
|
|
|
|
- for i := range progSpec.Instructions {
|
|
|
|
- ins := &progSpec.Instructions[i]
|
|
|
|
|
|
+ cl.programs[progName] = prog
|
|
|
|
+ return prog, nil
|
|
|
|
+}
|
|
|
|
|
|
- if !ins.IsLoadFromMap() || ins.Reference == "" {
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
|
|
+func (cl *collectionLoader) populateMaps() error {
|
|
|
|
+ for mapName, m := range cl.maps {
|
|
|
|
+ mapSpec, ok := cl.coll.Maps[mapName]
|
|
|
|
+ if !ok {
|
|
|
|
+ return fmt.Errorf("missing map spec %s", mapName)
|
|
|
|
+ }
|
|
|
|
|
|
- if uint32(ins.Constant) != math.MaxUint32 {
|
|
|
|
- // Don't overwrite maps already rewritten, users can
|
|
|
|
- // rewrite programs in the spec themselves
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
|
|
+ mapSpec = mapSpec.Copy()
|
|
|
|
|
|
- m, err := loadMap(ins.Reference)
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, fmt.Errorf("program %s: %w", progName, err)
|
|
|
|
- }
|
|
|
|
|
|
+ // Replace any object stubs with loaded objects.
|
|
|
|
+ for i, kv := range mapSpec.Contents {
|
|
|
|
+ switch v := kv.Value.(type) {
|
|
|
|
+ case programStub:
|
|
|
|
+ // loadProgram is idempotent and could return an existing Program.
|
|
|
|
+ prog, err := cl.loadProgram(string(v))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return fmt.Errorf("loading program %s, for map %s: %w", v, mapName, err)
|
|
|
|
+ }
|
|
|
|
+ mapSpec.Contents[i] = MapKV{kv.Key, prog}
|
|
|
|
|
|
- fd := m.FD()
|
|
|
|
- if fd < 0 {
|
|
|
|
- return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
|
|
|
|
- }
|
|
|
|
- if err := ins.RewriteMapPtr(m.FD()); err != nil {
|
|
|
|
- return nil, fmt.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
|
|
|
|
|
|
+ case mapStub:
|
|
|
|
+ // loadMap is idempotent and could return an existing Map.
|
|
|
|
+ innerMap, err := cl.loadMap(string(v))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return fmt.Errorf("loading inner map %s, for map %s: %w", v, mapName, err)
|
|
|
|
+ }
|
|
|
|
+ mapSpec.Contents[i] = MapKV{kv.Key, innerMap}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- prog, err := newProgramWithOptions(progSpec, opts.Programs, handles)
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, fmt.Errorf("program %s: %w", progName, err)
|
|
|
|
|
|
+ // Populate and freeze the map if specified.
|
|
|
|
+ if err := m.finalize(mapSpec); err != nil {
|
|
|
|
+ return fmt.Errorf("populating map %s: %w", mapName, err)
|
|
}
|
|
}
|
|
-
|
|
|
|
- progs[progName] = prog
|
|
|
|
- return prog, nil
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- return
|
|
|
|
|
|
+ return nil
|
|
}
|
|
}
|
|
|
|
|
|
// LoadCollection parses an object file and converts it to a collection.
|
|
// LoadCollection parses an object file and converts it to a collection.
|
|
@@ -466,108 +540,81 @@ func (coll *Collection) DetachProgram(name string) *Program {
|
|
return p
|
|
return p
|
|
}
|
|
}
|
|
|
|
|
|
-// Assign the contents of a collection to a struct.
|
|
|
|
-//
|
|
|
|
-// Deprecated: use CollectionSpec.Assign instead. It provides the same
|
|
|
|
-// functionality but creates only the maps and programs requested.
|
|
|
|
-func (coll *Collection) Assign(to interface{}) error {
|
|
|
|
- assignedMaps := make(map[string]struct{})
|
|
|
|
- assignedPrograms := make(map[string]struct{})
|
|
|
|
- valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
|
|
|
|
- switch typ {
|
|
|
|
- case reflect.TypeOf((*Program)(nil)):
|
|
|
|
- p := coll.Programs[name]
|
|
|
|
- if p == nil {
|
|
|
|
- return reflect.Value{}, fmt.Errorf("missing program %q", name)
|
|
|
|
- }
|
|
|
|
- assignedPrograms[name] = struct{}{}
|
|
|
|
- return reflect.ValueOf(p), nil
|
|
|
|
- case reflect.TypeOf((*Map)(nil)):
|
|
|
|
- m := coll.Maps[name]
|
|
|
|
- if m == nil {
|
|
|
|
- return reflect.Value{}, fmt.Errorf("missing map %q", name)
|
|
|
|
- }
|
|
|
|
- assignedMaps[name] = struct{}{}
|
|
|
|
- return reflect.ValueOf(m), nil
|
|
|
|
- default:
|
|
|
|
- return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if err := assignValues(to, valueOf); err != nil {
|
|
|
|
- return err
|
|
|
|
- }
|
|
|
|
|
|
+// structField represents a struct field containing the ebpf struct tag.
|
|
|
|
+type structField struct {
|
|
|
|
+ reflect.StructField
|
|
|
|
+ value reflect.Value
|
|
|
|
+}
|
|
|
|
|
|
- for name := range assignedPrograms {
|
|
|
|
- coll.DetachProgram(name)
|
|
|
|
|
|
+// ebpfFields extracts field names tagged with 'ebpf' from a struct type.
|
|
|
|
+// Keep track of visited types to avoid infinite recursion.
|
|
|
|
+func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]structField, error) {
|
|
|
|
+ if visited == nil {
|
|
|
|
+ visited = make(map[reflect.Type]bool)
|
|
}
|
|
}
|
|
|
|
|
|
- for name := range assignedMaps {
|
|
|
|
- coll.DetachMap(name)
|
|
|
|
|
|
+ structType := structVal.Type()
|
|
|
|
+ if structType.Kind() != reflect.Struct {
|
|
|
|
+ return nil, fmt.Errorf("%s is not a struct", structType)
|
|
}
|
|
}
|
|
|
|
|
|
- return nil
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Value, error)) error {
|
|
|
|
- type structField struct {
|
|
|
|
- reflect.StructField
|
|
|
|
- value reflect.Value
|
|
|
|
|
|
+ if visited[structType] {
|
|
|
|
+ return nil, fmt.Errorf("recursion on type %s", structType)
|
|
}
|
|
}
|
|
|
|
|
|
- var (
|
|
|
|
- fields []structField
|
|
|
|
- visitedTypes = make(map[reflect.Type]bool)
|
|
|
|
- flattenStruct func(reflect.Value) error
|
|
|
|
- )
|
|
|
|
|
|
+ fields := make([]structField, 0, structType.NumField())
|
|
|
|
+ for i := 0; i < structType.NumField(); i++ {
|
|
|
|
+ field := structField{structType.Field(i), structVal.Field(i)}
|
|
|
|
|
|
- flattenStruct = func(structVal reflect.Value) error {
|
|
|
|
- structType := structVal.Type()
|
|
|
|
- if structType.Kind() != reflect.Struct {
|
|
|
|
- return fmt.Errorf("%s is not a struct", structType)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if visitedTypes[structType] {
|
|
|
|
- return fmt.Errorf("recursion on type %s", structType)
|
|
|
|
|
|
+ // If the field is tagged, gather it and move on.
|
|
|
|
+ name := field.Tag.Get("ebpf")
|
|
|
|
+ if name != "" {
|
|
|
|
+ fields = append(fields, field)
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
|
|
|
|
- for i := 0; i < structType.NumField(); i++ {
|
|
|
|
- field := structField{structType.Field(i), structVal.Field(i)}
|
|
|
|
-
|
|
|
|
- name := field.Tag.Get("ebpf")
|
|
|
|
- if name != "" {
|
|
|
|
- fields = append(fields, field)
|
|
|
|
|
|
+ // If the field does not have an ebpf tag, but is a struct or a pointer
|
|
|
|
+ // to a struct, attempt to gather its fields as well.
|
|
|
|
+ var v reflect.Value
|
|
|
|
+ switch field.Type.Kind() {
|
|
|
|
+ case reflect.Ptr:
|
|
|
|
+ if field.Type.Elem().Kind() != reflect.Struct {
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
|
|
|
|
- var err error
|
|
|
|
- switch field.Type.Kind() {
|
|
|
|
- case reflect.Ptr:
|
|
|
|
- if field.Type.Elem().Kind() != reflect.Struct {
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if field.value.IsNil() {
|
|
|
|
- return fmt.Errorf("nil pointer to %s", structType)
|
|
|
|
- }
|
|
|
|
|
|
+ if field.value.IsNil() {
|
|
|
|
+ return nil, fmt.Errorf("nil pointer to %s", structType)
|
|
|
|
+ }
|
|
|
|
|
|
- err = flattenStruct(field.value.Elem())
|
|
|
|
|
|
+ // Obtain the destination type of the pointer.
|
|
|
|
+ v = field.value.Elem()
|
|
|
|
|
|
- case reflect.Struct:
|
|
|
|
- err = flattenStruct(field.value)
|
|
|
|
|
|
+ case reflect.Struct:
|
|
|
|
+ // Reference the value's type directly.
|
|
|
|
+ v = field.value
|
|
|
|
|
|
- default:
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
|
|
+ default:
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
|
|
- if err != nil {
|
|
|
|
- return fmt.Errorf("field %s: %w", field.Name, err)
|
|
|
|
- }
|
|
|
|
|
|
+ inner, err := ebpfFields(v, visited)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, fmt.Errorf("field %s: %w", field.Name, err)
|
|
}
|
|
}
|
|
|
|
|
|
- return nil
|
|
|
|
|
|
+ fields = append(fields, inner...)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ return fields, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// assignValues attempts to populate all fields of 'to' tagged with 'ebpf'.
|
|
|
|
+//
|
|
|
|
+// getValue is called for every tagged field of 'to' and must return the value
|
|
|
|
+// to be assigned to the field with the given typ and name.
|
|
|
|
+func assignValues(to interface{},
|
|
|
|
+ getValue func(typ reflect.Type, name string) (interface{}, error)) error {
|
|
|
|
+
|
|
toValue := reflect.ValueOf(to)
|
|
toValue := reflect.ValueOf(to)
|
|
if toValue.Type().Kind() != reflect.Ptr {
|
|
if toValue.Type().Kind() != reflect.Ptr {
|
|
return fmt.Errorf("%T is not a pointer to struct", to)
|
|
return fmt.Errorf("%T is not a pointer to struct", to)
|
|
@@ -577,7 +624,8 @@ func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Va
|
|
return fmt.Errorf("nil pointer to %T", to)
|
|
return fmt.Errorf("nil pointer to %T", to)
|
|
}
|
|
}
|
|
|
|
|
|
- if err := flattenStruct(toValue.Elem()); err != nil {
|
|
|
|
|
|
+ fields, err := ebpfFields(toValue.Elem(), nil)
|
|
|
|
+ if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
@@ -587,19 +635,23 @@ func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Va
|
|
name string
|
|
name string
|
|
}
|
|
}
|
|
|
|
|
|
- assignedTo := make(map[elem]string)
|
|
|
|
|
|
+ assigned := make(map[elem]string)
|
|
for _, field := range fields {
|
|
for _, field := range fields {
|
|
- name := field.Tag.Get("ebpf")
|
|
|
|
- if strings.Contains(name, ",") {
|
|
|
|
|
|
+ // Get string value the field is tagged with.
|
|
|
|
+ tag := field.Tag.Get("ebpf")
|
|
|
|
+ if strings.Contains(tag, ",") {
|
|
return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name)
|
|
return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name)
|
|
}
|
|
}
|
|
|
|
|
|
- e := elem{field.Type, name}
|
|
|
|
- if assignedField := assignedTo[e]; assignedField != "" {
|
|
|
|
- return fmt.Errorf("field %s: %q was already assigned to %s", field.Name, name, assignedField)
|
|
|
|
|
|
+ // Check if the eBPF object with the requested
|
|
|
|
+ // type and tag was already assigned elsewhere.
|
|
|
|
+ e := elem{field.Type, tag}
|
|
|
|
+ if af := assigned[e]; af != "" {
|
|
|
|
+ return fmt.Errorf("field %s: object %q was already assigned to %s", field.Name, tag, af)
|
|
}
|
|
}
|
|
|
|
|
|
- value, err := valueOf(field.Type, name)
|
|
|
|
|
|
+ // Get the eBPF object referred to by the tag.
|
|
|
|
+ value, err := getValue(field.Type, tag)
|
|
if err != nil {
|
|
if err != nil {
|
|
return fmt.Errorf("field %s: %w", field.Name, err)
|
|
return fmt.Errorf("field %s: %w", field.Name, err)
|
|
}
|
|
}
|
|
@@ -607,9 +659,9 @@ func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Va
|
|
if !field.value.CanSet() {
|
|
if !field.value.CanSet() {
|
|
return fmt.Errorf("field %s: can't set value", field.Name)
|
|
return fmt.Errorf("field %s: can't set value", field.Name)
|
|
}
|
|
}
|
|
|
|
+ field.value.Set(reflect.ValueOf(value))
|
|
|
|
|
|
- field.value.Set(value)
|
|
|
|
- assignedTo[e] = field.Name
|
|
|
|
|
|
+ assigned[e] = field.Name
|
|
}
|
|
}
|
|
|
|
|
|
return nil
|
|
return nil
|