Skip to content

Commit

Permalink
Skip empty dns responses in kernel
Browse files Browse the repository at this point in the history
  • Loading branch information
anjmao committed Dec 6, 2024
1 parent 0c916c3 commit dc50752
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 40 deletions.
12 changes: 10 additions & 2 deletions cmd/agent/daemon/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,16 @@ func buildEBPFPolicy(log *logging.Logger, cfg *Config, exporters *state.Exporter
}

dnsEventPolicy := &ebpftracer.EventPolicy{
ID: events.NetPacketDNSBase,
PreFilterGenerator: ebpftracer.DnsEventsFilter(log, 100, 60*time.Second),
ID: events.NetPacketDNSBase,
KernelFilters: []ebpftracer.KernelEventFilter{
{
Name: "Skip emtpy dns answers",
Description: `Helper net_l7_empty_dns_answer is used to check if dns header answers field is non zero.
Currently we care only care about dns responses with valid answers.
`,
},
},
PreFilterGenerator: ebpftracer.DnsEventsFilter(log, 200, 60*time.Second),
}

if cfg.ProcessTree.Enabled {
Expand Down
26 changes: 26 additions & 0 deletions pkg/ebpftracer/c/tracee.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -2256,6 +2256,23 @@ statfunc bool net_l7_is_ssh(struct __sk_buff *skb, u32 l7_off)
return false;
}

// see https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1 for dns header.
statfunc bool net_l7_empty_dns_answer(struct __sk_buff *skb, u32 l7_off)
{
char buf[ssh_min_len];
__builtin_memset(&buf, 0, sizeof(buf));

if (skb->len < l7_off) {
return false;
}

u16 ancount = 0;
if (bpf_skb_load_bytes(skb, l7_off + 6, &ancount, sizeof(ancount)) < 0) {
return false;
}
return bpf_ntohs(ancount) == 0;
}

//
// SUPPORTED L4 NETWORK PROTOCOL (tcp, udp, icmp) HANDLERS
//
Expand Down Expand Up @@ -2360,6 +2377,11 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_udp)

CGROUP_SKB_HANDLE_FUNCTION(proto_tcp_dns)
{
// Skip the 2-byte length prefix for dns over tcp.
if (net_l7_empty_dns_answer(ctx, md.header_size + 2)) {
return 1;
}

// submit DNS base event if needed (full packet)
if (should_submit_net_event(neteventctx, SUB_NET_PACKET_DNS))
cgroup_skb_submit_event(ctx, md, neteventctx, NET_PACKET_DNS, FULL);
Expand All @@ -2369,6 +2391,10 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_tcp_dns)

CGROUP_SKB_HANDLE_FUNCTION(proto_udp_dns)
{
if (net_l7_empty_dns_answer(ctx, md.header_size)) {
return 1;
}

// submit DNS base event if needed (full packet)
if (should_submit_net_event(neteventctx, SUB_NET_PACKET_DNS))
cgroup_skb_submit_event(ctx, md, neteventctx, NET_PACKET_DNS, FULL);
Expand Down
8 changes: 8 additions & 0 deletions pkg/ebpftracer/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,18 @@ type EventFilter func(event *types.Event) error
// EventFilterGenerator Produces an event filter for each call
type EventFilterGenerator func() EventFilter

// KernelEventFilter is a placeholder and currently used for documentation purposes only.
// Each used filter is describer with explanation how it's implemented in the kernel.
type KernelEventFilter struct {
Name string
Description string
}

type EventPolicy struct {
ID events.ID
PreFilterGenerator PreEventFilterGenerator
FilterGenerator EventFilterGenerator
KernelFilters []KernelEventFilter
}

// RateLimitPolicy allows to configure event rate limiting.
Expand Down
42 changes: 8 additions & 34 deletions pkg/ebpftracer/policy_filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,33 +128,6 @@ func newRateLimiter(spec RateLimitPolicy) *rate.Limiter {
return rateLimiter
}

// FilterEmptyDnsAnswers will drop any DNS event, that is missing an answer section
func FilterEmptyDnsAnswers(l *logging.Logger) EventFilterGenerator {
return func() EventFilter {
return func(event *types.Event) error {
if event.Context.EventID != events.NetPacketDNSBase {
return FilterPass
}

dnsEventArgs, ok := event.Args.(types.NetPacketDNSBaseArgs)
if !ok {
return FilterPass
}

if dnsEventArgs.Payload == nil {
l.Warn("retreived invalid event for event type dns")
return FilterPass
}

if len(dnsEventArgs.Payload.Answers) == 0 {
return FilterErrEmptyDNSResponse
}

return FilterPass
}
}
}

// more hash function in https://github.com/elastic/go-freelru/blob/main/bench/hash.go
func hashStringXXHASH(s string) uint32 {
return uint32(xxhash.Sum64String(s)) // nolint:gosec
Expand All @@ -171,7 +144,6 @@ func DeduplicateDnsEvents(l *logging.Logger, size uint32, ttl time.Duration) Eve
if err != nil {
panic(err)
}

cache.SetLifetime(ttl)

return func(event *types.Event) error {
Expand Down Expand Up @@ -218,15 +190,17 @@ func DnsEventsFilter(log *logging.Logger, size uint32, ttl time.Duration) PreEve
}

cache.SetLifetime(ttl)
var discard uint8

return func(ctx *types.EventContext, decoder *decoder.Decoder) (types.Args, error) {
if ctx.EventID != events.NetPacketDNSBase {
return nil, FilterPass
}

var zero uint8
err = decoder.DecodeUint8(&zero)
err = decoder.DecodeUint8(&zero)
// Read firsts two bytes and discard. It's mapped to argsnum and index.
// For network events in most cases there is only 1 argument (payload).
_ = decoder.DecodeUint8(&discard)
_ = decoder.DecodeUint8(&discard)

packetData, err := decoder.ReadMaxByteSliceFromBuff(-1)
if err != nil {
Expand All @@ -246,6 +220,7 @@ func DnsEventsFilter(log *logging.Logger, size uint32, ttl time.Duration) PreEve
return nil, FilterErrEmptyDNSResponse
}

// Cache dns by dns question. Cached records are not dropped.
cacheKey := xxhash.Sum64(dns.Questions[0].Name)
if cache.Contains(cacheKey) {
if log.IsEnabled(slog.LevelDebug) {
Expand All @@ -255,10 +230,9 @@ func DnsEventsFilter(log *logging.Logger, size uint32, ttl time.Duration) PreEve
}
cache.Add(cacheKey, cacheValue{})

result := types.NetPacketDNSBaseArgs{
return types.NetPacketDNSBaseArgs{
Payload: toProtoDNS(&details, dns),
}
return result, FilterPass
}, FilterPass
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions pkg/ebpftracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,14 +482,12 @@ func (t *Tracer) getFilterPolicy(eventID events.ID, cgroupID uint64) *cgroupEven
eventPolicy, found := t.eventPoliciesMap[eventID]
if found {
cgPolicyMap, found := t.cgroupEventPolicy[cgroupID]

if !found {
cgPolicyMap = make(map[events.ID]*cgroupEventPolicy)
t.cgroupEventPolicy[cgroupID] = cgPolicyMap
}

cgPolicy, found := cgPolicyMap[eventID]

if !found {
cgPolicy = newCgroupEventPolicy(eventPolicy)
t.cgroupEventPolicy[cgroupID][eventID] = cgPolicy
Expand Down
Binary file modified pkg/ebpftracer/tracer_arm64_bpfel.o
Binary file not shown.
3 changes: 1 addition & 2 deletions pkg/ebpftracer/tracer_playground_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,11 @@ func TestTracer(t *testing.T) {
Events: []*ebpftracer.EventPolicy{
{ID: events.SockSetState},
{ID: events.SchedProcessExec},
{ID: events.NetPacketDNSBase},
{ID: events.MagicWrite},
{ID: events.ProcessOomKilled},
{ID: events.StdioViaSocket},
{ID: events.TtyWrite},
{ID: events.NetPacketSSHBase},
{ID: events.NetPacketDNSBase},
},
SignatureEvents: signatureEngine.TargetEvents(),
}
Expand Down
Binary file modified pkg/ebpftracer/tracer_x86_bpfel.o
Binary file not shown.

0 comments on commit dc50752

Please sign in to comment.