From 2347755900fffe100ab9eebaa180bb9f482df6fb Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Tue, 17 Oct 2023 21:43:45 +0800 Subject: [PATCH] Support tracing tc-bpf 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: ... [] dummy Signed-off-by: Leon Hwang --- bpf/kprobe_pwru.c | 47 +++++++++---- internal/pwru/bpf_prog.go | 135 ++++++++++++++++++++++++++++++++++++++ internal/pwru/output.go | 17 +++-- internal/pwru/types.go | 2 + main.go | 25 ++++++- 5 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 internal/pwru/bpf_prog.go diff --git a/bpf/kprobe_pwru.c b/bpf/kprobe_pwru.c index dfb943a1..355d19f8 100644 --- a/bpf/kprobe_pwru.c +++ b/bpf/kprobe_pwru.c @@ -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); } @@ -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; @@ -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 @@ -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) @@ -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"; diff --git a/internal/pwru/bpf_prog.go b/internal/pwru/bpf_prog.go new file mode 100644 index 00000000..8813614f --- /dev/null +++ b/internal/pwru/bpf_prog.go @@ -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 +} diff --git a/internal/pwru/output.go b/internal/pwru/output.go index 13102a67..d6ea8873 100644 --- a/internal/pwru/output.go +++ b/internal/pwru/output.go @@ -14,6 +14,7 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "syscall" "time" @@ -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 != "" { @@ -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__ [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 { @@ -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 @@ -293,7 +303,6 @@ func getIfaces() (map[uint64]map[uint32]string, error) { } return ifaceCache, err - } func getIfacesInNetNs(path string) (map[uint32]string, error) { diff --git a/internal/pwru/types.go b/internal/pwru/types.go index 384424b0..d448e4ff 100644 --- a/internal/pwru/types.go +++ b/internal/pwru/types.go @@ -28,6 +28,7 @@ type Flags struct { FilterMark uint32 FilterFunc string FilterTrackSkb bool + FilterTraceTc bool FilterIfname string FilterPcap string @@ -56,6 +57,7 @@ func (f *Flags) SetFlags() { flag.StringVar(&f.FilterNetns, "filter-netns", "", "filter netns (\"/proc//ns/net\", \"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") diff --git a/main.go b/main.go index b1dd2ef7..e527bf5a 100644 --- a/main.go +++ b/main.go @@ -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) } @@ -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 ( @@ -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 {