github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/operators/ebpf/snapshotter.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
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"io"
    21  	"strings"
    22  
    23  	"github.com/cilium/ebpf"
    24  	"github.com/cilium/ebpf/btf"
    25  	"github.com/cilium/ebpf/link"
    26  
    27  	"github.com/inspektor-gadget/inspektor-gadget/pkg/datasource"
    28  	metadatav1 "github.com/inspektor-gadget/inspektor-gadget/pkg/metadata/v1"
    29  	"github.com/inspektor-gadget/inspektor-gadget/pkg/netnsenter"
    30  	bpfiterns "github.com/inspektor-gadget/inspektor-gadget/pkg/utils/bpf-iter-ns"
    31  )
    32  
    33  type linkSnapshotter struct {
    34  	link *link.Iter
    35  	typ  string
    36  }
    37  
    38  type Snapshotter struct {
    39  	metadatav1.Snapshotter
    40  
    41  	ds       datasource.DataSource
    42  	accessor datasource.FieldAccessor
    43  	netns    datasource.FieldAccessor
    44  
    45  	// iterators is a list of iterators that this snapshotter needs to run to
    46  	// get the data. This information is gathered from the snapshotter
    47  	// definition in the eBPF program.
    48  	iterators map[string]struct{}
    49  
    50  	// links is a map of iterators to their links. Links are created when the
    51  	// iterator is attached to the kernel.
    52  	links map[string]*linkSnapshotter
    53  }
    54  
    55  func (i *ebpfInstance) parseSnapshotterPrograms(programs []string) (map[string]struct{}, error) {
    56  	iterators := make(map[string]struct{}, len(programs))
    57  
    58  	for _, program := range programs {
    59  		if program == "" {
    60  			return nil, errors.New("empty program name")
    61  		}
    62  
    63  		i.logger.Debugf("> program %q", program)
    64  
    65  		// Check if the program is in the eBPF object
    66  		p, ok := i.collectionSpec.Programs[program]
    67  		if !ok {
    68  			return nil, fmt.Errorf("program %q not found in eBPF object", program)
    69  		}
    70  
    71  		if p.Type != ebpf.Tracing || !strings.HasPrefix(p.SectionName, "iter/") {
    72  			return nil, fmt.Errorf("invalid program %q: expecting type %q and section name prefix \"iter/\", got %q and %q",
    73  				program, ebpf.Tracing, p.Type, p.SectionName)
    74  		}
    75  
    76  		iterators[program] = struct{}{}
    77  	}
    78  
    79  	return iterators, nil
    80  }
    81  
    82  func (i *ebpfInstance) populateSnapshotter(t btf.Type, varName string) error {
    83  	i.logger.Debugf("populating snapshotter %q", varName)
    84  
    85  	parts := strings.Split(varName, typeSplitter)
    86  	if len(parts) < 3 {
    87  		// At least one program is required
    88  		return fmt.Errorf("invalid snapshotter definition, expected format: <name>___<structName>___<program1>___...___<programN>, got %q",
    89  			varName)
    90  	}
    91  
    92  	name := parts[0]
    93  	structName := parts[1]
    94  
    95  	i.logger.Debugf("> name       : %q", name)
    96  	i.logger.Debugf("> struct name: %q", structName)
    97  
    98  	snapConfig := i.config.Sub("snapshotters." + name)
    99  	if snapConfig != nil {
   100  		if configStructName := snapConfig.GetString("structName"); configStructName != "" && configStructName != structName {
   101  			return fmt.Errorf("validating snapshotter %q: structName %q in eBPF program does not match %q from metadata file",
   102  				name, configStructName, structName)
   103  		}
   104  		i.logger.Debugf("> successfully validated with metadata")
   105  	}
   106  
   107  	iterators, err := i.parseSnapshotterPrograms(parts[2:])
   108  	if err != nil {
   109  		return fmt.Errorf("parsing snapshotter %q programs: %w", name, err)
   110  	}
   111  
   112  	if _, ok := i.snapshotters[name]; ok {
   113  		i.logger.Debugf("snapshotter %q already defined, skipping", name)
   114  		return nil
   115  	}
   116  
   117  	var btfStruct *btf.Struct
   118  	if err := i.collectionSpec.Types.TypeByName(structName, &btfStruct); err != nil {
   119  		return fmt.Errorf("finding struct %q in eBPF object: %w", structName, err)
   120  	}
   121  
   122  	i.logger.Debugf("adding snapshotter %q", name)
   123  	i.snapshotters[name] = &Snapshotter{
   124  		Snapshotter: metadatav1.Snapshotter{
   125  			StructName: btfStruct.Name,
   126  		},
   127  		iterators: iterators,
   128  		links:     make(map[string]*linkSnapshotter),
   129  	}
   130  
   131  	err = i.populateStructDirect(btfStruct)
   132  	if err != nil {
   133  		return fmt.Errorf("populating struct %q for snapshotter %q: %w", btfStruct.Name, name, err)
   134  	}
   135  
   136  	return nil
   137  }
   138  
   139  func (i *ebpfInstance) runSnapshotters() error {
   140  	for sName, snapshotter := range i.snapshotters {
   141  		i.logger.Debugf("Running snapshotter %q", sName)
   142  
   143  		for pName, l := range snapshotter.links {
   144  			i.logger.Debugf("Running iterator %q", pName)
   145  			switch l.typ {
   146  			case "task":
   147  				buf, err := bpfiterns.Read(l.link)
   148  				if err != nil {
   149  					return fmt.Errorf("reading iterator %q: %w", pName, err)
   150  				}
   151  
   152  				size := snapshotter.accessor.Size()
   153  				if uint32(len(buf))%size != 0 {
   154  					return fmt.Errorf("iter %q returned an invalid buffer's size %d, expected multiple of %d",
   155  						pName, len(buf), size)
   156  				}
   157  
   158  				for i := uint32(0); i < uint32(len(buf)); i += size {
   159  					data := snapshotter.ds.NewData()
   160  					snapshotter.accessor.Set(data, buf[i:i+size])
   161  					snapshotter.ds.EmitAndRelease(data)
   162  				}
   163  			case "tcp", "udp":
   164  				visitedNetNs := make(map[uint64]struct{})
   165  				for _, container := range i.containers {
   166  					_, visited := visitedNetNs[container.Netns]
   167  					if visited {
   168  						continue
   169  					}
   170  					visitedNetNs[container.Netns] = struct{}{}
   171  
   172  					err := netnsenter.NetnsEnter(int(container.Pid), func() error {
   173  						reader, err := l.link.Open()
   174  						if err != nil {
   175  							return err
   176  						}
   177  						defer reader.Close()
   178  
   179  						buf, err := io.ReadAll(reader)
   180  						if err != nil {
   181  							return fmt.Errorf("reading iterator %q: %w", pName, err)
   182  						}
   183  
   184  						size := snapshotter.accessor.Size()
   185  						if uint32(len(buf))%size != 0 {
   186  							return fmt.Errorf("iter %q returned an invalid buffer's size %d, expected multiple of %d",
   187  								pName, len(buf), size)
   188  						}
   189  
   190  						for i := uint32(0); i < uint32(len(buf)); i += size {
   191  							data := snapshotter.ds.NewData()
   192  							snapshotter.accessor.Set(data, buf[i:i+size])
   193  
   194  							// TODO: this isn't ideal; make DS reserve memory / clean on demand
   195  							// instead of allocating in here - or: reserve those 8 bytes in eBPF
   196  							snapshotter.netns.Set(data, make([]byte, 8))
   197  							snapshotter.netns.PutUint64(data, container.Netns)
   198  
   199  							snapshotter.ds.EmitAndRelease(data)
   200  						}
   201  
   202  						return nil
   203  					})
   204  					if err != nil {
   205  						return fmt.Errorf("entering container %q's netns to run iterator %q: %w",
   206  							container.Runtime.RuntimeName, pName, err)
   207  					}
   208  				}
   209  			}
   210  		}
   211  	}
   212  	return nil
   213  }