Skip to content

Commit 6489887

Browse files
Merge pull request #3 from jschwinger233/pr/comments
Add comments
2 parents f7bf59b + 3b892ff commit 6489887

File tree

2 files changed

+58
-7
lines changed

2 files changed

+58
-7
lines changed

compile.go

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ const (
3636
AvailableOffset
3737
)
3838

39+
/*
40+
Steps:
41+
1. Compile pcap expresion to cbpf using libpcap
42+
2. Convert cbpf to ebpf using cloudflare/cbpfc
43+
3. [!DirectRead] Convert direct memory load to bpf_probe_read_kernel call
44+
*/
3945
func CompileEbpf(expr string, opts Options) (insts asm.Instructions, err error) {
4046
if expr == "__reject_all__" {
4147
return asm.Instructions{
@@ -48,10 +54,12 @@ func CompileEbpf(expr string, opts Options) (insts asm.Instructions, err error)
4854
}
4955

5056
ebpfInsts, err := cbpfc.ToEBPF(cbpfInsts, cbpfc.EBPFOpts{
57+
// skb->data is at r4, skb->data_end is at r5.
5158
PacketStart: asm.R4,
5259
PacketEnd: asm.R5,
5360
Result: opts.result(),
5461
ResultLabel: opts.resultLabel(),
62+
// _skb is at R0, __skb is at R1, ___skb is at R2.
5563
Working: [4]asm.Register{asm.R0, asm.R1, asm.R2, asm.R3},
5664
LabelPrefix: opts.labelPrefix(),
5765
StackOffset: -int(AvailableOffset),
@@ -98,6 +106,24 @@ func CompileCbpf(expr string, l2 bool) (insts []bpf.Instruction, err error) {
98106
return
99107
}
100108

109+
/*
110+
If !DirectRead, We have to adjust the ebpf instructions because verifier prevents us from
111+
directly loading data from memory. For example, the instruction "r0 = *(u8 *)(r4 +0)"
112+
will break verifier with error "R4 invalid mem access 'scalar", we therefore
113+
need to convert this direct memory load to bpf_probe_read_kernel function call:
114+
115+
- r1 = r10 // r10 is stack top
116+
- r1 += -8 // r1 = r10-8
117+
- r2 = 1 // r2 = sizeof(u8)
118+
- r3 = r4 // r4 is start of packet data, aka L3 header
119+
- r3 += 0 // r3 = r4+0
120+
- call bpf_probe_read_kernel // *(r10-8) = *(u8 *)(r4+0)
121+
- r0 = *(u8 *)(r10 -8) // r0 = *(r10-8)
122+
123+
To safely borrow R1, R2 and R3 for setting up the arguments for
124+
bpf_probe_read_kernel(), we need to save the original values of R1, R2 and R3
125+
on stack, and restore them after the function call.
126+
*/
101127
func adjustEbpf(insts asm.Instructions, opts Options) (newInsts asm.Instructions, err error) {
102128
if !opts.DirectRead {
103129
replaceIdx := []int{}
@@ -107,23 +133,28 @@ func adjustEbpf(insts asm.Instructions, opts Options) (newInsts asm.Instructions
107133
replaceIdx = append(replaceIdx, idx)
108134
replaceInsts[idx] = append(replaceInsts[idx],
109135

136+
// Store R1, R2, R3 on stack.
110137
asm.StoreMem(asm.RFP, int16(R1Offset), asm.R1, asm.DWord),
111138
asm.StoreMem(asm.RFP, int16(R2Offset), asm.R2, asm.DWord),
112139
asm.StoreMem(asm.RFP, int16(R3Offset), asm.R3, asm.DWord),
113140

141+
// bpf_probe_read_kernel(RFP-8, size, inst.Src)
114142
asm.Mov.Reg(asm.R1, asm.RFP),
115143
asm.Add.Imm(asm.R1, int32(BpfReadKernelOffset)),
116144
asm.Mov.Imm(asm.R2, int32(inst.OpCode.Size().Sizeof())),
117145
asm.Mov.Reg(asm.R3, inst.Src),
118146
asm.Add.Imm(asm.R3, int32(inst.Offset)),
119147
asm.FnProbeReadKernel.Call(),
120148

149+
// inst.Dst = *(RFP-8)
121150
asm.LoadMem(inst.Dst, asm.RFP, int16(BpfReadKernelOffset), inst.OpCode.Size()),
122151

152+
// 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.
123153
asm.LoadMem(asm.R4, asm.RFP, int16(R4Offset), asm.DWord),
124154
asm.LoadMem(asm.R5, asm.RFP, int16(R5Offset), asm.DWord),
125155
)
126156

157+
// Restore R1, R2, R3 from stack
127158
restoreInsts := asm.Instructions{
128159
asm.LoadMem(asm.R1, asm.RFP, int16(R1Offset), asm.DWord),
129160
asm.LoadMem(asm.R2, asm.RFP, int16(R2Offset), asm.DWord),
@@ -136,26 +167,33 @@ func adjustEbpf(insts asm.Instructions, opts Options) (newInsts asm.Instructions
136167
}
137168

138169
replaceInsts[idx] = append(replaceInsts[idx], restoreInsts...)
170+
171+
// Metadata is crucial for adjusting jump offsets. We
172+
// ditched original instructions, which could hold symbol
173+
// names targeted by other jump instructions, so here we
174+
// inherit the metadata from the ditched ones.
139175
replaceInsts[idx][0].Metadata = inst.Metadata
140176
}
141177
}
142178

179+
// Replace the memory load instructions with the new ones
143180
for i := len(replaceIdx) - 1; i >= 0; i-- {
144181
idx := replaceIdx[i]
145182
insts = append(insts[:idx], append(replaceInsts[idx], insts[idx+1:]...)...)
146183
}
147184

185+
// Store R4, R5 on stack.
148186
insts = append([]asm.Instruction{
149187
asm.StoreMem(asm.RFP, int16(R4Offset), asm.R4, asm.DWord),
150188
asm.StoreMem(asm.RFP, int16(R5Offset), asm.R5, asm.DWord),
151189
}, insts...)
152190
}
153191

154192
return append(insts,
155-
asm.Mov.Imm(asm.R1, 0).WithSymbol(opts.resultLabel()),
156-
asm.Mov.Imm(asm.R2, 0),
157-
asm.Mov.Imm(asm.R3, 0),
158-
asm.Mov.Reg(asm.R4, opts.result()),
159-
asm.Mov.Imm(asm.R5, 0),
193+
asm.Mov.Imm(asm.R1, 0).WithSymbol(opts.resultLabel()), // r1 = 0 (_skb)
194+
asm.Mov.Imm(asm.R2, 0), // r2 = 0 (__skb)
195+
asm.Mov.Imm(asm.R3, 0), // r3 = 0 (___skb)
196+
asm.Mov.Reg(asm.R4, opts.result()), // r4 = $result (data)
197+
asm.Mov.Imm(asm.R5, 0), // r5 = 0 (data_end)
160198
), nil
161199
}

options.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,22 @@ package elibpcap
33
import "github.com/cilium/ebpf/asm"
44

55
type Options struct {
6-
AtBpf2Bpf string
6+
// AtBpf2Bpf is the label where the bpf2bpf call is injected.
7+
// The rejection position requires the function to be declared as:
8+
//
9+
// static __noinline bool
10+
// filter_pcap_ebpf(void *_skb, void *__skb, void *___skb, void *data, void* data_end)
11+
//
12+
// In this case, AtBpf2Bpf is the name of the function: filter_pcap_ebpf
13+
AtBpf2Bpf string
14+
15+
// DirectRead indicates if the injected bpf program should use "direct packet access" or not.
16+
// See https://docs.kernel.org/bpf/verifier.html#direct-packet-access
717
DirectRead bool
8-
L2Skb bool
18+
19+
// L2Skb indicates if the injected bpf program should use L2 skb or not.
20+
// The L2 skb is the one that contains the ethernet header, while the L3 skb->data points to the IP header.
21+
L2Skb bool
922
}
1023

1124
func (o Options) resultLabel() string {

0 commit comments

Comments
 (0)