github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/info_test.go (about) 1 package ebpf 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "strings" 8 "testing" 9 10 "github.com/go-quicktest/qt" 11 12 "github.com/cilium/ebpf/asm" 13 "github.com/cilium/ebpf/btf" 14 "github.com/cilium/ebpf/internal" 15 "github.com/cilium/ebpf/internal/sys" 16 "github.com/cilium/ebpf/internal/testutils" 17 "github.com/cilium/ebpf/internal/unix" 18 ) 19 20 func TestMapInfoFromProc(t *testing.T) { 21 hash, err := NewMap(&MapSpec{ 22 Name: "testing", 23 Type: Hash, 24 KeySize: 4, 25 ValueSize: 5, 26 MaxEntries: 2, 27 Flags: unix.BPF_F_NO_PREALLOC, 28 }) 29 testutils.SkipIfNotSupported(t, err) 30 if err != nil { 31 t.Fatal(err) 32 } 33 defer hash.Close() 34 35 info, err := newMapInfoFromProc(hash.fd) 36 testutils.SkipIfNotSupported(t, err) 37 if err != nil { 38 t.Fatal("Can't get map info:", err) 39 } 40 41 if info.Type != Hash { 42 t.Error("Expected Hash, got", info.Type) 43 } 44 45 if info.KeySize != 4 { 46 t.Error("Expected KeySize of 4, got", info.KeySize) 47 } 48 49 if info.ValueSize != 5 { 50 t.Error("Expected ValueSize of 5, got", info.ValueSize) 51 } 52 53 if info.MaxEntries != 2 { 54 t.Error("Expected MaxEntries of 2, got", info.MaxEntries) 55 } 56 57 if info.Flags != unix.BPF_F_NO_PREALLOC { 58 t.Errorf("Expected Flags to be %d, got %d", unix.BPF_F_NO_PREALLOC, info.Flags) 59 } 60 61 if info.Name != "" && info.Name != "testing" { 62 t.Error("Expected name to be testing, got", info.Name) 63 } 64 65 if _, ok := info.ID(); ok { 66 t.Error("Expected ID to not be available") 67 } 68 69 nested, err := NewMap(&MapSpec{ 70 Type: ArrayOfMaps, 71 KeySize: 4, 72 MaxEntries: 2, 73 InnerMap: &MapSpec{ 74 Type: Array, 75 KeySize: 4, 76 ValueSize: 4, 77 MaxEntries: 2, 78 }, 79 }) 80 testutils.SkipIfNotSupported(t, err) 81 if err != nil { 82 t.Fatal(err) 83 } 84 defer nested.Close() 85 86 _, err = newMapInfoFromProc(nested.fd) 87 if err != nil { 88 t.Fatal("Can't get nested map info from /proc:", err) 89 } 90 } 91 92 func TestProgramInfo(t *testing.T) { 93 prog := mustSocketFilter(t) 94 95 for name, fn := range map[string]func(*sys.FD) (*ProgramInfo, error){ 96 "generic": newProgramInfoFromFd, 97 "proc": newProgramInfoFromProc, 98 } { 99 t.Run(name, func(t *testing.T) { 100 info, err := fn(prog.fd) 101 testutils.SkipIfNotSupported(t, err) 102 if err != nil { 103 t.Fatal("Can't get program info:", err) 104 } 105 106 if info.Type != SocketFilter { 107 t.Error("Expected Type to be SocketFilter, got", info.Type) 108 } 109 110 if info.Name != "" && info.Name != "test" { 111 t.Error("Expected Name to be test, got", info.Name) 112 } 113 114 if want := "d7edec644f05498d"; info.Tag != want { 115 t.Errorf("Expected Tag to be %s, got %s", want, info.Tag) 116 } 117 118 if id, ok := info.ID(); ok && id == 0 { 119 t.Error("Expected a valid ID:", id) 120 } else if name == "proc" && ok { 121 t.Error("Expected ID to not be available") 122 } 123 124 if name == "proc" { 125 _, ok := info.CreatedByUID() 126 qt.Assert(t, qt.IsFalse(ok)) 127 } else { 128 uid, ok := info.CreatedByUID() 129 if testutils.IsKernelLessThan(t, "4.15") { 130 qt.Assert(t, qt.IsFalse(ok)) 131 } else { 132 qt.Assert(t, qt.IsTrue(ok)) 133 qt.Assert(t, qt.Equals(uid, uint32(os.Getuid()))) 134 } 135 } 136 }) 137 } 138 } 139 140 func TestProgramInfoMapIDs(t *testing.T) { 141 arr, err := NewMap(&MapSpec{ 142 Type: Array, 143 KeySize: 4, 144 ValueSize: 4, 145 MaxEntries: 1, 146 }) 147 qt.Assert(t, qt.IsNil(err)) 148 defer arr.Close() 149 150 prog, err := NewProgram(&ProgramSpec{ 151 Type: SocketFilter, 152 Instructions: asm.Instructions{ 153 asm.LoadMapPtr(asm.R0, arr.FD()), 154 asm.LoadImm(asm.R0, 2, asm.DWord), 155 asm.Return(), 156 }, 157 License: "MIT", 158 }) 159 qt.Assert(t, qt.IsNil(err)) 160 defer prog.Close() 161 162 info, err := prog.Info() 163 testutils.SkipIfNotSupported(t, err) 164 qt.Assert(t, qt.IsNil(err)) 165 166 ids, ok := info.MapIDs() 167 switch { 168 case testutils.IsKernelLessThan(t, "4.15"): 169 qt.Assert(t, qt.IsFalse(ok)) 170 qt.Assert(t, qt.HasLen(ids, 0)) 171 172 default: 173 qt.Assert(t, qt.IsTrue(ok)) 174 175 mapInfo, err := arr.Info() 176 qt.Assert(t, qt.IsNil(err)) 177 178 mapID, ok := mapInfo.ID() 179 qt.Assert(t, qt.IsTrue(ok)) 180 qt.Assert(t, qt.ContentEquals(ids, []MapID{mapID})) 181 } 182 } 183 184 func TestProgramInfoMapIDsNoMaps(t *testing.T) { 185 prog, err := NewProgram(&ProgramSpec{ 186 Type: SocketFilter, 187 Instructions: asm.Instructions{ 188 asm.LoadImm(asm.R0, 0, asm.DWord), 189 asm.Return(), 190 }, 191 License: "MIT", 192 }) 193 qt.Assert(t, qt.IsNil(err)) 194 defer prog.Close() 195 196 info, err := prog.Info() 197 testutils.SkipIfNotSupported(t, err) 198 qt.Assert(t, qt.IsNil(err)) 199 200 ids, ok := info.MapIDs() 201 switch { 202 case testutils.IsKernelLessThan(t, "4.15"): 203 qt.Assert(t, qt.IsFalse(ok)) 204 qt.Assert(t, qt.HasLen(ids, 0)) 205 206 default: 207 qt.Assert(t, qt.IsTrue(ok)) 208 qt.Assert(t, qt.HasLen(ids, 0)) 209 } 210 } 211 212 func TestScanFdInfoReader(t *testing.T) { 213 tests := []struct { 214 fields map[string]interface{} 215 valid bool 216 }{ 217 {nil, true}, 218 {map[string]interface{}{"foo": new(string)}, true}, 219 {map[string]interface{}{"zap": new(string)}, false}, 220 {map[string]interface{}{"foo": new(int)}, false}, 221 } 222 223 for _, test := range tests { 224 err := scanFdInfoReader(strings.NewReader("foo:\tbar\n"), test.fields) 225 if test.valid { 226 if err != nil { 227 t.Errorf("fields %v returns an error: %s", test.fields, err) 228 } 229 } else { 230 if err == nil { 231 t.Errorf("fields %v doesn't return an error", test.fields) 232 } 233 } 234 } 235 } 236 237 // TestStats loads a BPF program once and executes back-to-back test runs 238 // of the program. See testStats for details. 239 func TestStats(t *testing.T) { 240 testutils.SkipOnOldKernel(t, "5.8", "BPF_ENABLE_STATS") 241 242 prog := mustSocketFilter(t) 243 244 pi, err := prog.Info() 245 if err != nil { 246 t.Errorf("failed to get ProgramInfo: %v", err) 247 } 248 249 rc, ok := pi.RunCount() 250 if !ok { 251 t.Errorf("expected run count info to be available") 252 } 253 if rc != 0 { 254 t.Errorf("expected a run count of 0 but got %d", rc) 255 } 256 257 rt, ok := pi.Runtime() 258 if !ok { 259 t.Errorf("expected runtime info to be available") 260 } 261 if rt != 0 { 262 t.Errorf("expected a runtime of 0ns but got %v", rt) 263 } 264 265 if err := testStats(prog); err != nil { 266 t.Error(err) 267 } 268 } 269 270 // BenchmarkStats is a benchmark of TestStats. See testStats for details. 271 func BenchmarkStats(b *testing.B) { 272 testutils.SkipOnOldKernel(b, "5.8", "BPF_ENABLE_STATS") 273 274 prog := mustSocketFilter(b) 275 276 for n := 0; n < b.N; n++ { 277 if err := testStats(prog); err != nil { 278 b.Fatal(fmt.Errorf("iter %d: %w", n, err)) 279 } 280 } 281 } 282 283 // testStats implements the behaviour under test for TestStats 284 // and BenchmarkStats. First, a test run is executed with runtime statistics 285 // enabled, followed by another with runtime stats disabled. Counters are only 286 // expected to increase on the runs where runtime stats are enabled. 287 // 288 // Due to runtime behaviour on Go 1.14 and higher, the syscall backing 289 // (*Program).Test() could be invoked multiple times for each call to Test(), 290 // resulting in RunCount incrementing by more than one. Expecting RunCount to 291 // be of a specific value after a call to Test() is therefore not possible. 292 // See https://golang.org/doc/go1.14#runtime for more details. 293 func testStats(prog *Program) error { 294 in := internal.EmptyBPFContext 295 296 stats, err := EnableStats(uint32(unix.BPF_STATS_RUN_TIME)) 297 if err != nil { 298 return fmt.Errorf("failed to enable stats: %v", err) 299 } 300 defer stats.Close() 301 302 // Program execution with runtime statistics enabled. 303 // Should increase both runtime and run counter. 304 if _, _, err := prog.Test(in); err != nil { 305 return fmt.Errorf("failed to trigger program: %v", err) 306 } 307 308 pi, err := prog.Info() 309 if err != nil { 310 return fmt.Errorf("failed to get ProgramInfo: %v", err) 311 } 312 313 rc, ok := pi.RunCount() 314 if !ok { 315 return errors.New("expected run count info to be available") 316 } 317 if rc < 1 { 318 return fmt.Errorf("expected a run count of at least 1 but got %d", rc) 319 } 320 // Store the run count for the next invocation. 321 lc := rc 322 323 rt, ok := pi.Runtime() 324 if !ok { 325 return errors.New("expected runtime info to be available") 326 } 327 if rt == 0 { 328 return errors.New("expected a runtime other than 0ns") 329 } 330 // Store the runtime value for the next invocation. 331 lt := rt 332 333 if err := stats.Close(); err != nil { 334 return fmt.Errorf("failed to disable statistics: %v", err) 335 } 336 337 // Second program execution, with runtime statistics gathering disabled. 338 // Total runtime and run counters are not expected to increase. 339 if _, _, err := prog.Test(in); err != nil { 340 return fmt.Errorf("failed to trigger program: %v", err) 341 } 342 343 pi, err = prog.Info() 344 if err != nil { 345 return fmt.Errorf("failed to get ProgramInfo: %v", err) 346 } 347 348 rc, ok = pi.RunCount() 349 if !ok { 350 return errors.New("expected run count info to be available") 351 } 352 if rc != lc { 353 return fmt.Errorf("run count unexpectedly increased over previous value (current: %v, prev: %v)", rc, lc) 354 } 355 356 rt, ok = pi.Runtime() 357 if !ok { 358 return errors.New("expected runtime info to be available") 359 } 360 if rt != lt { 361 return fmt.Errorf("runtime unexpectedly increased over the previous value (current: %v, prev: %v)", rt, lt) 362 } 363 364 return nil 365 } 366 367 func TestHaveProgramInfoMapIDs(t *testing.T) { 368 testutils.CheckFeatureTest(t, haveProgramInfoMapIDs) 369 } 370 371 func TestProgInfoExtBTF(t *testing.T) { 372 testutils.SkipOnOldKernel(t, "5.0", "Program BTF (func/line_info)") 373 374 spec, err := LoadCollectionSpec(testutils.NativeFile(t, "testdata/loader-%s.elf")) 375 if err != nil { 376 t.Fatal(err) 377 } 378 379 var obj struct { 380 Main *Program `ebpf:"xdp_prog"` 381 } 382 383 err = spec.LoadAndAssign(&obj, nil) 384 testutils.SkipIfNotSupported(t, err) 385 if err != nil { 386 t.Fatal(err) 387 } 388 defer obj.Main.Close() 389 390 info, err := obj.Main.Info() 391 if err != nil { 392 t.Fatal(err) 393 } 394 395 inst, err := info.Instructions() 396 if err != nil { 397 t.Fatal(err) 398 } 399 400 expectedLineInfoCount := 26 401 expectedFuncInfo := map[string]bool{ 402 "xdp_prog": false, 403 "static_fn": false, 404 "global_fn2": false, 405 "global_fn3": false, 406 } 407 408 lineInfoCount := 0 409 410 for _, ins := range inst { 411 if ins.Source() != nil { 412 lineInfoCount++ 413 } 414 415 fn := btf.FuncMetadata(&ins) 416 if fn != nil { 417 expectedFuncInfo[fn.Name] = true 418 } 419 } 420 421 if lineInfoCount != expectedLineInfoCount { 422 t.Errorf("expected %d line info entries, got %d", expectedLineInfoCount, lineInfoCount) 423 } 424 425 for fn, found := range expectedFuncInfo { 426 if !found { 427 t.Errorf("func %q not found", fn) 428 } 429 } 430 }