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 }