github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/map_abstract.go (about)

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