github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/tcpip/stack/neighbor_cache_test.go (about) 1 // Copyright 2019 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package stack 16 17 import ( 18 "fmt" 19 "math" 20 "math/rand" 21 "strings" 22 "sync" 23 "sync/atomic" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 "github.com/SagerNet/gvisor/pkg/tcpip" 30 "github.com/SagerNet/gvisor/pkg/tcpip/faketime" 31 ) 32 33 const ( 34 // entryStoreSize is the default number of entries that will be generated and 35 // added to the entry store. This number needs to be larger than the size of 36 // the neighbor cache to give ample opportunity for verifying behavior during 37 // cache overflows. Four times the size of the neighbor cache allows for 38 // three complete cache overflows. 39 entryStoreSize = 4 * neighborCacheSize 40 41 // typicalLatency is the typical latency for an ARP or NDP packet to travel 42 // to a router and back. 43 typicalLatency = time.Millisecond 44 45 // testEntryBroadcastAddr is a special address that indicates a packet should 46 // be sent to all nodes. 47 testEntryBroadcastAddr = tcpip.Address("broadcast") 48 49 // testEntryBroadcastLinkAddr is a special link address sent back to 50 // multicast neighbor probes. 51 testEntryBroadcastLinkAddr = tcpip.LinkAddress("mac_broadcast") 52 53 // infiniteDuration indicates that a task will not occur in our lifetime. 54 infiniteDuration = time.Duration(math.MaxInt64) 55 ) 56 57 // unorderedEventsDiffOpts returns options passed to cmp.Diff to sort slices of 58 // events for cases where ordering must be ignored. 59 func unorderedEventsDiffOpts() []cmp.Option { 60 return []cmp.Option{ 61 cmpopts.SortSlices(func(a, b testEntryEventInfo) bool { 62 return strings.Compare(string(a.Entry.Addr), string(b.Entry.Addr)) < 0 63 }), 64 } 65 } 66 67 // unorderedEntriesDiffOpts returns options passed to cmp.Diff to sort slices of 68 // entries for cases where ordering must be ignored. 69 func unorderedEntriesDiffOpts() []cmp.Option { 70 return []cmp.Option{ 71 cmpopts.SortSlices(func(a, b NeighborEntry) bool { 72 return strings.Compare(string(a.Addr), string(b.Addr)) < 0 73 }), 74 } 75 } 76 77 func newTestNeighborResolver(nudDisp NUDDispatcher, config NUDConfigurations, clock tcpip.Clock) *testNeighborResolver { 78 config.resetInvalidFields() 79 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 80 linkRes := &testNeighborResolver{ 81 clock: clock, 82 entries: newTestEntryStore(), 83 delay: typicalLatency, 84 } 85 linkRes.neigh.init(&nic{ 86 stack: &Stack{ 87 clock: clock, 88 nudDisp: nudDisp, 89 nudConfigs: config, 90 randomGenerator: rng, 91 }, 92 id: 1, 93 stats: makeNICStats(tcpip.NICStats{}.FillIn()), 94 }, linkRes) 95 return linkRes 96 } 97 98 // testEntryStore contains a set of IP to NeighborEntry mappings. 99 type testEntryStore struct { 100 mu sync.RWMutex 101 entriesMap map[tcpip.Address]NeighborEntry 102 } 103 104 func toAddress(i uint16) tcpip.Address { 105 return tcpip.Address([]byte{ 106 1, 107 0, 108 byte(i >> 8), 109 byte(i), 110 }) 111 } 112 113 func toLinkAddress(i uint16) tcpip.LinkAddress { 114 return tcpip.LinkAddress([]byte{ 115 1, 116 0, 117 0, 118 0, 119 byte(i >> 8), 120 byte(i), 121 }) 122 } 123 124 // newTestEntryStore returns a testEntryStore pre-populated with entries. 125 func newTestEntryStore() *testEntryStore { 126 store := &testEntryStore{ 127 entriesMap: make(map[tcpip.Address]NeighborEntry), 128 } 129 for i := uint16(0); i < entryStoreSize; i++ { 130 addr := toAddress(i) 131 linkAddr := toLinkAddress(i) 132 133 store.entriesMap[addr] = NeighborEntry{ 134 Addr: addr, 135 LinkAddr: linkAddr, 136 } 137 } 138 return store 139 } 140 141 // size returns the number of entries in the store. 142 func (s *testEntryStore) size() uint16 { 143 s.mu.RLock() 144 defer s.mu.RUnlock() 145 return uint16(len(s.entriesMap)) 146 } 147 148 // entry returns the entry at index i. Returns an empty entry and false if i is 149 // out of bounds. 150 func (s *testEntryStore) entry(i uint16) (NeighborEntry, bool) { 151 return s.entryByAddr(toAddress(i)) 152 } 153 154 // entryByAddr returns the entry matching addr for situations when the index is 155 // not available. Returns an empty entry and false if no entries match addr. 156 func (s *testEntryStore) entryByAddr(addr tcpip.Address) (NeighborEntry, bool) { 157 s.mu.RLock() 158 defer s.mu.RUnlock() 159 entry, ok := s.entriesMap[addr] 160 return entry, ok 161 } 162 163 // entries returns all entries in the store. 164 func (s *testEntryStore) entries() []NeighborEntry { 165 entries := make([]NeighborEntry, 0, len(s.entriesMap)) 166 s.mu.RLock() 167 defer s.mu.RUnlock() 168 for i := uint16(0); i < entryStoreSize; i++ { 169 addr := toAddress(i) 170 if entry, ok := s.entriesMap[addr]; ok { 171 entries = append(entries, entry) 172 } 173 } 174 return entries 175 } 176 177 // set modifies the link addresses of an entry. 178 func (s *testEntryStore) set(i uint16, linkAddr tcpip.LinkAddress) { 179 addr := toAddress(i) 180 s.mu.Lock() 181 defer s.mu.Unlock() 182 if entry, ok := s.entriesMap[addr]; ok { 183 entry.LinkAddr = linkAddr 184 s.entriesMap[addr] = entry 185 } 186 } 187 188 // testNeighborResolver implements LinkAddressResolver to emulate sending a 189 // neighbor probe. 190 type testNeighborResolver struct { 191 clock tcpip.Clock 192 neigh neighborCache 193 entries *testEntryStore 194 delay time.Duration 195 onLinkAddressRequest func() 196 dropReplies bool 197 } 198 199 var _ LinkAddressResolver = (*testNeighborResolver)(nil) 200 201 func (r *testNeighborResolver) LinkAddressRequest(targetAddr, _ tcpip.Address, _ tcpip.LinkAddress) tcpip.Error { 202 if !r.dropReplies { 203 // Delay handling the request to emulate network latency. 204 r.clock.AfterFunc(r.delay, func() { 205 r.fakeRequest(targetAddr) 206 }) 207 } 208 209 // Execute post address resolution action, if available. 210 if f := r.onLinkAddressRequest; f != nil { 211 f() 212 } 213 return nil 214 } 215 216 // fakeRequest emulates handling a response for a link address request. 217 func (r *testNeighborResolver) fakeRequest(addr tcpip.Address) { 218 if entry, ok := r.entries.entryByAddr(addr); ok { 219 r.neigh.handleConfirmation(addr, entry.LinkAddr, ReachabilityConfirmationFlags{ 220 Solicited: true, 221 Override: false, 222 IsRouter: false, 223 }) 224 } 225 } 226 227 func (*testNeighborResolver) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) { 228 if addr == testEntryBroadcastAddr { 229 return testEntryBroadcastLinkAddr, true 230 } 231 return "", false 232 } 233 234 func (*testNeighborResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber { 235 return 0 236 } 237 238 func TestNeighborCacheGetConfig(t *testing.T) { 239 nudDisp := testNUDDispatcher{} 240 c := DefaultNUDConfigurations() 241 clock := faketime.NewManualClock() 242 linkRes := newTestNeighborResolver(&nudDisp, c, clock) 243 244 if got, want := linkRes.neigh.config(), c; got != want { 245 t.Errorf("got linkRes.neigh.config() = %+v, want = %+v", got, want) 246 } 247 248 // No events should have been dispatched. 249 nudDisp.mu.Lock() 250 defer nudDisp.mu.Unlock() 251 if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" { 252 t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 253 } 254 } 255 256 func TestNeighborCacheSetConfig(t *testing.T) { 257 nudDisp := testNUDDispatcher{} 258 c := DefaultNUDConfigurations() 259 clock := faketime.NewManualClock() 260 linkRes := newTestNeighborResolver(&nudDisp, c, clock) 261 262 c.MinRandomFactor = 1 263 c.MaxRandomFactor = 1 264 linkRes.neigh.setConfig(c) 265 266 if got, want := linkRes.neigh.config(), c; got != want { 267 t.Errorf("got linkRes.neigh.config() = %+v, want = %+v", got, want) 268 } 269 270 // No events should have been dispatched. 271 nudDisp.mu.Lock() 272 defer nudDisp.mu.Unlock() 273 if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" { 274 t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 275 } 276 } 277 278 func addReachableEntryWithRemoved(nudDisp *testNUDDispatcher, clock *faketime.ManualClock, linkRes *testNeighborResolver, entry NeighborEntry, removed []NeighborEntry) error { 279 var gotLinkResolutionResult LinkResolutionResult 280 281 _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) { 282 gotLinkResolutionResult = r 283 }) 284 if _, ok := err.(*tcpip.ErrWouldBlock); !ok { 285 return fmt.Errorf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{}) 286 } 287 288 { 289 var wantEvents []testEntryEventInfo 290 291 for _, removedEntry := range removed { 292 wantEvents = append(wantEvents, testEntryEventInfo{ 293 EventType: entryTestRemoved, 294 NICID: 1, 295 Entry: NeighborEntry{ 296 Addr: removedEntry.Addr, 297 LinkAddr: removedEntry.LinkAddr, 298 State: Reachable, 299 UpdatedAt: clock.Now(), 300 }, 301 }) 302 } 303 304 wantEvents = append(wantEvents, testEntryEventInfo{ 305 EventType: entryTestAdded, 306 NICID: 1, 307 Entry: NeighborEntry{ 308 Addr: entry.Addr, 309 LinkAddr: "", 310 State: Incomplete, 311 UpdatedAt: clock.Now(), 312 }, 313 }) 314 315 nudDisp.mu.Lock() 316 diff := cmp.Diff(wantEvents, nudDisp.mu.events) 317 nudDisp.mu.events = nil 318 nudDisp.mu.Unlock() 319 if diff != "" { 320 return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 321 } 322 } 323 324 clock.Advance(typicalLatency) 325 326 select { 327 case <-ch: 328 default: 329 return fmt.Errorf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr) 330 } 331 wantLinkResolutionResult := LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil} 332 if diff := cmp.Diff(wantLinkResolutionResult, gotLinkResolutionResult); diff != "" { 333 return fmt.Errorf("got link resolution result mismatch (-want +got):\n%s", diff) 334 } 335 336 { 337 wantEvents := []testEntryEventInfo{ 338 { 339 EventType: entryTestChanged, 340 NICID: 1, 341 Entry: NeighborEntry{ 342 Addr: entry.Addr, 343 LinkAddr: entry.LinkAddr, 344 State: Reachable, 345 UpdatedAt: clock.Now(), 346 }, 347 }, 348 } 349 nudDisp.mu.Lock() 350 diff := cmp.Diff(wantEvents, nudDisp.mu.events) 351 nudDisp.mu.events = nil 352 nudDisp.mu.Unlock() 353 if diff != "" { 354 return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 355 } 356 } 357 358 return nil 359 } 360 361 func addReachableEntry(nudDisp *testNUDDispatcher, clock *faketime.ManualClock, linkRes *testNeighborResolver, entry NeighborEntry) error { 362 return addReachableEntryWithRemoved(nudDisp, clock, linkRes, entry, nil /* removed */) 363 } 364 365 func TestNeighborCacheEntry(t *testing.T) { 366 c := DefaultNUDConfigurations() 367 nudDisp := testNUDDispatcher{} 368 clock := faketime.NewManualClock() 369 linkRes := newTestNeighborResolver(&nudDisp, c, clock) 370 371 entry, ok := linkRes.entries.entry(0) 372 if !ok { 373 t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ") 374 } 375 if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil { 376 t.Fatalf("addReachableEntry(...) = %s", err) 377 } 378 379 if _, _, err := linkRes.neigh.entry(entry.Addr, "", nil); err != nil { 380 t.Fatalf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err) 381 } 382 383 // No more events should have been dispatched. 384 nudDisp.mu.Lock() 385 defer nudDisp.mu.Unlock() 386 if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" { 387 t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 388 } 389 } 390 391 func TestNeighborCacheRemoveEntry(t *testing.T) { 392 config := DefaultNUDConfigurations() 393 394 nudDisp := testNUDDispatcher{} 395 clock := faketime.NewManualClock() 396 linkRes := newTestNeighborResolver(&nudDisp, config, clock) 397 398 entry, ok := linkRes.entries.entry(0) 399 if !ok { 400 t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ") 401 } 402 if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil { 403 t.Fatalf("addReachableEntry(...) = %s", err) 404 } 405 406 linkRes.neigh.removeEntry(entry.Addr) 407 408 { 409 wantEvents := []testEntryEventInfo{ 410 { 411 EventType: entryTestRemoved, 412 NICID: 1, 413 Entry: NeighborEntry{ 414 Addr: entry.Addr, 415 LinkAddr: entry.LinkAddr, 416 State: Reachable, 417 UpdatedAt: clock.Now(), 418 }, 419 }, 420 } 421 nudDisp.mu.Lock() 422 diff := cmp.Diff(wantEvents, nudDisp.mu.events) 423 nudDisp.mu.Unlock() 424 if diff != "" { 425 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 426 } 427 } 428 429 { 430 _, _, err := linkRes.neigh.entry(entry.Addr, "", nil) 431 if _, ok := err.(*tcpip.ErrWouldBlock); !ok { 432 t.Errorf("got linkRes.neigh.entry(%s, '', nil) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{}) 433 } 434 } 435 } 436 437 type testContext struct { 438 clock *faketime.ManualClock 439 linkRes *testNeighborResolver 440 nudDisp *testNUDDispatcher 441 } 442 443 func newTestContext(c NUDConfigurations) testContext { 444 nudDisp := &testNUDDispatcher{} 445 clock := faketime.NewManualClock() 446 linkRes := newTestNeighborResolver(nudDisp, c, clock) 447 448 return testContext{ 449 clock: clock, 450 linkRes: linkRes, 451 nudDisp: nudDisp, 452 } 453 } 454 455 type overflowOptions struct { 456 startAtEntryIndex uint16 457 wantStaticEntries []NeighborEntry 458 } 459 460 func (c *testContext) overflowCache(opts overflowOptions) error { 461 // Fill the neighbor cache to capacity to verify the LRU eviction strategy is 462 // working properly after the entry removal. 463 for i := opts.startAtEntryIndex; i < c.linkRes.entries.size(); i++ { 464 var removedEntries []NeighborEntry 465 466 // When beyond the full capacity, the cache will evict an entry as per the 467 // LRU eviction strategy. Note that the number of static entries should not 468 // affect the total number of dynamic entries that can be added. 469 if i >= neighborCacheSize+opts.startAtEntryIndex { 470 removedEntry, ok := c.linkRes.entries.entry(i - neighborCacheSize) 471 if !ok { 472 return fmt.Errorf("got linkRes.entries.entry(%d) = _, false, want = true", i-neighborCacheSize) 473 } 474 removedEntries = append(removedEntries, removedEntry) 475 } 476 477 entry, ok := c.linkRes.entries.entry(i) 478 if !ok { 479 return fmt.Errorf("got c.linkRes.entries.entry(%d) = _, false, want = true", i) 480 } 481 if err := addReachableEntryWithRemoved(c.nudDisp, c.clock, c.linkRes, entry, removedEntries); err != nil { 482 return fmt.Errorf("addReachableEntryWithRemoved(...) = %s", err) 483 } 484 } 485 486 // Expect to find only the most recent entries. The order of entries reported 487 // by entries() is nondeterministic, so entries have to be sorted before 488 // comparison. 489 wantUnorderedEntries := opts.wantStaticEntries 490 for i := c.linkRes.entries.size() - neighborCacheSize; i < c.linkRes.entries.size(); i++ { 491 entry, ok := c.linkRes.entries.entry(i) 492 if !ok { 493 return fmt.Errorf("got c.linkRes.entries.entry(%d) = _, false, want = true", i) 494 } 495 durationReachableNanos := time.Duration(c.linkRes.entries.size()-i-1) * typicalLatency 496 wantEntry := NeighborEntry{ 497 Addr: entry.Addr, 498 LinkAddr: entry.LinkAddr, 499 State: Reachable, 500 UpdatedAt: c.clock.Now().Add(-durationReachableNanos), 501 } 502 wantUnorderedEntries = append(wantUnorderedEntries, wantEntry) 503 } 504 505 if diff := cmp.Diff(wantUnorderedEntries, c.linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" { 506 return fmt.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff) 507 } 508 509 // No more events should have been dispatched. 510 c.nudDisp.mu.Lock() 511 defer c.nudDisp.mu.Unlock() 512 if diff := cmp.Diff([]testEntryEventInfo(nil), c.nudDisp.mu.events); diff != "" { 513 return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 514 } 515 516 return nil 517 } 518 519 // TestNeighborCacheOverflow verifies that the LRU cache eviction strategy 520 // respects the dynamic entry count. 521 func TestNeighborCacheOverflow(t *testing.T) { 522 config := DefaultNUDConfigurations() 523 // Stay in Reachable so the cache can overflow 524 config.BaseReachableTime = infiniteDuration 525 config.MinRandomFactor = 1 526 config.MaxRandomFactor = 1 527 528 c := newTestContext(config) 529 opts := overflowOptions{ 530 startAtEntryIndex: 0, 531 } 532 if err := c.overflowCache(opts); err != nil { 533 t.Errorf("c.overflowCache(%+v): %s", opts, err) 534 } 535 } 536 537 // TestNeighborCacheRemoveEntryThenOverflow verifies that the LRU cache 538 // eviction strategy respects the dynamic entry count when an entry is removed. 539 func TestNeighborCacheRemoveEntryThenOverflow(t *testing.T) { 540 config := DefaultNUDConfigurations() 541 // Stay in Reachable so the cache can overflow 542 config.BaseReachableTime = infiniteDuration 543 config.MinRandomFactor = 1 544 config.MaxRandomFactor = 1 545 546 c := newTestContext(config) 547 548 // Add a dynamic entry 549 entry, ok := c.linkRes.entries.entry(0) 550 if !ok { 551 t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ") 552 } 553 if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil { 554 t.Fatalf("addReachableEntry(...) = %s", err) 555 } 556 557 // Remove the entry 558 c.linkRes.neigh.removeEntry(entry.Addr) 559 560 { 561 wantEvents := []testEntryEventInfo{ 562 { 563 EventType: entryTestRemoved, 564 NICID: 1, 565 Entry: NeighborEntry{ 566 Addr: entry.Addr, 567 LinkAddr: entry.LinkAddr, 568 State: Reachable, 569 UpdatedAt: c.clock.Now(), 570 }, 571 }, 572 } 573 c.nudDisp.mu.Lock() 574 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 575 c.nudDisp.mu.events = nil 576 c.nudDisp.mu.Unlock() 577 if diff != "" { 578 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 579 } 580 } 581 582 opts := overflowOptions{ 583 startAtEntryIndex: 0, 584 } 585 if err := c.overflowCache(opts); err != nil { 586 t.Errorf("c.overflowCache(%+v): %s", opts, err) 587 } 588 } 589 590 // TestNeighborCacheDuplicateStaticEntryWithSameLinkAddress verifies that 591 // adding a duplicate static entry with the same link address does not dispatch 592 // any events. 593 func TestNeighborCacheDuplicateStaticEntryWithSameLinkAddress(t *testing.T) { 594 config := DefaultNUDConfigurations() 595 c := newTestContext(config) 596 597 // Add a static entry 598 entry, ok := c.linkRes.entries.entry(0) 599 if !ok { 600 t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ") 601 } 602 staticLinkAddr := entry.LinkAddr + "static" 603 c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr) 604 605 { 606 wantEvents := []testEntryEventInfo{ 607 { 608 EventType: entryTestAdded, 609 NICID: 1, 610 Entry: NeighborEntry{ 611 Addr: entry.Addr, 612 LinkAddr: staticLinkAddr, 613 State: Static, 614 UpdatedAt: c.clock.Now(), 615 }, 616 }, 617 } 618 c.nudDisp.mu.Lock() 619 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 620 c.nudDisp.mu.events = nil 621 c.nudDisp.mu.Unlock() 622 if diff != "" { 623 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 624 } 625 } 626 627 // Add a duplicate static entry with the same link address. 628 c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr) 629 630 c.nudDisp.mu.Lock() 631 defer c.nudDisp.mu.Unlock() 632 if diff := cmp.Diff([]testEntryEventInfo(nil), c.nudDisp.mu.events); diff != "" { 633 t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 634 } 635 } 636 637 // TestNeighborCacheDuplicateStaticEntryWithDifferentLinkAddress verifies that 638 // adding a duplicate static entry with a different link address dispatches a 639 // change event. 640 func TestNeighborCacheDuplicateStaticEntryWithDifferentLinkAddress(t *testing.T) { 641 config := DefaultNUDConfigurations() 642 c := newTestContext(config) 643 644 // Add a static entry 645 entry, ok := c.linkRes.entries.entry(0) 646 if !ok { 647 t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ") 648 } 649 staticLinkAddr := entry.LinkAddr + "static" 650 c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr) 651 652 { 653 wantEvents := []testEntryEventInfo{ 654 { 655 EventType: entryTestAdded, 656 NICID: 1, 657 Entry: NeighborEntry{ 658 Addr: entry.Addr, 659 LinkAddr: staticLinkAddr, 660 State: Static, 661 UpdatedAt: c.clock.Now(), 662 }, 663 }, 664 } 665 c.nudDisp.mu.Lock() 666 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 667 c.nudDisp.mu.events = nil 668 c.nudDisp.mu.Unlock() 669 if diff != "" { 670 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 671 } 672 } 673 674 // Add a duplicate entry with a different link address 675 staticLinkAddr += "duplicate" 676 c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr) 677 678 { 679 wantEvents := []testEntryEventInfo{ 680 { 681 EventType: entryTestChanged, 682 NICID: 1, 683 Entry: NeighborEntry{ 684 Addr: entry.Addr, 685 LinkAddr: staticLinkAddr, 686 State: Static, 687 UpdatedAt: c.clock.Now(), 688 }, 689 }, 690 } 691 c.nudDisp.mu.Lock() 692 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 693 c.nudDisp.mu.events = nil 694 c.nudDisp.mu.Unlock() 695 if diff != "" { 696 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 697 } 698 } 699 } 700 701 // TestNeighborCacheRemoveStaticEntryThenOverflow verifies that the LRU cache 702 // eviction strategy respects the dynamic entry count when a static entry is 703 // added then removed. In this case, the dynamic entry count shouldn't have 704 // been touched. 705 func TestNeighborCacheRemoveStaticEntryThenOverflow(t *testing.T) { 706 config := DefaultNUDConfigurations() 707 // Stay in Reachable so the cache can overflow 708 config.BaseReachableTime = infiniteDuration 709 config.MinRandomFactor = 1 710 config.MaxRandomFactor = 1 711 712 c := newTestContext(config) 713 714 // Add a static entry 715 entry, ok := c.linkRes.entries.entry(0) 716 if !ok { 717 t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ") 718 } 719 staticLinkAddr := entry.LinkAddr + "static" 720 c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr) 721 722 { 723 wantEvents := []testEntryEventInfo{ 724 { 725 EventType: entryTestAdded, 726 NICID: 1, 727 Entry: NeighborEntry{ 728 Addr: entry.Addr, 729 LinkAddr: staticLinkAddr, 730 State: Static, 731 UpdatedAt: c.clock.Now(), 732 }, 733 }, 734 } 735 c.nudDisp.mu.Lock() 736 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 737 c.nudDisp.mu.events = nil 738 c.nudDisp.mu.Unlock() 739 if diff != "" { 740 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 741 } 742 } 743 744 // Remove the static entry that was just added 745 c.linkRes.neigh.removeEntry(entry.Addr) 746 747 { 748 wantEvents := []testEntryEventInfo{ 749 { 750 EventType: entryTestRemoved, 751 NICID: 1, 752 Entry: NeighborEntry{ 753 Addr: entry.Addr, 754 LinkAddr: staticLinkAddr, 755 State: Static, 756 UpdatedAt: c.clock.Now(), 757 }, 758 }, 759 } 760 c.nudDisp.mu.Lock() 761 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 762 c.nudDisp.mu.events = nil 763 c.nudDisp.mu.Unlock() 764 if diff != "" { 765 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 766 } 767 } 768 769 opts := overflowOptions{ 770 startAtEntryIndex: 0, 771 } 772 if err := c.overflowCache(opts); err != nil { 773 t.Errorf("c.overflowCache(%+v): %s", opts, err) 774 } 775 } 776 777 // TestNeighborCacheOverwriteWithStaticEntryThenOverflow verifies that the LRU 778 // cache eviction strategy keeps count of the dynamic entry count when an entry 779 // is overwritten by a static entry. Static entries should not count towards 780 // the size of the LRU cache. 781 func TestNeighborCacheOverwriteWithStaticEntryThenOverflow(t *testing.T) { 782 config := DefaultNUDConfigurations() 783 // Stay in Reachable so the cache can overflow 784 config.BaseReachableTime = infiniteDuration 785 config.MinRandomFactor = 1 786 config.MaxRandomFactor = 1 787 788 c := newTestContext(config) 789 790 // Add a dynamic entry 791 entry, ok := c.linkRes.entries.entry(0) 792 if !ok { 793 t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ") 794 } 795 if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil { 796 t.Fatalf("addReachableEntry(...) = %s", err) 797 } 798 799 // Override the entry with a static one using the same address 800 staticLinkAddr := entry.LinkAddr + "static" 801 c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr) 802 803 { 804 wantEvents := []testEntryEventInfo{ 805 { 806 EventType: entryTestRemoved, 807 NICID: 1, 808 Entry: NeighborEntry{ 809 Addr: entry.Addr, 810 LinkAddr: entry.LinkAddr, 811 State: Reachable, 812 UpdatedAt: c.clock.Now(), 813 }, 814 }, 815 { 816 EventType: entryTestAdded, 817 NICID: 1, 818 Entry: NeighborEntry{ 819 Addr: entry.Addr, 820 LinkAddr: staticLinkAddr, 821 State: Static, 822 UpdatedAt: c.clock.Now(), 823 }, 824 }, 825 } 826 c.nudDisp.mu.Lock() 827 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 828 c.nudDisp.mu.events = nil 829 c.nudDisp.mu.Unlock() 830 if diff != "" { 831 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 832 } 833 } 834 835 opts := overflowOptions{ 836 startAtEntryIndex: 1, 837 wantStaticEntries: []NeighborEntry{ 838 { 839 Addr: entry.Addr, 840 LinkAddr: staticLinkAddr, 841 State: Static, 842 UpdatedAt: c.clock.Now(), 843 }, 844 }, 845 } 846 if err := c.overflowCache(opts); err != nil { 847 t.Errorf("c.overflowCache(%+v): %s", opts, err) 848 } 849 } 850 851 func TestNeighborCacheAddStaticEntryThenOverflow(t *testing.T) { 852 config := DefaultNUDConfigurations() 853 // Stay in Reachable so the cache can overflow 854 config.BaseReachableTime = infiniteDuration 855 config.MinRandomFactor = 1 856 config.MaxRandomFactor = 1 857 858 c := newTestContext(config) 859 860 entry, ok := c.linkRes.entries.entry(0) 861 if !ok { 862 t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ") 863 } 864 c.linkRes.neigh.addStaticEntry(entry.Addr, entry.LinkAddr) 865 e, _, err := c.linkRes.neigh.entry(entry.Addr, "", nil) 866 if err != nil { 867 t.Errorf("unexpected error from c.linkRes.neigh.entry(%s, \"\", nil): %s", entry.Addr, err) 868 } 869 want := NeighborEntry{ 870 Addr: entry.Addr, 871 LinkAddr: entry.LinkAddr, 872 State: Static, 873 UpdatedAt: c.clock.Now(), 874 } 875 if diff := cmp.Diff(want, e); diff != "" { 876 t.Errorf("c.linkRes.neigh.entry(%s, \"\", nil) mismatch (-want, +got):\n%s", entry.Addr, diff) 877 } 878 879 { 880 wantEvents := []testEntryEventInfo{ 881 { 882 EventType: entryTestAdded, 883 NICID: 1, 884 Entry: NeighborEntry{ 885 Addr: entry.Addr, 886 LinkAddr: entry.LinkAddr, 887 State: Static, 888 UpdatedAt: c.clock.Now(), 889 }, 890 }, 891 } 892 c.nudDisp.mu.Lock() 893 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 894 c.nudDisp.mu.events = nil 895 c.nudDisp.mu.Unlock() 896 if diff != "" { 897 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 898 } 899 } 900 901 opts := overflowOptions{ 902 startAtEntryIndex: 1, 903 wantStaticEntries: []NeighborEntry{ 904 { 905 Addr: entry.Addr, 906 LinkAddr: entry.LinkAddr, 907 State: Static, 908 UpdatedAt: c.clock.Now(), 909 }, 910 }, 911 } 912 if err := c.overflowCache(opts); err != nil { 913 t.Errorf("c.overflowCache(%+v): %s", opts, err) 914 } 915 } 916 917 func TestNeighborCacheClear(t *testing.T) { 918 config := DefaultNUDConfigurations() 919 920 nudDisp := testNUDDispatcher{} 921 clock := faketime.NewManualClock() 922 linkRes := newTestNeighborResolver(&nudDisp, config, clock) 923 924 // Add a dynamic entry. 925 entry, ok := linkRes.entries.entry(0) 926 if !ok { 927 t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ") 928 } 929 if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil { 930 t.Fatalf("addReachableEntry(...) = %s", err) 931 } 932 933 // Add a static entry. 934 linkRes.neigh.addStaticEntry(entryTestAddr1, entryTestLinkAddr1) 935 936 { 937 wantEvents := []testEntryEventInfo{ 938 { 939 EventType: entryTestAdded, 940 NICID: 1, 941 Entry: NeighborEntry{ 942 Addr: entryTestAddr1, 943 LinkAddr: entryTestLinkAddr1, 944 State: Static, 945 UpdatedAt: clock.Now(), 946 }, 947 }, 948 } 949 nudDisp.mu.Lock() 950 diff := cmp.Diff(wantEvents, nudDisp.mu.events) 951 nudDisp.mu.events = nil 952 nudDisp.mu.Unlock() 953 if diff != "" { 954 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 955 } 956 } 957 958 // Clear should remove both dynamic and static entries. 959 linkRes.neigh.clear() 960 961 // Remove events dispatched from clear() have no deterministic order so they 962 // need to be sorted before comparison. 963 wantUnorderedEvents := []testEntryEventInfo{ 964 { 965 EventType: entryTestRemoved, 966 NICID: 1, 967 Entry: NeighborEntry{ 968 Addr: entry.Addr, 969 LinkAddr: entry.LinkAddr, 970 State: Reachable, 971 UpdatedAt: clock.Now(), 972 }, 973 }, 974 { 975 EventType: entryTestRemoved, 976 NICID: 1, 977 Entry: NeighborEntry{ 978 Addr: entryTestAddr1, 979 LinkAddr: entryTestLinkAddr1, 980 State: Static, 981 UpdatedAt: clock.Now(), 982 }, 983 }, 984 } 985 nudDisp.mu.Lock() 986 defer nudDisp.mu.Unlock() 987 if diff := cmp.Diff(wantUnorderedEvents, nudDisp.mu.events, unorderedEventsDiffOpts()...); diff != "" { 988 t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 989 } 990 } 991 992 // TestNeighborCacheClearThenOverflow verifies that the LRU cache eviction 993 // strategy keeps count of the dynamic entry count when all entries are 994 // cleared. 995 func TestNeighborCacheClearThenOverflow(t *testing.T) { 996 config := DefaultNUDConfigurations() 997 // Stay in Reachable so the cache can overflow 998 config.BaseReachableTime = infiniteDuration 999 config.MinRandomFactor = 1 1000 config.MaxRandomFactor = 1 1001 1002 c := newTestContext(config) 1003 1004 // Add a dynamic entry 1005 entry, ok := c.linkRes.entries.entry(0) 1006 if !ok { 1007 t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ") 1008 } 1009 if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil { 1010 t.Fatalf("addReachableEntry(...) = %s", err) 1011 } 1012 1013 // Clear the cache. 1014 c.linkRes.neigh.clear() 1015 1016 { 1017 wantEvents := []testEntryEventInfo{ 1018 { 1019 EventType: entryTestRemoved, 1020 NICID: 1, 1021 Entry: NeighborEntry{ 1022 Addr: entry.Addr, 1023 LinkAddr: entry.LinkAddr, 1024 State: Reachable, 1025 UpdatedAt: c.clock.Now(), 1026 }, 1027 }, 1028 } 1029 c.nudDisp.mu.Lock() 1030 diff := cmp.Diff(wantEvents, c.nudDisp.mu.events) 1031 c.nudDisp.mu.events = nil 1032 c.nudDisp.mu.Unlock() 1033 if diff != "" { 1034 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 1035 } 1036 } 1037 1038 opts := overflowOptions{ 1039 startAtEntryIndex: 0, 1040 } 1041 if err := c.overflowCache(opts); err != nil { 1042 t.Errorf("c.overflowCache(%+v): %s", opts, err) 1043 } 1044 } 1045 1046 func TestNeighborCacheKeepFrequentlyUsed(t *testing.T) { 1047 config := DefaultNUDConfigurations() 1048 // Stay in Reachable so the cache can overflow 1049 config.BaseReachableTime = infiniteDuration 1050 config.MinRandomFactor = 1 1051 config.MaxRandomFactor = 1 1052 1053 nudDisp := testNUDDispatcher{} 1054 clock := faketime.NewManualClock() 1055 linkRes := newTestNeighborResolver(&nudDisp, config, clock) 1056 1057 startedAt := clock.Now() 1058 1059 // The following logic is very similar to overflowCache, but 1060 // periodically refreshes the frequently used entry. 1061 1062 // Fill the neighbor cache to capacity 1063 for i := uint16(0); i < neighborCacheSize; i++ { 1064 entry, ok := linkRes.entries.entry(i) 1065 if !ok { 1066 t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i) 1067 } 1068 if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil { 1069 t.Fatalf("addReachableEntry(...) = %s", err) 1070 } 1071 } 1072 1073 frequentlyUsedEntry, ok := linkRes.entries.entry(0) 1074 if !ok { 1075 t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ") 1076 } 1077 1078 // Keep adding more entries 1079 for i := uint16(neighborCacheSize); i < linkRes.entries.size(); i++ { 1080 // Periodically refresh the frequently used entry 1081 if i%(neighborCacheSize/2) == 0 { 1082 if _, _, err := linkRes.neigh.entry(frequentlyUsedEntry.Addr, "", nil); err != nil { 1083 t.Errorf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", frequentlyUsedEntry.Addr, err) 1084 } 1085 } 1086 1087 entry, ok := linkRes.entries.entry(i) 1088 if !ok { 1089 t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i) 1090 } 1091 1092 // An entry should have been removed, as per the LRU eviction strategy 1093 removedEntry, ok := linkRes.entries.entry(i - neighborCacheSize + 1) 1094 if !ok { 1095 t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i-neighborCacheSize+1) 1096 } 1097 1098 if err := addReachableEntryWithRemoved(&nudDisp, clock, linkRes, entry, []NeighborEntry{removedEntry}); err != nil { 1099 t.Fatalf("addReachableEntryWithRemoved(...) = %s", err) 1100 } 1101 } 1102 1103 // Expect to find only the frequently used entry and the most recent entries. 1104 // The order of entries reported by entries() is nondeterministic, so entries 1105 // have to be sorted before comparison. 1106 wantUnsortedEntries := []NeighborEntry{ 1107 { 1108 Addr: frequentlyUsedEntry.Addr, 1109 LinkAddr: frequentlyUsedEntry.LinkAddr, 1110 State: Reachable, 1111 // Can be inferred since the frequently used entry is the first to 1112 // be created and transitioned to Reachable. 1113 UpdatedAt: startedAt.Add(typicalLatency), 1114 }, 1115 } 1116 1117 for i := linkRes.entries.size() - neighborCacheSize + 1; i < linkRes.entries.size(); i++ { 1118 entry, ok := linkRes.entries.entry(i) 1119 if !ok { 1120 t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i) 1121 } 1122 durationReachableNanos := time.Duration(linkRes.entries.size()-i-1) * typicalLatency 1123 wantUnsortedEntries = append(wantUnsortedEntries, NeighborEntry{ 1124 Addr: entry.Addr, 1125 LinkAddr: entry.LinkAddr, 1126 State: Reachable, 1127 UpdatedAt: clock.Now().Add(-durationReachableNanos), 1128 }) 1129 } 1130 1131 if diff := cmp.Diff(wantUnsortedEntries, linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" { 1132 t.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff) 1133 } 1134 1135 // No more events should have been dispatched. 1136 nudDisp.mu.Lock() 1137 defer nudDisp.mu.Unlock() 1138 if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" { 1139 t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 1140 } 1141 } 1142 1143 func TestNeighborCacheConcurrent(t *testing.T) { 1144 const concurrentProcesses = 16 1145 1146 config := DefaultNUDConfigurations() 1147 1148 nudDisp := testNUDDispatcher{} 1149 clock := faketime.NewManualClock() 1150 linkRes := newTestNeighborResolver(&nudDisp, config, clock) 1151 1152 storeEntries := linkRes.entries.entries() 1153 for _, entry := range storeEntries { 1154 var wg sync.WaitGroup 1155 for r := 0; r < concurrentProcesses; r++ { 1156 wg.Add(1) 1157 go func(entry NeighborEntry) { 1158 defer wg.Done() 1159 switch e, _, err := linkRes.neigh.entry(entry.Addr, "", nil); err.(type) { 1160 case nil, *tcpip.ErrWouldBlock: 1161 default: 1162 t.Errorf("got linkRes.neigh.entry(%s, '', nil) = (%+v, _, %s), want (_, _, nil) or (_, _, %s)", entry.Addr, e, err, &tcpip.ErrWouldBlock{}) 1163 } 1164 }(entry) 1165 } 1166 1167 // Wait for all goroutines to send a request 1168 wg.Wait() 1169 1170 // Process all the requests for a single entry concurrently 1171 clock.Advance(typicalLatency) 1172 } 1173 1174 // All goroutines add in the same order and add more values than can fit in 1175 // the cache. Our eviction strategy requires that the last entries are 1176 // present, up to the size of the neighbor cache, and the rest are missing. 1177 // The order of entries reported by entries() is nondeterministic, so entries 1178 // have to be sorted before comparison. 1179 var wantUnsortedEntries []NeighborEntry 1180 for i := linkRes.entries.size() - neighborCacheSize; i < linkRes.entries.size(); i++ { 1181 entry, ok := linkRes.entries.entry(i) 1182 if !ok { 1183 t.Errorf("got linkRes.entries.entry(%d) = _, false, want = true", i) 1184 } 1185 durationReachableNanos := time.Duration(linkRes.entries.size()-i-1) * typicalLatency 1186 wantUnsortedEntries = append(wantUnsortedEntries, NeighborEntry{ 1187 Addr: entry.Addr, 1188 LinkAddr: entry.LinkAddr, 1189 State: Reachable, 1190 UpdatedAt: clock.Now().Add(-durationReachableNanos), 1191 }) 1192 } 1193 1194 if diff := cmp.Diff(wantUnsortedEntries, linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" { 1195 t.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff) 1196 } 1197 } 1198 1199 func TestNeighborCacheReplace(t *testing.T) { 1200 config := DefaultNUDConfigurations() 1201 1202 nudDisp := testNUDDispatcher{} 1203 clock := faketime.NewManualClock() 1204 linkRes := newTestNeighborResolver(&nudDisp, config, clock) 1205 1206 entry, ok := linkRes.entries.entry(0) 1207 if !ok { 1208 t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ") 1209 } 1210 if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil { 1211 t.Fatalf("addReachableEntry(...) = %s", err) 1212 } 1213 1214 // Notify of a link address change 1215 var updatedLinkAddr tcpip.LinkAddress 1216 { 1217 entry, ok := linkRes.entries.entry(1) 1218 if !ok { 1219 t.Fatal("got linkRes.entries.entry(1) = _, false, want = true") 1220 } 1221 updatedLinkAddr = entry.LinkAddr 1222 } 1223 linkRes.entries.set(0, updatedLinkAddr) 1224 linkRes.neigh.handleConfirmation(entry.Addr, updatedLinkAddr, ReachabilityConfirmationFlags{ 1225 Solicited: false, 1226 Override: true, 1227 IsRouter: false, 1228 }) 1229 1230 // Requesting the entry again should start neighbor reachability confirmation. 1231 // 1232 // Verify the entry's new link address and the new state. 1233 { 1234 e, _, err := linkRes.neigh.entry(entry.Addr, "", nil) 1235 if err != nil { 1236 t.Fatalf("linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err) 1237 } 1238 want := NeighborEntry{ 1239 Addr: entry.Addr, 1240 LinkAddr: updatedLinkAddr, 1241 State: Delay, 1242 UpdatedAt: clock.Now(), 1243 } 1244 if diff := cmp.Diff(want, e); diff != "" { 1245 t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff) 1246 } 1247 } 1248 1249 clock.Advance(config.DelayFirstProbeTime + typicalLatency) 1250 1251 // Verify that the neighbor is now reachable. 1252 { 1253 e, _, err := linkRes.neigh.entry(entry.Addr, "", nil) 1254 if err != nil { 1255 t.Errorf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err) 1256 } 1257 want := NeighborEntry{ 1258 Addr: entry.Addr, 1259 LinkAddr: updatedLinkAddr, 1260 State: Reachable, 1261 UpdatedAt: clock.Now(), 1262 } 1263 if diff := cmp.Diff(want, e); diff != "" { 1264 t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff) 1265 } 1266 } 1267 } 1268 1269 func TestNeighborCacheResolutionFailed(t *testing.T) { 1270 config := DefaultNUDConfigurations() 1271 1272 nudDisp := testNUDDispatcher{} 1273 clock := faketime.NewManualClock() 1274 linkRes := newTestNeighborResolver(&nudDisp, config, clock) 1275 1276 var requestCount uint32 1277 linkRes.onLinkAddressRequest = func() { 1278 atomic.AddUint32(&requestCount, 1) 1279 } 1280 1281 entry, ok := linkRes.entries.entry(0) 1282 if !ok { 1283 t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ") 1284 } 1285 1286 // First, sanity check that resolution is working 1287 if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil { 1288 t.Fatalf("addReachableEntry(...) = %s", err) 1289 } 1290 1291 got, _, err := linkRes.neigh.entry(entry.Addr, "", nil) 1292 if err != nil { 1293 t.Fatalf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err) 1294 } 1295 want := NeighborEntry{ 1296 Addr: entry.Addr, 1297 LinkAddr: entry.LinkAddr, 1298 State: Reachable, 1299 UpdatedAt: clock.Now(), 1300 } 1301 if diff := cmp.Diff(want, got); diff != "" { 1302 t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff) 1303 } 1304 1305 // Verify address resolution fails for an unknown address. 1306 before := atomic.LoadUint32(&requestCount) 1307 1308 entry.Addr += "2" 1309 { 1310 _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) { 1311 if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" { 1312 t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff) 1313 } 1314 }) 1315 if _, ok := err.(*tcpip.ErrWouldBlock); !ok { 1316 t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{}) 1317 } 1318 waitFor := config.DelayFirstProbeTime + typicalLatency*time.Duration(config.MaxMulticastProbes) 1319 clock.Advance(waitFor) 1320 select { 1321 case <-ch: 1322 default: 1323 t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr) 1324 } 1325 } 1326 1327 maxAttempts := linkRes.neigh.config().MaxUnicastProbes 1328 if got, want := atomic.LoadUint32(&requestCount)-before, maxAttempts; got != want { 1329 t.Errorf("got link address request count = %d, want = %d", got, want) 1330 } 1331 } 1332 1333 // TestNeighborCacheResolutionTimeout simulates sending MaxMulticastProbes 1334 // probes and not retrieving a confirmation before the duration defined by 1335 // MaxMulticastProbes * RetransmitTimer. 1336 func TestNeighborCacheResolutionTimeout(t *testing.T) { 1337 config := DefaultNUDConfigurations() 1338 config.RetransmitTimer = time.Millisecond // small enough to cause timeout 1339 1340 clock := faketime.NewManualClock() 1341 linkRes := newTestNeighborResolver(nil, config, clock) 1342 // large enough to cause timeout 1343 linkRes.delay = time.Minute 1344 1345 entry, ok := linkRes.entries.entry(0) 1346 if !ok { 1347 t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ") 1348 } 1349 1350 _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) { 1351 if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" { 1352 t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff) 1353 } 1354 }) 1355 if _, ok := err.(*tcpip.ErrWouldBlock); !ok { 1356 t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{}) 1357 } 1358 waitFor := config.RetransmitTimer * time.Duration(config.MaxMulticastProbes) 1359 clock.Advance(waitFor) 1360 1361 select { 1362 case <-ch: 1363 default: 1364 t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr) 1365 } 1366 } 1367 1368 // TestNeighborCacheRetryResolution simulates retrying communication after 1369 // failing to perform address resolution. 1370 func TestNeighborCacheRetryResolution(t *testing.T) { 1371 config := DefaultNUDConfigurations() 1372 nudDisp := testNUDDispatcher{} 1373 clock := faketime.NewManualClock() 1374 linkRes := newTestNeighborResolver(&nudDisp, config, clock) 1375 // Simulate a faulty link. 1376 linkRes.dropReplies = true 1377 1378 entry, ok := linkRes.entries.entry(0) 1379 if !ok { 1380 t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ") 1381 } 1382 1383 // Perform address resolution with a faulty link, which will fail. 1384 { 1385 _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) { 1386 if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" { 1387 t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff) 1388 } 1389 }) 1390 if _, ok := err.(*tcpip.ErrWouldBlock); !ok { 1391 t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{}) 1392 } 1393 1394 { 1395 wantEvents := []testEntryEventInfo{ 1396 { 1397 EventType: entryTestAdded, 1398 NICID: 1, 1399 Entry: NeighborEntry{ 1400 Addr: entry.Addr, 1401 LinkAddr: "", 1402 State: Incomplete, 1403 UpdatedAt: clock.Now(), 1404 }, 1405 }, 1406 } 1407 nudDisp.mu.Lock() 1408 diff := cmp.Diff(wantEvents, nudDisp.mu.events) 1409 nudDisp.mu.events = nil 1410 nudDisp.mu.Unlock() 1411 if diff != "" { 1412 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 1413 } 1414 } 1415 1416 waitFor := config.RetransmitTimer * time.Duration(config.MaxMulticastProbes) 1417 clock.Advance(waitFor) 1418 1419 select { 1420 case <-ch: 1421 default: 1422 t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr) 1423 } 1424 1425 { 1426 wantEvents := []testEntryEventInfo{ 1427 { 1428 EventType: entryTestChanged, 1429 NICID: 1, 1430 Entry: NeighborEntry{ 1431 Addr: entry.Addr, 1432 LinkAddr: "", 1433 State: Unreachable, 1434 UpdatedAt: clock.Now(), 1435 }, 1436 }, 1437 } 1438 nudDisp.mu.Lock() 1439 diff := cmp.Diff(wantEvents, nudDisp.mu.events) 1440 nudDisp.mu.events = nil 1441 nudDisp.mu.Unlock() 1442 if diff != "" { 1443 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 1444 } 1445 } 1446 1447 { 1448 wantEntries := []NeighborEntry{ 1449 { 1450 Addr: entry.Addr, 1451 LinkAddr: "", 1452 State: Unreachable, 1453 UpdatedAt: clock.Now(), 1454 }, 1455 } 1456 if diff := cmp.Diff(linkRes.neigh.entries(), wantEntries, unorderedEntriesDiffOpts()...); diff != "" { 1457 t.Fatalf("neighbor entries mismatch (-got, +want):\n%s", diff) 1458 } 1459 } 1460 } 1461 1462 // Retry address resolution with a working link. 1463 linkRes.dropReplies = false 1464 { 1465 incompleteEntry, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) { 1466 if diff := cmp.Diff(LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil}, r); diff != "" { 1467 t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff) 1468 } 1469 }) 1470 if _, ok := err.(*tcpip.ErrWouldBlock); !ok { 1471 t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{}) 1472 } 1473 if incompleteEntry.State != Incomplete { 1474 t.Fatalf("got entry.State = %s, want = %s", incompleteEntry.State, Incomplete) 1475 } 1476 1477 { 1478 wantEvents := []testEntryEventInfo{ 1479 { 1480 EventType: entryTestChanged, 1481 NICID: 1, 1482 Entry: NeighborEntry{ 1483 Addr: entry.Addr, 1484 LinkAddr: "", 1485 State: Incomplete, 1486 UpdatedAt: clock.Now(), 1487 }, 1488 }, 1489 } 1490 nudDisp.mu.Lock() 1491 diff := cmp.Diff(wantEvents, nudDisp.mu.events) 1492 nudDisp.mu.events = nil 1493 nudDisp.mu.Unlock() 1494 if diff != "" { 1495 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 1496 } 1497 } 1498 1499 clock.Advance(typicalLatency) 1500 1501 select { 1502 case <-ch: 1503 default: 1504 t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr) 1505 } 1506 1507 { 1508 wantEvents := []testEntryEventInfo{ 1509 { 1510 EventType: entryTestChanged, 1511 NICID: 1, 1512 Entry: NeighborEntry{ 1513 Addr: entry.Addr, 1514 LinkAddr: entry.LinkAddr, 1515 State: Reachable, 1516 UpdatedAt: clock.Now(), 1517 }, 1518 }, 1519 } 1520 nudDisp.mu.Lock() 1521 diff := cmp.Diff(wantEvents, nudDisp.mu.events) 1522 nudDisp.mu.events = nil 1523 nudDisp.mu.Unlock() 1524 if diff != "" { 1525 t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff) 1526 } 1527 } 1528 1529 { 1530 gotEntry, _, err := linkRes.neigh.entry(entry.Addr, "", nil) 1531 if err != nil { 1532 t.Fatalf("linkRes.neigh.entry(%s, '', _): %s", entry.Addr, err) 1533 } 1534 1535 wantEntry := NeighborEntry{ 1536 Addr: entry.Addr, 1537 LinkAddr: entry.LinkAddr, 1538 State: Reachable, 1539 UpdatedAt: clock.Now(), 1540 } 1541 if diff := cmp.Diff(gotEntry, wantEntry); diff != "" { 1542 t.Fatalf("neighbor entry mismatch (-got, +want):\n%s", diff) 1543 } 1544 } 1545 } 1546 } 1547 1548 func BenchmarkCacheClear(b *testing.B) { 1549 b.StopTimer() 1550 config := DefaultNUDConfigurations() 1551 clock := tcpip.NewStdClock() 1552 linkRes := newTestNeighborResolver(nil, config, clock) 1553 linkRes.delay = 0 1554 1555 // Clear for every possible size of the cache 1556 for cacheSize := uint16(0); cacheSize < neighborCacheSize; cacheSize++ { 1557 // Fill the neighbor cache to capacity. 1558 for i := uint16(0); i < cacheSize; i++ { 1559 entry, ok := linkRes.entries.entry(i) 1560 if !ok { 1561 b.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i) 1562 } 1563 1564 _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) { 1565 if diff := cmp.Diff(LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil}, r); diff != "" { 1566 b.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff) 1567 } 1568 }) 1569 if _, ok := err.(*tcpip.ErrWouldBlock); !ok { 1570 b.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{}) 1571 } 1572 1573 select { 1574 case <-ch: 1575 default: 1576 b.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr) 1577 } 1578 } 1579 1580 b.StartTimer() 1581 linkRes.neigh.clear() 1582 b.StopTimer() 1583 } 1584 }