diff --git a/cmd/xdpcap/filter.go b/cmd/xdpcap/filter.go index 6fa9e89..9b9f2bc 100644 --- a/cmd/xdpcap/filter.go +++ b/cmd/xdpcap/filter.go @@ -1,12 +1,16 @@ package main import ( + "fmt" + "os" + "github.com/cloudflare/xdpcap/internal" "github.com/cilium/ebpf" "github.com/cilium/ebpf/perf" "github.com/pkg/errors" "golang.org/x/net/bpf" + "golang.org/x/sys/unix" ) type packet struct { @@ -85,13 +89,32 @@ func newFilterWithMap(hookMap *ebpf.Map, opts filterOpts) (*filter, error) { actions: opts.actions, } - for _, action := range opts.actions { - program, err := newProgram(opts.filter, action, perfMap) + xdpFragsMode := false + for i, action := range opts.actions { + program, err := newProgram(opts.filter, action, perfMap, xdpFragsMode) if err != nil { return nil, errors.Wrapf(err, "loading filter program for %v", action) } err = attachProg(hookMap, program.program.FD(), action) + if errors.Is(err, unix.EINVAL) && i == 0 { + // attempt to load first action in XDP frags mode and retry attaching + // if this doesn't work then there is underlying issue that is not + // related to the hook being attached in XDP frags mode and we make + // sure to return the original attachment error. + xdpFragsMode = true + + var programErr error + if program, programErr = newProgram(opts.filter, action, perfMap, xdpFragsMode); programErr != nil { + return nil, errors.Wrapf(programErr, "loading filter program in XDP frags mode for %v", action) + } + + if attachProg(hookMap, program.program.FD(), action) == nil { + fmt.Fprintf(os.Stderr, "attaching filter actions in XDP frags mode\n") + err = nil + } + } + if err != nil { // close and detach any previously successfully attached programs, but not this one filter.close() diff --git a/cmd/xdpcap/filter_test.go b/cmd/xdpcap/filter_test.go index 11d52c5..0e93285 100644 --- a/cmd/xdpcap/filter_test.go +++ b/cmd/xdpcap/filter_test.go @@ -8,7 +8,9 @@ import ( "github.com/cloudflare/xdpcap/internal" "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" "golang.org/x/net/bpf" + "golang.org/x/sys/unix" ) func testOpts(filter ...bpf.Instruction) filterOpts { @@ -44,6 +46,57 @@ func TestMissingFilter(t *testing.T) { } } +func TestFilterProgramForAllModes(t *testing.T) { + testFunc := func(xdpFragsEnabled bool) func(t *testing.T) { + return func(t *testing.T) { + hookMapSpec := internal.HookMapSpec.Copy() + hookMapSpec.Name = "test_mode_hook_map" + hookMap, err := ebpf.NewMap(hookMapSpec) + if err != nil { + t.Fatal(err) + } + + entrrypointSpec := &ebpf.ProgramSpec{ + Type: ebpf.XDP, + Instructions: asm.Instructions{ + // Context is already in R1. + asm.LoadMapPtr(asm.R2, hookMap.FD()), + asm.Mov.Imm(asm.R3, int32(xdpPass)), + asm.FnTailCall.Call(), + asm.Mov.Imm(asm.R0, int32(xdpPass)), + asm.Return(), + }, + } + + if xdpFragsEnabled { + entrrypointSpec.Flags = unix.BPF_F_XDP_HAS_FRAGS + } + + entrypoint, err := ebpf.NewProgram(entrrypointSpec) + if err != nil { + t.Fatal(err) + } + + _, err = entrypoint.Run(&ebpf.RunOptions{}) + if err != nil { + t.Fatal(err) + } + defer entrypoint.Close() + + opts := testOpts(bpf.RetConstant{Val: 0}) + opts.actions = []xdpAction{xdpPass} + filter, err := newFilterWithMap(hookMap, opts) + if err != nil { + t.Fatal(err) + } + filter.close() + } + } + + t.Run("linear_buffer", testFunc(false)) + t.Run("xdp_frags", testFunc(true)) +} + func TestUnknownAction(t *testing.T) { // progs with actions from 0-9. Only 0-3 are used currently. opts := testOpts(bpf.RetConstant{0}) diff --git a/cmd/xdpcap/program.go b/cmd/xdpcap/program.go index 7c92dc4..187d851 100644 --- a/cmd/xdpcap/program.go +++ b/cmd/xdpcap/program.go @@ -6,6 +6,7 @@ import ( "github.com/cloudflare/cbpfc" "github.com/pkg/errors" "golang.org/x/net/bpf" + "golang.org/x/sys/unix" ) const BPF_F_CURRENT_CPU int64 = 0xFFFFFFFF @@ -42,7 +43,7 @@ type program struct { } // newProgram builds an eBPF program that copies packets matching a cBPF program to userspace via perf -func newProgram(filter []bpf.Instruction, action xdpAction, perfMap *ebpf.Map) (*program, error) { +func newProgram(filter []bpf.Instruction, action xdpAction, perfMap *ebpf.Map, xdpFragsMode bool) (*program, error) { metricsMap, err := ebpf.NewMap(&metricsSpec) if err != nil { return nil, errors.Wrap(err, "creating metrics map") @@ -161,14 +162,17 @@ func newProgram(filter []bpf.Instruction, action xdpAction, perfMap *ebpf.Map) ( asm.Return(), ) - prog, err := ebpf.NewProgram( - &ebpf.ProgramSpec{ - Name: "xdpcap_filter", - Type: ebpf.XDP, - Instructions: insns, - License: "GPL", - }, - ) + progSpec := &ebpf.ProgramSpec{ + Name: "xdpcap_filter", + Type: ebpf.XDP, + Instructions: insns, + License: "GPL", + } + if xdpFragsMode { + progSpec.Flags = progSpec.Flags | unix.BPF_F_XDP_HAS_FRAGS + } + + prog, err := ebpf.NewProgram(progSpec) if err != nil { return nil, errors.Wrap(err, "loading filter") }