Skip to content

Commit 6169518

Browse files
committed
[WP] Add metric for dropped packets
1 parent 3af3972 commit 6169518

13 files changed

Lines changed: 311 additions & 8 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: 94 additions & 7 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,14 +237,25 @@ 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

@@ -386,3 +465,11 @@ func DropActionsToProgramSpecs(rawPacketEventMapFd, clsRouterMapFd int, filters
386465

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

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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,20 @@ func TestRawPacketDropAction(t *testing.T) {
335335
}
336336
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", probes.TCActUnspec, 1, rawpacket.DefaultProgOpts(), true)
337337
})
338+
339+
t.Run("syn-port-std-drop-stats", func(t *testing.T) {
340+
filters := []rawpacket.Filter{
341+
{
342+
RuleID: "ok",
343+
BPFFilter: "tcp dst port 5555 and tcp[tcpflags] == tcp-syn",
344+
Policy: rawpacket.PolicyDrop,
345+
Pid: 123,
346+
},
347+
}
348+
opts := rawpacket.DefaultProgOpts()
349+
opts.WithDropStatsMapFd(42)
350+
testRawPacketDropAction(t, filters, "test/raw_packet_drop_action", 255, 1, opts, true)
351+
})
338352
}
339353

340354
// TestRawPacketActionWithInvalidFilter ensures that when a set of filters contains an

pkg/security/metrics/metrics.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,10 @@ var (
579579
// MetricEventSampleSampled is the name of the metric used to report events that were sampled in kernel
580580
// Tags: event_type
581581
MetricEventSampleSampled = newRuntimeMetric(".event_sample.sampled")
582+
583+
// MetricRawPacketDropped is the name of the metric used to count packets dropped by network_filter actions
584+
// Tags: rule_id
585+
MetricRawPacketDropped = newRuntimeMetric(".network.raw_packet.dropped")
582586
)
583587

584588
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+
)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-present Datadog, Inc.
5+
6+
//go:build linux
7+
8+
// Package rawpacketdrop holds raw packet drop monitor related files
9+
package rawpacketdrop
10+
11+
import (
12+
"fmt"
13+
14+
manager "github.com/DataDog/ebpf-manager"
15+
lib "github.com/cilium/ebpf"
16+
17+
"github.com/DataDog/datadog-go/v5/statsd"
18+
19+
"github.com/DataDog/datadog-agent/pkg/security/metrics"
20+
"github.com/DataDog/datadog-agent/pkg/security/probe/managerhelper"
21+
)
22+
23+
// RuleIDsProvider returns the current filter index to rule_id mapping.
24+
type RuleIDsProvider func() map[uint32]string
25+
26+
// Monitor reports kernel-side raw packet drop counters grouped by rule_id.
27+
type Monitor struct {
28+
statsdClient statsd.ClientInterface
29+
droppedMap *lib.Map
30+
ruleIDs RuleIDsProvider
31+
lastCounts map[string]uint64
32+
}
33+
34+
// NewMonitor returns a new Monitor.
35+
func NewMonitor(manager *manager.Manager, statsdClient statsd.ClientInterface, ruleIDs RuleIDsProvider) (*Monitor, error) {
36+
droppedMap, err := managerhelper.Map(manager, "dropped_packets")
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
return &Monitor{
42+
statsdClient: statsdClient,
43+
droppedMap: droppedMap,
44+
ruleIDs: ruleIDs,
45+
lastCounts: make(map[string]uint64),
46+
}, nil
47+
}
48+
49+
// SendStats emits deltas from the kernel dropped_packets map grouped by rule_id.
50+
func (m *Monitor) SendStats() error {
51+
ruleIDs := m.ruleIDs()
52+
if len(ruleIDs) == 0 {
53+
m.lastCounts = make(map[string]uint64)
54+
return nil
55+
}
56+
57+
currentCounts := make(map[string]uint64, len(ruleIDs))
58+
iterator := m.droppedMap.Iterate()
59+
60+
var filterIndex uint64
61+
var count uint64
62+
for iterator.Next(&filterIndex, &count) {
63+
ruleID, ok := ruleIDs[uint32(filterIndex)]
64+
if !ok || ruleID == "" {
65+
continue
66+
}
67+
currentCounts[ruleID] += count
68+
}
69+
70+
for ruleID, count := range currentCounts {
71+
last := m.lastCounts[ruleID]
72+
if count <= last {
73+
continue
74+
}
75+
76+
delta := count - last
77+
tags := []string{"rule_id:" + ruleID}
78+
if err := m.statsdClient.Count(metrics.MetricRawPacketDropped, int64(delta), tags, 1.0); err != nil {
79+
return fmt.Errorf("failed to send raw packet dropped metric: %w", err)
80+
}
81+
m.lastCounts[ruleID] = count
82+
}
83+
84+
for ruleID := range m.lastCounts {
85+
if _, ok := currentCounts[ruleID]; !ok {
86+
delete(m.lastCounts, ruleID)
87+
}
88+
}
89+
90+
return nil
91+
}

0 commit comments

Comments
 (0)