blob: e1253f3103034a15969b5a29864a6ef5635618f2 [file] [log] [blame]
// 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"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/faketime"
"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
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
}
func TestNUDFunctions(t *testing.T) {
const nicID = 1
tests := []struct {
name string
nicID tcpip.NICID
netProtoFactory []stack.NetworkProtocolFactory
extraLinkCapabilities stack.LinkEndpointCapabilities
expectedErr tcpip.Error
}{
{
name: "Invalid NICID",
nicID: nicID + 1,
netProtoFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
extraLinkCapabilities: stack.CapabilityResolutionRequired,
expectedErr: &tcpip.ErrUnknownNICID{},
},
{
name: "No network protocol",
nicID: nicID,
expectedErr: &tcpip.ErrNotSupported{},
},
{
name: "With IPv6",
nicID: nicID,
netProtoFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
expectedErr: &tcpip.ErrNotSupported{},
},
{
name: "With resolution capability",
nicID: nicID,
extraLinkCapabilities: stack.CapabilityResolutionRequired,
expectedErr: &tcpip.ErrNotSupported{},
},
{
name: "With IPv6 and resolution capability",
nicID: nicID,
netProtoFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
extraLinkCapabilities: stack.CapabilityResolutionRequired,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clock := faketime.NewManualClock()
s := stack.New(stack.Options{
NUDConfigs: stack.DefaultNUDConfigurations(),
NetworkProtocols: test.netProtoFactory,
Clock: clock,
})
e := channel.New(0, 0, linkAddr1)
e.LinkEPCapabilities &^= stack.CapabilityResolutionRequired
e.LinkEPCapabilities |= test.extraLinkCapabilities
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
configs := stack.DefaultNUDConfigurations()
configs.BaseReachableTime = time.Hour
{
err := s.SetNUDConfigurations(test.nicID, ipv6.ProtocolNumber, configs)
if diff := cmp.Diff(test.expectedErr, err); diff != "" {
t.Errorf("s.SetNUDConfigurations(%d, %d, _) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
}
}
{
gotConfigs, err := s.NUDConfigurations(test.nicID, ipv6.ProtocolNumber)
if diff := cmp.Diff(test.expectedErr, err); diff != "" {
t.Errorf("s.NUDConfigurations(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
} else if test.expectedErr == nil {
if diff := cmp.Diff(configs, gotConfigs); diff != "" {
t.Errorf("got configs mismatch (-want +got):\n%s", diff)
}
}
}
for _, addr := range []tcpip.Address{llAddr1, llAddr2} {
{
err := s.AddStaticNeighbor(test.nicID, ipv6.ProtocolNumber, addr, linkAddr1)
if diff := cmp.Diff(test.expectedErr, err); diff != "" {
t.Errorf("s.AddStaticNeighbor(%d, %d, %s, %s) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, addr, linkAddr1, diff)
}
}
}
{
wantErr := test.expectedErr
for i := 0; i < 2; i++ {
{
err := s.RemoveNeighbor(test.nicID, ipv6.ProtocolNumber, llAddr1)
if diff := cmp.Diff(wantErr, err); diff != "" {
t.Errorf("s.RemoveNeighbor(%d, %d, '') error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
}
}
if test.expectedErr != nil {
break
}
// Removing a neighbor that does not exist should give us a bad address
// error.
wantErr = &tcpip.ErrBadAddress{}
}
}
{
neighbors, err := s.Neighbors(test.nicID, ipv6.ProtocolNumber)
if diff := cmp.Diff(test.expectedErr, err); diff != "" {
t.Errorf("s.Neigbors(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
} else if test.expectedErr == nil {
if diff := cmp.Diff(
[]stack.NeighborEntry{{Addr: llAddr2, LinkAddr: linkAddr1, State: stack.Static, UpdatedAtNanos: clock.NowNanoseconds()}},
neighbors,
); diff != "" {
t.Errorf("neighbors mismatch (-want +got):\n%s", diff)
}
}
}
{
err := s.ClearNeighbors(test.nicID, ipv6.ProtocolNumber)
if diff := cmp.Diff(test.expectedErr, err); diff != "" {
t.Errorf("s.ClearNeigbors(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
} else if test.expectedErr == nil {
if neighbors, err := s.Neighbors(test.nicID, ipv6.ProtocolNumber); err != nil {
t.Errorf("s.Neighbors(%d, %d): %s", test.nicID, ipv6.ProtocolNumber, err)
} else if len(neighbors) != 0 {
t.Errorf("got len(neighbors) = %d, want = 0; neighbors = %#v", len(neighbors), neighbors)
}
}
}
})
}
}
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.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: stack.DefaultNUDConfigurations(),
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
c, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
if err != nil {
t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
}
if got, want := c, stack.DefaultNUDConfigurations(); got != want {
t.Errorf("got stack.NUDConfigurations(%d, %d) = %+v, want = %+v", nicID, ipv6.ProtocolNumber, 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.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
if err != nil {
t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, 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.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
if err != nil {
t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, 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.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
if err != nil {
t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, 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.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
if err != nil {
t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, 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.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
if err != nil {
t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, 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.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
if err != nil {
t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, 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.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
if err != nil {
t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
}
if got := sc.MaxUnicastProbes; got != test.want {
t.Errorf("got MaxUnicastProbes = %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)
}
})
}
}