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 L2 filters such as ARP #255

Merged
merged 4 commits into from
Sep 25, 2023
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
37 changes: 28 additions & 9 deletions bpf/kprobe_pwru.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,40 @@ filter_meta(struct sk_buff *skb) {
}

static __noinline bool
filter_pcap_ebpf(void *_skb, void *__skb, void *___skb, void *data, void* data_end)
filter_pcap_ebpf_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(struct sk_buff *skb) {
BPF_CORE_READ(skb, head);
filter_pcap_l3(struct sk_buff *skb)
{
void *skb_head = BPF_CORE_READ(skb, head);
u16 l3_off = BPF_CORE_READ(skb, network_header);
void *data = skb_head + l3_off;
u16 l4_off = BPF_CORE_READ(skb, transport_header);
u16 len = BPF_CORE_READ(skb, len);
void *data_end = skb_head + l4_off + len;
return filter_pcap_ebpf((void *)skb, (void *)skb, (void *)skb, data, data_end);
void *data = skb_head + BPF_CORE_READ(skb, network_header);
void *data_end = skb_head + BPF_CORE_READ(skb, tail);
return filter_pcap_ebpf_l3((void *)skb, (void *)skb, (void *)skb, data, data_end);
}

static __noinline bool
filter_pcap_ebpf_l2(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_l2(struct sk_buff *skb)
{
void *skb_head = BPF_CORE_READ(skb, head);
void *data = skb_head + BPF_CORE_READ(skb, mac_header);
void *data_end = skb_head + BPF_CORE_READ(skb, tail);
return filter_pcap_ebpf_l2((void *)skb, (void *)skb, (void *)skb, data, data_end);
}

static __always_inline bool
filter_pcap(struct sk_buff *skb) {
if (BPF_CORE_READ(skb, mac_len) == 0)
return filter_pcap_l3(skb);
return filter_pcap_l2(skb);
}

static __always_inline bool
Expand Down
29 changes: 13 additions & 16 deletions internal/libpcap/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,15 @@ const (
AvailableOffset
)

func CompileCbpf(expr string) (insts []bpf.Instruction, err error) {
func CompileCbpf(expr string, l3 bool) (insts []bpf.Instruction, err error) {
if len(expr) == 0 {
return
}

/*
DLT_RAW linktype tells pcap_compile() to generate cbpf instructions for
skb without link layer. This is because kernel doesn't supply L2 data
for many of functions, where skb->mac_len == 0, while the default
pcap_compile mode only works for a complete frame data.
*/
pcap := C.pcap_open_dead(C.DLT_RAW, MAXIMUM_SNAPLEN)
pcap := C.pcap_open_dead(C.DLT_EN10MB, MAXIMUM_SNAPLEN)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: does the Go compiler optimize this statement? I.e., pcap := C.pcap_open_dead(C.DLT_EN10MB, MAXIMUM_SNAPLEN) is called only if l3=false?

if l3 {
pcap = C.pcap_open_dead(C.DLT_RAW, MAXIMUM_SNAPLEN)
}
if pcap == nil {
return nil, fmt.Errorf("failed to pcap_open_dead: %+v\n", C.PCAP_ERROR)
}
Expand All @@ -59,7 +56,7 @@ func CompileCbpf(expr string) (insts []bpf.Instruction, err error) {

var bpfProg pcapBpfProgram
if C.pcap_compile(pcap, (*C.struct_bpf_program)(&bpfProg), cexpr, 1, C.PCAP_NETMASK_UNKNOWN) < 0 {
return nil, fmt.Errorf("failed to pcap_compile '%s': %+v", expr, C.GoString(C.pcap_geterr(pcap)))
return nil, fmt.Errorf("failed to pcap_compile '%s': %+v (l3=%+v)", expr, C.GoString(C.pcap_geterr(pcap)), l3)
}
defer C.pcap_freecode((*C.struct_bpf_program)(&bpfProg))

Expand All @@ -85,8 +82,8 @@ end of the packet data. As we mentioned in the comment of DLT_RAW,
packet data starts from L3 network header, rather than L2 ethernet
header, caller should make sure to pass the correct arguments.
*/
func CompileEbpf(expr string, opts cbpfc.EBPFOpts) (insts asm.Instructions, err error) {
cbpfInsts, err := CompileCbpf(expr)
func CompileEbpf(expr string, opts cbpfc.EBPFOpts, l3 bool) (insts asm.Instructions, err error) {
cbpfInsts, err := CompileCbpf(expr, l3)
if err != nil {
return
}
Expand Down Expand Up @@ -181,11 +178,11 @@ func adjustEbpf(insts asm.Instructions, opts cbpfc.EBPFOpts) (newInsts asm.Instr
}, insts...)

insts = append(insts,
asm.Mov.Imm(asm.R1, 0).WithSymbol("result"), // r1 = 0 (_skb)
asm.Mov.Imm(asm.R2, 0), // r2 = 0 (__skb)
asm.Mov.Imm(asm.R3, 0), // r3 = 0 (___skb)
asm.Mov.Reg(asm.R4, opts.Result), // r4 = $result (data)
asm.Mov.Imm(asm.R5, 0), // r5 = 0 (data_end)
asm.Mov.Imm(asm.R1, 0).WithSymbol(opts.ResultLabel), // r1 = 0 (_skb)
asm.Mov.Imm(asm.R2, 0), // r2 = 0 (__skb)
asm.Mov.Imm(asm.R3, 0), // r3 = 0 (___skb)
asm.Mov.Reg(asm.R4, opts.Result), // r4 = $result (data)
asm.Mov.Imm(asm.R5, 0), // r5 = 0 (data_end)
)

return insts, nil
Expand Down
29 changes: 24 additions & 5 deletions internal/libpcap/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,39 @@ package libpcap

import (
"errors"
"fmt"

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

func InjectFilter(program *ebpf.ProgramSpec, filterExpr string) (err error) {
func InjectFilters(program *ebpf.ProgramSpec, filterExpr string) (err error) {
if err = injectFilter(program, filterExpr, false); err != nil {
return
}
if err = injectFilter(program, filterExpr, true); 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.
fmt.Printf("L3 filter injection failed while L2 filter injection succeeded, injecting a deny-all L3 filter: %+v\n", err)
return injectFilter(program, "src host 0.0.0.0 and dst host 0.0.0.0 and tcp", true)
}
return
}

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

suffix := "_l2"
if l3 {
suffix = "_l3"
}
injectIdx := -1
for idx, inst := range program.Instructions {
if inst.Symbol() == "filter_pcap_ebpf" {
if inst.Symbol() == "filter_pcap_ebpf"+suffix {
injectIdx = idx
break
}
Expand All @@ -31,12 +50,12 @@ func InjectFilter(program *ebpf.ProgramSpec, filterExpr string) (err error) {
PacketStart: asm.R4,
PacketEnd: asm.R5,
Result: asm.R0,
ResultLabel: "result",
ResultLabel: "result" + suffix,
// R0-R3 are also safe to use thanks to the placeholder parameters _skb, __skb, ___skb.
Working: [4]asm.Register{asm.R0, asm.R1, asm.R2, asm.R3},
LabelPrefix: "filter",
LabelPrefix: "filter" + suffix,
StackOffset: -int(AvailableOffset),
})
}, l3)
if err != nil {
return
}
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func main() {
if name == "kprobe_skb_lifetime_termination" {
continue
}
if err = libpcap.InjectFilter(program, flags.FilterPcap); err != nil {
if err = libpcap.InjectFilters(program, flags.FilterPcap); err != nil {
log.Fatalf("Failed to inject filter ebpf for %s: %v", name, err)
}
}
Expand Down