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

     1  package gobpfld
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"syscall"
     8  	"unsafe"
     9  
    10  	"github.com/dylandreimerink/gobpfld/bpfsys"
    11  	bpfSyscall "github.com/dylandreimerink/gobpfld/internal/syscall"
    12  )
    13  
    14  // A MapIterator describes an iterator which can iterate over all keys and values of a map without keeping all
    15  // contents in userspace memory at the same time. Since maps can be constantly updated by a eBPF program
    16  // the results are not guaranteed, expect to read duplicate values or not get all keys. This depends greatly
    17  // on the frequency of change of the map, the type of map (arrays are not effected, hashes are) and speed of
    18  // iteration. It is recommended to quickly iterate over maps and not to change them during iteration to reduce
    19  // these effects.
    20  type MapIterator interface {
    21  	// Init should be called with a key and value pointer to variables which will be used on subsequent calls to
    22  	// Next to set values. The key and value pointers must be compatible with the map.
    23  	// The value of key should not be modified between the first call to Next and discarding of the iterator since
    24  	// it is reused. Doing so may cause skipped entries, duplicate entries, or error opon calling Next.
    25  	Init(key, value interface{}) error
    26  	// Next assigns the next value to the key and value last passed via the Init func.
    27  	// True is returned if key and value was updated.
    28  	// If updated is false and err is nil, all values from the iterator were read.
    29  	// On error a iterator should also be considered empty and can be discarded.
    30  	Next() (updated bool, err error)
    31  }
    32  
    33  // MapIterForEach fully loops over the given iterator, calling the callback for each entry.
    34  // This offers less control but requires less external setup.
    35  //
    36  // MapIterForEach accepts non-pointer values for key and value in which case they will only be used
    37  // for type information. If callback returns an error the iterator will stop iterating and return the error from
    38  // callback. Callback is always invoked with pointer types, even if non-pointer types were supplied to key and value.
    39  func MapIterForEach(iter MapIterator, key, value interface{}, callback func(key, value interface{}) error) error {
    40  	// Key can be nil for some map types like stacks an queues
    41  	if key != nil {
    42  		// If the key is not a pointer
    43  		if reflect.TypeOf(key).Kind() != reflect.Ptr {
    44  			// Create a new value with the same type as 'key' and set 'key' to its pointer
    45  			key = reflect.New(reflect.TypeOf(key)).Interface()
    46  		}
    47  	}
    48  
    49  	// / If the value is not a pointer
    50  	if reflect.TypeOf(value).Kind() != reflect.Ptr {
    51  		// Create a new value with the same type as 'value' and set 'value' to its pointer
    52  		value = reflect.New(reflect.TypeOf(value)).Interface()
    53  	}
    54  
    55  	err := iter.Init(key, value)
    56  	if err != nil {
    57  		return fmt.Errorf("init: %w", err)
    58  	}
    59  
    60  	var updated bool
    61  	for {
    62  		updated, err = iter.Next()
    63  		if err != nil || !updated {
    64  			break
    65  		}
    66  
    67  		err = callback(key, value)
    68  		if err != nil {
    69  			return fmt.Errorf("callback: %w", err)
    70  		}
    71  	}
    72  	if err != nil {
    73  		return fmt.Errorf("next: %w", err)
    74  	}
    75  
    76  	return nil
    77  }
    78  
    79  var _ MapIterator = (*singleLookupIterator)(nil)
    80  
    81  type errIterator struct {
    82  	err error
    83  }
    84  
    85  func (ei *errIterator) Init(key, value interface{}) error {
    86  	return ei.err
    87  }
    88  
    89  func (ei *errIterator) Next() (updated bool, err error) {
    90  	return false, ei.err
    91  }
    92  
    93  // ErrIteratorDone indicates that Next has been called on an iterator which is done iterating
    94  var ErrIteratorDone = errors.New("iterator is done")
    95  
    96  var _ MapIterator = (*singleLookupIterator)(nil)
    97  
    98  // singleLookupIterator uses the MapGetNextKey and MapLookupElem commands to iterate over a map.
    99  // This is very widely supported but not the fastest option.
   100  type singleLookupIterator struct {
   101  	// The map over which to iterate
   102  	BPFMap BPFMap
   103  
   104  	// clone of the map so it can't change during iteration
   105  	am    *AbstractMap
   106  	key   uintptr
   107  	value uintptr
   108  	attr  bpfsys.BPFAttrMapElem
   109  	done  bool
   110  }
   111  
   112  func (sli *singleLookupIterator) Init(key, value interface{}) error {
   113  	if sli.BPFMap == nil {
   114  		return fmt.Errorf("BPFMap may not be nil")
   115  	}
   116  
   117  	// Copy the important features of the map so they are imutable from
   118  	// outside the package during iteration.
   119  	sli.am = &AbstractMap{
   120  		Name:       sli.BPFMap.GetName(),
   121  		loaded:     sli.BPFMap.IsLoaded(),
   122  		fd:         sli.BPFMap.GetFD(),
   123  		Definition: sli.BPFMap.GetDefinition(),
   124  	}
   125  
   126  	sli.attr.MapFD = sli.am.fd
   127  
   128  	var err error
   129  	sli.key, err = sli.am.toKeyPtr(key)
   130  	if err != nil {
   131  		return fmt.Errorf("toKeyPtr: %w", err)
   132  	}
   133  
   134  	sli.value, err = sli.am.toValuePtr(value)
   135  	if err != nil {
   136  		return fmt.Errorf("toValuePtr: %w", err)
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  // Next gets the key and value at the current location and writes them to the pointers given to the iterator
   143  // during initialization. It then advances the internal pointer to the next key and value.
   144  // If the iterator can't get the key and value at the current location since we are done iterating or an error
   145  // was encountered 'updated' is false.
   146  func (sli *singleLookupIterator) Next() (updated bool, err error) {
   147  	if sli.am == nil {
   148  		return false, fmt.Errorf("iterator not initialized")
   149  	}
   150  
   151  	if sli.done {
   152  		return false, ErrIteratorDone
   153  	}
   154  
   155  	sli.attr.Value_NextKey = sli.key
   156  
   157  	err = bpfsys.MapGetNextKey(&sli.attr)
   158  	if err != nil {
   159  		sli.done = true
   160  		if sysErr, ok := err.(*bpfSyscall.Error); ok && sysErr.Errno == syscall.ENOENT {
   161  			return false, nil
   162  		}
   163  
   164  		return false, fmt.Errorf("map get next: %w", err)
   165  	}
   166  
   167  	sli.attr.Key = sli.attr.Value_NextKey
   168  	sli.attr.Value_NextKey = sli.value
   169  
   170  	err = bpfsys.MapLookupElem(&sli.attr)
   171  	if err != nil {
   172  		sli.done = true
   173  		return false, fmt.Errorf("map lookup: %w", err)
   174  	}
   175  
   176  	return true, nil
   177  }
   178  
   179  var _ MapIterator = (*batchLookupIterator)(nil)
   180  
   181  type batchLookupIterator struct {
   182  	// The map over which to iterate
   183  	BPFMap BPFMap
   184  	// Size of the buffer, bigger buffers are more cpu efficient but takeup more memory
   185  	BufSize int
   186  
   187  	// clone of BPFMap so it can't change during iteration
   188  	am *AbstractMap
   189  	// clone of BufSize so it can't change during iteration
   190  	bufSize int
   191  
   192  	// pointer to key
   193  	key reflect.Value
   194  	// pointer to value
   195  	value reflect.Value
   196  	// slice of keys
   197  	keyBuf reflect.Value
   198  	// slice of values
   199  	valueBuf reflect.Value
   200  	inBatch  uint64
   201  	outBatch uint64
   202  	attr     bpfsys.BPFAttrMapBatch
   203  
   204  	// Offset into the buffers
   205  	off int
   206  	// Length of the buffer, which may be smaller than bufSize if the kernel
   207  	// returned less then bufSize of entries
   208  	bufLen int
   209  
   210  	done    bool
   211  	mapDone bool
   212  }
   213  
   214  // According to benchmarks 1024 is a good sweetspot between memory usage and speed
   215  const defaultBufSize = 1024
   216  
   217  func (bli *batchLookupIterator) Init(key, value interface{}) error {
   218  	if bli.BPFMap == nil {
   219  		return fmt.Errorf("BPFMap may not be nil")
   220  	}
   221  
   222  	bli.bufSize = bli.BufSize
   223  	if bli.bufSize == 0 {
   224  		bli.bufSize = defaultBufSize
   225  	}
   226  
   227  	// Copy the important features of the map so they are imutable from
   228  	// outside the package during iteration.
   229  	bli.am = &AbstractMap{
   230  		Name:       bli.BPFMap.GetName(),
   231  		loaded:     bli.BPFMap.IsLoaded(),
   232  		fd:         bli.BPFMap.GetFD(),
   233  		Definition: bli.BPFMap.GetDefinition(),
   234  	}
   235  
   236  	keyType := reflect.TypeOf(key)
   237  	if keyType.Kind() != reflect.Ptr {
   238  		return fmt.Errorf("key argument must be a pointer")
   239  	}
   240  
   241  	if keyType.Elem().Size() != uintptr(bli.am.Definition.KeySize) {
   242  		return fmt.Errorf(
   243  			"key type size(%d) doesn't match size of bfp key(%d)",
   244  			keyType.Elem().Size(),
   245  			bli.am.Definition.KeySize,
   246  		)
   247  	}
   248  
   249  	bli.key = reflect.ValueOf(key)
   250  	bli.keyBuf = reflect.New(reflect.ArrayOf(bli.bufSize, keyType.Elem()))
   251  
   252  	valueType := reflect.TypeOf(value)
   253  	if keyType.Kind() != reflect.Ptr {
   254  		return fmt.Errorf("value argument must be a pointer")
   255  	}
   256  
   257  	bli.value = reflect.ValueOf(value)
   258  	bli.valueBuf = reflect.New(reflect.ArrayOf(bli.bufSize, valueType.Elem()))
   259  
   260  	bli.attr = bpfsys.BPFAttrMapBatch{
   261  		MapFD:    bli.am.fd,
   262  		OutBatch: uintptr(unsafe.Pointer(&bli.outBatch)),
   263  		Keys:     bli.keyBuf.Pointer(),
   264  		Values:   bli.valueBuf.Pointer(),
   265  		Count:    uint32(bli.bufSize),
   266  	}
   267  
   268  	return nil
   269  }
   270  
   271  // Next gets the key and value at the current location and writes them to the pointers given to the iterator
   272  // during initialization. It then advances the internal pointer to the next key and value.
   273  // If the iterator can't get the key and value at the current location since we are done iterating or an error
   274  // was encountered 'updated' is false.
   275  func (bli *batchLookupIterator) Next() (updated bool, err error) {
   276  	if bli.am == nil {
   277  		return false, fmt.Errorf("iterator not initialized")
   278  	}
   279  
   280  	if bli.done {
   281  		return false, ErrIteratorDone
   282  	}
   283  
   284  	// If the buffer has never been filled or we have read until the end of the buffer
   285  	if bli.bufLen == 0 || bli.off >= bli.bufLen {
   286  		// If the current buffer was the last the map had to offer
   287  		// and we are done reading that buffer, the iterator is done
   288  		if bli.mapDone {
   289  			bli.done = true
   290  			return false, nil
   291  		}
   292  
   293  		err = bpfsys.MapLookupBatch(&bli.attr)
   294  		if err != nil {
   295  			sysErr, ok := err.(*bpfSyscall.Error)
   296  			if !ok || sysErr.Errno != syscall.ENOENT {
   297  				return false, err
   298  			}
   299  
   300  			bli.mapDone = true
   301  		}
   302  
   303  		// Reset offset since we will start reading from the start of the buffer again
   304  		bli.off = 0
   305  		bli.bufLen = int(bli.attr.Count)
   306  
   307  		if bli.bufLen == 0 {
   308  			bli.done = true
   309  			return false, nil
   310  		}
   311  
   312  		// Set the address of the in batch, only applicable after the first run
   313  		if bli.attr.InBatch == 0 {
   314  			bli.attr.InBatch = uintptr(unsafe.Pointer(&bli.inBatch))
   315  		}
   316  
   317  		bli.inBatch = bli.outBatch
   318  	}
   319  
   320  	// Change the underlaying value of 'value' to valueBuf[bli.off]
   321  	bli.value.Elem().Set(bli.valueBuf.Elem().Index(bli.off))
   322  	// Change the underlaying value of 'key' to keyBuf[bli.off]
   323  	bli.key.Elem().Set(bli.keyBuf.Elem().Index(bli.off))
   324  
   325  	// Increment the offset
   326  	bli.off++
   327  
   328  	return true, nil
   329  }
   330  
   331  var _ MapIterator = (*mmappedIterator)(nil)
   332  
   333  // mmappedIterator is a special iterator which can loop over mmapped(memory mapped) maps.
   334  // This will use the mmapped memory instread of syscalls which improves performance, but only works on array maps which
   335  // were loaded with the bpftypes.BPFMapFlagsMMapable flag.
   336  type mmappedIterator struct {
   337  	am      *ArrayMap
   338  	nextKey uint32
   339  	key     *uint32
   340  	value   uintptr
   341  }
   342  
   343  // Init should be called with a key and value pointer to variables which will be used on subsequent calls to
   344  // Next to set values. The key and value pointers must be compatible with the map.
   345  // The value of key should not be modified between the first call to Next and discarding of the iterator since
   346  // it is reused. Doing so may cause skipped entries, duplicate entries, or error opon calling Next.
   347  func (mmi *mmappedIterator) Init(key, value interface{}) error {
   348  	if mmi.am == nil {
   349  		return fmt.Errorf("array map may not be nil")
   350  	}
   351  
   352  	if ikey, ok := key.(*uint32); ok {
   353  		mmi.key = ikey
   354  	} else {
   355  		return fmt.Errorf("key must be an uint32 key")
   356  	}
   357  
   358  	mmi.nextKey = 0
   359  
   360  	var err error
   361  	mmi.value, err = mmi.am.toValuePtr(value)
   362  	if err != nil {
   363  		return fmt.Errorf("toValuePtr: %w", err)
   364  	}
   365  
   366  	return nil
   367  }
   368  
   369  // Next assignes the next value to the key and value last passed via the Init func.
   370  // True is returned if key and value was updated.
   371  // If updated is false and err is nil, all values from the iterator were read.
   372  // On error a iterator should also be considered empty and can be discarded.
   373  func (mmi *mmappedIterator) Next() (updated bool, err error) {
   374  	if mmi.am.Definition.MaxEntries == mmi.nextKey {
   375  		// Use next key to double as an 'done' indicator
   376  		mmi.nextKey++
   377  		return false, nil
   378  	}
   379  
   380  	if mmi.am.Definition.MaxEntries < mmi.nextKey {
   381  		return false, ErrIteratorDone
   382  	}
   383  
   384  	*mmi.key = mmi.nextKey
   385  	mmi.nextKey++
   386  
   387  	// We construct a fake slice of bytes with the memory address that was given.
   388  	// We need to do this so we can copy the memory, even if the value isn't an slice type
   389  	dstHdr := reflect.SliceHeader{
   390  		Data: mmi.value,
   391  		Len:  int(mmi.am.Definition.ValueSize),
   392  		Cap:  int(mmi.am.Definition.ValueSize),
   393  	}
   394  	//nolint:govet // should be fine if we make sure len and cap are set correctly and the slice doesn't exit scope
   395  	dstSlice := *(*[]byte)(unsafe.Pointer(&dstHdr))
   396  
   397  	start := int(*mmi.key * mmi.am.Definition.ValueSize)
   398  	end := int((*mmi.key + 1) * mmi.am.Definition.ValueSize)
   399  	copy(dstSlice, mmi.am.memoryMapped[start:end])
   400  
   401  	return true, nil
   402  }
   403  
   404  var _ MapIterator = (*singleMapLookupIterator)(nil)
   405  
   406  // singleMapLookupIterator uses the MapGetNextKey and MapLookupElem commands to iterate over a map to get map file
   407  // descriptors. It then uses the MapFromFD function to turn these file descriptors into BPFMap's an assinging them
   408  // to the value pointer. It is a specialized iterator type for array of maps and hash of maps type maps.
   409  type singleMapLookupIterator struct {
   410  	// The map over which to iterate
   411  	BPFMap BPFMap
   412  
   413  	// clone of the map so it can't change during iteration
   414  	am    *AbstractMap
   415  	key   uintptr
   416  	value *BPFMap
   417  	id    uint32
   418  	attr  bpfsys.BPFAttrMapElem
   419  	done  bool
   420  }
   421  
   422  func (sli *singleMapLookupIterator) Init(key, value interface{}) error {
   423  	if sli.BPFMap == nil {
   424  		return fmt.Errorf("BPFMap may not be nil")
   425  	}
   426  
   427  	// Copy the important features of the map so they are imutable from
   428  	// outside the package during iteration.
   429  	sli.am = &AbstractMap{
   430  		Name:       sli.BPFMap.GetName(),
   431  		loaded:     sli.BPFMap.IsLoaded(),
   432  		fd:         sli.BPFMap.GetFD(),
   433  		Definition: sli.BPFMap.GetDefinition(),
   434  	}
   435  
   436  	sli.attr.MapFD = sli.am.fd
   437  
   438  	var err error
   439  	sli.key, err = sli.am.toKeyPtr(key)
   440  	if err != nil {
   441  		return fmt.Errorf("toKeyPtr: %w", err)
   442  	}
   443  
   444  	mPtr, ok := value.(*BPFMap)
   445  	if !ok {
   446  		return fmt.Errorf("value is not of type *BPFMap")
   447  	}
   448  	sli.value = mPtr
   449  
   450  	return nil
   451  }
   452  
   453  // Next gets the key and value at the current location and writes them to the pointers given to the iterator
   454  // during initialization. It then advances the internal pointer to the next key and value.
   455  // If the iterator can't get the key and value at the current location since we are done iterating or an error
   456  // was encountered 'updated' is false.
   457  func (sli *singleMapLookupIterator) Next() (updated bool, err error) {
   458  	if sli.am == nil {
   459  		return false, fmt.Errorf("iterator not initialized")
   460  	}
   461  
   462  	if sli.done {
   463  		return false, ErrIteratorDone
   464  	}
   465  
   466  	sli.attr.Value_NextKey = sli.key
   467  
   468  	err = bpfsys.MapGetNextKey(&sli.attr)
   469  	if err != nil {
   470  		sli.done = true
   471  		if sysErr, ok := err.(*bpfSyscall.Error); ok && sysErr.Errno == syscall.ENOENT {
   472  			return false, nil
   473  		}
   474  
   475  		return false, err
   476  	}
   477  
   478  	sli.attr.Key = sli.attr.Value_NextKey
   479  	sli.attr.Value_NextKey = uintptr(unsafe.Pointer(&sli.id))
   480  
   481  	err = bpfsys.MapLookupElem(&sli.attr)
   482  	if err != nil {
   483  		sli.done = true
   484  		return false, fmt.Errorf("map lookup elem: %w", err)
   485  	}
   486  
   487  	*sli.value, err = MapFromID(sli.id)
   488  	if err != nil {
   489  		sli.done = true
   490  		return false, fmt.Errorf("map from fd: %w", err)
   491  	}
   492  
   493  	return true, nil
   494  }
   495  
   496  // lookupAndDeleteIterator uses the MapLookupAndDeleteElem commands to iterate over a map, delete all values in the
   497  // process. Using in stack and queue maps
   498  type lookupAndDeleteIterator struct {
   499  	// The map over which to iterate
   500  	BPFMap BPFMap
   501  
   502  	// clone of the map so it can't change during iteration
   503  	am   *AbstractMap
   504  	attr bpfsys.BPFAttrMapElem
   505  	done bool
   506  }
   507  
   508  func (ldi *lookupAndDeleteIterator) Init(key, value interface{}) error {
   509  	if ldi.BPFMap == nil {
   510  		return fmt.Errorf("BPFMap may not be nil")
   511  	}
   512  
   513  	// Copy the important features of the map so they are imutable from
   514  	// outside the package during iteration.
   515  	ldi.am = &AbstractMap{
   516  		Name:       ldi.BPFMap.GetName(),
   517  		loaded:     ldi.BPFMap.IsLoaded(),
   518  		fd:         ldi.BPFMap.GetFD(),
   519  		Definition: ldi.BPFMap.GetDefinition(),
   520  	}
   521  
   522  	ldi.attr.MapFD = ldi.am.fd
   523  
   524  	var err error
   525  	ldi.attr.Value_NextKey, err = ldi.am.toValuePtr(value)
   526  	if err != nil {
   527  		return fmt.Errorf("toValuePtr: %w", err)
   528  	}
   529  
   530  	return nil
   531  }
   532  
   533  // Next gets the next key from the map
   534  func (ldi *lookupAndDeleteIterator) Next() (updated bool, err error) {
   535  	if ldi.am == nil {
   536  		return false, fmt.Errorf("iterator not initialized")
   537  	}
   538  
   539  	if ldi.done {
   540  		return false, ErrIteratorDone
   541  	}
   542  
   543  	err = bpfsys.MapLookupAndDeleteElement(&ldi.attr)
   544  	if err != nil {
   545  		ldi.done = true
   546  		if sysErr, ok := err.(*bpfSyscall.Error); ok && sysErr.Errno == syscall.ENOENT {
   547  			return false, nil
   548  		}
   549  
   550  		return false, fmt.Errorf("map lookup and delete: %w", err)
   551  	}
   552  
   553  	return true, nil
   554  }