inspektor-gadget
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
17package tracer18
19import (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
30gadgetcontext "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"33eventtypes "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
38type Config struct {39MountnsMap *ebpf.Map40TargetPid int3241TargetPorts []uint1642IgnoreErrors bool43}
44
45type Tracer struct {46config *Config47enricher gadgets.DataEnricherByMntNs48eventCallback func(*types.Event)49
50objs bindsnoopObjects
51ipv4Entry link.Link52ipv4Exit link.Link53ipv6Entry link.Link54ipv6Exit link.Link55reader *perf.Reader56}
57
58func NewTracer(config *Config, enricher gadgets.DataEnricherByMntNs,59eventCallback func(*types.Event),60) (*Tracer, error) {61t := &Tracer{62config: config,63enricher: enricher,64eventCallback: eventCallback,65}66
67if err := t.install(); err != nil {68t.close()69return nil, err70}71
72go t.run()73
74return t, nil75}
76
77// Stop stops the tracer
78// TODO: Remove after refactoring
79func (t *Tracer) Stop() {80t.close()81}
82
83func (t *Tracer) close() {84t.ipv4Entry = gadgets.CloseLink(t.ipv4Entry)85t.ipv4Exit = gadgets.CloseLink(t.ipv4Exit)86t.ipv6Entry = gadgets.CloseLink(t.ipv6Entry)87t.ipv6Exit = gadgets.CloseLink(t.ipv6Exit)88
89if t.reader != nil {90t.reader.Close()91}92
93t.objs.Close()94}
95
96func (t *Tracer) install() error {97spec, err := loadBindsnoop()98if err != nil {99return fmt.Errorf("loading ebpf program: %w", err)100}101
102filterByPort := false103if len(t.config.TargetPorts) > 0 {104filterByPort = true105
106m := spec.Maps["ports"]107for _, port := range t.config.TargetPorts {108m.Contents = append(m.Contents, ebpf.MapKV{Key: port, Value: port})109}110}111
112consts := map[string]interface{}{113"target_pid": t.config.TargetPid,114"filter_by_port": filterByPort,115"ignore_errors": t.config.IgnoreErrors,116}117
118if err := gadgets.LoadeBPFSpec(t.config.MountnsMap, spec, consts, &t.objs); err != nil {119return fmt.Errorf("loading ebpf spec: %w", err)120}121
122t.ipv4Entry, err = link.Kprobe("inet_bind", t.objs.IgBindIpv4E, nil)123if err != nil {124return fmt.Errorf("attaching ipv4 kprobe: %w", err)125}126
127t.ipv4Exit, err = link.Kretprobe("inet_bind", t.objs.IgBindIpv4X, nil)128if err != nil {129return fmt.Errorf("attaching ipv4 kprobe: %w", err)130}131
132t.ipv6Entry, err = link.Kprobe("inet6_bind", t.objs.IgBindIpv6E, nil)133if err != nil {134return fmt.Errorf("attaching ipv6 kprobe: %w", err)135}136
137t.ipv6Exit, err = link.Kretprobe("inet6_bind", t.objs.IgBindIpv6X, nil)138if err != nil {139return fmt.Errorf("attaching ipv6 kprobe: %w", err)140}141
142t.reader, err = perf.NewReader(t.objs.bindsnoopMaps.Events, gadgets.PerfBufferPages*os.Getpagesize())143if err != nil {144return fmt.Errorf("creating perf ring buffer: %w", err)145}146
147return nil148}
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")
154func optionsToString(options uint8) string {155ret := ""156bit := uint8(1)157
158for _, option := range []string{"F", "T", "N", "R", "r"} {159if (options & bit) != 0 {160ret = option + ret161} else {162ret = "." + ret163}164bit <<= 1165}166
167return ret168}
169
170// Taken from:
171// https://elixir.bootlin.com/linux/v5.16.10/source/include/uapi/linux/in.h#L28
172var socketProtocol = map[uint16]string{1730: "IP", // Dummy protocol for TCP1741: "ICMP", // Internet Control Message Protocol1752: "IGMP", // Internet Group Management Protocol1764: "IPIP", // IPIP tunnels (older KA9Q tunnels use 94)1776: "TCP", // Transmission Control Protocol1788: "EGP", // Exterior Gateway Protocol17912: "PUP", // PUP protocol18017: "UDP", // User Datagram Protocol18122: "IDP", // XNS IDP protocol18229: "TP", // SO Transport Protocol Class 418333: "DCCP", // Datagram Congestion Control Protocol18441: "IPV6", // IPv6-in-IPv4 tunnelling18546: "RSVP", // RSVP Protocol18647: "GRE", // Cisco GRE tunnels (rfc 1701,1702)18750: "ESP", // Encapsulation Security Payload protocol18851: "AH", // Authentication Header protocol18992: "MTP", // Multicast Transport Protocol19094: "BEETPH", // IP option pseudo header for BEET19198: "ENCAP", // Encapsulation Header192103: "PIM", // Protocol Independent Multicast193108: "COMP", // Compression Header Protocol194132: "SCTP", // Stream Control Transport Protocol195136: "UDPLITE", // UDP-Lite (RFC 3828)196137: "MPLS", // MPLS in IP (RFC 4023)197143: "ETHERNET", // Ethernet-within-IPv6 Encapsulation198255: "RAW", // Raw IP packets199262: "MPTCP", // Multipath TCP connection200}
201
202// protocolToString translates a kernel protocol enum value to the protocol
203// name.
204func protocolToString(protocol uint16) string {205protocolString, ok := socketProtocol[protocol]206if !ok {207protocolString = "UNKNOWN"208}209
210return protocolString211}
212
213func (t *Tracer) run() {214for {215record, err := t.reader.Read()216if err != nil {217if errors.Is(err, perf.ErrClosed) {218// nothing to do, we're done219return220}221
222msg := fmt.Sprintf("Error reading perf ring buffer: %s", err)223t.eventCallback(types.Base(eventtypes.Err(msg)))224return225}226
227if record.LostSamples > 0 {228msg := fmt.Sprintf("lost %d samples", record.LostSamples)229t.eventCallback(types.Base(eventtypes.Warn(msg)))230continue231}232
233bpfEvent := (*bindsnoopBindEvent)(unsafe.Pointer(&record.RawSample[0]))234
235interfaceString := ""236interfaceNum := int(bpfEvent.BoundDevIf)237if 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#L16241// So, we only use this function if interface number is different than 0.242interf, err := netlink.LinkByIndex(interfaceNum)243if err != nil {244msg := fmt.Sprintf("Cannot get net interface for %d : %s", interfaceNum, err)245t.eventCallback(types.Base(eventtypes.Err(msg)))246return247}248
249interfaceString = interf.Attrs().Name250}251
252addr := gadgets.IPStringFromBytes(bpfEvent.Addr, int(bpfEvent.Ver))253
254event := types.Event{255Event: eventtypes.Event{256Type: eventtypes.NORMAL,257Timestamp: gadgets.WallTimeFromBootTime(bpfEvent.Timestamp),258},259Pid: bpfEvent.Pid,260Protocol: protocolToString(bpfEvent.Proto),261Addr: addr,262Port: bpfEvent.Port,263Options: optionsToString(bpfEvent.Opts),264Interface: interfaceString,265Comm: gadgets.FromCString(bpfEvent.Task[:]),266WithMountNsID: eventtypes.WithMountNsID{MountNsID: bpfEvent.MountNsId},267Uid: bpfEvent.Uid,268Gid: bpfEvent.Gid,269}270
271if t.enricher != nil {272t.enricher.EnrichByMntNs(&event.CommonData, event.MountNsID)273}274
275t.eventCallback(&event)276}277}
278
279// --- Registry changes
280
281func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {282params := gadgetCtx.GadgetParams()283t.config.TargetPid = params.Get(ParamPID).AsInt32()284t.config.TargetPorts = params.Get(ParamPorts).AsUint16Slice()285t.config.IgnoreErrors = params.Get(ParamIgnoreErrors).AsBool()286
287defer t.close()288if err := t.install(); err != nil {289return fmt.Errorf("installing tracer: %w", err)290}291
292// TODO: Rework this to be able to stop the gadget when an error occurs in293// run(). Notice it is the same for most of gadgets in the trace category.294go t.run()295gadgetcontext.WaitForTimeoutOrDone(gadgetCtx)296
297return nil298}
299
300func (t *Tracer) SetMountNsMap(mountnsMap *ebpf.Map) {301t.config.MountnsMap = mountnsMap302}
303
304func (t *Tracer) SetEventHandler(handler any) {305nh, ok := handler.(func(ev *types.Event))306if !ok {307panic("event handler invalid")308}309t.eventCallback = nh310}
311
312func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {313tracer := &Tracer{314config: &Config{},315}316return tracer, nil317}
318