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