collection.go (21502B)
1 package ebpf 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 "reflect" 8 "strings" 9 10 "github.com/cilium/ebpf/asm" 11 "github.com/cilium/ebpf/btf" 12 ) 13 14 // CollectionOptions control loading a collection into the kernel. 15 // 16 // Maps and Programs are passed to NewMapWithOptions and NewProgramsWithOptions. 17 type CollectionOptions struct { 18 Maps MapOptions 19 Programs ProgramOptions 20 21 // MapReplacements takes a set of Maps that will be used instead of 22 // creating new ones when loading the CollectionSpec. 23 // 24 // For each given Map, there must be a corresponding MapSpec in 25 // CollectionSpec.Maps, and its type, key/value size, max entries and flags 26 // must match the values of the MapSpec. 27 // 28 // The given Maps are Clone()d before being used in the Collection, so the 29 // caller can Close() them freely when they are no longer needed. 30 MapReplacements map[string]*Map 31 } 32 33 // CollectionSpec describes a collection. 34 type CollectionSpec struct { 35 Maps map[string]*MapSpec 36 Programs map[string]*ProgramSpec 37 38 // Types holds type information about Maps and Programs. 39 // Modifications to Types are currently undefined behaviour. 40 Types *btf.Spec 41 42 // ByteOrder specifies whether the ELF was compiled for 43 // big-endian or little-endian architectures. 44 ByteOrder binary.ByteOrder 45 } 46 47 // Copy returns a recursive copy of the spec. 48 func (cs *CollectionSpec) Copy() *CollectionSpec { 49 if cs == nil { 50 return nil 51 } 52 53 cpy := CollectionSpec{ 54 Maps: make(map[string]*MapSpec, len(cs.Maps)), 55 Programs: make(map[string]*ProgramSpec, len(cs.Programs)), 56 ByteOrder: cs.ByteOrder, 57 Types: cs.Types, 58 } 59 60 for name, spec := range cs.Maps { 61 cpy.Maps[name] = spec.Copy() 62 } 63 64 for name, spec := range cs.Programs { 65 cpy.Programs[name] = spec.Copy() 66 } 67 68 return &cpy 69 } 70 71 // RewriteMaps replaces all references to specific maps. 72 // 73 // Use this function to use pre-existing maps instead of creating new ones 74 // when calling NewCollection. Any named maps are removed from CollectionSpec.Maps. 75 // 76 // Returns an error if a named map isn't used in at least one program. 77 // 78 // Deprecated: Pass CollectionOptions.MapReplacements when loading the Collection 79 // instead. 80 func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error { 81 for symbol, m := range maps { 82 // have we seen a program that uses this symbol / map 83 seen := false 84 for progName, progSpec := range cs.Programs { 85 err := progSpec.Instructions.AssociateMap(symbol, m) 86 87 switch { 88 case err == nil: 89 seen = true 90 91 case errors.Is(err, asm.ErrUnreferencedSymbol): 92 // Not all programs need to use the map 93 94 default: 95 return fmt.Errorf("program %s: %w", progName, err) 96 } 97 } 98 99 if !seen { 100 return fmt.Errorf("map %s not referenced by any programs", symbol) 101 } 102 103 // Prevent NewCollection from creating rewritten maps 104 delete(cs.Maps, symbol) 105 } 106 107 return nil 108 } 109 110 // RewriteConstants replaces the value of multiple constants. 111 // 112 // The constant must be defined like so in the C program: 113 // 114 // volatile const type foobar; 115 // volatile const type foobar = default; 116 // 117 // Replacement values must be of the same length as the C sizeof(type). 118 // If necessary, they are marshalled according to the same rules as 119 // map values. 120 // 121 // From Linux 5.5 the verifier will use constants to eliminate dead code. 122 // 123 // Returns an error if a constant doesn't exist. 124 func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error { 125 replaced := make(map[string]bool) 126 127 for name, spec := range cs.Maps { 128 if !strings.HasPrefix(name, ".rodata") { 129 continue 130 } 131 132 b, ds, err := spec.dataSection() 133 if errors.Is(err, errMapNoBTFValue) { 134 // Data sections without a BTF Datasec are valid, but don't support 135 // constant replacements. 136 continue 137 } 138 if err != nil { 139 return fmt.Errorf("map %s: %w", name, err) 140 } 141 142 // MapSpec.Copy() performs a shallow copy. Fully copy the byte slice 143 // to avoid any changes affecting other copies of the MapSpec. 144 cpy := make([]byte, len(b)) 145 copy(cpy, b) 146 147 for _, v := range ds.Vars { 148 vname := v.Type.TypeName() 149 replacement, ok := consts[vname] 150 if !ok { 151 continue 152 } 153 154 if replaced[vname] { 155 return fmt.Errorf("section %s: duplicate variable %s", name, vname) 156 } 157 158 if int(v.Offset+v.Size) > len(cpy) { 159 return fmt.Errorf("section %s: offset %d(+%d) for variable %s is out of bounds", name, v.Offset, v.Size, vname) 160 } 161 162 b, err := marshalBytes(replacement, int(v.Size)) 163 if err != nil { 164 return fmt.Errorf("marshaling constant replacement %s: %w", vname, err) 165 } 166 167 copy(cpy[v.Offset:v.Offset+v.Size], b) 168 169 replaced[vname] = true 170 } 171 172 spec.Contents[0] = MapKV{Key: uint32(0), Value: cpy} 173 } 174 175 var missing []string 176 for c := range consts { 177 if !replaced[c] { 178 missing = append(missing, c) 179 } 180 } 181 182 if len(missing) != 0 { 183 return fmt.Errorf("spec is missing one or more constants: %s", strings.Join(missing, ",")) 184 } 185 186 return nil 187 } 188 189 // Assign the contents of a CollectionSpec to a struct. 190 // 191 // This function is a shortcut to manually checking the presence 192 // of maps and programs in a CollectionSpec. Consider using bpf2go 193 // if this sounds useful. 194 // 195 // 'to' must be a pointer to a struct. A field of the 196 // struct is updated with values from Programs or Maps if it 197 // has an `ebpf` tag and its type is *ProgramSpec or *MapSpec. 198 // The tag's value specifies the name of the program or map as 199 // found in the CollectionSpec. 200 // 201 // struct { 202 // Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"` 203 // Bar *ebpf.MapSpec `ebpf:"bar_map"` 204 // Ignored int 205 // } 206 // 207 // Returns an error if any of the eBPF objects can't be found, or 208 // if the same MapSpec or ProgramSpec is assigned multiple times. 209 func (cs *CollectionSpec) Assign(to interface{}) error { 210 // Assign() only supports assigning ProgramSpecs and MapSpecs, 211 // so doesn't load any resources into the kernel. 212 getValue := func(typ reflect.Type, name string) (interface{}, error) { 213 switch typ { 214 215 case reflect.TypeOf((*ProgramSpec)(nil)): 216 if p := cs.Programs[name]; p != nil { 217 return p, nil 218 } 219 return nil, fmt.Errorf("missing program %q", name) 220 221 case reflect.TypeOf((*MapSpec)(nil)): 222 if m := cs.Maps[name]; m != nil { 223 return m, nil 224 } 225 return nil, fmt.Errorf("missing map %q", name) 226 227 default: 228 return nil, fmt.Errorf("unsupported type %s", typ) 229 } 230 } 231 232 return assignValues(to, getValue) 233 } 234 235 // LoadAndAssign loads Maps and Programs into the kernel and assigns them 236 // to a struct. 237 // 238 // Omitting Map/Program.Close() during application shutdown is an error. 239 // See the package documentation for details around Map and Program lifecycle. 240 // 241 // This function is a shortcut to manually checking the presence 242 // of maps and programs in a CollectionSpec. Consider using bpf2go 243 // if this sounds useful. 244 // 245 // 'to' must be a pointer to a struct. A field of the struct is updated with 246 // a Program or Map if it has an `ebpf` tag and its type is *Program or *Map. 247 // The tag's value specifies the name of the program or map as found in the 248 // CollectionSpec. Before updating the struct, the requested objects and their 249 // dependent resources are loaded into the kernel and populated with values if 250 // specified. 251 // 252 // struct { 253 // Foo *ebpf.Program `ebpf:"xdp_foo"` 254 // Bar *ebpf.Map `ebpf:"bar_map"` 255 // Ignored int 256 // } 257 // 258 // opts may be nil. 259 // 260 // Returns an error if any of the fields can't be found, or 261 // if the same Map or Program is assigned multiple times. 262 func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error { 263 loader, err := newCollectionLoader(cs, opts) 264 if err != nil { 265 return err 266 } 267 defer loader.close() 268 269 // Support assigning Programs and Maps, lazy-loading the required objects. 270 assignedMaps := make(map[string]bool) 271 assignedProgs := make(map[string]bool) 272 273 getValue := func(typ reflect.Type, name string) (interface{}, error) { 274 switch typ { 275 276 case reflect.TypeOf((*Program)(nil)): 277 assignedProgs[name] = true 278 return loader.loadProgram(name) 279 280 case reflect.TypeOf((*Map)(nil)): 281 assignedMaps[name] = true 282 return loader.loadMap(name) 283 284 default: 285 return nil, fmt.Errorf("unsupported type %s", typ) 286 } 287 } 288 289 // Load the Maps and Programs requested by the annotated struct. 290 if err := assignValues(to, getValue); err != nil { 291 return err 292 } 293 294 // Populate the requested maps. Has a chance of lazy-loading other dependent maps. 295 if err := loader.populateMaps(); err != nil { 296 return err 297 } 298 299 // Evaluate the loader's objects after all (lazy)loading has taken place. 300 for n, m := range loader.maps { 301 switch m.typ { 302 case ProgramArray: 303 // Require all lazy-loaded ProgramArrays to be assigned to the given object. 304 // The kernel empties a ProgramArray once the last user space reference 305 // to it closes, which leads to failed tail calls. Combined with the library 306 // closing map fds via GC finalizers this can lead to surprising behaviour. 307 // Only allow unassigned ProgramArrays when the library hasn't pre-populated 308 // any entries from static value declarations. At this point, we know the map 309 // is empty and there's no way for the caller to interact with the map going 310 // forward. 311 if !assignedMaps[n] && len(cs.Maps[n].Contents) > 0 { 312 return fmt.Errorf("ProgramArray %s must be assigned to prevent missed tail calls", n) 313 } 314 } 315 } 316 317 // Prevent loader.cleanup() from closing assigned Maps and Programs. 318 for m := range assignedMaps { 319 delete(loader.maps, m) 320 } 321 for p := range assignedProgs { 322 delete(loader.programs, p) 323 } 324 325 return nil 326 } 327 328 // Collection is a collection of Programs and Maps associated 329 // with their symbols 330 type Collection struct { 331 Programs map[string]*Program 332 Maps map[string]*Map 333 } 334 335 // NewCollection creates a Collection from the given spec, creating and 336 // loading its declared resources into the kernel. 337 // 338 // Omitting Collection.Close() during application shutdown is an error. 339 // See the package documentation for details around Map and Program lifecycle. 340 func NewCollection(spec *CollectionSpec) (*Collection, error) { 341 return NewCollectionWithOptions(spec, CollectionOptions{}) 342 } 343 344 // NewCollectionWithOptions creates a Collection from the given spec using 345 // options, creating and loading its declared resources into the kernel. 346 // 347 // Omitting Collection.Close() during application shutdown is an error. 348 // See the package documentation for details around Map and Program lifecycle. 349 func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) { 350 loader, err := newCollectionLoader(spec, &opts) 351 if err != nil { 352 return nil, err 353 } 354 defer loader.close() 355 356 // Create maps first, as their fds need to be linked into programs. 357 for mapName := range spec.Maps { 358 if _, err := loader.loadMap(mapName); err != nil { 359 return nil, err 360 } 361 } 362 363 for progName, prog := range spec.Programs { 364 if prog.Type == UnspecifiedProgram { 365 continue 366 } 367 368 if _, err := loader.loadProgram(progName); err != nil { 369 return nil, err 370 } 371 } 372 373 // Maps can contain Program and Map stubs, so populate them after 374 // all Maps and Programs have been successfully loaded. 375 if err := loader.populateMaps(); err != nil { 376 return nil, err 377 } 378 379 // Prevent loader.cleanup from closing maps and programs. 380 maps, progs := loader.maps, loader.programs 381 loader.maps, loader.programs = nil, nil 382 383 return &Collection{ 384 progs, 385 maps, 386 }, nil 387 } 388 389 type handleCache struct { 390 btfHandles map[*btf.Spec]*btf.Handle 391 } 392 393 func newHandleCache() *handleCache { 394 return &handleCache{ 395 btfHandles: make(map[*btf.Spec]*btf.Handle), 396 } 397 } 398 399 func (hc handleCache) btfHandle(spec *btf.Spec) (*btf.Handle, error) { 400 if hc.btfHandles[spec] != nil { 401 return hc.btfHandles[spec], nil 402 } 403 404 handle, err := btf.NewHandle(spec) 405 if err != nil { 406 return nil, err 407 } 408 409 hc.btfHandles[spec] = handle 410 return handle, nil 411 } 412 413 func (hc handleCache) close() { 414 for _, handle := range hc.btfHandles { 415 handle.Close() 416 } 417 } 418 419 type collectionLoader struct { 420 coll *CollectionSpec 421 opts *CollectionOptions 422 maps map[string]*Map 423 programs map[string]*Program 424 handles *handleCache 425 } 426 427 func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collectionLoader, error) { 428 if opts == nil { 429 opts = &CollectionOptions{} 430 } 431 432 // Check for existing MapSpecs in the CollectionSpec for all provided replacement maps. 433 for name, m := range opts.MapReplacements { 434 spec, ok := coll.Maps[name] 435 if !ok { 436 return nil, fmt.Errorf("replacement map %s not found in CollectionSpec", name) 437 } 438 439 if err := spec.checkCompatibility(m); err != nil { 440 return nil, fmt.Errorf("using replacement map %s: %w", spec.Name, err) 441 } 442 } 443 444 return &collectionLoader{ 445 coll, 446 opts, 447 make(map[string]*Map), 448 make(map[string]*Program), 449 newHandleCache(), 450 }, nil 451 } 452 453 // close all resources left over in the collectionLoader. 454 func (cl *collectionLoader) close() { 455 cl.handles.close() 456 for _, m := range cl.maps { 457 m.Close() 458 } 459 for _, p := range cl.programs { 460 p.Close() 461 } 462 } 463 464 func (cl *collectionLoader) loadMap(mapName string) (*Map, error) { 465 if m := cl.maps[mapName]; m != nil { 466 return m, nil 467 } 468 469 mapSpec := cl.coll.Maps[mapName] 470 if mapSpec == nil { 471 return nil, fmt.Errorf("missing map %s", mapName) 472 } 473 474 if mapSpec.BTF != nil && cl.coll.Types != mapSpec.BTF { 475 return nil, fmt.Errorf("map %s: BTF doesn't match collection", mapName) 476 } 477 478 if replaceMap, ok := cl.opts.MapReplacements[mapName]; ok { 479 // Clone the map to avoid closing user's map later on. 480 m, err := replaceMap.Clone() 481 if err != nil { 482 return nil, err 483 } 484 485 cl.maps[mapName] = m 486 return m, nil 487 } 488 489 m, err := newMapWithOptions(mapSpec, cl.opts.Maps, cl.handles) 490 if err != nil { 491 return nil, fmt.Errorf("map %s: %w", mapName, err) 492 } 493 494 cl.maps[mapName] = m 495 return m, nil 496 } 497 498 func (cl *collectionLoader) loadProgram(progName string) (*Program, error) { 499 if prog := cl.programs[progName]; prog != nil { 500 return prog, nil 501 } 502 503 progSpec := cl.coll.Programs[progName] 504 if progSpec == nil { 505 return nil, fmt.Errorf("unknown program %s", progName) 506 } 507 508 // Bail out early if we know the kernel is going to reject the program. 509 // This skips loading map dependencies, saving some cleanup work later. 510 if progSpec.Type == UnspecifiedProgram { 511 return nil, fmt.Errorf("cannot load program %s: program type is unspecified", progName) 512 } 513 514 if progSpec.BTF != nil && cl.coll.Types != progSpec.BTF { 515 return nil, fmt.Errorf("program %s: BTF doesn't match collection", progName) 516 } 517 518 progSpec = progSpec.Copy() 519 520 // Rewrite any reference to a valid map in the program's instructions, 521 // which includes all of its dependencies. 522 for i := range progSpec.Instructions { 523 ins := &progSpec.Instructions[i] 524 525 if !ins.IsLoadFromMap() || ins.Reference() == "" { 526 continue 527 } 528 529 // Don't overwrite map loads containing non-zero map fd's, 530 // they can be manually included by the caller. 531 // Map FDs/IDs are placed in the lower 32 bits of Constant. 532 if int32(ins.Constant) > 0 { 533 continue 534 } 535 536 m, err := cl.loadMap(ins.Reference()) 537 if err != nil { 538 return nil, fmt.Errorf("program %s: %w", progName, err) 539 } 540 541 if err := ins.AssociateMap(m); err != nil { 542 return nil, fmt.Errorf("program %s: map %s: %w", progName, ins.Reference(), err) 543 } 544 } 545 546 prog, err := newProgramWithOptions(progSpec, cl.opts.Programs, cl.handles) 547 if err != nil { 548 return nil, fmt.Errorf("program %s: %w", progName, err) 549 } 550 551 cl.programs[progName] = prog 552 return prog, nil 553 } 554 555 func (cl *collectionLoader) populateMaps() error { 556 for mapName, m := range cl.maps { 557 mapSpec, ok := cl.coll.Maps[mapName] 558 if !ok { 559 return fmt.Errorf("missing map spec %s", mapName) 560 } 561 562 mapSpec = mapSpec.Copy() 563 564 // MapSpecs that refer to inner maps or programs within the same 565 // CollectionSpec do so using strings. These strings are used as the key 566 // to look up the respective object in the Maps or Programs fields. 567 // Resolve those references to actual Map or Program resources that 568 // have been loaded into the kernel. 569 for i, kv := range mapSpec.Contents { 570 if objName, ok := kv.Value.(string); ok { 571 switch mapSpec.Type { 572 case ProgramArray: 573 // loadProgram is idempotent and could return an existing Program. 574 prog, err := cl.loadProgram(objName) 575 if err != nil { 576 return fmt.Errorf("loading program %s, for map %s: %w", objName, mapName, err) 577 } 578 mapSpec.Contents[i] = MapKV{kv.Key, prog} 579 580 case ArrayOfMaps, HashOfMaps: 581 // loadMap is idempotent and could return an existing Map. 582 innerMap, err := cl.loadMap(objName) 583 if err != nil { 584 return fmt.Errorf("loading inner map %s, for map %s: %w", objName, mapName, err) 585 } 586 mapSpec.Contents[i] = MapKV{kv.Key, innerMap} 587 } 588 } 589 } 590 591 // Populate and freeze the map if specified. 592 if err := m.finalize(mapSpec); err != nil { 593 return fmt.Errorf("populating map %s: %w", mapName, err) 594 } 595 } 596 597 return nil 598 } 599 600 // LoadCollection reads an object file and creates and loads its declared 601 // resources into the kernel. 602 // 603 // Omitting Collection.Close() during application shutdown is an error. 604 // See the package documentation for details around Map and Program lifecycle. 605 func LoadCollection(file string) (*Collection, error) { 606 spec, err := LoadCollectionSpec(file) 607 if err != nil { 608 return nil, err 609 } 610 return NewCollection(spec) 611 } 612 613 // Close frees all maps and programs associated with the collection. 614 // 615 // The collection mustn't be used afterwards. 616 func (coll *Collection) Close() { 617 for _, prog := range coll.Programs { 618 prog.Close() 619 } 620 for _, m := range coll.Maps { 621 m.Close() 622 } 623 } 624 625 // DetachMap removes the named map from the Collection. 626 // 627 // This means that a later call to Close() will not affect this map. 628 // 629 // Returns nil if no map of that name exists. 630 func (coll *Collection) DetachMap(name string) *Map { 631 m := coll.Maps[name] 632 delete(coll.Maps, name) 633 return m 634 } 635 636 // DetachProgram removes the named program from the Collection. 637 // 638 // This means that a later call to Close() will not affect this program. 639 // 640 // Returns nil if no program of that name exists. 641 func (coll *Collection) DetachProgram(name string) *Program { 642 p := coll.Programs[name] 643 delete(coll.Programs, name) 644 return p 645 } 646 647 // structField represents a struct field containing the ebpf struct tag. 648 type structField struct { 649 reflect.StructField 650 value reflect.Value 651 } 652 653 // ebpfFields extracts field names tagged with 'ebpf' from a struct type. 654 // Keep track of visited types to avoid infinite recursion. 655 func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]structField, error) { 656 if visited == nil { 657 visited = make(map[reflect.Type]bool) 658 } 659 660 structType := structVal.Type() 661 if structType.Kind() != reflect.Struct { 662 return nil, fmt.Errorf("%s is not a struct", structType) 663 } 664 665 if visited[structType] { 666 return nil, fmt.Errorf("recursion on type %s", structType) 667 } 668 669 fields := make([]structField, 0, structType.NumField()) 670 for i := 0; i < structType.NumField(); i++ { 671 field := structField{structType.Field(i), structVal.Field(i)} 672 673 // If the field is tagged, gather it and move on. 674 name := field.Tag.Get("ebpf") 675 if name != "" { 676 fields = append(fields, field) 677 continue 678 } 679 680 // If the field does not have an ebpf tag, but is a struct or a pointer 681 // to a struct, attempt to gather its fields as well. 682 var v reflect.Value 683 switch field.Type.Kind() { 684 case reflect.Ptr: 685 if field.Type.Elem().Kind() != reflect.Struct { 686 continue 687 } 688 689 if field.value.IsNil() { 690 return nil, fmt.Errorf("nil pointer to %s", structType) 691 } 692 693 // Obtain the destination type of the pointer. 694 v = field.value.Elem() 695 696 case reflect.Struct: 697 // Reference the value's type directly. 698 v = field.value 699 700 default: 701 continue 702 } 703 704 inner, err := ebpfFields(v, visited) 705 if err != nil { 706 return nil, fmt.Errorf("field %s: %w", field.Name, err) 707 } 708 709 fields = append(fields, inner...) 710 } 711 712 return fields, nil 713 } 714 715 // assignValues attempts to populate all fields of 'to' tagged with 'ebpf'. 716 // 717 // getValue is called for every tagged field of 'to' and must return the value 718 // to be assigned to the field with the given typ and name. 719 func assignValues(to interface{}, 720 getValue func(typ reflect.Type, name string) (interface{}, error)) error { 721 722 toValue := reflect.ValueOf(to) 723 if toValue.Type().Kind() != reflect.Ptr { 724 return fmt.Errorf("%T is not a pointer to struct", to) 725 } 726 727 if toValue.IsNil() { 728 return fmt.Errorf("nil pointer to %T", to) 729 } 730 731 fields, err := ebpfFields(toValue.Elem(), nil) 732 if err != nil { 733 return err 734 } 735 736 type elem struct { 737 // Either *Map or *Program 738 typ reflect.Type 739 name string 740 } 741 742 assigned := make(map[elem]string) 743 for _, field := range fields { 744 // Get string value the field is tagged with. 745 tag := field.Tag.Get("ebpf") 746 if strings.Contains(tag, ",") { 747 return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name) 748 } 749 750 // Check if the eBPF object with the requested 751 // type and tag was already assigned elsewhere. 752 e := elem{field.Type, tag} 753 if af := assigned[e]; af != "" { 754 return fmt.Errorf("field %s: object %q was already assigned to %s", field.Name, tag, af) 755 } 756 757 // Get the eBPF object referred to by the tag. 758 value, err := getValue(field.Type, tag) 759 if err != nil { 760 return fmt.Errorf("field %s: %w", field.Name, err) 761 } 762 763 if !field.value.CanSet() { 764 return fmt.Errorf("field %s: can't set value", field.Name) 765 } 766 field.value.Set(reflect.ValueOf(value)) 767 768 assigned[e] = field.Name 769 } 770 771 return nil 772 }