github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/trace/open/tracer/tracer.go (about)

     1  // Copyright 2019-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  //go:build !withoutebpf
    16  
    17  package tracer
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io/fs"
    23  	"os"
    24  	"runtime"
    25  	"unsafe"
    26  
    27  	"github.com/cilium/ebpf"
    28  	"github.com/cilium/ebpf/link"
    29  	"github.com/cilium/ebpf/perf"
    30  
    31  	gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context"
    32  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    33  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/open/types"
    34  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    35  )
    36  
    37  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -no-global-types -target bpfel -cc clang -cflags ${CFLAGS} -type event -type prefix_key opensnoop ./bpf/opensnoop.bpf.c -- -I./bpf/
    38  
    39  const (
    40  	// Keep in sync with opensnoop.h.
    41  	NAME_MAX = 255
    42  	// Keep in sync with opensnoop.bpf.c.
    43  	CHAR_BIT = 8
    44  )
    45  
    46  // needs to be kept in sync with opensnoopEvent from opensnoop_bpfel.go without the FullFname field
    47  type opensnoopEventAbbrev struct {
    48  	Timestamp uint64
    49  	Pid       uint32
    50  	Uid       uint32
    51  	Gid       uint32
    52  	_         [4]byte
    53  	MntnsId   uint64
    54  	Err       int32
    55  	Fd        uint32
    56  	Flags     int32
    57  	Mode      uint16
    58  	Comm      [16]uint8
    59  	Fname     [255]uint8
    60  }
    61  
    62  type Config struct {
    63  	MountnsMap *ebpf.Map
    64  	FullPath   bool
    65  	Prefixes   []string
    66  }
    67  
    68  type Tracer struct {
    69  	config        *Config
    70  	enricher      gadgets.DataEnricherByMntNs
    71  	eventCallback func(*types.Event)
    72  
    73  	objs            opensnoopObjects
    74  	openEnterLink   link.Link
    75  	openAtEnterLink link.Link
    76  	openExitLink    link.Link
    77  	openAtExitLink  link.Link
    78  	reader          *perf.Reader
    79  }
    80  
    81  func NewTracer(config *Config, enricher gadgets.DataEnricherByMntNs,
    82  	eventCallback func(*types.Event),
    83  ) (*Tracer, error) {
    84  	t := &Tracer{
    85  		config:        config,
    86  		enricher:      enricher,
    87  		eventCallback: eventCallback,
    88  	}
    89  
    90  	if err := t.install(); err != nil {
    91  		t.close()
    92  		return nil, err
    93  	}
    94  
    95  	go t.run()
    96  
    97  	return t, nil
    98  }
    99  
   100  // Stop stops the tracer
   101  // TODO: Remove after refactoring
   102  func (t *Tracer) Stop() {
   103  	t.close()
   104  }
   105  
   106  func (t *Tracer) close() {
   107  	t.openEnterLink = gadgets.CloseLink(t.openEnterLink)
   108  	t.openAtEnterLink = gadgets.CloseLink(t.openAtEnterLink)
   109  	t.openExitLink = gadgets.CloseLink(t.openExitLink)
   110  	t.openAtExitLink = gadgets.CloseLink(t.openAtExitLink)
   111  
   112  	if t.reader != nil {
   113  		t.reader.Close()
   114  	}
   115  
   116  	t.objs.Close()
   117  }
   118  
   119  func (t *Tracer) install() error {
   120  	spec, err := loadOpensnoop()
   121  	if err != nil {
   122  		return fmt.Errorf("loading ebpf program: %w", err)
   123  	}
   124  
   125  	prefixesNumber := uint32(len(t.config.Prefixes))
   126  	prefixesMax := spec.Maps["prefixes"].MaxEntries
   127  	if prefixesNumber > prefixesMax {
   128  		return fmt.Errorf("%d maximum prefixes supported, got %d", prefixesMax, prefixesNumber)
   129  	}
   130  
   131  	consts := make(map[string]interface{})
   132  	consts["get_full_path"] = t.config.FullPath
   133  	consts["prefixes_nr"] = prefixesNumber
   134  
   135  	for _, prefix := range t.config.Prefixes {
   136  		var pfx [NAME_MAX]uint8
   137  
   138  		bytes := uint32(len(prefix))
   139  		if bytes > NAME_MAX {
   140  			bytes = NAME_MAX
   141  		}
   142  		copy(pfx[:], prefix)
   143  
   144  		spec.Maps["prefixes"].Contents = append(spec.Maps["prefixes"].Contents, ebpf.MapKV{
   145  			// We need to give the exact length of the prefix here.
   146  			// Otherwise, the kernel will compare until NAME_MAX * CHAR_BIT and there
   147  			// will never be a match (unless the filename is NAME_MAX long and equals
   148  			// to the prefix).
   149  			Key:   opensnoopPrefixKey{Prefixlen: bytes * CHAR_BIT, Filename: pfx},
   150  			Value: uint8(0),
   151  		})
   152  	}
   153  
   154  	if err := gadgets.LoadeBPFSpec(t.config.MountnsMap, spec, consts, &t.objs); err != nil {
   155  		return fmt.Errorf("loading ebpf spec: %w", err)
   156  	}
   157  
   158  	// arm64 does not define the open() syscall, only openat().
   159  	if runtime.GOARCH != "arm64" {
   160  		openEnter, err := link.Tracepoint("syscalls", "sys_enter_open", t.objs.IgOpenE, nil)
   161  		if err != nil {
   162  			return fmt.Errorf("attaching tracepoint: %w", err)
   163  		}
   164  		t.openEnterLink = openEnter
   165  	}
   166  
   167  	openAtEnter, err := link.Tracepoint("syscalls", "sys_enter_openat", t.objs.IgOpenatE, nil)
   168  	if err != nil {
   169  		return fmt.Errorf("attaching tracepoint: %w", err)
   170  	}
   171  	t.openAtEnterLink = openAtEnter
   172  
   173  	if runtime.GOARCH != "arm64" {
   174  		openExit, err := link.Tracepoint("syscalls", "sys_exit_open", t.objs.IgOpenX, nil)
   175  		if err != nil {
   176  			return fmt.Errorf("attaching tracepoint: %w", err)
   177  		}
   178  		t.openExitLink = openExit
   179  	}
   180  
   181  	openAtExit, err := link.Tracepoint("syscalls", "sys_exit_openat", t.objs.IgOpenatX, nil)
   182  	if err != nil {
   183  		return fmt.Errorf("attaching tracepoint: %w", err)
   184  	}
   185  	t.openAtExitLink = openAtExit
   186  
   187  	reader, err := perf.NewReader(t.objs.opensnoopMaps.Events, gadgets.PerfBufferPages*os.Getpagesize())
   188  	if err != nil {
   189  		return fmt.Errorf("creating perf ring buffer: %w", err)
   190  	}
   191  	t.reader = reader
   192  
   193  	return nil
   194  }
   195  
   196  func (t *Tracer) run() {
   197  	for {
   198  		record, err := t.reader.Read()
   199  		if err != nil {
   200  			if errors.Is(err, perf.ErrClosed) {
   201  				// nothing to do, we're done
   202  				return
   203  			}
   204  
   205  			msg := fmt.Sprintf("Error reading perf ring buffer: %s", err)
   206  			t.eventCallback(types.Base(eventtypes.Err(msg)))
   207  			return
   208  		}
   209  
   210  		if record.LostSamples > 0 {
   211  			msg := fmt.Sprintf("lost %d samples", record.LostSamples)
   212  			t.eventCallback(types.Base(eventtypes.Warn(msg)))
   213  			continue
   214  		}
   215  
   216  		bpfEvent := (*opensnoopEventAbbrev)(unsafe.Pointer(&record.RawSample[0]))
   217  
   218  		mode := fs.FileMode(bpfEvent.Mode)
   219  
   220  		event := types.Event{
   221  			Event: eventtypes.Event{
   222  				Type:      eventtypes.NORMAL,
   223  				Timestamp: gadgets.WallTimeFromBootTime(bpfEvent.Timestamp),
   224  			},
   225  			WithMountNsID: eventtypes.WithMountNsID{MountNsID: bpfEvent.MntnsId},
   226  			Pid:           bpfEvent.Pid,
   227  			Uid:           bpfEvent.Uid,
   228  			Gid:           bpfEvent.Gid,
   229  			Comm:          gadgets.FromCString(bpfEvent.Comm[:]),
   230  			Fd:            bpfEvent.Fd,
   231  			Err:           bpfEvent.Err,
   232  			FlagsRaw:      bpfEvent.Flags,
   233  			Flags:         DecodeFlags(bpfEvent.Flags),
   234  			ModeRaw:       mode,
   235  			Mode:          mode.String(),
   236  			Path:          gadgets.FromCString(bpfEvent.Fname[:]),
   237  			FullPath:      gadgets.FromCString(record.RawSample[unsafe.Offsetof(opensnoopEvent{}.FullFname):]),
   238  		}
   239  
   240  		if t.enricher != nil {
   241  			t.enricher.EnrichByMntNs(&event.CommonData, event.MountNsID)
   242  		}
   243  
   244  		t.eventCallback(&event)
   245  	}
   246  }
   247  
   248  // --- Registry changes
   249  
   250  func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {
   251  	t.config.FullPath = gadgetCtx.GadgetParams().Get(ParamFullPath).AsBool()
   252  	t.config.Prefixes = gadgetCtx.GadgetParams().Get(ParamPrefixes).AsStringSlice()
   253  
   254  	defer t.close()
   255  	if err := t.install(); err != nil {
   256  		return fmt.Errorf("installing tracer: %w", err)
   257  	}
   258  
   259  	go t.run()
   260  	gadgetcontext.WaitForTimeoutOrDone(gadgetCtx)
   261  
   262  	return nil
   263  }
   264  
   265  func (t *Tracer) SetMountNsMap(mountnsMap *ebpf.Map) {
   266  	t.config.MountnsMap = mountnsMap
   267  }
   268  
   269  func (t *Tracer) SetEventHandler(handler any) {
   270  	nh, ok := handler.(func(ev *types.Event))
   271  	if !ok {
   272  		panic("event handler invalid")
   273  	}
   274  	t.eventCallback = nh
   275  }
   276  
   277  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
   278  	tracer := &Tracer{
   279  		config: &Config{},
   280  	}
   281  	return tracer, nil
   282  }