Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize(bpf): Skip attaching {tcp,udp}_send* hooks when cgroup hooks are attached #197

Merged
merged 4 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 26 additions & 121 deletions bpf/bpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/mdlayher/netlink"
"strings"
"unsafe"

Expand Down Expand Up @@ -198,143 +199,39 @@ func (b *BPF) UpdateFlowPidMapValues(data map[*BpfFlowPidKeyT]BpfProcessMetaT) e
return nil
}

func (b *BPF) AttachCgroups(cgroupPath string) error {
if b.skipAttachCgroup {
return nil
}

lk, err := link.AttachCgroup(link.CgroupOptions{
Path: cgroupPath,
Attach: ebpf.AttachCGroupInetSockCreate,
Program: b.objs.CgroupSockCreate,
})
if err != nil {
return fmt.Errorf("attach cgroup/sock_create: %w", err)
}
b.links = append(b.links, lk)

lk, err = link.AttachCgroup(link.CgroupOptions{
Path: cgroupPath,
Attach: ebpf.AttachCgroupInetSockRelease,
Program: b.objs.CgroupSockRelease,
})
if err != nil {
return fmt.Errorf("attach cgroup/sock_release: %w", err)
}
b.links = append(b.links, lk)

return nil
}

func (b *BPF) AttachKprobes() error {
err := b.attachFentryOrKprobe("security_sk_classify_flow",
b.objs.FentrySecuritySkClassifyFlow, b.objs.KprobeSecuritySkClassifyFlow)
if err != nil {
return fmt.Errorf(": %w", err)
}

err = b.attachFentryOrKprobe("tcp_sendmsg",
b.objs.FentryTcpSendmsg, b.objs.KprobeTcpSendmsg)
if err != nil {
return fmt.Errorf(": %w", err)
}

err = b.attachFentryOrKprobe("udp_send_skb", b.objs.FentryUdpSendSkb, b.objs.KprobeUdpSendSkb)
if err != nil {
log.Infof("%+v", err)
if strings.Contains(err.Error(), "no such file or directory") {
err = b.attachFentryOrKprobe("udp_sendmsg", b.objs.FentryUdpSendmsg, b.objs.KprobeUdpSendmsg)
if err != nil {
return fmt.Errorf(": %w", err)
}
} else {
return fmt.Errorf(": %w", err)
}
}

err = b.attachFentryOrKprobe("nf_nat_packet",
b.objs.FentryNfNatPacket, b.objs.KprobeNfNatPacket)
if err != nil {
log.Infof("%+v", err)
if strings.Contains(err.Error(), "no such file or directory") {
log.Info("the kernel does not support netfilter based NAT feature, skip attach kprobe/nf_nat_packet")
} else {
return fmt.Errorf(": %w", err)
}
}

err = b.attachFentryOrKprobe("nf_nat_manip_pkt",
b.objs.FentryNfNatManipPkt, b.objs.KprobeNfNatManipPkt)
if err != nil {
log.Infof("%+v", err)
if strings.Contains(err.Error(), "no such file or directory") {
log.Info("the kernel does not support netfilter based NAT feature, skip attach kprobe/nf_nat_manip_pkt")
} else {
if b.skipAttachCgroup {
err = b.attachFentryOrKprobe("tcp_sendmsg",
b.objs.FentryTcpSendmsg, b.objs.KprobeTcpSendmsg)
if err != nil {
return fmt.Errorf(": %w", err)
}
}

return b.attachNetDevHooks()
}

func (b *BPF) attachNetDevHooks() error {
if !b.opts.hookNetDev {
return nil
}

err := b.attachFexitOrKprobe("register_netdevice",
nil, b.objs.KprobeRegisterNetdevice, b.objs.KretprobeRegisterNetdevice)
if err != nil {
return err
}

// TODO: refine
err = b.attachFexitOrKprobe("__dev_get_by_index",
nil, nil, b.objs.KretprobeDevGetByIndex)
if err != nil {
log.Infof("%+v", err)
if strings.Contains(err.Error(), "no such file or directory") {
err = b.attachFexitOrKprobe("dev_get_by_index",
nil, nil, b.objs.KretprobeDevGetByIndexLegacy)
if err != nil {
return err
}
} else {
return err
}
}

err = b.attachFentryOrKprobe("__dev_change_net_namespace",
nil, b.objs.KprobeDevChangeNetNamespace)
if err != nil {
log.Infof("%+v", err)
if strings.Contains(err.Error(), "no such file or directory") {
err = b.attachFentryOrKprobe("dev_change_net_namespace",
nil, b.objs.KprobeDevChangeNetNamespaceLegacy)
if err != nil {
return err
err = b.attachFentryOrKprobe("udp_send_skb", b.objs.FentryUdpSendSkb, b.objs.KprobeUdpSendSkb)
if err != nil {
log.Infof("%+v", err)
if isProbeNotSupportErr(err) {
err = b.attachFentryOrKprobe("udp_sendmsg", b.objs.FentryUdpSendmsg, b.objs.KprobeUdpSendmsg)
if err != nil {
return fmt.Errorf(": %w", err)
}
} else {
return fmt.Errorf(": %w", err)
}
} else {
return err
}
}

err = b.attachFexitOrKprobe("__dev_change_net_namespace",
nil, nil, b.objs.KretprobeDevChangeNetNamespace)
if err != nil {
log.Infof("%+v", err)
if strings.Contains(err.Error(), "no such file or directory") {
err = b.attachFexitOrKprobe("dev_change_net_namespace",
nil, nil, b.objs.KretprobeDevChangeNetNamespaceLegacy)
if err != nil {
return err
}
} else {
return err
}
if err := b.attachNatHooks(); err != nil {
return fmt.Errorf(": %w", err)
}

return nil
return b.attachNetDevHooks()
}

func (b *BPF) AttachTracepoints() error {
Expand Down Expand Up @@ -472,6 +369,10 @@ func attachTcHook(ifindex int, prog *ebpf.Program, ingress bool) (func(), error)
}
}
}
err = tcnl.SetOption(netlink.ExtendedAcknowledge, true)
if err != nil {
return closeFunc, fmt.Errorf("tc: set option ExtendedAcknowledge: %w", err)
}

var filter *tc.Object
fd := uint32(prog.FD())
Expand Down Expand Up @@ -544,6 +445,10 @@ func ensureTcQdisc(ifindex int) (func(), error) {
}
}
}
err = tcnl.SetOption(netlink.ExtendedAcknowledge, true)
if err != nil {
return closeFunc, fmt.Errorf("tc: set option ExtendedAcknowledge: %w", err)
}

qdisc := tc.Object{
Msg: tc.Msg{
Expand Down
41 changes: 41 additions & 0 deletions bpf/cgroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package bpf

import (
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/mozillazg/ptcpdump/internal/log"
)

func (b *BPF) AttachCgroups(cgroupPath string) error {
if cgroupPath == "" {
b.skipAttachCgroup = true
}
if b.skipAttachCgroup {
return nil
}

log.Info("attaching cgroup/sock_create")
lk, err := link.AttachCgroup(link.CgroupOptions{
Path: cgroupPath,
Attach: ebpf.AttachCGroupInetSockCreate,
Program: b.objs.CgroupSockCreate,
})
if err != nil {
return fmt.Errorf("attach cgroup/sock_create: %w", err)
}
b.links = append(b.links, lk)

log.Info("attaching cgroup/sock_release")
lk, err = link.AttachCgroup(link.CgroupOptions{
Path: cgroupPath,
Attach: ebpf.AttachCgroupInetSockRelease,
Program: b.objs.CgroupSockRelease,
})
if err != nil {
return fmt.Errorf("attach cgroup/sock_release: %w", err)
}
b.links = append(b.links, lk)

return nil
}
32 changes: 32 additions & 0 deletions bpf/nat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package bpf

import (
"fmt"
"github.com/mozillazg/ptcpdump/internal/log"
)

func (b *BPF) attachNatHooks() error {
err := b.attachFentryOrKprobe("nf_nat_packet",
b.objs.FentryNfNatPacket, b.objs.KprobeNfNatPacket)
if err != nil {
log.Infof("%+v", err)
if isProbeNotSupportErr(err) {
log.Info("the kernel does not support netfilter based NAT feature, skip attach kprobe/nf_nat_packet")
} else {
return fmt.Errorf(": %w", err)
}
}

err = b.attachFentryOrKprobe("nf_nat_manip_pkt",
b.objs.FentryNfNatManipPkt, b.objs.KprobeNfNatManipPkt)
if err != nil {
log.Infof("%+v", err)
if isProbeNotSupportErr(err) {
log.Info("the kernel does not support netfilter based NAT feature, skip attach kprobe/nf_nat_manip_pkt")
} else {
return fmt.Errorf(": %w", err)
}
}

return nil
}
65 changes: 65 additions & 0 deletions bpf/net_dev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package bpf

import (
"github.com/mozillazg/ptcpdump/internal/log"
)

func (b *BPF) attachNetDevHooks() error {
if !b.opts.hookNetDev {
return nil
}

err := b.attachFexitOrKprobe("register_netdevice",
nil, b.objs.KprobeRegisterNetdevice, b.objs.KretprobeRegisterNetdevice)
if err != nil {
return err
}

// TODO: refine
err = b.attachFexitOrKprobe("__dev_get_by_index",
nil, nil, b.objs.KretprobeDevGetByIndex)
if err != nil {
log.Infof("%+v", err)
if isProbeNotSupportErr(err) {
err = b.attachFexitOrKprobe("dev_get_by_index",
nil, nil, b.objs.KretprobeDevGetByIndexLegacy)
if err != nil {
return err
}
} else {
return err
}
}

err = b.attachFentryOrKprobe("__dev_change_net_namespace",
nil, b.objs.KprobeDevChangeNetNamespace)
if err != nil {
log.Infof("%+v", err)
if isProbeNotSupportErr(err) {
err = b.attachFentryOrKprobe("dev_change_net_namespace",
nil, b.objs.KprobeDevChangeNetNamespaceLegacy)
if err != nil {
return err
}
} else {
return err
}
}

err = b.attachFexitOrKprobe("__dev_change_net_namespace",
nil, nil, b.objs.KretprobeDevChangeNetNamespace)
if err != nil {
log.Infof("%+v", err)
if isProbeNotSupportErr(err) {
err = b.attachFexitOrKprobe("dev_change_net_namespace",
nil, nil, b.objs.KretprobeDevChangeNetNamespaceLegacy)
if err != nil {
return err
}
} else {
return err
}
}

return nil
}
12 changes: 12 additions & 0 deletions bpf/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/mozillazg/ptcpdump/internal/log"
"strings"
)

func (b *BPF) attachFentryOrKprobe(symbol string, fentryProg *ebpf.Program, kprobeProg *ebpf.Program) error {
Expand Down Expand Up @@ -101,3 +102,14 @@ func (b *BPF) attachBTFTracepointOrRawTP(name string, btfProg *ebpf.Program, raw

return nil
}

func isProbeNotSupportErr(err error) bool {
// TODO: refine
if strings.Contains(err.Error(), "no such file or directory") ||
strings.Contains(err.Error(), "invalid argument") {
log.Infof("%T", err)
log.Infof("%#v", err)
return true
}
return false
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/containerd/errdefs v0.3.0
github.com/go-logr/logr v1.4.2
github.com/mandiant/GoReSym v1.7.2-0.20240819162932-534ca84b42d5
github.com/mdlayher/netlink v1.7.2
github.com/smira/go-xz v0.1.0
github.com/stretchr/testify v1.9.0
github.com/vishvananda/netns v0.0.5
Expand Down Expand Up @@ -61,7 +62,6 @@ require (
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
Expand Down
6 changes: 2 additions & 4 deletions internal/capturer/capturer.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,8 @@ func (c *Capturer) AttachTracingHooks() error {
if err != nil {
log.Warnf("skip attach cgroup due to get cgroup v2 root dir failed: %s", err)
}
if cgroupPath != "" {
if err := bf.AttachCgroups(cgroupPath); err != nil {
return err
}
if err := bf.AttachCgroups(cgroupPath); err != nil {
return err
}

if err := bf.AttachKprobes(); err != nil {
Expand Down
6 changes: 0 additions & 6 deletions internal/utils/cgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ var reCgroup2Mount = regexp.MustCompile(`(?m)^cgroup2\s(/\S+)\scgroup2\s`)

func GetCgroupV2RootDir() (string, error) {
p, err := getCgroupV2RootDir(pathProcMounts)
if err != nil {
st, errv2 := os.Stat(defaultCgroupV2RootDir)
if errv2 == nil && st.IsDir() {
return defaultCgroupV2RootDir, nil
}
}
return p, err
}

Expand Down
Loading