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

     1  // Copyright 2023 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 kfilefields
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"os"
    21  	"syscall"
    22  
    23  	"github.com/cilium/ebpf"
    24  	"github.com/cilium/ebpf/link"
    25  	"golang.org/x/sys/unix"
    26  
    27  	"github.com/inspektor-gadget/inspektor-gadget/pkg/btfgen"
    28  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    29  )
    30  
    31  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -cc clang -cflags ${CFLAGS} -no-global-types filefields ./bpf/filefields.bpf.c -- -I./bpf/
    32  
    33  type FdType int
    34  
    35  const (
    36  	FdTypeSocket FdType = iota
    37  	FdTypeEbpfProgram
    38  )
    39  
    40  var supportedFdTypesForFOp = map[FdType]struct{}{
    41  	FdTypeSocket:      {},
    42  	FdTypeEbpfProgram: {},
    43  }
    44  
    45  func (fd FdType) String() string {
    46  	switch fd {
    47  	case FdTypeSocket:
    48  		return "socket"
    49  	case FdTypeEbpfProgram:
    50  		return "ebpf_program"
    51  	default:
    52  		return fmt.Sprintf("unknown(%d)", fd)
    53  	}
    54  }
    55  
    56  type fileFields struct {
    57  	PrivateData uint64
    58  	FOp         uint64
    59  	RealInode   uint64
    60  }
    61  
    62  type Tracer struct {
    63  	objs      filefieldsObjects
    64  	links     []link.Link
    65  	sock      [2]int
    66  	installed bool
    67  }
    68  
    69  func creatAndInstallTracer() (*Tracer, error) {
    70  	t := &Tracer{}
    71  
    72  	if err := t.install(); err != nil {
    73  		t.close()
    74  		return nil, fmt.Errorf("installing tracer: %w", err)
    75  	}
    76  
    77  	return t, nil
    78  }
    79  
    80  func (t *Tracer) close() {
    81  	for _, l := range t.links {
    82  		gadgets.CloseLink(l)
    83  	}
    84  	for i := 0; i < len(t.sock); i++ {
    85  		if t.sock[i] != -1 {
    86  			unix.Close(t.sock[i])
    87  		}
    88  	}
    89  	t.objs.Close()
    90  }
    91  
    92  func (t *Tracer) install() error {
    93  	// Create a socket pair
    94  	var err error
    95  	t.sock, err = unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0)
    96  	if err != nil {
    97  		return fmt.Errorf("creating socket pair: %w", err)
    98  	}
    99  
   100  	// Find the inode of the socket
   101  	fdFileInfo, err := os.Stat(fmt.Sprintf("/proc/self/fd/%d", t.sock[0]))
   102  	if err != nil {
   103  		return fmt.Errorf("reading file info from sock fd %d: %w", t.sock[0], err)
   104  	}
   105  	fdStat, ok := fdFileInfo.Sys().(*syscall.Stat_t)
   106  	if !ok {
   107  		return errors.New("not a syscall.Stat_t")
   108  	}
   109  	fdIno := fdStat.Ino
   110  
   111  	// Load ebpf program configured with the socket inode
   112  	spec, err := loadFilefields()
   113  	if err != nil {
   114  		return fmt.Errorf("load ebpf program to read file fields: %w", err)
   115  	}
   116  	consts := map[string]interface{}{
   117  		"socket_ino": uint64(fdIno),
   118  	}
   119  	if err := spec.RewriteConstants(consts); err != nil {
   120  		return fmt.Errorf("RewriteConstants: %w", err)
   121  	}
   122  
   123  	opts := ebpf.CollectionOptions{
   124  		Programs: ebpf.ProgramOptions{
   125  			KernelTypes: btfgen.GetBTFSpec(),
   126  		},
   127  	}
   128  	if err := spec.LoadAndAssign(&t.objs, &opts); err != nil {
   129  		return fmt.Errorf("loading maps and programs: %w", err)
   130  	}
   131  
   132  	// Attach ebpf programs
   133  	l, err := link.Kprobe("__scm_send", t.objs.IgScmSndE, nil)
   134  	if err != nil {
   135  		return fmt.Errorf("attaching kprobe __scm_send: %w", err)
   136  	}
   137  	t.links = append(t.links, l)
   138  
   139  	l, err = link.Kretprobe("fget_raw", t.objs.IgFgetX, nil)
   140  	if err != nil {
   141  		return fmt.Errorf("attaching kretprobe fget_raw: %w", err)
   142  	}
   143  	t.links = append(t.links, l)
   144  	t.installed = true
   145  
   146  	return nil
   147  }
   148  
   149  func (t *Tracer) getFdFromType(kind FdType) (int, error) {
   150  	if !t.installed {
   151  		return -1, errors.New("tracer not installed")
   152  	}
   153  	switch kind {
   154  	case FdTypeSocket:
   155  		return t.sock[0], nil
   156  	case FdTypeEbpfProgram:
   157  		return t.objs.IgFgetX.FD(), nil
   158  	default:
   159  		return -1, fmt.Errorf("unknown fd type %d", kind)
   160  	}
   161  }
   162  
   163  func (t *Tracer) readStructFileFields(fd int) (*fileFields, error) {
   164  	if !t.installed {
   165  		return nil, errors.New("tracer not installed")
   166  	}
   167  	// Send the fd through the socket with SCM_RIGHTS.
   168  	// This will trigger the __scm_send kprobe and fget_raw kretprobe
   169  	buf := make([]byte, 1)
   170  	err := unix.Sendmsg(t.sock[0], buf, unix.UnixRights(fd), nil, 0)
   171  	if err != nil {
   172  		return nil, fmt.Errorf("sending fd: %w", err)
   173  	}
   174  
   175  	var ff fileFields
   176  	err = t.objs.IgFileFields.Lookup(uint32(0), &ff)
   177  	if err != nil {
   178  		return nil, fmt.Errorf("reading file fields: %w", err)
   179  	}
   180  
   181  	return &ff, nil
   182  }