github.com/cilium/cilium@v1.16.2/pkg/bpf/ops_linux.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package bpf
     5  
     6  import (
     7  	"context"
     8  	"encoding"
     9  	"errors"
    10  	"reflect"
    11  	"unsafe"
    12  
    13  	"github.com/cilium/ebpf"
    14  	"github.com/cilium/statedb"
    15  	"github.com/cilium/statedb/reconciler"
    16  	"k8s.io/apimachinery/pkg/util/sets"
    17  )
    18  
    19  // ErrMapNotOpened is returned when the MapOps is used with a BPF map that is not open yet.
    20  // In general this should be avoided and the map should be opened in a start hook before
    21  // the reconciler. If the map won't be used then the reconciler should not be started.
    22  var ErrMapNotOpened = errors.New("BPF map has not been opened")
    23  
    24  // KeyValue is the interface that an BPF map value object must implement.
    25  //
    26  // The object can either store the key and value directly in struct form
    27  // and use StructBinaryMarshaler{}, or it can implement conversion to binary
    28  // form on the fly by implementing BinaryMarshaler by hand.
    29  type KeyValue interface {
    30  	BinaryKey() encoding.BinaryMarshaler
    31  	BinaryValue() encoding.BinaryMarshaler
    32  }
    33  
    34  // StructBinaryMarshaler implements a BinaryMarshaler for a struct of
    35  // primitive fields. Same caviats apply as with cilium/ebpf when using a
    36  // struct as key or value.
    37  // Example usage:
    38  //
    39  //	func (x *X) Key() encoding.BinaryMarshaler {
    40  //	  return StructBinaryMarshaler{x}
    41  //	}
    42  type StructBinaryMarshaler struct {
    43  	Target any // pointer to struct
    44  }
    45  
    46  func (m StructBinaryMarshaler) MarshalBinary() ([]byte, error) {
    47  	v := reflect.ValueOf(m.Target)
    48  	size := int(v.Type().Elem().Size())
    49  	return unsafe.Slice((*byte)(v.UnsafePointer()), size), nil
    50  }
    51  
    52  type mapOps[KV KeyValue] struct {
    53  	m *Map
    54  }
    55  
    56  func NewMapOps[KV KeyValue](m *Map) reconciler.Operations[KV] {
    57  	ops := &mapOps[KV]{m}
    58  	return ops
    59  }
    60  
    61  func (ops *mapOps[KV]) withMap(do func(m *ebpf.Map) error) error {
    62  	ops.m.lock.RLock()
    63  	defer ops.m.lock.RUnlock()
    64  	if ops.m.m == nil {
    65  		return ErrMapNotOpened
    66  	}
    67  	return do(ops.m.m)
    68  }
    69  
    70  // Delete implements reconciler.Operations.
    71  func (ops *mapOps[KV]) Delete(ctx context.Context, txn statedb.ReadTxn, entry KV) error {
    72  	return ops.withMap(func(m *ebpf.Map) error {
    73  		err := ops.m.m.Delete(entry.BinaryKey())
    74  		if errors.Is(err, ebpf.ErrKeyNotExist) {
    75  			// Silently ignore deletions of non-existing keys.
    76  			return nil
    77  		}
    78  		return err
    79  	})
    80  }
    81  
    82  type keyIterator struct {
    83  	m          *ebpf.Map
    84  	nextKey    []byte
    85  	err        error
    86  	maxEntries uint32
    87  }
    88  
    89  func (it *keyIterator) Err() error {
    90  	return it.err
    91  }
    92  
    93  func (it *keyIterator) Next() []byte {
    94  	if it.maxEntries == 0 {
    95  		return nil
    96  	}
    97  	var key []byte
    98  	if it.nextKey == nil {
    99  		key, it.err = it.m.NextKeyBytes(nil)
   100  	} else {
   101  		key, it.err = it.m.NextKeyBytes(it.nextKey)
   102  	}
   103  	if key == nil || it.err != nil {
   104  		return nil
   105  	}
   106  	it.nextKey = key
   107  	it.maxEntries--
   108  	return key
   109  }
   110  
   111  func (ops *mapOps[KV]) toStringKey(kv KV) string {
   112  	key, _ := kv.BinaryKey().MarshalBinary()
   113  	return string(key)
   114  }
   115  
   116  // Prune BPF map values that do not exist in the table.
   117  func (ops *mapOps[KV]) Prune(ctx context.Context, txn statedb.ReadTxn, iter statedb.Iterator[KV]) error {
   118  	return ops.withMap(func(m *ebpf.Map) error {
   119  		desiredKeys := sets.New(statedb.Collect(statedb.Map(iter, func(kv KV) string { return ops.toStringKey(kv) }))...)
   120  
   121  		// We need to collect the keys to prune first, as it is not safe to
   122  		// delete entries while iterating
   123  		keysToPrune := [][]byte{}
   124  		mapIter := &keyIterator{m, nil, nil, m.MaxEntries()}
   125  		for key := mapIter.Next(); key != nil; key = mapIter.Next() {
   126  			if !desiredKeys.Has(string(key)) {
   127  				keysToPrune = append(keysToPrune, key)
   128  			}
   129  		}
   130  
   131  		var errs []error
   132  		for _, key := range keysToPrune {
   133  			if err := m.Delete(key); err != nil {
   134  				errs = append(errs, err)
   135  			}
   136  		}
   137  
   138  		errs = append(errs, mapIter.Err())
   139  		return errors.Join(errs...)
   140  	})
   141  }
   142  
   143  // Update the BPF map value to match with the object in the desired state table.
   144  func (ops *mapOps[KV]) Update(ctx context.Context, txn statedb.ReadTxn, entry KV) error {
   145  	return ops.withMap(func(m *ebpf.Map) error {
   146  		return m.Put(entry.BinaryKey(), entry.BinaryValue())
   147  	})
   148  }