1package ebpf
2
3import (
4	"errors"
5	"fmt"
6	"math"
7	"reflect"
8	"strings"
9
10	"github.com/cilium/ebpf/asm"
11	"github.com/cilium/ebpf/internal"
12	"github.com/cilium/ebpf/internal/btf"
13)
14
15// CollectionOptions control loading a collection into the kernel.
16//
17// Maps and Programs are passed to NewMapWithOptions and NewProgramsWithOptions.
18type CollectionOptions struct {
19	Maps     MapOptions
20	Programs ProgramOptions
21}
22
23// CollectionSpec describes a collection.
24type CollectionSpec struct {
25	Maps     map[string]*MapSpec
26	Programs map[string]*ProgramSpec
27}
28
29// Copy returns a recursive copy of the spec.
30func (cs *CollectionSpec) Copy() *CollectionSpec {
31	if cs == nil {
32		return nil
33	}
34
35	cpy := CollectionSpec{
36		Maps:     make(map[string]*MapSpec, len(cs.Maps)),
37		Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
38	}
39
40	for name, spec := range cs.Maps {
41		cpy.Maps[name] = spec.Copy()
42	}
43
44	for name, spec := range cs.Programs {
45		cpy.Programs[name] = spec.Copy()
46	}
47
48	return &cpy
49}
50
51// RewriteMaps replaces all references to specific maps.
52//
53// Use this function to use pre-existing maps instead of creating new ones
54// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps.
55//
56// Returns an error if a named map isn't used in at least one program.
57func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
58	for symbol, m := range maps {
59		// have we seen a program that uses this symbol / map
60		seen := false
61		fd := m.FD()
62		for progName, progSpec := range cs.Programs {
63			err := progSpec.Instructions.RewriteMapPtr(symbol, fd)
64
65			switch {
66			case err == nil:
67				seen = true
68
69			case asm.IsUnreferencedSymbol(err):
70				// Not all programs need to use the map
71
72			default:
73				return fmt.Errorf("program %s: %w", progName, err)
74			}
75		}
76
77		if !seen {
78			return fmt.Errorf("map %s not referenced by any programs", symbol)
79		}
80
81		// Prevent NewCollection from creating rewritten maps
82		delete(cs.Maps, symbol)
83	}
84
85	return nil
86}
87
88// RewriteConstants replaces the value of multiple constants.
89//
90// The constant must be defined like so in the C program:
91//
92//    static volatile const type foobar;
93//    static volatile const type foobar = default;
94//
95// Replacement values must be of the same length as the C sizeof(type).
96// If necessary, they are marshalled according to the same rules as
97// map values.
98//
99// From Linux 5.5 the verifier will use constants to eliminate dead code.
100//
101// Returns an error if a constant doesn't exist.
102func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
103	rodata := cs.Maps[".rodata"]
104	if rodata == nil {
105		return errors.New("missing .rodata section")
106	}
107
108	if rodata.BTF == nil {
109		return errors.New(".rodata section has no BTF")
110	}
111
112	if n := len(rodata.Contents); n != 1 {
113		return fmt.Errorf("expected one key in .rodata, found %d", n)
114	}
115
116	kv := rodata.Contents[0]
117	value, ok := kv.Value.([]byte)
118	if !ok {
119		return fmt.Errorf("first value in .rodata is %T not []byte", kv.Value)
120	}
121
122	buf := make([]byte, len(value))
123	copy(buf, value)
124
125	err := patchValue(buf, btf.MapValue(rodata.BTF), consts)
126	if err != nil {
127		return err
128	}
129
130	rodata.Contents[0] = MapKV{kv.Key, buf}
131	return nil
132}
133
134// Assign the contents of a collection spec to a struct.
135//
136// This function is a short-cut to manually checking the presence
137// of maps and programs in a collection spec.
138//
139// The argument to must be a pointer to a struct. A field of the
140// struct is updated with values from Programs or Maps if it
141// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
142// The tag gives the name of the program or map as found in
143// the CollectionSpec.
144//
145//    struct {
146//        Foo     *ebpf.ProgramSpec `ebpf:"xdp_foo"`
147//        Bar     *ebpf.MapSpec     `ebpf:"bar_map"`
148//        Ignored int
149//    }
150//
151// Returns an error if any of the fields can't be found, or
152// if the same map or program is assigned multiple times.
153func (cs *CollectionSpec) Assign(to interface{}) error {
154	valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
155		switch typ {
156		case reflect.TypeOf((*ProgramSpec)(nil)):
157			p := cs.Programs[name]
158			if p == nil {
159				return reflect.Value{}, fmt.Errorf("missing program %q", name)
160			}
161			return reflect.ValueOf(p), nil
162		case reflect.TypeOf((*MapSpec)(nil)):
163			m := cs.Maps[name]
164			if m == nil {
165				return reflect.Value{}, fmt.Errorf("missing map %q", name)
166			}
167			return reflect.ValueOf(m), nil
168		default:
169			return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
170		}
171	}
172
173	return assignValues(to, valueOf)
174}
175
176// LoadAndAssign creates a collection from a spec, and assigns it to a struct.
177//
178// See Collection.Assign for details.
179func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error {
180	if opts == nil {
181		opts = &CollectionOptions{}
182	}
183
184	coll, err := NewCollectionWithOptions(cs, *opts)
185	if err != nil {
186		return err
187	}
188	defer coll.Close()
189
190	return coll.Assign(to)
191}
192
193// Collection is a collection of Programs and Maps associated
194// with their symbols
195type Collection struct {
196	Programs map[string]*Program
197	Maps     map[string]*Map
198}
199
200// NewCollection creates a Collection from a specification.
201//
202// Only maps referenced by at least one of the programs are initialized.
203func NewCollection(spec *CollectionSpec) (*Collection, error) {
204	return NewCollectionWithOptions(spec, CollectionOptions{})
205}
206
207// NewCollectionWithOptions creates a Collection from a specification.
208//
209// Only maps referenced by at least one of the programs are initialized.
210func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (coll *Collection, err error) {
211	var (
212		maps  = make(map[string]*Map)
213		progs = make(map[string]*Program)
214		btfs  = make(map[*btf.Spec]*btf.Handle)
215	)
216
217	defer func() {
218		for _, btf := range btfs {
219			btf.Close()
220		}
221
222		if err == nil {
223			return
224		}
225
226		for _, m := range maps {
227			m.Close()
228		}
229
230		for _, p := range progs {
231			p.Close()
232		}
233	}()
234
235	loadBTF := func(spec *btf.Spec) (*btf.Handle, error) {
236		if btfs[spec] != nil {
237			return btfs[spec], nil
238		}
239
240		handle, err := btf.NewHandle(spec)
241		if err != nil {
242			return nil, err
243		}
244
245		btfs[spec] = handle
246		return handle, nil
247	}
248
249	for mapName, mapSpec := range spec.Maps {
250		var handle *btf.Handle
251		if mapSpec.BTF != nil {
252			handle, err = loadBTF(btf.MapSpec(mapSpec.BTF))
253			if err != nil && !errors.Is(err, btf.ErrNotSupported) {
254				return nil, err
255			}
256		}
257
258		m, err := newMapWithBTF(mapSpec, handle, opts.Maps)
259		if err != nil {
260			return nil, fmt.Errorf("map %s: %w", mapName, err)
261		}
262		maps[mapName] = m
263	}
264
265	for progName, origProgSpec := range spec.Programs {
266		progSpec := origProgSpec.Copy()
267
268		// Rewrite any reference to a valid map.
269		for i := range progSpec.Instructions {
270			ins := &progSpec.Instructions[i]
271
272			if ins.OpCode != asm.LoadImmOp(asm.DWord) || ins.Reference == "" {
273				continue
274			}
275
276			if uint32(ins.Constant) != math.MaxUint32 {
277				// Don't overwrite maps already rewritten, users can
278				// rewrite programs in the spec themselves
279				continue
280			}
281
282			m := maps[ins.Reference]
283			if m == nil {
284				return nil, fmt.Errorf("program %s: missing map %s", progName, ins.Reference)
285			}
286
287			fd := m.FD()
288			if fd < 0 {
289				return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
290			}
291			if err := ins.RewriteMapPtr(m.FD()); err != nil {
292				return nil, fmt.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
293			}
294		}
295
296		var handle *btf.Handle
297		if progSpec.BTF != nil {
298			handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF))
299			if err != nil && !errors.Is(err, btf.ErrNotSupported) {
300				return nil, err
301			}
302		}
303
304		prog, err := newProgramWithBTF(progSpec, handle, opts.Programs)
305		if err != nil {
306			return nil, fmt.Errorf("program %s: %w", progName, err)
307		}
308		progs[progName] = prog
309	}
310
311	return &Collection{
312		progs,
313		maps,
314	}, nil
315}
316
317// LoadCollection parses an object file and converts it to a collection.
318func LoadCollection(file string) (*Collection, error) {
319	spec, err := LoadCollectionSpec(file)
320	if err != nil {
321		return nil, err
322	}
323	return NewCollection(spec)
324}
325
326// Close frees all maps and programs associated with the collection.
327//
328// The collection mustn't be used afterwards.
329func (coll *Collection) Close() {
330	for _, prog := range coll.Programs {
331		prog.Close()
332	}
333	for _, m := range coll.Maps {
334		m.Close()
335	}
336}
337
338// DetachMap removes the named map from the Collection.
339//
340// This means that a later call to Close() will not affect this map.
341//
342// Returns nil if no map of that name exists.
343func (coll *Collection) DetachMap(name string) *Map {
344	m := coll.Maps[name]
345	delete(coll.Maps, name)
346	return m
347}
348
349// DetachProgram removes the named program from the Collection.
350//
351// This means that a later call to Close() will not affect this program.
352//
353// Returns nil if no program of that name exists.
354func (coll *Collection) DetachProgram(name string) *Program {
355	p := coll.Programs[name]
356	delete(coll.Programs, name)
357	return p
358}
359
360// Assign the contents of a collection to a struct.
361//
362// `to` must be a pointer to a struct like the following:
363//
364//    struct {
365//        Foo     *ebpf.Program `ebpf:"xdp_foo"`
366//        Bar     *ebpf.Map     `ebpf:"bar_map"`
367//        Ignored int
368//    }
369//
370// See CollectionSpec.Assign for the semantics of this function.
371//
372// DetachMap and DetachProgram is invoked for all assigned elements
373// if the function is successful.
374func (coll *Collection) Assign(to interface{}) error {
375	assignedMaps := make(map[string]struct{})
376	assignedPrograms := make(map[string]struct{})
377	valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
378		switch typ {
379		case reflect.TypeOf((*Program)(nil)):
380			p := coll.Programs[name]
381			if p == nil {
382				return reflect.Value{}, fmt.Errorf("missing program %q", name)
383			}
384			assignedPrograms[name] = struct{}{}
385			return reflect.ValueOf(p), nil
386		case reflect.TypeOf((*Map)(nil)):
387			m := coll.Maps[name]
388			if m == nil {
389				return reflect.Value{}, fmt.Errorf("missing map %q", name)
390			}
391			assignedMaps[name] = struct{}{}
392			return reflect.ValueOf(m), nil
393		default:
394			return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
395		}
396	}
397
398	if err := assignValues(to, valueOf); err != nil {
399		return err
400	}
401
402	for name := range assignedPrograms {
403		coll.DetachProgram(name)
404	}
405
406	for name := range assignedMaps {
407		coll.DetachMap(name)
408	}
409
410	return nil
411}
412
413func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Value, error)) error {
414	v := reflect.ValueOf(to)
415	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
416		return fmt.Errorf("%T is not a pointer to a struct", to)
417	}
418
419	type elem struct {
420		typ  reflect.Type
421		name string
422	}
423
424	var (
425		s          = v.Elem()
426		sT         = s.Type()
427		assignedTo = make(map[elem]string)
428	)
429	for i := 0; i < sT.NumField(); i++ {
430		field := sT.Field(i)
431
432		name := field.Tag.Get("ebpf")
433		if name == "" {
434			continue
435		}
436		if strings.Contains(name, ",") {
437			return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name)
438		}
439
440		e := elem{field.Type, name}
441		if assignedField := assignedTo[e]; assignedField != "" {
442			return fmt.Errorf("field %s: %q was already assigned to %s", field.Name, name, assignedField)
443		}
444
445		value, err := valueOf(field.Type, name)
446		if err != nil {
447			return fmt.Errorf("field %s: %w", field.Name, err)
448		}
449
450		fieldValue := s.Field(i)
451		if !fieldValue.CanSet() {
452			return fmt.Errorf("can't set value of field %s", field.Name)
453		}
454
455		fieldValue.Set(value)
456		assignedTo[e] = field.Name
457	}
458
459	return nil
460}
461