Skip to content

Commit dcd9e29

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

12 files changed

Lines changed: 273 additions & 4 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: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const (
3939

4040
// payload size
4141
structRawPacketEventDataSize = 256
42+
43+
dropStatsKeyStackOffset = int16(-8)
44+
dropStatsValStackOffset = int16(-16)
4245
)
4346

4447
// ProgOpts defines options
@@ -60,6 +63,7 @@ type ProgOpts struct {
6063
ctxSaveReg asm.Register
6164
tailCallMapFd int
6265
hasGetCurrentCgroupId bool
66+
dropStatsMapFd int
6367
}
6468

6569
// DefaultProgOpts default options
@@ -97,6 +101,60 @@ func (opts *ProgOpts) WithGetCurrentCgroupID(hasGetCurrentCgroupId bool) *ProgOp
97101
return opts
98102
}
99103

104+
// WithDropStatsMapFd sets the map fd used to count dropped packets per filter index.
105+
func (opts *ProgOpts) WithDropStatsMapFd(fd int) *ProgOpts {
106+
opts.dropStatsMapFd = fd
107+
return opts
108+
}
109+
110+
func dropStatsIncrementInsts(filterIndex int, dropStatsMapFd int, nextLabel string) asm.Instructions {
111+
incLabel := fmt.Sprintf("inc_drop_stat_%d", filterIndex)
112+
initLabel := fmt.Sprintf("init_drop_stat_%d", filterIndex)
113+
114+
return asm.Instructions{
115+
asm.Mov.Reg(asm.R1, asm.RFP).WithSymbol(incLabel),
116+
asm.Add.Imm(asm.R1, int32(dropStatsKeyStackOffset)),
117+
asm.Mov.Imm(asm.R2, int32(filterIndex)),
118+
asm.StoreMem(asm.R1, 0, asm.R2, asm.DWord),
119+
asm.StoreImm(asm.R1, 4, 0, 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.Instruction{
133+
OpCode: asm.Ja.Op(asm.ImmSource),
134+
Constant: 0,
135+
}.WithReference(nextLabel),
136+
137+
asm.Mov.Reg(asm.R3, asm.RFP).WithSymbol(initLabel),
138+
asm.Add.Imm(asm.R3, int32(dropStatsValStackOffset)),
139+
asm.Mov.Imm(asm.R4, 1),
140+
asm.StoreMem(asm.R3, 0, asm.R4, asm.DWord),
141+
asm.StoreImm(asm.R3, 4, 0, asm.DWord),
142+
143+
asm.LoadMapPtr(asm.R1, dropStatsMapFd),
144+
asm.Mov.Reg(asm.R2, asm.RFP),
145+
asm.Add.Imm(asm.R2, int32(dropStatsKeyStackOffset)),
146+
asm.Mov.Reg(asm.R3, asm.RFP),
147+
asm.Add.Imm(asm.R3, int32(dropStatsValStackOffset)),
148+
asm.Mov.Imm(asm.R4, 0),
149+
asm.FnMapUpdateElem.Call(),
150+
151+
asm.Instruction{
152+
OpCode: asm.Ja.Op(asm.ImmSource),
153+
Constant: 0,
154+
}.WithReference(nextLabel),
155+
}
156+
}
157+
100158
// FilterToInsts compile a bpf filter expression
101159
func FilterToInsts(index int, filter Filter, opts ProgOpts) (asm.Instructions, error) {
102160
pcapBPF, err := pcap.CompileBPFFilter(layers.LinkTypeEthernet, 256, filter.BPFFilter)
@@ -133,6 +191,10 @@ func FilterToInsts(index int, filter Filter, opts ProgOpts) (asm.Instructions, e
133191
}
134192

135193
mismatchLabel := fmt.Sprintf("mismatch_%d_", index)
194+
matchLabel := opts.onMatchLabel
195+
if opts.dropStatsMapFd != 0 {
196+
matchLabel = fmt.Sprintf("inc_drop_stat_%d", index)
197+
}
136198

137199
if filter.Pid != 0 {
138200
insts = append(insts,
@@ -142,7 +204,7 @@ func FilterToInsts(index int, filter Filter, opts ProgOpts) (asm.Instructions, e
142204
// check the pid
143205
// load the pid from the packet
144206
asm.LoadMem(asm.R7, opts.eventPtrReg, structRawPacketEventPidOffset, asm.Word),
145-
asm.JEq.Imm(asm.R7, int32(filter.Pid), opts.onMatchLabel),
207+
asm.JEq.Imm(asm.R7, int32(filter.Pid), matchLabel),
146208
asm.Mov.Imm(asm.R4, 0).WithSymbol(mismatchLabel), // nop instruction, just hold the symbol
147209
)
148210
} else if !filter.CGroupPathKey.IsNull() {
@@ -169,14 +231,19 @@ func FilterToInsts(index int, filter Filter, opts ProgOpts) (asm.Instructions, e
169231

170232
// check the cgroup id
171233
asm.LoadImm(asm.R4, int64(filter.CGroupPathKey.Inode), asm.DWord),
172-
asm.JEq.Reg(asm.R7, asm.R4, opts.onMatchLabel),
234+
asm.JEq.Reg(asm.R7, asm.R4, matchLabel),
173235
asm.Mov.Imm(asm.R4, 0).WithSymbol(mismatchLabel), // nop instruction, just hold the symbol
174236
)
175237
} else {
176238
insts = append(insts,
177-
asm.JNE.Imm(cbpfcOpts.Result, 0, opts.onMatchLabel).WithSymbol(resultLabel),
239+
asm.JNE.Imm(cbpfcOpts.Result, 0, matchLabel).WithSymbol(resultLabel),
178240
)
179241
}
242+
243+
if opts.dropStatsMapFd != 0 {
244+
insts = append(insts, dropStatsIncrementInsts(index, opts.dropStatsMapFd, opts.onMatchLabel)...)
245+
}
246+
180247
return insts, nil
181248
}
182249

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/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+
}

pkg/security/probe/probe_ebpf.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ type EBPFProbe struct {
187187

188188
// raw packet filter for actions
189189
rawPacketActionFilters []rawpacket.Filter
190+
dropActionRuleIDs map[uint32]string
191+
dropActionRuleIDsLock sync.RWMutex
190192

191193
// remediation tracking
192194
activeRemediations map[string]*Remediation
@@ -783,10 +785,17 @@ func (p *EBPFProbe) setupRawPacketFiltersOnNewRuleset(rs *rules.RuleSet) error {
783785
func (p *EBPFProbe) applyRawPacketActionFilters(applyFromRuleset bool) error {
784786
// TODO check cgroupv2
785787

788+
p.rebuildDropActionRuleIDs()
789+
786790
opts := rawpacket.DefaultProgOpts()
787791
opts.WithProgPrefix("raw_packet_drop_action_")
788792
opts.WithGetCurrentCgroupID(p.kernelVersion.HasBpfGetCurrentPidTgidForSchedCLS())
789793

794+
if droppedPacketsMap, err := managerhelper.Map(p.Manager, "dropped_packets"); err == nil {
795+
p.clearDroppedPacketsMap(droppedPacketsMap)
796+
opts.WithDropStatsMapFd(droppedPacketsMap.FD())
797+
}
798+
790799
// adapt max instruction limits depending of the kernel version
791800
if p.kernelVersion.Code >= kernel.Kernel5_2 {
792801
opts.MaxProgSize = 1_000_000
@@ -831,6 +840,40 @@ func (p *EBPFProbe) addRawPacketActionFilter(actionFilter rawpacket.Filter) erro
831840
return p.applyRawPacketActionFilters(false)
832841
}
833842

843+
func (p *EBPFProbe) rebuildDropActionRuleIDs() {
844+
ruleIDs := make(map[uint32]string, len(p.rawPacketActionFilters))
845+
for i, filter := range p.rawPacketActionFilters {
846+
if i >= rawpacket.MaxDropActionFilters {
847+
break
848+
}
849+
ruleIDs[uint32(i)] = string(filter.RuleID)
850+
}
851+
852+
p.dropActionRuleIDsLock.Lock()
853+
p.dropActionRuleIDs = ruleIDs
854+
p.dropActionRuleIDsLock.Unlock()
855+
}
856+
857+
func (p *EBPFProbe) getDropActionRuleIDs() map[uint32]string {
858+
p.dropActionRuleIDsLock.RLock()
859+
defer p.dropActionRuleIDsLock.RUnlock()
860+
861+
out := make(map[uint32]string, len(p.dropActionRuleIDs))
862+
for index, ruleID := range p.dropActionRuleIDs {
863+
out[index] = ruleID
864+
}
865+
return out
866+
}
867+
868+
func (p *EBPFProbe) clearDroppedPacketsMap(droppedPacketsMap *lib.Map) {
869+
iterator := droppedPacketsMap.Iterate()
870+
var key uint64
871+
var value uint64
872+
for iterator.Next(&key, &value) {
873+
_ = droppedPacketsMap.Delete(key)
874+
}
875+
}
876+
834877
// Start the probe
835878
func (p *EBPFProbe) Start() error {
836879
// Apply rules to the already stored data before starting the event stream to avoid concurrency issues
@@ -3177,6 +3220,7 @@ func NewEBPFProbe(probe *Probe, config *config.Config, hostname string, opts Opt
31773220
MetricNameTruncated: atomic.NewUint64(0),
31783221
activeRemediations: make(map[string]*Remediation),
31793222
pid: utils.Getpid(),
3223+
dropActionRuleIDs: make(map[uint32]string),
31803224
}
31813225

31823226
p.onNewPCE = func(pce *model.ProcessCacheEntry, err error) {

0 commit comments

Comments
 (0)