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

     1  // Copyright 2019-2024 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  	"context"
    21  	"fmt"
    22  	"time"
    23  	"unsafe"
    24  
    25  	"github.com/google/gopacket"
    26  	"github.com/google/gopacket/layers"
    27  	log "github.com/sirupsen/logrus"
    28  
    29  	gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context"
    30  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
    31  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/dns/types"
    32  	"github.com/inspektor-gadget/inspektor-gadget/pkg/logger"
    33  	"github.com/inspektor-gadget/inspektor-gadget/pkg/networktracer"
    34  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    35  )
    36  
    37  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags ${CFLAGS} -type event_t dns ./bpf/dns.c -- $CLANG_OS_FLAGS -I./bpf/
    38  
    39  // Keep in sync with values in bpf/dns.c
    40  const (
    41  	BPFQueryMapName = "query_map"
    42  	maxPorts        = 16
    43  )
    44  
    45  type Tracer struct {
    46  	*networktracer.Tracer[types.Event]
    47  
    48  	ctx    context.Context
    49  	cancel context.CancelFunc
    50  }
    51  
    52  func NewTracer() (*Tracer, error) {
    53  	t := &Tracer{}
    54  
    55  	if err := t.install(); err != nil {
    56  		t.Close()
    57  		return nil, fmt.Errorf("installing tracer: %w", err)
    58  	}
    59  
    60  	return t, nil
    61  }
    62  
    63  // RunWorkaround is used by pkg/gadget-collection/gadgets/trace/dns/gadget.go to run the gadget
    64  // after calling NewTracer()
    65  func (t *Tracer) RunWorkaround() error {
    66  	// timeout nor ports configurable in this case
    67  	if err := t.run(context.TODO(), log.StandardLogger(), time.Minute, []uint16{53, 5353}); err != nil {
    68  		t.Close()
    69  		return fmt.Errorf("running tracer: %w", err)
    70  	}
    71  	return nil
    72  }
    73  
    74  // pkt_type definitions:
    75  // https://github.com/torvalds/linux/blob/v5.14-rc7/include/uapi/linux/if_packet.h#L26
    76  var pktTypeNames = []string{
    77  	"HOST",
    78  	"BROADCAST",
    79  	"MULTICAST",
    80  	"OTHERHOST",
    81  	"OUTGOING",
    82  	"LOOPBACK",
    83  	"USER",
    84  	"KERNEL",
    85  }
    86  
    87  func pktTypeToString(pktType uint8) string {
    88  	pktTypeUint := uint(pktType)
    89  	if pktTypeUint < uint(len(pktTypeNames)) {
    90  		return pktTypeNames[pktType]
    91  	}
    92  
    93  	return "UNKNOWN"
    94  }
    95  
    96  // --- Registry changes
    97  
    98  func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
    99  	return &Tracer{}, nil
   100  }
   101  
   102  func (t *Tracer) Init(gadgetCtx gadgets.GadgetContext) error {
   103  	if err := t.install(); err != nil {
   104  		t.Close()
   105  		return fmt.Errorf("installing tracer: %w", err)
   106  	}
   107  
   108  	t.ctx, t.cancel = gadgetcontext.WithTimeoutOrCancel(gadgetCtx.Context(), gadgetCtx.Timeout())
   109  
   110  	return nil
   111  }
   112  
   113  func (t *Tracer) install() error {
   114  	networkTracer, err := networktracer.NewTracer[types.Event]()
   115  	if err != nil {
   116  		return fmt.Errorf("creating network tracer: %w", err)
   117  	}
   118  	t.Tracer = networkTracer
   119  	return nil
   120  }
   121  
   122  func (t *Tracer) parseDNSPacket(rawSample []byte, netns uint64) (*types.Event, error) {
   123  	// The sample received is a concatenation of the dnsEventT structure and the packet bytes.
   124  	bpfEvent := (*dnsEventT)(unsafe.Pointer(&rawSample[0]))
   125  	packetBytes := rawSample[unsafe.Sizeof(*bpfEvent):]
   126  
   127  	if len(packetBytes) < int(bpfEvent.DnsOff) {
   128  		return nil, fmt.Errorf("packet too short")
   129  	}
   130  
   131  	dnsLayer := layers.DNS{}
   132  	err := dnsLayer.DecodeFromBytes(packetBytes[bpfEvent.DnsOff:], gopacket.NilDecodeFeedback)
   133  	if err != nil {
   134  		return nil, fmt.Errorf("decoding dns layer: %w", err)
   135  	}
   136  
   137  	ipversion := gadgets.IPVerFromAF(bpfEvent.Af)
   138  
   139  	event := &types.Event{
   140  		Event: eventtypes.Event{
   141  			Type:      eventtypes.NORMAL,
   142  			Timestamp: gadgets.WallTimeFromBootTime(bpfEvent.Timestamp),
   143  		},
   144  		WithNetNsID:   eventtypes.WithNetNsID{NetNsID: netns},
   145  		WithMountNsID: eventtypes.WithMountNsID{MountNsID: bpfEvent.MountNsId},
   146  		Pid:           bpfEvent.Pid,
   147  		Tid:           bpfEvent.Tid,
   148  		Uid:           bpfEvent.Uid,
   149  		Gid:           bpfEvent.Gid,
   150  		Comm:          gadgets.FromCString(bpfEvent.Task[:]),
   151  		PktType:       pktTypeToString(bpfEvent.PktType),
   152  
   153  		SrcIP:    gadgets.IPStringFromBytes(bpfEvent.SaddrV6, ipversion),
   154  		DstIP:    gadgets.IPStringFromBytes(bpfEvent.DaddrV6, ipversion),
   155  		SrcPort:  bpfEvent.Sport,
   156  		DstPort:  bpfEvent.Dport,
   157  		Protocol: gadgets.ProtoString(int(bpfEvent.Proto)),
   158  
   159  		ID:         fmt.Sprintf("%.4x", dnsLayer.ID),
   160  		NumAnswers: int(dnsLayer.ANCount),
   161  	}
   162  
   163  	if dnsLayer.QR {
   164  		event.Qr = types.DNSPktTypeResponse
   165  		event.Nameserver = event.SrcIP
   166  
   167  		event.Latency = time.Duration(bpfEvent.LatencyNs)
   168  		event.Rcode = dnsLayer.ResponseCode.String()
   169  	} else {
   170  		event.Qr = types.DNSPktTypeQuery
   171  		event.Nameserver = event.DstIP
   172  	}
   173  
   174  	if len(dnsLayer.Questions) > 0 {
   175  		question := dnsLayer.Questions[0]
   176  		event.QType = question.Type.String()
   177  		event.DNSName = string(question.Name) + "."
   178  	}
   179  
   180  	for _, answer := range dnsLayer.Answers {
   181  		if answer.IP == nil {
   182  			continue
   183  		}
   184  
   185  		event.Addresses = append(event.Addresses, answer.IP.String())
   186  	}
   187  
   188  	return event, nil
   189  }
   190  
   191  func (t *Tracer) run(ctx context.Context, logger logger.Logger, dnsTimeout time.Duration, ports []uint16) error {
   192  	spec, err := loadDns()
   193  	if err != nil {
   194  		return fmt.Errorf("loading asset: %w", err)
   195  	}
   196  
   197  	if len(ports) > maxPorts {
   198  		return fmt.Errorf("too many ports specified, max is %d", maxPorts)
   199  	}
   200  
   201  	portsArray := [maxPorts]uint16{0}
   202  	copy(portsArray[:], ports)
   203  
   204  	constants := map[string]any{
   205  		"ports":     portsArray,
   206  		"ports_len": uint16(len(ports)),
   207  	}
   208  	if err := spec.RewriteConstants(constants); err != nil {
   209  		return fmt.Errorf("rewriting constants: %w", err)
   210  	}
   211  
   212  	if err := t.Tracer.Run(spec, types.Base, t.parseDNSPacket); err != nil {
   213  		return fmt.Errorf("setting network tracer spec: %w", err)
   214  	}
   215  
   216  	// Start a background thread to garbage collect queries without responses
   217  	// from the queries map (used to calculate DNS latency).
   218  	// The goroutine terminates when t.ctx is done.
   219  	queryMap := t.Tracer.GetMap(BPFQueryMapName)
   220  	if queryMap == nil {
   221  		t.Close()
   222  		return fmt.Errorf("got nil retrieving DNS query map")
   223  	}
   224  	startGarbageCollector(ctx, logger, dnsTimeout, queryMap)
   225  
   226  	return nil
   227  }
   228  
   229  func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {
   230  	dnsTimeout := gadgetCtx.GadgetParams().Get(ParamDNSTimeout).AsDuration()
   231  	ports := gadgetCtx.GadgetParams().Get(ParamPorts).AsUint16Slice()
   232  
   233  	if err := t.run(t.ctx, gadgetCtx.Logger(), dnsTimeout, ports); err != nil {
   234  		return err
   235  	}
   236  
   237  	<-t.ctx.Done()
   238  	return nil
   239  }
   240  
   241  func (t *Tracer) Close() {
   242  	if t.cancel != nil {
   243  		t.cancel()
   244  	}
   245  
   246  	if t.Tracer != nil {
   247  		t.Tracer.Close()
   248  	}
   249  }