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

     1  // Copyright 2019-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  //go:build !withoutebpf
    16  
    17  package tracer
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"unsafe"
    24  
    25  	"github.com/cilium/ebpf"
    26  	"github.com/cilium/ebpf/link"
    27  	"github.com/cilium/ebpf/perf"
    28  	"github.com/vishvananda/netlink"
    29  
    30  	gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context"
    31  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    32  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/bind/types"
    33  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    34  )
    35  
    36  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -cc clang -cflags ${CFLAGS} -type bind_event bindsnoop ./bpf/bindsnoop.bpf.c -- -I./bpf/
    37  
    38  type Config struct {
    39  	MountnsMap   *ebpf.Map
    40  	TargetPid    int32
    41  	TargetPorts  []uint16
    42  	IgnoreErrors bool
    43  }
    44  
    45  type Tracer struct {
    46  	config        *Config
    47  	enricher      gadgets.DataEnricherByMntNs
    48  	eventCallback func(*types.Event)
    49  
    50  	objs      bindsnoopObjects
    51  	ipv4Entry link.Link
    52  	ipv4Exit  link.Link
    53  	ipv6Entry link.Link
    54  	ipv6Exit  link.Link
    55  	reader    *perf.Reader
    56  }
    57  
    58  func NewTracer(config *Config, enricher gadgets.DataEnricherByMntNs,
    59  	eventCallback func(*types.Event),
    60  ) (*Tracer, error) {
    61  	t := &Tracer{
    62  		config:        config,
    63  		enricher:      enricher,
    64  		eventCallback: eventCallback,
    65  	}
    66  
    67  	if err := t.install(); err != nil {
    68  		t.close()
    69  		return nil, err
    70  	}
    71  
    72  	go t.run()
    73  
    74  	return t, nil
    75  }
    76  
    77  // Stop stops the tracer
    78  // TODO: Remove after refactoring
    79  func (t *Tracer) Stop() {
    80  	t.close()
    81  }
    82  
    83  func (t *Tracer) close() {
    84  	t.ipv4Entry = gadgets.CloseLink(t.ipv4Entry)
    85  	t.ipv4Exit = gadgets.CloseLink(t.ipv4Exit)
    86  	t.ipv6Entry = gadgets.CloseLink(t.ipv6Entry)
    87  	t.ipv6Exit = gadgets.CloseLink(t.ipv6Exit)
    88  
    89  	if t.reader != nil {
    90  		t.reader.Close()
    91  	}
    92  
    93  	t.objs.Close()
    94  }
    95  
    96  func (t *Tracer) install() error {
    97  	spec, err := loadBindsnoop()
    98  	if err != nil {
    99  		return fmt.Errorf("loading ebpf program: %w", err)
   100  	}
   101  
   102  	filterByPort := false
   103  	if len(t.config.TargetPorts) > 0 {
   104  		filterByPort = true
   105  
   106  		m := spec.Maps["ports"]
   107  		for _, port := range t.config.TargetPorts {
   108  			m.Contents = append(m.Contents, ebpf.MapKV{Key: port, Value: port})
   109  		}
   110  	}
   111  
   112  	consts := map[string]interface{}{
   113  		"target_pid":     t.config.TargetPid,
   114  		"filter_by_port": filterByPort,
   115  		"ignore_errors":  t.config.IgnoreErrors,
   116  	}
   117  
   118  	if err := gadgets.LoadeBPFSpec(t.config.MountnsMap, spec, consts, &t.objs); err != nil {
   119  		return fmt.Errorf("loading ebpf spec: %w", err)
   120  	}
   121  
   122  	t.ipv4Entry, err = link.Kprobe("inet_bind", t.objs.IgBindIpv4E, nil)
   123  	if err != nil {
   124  		return fmt.Errorf("attaching ipv4 kprobe: %w", err)
   125  	}
   126  
   127  	t.ipv4Exit, err = link.Kretprobe("inet_bind", t.objs.IgBindIpv4X, nil)
   128  	if err != nil {
   129  		return fmt.Errorf("attaching ipv4 kprobe: %w", err)
   130  	}
   131  
   132  	t.ipv6Entry, err = link.Kprobe("inet6_bind", t.objs.IgBindIpv6E, nil)
   133  	if err != nil {
   134  		return fmt.Errorf("attaching ipv6 kprobe: %w", err)
   135  	}
   136  
   137  	t.ipv6Exit, err = link.Kretprobe("inet6_bind", t.objs.IgBindIpv6X, nil)
   138  	if err != nil {
   139  		return fmt.Errorf("attaching ipv6 kprobe: %w", err)
   140  	}
   141  
   142  	t.reader, err = perf.NewReader(t.objs.bindsnoopMaps.Events, gadgets.PerfBufferPages*os.Getpagesize())
   143  	if err != nil {
   144  		return fmt.Errorf("creating perf ring buffer: %w", err)
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  // optionsToString translates options bitfield to a string containing a letter
   151  // if the option is set or a dot.
   152  // It is a translation of opts2array added in this commit of kinvolk/bcc:
   153  // 9621f010e33c ("tools/bindsnoop: add support for --json")
   154  func optionsToString(options uint8) string {
   155  	ret := ""
   156  	bit := uint8(1)
   157  
   158  	for _, option := range []string{"F", "T", "N", "R", "r"} {
   159  		if (options & bit) != 0 {
   160  			ret = option + ret
   161  		} else {
   162  			ret = "." + ret
   163  		}
   164  		bit <<= 1
   165  	}
   166  
   167  	return ret
   168  }
   169  
   170  // Taken from:
   171  // https://elixir.bootlin.com/linux/v5.16.10/source/include/uapi/linux/in.h#L28
   172  var socketProtocol = map[uint16]string{
   173  	0:   "IP",       // Dummy protocol for TCP
   174  	1:   "ICMP",     // Internet Control Message Protocol
   175  	2:   "IGMP",     // Internet Group Management Protocol
   176  	4:   "IPIP",     // IPIP tunnels (older KA9Q tunnels use 94)
   177  	6:   "TCP",      // Transmission Control Protocol
   178  	8:   "EGP",      // Exterior Gateway Protocol
   179  	12:  "PUP",      // PUP protocol
   180  	17:  "UDP",      // User Datagram Protocol
   181  	22:  "IDP",      // XNS IDP protocol
   182  	29:  "TP",       // SO Transport Protocol Class 4
   183  	33:  "DCCP",     // Datagram Congestion Control Protocol
   184  	41:  "IPV6",     // IPv6-in-IPv4 tunnelling
   185  	46:  "RSVP",     // RSVP Protocol
   186  	47:  "GRE",      // Cisco GRE tunnels (rfc 1701,1702)
   187  	50:  "ESP",      // Encapsulation Security Payload protocol
   188  	51:  "AH",       // Authentication Header protocol
   189  	92:  "MTP",      // Multicast Transport Protocol
   190  	94:  "BEETPH",   // IP option pseudo header for BEET
   191  	98:  "ENCAP",    // Encapsulation Header
   192  	103: "PIM",      // Protocol Independent Multicast
   193  	108: "COMP",     // Compression Header Protocol
   194  	132: "SCTP",     // Stream Control Transport Protocol
   195  	136: "UDPLITE",  // UDP-Lite (RFC 3828)
   196  	137: "MPLS",     // MPLS in IP (RFC 4023)
   197  	143: "ETHERNET", // Ethernet-within-IPv6 Encapsulation
   198  	255: "RAW",      // Raw IP packets
   199  	262: "MPTCP",    // Multipath TCP connection
   200  }
   201  
   202  // protocolToString translates a kernel protocol enum value to the protocol
   203  // name.
   204  func protocolToString(protocol uint16) string {
   205  	protocolString, ok := socketProtocol[protocol]
   206  	if !ok {
   207  		protocolString = "UNKNOWN"
   208  	}
   209  
   210  	return protocolString
   211  }
   212  
   213  func (t *Tracer) run() {
   214  	for {
   215  		record, err := t.reader.Read()
   216  		if err != nil {
   217  			if errors.Is(err, perf.ErrClosed) {
   218  				// nothing to do, we're done
   219  				return
   220  			}
   221  
   222  			msg := fmt.Sprintf("Error reading perf ring buffer: %s", err)
   223  			t.eventCallback(types.Base(eventtypes.Err(msg)))
   224  			return
   225  		}
   226  
   227  		if record.LostSamples > 0 {
   228  			msg := fmt.Sprintf("lost %d samples", record.LostSamples)
   229  			t.eventCallback(types.Base(eventtypes.Warn(msg)))
   230  			continue
   231  		}
   232  
   233  		bpfEvent := (*bindsnoopBindEvent)(unsafe.Pointer(&record.RawSample[0]))
   234  
   235  		interfaceString := ""
   236  		interfaceNum := int(bpfEvent.BoundDevIf)
   237  		if interfaceNum != 0 {
   238  			// It does exist a net link which index is 0.
   239  			// But eBPF bindsnoop code often gives 0 as interface number:
   240  			// https://github.com/iovisor/bcc/blob/63618552f81a2631990eff59fd7460802c58c30b/tools/bindsnoop_example.txt#L16
   241  			// So, we only use this function if interface number is different than 0.
   242  			interf, err := netlink.LinkByIndex(interfaceNum)
   243  			if err != nil {
   244  				msg := fmt.Sprintf("Cannot get net interface for %d : %s", interfaceNum, err)
   245  				t.eventCallback(types.Base(eventtypes.Err(msg)))
   246  				return
   247  			}
   248  
   249  			interfaceString = interf.Attrs().Name
   250  		}
   251  
   252  		addr := gadgets.IPStringFromBytes(bpfEvent.Addr, int(bpfEvent.Ver))
   253  
   254  		event := types.Event{
   255  			Event: eventtypes.Event{
   256  				Type:      eventtypes.NORMAL,
   257  				Timestamp: gadgets.WallTimeFromBootTime(bpfEvent.Timestamp),
   258  			},
   259  			Pid:           bpfEvent.Pid,
   260  			Protocol:      protocolToString(bpfEvent.Proto),
   261  			Addr:          addr,
   262  			Port:          bpfEvent.Port,
   263  			Options:       optionsToString(bpfEvent.Opts),
   264  			Interface:     interfaceString,
   265  			Comm:          gadgets.FromCString(bpfEvent.Task[:]),
   266  			WithMountNsID: eventtypes.WithMountNsID{MountNsID: bpfEvent.MountNsId},
   267  			Uid:           bpfEvent.Uid,
   268  			Gid:           bpfEvent.Gid,
   269  		}
   270  
   271  		if t.enricher != nil {
   272  			t.enricher.EnrichByMntNs(&event.CommonData, event.MountNsID)
   273  		}
   274  
   275  		t.eventCallback(&event)
   276  	}
   277  }
   278  
   279  // --- Registry changes
   280  
   281  func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {
   282  	params := gadgetCtx.GadgetParams()
   283  	t.config.TargetPid = params.Get(ParamPID).AsInt32()
   284  	t.config.TargetPorts = params.Get(ParamPorts).AsUint16Slice()
   285  	t.config.IgnoreErrors = params.Get(ParamIgnoreErrors).AsBool()
   286  
   287  	defer t.close()
   288  	if err := t.install(); err != nil {
   289  		return fmt.Errorf("installing tracer: %w", err)
   290  	}
   291  
   292  	// TODO: Rework this to be able to stop the gadget when an error occurs in
   293  	// run(). Notice it is the same for most of gadgets in the trace category.
   294  	go t.run()
   295  	gadgetcontext.WaitForTimeoutOrDone(gadgetCtx)
   296  
   297  	return nil
   298  }
   299  
   300  func (t *Tracer) SetMountNsMap(mountnsMap *ebpf.Map) {
   301  	t.config.MountnsMap = mountnsMap
   302  }
   303  
   304  func (t *Tracer) SetEventHandler(handler any) {
   305  	nh, ok := handler.(func(ev *types.Event))
   306  	if !ok {
   307  		panic("event handler invalid")
   308  	}
   309  	t.eventCallback = nh
   310  }
   311  
   312  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
   313  	tracer := &Tracer{
   314  		config: &Config{},
   315  	}
   316  	return tracer, nil
   317  }