inspektor-gadget

Форк
0
394 строки · 9.5 Кб
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
	"net/netip"
23
	"time"
24
	"unsafe"
25

26
	log "github.com/sirupsen/logrus"
27

28
	gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context"
29
	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets"
30
	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/dns/types"
31
	"github.com/inspektor-gadget/inspektor-gadget/pkg/logger"
32
	"github.com/inspektor-gadget/inspektor-gadget/pkg/networktracer"
33
	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
34
)
35

36
//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/
37

38
const (
39
	BPFQueryMapName = "query_map"
40
	MaxAddrAnswers  = 8 // Keep aligned with MAX_ADDR_ANSWERS in bpf/dns-common.h
41
)
42

43
// needs to be kept in sync with dnsEventT from dns_bpfel.go (without the Anaddr field)
44
type dnsEventTAbbrev struct {
45
	Netns       uint32
46
	_           [4]byte
47
	Timestamp   uint64
48
	MountNsId   uint64
49
	Pid         uint32
50
	Tid         uint32
51
	Uid         uint32
52
	Gid         uint32
53
	Task        [16]uint8
54
	SaddrV6     [16]uint8
55
	DaddrV6     [16]uint8
56
	Af          uint16
57
	Sport       uint16
58
	Dport       uint16
59
	Proto       uint8
60
	_           [1]byte
61
	Id          uint16
62
	Qtype       uint16
63
	Qr          uint8
64
	PktType     uint8
65
	Rcode       uint8
66
	_           [1]byte
67
	LatencyNs   uint64
68
	Name        [255]uint8
69
	_           [1]byte
70
	Ancount     uint16
71
	Anaddrcount uint16
72
}
73

74
type Tracer struct {
75
	*networktracer.Tracer[types.Event]
76

77
	ctx    context.Context
78
	cancel context.CancelFunc
79
}
80

81
func NewTracer() (*Tracer, error) {
82
	t := &Tracer{}
83

84
	if err := t.install(); err != nil {
85
		t.Close()
86
		return nil, fmt.Errorf("installing tracer: %w", err)
87
	}
88

89
	// timeout not configurable in this case
90
	if err := t.run(context.TODO(), log.StandardLogger(), time.Minute); err != nil {
91
		t.Close()
92
		return nil, fmt.Errorf("running tracer: %w", err)
93
	}
94

95
	return t, nil
96
}
97

98
// pkt_type definitions:
99
// https://github.com/torvalds/linux/blob/v5.14-rc7/include/uapi/linux/if_packet.h#L26
100
var pktTypeNames = []string{
101
	"HOST",
102
	"BROADCAST",
103
	"MULTICAST",
104
	"OTHERHOST",
105
	"OUTGOING",
106
	"LOOPBACK",
107
	"USER",
108
	"KERNEL",
109
}
110

111
// List taken from:
112
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
113
var qTypeNames = map[uint]string{
114
	1:     "A",
115
	2:     "NS",
116
	3:     "MD",
117
	4:     "MF",
118
	5:     "CNAME",
119
	6:     "SOA",
120
	7:     "MB",
121
	8:     "MG",
122
	9:     "MR",
123
	10:    "NULL",
124
	11:    "WKS",
125
	12:    "PTR",
126
	13:    "HINFO",
127
	14:    "MINFO",
128
	15:    "MX",
129
	16:    "TXT",
130
	17:    "RP",
131
	18:    "AFSDB",
132
	19:    "X25",
133
	20:    "ISDN",
134
	21:    "RT",
135
	22:    "NSAP",
136
	23:    "NSAP-PTR",
137
	24:    "SIG",
138
	25:    "KEY",
139
	26:    "PX",
140
	27:    "GPOS",
141
	28:    "AAAA",
142
	29:    "LOC",
143
	30:    "NXT",
144
	31:    "EID",
145
	32:    "NIMLOC",
146
	33:    "SRV",
147
	34:    "ATMA",
148
	35:    "NAPTR",
149
	36:    "KX",
150
	37:    "CERT",
151
	38:    "A6",
152
	39:    "DNAME",
153
	40:    "SINK",
154
	41:    "OPT",
155
	42:    "APL",
156
	43:    "DS",
157
	44:    "SSHFP",
158
	45:    "IPSECKEY",
159
	46:    "RRSIG",
160
	47:    "NSEC",
161
	48:    "DNSKEY",
162
	49:    "DHCID",
163
	50:    "NSEC3",
164
	51:    "NSEC3PARAM",
165
	52:    "TLSA",
166
	53:    "SMIMEA",
167
	55:    "HIP",
168
	56:    "NINFO",
169
	57:    "RKEY",
170
	58:    "TALINK",
171
	59:    "CDS",
172
	60:    "CDNSKEY",
173
	61:    "OPENPGPKEY",
174
	62:    "CSYNC",
175
	63:    "ZONEMD",
176
	64:    "SVCB",
177
	65:    "HTTPS",
178
	99:    "SPF",
179
	100:   "UINFO",
180
	101:   "UID",
181
	102:   "GID",
182
	103:   "UNSPEC",
183
	104:   "NID",
184
	105:   "L32",
185
	106:   "L64",
186
	107:   "LP",
187
	108:   "EUI48",
188
	109:   "EUI64",
189
	249:   "TKEY",
190
	250:   "TSIG",
191
	251:   "IXFR",
192
	252:   "AXFR",
193
	253:   "MAILB",
194
	254:   "MAILA",
195
	255:   "*",
196
	256:   "URI",
197
	257:   "CAA",
198
	258:   "AVC",
199
	259:   "DOA",
200
	260:   "AMTRELAY",
201
	32768: "TA",
202
	32769: "DLV",
203
}
204

205
const MaxDNSName = int(unsafe.Sizeof(dnsEventT{}.Name))
206

207
// DNS header RCODE (response code) field.
208
// https://datatracker.ietf.org/doc/rfc1035#section-4.1.1
209
var rCodeNames = map[uint8]string{
210
	0: "NoError",
211
	1: "FormErr",
212
	2: "ServFail",
213
	3: "NXDomain",
214
	4: "NotImp",
215
	5: "Refused",
216
}
217

218
// parseLabelSequence parses a label sequence into a string with dots.
219
// See https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.2
220
func parseLabelSequence(sample []byte) (ret string) {
221
	sampleBounded := make([]byte, MaxDNSName)
222
	copy(sampleBounded, sample)
223

224
	for i := 0; i < MaxDNSName; i++ {
225
		length := int(sampleBounded[i])
226
		if length == 0 {
227
			break
228
		}
229
		if i+1+length < MaxDNSName {
230
			ret += string(sampleBounded[i+1:i+1+length]) + "."
231
		}
232
		i += length
233
	}
234
	return ret
235
}
236

237
func bpfEventToDNSEvent(bpfEvent *dnsEventTAbbrev, answers []byte, netns uint64) (*types.Event, error) {
238
	event := types.Event{
239
		Event: eventtypes.Event{
240
			Type: eventtypes.NORMAL,
241
		},
242
		Pid:           bpfEvent.Pid,
243
		Tid:           bpfEvent.Tid,
244
		Uid:           bpfEvent.Uid,
245
		Gid:           bpfEvent.Gid,
246
		WithMountNsID: eventtypes.WithMountNsID{MountNsID: bpfEvent.MountNsId},
247
		WithNetNsID:   eventtypes.WithNetNsID{NetNsID: netns},
248
		Comm:          gadgets.FromCString(bpfEvent.Task[:]),
249
	}
250
	event.Event.Timestamp = gadgets.WallTimeFromBootTime(bpfEvent.Timestamp)
251

252
	event.ID = fmt.Sprintf("%.4x", bpfEvent.Id)
253
	ipversion := gadgets.IPVerFromAF(bpfEvent.Af)
254
	event.SrcIP = gadgets.IPStringFromBytes(bpfEvent.SaddrV6, ipversion)
255
	event.DstIP = gadgets.IPStringFromBytes(bpfEvent.DaddrV6, ipversion)
256

257
	if bpfEvent.Qr == 1 {
258
		event.Qr = types.DNSPktTypeResponse
259
		event.Nameserver = event.SrcIP
260
	} else {
261
		event.Qr = types.DNSPktTypeQuery
262
		event.Nameserver = event.DstIP
263
	}
264

265
	event.SrcPort = bpfEvent.Sport
266
	event.DstPort = bpfEvent.Dport
267
	event.Protocol = gadgets.ProtoString(int(bpfEvent.Proto))
268

269
	// Convert name into a string with dots
270
	event.DNSName = parseLabelSequence(bpfEvent.Name[:])
271

272
	// Parse the packet type
273
	event.PktType = "UNKNOWN"
274
	pktTypeUint := uint(bpfEvent.PktType)
275
	if pktTypeUint < uint(len(pktTypeNames)) {
276
		event.PktType = pktTypeNames[pktTypeUint]
277
	}
278

279
	qTypeUint := uint(bpfEvent.Qtype)
280
	var ok bool
281
	event.QType, ok = qTypeNames[qTypeUint]
282
	if !ok {
283
		event.QType = "UNASSIGNED"
284
	}
285

286
	if bpfEvent.Qr == 1 {
287
		rCodeUint := uint8(bpfEvent.Rcode)
288
		event.Rcode, ok = rCodeNames[rCodeUint]
289
		if !ok {
290
			event.Rcode = "UNKNOWN"
291
		}
292

293
		event.Latency = time.Duration(bpfEvent.LatencyNs)
294
	}
295

296
	// There's a limit on the number of addresses in the BPF event,
297
	// so bpfEvent.AnaddrCount is always less than or equal to bpfEvent.Ancount
298
	event.NumAnswers = int(bpfEvent.Ancount)
299
	for i := uint16(0); i < bpfEvent.Anaddrcount; i++ {
300
		// For A records, the address in the bpf event will be
301
		// IPv4-mapped-IPv6, which netip.Addr.Unmap() converts back to IPv4.
302
		addr := netip.AddrFrom16([16]byte(answers[i*16 : i*16+16])).Unmap().String()
303
		event.Addresses = append(event.Addresses, addr)
304
	}
305

306
	return &event, nil
307
}
308

309
// --- Registry changes
310

311
func (g *GadgetDesc) NewInstance() (gadgets.Gadget, error) {
312
	return &Tracer{}, nil
313
}
314

315
func (t *Tracer) Init(gadgetCtx gadgets.GadgetContext) error {
316
	if err := t.install(); err != nil {
317
		t.Close()
318
		return fmt.Errorf("installing tracer: %w", err)
319
	}
320

321
	t.ctx, t.cancel = gadgetcontext.WithTimeoutOrCancel(gadgetCtx.Context(), gadgetCtx.Timeout())
322

323
	return nil
324
}
325

326
func (t *Tracer) install() error {
327
	networkTracer, err := networktracer.NewTracer[types.Event]()
328
	if err != nil {
329
		return fmt.Errorf("creating network tracer: %w", err)
330
	}
331
	t.Tracer = networkTracer
332
	return nil
333
}
334

335
func (t *Tracer) run(ctx context.Context, logger logger.Logger, dnsTimeout time.Duration) error {
336
	spec, err := loadDns()
337
	if err != nil {
338
		return fmt.Errorf("loading asset: %w", err)
339
	}
340

341
	parseDNSEvent := func(rawSample []byte, netns uint64) (*types.Event, error) {
342
		bpfEvent := (*dnsEventTAbbrev)(unsafe.Pointer(&rawSample[0]))
343
		// 4 (padding) + (size of event without addresses) + (address count * 16)
344
		expected := 4 + int(unsafe.Sizeof(*bpfEvent)) + int(bpfEvent.Anaddrcount)*16
345
		if len(rawSample) != expected {
346
			return nil, fmt.Errorf("invalid sample size: received: %d vs expected: %d",
347
				len(rawSample), expected)
348
		}
349

350
		event, err := bpfEventToDNSEvent(bpfEvent, rawSample[unsafe.Offsetof(dnsEventT{}.Anaddr):], netns)
351
		if err != nil {
352
			return nil, err
353
		}
354

355
		return event, nil
356
	}
357

358
	if err := t.Tracer.Run(spec, types.Base, parseDNSEvent); err != nil {
359
		return fmt.Errorf("setting network tracer spec: %w", err)
360
	}
361

362
	// Start a background thread to garbage collect queries without responses
363
	// from the queries map (used to calculate DNS latency).
364
	// The goroutine terminates when t.ctx is done.
365
	queryMap := t.Tracer.GetMap(BPFQueryMapName)
366
	if queryMap == nil {
367
		t.Close()
368
		return fmt.Errorf("got nil retrieving DNS query map")
369
	}
370
	startGarbageCollector(ctx, logger, dnsTimeout, queryMap)
371

372
	return nil
373
}
374

375
func (t *Tracer) Run(gadgetCtx gadgets.GadgetContext) error {
376
	dnsTimeout := gadgetCtx.GadgetParams().Get(ParamDNSTimeout).AsDuration()
377

378
	if err := t.run(t.ctx, gadgetCtx.Logger(), dnsTimeout); err != nil {
379
		return err
380
	}
381

382
	<-t.ctx.Done()
383
	return nil
384
}
385

386
func (t *Tracer) Close() {
387
	if t.cancel != nil {
388
		t.cancel()
389
	}
390

391
	if t.Tracer != nil {
392
		t.Tracer.Close()
393
	}
394
}
395

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.