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