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  }