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 }