-
Notifications
You must be signed in to change notification settings - Fork 2
/
map_abstract.go
817 lines (680 loc) · 22.2 KB
/
map_abstract.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
package gobpfld
import (
"fmt"
"reflect"
"runtime"
"syscall"
"unsafe"
"github.com/dylandreimerink/gobpfld/bpfsys"
"github.com/dylandreimerink/gobpfld/bpftypes"
bpfSyscall "github.com/dylandreimerink/gobpfld/internal/syscall"
"github.com/dylandreimerink/gobpfld/kernelsupport"
)
// AbstractMap is a base struct which implements BPFMap however it lacks any features for interacting
// with the map, these need to be implemented by a specific map type which can embed this type to reduce
// code dupplication. This type is exported so users of the library can also embed this struct in application
// specific implementation.
type AbstractMap struct {
// The name of map. This value is passed to the kernel, it is limited to 15 characters. Its use is limited
// and mostly to aid diagnostic tools which inspect the BPF subsystem. For primary identification the ID or FD
// should be used.
Name ObjName
// Definition describes the properties of this map
Definition BPFMapDef
// A reference to the BTF which contains the type of this map.
BTF *BTF
// The type of the map.
BTFMapType BTFMap
// Initial contents of the map, set just after loading
InitialData map[interface{}]interface{}
// definition is an unexported copy of Definition which will be pinned as soon as the map is loaded
// to prevent the user from chaning the definition while the map is loaded.
definition BPFMapDef
loaded bool
fd bpfsys.BPFfd
}
func (m *AbstractMap) GetBTF() *BTF {
return m.BTF
}
func (m *AbstractMap) GetBTFMapType() BTFMap {
return m.BTFMapType
}
func (m *AbstractMap) GetInitialData() map[interface{}]interface{} {
return m.InitialData
}
// Load validates and loads the userspace map definition into the kernel.
func (m *AbstractMap) load(changeAttr func(attr *bpfsys.BPFAttrMapCreate)) error {
err := m.Definition.Validate()
if err != nil {
return err
}
attr := &bpfsys.BPFAttrMapCreate{
MapType: m.Definition.Type,
KeySize: m.Definition.KeySize,
ValueSize: m.Definition.ValueSize,
MaxEntries: m.Definition.MaxEntries,
MapFlags: m.Definition.Flags,
}
if kernelsupport.CurrentFeatures.API.Has(kernelsupport.KFeatAPIMapName) {
attr.MapName = m.Name.GetCstr()
}
// If BTF info is available and the current kernel supports it
if m.BTF != nil && kernelsupport.CurrentFeatures.API.Has(kernelsupport.KFeatAPIBTFLoad) {
// Load BTF if not already loaded
if !m.BTF.loaded {
var log string
log, err = m.BTF.Load(BTFLoadOpts{
LogLevel: bpftypes.BPFLogLevelVerbose,
})
if err != nil {
return fmt.Errorf("load BTF: %w\nLog: %s", err, log)
}
}
btfFd, err := m.BTF.Fd()
if err != nil {
return fmt.Errorf("get BTF fd: %w", err)
}
attr.BTFFD = btfFd
if m.BTFMapType.Key != nil && m.BTFMapType.Value != nil {
attr.BTFKeyTypeID = uint32(m.BTFMapType.Key.GetID())
attr.BTFValueTypeID = uint32(m.BTFMapType.Value.GetID())
}
}
if changeAttr != nil {
changeAttr(attr)
}
m.fd, err = bpfsys.MapCreate(attr)
if err != nil {
return fmt.Errorf("bpf syscall error: %w", err)
}
// Copy exported definition to internal definition so it we always have a copy of the loaded definition which
// the user can't change while loaded.
m.definition = m.Definition
m.loaded = true
return nil
}
// Unload closes the file descriptor associate with the map, this will cause the map to close from the kernel
// if it is not still in use by a eBPF program, bpf FS, or a userspace program still holding a fd to the map.
func (m *AbstractMap) close() error {
err := m.fd.Close()
if err != nil {
return fmt.Errorf("error while closing fd: %w", err)
}
m.fd = 0
m.loaded = false
return nil
}
// Pin pins the map to a location in the bpf filesystem, since the file system now also holds a reference
// to the map the original creator of the map can terminate without triggering the map to be closed as well.
// A map can be unpinned from the bpf FS by another process thus transferring it or persisting it across
// multiple runs of the same program.
func (m *AbstractMap) Pin(relativePath string) error {
if !m.loaded {
return fmt.Errorf("can't pin an unloaded map")
}
return PinFD(relativePath, m.fd)
}
// Unpin captures the file descriptor of the map at the given 'relativePath' from the kernel.
// The definition in this map must match the definition of the pinned map, otherwise this function
// will return an error since mismatched definitions might cause seemingly unrelated bugs in other functions.
// If 'deletePin' is true the bpf FS pin will be removed after successfully loading the map, thus transferring
// ownership of the map in a scenario where the map is not shared between multiple programs.
// Otherwise the pin will keep existing which will cause the map to not be deleted when this program exits.
func (m *AbstractMap) Unpin(relativePath string, deletePin bool) error {
if m.loaded {
return fmt.Errorf("can't unpin a map since it is already loaded")
}
var err error
m.fd, err = UnpinFD(relativePath, deletePin)
if err != nil {
return fmt.Errorf("unpin error: %w", err)
}
pinnedMapDef := BPFMapDef{}
err = bpfsys.ObjectGetInfoByFD(&bpfsys.BPFAttrGetInfoFD{
BPFFD: m.fd,
Info: uintptr(unsafe.Pointer(&pinnedMapDef)),
InfoLen: uint32(bpfMapDefSize),
})
if err != nil {
return fmt.Errorf("bpf obj get info by fd syscall error: %w", err)
}
// Since other functions use the definition for userspace checks we need to make sure
// that the map def in the kernel is the same a the one in userspace.
// The other approach would be to just match the userspace definition to the one in the kernel
// but if this AbstractMap is embedded in a specialized map and we unpin a generic map by accident
// it could result in strange bugs, so this is more fool proof but less user automatic.
// Map types embedding the AbstractType should define their own constructor functions which can
// make a map from a pinned map path.
if m.Definition.Equal(pinnedMapDef) {
// Getting the map from the FS created a new file descriptor. Close it so the kernel knows we will
// not be using it. If we leak the FD the map will never close.
fdErr := m.fd.Close()
if fdErr != nil {
return fmt.Errorf("pinned map definition doesn't match definition of current map, "+
"new fd wasn't closed: %w", err)
}
return fmt.Errorf("pinned map definition doesn't match definition of current map")
}
// Copy exported definition to internal definition so it we always have a copy of the loaded definition which
// the user can't change while loaded.
m.definition = m.Definition
m.loaded = true
return nil
}
func (m *AbstractMap) IsLoaded() bool {
return m.loaded
}
func (m *AbstractMap) GetName() ObjName {
return m.Name
}
func (m *AbstractMap) GetFD() bpfsys.BPFfd {
return m.fd
}
func (m *AbstractMap) GetDefinition() BPFMapDef {
// If the map is loaded we will return the internal version of definition since we know it will not be modified
// to avoid misuse of the library
if m.loaded {
return m.definition
}
return m.Definition
}
// get uses reflection to to dynamically get a k/v pair from any map as long as the sizes of the key and value match
// the map definition.
func (m *AbstractMap) get(key interface{}, value interface{}) error {
if !m.loaded {
return fmt.Errorf("can't read from an unloaded map")
}
// Return a human readable error since the kernel will not allow us to read from the map anyway
if m.Definition.Flags&bpftypes.BPFMapFlagsWriteOnly != 0 {
return fmt.Errorf("can't read from map since the 'write only' flag is set")
}
attr := &bpfsys.BPFAttrMapElem{
MapFD: m.fd,
}
var err error
// key can be nil for some map types which don't have keys like stacks and queues
if key != nil {
attr.Key, err = m.toKeyPtr(key)
if err != nil {
return err
}
}
attr.Value_NextKey, err = m.toValuePtr(value)
if err != nil {
return err
}
err = bpfsys.MapLookupElem(attr)
if err != nil {
return fmt.Errorf("bpf syscall error: %w", err)
}
return nil
}
// toKeyPtr checks if 'key' is a pointer to a type which has the same
// size in memory as the key of the eBPF map.
func (m *AbstractMap) toKeyPtr(key interface{}) (uintptr, error) {
keyType := reflect.TypeOf(key)
if keyType.Kind() != reflect.Ptr {
return 0, fmt.Errorf("key argument must be a pointer")
}
if keyType.Elem().Size() != uintptr(m.Definition.KeySize) {
return 0, fmt.Errorf(
"key type size(%d) doesn't match size of bpf key(%d)",
keyType.Elem().Size(),
m.Definition.KeySize,
)
}
return reflect.ValueOf(key).Pointer(), nil
}
// toValuePtr checks if 'value' is a pointer to a type which has the same
// size in memory as the value of the eBPF map.
func (m *AbstractMap) toValuePtr(value interface{}) (uintptr, error) {
valueType := reflect.TypeOf(value)
if valueType.Kind() != reflect.Ptr {
return 0, fmt.Errorf("value argument must be a pointer")
}
elem := valueType.Elem()
// If the map type is a per CPU map, the value must be an array
// or slice with at least as much elements as the system has CPU cores
if m.isPerCPUMap() {
switch elem.Kind() {
case reflect.Array:
arrayElem := elem.Elem()
if arrayElem.Size() != uintptr(m.Definition.ValueSize) {
return 0, fmt.Errorf(
"value array element type size(%d) doesn't match size of bpf value(%d)",
arrayElem.Size(),
m.Definition.ValueSize,
)
}
if elem.Len() < numCPUs {
return 0, fmt.Errorf(
"value argument must be a pointer to an array or slice containing at least %d elements"+
" given array only has %d elements",
numCPUs,
elem.Len(),
)
}
return reflect.ValueOf(value).Pointer(), nil
case reflect.Slice:
sliceElemType := elem.Elem()
if sliceElemType.Size() != uintptr(m.Definition.ValueSize) {
return 0, fmt.Errorf(
"value slice element type size(%d) doesn't match size of bpf value(%d)",
sliceElemType.Size(),
m.Definition.ValueSize,
)
}
sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(reflect.ValueOf(value).Pointer()))
if sliceHdr.Len < numCPUs {
return 0, fmt.Errorf(
"value argument must be a pointer to an array or slice containing at least %d elements"+
" given slice only has %d elements",
numCPUs,
sliceHdr.Len,
)
}
return sliceHdr.Data, nil
default:
return 0, fmt.Errorf(
"value argument must be a pointer to an array or slice containing at least %d elements",
numCPUs,
)
}
}
switch elem.Kind() {
case reflect.Array:
// If an array was passed, make sure the total size of the array is equal to the size of the BPF value
arrayElem := elem.Elem()
totalSize := uint32(arrayElem.Size()) * uint32(elem.Len())
if totalSize != m.Definition.ValueSize {
return 0, fmt.Errorf(
"value array total size(%d) doesn't match size of bpf value size(%d)",
totalSize,
m.Definition.ValueSize,
)
}
return reflect.ValueOf(value).Pointer(), nil
case reflect.Slice:
// If a slice was passed, make sure the total size of the slice is equal to the size of the BPF value.
// And return a pointer to the underlying array
sliceElemType := elem.Elem()
sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(reflect.ValueOf(value).Pointer()))
totalSize := uint32(sliceHdr.Len) * uint32(sliceElemType.Size())
if totalSize != m.Definition.ValueSize {
return 0, fmt.Errorf(
"value slice total size(%d) doesn't match size of bpf value size(%d)",
totalSize,
m.Definition.ValueSize,
)
}
return sliceHdr.Data, nil
default:
if elem.Size() != uintptr(m.Definition.ValueSize) {
return 0, fmt.Errorf(
"value type size(%d) doesn't match size of bpf value(%d)",
elem.Size(),
m.Definition.ValueSize,
)
}
return reflect.ValueOf(value).Pointer(), nil
}
}
// getBatch fills the keys and values array/slice with the keys and values inside the map up to a maximum of
// maxBatchSize entries. The keys and values array/slice must have at least a length of maxBatchSize.
// The key and value of an entry is has the same index, so for example the value for keys[2] is in values[2].
// Count is the amount of entries returns, partial is true if not all elements of keys and values could be set.
//
// This function is intended for small maps which can be read into userspace all at once since
// getBatch can only read from the beginning of the map. If the map is to large to read all at once
// a iterator should be used instead of the get or getBatch function.
func (m *AbstractMap) getBatch(
keys interface{},
values interface{},
maxBatchSize uint32,
) (
count int,
partial bool,
err error,
) {
if !m.loaded {
return 0, false, fmt.Errorf("can't read from an unloaded map")
}
// Return a human readable error since the kernel will not allow us to read from the map anyway
if m.Definition.Flags&bpftypes.BPFMapFlagsWriteOnly != 0 {
return 0, false, fmt.Errorf("can't read from map since the 'write only' flag is set")
}
var batch uint64
attr := &bpfsys.BPFAttrMapBatch{
MapFD: m.fd,
OutBatch: uintptr(unsafe.Pointer(&batch)),
Count: maxBatchSize,
}
attr.Keys, err = m.toBatchKeysPtr(keys, maxBatchSize)
if err != nil {
return 0, false, err
}
attr.Values, err = m.toBatchValuesPtr(values, maxBatchSize)
if err != nil {
return 0, false, err
}
err = bpfsys.MapLookupBatch(attr)
if err != nil {
// A ENOENT is not an acutal error, the kernel uses it to signal there is no more data after this batch
if sysErr, ok := err.(*bpfSyscall.Error); ok && sysErr.Errno == syscall.ENOENT {
return int(attr.Count), true, nil
}
return 0, false, fmt.Errorf("bpf syscall error: %w", err)
}
return int(attr.Count), false, nil
}
func (m *AbstractMap) set(key interface{}, value interface{}, flags bpfsys.BPFAttrMapElemFlags) error {
if !m.loaded {
return fmt.Errorf("can't write to an unloaded map")
}
attr := &bpfsys.BPFAttrMapElem{
MapFD: m.fd,
Flags: flags,
}
var err error
// Key can be nil if the map type has no key like stacks and queues
if key != nil {
attr.Key, err = m.toKeyPtr(key)
if err != nil {
return err
}
}
attr.Value_NextKey, err = m.toValuePtr(value)
if err != nil {
return err
}
err = bpfsys.MapUpdateElem(attr)
if err != nil {
return fmt.Errorf("bpf syscall error: %w", err)
}
return nil
}
func (m *AbstractMap) lookupAndDelete(key interface{}, value interface{}, flags bpfsys.BPFAttrMapElemFlags) error {
if !m.loaded {
return fmt.Errorf("can't write to an unloaded map")
}
attr := &bpfsys.BPFAttrMapElem{
MapFD: m.fd,
Flags: flags,
}
var err error
// Key can be nil if the map type has no key like stacks and queues
if key != nil {
attr.Key, err = m.toKeyPtr(key)
if err != nil {
return err
}
}
attr.Value_NextKey, err = m.toValuePtr(value)
if err != nil {
return err
}
err = bpfsys.MapLookupAndDeleteElement(attr)
if err != nil {
return fmt.Errorf("bpf syscall error: %w", err)
}
return nil
}
func (m *AbstractMap) setBatch(
keys interface{},
values interface{},
flags bpfsys.BPFAttrMapElemFlags,
maxBatchSize uint32,
) (
count int,
err error,
) {
if !m.loaded {
return 0, fmt.Errorf("can't write to an unloaded map")
}
var batch uint64
attr := &bpfsys.BPFAttrMapBatch{
MapFD: m.fd,
OutBatch: uintptr(unsafe.Pointer(&batch)),
Count: maxBatchSize,
Flags: flags,
// TODO ElemFlags is only used for the spinlock flag, for which we will add support later
}
attr.Keys, err = m.toBatchKeysPtr(keys, maxBatchSize)
if err != nil {
return 0, err
}
attr.Values, err = m.toBatchValuesPtr(values, maxBatchSize)
if err != nil {
return 0, err
}
err = bpfsys.MapUpdateBatch(attr)
if err != nil {
return 0, fmt.Errorf("bpf syscall error: %w", err)
}
return int(attr.Count), nil
}
func (m *AbstractMap) delete(key interface{}) error {
if !m.loaded {
return fmt.Errorf("can't delete elements in an unloaded map")
}
if m.isArrayMap() {
return fmt.Errorf("can't delete elements from an array type map")
}
attr := &bpfsys.BPFAttrMapElem{
MapFD: m.fd,
}
var err error
attr.Key, err = m.toKeyPtr(key)
if err != nil {
return err
}
err = bpfsys.MapDeleteElem(attr)
if err != nil {
return fmt.Errorf("bpf syscall error: %w", err)
}
return nil
}
func (m *AbstractMap) deleteBatch(
keys interface{},
maxBatchSize uint32,
) (
count int,
err error,
) {
if !m.loaded {
return 0, fmt.Errorf("can't delete elements in an unloaded map")
}
if m.isArrayMap() {
return 0, fmt.Errorf("can't delete elements from an array type map")
}
var batch uint64
attr := &bpfsys.BPFAttrMapBatch{
MapFD: m.fd,
OutBatch: uintptr(unsafe.Pointer(&batch)),
Count: maxBatchSize,
}
attr.Keys, err = m.toBatchKeysPtr(keys, maxBatchSize)
if err != nil {
return 0, err
}
err = bpfsys.MapDeleteBatch(attr)
if err != nil {
return 0, fmt.Errorf("bpf syscall error: %w", err)
}
return int(attr.Count), nil
}
func (m *AbstractMap) getAndDelete(key interface{}, value interface{}) error {
if !m.loaded {
return fmt.Errorf("can't read from an unloaded map")
}
if m.isArrayMap() {
return fmt.Errorf("can't delete elements from an array type map")
}
attr := &bpfsys.BPFAttrMapElem{
MapFD: m.fd,
}
var err error
attr.Key, err = m.toKeyPtr(key)
if err != nil {
return err
}
attr.Value_NextKey, err = m.toValuePtr(value)
if err != nil {
return err
}
err = bpfsys.MapLookupAndDeleteElement(attr)
if err != nil {
return fmt.Errorf("bpf syscall error: %w", err)
}
return nil
}
func (m *AbstractMap) getAndDeleteBatch(
keys interface{},
values interface{},
maxBatchSize uint32,
) (
count int,
err error,
) {
if !m.loaded {
return 0, fmt.Errorf("can't read from an unloaded map")
}
if m.isArrayMap() {
return 0, fmt.Errorf("can't delete elements from an array type map")
}
var batch uint64
attr := &bpfsys.BPFAttrMapBatch{
MapFD: m.fd,
OutBatch: uintptr(unsafe.Pointer(&batch)),
Count: maxBatchSize,
}
attr.Keys, err = m.toBatchKeysPtr(keys, maxBatchSize)
if err != nil {
return 0, err
}
attr.Values, err = m.toBatchValuesPtr(values, maxBatchSize)
if err != nil {
return 0, err
}
err = bpfsys.MapLookupBatchAndDelete(attr)
if err != nil {
return 0, fmt.Errorf("bpf syscall error: %w", err)
}
return int(attr.Count), nil
}
// toBatchKeysPtr checks if 'keys' is a pointer to a array or slice of at least enough elements to hold
// all keys in one batch and that the type of this array has the same memory size as the eBPF map key.
func (m *AbstractMap) toBatchKeysPtr(keys interface{}, maxBatchSize uint32) (uintptr, error) {
keyType := reflect.TypeOf(keys)
if keyType.Kind() != reflect.Ptr {
return 0, fmt.Errorf("keys argument must be a pointer")
}
elem := keyType.Elem()
switch elem.Kind() {
case reflect.Array:
arrayElem := elem.Elem()
if arrayElem.Size() != uintptr(m.Definition.KeySize) {
return 0, fmt.Errorf(
"keys array element type size(%d) doesn't match size of bpf key(%d)",
arrayElem.Size(),
m.Definition.KeySize,
)
}
if elem.Len() < int(maxBatchSize) {
return 0, fmt.Errorf(
"keys argument must be a pointer to an array or slice containing at least %d elements"+
" given array only has %d elements",
maxBatchSize,
elem.Len(),
)
}
return reflect.ValueOf(elem).Pointer(), nil
case reflect.Slice:
sliceElemType := elem.Elem()
if sliceElemType.Size() != uintptr(m.Definition.KeySize) {
return 0, fmt.Errorf(
"keys slice element type size(%d) doesn't match size of bpf key(%d)",
sliceElemType.Size(),
m.Definition.KeySize,
)
}
sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(reflect.ValueOf(keys).Pointer()))
if sliceHdr.Len < int(maxBatchSize) {
return 0, fmt.Errorf(
"keys argument must be a pointer to an array or slice containing at least %d elements"+
" given slice only has %d elements",
maxBatchSize,
sliceHdr.Len,
)
}
return sliceHdr.Data, nil
default:
return 0, fmt.Errorf("keys argument must be a pointer to an array or slice")
}
}
// toBatchValuesPtr checks if 'values' is a pointer to a array or slice of at least enough elements to hold
// all value in one batch and that the type of this array has the same memory size as the eBPF map key.
// If the map type is an per-cpu type the array/slice size is multiplied by the CPU count
func (m *AbstractMap) toBatchValuesPtr(values interface{}, maxBatchSize uint32) (uintptr, error) {
valuesType := reflect.TypeOf(values)
if valuesType.Kind() != reflect.Ptr {
return 0, fmt.Errorf("values argument must be a pointer")
}
elem := valuesType.Elem()
// If the map type is a per CPU map type we need to multiply the batch size by the CPU count
// Since per CPU types will return a separate value for each CPU
if m.isPerCPUMap() {
maxBatchSize = maxBatchSize * uint32(numCPUs)
}
switch elem.Kind() {
case reflect.Array:
arrayElem := elem.Elem()
if arrayElem.Size() != uintptr(m.Definition.ValueSize) {
return 0, fmt.Errorf(
"values array element type size(%d) doesn't match size of bpf value(%d)",
arrayElem.Size(),
m.Definition.ValueSize,
)
}
if elem.Len() < int(maxBatchSize) {
return 0, fmt.Errorf(
"values argument must be a pointer to an array or slice containing at least %d elements"+
" given array only has %d elements",
maxBatchSize,
elem.Len(),
)
}
return reflect.ValueOf(elem).Pointer(), nil
case reflect.Slice:
sliceElemType := elem.Elem()
if sliceElemType.Size() != uintptr(m.Definition.ValueSize) {
return 0, fmt.Errorf(
"values slice element type size(%d) doesn't match size of bpf value(%d)",
sliceElemType.Size(),
m.Definition.ValueSize,
)
}
sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(reflect.ValueOf(values).Pointer()))
if sliceHdr.Len < int(maxBatchSize) {
return 0, fmt.Errorf(
"values argument must be a pointer to an array or slice containing at least %d elements"+
" given slice only has %d elements",
maxBatchSize,
sliceHdr.Len,
)
}
return sliceHdr.Data, nil
default:
return 0, fmt.Errorf("values argument must be a pointer to an array or slice")
}
}
var numCPUs = runtime.NumCPU()
func (m *AbstractMap) isPerCPUMap() bool {
return m.Definition.Type == bpftypes.BPF_MAP_TYPE_PERCPU_ARRAY ||
m.Definition.Type == bpftypes.BPF_MAP_TYPE_PERCPU_HASH ||
m.Definition.Type == bpftypes.BPF_MAP_TYPE_LRU_PERCPU_HASH
}
func (m *AbstractMap) isArrayMap() bool {
return m.Definition.Type == bpftypes.BPF_MAP_TYPE_ARRAY ||
m.Definition.Type == bpftypes.BPF_MAP_TYPE_PERCPU_ARRAY
}