github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/profile/tcprtt/tracer/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  //go:build !withoutebpf
    16  
    17  package tracer
    18  
    19  import (
    20  	"encoding/binary"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"strconv"
    25  	"unsafe"
    26  
    27  	"github.com/cilium/ebpf"
    28  	"github.com/cilium/ebpf/link"
    29  
    30  	"github.com/inspektor-gadget/inspektor-gadget/pkg/btfgen"
    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/profile/tcprtt/types"
    34  	"github.com/inspektor-gadget/inspektor-gadget/pkg/histogram"
    35  	"github.com/inspektor-gadget/inspektor-gadget/pkg/logger"
    36  	"github.com/inspektor-gadget/inspektor-gadget/pkg/params"
    37  )
    38  
    39  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -type hist -cc clang -cflags ${CFLAGS} tcpRTT ./bpf/tcprtt.bpf.c -- -I./bpf/
    40  
    41  type Config struct {
    42  	useMilliseconds       bool
    43  	localAddrHist         bool
    44  	remoteAddrHist        bool
    45  	filterLocalPort       uint16
    46  	filterRemotePort      uint16
    47  	filterLocalAddress    uint32
    48  	filterRemoteAddress   uint32
    49  	filterLocalAddressV6  [16]byte
    50  	filterRemoteAddressV6 [16]byte
    51  }
    52  
    53  type Tracer struct {
    54  	objs                tcpRTTObjects
    55  	tcpRcvEstKprobeLink link.Link
    56  
    57  	config *Config
    58  	logger logger.Logger
    59  }
    60  
    61  func (t *Tracer) RunWithResult(gadgetCtx gadgets.GadgetContext) ([]byte, error) {
    62  	t.logger = gadgetCtx.Logger()
    63  
    64  	if err := t.parseParams(gadgetCtx.GadgetParams()); err != nil {
    65  		return nil, fmt.Errorf("parsing parameters: %w", err)
    66  	}
    67  
    68  	defer t.close()
    69  	if err := t.install(); err != nil {
    70  		return nil, fmt.Errorf("installing tracer: %w", err)
    71  	}
    72  
    73  	gadgetcontext.WaitForTimeoutOrDone(gadgetCtx)
    74  
    75  	result, err := t.collectResult()
    76  	if err != nil {
    77  		return nil, fmt.Errorf("collecting result: %w", err)
    78  	}
    79  	return result, nil
    80  }
    81  
    82  // htons converts an unsigned short integer from host byte order to network byte order.
    83  func htons(i uint16) uint16 {
    84  	b := make([]byte, 2)
    85  	binary.BigEndian.PutUint16(b, i)
    86  	return *(*uint16)(unsafe.Pointer(&b[0]))
    87  }
    88  
    89  func (t *Tracer) parseParams(params *params.Params) error {
    90  	t.config.useMilliseconds = params.Get(ParamMilliseconds).AsBool()
    91  
    92  	t.config.localAddrHist = params.Get(ParamByLocalAddress).AsBool()
    93  	t.config.remoteAddrHist = params.Get(ParamByRemoteAddress).AsBool()
    94  	if t.config.localAddrHist && t.config.remoteAddrHist {
    95  		return fmt.Errorf("local and remote address histograms cannot be enabled at the same time")
    96  	}
    97  
    98  	lPort := params.Get(ParamFilterLocalPort).AsString()
    99  	if lPort != "" {
   100  		p, err := strconv.ParseUint(lPort, 10, 16)
   101  		if err != nil {
   102  			return fmt.Errorf("parsing local port: %w", err)
   103  		}
   104  		t.config.filterLocalPort = uint16(p)
   105  	}
   106  
   107  	rPort := params.Get(ParamFilterRemotePort).AsString()
   108  	if rPort != "" {
   109  		p, err := strconv.ParseUint(rPort, 10, 16)
   110  		if err != nil {
   111  			return fmt.Errorf("parsing remote port: %w", err)
   112  		}
   113  		t.config.filterRemotePort = uint16(p)
   114  	}
   115  
   116  	lAddr := params.Get(ParamFilterLocalAddress).AsString()
   117  	if lAddr != "" {
   118  		l, err := gadgets.IPStringToUint32(lAddr)
   119  		if err != nil {
   120  			return fmt.Errorf("parsing local address: %w", err)
   121  		}
   122  		t.config.filterLocalAddress = l
   123  	}
   124  
   125  	rAddr := params.Get(ParamFilterRemoteAddress).AsString()
   126  	if rAddr != "" {
   127  		r, err := gadgets.IPStringToUint32(rAddr)
   128  		if err != nil {
   129  			return fmt.Errorf("parsing remote address: %w", err)
   130  		}
   131  		t.config.filterRemoteAddress = r
   132  	}
   133  
   134  	lAddrV6 := params.Get(ParamFilterLocalAddressV6).AsString()
   135  	if lAddrV6 != "" {
   136  		if lAddr != "" || rAddr != "" {
   137  			return errors.New("filtering by any IPv4 and local IPv6 is not permitted")
   138  		}
   139  		l, err := gadgets.IPStringToByteArray(lAddrV6)
   140  		if err != nil {
   141  			return fmt.Errorf("parsing local address: %w", err)
   142  		}
   143  		t.config.filterLocalAddressV6 = l
   144  	}
   145  
   146  	rAddrV6 := params.Get(ParamFilterRemoteAddressV6).AsString()
   147  	if rAddrV6 != "" {
   148  		if rAddr != "" || lAddr != "" {
   149  			return errors.New("filtering by any IPv4 and remote IPv6 is not permitted")
   150  		}
   151  		r, err := gadgets.IPStringToByteArray(rAddrV6)
   152  		if err != nil {
   153  			return fmt.Errorf("parsing remote address: %w", err)
   154  		}
   155  		t.config.filterRemoteAddressV6 = r
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  func (t *Tracer) collectResult() ([]byte, error) {
   162  	histsMap := t.objs.Hists
   163  
   164  	var key tcpRTTHistKey
   165  	if err := histsMap.NextKey(nil, unsafe.Pointer(&key)); err != nil {
   166  		if errors.Is(err, ebpf.ErrKeyNotExist) {
   167  			return nil, fmt.Errorf("no data was collected to generate the histogram")
   168  		}
   169  		return nil, fmt.Errorf("getting first histogram key: %w", err)
   170  	}
   171  
   172  	var unit histogram.Unit
   173  	if t.config.useMilliseconds {
   174  		unit = histogram.UnitMilliseconds
   175  	} else {
   176  		unit = histogram.UnitMicroseconds
   177  	}
   178  
   179  	var addressType types.AddressType
   180  	if t.config.localAddrHist {
   181  		addressType = types.AddressTypeLocal
   182  	} else if t.config.remoteAddrHist {
   183  		addressType = types.AddressTypeRemote
   184  	} else {
   185  		addressType = types.AddressTypeAll
   186  	}
   187  
   188  	report := types.Report{
   189  		Histograms: make([]*types.ExtendedHistogram, 0),
   190  	}
   191  
   192  	var prev tcpRTTHistKey
   193  	for {
   194  		var addr string
   195  		if addressType == types.AddressTypeAll {
   196  			addr = types.WildcardAddress
   197  		} else {
   198  			addr = gadgets.IPStringFromBytes(key.Addr, gadgets.IPVerFromAF(key.Family))
   199  		}
   200  
   201  		hist := tcpRTTHist{}
   202  		if err := histsMap.Lookup(key, unsafe.Pointer(&hist)); err != nil {
   203  			return nil, fmt.Errorf("getting data for histogram key %d (%s): %w", key, addr, err)
   204  		}
   205  
   206  		var avg float64
   207  		if hist.Cnt > 0 {
   208  			avg = float64(hist.Latency) / float64(hist.Cnt)
   209  		}
   210  
   211  		h := types.NewHistogram(unit, hist.Slots[:], addressType, addr, avg, t.config.filterLocalPort, t.config.filterRemotePort)
   212  		report.Histograms = append(report.Histograms, h)
   213  
   214  		prev = key
   215  		if err := histsMap.NextKey(unsafe.Pointer(&prev), unsafe.Pointer(&key)); err != nil {
   216  			if errors.Is(err, ebpf.ErrKeyNotExist) {
   217  				break
   218  			}
   219  			return nil, fmt.Errorf("getting next histogram key: %w", err)
   220  		}
   221  	}
   222  
   223  	return json.Marshal(report)
   224  }
   225  
   226  func (t *Tracer) close() {
   227  	t.tcpRcvEstKprobeLink = gadgets.CloseLink(t.tcpRcvEstKprobeLink)
   228  
   229  	t.objs.Close()
   230  }
   231  
   232  func (t *Tracer) install() error {
   233  	var spec *ebpf.CollectionSpec
   234  
   235  	spec, err := loadTcpRTT()
   236  	if err != nil {
   237  		return fmt.Errorf("loading specs: %w", err)
   238  	}
   239  
   240  	consts := map[string]interface{}{
   241  		"targ_ms":         t.config.useMilliseconds,
   242  		"targ_laddr_hist": t.config.localAddrHist,
   243  		"targ_raddr_hist": t.config.remoteAddrHist,
   244  		"targ_sport":      htons(t.config.filterLocalPort),
   245  		"targ_dport":      htons(t.config.filterRemotePort),
   246  		"targ_saddr":      t.config.filterLocalAddress,
   247  		"targ_daddr":      t.config.filterRemoteAddress,
   248  		"targ_saddr_v6":   t.config.filterLocalAddressV6,
   249  		"targ_daddr_v6":   t.config.filterRemoteAddressV6,
   250  	}
   251  
   252  	if err := spec.RewriteConstants(consts); err != nil {
   253  		return fmt.Errorf("rewriting constants: %w", err)
   254  	}
   255  
   256  	opts := ebpf.CollectionOptions{
   257  		Programs: ebpf.ProgramOptions{
   258  			KernelTypes: btfgen.GetBTFSpec(),
   259  		},
   260  	}
   261  
   262  	if err := spec.LoadAndAssign(&t.objs, &opts); err != nil {
   263  		return fmt.Errorf("loading ebpf program: %w", err)
   264  	}
   265  
   266  	tcpRcvEstKprobeLink, err := link.Kprobe("tcp_rcv_established", t.objs.IgTcprcvestKp, nil)
   267  	if err != nil {
   268  		return fmt.Errorf("attaching kprobe: %w", err)
   269  	}
   270  	t.tcpRcvEstKprobeLink = tcpRcvEstKprobeLink
   271  
   272  	return nil
   273  }
   274  
   275  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
   276  	return &Tracer{
   277  		config: &Config{},
   278  	}, nil
   279  }