github.com/kubeshark/ebpf@v0.9.2/collection_test.go (about) 1 package ebpf 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "testing" 8 9 "github.com/kubeshark/ebpf/asm" 10 "github.com/kubeshark/ebpf/btf" 11 "github.com/kubeshark/ebpf/internal" 12 "github.com/kubeshark/ebpf/internal/testutils" 13 ) 14 15 func TestCollectionSpecNotModified(t *testing.T) { 16 cs := CollectionSpec{ 17 Maps: map[string]*MapSpec{ 18 "my-map": { 19 Type: Array, 20 KeySize: 4, 21 ValueSize: 4, 22 MaxEntries: 1, 23 }, 24 }, 25 Programs: map[string]*ProgramSpec{ 26 "test": { 27 Type: SocketFilter, 28 Instructions: asm.Instructions{ 29 asm.LoadImm(asm.R1, 0, asm.DWord).WithReference("my-map"), 30 asm.LoadImm(asm.R0, 0, asm.DWord), 31 asm.Return(), 32 }, 33 License: "MIT", 34 }, 35 }, 36 } 37 38 coll, err := NewCollection(&cs) 39 if err != nil { 40 t.Fatal(err) 41 } 42 coll.Close() 43 44 if cs.Programs["test"].Instructions[0].Constant != 0 { 45 t.Error("Creating a collection modifies input spec") 46 } 47 } 48 49 func TestCollectionSpecCopy(t *testing.T) { 50 cs := &CollectionSpec{ 51 Maps: map[string]*MapSpec{ 52 "my-map": { 53 Type: Array, 54 KeySize: 4, 55 ValueSize: 4, 56 MaxEntries: 1, 57 }, 58 }, 59 Programs: map[string]*ProgramSpec{ 60 "test": { 61 Type: SocketFilter, 62 Instructions: asm.Instructions{ 63 asm.LoadMapPtr(asm.R1, 0), 64 asm.LoadImm(asm.R0, 0, asm.DWord), 65 asm.Return(), 66 }, 67 License: "MIT", 68 }, 69 }, 70 Types: &btf.Spec{}, 71 } 72 cpy := cs.Copy() 73 74 if cpy == cs { 75 t.Error("Copy returned the same pointner") 76 } 77 78 if cpy.Maps["my-map"] == cs.Maps["my-map"] { 79 t.Error("Copy returned same Maps") 80 } 81 82 if cpy.Programs["test"] == cs.Programs["test"] { 83 t.Error("Copy returned same Programs") 84 } 85 86 if cpy.Types != cs.Types { 87 t.Error("Copy returned different Types") 88 } 89 } 90 91 func TestCollectionSpecLoadCopy(t *testing.T) { 92 file := fmt.Sprintf("testdata/loader-%s.elf", internal.ClangEndian) 93 spec, err := LoadCollectionSpec(file) 94 if err != nil { 95 t.Fatal(err) 96 } 97 98 spec2 := spec.Copy() 99 100 var objs struct { 101 Prog *Program `ebpf:"xdp_prog"` 102 } 103 104 err = spec.LoadAndAssign(&objs, nil) 105 testutils.SkipIfNotSupported(t, err) 106 if err != nil { 107 t.Fatal("Loading original spec:", err) 108 } 109 defer objs.Prog.Close() 110 111 if err := spec2.LoadAndAssign(&objs, nil); err != nil { 112 t.Fatal("Loading copied spec:", err) 113 } 114 defer objs.Prog.Close() 115 } 116 117 func TestCollectionSpecRewriteMaps(t *testing.T) { 118 insns := asm.Instructions{ 119 // R1 map 120 asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"), 121 // R2 key 122 asm.Mov.Reg(asm.R2, asm.R10), 123 asm.Add.Imm(asm.R2, -4), 124 asm.StoreImm(asm.R2, 0, 0, asm.Word), 125 // Lookup map[0] 126 asm.FnMapLookupElem.Call(), 127 asm.JEq.Imm(asm.R0, 0, "ret"), 128 asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), 129 asm.Return().WithSymbol("ret"), 130 } 131 132 cs := &CollectionSpec{ 133 Maps: map[string]*MapSpec{ 134 "test-map": { 135 Type: Array, 136 KeySize: 4, 137 ValueSize: 4, 138 MaxEntries: 1, 139 }, 140 }, 141 Programs: map[string]*ProgramSpec{ 142 "test-prog": { 143 Type: SocketFilter, 144 Instructions: insns, 145 License: "MIT", 146 }, 147 }, 148 } 149 150 // Override the map with another one 151 newMap, err := NewMap(cs.Maps["test-map"]) 152 if err != nil { 153 t.Fatal(err) 154 } 155 defer newMap.Close() 156 157 err = newMap.Put(uint32(0), uint32(2)) 158 if err != nil { 159 t.Fatal(err) 160 } 161 162 err = cs.RewriteMaps(map[string]*Map{ 163 "test-map": newMap, 164 }) 165 if err != nil { 166 t.Fatal(err) 167 } 168 169 if cs.Maps["test-map"] != nil { 170 t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps") 171 } 172 173 coll, err := NewCollection(cs) 174 if err != nil { 175 t.Fatal(err) 176 } 177 defer coll.Close() 178 179 ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14)) 180 testutils.SkipIfNotSupported(t, err) 181 if err != nil { 182 t.Fatal(err) 183 } 184 185 if ret != 2 { 186 t.Fatal("new / override map not used") 187 } 188 } 189 190 func TestCollectionSpecMapReplacements(t *testing.T) { 191 insns := asm.Instructions{ 192 // R1 map 193 asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"), 194 // R2 key 195 asm.Mov.Reg(asm.R2, asm.R10), 196 asm.Add.Imm(asm.R2, -4), 197 asm.StoreImm(asm.R2, 0, 0, asm.Word), 198 // Lookup map[0] 199 asm.FnMapLookupElem.Call(), 200 asm.JEq.Imm(asm.R0, 0, "ret"), 201 asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), 202 asm.Return().WithSymbol("ret"), 203 } 204 205 cs := &CollectionSpec{ 206 Maps: map[string]*MapSpec{ 207 "test-map": { 208 Type: Array, 209 KeySize: 4, 210 ValueSize: 4, 211 MaxEntries: 1, 212 }, 213 }, 214 Programs: map[string]*ProgramSpec{ 215 "test-prog": { 216 Type: SocketFilter, 217 Instructions: insns, 218 License: "MIT", 219 }, 220 }, 221 } 222 223 // Replace the map with another one 224 newMap, err := NewMap(cs.Maps["test-map"]) 225 if err != nil { 226 t.Fatal(err) 227 } 228 defer newMap.Close() 229 230 err = newMap.Put(uint32(0), uint32(2)) 231 if err != nil { 232 t.Fatal(err) 233 } 234 235 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 236 MapReplacements: map[string]*Map{ 237 "test-map": newMap, 238 }, 239 }) 240 if err != nil { 241 t.Fatal(err) 242 } 243 defer coll.Close() 244 245 ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14)) 246 testutils.SkipIfNotSupported(t, err) 247 if err != nil { 248 t.Fatal(err) 249 } 250 251 if ret != 2 { 252 t.Fatal("new / override map not used") 253 } 254 255 // Check that newMap isn't closed when the collection is closed 256 coll.Close() 257 err = newMap.Put(uint32(0), uint32(3)) 258 if err != nil { 259 t.Fatalf("failed to update replaced map: %s", err) 260 } 261 } 262 func TestCollectionSpecMapReplacements_NonExistingMap(t *testing.T) { 263 cs := &CollectionSpec{ 264 Maps: map[string]*MapSpec{ 265 "test-map": { 266 Type: Array, 267 KeySize: 4, 268 ValueSize: 4, 269 MaxEntries: 1, 270 }, 271 }, 272 } 273 274 // Override non-existing map 275 newMap, err := NewMap(cs.Maps["test-map"]) 276 if err != nil { 277 t.Fatal(err) 278 } 279 defer newMap.Close() 280 281 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 282 MapReplacements: map[string]*Map{ 283 "non-existing-map": newMap, 284 }, 285 }) 286 if err == nil { 287 coll.Close() 288 t.Fatal("Overriding a non existing map did not fail") 289 } 290 } 291 292 func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) { 293 cs := &CollectionSpec{ 294 Maps: map[string]*MapSpec{ 295 "test-map": { 296 Type: Array, 297 KeySize: 4, 298 ValueSize: 4, 299 MaxEntries: 1, 300 }, 301 }, 302 } 303 304 // Override map with mismatching spec 305 newMap, err := NewMap(&MapSpec{ 306 Type: Array, 307 KeySize: 4, 308 ValueSize: 8, // this is different 309 MaxEntries: 1, 310 }) 311 if err != nil { 312 t.Fatal(err) 313 } 314 // Map fd is duplicated by MapReplacements, this one can be safely closed. 315 defer newMap.Close() 316 317 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 318 MapReplacements: map[string]*Map{ 319 "test-map": newMap, 320 }, 321 }) 322 if err == nil { 323 coll.Close() 324 t.Fatal("Overriding a map with a mismatching spec did not fail") 325 } 326 if !errors.Is(err, ErrMapIncompatible) { 327 t.Fatalf("Overriding a map with a mismatching spec failed with the wrong error") 328 } 329 } 330 331 func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) { 332 spec := &CollectionSpec{ 333 Maps: map[string]*MapSpec{ 334 "valid": { 335 Type: Array, 336 KeySize: 4, 337 ValueSize: 4, 338 MaxEntries: 1, 339 }, 340 "bogus": { 341 Type: Array, 342 MaxEntries: 0, 343 }, 344 }, 345 Programs: map[string]*ProgramSpec{ 346 "valid": { 347 Type: SocketFilter, 348 Instructions: asm.Instructions{ 349 asm.LoadImm(asm.R0, 0, asm.DWord), 350 asm.Return(), 351 }, 352 License: "MIT", 353 }, 354 "bogus": { 355 Type: SocketFilter, 356 Instructions: asm.Instructions{ 357 // Undefined return value is rejected 358 asm.Return(), 359 }, 360 License: "MIT", 361 }, 362 }, 363 } 364 365 var objs struct { 366 Prog *Program `ebpf:"valid"` 367 Map *Map `ebpf:"valid"` 368 } 369 370 if err := spec.LoadAndAssign(&objs, nil); err != nil { 371 t.Fatal("Assign loads a map or program that isn't requested in the struct:", err) 372 } 373 defer objs.Prog.Close() 374 defer objs.Map.Close() 375 376 if objs.Prog == nil { 377 t.Error("Program is nil") 378 } 379 380 if objs.Map == nil { 381 t.Error("Map is nil") 382 } 383 } 384 385 func TestCollectionAssign(t *testing.T) { 386 var specs struct { 387 Program *ProgramSpec `ebpf:"prog1"` 388 Map *MapSpec `ebpf:"map1"` 389 } 390 391 mapSpec := &MapSpec{ 392 Type: Array, 393 KeySize: 4, 394 ValueSize: 4, 395 MaxEntries: 1, 396 } 397 progSpec := &ProgramSpec{ 398 Type: SocketFilter, 399 Instructions: asm.Instructions{ 400 asm.LoadImm(asm.R0, 0, asm.DWord), 401 asm.Return(), 402 }, 403 License: "MIT", 404 } 405 406 cs := &CollectionSpec{ 407 Maps: map[string]*MapSpec{ 408 "map1": mapSpec, 409 }, 410 Programs: map[string]*ProgramSpec{ 411 "prog1": progSpec, 412 }, 413 } 414 415 if err := cs.Assign(&specs); err != nil { 416 t.Fatal("Can't assign spec:", err) 417 } 418 419 if specs.Program != progSpec { 420 t.Fatalf("Expected Program to be %p, got %p", progSpec, specs.Program) 421 } 422 423 if specs.Map != mapSpec { 424 t.Fatalf("Expected Map to be %p, got %p", mapSpec, specs.Map) 425 } 426 427 if err := cs.Assign(new(int)); err == nil { 428 t.Fatal("Assign allows to besides *struct") 429 } 430 431 if err := cs.Assign(new(struct{ Foo int })); err != nil { 432 t.Fatal("Assign doesn't ignore untagged fields") 433 } 434 435 unexported := new(struct { 436 foo *MapSpec `ebpf:"map1"` 437 }) 438 439 if err := cs.Assign(unexported); err == nil { 440 t.Error("Assign should return an error on unexported fields") 441 } 442 } 443 444 func TestAssignValues(t *testing.T) { 445 zero := func(t reflect.Type, name string) (interface{}, error) { 446 return reflect.Zero(t).Interface(), nil 447 } 448 449 type t1 struct { 450 Bar int `ebpf:"bar"` 451 } 452 453 type t2 struct { 454 t1 455 Foo int `ebpf:"foo"` 456 } 457 458 type t2ptr struct { 459 *t1 460 Foo int `ebpf:"foo"` 461 } 462 463 invalid := []struct { 464 name string 465 to interface{} 466 }{ 467 {"non-struct", 1}, 468 {"non-pointer struct", t1{}}, 469 {"pointer to non-struct", new(int)}, 470 {"embedded nil pointer", &t2ptr{}}, 471 {"unexported field", new(struct { 472 foo int `ebpf:"foo"` 473 })}, 474 {"identical tag", new(struct { 475 Foo1 int `ebpf:"foo"` 476 Foo2 int `ebpf:"foo"` 477 })}, 478 } 479 480 for _, testcase := range invalid { 481 t.Run(testcase.name, func(t *testing.T) { 482 if err := assignValues(testcase.to, zero); err == nil { 483 t.Fatal("assignValues didn't return an error") 484 } else { 485 t.Log(err) 486 } 487 }) 488 } 489 490 valid := []struct { 491 name string 492 to interface{} 493 }{ 494 {"pointer to struct", new(t1)}, 495 {"embedded struct", new(t2)}, 496 {"embedded struct pointer", &t2ptr{t1: new(t1)}}, 497 {"untagged field", new(struct{ Foo int })}, 498 } 499 500 for _, testcase := range valid { 501 t.Run(testcase.name, func(t *testing.T) { 502 if err := assignValues(testcase.to, zero); err != nil { 503 t.Fatal("assignValues returned", err) 504 } 505 }) 506 } 507 508 } 509 510 func TestIncompleteLoadAndAssign(t *testing.T) { 511 spec := &CollectionSpec{ 512 Programs: map[string]*ProgramSpec{ 513 "valid": { 514 Type: SocketFilter, 515 Instructions: asm.Instructions{ 516 asm.LoadImm(asm.R0, 0, asm.DWord), 517 asm.Return(), 518 }, 519 License: "MIT", 520 }, 521 "invalid": { 522 Type: SocketFilter, 523 Instructions: asm.Instructions{ 524 asm.Return(), 525 }, 526 License: "MIT", 527 }, 528 }, 529 } 530 531 s := struct { 532 // Assignment to Valid should execute and succeed. 533 Valid *Program `ebpf:"valid"` 534 // Assignment to Invalid should fail and cause Valid's fd to be closed. 535 Invalid *Program `ebpf:"invalid"` 536 }{} 537 538 if err := spec.LoadAndAssign(&s, nil); err == nil { 539 t.Fatal("expected error loading invalid ProgramSpec") 540 } 541 542 if fd := s.Valid.FD(); fd != -1 { 543 t.Fatal("expected valid prog to have closed fd -1, got:", fd) 544 } 545 546 if s.Invalid != nil { 547 t.Fatal("expected invalid prog to be nil due to never being assigned") 548 } 549 } 550 551 func ExampleCollectionSpec_Assign() { 552 spec := &CollectionSpec{ 553 Maps: map[string]*MapSpec{ 554 "map1": { 555 Type: Array, 556 KeySize: 4, 557 ValueSize: 4, 558 MaxEntries: 1, 559 }, 560 }, 561 Programs: map[string]*ProgramSpec{ 562 "prog1": { 563 Type: SocketFilter, 564 Instructions: asm.Instructions{ 565 asm.LoadImm(asm.R0, 0, asm.DWord), 566 asm.Return(), 567 }, 568 License: "MIT", 569 }, 570 }, 571 } 572 573 type maps struct { 574 Map *MapSpec `ebpf:"map1"` 575 } 576 577 var specs struct { 578 maps 579 Program *ProgramSpec `ebpf:"prog1"` 580 } 581 582 if err := spec.Assign(&specs); err != nil { 583 panic(err) 584 } 585 586 fmt.Println(specs.Program.Type) 587 fmt.Println(specs.Map.Type) 588 589 // Output: SocketFilter 590 // Array 591 } 592 593 func ExampleCollectionSpec_LoadAndAssign() { 594 spec := &CollectionSpec{ 595 Maps: map[string]*MapSpec{ 596 "map1": { 597 Type: Array, 598 KeySize: 4, 599 ValueSize: 4, 600 MaxEntries: 1, 601 }, 602 }, 603 Programs: map[string]*ProgramSpec{ 604 "prog1": { 605 Type: SocketFilter, 606 Instructions: asm.Instructions{ 607 asm.LoadImm(asm.R0, 0, asm.DWord), 608 asm.Return(), 609 }, 610 License: "MIT", 611 }, 612 }, 613 } 614 615 var objs struct { 616 Program *Program `ebpf:"prog1"` 617 Map *Map `ebpf:"map1"` 618 } 619 620 if err := spec.LoadAndAssign(&objs, nil); err != nil { 621 panic(err) 622 } 623 defer objs.Program.Close() 624 defer objs.Map.Close() 625 626 fmt.Println(objs.Program.Type()) 627 fmt.Println(objs.Map.Type()) 628 629 // Output: SocketFilter 630 // Array 631 }