github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/operators/ebpf/ebpf.go (about) 1 // Copyright 2024 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package ebpfoperator provides an operator that is capable of analyzing and running 16 // an eBFP based gadget. 17 package ebpfoperator 18 19 import ( 20 "bufio" 21 "bytes" 22 "fmt" 23 "io" 24 "os" 25 "reflect" 26 "strings" 27 "sync" 28 29 "github.com/cilium/ebpf" 30 "github.com/cilium/ebpf/btf" 31 "github.com/cilium/ebpf/link" 32 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 33 "github.com/spf13/viper" 34 35 containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" 36 "github.com/inspektor-gadget/inspektor-gadget/pkg/datasource" 37 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api" 38 apihelpers "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api-helpers" 39 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets" 40 "github.com/inspektor-gadget/inspektor-gadget/pkg/logger" 41 "github.com/inspektor-gadget/inspektor-gadget/pkg/networktracer" 42 "github.com/inspektor-gadget/inspektor-gadget/pkg/oci" 43 "github.com/inspektor-gadget/inspektor-gadget/pkg/operators" 44 "github.com/inspektor-gadget/inspektor-gadget/pkg/params" 45 "github.com/inspektor-gadget/inspektor-gadget/pkg/socketenricher" 46 "github.com/inspektor-gadget/inspektor-gadget/pkg/tchandler" 47 "github.com/inspektor-gadget/inspektor-gadget/pkg/uprobetracer" 48 ) 49 50 const ( 51 eBPFObjectMediaType = "application/vnd.gadget.ebpf.program.v1+binary" 52 53 typeSplitter = "___" 54 55 ParamIface = "iface" 56 ParamTraceKernel = "trace-pipe" 57 58 kernelTypesVar = "kernelTypes" 59 ) 60 61 type param struct { 62 *api.Param 63 fromEbpf bool 64 } 65 66 // ebpfOperator reads ebpf programs from OCI images and runs them 67 type ebpfOperator struct{} 68 69 func (o *ebpfOperator) Name() string { 70 return "ebpf" 71 } 72 73 func (o *ebpfOperator) Description() string { 74 return "handles ebpf programs" 75 } 76 77 func (o *ebpfOperator) InstantiateImageOperator( 78 gadgetCtx operators.GadgetContext, 79 desc ocispec.Descriptor, 80 paramValues api.ParamValues, 81 ) ( 82 operators.ImageOperatorInstance, error, 83 ) { 84 r, err := oci.GetContentFromDescriptor(gadgetCtx.Context(), desc) 85 if err != nil { 86 return nil, fmt.Errorf("getting ebpf binary: %w", err) 87 } 88 program, err := io.ReadAll(r) 89 if err != nil { 90 r.Close() 91 return nil, fmt.Errorf("reading ebpf binary: %w", err) 92 } 93 r.Close() 94 95 // TODO: do some pre-checks in here, maybe validate hashes, signatures, etc. 96 97 newInstance := &ebpfInstance{ 98 gadgetCtx: gadgetCtx, // context usually should not be stored, but should we really carry it through all funcs? 99 100 logger: gadgetCtx.Logger(), 101 program: program, 102 103 // Preallocate maps 104 tracers: make(map[string]*Tracer), 105 structs: make(map[string]*Struct), 106 snapshotters: make(map[string]*Snapshotter), 107 params: make(map[string]*param), 108 109 containers: make(map[string]*containercollection.Container), 110 111 enums: make(map[string]*btf.Enum), 112 converters: make(map[datasource.DataSource][]func(ds datasource.DataSource, data datasource.Data) error), 113 114 vars: make(map[string]*ebpfVar), 115 116 networkTracers: make(map[string]*networktracer.Tracer[api.GadgetData]), 117 tcHandlers: make(map[string]*tchandler.Handler), 118 uprobeTracers: make(map[string]*uprobetracer.Tracer[api.GadgetData]), 119 120 paramValues: paramValues, 121 } 122 123 cfg, ok := gadgetCtx.GetVar("config") 124 if !ok { 125 return nil, fmt.Errorf("missing configuration") 126 } 127 v, ok := cfg.(*viper.Viper) 128 if !ok { 129 return nil, fmt.Errorf("invalid configuration format") 130 } 131 newInstance.config = v 132 133 err = newInstance.init(gadgetCtx) 134 if err != nil { 135 return nil, fmt.Errorf("initializing ebpf gadget: %w", err) 136 } 137 138 return newInstance, nil 139 } 140 141 type ebpfInstance struct { 142 mu sync.Mutex 143 144 config *viper.Viper 145 146 program []byte 147 logger logger.Logger 148 collectionSpec *ebpf.CollectionSpec 149 collection *ebpf.Collection 150 151 tracers map[string]*Tracer 152 structs map[string]*Struct 153 snapshotters map[string]*Snapshotter 154 params map[string]*param 155 paramValues map[string]string 156 157 networkTracers map[string]*networktracer.Tracer[api.GadgetData] 158 tcHandlers map[string]*tchandler.Handler 159 uprobeTracers map[string]*uprobetracer.Tracer[api.GadgetData] 160 161 // map from ebpf variable name to ebpfVar struct 162 vars map[string]*ebpfVar 163 164 links []link.Link 165 166 containers map[string]*containercollection.Container 167 168 enums map[string]*btf.Enum 169 converters map[datasource.DataSource][]func(ds datasource.DataSource, data datasource.Data) error 170 171 gadgetCtx operators.GadgetContext 172 } 173 174 func (i *ebpfInstance) loadSpec() error { 175 progReader := bytes.NewReader(i.program) 176 spec, err := ebpf.LoadCollectionSpecFromReader(progReader) 177 if err != nil { 178 return fmt.Errorf("loading spec: %w", err) 179 } 180 i.collectionSpec = spec 181 return nil 182 } 183 184 func (i *ebpfInstance) analyze() error { 185 prefixLookups := []populateEntry{ 186 { 187 prefixFunc: hasPrefix(tracerInfoPrefix), 188 validator: i.validateGlobalConstVoidPtrVar, 189 populateFunc: i.populateTracer, 190 }, 191 { 192 prefixFunc: hasPrefix(snapshottersPrefix), 193 validator: i.validateGlobalConstVoidPtrVar, 194 populateFunc: i.populateSnapshotter, 195 }, 196 { 197 prefixFunc: hasPrefix(paramPrefix), 198 validator: i.validateGlobalConstVoidPtrVar, 199 populateFunc: i.populateParam, 200 }, 201 // { 202 // prefixFunc: hasPrefix(tracerMapPrefix), 203 // validator: i.validateGlobalConstVoidPtrVar, 204 // populateFunc: i.populateMap, 205 // }, 206 { 207 prefixFunc: func(s string) (string, bool) { 208 // Exceptions for backwards-compatibility 209 if s == gadgets.MntNsFilterMapName { 210 return gadgets.MntNsFilterMapName, true 211 } 212 if s == socketenricher.SocketsMapName { 213 return socketenricher.SocketsMapName, true 214 } 215 return "", false 216 }, 217 populateFunc: i.populateMap, 218 }, 219 { 220 prefixFunc: func(s string) (string, bool) { 221 // Exceptions for backwards-compatibility 222 if s == gadgets.FilterByMntNsName { 223 return gadgets.FilterByMntNsName, true 224 } 225 return hasPrefix(varPrefix)(s) 226 }, 227 validator: nil, 228 populateFunc: i.populateVar, 229 }, 230 } 231 232 // Iterate over types and populate the gadget 233 it := i.collectionSpec.Types.Iterate() 234 for it.Next() { 235 for _, entry := range prefixLookups { 236 typeName, ok := entry.prefixFunc(it.Type.TypeName()) 237 if !ok { 238 continue 239 } 240 if entry.validator != nil { 241 err := entry.validator(it.Type, strings.TrimPrefix(it.Type.TypeName(), typeName)) 242 if err != nil { 243 i.logger.Debugf("type %q error: %v", it.Type.TypeName(), err) 244 continue 245 } 246 } 247 err := entry.populateFunc(it.Type, typeName) 248 if err != nil { 249 return fmt.Errorf("handling type by prefix %q: %w", typeName, err) 250 } 251 } 252 } 253 254 // Fill param defaults 255 err := i.fillParamDefaults() 256 if err != nil { 257 i.logger.Debugf("error extracting default values for params: %v", err) 258 } 259 260 // Iterate over programs 261 for name, program := range i.collectionSpec.Programs { 262 i.logger.Debugf("program %q", name) 263 i.logger.Debugf("> type : %s", program.Type.String()) 264 i.logger.Debugf("> attachType : %s", program.AttachType.String()) 265 i.logger.Debugf("> sectionName: %s", program.SectionName) 266 i.logger.Debugf("> license : %s", program.License) 267 } 268 return nil 269 } 270 271 func (i *ebpfInstance) init(gadgetCtx operators.GadgetContext) error { 272 // hack for backward-compability and until we have nicer interfaces available 273 gadgetCtx.SetVar("ebpfInstance", i) 274 275 // loadSpec and analyze could be lazily executed, if the gadget has been cached before 276 err := i.loadSpec() 277 if err != nil { 278 return fmt.Errorf("initializing: %w", err) 279 } 280 err = i.analyze() 281 if err != nil { 282 return fmt.Errorf("analyzing: %w", err) 283 } 284 285 err = i.register(gadgetCtx) 286 if err != nil { 287 return fmt.Errorf("registering datasources: %w", err) 288 } 289 290 err = i.initConverters(gadgetCtx) 291 if err != nil { 292 return fmt.Errorf("initializing formatters: %w", err) 293 } 294 295 return nil 296 } 297 298 func (i *ebpfInstance) addDataSource( 299 gadgetCtx operators.GadgetContext, 300 dsType datasource.Type, 301 name string, 302 size uint32, 303 fields []*Field, 304 ) ( 305 datasource.DataSource, datasource.FieldAccessor, error, 306 ) { 307 ds, err := gadgetCtx.RegisterDataSource(dsType, name) 308 if err != nil { 309 return nil, nil, fmt.Errorf("adding tracer datasource: %w", err) 310 } 311 staticFields := make([]datasource.StaticField, 0, len(fields)) 312 for _, field := range fields { 313 staticFields = append(staticFields, field) 314 } 315 accessor, err := ds.AddStaticFields(size, staticFields) 316 if err != nil { 317 return nil, nil, fmt.Errorf("adding fields for datasource: %w", err) 318 } 319 return ds, accessor, nil 320 } 321 322 func (i *ebpfInstance) register(gadgetCtx operators.GadgetContext) error { 323 // register datasources 324 for name, m := range i.tracers { 325 ds, accessor, err := i.addDataSource(gadgetCtx, datasource.TypeEvent, name, i.structs[m.StructName].Size, i.structs[m.StructName].Fields) 326 if err != nil { 327 return fmt.Errorf("adding datasource: %w", err) 328 } 329 m.accessor = accessor 330 m.ds = ds 331 } 332 for name, m := range i.snapshotters { 333 ds, accessor, err := i.addDataSource(gadgetCtx, datasource.TypeEvent, name, i.structs[m.StructName].Size, i.structs[m.StructName].Fields) 334 if err != nil { 335 return fmt.Errorf("adding datasource: %w", err) 336 } 337 338 // TODO: need a link to find out if this is a snapshotter for network; if so, we can add the netns id 339 m.netns, err = ds.AddField("netns", 340 datasource.WithTags("type:gadget_netns_id", "name:netns"), 341 datasource.WithKind(api.Kind_Uint64)) 342 if err != nil { 343 return fmt.Errorf("adding netnsid") 344 } 345 346 m.accessor = accessor 347 m.ds = ds 348 } 349 return nil 350 } 351 352 func (i *ebpfInstance) Name() string { 353 return "ebpf" 354 } 355 356 func (i *ebpfInstance) ExtraParams(gadgetCtx operators.GadgetContext) api.Params { 357 res := make(api.Params, 0, len(i.params)) 358 for _, p := range i.params { 359 res = append(res, p.Param) 360 } 361 return res 362 } 363 364 func (i *ebpfInstance) Prepare(gadgetCtx operators.GadgetContext) error { 365 for ds, converters := range i.converters { 366 for _, converter := range converters { 367 converter := converter 368 ds.Subscribe(func(ds datasource.DataSource, data datasource.Data) error { 369 return converter(ds, data) 370 }, 0) 371 } 372 } 373 374 // Create network tracers, one for each socket filter program 375 // The same applies to uprobe / uretprobe as well. 376 for _, p := range i.collectionSpec.Programs { 377 switch p.Type { 378 case ebpf.Kprobe: 379 if strings.HasPrefix(p.SectionName, "uprobe/") || 380 strings.HasPrefix(p.SectionName, "uretprobe/") || 381 strings.HasPrefix(p.SectionName, "usdt/") { 382 uprobeTracer, err := uprobetracer.NewTracer[api.GadgetData](gadgetCtx.Logger()) 383 if err != nil { 384 i.Close() 385 return fmt.Errorf("creating uprobe tracer: %w", err) 386 } 387 i.uprobeTracers[p.Name] = uprobeTracer 388 } 389 case ebpf.SocketFilter: 390 if strings.HasPrefix(p.SectionName, "socket") { 391 networkTracer, err := networktracer.NewTracer[api.GadgetData]() 392 if err != nil { 393 i.Close() 394 return fmt.Errorf("creating network tracer: %w", err) 395 } 396 i.networkTracers[p.Name] = networkTracer 397 } 398 case ebpf.SchedCLS: 399 parts := strings.Split(p.SectionName, "/") 400 if len(parts) != 3 { 401 return fmt.Errorf("invalid section name %q", p.SectionName) 402 } 403 if parts[0] != "classifier" { 404 return fmt.Errorf("invalid section name %q", p.SectionName) 405 } 406 407 var direction tchandler.AttachmentDirection 408 409 switch parts[1] { 410 case "ingress": 411 direction = tchandler.AttachmentDirectionIngress 412 case "egress": 413 direction = tchandler.AttachmentDirectionEgress 414 default: 415 return fmt.Errorf("unsupported hook type %q", parts[1]) 416 } 417 418 handler, err := tchandler.NewHandler(direction) 419 if err != nil { 420 i.Close() 421 return fmt.Errorf("creating tc network tracer: %w", err) 422 } 423 424 i.tcHandlers[p.Name] = handler 425 } 426 } 427 428 if len(i.tcHandlers) > 0 { 429 // For now, override enrichment 430 gadgetCtx.SetVar("NeedContainerEvents", true) 431 i.params["iface"] = ¶m{ 432 Param: &api.Param{ 433 Key: ParamIface, 434 Description: "Network interface to attach to", 435 }, 436 } 437 } 438 439 i.params[ParamTraceKernel] = ¶m{ 440 Param: &api.Param{ 441 Key: ParamTraceKernel, 442 DefaultValue: "false", 443 TypeHint: api.TypeBool, 444 }, 445 } 446 return nil 447 } 448 449 func (i *ebpfInstance) tracePipe(gadgetCtx operators.GadgetContext) error { 450 tracePipe, err := os.Open("/sys/kernel/debug/tracing/trace_pipe") 451 if err != nil { 452 return fmt.Errorf("opening trace_pipe: %w", err) 453 } 454 go func() { 455 <-gadgetCtx.Context().Done() 456 tracePipe.Close() 457 }() 458 go func() { 459 log := gadgetCtx.Logger() 460 461 defer tracePipe.Close() 462 scanner := bufio.NewScanner(tracePipe) 463 for scanner.Scan() { 464 log.Debug(scanner.Text()) 465 } 466 }() 467 return nil 468 } 469 470 func (i *ebpfInstance) Start(gadgetCtx operators.GadgetContext) error { 471 i.logger.Debugf("starting ebpfInstance") 472 473 gadgets.FixBpfKtimeGetBootNs(i.collectionSpec.Programs) 474 475 parameters := params.Params{} // used to CopyFromMap 476 paramMap := make(map[string]*params.Param) // used for second iteration 477 for name, p := range i.params { 478 param := apihelpers.ParamToParamDesc(p.Param).ToParam() 479 paramMap[name] = param 480 parameters = append(parameters, param) 481 } 482 err := parameters.CopyFromMap(i.paramValues, "") 483 if err != nil { 484 return fmt.Errorf("parsing parameter values: %w", err) 485 } 486 487 if paramMap[ParamTraceKernel].AsBool() { 488 err := i.tracePipe(gadgetCtx) 489 if err != nil { 490 return err 491 } 492 } 493 494 mapReplacements := make(map[string]*ebpf.Map) 495 constReplacements := make(map[string]any) 496 497 // Set gadget params 498 for name, p := range i.params { 499 if !p.fromEbpf { 500 continue 501 } 502 constReplacements[name] = paramMap[name].AsAny() 503 i.logger.Debugf("setting param value %q = %v", name, paramMap[name].AsAny()) 504 } 505 506 for _, v := range i.vars { 507 res, ok := gadgetCtx.GetVar(v.name) 508 if !ok { 509 continue 510 } 511 i.logger.Debugf("got var %q: %+v", v.name, res) 512 switch t := res.(type) { 513 case *ebpf.Map: 514 if t == nil { 515 continue 516 } 517 i.logger.Debugf("replacing map %q", v.name) 518 mapReplacements[v.name] = t 519 default: 520 if !reflect.TypeOf(res).AssignableTo(v.refType) { 521 i.logger.Debugf("variable %q can not be set to type %T (expected %s)", v.name, res, v.refType.Name()) 522 continue 523 } 524 i.logger.Debugf("setting var %q to %v", v.name, t) 525 constReplacements[v.name] = res 526 } 527 } 528 529 if err := i.collectionSpec.RewriteConstants(constReplacements); err != nil { 530 return fmt.Errorf("rewriting constants: %w", err) 531 } 532 533 i.logger.Debugf("creating ebpf collection") 534 opts := ebpf.CollectionOptions{ 535 MapReplacements: mapReplacements, 536 } 537 538 // check if the btfgen operator has stored the kernel types in the context 539 if btfSpecI, ok := gadgetCtx.GetVar(kernelTypesVar); ok { 540 gadgetCtx.Logger().Debugf("using kernel types from BTFHub") 541 btfSpec, ok := btfSpecI.(*btf.Spec) 542 if !ok { 543 return fmt.Errorf("invalid BTF spec: expected btf.Spec, got %T", btfSpecI) 544 } 545 opts.Programs.KernelTypes = btfSpec 546 } 547 collection, err := ebpf.NewCollectionWithOptions(i.collectionSpec, opts) 548 if err != nil { 549 return fmt.Errorf("creating eBPF collection: %w", err) 550 } 551 i.collection = collection 552 553 for _, tracer := range i.tracers { 554 i.logger.Debugf("starting tracer %q", tracer.MapName) 555 go func(tracer *Tracer) { 556 err := i.runTracer(gadgetCtx, tracer) 557 if err != nil { 558 i.logger.Errorf("starting tracer: %w", err) 559 } 560 }(tracer) 561 } 562 563 // Attach programs 564 for progName, p := range i.collectionSpec.Programs { 565 l, err := i.attachProgram(gadgetCtx, p, i.collection.Programs[progName]) 566 if err != nil { 567 i.Close() 568 return fmt.Errorf("attaching eBPF program %q: %w", progName, err) 569 } 570 571 if l != nil { 572 i.links = append(i.links, l) 573 } 574 575 // We need to store iterators' links because we need them to run the programs 576 if p.Type == ebpf.Tracing && strings.HasPrefix(p.SectionName, iterPrefix) { 577 lIter, ok := l.(*link.Iter) 578 if !ok { 579 i.Close() 580 return fmt.Errorf("link is not an iterator") 581 } 582 583 found := false 584 for _, snapshotter := range i.snapshotters { 585 if _, ok := snapshotter.iterators[progName]; ok { 586 snapshotter.links[progName] = &linkSnapshotter{ 587 link: lIter, 588 typ: p.AttachTo, 589 } 590 found = true 591 break 592 } 593 } 594 if !found { 595 i.logger.Warnf("None snapshotter will run iterator %q", progName) 596 } 597 } 598 } 599 600 err = i.runSnapshotters() 601 if err != nil { 602 i.Close() 603 return fmt.Errorf("running snapshotters: %w", err) 604 } 605 606 return nil 607 } 608 609 func (i *ebpfInstance) Stop(gadgetCtx operators.GadgetContext) error { 610 i.Close() 611 return nil 612 } 613 614 func (i *ebpfInstance) Close() { 615 if i.collection != nil { 616 i.collection.Close() 617 i.collection = nil 618 } 619 for _, l := range i.links { 620 gadgets.CloseLink(l) 621 } 622 i.links = nil 623 624 for _, networkTracer := range i.networkTracers { 625 networkTracer.Close() 626 } 627 for _, handler := range i.tcHandlers { 628 handler.Close() 629 } 630 for _, uprobeTracer := range i.uprobeTracers { 631 uprobeTracer.Close() 632 } 633 } 634 635 // Using Attacher interface for network tracers for now 636 637 func (i *ebpfInstance) AttachContainer(container *containercollection.Container) error { 638 i.mu.Lock() 639 i.containers[container.Runtime.ContainerID] = container 640 i.mu.Unlock() 641 642 for _, networkTracer := range i.networkTracers { 643 if err := networkTracer.Attach(container.Pid); err != nil { 644 return err 645 } 646 } 647 648 if ifaceName := i.paramValues[ParamIface]; ifaceName == "" { 649 for _, handler := range i.tcHandlers { 650 if err := handler.AttachContainer(container); err != nil { 651 return err 652 } 653 } 654 } 655 656 for _, handler := range i.uprobeTracers { 657 if err := handler.AttachContainer(container); err != nil { 658 return err 659 } 660 } 661 662 return nil 663 } 664 665 func (i *ebpfInstance) DetachContainer(container *containercollection.Container) error { 666 i.mu.Lock() 667 delete(i.containers, container.Runtime.ContainerID) 668 i.mu.Unlock() 669 670 for _, networkTracer := range i.networkTracers { 671 if err := networkTracer.Detach(container.Pid); err != nil { 672 return err 673 } 674 } 675 676 if ifaceName := i.paramValues[ParamIface]; ifaceName == "" { 677 for _, handler := range i.tcHandlers { 678 if err := handler.DetachContainer(container); err != nil { 679 return err 680 } 681 } 682 } 683 684 for _, uTracer := range i.uprobeTracers { 685 if err := uTracer.DetachContainer(container); err != nil { 686 return err 687 } 688 } 689 690 return nil 691 } 692 693 func init() { 694 operators.RegisterOperatorForMediaType(eBPFObjectMediaType, &ebpfOperator{}) 695 }