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

Support outputting and filtering by vxlan/geneve tunnel data #494

Merged
merged 2 commits into from
Mar 27, 2025
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
59 changes: 58 additions & 1 deletion bpf/kprobe_pwru.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ struct event_t {
u64 print_shinfo_id;
struct skb_meta meta;
struct tuple tuple;
struct tuple tunnel_tuple;
s64 print_stack_id;
u64 param_second;
u64 param_third;
Expand Down Expand Up @@ -146,7 +147,7 @@ struct config {
u8 output_stack: 1;
u8 output_caller: 1;
u8 output_cb: 1;
u8 output_unused: 1;
u8 output_tunnel: 1;
u8 is_set: 1;
u8 track_skb: 1;
u8 track_skb_by_stackid: 1;
Expand Down Expand Up @@ -259,8 +260,47 @@ filter_pcap_l2(struct sk_buff *skb)
return filter_pcap_ebpf_l2((void *)skb, (void *)skb, (void *)skb, data, data_end);
}

static __noinline bool
filter_pcap_ebpf_tunnel_l2(void *_skb, void *__skb, void *___skb, void *data, void* data_end)
{
return data != data_end && _skb == __skb && __skb == ___skb;
}

static __noinline bool
filter_pcap_ebpf_tunnel_l3(void *_skb, void *__skb, void *___skb, void *data, void* data_end)
{
return data != data_end && _skb == __skb && __skb == ___skb;
}

static __always_inline bool
filter_pcap_tunnel_l2(struct sk_buff *skb)
{
u16 tunnel_l3_off = BPF_CORE_READ(skb, inner_network_header);
// If we don't have a tunnel network header, assume its not tunnel traffic.
// We'll let the injected pcap program handle figuring out the transport
// headers.
// Return true as the tunnel pcap should only filter on tunnel traffic.
if (tunnel_l3_off == 0)
return true;

void *skb_head = BPF_CORE_READ(skb, head);
void *data = skb_head;
void *data_end = skb_head + BPF_CORE_READ(skb, tail);

if (!filter_pcap_ebpf_tunnel_l2((void *)skb, (void *)skb, (void *)skb,
data + BPF_CORE_READ(skb, inner_mac_header), data_end)) {
return false;
}

return filter_pcap_ebpf_tunnel_l3((void *)skb, (void *)skb, (void *)skb,
data + BPF_CORE_READ(skb, inner_network_header), data_end);
}

static __always_inline bool
filter_pcap(struct sk_buff *skb) {
if (!filter_pcap_tunnel_l2(skb)) {
return false;
}
if (BPF_CORE_READ(skb, mac_len) == 0)
return filter_pcap_l3(skb);
return filter_pcap_l2(skb);
Expand Down Expand Up @@ -336,6 +376,19 @@ set_tuple(struct sk_buff *skb, struct tuple *tpl) {
__set_tuple(tpl, skb_head, l3_off, is_ipv4);
}

static __always_inline void
set_tunnel(struct sk_buff *skb, struct tuple *tpl, struct tuple *tunnel_tpl) {
u16 tunnel_l3_off = BPF_CORE_READ(skb, inner_network_header);
// If we don't have a tunnel network header, assume its not tunnel traffic.
if (tunnel_l3_off == 0)
return;
void *skb_head = BPF_CORE_READ(skb, head);
struct iphdr *l3_hdr = (struct iphdr *) (skb_head + tunnel_l3_off);
u8 ip_vsn = BPF_CORE_READ_BITFIELD_PROBED(l3_hdr, version);
bool is_ipv4 = ip_vsn == 4;
__set_tuple(tunnel_tpl, skb_head, tunnel_l3_off, is_ipv4);
}

static __always_inline u64
sync_fetch_and_add(void *id_map) {
u32 *id = bpf_map_lookup_elem(id_map, &ZERO);
Expand Down Expand Up @@ -436,6 +489,10 @@ set_output(void *ctx, struct sk_buff *skb, struct event_t *event) {
set_tuple(skb, &event->tuple);
}

if (cfg->output_tunnel) {
set_tunnel(skb, &event->tuple, &event->tunnel_tuple);
}

if (cfg->output_skb) {
set_skb_btf(skb, &event->print_skb_id);
}
Expand Down
35 changes: 26 additions & 9 deletions internal/libpcap/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,54 @@ package libpcap

import (
"errors"
"fmt"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cloudflare/cbpfc"
)

func InjectL2TunnelFilter(program *ebpf.ProgramSpec, filterExpr, l2TunnelFilterExpr string) (err error) {
return injectFilter(program, filterExpr, false, true)
}

func InjectL2Filter(program *ebpf.ProgramSpec, filterExpr string) (err error) {
return injectFilter(program, filterExpr, false)
return injectFilter(program, filterExpr, false, false)
}

func InjectFilters(program *ebpf.ProgramSpec, filterExpr string) (err error) {
if err = injectFilter(program, filterExpr, false); err != nil {
func InjectFilters(program *ebpf.ProgramSpec, filterExpr, tunnelFilterExprL2, tunnelFilterExprL3 string) (err error) {
if err = injectFilter(program, filterExpr, false, false); err != nil {
return
}
if err = injectFilter(program, filterExpr, true); err != nil {
if err = injectFilter(program, filterExpr, true, false); err != nil {
// This could happen for l2 only filters such as "arp". In this
// case we don't want to exit with an error, but instead inject
// a deny-all filter to reject all l3 skbs.
return injectFilter(program, "__pwru_reject_all__", true)
return injectFilter(program, "__pwru_reject_all__", true, false)
}
// Attach any tunnel filters.
if err := injectFilter(program, tunnelFilterExprL2, false, true); err != nil {
return fmt.Errorf("l2 tunnel filter: %w", err)
}
if err := injectFilter(program, tunnelFilterExprL3, true, true); err != nil {
return fmt.Errorf("l3 tunnel filter: %w", err)
}
return
return nil
}

func injectFilter(program *ebpf.ProgramSpec, filterExpr string, l3 bool) (err error) {
func injectFilter(program *ebpf.ProgramSpec, filterExpr string, l3 bool, tunnel bool) (err error) {
if filterExpr == "" {
return
}

suffix := "_l2"
tunnelSuffix := ""
if tunnel {
tunnelSuffix = "_tunnel"
}

suffix := tunnelSuffix + "_l2"
if l3 {
suffix = "_l3"
suffix = tunnelSuffix + "_l3"
}
injectIdx := -1
for idx, inst := range program.Instructions {
Expand Down
4 changes: 4 additions & 0 deletions internal/pwru/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
OutputStackMask
OutputCallerMask
OutputCbMask
OutputTunnelMask
)

const (
Expand Down Expand Up @@ -66,6 +67,9 @@ func GetConfig(flags *Flags) (cfg FilterCfg, err error) {
if flags.OutputTuple {
cfg.OutputFlags |= OutputTupleMask
}
if flags.OutputTunnel {
cfg.OutputFlags |= OutputTunnelMask
}
if flags.OutputStack {
cfg.OutputFlags |= OutputStackMask
}
Expand Down
34 changes: 28 additions & 6 deletions internal/pwru/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type jsonPrinter struct {
Len uint32 `json:"len,omitempty"`
Cb [5]uint32 `json:"cb,omitempty"`
Tuple *jsonTuple `json:"tuple,omitempty"`
TunnelTuple *jsonTuple `json:"tunnel_tuple,omitempty"`
Stack interface{} `json:"stack,omitempty"`
SkbMetadata interface{} `json:"skb_metadata,omitempty"`
}
Expand Down Expand Up @@ -155,6 +156,9 @@ func (o *output) PrintHeader() {
if o.flags.OutputCaller {
fmt.Fprintf(o.writer, " %s", "CALLER")
}
if o.flags.OutputTunnel {
fmt.Fprintf(o.writer, " %s", "TUNNEL")
}
fmt.Fprintf(o.writer, "\n")
}

Expand Down Expand Up @@ -209,6 +213,16 @@ func (o *output) PrintJson(event *Event) {
d.Tuple = t
}

if o.flags.OutputTuple {
t := &jsonTuple{}
t.Saddr = addrToStr(event.TunnelTuple.L3Proto, event.TunnelTuple.Saddr)
t.Daddr = addrToStr(event.TunnelTuple.L3Proto, event.TunnelTuple.Daddr)
t.Sport = byteorder.NetworkToHost16(event.TunnelTuple.Sport)
t.Dport = byteorder.NetworkToHost16(event.TunnelTuple.Dport)
t.Proto = event.TunnelTuple.L4Proto
d.TunnelTuple = t
}

if o.flags.OutputStack && event.PrintStackId > 0 {
d.Stack = getStackData(event, o)
}
Expand Down Expand Up @@ -273,20 +287,24 @@ func getAddrByArch(event *Event, o *output) (addr uint64) {
return addr
}

func getTupleData(event *Event, outputTCPFlags bool) (tupleData string) {
func getTuple(tpl Tuple, outputTCPFlags bool) (tupleData string) {
var l4Info string
if event.Tuple.L4Proto == syscall.IPPROTO_TCP && event.Tuple.TCPFlag != 0 && outputTCPFlags {
l4Info = fmt.Sprintf("%s:%s", protoToStr(event.Tuple.L4Proto), event.Tuple.TCPFlag)
if tpl.L4Proto == syscall.IPPROTO_TCP && tpl.TCPFlag != 0 && outputTCPFlags {
l4Info = fmt.Sprintf("%s:%s", protoToStr(tpl.L4Proto), tpl.TCPFlag)
} else {
l4Info = protoToStr(event.Tuple.L4Proto)
l4Info = protoToStr(tpl.L4Proto)
}
tupleData = fmt.Sprintf("%s:%d->%s:%d(%s)",
addrToStr(event.Tuple.L3Proto, event.Tuple.Saddr), byteorder.NetworkToHost16(event.Tuple.Sport),
addrToStr(event.Tuple.L3Proto, event.Tuple.Daddr), byteorder.NetworkToHost16(event.Tuple.Dport),
addrToStr(tpl.L3Proto, tpl.Saddr), byteorder.NetworkToHost16(tpl.Sport),
addrToStr(tpl.L3Proto, tpl.Daddr), byteorder.NetworkToHost16(tpl.Dport),
l4Info)
return tupleData
}

func getTupleData(event *Event, outputTCPFlags bool) (tupleData string) {
return getTuple(event.Tuple, outputTCPFlags)
}

func getStackData(event *Event, o *output) (stackData string) {
var stack StackData
id := uint32(event.PrintStackId)
Expand Down Expand Up @@ -462,6 +480,10 @@ func (o *output) Print(event *Event) {
fmt.Fprintf(o.writer, "%s", getShinfoData(event, o))
}

if o.flags.OutputTunnel {
fmt.Fprintf(o.writer, " %s", getTuple(event.TunnelTuple, o.flags.OutputTCPFlags))
}

fmt.Fprintln(o.writer)
}

Expand Down
7 changes: 7 additions & 0 deletions internal/pwru/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type Flags struct {
FilterTrackBpfHelpers bool
FilterIfname string
FilterPcap string
FilterTunnelPcapL2 string
FilterTunnelPcapL3 string
FilterKprobeBatch uint

OutputTS string
Expand All @@ -52,6 +54,7 @@ type Flags struct {
OutputFile string
OutputJson bool
OutputTCPFlags bool
OutputTunnel bool

KMods []string
AllKMods bool
Expand All @@ -74,6 +77,8 @@ func (f *Flags) SetFlags() {
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.FilterTrackSkbByStackid, "filter-track-skb-by-stackid", false, "trace a packet even after it is kfreed (e.g., traffic going through bridge)")
flag.BoolVar(&f.FilterTraceTc, "filter-trace-tc", false, "trace TC bpf progs")
flag.StringVar(&f.FilterTunnelPcapL2, "filter-tunnel-pcap-l2", "", "pcap expression for vxlan/geneve tunnel (l2)")
flag.StringVar(&f.FilterTunnelPcapL3, "filter-tunnel-pcap-l3", "", "pcap expression for vxlan/geneve tunnel (l3)")
flag.BoolVar(&f.FilterTraceXdp, "filter-trace-xdp", false, "trace XDP bpf progs")
flag.BoolVar(&f.FilterTrackBpfHelpers, "filter-track-bpf-helpers", false, "trace BPF helper functions")
flag.StringVar(&f.FilterIfname, "filter-ifname", "", "filter skb ifname in --filter-netns (if not specified, use current netns)")
Expand All @@ -83,6 +88,7 @@ func (f *Flags) SetFlags() {
flag.BoolVar(&f.OutputTuple, "output-tuple", true, "print L4 tuple")
flag.BoolVar(&f.OutputSkb, "output-skb", false, "print skb")
flag.BoolVar(&f.OutputShinfo, "output-skb-shared-info", false, "print skb shared info")
flag.BoolVar(&f.OutputTunnel, "output-tunnel", false, "print encapsulated tunnel header data")
flag.BoolVar(&f.OutputStack, "output-stack", false, "print stack")
flag.BoolVar(&f.OutputCaller, "output-caller", false, "print caller function name")
flag.Uint64Var(&f.OutputLimitLines, "output-limit-lines", 0, "exit the program after the number of events has been received/printed")
Expand Down Expand Up @@ -178,6 +184,7 @@ type Event struct {
PrintShinfoId uint64
Meta Meta
Tuple Tuple
TunnelTuple Tuple
PrintStackId int64
ParamSecond uint64
ParamThird uint64
Expand Down
5 changes: 4 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,10 @@ func main() {
}
continue
}
if err = libpcap.InjectFilters(program, flags.FilterPcap); err != nil {
if err = libpcap.InjectFilters(program,
flags.FilterPcap,
flags.FilterTunnelPcapL2,
flags.FilterTunnelPcapL3); err != nil {
log.Fatalf("Failed to inject filter ebpf for %s: %v", name, err)
}
}
Expand Down
Loading