Skip to content

Commit 3e2df14

Browse files
committed
[WP] Add metric for dropped packets
1 parent bbbb55a commit 3e2df14

13 files changed

Lines changed: 332 additions & 24 deletions

File tree

pkg/security/ebpf/c/include/maps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ BPF_LRU_MAP(dns_responses_sent_to_userspace, u16, struct dns_responses_sent_to_u
9898
BPF_LRU_MAP(capabilities_usage, struct capabilities_usage_key_t, struct capabilities_usage_entry_t, 1) // max entries will be overridden at runtime
9999
BPF_LRU_MAP(sock_cookie_pid, u64, u32, 1); // max entries will be overridden at runtime
100100
BPF_LRU_MAP(memfd_tracking, struct memfd_key_t, u32, 1024)
101+
BPF_LRU_MAP(dropped_packets, u64, u64, 512)
101102

102103
BPF_LRU_MAP_FLAGS(tasks_in_coredump, u64, u8, 64, BPF_F_NO_COMMON_LRU)
103104
BPF_LRU_MAP_FLAGS(syscalls, u64, struct syscall_cache_t, 1, BPF_F_NO_COMMON_LRU) // max entries will be overridden at runtime

pkg/security/ebpf/probes/rawpacket/bpffilter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ const (
3737
PolicyDrop
3838
)
3939

40+
// MaxDropActionFilters is the maximum number of network drop action filters tracked in kernel.
41+
const MaxDropActionFilters = 512
42+
4043
// ToTCAct converts a policy to a TCAct
4144
func (p Policy) ToTCAct() TCAct {
4245
switch p {

pkg/security/ebpf/probes/rawpacket/pcap.go

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package rawpacket
1111
import (
1212
"errors"
1313
"fmt"
14+
"strings"
1415

1516
"github.com/cilium/ebpf"
1617
"github.com/cilium/ebpf/asm"
@@ -39,6 +40,9 @@ const (
3940

4041
// payload size
4142
structRawPacketEventDataSize = 256
43+
44+
dropStatsKeyStackOffset = int16(-8)
45+
dropStatsValStackOffset = int16(-16)
4246
)
4347

4448
// ProgOpts defines options
@@ -60,6 +64,7 @@ type ProgOpts struct {
6064
ctxSaveReg asm.Register
6165
tailCallMapFd int
6266
hasGetCurrentCgroupId bool
67+
dropStatsMapFd int
6368
}
6469

6570
// DefaultProgOpts default options
@@ -97,6 +102,52 @@ func (opts *ProgOpts) WithGetCurrentCgroupID(hasGetCurrentCgroupId bool) *ProgOp
97102
return opts
98103
}
99104

105+
// WithDropStatsMapFd sets the map fd used to count dropped packets per filter index.
106+
func (opts *ProgOpts) WithDropStatsMapFd(fd int) *ProgOpts {
107+
opts.dropStatsMapFd = fd
108+
return opts
109+
}
110+
111+
func dropStatsIncrementInsts(filterIndex int, dropStatsMapFd int, nextLabel string) asm.Instructions {
112+
incLabel := fmt.Sprintf("inc_drop_stat_%d", filterIndex)
113+
initLabel := fmt.Sprintf("init_drop_stat_%d", filterIndex)
114+
115+
return asm.Instructions{
116+
asm.Mov.Reg(asm.R1, asm.RFP).WithSymbol(incLabel),
117+
asm.Add.Imm(asm.R1, int32(dropStatsKeyStackOffset)),
118+
asm.Mov.Imm(asm.R2, int32(filterIndex)),
119+
asm.StoreMem(asm.R1, 0, asm.R2, asm.DWord),
120+
121+
asm.LoadMapPtr(asm.R1, dropStatsMapFd),
122+
asm.Mov.Reg(asm.R2, asm.RFP),
123+
asm.Add.Imm(asm.R2, int32(dropStatsKeyStackOffset)),
124+
asm.FnMapLookupElem.Call(),
125+
asm.JEq.Imm(asm.R0, 0, initLabel),
126+
127+
asm.Mov.Reg(asm.R5, asm.R0),
128+
asm.LoadMem(asm.R6, asm.R5, 0, asm.DWord),
129+
asm.Add.Imm(asm.R6, 1),
130+
asm.StoreMem(asm.R5, 0, asm.R6, asm.DWord),
131+
132+
asm.Ja.Label(nextLabel),
133+
134+
asm.Mov.Reg(asm.R3, asm.RFP).WithSymbol(initLabel),
135+
asm.Add.Imm(asm.R3, int32(dropStatsValStackOffset)),
136+
asm.Mov.Imm(asm.R4, 1),
137+
asm.StoreMem(asm.R3, 0, asm.R4, asm.DWord),
138+
139+
asm.LoadMapPtr(asm.R1, dropStatsMapFd),
140+
asm.Mov.Reg(asm.R2, asm.RFP),
141+
asm.Add.Imm(asm.R2, int32(dropStatsKeyStackOffset)),
142+
asm.Mov.Reg(asm.R3, asm.RFP),
143+
asm.Add.Imm(asm.R3, int32(dropStatsValStackOffset)),
144+
asm.Mov.Imm(asm.R4, 0),
145+
asm.FnMapUpdateElem.Call(),
146+
147+
asm.Ja.Label(nextLabel),
148+
}
149+
}
150+
100151
// FilterToInsts compile a bpf filter expression
101152
func FilterToInsts(index int, filter Filter, opts ProgOpts) (asm.Instructions, error) {
102153
pcapBPF, err := pcap.CompileBPFFilter(layers.LinkTypeEthernet, 256, filter.BPFFilter)
@@ -131,25 +182,42 @@ func FilterToInsts(index int, filter Filter, opts ProgOpts) (asm.Instructions, e
131182
)
132183
resultLabel = ""
133184
}
185+
useDropStats := opts.dropStatsMapFd != 0
134186

187+
// Initialize labels
135188
mismatchLabel := fmt.Sprintf("mismatch_%d_", index)
189+
afterDropStatsLabel := fmt.Sprintf("after_drop_stat_%d", index)
190+
matchLabel := opts.onMatchLabel
191+
mismatchTail := asm.Instructions{
192+
asm.Mov.Imm(asm.R4, 0).WithSymbol(mismatchLabel),
193+
}
194+
skipLabel := mismatchLabel
195+
196+
// Change labels if it's a drop filter with drop stats
197+
if useDropStats {
198+
matchLabel = fmt.Sprintf("inc_drop_stat_%d", index)
199+
skipLabel = afterDropStatsLabel
200+
mismatchTail = asm.Instructions{
201+
asm.Ja.Label(skipLabel),
202+
}
203+
}
136204

137205
if filter.Pid != 0 {
138206
insts = append(insts,
139207
// == 0, no match
140-
asm.JEq.Imm(cbpfcOpts.Result, 0, mismatchLabel).WithSymbol(resultLabel),
208+
asm.JEq.Imm(cbpfcOpts.Result, 0, skipLabel).WithSymbol(resultLabel),
141209

142210
// check the pid
143211
// load the pid from the packet
144212
asm.LoadMem(asm.R7, opts.eventPtrReg, structRawPacketEventPidOffset, asm.Word),
145-
asm.JEq.Imm(asm.R7, int32(filter.Pid), opts.onMatchLabel),
146-
asm.Mov.Imm(asm.R4, 0).WithSymbol(mismatchLabel), // nop instruction, just hold the symbol
213+
asm.JEq.Imm(asm.R7, int32(filter.Pid), matchLabel),
147214
)
215+
insts = append(insts, mismatchTail...)
148216
} else if !filter.CGroupPathKey.IsNull() {
149217
// use the cgroup id which the inode of the cgroup path
150218
insts = append(insts,
151219
// == 0, no match
152-
asm.JEq.Imm(cbpfcOpts.Result, 0, mismatchLabel).WithSymbol(resultLabel),
220+
asm.JEq.Imm(cbpfcOpts.Result, 0, skipLabel).WithSymbol(resultLabel),
153221

154222
// load the cgroup id from the packet
155223
asm.LoadMem(asm.R7, opts.eventPtrReg, structRawPacketEventCgroupIdOffset, asm.DWord),
@@ -169,21 +237,33 @@ func FilterToInsts(index int, filter Filter, opts ProgOpts) (asm.Instructions, e
169237

170238
// check the cgroup id
171239
asm.LoadImm(asm.R4, int64(filter.CGroupPathKey.Inode), asm.DWord),
172-
asm.JEq.Reg(asm.R7, asm.R4, opts.onMatchLabel),
173-
asm.Mov.Imm(asm.R4, 0).WithSymbol(mismatchLabel), // nop instruction, just hold the symbol
240+
asm.JEq.Reg(asm.R7, asm.R4, matchLabel),
241+
)
242+
insts = append(insts, mismatchTail...)
243+
} else if useDropStats {
244+
insts = append(insts,
245+
asm.JEq.Imm(cbpfcOpts.Result, 0, skipLabel).WithSymbol(resultLabel),
246+
asm.Ja.Label(matchLabel),
174247
)
175248
} else {
176249
insts = append(insts,
177-
asm.JNE.Imm(cbpfcOpts.Result, 0, opts.onMatchLabel).WithSymbol(resultLabel),
250+
asm.JNE.Imm(cbpfcOpts.Result, 0, matchLabel).WithSymbol(resultLabel),
178251
)
179252
}
253+
254+
if useDropStats {
255+
insts = append(insts, dropStatsIncrementInsts(index, opts.dropStatsMapFd, opts.onMatchLabel)...)
256+
insts = append(insts, asm.Mov.Imm(asm.R4, 0).WithSymbol(afterDropStatsLabel)) // nop instruction, just hold the symbol
257+
}
258+
180259
return insts, nil
181260
}
182261

183-
// we want to creates progs like that
184-
// prog1 -> tc1 -> footer -> prog2 -> tc2 -> footer -> progN -> footer
185-
// where each prog is like this
186-
// header -> filter 1 -> filter 2 -> ... -> filter n -> footer
262+
// we want to create progs like that
263+
// prog1 -> prog2 -> ... -> progN
264+
//
265+
// where each prog is:
266+
// header -> filter 1 -> filter 2 -> ... -> filter n -> [tail_call] -> footer
187267
func filtersToProgs(filters []Filter, opts ProgOpts, headerInsts, footerInsts asm.Instructions) ([]asm.Instructions, *multierror.Error) {
188268
var (
189269
progInsts []asm.Instructions
@@ -386,3 +466,11 @@ func DropActionsToProgramSpecs(rawPacketEventMapFd, clsRouterMapFd int, filters
386466

387467
return progSpecs, mErr.ErrorOrNil()
388468
}
469+
470+
// FormatProgramInstructions returns BPF instructions with their indices, useful when
471+
// debugging verifier errors such as "unreachable insn N".
472+
func FormatProgramInstructions(insts asm.Instructions) string {
473+
var b strings.Builder
474+
fmt.Fprintf(&b, "% 1v", insts)
475+
return b.String()
476+
}

pkg/security/ebpf/probes/rawpacket/pcap_unsupported.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ func (opts *ProgOpts) WithGetCurrentCgroupID(_ bool) *ProgOpts {
4040
return opts
4141
}
4242

43+
// WithDropStatsMapFd sets the map fd used to count dropped packets per filter index.
44+
func (opts *ProgOpts) WithDropStatsMapFd(_ int) *ProgOpts {
45+
return opts
46+
}
47+
4348
// FilterToInsts compile a bpf filter expression
4449
func FilterToInsts(_ int, _ Filter, _ ProgOpts) (asm.Instructions, error) {
4550
return asm.Instructions{}, errors.New("not supported")

pkg/security/ebpf/tests/raw_packet_test.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func testRawPacketFilter(t *testing.T, filters []rawpacket.Filter, progName stri
8484
assert.Equal(t, expRetCode, code, "return code error: %v, please check the kernel structure offset of raw_packet_event", err)
8585
}
8686

87-
func testRawPacketDropAction(t *testing.T, filters []rawpacket.Filter, progName string, expRetCode int64, expProgNum int, opts rawpacket.ProgOpts, catchCompilerError bool) {
87+
func testRawPacketDropAction(t *testing.T, filters []rawpacket.Filter, progName string, expRetCode int64, expProgNum int, opts rawpacket.ProgOpts, catchCompilerError bool, withDropStats bool) {
8888
var ctx baloum.StdContext
8989

9090
vm := newVM(t)
@@ -101,6 +101,12 @@ func testRawPacketDropAction(t *testing.T, filters []rawpacket.Filter, progName
101101
routerMap, err := vm.LoadMap(inactiveName)
102102
assert.Nil(t, err, "map not found")
103103

104+
if withDropStats {
105+
droppedPacketsMap, err := vm.LoadMap("dropped_packets")
106+
assert.Nil(t, err, "dropped_packets map not found")
107+
opts.WithDropStatsMapFd(droppedPacketsMap.FD())
108+
}
109+
104110
progSpecs, err := rawpacket.DropActionsToProgramSpecs(rawPacketEventMap.FD(), routerMap.FD(), filters, opts)
105111
if err != nil {
106112
if catchCompilerError {
@@ -293,7 +299,7 @@ func TestRawPacketDropAction(t *testing.T) {
293299
Pid: 123,
294300
},
295301
}
296-
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", 255, 1, rawpacket.DefaultProgOpts(), true)
302+
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", 255, 1, rawpacket.DefaultProgOpts(), true, false)
297303
})
298304

299305
t.Run("syn-port-std-pid-ko", func(t *testing.T) {
@@ -305,7 +311,7 @@ func TestRawPacketDropAction(t *testing.T) {
305311
Pid: 999,
306312
},
307313
}
308-
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", probes.TCActUnspec, 1, rawpacket.DefaultProgOpts(), true)
314+
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", probes.TCActUnspec, 1, rawpacket.DefaultProgOpts(), true, false)
309315
})
310316

311317
t.Run("syn-port-std-cgroup-ok", func(t *testing.T) {
@@ -319,7 +325,7 @@ func TestRawPacketDropAction(t *testing.T) {
319325
},
320326
},
321327
}
322-
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", 255, 1, rawpacket.DefaultProgOpts(), true)
328+
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", 255, 1, rawpacket.DefaultProgOpts(), true, false)
323329
})
324330

325331
t.Run("syn-port-std-cgroup-ko", func(t *testing.T) {
@@ -333,7 +339,19 @@ func TestRawPacketDropAction(t *testing.T) {
333339
},
334340
},
335341
}
336-
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", probes.TCActUnspec, 1, rawpacket.DefaultProgOpts(), true)
342+
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", probes.TCActUnspec, 1, rawpacket.DefaultProgOpts(), true, false)
343+
})
344+
345+
t.Run("syn-port-std-drop-stats", func(t *testing.T) {
346+
filters := []rawpacket.Filter{
347+
{
348+
RuleID: "ok",
349+
BPFFilter: "tcp dst port 5555 and tcp[tcpflags] == tcp-syn",
350+
Policy: rawpacket.PolicyDrop,
351+
Pid: 123,
352+
},
353+
}
354+
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", 255, 1, rawpacket.DefaultProgOpts(), true, true)
337355
})
338356
}
339357

@@ -362,5 +380,5 @@ func TestRawPacketActionWithInvalidFilter(t *testing.T) {
362380

363381
// catchCompilerError is false: the invalid filter is expected to fail compilation and
364382
// be skipped, but the valid filter should still produce a single program (and match).
365-
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", 255, 1, rawpacket.DefaultProgOpts(), false)
383+
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", 255, 1, rawpacket.DefaultProgOpts(), false, false)
366384
}

pkg/security/metrics/metrics.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,10 @@ var (
593593
// MetricEventSampleSampled is the name of the metric used to report events that were sampled in kernel
594594
// Tags: event_type
595595
MetricEventSampleSampled = newRuntimeMetric(".event_sample.sampled")
596+
597+
// MetricRawPacketDropped is the name of the metric used to count packets dropped by network_filter actions
598+
// Tags: rule_id
599+
MetricRawPacketDropped = newRuntimeMetric(".network.raw_packet.dropped")
596600
)
597601

598602
var (

pkg/security/probe/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ go_library(
9696
"//pkg/security/probe/monitors/discarder",
9797
"//pkg/security/probe/monitors/dns",
9898
"//pkg/security/probe/monitors/eventsample",
99+
"//pkg/security/probe/monitors/rawpacketdrop",
99100
"//pkg/security/probe/monitors/syscalls",
100101
"//pkg/security/probe/procfs",
101102
"//pkg/security/probe/sysctl",
@@ -213,6 +214,7 @@ go_library(
213214
"//pkg/security/probe/monitors/discarder",
214215
"//pkg/security/probe/monitors/dns",
215216
"//pkg/security/probe/monitors/eventsample",
217+
"//pkg/security/probe/monitors/rawpacketdrop",
216218
"//pkg/security/probe/monitors/syscalls",
217219
"//pkg/security/probe/procfs",
218220
"//pkg/security/probe/sysctl",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
load("@rules_go//go:def.bzl", "go_library")
2+
3+
go_library(
4+
name = "rawpacketdrop",
5+
srcs = ["rawpacketdrop_monitor.go"],
6+
importpath = "github.com/DataDog/datadog-agent/pkg/security/probe/monitors/rawpacketdrop",
7+
visibility = ["//visibility:public"],
8+
deps = select({
9+
"@rules_go//go/platform:android": [
10+
"//pkg/security/metrics",
11+
"//pkg/security/probe/managerhelper",
12+
"@com_github_cilium_ebpf//:ebpf",
13+
"@com_github_datadog_datadog_go_v5//statsd",
14+
"@com_github_datadog_ebpf_manager//:ebpf-manager",
15+
],
16+
"@rules_go//go/platform:linux": [
17+
"//pkg/security/metrics",
18+
"//pkg/security/probe/managerhelper",
19+
"@com_github_cilium_ebpf//:ebpf",
20+
"@com_github_datadog_datadog_go_v5//statsd",
21+
"@com_github_datadog_ebpf_manager//:ebpf-manager",
22+
],
23+
"//conditions:default": [],
24+
}),
25+
)

0 commit comments

Comments
 (0)