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 }