Skip to content

Commit

Permalink
Support tracing tc-bpf
Browse files Browse the repository at this point in the history
Add an option --filter-trace-tc to trace all tc-bpf progs on host by
fentry-ing on the progs.

To trace tc-bpf, we list all tc-bpf progs first. Then, for each prog, we
have to retrieve its entry function name as fentry attaching function.
Next, we do fentry on the prog.

Example:

    ... [<empty>]                 dummy

Signed-off-by: Leon Hwang <hffilwlqm@gmail.com>
  • Loading branch information
Asphaltt authored and brb committed Oct 23, 2023
1 parent 514bedf commit 2347755
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 17 deletions.
47 changes: 35 additions & 12 deletions bpf/kprobe_pwru.c
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ set_skb_btf(struct sk_buff *skb, typeof(print_skb_id) *event_id) {
}

static __always_inline void
set_output(struct pt_regs *ctx, struct sk_buff *skb, struct event_t *event) {
set_output(void *ctx, struct sk_buff *skb, struct event_t *event) {
if (cfg->output_meta) {
set_meta(skb, &event->meta);
}
Expand All @@ -268,9 +268,8 @@ set_output(struct pt_regs *ctx, struct sk_buff *skb, struct event_t *event) {
}
}

static __noinline int
handle_everything(struct sk_buff *skb, struct pt_regs *ctx, bool has_get_func_ip) {
struct event_t event = {};
static __noinline bool
handle_everything(struct sk_buff *skb, void *ctx, struct event_t *event) {
bool tracked = false;
u64 skb_addr = (u64) skb;

Expand All @@ -281,27 +280,37 @@ handle_everything(struct sk_buff *skb, struct pt_regs *ctx, bool has_get_func_ip
}

if (!filter(skb)) {
return 0;
return false;
}

cont:
set_output(ctx, skb, &event);
set_output(ctx, skb, event);
}

if (cfg->track_skb && !tracked) {
bpf_map_update_elem(&skb_addresses, &skb_addr, &TRUE, BPF_ANY);
}

event->pid = bpf_get_current_pid_tgid();
event->ts = bpf_ktime_get_ns();
event->cpu_id = bpf_get_smp_processor_id();

return true;
}

static __always_inline int
kprobe_skb(struct sk_buff *skb, struct pt_regs *ctx, bool has_get_func_ip) {
struct event_t event = {};

if (!handle_everything(skb, ctx, &event))
return BPF_OK;

event.skb_addr = (u64) skb;
event.pid = bpf_get_current_pid_tgid();
event.addr = has_get_func_ip ? bpf_get_func_ip(ctx) : PT_REGS_IP(ctx);
event.ts = bpf_ktime_get_ns();
event.cpu_id = bpf_get_smp_processor_id();
event.param_second = PT_REGS_PARM2(ctx);

bpf_map_push_elem(&events, &event, BPF_EXIST);

return 0;
return BPF_OK;
}

#ifdef HAS_KPROBE_MULTI
Expand All @@ -316,7 +325,7 @@ handle_everything(struct sk_buff *skb, struct pt_regs *ctx, bool has_get_func_ip
SEC(PWRU_KPROBE_TYPE "/skb-" #X) \
int kprobe_skb_##X(struct pt_regs *ctx) { \
struct sk_buff *skb = (struct sk_buff *) PT_REGS_PARM##X(ctx); \
return handle_everything(skb, ctx, PWRU_HAS_GET_FUNC_IP); \
return kprobe_skb(skb, ctx, PWRU_HAS_GET_FUNC_IP); \
}

PWRU_ADD_KPROBE(1)
Expand All @@ -338,4 +347,18 @@ int kprobe_skb_lifetime_termination(struct pt_regs *ctx) {
return 0;
}

SEC("fentry/tc")
int BPF_PROG(fentry_tc, struct sk_buff *skb) {
struct event_t event = {};

if (!handle_everything(skb, ctx, &event))
return BPF_OK;

event.skb_addr = (u64) skb;
event.addr = bpf_get_func_ip(ctx);
bpf_map_push_elem(&events, &event, BPF_EXIST);

return BPF_OK;
}

char __license[] SEC("license") = "Dual BSD/GPL";
135 changes: 135 additions & 0 deletions internal/pwru/bpf_prog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Leon Hwang.

package pwru

import (
"errors"
"fmt"
"log"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/link"
"golang.org/x/sys/unix"
)

func listBpfProgs(typ ebpf.ProgramType) ([]*ebpf.Program, error) {
var (
id ebpf.ProgramID
err error
)

var progs []*ebpf.Program
for id, err = ebpf.ProgramGetNextID(id); err == nil; id, err = ebpf.ProgramGetNextID(id) {
prog, err := ebpf.NewProgramFromID(id)
if err != nil {
return nil, err
}

if prog.Type() == typ {
progs = append(progs, prog)
} else {
_ = prog.Close()
}
}

if err != nil && !errors.Is(err, unix.ENOENT) {
return nil, err
}

return progs, nil
}

func getEntryFuncName(prog *ebpf.Program) (string, error) {
info, err := prog.Info()
if err != nil {
return "", fmt.Errorf("failed to get program info: %w", err)
}

id, ok := info.BTFID()
if !ok {
return "", fmt.Errorf("bpf program %s does not have BTF", info.Name)
}

handle, err := btf.NewHandleFromID(id)
if err != nil {
return "", fmt.Errorf("failed to get BTF handle: %w", err)
}
defer handle.Close()

spec, err := handle.Spec(nil)
if err != nil {
return "", fmt.Errorf("failed to get BTF spec: %w", err)
}

iter := spec.Iterate()
for iter.Next() {
if fn, ok := iter.Type.(*btf.Func); ok {
return fn.Name, nil
}
}

return "", fmt.Errorf("no function found in %s bpf prog", info.Name)
}

func TraceTC(prevColl *ebpf.Collection, spec *ebpf.CollectionSpec,
opts *ebpf.CollectionOptions, outputSkb bool,
) (func(), error) {
progs, err := listBpfProgs(ebpf.SchedCLS)
if err != nil {
log.Fatalf("Failed to list TC bpf progs: %v", err)
}

// Reusing maps from previous collection is to handle the events together
// with the kprobes.
replacedMaps := map[string]*ebpf.Map{
"events": prevColl.Maps["events"],
"print_stack_map": prevColl.Maps["print_stack_map"],
}
if outputSkb {
replacedMaps["print_skb_map"] = prevColl.Maps["print_skb_map"]
}
opts.MapReplacements = replacedMaps

tracings := make([]link.Link, 0, len(progs))
for _, prog := range progs {
entryFn, err := getEntryFuncName(prog)
if err != nil {
log.Fatalf("Failed to get entry function name: %v", err)
}
spec := spec.Copy()
spec.Programs["fentry_tc"].AttachTarget = prog
spec.Programs["fentry_tc"].AttachTo = entryFn
coll, err := ebpf.NewCollectionWithOptions(spec, *opts)
if err != nil {
var (
ve *ebpf.VerifierError
verifierLog string
)
if errors.As(err, &ve) {
verifierLog = fmt.Sprintf("Verifier error: %+v\n", ve)
}

log.Fatalf("Failed to load objects: %s\n%+v", verifierLog, err)
}
defer coll.Close()

tracing, err := link.AttachTracing(link.TracingOptions{
Program: coll.Programs["fentry_tc"],
})
if err != nil {
log.Fatalf("Failed to attach tracing: %v", err)
}
tracings = append(tracings, tracing)
}

return func() {
for _, tracing := range tracings {
_ = tracing.Close()
}
for _, prog := range progs {
_ = prog.Close()
}
}, nil
}
17 changes: 13 additions & 4 deletions internal/pwru/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"

Expand Down Expand Up @@ -42,8 +43,8 @@ type output struct {
}

func NewOutput(flags *Flags, printSkbMap *ebpf.Map, printStackMap *ebpf.Map,
addr2Name Addr2Name, kprobeMulti bool, btfSpec *btf.Spec) (*output, error) {

addr2Name Addr2Name, kprobeMulti bool, btfSpec *btf.Spec,
) (*output, error) {
writer := os.Stdout

if flags.OutputFile != "" {
Expand Down Expand Up @@ -131,6 +132,16 @@ func (o *output) Print(event *Event) {
funcName = fmt.Sprintf("0x%x", addr)
}

if strings.HasPrefix(funcName, "bpf_prog_") && strings.HasSuffix(funcName, "[bpf]") {
// The name of bpf prog is "bpf_prog_<id>_<name> [bpf]". We want to
// print only the name.
items := strings.Split(funcName, "_")
if len(items) > 3 {
funcName = strings.Join(items[3:], "_")
funcName = strings.TrimSpace(funcName[:len(funcName)-5])
}
}

outFuncName := funcName
if funcName == "kfree_skb_reason" {
if reason, ok := o.kfreeReasons[event.ParamSecond]; ok {
Expand Down Expand Up @@ -235,7 +246,6 @@ func getKFreeSKBReasons(spec *btf.Spec) (map[uint64]string, error) {
ret := map[uint64]string{}
for _, val := range dropReasonsEnum.Values {
ret[uint64(val.Value)] = val.Name

}

return ret, nil
Expand Down Expand Up @@ -293,7 +303,6 @@ func getIfaces() (map[uint64]map[uint32]string, error) {
}

return ifaceCache, err

}

func getIfacesInNetNs(path string) (map[uint32]string, error) {
Expand Down
2 changes: 2 additions & 0 deletions internal/pwru/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Flags struct {
FilterMark uint32
FilterFunc string
FilterTrackSkb bool
FilterTraceTc bool
FilterIfname string
FilterPcap string

Expand Down Expand Up @@ -56,6 +57,7 @@ func (f *Flags) SetFlags() {
flag.StringVar(&f.FilterNetns, "filter-netns", "", "filter netns (\"/proc/<pid>/ns/net\", \"inode:<inode>\")")
flag.Uint32Var(&f.FilterMark, "filter-mark", 0, "filter skb mark")
flag.BoolVar(&f.FilterTrackSkb, "filter-track-skb", false, "trace a packet even if it does not match given filters (e.g., after NAT or tunnel decapsulation)")
flag.BoolVar(&f.FilterTraceTc, "filter-trace-tc", false, "trace TC bpf progs")
flag.StringVar(&f.FilterIfname, "filter-ifname", "", "filter skb ifname in --filter-netns (if not specified, use current netns)")
flag.StringVar(&f.OutputTS, "timestamp", "none", "print timestamp per skb (\"current\", \"relative\", \"absolute\", \"none\")")
flag.BoolVar(&f.OutputMeta, "output-meta", false, "print skb metadata")
Expand Down
25 changes: 24 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ func main() {
if len(funcs) <= 0 {
log.Fatalf("Cannot find a matching kernel function")
}
addr2name, err := pwru.GetAddrs(funcs, flags.OutputStack || len(flags.KMods) != 0)
// If --filter-trace-tc, it's to retrieve and print bpf prog's name.
addr2name, err := pwru.GetAddrs(funcs, flags.OutputStack ||
len(flags.KMods) != 0 || flags.FilterTraceTc)
if err != nil {
log.Fatalf("Failed to get function addrs: %s", err)
}
Expand Down Expand Up @@ -137,6 +139,19 @@ func main() {
log.Fatalf("Failed to rewrite config: %v", err)
}

// As we know, for every fentry tracing program, there is a corresponding
// bpf prog spec with attaching target and attaching function. So, we can
// just copy the spec and keep the fentry_tc program spec only in the copied
// spec.
bpfSpecFentry := bpfSpec.Copy()
bpfSpecFentry.Programs = map[string]*ebpf.ProgramSpec{
"fentry_tc": bpfSpec.Programs["fentry_tc"],
}

// fentry_tc is not used in the kprobe/kprobe-multi cases. So, it should be
// deleted from the spec.
delete(bpfSpec.Programs, "fentry_tc")

coll, err := ebpf.NewCollectionWithOptions(bpfSpec, opts)
if err != nil {
var (
Expand All @@ -162,6 +177,14 @@ func main() {
printStackMap := coll.Maps["print_stack_map"]
printSkbMap := coll.Maps["print_skb_map"]

if flags.FilterTraceTc {
close, err := pwru.TraceTC(coll, bpfSpecFentry, &opts, flags.OutputSkb)
if err != nil {
log.Fatalf("Failed to trace TC: %v", err)
}
defer close()
}

var kprobes []link.Link
defer func() {
select {
Expand Down

0 comments on commit 2347755

Please sign in to comment.