Skip to content
Open
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ with the `BPF_F_XDP_HAS_FRAGS` flag.
* Display captured packets:
`sudo xdpcap /path/to/pinned/map - "tcp and port 80" | sudo tcpdump -r -`

if you wish to see the verifier logs in cases where the program fails the checks
you can use the `-print-verifier-logs` flag. You can control the size of the buffer
for the verifier logs using the flag `-verifier-log-size`.


## Limitations

Expand Down
11 changes: 5 additions & 6 deletions cmd/xdpcap/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ type filter struct {
}

// newFilter creates a filter from a tcpdump / libpcap filter expression
func newFilter(hookMapPath string, opts filterOpts) (*filter, error) {
func newFilter(hookMapPath string, opts filterOpts, programOpts ebpf.ProgramOptions) (*filter, error) {
hookMap, err := ebpf.LoadPinnedMap(hookMapPath, nil)
if err != nil {
return nil, errors.Wrapf(err, "loading hook map")
}

return newFilterWithMap(hookMap, opts)
return newFilterWithMap(hookMap, opts, programOpts)
}

// newFilterWithMap creates a filter from a tcpdump / libpcap filter expression
func newFilterWithMap(hookMap *ebpf.Map, opts filterOpts) (*filter, error) {
func newFilterWithMap(hookMap *ebpf.Map, opts filterOpts, programOpts ebpf.ProgramOptions) (*filter, error) {
if len(opts.filter) == 0 {
return nil, errors.New("at least one filter cBPF instruction required")
}
Expand Down Expand Up @@ -91,7 +91,7 @@ func newFilterWithMap(hookMap *ebpf.Map, opts filterOpts) (*filter, error) {

xdpFragsMode := false
for i, action := range opts.actions {
program, err := newProgram(opts.filter, action, perfMap, xdpFragsMode)
program, err := newProgram(opts.filter, action, perfMap, xdpFragsMode, programOpts)
if err != nil {
return nil, errors.Wrapf(err, "loading filter program for %v", action)
}
Expand All @@ -105,7 +105,7 @@ func newFilterWithMap(hookMap *ebpf.Map, opts filterOpts) (*filter, error) {
xdpFragsMode = true

var programErr error
if program, programErr = newProgram(opts.filter, action, perfMap, xdpFragsMode); programErr != nil {
if program, programErr = newProgram(opts.filter, action, perfMap, xdpFragsMode, programOpts); programErr != nil {
return nil, errors.Wrapf(programErr, "loading filter program in XDP frags mode for %v", action)
}

Expand Down Expand Up @@ -145,7 +145,6 @@ func attachProg(hookMap *ebpf.Map, fd int, action xdpAction) error {
if err != nil {
return errors.Wrap(err, "attaching filter programs")
}

return nil
}

Expand Down
8 changes: 4 additions & 4 deletions cmd/xdpcap/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestMain(m *testing.M) {
}

func TestMissingFilter(t *testing.T) {
_, err := newFilterWithMap(hookMap(t, 1), testOpts())
_, err := newFilterWithMap(hookMap(t, 1), testOpts(), ebpf.ProgramOptions{})
if err == nil {
t.Fatal("empty filter accepted")
}
Expand Down Expand Up @@ -85,7 +85,7 @@ func TestFilterProgramForAllModes(t *testing.T) {

opts := testOpts(bpf.RetConstant{Val: 0})
opts.actions = []xdpAction{xdpPass}
filter, err := newFilterWithMap(hookMap, opts)
filter, err := newFilterWithMap(hookMap, opts, ebpf.ProgramOptions{})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -116,7 +116,7 @@ func TestAllActions(t *testing.T) {
opts.actions = []xdpAction{}

// progs with actions from 0-9. Only 0-3 are used currently.
filter, err := newFilterWithMap(hookMap(t, 10), opts)
filter, err := newFilterWithMap(hookMap(t, 10), opts, ebpf.ProgramOptions{})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -298,7 +298,7 @@ func hookMap(t *testing.T, entries int) *ebpf.Map {
func mustNew(t *testing.T, opts filterOpts) *filter {
t.Helper()

filter, err := newFilterWithMap(hookMap(t, len(opts.actions)), opts)
filter, err := newFilterWithMap(hookMap(t, len(opts.actions)), opts, ebpf.ProgramOptions{})
if err != nil {
t.Fatal(err)
}
Expand Down
6 changes: 6 additions & 0 deletions cmd/xdpcap/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strconv"
"strings"

"github.com/cilium/ebpf"
"github.com/cloudflare/xdpcap/internal"

"github.com/google/gopacket/layers"
Expand Down Expand Up @@ -181,6 +182,9 @@ type flags struct {
// Filter provided as input. Not in any particular format, for metadata / debugging only.
filterExpr string
filterOpts filterOpts

printVerifierLogs bool
verifierLogSize int
}

// parseFlags creates the flags, and attempts to parse args.
Expand All @@ -198,6 +202,8 @@ func parseFlags(name string, args []string) (flags, error) {
flags.IntVar(&flags.filterOpts.perfWatermark, "watermark", 1, "Perf watermark (`bytes`). Must be < buffer.")
flags.BoolVar(&flags.quiet, "q", false, "Don't print statistics")
flags.BoolVar(&flags.flush, "flush", false, "Flush pcap data written to <output> for every packet received")
flags.BoolVar(&flags.printVerifierLogs, "print-verifier-logs", false, "If the verifier rejects the eBPF program, dump the logs")
flags.IntVar(&flags.verifierLogSize, "verifier-log-size", ebpf.DefaultVerifierLogSize, "Size of buffer to use for the verifier logs")

flags.filterOpts.actions = []xdpAction{}
flags.Var((*actionsFlag)(&flags.filterOpts.actions), "actions", fmt.Sprintf("XDP `actions` to capture packets for. Comma separated list of names (%v) or enum values (default all actions exposed by the <debug map>)", xdpActions))
Expand Down
20 changes: 11 additions & 9 deletions cmd/xdpcap/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"testing"

"github.com/cilium/ebpf"
"github.com/cloudflare/xdpcap/internal"

"github.com/google/gopacket/layers"
Expand All @@ -17,18 +18,18 @@ import (
func TestRequiredArgs(t *testing.T) {
output := tempOutput(t)

flags, err := parseFlags("", []string{})
_, err := parseFlags("", []string{})
if err == nil {
t.Fatal("missing main args")
}

flags, err = parseFlags("", []string{"foo"})
_, err = parseFlags("", []string{"foo"})
if err == nil {
t.Fatal("missing main args")
}

// Two args - empty filter
flags, err = parseFlags("", []string{"foo", output})
flags, err := parseFlags("", []string{"foo", output})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -253,12 +254,13 @@ func tempOutput(t *testing.T) string {

func defaultFlags(mapPath string) flags {
return flags{
mapPath: mapPath,
pcapFile: nil,
quiet: false,
flush: false,
linkType: layers.LinkTypeEthernet,
filterExpr: "",
mapPath: mapPath,
pcapFile: nil,
quiet: false,
flush: false,
linkType: layers.LinkTypeEthernet,
filterExpr: "",
verifierLogSize: ebpf.DefaultVerifierLogSize,
filterOpts: filterOpts{
perfPerCPUBuffer: 8192,
perfWatermark: 1,
Expand Down
15 changes: 13 additions & 2 deletions cmd/xdpcap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"syscall"
"time"

"github.com/cilium/ebpf"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcapgo"
Expand All @@ -48,7 +49,12 @@ func main() {

err = capture(flags)
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
var verifierErr *ebpf.VerifierError
if errors.As(err, &verifierErr) && flags.printVerifierLogs {
fmt.Printf("Verifier error: %+v\n", verifierErr)
} else {
fmt.Fprintln(os.Stderr, "Error:", err)
}
os.Exit(2)
}
}
Expand All @@ -64,7 +70,12 @@ func capture(flags flags) error {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)

filter, err := newFilter(flags.mapPath, flags.filterOpts)
programOpts := ebpf.ProgramOptions{
LogSize: flags.verifierLogSize,
LogDisabled: !flags.printVerifierLogs,
LogLevel: ebpf.LogLevelInstruction,
}
filter, err := newFilter(flags.mapPath, flags.filterOpts, programOpts)
if err != nil {
return errors.Wrap(err, "creating filter")
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/xdpcap/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,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, xdpFragsMode bool) (*program, error) {
func newProgram(filter []bpf.Instruction, action xdpAction, perfMap *ebpf.Map, xdpFragsMode bool, opts ebpf.ProgramOptions) (*program, error) {
metricsMap, err := ebpf.NewMap(&metricsSpec)
if err != nil {
return nil, errors.Wrap(err, "creating metrics map")
Expand Down Expand Up @@ -172,7 +172,7 @@ func newProgram(filter []bpf.Instruction, action xdpAction, perfMap *ebpf.Map, x
progSpec.Flags = progSpec.Flags | unix.BPF_F_XDP_HAS_FRAGS
}

prog, err := ebpf.NewProgram(progSpec)
prog, err := ebpf.NewProgramWithOptions(progSpec, opts)
if err != nil {
return nil, errors.Wrap(err, "loading filter")
}
Expand Down