github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/tcpip/stack/neighbor_cache_test.go (about)

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