github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/operators/localmanager/localmanager.go (about) 1 // Copyright 2022-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 localmanager 16 17 import ( 18 "errors" 19 "fmt" 20 "strings" 21 22 "github.com/cilium/ebpf" 23 "github.com/containerd/containerd/pkg/cri/constants" 24 "github.com/google/uuid" 25 log "github.com/sirupsen/logrus" 26 27 commonutils "github.com/inspektor-gadget/inspektor-gadget/cmd/common/utils" 28 containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" 29 containerutils "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils" 30 runtimeclient "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/runtime-client" 31 containerutilsTypes "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/types" 32 "github.com/inspektor-gadget/inspektor-gadget/pkg/datasource" 33 "github.com/inspektor-gadget/inspektor-gadget/pkg/datasource/compat" 34 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api" 35 apihelpers "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api-helpers" 36 "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets" 37 igmanager "github.com/inspektor-gadget/inspektor-gadget/pkg/ig-manager" 38 "github.com/inspektor-gadget/inspektor-gadget/pkg/operators" 39 "github.com/inspektor-gadget/inspektor-gadget/pkg/params" 40 "github.com/inspektor-gadget/inspektor-gadget/pkg/types" 41 ) 42 43 const ( 44 OperatorName = "LocalManager" 45 Runtimes = "runtimes" 46 ContainerName = "containername" 47 Host = "host" 48 DockerSocketPath = "docker-socketpath" 49 ContainerdSocketPath = "containerd-socketpath" 50 CrioSocketPath = "crio-socketpath" 51 PodmanSocketPath = "podman-socketpath" 52 ContainerdNamespace = "containerd-namespace" 53 RuntimeProtocol = "runtime-protocol" 54 ) 55 56 type MountNsMapSetter interface { 57 SetMountNsMap(*ebpf.Map) 58 } 59 60 type Attacher interface { 61 AttachContainer(container *containercollection.Container) error 62 DetachContainer(*containercollection.Container) error 63 } 64 65 type LocalManager struct { 66 igManager *igmanager.IGManager 67 rc []*containerutilsTypes.RuntimeConfig 68 } 69 70 func (l *LocalManager) Name() string { 71 return OperatorName 72 } 73 74 func (l *LocalManager) Description() string { 75 return "Handles enrichment of container data and attaching/detaching to and from containers" 76 } 77 78 func (l *LocalManager) Dependencies() []string { 79 return nil 80 } 81 82 func (l *LocalManager) GlobalParamDescs() params.ParamDescs { 83 return params.ParamDescs{ 84 { 85 Key: Runtimes, 86 Alias: "r", 87 DefaultValue: strings.Join(containerutils.AvailableRuntimes, ","), 88 Description: fmt.Sprintf("Container runtimes to be used separated by comma. Supported values are: %s", 89 strings.Join(containerutils.AvailableRuntimes, ", ")), 90 // PossibleValues: containerutils.AvailableRuntimes, // TODO 91 }, 92 { 93 Key: DockerSocketPath, 94 DefaultValue: runtimeclient.DockerDefaultSocketPath, 95 Description: "Docker Engine API Unix socket path", 96 }, 97 { 98 Key: ContainerdSocketPath, 99 DefaultValue: runtimeclient.ContainerdDefaultSocketPath, 100 Description: "Containerd CRI Unix socket path", 101 }, 102 { 103 Key: CrioSocketPath, 104 DefaultValue: runtimeclient.CrioDefaultSocketPath, 105 Description: "CRI-O CRI Unix socket path", 106 }, 107 { 108 Key: PodmanSocketPath, 109 DefaultValue: runtimeclient.PodmanDefaultSocketPath, 110 Description: "Podman Unix socket path", 111 }, 112 { 113 Key: ContainerdNamespace, 114 DefaultValue: constants.K8sContainerdNamespace, 115 Description: "Containerd namespace to use", 116 }, 117 { 118 Key: RuntimeProtocol, 119 DefaultValue: "internal", 120 Description: "Container runtime protocol. Supported values are: internal, cri", 121 }, 122 } 123 } 124 125 func (l *LocalManager) ParamDescs() params.ParamDescs { 126 return params.ParamDescs{ 127 { 128 Key: ContainerName, 129 Alias: "c", 130 Description: "Show only data from containers with that name", 131 ValueHint: gadgets.LocalContainer, 132 }, 133 { 134 Key: Host, 135 Description: "Show data from both the host and containers", 136 DefaultValue: "false", 137 TypeHint: params.TypeBool, 138 }, 139 } 140 } 141 142 func (l *LocalManager) CanOperateOn(gadget gadgets.GadgetDesc) bool { 143 // We need to be able to get MountNSID or NetNSID, and set ContainerInfo, so 144 // check for that first 145 _, canEnrichEventFromMountNs := gadget.EventPrototype().(operators.ContainerInfoFromMountNSID) 146 _, canEnrichEventFromNetNs := gadget.EventPrototype().(operators.ContainerInfoFromNetNSID) 147 canEnrichEvent := canEnrichEventFromMountNs || canEnrichEventFromNetNs 148 149 // Secondly, we need to be able to inject the ebpf map onto the gadget instance 150 gi, ok := gadget.(gadgets.GadgetInstantiate) 151 if !ok { 152 return false 153 } 154 155 instance, err := gi.NewInstance() 156 if err != nil { 157 log.Warnf("failed to create dummy %s instance: %s", OperatorName, err) 158 return false 159 } 160 _, isMountNsMapSetter := instance.(MountNsMapSetter) 161 _, isAttacher := instance.(Attacher) 162 163 log.Debugf("> canEnrichEvent: %v", canEnrichEvent) 164 log.Debugf("\t> canEnrichEventFromMountNs: %v", canEnrichEventFromMountNs) 165 log.Debugf("\t> canEnrichEventFromNetNs: %v", canEnrichEventFromNetNs) 166 log.Debugf("> isMountNsMapSetter: %v", isMountNsMapSetter) 167 log.Debugf("> isAttacher: %v", isAttacher) 168 169 return isMountNsMapSetter || canEnrichEvent || isAttacher 170 } 171 172 func (l *LocalManager) Init(operatorParams *params.Params) error { 173 rc := make([]*containerutilsTypes.RuntimeConfig, 0) 174 parts := operatorParams.Get(Runtimes).AsStringSlice() 175 176 partsLoop: 177 for _, p := range parts { 178 runtimeName := types.String2RuntimeName(strings.TrimSpace(p)) 179 socketPath := "" 180 namespace := "" 181 182 switch runtimeName { 183 case types.RuntimeNameDocker: 184 socketPath = operatorParams.Get(DockerSocketPath).AsString() 185 case types.RuntimeNameContainerd: 186 socketPath = operatorParams.Get(ContainerdSocketPath).AsString() 187 namespace = operatorParams.Get(ContainerdNamespace).AsString() 188 case types.RuntimeNameCrio: 189 socketPath = operatorParams.Get(CrioSocketPath).AsString() 190 case types.RuntimeNamePodman: 191 socketPath = operatorParams.Get(PodmanSocketPath).AsString() 192 default: 193 return commonutils.WrapInErrInvalidArg("--runtime / -r", 194 fmt.Errorf("runtime %q is not supported", p)) 195 } 196 197 for _, r := range rc { 198 if r.Name == runtimeName { 199 log.Infof("Ignoring duplicated runtime %q from %v", 200 runtimeName, parts) 201 continue partsLoop 202 } 203 } 204 205 r := &containerutilsTypes.RuntimeConfig{ 206 Name: runtimeName, 207 SocketPath: socketPath, 208 RuntimeProtocol: operatorParams.Get(RuntimeProtocol).AsString(), 209 Extra: containerutilsTypes.ExtraConfig{ 210 Namespace: namespace, 211 }, 212 } 213 214 rc = append(rc, r) 215 } 216 217 l.rc = rc 218 219 igManager, err := igmanager.NewManager(l.rc) 220 if err != nil { 221 log.Warnf("Failed to create container-collection") 222 log.Debugf("Failed to create container-collection: %s", err) 223 } 224 l.igManager = igManager 225 return nil 226 } 227 228 func (l *LocalManager) Close() error { 229 if l.igManager != nil { 230 l.igManager.Close() 231 } 232 return nil 233 } 234 235 func (l *LocalManager) Instantiate(gadgetContext operators.GadgetContext, gadgetInstance any, params *params.Params) (operators.OperatorInstance, error) { 236 _, canEnrichEventFromMountNs := gadgetContext.GadgetDesc().EventPrototype().(operators.ContainerInfoFromMountNSID) 237 _, canEnrichEventFromNetNs := gadgetContext.GadgetDesc().EventPrototype().(operators.ContainerInfoFromNetNSID) 238 canEnrichEvent := canEnrichEventFromMountNs || canEnrichEventFromNetNs 239 240 traceInstance := &localManagerTrace{ 241 manager: l, 242 enrichEvents: canEnrichEvent, 243 attachedContainers: make(map[*containercollection.Container]struct{}), 244 params: params, 245 gadgetInstance: gadgetInstance, 246 gadgetCtx: gadgetContext, 247 } 248 249 if l.igManager == nil { 250 traceInstance.enrichEvents = false 251 } 252 253 return traceInstance, nil 254 } 255 256 type localManagerTrace struct { 257 manager *LocalManager 258 mountnsmap *ebpf.Map 259 enrichEvents bool 260 subscriptionKey string 261 262 // Keep a map to attached containers, so we can clean up properly 263 attachedContainers map[*containercollection.Container]struct{} 264 attacher Attacher 265 params *params.Params 266 gadgetInstance any 267 gadgetCtx operators.GadgetContext 268 269 eventWrappers map[datasource.DataSource]*compat.EventWrapperBase 270 } 271 272 func (l *localManagerTrace) Name() string { 273 return OperatorName 274 } 275 276 func (l *localManagerTrace) PreGadgetRun() error { 277 log := l.gadgetCtx.Logger() 278 id := uuid.New() 279 host := l.params.Get(Host).AsBool() 280 281 // TODO: Improve filtering, see further details in 282 // https://github.com/inspektor-gadget/inspektor-gadget/issues/644. 283 containerSelector := containercollection.ContainerSelector{ 284 Runtime: containercollection.RuntimeSelector{ 285 ContainerName: l.params.Get(ContainerName).AsString(), 286 }, 287 } 288 289 // If --host is set, we do not want to create the below map because we do not 290 // want any filtering. 291 if setter, ok := l.gadgetInstance.(MountNsMapSetter); ok { 292 if !host { 293 if l.manager.igManager == nil { 294 return fmt.Errorf("container-collection isn't available") 295 } 296 297 // Create mount namespace map to filter by containers 298 mountnsmap, err := l.manager.igManager.CreateMountNsMap(id.String(), containerSelector) 299 if err != nil { 300 return commonutils.WrapInErrManagerCreateMountNsMap(err) 301 } 302 303 log.Debugf("set mountnsmap for gadget") 304 setter.SetMountNsMap(mountnsmap) 305 306 l.mountnsmap = mountnsmap 307 } else if l.manager.igManager == nil { 308 log.Warn("container-collection isn't available: container enrichment and filtering won't work") 309 } 310 } 311 312 if attacher, ok := l.gadgetInstance.(Attacher); ok { 313 if l.manager.igManager == nil { 314 if !host { 315 return fmt.Errorf("container-collection isn't available") 316 } 317 318 log.Warn("container-collection isn't available: no containers will be traced") 319 } 320 321 l.attacher = attacher 322 var containers []*containercollection.Container 323 324 attachContainerFunc := func(container *containercollection.Container) { 325 log.Debugf("calling gadget.AttachContainer()") 326 err := attacher.AttachContainer(container) 327 if err != nil { 328 var ve *ebpf.VerifierError 329 if errors.As(err, &ve) { 330 l.gadgetCtx.Logger().Debugf("start tracing container %q: verifier error: %+v\n", container.K8s.ContainerName, ve) 331 } 332 333 log.Warnf("start tracing container %q: %s", container.K8s.ContainerName, err) 334 return 335 } 336 337 l.attachedContainers[container] = struct{}{} 338 339 log.Debugf("tracer attached: container %q pid %d mntns %d netns %d", 340 container.K8s.ContainerName, container.Pid, container.Mntns, container.Netns) 341 } 342 343 detachContainerFunc := func(container *containercollection.Container) { 344 log.Debugf("calling gadget.DetachContainer()") 345 err := attacher.DetachContainer(container) 346 if err != nil { 347 log.Warnf("stop tracing container %q: %s", container.K8s.ContainerName, err) 348 return 349 } 350 log.Debugf("tracer detached: container %q pid %d mntns %d netns %d", 351 container.K8s.ContainerName, container.Pid, container.Mntns, container.Netns) 352 } 353 354 if l.manager.igManager != nil { 355 l.subscriptionKey = id.String() 356 log.Debugf("add subscription to igManager") 357 containers = l.manager.igManager.Subscribe( 358 l.subscriptionKey, 359 containerSelector, 360 func(event containercollection.PubSubEvent) { 361 log.Debugf("%s: %s", event.Type.String(), event.Container.Runtime.ContainerID) 362 switch event.Type { 363 case containercollection.EventTypeAddContainer: 364 attachContainerFunc(event.Container) 365 case containercollection.EventTypeRemoveContainer: 366 detachContainerFunc(event.Container) 367 } 368 }, 369 ) 370 } 371 372 if host { 373 // We need to attach this fake container for gadget which rely only on the 374 // Attacher concept. 375 containers = append(containers, &containercollection.Container{Pid: 1}) 376 } 377 378 for _, container := range containers { 379 attachContainerFunc(container) 380 } 381 } 382 383 return nil 384 } 385 386 func (l *localManagerTrace) PostGadgetRun() error { 387 if l.mountnsmap != nil { 388 log.Debugf("calling RemoveMountNsMap()") 389 l.manager.igManager.RemoveMountNsMap(l.subscriptionKey) 390 } 391 if l.subscriptionKey != "" { 392 host := l.params.Get(Host).AsBool() 393 394 log.Debugf("calling Unsubscribe()") 395 l.manager.igManager.Unsubscribe(l.subscriptionKey) 396 397 if l.attacher != nil { 398 // emit detach for all remaining containers 399 for container := range l.attachedContainers { 400 l.attacher.DetachContainer(container) 401 } 402 403 if host { 404 // Reciprocal operation of attaching fake container with PID 1 which is 405 // needed by gadgets relying on the Attacher concept. 406 l.attacher.DetachContainer(&containercollection.Container{Pid: 1}) 407 } 408 } 409 } 410 return nil 411 } 412 413 func (l *localManagerTrace) enrich(ev any) { 414 if event, canEnrichEventFromMountNs := ev.(operators.ContainerInfoFromMountNSID); canEnrichEventFromMountNs { 415 l.manager.igManager.ContainerCollection.EnrichEventByMntNs(event) 416 } 417 if event, canEnrichEventFromNetNs := ev.(operators.ContainerInfoFromNetNSID); canEnrichEventFromNetNs { 418 l.manager.igManager.ContainerCollection.EnrichEventByNetNs(event) 419 } 420 } 421 422 func (l *localManagerTrace) EnrichEvent(ev any) error { 423 if !l.enrichEvents { 424 return nil 425 } 426 l.enrich(ev) 427 return nil 428 } 429 430 type localManagerTraceWrapper struct { 431 localManagerTrace 432 runID string 433 } 434 435 func (l *LocalManager) GlobalParams() api.Params { 436 return apihelpers.ParamDescsToParams(l.GlobalParamDescs()) 437 } 438 439 func (l *LocalManager) InstanceParams() api.Params { 440 return apihelpers.ParamDescsToParams(l.ParamDescs()) 441 } 442 443 func (l *LocalManager) InstantiateDataOperator(gadgetCtx operators.GadgetContext, paramValues api.ParamValues) ( 444 operators.DataOperatorInstance, error, 445 ) { 446 params := l.ParamDescs().ToParams() 447 err := params.CopyFromMap(paramValues, "") 448 if err != nil { 449 return nil, err 450 } 451 452 // Wrapper is used to have ParamDescs() with the new signature 453 traceInstance := &localManagerTraceWrapper{ 454 localManagerTrace: localManagerTrace{ 455 manager: l, 456 enrichEvents: false, 457 attachedContainers: make(map[*containercollection.Container]struct{}), 458 params: params, 459 gadgetCtx: gadgetCtx, 460 eventWrappers: make(map[datasource.DataSource]*compat.EventWrapperBase), 461 }, 462 } 463 464 activate := false 465 466 // Check, whether the gadget requested a map from us 467 if t, ok := gadgetCtx.GetVar(gadgets.MntNsFilterMapName); ok { 468 if _, ok := t.(*ebpf.Map); ok { 469 gadgetCtx.Logger().Debugf("gadget requested map %s", gadgets.MntNsFilterMapName) 470 activate = true 471 } 472 } 473 474 // Check for override - currently needed for tchandlers 475 if val, ok := gadgetCtx.GetVar("NeedContainerEvents"); ok { 476 if b, ok := val.(bool); ok && b { 477 activate = true 478 } 479 } 480 481 wrappers, err := compat.GetEventWrappers(gadgetCtx) 482 if err != nil { 483 return nil, fmt.Errorf("getting event wrappers: %w", err) 484 } 485 traceInstance.eventWrappers = wrappers 486 if len(wrappers) > 0 { 487 activate = true 488 } 489 490 if !activate { 491 return nil, nil 492 } 493 494 return traceInstance, nil 495 } 496 497 func (l *localManagerTrace) ParamDescs() params.ParamDescs { 498 return params.ParamDescs{ 499 { 500 Key: ContainerName, 501 Alias: "c", 502 Description: "Show only data from containers with that name", 503 ValueHint: gadgets.LocalContainer, 504 }, 505 { 506 Key: Host, 507 Description: "Show data from both the host and containers", 508 DefaultValue: "false", 509 TypeHint: params.TypeBool, 510 }, 511 } 512 } 513 514 func (l *localManagerTraceWrapper) ParamDescs(gadgetCtx operators.GadgetContext) params.ParamDescs { 515 return l.localManagerTrace.ParamDescs() 516 } 517 518 func (l *LocalManager) Priority() int { 519 return -1 520 } 521 522 func (l *localManagerTraceWrapper) PreStart(gadgetCtx operators.GadgetContext) error { 523 // hack - this makes it possible to use the Attacher interface 524 var ok bool 525 l.gadgetInstance, ok = gadgetCtx.GetVar("ebpfInstance") 526 if !ok { 527 return fmt.Errorf("getting ebpfInstance") 528 } 529 530 compat.Subscribe( 531 l.eventWrappers, 532 l.manager.igManager.ContainerCollection.EnrichEventByMntNs, 533 l.manager.igManager.ContainerCollection.EnrichEventByNetNs, 534 0, 535 ) 536 537 id := uuid.New() 538 host := l.params.Get(Host).AsBool() 539 540 containerSelector := containercollection.ContainerSelector{ 541 Runtime: containercollection.RuntimeSelector{ 542 ContainerName: l.params.Get(ContainerName).AsString(), 543 }, 544 } 545 546 // mountnsmap will be handled differently than above 547 if !host { 548 if l.manager.igManager == nil { 549 return fmt.Errorf("container-collection isn't available") 550 } 551 552 // Create mount namespace map to filter by containers 553 mountnsmap, err := l.manager.igManager.CreateMountNsMap(id.String(), containerSelector) 554 if err != nil { 555 return commonutils.WrapInErrManagerCreateMountNsMap(err) 556 } 557 558 gadgetCtx.Logger().Debugf("set mountnsmap for gadget") 559 gadgetCtx.SetVar(gadgets.MntNsFilterMapName, mountnsmap) 560 gadgetCtx.SetVar(gadgets.FilterByMntNsName, true) 561 562 l.mountnsmap = mountnsmap 563 } else if l.manager.igManager == nil { 564 log.Warn("container-collection isn't available: container enrichment and filtering won't work") 565 } 566 567 return l.PreGadgetRun() 568 } 569 570 func (l *localManagerTraceWrapper) Start(gadgetCtx operators.GadgetContext) error { 571 return nil 572 } 573 574 func (l *localManagerTraceWrapper) Stop(gadgetCtx operators.GadgetContext) error { 575 return l.PostGadgetRun() 576 } 577 578 func init() { 579 lm := &LocalManager{} 580 operators.Register(lm) 581 operators.RegisterDataOperator(lm) 582 }