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 }