From 66d2a3d4451ab2117b8238ab3a2a6c6cdb1bff72 Mon Sep 17 00:00:00 2001 From: gray Date: Tue, 15 Apr 2025 20:11:13 +0800 Subject: [PATCH 1/3] Set CGO_LDFLAGS and CGO_CFLAGS in makefile This is because we are going to import elibpcap to compile with cgo, we can't count on the pseudo #cgo directives from elibpcap package. Signed-off-by: gray --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c145f92a..ef1f6e7b 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,8 @@ LIBPCAP_ARCH ?= x86_64-unknown-linux-gnu # For compiling libpcap and CGO CC ?= gcc ARCHS ?= amd64 arm64 +CGO_CFLAGS="-I/pwru/libpcap" +CGO_LDFLAGS="-L/pwru/libpcap -lpcap -static" TEST_TIMEOUT ?= 5s .DEFAULT_GOAL := pwru @@ -19,7 +21,7 @@ TEST_TIMEOUT ?= 5s ## Build the GO binary pwru: libpcap/libpcap.a TARGET_GOARCH=$(TARGET_GOARCH) $(GO_GENERATE) - CC=$(CC) GOARCH=$(TARGET_GOARCH) $(GO_BUILD) $(if $(GO_TAGS),-tags $(GO_TAGS)) \ + CGO_CFLAGS=$(CGO_CFLAGS) CGO_LDFLAGS=$(CGO_LDFLAGS) CC=$(CC) GOARCH=$(TARGET_GOARCH) $(GO_BUILD) $(if $(GO_TAGS),-tags $(GO_TAGS)) \ -ldflags "-w -s \ -X 'github.com/cilium/pwru/internal/pwru.Version=${VERSION}'" From 88c00a7daf0a252ba9323f1be413cfe90506ec25 Mon Sep 17 00:00:00 2001 From: gray Date: Tue, 15 Apr 2025 19:00:37 +0800 Subject: [PATCH 2/3] Use jschwinger233/elibpcap to compile and inject libpcap Signed-off-by: gray --- go.mod | 5 +- go.sum | 2 + internal/libpcap/compile.go | 190 ------------------------------------ internal/libpcap/inject.go | 55 +++-------- 4 files changed, 16 insertions(+), 236 deletions(-) delete mode 100644 internal/libpcap/compile.go diff --git a/go.mod b/go.mod index d939898d..76c33da0 100644 --- a/go.mod +++ b/go.mod @@ -6,20 +6,20 @@ require ( github.com/Asphaltt/mybtf v0.0.0-20250315135407-f9d09086616b github.com/cheggaaa/pb/v3 v3.1.7 github.com/cilium/ebpf v0.18.0 - github.com/cloudflare/cbpfc v0.0.0-20240920015331-ff978e94500b + github.com/jschwinger233/elibpcap v1.0.2 github.com/jsimonetti/rtnetlink v1.4.2 github.com/leonhwangprojects/bice v0.1.2 github.com/spf13/pflag v1.0.6 github.com/tklauser/ps v0.0.3 github.com/vishvananda/netns v0.0.5 golang.org/x/arch v0.15.0 - golang.org/x/net v0.38.0 golang.org/x/sync v0.12.0 golang.org/x/sys v0.31.0 ) require ( github.com/VividCortex/ewma v1.2.0 // indirect + github.com/cloudflare/cbpfc v0.0.0-20240920015331-ff978e94500b // indirect github.com/fatih/color v1.18.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/josharian/native v1.1.0 // indirect @@ -31,5 +31,6 @@ require ( github.com/mdlayher/socket v0.4.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/net v0.38.0 // indirect rsc.io/c2go v0.0.0-20170620140410-520c22818a08 // indirect ) diff --git a/go.sum b/go.sum index 043d2c00..68f13f7c 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jschwinger233/elibpcap v1.0.2 h1:9VVQi2fZhxxLK6ErLtPZZsC9WuLjp1+XiYAImn78kEk= +github.com/jschwinger233/elibpcap v1.0.2/go.mod h1:fUmq00C6Pechtr089JDPhvIc6TxrbUHDlZ5QCYc9tJQ= github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= github.com/jsimonetti/rtnetlink/v2 v2.0.2 h1:ZKlbCujrIpp4/u3V2Ka0oxlf4BCkt6ojkvpy3nZoCBY= diff --git a/internal/libpcap/compile.go b/internal/libpcap/compile.go deleted file mode 100644 index 0788834e..00000000 --- a/internal/libpcap/compile.go +++ /dev/null @@ -1,190 +0,0 @@ -package libpcap - -import ( - "fmt" - "unsafe" - - "github.com/cilium/ebpf/asm" - "github.com/cloudflare/cbpfc" - "golang.org/x/net/bpf" -) - -/* -#cgo CFLAGS: -I${SRCDIR}/../../libpcap -#cgo LDFLAGS: -L${SRCDIR}/../../libpcap -lpcap -static -#include -#include -*/ -import "C" - -type pcapBpfProgram C.struct_bpf_program - -const ( - MaxBpfInstructions = 4096 - bpfInstructionBufferSize = 8 * MaxBpfInstructions - MAXIMUM_SNAPLEN = 262144 -) - -type StackOffset int - -const ( - BpfReadKernelOffset StackOffset = -8 * (iota + 1) - R1Offset - R2Offset - R3Offset - R4Offset - R5Offset - AvailableOffset -) - -func CompileCbpf(expr string, l3 bool) (insts []bpf.Instruction, err error) { - if len(expr) == 0 { - return - } - - pcapType := C.DLT_EN10MB - if l3 { - pcapType = C.DLT_RAW - } - pcap := C.pcap_open_dead(C.int(pcapType), MAXIMUM_SNAPLEN) - if pcap == nil { - return nil, fmt.Errorf("failed to pcap_open_dead: %+v\n", C.PCAP_ERROR) - } - defer C.pcap_close(pcap) - - cexpr := C.CString(expr) - defer C.free(unsafe.Pointer(cexpr)) - - 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 (l3=%+v)", expr, C.GoString(C.pcap_geterr(pcap)), l3) - } - defer C.pcap_freecode((*C.struct_bpf_program)(&bpfProg)) - - for _, v := range (*[bpfInstructionBufferSize]C.struct_bpf_insn)(unsafe.Pointer(bpfProg.bf_insns))[0:bpfProg.bf_len:bpfProg.bf_len] { - insts = append(insts, bpf.RawInstruction{ - Op: uint16(v.code), - Jt: uint8(v.jt), - Jf: uint8(v.jf), - K: uint32(v.k), - }.Disassemble()) - } - return -} - -/* -Steps: -1. Compile pcap expresion to cbpf using libpcap -2. Convert cbpf to ebpf using cloudflare/cbpfc -3. Convert direct memory load to bpf_probe_read_kernel - -The conversion to ebpf requires two registers pointing to the start and -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, l3 bool) (insts asm.Instructions, err error) { - cbpfInsts, err := CompileCbpf(expr, l3) - if err != nil { - return - } - - ebpfInsts, err := cbpfc.ToEBPF(cbpfInsts, opts) - if err != nil { - return - } - - return adjustEbpf(ebpfInsts, opts) -} - -/* -We have to adjust the ebpf instructions because verifier prevents us from -directly loading data from memory. For example, the instruction "r0 = *(u8 *)(r4 +0)" -will break verifier with error "R4 invalid mem access 'scalar", we therefore -need to convert this direct memory load to bpf_probe_read_kernel function call: - -- r1 = r10 // r10 is stack top -- r1 += -8 // r1 = r10-8 -- r2 = 1 // r2 = sizeof(u8) -- r3 = r4 // r4 is start of packet data, aka L3 header -- r3 += 0 // r3 = r4+0 -- call bpf_probe_read_kernel // *(r10-8) = *(u8 *)(r4+0) -- r0 = *(u8 *)(r10 -8) // r0 = *(r10-8) - -To safely borrow R1, R2 and R3 for setting up the arguments for -bpf_probe_read_kernel(), we need to save the original values of R1, R2 and R3 -on stack, and restore them after the function call. -*/ -func adjustEbpf(insts asm.Instructions, opts cbpfc.EBPFOpts) (newInsts asm.Instructions, err error) { - replaceIdx := []int{} - replaceInsts := map[int]asm.Instructions{} - for idx, inst := range insts { - if inst.OpCode.Class().IsLoad() { - replaceIdx = append(replaceIdx, idx) - replaceInsts[idx] = append(replaceInsts[idx], - - // Store R1, R2, R3 on stack. - asm.StoreMem(asm.RFP, int16(R1Offset), asm.R1, asm.DWord), - asm.StoreMem(asm.RFP, int16(R2Offset), asm.R2, asm.DWord), - asm.StoreMem(asm.RFP, int16(R3Offset), asm.R3, asm.DWord), - - // bpf_probe_read_kernel(RFP-8, size, inst.Src) - asm.Mov.Reg(asm.R1, asm.RFP), - asm.Add.Imm(asm.R1, int32(BpfReadKernelOffset)), - asm.Mov.Imm(asm.R2, int32(inst.OpCode.Size().Sizeof())), - asm.Mov.Reg(asm.R3, inst.Src), - asm.Add.Imm(asm.R3, int32(inst.Offset)), - asm.FnProbeReadKernel.Call(), - - // inst.Dst = *(RFP-8) - asm.LoadMem(inst.Dst, asm.RFP, int16(BpfReadKernelOffset), inst.OpCode.Size()), - - // Restore R4, R5 from stack. This is needed because bpf_probe_read_kernel always resets R4 and R5 even if they are not used by bpf_probe_read_kernel. - asm.LoadMem(asm.R4, asm.RFP, int16(R4Offset), asm.DWord), - asm.LoadMem(asm.R5, asm.RFP, int16(R5Offset), asm.DWord), - ) - - // Restore R1, R2, R3 from stack - restoreInsts := asm.Instructions{ - asm.LoadMem(asm.R1, asm.RFP, int16(R1Offset), asm.DWord), - asm.LoadMem(asm.R2, asm.RFP, int16(R2Offset), asm.DWord), - asm.LoadMem(asm.R3, asm.RFP, int16(R3Offset), asm.DWord), - } - switch inst.Dst { - case asm.R1, asm.R2, asm.R3: - restoreInsts = append(restoreInsts[:inst.Dst-1], restoreInsts[inst.Dst:]...) - } - replaceInsts[idx] = append(replaceInsts[idx], restoreInsts...) - - /* - Metadata is crucial for adjusting jump offsets. We - ditched original instructions, which could hold symbol - names targeted by other jump instructions, so here we - inherit the metadata from the ditched ones. - */ - replaceInsts[idx][0].Metadata = inst.Metadata - } - } - - // Replace the memory load instructions with the new ones - for i := len(replaceIdx) - 1; i >= 0; i-- { - idx := replaceIdx[i] - insts = append(insts[:idx], append(replaceInsts[idx], insts[idx+1:]...)...) - } - - // Store R4, R5 on stack. - insts = append([]asm.Instruction{ - asm.StoreMem(asm.RFP, int16(R4Offset), asm.R4, asm.DWord), - asm.StoreMem(asm.RFP, int16(R5Offset), asm.R5, asm.DWord), - }, insts...) - - insts = append(insts, - 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 -} diff --git a/internal/libpcap/inject.go b/internal/libpcap/inject.go index 4cb1b3c9..17809314 100644 --- a/internal/libpcap/inject.go +++ b/internal/libpcap/inject.go @@ -1,12 +1,10 @@ package libpcap import ( - "errors" "fmt" "github.com/cilium/ebpf" - "github.com/cilium/ebpf/asm" - "github.com/cloudflare/cbpfc" + "github.com/jschwinger233/elibpcap" ) func InjectL2TunnelFilter(program *ebpf.ProgramSpec, filterExpr, l2TunnelFilterExpr string) (err error) { @@ -25,7 +23,7 @@ func InjectFilters(program *ebpf.ProgramSpec, filterExpr, tunnelFilterExprL2, tu // 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, false) + return injectFilter(program, elibpcap.RejectAllExpr, true, false) } // Attach any tunnel filters. if err := injectFilter(program, tunnelFilterExprL2, false, true); err != nil { @@ -51,47 +49,16 @@ func injectFilter(program *ebpf.ProgramSpec, filterExpr string, l3 bool, tunnel if l3 { suffix = tunnelSuffix + "_l3" } - injectIdx := -1 - for idx, inst := range program.Instructions { - if inst.Symbol() == "filter_pcap_ebpf"+suffix { - injectIdx = idx - break - } - } - if injectIdx == -1 { - return errors.New("Cannot find the injection position") - } - var filterEbpf asm.Instructions - if filterExpr == "__pwru_reject_all__" { - // let data = data_end, so kprobe_pwru.c:filter_pcap_ebpf_l3() always returns false - filterEbpf = asm.Instructions{ - asm.Mov.Reg(asm.R4, asm.R5), // r4 = r5 (data = data_end) - } - } else { - filterEbpf, err = CompileEbpf(filterExpr, cbpfc.EBPFOpts{ - // The rejection position is in the beginning of the `filter_pcap_ebpf` function: - // filter_pcap_ebpf(void *_skb, void *__skb, void *___skb, void *data, void* data_end) - // So we can confidently say, skb->data is at r4, skb->data_end is at r5. - PacketStart: asm.R4, - PacketEnd: asm.R5, - Result: asm.R0, - 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" + suffix, - StackOffset: -int(AvailableOffset), - }, l3) - } - if err != nil { - return - } - - filterEbpf[0] = filterEbpf[0].WithMetadata(program.Instructions[injectIdx].Metadata) - program.Instructions[injectIdx] = program.Instructions[injectIdx].WithMetadata(asm.Metadata{}) - program.Instructions = append(program.Instructions[:injectIdx], - append(filterEbpf, program.Instructions[injectIdx:]...)..., + program.Instructions, err = elibpcap.Inject( + filterExpr, + program.Instructions, + elibpcap.Options{ + AtBpf2Bpf: "filter_pcap_ebpf" + suffix, + DirectRead: false, + L2Skb: !l3, + }, ) + return - return nil } From 3c948a725e5a47e8e4dfcb11a6ee0f6ae2c87b94 Mon Sep 17 00:00:00 2001 From: gray Date: Tue, 15 Apr 2025 19:03:37 +0800 Subject: [PATCH 3/3] Update vendor Signed-off-by: gray --- .../jschwinger233/elibpcap/.gitignore | 21 ++ .../jschwinger233/elibpcap/README.md | 8 + .../jschwinger233/elibpcap/compile.go | 201 ++++++++++++++++++ .../jschwinger233/elibpcap/compile_static.go | 12 ++ .../jschwinger233/elibpcap/elibpcap.go | 35 +++ .../jschwinger233/elibpcap/options.go | 34 +++ vendor/modules.txt | 3 + 7 files changed, 314 insertions(+) create mode 100644 vendor/github.com/jschwinger233/elibpcap/.gitignore create mode 100644 vendor/github.com/jschwinger233/elibpcap/README.md create mode 100644 vendor/github.com/jschwinger233/elibpcap/compile.go create mode 100644 vendor/github.com/jschwinger233/elibpcap/compile_static.go create mode 100644 vendor/github.com/jschwinger233/elibpcap/elibpcap.go create mode 100644 vendor/github.com/jschwinger233/elibpcap/options.go diff --git a/vendor/github.com/jschwinger233/elibpcap/.gitignore b/vendor/github.com/jschwinger233/elibpcap/.gitignore new file mode 100644 index 00000000..3b735ec4 --- /dev/null +++ b/vendor/github.com/jschwinger233/elibpcap/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/vendor/github.com/jschwinger233/elibpcap/README.md b/vendor/github.com/jschwinger233/elibpcap/README.md new file mode 100644 index 00000000..a0b5b63f --- /dev/null +++ b/vendor/github.com/jschwinger233/elibpcap/README.md @@ -0,0 +1,8 @@ +# elibpcap + +Projects that use elibpcap: + +- [eCapture](https://github.com/gojue/ecapture) +- [ptcpdump](https://github.com/mozillazg/ptcpdump) +- [skbdist](https://github.com/Asphaltt/skbdist) +- [bpfsnoop](https://github.com/bpfsnoop/bpfsnoop) diff --git a/vendor/github.com/jschwinger233/elibpcap/compile.go b/vendor/github.com/jschwinger233/elibpcap/compile.go new file mode 100644 index 00000000..69802b70 --- /dev/null +++ b/vendor/github.com/jschwinger233/elibpcap/compile.go @@ -0,0 +1,201 @@ +package elibpcap + +import ( + "fmt" + "unsafe" + + "github.com/cilium/ebpf/asm" + "github.com/cloudflare/cbpfc" + "golang.org/x/net/bpf" +) + +/* +#cgo LDFLAGS: -L/usr/local/lib -lpcap +#include +#include +*/ +import "C" + +type pcapBpfProgram C.struct_bpf_program + +const ( + MaxBpfInstructions = 4096 + bpfInstructionBufferSize = 8 * MaxBpfInstructions + MAXIMUM_SNAPLEN = 262144 + + RejectAllExpr = "__reject_all__" +) + +type StackOffset int + +const ( + BpfReadKernelOffset StackOffset = -8 * (iota + 1) + R1Offset + R2Offset + R3Offset + R4Offset + R5Offset + AvailableOffset +) + +/* +Steps: +1. Compile pcap expresion to cbpf using libpcap +2. Convert cbpf to ebpf using cloudflare/cbpfc +3. [!DirectRead] Convert direct memory load to bpf_probe_read_kernel call +*/ +func CompileEbpf(expr string, opts Options) (insts asm.Instructions, err error) { + if expr == RejectAllExpr { + return asm.Instructions{ + asm.Mov.Reg(asm.R4, asm.R5), // r4 = r5 (data = data_end) + }, nil + } + cbpfInsts, err := CompileCbpf(expr, opts.L2Skb) + if err != nil { + return + } + + ebpfInsts, err := cbpfc.ToEBPF(cbpfInsts, cbpfc.EBPFOpts{ + // skb->data is at r4, skb->data_end is at r5. + PacketStart: asm.R4, + PacketEnd: asm.R5, + Result: opts.result(), + ResultLabel: opts.resultLabel(), + // _skb is at R0, __skb is at R1, ___skb is at R2. + Working: [4]asm.Register{asm.R0, asm.R1, asm.R2, asm.R3}, + LabelPrefix: opts.labelPrefix(), + StackOffset: -int(AvailableOffset), + }) + if err != nil { + return + } + + return adjustEbpf(ebpfInsts, opts) +} + +func CompileCbpf(expr string, l2 bool) (insts []bpf.Instruction, err error) { + if len(expr) == 0 { + return + } + + pcapType := C.DLT_RAW + if l2 { + pcapType = C.DLT_EN10MB + } + pcap := C.pcap_open_dead(C.int(pcapType), MAXIMUM_SNAPLEN) + if pcap == nil { + return nil, fmt.Errorf("failed to pcap_open_dead: %+v\n", C.PCAP_ERROR) + } + defer C.pcap_close(pcap) + + cexpr := C.CString(expr) + defer C.free(unsafe.Pointer(cexpr)) + + 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))) + } + defer C.pcap_freecode((*C.struct_bpf_program)(&bpfProg)) + + for _, v := range (*[bpfInstructionBufferSize]C.struct_bpf_insn)(unsafe.Pointer(bpfProg.bf_insns))[0:bpfProg.bf_len:bpfProg.bf_len] { + insts = append(insts, bpf.RawInstruction{ + Op: uint16(v.code), + Jt: uint8(v.jt), + Jf: uint8(v.jf), + K: uint32(v.k), + }.Disassemble()) + } + return +} + +/* +If !DirectRead, We have to adjust the ebpf instructions because verifier prevents us from +directly loading data from memory. For example, the instruction "r0 = *(u8 *)(r4 +0)" +will break verifier with error "R4 invalid mem access 'scalar", we therefore +need to convert this direct memory load to bpf_probe_read_kernel function call: + +- r1 = r10 // r10 is stack top +- r1 += -8 // r1 = r10-8 +- r2 = 1 // r2 = sizeof(u8) +- r3 = r4 // r4 is start of packet data, aka L3 header +- r3 += 0 // r3 = r4+0 +- call bpf_probe_read_kernel // *(r10-8) = *(u8 *)(r4+0) +- r0 = *(u8 *)(r10 -8) // r0 = *(r10-8) + +To safely borrow R1, R2 and R3 for setting up the arguments for +bpf_probe_read_kernel(), we need to save the original values of R1, R2 and R3 +on stack, and restore them after the function call. +*/ +func adjustEbpf(insts asm.Instructions, opts Options) (newInsts asm.Instructions, err error) { + if !opts.DirectRead { + replaceIdx := []int{} + replaceInsts := map[int]asm.Instructions{} + for idx, inst := range insts { + if inst.OpCode.Class().IsLoad() { + replaceIdx = append(replaceIdx, idx) + replaceInsts[idx] = append(replaceInsts[idx], + + // Store R1, R2, R3 on stack. + asm.StoreMem(asm.RFP, int16(R1Offset), asm.R1, asm.DWord), + asm.StoreMem(asm.RFP, int16(R2Offset), asm.R2, asm.DWord), + asm.StoreMem(asm.RFP, int16(R3Offset), asm.R3, asm.DWord), + + // bpf_probe_read_kernel(RFP-8, size, inst.Src) + asm.Mov.Reg(asm.R1, asm.RFP), + asm.Add.Imm(asm.R1, int32(BpfReadKernelOffset)), + asm.Mov.Imm(asm.R2, int32(inst.OpCode.Size().Sizeof())), + asm.Mov.Reg(asm.R3, inst.Src), + asm.Add.Imm(asm.R3, int32(inst.Offset)), + asm.FnProbeReadKernel.Call(), + + // inst.Dst = *(RFP-8) + asm.LoadMem(inst.Dst, asm.RFP, int16(BpfReadKernelOffset), inst.OpCode.Size()), + + // Restore R4, R5 from stack. This is needed because bpf_probe_read_kernel always resets R4 and R5 even if they are not used by bpf_probe_read_kernel. + asm.LoadMem(asm.R4, asm.RFP, int16(R4Offset), asm.DWord), + asm.LoadMem(asm.R5, asm.RFP, int16(R5Offset), asm.DWord), + ) + + // Restore R1, R2, R3 from stack + restoreInsts := asm.Instructions{ + asm.LoadMem(asm.R1, asm.RFP, int16(R1Offset), asm.DWord), + asm.LoadMem(asm.R2, asm.RFP, int16(R2Offset), asm.DWord), + asm.LoadMem(asm.R3, asm.RFP, int16(R3Offset), asm.DWord), + } + + switch inst.Dst { + case asm.R1, asm.R2, asm.R3: + restoreInsts = append(restoreInsts[:inst.Dst-1], restoreInsts[inst.Dst:]...) + } + + replaceInsts[idx] = append(replaceInsts[idx], restoreInsts...) + + // Metadata is crucial for adjusting jump offsets. We + // ditched original instructions, which could hold symbol + // names targeted by other jump instructions, so here we + // inherit the metadata from the ditched ones. + replaceInsts[idx][0].Metadata = inst.Metadata + } + } + + // Replace the memory load instructions with the new ones + for i := len(replaceIdx) - 1; i >= 0; i-- { + idx := replaceIdx[i] + insts = append(insts[:idx], append(replaceInsts[idx], insts[idx+1:]...)...) + } + + // Store R4, R5 on stack. + insts = append([]asm.Instruction{ + asm.StoreMem(asm.RFP, int16(R4Offset), asm.R4, asm.DWord), + asm.StoreMem(asm.RFP, int16(R5Offset), asm.R5, asm.DWord), + }, insts...) + } + + return append(insts, + 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) + ), nil +} diff --git a/vendor/github.com/jschwinger233/elibpcap/compile_static.go b/vendor/github.com/jschwinger233/elibpcap/compile_static.go new file mode 100644 index 00000000..928555ff --- /dev/null +++ b/vendor/github.com/jschwinger233/elibpcap/compile_static.go @@ -0,0 +1,12 @@ +// +build static !dynamic + +package elibpcap + + +/* +#cgo LDFLAGS: -L/usr/local/lib -lpcap -static +#include +#include +*/ +import "C" + diff --git a/vendor/github.com/jschwinger233/elibpcap/elibpcap.go b/vendor/github.com/jschwinger233/elibpcap/elibpcap.go new file mode 100644 index 00000000..221c21dd --- /dev/null +++ b/vendor/github.com/jschwinger233/elibpcap/elibpcap.go @@ -0,0 +1,35 @@ +package elibpcap + +import ( + "fmt" + + "github.com/cilium/ebpf/asm" +) + +func Inject(filter string, insns asm.Instructions, opts Options) (_ asm.Instructions, err error) { + if filter == "" { + return insns, nil + } + + injectIdx := -1 + for idx, inst := range insns { + if inst.Symbol() == opts.AtBpf2Bpf { + injectIdx = idx + break + } + } + if injectIdx == -1 { + return insns, fmt.Errorf("Cannot find bpf2bpf: %s", opts.AtBpf2Bpf) + } + + filterInsns, err := CompileEbpf(filter, opts) + if err != nil { + return insns, err + } + + filterInsns[0] = filterInsns[0].WithMetadata(insns[injectIdx].Metadata) + insns[injectIdx] = insns[injectIdx].WithMetadata(asm.Metadata{}) + return append(insns[:injectIdx], + append(filterInsns, insns[injectIdx:]...)..., + ), nil +} diff --git a/vendor/github.com/jschwinger233/elibpcap/options.go b/vendor/github.com/jschwinger233/elibpcap/options.go new file mode 100644 index 00000000..11e5fc69 --- /dev/null +++ b/vendor/github.com/jschwinger233/elibpcap/options.go @@ -0,0 +1,34 @@ +package elibpcap + +import "github.com/cilium/ebpf/asm" + +type Options struct { + // AtBpf2Bpf is the label where the bpf2bpf call is injected. + // The rejection position requires the function to be declared as: + // + // static __noinline bool + // filter_pcap_ebpf(void *_skb, void *__skb, void *___skb, void *data, void* data_end) + // + // In this case, AtBpf2Bpf is the name of the function: filter_pcap_ebpf + AtBpf2Bpf string + + // DirectRead indicates if the injected bpf program should use "direct packet access" or not. + // See https://docs.kernel.org/bpf/verifier.html#direct-packet-access + DirectRead bool + + // L2Skb indicates if the injected bpf program should use L2 skb or not. + // The L2 skb is the one that contains the ethernet header, while the L3 skb->data points to the IP header. + L2Skb bool +} + +func (o Options) resultLabel() string { + return "_result_" + o.AtBpf2Bpf +} + +func (o Options) labelPrefix() string { + return "_prefix_" + o.AtBpf2Bpf +} + +func (o Options) result() asm.Register { + return asm.R0 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0ca4c199..62569708 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -45,6 +45,9 @@ github.com/google/go-cmp/cmp/internal/value # github.com/josharian/native v1.1.0 ## explicit; go 1.13 github.com/josharian/native +# github.com/jschwinger233/elibpcap v1.0.2 +## explicit; go 1.21.0 +github.com/jschwinger233/elibpcap # github.com/jsimonetti/rtnetlink v1.4.2 ## explicit; go 1.20 github.com/jsimonetti/rtnetlink