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  }