github.com/cilium/ebpf@v0.10.0/collection.go (about) 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 _, ok := v.Type.(*btf.Var); !ok { 155 return fmt.Errorf("section %s: unexpected type %T for variable %s", name, v.Type, vname) 156 } 157 158 if replaced[vname] { 159 return fmt.Errorf("section %s: duplicate variable %s", name, vname) 160 } 161 162 if int(v.Offset+v.Size) > len(cpy) { 163 return fmt.Errorf("section %s: offset %d(+%d) for variable %s is out of bounds", name, v.Offset, v.Size, vname) 164 } 165 166 b, err := marshalBytes(replacement, int(v.Size)) 167 if err != nil { 168 return fmt.Errorf("marshaling constant replacement %s: %w", vname, err) 169 } 170 171 copy(cpy[v.Offset:v.Offset+v.Size], b) 172 173 replaced[vname] = true 174 } 175 176 spec.Contents[0] = MapKV{Key: uint32(0), Value: cpy} 177 } 178 179 var missing []string 180 for c := range consts { 181 if !replaced[c] { 182 missing = append(missing, c) 183 } 184 } 185 186 if len(missing) != 0 { 187 return fmt.Errorf("spec is missing one or more constants: %s", strings.Join(missing, ",")) 188 } 189 190 return nil 191 } 192 193 // Assign the contents of a CollectionSpec to a struct. 194 // 195 // This function is a shortcut to manually checking the presence 196 // of maps and programs in a CollectionSpec. Consider using bpf2go 197 // if this sounds useful. 198 // 199 // 'to' must be a pointer to a struct. A field of the 200 // struct is updated with values from Programs or Maps if it 201 // has an `ebpf` tag and its type is *ProgramSpec or *MapSpec. 202 // The tag's value specifies the name of the program or map as 203 // found in the CollectionSpec. 204 // 205 // struct { 206 // Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"` 207 // Bar *ebpf.MapSpec `ebpf:"bar_map"` 208 // Ignored int 209 // } 210 // 211 // Returns an error if any of the eBPF objects can't be found, or 212 // if the same MapSpec or ProgramSpec is assigned multiple times. 213 func (cs *CollectionSpec) Assign(to interface{}) error { 214 // Assign() only supports assigning ProgramSpecs and MapSpecs, 215 // so doesn't load any resources into the kernel. 216 getValue := func(typ reflect.Type, name string) (interface{}, error) { 217 switch typ { 218 219 case reflect.TypeOf((*ProgramSpec)(nil)): 220 if p := cs.Programs[name]; p != nil { 221 return p, nil 222 } 223 return nil, fmt.Errorf("missing program %q", name) 224 225 case reflect.TypeOf((*MapSpec)(nil)): 226 if m := cs.Maps[name]; m != nil { 227 return m, nil 228 } 229 return nil, fmt.Errorf("missing map %q", name) 230 231 default: 232 return nil, fmt.Errorf("unsupported type %s", typ) 233 } 234 } 235 236 return assignValues(to, getValue) 237 } 238 239 // LoadAndAssign loads Maps and Programs into the kernel and assigns them 240 // to a struct. 241 // 242 // Omitting Map/Program.Close() during application shutdown is an error. 243 // See the package documentation for details around Map and Program lifecycle. 244 // 245 // This function is a shortcut to manually checking the presence 246 // of maps and programs in a CollectionSpec. Consider using bpf2go 247 // if this sounds useful. 248 // 249 // 'to' must be a pointer to a struct. A field of the struct is updated with 250 // a Program or Map if it has an `ebpf` tag and its type is *Program or *Map. 251 // The tag's value specifies the name of the program or map as found in the 252 // CollectionSpec. Before updating the struct, the requested objects and their 253 // dependent resources are loaded into the kernel and populated with values if 254 // specified. 255 // 256 // struct { 257 // Foo *ebpf.Program `ebpf:"xdp_foo"` 258 // Bar *ebpf.Map `ebpf:"bar_map"` 259 // Ignored int 260 // } 261 // 262 // opts may be nil. 263 // 264 // Returns an error if any of the fields can't be found, or 265 // if the same Map or Program is assigned multiple times. 266 func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error { 267 loader, err := newCollectionLoader(cs, opts) 268 if err != nil { 269 return err 270 } 271 defer loader.close() 272 273 // Support assigning Programs and Maps, lazy-loading the required objects. 274 assignedMaps := make(map[string]bool) 275 assignedProgs := make(map[string]bool) 276 277 getValue := func(typ reflect.Type, name string) (interface{}, error) { 278 switch typ { 279 280 case reflect.TypeOf((*Program)(nil)): 281 assignedProgs[name] = true 282 return loader.loadProgram(name) 283 284 case reflect.TypeOf((*Map)(nil)): 285 assignedMaps[name] = true 286 return loader.loadMap(name) 287 288 default: 289 return nil, fmt.Errorf("unsupported type %s", typ) 290 } 291 } 292 293 // Load the Maps and Programs requested by the annotated struct. 294 if err := assignValues(to, getValue); err != nil { 295 return err 296 } 297 298 // Populate the requested maps. Has a chance of lazy-loading other dependent maps. 299 if err := loader.populateMaps(); err != nil { 300 return err 301 } 302 303 // Evaluate the loader's objects after all (lazy)loading has taken place. 304 for n, m := range loader.maps { 305 switch m.typ { 306 case ProgramArray: 307 // Require all lazy-loaded ProgramArrays to be assigned to the given object. 308 // The kernel empties a ProgramArray once the last user space reference 309 // to it closes, which leads to failed tail calls. Combined with the library 310 // closing map fds via GC finalizers this can lead to surprising behaviour. 311 // Only allow unassigned ProgramArrays when the library hasn't pre-populated 312 // any entries from static value declarations. At this point, we know the map 313 // is empty and there's no way for the caller to interact with the map going 314 // forward. 315 if !assignedMaps[n] && len(cs.Maps[n].Contents) > 0 { 316 return fmt.Errorf("ProgramArray %s must be assigned to prevent missed tail calls", n) 317 } 318 } 319 } 320 321 // Prevent loader.cleanup() from closing assigned Maps and Programs. 322 for m := range assignedMaps { 323 delete(loader.maps, m) 324 } 325 for p := range assignedProgs { 326 delete(loader.programs, p) 327 } 328 329 return nil 330 } 331 332 // Collection is a collection of Programs and Maps associated 333 // with their symbols 334 type Collection struct { 335 Programs map[string]*Program 336 Maps map[string]*Map 337 } 338 339 // NewCollection creates a Collection from the given spec, creating and 340 // loading its declared resources into the kernel. 341 // 342 // Omitting Collection.Close() during application shutdown is an error. 343 // See the package documentation for details around Map and Program lifecycle. 344 func NewCollection(spec *CollectionSpec) (*Collection, error) { 345 return NewCollectionWithOptions(spec, CollectionOptions{}) 346 } 347 348 // NewCollectionWithOptions creates a Collection from the given spec using 349 // options, creating and loading its declared resources into the kernel. 350 // 351 // Omitting Collection.Close() during application shutdown is an error. 352 // See the package documentation for details around Map and Program lifecycle. 353 func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) { 354 loader, err := newCollectionLoader(spec, &opts) 355 if err != nil { 356 return nil, err 357 } 358 defer loader.close() 359 360 // Create maps first, as their fds need to be linked into programs. 361 for mapName := range spec.Maps { 362 if _, err := loader.loadMap(mapName); err != nil { 363 return nil, err 364 } 365 } 366 367 for progName, prog := range spec.Programs { 368 if prog.Type == UnspecifiedProgram { 369 continue 370 } 371 372 if _, err := loader.loadProgram(progName); err != nil { 373 return nil, err 374 } 375 } 376 377 // Maps can contain Program and Map stubs, so populate them after 378 // all Maps and Programs have been successfully loaded. 379 if err := loader.populateMaps(); err != nil { 380 return nil, err 381 } 382 383 // Prevent loader.cleanup from closing maps and programs. 384 maps, progs := loader.maps, loader.programs 385 loader.maps, loader.programs = nil, nil 386 387 return &Collection{ 388 progs, 389 maps, 390 }, nil 391 } 392 393 type collectionLoader struct { 394 coll *CollectionSpec 395 opts *CollectionOptions 396 maps map[string]*Map 397 programs map[string]*Program 398 } 399 400 func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collectionLoader, error) { 401 if opts == nil { 402 opts = &CollectionOptions{} 403 } 404 405 // Check for existing MapSpecs in the CollectionSpec for all provided replacement maps. 406 for name, m := range opts.MapReplacements { 407 spec, ok := coll.Maps[name] 408 if !ok { 409 return nil, fmt.Errorf("replacement map %s not found in CollectionSpec", name) 410 } 411 412 if err := spec.Compatible(m); err != nil { 413 return nil, fmt.Errorf("using replacement map %s: %w", spec.Name, err) 414 } 415 } 416 417 return &collectionLoader{ 418 coll, 419 opts, 420 make(map[string]*Map), 421 make(map[string]*Program), 422 }, nil 423 } 424 425 // close all resources left over in the collectionLoader. 426 func (cl *collectionLoader) close() { 427 for _, m := range cl.maps { 428 m.Close() 429 } 430 for _, p := range cl.programs { 431 p.Close() 432 } 433 } 434 435 func (cl *collectionLoader) loadMap(mapName string) (*Map, error) { 436 if m := cl.maps[mapName]; m != nil { 437 return m, nil 438 } 439 440 mapSpec := cl.coll.Maps[mapName] 441 if mapSpec == nil { 442 return nil, fmt.Errorf("missing map %s", mapName) 443 } 444 445 if replaceMap, ok := cl.opts.MapReplacements[mapName]; ok { 446 // Clone the map to avoid closing user's map later on. 447 m, err := replaceMap.Clone() 448 if err != nil { 449 return nil, err 450 } 451 452 cl.maps[mapName] = m 453 return m, nil 454 } 455 456 m, err := newMapWithOptions(mapSpec, cl.opts.Maps) 457 if err != nil { 458 return nil, fmt.Errorf("map %s: %w", mapName, err) 459 } 460 461 cl.maps[mapName] = m 462 return m, nil 463 } 464 465 func (cl *collectionLoader) loadProgram(progName string) (*Program, error) { 466 if prog := cl.programs[progName]; prog != nil { 467 return prog, nil 468 } 469 470 progSpec := cl.coll.Programs[progName] 471 if progSpec == nil { 472 return nil, fmt.Errorf("unknown program %s", progName) 473 } 474 475 // Bail out early if we know the kernel is going to reject the program. 476 // This skips loading map dependencies, saving some cleanup work later. 477 if progSpec.Type == UnspecifiedProgram { 478 return nil, fmt.Errorf("cannot load program %s: program type is unspecified", progName) 479 } 480 481 progSpec = progSpec.Copy() 482 483 // Rewrite any reference to a valid map in the program's instructions, 484 // which includes all of its dependencies. 485 for i := range progSpec.Instructions { 486 ins := &progSpec.Instructions[i] 487 488 if !ins.IsLoadFromMap() || ins.Reference() == "" { 489 continue 490 } 491 492 // Don't overwrite map loads containing non-zero map fd's, 493 // they can be manually included by the caller. 494 // Map FDs/IDs are placed in the lower 32 bits of Constant. 495 if int32(ins.Constant) > 0 { 496 continue 497 } 498 499 m, err := cl.loadMap(ins.Reference()) 500 if err != nil { 501 return nil, fmt.Errorf("program %s: %w", progName, err) 502 } 503 504 if err := ins.AssociateMap(m); err != nil { 505 return nil, fmt.Errorf("program %s: map %s: %w", progName, ins.Reference(), err) 506 } 507 } 508 509 prog, err := newProgramWithOptions(progSpec, cl.opts.Programs) 510 if err != nil { 511 return nil, fmt.Errorf("program %s: %w", progName, err) 512 } 513 514 cl.programs[progName] = prog 515 return prog, nil 516 } 517 518 func (cl *collectionLoader) populateMaps() error { 519 for mapName, m := range cl.maps { 520 mapSpec, ok := cl.coll.Maps[mapName] 521 if !ok { 522 return fmt.Errorf("missing map spec %s", mapName) 523 } 524 525 // MapSpecs that refer to inner maps or programs within the same 526 // CollectionSpec do so using strings. These strings are used as the key 527 // to look up the respective object in the Maps or Programs fields. 528 // Resolve those references to actual Map or Program resources that 529 // have been loaded into the kernel. 530 if mapSpec.Type.canStoreMap() || mapSpec.Type.canStoreProgram() { 531 mapSpec = mapSpec.Copy() 532 533 for i, kv := range mapSpec.Contents { 534 objName, ok := kv.Value.(string) 535 if !ok { 536 continue 537 } 538 539 switch t := mapSpec.Type; { 540 case t.canStoreProgram(): 541 // loadProgram is idempotent and could return an existing Program. 542 prog, err := cl.loadProgram(objName) 543 if err != nil { 544 return fmt.Errorf("loading program %s, for map %s: %w", objName, mapName, err) 545 } 546 mapSpec.Contents[i] = MapKV{kv.Key, prog} 547 548 case t.canStoreMap(): 549 // loadMap is idempotent and could return an existing Map. 550 innerMap, err := cl.loadMap(objName) 551 if err != nil { 552 return fmt.Errorf("loading inner map %s, for map %s: %w", objName, mapName, err) 553 } 554 mapSpec.Contents[i] = MapKV{kv.Key, innerMap} 555 } 556 } 557 } 558 559 // Populate and freeze the map if specified. 560 if err := m.finalize(mapSpec); err != nil { 561 return fmt.Errorf("populating map %s: %w", mapName, err) 562 } 563 } 564 565 return nil 566 } 567 568 // LoadCollection reads an object file and creates and loads its declared 569 // resources into the kernel. 570 // 571 // Omitting Collection.Close() during application shutdown is an error. 572 // See the package documentation for details around Map and Program lifecycle. 573 func LoadCollection(file string) (*Collection, error) { 574 spec, err := LoadCollectionSpec(file) 575 if err != nil { 576 return nil, err 577 } 578 return NewCollection(spec) 579 } 580 581 // Close frees all maps and programs associated with the collection. 582 // 583 // The collection mustn't be used afterwards. 584 func (coll *Collection) Close() { 585 for _, prog := range coll.Programs { 586 prog.Close() 587 } 588 for _, m := range coll.Maps { 589 m.Close() 590 } 591 } 592 593 // DetachMap removes the named map from the Collection. 594 // 595 // This means that a later call to Close() will not affect this map. 596 // 597 // Returns nil if no map of that name exists. 598 func (coll *Collection) DetachMap(name string) *Map { 599 m := coll.Maps[name] 600 delete(coll.Maps, name) 601 return m 602 } 603 604 // DetachProgram removes the named program from the Collection. 605 // 606 // This means that a later call to Close() will not affect this program. 607 // 608 // Returns nil if no program of that name exists. 609 func (coll *Collection) DetachProgram(name string) *Program { 610 p := coll.Programs[name] 611 delete(coll.Programs, name) 612 return p 613 } 614 615 // structField represents a struct field containing the ebpf struct tag. 616 type structField struct { 617 reflect.StructField 618 value reflect.Value 619 } 620 621 // ebpfFields extracts field names tagged with 'ebpf' from a struct type. 622 // Keep track of visited types to avoid infinite recursion. 623 func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]structField, error) { 624 if visited == nil { 625 visited = make(map[reflect.Type]bool) 626 } 627 628 structType := structVal.Type() 629 if structType.Kind() != reflect.Struct { 630 return nil, fmt.Errorf("%s is not a struct", structType) 631 } 632 633 if visited[structType] { 634 return nil, fmt.Errorf("recursion on type %s", structType) 635 } 636 637 fields := make([]structField, 0, structType.NumField()) 638 for i := 0; i < structType.NumField(); i++ { 639 field := structField{structType.Field(i), structVal.Field(i)} 640 641 // If the field is tagged, gather it and move on. 642 name := field.Tag.Get("ebpf") 643 if name != "" { 644 fields = append(fields, field) 645 continue 646 } 647 648 // If the field does not have an ebpf tag, but is a struct or a pointer 649 // to a struct, attempt to gather its fields as well. 650 var v reflect.Value 651 switch field.Type.Kind() { 652 case reflect.Ptr: 653 if field.Type.Elem().Kind() != reflect.Struct { 654 continue 655 } 656 657 if field.value.IsNil() { 658 return nil, fmt.Errorf("nil pointer to %s", structType) 659 } 660 661 // Obtain the destination type of the pointer. 662 v = field.value.Elem() 663 664 case reflect.Struct: 665 // Reference the value's type directly. 666 v = field.value 667 668 default: 669 continue 670 } 671 672 inner, err := ebpfFields(v, visited) 673 if err != nil { 674 return nil, fmt.Errorf("field %s: %w", field.Name, err) 675 } 676 677 fields = append(fields, inner...) 678 } 679 680 return fields, nil 681 } 682 683 // assignValues attempts to populate all fields of 'to' tagged with 'ebpf'. 684 // 685 // getValue is called for every tagged field of 'to' and must return the value 686 // to be assigned to the field with the given typ and name. 687 func assignValues(to interface{}, 688 getValue func(typ reflect.Type, name string) (interface{}, error)) error { 689 690 toValue := reflect.ValueOf(to) 691 if toValue.Type().Kind() != reflect.Ptr { 692 return fmt.Errorf("%T is not a pointer to struct", to) 693 } 694 695 if toValue.IsNil() { 696 return fmt.Errorf("nil pointer to %T", to) 697 } 698 699 fields, err := ebpfFields(toValue.Elem(), nil) 700 if err != nil { 701 return err 702 } 703 704 type elem struct { 705 // Either *Map or *Program 706 typ reflect.Type 707 name string 708 } 709 710 assigned := make(map[elem]string) 711 for _, field := range fields { 712 // Get string value the field is tagged with. 713 tag := field.Tag.Get("ebpf") 714 if strings.Contains(tag, ",") { 715 return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name) 716 } 717 718 // Check if the eBPF object with the requested 719 // type and tag was already assigned elsewhere. 720 e := elem{field.Type, tag} 721 if af := assigned[e]; af != "" { 722 return fmt.Errorf("field %s: object %q was already assigned to %s", field.Name, tag, af) 723 } 724 725 // Get the eBPF object referred to by the tag. 726 value, err := getValue(field.Type, tag) 727 if err != nil { 728 return fmt.Errorf("field %s: %w", field.Name, err) 729 } 730 731 if !field.value.CanSet() { 732 return fmt.Errorf("field %s: can't set value", field.Name) 733 } 734 field.value.Set(reflect.ValueOf(value)) 735 736 assigned[e] = field.Name 737 } 738 739 return nil 740 }