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

     1  // Copyright 2021-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  	"fmt"
    21  	"io"
    22  	"unsafe"
    23  
    24  	"github.com/cilium/ebpf"
    25  	"github.com/cilium/ebpf/link"
    26  
    27  	containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection"
    28  	containerutils "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils"
    29  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    30  	socketcollectortypes "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/snapshot/socket/types"
    31  	"github.com/inspektor-gadget/inspektor-gadget/pkg/netnsenter"
    32  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    33  )
    34  
    35  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags ${CFLAGS} -type entry  socket ./bpf/socket.bpf.c -- -Werror -O2 -g -c -x c
    36  
    37  type Tracer struct {
    38  	iters []*link.Iter
    39  
    40  	// visitedNamespaces is a map where the key is the netns inode number and
    41  	// the value is the pid of one of the containers that share that netns. Such
    42  	// pid is used by NetnsEnter. TODO: Improve NetnsEnter to also work with the
    43  	// netns directly.
    44  	visitedNamespaces map[uint64]uint32
    45  	protocols         socketcollectortypes.Proto
    46  	eventHandler      func([]*socketcollectortypes.Event)
    47  }
    48  
    49  // Format from socket_bpf_seq_print() in bpf/socket_common.h
    50  func parseStatus(proto string, statusUint uint8) (string, error) {
    51  	statusMap := [...]string{
    52  		"ESTABLISHED", "SYN_SENT", "SYN_RECV",
    53  		"FIN_WAIT1", "FIN_WAIT2", "TIME_WAIT", "CLOSE", "CLOSE_WAIT",
    54  		"LAST_ACK", "LISTEN", "CLOSING", "NEW_SYN_RECV",
    55  	}
    56  
    57  	// Kernel enum starts from 1, adjust it to the statusMap
    58  	if statusUint == 0 || len(statusMap) <= int(statusUint-1) {
    59  		return "", fmt.Errorf("invalid %s status: %d", proto, statusUint)
    60  	}
    61  	status := statusMap[statusUint-1]
    62  
    63  	// Transform TCP status into something more suitable for UDP
    64  	if proto == "UDP" {
    65  		switch status {
    66  		case "ESTABLISHED":
    67  			status = "ACTIVE"
    68  		case "CLOSE":
    69  			status = "INACTIVE"
    70  		default:
    71  			return "", fmt.Errorf("unexpected %s status %s", proto, status)
    72  		}
    73  	}
    74  
    75  	return status, nil
    76  }
    77  
    78  func (t *Tracer) runCollector(pid uint32, netns uint64) ([]*socketcollectortypes.Event, error) {
    79  	sockets := []*socketcollectortypes.Event{}
    80  	err := netnsenter.NetnsEnter(int(pid), func() error {
    81  		for _, it := range t.iters {
    82  			reader, err := it.Open()
    83  			if err != nil {
    84  				return fmt.Errorf("opening BPF iterator: %w", err)
    85  			}
    86  			defer reader.Close()
    87  
    88  			buf, err := io.ReadAll(reader)
    89  			if err != nil {
    90  				return fmt.Errorf("reading BPF iterator: %w", err)
    91  			}
    92  			entrySize := int(unsafe.Sizeof(socketEntry{}))
    93  
    94  			for i := 0; i < len(buf)/entrySize; i++ {
    95  				entry := (*socketEntry)(unsafe.Pointer(&buf[i*entrySize]))
    96  
    97  				proto := gadgets.ProtoString(int(entry.Proto))
    98  				status, err := parseStatus(proto, entry.State)
    99  				if err != nil {
   100  					return err
   101  				}
   102  
   103  				ipversion := gadgets.IPVerFromAF(entry.Family)
   104  
   105  				event := &socketcollectortypes.Event{
   106  					Event: eventtypes.Event{
   107  						Type: eventtypes.NORMAL,
   108  					},
   109  					Protocol: proto,
   110  					SrcEndpoint: eventtypes.L4Endpoint{
   111  						L3Endpoint: eventtypes.L3Endpoint{
   112  							Addr:    gadgets.IPStringFromBytes(entry.Saddr, ipversion),
   113  							Version: uint8(ipversion),
   114  						},
   115  						Port: entry.Sport,
   116  					},
   117  					DstEndpoint: eventtypes.L4Endpoint{
   118  						L3Endpoint: eventtypes.L3Endpoint{
   119  							Addr:    gadgets.IPStringFromBytes(entry.Daddr, ipversion),
   120  							Version: uint8(ipversion),
   121  						},
   122  						Port: entry.Dport,
   123  					},
   124  					Status:      status,
   125  					InodeNumber: entry.Inode,
   126  					WithNetNsID: eventtypes.WithNetNsID{NetNsID: netns},
   127  				}
   128  
   129  				sockets = append(sockets, event)
   130  			}
   131  		}
   132  		return nil
   133  	})
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	return sockets, nil
   139  }
   140  
   141  // RunCollector is currently exported so it can be called from Collect(). It can be removed once
   142  // pkg/gadget-collection/gadgets/snapshot/socket/gadget.go is gone.
   143  func (t *Tracer) RunCollector(pid uint32, podname, namespace, node string) ([]*socketcollectortypes.Event, error) {
   144  	netns, err := containerutils.GetNetNs(int(pid))
   145  	if err != nil {
   146  		return nil, fmt.Errorf("getting netns for pid %d: %w", pid, err)
   147  	}
   148  
   149  	sockets, err := t.runCollector(pid, netns)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	for _, socket := range sockets {
   155  		socket.K8s.Node = node
   156  		socket.K8s.Namespace = namespace
   157  		socket.K8s.PodName = podname
   158  	}
   159  
   160  	return sockets, nil
   161  }
   162  
   163  // ---
   164  
   165  func NewTracer(protocols socketcollectortypes.Proto) (*Tracer, error) {
   166  	tracer := &Tracer{
   167  		visitedNamespaces: make(map[uint64]uint32),
   168  		protocols:         protocols,
   169  	}
   170  
   171  	if err := tracer.openIters(); err != nil {
   172  		tracer.CloseIters()
   173  		return nil, fmt.Errorf("installing tracer: %w", err)
   174  	}
   175  
   176  	return tracer, nil
   177  }
   178  
   179  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
   180  	return &Tracer{
   181  		visitedNamespaces: make(map[uint64]uint32),
   182  	}, nil
   183  }
   184  
   185  func (t *Tracer) AttachContainer(container *containercollection.Container) error {
   186  	if _, ok := t.visitedNamespaces[container.Netns]; ok {
   187  		return nil
   188  	}
   189  	t.visitedNamespaces[container.Netns] = container.Pid
   190  	return nil
   191  }
   192  
   193  func (t *Tracer) DetachContainer(container *containercollection.Container) error {
   194  	return nil
   195  }
   196  
   197  func (t *Tracer) SetEventHandlerArray(handler any) {
   198  	nh, ok := handler.(func(ev []*socketcollectortypes.Event))
   199  	if !ok {
   200  		panic("event handler invalid")
   201  	}
   202  	t.eventHandler = nh
   203  }
   204  
   205  // CloseIters is currently exported so it can be called from Collect()
   206  func (t *Tracer) CloseIters() {
   207  	for _, it := range t.iters {
   208  		it.Close()
   209  	}
   210  	t.iters = nil
   211  }
   212  
   213  func (t *Tracer) openIters() error {
   214  	// TODO: how to avoid loading programs that aren't needed?
   215  	objs := &socketObjects{}
   216  	if err := loadSocketObjects(objs, nil); err != nil {
   217  		return err
   218  	}
   219  
   220  	toAttach := []*ebpf.Program{}
   221  
   222  	switch t.protocols {
   223  	case socketcollectortypes.TCP:
   224  		toAttach = append(toAttach, objs.IgSnapTcp)
   225  	case socketcollectortypes.UDP:
   226  		toAttach = append(toAttach, objs.IgSnapUdp)
   227  	case socketcollectortypes.ALL:
   228  		toAttach = append(toAttach, objs.IgSnapTcp, objs.IgSnapUdp)
   229  	}
   230  
   231  	for _, prog := range toAttach {
   232  		it, err := link.AttachIter(link.IterOptions{
   233  			Program: prog,
   234  		})
   235  		if err != nil {
   236  			var name string
   237  			if info, err := prog.Info(); err == nil {
   238  				name = info.Name
   239  			}
   240  			return fmt.Errorf("attaching program %q: %w", name, err)
   241  		}
   242  		t.iters = append(t.iters, it)
   243  	}
   244  
   245  	return nil
   246  }
   247  
   248  func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {
   249  	protocols := gadgetCtx.GadgetParams().Get(ParamProto).AsString()
   250  	t.protocols = socketcollectortypes.ProtocolsMap[protocols]
   251  
   252  	defer t.CloseIters()
   253  	if err := t.openIters(); err != nil {
   254  		return fmt.Errorf("installing tracer: %w", err)
   255  	}
   256  
   257  	allSockets := []*socketcollectortypes.Event{}
   258  	for netns, pid := range t.visitedNamespaces {
   259  		sockets, err := t.runCollector(pid, netns)
   260  		if err != nil {
   261  			return fmt.Errorf("snapshotting sockets in netns %d: %w", netns, err)
   262  		}
   263  		allSockets = append(allSockets, sockets...)
   264  	}
   265  
   266  	t.eventHandler(allSockets)
   267  	return nil
   268  }