gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/stack/nud_test.go (about)

     1  // Copyright 2020 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_test
    16  
    17  import (
    18  	"math"
    19  	"math/rand"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"gvisor.dev/gvisor/pkg/tcpip"
    25  	"gvisor.dev/gvisor/pkg/tcpip/faketime"
    26  	"gvisor.dev/gvisor/pkg/tcpip/link/channel"
    27  	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
    28  	"gvisor.dev/gvisor/pkg/tcpip/stack"
    29  )
    30  
    31  const (
    32  	defaultBaseReachableTime   = 30 * time.Second
    33  	minimumBaseReachableTime   = time.Millisecond
    34  	defaultMinRandomFactor     = 0.5
    35  	defaultMaxRandomFactor     = 1.5
    36  	defaultRetransmitTimer     = time.Second
    37  	minimumRetransmitTimer     = time.Millisecond
    38  	defaultDelayFirstProbeTime = 5 * time.Second
    39  	defaultMaxMulticastProbes  = 3
    40  	defaultMaxUnicastProbes    = 3
    41  
    42  	defaultFakeRandomNum = 0.5
    43  )
    44  
    45  // fakeRand is a deterministic random number generator.
    46  type fakeRand struct {
    47  	num float32
    48  }
    49  
    50  var _ rand.Source = (*fakeRand)(nil)
    51  
    52  func (f *fakeRand) Int63() int64 {
    53  	return int64(f.num * float32(1<<63))
    54  }
    55  
    56  func (*fakeRand) Seed(int64) {}
    57  
    58  func TestNUDFunctions(t *testing.T) {
    59  	const nicID = 1
    60  
    61  	tests := []struct {
    62  		name                  string
    63  		nicID                 tcpip.NICID
    64  		netProtoFactory       []stack.NetworkProtocolFactory
    65  		extraLinkCapabilities stack.LinkEndpointCapabilities
    66  		expectedErr           tcpip.Error
    67  	}{
    68  		{
    69  			name:                  "Invalid NICID",
    70  			nicID:                 nicID + 1,
    71  			netProtoFactory:       []stack.NetworkProtocolFactory{ipv6.NewProtocol},
    72  			extraLinkCapabilities: stack.CapabilityResolutionRequired,
    73  			expectedErr:           &tcpip.ErrUnknownNICID{},
    74  		},
    75  		{
    76  			name:        "No network protocol",
    77  			nicID:       nicID,
    78  			expectedErr: &tcpip.ErrNotSupported{},
    79  		},
    80  		{
    81  			name:            "With IPv6",
    82  			nicID:           nicID,
    83  			netProtoFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
    84  			expectedErr:     &tcpip.ErrNotSupported{},
    85  		},
    86  		{
    87  			name:                  "With resolution capability",
    88  			nicID:                 nicID,
    89  			extraLinkCapabilities: stack.CapabilityResolutionRequired,
    90  			expectedErr:           &tcpip.ErrNotSupported{},
    91  		},
    92  		{
    93  			name:                  "With IPv6 and resolution capability",
    94  			nicID:                 nicID,
    95  			netProtoFactory:       []stack.NetworkProtocolFactory{ipv6.NewProtocol},
    96  			extraLinkCapabilities: stack.CapabilityResolutionRequired,
    97  		},
    98  	}
    99  
   100  	for _, test := range tests {
   101  		t.Run(test.name, func(t *testing.T) {
   102  			clock := faketime.NewManualClock()
   103  			s := stack.New(stack.Options{
   104  				NUDConfigs:       stack.DefaultNUDConfigurations(),
   105  				NetworkProtocols: test.netProtoFactory,
   106  				Clock:            clock,
   107  			})
   108  
   109  			e := channel.New(0, 0, linkAddr1)
   110  			e.LinkEPCapabilities &^= stack.CapabilityResolutionRequired
   111  			e.LinkEPCapabilities |= test.extraLinkCapabilities
   112  
   113  			if err := s.CreateNIC(nicID, e); err != nil {
   114  				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   115  			}
   116  
   117  			configs := stack.DefaultNUDConfigurations()
   118  			configs.BaseReachableTime = time.Hour
   119  
   120  			{
   121  				err := s.SetNUDConfigurations(test.nicID, ipv6.ProtocolNumber, configs)
   122  				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
   123  					t.Errorf("s.SetNUDConfigurations(%d, %d, _) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
   124  				}
   125  			}
   126  
   127  			{
   128  				gotConfigs, err := s.NUDConfigurations(test.nicID, ipv6.ProtocolNumber)
   129  				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
   130  					t.Errorf("s.NUDConfigurations(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
   131  				} else if test.expectedErr == nil {
   132  					if diff := cmp.Diff(configs, gotConfigs); diff != "" {
   133  						t.Errorf("got configs mismatch (-want +got):\n%s", diff)
   134  					}
   135  				}
   136  			}
   137  
   138  			for _, addr := range []tcpip.Address{llAddr1, llAddr2} {
   139  				{
   140  					err := s.AddStaticNeighbor(test.nicID, ipv6.ProtocolNumber, addr, linkAddr1)
   141  					if diff := cmp.Diff(test.expectedErr, err); diff != "" {
   142  						t.Errorf("s.AddStaticNeighbor(%d, %d, %s, %s) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, addr, linkAddr1, diff)
   143  					}
   144  				}
   145  			}
   146  
   147  			{
   148  				wantErr := test.expectedErr
   149  				for i := 0; i < 2; i++ {
   150  					{
   151  						err := s.RemoveNeighbor(test.nicID, ipv6.ProtocolNumber, llAddr1)
   152  						if diff := cmp.Diff(wantErr, err); diff != "" {
   153  							t.Errorf("s.RemoveNeighbor(%d, %d, '') error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
   154  						}
   155  					}
   156  
   157  					if test.expectedErr != nil {
   158  						break
   159  					}
   160  
   161  					// Removing a neighbor that does not exist should give us a bad address
   162  					// error.
   163  					wantErr = &tcpip.ErrBadAddress{}
   164  				}
   165  			}
   166  
   167  			{
   168  				neighbors, err := s.Neighbors(test.nicID, ipv6.ProtocolNumber)
   169  				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
   170  					t.Errorf("s.Neighbors(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
   171  				} else if test.expectedErr == nil {
   172  					if diff := cmp.Diff(
   173  						[]stack.NeighborEntry{{Addr: llAddr2, LinkAddr: linkAddr1, State: stack.Static, UpdatedAt: clock.NowMonotonic()}},
   174  						neighbors,
   175  						cmp.AllowUnexported(tcpip.MonotonicTime{}),
   176  					); diff != "" {
   177  						t.Errorf("neighbors mismatch (-want +got):\n%s", diff)
   178  					}
   179  				}
   180  			}
   181  
   182  			{
   183  				err := s.ClearNeighbors(test.nicID, ipv6.ProtocolNumber)
   184  				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
   185  					t.Errorf("s.ClearNeigbors(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
   186  				} else if test.expectedErr == nil {
   187  					if neighbors, err := s.Neighbors(test.nicID, ipv6.ProtocolNumber); err != nil {
   188  						t.Errorf("s.Neighbors(%d, %d): %s", test.nicID, ipv6.ProtocolNumber, err)
   189  					} else if len(neighbors) != 0 {
   190  						t.Errorf("got len(neighbors) = %d, want = 0; neighbors = %#v", len(neighbors), neighbors)
   191  					}
   192  				}
   193  			}
   194  		})
   195  	}
   196  }
   197  
   198  func TestDefaultNUDConfigurations(t *testing.T) {
   199  	const nicID = 1
   200  
   201  	e := channel.New(0, 1280, linkAddr1)
   202  	e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   203  
   204  	s := stack.New(stack.Options{
   205  		// A neighbor cache is required to store NUDConfigurations. The networking
   206  		// stack will only allocate neighbor caches if a protocol providing link
   207  		// address resolution is specified (e.g. ARP or IPv6).
   208  		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
   209  		NUDConfigs:       stack.DefaultNUDConfigurations(),
   210  	})
   211  	if err := s.CreateNIC(nicID, e); err != nil {
   212  		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   213  	}
   214  	c, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
   215  	if err != nil {
   216  		t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
   217  	}
   218  	if got, want := c, stack.DefaultNUDConfigurations(); got != want {
   219  		t.Errorf("got stack.NUDConfigurations(%d, %d) = %+v, want = %+v", nicID, ipv6.ProtocolNumber, got, want)
   220  	}
   221  }
   222  
   223  func TestNUDConfigurationsBaseReachableTime(t *testing.T) {
   224  	tests := []struct {
   225  		name              string
   226  		baseReachableTime time.Duration
   227  		want              time.Duration
   228  	}{
   229  		// Invalid cases
   230  		{
   231  			name:              "EqualToZero",
   232  			baseReachableTime: 0,
   233  			want:              defaultBaseReachableTime,
   234  		},
   235  		// Valid cases
   236  		{
   237  			name:              "MoreThanZero",
   238  			baseReachableTime: time.Millisecond,
   239  			want:              time.Millisecond,
   240  		},
   241  		{
   242  			name:              "MoreThanDefaultBaseReachableTime",
   243  			baseReachableTime: 2 * defaultBaseReachableTime,
   244  			want:              2 * defaultBaseReachableTime,
   245  		},
   246  	}
   247  
   248  	for _, test := range tests {
   249  		t.Run(test.name, func(t *testing.T) {
   250  			const nicID = 1
   251  
   252  			c := stack.DefaultNUDConfigurations()
   253  			c.BaseReachableTime = test.baseReachableTime
   254  
   255  			e := channel.New(0, 1280, linkAddr1)
   256  			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   257  
   258  			s := stack.New(stack.Options{
   259  				// A neighbor cache is required to store NUDConfigurations. The
   260  				// networking stack will only allocate neighbor caches if a protocol
   261  				// providing link address resolution is specified (e.g. ARP or IPv6).
   262  				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
   263  				NUDConfigs:       c,
   264  			})
   265  			if err := s.CreateNIC(nicID, e); err != nil {
   266  				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   267  			}
   268  			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
   269  			if err != nil {
   270  				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
   271  			}
   272  			if got := sc.BaseReachableTime; got != test.want {
   273  				t.Errorf("got BaseReachableTime = %q, want = %q", got, test.want)
   274  			}
   275  		})
   276  	}
   277  }
   278  
   279  func TestNUDConfigurationsMinRandomFactor(t *testing.T) {
   280  	tests := []struct {
   281  		name            string
   282  		minRandomFactor float32
   283  		want            float32
   284  	}{
   285  		// Invalid cases
   286  		{
   287  			name:            "LessThanZero",
   288  			minRandomFactor: -1,
   289  			want:            defaultMinRandomFactor,
   290  		},
   291  		{
   292  			name:            "EqualToZero",
   293  			minRandomFactor: 0,
   294  			want:            defaultMinRandomFactor,
   295  		},
   296  		// Valid cases
   297  		{
   298  			name:            "MoreThanZero",
   299  			minRandomFactor: 1,
   300  			want:            1,
   301  		},
   302  	}
   303  
   304  	for _, test := range tests {
   305  		t.Run(test.name, func(t *testing.T) {
   306  			const nicID = 1
   307  
   308  			c := stack.DefaultNUDConfigurations()
   309  			c.MinRandomFactor = test.minRandomFactor
   310  
   311  			e := channel.New(0, 1280, linkAddr1)
   312  			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   313  
   314  			s := stack.New(stack.Options{
   315  				// A neighbor cache is required to store NUDConfigurations. The
   316  				// networking stack will only allocate neighbor caches if a protocol
   317  				// providing link address resolution is specified (e.g. ARP or IPv6).
   318  				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
   319  				NUDConfigs:       c,
   320  			})
   321  			if err := s.CreateNIC(nicID, e); err != nil {
   322  				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   323  			}
   324  			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
   325  			if err != nil {
   326  				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
   327  			}
   328  			if got := sc.MinRandomFactor; got != test.want {
   329  				t.Errorf("got MinRandomFactor = %f, want = %f", got, test.want)
   330  			}
   331  		})
   332  	}
   333  }
   334  
   335  func TestNUDConfigurationsMaxRandomFactor(t *testing.T) {
   336  	tests := []struct {
   337  		name            string
   338  		minRandomFactor float32
   339  		maxRandomFactor float32
   340  		want            float32
   341  	}{
   342  		// Invalid cases
   343  		{
   344  			name:            "LessThanZero",
   345  			minRandomFactor: defaultMinRandomFactor,
   346  			maxRandomFactor: -1,
   347  			want:            defaultMaxRandomFactor,
   348  		},
   349  		{
   350  			name:            "EqualToZero",
   351  			minRandomFactor: defaultMinRandomFactor,
   352  			maxRandomFactor: 0,
   353  			want:            defaultMaxRandomFactor,
   354  		},
   355  		{
   356  			name:            "LessThanMinRandomFactor",
   357  			minRandomFactor: defaultMinRandomFactor,
   358  			maxRandomFactor: defaultMinRandomFactor * 0.99,
   359  			want:            defaultMaxRandomFactor,
   360  		},
   361  		{
   362  			name:            "MoreThanMinRandomFactorWhenMinRandomFactorIsLargerThanMaxRandomFactorDefault",
   363  			minRandomFactor: defaultMaxRandomFactor * 2,
   364  			maxRandomFactor: defaultMaxRandomFactor,
   365  			want:            defaultMaxRandomFactor * 6,
   366  		},
   367  		// Valid cases
   368  		{
   369  			name:            "EqualToMinRandomFactor",
   370  			minRandomFactor: defaultMinRandomFactor,
   371  			maxRandomFactor: defaultMinRandomFactor,
   372  			want:            defaultMinRandomFactor,
   373  		},
   374  		{
   375  			name:            "MoreThanMinRandomFactor",
   376  			minRandomFactor: defaultMinRandomFactor,
   377  			maxRandomFactor: defaultMinRandomFactor * 1.1,
   378  			want:            defaultMinRandomFactor * 1.1,
   379  		},
   380  	}
   381  
   382  	for _, test := range tests {
   383  		t.Run(test.name, func(t *testing.T) {
   384  			const nicID = 1
   385  
   386  			c := stack.DefaultNUDConfigurations()
   387  			c.MinRandomFactor = test.minRandomFactor
   388  			c.MaxRandomFactor = test.maxRandomFactor
   389  
   390  			e := channel.New(0, 1280, linkAddr1)
   391  			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   392  
   393  			s := stack.New(stack.Options{
   394  				// A neighbor cache is required to store NUDConfigurations. The
   395  				// networking stack will only allocate neighbor caches if a protocol
   396  				// providing link address resolution is specified (e.g. ARP or IPv6).
   397  				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
   398  				NUDConfigs:       c,
   399  			})
   400  			if err := s.CreateNIC(nicID, e); err != nil {
   401  				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   402  			}
   403  			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
   404  			if err != nil {
   405  				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
   406  			}
   407  			if got := sc.MaxRandomFactor; got != test.want {
   408  				t.Errorf("got MaxRandomFactor = %f, want = %f", got, test.want)
   409  			}
   410  		})
   411  	}
   412  }
   413  
   414  func TestNUDConfigurationsRetransmitTimer(t *testing.T) {
   415  	tests := []struct {
   416  		name            string
   417  		retransmitTimer time.Duration
   418  		want            time.Duration
   419  	}{
   420  		// Invalid cases
   421  		{
   422  			name:            "EqualToZero",
   423  			retransmitTimer: 0,
   424  			want:            defaultRetransmitTimer,
   425  		},
   426  		{
   427  			name:            "LessThanMinimumRetransmitTimer",
   428  			retransmitTimer: minimumRetransmitTimer - time.Nanosecond,
   429  			want:            defaultRetransmitTimer,
   430  		},
   431  		// Valid cases
   432  		{
   433  			name:            "EqualToMinimumRetransmitTimer",
   434  			retransmitTimer: minimumRetransmitTimer,
   435  			want:            minimumBaseReachableTime,
   436  		},
   437  		{
   438  			name:            "LargetThanMinimumRetransmitTimer",
   439  			retransmitTimer: 2 * minimumBaseReachableTime,
   440  			want:            2 * minimumBaseReachableTime,
   441  		},
   442  	}
   443  
   444  	for _, test := range tests {
   445  		t.Run(test.name, func(t *testing.T) {
   446  			const nicID = 1
   447  
   448  			c := stack.DefaultNUDConfigurations()
   449  			c.RetransmitTimer = test.retransmitTimer
   450  
   451  			e := channel.New(0, 1280, linkAddr1)
   452  			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   453  
   454  			s := stack.New(stack.Options{
   455  				// A neighbor cache is required to store NUDConfigurations. The
   456  				// networking stack will only allocate neighbor caches if a protocol
   457  				// providing link address resolution is specified (e.g. ARP or IPv6).
   458  				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
   459  				NUDConfigs:       c,
   460  			})
   461  			if err := s.CreateNIC(nicID, e); err != nil {
   462  				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   463  			}
   464  			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
   465  			if err != nil {
   466  				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
   467  			}
   468  			if got := sc.RetransmitTimer; got != test.want {
   469  				t.Errorf("got RetransmitTimer = %q, want = %q", got, test.want)
   470  			}
   471  		})
   472  	}
   473  }
   474  
   475  func TestNUDConfigurationsDelayFirstProbeTime(t *testing.T) {
   476  	tests := []struct {
   477  		name                string
   478  		delayFirstProbeTime time.Duration
   479  		want                time.Duration
   480  	}{
   481  		// Invalid cases
   482  		{
   483  			name:                "EqualToZero",
   484  			delayFirstProbeTime: 0,
   485  			want:                defaultDelayFirstProbeTime,
   486  		},
   487  		// Valid cases
   488  		{
   489  			name:                "MoreThanZero",
   490  			delayFirstProbeTime: time.Millisecond,
   491  			want:                time.Millisecond,
   492  		},
   493  	}
   494  
   495  	for _, test := range tests {
   496  		t.Run(test.name, func(t *testing.T) {
   497  			const nicID = 1
   498  
   499  			c := stack.DefaultNUDConfigurations()
   500  			c.DelayFirstProbeTime = test.delayFirstProbeTime
   501  
   502  			e := channel.New(0, 1280, linkAddr1)
   503  			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   504  
   505  			s := stack.New(stack.Options{
   506  				// A neighbor cache is required to store NUDConfigurations. The
   507  				// networking stack will only allocate neighbor caches if a protocol
   508  				// providing link address resolution is specified (e.g. ARP or IPv6).
   509  				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
   510  				NUDConfigs:       c,
   511  			})
   512  			if err := s.CreateNIC(nicID, e); err != nil {
   513  				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   514  			}
   515  			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
   516  			if err != nil {
   517  				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
   518  			}
   519  			if got := sc.DelayFirstProbeTime; got != test.want {
   520  				t.Errorf("got DelayFirstProbeTime = %q, want = %q", got, test.want)
   521  			}
   522  		})
   523  	}
   524  }
   525  
   526  func TestNUDConfigurationsMaxMulticastProbes(t *testing.T) {
   527  	tests := []struct {
   528  		name               string
   529  		maxMulticastProbes uint32
   530  		want               uint32
   531  	}{
   532  		// Invalid cases
   533  		{
   534  			name:               "EqualToZero",
   535  			maxMulticastProbes: 0,
   536  			want:               defaultMaxMulticastProbes,
   537  		},
   538  		// Valid cases
   539  		{
   540  			name:               "MoreThanZero",
   541  			maxMulticastProbes: 1,
   542  			want:               1,
   543  		},
   544  	}
   545  
   546  	for _, test := range tests {
   547  		t.Run(test.name, func(t *testing.T) {
   548  			const nicID = 1
   549  
   550  			c := stack.DefaultNUDConfigurations()
   551  			c.MaxMulticastProbes = test.maxMulticastProbes
   552  
   553  			e := channel.New(0, 1280, linkAddr1)
   554  			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   555  
   556  			s := stack.New(stack.Options{
   557  				// A neighbor cache is required to store NUDConfigurations. The
   558  				// networking stack will only allocate neighbor caches if a protocol
   559  				// providing link address resolution is specified (e.g. ARP or IPv6).
   560  				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
   561  				NUDConfigs:       c,
   562  			})
   563  			if err := s.CreateNIC(nicID, e); err != nil {
   564  				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   565  			}
   566  			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
   567  			if err != nil {
   568  				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
   569  			}
   570  			if got := sc.MaxMulticastProbes; got != test.want {
   571  				t.Errorf("got MaxMulticastProbes = %q, want = %q", got, test.want)
   572  			}
   573  		})
   574  	}
   575  }
   576  
   577  func TestNUDConfigurationsMaxUnicastProbes(t *testing.T) {
   578  	tests := []struct {
   579  		name             string
   580  		maxUnicastProbes uint32
   581  		want             uint32
   582  	}{
   583  		// Invalid cases
   584  		{
   585  			name:             "EqualToZero",
   586  			maxUnicastProbes: 0,
   587  			want:             defaultMaxUnicastProbes,
   588  		},
   589  		// Valid cases
   590  		{
   591  			name:             "MoreThanZero",
   592  			maxUnicastProbes: 1,
   593  			want:             1,
   594  		},
   595  	}
   596  
   597  	for _, test := range tests {
   598  		t.Run(test.name, func(t *testing.T) {
   599  			const nicID = 1
   600  
   601  			c := stack.DefaultNUDConfigurations()
   602  			c.MaxUnicastProbes = test.maxUnicastProbes
   603  
   604  			e := channel.New(0, 1280, linkAddr1)
   605  			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   606  
   607  			s := stack.New(stack.Options{
   608  				// A neighbor cache is required to store NUDConfigurations. The
   609  				// networking stack will only allocate neighbor caches if a protocol
   610  				// providing link address resolution is specified (e.g. ARP or IPv6).
   611  				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
   612  				NUDConfigs:       c,
   613  			})
   614  			if err := s.CreateNIC(nicID, e); err != nil {
   615  				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
   616  			}
   617  			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
   618  			if err != nil {
   619  				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
   620  			}
   621  			if got := sc.MaxUnicastProbes; got != test.want {
   622  				t.Errorf("got MaxUnicastProbes = %q, want = %q", got, test.want)
   623  			}
   624  		})
   625  	}
   626  }
   627  
   628  // TestNUDStateReachableTime verifies the correctness of the ReachableTime
   629  // computation.
   630  func TestNUDStateReachableTime(t *testing.T) {
   631  	tests := []struct {
   632  		name              string
   633  		baseReachableTime time.Duration
   634  		minRandomFactor   float32
   635  		maxRandomFactor   float32
   636  		want              time.Duration
   637  	}{
   638  		{
   639  			name:              "AllZeros",
   640  			baseReachableTime: 0,
   641  			minRandomFactor:   0,
   642  			maxRandomFactor:   0,
   643  			want:              0,
   644  		},
   645  		{
   646  			name:              "ZeroMaxRandomFactor",
   647  			baseReachableTime: time.Second,
   648  			minRandomFactor:   0,
   649  			maxRandomFactor:   0,
   650  			want:              0,
   651  		},
   652  		{
   653  			name:              "ZeroMinRandomFactor",
   654  			baseReachableTime: time.Second,
   655  			minRandomFactor:   0,
   656  			maxRandomFactor:   1,
   657  			want:              time.Duration(defaultFakeRandomNum * float32(time.Second)),
   658  		},
   659  		{
   660  			name:              "FractionalRandomFactor",
   661  			baseReachableTime: time.Duration(math.MaxInt64),
   662  			minRandomFactor:   0.001,
   663  			maxRandomFactor:   0.002,
   664  			want:              time.Duration((0.001 + (0.001 * defaultFakeRandomNum)) * float32(math.MaxInt64)),
   665  		},
   666  		{
   667  			name:              "MinAndMaxRandomFactorsEqual",
   668  			baseReachableTime: time.Second,
   669  			minRandomFactor:   1,
   670  			maxRandomFactor:   1,
   671  			want:              time.Second,
   672  		},
   673  		{
   674  			name:              "MinAndMaxRandomFactorsDifferent",
   675  			baseReachableTime: time.Second,
   676  			minRandomFactor:   1,
   677  			maxRandomFactor:   2,
   678  			want:              time.Duration((1.0 + defaultFakeRandomNum) * float32(time.Second)),
   679  		},
   680  		{
   681  			name:              "MaxInt64",
   682  			baseReachableTime: time.Duration(math.MaxInt64),
   683  			minRandomFactor:   1,
   684  			maxRandomFactor:   1,
   685  			want:              time.Duration(math.MaxInt64),
   686  		},
   687  		{
   688  			name:              "Overflow",
   689  			baseReachableTime: time.Duration(math.MaxInt64),
   690  			minRandomFactor:   1.5,
   691  			maxRandomFactor:   1.5,
   692  			want:              time.Duration(math.MaxInt64),
   693  		},
   694  		{
   695  			name:              "DoubleOverflow",
   696  			baseReachableTime: time.Duration(math.MaxInt64),
   697  			minRandomFactor:   2.5,
   698  			maxRandomFactor:   2.5,
   699  			want:              time.Duration(math.MaxInt64),
   700  		},
   701  	}
   702  
   703  	for _, test := range tests {
   704  		t.Run(test.name, func(t *testing.T) {
   705  			c := stack.NUDConfigurations{
   706  				BaseReachableTime: test.baseReachableTime,
   707  				MinRandomFactor:   test.minRandomFactor,
   708  				MaxRandomFactor:   test.maxRandomFactor,
   709  			}
   710  			// A fake random number generator is used to ensure deterministic
   711  			// results.
   712  			rng := fakeRand{
   713  				num: defaultFakeRandomNum,
   714  			}
   715  			var clock faketime.NullClock
   716  			s := stack.NewNUDState(c, &clock, rand.New(&rng))
   717  			if got, want := s.ReachableTime(), test.want; got != want {
   718  				t.Errorf("got ReachableTime = %q, want = %q", got, want)
   719  			}
   720  		})
   721  	}
   722  }
   723  
   724  // TestNUDStateRecomputeReachableTime exercises the ReachableTime function
   725  // twice to verify recomputation of reachable time when the min random factor,
   726  // max random factor, or base reachable time changes.
   727  func TestNUDStateRecomputeReachableTime(t *testing.T) {
   728  	const defaultBase = time.Second
   729  	const defaultMin = 2.0 * defaultMaxRandomFactor
   730  	const defaultMax = 3.0 * defaultMaxRandomFactor
   731  
   732  	tests := []struct {
   733  		name              string
   734  		baseReachableTime time.Duration
   735  		minRandomFactor   float32
   736  		maxRandomFactor   float32
   737  		want              time.Duration
   738  	}{
   739  		{
   740  			name:              "BaseReachableTime",
   741  			baseReachableTime: 2 * defaultBase,
   742  			minRandomFactor:   defaultMin,
   743  			maxRandomFactor:   defaultMax,
   744  			want:              time.Duration((defaultMin + (defaultMax-defaultMin)*defaultFakeRandomNum) * float32(2*defaultBase)),
   745  		},
   746  		{
   747  			name:              "MinRandomFactor",
   748  			baseReachableTime: defaultBase,
   749  			minRandomFactor:   defaultMax,
   750  			maxRandomFactor:   defaultMax,
   751  			want:              time.Duration(defaultMax * float32(defaultBase)),
   752  		},
   753  		{
   754  			name:              "MaxRandomFactor",
   755  			baseReachableTime: defaultBase,
   756  			minRandomFactor:   defaultMin,
   757  			maxRandomFactor:   defaultMin,
   758  			want:              time.Duration(defaultMin * float32(defaultBase)),
   759  		},
   760  		{
   761  			name:              "BothRandomFactor",
   762  			baseReachableTime: defaultBase,
   763  			minRandomFactor:   2 * defaultMin,
   764  			maxRandomFactor:   2 * defaultMax,
   765  			want:              time.Duration((2*defaultMin + (2*defaultMax-2*defaultMin)*defaultFakeRandomNum) * float32(defaultBase)),
   766  		},
   767  		{
   768  			name:              "BaseReachableTimeAndBothRandomFactors",
   769  			baseReachableTime: 2 * defaultBase,
   770  			minRandomFactor:   2 * defaultMin,
   771  			maxRandomFactor:   2 * defaultMax,
   772  			want:              time.Duration((2*defaultMin + (2*defaultMax-2*defaultMin)*defaultFakeRandomNum) * float32(2*defaultBase)),
   773  		},
   774  	}
   775  
   776  	for _, test := range tests {
   777  		t.Run(test.name, func(t *testing.T) {
   778  			c := stack.DefaultNUDConfigurations()
   779  			c.BaseReachableTime = defaultBase
   780  			c.MinRandomFactor = defaultMin
   781  			c.MaxRandomFactor = defaultMax
   782  
   783  			// A fake random number generator is used to ensure deterministic
   784  			// results.
   785  			rng := fakeRand{
   786  				num: defaultFakeRandomNum,
   787  			}
   788  			var clock faketime.NullClock
   789  			s := stack.NewNUDState(c, &clock, rand.New(&rng))
   790  			old := s.ReachableTime()
   791  
   792  			if got, want := s.ReachableTime(), old; got != want {
   793  				t.Errorf("got ReachableTime = %q, want = %q", got, want)
   794  			}
   795  
   796  			// Check for recomputation when changing the min random factor, the max
   797  			// random factor, the base reachability time, or any permutation of those
   798  			// three options.
   799  			c.BaseReachableTime = test.baseReachableTime
   800  			c.MinRandomFactor = test.minRandomFactor
   801  			c.MaxRandomFactor = test.maxRandomFactor
   802  			s.SetConfig(c)
   803  
   804  			if got, want := s.ReachableTime(), test.want; got != want {
   805  				t.Errorf("got ReachableTime = %q, want = %q", got, want)
   806  			}
   807  
   808  			// Verify that ReachableTime isn't recomputed when none of the
   809  			// configuration options change. The random factor is changed so that if
   810  			// a recompution were to occur, ReachableTime would change.
   811  			rng.num = defaultFakeRandomNum / 2.0
   812  			if got, want := s.ReachableTime(), test.want; got != want {
   813  				t.Errorf("got ReachableTime = %q, want = %q", got, want)
   814  			}
   815  		})
   816  	}
   817  }