// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package stack_test import ( "math" "testing" "time" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/link/channel" "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" "gvisor.dev/gvisor/pkg/tcpip/stack" ) const ( defaultBaseReachableTime = 30 * time.Second minimumBaseReachableTime = time.Millisecond defaultMinRandomFactor = 0.5 defaultMaxRandomFactor = 1.5 defaultRetransmitTimer = time.Second minimumRetransmitTimer = time.Millisecond defaultDelayFirstProbeTime = 5 * time.Second defaultMaxMulticastProbes = 3 defaultMaxUnicastProbes = 3 defaultMaxAnycastDelayTime = time.Second defaultMaxReachbilityConfirmations = 3 defaultUnreachableTime = 5 * time.Second defaultFakeRandomNum = 0.5 ) // fakeRand is a deterministic random number generator. type fakeRand struct { num float32 } var _ stack.Rand = (*fakeRand)(nil) func (f *fakeRand) Float32() float32 { return f.num } // TestSetNUDConfigurationFailsForBadNICID tests to make sure we get an error if // we attempt to update NUD configurations using an invalid NICID. func TestSetNUDConfigurationFailsForBadNICID(t *testing.T) { s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The networking // stack will only allocate neighbor caches if a protocol providing link // address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, UseNeighborCache: true, }) // No NIC with ID 1 yet. config := stack.NUDConfigurations{} if err := s.SetNUDConfigurations(1, config); err != tcpip.ErrUnknownNICID { t.Fatalf("got s.SetNDPConfigurations(1, %+v) = %v, want = %s", config, err, tcpip.ErrUnknownNICID) } } // TestNUDConfigurationFailsForNotSupported tests to make sure we get a // NotSupported error if we attempt to retrieve NUD configurations when the // stack doesn't support NUD. // // The stack will report to not support NUD if a neighbor cache for a given NIC // is not allocated. The networking stack will only allocate neighbor caches if // a protocol providing link address resolution is specified (e.g. ARP, IPv6). func TestNUDConfigurationFailsForNotSupported(t *testing.T) { const nicID = 1 e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ NUDConfigs: stack.DefaultNUDConfigurations(), UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } if _, err := s.NUDConfigurations(nicID); err != tcpip.ErrNotSupported { t.Fatalf("got s.NDPConfigurations(%d) = %v, want = %s", nicID, err, tcpip.ErrNotSupported) } } // TestNUDConfigurationFailsForNotSupported tests to make sure we get a // NotSupported error if we attempt to set NUD configurations when the stack // doesn't support NUD. // // The stack will report to not support NUD if a neighbor cache for a given NIC // is not allocated. The networking stack will only allocate neighbor caches if // a protocol providing link address resolution is specified (e.g. ARP, IPv6). func TestSetNUDConfigurationFailsForNotSupported(t *testing.T) { const nicID = 1 e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ NUDConfigs: stack.DefaultNUDConfigurations(), UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } config := stack.NUDConfigurations{} if err := s.SetNUDConfigurations(nicID, config); err != tcpip.ErrNotSupported { t.Fatalf("got s.SetNDPConfigurations(%d, %+v) = %v, want = %s", nicID, config, err, tcpip.ErrNotSupported) } } // TestDefaultNUDConfigurationIsValid verifies that calling // resetInvalidFields() on the result of DefaultNUDConfigurations() does not // change anything. DefaultNUDConfigurations() should return a valid // NUDConfigurations. func TestDefaultNUDConfigurations(t *testing.T) { const nicID = 1 e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The networking // stack will only allocate neighbor caches if a protocol providing link // address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: stack.DefaultNUDConfigurations(), UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } c, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got, want := c, stack.DefaultNUDConfigurations(); got != want { t.Errorf("got stack.NUDConfigurations(%d) = %+v, want = %+v", nicID, got, want) } } func TestNUDConfigurationsBaseReachableTime(t *testing.T) { tests := []struct { name string baseReachableTime time.Duration want time.Duration }{ // Invalid cases { name: "EqualToZero", baseReachableTime: 0, want: defaultBaseReachableTime, }, // Valid cases { name: "MoreThanZero", baseReachableTime: time.Millisecond, want: time.Millisecond, }, { name: "MoreThanDefaultBaseReachableTime", baseReachableTime: 2 * defaultBaseReachableTime, want: 2 * defaultBaseReachableTime, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { const nicID = 1 c := stack.DefaultNUDConfigurations() c.BaseReachableTime = test.baseReachableTime e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The // networking stack will only allocate neighbor caches if a protocol // providing link address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: c, UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } sc, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got := sc.BaseReachableTime; got != test.want { t.Errorf("got BaseReachableTime = %q, want = %q", got, test.want) } }) } } func TestNUDConfigurationsMinRandomFactor(t *testing.T) { tests := []struct { name string minRandomFactor float32 want float32 }{ // Invalid cases { name: "LessThanZero", minRandomFactor: -1, want: defaultMinRandomFactor, }, { name: "EqualToZero", minRandomFactor: 0, want: defaultMinRandomFactor, }, // Valid cases { name: "MoreThanZero", minRandomFactor: 1, want: 1, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { const nicID = 1 c := stack.DefaultNUDConfigurations() c.MinRandomFactor = test.minRandomFactor e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The // networking stack will only allocate neighbor caches if a protocol // providing link address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: c, UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } sc, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got := sc.MinRandomFactor; got != test.want { t.Errorf("got MinRandomFactor = %f, want = %f", got, test.want) } }) } } func TestNUDConfigurationsMaxRandomFactor(t *testing.T) { tests := []struct { name string minRandomFactor float32 maxRandomFactor float32 want float32 }{ // Invalid cases { name: "LessThanZero", minRandomFactor: defaultMinRandomFactor, maxRandomFactor: -1, want: defaultMaxRandomFactor, }, { name: "EqualToZero", minRandomFactor: defaultMinRandomFactor, maxRandomFactor: 0, want: defaultMaxRandomFactor, }, { name: "LessThanMinRandomFactor", minRandomFactor: defaultMinRandomFactor, maxRandomFactor: defaultMinRandomFactor * 0.99, want: defaultMaxRandomFactor, }, { name: "MoreThanMinRandomFactorWhenMinRandomFactorIsLargerThanMaxRandomFactorDefault", minRandomFactor: defaultMaxRandomFactor * 2, maxRandomFactor: defaultMaxRandomFactor, want: defaultMaxRandomFactor * 6, }, // Valid cases { name: "EqualToMinRandomFactor", minRandomFactor: defaultMinRandomFactor, maxRandomFactor: defaultMinRandomFactor, want: defaultMinRandomFactor, }, { name: "MoreThanMinRandomFactor", minRandomFactor: defaultMinRandomFactor, maxRandomFactor: defaultMinRandomFactor * 1.1, want: defaultMinRandomFactor * 1.1, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { const nicID = 1 c := stack.DefaultNUDConfigurations() c.MinRandomFactor = test.minRandomFactor c.MaxRandomFactor = test.maxRandomFactor e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The // networking stack will only allocate neighbor caches if a protocol // providing link address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: c, UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } sc, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got := sc.MaxRandomFactor; got != test.want { t.Errorf("got MaxRandomFactor = %f, want = %f", got, test.want) } }) } } func TestNUDConfigurationsRetransmitTimer(t *testing.T) { tests := []struct { name string retransmitTimer time.Duration want time.Duration }{ // Invalid cases { name: "EqualToZero", retransmitTimer: 0, want: defaultRetransmitTimer, }, { name: "LessThanMinimumRetransmitTimer", retransmitTimer: minimumRetransmitTimer - time.Nanosecond, want: defaultRetransmitTimer, }, // Valid cases { name: "EqualToMinimumRetransmitTimer", retransmitTimer: minimumRetransmitTimer, want: minimumBaseReachableTime, }, { name: "LargetThanMinimumRetransmitTimer", retransmitTimer: 2 * minimumBaseReachableTime, want: 2 * minimumBaseReachableTime, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { const nicID = 1 c := stack.DefaultNUDConfigurations() c.RetransmitTimer = test.retransmitTimer e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The // networking stack will only allocate neighbor caches if a protocol // providing link address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: c, UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } sc, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got := sc.RetransmitTimer; got != test.want { t.Errorf("got RetransmitTimer = %q, want = %q", got, test.want) } }) } } func TestNUDConfigurationsDelayFirstProbeTime(t *testing.T) { tests := []struct { name string delayFirstProbeTime time.Duration want time.Duration }{ // Invalid cases { name: "EqualToZero", delayFirstProbeTime: 0, want: defaultDelayFirstProbeTime, }, // Valid cases { name: "MoreThanZero", delayFirstProbeTime: time.Millisecond, want: time.Millisecond, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { const nicID = 1 c := stack.DefaultNUDConfigurations() c.DelayFirstProbeTime = test.delayFirstProbeTime e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The // networking stack will only allocate neighbor caches if a protocol // providing link address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: c, UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } sc, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got := sc.DelayFirstProbeTime; got != test.want { t.Errorf("got DelayFirstProbeTime = %q, want = %q", got, test.want) } }) } } func TestNUDConfigurationsMaxMulticastProbes(t *testing.T) { tests := []struct { name string maxMulticastProbes uint32 want uint32 }{ // Invalid cases { name: "EqualToZero", maxMulticastProbes: 0, want: defaultMaxMulticastProbes, }, // Valid cases { name: "MoreThanZero", maxMulticastProbes: 1, want: 1, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { const nicID = 1 c := stack.DefaultNUDConfigurations() c.MaxMulticastProbes = test.maxMulticastProbes e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The // networking stack will only allocate neighbor caches if a protocol // providing link address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: c, UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } sc, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got := sc.MaxMulticastProbes; got != test.want { t.Errorf("got MaxMulticastProbes = %q, want = %q", got, test.want) } }) } } func TestNUDConfigurationsMaxUnicastProbes(t *testing.T) { tests := []struct { name string maxUnicastProbes uint32 want uint32 }{ // Invalid cases { name: "EqualToZero", maxUnicastProbes: 0, want: defaultMaxUnicastProbes, }, // Valid cases { name: "MoreThanZero", maxUnicastProbes: 1, want: 1, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { const nicID = 1 c := stack.DefaultNUDConfigurations() c.MaxUnicastProbes = test.maxUnicastProbes e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The // networking stack will only allocate neighbor caches if a protocol // providing link address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: c, UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } sc, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got := sc.MaxUnicastProbes; got != test.want { t.Errorf("got MaxUnicastProbes = %q, want = %q", got, test.want) } }) } } func TestNUDConfigurationsUnreachableTime(t *testing.T) { tests := []struct { name string unreachableTime time.Duration want time.Duration }{ // Invalid cases { name: "EqualToZero", unreachableTime: 0, want: defaultUnreachableTime, }, // Valid cases { name: "MoreThanZero", unreachableTime: time.Millisecond, want: time.Millisecond, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { const nicID = 1 c := stack.DefaultNUDConfigurations() c.UnreachableTime = test.unreachableTime e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired s := stack.New(stack.Options{ // A neighbor cache is required to store NUDConfigurations. The // networking stack will only allocate neighbor caches if a protocol // providing link address resolution is specified (e.g. ARP or IPv6). NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()}, NUDConfigs: c, UseNeighborCache: true, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } sc, err := s.NUDConfigurations(nicID) if err != nil { t.Fatalf("got stack.NUDConfigurations(%d) = %s", nicID, err) } if got := sc.UnreachableTime; got != test.want { t.Errorf("got UnreachableTime = %q, want = %q", got, test.want) } }) } } // TestNUDStateReachableTime verifies the correctness of the ReachableTime // computation. func TestNUDStateReachableTime(t *testing.T) { tests := []struct { name string baseReachableTime time.Duration minRandomFactor float32 maxRandomFactor float32 want time.Duration }{ { name: "AllZeros", baseReachableTime: 0, minRandomFactor: 0, maxRandomFactor: 0, want: 0, }, { name: "ZeroMaxRandomFactor", baseReachableTime: time.Second, minRandomFactor: 0, maxRandomFactor: 0, want: 0, }, { name: "ZeroMinRandomFactor", baseReachableTime: time.Second, minRandomFactor: 0, maxRandomFactor: 1, want: time.Duration(defaultFakeRandomNum * float32(time.Second)), }, { name: "FractionalRandomFactor", baseReachableTime: time.Duration(math.MaxInt64), minRandomFactor: 0.001, maxRandomFactor: 0.002, want: time.Duration((0.001 + (0.001 * defaultFakeRandomNum)) * float32(math.MaxInt64)), }, { name: "MinAndMaxRandomFactorsEqual", baseReachableTime: time.Second, minRandomFactor: 1, maxRandomFactor: 1, want: time.Second, }, { name: "MinAndMaxRandomFactorsDifferent", baseReachableTime: time.Second, minRandomFactor: 1, maxRandomFactor: 2, want: time.Duration((1.0 + defaultFakeRandomNum) * float32(time.Second)), }, { name: "MaxInt64", baseReachableTime: time.Duration(math.MaxInt64), minRandomFactor: 1, maxRandomFactor: 1, want: time.Duration(math.MaxInt64), }, { name: "Overflow", baseReachableTime: time.Duration(math.MaxInt64), minRandomFactor: 1.5, maxRandomFactor: 1.5, want: time.Duration(math.MaxInt64), }, { name: "DoubleOverflow", baseReachableTime: time.Duration(math.MaxInt64), minRandomFactor: 2.5, maxRandomFactor: 2.5, want: time.Duration(math.MaxInt64), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { c := stack.NUDConfigurations{ BaseReachableTime: test.baseReachableTime, MinRandomFactor: test.minRandomFactor, MaxRandomFactor: test.maxRandomFactor, } // A fake random number generator is used to ensure deterministic // results. rng := fakeRand{ num: defaultFakeRandomNum, } s := stack.NewNUDState(c, &rng) if got, want := s.ReachableTime(), test.want; got != want { t.Errorf("got ReachableTime = %q, want = %q", got, want) } }) } } // TestNUDStateRecomputeReachableTime exercises the ReachableTime function // twice to verify recomputation of reachable time when the min random factor, // max random factor, or base reachable time changes. func TestNUDStateRecomputeReachableTime(t *testing.T) { const defaultBase = time.Second const defaultMin = 2.0 * defaultMaxRandomFactor const defaultMax = 3.0 * defaultMaxRandomFactor tests := []struct { name string baseReachableTime time.Duration minRandomFactor float32 maxRandomFactor float32 want time.Duration }{ { name: "BaseReachableTime", baseReachableTime: 2 * defaultBase, minRandomFactor: defaultMin, maxRandomFactor: defaultMax, want: time.Duration((defaultMin + (defaultMax-defaultMin)*defaultFakeRandomNum) * float32(2*defaultBase)), }, { name: "MinRandomFactor", baseReachableTime: defaultBase, minRandomFactor: defaultMax, maxRandomFactor: defaultMax, want: time.Duration(defaultMax * float32(defaultBase)), }, { name: "MaxRandomFactor", baseReachableTime: defaultBase, minRandomFactor: defaultMin, maxRandomFactor: defaultMin, want: time.Duration(defaultMin * float32(defaultBase)), }, { name: "BothRandomFactor", baseReachableTime: defaultBase, minRandomFactor: 2 * defaultMin, maxRandomFactor: 2 * defaultMax, want: time.Duration((2*defaultMin + (2*defaultMax-2*defaultMin)*defaultFakeRandomNum) * float32(defaultBase)), }, { name: "BaseReachableTimeAndBothRandomFactors", baseReachableTime: 2 * defaultBase, minRandomFactor: 2 * defaultMin, maxRandomFactor: 2 * defaultMax, want: time.Duration((2*defaultMin + (2*defaultMax-2*defaultMin)*defaultFakeRandomNum) * float32(2*defaultBase)), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { c := stack.DefaultNUDConfigurations() c.BaseReachableTime = defaultBase c.MinRandomFactor = defaultMin c.MaxRandomFactor = defaultMax // A fake random number generator is used to ensure deterministic // results. rng := fakeRand{ num: defaultFakeRandomNum, } s := stack.NewNUDState(c, &rng) old := s.ReachableTime() if got, want := s.ReachableTime(), old; got != want { t.Errorf("got ReachableTime = %q, want = %q", got, want) } // Check for recomputation when changing the min random factor, the max // random factor, the base reachability time, or any permutation of those // three options. c.BaseReachableTime = test.baseReachableTime c.MinRandomFactor = test.minRandomFactor c.MaxRandomFactor = test.maxRandomFactor s.SetConfig(c) if got, want := s.ReachableTime(), test.want; got != want { t.Errorf("got ReachableTime = %q, want = %q", got, want) } // Verify that ReachableTime isn't recomputed when none of the // configuration options change. The random factor is changed so that if // a recompution were to occur, ReachableTime would change. rng.num = defaultFakeRandomNum / 2.0 if got, want := s.ReachableTime(), test.want; got != want { t.Errorf("got ReachableTime = %q, want = %q", got, want) } }) } }