Files
visor/pkg/tcpip/stack/neighbor_entry_test.go
Tamir DubersteinandHanzo Dev e479442145 checklocks: guard networking test helpers
The fake clock and networking helpers protect mutable state with mutexes
without exposing those contracts to checklocks. Annotate the clock,
timers, notifications, and helper state, including the existing locked
clock and DAD initialization helpers.

Adapt the helper annotations from #7053 to current source, including
multicast forwarding state added since that proposal.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-21 12:23:26 -04:00

2315 lines
68 KiB
Go

// 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"
"gvisor.dev/gvisor/pkg/tcpip/testutil"
)
const (
entryTestNetNumber tcpip.NetworkProtocolNumber = math.MaxUint32
entryTestNICID tcpip.NICID = 1
entryTestLinkAddr1 = tcpip.LinkAddress("\x0a\x00\x00\x00\x00\x01")
entryTestLinkAddr2 = tcpip.LinkAddress("\x0a\x00\x00\x00\x00\x02")
)
var (
entryTestAddr1 = testutil.MustParse6("a::1")
entryTestAddr2 = testutil.MustParse6("a::2")
)
// 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
// +checklocks: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
// +checklocks: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 (*entryTestLinkResolver) ResolveStaticAddress(tcpip.Address) (tcpip.LinkAddress, bool) {
return "", false
}
// LinkAddressProtocol returns the network protocol of the addresses this
// resolver can resolve.
func (*entryTestLinkResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
return entryTestNetNumber
}
func entryTestSetup(c NUDConfigurations) (*neighborEntry, *testNUDDispatcher, *entryTestLinkResolver, *faketime.ManualClock) {
clock := faketime.NewManualClock()
disp := testNUDDispatcher{}
nic := nic{
NetworkLinkEndpoint: nil, // entryTestLinkResolver doesn't use this.
id: entryTestNICID,
stack: &Stack{
clock: clock,
nudDisp: &disp,
nudConfigs: c,
insecureRNG: rand.New(rand.NewSource(time.Now().UnixNano())),
},
stats: makeNICStats(tcpip.NICStats{}.FillIn()),
}
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, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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.UpdatedAt
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.UpdatedAt, 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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.getNetworkEndpoint(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, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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.handleUpperLevelConfirmation()
e.mu.RLock()
if e.mu.neigh.State != Reachable {
t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
}
e.mu.RUnlock()
// 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
if diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{})); 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 TestGetRemoteLinkAddressFailsWhenResolutionRequired(t *testing.T) {
c := DefaultNUDConfigurations()
c.MinRandomFactor = 1
c.MaxRandomFactor = 1
e, nudDisp, linkRes, clock := entryTestSetup(c)
if _, ok := e.getRemoteLinkAddress(); ok {
t.Errorf("getRemoteLinkAddress() = _, true, want false")
}
if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("unknownToStale(...) = %s", err)
}
if _, ok := e.getRemoteLinkAddress(); ok {
t.Errorf("getRemoteLinkAddress() = _, true, want false")
}
if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("staleToDelay(...) = %s", err)
}
if _, ok := e.getRemoteLinkAddress(); !ok {
t.Errorf("getRemoteLinkAddress() = _, false, want true")
}
if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("delayToProbe(...) = %s", err)
}
if _, ok := e.getRemoteLinkAddress(); !ok {
t.Errorf("getRemoteLinkAddress() = _, false, want true")
}
if err := probeToReachable(e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("probeToReachable(...) = %s", err)
}
if _, ok := e.getRemoteLinkAddress(); !ok {
t.Errorf("getRemoteLinkAddress() = _, false, want true")
}
if err := reachableToStale(c, e, nudDisp, linkRes, clock); err != nil {
t.Fatalf("reachableToStale(...) = %s", err)
}
if _, ok := e.getRemoteLinkAddress(); ok {
t.Errorf("getRemoteLinkAddress() = _, true, want false")
}
}
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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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,
UpdatedAt: clock.NowMonotonic(),
},
},
}
{
nudDisp.mu.Lock()
diff := cmp.Diff(wantEvents, nudDisp.mu.events, cmp.AllowUnexported(tcpip.MonotonicTime{}))
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)
}
}