Skip to content

Commit 5bb4c2f

Browse files
authored
collection: Add Assign method
Assign() bridges bpf2go generated code and functionality implemented in Collection. A few things are easier to do in Collection, like iterating through maps and programs. It is harder to guarantee invariants like map and program initialization using reflection as well as being more complicated. Signed-off-by: Daniel Xu <dxu@dxuuu.xyz>
1 parent c58aedd commit 5bb4c2f

File tree

2 files changed

+149
-1
lines changed

2 files changed

+149
-1
lines changed

collection.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,71 @@ func LoadCollection(file string) (*Collection, error) {
695695
return NewCollection(spec)
696696
}
697697

698+
// Assign the contents of a Collection to a struct.
699+
//
700+
// This function bridges functionality between bpf2go generated
701+
// code and any functionality better implemented in Collection.
702+
//
703+
// 'to' must be a pointer to a struct. A field of the
704+
// struct is updated with values from Programs or Maps if it
705+
// has an `ebpf` tag and its type is *Program or *Map.
706+
// The tag's value specifies the name of the program or map as
707+
// found in the CollectionSpec.
708+
//
709+
// struct {
710+
// Foo *ebpf.Program `ebpf:"xdp_foo"`
711+
// Bar *ebpf.Map `ebpf:"bar_map"`
712+
// Ignored int
713+
// }
714+
//
715+
// Returns an error if any of the eBPF objects can't be found, or
716+
// if the same Map or Program is assigned multiple times.
717+
//
718+
// Ownership and Close()ing responsibility is transferred to `to`
719+
// for any successful assigns. On error `to` is left in an undefined state.
720+
func (coll *Collection) Assign(to interface{}) error {
721+
assignedMaps := make(map[string]bool)
722+
assignedProgs := make(map[string]bool)
723+
724+
// Assign() only transfers already-loaded Maps and Programs. No extra
725+
// loading is done.
726+
getValue := func(typ reflect.Type, name string) (interface{}, error) {
727+
switch typ {
728+
729+
case reflect.TypeOf((*Program)(nil)):
730+
if p := coll.Programs[name]; p != nil {
731+
assignedProgs[name] = true
732+
return p, nil
733+
}
734+
return nil, fmt.Errorf("missing program %q", name)
735+
736+
case reflect.TypeOf((*Map)(nil)):
737+
if m := coll.Maps[name]; m != nil {
738+
assignedMaps[name] = true
739+
return m, nil
740+
}
741+
return nil, fmt.Errorf("missing map %q", name)
742+
743+
default:
744+
return nil, fmt.Errorf("unsupported type %s", typ)
745+
}
746+
}
747+
748+
if err := assignValues(to, getValue); err != nil {
749+
return err
750+
}
751+
752+
// Finalize ownership transfer
753+
for p := range assignedProgs {
754+
delete(coll.Programs, p)
755+
}
756+
for m := range assignedMaps {
757+
delete(coll.Maps, m)
758+
}
759+
760+
return nil
761+
}
762+
698763
// Close frees all maps and programs associated with the collection.
699764
//
700765
// The collection mustn't be used afterwards.

collection_test.go

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {
434434
}
435435
}
436436

437-
func TestCollectionAssign(t *testing.T) {
437+
func TestCollectionSpecAssign(t *testing.T) {
438438
var specs struct {
439439
Program *ProgramSpec `ebpf:"prog1"`
440440
Map *MapSpec `ebpf:"map1"`
@@ -559,6 +559,89 @@ func TestAssignValues(t *testing.T) {
559559

560560
}
561561

562+
func TestCollectionAssign(t *testing.T) {
563+
var objs struct {
564+
Program *Program `ebpf:"prog1"`
565+
Map *Map `ebpf:"map1"`
566+
}
567+
568+
cs := &CollectionSpec{
569+
Maps: map[string]*MapSpec{
570+
"map1": {
571+
Type: Array,
572+
KeySize: 4,
573+
ValueSize: 4,
574+
MaxEntries: 1,
575+
},
576+
},
577+
Programs: map[string]*ProgramSpec{
578+
"prog1": {
579+
Type: SocketFilter,
580+
Instructions: asm.Instructions{
581+
asm.LoadImm(asm.R0, 0, asm.DWord),
582+
asm.Return(),
583+
},
584+
License: "MIT",
585+
},
586+
},
587+
}
588+
589+
coll, err := NewCollection(cs)
590+
qt.Assert(t, err, qt.IsNil)
591+
defer coll.Close()
592+
593+
qt.Assert(t, coll.Assign(&objs), qt.IsNil)
594+
defer objs.Program.Close()
595+
defer objs.Map.Close()
596+
597+
// Check that objs has received ownership of map and prog
598+
qt.Assert(t, objs.Program.FD() >= 0, qt.IsTrue)
599+
qt.Assert(t, objs.Map.FD() >= 0, qt.IsTrue)
600+
601+
// Check that the collection has lost ownership
602+
qt.Assert(t, coll.Programs["prog1"], qt.IsNil)
603+
qt.Assert(t, coll.Maps["map1"], qt.IsNil)
604+
}
605+
606+
func TestCollectionAssignFail(t *testing.T) {
607+
// `map2` does not exist
608+
var objs struct {
609+
Program *Program `ebpf:"prog1"`
610+
Map *Map `ebpf:"map2"`
611+
}
612+
613+
cs := &CollectionSpec{
614+
Maps: map[string]*MapSpec{
615+
"map1": {
616+
Type: Array,
617+
KeySize: 4,
618+
ValueSize: 4,
619+
MaxEntries: 1,
620+
},
621+
},
622+
Programs: map[string]*ProgramSpec{
623+
"prog1": {
624+
Type: SocketFilter,
625+
Instructions: asm.Instructions{
626+
asm.LoadImm(asm.R0, 0, asm.DWord),
627+
asm.Return(),
628+
},
629+
License: "MIT",
630+
},
631+
},
632+
}
633+
634+
coll, err := NewCollection(cs)
635+
qt.Assert(t, err, qt.IsNil)
636+
defer coll.Close()
637+
638+
qt.Assert(t, coll.Assign(&objs), qt.IsNotNil)
639+
640+
// Check that the collection has retained ownership
641+
qt.Assert(t, coll.Programs["prog1"], qt.IsNotNil)
642+
qt.Assert(t, coll.Maps["map1"], qt.IsNotNil)
643+
}
644+
562645
func TestIncompleteLoadAndAssign(t *testing.T) {
563646
spec := &CollectionSpec{
564647
Programs: map[string]*ProgramSpec{

0 commit comments

Comments
 (0)