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.
And provide prog ID to bpf spec in order to output prog name while
printing event data. And cache prog ID => prog info BTW. Next, we do
fentry on the prog.

Example:

    ... [<empty>]                 dummy@TC

Signed-off-by: Leon Hwang <[email protected]>
  • Loading branch information
Asphaltt committed Oct 19, 2023
1 parent eee498c commit 0cf6b33
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 18 deletions.
56 changes: 44 additions & 12 deletions bpf/kprobe_pwru.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ struct tuple {

u64 print_skb_id = 0;

enum event_type {
EVENT_TYPE_KPROBE,
EVENT_TYPE_TC,
};

struct event_t {
u32 pid;
u32 type;
Expand Down Expand Up @@ -90,6 +95,8 @@ struct config {
static volatile const struct config CFG;
#define cfg (&CFG)

static volatile const u32 BPF_PROG_ID;

#define MAX_STACK_DEPTH 50
struct {
__uint(type, BPF_MAP_TYPE_STACK_TRACE);
Expand Down Expand Up @@ -250,7 +257,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 +275,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 +287,38 @@ 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.type = EVENT_TYPE_KPROBE;
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 +333,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 +355,19 @@ 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.type = EVENT_TYPE_TC;
event.skb_addr = (u64) skb;
event.addr = BPF_PROG_ID;
bpf_map_push_elem(&events, &event, BPF_EXIST);

return BPF_OK;
}

char __license[] SEC("license") = "Dual BSD/GPL";
152 changes: 152 additions & 0 deletions internal/pwru/bpf_prog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// 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,
progsInfo map[uint32]*ebpf.ProgramInfo,
) (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 {
info, err := prog.Info()
if err != nil {
log.Fatalf("Failed to get program info: %v", err)
}

id, ok := info.ID()
if !ok {
log.Fatalf("Failed to get program ID")
}
progsInfo[uint32(id)] = info

entryFn, err := getEntryFuncName(prog)
if err != nil {
log.Fatalf("Failed to get entry function name: %v", err)
}
spec := spec.Copy()
if err := spec.RewriteConstants(map[string]interface{}{
"BPF_PROG_ID": uint32(id),
}); err != nil {
log.Fatalf("Failed to rewrite BPF_PROG_ID: %v", err)
}
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 @@ -39,11 +39,13 @@ type output struct {
kprobeMulti bool
kfreeReasons map[uint64]string
ifaceCache map[uint64]map[uint32]string
progsInfo map[uint32]*ebpf.ProgramInfo
}

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,
info map[uint32]*ebpf.ProgramInfo,
) (*output, error) {
writer := os.Stdout

if flags.OutputFile != "" {
Expand Down Expand Up @@ -77,6 +79,7 @@ func NewOutput(flags *Flags, printSkbMap *ebpf.Map, printStackMap *ebpf.Map,
kprobeMulti: kprobeMulti,
kfreeReasons: reasons,
ifaceCache: ifs,
progsInfo: info,
}, nil
}

Expand Down Expand Up @@ -127,6 +130,14 @@ func (o *output) Print(event *Event) {
// See https://lore.kernel.org/bpf/[email protected]/
// for more ctx.
funcName = ksym.name
} else if event.Type == EventTypeTc {
progID := uint32(event.Addr)
info, ok := o.progsInfo[progID]
if ok {
funcName = fmt.Sprintf("%s@TC", info.Name)
} else {
funcName = fmt.Sprintf("ID(%d)@TC", progID)
}
} else {
funcName = fmt.Sprintf("0x%x", addr)
}
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
11 changes: 10 additions & 1 deletion 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 Expand Up @@ -111,7 +113,7 @@ type StackData struct {

type Event struct {
PID uint32
Type uint32
Type EventType
Addr uint64
SAddr uint64
Timestamp uint64
Expand All @@ -122,3 +124,10 @@ type Event struct {
ParamSecond uint64
CPU uint32
}

type EventType uint32

const (
EventTypeKprobe EventType = iota
EventTypeTc
)
Loading

0 comments on commit 0cf6b33

Please sign in to comment.