github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/map_abstract.go (about) 1 package gobpfld 2 3 import ( 4 "fmt" 5 "reflect" 6 "runtime" 7 "syscall" 8 "unsafe" 9 10 "github.com/dylandreimerink/gobpfld/bpfsys" 11 "github.com/dylandreimerink/gobpfld/bpftypes" 12 bpfSyscall "github.com/dylandreimerink/gobpfld/internal/syscall" 13 "github.com/dylandreimerink/gobpfld/kernelsupport" 14 ) 15 16 // AbstractMap is a base struct which implements BPFMap however it lacks any features for interacting 17 // with the map, these need to be implemented by a specific map type which can embed this type to reduce 18 // code dupplication. This type is exported so users of the library can also embed this struct in application 19 // specific implementation. 20 type AbstractMap struct { 21 // The name of map. This value is passed to the kernel, it is limited to 15 characters. Its use is limited 22 // and mostly to aid diagnostic tools which inspect the BPF subsystem. For primary identification the ID or FD 23 // should be used. 24 Name ObjName 25 // Definition describes the properties of this map 26 Definition BPFMapDef 27 // A reference to the BTF which contains the type of this map. 28 BTF *BTF 29 // The type of the map. 30 BTFMapType BTFMap 31 32 // Initial contents of the map, set just after loading 33 InitialData map[interface{}]interface{} 34 35 // definition is an unexported copy of Definition which will be pinned as soon as the map is loaded 36 // to prevent the user from chaning the definition while the map is loaded. 37 definition BPFMapDef 38 loaded bool 39 fd bpfsys.BPFfd 40 } 41 42 func (m *AbstractMap) GetBTF() *BTF { 43 return m.BTF 44 } 45 46 func (m *AbstractMap) GetBTFMapType() BTFMap { 47 return m.BTFMapType 48 } 49 50 func (m *AbstractMap) GetInitialData() map[interface{}]interface{} { 51 return m.InitialData 52 } 53 54 // Load validates and loads the userspace map definition into the kernel. 55 func (m *AbstractMap) load(changeAttr func(attr *bpfsys.BPFAttrMapCreate)) error { 56 err := m.Definition.Validate() 57 if err != nil { 58 return err 59 } 60 61 attr := &bpfsys.BPFAttrMapCreate{ 62 MapType: m.Definition.Type, 63 KeySize: m.Definition.KeySize, 64 ValueSize: m.Definition.ValueSize, 65 MaxEntries: m.Definition.MaxEntries, 66 MapFlags: m.Definition.Flags, 67 } 68 69 if kernelsupport.CurrentFeatures.API.Has(kernelsupport.KFeatAPIMapName) { 70 attr.MapName = m.Name.GetCstr() 71 } 72 73 // If BTF info is available and the current kernel supports it 74 if m.BTF != nil && kernelsupport.CurrentFeatures.API.Has(kernelsupport.KFeatAPIBTFLoad) { 75 // Load BTF if not already loaded 76 if !m.BTF.loaded { 77 var log string 78 log, err = m.BTF.Load(BTFLoadOpts{ 79 LogLevel: bpftypes.BPFLogLevelVerbose, 80 }) 81 if err != nil { 82 return fmt.Errorf("load BTF: %w\nLog: %s", err, log) 83 } 84 } 85 86 btfFd, err := m.BTF.Fd() 87 if err != nil { 88 return fmt.Errorf("get BTF fd: %w", err) 89 } 90 91 attr.BTFFD = btfFd 92 93 if m.BTFMapType.Key != nil && m.BTFMapType.Value != nil { 94 attr.BTFKeyTypeID = uint32(m.BTFMapType.Key.GetID()) 95 attr.BTFValueTypeID = uint32(m.BTFMapType.Value.GetID()) 96 } 97 } 98 99 if changeAttr != nil { 100 changeAttr(attr) 101 } 102 103 m.fd, err = bpfsys.MapCreate(attr) 104 if err != nil { 105 return fmt.Errorf("bpf syscall error: %w", err) 106 } 107 108 // Copy exported definition to internal definition so it we always have a copy of the loaded definition which 109 // the user can't change while loaded. 110 m.definition = m.Definition 111 m.loaded = true 112 113 return nil 114 } 115 116 // Unload closes the file descriptor associate with the map, this will cause the map to close from the kernel 117 // if it is not still in use by a eBPF program, bpf FS, or a userspace program still holding a fd to the map. 118 func (m *AbstractMap) close() error { 119 err := m.fd.Close() 120 if err != nil { 121 return fmt.Errorf("error while closing fd: %w", err) 122 } 123 124 m.fd = 0 125 m.loaded = false 126 127 return nil 128 } 129 130 // Pin pins the map to a location in the bpf filesystem, since the file system now also holds a reference 131 // to the map the original creator of the map can terminate without triggering the map to be closed as well. 132 // A map can be unpinned from the bpf FS by another process thus transferring it or persisting it across 133 // multiple runs of the same program. 134 func (m *AbstractMap) Pin(relativePath string) error { 135 if !m.loaded { 136 return fmt.Errorf("can't pin an unloaded map") 137 } 138 139 return PinFD(relativePath, m.fd) 140 } 141 142 // Unpin captures the file descriptor of the map at the given 'relativePath' from the kernel. 143 // The definition in this map must match the definition of the pinned map, otherwise this function 144 // will return an error since mismatched definitions might cause seemingly unrelated bugs in other functions. 145 // If 'deletePin' is true the bpf FS pin will be removed after successfully loading the map, thus transferring 146 // ownership of the map in a scenario where the map is not shared between multiple programs. 147 // Otherwise the pin will keep existing which will cause the map to not be deleted when this program exits. 148 func (m *AbstractMap) Unpin(relativePath string, deletePin bool) error { 149 if m.loaded { 150 return fmt.Errorf("can't unpin a map since it is already loaded") 151 } 152 153 var err error 154 m.fd, err = UnpinFD(relativePath, deletePin) 155 if err != nil { 156 return fmt.Errorf("unpin error: %w", err) 157 } 158 159 pinnedMapDef := BPFMapDef{} 160 err = bpfsys.ObjectGetInfoByFD(&bpfsys.BPFAttrGetInfoFD{ 161 BPFFD: m.fd, 162 Info: uintptr(unsafe.Pointer(&pinnedMapDef)), 163 InfoLen: uint32(bpfMapDefSize), 164 }) 165 if err != nil { 166 return fmt.Errorf("bpf obj get info by fd syscall error: %w", err) 167 } 168 169 // Since other functions use the definition for userspace checks we need to make sure 170 // that the map def in the kernel is the same a the one in userspace. 171 // The other approach would be to just match the userspace definition to the one in the kernel 172 // but if this AbstractMap is embedded in a specialized map and we unpin a generic map by accident 173 // it could result in strange bugs, so this is more fool proof but less user automatic. 174 // Map types embedding the AbstractType should define their own constructor functions which can 175 // make a map from a pinned map path. 176 if m.Definition.Equal(pinnedMapDef) { 177 // Getting the map from the FS created a new file descriptor. Close it so the kernel knows we will 178 // not be using it. If we leak the FD the map will never close. 179 fdErr := m.fd.Close() 180 if fdErr != nil { 181 return fmt.Errorf("pinned map definition doesn't match definition of current map, "+ 182 "new fd wasn't closed: %w", err) 183 } 184 185 return fmt.Errorf("pinned map definition doesn't match definition of current map") 186 } 187 188 // Copy exported definition to internal definition so it we always have a copy of the loaded definition which 189 // the user can't change while loaded. 190 m.definition = m.Definition 191 m.loaded = true 192 193 return nil 194 } 195 196 func (m *AbstractMap) IsLoaded() bool { 197 return m.loaded 198 } 199 200 func (m *AbstractMap) GetName() ObjName { 201 return m.Name 202 } 203 204 func (m *AbstractMap) GetFD() bpfsys.BPFfd { 205 return m.fd 206 } 207 208 func (m *AbstractMap) GetDefinition() BPFMapDef { 209 // If the map is loaded we will return the internal version of definition since we know it will not be modified 210 // to avoid misuse of the library 211 if m.loaded { 212 return m.definition 213 } 214 215 return m.Definition 216 } 217 218 // get uses reflection to to dynamically get a k/v pair from any map as long as the sizes of the key and value match 219 // the map definition. 220 func (m *AbstractMap) get(key interface{}, value interface{}) error { 221 if !m.loaded { 222 return fmt.Errorf("can't read from an unloaded map") 223 } 224 225 // Return a human readable error since the kernel will not allow us to read from the map anyway 226 if m.Definition.Flags&bpftypes.BPFMapFlagsWriteOnly != 0 { 227 return fmt.Errorf("can't read from map since the 'write only' flag is set") 228 } 229 230 attr := &bpfsys.BPFAttrMapElem{ 231 MapFD: m.fd, 232 } 233 234 var err error 235 236 // key can be nil for some map types which don't have keys like stacks and queues 237 if key != nil { 238 attr.Key, err = m.toKeyPtr(key) 239 if err != nil { 240 return err 241 } 242 } 243 244 attr.Value_NextKey, err = m.toValuePtr(value) 245 if err != nil { 246 return err 247 } 248 249 err = bpfsys.MapLookupElem(attr) 250 if err != nil { 251 return fmt.Errorf("bpf syscall error: %w", err) 252 } 253 254 return nil 255 } 256 257 // toKeyPtr checks if 'key' is a pointer to a type which has the same 258 // size in memory as the key of the eBPF map. 259 func (m *AbstractMap) toKeyPtr(key interface{}) (uintptr, error) { 260 keyType := reflect.TypeOf(key) 261 if keyType.Kind() != reflect.Ptr { 262 return 0, fmt.Errorf("key argument must be a pointer") 263 } 264 265 if keyType.Elem().Size() != uintptr(m.Definition.KeySize) { 266 return 0, fmt.Errorf( 267 "key type size(%d) doesn't match size of bpf key(%d)", 268 keyType.Elem().Size(), 269 m.Definition.KeySize, 270 ) 271 } 272 273 return reflect.ValueOf(key).Pointer(), nil 274 } 275 276 // toValuePtr checks if 'value' is a pointer to a type which has the same 277 // size in memory as the value of the eBPF map. 278 func (m *AbstractMap) toValuePtr(value interface{}) (uintptr, error) { 279 valueType := reflect.TypeOf(value) 280 if valueType.Kind() != reflect.Ptr { 281 return 0, fmt.Errorf("value argument must be a pointer") 282 } 283 284 elem := valueType.Elem() 285 286 // If the map type is a per CPU map, the value must be an array 287 // or slice with at least as much elements as the system has CPU cores 288 if m.isPerCPUMap() { 289 switch elem.Kind() { 290 case reflect.Array: 291 arrayElem := elem.Elem() 292 if arrayElem.Size() != uintptr(m.Definition.ValueSize) { 293 return 0, fmt.Errorf( 294 "value array element type size(%d) doesn't match size of bpf value(%d)", 295 arrayElem.Size(), 296 m.Definition.ValueSize, 297 ) 298 } 299 300 if elem.Len() < numCPUs { 301 return 0, fmt.Errorf( 302 "value argument must be a pointer to an array or slice containing at least %d elements"+ 303 " given array only has %d elements", 304 numCPUs, 305 elem.Len(), 306 ) 307 } 308 309 return reflect.ValueOf(value).Pointer(), nil 310 311 case reflect.Slice: 312 sliceElemType := elem.Elem() 313 if sliceElemType.Size() != uintptr(m.Definition.ValueSize) { 314 return 0, fmt.Errorf( 315 "value slice element type size(%d) doesn't match size of bpf value(%d)", 316 sliceElemType.Size(), 317 m.Definition.ValueSize, 318 ) 319 } 320 321 sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(reflect.ValueOf(value).Pointer())) 322 if sliceHdr.Len < numCPUs { 323 return 0, fmt.Errorf( 324 "value argument must be a pointer to an array or slice containing at least %d elements"+ 325 " given slice only has %d elements", 326 numCPUs, 327 sliceHdr.Len, 328 ) 329 } 330 return sliceHdr.Data, nil 331 332 default: 333 return 0, fmt.Errorf( 334 "value argument must be a pointer to an array or slice containing at least %d elements", 335 numCPUs, 336 ) 337 } 338 } 339 340 switch elem.Kind() { 341 case reflect.Array: 342 // If an array was passed, make sure the total size of the array is equal to the size of the BPF value 343 arrayElem := elem.Elem() 344 totalSize := uint32(arrayElem.Size()) * uint32(elem.Len()) 345 if totalSize != m.Definition.ValueSize { 346 return 0, fmt.Errorf( 347 "value array total size(%d) doesn't match size of bpf value size(%d)", 348 totalSize, 349 m.Definition.ValueSize, 350 ) 351 } 352 353 return reflect.ValueOf(value).Pointer(), nil 354 355 case reflect.Slice: 356 // If a slice was passed, make sure the total size of the slice is equal to the size of the BPF value. 357 // And return a pointer to the underlying array 358 sliceElemType := elem.Elem() 359 sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(reflect.ValueOf(value).Pointer())) 360 totalSize := uint32(sliceHdr.Len) * uint32(sliceElemType.Size()) 361 if totalSize != m.Definition.ValueSize { 362 return 0, fmt.Errorf( 363 "value slice total size(%d) doesn't match size of bpf value size(%d)", 364 totalSize, 365 m.Definition.ValueSize, 366 ) 367 } 368 369 return sliceHdr.Data, nil 370 371 default: 372 if elem.Size() != uintptr(m.Definition.ValueSize) { 373 return 0, fmt.Errorf( 374 "value type size(%d) doesn't match size of bpf value(%d)", 375 elem.Size(), 376 m.Definition.ValueSize, 377 ) 378 } 379 380 return reflect.ValueOf(value).Pointer(), nil 381 } 382 } 383 384 // getBatch fills the keys and values array/slice with the keys and values inside the map up to a maximum of 385 // maxBatchSize entries. The keys and values array/slice must have at least a length of maxBatchSize. 386 // The key and value of an entry is has the same index, so for example the value for keys[2] is in values[2]. 387 // Count is the amount of entries returns, partial is true if not all elements of keys and values could be set. 388 // 389 // This function is intended for small maps which can be read into userspace all at once since 390 // getBatch can only read from the beginning of the map. If the map is to large to read all at once 391 // a iterator should be used instead of the get or getBatch function. 392 func (m *AbstractMap) getBatch( 393 keys interface{}, 394 values interface{}, 395 maxBatchSize uint32, 396 ) ( 397 count int, 398 partial bool, 399 err error, 400 ) { 401 if !m.loaded { 402 return 0, false, fmt.Errorf("can't read from an unloaded map") 403 } 404 405 // Return a human readable error since the kernel will not allow us to read from the map anyway 406 if m.Definition.Flags&bpftypes.BPFMapFlagsWriteOnly != 0 { 407 return 0, false, fmt.Errorf("can't read from map since the 'write only' flag is set") 408 } 409 410 var batch uint64 411 attr := &bpfsys.BPFAttrMapBatch{ 412 MapFD: m.fd, 413 OutBatch: uintptr(unsafe.Pointer(&batch)), 414 Count: maxBatchSize, 415 } 416 417 attr.Keys, err = m.toBatchKeysPtr(keys, maxBatchSize) 418 if err != nil { 419 return 0, false, err 420 } 421 422 attr.Values, err = m.toBatchValuesPtr(values, maxBatchSize) 423 if err != nil { 424 return 0, false, err 425 } 426 427 err = bpfsys.MapLookupBatch(attr) 428 if err != nil { 429 // A ENOENT is not an acutal error, the kernel uses it to signal there is no more data after this batch 430 if sysErr, ok := err.(*bpfSyscall.Error); ok && sysErr.Errno == syscall.ENOENT { 431 return int(attr.Count), true, nil 432 } 433 434 return 0, false, fmt.Errorf("bpf syscall error: %w", err) 435 } 436 437 return int(attr.Count), false, nil 438 } 439 440 func (m *AbstractMap) set(key interface{}, value interface{}, flags bpfsys.BPFAttrMapElemFlags) error { 441 if !m.loaded { 442 return fmt.Errorf("can't write to an unloaded map") 443 } 444 445 attr := &bpfsys.BPFAttrMapElem{ 446 MapFD: m.fd, 447 Flags: flags, 448 } 449 450 var err error 451 452 // Key can be nil if the map type has no key like stacks and queues 453 if key != nil { 454 attr.Key, err = m.toKeyPtr(key) 455 if err != nil { 456 return err 457 } 458 } 459 460 attr.Value_NextKey, err = m.toValuePtr(value) 461 if err != nil { 462 return err 463 } 464 465 err = bpfsys.MapUpdateElem(attr) 466 if err != nil { 467 return fmt.Errorf("bpf syscall error: %w", err) 468 } 469 470 return nil 471 } 472 473 func (m *AbstractMap) lookupAndDelete(key interface{}, value interface{}, flags bpfsys.BPFAttrMapElemFlags) error { 474 if !m.loaded { 475 return fmt.Errorf("can't write to an unloaded map") 476 } 477 478 attr := &bpfsys.BPFAttrMapElem{ 479 MapFD: m.fd, 480 Flags: flags, 481 } 482 483 var err error 484 485 // Key can be nil if the map type has no key like stacks and queues 486 if key != nil { 487 attr.Key, err = m.toKeyPtr(key) 488 if err != nil { 489 return err 490 } 491 } 492 493 attr.Value_NextKey, err = m.toValuePtr(value) 494 if err != nil { 495 return err 496 } 497 498 err = bpfsys.MapLookupAndDeleteElement(attr) 499 if err != nil { 500 return fmt.Errorf("bpf syscall error: %w", err) 501 } 502 503 return nil 504 } 505 506 func (m *AbstractMap) setBatch( 507 keys interface{}, 508 values interface{}, 509 flags bpfsys.BPFAttrMapElemFlags, 510 maxBatchSize uint32, 511 ) ( 512 count int, 513 err error, 514 ) { 515 if !m.loaded { 516 return 0, fmt.Errorf("can't write to an unloaded map") 517 } 518 519 var batch uint64 520 attr := &bpfsys.BPFAttrMapBatch{ 521 MapFD: m.fd, 522 OutBatch: uintptr(unsafe.Pointer(&batch)), 523 Count: maxBatchSize, 524 Flags: flags, 525 // TODO ElemFlags is only used for the spinlock flag, for which we will add support later 526 } 527 528 attr.Keys, err = m.toBatchKeysPtr(keys, maxBatchSize) 529 if err != nil { 530 return 0, err 531 } 532 533 attr.Values, err = m.toBatchValuesPtr(values, maxBatchSize) 534 if err != nil { 535 return 0, err 536 } 537 538 err = bpfsys.MapUpdateBatch(attr) 539 if err != nil { 540 return 0, fmt.Errorf("bpf syscall error: %w", err) 541 } 542 543 return int(attr.Count), nil 544 } 545 546 func (m *AbstractMap) delete(key interface{}) error { 547 if !m.loaded { 548 return fmt.Errorf("can't delete elements in an unloaded map") 549 } 550 551 if m.isArrayMap() { 552 return fmt.Errorf("can't delete elements from an array type map") 553 } 554 555 attr := &bpfsys.BPFAttrMapElem{ 556 MapFD: m.fd, 557 } 558 559 var err error 560 561 attr.Key, err = m.toKeyPtr(key) 562 if err != nil { 563 return err 564 } 565 566 err = bpfsys.MapDeleteElem(attr) 567 if err != nil { 568 return fmt.Errorf("bpf syscall error: %w", err) 569 } 570 571 return nil 572 } 573 574 func (m *AbstractMap) deleteBatch( 575 keys interface{}, 576 maxBatchSize uint32, 577 ) ( 578 count int, 579 err error, 580 ) { 581 if !m.loaded { 582 return 0, fmt.Errorf("can't delete elements in an unloaded map") 583 } 584 585 if m.isArrayMap() { 586 return 0, fmt.Errorf("can't delete elements from an array type map") 587 } 588 589 var batch uint64 590 attr := &bpfsys.BPFAttrMapBatch{ 591 MapFD: m.fd, 592 OutBatch: uintptr(unsafe.Pointer(&batch)), 593 Count: maxBatchSize, 594 } 595 596 attr.Keys, err = m.toBatchKeysPtr(keys, maxBatchSize) 597 if err != nil { 598 return 0, err 599 } 600 601 err = bpfsys.MapDeleteBatch(attr) 602 if err != nil { 603 return 0, fmt.Errorf("bpf syscall error: %w", err) 604 } 605 606 return int(attr.Count), nil 607 } 608 609 func (m *AbstractMap) getAndDelete(key interface{}, value interface{}) error { 610 if !m.loaded { 611 return fmt.Errorf("can't read from an unloaded map") 612 } 613 614 if m.isArrayMap() { 615 return fmt.Errorf("can't delete elements from an array type map") 616 } 617 618 attr := &bpfsys.BPFAttrMapElem{ 619 MapFD: m.fd, 620 } 621 622 var err error 623 624 attr.Key, err = m.toKeyPtr(key) 625 if err != nil { 626 return err 627 } 628 629 attr.Value_NextKey, err = m.toValuePtr(value) 630 if err != nil { 631 return err 632 } 633 634 err = bpfsys.MapLookupAndDeleteElement(attr) 635 if err != nil { 636 return fmt.Errorf("bpf syscall error: %w", err) 637 } 638 639 return nil 640 } 641 642 func (m *AbstractMap) getAndDeleteBatch( 643 keys interface{}, 644 values interface{}, 645 maxBatchSize uint32, 646 ) ( 647 count int, 648 err error, 649 ) { 650 if !m.loaded { 651 return 0, fmt.Errorf("can't read from an unloaded map") 652 } 653 654 if m.isArrayMap() { 655 return 0, fmt.Errorf("can't delete elements from an array type map") 656 } 657 658 var batch uint64 659 attr := &bpfsys.BPFAttrMapBatch{ 660 MapFD: m.fd, 661 OutBatch: uintptr(unsafe.Pointer(&batch)), 662 Count: maxBatchSize, 663 } 664 665 attr.Keys, err = m.toBatchKeysPtr(keys, maxBatchSize) 666 if err != nil { 667 return 0, err 668 } 669 670 attr.Values, err = m.toBatchValuesPtr(values, maxBatchSize) 671 if err != nil { 672 return 0, err 673 } 674 675 err = bpfsys.MapLookupBatchAndDelete(attr) 676 if err != nil { 677 return 0, fmt.Errorf("bpf syscall error: %w", err) 678 } 679 680 return int(attr.Count), nil 681 } 682 683 // toBatchKeysPtr checks if 'keys' is a pointer to a array or slice of at least enough elements to hold 684 // all keys in one batch and that the type of this array has the same memory size as the eBPF map key. 685 func (m *AbstractMap) toBatchKeysPtr(keys interface{}, maxBatchSize uint32) (uintptr, error) { 686 keyType := reflect.TypeOf(keys) 687 if keyType.Kind() != reflect.Ptr { 688 return 0, fmt.Errorf("keys argument must be a pointer") 689 } 690 691 elem := keyType.Elem() 692 693 switch elem.Kind() { 694 case reflect.Array: 695 arrayElem := elem.Elem() 696 if arrayElem.Size() != uintptr(m.Definition.KeySize) { 697 return 0, fmt.Errorf( 698 "keys array element type size(%d) doesn't match size of bpf key(%d)", 699 arrayElem.Size(), 700 m.Definition.KeySize, 701 ) 702 } 703 704 if elem.Len() < int(maxBatchSize) { 705 return 0, fmt.Errorf( 706 "keys argument must be a pointer to an array or slice containing at least %d elements"+ 707 " given array only has %d elements", 708 maxBatchSize, 709 elem.Len(), 710 ) 711 } 712 713 return reflect.ValueOf(elem).Pointer(), nil 714 715 case reflect.Slice: 716 sliceElemType := elem.Elem() 717 if sliceElemType.Size() != uintptr(m.Definition.KeySize) { 718 return 0, fmt.Errorf( 719 "keys slice element type size(%d) doesn't match size of bpf key(%d)", 720 sliceElemType.Size(), 721 m.Definition.KeySize, 722 ) 723 } 724 725 sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(reflect.ValueOf(keys).Pointer())) 726 if sliceHdr.Len < int(maxBatchSize) { 727 return 0, fmt.Errorf( 728 "keys argument must be a pointer to an array or slice containing at least %d elements"+ 729 " given slice only has %d elements", 730 maxBatchSize, 731 sliceHdr.Len, 732 ) 733 } 734 return sliceHdr.Data, nil 735 736 default: 737 return 0, fmt.Errorf("keys argument must be a pointer to an array or slice") 738 } 739 } 740 741 // toBatchValuesPtr checks if 'values' is a pointer to a array or slice of at least enough elements to hold 742 // all value in one batch and that the type of this array has the same memory size as the eBPF map key. 743 // If the map type is an per-cpu type the array/slice size is multiplied by the CPU count 744 func (m *AbstractMap) toBatchValuesPtr(values interface{}, maxBatchSize uint32) (uintptr, error) { 745 valuesType := reflect.TypeOf(values) 746 if valuesType.Kind() != reflect.Ptr { 747 return 0, fmt.Errorf("values argument must be a pointer") 748 } 749 750 elem := valuesType.Elem() 751 752 // If the map type is a per CPU map type we need to multiply the batch size by the CPU count 753 // Since per CPU types will return a separate value for each CPU 754 if m.isPerCPUMap() { 755 maxBatchSize = maxBatchSize * uint32(numCPUs) 756 } 757 758 switch elem.Kind() { 759 case reflect.Array: 760 arrayElem := elem.Elem() 761 if arrayElem.Size() != uintptr(m.Definition.ValueSize) { 762 return 0, fmt.Errorf( 763 "values array element type size(%d) doesn't match size of bpf value(%d)", 764 arrayElem.Size(), 765 m.Definition.ValueSize, 766 ) 767 } 768 769 if elem.Len() < int(maxBatchSize) { 770 return 0, fmt.Errorf( 771 "values argument must be a pointer to an array or slice containing at least %d elements"+ 772 " given array only has %d elements", 773 maxBatchSize, 774 elem.Len(), 775 ) 776 } 777 778 return reflect.ValueOf(elem).Pointer(), nil 779 780 case reflect.Slice: 781 sliceElemType := elem.Elem() 782 if sliceElemType.Size() != uintptr(m.Definition.ValueSize) { 783 return 0, fmt.Errorf( 784 "values slice element type size(%d) doesn't match size of bpf value(%d)", 785 sliceElemType.Size(), 786 m.Definition.ValueSize, 787 ) 788 } 789 790 sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(reflect.ValueOf(values).Pointer())) 791 if sliceHdr.Len < int(maxBatchSize) { 792 return 0, fmt.Errorf( 793 "values argument must be a pointer to an array or slice containing at least %d elements"+ 794 " given slice only has %d elements", 795 maxBatchSize, 796 sliceHdr.Len, 797 ) 798 } 799 return sliceHdr.Data, nil 800 801 default: 802 return 0, fmt.Errorf("values argument must be a pointer to an array or slice") 803 } 804 } 805 806 var numCPUs = runtime.NumCPU() 807 808 func (m *AbstractMap) isPerCPUMap() bool { 809 return m.Definition.Type == bpftypes.BPF_MAP_TYPE_PERCPU_ARRAY || 810 m.Definition.Type == bpftypes.BPF_MAP_TYPE_PERCPU_HASH || 811 m.Definition.Type == bpftypes.BPF_MAP_TYPE_LRU_PERCPU_HASH 812 } 813 814 func (m *AbstractMap) isArrayMap() bool { 815 return m.Definition.Type == bpftypes.BPF_MAP_TYPE_ARRAY || 816 m.Definition.Type == bpftypes.BPF_MAP_TYPE_PERCPU_ARRAY 817 }