From 28461388bb0fc4ccf86b8531b0c41f7995e59ffb Mon Sep 17 00:00:00 2001 From: Arthur Fabre Date: Thu, 27 Aug 2026 15:10:10 +0200 Subject: [PATCH 1/2] Replace github.com/pkg/errors with stdlib Golang's standard library now has good support for errors, switch to using it so we can drop the github.com/pkg/errors dependency. --- c.go | 15 +++++++-------- c_example_test.go | 8 ++++---- cbpfc.go | 26 +++++++++++++------------- clang/clang.go | 10 ++++------ ebpf.go | 13 ++++++------- ebpf_example_test.go | 5 +++-- go.mod | 1 - go.sum | 2 -- 8 files changed, 37 insertions(+), 43 deletions(-) diff --git a/c.go b/c.go index 4d28de4..188ca2d 100644 --- a/c.go +++ b/c.go @@ -6,7 +6,6 @@ import ( "strings" "text/template" - "github.com/pkg/errors" "golang.org/x/net/bpf" ) @@ -103,7 +102,7 @@ type COpts struct { // non 0 if the packet does match. func ToC(filter []bpf.Instruction, opts COpts) (string, error) { if !funcNameRegex.MatchString(opts.FunctionName) { - return "", errors.Errorf("invalid FunctionName %q", opts.FunctionName) + return "", fmt.Errorf("invalid FunctionName %q", opts.FunctionName) } blocks, err := compile(filter, compileOpts{ @@ -130,13 +129,13 @@ func ToC(filter []bpf.Instruction, opts COpts) (string, error) { // Fill in the template tmpl, err := template.New("cbfp_func").Parse(funcTemplate) if err != nil { - return "", errors.Wrapf(err, "unable to parse func template") + return "", fmt.Errorf("unable to parse func template: %w", err) } c := strings.Builder{} if err := tmpl.Execute(&c, fun); err != nil { - return "", errors.Wrapf(err, "unable to execute func template") + return "", fmt.Errorf("unable to execute func template: %w", err) } return c.String(), nil @@ -151,7 +150,7 @@ func blockToC(blk *block) (cBlock, error) { for _, insn := range blk.insns { stat, err := insnToC(insn, blk) if err != nil { - return cBlk, errors.Wrapf(err, "unable to compile %v", insn) + return cBlk, fmt.Errorf("unable to compile %v: %w", insn, err) } cBlk.Statements = append(cBlk.Statements, stat...) @@ -180,7 +179,7 @@ func insnToC(insn instruction, blk *block) ([]string, error) { case bpf.LoadExtension: if i.Num != bpf.ExtLen { - return nil, errors.Errorf("unsupported BPF extension %v", i) + return nil, fmt.Errorf("unsupported BPF extension %v", i) } return stat("a = data_end - data;") @@ -227,7 +226,7 @@ func insnToC(insn instruction, blk *block) ([]string, error) { return stat("if (x == 0) return 0;") default: - return nil, errors.Errorf("unsupported instruction %v", insn) + return nil, fmt.Errorf("unsupported instruction %v", insn) } } @@ -243,7 +242,7 @@ func packetLoadToC(size int, offsetFmt string, offsetArgs ...interface{}) ([]str return stat("a = ntohl(*((uint32_t *) (%s)));", offset) } - return nil, errors.Errorf("unsupported load size %d", size) + return nil, fmt.Errorf("unsupported load size %d", size) } func condToC(skipTrue, skipFalse skip, blk *block, condFmt string, condArgs ...interface{}) ([]string, error) { diff --git a/c_example_test.go b/c_example_test.go index f809a81..5ecf3d1 100644 --- a/c_example_test.go +++ b/c_example_test.go @@ -2,12 +2,12 @@ package cbpfc import ( "bytes" + "fmt" "os" "text/template" "github.com/cloudflare/cbpfc/clang" - "github.com/pkg/errors" "golang.org/x/net/bpf" ) @@ -97,7 +97,7 @@ func buildC(filter []bpf.Instruction, programName string, opts COpts) ([]byte, e // convert filter to C ebpfFilter, err := ToC(filter, opts) if err != nil { - return nil, errors.Wrap(err, "converting filter to C") + return nil, fmt.Errorf("converting filter to C: %w", err) } // embed filter in C template @@ -109,7 +109,7 @@ func buildC(filter []bpf.Instruction, programName string, opts COpts) ([]byte, e Offset: opts.PacketStartMaxOffset, }) if err != nil { - return nil, errors.Wrap(err, "executing template with C filter") + return nil, fmt.Errorf("executing template with C filter: %w", err) } // lookup clang binary to use @@ -124,7 +124,7 @@ func buildC(filter []bpf.Instruction, programName string, opts COpts) ([]byte, e EmitDebug: true, // For BTF }) if err != nil { - return nil, errors.Wrap(err, "compiling C") + return nil, fmt.Errorf("compiling C: %w", err) } return elf, nil diff --git a/cbpfc.go b/cbpfc.go index f00d3fb..460e590 100644 --- a/cbpfc.go +++ b/cbpfc.go @@ -21,10 +21,10 @@ package cbpfc import ( + "errors" "fmt" "sort" - "github.com/pkg/errors" "golang.org/x/net/bpf" ) @@ -160,7 +160,7 @@ func (a packetGuardAbsolute) adjustInsns(insns []instruction) {} // Assemble implements the Instruction Assemble method. func (p packetGuardAbsolute) Assemble() (bpf.RawInstruction, error) { - return bpf.RawInstruction{}, errors.Errorf("unsupported") + return bpf.RawInstruction{}, fmt.Errorf("unsupported") } // packetGuardIndirect checks packet bounds for indirect packet loads (RegX + constant offset). @@ -294,7 +294,7 @@ func (p packetGuardIndirect) adjustInsns(insns []instruction) { // Assemble implements the Instruction Assemble method. func (p packetGuardIndirect) Assemble() (bpf.RawInstruction, error) { - return bpf.RawInstruction{}, errors.Errorf("unsupported") + return bpf.RawInstruction{}, fmt.Errorf("unsupported") } // checksXNotZero is a "fake" instruction @@ -304,7 +304,7 @@ type checkXNotZero struct { // Assemble implements the Instruction Assemble method. func (c checkXNotZero) Assemble() (bpf.RawInstruction, error) { - return bpf.RawInstruction{}, errors.Errorf("unsupported") + return bpf.RawInstruction{}, fmt.Errorf("unsupported") } type compileOpts struct { @@ -329,7 +329,7 @@ func compile(insns []bpf.Instruction, opts compileOpts) ([]*block, error) { // Split into blocks blocks, err := splitBlocks(instructions) if err != nil { - return nil, errors.Wrapf(err, "unable to compute blocks") + return nil, fmt.Errorf("unable to compute blocks: %w", err) } // Initialize registers @@ -364,22 +364,22 @@ func validateInstructions(insns []bpf.Instruction) error { // Assemble does some input validation _, err := insn.Assemble() if err != nil { - return errors.Errorf("can't assemble instruction %d: %v", pc, insn) + return fmt.Errorf("can't assemble instruction %d: %v", pc, insn) } switch i := insn.(type) { case bpf.RawInstruction: - return errors.Errorf("unsupported instruction %d: %v", pc, insn) + return fmt.Errorf("unsupported instruction %d: %v", pc, insn) // Negative constant offsets are used for extensions (and if they're supported, x/net/bpf will parse them) // and other packet addressing modes we don't support: https://elixir.bootlin.com/linux/v5.14.10/source/kernel/bpf/core.c#L65 case bpf.LoadAbsolute: if int32(i.Off) < 0 { - return errors.Errorf("LoadAbsolute negative offset %v", int32(i.Off)) + return fmt.Errorf("LoadAbsolute negative offset %v", int32(i.Off)) } case bpf.LoadMemShift: if int32(i.Off) < 0 { - return errors.Errorf("LoadMemShift negative offset %v", int32(i.Off)) + return fmt.Errorf("LoadMemShift negative offset %v", int32(i.Off)) } case bpf.LoadExtension: @@ -387,7 +387,7 @@ func validateInstructions(insns []bpf.Instruction) error { case bpf.ExtLen: break default: - return errors.Errorf("unsupported BPF extension %d: %v", pc, insn) + return fmt.Errorf("unsupported BPF extension %d: %v", pc, insn) } } } @@ -504,7 +504,7 @@ func splitBlocks(instructions []instruction) ([]*block, error) { t := next.skipToPos(s) if t >= pos(len(instructions)) { - return nil, errors.Errorf("instruction %v flows past last instruction", next.last()) + return nil, fmt.Errorf("instruction %v flows past last instruction", next.last()) } targets[t] = append(targets[t], next) @@ -563,7 +563,7 @@ func addDivideByZeroGuards(blocks []*block) error { switch i := insn.Instruction.(type) { case bpf.ALUOpConstant: if isDivision(i.Op) && i.Val == 0 { - return errors.Errorf("instruction %v divides by 0", insn) + return fmt.Errorf("instruction %v divides by 0", insn) } case bpf.ALUOpX: if isDivision(i.Op) && !notZero { @@ -947,7 +947,7 @@ func initializeMemory(blocks []*block) error { // Check no uninitialized scratch registers are read for scratch, uninit := range insnUninitialized.scratch { if uninit { - return errors.Errorf("instruction %v reads potentially uninitialized scratch register M[%d]", insn, scratch) + return fmt.Errorf("instruction %v reads potentially uninitialized scratch register M[%d]", insn, scratch) } } diff --git a/clang/clang.go b/clang/clang.go index 15a77d2..e997572 100644 --- a/clang/clang.go +++ b/clang/clang.go @@ -10,8 +10,6 @@ import ( "path/filepath" "strings" "time" - - "github.com/pkg/errors" ) // Opts configure how an XDP program is compiled / built @@ -56,7 +54,7 @@ func (o Opts) cmd(inputFile string, outputFile string) (*exec.Cmd, error) { // debug build script will be in a different directory, relative imports won't work absInclude, err := filepath.Abs(include) if err != nil { - return nil, errors.Wrapf(err, "can't get absolute path to include %s", include) + return nil, fmt.Errorf("can't get absolute path to include %s: %w", include, err) } flags = append(flags, "-I", absInclude) @@ -99,7 +97,7 @@ func CompileRes(source []byte, name string, opts Opts) (Res, error) { cmdline := cmd.Path + " " + strings.Join(cmd.Args, " ") + "\n" err := os.WriteFile(filepath.Join(opts.Output, "build"), []byte(cmdline), 0644) if err != nil { - return Res{}, errors.Wrap(err, "can't write build cmdline") + return Res{}, fmt.Errorf("can't write build cmdline: %w", err) } } else { cmd.Stdin = bytes.NewReader(source) @@ -113,9 +111,9 @@ func compileRes(cmd *exec.Cmd, output func(stdout []byte) ([]byte, error)) (Res, if err != nil { switch e := err.(type) { case *exec.ExitError: - return Res{}, errors.Wrapf(e, "unable to compile C:\n%s", string(e.Stderr)) + return Res{}, fmt.Errorf("unable to compile C:\n%s: %w", string(e.Stderr), e) default: - return Res{}, errors.Wrapf(e, "unable to compile C") + return Res{}, fmt.Errorf("unable to compile C: %w", e) } } elf, err := output(stdout) diff --git a/ebpf.go b/ebpf.go index 3f57d7e..a3bc2cd 100644 --- a/ebpf.go +++ b/ebpf.go @@ -5,7 +5,6 @@ import ( "math" "github.com/cilium/ebpf/asm" - "github.com/pkg/errors" "golang.org/x/net/bpf" ) @@ -134,7 +133,7 @@ func ToEBPF(filter []bpf.Instruction, opts EBPFOpts) (asm.Instructions, error) { } if eOpts.StackOffset&1 == 1 { - return nil, errors.Errorf("unaligned stack offset") + return nil, fmt.Errorf("unaligned stack offset") } eInsns := asm.Instructions{} @@ -143,7 +142,7 @@ func ToEBPF(filter []bpf.Instruction, opts EBPFOpts) (asm.Instructions, error) { for i, insn := range block.insns { eInsn, err := insnToEBPF(insn, block, eOpts) if err != nil { - return nil, errors.Wrapf(err, "unable to compile %v", insn) + return nil, fmt.Errorf("unable to compile %v: %w", insn, err) } // First insn of the block, add symbol so it can be referenced in jumps @@ -176,7 +175,7 @@ func registersUnique(regs ...asm.Register) error { } if _, ok := seen[reg]; ok { - return errors.Errorf("register %v used twice", reg) + return fmt.Errorf("register %v used twice", reg) } seen[reg] = struct{}{} } @@ -187,7 +186,7 @@ func registersUnique(regs ...asm.Register) error { // registerValid ensures that a register is a valid ebpf register func registerValid(reg asm.Register) error { if reg > asm.R9 { - return errors.Errorf("invalid register %v", reg) + return fmt.Errorf("invalid register %v", reg) } return nil @@ -231,7 +230,7 @@ func insnToEBPF(insn instruction, blk *block, opts ebpfOpts) (asm.Instructions, case bpf.LoadExtension: if i.Num != bpf.ExtLen { - return nil, errors.Errorf("unsupported BPF extension %v", i) + return nil, fmt.Errorf("unsupported BPF extension %v", i) } return ebpfInsn( @@ -312,7 +311,7 @@ func insnToEBPF(insn instruction, blk *block, opts ebpfOpts) (asm.Instructions, return ebpfInsn(asm.JEq.Imm(opts.regX, 0, opts.label(noMatchLabel))) default: - return nil, errors.Errorf("unsupported instruction %v", insn) + return nil, fmt.Errorf("unsupported instruction %v", insn) } } diff --git a/ebpf_example_test.go b/ebpf_example_test.go index 943c4b1..fe27ff3 100644 --- a/ebpf_example_test.go +++ b/ebpf_example_test.go @@ -1,8 +1,9 @@ package cbpfc import ( + "fmt" + "github.com/cilium/ebpf/asm" - "github.com/pkg/errors" "golang.org/x/net/bpf" ) @@ -41,7 +42,7 @@ func buildEBPF(filter []bpf.Instruction, offset uint16) (asm.Instructions, error LabelPrefix: "filter", }) if err != nil { - return nil, errors.Wrap(err, "converting filter to eBPF") + return nil, fmt.Errorf("converting filter to eBPF: %w", err) } prog := asm.Instructions{ diff --git a/go.mod b/go.mod index cf076fd..3f7fc9b 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,6 @@ module github.com/cloudflare/cbpfc require ( github.com/cilium/ebpf v0.22.0 - github.com/pkg/errors v0.9.1 golang.org/x/net v0.57.0 golang.org/x/sys v0.47.0 ) diff --git a/go.sum b/go.sum index 86942de..875e6e5 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= From d1fa0bfde67dfca7b32ea6ad65d6b9e50223de5e Mon Sep 17 00:00:00 2001 From: Arthur Fabre Date: Thu, 27 Aug 2026 16:15:14 +0200 Subject: [PATCH 2/2] Remove direct dependency on golang.org/x/sys We used golang.org/x/sys in only two places: - For raising the RLIMIT in tests. This isn't required anymore as of kernel 5.11, so can stop doing it. - For attaching the socket filter to a unix socket. But this is also trivially doable with the syscall package. This removes the direct dependency on golang.org/x/sys: one less thing for us to go update. --- go.mod | 3 ++- insn_test.go | 23 ----------------------- kernel_test.go | 21 +++++++++++++++------ 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 3f7fc9b..2a75845 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/cloudflare/cbpfc require ( github.com/cilium/ebpf v0.22.0 golang.org/x/net v0.57.0 - golang.org/x/sys v0.47.0 ) +require golang.org/x/sys v0.47.0 // indirect + go 1.25.0 diff --git a/insn_test.go b/insn_test.go index 651717a..c7ca084 100644 --- a/insn_test.go +++ b/insn_test.go @@ -2,37 +2,14 @@ package cbpfc import ( "bytes" - "flag" "fmt" "math" - "os" "testing" "github.com/cilium/ebpf" "golang.org/x/net/bpf" - - // syscall has a wonky RLIM_INFINITY, and no RLIMIT_MEMLOCK - "golang.org/x/sys/unix" ) -func TestMain(m *testing.M) { - // Needed for testing.Short - flag.Parse() - - if !testing.Short() { - // Remove any locked memory limits so we can load BPF programs - err := unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{ - Cur: unix.RLIM_INFINITY, - Max: unix.RLIM_INFINITY, - }) - if err != nil { - panic(err) - } - } - - os.Exit(m.Run()) -} - func TestZeroInitA(t *testing.T) { t.Parallel() diff --git a/kernel_test.go b/kernel_test.go index bc1681d..c9f0f03 100644 --- a/kernel_test.go +++ b/kernel_test.go @@ -3,12 +3,12 @@ package cbpfc import ( "bytes" "net" + "syscall" "testing" "time" "unsafe" "golang.org/x/net/bpf" - "golang.org/x/sys/unix" ) // kernelBackend is a backend that runs cBPF in the kernel @@ -38,12 +38,21 @@ func kernelBackend(tb testing.TB, insns []bpf.Instruction, in []byte, opts backe tb.Fatal(err) } err = readConn.Control(func(fd uintptr) { - err := unix.SetsockoptSockFprog(int(fd), unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, &unix.SockFprog{ + sockFProg := syscall.SockFprog{ Len: uint16(len(filter)), - Filter: (*unix.SockFilter)(unsafe.Pointer(&filter[0])), - }) - if err != nil { - tb.Fatal(err) + Filter: (*syscall.SockFilter)(unsafe.Pointer(&filter[0])), + } + + if _, _, errno := syscall.Syscall6( + syscall.SYS_SETSOCKOPT, + uintptr(fd), + uintptr(syscall.SOL_SOCKET), + uintptr(syscall.SO_ATTACH_FILTER), + uintptr(unsafe.Pointer(&sockFProg)), + uintptr(unsafe.Sizeof(sockFProg)), + 0, + ); errno != 0 { + tb.Fatal(errno) } }) if err != nil {