github.com/sagernet/netlink@v0.0.0-20240612041022-b9a21c07ac6a/neigh_test.go (about) 1 //go:build linux 2 // +build linux 3 4 package netlink 5 6 import ( 7 "net" 8 "syscall" 9 "testing" 10 "time" 11 12 "github.com/vishvananda/netns" 13 "golang.org/x/sys/unix" 14 ) 15 16 type arpEntry struct { 17 ip net.IP 18 mac net.HardwareAddr 19 } 20 21 type proxyEntry struct { 22 ip net.IP 23 dev int 24 } 25 26 func parseMAC(s string) net.HardwareAddr { 27 m, err := net.ParseMAC(s) 28 if err != nil { 29 panic(err) 30 } 31 return m 32 } 33 34 func dumpContains(dump []Neigh, e arpEntry) bool { 35 for _, n := range dump { 36 if n.IP.Equal(e.ip) && (n.State&NUD_INCOMPLETE) == 0 { 37 return true 38 } 39 } 40 return false 41 } 42 43 func dumpContainsNeigh(dump []Neigh, ne Neigh) bool { 44 for _, n := range dump { 45 if n.IP.Equal(ne.IP) && n.LLIPAddr.Equal(ne.LLIPAddr) { 46 return true 47 } 48 } 49 return false 50 } 51 52 func dumpContainsState(dump []Neigh, e arpEntry, s uint16) bool { 53 for _, n := range dump { 54 if n.IP.Equal(e.ip) && uint16(n.State) == s { 55 return true 56 } 57 } 58 return false 59 } 60 61 func dumpContainsProxy(dump []Neigh, p proxyEntry) bool { 62 for _, n := range dump { 63 if n.IP.Equal(p.ip) && (n.LinkIndex == p.dev) && (n.Flags&NTF_PROXY) == NTF_PROXY { 64 return true 65 } 66 } 67 return false 68 } 69 70 func TestNeighAddDelLLIPAddr(t *testing.T) { 71 setUpNetlinkTestWithKModule(t, "ip_gre") 72 73 tearDown := setUpNetlinkTest(t) 74 defer tearDown() 75 76 dummy := Gretun{ 77 LinkAttrs: LinkAttrs{Name: "neigh0"}, 78 Local: net.IPv4(127, 0, 0, 1), 79 IKey: 1234, 80 OKey: 1234} 81 if err := LinkAdd(&dummy); err != nil { 82 t.Errorf("Failed to create link: %v", err) 83 } 84 ensureIndex(dummy.Attrs()) 85 86 entry := Neigh{ 87 LinkIndex: dummy.Index, 88 State: NUD_PERMANENT, 89 IP: net.IPv4(198, 51, 100, 2), 90 LLIPAddr: net.IPv4(198, 51, 100, 1), 91 } 92 93 err := NeighAdd(&entry) 94 if err != nil { 95 t.Errorf("Failed to NeighAdd: %v", err) 96 } 97 98 // Dump and see that all added entries are there 99 dump, err := NeighList(dummy.Index, 0) 100 if err != nil { 101 t.Errorf("Failed to NeighList: %v", err) 102 } 103 104 if !dumpContainsNeigh(dump, entry) { 105 t.Errorf("Dump does not contain: %v: %v", entry, dump) 106 } 107 108 // Delete the entry 109 err = NeighDel(&entry) 110 if err != nil { 111 t.Errorf("Failed to NeighDel: %v", err) 112 } 113 114 if err := LinkDel(&dummy); err != nil { 115 t.Fatal(err) 116 } 117 } 118 119 func TestNeighAddDel(t *testing.T) { 120 tearDown := setUpNetlinkTest(t) 121 defer tearDown() 122 123 dummy := Dummy{LinkAttrs{Name: "neigh0"}} 124 if err := LinkAdd(&dummy); err != nil { 125 t.Fatal(err) 126 } 127 128 ensureIndex(dummy.Attrs()) 129 130 arpTable := []arpEntry{ 131 {net.ParseIP("10.99.0.1"), parseMAC("aa:bb:cc:dd:00:01")}, 132 {net.ParseIP("10.99.0.2"), parseMAC("aa:bb:cc:dd:00:02")}, 133 {net.ParseIP("10.99.0.3"), parseMAC("aa:bb:cc:dd:00:03")}, 134 {net.ParseIP("10.99.0.4"), parseMAC("aa:bb:cc:dd:00:04")}, 135 {net.ParseIP("10.99.0.5"), parseMAC("aa:bb:cc:dd:00:05")}, 136 } 137 138 // Add the arpTable 139 for _, entry := range arpTable { 140 err := NeighAdd(&Neigh{ 141 LinkIndex: dummy.Index, 142 State: NUD_REACHABLE, 143 IP: entry.ip, 144 HardwareAddr: entry.mac, 145 }) 146 147 if err != nil { 148 t.Errorf("Failed to NeighAdd: %v", err) 149 } 150 } 151 152 // Dump and see that all added entries are there 153 dump, err := NeighList(dummy.Index, 0) 154 if err != nil { 155 t.Errorf("Failed to NeighList: %v", err) 156 } 157 158 for _, entry := range arpTable { 159 if !dumpContains(dump, entry) { 160 t.Errorf("Dump does not contain: %v", entry) 161 } 162 } 163 164 // Delete the arpTable 165 for _, entry := range arpTable { 166 err := NeighDel(&Neigh{ 167 LinkIndex: dummy.Index, 168 IP: entry.ip, 169 HardwareAddr: entry.mac, 170 }) 171 172 if err != nil { 173 t.Errorf("Failed to NeighDel: %v", err) 174 } 175 } 176 177 // TODO: seems not working because of cache 178 //// Dump and see that none of deleted entries are there 179 //dump, err = NeighList(dummy.Index, 0) 180 //if err != nil { 181 //t.Errorf("Failed to NeighList: %v", err) 182 //} 183 184 //for _, entry := range arpTable { 185 //if dumpContains(dump, entry) { 186 //t.Errorf("Dump contains: %v", entry) 187 //} 188 //} 189 190 if err := LinkDel(&dummy); err != nil { 191 t.Fatal(err) 192 } 193 } 194 195 func TestNeighAddDelProxy(t *testing.T) { 196 tearDown := setUpNetlinkTest(t) 197 defer tearDown() 198 199 dummy := Dummy{LinkAttrs{Name: "neigh0"}} 200 if err := LinkAdd(&dummy); err != nil { 201 t.Fatal(err) 202 } 203 204 ensureIndex(dummy.Attrs()) 205 206 proxyTable := []proxyEntry{ 207 {net.ParseIP("10.99.0.1"), dummy.Index}, 208 {net.ParseIP("10.99.0.2"), dummy.Index}, 209 {net.ParseIP("10.99.0.3"), dummy.Index}, 210 {net.ParseIP("10.99.0.4"), dummy.Index}, 211 {net.ParseIP("10.99.0.5"), dummy.Index}, 212 } 213 214 // Add the proxyTable 215 for _, entry := range proxyTable { 216 err := NeighAdd(&Neigh{ 217 LinkIndex: dummy.Index, 218 Flags: NTF_PROXY, 219 IP: entry.ip, 220 }) 221 222 if err != nil { 223 t.Errorf("Failed to NeighAdd: %v", err) 224 } 225 } 226 227 // Dump and see that all added entries are there 228 dump, err := NeighProxyList(dummy.Index, 0) 229 if err != nil { 230 t.Errorf("Failed to NeighList: %v", err) 231 } 232 233 for _, entry := range proxyTable { 234 if !dumpContainsProxy(dump, entry) { 235 t.Errorf("Dump does not contain: %v", entry) 236 } 237 } 238 239 // Delete the proxyTable 240 for _, entry := range proxyTable { 241 err := NeighDel(&Neigh{ 242 LinkIndex: dummy.Index, 243 Flags: NTF_PROXY, 244 IP: entry.ip, 245 }) 246 247 if err != nil { 248 t.Errorf("Failed to NeighDel: %v", err) 249 } 250 } 251 252 // Dump and see that none of deleted entries are there 253 dump, err = NeighProxyList(dummy.Index, 0) 254 if err != nil { 255 t.Errorf("Failed to NeighList: %v", err) 256 } 257 258 for _, entry := range proxyTable { 259 if dumpContainsProxy(dump, entry) { 260 t.Errorf("Dump contains: %v", entry) 261 } 262 } 263 264 if err := LinkDel(&dummy); err != nil { 265 t.Fatal(err) 266 } 267 } 268 269 // expectNeighUpdate returns whether the expected updates are received within one second. 270 func expectNeighUpdate(ch <-chan NeighUpdate, expected []NeighUpdate) bool { 271 for { 272 timeout := time.After(time.Second) 273 select { 274 case update := <-ch: 275 var toDelete []int 276 for index, elem := range expected { 277 if update.Type == elem.Type && 278 update.Neigh.State == elem.Neigh.State && 279 update.Neigh.IP != nil && 280 update.Neigh.IP.Equal(elem.Neigh.IP) { 281 toDelete = append(toDelete, index) 282 } 283 } 284 for done, index := range toDelete { 285 expected = append(expected[:index-done], expected[index-done+1:]...) 286 } 287 if len(expected) == 0 { 288 return true 289 } 290 case <-timeout: 291 return false 292 } 293 } 294 } 295 296 func TestNeighSubscribe(t *testing.T) { 297 tearDown := setUpNetlinkTest(t) 298 defer tearDown() 299 300 dummy := &Dummy{LinkAttrs{Name: "neigh0"}} 301 if err := LinkAdd(dummy); err != nil { 302 t.Errorf("Failed to create link: %v", err) 303 } 304 ensureIndex(dummy.Attrs()) 305 defer func() { 306 if err := LinkDel(dummy); err != nil { 307 t.Fatal(err) 308 } 309 }() 310 311 ch := make(chan NeighUpdate) 312 done := make(chan struct{}) 313 defer close(done) 314 if err := NeighSubscribe(ch, done); err != nil { 315 t.Fatal(err) 316 } 317 318 entry := &Neigh{ 319 LinkIndex: dummy.Index, 320 State: NUD_REACHABLE, 321 IP: net.IPv4(10, 99, 0, 1), 322 HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), 323 } 324 325 if err := NeighAdd(entry); err != nil { 326 t.Errorf("Failed to NeighAdd: %v", err) 327 } 328 if !expectNeighUpdate(ch, []NeighUpdate{{ 329 Type: unix.RTM_NEWNEIGH, 330 Neigh: *entry, 331 }}) { 332 t.Fatalf("Add update not received as expected") 333 } 334 if err := NeighDel(entry); err != nil { 335 t.Fatal(err) 336 } 337 if !expectNeighUpdate(ch, []NeighUpdate{{ 338 Type: unix.RTM_NEWNEIGH, 339 Neigh: Neigh{ 340 State: NUD_FAILED, 341 IP: entry.IP}, 342 }}) { 343 t.Fatalf("Del update not received as expected") 344 } 345 } 346 347 func TestNeighSubscribeWithOptions(t *testing.T) { 348 tearDown := setUpNetlinkTest(t) 349 defer tearDown() 350 351 ch := make(chan NeighUpdate) 352 done := make(chan struct{}) 353 defer close(done) 354 var lastError error 355 defer func() { 356 if lastError != nil { 357 t.Fatalf("Fatal error received during subscription: %v", lastError) 358 } 359 }() 360 if err := NeighSubscribeWithOptions(ch, done, NeighSubscribeOptions{ 361 ErrorCallback: func(err error) { 362 lastError = err 363 }, 364 }); err != nil { 365 t.Fatal(err) 366 } 367 368 dummy := &Dummy{LinkAttrs{Name: "neigh0"}} 369 if err := LinkAdd(dummy); err != nil { 370 t.Errorf("Failed to create link: %v", err) 371 } 372 ensureIndex(dummy.Attrs()) 373 defer func() { 374 if err := LinkDel(dummy); err != nil { 375 t.Fatal(err) 376 } 377 }() 378 379 entry := &Neigh{ 380 LinkIndex: dummy.Index, 381 State: NUD_REACHABLE, 382 IP: net.IPv4(10, 99, 0, 1), 383 HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), 384 } 385 386 err := NeighAdd(entry) 387 if err != nil { 388 t.Errorf("Failed to NeighAdd: %v", err) 389 } 390 if !expectNeighUpdate(ch, []NeighUpdate{{ 391 Type: unix.RTM_NEWNEIGH, 392 Neigh: *entry, 393 }}) { 394 t.Fatalf("Add update not received as expected") 395 } 396 } 397 398 func TestNeighSubscribeAt(t *testing.T) { 399 skipUnlessRoot(t) 400 401 // Create an handle on a custom netns 402 newNs, err := netns.New() 403 if err != nil { 404 t.Fatal(err) 405 } 406 defer newNs.Close() 407 408 nh, err := NewHandleAt(newNs) 409 if err != nil { 410 t.Fatal(err) 411 } 412 defer nh.Close() 413 414 // Subscribe for Neigh events on the custom netns 415 ch := make(chan NeighUpdate) 416 done := make(chan struct{}) 417 defer close(done) 418 if err := NeighSubscribeAt(newNs, ch, done); err != nil { 419 t.Fatal(err) 420 } 421 422 dummy := &Dummy{LinkAttrs{Name: "neigh0"}} 423 if err := nh.LinkAdd(dummy); err != nil { 424 t.Errorf("Failed to create link: %v", err) 425 } 426 ensureIndex(dummy.Attrs()) 427 defer func() { 428 if err := nh.LinkDel(dummy); err != nil { 429 t.Fatal(err) 430 } 431 }() 432 433 entry := &Neigh{ 434 LinkIndex: dummy.Index, 435 State: NUD_REACHABLE, 436 IP: net.IPv4(198, 51, 100, 1), 437 HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), 438 } 439 440 err = nh.NeighAdd(entry) 441 if err != nil { 442 t.Errorf("Failed to NeighAdd: %v", err) 443 } 444 if !expectNeighUpdate(ch, []NeighUpdate{{ 445 Type: unix.RTM_NEWNEIGH, 446 Neigh: *entry, 447 }}) { 448 t.Fatalf("Add update not received as expected") 449 } 450 } 451 452 func TestNeighSubscribeListExisting(t *testing.T) { 453 skipUnlessRoot(t) 454 455 // Create an handle on a custom netns 456 newNs, err := netns.New() 457 if err != nil { 458 t.Fatal(err) 459 } 460 defer newNs.Close() 461 462 nh, err := NewHandleAt(newNs) 463 if err != nil { 464 t.Fatal(err) 465 } 466 defer nh.Close() 467 468 dummy := &Dummy{LinkAttrs{Name: "neigh0"}} 469 if err := nh.LinkAdd(dummy); err != nil { 470 t.Errorf("Failed to create link: %v", err) 471 } 472 ensureIndex(dummy.Attrs()) 473 defer func() { 474 if err := nh.LinkDel(dummy); err != nil { 475 t.Fatal(err) 476 } 477 }() 478 479 vxlani := &Vxlan{LinkAttrs: LinkAttrs{Name: "neigh1"}, VxlanId: 1} 480 if err := nh.LinkAdd(vxlani); err != nil { 481 t.Errorf("Failed to create link: %v", err) 482 } 483 ensureIndex(vxlani.Attrs()) 484 defer func() { 485 if err := nh.LinkDel(vxlani); err != nil { 486 t.Fatal(err) 487 } 488 }() 489 490 entry1 := &Neigh{ 491 LinkIndex: dummy.Index, 492 State: NUD_REACHABLE, 493 IP: net.IPv4(198, 51, 100, 1), 494 HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), 495 } 496 497 entryBr := &Neigh{ 498 Family: syscall.AF_BRIDGE, 499 LinkIndex: vxlani.Index, 500 State: NUD_PERMANENT, 501 Flags: NTF_SELF, 502 IP: net.IPv4(198, 51, 100, 3), 503 HardwareAddr: parseMAC("aa:bb:cc:dd:00:03"), 504 } 505 506 err = nh.NeighAdd(entry1) 507 if err != nil { 508 t.Errorf("Failed to NeighAdd: %v", err) 509 } 510 err = nh.NeighAppend(entryBr) 511 if err != nil { 512 t.Errorf("Failed to NeighAdd: %v", err) 513 } 514 515 // Subscribe for Neigh events including existing neighbors 516 ch := make(chan NeighUpdate) 517 done := make(chan struct{}) 518 defer close(done) 519 if err := NeighSubscribeWithOptions(ch, done, NeighSubscribeOptions{ 520 Namespace: &newNs, 521 ListExisting: true}, 522 ); err != nil { 523 t.Fatal(err) 524 } 525 526 if !expectNeighUpdate(ch, []NeighUpdate{ 527 { 528 Type: unix.RTM_NEWNEIGH, 529 Neigh: *entry1, 530 }, 531 { 532 Type: unix.RTM_NEWNEIGH, 533 Neigh: *entryBr, 534 }, 535 }) { 536 t.Fatalf("Existing add update not received as expected") 537 } 538 539 entry2 := &Neigh{ 540 LinkIndex: dummy.Index, 541 State: NUD_PERMANENT, 542 IP: net.IPv4(198, 51, 100, 2), 543 HardwareAddr: parseMAC("aa:bb:cc:dd:00:02"), 544 } 545 546 err = nh.NeighAdd(entry2) 547 if err != nil { 548 t.Errorf("Failed to NeighAdd: %v", err) 549 } 550 551 if !expectNeighUpdate(ch, []NeighUpdate{{ 552 Type: unix.RTM_NEWNEIGH, 553 Neigh: *entry2, 554 }}) { 555 t.Fatalf("Existing add update not received as expected") 556 } 557 } 558 559 func TestNeighListExecuteStateFilter(t *testing.T) { 560 tearDown := setUpNetlinkTest(t) 561 defer tearDown() 562 563 // Create dummy iface 564 dummy := Dummy{LinkAttrs{Name: "neigh0"}} 565 if err := LinkAdd(&dummy); err != nil { 566 t.Fatal(err) 567 } 568 569 ensureIndex(dummy.Attrs()) 570 571 // Define some entries 572 reachArpTable := []arpEntry{ 573 {net.ParseIP("198.51.100.1"), parseMAC("44:bb:cc:dd:00:01")}, 574 {net.ParseIP("2001:db8::1"), parseMAC("66:bb:cc:dd:00:02")}, 575 } 576 577 staleArpTable := []arpEntry{ 578 {net.ParseIP("198.51.100.10"), parseMAC("44:bb:cc:dd:00:10")}, 579 {net.ParseIP("2001:db8::10"), parseMAC("66:bb:cc:dd:00:10")}, 580 } 581 582 entries := append(reachArpTable, staleArpTable...) 583 584 // Add reachable neigh entries 585 for _, entry := range reachArpTable { 586 err := NeighAdd(&Neigh{ 587 LinkIndex: dummy.Index, 588 State: NUD_REACHABLE, 589 IP: entry.ip, 590 HardwareAddr: entry.mac, 591 }) 592 593 if err != nil { 594 t.Errorf("Failed to NeighAdd: %v", err) 595 } 596 } 597 // Add stale neigh entries 598 for _, entry := range staleArpTable { 599 err := NeighAdd(&Neigh{ 600 LinkIndex: dummy.Index, 601 State: NUD_STALE, 602 IP: entry.ip, 603 HardwareAddr: entry.mac, 604 }) 605 606 if err != nil { 607 t.Errorf("Failed to NeighAdd: %v", err) 608 } 609 } 610 611 // Dump reachable and see that all added reachable entries are present and there are no stale entries 612 dump, err := NeighListExecute(Ndmsg{ 613 Index: uint32(dummy.Index), 614 State: NUD_REACHABLE, 615 }) 616 if err != nil { 617 t.Errorf("Failed to NeighListExecute: %v", err) 618 } 619 620 for _, entry := range reachArpTable { 621 if !dumpContainsState(dump, entry, NUD_REACHABLE) { 622 t.Errorf("Dump does not contains: %v", entry) 623 } 624 } 625 for _, entry := range staleArpTable { 626 if dumpContainsState(dump, entry, NUD_STALE) { 627 t.Errorf("Dump contains: %v", entry) 628 } 629 } 630 631 // Delete all neigh entries 632 for _, entry := range entries { 633 err := NeighDel(&Neigh{ 634 LinkIndex: dummy.Index, 635 IP: entry.ip, 636 HardwareAddr: entry.mac, 637 }) 638 639 if err != nil { 640 t.Errorf("Failed to NeighDel: %v", err) 641 } 642 } 643 }