blob: bb2b2d705abf9ee6ef59d68a6484f8ddad9da121 [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
import (
"fmt"
"math"
"math/rand"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/faketime"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
const (
entryTestNetNumber tcpip.NetworkProtocolNumber = math.MaxUint32
entryTestNICID tcpip.NICID = 1
entryTestAddr1 = tcpip.Address("\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
entryTestAddr2 = tcpip.Address("\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
entryTestLinkAddr1 = tcpip.LinkAddress("\x0a\x00\x00\x00\x00\x01")
entryTestLinkAddr2 = tcpip.LinkAddress("\x0a\x00\x00\x00\x00\x02")
// entryTestNetDefaultMTU is the MTU, in bytes, used throughout the tests,
// except where another value is explicitly used. It is chosen to match the
// MTU of loopback interfaces on Linux systems.
entryTestNetDefaultMTU = 65536
)
// runImmediatelyScheduledJobs runs all jobs scheduled to run at the current
// time.
func runImmediatelyScheduledJobs(clock *faketime.ManualClock) {
clock.Advance(immediateDuration)
}
// The following unit tests exercise every state transition and verify its
// behavior with RFC 4681 and RFC 7048.
//
// | From | To | Cause | Update | Action | Event |
// | =========== | =========== | ========================================== | ======== | ===========| ======= |
// | Unknown | Unknown | Confirmation w/ unknown address | | | Added |
// | Unknown | Incomplete | Packet queued to unknown address | | Send probe | Added |
// | Unknown | Stale | Probe | | | Added |
// | Incomplete | Incomplete | Retransmit timer expired | | Send probe | Changed |
// | Incomplete | Reachable | Solicited confirmation | LinkAddr | Notify | Changed |
// | Incomplete | Stale | Unsolicited confirmation | LinkAddr | Notify | Changed |
// | Incomplete | Stale | Probe | LinkAddr | Notify | Changed |
// | Incomplete | Unreachable | Max probes sent without reply | | Notify | Changed |
// | Reachable | Reachable | Confirmation w/ different isRouter flag | IsRouter | | |
// | Reachable | Stale | Reachable timer expired | | | Changed |
// | Reachable | Stale | Probe or confirmation w/ different address | | | Changed |
// | Stale | Reachable | Solicited override confirmation | LinkAddr | | Changed |
// | Stale | Reachable | Solicited confirmation w/o address | | Notify | Changed |
// | Stale | Stale | Override confirmation | LinkAddr | | Changed |
// | Stale | Stale | Probe w/ different address | LinkAddr | | Changed |
// | Stale | Delay | Packet sent | | | Changed |
// | Delay | Reachable | Upper-layer confirmation | | | Changed |
// | Delay | Reachable | Solicited override confirmation | LinkAddr | | Changed |
// | Delay | Reachable | Solicited confirmation w/o address | | Notify | Changed |
// | Delay | Stale | Probe or confirmation w/ different address | | | Changed |
// | Delay | Probe | Delay timer expired | | Send probe | Changed |
// | Probe | Reachable | Solicited override confirmation | LinkAddr | | Changed |
// | Probe | Reachable | Solicited confirmation w/ same address | | Notify | Changed |
// | Probe | Reachable | Solicited confirmation w/o address | | Notify | Changed |
// | Probe | Stale | Probe or confirmation w/ different address | | | Changed |
// | Probe | Probe | Retransmit timer expired | | | Changed |
// | Probe | Unreachable | Max probes sent without reply | | Notify | Changed |
// | Unreachable | Incomplete | Packet queued | | Send probe | Changed |
// | Unreachable | Stale | Probe w/ different address | LinkAddr | | Changed |
type testEntryEventType uint8
const (
entryTestAdded testEntryEventType = iota
entryTestChanged
entryTestRemoved
)
func (t testEntryEventType) String() string {
switch t {
case entryTestAdded:
return "add"
case entryTestChanged:
return "change"
case entryTestRemoved:
return "remove"
default:
return fmt.Sprintf("unknown (%d)", t)
}
}
// Fields are exported for use with cmp.Diff.
type testEntryEventInfo struct {
EventType testEntryEventType
NICID tcpip.NICID
Entry NeighborEntry
}
func (e testEntryEventInfo) String() string {
return fmt.Sprintf("%s event for NIC #%d, %#v", e.EventType, e.NICID, e.Entry)
}
// testNUDDispatcher implements NUDDispatcher to validate the dispatching of
// events upon certain NUD state machine events.
type testNUDDispatcher struct {
mu struct {
sync.Mutex
events []testEntryEventInfo
}
}
var _ NUDDispatcher = (*testNUDDispatcher)(nil)
func (d *testNUDDispatcher) queueEvent(e testEntryEventInfo) {
d.mu.Lock()
defer d.mu.Unlock()
d.mu.events = append(d.mu.events, e)
}
func (d *testNUDDispatcher) OnNeighborAdded(nicID tcpip.NICID, entry NeighborEntry) {
d.queueEvent(testEntryEventInfo{
EventType: entryTestAdded,
NICID: nicID,
Entry: entry,
})
}
func (d *testNUDDispatcher) OnNeighborChanged(nicID tcpip.NICID, entry NeighborEntry) {
d.queueEvent(testEntryEventInfo{
EventType: entryTestChanged,
NICID: nicID,
Entry: entry,
})
}
func (d *testNUDDispatcher) OnNeighborRemoved(nicID tcpip.NICID, entry NeighborEntry) {
d.queueEvent(testEntryEventInfo{
EventType: entryTestRemoved,
NICID: nicID,
Entry: entry,
})
}
type entryTestLinkResolver struct {
mu struct {
sync.Mutex
probes []entryTestProbeInfo
}
}
var _ LinkAddressResolver = (*entryTestLinkResolver)(nil)
type entryTestProbeInfo struct {
RemoteAddress tcpip.Address
RemoteLinkAddress tcpip.LinkAddress
LocalAddress tcpip.Address
}
func (p entryTestProbeInfo) String() string {
return fmt.Sprintf("probe with RemoteAddress=%q, RemoteLinkAddress=%q, LocalAddress=%q", p.RemoteAddress, p.RemoteLinkAddress, p.LocalAddress)
}
// LinkAddressRequest sends a request for the LinkAddress of addr. Broadcasts
// to the local network if linkAddr is the zero value.
func (r *entryTestLinkResolver) LinkAddressRequest(targetAddr, localAddr tcpip.Address, linkAddr tcpip.LinkAddress) tcpip.Error {
r.mu.Lock()
defer r.mu.Unlock()
r.mu.probes = append(r.mu.probes, entryTestProbeInfo{
RemoteAddress: targetAddr,
RemoteLinkAddress: linkAddr,
LocalAddress: localAddr,
})
return nil
}
// ResolveStaticAddress attempts to resolve address without sending requests.
// It either resolves the name immediately or returns the empty LinkAddress.
func (r *entryTestLinkResolver) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) {
return "", false
}
// LinkAddressProtocol returns the network protocol of the addresses this
// resolver can resolve.
func (r *entryTestLinkResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
return entryTestNetNumber
}
func entryTestSetup(c NUDConfigurations) (*neighborEntry, *testNUDDispatcher, *entryTestLinkResolver, *faketime.ManualClock) {
clock := faketime.NewManualClock()
disp := testNUDDispatcher{}
nic := nic{
LinkEndpoint: nil, // entryTestLinkResolver doesn't use a LinkEndpoint
id: entryTestNICID,
stack: &Stack{
clock: clock,
nudDisp: &disp,
nudConfigs: c,
randomGenerator: rand.New(rand.NewSource(time.Now().UnixNano())),
},
stats: makeNICStats(),
}
netEP := (&testIPv6Protocol{}).NewEndpoint(&nic, nil)
nic.networkEndpoints = map[tcpip.NetworkProtocolNumber]NetworkEndpoint{
header.IPv6ProtocolNumber: netEP,
}
var linkRes entryTestLinkResolver
// Stub out the neighbor cache to verify deletion from the cache.
l := &linkResolver{
resolver: &linkRes,
}
l.neigh.init(&nic, &linkRes)
entry := newNeighborEntry(&l.neigh, entryTestAddr1 /* remoteAddr */, l.neigh.state)
l.neigh.mu.Lock()
l.neigh.mu.cache[entryTestAddr1] = entry
l.neigh.mu.Unlock()
nic.linkAddrResolvers = map[tcpip.NetworkProtocolNumber]*linkResolver{
header.IPv6ProtocolNumber: l,
}
return entry, &disp, &linkRes, clock
}
// TestEntryInitiallyUnknown verifies that the state of a newly created
// neighborEntry is Unknown.
func TestEntryInitiallyUnknown(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
e.mu.Lock()
if e.mu.neigh.State != Unknown {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
}
e.mu.Unlock()
clock.Advance(c.RetransmitTimer)
// No probes should have been sent.
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
// No events should have been dispatched.
nudDisp.mu.Lock()
if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryUnknownToUnknownWhenConfirmationWithUnknownAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
Solicited: false,
Override: false,
IsRouter: false,
})
if e.mu.neigh.State != Unknown {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
}
e.mu.Unlock()
clock.Advance(time.Hour)
// No probes should have been sent.
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
// No events should have been dispatched.
nudDisp.mu.Lock()
if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryUnknownToIncomplete(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
}
func unknownToIncomplete(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
if err := func() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.mu.neigh.State != Unknown {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
}
e.handlePacketQueuedLocked(entryTestAddr2)
if e.mu.neigh.State != Incomplete {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
}
return nil
}(); err != nil {
return err
}
runImmediatelyScheduledJobs(clock)
wantProbes := []entryTestProbeInfo{
{
RemoteAddress: entryTestAddr1,
RemoteLinkAddress: tcpip.LinkAddress(""),
LocalAddress: entryTestAddr2,
},
}
linkRes.mu.Lock()
diff := cmp.Diff(wantProbes, linkRes.mu.probes)
linkRes.mu.probes = nil
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestAdded,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: tcpip.LinkAddress(""),
State: Incomplete,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
return nil
}
func TestEntryUnknownToStale(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
}
func unknownToStale(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
if err := func() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.mu.neigh.State != Unknown {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
}
e.handleProbeLocked(entryTestLinkAddr1)
if e.mu.neigh.State != Stale {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
return nil
}(); err != nil {
return err
}
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestAdded,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
return nil
}
func TestEntryIncompleteToIncompleteDoesNotChangeUpdatedAt(t *testing.T) {
c := DefaultNUDConfigurations()
c.MaxMulticastProbes = 3
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
// UpdatedAt should remain the same during address resolution.
e.mu.Lock()
startedAt := e.mu.neigh.UpdatedAtNanos
e.mu.Unlock()
// Wait for the rest of the reachability probe transmissions, signifying
// Incomplete to Incomplete transitions.
for i := uint32(1); i < c.MaxMulticastProbes; i++ {
clock.Advance(c.RetransmitTimer)
wantProbes := []entryTestProbeInfo{
{
RemoteAddress: entryTestAddr1,
RemoteLinkAddress: tcpip.LinkAddress(""),
LocalAddress: entryTestAddr2,
},
}
linkRes.mu.Lock()
diff := cmp.Diff(wantProbes, linkRes.mu.probes)
linkRes.mu.probes = nil
linkRes.mu.Unlock()
if diff != "" {
t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
e.mu.Lock()
if got, want := e.mu.neigh.UpdatedAtNanos, startedAt; got != want {
t.Errorf("got e.mu.neigh.UpdatedAt = %q, want = %q", got, want)
}
e.mu.Unlock()
}
// UpdatedAt should change after failing address resolution. Timing out after
// sending the last probe transitions the entry to Unreachable.
clock.Advance(c.RetransmitTimer)
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: tcpip.LinkAddress(""),
State: Unreachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryIncompleteToReachable(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachable(...) = %s", err)
}
}
func incompleteToReachableWithFlags(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, flags ReachabilityConfirmationFlags) error {
if err := func() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.mu.neigh.State != Incomplete {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
}
e.handleConfirmationLocked(entryTestLinkAddr1, flags)
if e.mu.neigh.State != Reachable {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
return fmt.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
}
return nil
}(); err != nil {
return err
}
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Reachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
return nil
}
func incompleteToReachable(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
if err := incompleteToReachableWithFlags(e, nudDisp, linkRes, clock, ReachabilityConfirmationFlags{
Solicited: true,
Override: false,
IsRouter: false,
}); err != nil {
return err
}
e.mu.Lock()
isRouter := e.mu.isRouter
e.mu.Unlock()
if isRouter {
return fmt.Errorf("got e.mu.isRouter = %t, want = false", isRouter)
}
return nil
}
func TestEntryIncompleteToReachableWithRouterFlag(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToReachableWithRouterFlag(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachableWithRouterFlag(...) = %s", err)
}
}
func incompleteToReachableWithRouterFlag(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
if err := incompleteToReachableWithFlags(e, nudDisp, linkRes, clock, ReachabilityConfirmationFlags{
Solicited: true,
Override: false,
IsRouter: true,
}); err != nil {
return err
}
e.mu.Lock()
isRouter := e.mu.isRouter
e.mu.Unlock()
if !isRouter {
return fmt.Errorf("got e.mu.isRouter = %t, want = true", isRouter)
}
return nil
}
func TestEntryIncompleteToStaleWhenUnsolicitedConfirmation(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
Solicited: false,
Override: false,
IsRouter: false,
})
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
if e.mu.isRouter {
t.Errorf("got e.mu.isRouter = %t, want = false", e.mu.isRouter)
}
e.mu.Unlock()
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryIncompleteToStaleWhenProbe(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
e.mu.Lock()
e.handleProbeLocked(entryTestLinkAddr1)
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.mu.Unlock()
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryIncompleteToUnreachable(t *testing.T) {
c := DefaultNUDConfigurations()
c.MaxMulticastProbes = 3
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToUnreachable(...) = %s", err)
}
}
func incompleteToUnreachable(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
{
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Incomplete {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Incomplete)
}
}
// The first probe was sent in the transition from Unknown to Incomplete.
clock.Advance(c.RetransmitTimer)
// Observe each subsequent multicast probe transmitted.
for i := uint32(1); i < c.MaxMulticastProbes; i++ {
wantProbes := []entryTestProbeInfo{{
RemoteAddress: entryTestAddr1,
RemoteLinkAddress: "",
LocalAddress: entryTestAddr2,
}}
linkRes.mu.Lock()
diff := cmp.Diff(wantProbes, linkRes.mu.probes)
linkRes.mu.probes = nil
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probe #%d mismatch (-want, +got):\n%s", i+1, diff)
}
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Incomplete {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Incomplete)
}
clock.Advance(c.RetransmitTimer)
}
{
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Unreachable {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Unreachable)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: tcpip.LinkAddress(""),
State: Unreachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
return nil
}
type testLocker struct{}
var _ sync.Locker = (*testLocker)(nil)
func (*testLocker) Lock() {}
func (*testLocker) Unlock() {}
func TestEntryReachableToReachableClearsRouterWhenConfirmationWithoutRouter(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToReachableWithRouterFlag(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachableWithRouterFlag(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
Solicited: false,
Override: false,
IsRouter: false,
})
if e.mu.neigh.State != Reachable {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
if got, want := e.mu.isRouter, false; got != want {
t.Errorf("got e.mu.isRouter = %t, want = %t", got, want)
}
ipv6EP := e.cache.nic.networkEndpoints[header.IPv6ProtocolNumber].(*testIPv6Endpoint)
if ipv6EP.invalidatedRtr != e.mu.neigh.Addr {
t.Errorf("got ipv6EP.invalidatedRtr = %s, want = %s", ipv6EP.invalidatedRtr, e.mu.neigh.Addr)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
// No events should have been dispatched.
nudDisp.mu.Lock()
diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
nudDisp.mu.Unlock()
if diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
func TestEntryReachableToReachableWhenProbeWithSameAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachable(...) = %s", err)
}
e.mu.Lock()
e.handleProbeLocked(entryTestLinkAddr1)
if e.mu.neigh.State != Reachable {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
// No events should have been dispatched.
nudDisp.mu.Lock()
diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
nudDisp.mu.Unlock()
if diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
func TestEntryReachableToStaleWhenTimeout(t *testing.T) {
c := DefaultNUDConfigurations()
// Eliminate random factors from ReachableTime computation so the transition
// from Stale to Reachable will only take BaseReachableTime duration.
c.MinRandomFactor = 1
c.MaxRandomFactor = 1
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachable(...) = %s", err)
}
if err := reachableToStale(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("reachableToStale(...) = %s", err)
}
}
// reachableToStale transitions a neighborEntry in Reachable state to Stale
// state. Depends on the elimination of random factors in the ReachableTime
// computation.
//
// c.MinRandomFactor = 1
// c.MaxRandomFactor = 1
func reachableToStale(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
// Ensure there are no random factors in the ReachableTime computation.
if c.MinRandomFactor != 1 {
return fmt.Errorf("got c.MinRandomFactor = %f, want = 1", c.MinRandomFactor)
}
if c.MaxRandomFactor != 1 {
return fmt.Errorf("got c.MaxRandomFactor = %f, want = 1", c.MaxRandomFactor)
}
{
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Reachable {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Reachable)
}
}
clock.Advance(c.BaseReachableTime)
{
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Stale {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Stale)
}
}
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
return nil
}
func TestEntryReachableToStaleWhenProbeWithDifferentAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachable(...) = %s", err)
}
e.mu.Lock()
e.handleProbeLocked(entryTestLinkAddr2)
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryReachableToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachable(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
Solicited: false,
Override: false,
IsRouter: false,
})
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryReachableToStaleWhenConfirmationWithDifferentAddressAndOverride(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachable(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
Solicited: false,
Override: true,
IsRouter: false,
})
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryStaleToStaleWhenProbeWithSameAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
e.mu.Lock()
e.handleProbeLocked(entryTestLinkAddr1)
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
// No events should have been dispatched.
nudDisp.mu.Lock()
if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryStaleToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
Solicited: true,
Override: true,
IsRouter: false,
})
if e.mu.neigh.State != Reachable {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Reachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryStaleToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked("" /* linkAddr */, ReachabilityConfirmationFlags{
Solicited: true,
Override: false,
IsRouter: false,
})
if e.mu.neigh.State != Reachable {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Reachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryStaleToStaleWhenOverrideConfirmation(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
Solicited: false,
Override: true,
IsRouter: false,
})
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
}
e.mu.Unlock()
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryStaleToStaleWhenProbeUpdateAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
e.mu.Lock()
e.handleProbeLocked(entryTestLinkAddr2)
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryStaleToDelay(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
}
func staleToDelay(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
if err := func() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.mu.neigh.State != Stale {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.handlePacketQueuedLocked(entryTestAddr2)
if e.mu.neigh.State != Delay {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Delay)
}
return nil
}(); err != nil {
return err
}
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Delay,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
return nil
}
func TestEntryDelayToReachableWhenUpperLevelConfirmation(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
e.mu.Lock()
e.handleUpperLevelConfirmationLocked()
if e.mu.neigh.State != Reachable {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Reachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryDelayToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
Solicited: true,
Override: true,
IsRouter: false,
})
if e.mu.neigh.State != Reachable {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Reachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryDelayToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked("" /* linkAddr */, ReachabilityConfirmationFlags{
Solicited: true,
Override: false,
IsRouter: false,
})
if e.mu.neigh.State != Reachable {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Reachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryDelayToDelayWhenOverrideConfirmationWithSameAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
Solicited: false,
Override: true,
IsRouter: false,
})
if e.mu.neigh.State != Delay {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Delay)
}
if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
// No events should have been dispatched.
nudDisp.mu.Lock()
if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryDelayToStaleWhenProbeWithDifferentAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
e.mu.Lock()
e.handleProbeLocked(entryTestLinkAddr2)
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryDelayToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
Solicited: false,
Override: true,
IsRouter: false,
})
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryDelayToProbe(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
}
func delayToProbe(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
{
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Delay {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Delay)
}
}
// Wait for the first unicast probe to be transmitted, marking the
// transition from Delay to Probe.
clock.Advance(c.DelayFirstProbeTime)
{
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Probe {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
}
}
wantProbes := []entryTestProbeInfo{
{
RemoteAddress: entryTestAddr1,
RemoteLinkAddress: entryTestLinkAddr1,
},
}
{
linkRes.mu.Lock()
diff := cmp.Diff(wantProbes, linkRes.mu.probes)
linkRes.mu.probes = nil
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Probe,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
return nil
}
func TestEntryProbeToStaleWhenProbeWithDifferentAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
e.mu.Lock()
e.handleProbeLocked(entryTestLinkAddr2)
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryProbeToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
Solicited: false,
Override: true,
IsRouter: false,
})
if e.mu.neigh.State != Stale {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr2,
State: Stale,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
nudDisp.mu.Unlock()
}
func TestEntryProbeToProbeWhenOverrideConfirmationWithSameAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
e.mu.Lock()
e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
Solicited: false,
Override: true,
IsRouter: false,
})
if e.mu.neigh.State != Probe {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Probe)
}
if got, want := e.mu.neigh.LinkAddr, entryTestLinkAddr1; got != want {
t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", got, want)
}
e.mu.Unlock()
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
// No events should have been dispatched.
nudDisp.mu.Lock()
diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
nudDisp.mu.Unlock()
if diff != "" {
t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
func TestEntryProbeToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
if err := probeToReachableWithOverride(e, nudDisp, linkRes, clock, entryTestLinkAddr2); err != nil {
t.Fatalf("probeToReachableWithOverride(...) = %s", err)
}
}
func probeToReachableWithFlags(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) error {
if err := func() error {
e.mu.Lock()
defer e.mu.Unlock()
prevLinkAddr := e.mu.neigh.LinkAddr
if e.mu.neigh.State != Probe {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Probe)
}
e.handleConfirmationLocked(linkAddr, flags)
if e.mu.neigh.State != Reachable {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
if linkAddr == "" {
linkAddr = prevLinkAddr
}
if e.mu.neigh.LinkAddr != linkAddr {
return fmt.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, linkAddr)
}
return nil
}(); err != nil {
return err
}
// No probes should have been sent.
runImmediatelyScheduledJobs(clock)
{
linkRes.mu.Lock()
diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: linkAddr,
State: Reachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
return nil
}
func probeToReachableWithOverride(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, linkAddr tcpip.LinkAddress) error {
return probeToReachableWithFlags(e, nudDisp, linkRes, clock, linkAddr, ReachabilityConfirmationFlags{
Solicited: true,
Override: true,
IsRouter: false,
})
}
func TestEntryProbeToReachableWhenSolicitedConfirmationWithSameAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
if err := probeToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("probeToReachable(...) = %s", err)
}
}
func probeToReachable(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
return probeToReachableWithFlags(e, nudDisp, linkRes, clock, entryTestLinkAddr1, ReachabilityConfirmationFlags{
Solicited: true,
Override: false,
IsRouter: false,
})
}
func TestEntryProbeToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
c := DefaultNUDConfigurations()
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
if err := probeToReachableWithoutAddress(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("probeToReachableWithoutAddress(...) = %s", err)
}
}
func probeToReachableWithoutAddress(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
return probeToReachableWithFlags(e, nudDisp, linkRes, clock, "" /* linkAddr */, ReachabilityConfirmationFlags{
Solicited: true,
Override: false,
IsRouter: false,
})
}
func TestEntryProbeToUnreachable(t *testing.T) {
c := DefaultNUDConfigurations()
c.MaxMulticastProbes = 3
c.MaxUnicastProbes = 3
c.DelayFirstProbeTime = c.RetransmitTimer
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
if err := probeToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("probeToUnreachable(...) = %s", err)
}
}
func probeToUnreachable(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
{
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Probe {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
}
}
// The first probe was sent in the transition from Delay to Probe.
clock.Advance(c.RetransmitTimer)
// Observe each subsequent unicast probe transmitted.
for i := uint32(1); i < c.MaxUnicastProbes; i++ {
wantProbes := []entryTestProbeInfo{{
RemoteAddress: entryTestAddr1,
RemoteLinkAddress: entryTestLinkAddr1,
}}
linkRes.mu.Lock()
diff := cmp.Diff(wantProbes, linkRes.mu.probes)
linkRes.mu.probes = nil
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probe #%d mismatch (-want, +got):\n%s", i+1, diff)
}
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Probe {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
}
clock.Advance(c.RetransmitTimer)
}
{
e.mu.Lock()
state := e.mu.neigh.State
e.mu.Unlock()
if state != Unreachable {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Unreachable)
}
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: entryTestLinkAddr1,
State: Unreachable,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
return nil
}
func TestEntryUnreachableToIncomplete(t *testing.T) {
c := DefaultNUDConfigurations()
c.MaxMulticastProbes = 3
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToUnreachable(...) = %s", err)
}
if err := unreachableToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unreachableToIncomplete(...) = %s", err)
}
}
func unreachableToIncomplete(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
if err := func() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.mu.neigh.State != Unreachable {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unreachable)
}
e.handlePacketQueuedLocked(entryTestAddr2)
if e.mu.neigh.State != Incomplete {
return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
}
return nil
}(); err != nil {
return err
}
runImmediatelyScheduledJobs(clock)
wantProbes := []entryTestProbeInfo{
{
RemoteAddress: entryTestAddr1,
RemoteLinkAddress: tcpip.LinkAddress(""),
LocalAddress: entryTestAddr2,
},
}
linkRes.mu.Lock()
diff := cmp.Diff(wantProbes, linkRes.mu.probes)
linkRes.mu.probes = nil
linkRes.mu.Unlock()
if diff != "" {
return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
}
wantEvents := []testEntryEventInfo{
{
EventType: entryTestChanged,
NICID: entryTestNICID,
Entry: NeighborEntry{
Addr: entryTestAddr1,
LinkAddr: tcpip.LinkAddress(""),
State: Incomplete,
UpdatedAtNanos: clock.NowNanoseconds(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events)
nudDisp.mu.events = nil
nudDisp.mu.Unlock()
if diff != "" {
return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
}
}
return nil
}
func TestEntryUnreachableToStale(t *testing.T) {
c := DefaultNUDConfigurations()
c.MaxMulticastProbes = 3
// Eliminate random factors from ReachableTime computation so the transition
// from Stale to Reachable will only take BaseReachableTime duration.
c.MinRandomFactor = 1
c.MaxRandomFactor = 1
e, nudDisp, linkRes, clock := entryTestSetup(c)
if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToIncomplete(...) = %s", err)
}
if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToUnreachable(...) = %s", err)
}
if err := unreachableToIncomplete(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unreachableToIncomplete(...) = %s", err)
}
if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("incompleteToReachable(...) = %s", err)
}
if err := reachableToStale(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("reachableToStale(...) = %s", err)
}
}