1 package ebpf
2
3 import (
4 "errors"
5 "fmt"
6 "strings"
7 "testing"
8
9 "github.com/cilium/ebpf/asm"
10 "github.com/cilium/ebpf/internal"
11 "github.com/cilium/ebpf/internal/sys"
12 "github.com/cilium/ebpf/internal/testutils"
13 "github.com/cilium/ebpf/internal/unix"
14 qt "github.com/frankban/quicktest"
15 )
16
17 func TestMapInfoFromProc(t *testing.T) {
18 hash, err := NewMap(&MapSpec{
19 Name: "testing",
20 Type: Hash,
21 KeySize: 4,
22 ValueSize: 5,
23 MaxEntries: 2,
24 Flags: unix.BPF_F_NO_PREALLOC,
25 })
26 testutils.SkipIfNotSupported(t, err)
27 if err != nil {
28 t.Fatal(err)
29 }
30 defer hash.Close()
31
32 info, err := newMapInfoFromProc(hash.fd)
33 testutils.SkipIfNotSupported(t, err)
34 if err != nil {
35 t.Fatal("Can't get map info:", err)
36 }
37
38 if info.Type != Hash {
39 t.Error("Expected Hash, got", info.Type)
40 }
41
42 if info.KeySize != 4 {
43 t.Error("Expected KeySize of 4, got", info.KeySize)
44 }
45
46 if info.ValueSize != 5 {
47 t.Error("Expected ValueSize of 5, got", info.ValueSize)
48 }
49
50 if info.MaxEntries != 2 {
51 t.Error("Expected MaxEntries of 2, got", info.MaxEntries)
52 }
53
54 if info.Flags != unix.BPF_F_NO_PREALLOC {
55 t.Errorf("Expected Flags to be %d, got %d", unix.BPF_F_NO_PREALLOC, info.Flags)
56 }
57
58 if info.Name != "" && info.Name != "testing" {
59 t.Error("Expected name to be testing, got", info.Name)
60 }
61
62 if _, ok := info.ID(); ok {
63 t.Error("Expected ID to not be available")
64 }
65
66 nested, err := NewMap(&MapSpec{
67 Type: ArrayOfMaps,
68 KeySize: 4,
69 MaxEntries: 2,
70 InnerMap: &MapSpec{
71 Type: Array,
72 KeySize: 4,
73 ValueSize: 4,
74 MaxEntries: 2,
75 },
76 })
77 testutils.SkipIfNotSupported(t, err)
78 if err != nil {
79 t.Fatal(err)
80 }
81 defer nested.Close()
82
83 _, err = newMapInfoFromProc(nested.fd)
84 if err != nil {
85 t.Fatal("Can't get nested map info from /proc:", err)
86 }
87 }
88
89 func TestProgramInfo(t *testing.T) {
90 prog := mustSocketFilter(t)
91
92 for name, fn := range map[string]func(*sys.FD) (*ProgramInfo, error){
93 "generic": newProgramInfoFromFd,
94 "proc": newProgramInfoFromProc,
95 } {
96 t.Run(name, func(t *testing.T) {
97 info, err := fn(prog.fd)
98 testutils.SkipIfNotSupported(t, err)
99 if err != nil {
100 t.Fatal("Can't get program info:", err)
101 }
102
103 if info.Type != SocketFilter {
104 t.Error("Expected Type to be SocketFilter, got", info.Type)
105 }
106
107 if info.Name != "" && info.Name != "test" {
108 t.Error("Expected Name to be test, got", info.Name)
109 }
110
111 if want := "d7edec644f05498d"; info.Tag != want {
112 t.Errorf("Expected Tag to be %s, got %s", want, info.Tag)
113 }
114
115 if id, ok := info.ID(); ok && id == 0 {
116 t.Error("Expected a valid ID:", id)
117 } else if name == "proc" && ok {
118 t.Error("Expected ID to not be available")
119 }
120 })
121 }
122 }
123
124 func TestProgramInfoMapIDs(t *testing.T) {
125 testutils.SkipOnOldKernel(t, "4.10", "reading program info")
126
127 arr, err := NewMap(&MapSpec{
128 Type: Array,
129 KeySize: 4,
130 ValueSize: 4,
131 MaxEntries: 1,
132 })
133 qt.Assert(t, err, qt.IsNil)
134 defer arr.Close()
135
136 prog, err := NewProgram(&ProgramSpec{
137 Type: SocketFilter,
138 Instructions: asm.Instructions{
139 asm.LoadMapPtr(asm.R0, arr.FD()),
140 asm.LoadImm(asm.R0, 2, asm.DWord),
141 asm.Return(),
142 },
143 License: "MIT",
144 })
145 qt.Assert(t, err, qt.IsNil)
146 defer prog.Close()
147
148 info, err := prog.Info()
149 qt.Assert(t, err, qt.IsNil)
150
151 ids, ok := info.MapIDs()
152 if testutils.MustKernelVersion().Less(internal.Version{4, 15, 0}) {
153 qt.Assert(t, ok, qt.IsFalse)
154 qt.Assert(t, ids, qt.HasLen, 0)
155 } else {
156 qt.Assert(t, ok, qt.IsTrue)
157 qt.Assert(t, ids, qt.HasLen, 1)
158
159 mapInfo, err := arr.Info()
160 qt.Assert(t, err, qt.IsNil)
161 mapID, ok := mapInfo.ID()
162 qt.Assert(t, ok, qt.IsTrue)
163 qt.Assert(t, ids[0], qt.Equals, mapID)
164 }
165 }
166
167 func TestScanFdInfoReader(t *testing.T) {
168 tests := []struct {
169 fields map[string]interface{}
170 valid bool
171 }{
172 {nil, true},
173 {map[string]interface{}{"foo": new(string)}, true},
174 {map[string]interface{}{"zap": new(string)}, false},
175 {map[string]interface{}{"foo": new(int)}, false},
176 }
177
178 for _, test := range tests {
179 err := scanFdInfoReader(strings.NewReader("foo:\tbar\n"), test.fields)
180 if test.valid {
181 if err != nil {
182 t.Errorf("fields %v returns an error: %s", test.fields, err)
183 }
184 } else {
185 if err == nil {
186 t.Errorf("fields %v doesn't return an error", test.fields)
187 }
188 }
189 }
190 }
191
192
193
194 func TestStats(t *testing.T) {
195 testutils.SkipOnOldKernel(t, "5.8", "BPF_ENABLE_STATS")
196
197 prog := mustSocketFilter(t)
198
199 pi, err := prog.Info()
200 if err != nil {
201 t.Errorf("failed to get ProgramInfo: %v", err)
202 }
203
204 rc, ok := pi.RunCount()
205 if !ok {
206 t.Errorf("expected run count info to be available")
207 }
208 if rc != 0 {
209 t.Errorf("expected a run count of 0 but got %d", rc)
210 }
211
212 rt, ok := pi.Runtime()
213 if !ok {
214 t.Errorf("expected runtime info to be available")
215 }
216 if rt != 0 {
217 t.Errorf("expected a runtime of 0ns but got %v", rt)
218 }
219
220 if err := testStats(prog); err != nil {
221 t.Error(err)
222 }
223 }
224
225
226 func BenchmarkStats(b *testing.B) {
227 testutils.SkipOnOldKernel(b, "5.8", "BPF_ENABLE_STATS")
228
229 prog := mustSocketFilter(b)
230
231 for n := 0; n < b.N; n++ {
232 if err := testStats(prog); err != nil {
233 b.Fatal(fmt.Errorf("iter %d: %w", n, err))
234 }
235 }
236 }
237
238
239
240
241
242
243
244
245
246
247
248 func testStats(prog *Program) error {
249 in := make([]byte, 14)
250
251 stats, err := EnableStats(uint32(unix.BPF_STATS_RUN_TIME))
252 if err != nil {
253 return fmt.Errorf("failed to enable stats: %v", err)
254 }
255 defer stats.Close()
256
257
258
259 if _, _, err := prog.Test(in); err != nil {
260 return fmt.Errorf("failed to trigger program: %v", err)
261 }
262
263 pi, err := prog.Info()
264 if err != nil {
265 return fmt.Errorf("failed to get ProgramInfo: %v", err)
266 }
267
268 rc, ok := pi.RunCount()
269 if !ok {
270 return errors.New("expected run count info to be available")
271 }
272 if rc < 1 {
273 return fmt.Errorf("expected a run count of at least 1 but got %d", rc)
274 }
275
276 lc := rc
277
278 rt, ok := pi.Runtime()
279 if !ok {
280 return errors.New("expected runtime info to be available")
281 }
282 if rt == 0 {
283 return errors.New("expected a runtime other than 0ns")
284 }
285
286 lt := rt
287
288 if err := stats.Close(); err != nil {
289 return fmt.Errorf("failed to disable statistics: %v", err)
290 }
291
292
293
294 if _, _, err := prog.Test(in); err != nil {
295 return fmt.Errorf("failed to trigger program: %v", err)
296 }
297
298 pi, err = prog.Info()
299 if err != nil {
300 return fmt.Errorf("failed to get ProgramInfo: %v", err)
301 }
302
303 rc, ok = pi.RunCount()
304 if !ok {
305 return errors.New("expected run count info to be available")
306 }
307 if rc != lc {
308 return fmt.Errorf("run count unexpectedly increased over previous value (current: %v, prev: %v)", rc, lc)
309 }
310
311 rt, ok = pi.Runtime()
312 if !ok {
313 return errors.New("expected runtime info to be available")
314 }
315 if rt != lt {
316 return fmt.Errorf("runtime unexpectedly increased over the previous value (current: %v, prev: %v)", rt, lt)
317 }
318
319 return nil
320 }
321
View as plain text