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

     1  package gobpfld
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"syscall"
     8  	"unsafe"
     9  
    10  	"github.com/dylandreimerink/gobpfld/bpfsys"
    11  	"github.com/dylandreimerink/gobpfld/bpftypes"
    12  	"github.com/dylandreimerink/gobpfld/kernelsupport"
    13  	"golang.org/x/sys/unix"
    14  )
    15  
    16  var _ BPFMap = (*ArrayMap)(nil)
    17  
    18  // ArrayMap is a map which has a integer key from 0 to MaxEntries. It is a generic map type so the value can be any
    19  // type.
    20  type ArrayMap struct {
    21  	AbstractMap
    22  
    23  	memoryMapped []byte
    24  }
    25  
    26  func (m *ArrayMap) Load() error {
    27  	if m.Definition.Type != bpftypes.BPF_MAP_TYPE_ARRAY {
    28  		return fmt.Errorf("map type in definition must be BPF_MAP_TYPE_ARRAY when using an ArrayMap")
    29  	}
    30  
    31  	// If the mmapable flag is set
    32  	mmapable := m.Definition.Flags&bpftypes.BPFMapFlagsMMapable != 0
    33  
    34  	if mmapable && !kernelsupport.CurrentFeatures.API.Has(kernelsupport.KFeatAPIMapMMap) {
    35  		return fmt.Errorf("flag bpftypes.BPFMapFlagsMMapable set, but current kernel version doesn't support mmapping")
    36  	}
    37  
    38  	err := m.load(nil)
    39  	if err != nil {
    40  		return fmt.Errorf("error while loading map: %w", err)
    41  	}
    42  
    43  	err = mapRegister.add(m)
    44  	if err != nil {
    45  		return fmt.Errorf("map register: %w", err)
    46  	}
    47  
    48  	// From bpf_map_mmap_sz in libbpf
    49  	valueSize := m.Definition.ValueSize
    50  	if valueSize < 8 {
    51  		valueSize = 8
    52  	}
    53  	mmapLen := int(valueSize * m.Definition.MaxEntries)
    54  	pz := os.Getpagesize()
    55  	if mmapLen < pz {
    56  		mmapLen = pz
    57  	}
    58  
    59  	if mmapable {
    60  		// This first mmap allocates memory which is not yet mapped to the BPF map yet.
    61  		m.memoryMapped, err = syscall.Mmap(
    62  			-1,
    63  			0,
    64  			mmapLen,
    65  			unix.PROT_READ|unix.PROT_WRITE,
    66  			unix.MAP_SHARED|unix.MAP_ANONYMOUS,
    67  		)
    68  		if err != nil {
    69  			return fmt.Errorf("mmap array map: %w", err)
    70  		}
    71  
    72  		// Set mmap prot based on the map flags
    73  		prot := unix.PROT_READ | unix.PROT_WRITE
    74  		if m.Definition.Flags&bpftypes.BPFMapFlagsWriteOnly != 0 {
    75  			prot = unix.PROT_WRITE
    76  		}
    77  		if m.Definition.Flags&bpftypes.BPFMapFlagsReadOnly != 0 {
    78  			prot = unix.PROT_READ
    79  		}
    80  
    81  		// Remap the same byteslice but this time attach it to the FD of the BPF map/
    82  		// I don't really know why this needs to be done in 2 steps, setting the FD in the first mmap doesn't work.
    83  		// Since libbpf does it like this, and it works, we will keep it.
    84  		_, err = mmap(
    85  			(*reflect.SliceHeader)(unsafe.Pointer(&m.memoryMapped)).Data,
    86  			uintptr(len(m.memoryMapped)),
    87  			prot,
    88  			unix.MAP_SHARED|unix.MAP_FIXED,
    89  			int(m.fd),
    90  			0,
    91  		)
    92  		if err != nil {
    93  			return fmt.Errorf("mmap array map: %w", err)
    94  		}
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // Close closes the file descriptor associate with the map, this will cause the map to unload from the kernel
   101  // if it is not still in use by a eBPF program, bpf FS, or a userspace program still holding a fd to the map.
   102  func (m *ArrayMap) Close() error {
   103  	err := mapRegister.delete(m)
   104  	if err != nil {
   105  		return fmt.Errorf("map register: %w", err)
   106  	}
   107  
   108  	if m.memoryMapped != nil {
   109  		err := syscall.Munmap(m.memoryMapped)
   110  		if err != nil {
   111  			return fmt.Errorf("error while munmapping array memory: %w", err)
   112  		}
   113  
   114  		m.memoryMapped = nil
   115  	}
   116  
   117  	return m.close()
   118  }
   119  
   120  func (m *ArrayMap) Get(key uint32, value interface{}) error {
   121  	// If the map is not mmapped we need to use regular syscall's to get the value
   122  	if m.memoryMapped == nil {
   123  		return m.get(&key, value)
   124  	}
   125  
   126  	// In this case, we the map is mmapped so we can just access the memory without syscalls.
   127  
   128  	if key >= m.Definition.MaxEntries {
   129  		return fmt.Errorf("key is outside of map bounds")
   130  	}
   131  
   132  	destAddr, err := m.toValuePtr(value)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	// We construct a fake slice of bytes with the memory address that was given.
   138  	// We need to do this so we can copy the memory, even if the value isn't an slice type
   139  	dstHdr := reflect.SliceHeader{
   140  		Data: destAddr,
   141  		Len:  int(m.Definition.ValueSize),
   142  		Cap:  int(m.Definition.ValueSize),
   143  	}
   144  	//nolint:govet // should be fine if we make sure len and cap are set correctly and the slice doesn't exit scope
   145  	dstSlice := *(*[]byte)(unsafe.Pointer(&dstHdr))
   146  
   147  	start := int(key * m.Definition.ValueSize)
   148  	end := int((key + 1) * m.Definition.ValueSize)
   149  	copy(dstSlice, m.memoryMapped[start:end])
   150  
   151  	return nil
   152  }
   153  
   154  // GetBatch fills the keys slice and values array/slice with the keys and values inside the map.
   155  // The keys slice and values array/slice must have the same length. The key and value of an entry is has the same
   156  // index, so for example the value for keys[2] is in values[2]. Count is the amount of entries returns,
   157  // partial is true if not all elements of keys and values could be set.
   158  //
   159  // This function is intended for small maps which can be read into userspace all at once since
   160  // GetBatch can only read from the beginning of the map. If the map is to large to read all at once
   161  // a iterator should be used instead of the Get or GetBatch function.
   162  func (m *ArrayMap) GetBatch(
   163  	keys []uint32,
   164  	values interface{},
   165  ) (
   166  	count int,
   167  	partial bool,
   168  	err error,
   169  ) {
   170  	keysLen := len(keys)
   171  
   172  	// Very unlikely, but we have to check
   173  	if keysLen > maxUint32 {
   174  		return 0, false, fmt.Errorf("max len of 'keys' allowed is %d", maxUint32)
   175  	}
   176  
   177  	// If the map is not mmapped we need to use regular syscall's to get the values
   178  	if m.memoryMapped == nil {
   179  		return m.getBatch(&keys, values, uint32(keysLen))
   180  	}
   181  
   182  	// In this case, we the map is mmapped so we can just access the memory without syscalls.
   183  
   184  	dstAddr, err := m.toBatchValuesPtr(values, uint32(keysLen))
   185  	if err != nil {
   186  		return 0, false, err
   187  	}
   188  
   189  	valueSize := int(m.Definition.ValueSize)
   190  
   191  	// We construct a fake slice of bytes with the memory address that was given.
   192  	// We need to do this so we can copy the memory, even if the value isn't an slice type
   193  	dstHdr := reflect.SliceHeader{
   194  		Data: dstAddr,
   195  		Len:  valueSize * keysLen,
   196  		Cap:  valueSize * keysLen,
   197  	}
   198  	//nolint:govet // should be fine if we make sure len and cap are set correctly and the slice doesn't exit scope
   199  	dstSlice := *(*[]byte)(unsafe.Pointer(&dstHdr))
   200  
   201  	// Set keys to the indexes
   202  	for i := 0; i < keysLen; i++ {
   203  		keys[i] = uint32(i)
   204  	}
   205  
   206  	// Copy until dstSlice is full or we have read the whole map
   207  	bytesCopied := copy(dstSlice, m.memoryMapped[:int(m.Definition.MaxEntries)*valueSize])
   208  
   209  	return bytesCopied / valueSize, (bytesCopied / valueSize) < keysLen, nil
   210  }
   211  
   212  func (m *ArrayMap) Set(key uint32, value interface{}, flags bpfsys.BPFAttrMapElemFlags) error {
   213  	// If the map is not mmapped we need to use regular syscall's to set the value
   214  	if m.memoryMapped == nil {
   215  		return m.set(&key, value, flags)
   216  	}
   217  
   218  	// In this case, we the map is mmapped so we can just access the memory without syscalls.
   219  
   220  	if key >= m.Definition.MaxEntries {
   221  		return fmt.Errorf("key is outside of map bounds")
   222  	}
   223  
   224  	srcAddr, err := m.toValuePtr(value)
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	// We construct a fake slice of bytes with the memory address that was given.
   230  	// We need to do this so we can copy the memory, even if the value isn't an slice type
   231  	srcHdr := reflect.SliceHeader{
   232  		Data: srcAddr,
   233  		Len:  int(m.Definition.ValueSize),
   234  		Cap:  int(m.Definition.ValueSize),
   235  	}
   236  	//nolint:govet // should be fine if we make sure len and cap are set correctly and the slice doesn't exit scope
   237  	srcSlice := *(*[]byte)(unsafe.Pointer(&srcHdr))
   238  
   239  	start := int(key * m.Definition.ValueSize)
   240  	end := int((key + 1) * m.Definition.ValueSize)
   241  	copy(m.memoryMapped[start:end], srcSlice)
   242  
   243  	return nil
   244  }
   245  
   246  func (m *ArrayMap) SetBatch(
   247  	keys []uint32,
   248  	values interface{},
   249  	flags bpfsys.BPFAttrMapElemFlags,
   250  ) (
   251  	count int,
   252  	err error,
   253  ) {
   254  	keysLen := len(keys)
   255  
   256  	// Very unlikely, but we have to check
   257  	if keysLen > maxUint32 {
   258  		return 0, fmt.Errorf("max len of 'keys' allowed is %d", maxUint32)
   259  	}
   260  
   261  	// If the map is not mmapped we need to use regular syscall's to set the values
   262  	if m.memoryMapped == nil {
   263  		return m.setBatch(&keys, values, flags, uint32(keysLen))
   264  	}
   265  
   266  	// In this case, we the map is mmapped so we can just access the memory without syscalls.
   267  
   268  	srcAddr, err := m.toBatchValuesPtr(values, uint32(keysLen))
   269  	if err != nil {
   270  		return 0, err
   271  	}
   272  
   273  	valueSize := int(m.Definition.ValueSize)
   274  
   275  	// We construct a fake slice of bytes with the memory address that was given.
   276  	// We need to do this so we can copy the memory, even if the value isn't an slice type
   277  	srcHdr := reflect.SliceHeader{
   278  		Data: srcAddr,
   279  		Len:  valueSize * keysLen,
   280  		Cap:  valueSize * keysLen,
   281  	}
   282  	//nolint:govet // should be fine if we make sure len and cap are set correctly and the slice doesn't exit scope
   283  	srcSlice := *(*[]byte)(unsafe.Pointer(&srcHdr))
   284  
   285  	for i, key := range keys {
   286  		// Out of bounds key will cause panics when trying to get that offset in the slice
   287  		if key >= m.Definition.MaxEntries {
   288  			return i, fmt.Errorf("key index is out of bounds, max key: %d", m.Definition.MaxEntries-1)
   289  		}
   290  
   291  		mmStart := int(key) * valueSize
   292  		mmEnd := int(key+1) * valueSize
   293  		srcStart := i * valueSize
   294  		srcEnd := (i + 1) * valueSize
   295  		copy(m.memoryMapped[mmStart:mmEnd], srcSlice[srcStart:srcEnd])
   296  	}
   297  
   298  	return keysLen, nil
   299  }
   300  
   301  func (m *ArrayMap) Iterator() MapIterator {
   302  	// If the array map is mmapped, using the MMappedIterator is the fastest option
   303  	if m.memoryMapped != nil {
   304  		return &mmappedIterator{
   305  			am: m,
   306  		}
   307  	}
   308  
   309  	// If the kernel doesn't have support for batch lookup, use single lookup
   310  	if !kernelsupport.CurrentFeatures.API.Has(kernelsupport.KFeatAPIMapBatchOps) {
   311  		return &singleLookupIterator{
   312  			BPFMap: m,
   313  		}
   314  	}
   315  
   316  	// If there is no reason not to use the batch lookup iterator, use it
   317  	return &batchLookupIterator{
   318  		BPFMap: m,
   319  	}
   320  }