Files
gvisor/pkg/tcpip/tests/integration/link_resolution_test.go
Kevin KrakauerandgVisor bot 3c75945fd6 netstack: remove PacketBuffer.IsNil()
The change was originally motivated by ticket references (cl/450976957), which
were never implemented.

PiperOrigin-RevId: 617480960
2024-03-20 05:31:48 -07:00

1840 lines
65 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 link_resolution_test
import (
"bytes"
"fmt"
"net"
"runtime"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checker"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/faketime"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/link/ethernet"
"gvisor.dev/gvisor/pkg/tcpip/link/pipe"
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/tests/utils"
tcptestutil "gvisor.dev/gvisor/pkg/tcpip/testutil"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
)
func setupStack(t *testing.T, stackOpts stack.Options, host1NICID, host2NICID tcpip.NICID) (*stack.Stack, *stack.Stack) {
return setupStackWithSeparateOpts(t, stackOpts, stackOpts, host1NICID, host2NICID)
}
func setupStackWithSeparateOpts(t *testing.T, stack1Opts stack.Options, stack2Opts stack.Options, host1NICID, host2NICID tcpip.NICID) (*stack.Stack, *stack.Stack) {
const maxFrameSize = header.IPv6MinimumMTU + header.EthernetMinimumSize
host1Stack := stack.New(stack1Opts)
host2Stack := stack.New(stack2Opts)
host1NIC, host2NIC := pipe.New(utils.LinkAddr1, utils.LinkAddr2, maxFrameSize)
if err := host1Stack.CreateNIC(host1NICID, utils.NewEthernetEndpoint(host1NIC)); err != nil {
t.Fatalf("host1Stack.CreateNIC(%d, _): %s", host1NICID, err)
}
if err := host2Stack.CreateNIC(host2NICID, utils.NewEthernetEndpoint(host2NIC)); err != nil {
t.Fatalf("host2Stack.CreateNIC(%d, _): %s", host2NICID, err)
}
if err := host1Stack.AddProtocolAddress(host1NICID, utils.Ipv4Addr1, stack.AddressProperties{}); err != nil {
t.Fatalf("host1Stack.AddProtocolAddress(%d, %+v, {}): %s", host1NICID, utils.Ipv4Addr1, err)
}
if err := host2Stack.AddProtocolAddress(host2NICID, utils.Ipv4Addr2, stack.AddressProperties{}); err != nil {
t.Fatalf("host2Stack.AddProtocolAddress(%d, %+v, {}): %s", host2NICID, utils.Ipv4Addr2, err)
}
if err := host1Stack.AddProtocolAddress(host1NICID, utils.Ipv6Addr1, stack.AddressProperties{}); err != nil {
t.Fatalf("host1Stack.AddProtocolAddress(%d, %+v, {}): %s", host1NICID, utils.Ipv6Addr1, err)
}
if err := host2Stack.AddProtocolAddress(host2NICID, utils.Ipv6Addr2, stack.AddressProperties{}); err != nil {
t.Fatalf("host2Stack.AddProtocolAddress(%d, %+v, {}): %s", host2NICID, utils.Ipv6Addr2, err)
}
host1Stack.SetRouteTable([]tcpip.Route{
{
Destination: utils.Ipv4Addr1.AddressWithPrefix.Subnet(),
NIC: host1NICID,
},
{
Destination: utils.Ipv6Addr1.AddressWithPrefix.Subnet(),
NIC: host1NICID,
},
})
host2Stack.SetRouteTable([]tcpip.Route{
{
Destination: utils.Ipv4Addr2.AddressWithPrefix.Subnet(),
NIC: host2NICID,
},
{
Destination: utils.Ipv6Addr2.AddressWithPrefix.Subnet(),
NIC: host2NICID,
},
})
return host1Stack, host2Stack
}
// TestPing tests that two hosts can ping each other when link resolution is
// enabled.
func TestPing(t *testing.T) {
const (
host1NICID = 1
host2NICID = 4
// icmpDataOffset is the offset to the data in both ICMPv4 and ICMPv6 echo
// request/reply packets.
icmpDataOffset = 8
)
tests := []struct {
name string
transProto tcpip.TransportProtocolNumber
netProto tcpip.NetworkProtocolNumber
remoteAddr tcpip.Address
icmpBuf func(*testing.T) []byte
}{
{
name: "IPv4 Ping",
transProto: icmp.ProtocolNumber4,
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
icmpBuf: func(t *testing.T) []byte {
data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
hdr := header.ICMPv4(make([]byte, header.ICMPv4MinimumSize+len(data)))
hdr.SetType(header.ICMPv4Echo)
if n := copy(hdr.Payload(), data[:]); n != len(data) {
t.Fatalf("copied %d bytes but expected to copy %d bytes", n, len(data))
}
return hdr
},
},
{
name: "IPv6 Ping",
transProto: icmp.ProtocolNumber6,
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
icmpBuf: func(t *testing.T) []byte {
data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
hdr := header.ICMPv6(make([]byte, header.ICMPv6MinimumSize+len(data)))
hdr.SetType(header.ICMPv6EchoRequest)
if n := copy(hdr.Payload(), data[:]); n != len(data) {
t.Fatalf("copied %d bytes but expected to copy %d bytes", n, len(data))
}
return hdr
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4, icmp.NewProtocol6},
}
host1Stack, host2Stack := setupStack(t, stackOpts, host1NICID, host2NICID)
defer host1Stack.Destroy()
defer host2Stack.Destroy()
var wq waiter.Queue
we, waiterCH := waiter.NewChannelEntry(waiter.ReadableEvents)
wq.EventRegister(&we)
ep, err := host1Stack.NewEndpoint(test.transProto, test.netProto, &wq)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", test.transProto, test.netProto, err)
}
defer ep.Close()
icmpBuf := test.icmpBuf(t)
var r bytes.Reader
r.Reset(icmpBuf)
wOpts := tcpip.WriteOptions{To: &tcpip.FullAddress{Addr: test.remoteAddr}}
if n, err := ep.Write(&r, wOpts); err != nil {
t.Fatalf("ep.Write(_, _): %s", err)
} else if want := int64(len(icmpBuf)); n != want {
t.Fatalf("got ep.Write(_, _) = (%d, _), want = (%d, _)", n, want)
}
// Wait for the endpoint to be readable.
<-waiterCH
var buf bytes.Buffer
opts := tcpip.ReadOptions{NeedRemoteAddr: true}
res, err := ep.Read(&buf, opts)
if err != nil {
t.Fatalf("ep.Read(_, %d, %#v): %s", len(icmpBuf), opts, err)
}
if diff := cmp.Diff(tcpip.ReadResult{
Count: buf.Len(),
Total: buf.Len(),
RemoteAddr: tcpip.FullAddress{Addr: test.remoteAddr},
}, res, checker.IgnoreCmpPath(
"ControlMessages",
"RemoteAddr.NIC",
"RemoteAddr.Port",
)); diff != "" {
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
}
if diff := cmp.Diff(buf.Bytes()[icmpDataOffset:], icmpBuf[icmpDataOffset:]); diff != "" {
t.Errorf("received data mismatch (-want +got):\n%s", diff)
}
})
}
}
type transportError struct {
origin tcpip.SockErrOrigin
typ uint8
code uint8
info uint32
kind stack.TransportErrorKind
}
func TestTCPLinkResolutionFailure(t *testing.T) {
const (
host1NICID = 1
host2NICID = 4
)
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
remoteAddr tcpip.Address
expectedWriteErr tcpip.Error
sockError tcpip.SockError
transErr transportError
}{
{
name: "IPv4 with resolvable remote",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
expectedWriteErr: nil,
},
{
name: "IPv6 with resolvable remote",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
expectedWriteErr: nil,
},
{
name: "IPv4 without resolvable remote",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Ipv4Addr3.AddressWithPrefix.Address,
expectedWriteErr: &tcpip.ErrHostUnreachable{},
sockError: tcpip.SockError{
Err: &tcpip.ErrHostUnreachable{},
Dst: tcpip.FullAddress{
NIC: host1NICID,
Addr: utils.Ipv4Addr3.AddressWithPrefix.Address,
Port: 1234,
},
Offender: tcpip.FullAddress{
NIC: host1NICID,
Addr: utils.Ipv4Addr1.AddressWithPrefix.Address,
},
NetProto: ipv4.ProtocolNumber,
},
transErr: transportError{
origin: tcpip.SockExtErrorOriginICMP,
typ: uint8(header.ICMPv4DstUnreachable),
code: uint8(header.ICMPv4HostUnreachable),
kind: stack.DestinationHostUnreachableTransportError,
},
},
{
name: "IPv6 without resolvable remote",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr3.AddressWithPrefix.Address,
expectedWriteErr: &tcpip.ErrHostUnreachable{},
sockError: tcpip.SockError{
Err: &tcpip.ErrHostUnreachable{},
Dst: tcpip.FullAddress{
NIC: host1NICID,
Addr: utils.Ipv6Addr3.AddressWithPrefix.Address,
Port: 1234,
},
Offender: tcpip.FullAddress{
NIC: host1NICID,
Addr: utils.Ipv6Addr1.AddressWithPrefix.Address,
},
NetProto: ipv6.ProtocolNumber,
},
transErr: transportError{
origin: tcpip.SockExtErrorOriginICMP6,
typ: uint8(header.ICMPv6DstUnreachable),
code: uint8(header.ICMPv6AddressUnreachable),
kind: stack.DestinationHostUnreachableTransportError,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clock := faketime.NewManualClock()
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
Clock: clock,
}
host1Stack, host2Stack := setupStack(t, stackOpts, host1NICID, host2NICID)
defer host1Stack.Destroy()
defer host2Stack.Destroy()
var listenerWQ waiter.Queue
listenerEP, err := host2Stack.NewEndpoint(tcp.ProtocolNumber, test.netProto, &listenerWQ)
if err != nil {
t.Fatalf("host2Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, test.netProto, err)
}
defer listenerEP.Close()
listenerAddr := tcpip.FullAddress{Port: 1234}
if err := listenerEP.Bind(listenerAddr); err != nil {
t.Fatalf("listenerEP.Bind(%#v): %s", listenerAddr, err)
}
if err := listenerEP.Listen(1); err != nil {
t.Fatalf("listenerEP.Listen(1): %s", err)
}
var clientWQ waiter.Queue
we, ch := waiter.NewChannelEntry(waiter.WritableEvents | waiter.EventErr)
clientWQ.EventRegister(&we)
clientEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, test.netProto, &clientWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, test.netProto, err)
}
defer clientEP.Close()
sockOpts := clientEP.SocketOptions()
sockOpts.SetIPv4RecvError(true)
sockOpts.SetIPv6RecvError(true)
remoteAddr := listenerAddr
remoteAddr.Addr = test.remoteAddr
{
err := clientEP.Connect(remoteAddr)
if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
t.Fatalf("got clientEP.Connect(%#v) = %s, want = %s", remoteAddr, err, &tcpip.ErrConnectStarted{})
}
}
// Wait for an error due to link resolution failing, or the endpoint to be
// writable.
if test.expectedWriteErr != nil {
nudConfigs, err := host1Stack.NUDConfigurations(host1NICID, test.netProto)
if err != nil {
t.Fatalf("host1Stack.NUDConfigurations(%d, %d): %s", host1NICID, test.netProto, err)
}
clock.Advance(time.Duration(nudConfigs.MaxMulticastProbes) * nudConfigs.RetransmitTimer)
} else {
clock.RunImmediatelyScheduledJobs()
}
<-ch
{
var r bytes.Reader
r.Reset([]byte{0})
var wOpts tcpip.WriteOptions
_, err := clientEP.Write(&r, wOpts)
if diff := cmp.Diff(test.expectedWriteErr, err); diff != "" {
t.Errorf("unexpected error from clientEP.Write(_, %#v), (-want, +got):\n%s", wOpts, diff)
}
}
if test.expectedWriteErr == nil {
return
}
sockErr := sockOpts.DequeueErr()
if sockErr == nil {
t.Fatalf("got sockOpts.DequeueErr() = nil, want = non-nil")
}
defer sockErr.Payload.Release()
sockErrCmpOpts := []cmp.Option{
cmpopts.IgnoreUnexported(tcpip.SockError{}),
cmp.Comparer(func(a, b tcpip.Error) bool {
// tcpip.Error holds an unexported field but the errors netstack uses
// are pre defined so we can simply compare pointers.
return a == b
}),
checker.IgnoreCmpPath(
// Ignore the payload since we do not know the TCP seq/ack numbers.
"Payload",
// Ignore the cause since we will compare its properties separately
// since the concrete type of the cause is unknown.
"Cause",
),
}
if addr, err := clientEP.GetLocalAddress(); err != nil {
t.Fatalf("clientEP.GetLocalAddress(): %s", err)
} else {
test.sockError.Offender.Port = addr.Port
}
if diff := cmp.Diff(&test.sockError, sockErr, sockErrCmpOpts...); diff != "" {
t.Errorf("socket error mismatch (-want +got):\n%s", diff)
}
transErr, ok := sockErr.Cause.(stack.TransportError)
if !ok {
t.Fatalf("socket error cause is not a transport error; cause = %#v", sockErr.Cause)
}
if diff := cmp.Diff(
test.transErr,
transportError{
origin: transErr.Origin(),
typ: transErr.Type(),
code: transErr.Code(),
info: transErr.Info(),
kind: transErr.Kind(),
},
cmp.AllowUnexported(transportError{}),
); diff != "" {
t.Errorf("socket error mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestForwardingWithLinkResolutionFailure(t *testing.T) {
const (
incomingNICID = 1
outgoingNICID = 2
ttl = 2
expectedHostUnreachableErrorCount = 1
)
outgoingLinkAddr := tcptestutil.MustParseLink("02:03:03:04:05:06")
rxICMPv4EchoRequest := func(e *channel.Endpoint, src, dst tcpip.Address) {
utils.RxICMPv4EchoRequest(e, src, dst, ttl)
}
rxICMPv6EchoRequest := func(e *channel.Endpoint, src, dst tcpip.Address) {
utils.RxICMPv6EchoRequest(e, src, dst, ttl)
}
arpChecker := func(t *testing.T, request *stack.PacketBuffer, src, dst tcpip.Address) {
if request.NetworkProtocolNumber != arp.ProtocolNumber {
t.Errorf("got request.NetworkProtocolNumber = %d, want = %d", request.NetworkProtocolNumber, arp.ProtocolNumber)
}
if request.EgressRoute.RemoteLinkAddress != header.EthernetBroadcastAddress {
t.Errorf("got request.EgressRoute.RemoteLinkAddress = %s, want = %s", request.EgressRoute.RemoteLinkAddress, header.EthernetBroadcastAddress)
}
rep := header.ARP(request.NetworkHeader().Slice())
if got := rep.Op(); got != header.ARPRequest {
t.Errorf("got Op() = %d, want = %d", got, header.ARPRequest)
}
if got := tcpip.LinkAddress(rep.HardwareAddressSender()); got != outgoingLinkAddr {
t.Errorf("got HardwareAddressSender = %s, want = %s", got, outgoingLinkAddr)
}
if got := tcpip.AddrFromSlice(rep.ProtocolAddressSender()); got != src {
t.Errorf("got ProtocolAddressSender = %s, want = %s", got, src)
}
if got := tcpip.AddrFromSlice(rep.ProtocolAddressTarget()); got != dst {
t.Errorf("got ProtocolAddressTarget = %s, want = %s", got, dst)
}
}
ndpChecker := func(t *testing.T, request *stack.PacketBuffer, src, dst tcpip.Address) {
if request.NetworkProtocolNumber != header.IPv6ProtocolNumber {
t.Fatalf("got Proto = %d, want = %d", request.NetworkProtocolNumber, header.IPv6ProtocolNumber)
}
snmc := header.SolicitedNodeAddr(dst)
if want := header.EthernetAddressFromMulticastIPv6Address(snmc); request.EgressRoute.RemoteLinkAddress != want {
t.Errorf("got remote link address = %s, want = %s", request.EgressRoute.RemoteLinkAddress, want)
}
payload := stack.PayloadSince(request.NetworkHeader())
defer payload.Release()
checker.IPv6(t, payload,
checker.SrcAddr(src),
checker.DstAddr(snmc),
checker.TTL(header.NDPHopLimit),
checker.NDPNS(
checker.NDPNSTargetAddress(dst),
))
}
icmpv4Checker := func(t *testing.T, v *buffer.View, src, dst tcpip.Address) {
checker.IPv4(t, v,
checker.SrcAddr(src),
checker.DstAddr(dst),
checker.TTL(ipv4.DefaultTTL),
checker.ICMPv4(
checker.ICMPv4Checksum(),
checker.ICMPv4Type(header.ICMPv4DstUnreachable),
checker.ICMPv4Code(header.ICMPv4HostUnreachable),
),
)
}
icmpv6Checker := func(t *testing.T, v *buffer.View, src, dst tcpip.Address) {
checker.IPv6(t, v,
checker.SrcAddr(src),
checker.DstAddr(dst),
checker.TTL(ipv6.DefaultTTL),
checker.ICMPv6(
checker.ICMPv6Type(header.ICMPv6DstUnreachable),
checker.ICMPv6Code(header.ICMPv6AddressUnreachable),
),
)
}
tests := []struct {
name string
networkProtocolFactory []stack.NetworkProtocolFactory
networkProtocolNumber tcpip.NetworkProtocolNumber
sourceAddr tcpip.Address
destAddr tcpip.Address
incomingAddr tcpip.AddressWithPrefix
outgoingAddr tcpip.AddressWithPrefix
transportProtocol func(*stack.Stack) stack.TransportProtocol
rx func(*channel.Endpoint, tcpip.Address, tcpip.Address)
linkResolutionRequestChecker func(*testing.T, *stack.PacketBuffer, tcpip.Address, tcpip.Address)
icmpReplyChecker func(*testing.T, *buffer.View, tcpip.Address, tcpip.Address)
mtu uint32
}{
{
name: "IPv4 Host unreachable",
networkProtocolFactory: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol},
networkProtocolNumber: header.IPv4ProtocolNumber,
sourceAddr: tcptestutil.MustParse4("10.0.0.2"),
destAddr: tcptestutil.MustParse4("11.0.0.2"),
incomingAddr: tcpip.AddressWithPrefix{
Address: tcpip.AddrFromSlice(net.ParseIP("10.0.0.1").To4()),
PrefixLen: 8,
},
outgoingAddr: tcpip.AddressWithPrefix{
Address: tcpip.AddrFromSlice(net.ParseIP("11.0.0.1").To4()),
PrefixLen: 8,
},
transportProtocol: icmp.NewProtocol4,
linkResolutionRequestChecker: arpChecker,
icmpReplyChecker: icmpv4Checker,
rx: rxICMPv4EchoRequest,
mtu: ipv4.MaxTotalSize,
},
{
name: "IPv6 Host unreachable",
networkProtocolFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
networkProtocolNumber: header.IPv6ProtocolNumber,
sourceAddr: tcptestutil.MustParse6("10::2"),
destAddr: tcptestutil.MustParse6("11::2"),
incomingAddr: tcpip.AddressWithPrefix{
Address: tcpip.AddrFromSlice(net.ParseIP("10::1").To16()),
PrefixLen: 64,
},
outgoingAddr: tcpip.AddressWithPrefix{
Address: tcpip.AddrFromSlice(net.ParseIP("11::1").To16()),
PrefixLen: 64,
},
transportProtocol: icmp.NewProtocol6,
linkResolutionRequestChecker: ndpChecker,
icmpReplyChecker: icmpv6Checker,
rx: rxICMPv6EchoRequest,
mtu: header.IPv6MinimumMTU,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clock := faketime.NewManualClock()
s := stack.New(stack.Options{
NetworkProtocols: test.networkProtocolFactory,
TransportProtocols: []stack.TransportProtocolFactory{test.transportProtocol},
Clock: clock,
})
defer s.Destroy()
// Set up endpoint through which we will receive packets.
incomingEndpoint := channel.New(1, test.mtu, "")
if err := s.CreateNIC(incomingNICID, incomingEndpoint); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", incomingNICID, err)
}
incomingProtoAddr := tcpip.ProtocolAddress{
Protocol: test.networkProtocolNumber,
AddressWithPrefix: test.incomingAddr,
}
if err := s.AddProtocolAddress(incomingNICID, incomingProtoAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", incomingNICID, incomingProtoAddr, err)
}
// Set up endpoint through which we will attempt to forward packets.
outgoingEndpoint := channel.New(1, test.mtu, outgoingLinkAddr)
outgoingEndpoint.LinkEPCapabilities |= stack.CapabilityResolutionRequired
if err := s.CreateNIC(outgoingNICID, outgoingEndpoint); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", outgoingNICID, err)
}
outgoingProtoAddr := tcpip.ProtocolAddress{
Protocol: test.networkProtocolNumber,
AddressWithPrefix: test.outgoingAddr,
}
if err := s.AddProtocolAddress(outgoingNICID, outgoingProtoAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", outgoingNICID, outgoingProtoAddr, err)
}
s.SetRouteTable([]tcpip.Route{
{
Destination: test.incomingAddr.Subnet(),
NIC: incomingNICID,
},
{
Destination: test.outgoingAddr.Subnet(),
NIC: outgoingNICID,
},
})
if err := s.SetForwardingDefaultAndAllNICs(test.networkProtocolNumber, true); err != nil {
t.Fatalf("SetForwardingDefaultAndAllNICs(%d, true): %s", test.networkProtocolNumber, err)
}
test.rx(incomingEndpoint, test.sourceAddr, test.destAddr)
nudConfigs, err := s.NUDConfigurations(outgoingNICID, test.networkProtocolNumber)
if err != nil {
t.Fatalf("s.NUDConfigurations(%d, %d): %s", outgoingNICID, test.networkProtocolNumber, err)
}
// Trigger the first packet on the endpoint.
clock.RunImmediatelyScheduledJobs()
for i := 0; i < int(nudConfigs.MaxMulticastProbes); i++ {
request := outgoingEndpoint.Read()
if request == nil {
t.Fatal("expected ARP packet through outgoing NIC")
}
test.linkResolutionRequestChecker(t, request, test.outgoingAddr.Address, test.destAddr)
request.DecRef()
// Advance the clock the span of one request timeout.
clock.Advance(nudConfigs.RetransmitTimer)
}
// Next, we make a blocking read to retrieve the error packet. This is
// necessary because outgoing packets are dequeued asynchronously when
// link resolution fails, and this dequeue is what triggers the ICMP
// error.
reply := incomingEndpoint.Read()
if reply == nil {
t.Fatal("expected ICMP packet through incoming NIC")
}
payload := stack.PayloadSince(reply.NetworkHeader())
defer payload.Release()
test.icmpReplyChecker(t, payload, test.incomingAddr.Address, test.sourceAddr)
reply.DecRef()
// Since link resolution failed, we don't expect the packet to be
// forwarded.
forwardedPacket := outgoingEndpoint.Read()
if forwardedPacket != nil {
t.Fatalf("expected no ICMP Echo packet through outgoing NIC, instead found: %#v", forwardedPacket)
}
if got, want := s.Stats().IP.Forwarding.HostUnreachable.Value(), expectedHostUnreachableErrorCount; int(got) != want {
t.Errorf("got rt.Stats().IP.Forwarding.HostUnreachable.Value() = %d, want = %d", got, want)
}
})
}
}
func TestGetLinkAddress(t *testing.T) {
const (
host1NICID = 1
host2NICID = 4
)
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
remoteAddr, localAddr tcpip.Address
expectedErr tcpip.Error
}{
{
name: "IPv4 resolvable",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
expectedErr: nil,
},
{
name: "IPv6 resolvable",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
expectedErr: nil,
},
{
name: "IPv4 not resolvable",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Ipv4Addr3.AddressWithPrefix.Address,
expectedErr: &tcpip.ErrTimeout{},
},
{
name: "IPv6 not resolvable",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr3.AddressWithPrefix.Address,
expectedErr: &tcpip.ErrTimeout{},
},
{
name: "IPv4 bad local address",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
localAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
expectedErr: &tcpip.ErrBadLocalAddress{},
},
{
name: "IPv6 bad local address",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
localAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
expectedErr: &tcpip.ErrBadLocalAddress{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clock := faketime.NewManualClock()
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
Clock: clock,
}
host1Stack, host2Stack := setupStack(t, stackOpts, host1NICID, host2NICID)
defer host1Stack.Destroy()
defer host2Stack.Destroy()
ch := make(chan stack.LinkResolutionResult, 1)
err := host1Stack.GetLinkAddress(host1NICID, test.remoteAddr, test.localAddr, test.netProto, func(r stack.LinkResolutionResult) {
ch <- r
})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
t.Fatalf("got host1Stack.GetLinkAddress(%d, %s, '', %d, _) = %s, want = %s", host1NICID, test.remoteAddr, test.netProto, err, &tcpip.ErrWouldBlock{})
}
wantRes := stack.LinkResolutionResult{Err: test.expectedErr}
if test.expectedErr == nil {
wantRes.LinkAddress = utils.LinkAddr2
}
nudConfigs, err := host1Stack.NUDConfigurations(host1NICID, test.netProto)
if err != nil {
t.Fatalf("host1Stack.NUDConfigurations(%d, %d): %s", host1NICID, test.netProto, err)
}
clock.Advance(time.Duration(nudConfigs.MaxMulticastProbes) * nudConfigs.RetransmitTimer)
select {
case got := <-ch:
if diff := cmp.Diff(wantRes, got); diff != "" {
t.Fatalf("link resolution result mismatch (-want +got):\n%s", diff)
}
default:
t.Fatal("event didn't arrive")
}
})
}
}
func TestRouteResolvedFields(t *testing.T) {
const (
host1NICID = 1
host2NICID = 4
)
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
localAddr tcpip.Address
remoteAddr tcpip.Address
immediatelyResolvable bool
expectedErr tcpip.Error
expectedLinkAddr tcpip.LinkAddress
}{
{
name: "IPv4 immediately resolvable",
netProto: ipv4.ProtocolNumber,
localAddr: utils.Ipv4Addr1.AddressWithPrefix.Address,
remoteAddr: header.IPv4AllSystems,
immediatelyResolvable: true,
expectedErr: nil,
expectedLinkAddr: header.EthernetAddressFromMulticastIPv4Address(header.IPv4AllSystems),
},
{
name: "IPv6 immediately resolvable",
netProto: ipv6.ProtocolNumber,
localAddr: utils.Ipv6Addr1.AddressWithPrefix.Address,
remoteAddr: header.IPv6AllNodesMulticastAddress,
immediatelyResolvable: true,
expectedErr: nil,
expectedLinkAddr: header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllNodesMulticastAddress),
},
{
name: "IPv4 resolvable",
netProto: ipv4.ProtocolNumber,
localAddr: utils.Ipv4Addr1.AddressWithPrefix.Address,
remoteAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
immediatelyResolvable: false,
expectedErr: nil,
expectedLinkAddr: utils.LinkAddr2,
},
{
name: "IPv6 resolvable",
netProto: ipv6.ProtocolNumber,
localAddr: utils.Ipv6Addr1.AddressWithPrefix.Address,
remoteAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
immediatelyResolvable: false,
expectedErr: nil,
expectedLinkAddr: utils.LinkAddr2,
},
{
name: "IPv4 not resolvable",
netProto: ipv4.ProtocolNumber,
localAddr: utils.Ipv4Addr1.AddressWithPrefix.Address,
remoteAddr: utils.Ipv4Addr3.AddressWithPrefix.Address,
immediatelyResolvable: false,
expectedErr: &tcpip.ErrTimeout{},
},
{
name: "IPv6 not resolvable",
netProto: ipv6.ProtocolNumber,
localAddr: utils.Ipv6Addr1.AddressWithPrefix.Address,
remoteAddr: utils.Ipv6Addr3.AddressWithPrefix.Address,
immediatelyResolvable: false,
expectedErr: &tcpip.ErrTimeout{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clock := faketime.NewManualClock()
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
Clock: clock,
}
host1Stack, host2Stack := setupStack(t, stackOpts, host1NICID, host2NICID)
defer host1Stack.Destroy()
defer host2Stack.Destroy()
r, err := host1Stack.FindRoute(host1NICID, test.localAddr, test.remoteAddr, test.netProto, false /* multicastLoop */)
if err != nil {
t.Fatalf("host1Stack.FindRoute(%d, %s, %s, %d, false): %s", host1NICID, test.localAddr, test.remoteAddr, test.netProto, err)
}
defer r.Release()
var wantRouteInfo stack.RouteInfo
wantRouteInfo.LocalLinkAddress = utils.LinkAddr1
wantRouteInfo.LocalAddress = test.localAddr
wantRouteInfo.RemoteAddress = test.remoteAddr
wantRouteInfo.NetProto = test.netProto
wantRouteInfo.Loop = stack.PacketOut
wantRouteInfo.RemoteLinkAddress = test.expectedLinkAddr
ch := make(chan stack.ResolvedFieldsResult, 1)
if !test.immediatelyResolvable {
wantUnresolvedRouteInfo := wantRouteInfo
wantUnresolvedRouteInfo.RemoteLinkAddress = ""
err := r.ResolvedFields(func(r stack.ResolvedFieldsResult) {
ch <- r
})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
t.Errorf("got r.ResolvedFields(_) = %s, want = %s", err, &tcpip.ErrWouldBlock{})
}
nudConfigs, err := host1Stack.NUDConfigurations(host1NICID, test.netProto)
if err != nil {
t.Fatalf("host1Stack.NUDConfigurations(%d, %d): %s", host1NICID, test.netProto, err)
}
clock.Advance(time.Duration(nudConfigs.MaxMulticastProbes) * nudConfigs.RetransmitTimer)
select {
case got := <-ch:
if diff := cmp.Diff(stack.ResolvedFieldsResult{RouteInfo: wantRouteInfo, Err: test.expectedErr}, got, cmp.AllowUnexported(stack.RouteInfo{})); diff != "" {
t.Errorf("route resolve result mismatch (-want +got):\n%s", diff)
}
default:
t.Fatalf("event didn't arrive")
}
if test.expectedErr != nil {
return
}
// At this point the neighbor table should be populated so the route
// should be immediately resolvable.
}
if err := r.ResolvedFields(func(r stack.ResolvedFieldsResult) {
ch <- r
}); err != nil {
t.Errorf("r.ResolvedFields(_): %s", err)
}
select {
case routeResolveRes := <-ch:
if diff := cmp.Diff(stack.ResolvedFieldsResult{RouteInfo: wantRouteInfo, Err: nil}, routeResolveRes, cmp.AllowUnexported(stack.RouteInfo{})); diff != "" {
t.Errorf("route resolve result from resolved route mismatch (-want +got):\n%s", diff)
}
default:
t.Fatal("expected route to be immediately resolvable")
}
})
}
}
func TestWritePacketsLinkResolution(t *testing.T) {
const (
host1NICID = 1
host2NICID = 4
)
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
remoteAddr tcpip.Address
expectedWriteErr tcpip.Error
}{
{
name: "IPv4",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
expectedWriteErr: nil,
},
{
name: "IPv6",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
expectedWriteErr: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
}
host1Stack, host2Stack := setupStack(t, stackOpts, host1NICID, host2NICID)
defer host1Stack.Destroy()
defer host2Stack.Destroy()
var serverWQ waiter.Queue
serverWE, serverCH := waiter.NewChannelEntry(waiter.ReadableEvents)
serverWQ.EventRegister(&serverWE)
serverEP, err := host2Stack.NewEndpoint(udp.ProtocolNumber, test.netProto, &serverWQ)
if err != nil {
t.Fatalf("host2Stack.NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, test.netProto, err)
}
defer serverEP.Close()
serverAddr := tcpip.FullAddress{Port: 1234}
if err := serverEP.Bind(serverAddr); err != nil {
t.Fatalf("serverEP.Bind(%#v): %s", serverAddr, err)
}
r, err := host1Stack.FindRoute(host1NICID, tcpip.Address{}, test.remoteAddr, test.netProto, false /* multicastLoop */)
if err != nil {
t.Fatalf("host1Stack.FindRoute(%d, '', %s, %d, false): %s", host1NICID, test.remoteAddr, test.netProto, err)
}
defer r.Release()
params := stack.NetworkHeaderParams{
Protocol: udp.ProtocolNumber,
TTL: 64,
TOS: stack.DefaultTOS,
}
data := []byte{1, 2}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: header.UDPMinimumSize + int(r.MaxHeaderLength()),
Payload: buffer.MakeWithData(data),
})
pkt.TransportProtocolNumber = udp.ProtocolNumber
length := uint16(pkt.Data().Size() + header.UDPMinimumSize)
udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
udpHdr.Encode(&header.UDPFields{
SrcPort: 5555,
DstPort: serverAddr.Port,
Length: length,
})
xsum := r.PseudoHeaderChecksum(udp.ProtocolNumber, length)
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(xsum))
if err := r.WritePacket(params, pkt); err != nil {
t.Fatalf("WritePacket(...): %s", err)
}
pkt.DecRef()
var writer bytes.Buffer
for {
var rOpts tcpip.ReadOptions
res, err := serverEP.Read(&writer, rOpts)
if err != nil {
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
<-serverCH
continue
}
t.Fatalf("serverEP.Read(_, %#v): %s", rOpts, err)
}
if res.Count != len(data) {
t.Fatalf("got res.Count = %d, want = %d", res.Count, len(data))
}
break
}
if got, want := host2Stack.Stats().UDP.PacketsReceived.Value(), uint64(1); got != want {
t.Errorf("got host2Stack.Stats().UDP.PacketsReceived.Value() = %d, want = %d", got, want)
}
if diff := cmp.Diff(data, writer.Bytes()); diff != "" {
t.Errorf("read bytes mismatch (-want +got):\n%s", diff)
}
})
}
}
type eventType int
const (
entryAdded eventType = iota
entryChanged
entryRemoved
)
func (t eventType) String() string {
switch t {
case entryAdded:
return "add"
case entryChanged:
return "change"
case entryRemoved:
return "remove"
default:
return fmt.Sprintf("unknown (%d)", t)
}
}
type eventInfo struct {
eventType eventType
nicID tcpip.NICID
entry stack.NeighborEntry
}
func (e eventInfo) String() string {
return fmt.Sprintf("%s event for NIC #%d, %#v", e.eventType, e.nicID, e.entry)
}
var _ stack.NUDDispatcher = (*nudDispatcher)(nil)
type nudDispatcher struct {
c chan eventInfo
}
func (d *nudDispatcher) OnNeighborAdded(nicID tcpip.NICID, entry stack.NeighborEntry) {
e := eventInfo{
eventType: entryAdded,
nicID: nicID,
entry: entry,
}
d.c <- e
}
func (d *nudDispatcher) OnNeighborChanged(nicID tcpip.NICID, entry stack.NeighborEntry) {
e := eventInfo{
eventType: entryChanged,
nicID: nicID,
entry: entry,
}
d.c <- e
}
func (d *nudDispatcher) OnNeighborRemoved(nicID tcpip.NICID, entry stack.NeighborEntry) {
e := eventInfo{
eventType: entryRemoved,
nicID: nicID,
entry: entry,
}
d.c <- e
}
func (d *nudDispatcher) expectEvent(want eventInfo) error {
if diff := cmp.Diff(want, <-d.c, cmp.AllowUnexported(eventInfo{}), cmpopts.IgnoreFields(stack.NeighborEntry{}, "UpdatedAt")); diff != "" {
return fmt.Errorf("got invalid event (-want +got):\n%s", diff)
}
return nil
}
// TestTCPConfirmNeighborReachability tests that TCP informs layers beneath it
// that the neighbor used for a route is reachable.
func TestTCPConfirmNeighborReachability(t *testing.T) {
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
remoteAddr tcpip.Address
neighborAddr tcpip.Address
getEndpoints func(*testing.T, *stack.Stack, *stack.Stack, *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{})
isHost1Listener bool
}{
{
name: "IPv4 active connection through neighbor",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Host2IPv4Addr.AddressWithPrefix.Address,
neighborAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
getEndpoints: func(t *testing.T, host1Stack, _, host2Stack *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{}) {
var listenerWQ waiter.Queue
listenerWE, listenerCH := waiter.NewChannelEntry(waiter.EventIn)
listenerWQ.EventRegister(&listenerWE)
listenerEP, err := host2Stack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &listenerWQ)
if err != nil {
t.Fatalf("host2Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
var clientWQ waiter.Queue
clientWE, clientCH := waiter.NewChannelEntry(waiter.ReadableEvents | waiter.WritableEvents)
clientWQ.EventRegister(&clientWE)
clientEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &clientWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
return listenerEP, listenerCH, clientEP, clientCH
},
},
{
name: "IPv6 active connection through neighbor",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Host2IPv6Addr.AddressWithPrefix.Address,
neighborAddr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
getEndpoints: func(t *testing.T, host1Stack, _, host2Stack *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{}) {
var listenerWQ waiter.Queue
listenerWE, listenerCH := waiter.NewChannelEntry(waiter.EventIn)
listenerWQ.EventRegister(&listenerWE)
listenerEP, err := host2Stack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &listenerWQ)
if err != nil {
t.Fatalf("host2Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv6.ProtocolNumber, err)
}
var clientWQ waiter.Queue
clientWE, clientCH := waiter.NewChannelEntry(waiter.ReadableEvents | waiter.WritableEvents)
clientWQ.EventRegister(&clientWE)
clientEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &clientWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv6.ProtocolNumber, err)
}
return listenerEP, listenerCH, clientEP, clientCH
},
},
{
name: "IPv4 active connection to neighbor",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
neighborAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
getEndpoints: func(t *testing.T, host1Stack, routerStack, _ *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{}) {
var listenerWQ waiter.Queue
listenerWE, listenerCH := waiter.NewChannelEntry(waiter.EventIn)
listenerWQ.EventRegister(&listenerWE)
listenerEP, err := routerStack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &listenerWQ)
if err != nil {
t.Fatalf("routerStack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
var clientWQ waiter.Queue
clientWE, clientCH := waiter.NewChannelEntry(waiter.ReadableEvents | waiter.WritableEvents)
clientWQ.EventRegister(&clientWE)
clientEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &clientWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
return listenerEP, listenerCH, clientEP, clientCH
},
},
{
name: "IPv6 active connection to neighbor",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
neighborAddr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
getEndpoints: func(t *testing.T, host1Stack, routerStack, _ *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{}) {
var listenerWQ waiter.Queue
listenerWE, listenerCH := waiter.NewChannelEntry(waiter.EventIn)
listenerWQ.EventRegister(&listenerWE)
listenerEP, err := routerStack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &listenerWQ)
if err != nil {
t.Fatalf("routerStack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv6.ProtocolNumber, err)
}
var clientWQ waiter.Queue
clientWE, clientCH := waiter.NewChannelEntry(waiter.ReadableEvents | waiter.WritableEvents)
clientWQ.EventRegister(&clientWE)
clientEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &clientWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv6.ProtocolNumber, err)
}
return listenerEP, listenerCH, clientEP, clientCH
},
},
{
name: "IPv4 passive connection to neighbor",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
neighborAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
getEndpoints: func(t *testing.T, host1Stack, routerStack, _ *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{}) {
var listenerWQ waiter.Queue
listenerWE, listenerCH := waiter.NewChannelEntry(waiter.EventIn)
listenerWQ.EventRegister(&listenerWE)
listenerEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &listenerWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
var clientWQ waiter.Queue
clientWE, clientCH := waiter.NewChannelEntry(waiter.ReadableEvents | waiter.WritableEvents)
clientWQ.EventRegister(&clientWE)
clientEP, err := routerStack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &clientWQ)
if err != nil {
t.Fatalf("routerStack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
return listenerEP, listenerCH, clientEP, clientCH
},
isHost1Listener: true,
},
{
name: "IPv6 passive connection to neighbor",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
neighborAddr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
getEndpoints: func(t *testing.T, host1Stack, routerStack, _ *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{}) {
var listenerWQ waiter.Queue
listenerWE, listenerCH := waiter.NewChannelEntry(waiter.EventIn)
listenerWQ.EventRegister(&listenerWE)
listenerEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &listenerWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv6.ProtocolNumber, err)
}
var clientWQ waiter.Queue
clientWE, clientCH := waiter.NewChannelEntry(waiter.ReadableEvents | waiter.WritableEvents)
clientWQ.EventRegister(&clientWE)
clientEP, err := routerStack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &clientWQ)
if err != nil {
t.Fatalf("routerStack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv6.ProtocolNumber, err)
}
return listenerEP, listenerCH, clientEP, clientCH
},
isHost1Listener: true,
},
{
name: "IPv4 passive connection through neighbor",
netProto: ipv4.ProtocolNumber,
remoteAddr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
neighborAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
getEndpoints: func(t *testing.T, host1Stack, _, host2Stack *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{}) {
var listenerWQ waiter.Queue
listenerWE, listenerCH := waiter.NewChannelEntry(waiter.EventIn)
listenerWQ.EventRegister(&listenerWE)
listenerEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &listenerWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
var clientWQ waiter.Queue
clientWE, clientCH := waiter.NewChannelEntry(waiter.ReadableEvents | waiter.WritableEvents)
clientWQ.EventRegister(&clientWE)
clientEP, err := host2Stack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &clientWQ)
if err != nil {
t.Fatalf("host2Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
return listenerEP, listenerCH, clientEP, clientCH
},
isHost1Listener: true,
},
{
name: "IPv6 passive connection through neighbor",
netProto: ipv6.ProtocolNumber,
remoteAddr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
neighborAddr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
getEndpoints: func(t *testing.T, host1Stack, _, host2Stack *stack.Stack) (tcpip.Endpoint, <-chan struct{}, tcpip.Endpoint, <-chan struct{}) {
var listenerWQ waiter.Queue
listenerWE, listenerCH := waiter.NewChannelEntry(waiter.EventIn)
listenerWQ.EventRegister(&listenerWE)
listenerEP, err := host1Stack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &listenerWQ)
if err != nil {
t.Fatalf("host1Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv6.ProtocolNumber, err)
}
var clientWQ waiter.Queue
clientWE, clientCH := waiter.NewChannelEntry(waiter.ReadableEvents | waiter.WritableEvents)
clientWQ.EventRegister(&clientWE)
clientEP, err := host2Stack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &clientWQ)
if err != nil {
t.Fatalf("host2Stack.NewEndpoint(%d, %d, _): %s", tcp.ProtocolNumber, ipv6.ProtocolNumber, err)
}
return listenerEP, listenerCH, clientEP, clientCH
},
isHost1Listener: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clock := faketime.NewManualClock()
nudDisp := nudDispatcher{
c: make(chan eventInfo, 3),
}
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
Clock: clock,
}
host1StackOpts := stackOpts
host1StackOpts.NUDDisp = &nudDisp
host1Stack := stack.New(host1StackOpts)
defer host1Stack.Destroy()
routerStack := stack.New(stackOpts)
defer routerStack.Destroy()
host2Stack := stack.New(stackOpts)
defer host2Stack.Destroy()
utils.SetupRoutedStacks(t, host1Stack, routerStack, host2Stack)
// Add a reachable dynamic entry to our neighbor table for the remote.
{
ch := make(chan stack.LinkResolutionResult, 1)
err := host1Stack.GetLinkAddress(utils.Host1NICID, test.neighborAddr, tcpip.Address{}, test.netProto, func(r stack.LinkResolutionResult) {
ch <- r
})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
t.Fatalf("got host1Stack.GetLinkAddress(%d, %s, '', %d, _) = %s, want = %s", utils.Host1NICID, test.neighborAddr, test.netProto, err, &tcpip.ErrWouldBlock{})
}
if diff := cmp.Diff(stack.LinkResolutionResult{LinkAddress: utils.LinkAddr2, Err: nil}, <-ch); diff != "" {
t.Fatalf("link resolution mismatch (-want +got):\n%s", diff)
}
}
if err := nudDisp.expectEvent(eventInfo{
eventType: entryAdded,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Incomplete, Addr: test.neighborAddr},
}); err != nil {
t.Fatalf("error waiting for initial NUD event: %s", err)
}
if err := nudDisp.expectEvent(eventInfo{
eventType: entryChanged,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Reachable, Addr: test.neighborAddr, LinkAddr: utils.LinkAddr2},
}); err != nil {
t.Fatalf("error waiting for reachable NUD event: %s", err)
}
// Wait for the remote's neighbor entry to be stale before creating a
// TCP connection from host1 to some remote.
nudConfigs, err := host1Stack.NUDConfigurations(utils.Host1NICID, test.netProto)
if err != nil {
t.Fatalf("host1Stack.NUDConfigurations(%d, %d): %s", utils.Host1NICID, test.netProto, err)
}
// The maximum reachable time for a neighbor is some maximum random factor
// applied to the base reachable time.
//
// See NUDConfigurations.BaseReachableTime for more information.
maxReachableTime := time.Duration(float32(nudConfigs.BaseReachableTime) * nudConfigs.MaxRandomFactor)
clock.Advance(maxReachableTime)
if err := nudDisp.expectEvent(eventInfo{
eventType: entryChanged,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Stale, Addr: test.neighborAddr, LinkAddr: utils.LinkAddr2},
}); err != nil {
t.Fatalf("error waiting for stale NUD event: %s", err)
}
listenerEP, listenerCH, clientEP, clientCH := test.getEndpoints(t, host1Stack, routerStack, host2Stack)
defer listenerEP.Close()
defer clientEP.Close()
listenerAddr := tcpip.FullAddress{Addr: test.remoteAddr, Port: 1234}
if err := listenerEP.Bind(listenerAddr); err != nil {
t.Fatalf("listenerEP.Bind(%#v): %s", listenerAddr, err)
}
// A backlog of 1 results in SYN cookies being used for all passive
// connections to make sure the only spot in the accept queue is not
// taken by a connection that never completes the handshake. We use
// a backlog of 2 to make sure SYN cookies are not used.
//
// We avoid SYN cookies to make sure that an accepted endpoint is able
// to confirm the neighbor's reachability through the cached neighbor
// entry in the endpoint's route. When SYN cookies are used, the
// accepted endpoint is constructed when the handshake has already been
// established and such an endpoint's route will not have a cached
// neighbor entry as it was not used to send any of packets for the
// handshake.
if err := listenerEP.Listen(2); err != nil {
t.Fatalf("listenerEP.Listen(2): %s", err)
}
{
err := clientEP.Connect(listenerAddr)
if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
t.Fatalf("got clientEP.Connect(%#v) = %s, want = %s", listenerAddr, err, &tcpip.ErrConnectStarted{})
}
}
// Wait for the TCP handshake to complete then make sure the neighbor is
// reachable without entering the probe state as TCP should provide NUD
// with confirmation that the neighbor is reachable (indicated by a
// successful 3-way handshake).
<-clientCH
if err := nudDisp.expectEvent(eventInfo{
eventType: entryChanged,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Delay, Addr: test.neighborAddr, LinkAddr: utils.LinkAddr2},
}); err != nil {
t.Fatalf("error waiting for delay NUD event: %s", err)
}
<-listenerCH
if err := nudDisp.expectEvent(eventInfo{
eventType: entryChanged,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Reachable, Addr: test.neighborAddr, LinkAddr: utils.LinkAddr2},
}); err != nil {
t.Fatalf("error waiting for reachable NUD event: %s", err)
}
peerEP, peerWQ, err := listenerEP.Accept(nil)
if err != nil {
t.Fatalf("listenerEP.Accept(): %s", err)
}
defer peerEP.Close()
peerWE, peerCH := waiter.NewChannelEntry(waiter.ReadableEvents)
peerWQ.EventRegister(&peerWE)
// Wait for the neighbor to be stale again then send data to the remote.
//
// On successful transmission, the neighbor should become reachable
// without probing the neighbor as a TCP ACK would be received which is an
// indication of the neighbor being reachable.
clock.Advance(maxReachableTime)
if err := nudDisp.expectEvent(eventInfo{
eventType: entryChanged,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Stale, Addr: test.neighborAddr, LinkAddr: utils.LinkAddr2},
}); err != nil {
t.Fatalf("error waiting for stale NUD event: %s", err)
}
{
var r bytes.Reader
r.Reset([]byte{0})
var wOpts tcpip.WriteOptions
if _, err := clientEP.Write(&r, wOpts); err != nil {
t.Errorf("clientEP.Write(_, %#v): %s", wOpts, err)
}
}
// Heads up, there is a race here.
//
// Incoming TCP segments are handled in
// tcp.(*endpoint).handleSegmentLocked:
//
// - tcp.(*endpoint).rcv.handleRcvdSegment puts the segment on the
// segment queue and notifies waiting readers (such as this channel)
//
// - tcp.(*endpoint).snd.handleRcvdSegment sends an ACK for the segment
// and notifies the NUD machinery that the peer is reachable
//
// Thus we must permit a delay between the readable signal and the
// expected NUD event.
//
// At the time of writing, this race is reliably hit with gotsan.
<-peerCH
for len(nudDisp.c) == 0 {
runtime.Gosched()
}
if err := nudDisp.expectEvent(eventInfo{
eventType: entryChanged,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Delay, Addr: test.neighborAddr, LinkAddr: utils.LinkAddr2},
}); err != nil {
t.Fatalf("error waiting for delay NUD event: %s", err)
}
if test.isHost1Listener {
// If host1 is not the client, host1 does not send any data so TCP
// has no way to know it is making forward progress. Because of this,
// TCP should not mark the route reachable and NUD should go through the
// probe state.
clock.Advance(nudConfigs.DelayFirstProbeTime)
if err := nudDisp.expectEvent(eventInfo{
eventType: entryChanged,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Probe, Addr: test.neighborAddr, LinkAddr: utils.LinkAddr2},
}); err != nil {
t.Fatalf("error waiting for probe NUD event: %s", err)
}
}
{
var r bytes.Reader
r.Reset([]byte{0})
var wOpts tcpip.WriteOptions
if _, err := peerEP.Write(&r, wOpts); err != nil {
t.Errorf("peerEP.Write(_, %#v): %s", wOpts, err)
}
}
<-clientCH
if err := nudDisp.expectEvent(eventInfo{
eventType: entryChanged,
nicID: utils.Host1NICID,
entry: stack.NeighborEntry{State: stack.Reachable, Addr: test.neighborAddr, LinkAddr: utils.LinkAddr2},
}); err != nil {
t.Fatalf("error waiting for reachable NUD event: %s", err)
}
})
}
}
func TestDAD(t *testing.T) {
dadConfigs := stack.DADConfigurations{
DupAddrDetectTransmits: 1,
RetransmitTimer: time.Second,
}
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
dadNetProto tcpip.NetworkProtocolNumber
remoteAddr tcpip.Address
expectedResult stack.DADResult
}{
{
name: "IPv4 own address",
netProto: ipv4.ProtocolNumber,
dadNetProto: arp.ProtocolNumber,
remoteAddr: utils.Ipv4Addr1.AddressWithPrefix.Address,
expectedResult: &stack.DADSucceeded{},
},
{
name: "IPv6 own address",
netProto: ipv6.ProtocolNumber,
dadNetProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr1.AddressWithPrefix.Address,
expectedResult: &stack.DADSucceeded{},
},
{
name: "IPv4 duplicate address",
netProto: ipv4.ProtocolNumber,
dadNetProto: arp.ProtocolNumber,
remoteAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
expectedResult: &stack.DADDupAddrDetected{HolderLinkAddress: utils.LinkAddr2},
},
{
name: "IPv6 duplicate address",
netProto: ipv6.ProtocolNumber,
dadNetProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
expectedResult: &stack.DADDupAddrDetected{HolderLinkAddress: utils.LinkAddr2},
},
{
name: "IPv4 no duplicate address",
netProto: ipv4.ProtocolNumber,
dadNetProto: arp.ProtocolNumber,
remoteAddr: utils.Ipv4Addr3.AddressWithPrefix.Address,
expectedResult: &stack.DADSucceeded{},
},
{
name: "IPv6 no duplicate address",
netProto: ipv6.ProtocolNumber,
dadNetProto: ipv6.ProtocolNumber,
remoteAddr: utils.Ipv6Addr3.AddressWithPrefix.Address,
expectedResult: &stack.DADSucceeded{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clock := faketime.NewManualClock()
stackOpts := stack.Options{
Clock: clock,
NetworkProtocols: []stack.NetworkProtocolFactory{
arp.NewProtocol,
ipv4.NewProtocol,
ipv6.NewProtocol,
},
}
host1Stack, host2Stack := setupStack(t, stackOpts, utils.Host1NICID, utils.Host2NICID)
defer host1Stack.Destroy()
defer host2Stack.Destroy()
// DAD should be disabled by default.
if res, err := host1Stack.CheckDuplicateAddress(utils.Host1NICID, test.netProto, test.remoteAddr, func(r stack.DADResult) {
t.Errorf("unexpectedly called DAD completion handler when DAD was supposed to be disabled")
}); err != nil {
t.Fatalf("host1Stack.CheckDuplicateAddress(%d, %d, %s, _): %s", utils.Host1NICID, test.netProto, test.remoteAddr, err)
} else if res != stack.DADDisabled {
t.Errorf("got host1Stack.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", utils.Host1NICID, test.netProto, test.remoteAddr, res, stack.DADDisabled)
}
// Enable DAD then attempt to check if an address is duplicated.
netEP, err := host1Stack.GetNetworkEndpoint(utils.Host1NICID, test.dadNetProto)
if err != nil {
t.Fatalf("host1Stack.GetNetworkEndpoint(%d, %d): %s", utils.Host1NICID, test.dadNetProto, err)
}
dad, ok := netEP.(stack.DuplicateAddressDetector)
if !ok {
t.Fatalf("expected %T to implement stack.DuplicateAddressDetector", netEP)
}
dad.SetDADConfigurations(dadConfigs)
ch := make(chan stack.DADResult, 3)
if res, err := host1Stack.CheckDuplicateAddress(utils.Host1NICID, test.netProto, test.remoteAddr, func(r stack.DADResult) {
ch <- r
}); err != nil {
t.Fatalf("host1Stack.CheckDuplicateAddress(%d, %d, %s, _): %s", utils.Host1NICID, test.netProto, test.remoteAddr, err)
} else if res != stack.DADStarting {
t.Errorf("got host1Stack.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", utils.Host1NICID, test.netProto, test.remoteAddr, res, stack.DADStarting)
}
expectResults := 1
if _, ok := test.expectedResult.(*stack.DADSucceeded); ok {
const delta = time.Nanosecond
clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer - delta)
select {
case r := <-ch:
t.Fatalf("unexpectedly got DAD result before the DAD timeout; r = %#v", r)
default:
}
// If we expect the resolve to succeed try requesting DAD again on the
// same address. The handler for the new request should be called once
// the original DAD request completes.
expectResults = 2
if res, err := host1Stack.CheckDuplicateAddress(utils.Host1NICID, test.netProto, test.remoteAddr, func(r stack.DADResult) {
ch <- r
}); err != nil {
t.Fatalf("host1Stack.CheckDuplicateAddress(%d, %d, %s, _): %s", utils.Host1NICID, test.netProto, test.remoteAddr, err)
} else if res != stack.DADAlreadyRunning {
t.Errorf("got host1Stack.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", utils.Host1NICID, test.netProto, test.remoteAddr, res, stack.DADAlreadyRunning)
}
clock.Advance(delta)
}
for i := 0; i < expectResults; i++ {
if diff := cmp.Diff(test.expectedResult, <-ch); diff != "" {
t.Errorf("(i=%d) DAD result mismatch (-want +got):\n%s", i, diff)
}
}
// Should have no more results.
select {
case r := <-ch:
t.Errorf("unexpectedly got an extra DAD result; r = %#v", r)
default:
}
})
}
}
type settableLinkEndpoint struct {
stack.LinkEndpoint
mu sync.Mutex
addr tcpip.LinkAddress
}
func newSettableLinkEndpoint(e stack.LinkEndpoint) *settableLinkEndpoint {
return &settableLinkEndpoint{
LinkEndpoint: e,
addr: e.LinkAddress(),
}
}
func (e *settableLinkEndpoint) setLinkAddress(addr tcpip.LinkAddress) {
e.mu.Lock()
defer e.mu.Unlock()
e.addr = addr
}
func (e *settableLinkEndpoint) LinkAddress() tcpip.LinkAddress {
e.mu.Lock()
defer e.mu.Unlock()
return e.addr
}
type monitorableLinkEndpoint struct {
stack.LinkEndpoint
ch chan tcpip.LinkAddress
}
func newMonitorableLinkEndpoint(e stack.LinkEndpoint) *monitorableLinkEndpoint {
return &monitorableLinkEndpoint{e, make(chan tcpip.LinkAddress, 1)}
}
func (e *monitorableLinkEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
for _, pkt := range pkts.AsSlice() {
dstAddr := header.Ethernet(pkt.LinkHeader().Slice()).DestinationAddress()
e.ch <- dstAddr
}
e.LinkEndpoint.WritePackets(pkts)
return 0, nil
}
func (e *monitorableLinkEndpoint) waitForLinkAddress(addr tcpip.LinkAddress, wait time.Duration) error {
c := time.After(wait)
for {
select {
case sentAddr := <-e.ch:
if addr == sentAddr {
return nil
}
case <-c:
return fmt.Errorf("timed out waiting for endpoint to send packet with destination address: %v", addr)
}
}
}
func TestUpdateCachedNeighborEntry(t *testing.T) {
d := []byte{1, 2}
params := stack.NetworkHeaderParams{
Protocol: udp.ProtocolNumber,
TTL: 64,
TOS: stack.DefaultTOS,
}
writePacket := func(t *testing.T, r *stack.Route) {
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: header.UDPMinimumSize + int(r.MaxHeaderLength()),
Payload: buffer.MakeWithData(d),
})
if err := r.WritePacket(params, pkt); err != nil {
t.Fatalf("WritePacket(...): %s", err)
}
pkt.DecRef()
}
const (
host1NICID = 1
host2NICID = 4
)
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
arp.NewProtocol,
ipv4.NewProtocolWithOptions(ipv4.Options{
IGMP: ipv4.IGMPOptions{
Enabled: false,
},
}),
ipv6.NewProtocolWithOptions(ipv6.Options{
MLD: ipv6.MLDOptions{
Enabled: false,
},
}),
},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
}
const maxFrameSize = header.IPv6MinimumMTU + header.EthernetMinimumSize
host1Stack := stack.New(stackOpts)
host2Stack := stack.New(stackOpts)
defer host1Stack.Destroy()
defer host2Stack.Destroy()
host1Pipe, host2Pipe := pipe.New(utils.LinkAddr1, utils.LinkAddr2, maxFrameSize)
host1NICMonitorable := newMonitorableLinkEndpoint(ethernet.New(host1Pipe))
host2NICSettable := newSettableLinkEndpoint(host2Pipe)
if err := host1Stack.CreateNIC(host1NICID, host1NICMonitorable); err != nil {
t.Fatalf("host1Stack.CreateNIC(%d, _): %s", host1NICID, err)
}
if err := host2Stack.CreateNIC(host2NICID, ethernet.New(host2NICSettable)); err != nil {
t.Fatalf("host2Stack.CreateNIC(%d, _): %s", host2NICID, err)
}
if err := host1Stack.AddProtocolAddress(host1NICID, utils.Ipv4Addr1, stack.AddressProperties{}); err != nil {
t.Fatalf("host1Stack.AddProtocolAddress(%d, %+v, {}): %s", host1NICID, utils.Ipv4Addr1, err)
}
if err := host2Stack.AddProtocolAddress(host2NICID, utils.Ipv4Addr2, stack.AddressProperties{}); err != nil {
t.Fatalf("host2Stack.AddProtocolAddress(%d, %+v, {}): %s", host2NICID, utils.Ipv4Addr2, err)
}
host1Stack.SetRouteTable([]tcpip.Route{
{
Destination: utils.Ipv4Addr1.AddressWithPrefix.Subnet(),
NIC: host1NICID,
},
})
host2Stack.SetRouteTable([]tcpip.Route{
{
Destination: utils.Ipv4Addr2.AddressWithPrefix.Subnet(),
NIC: host2NICID,
},
})
localAddr := utils.Ipv4Addr1.AddressWithPrefix.Address
neighborAddr := utils.Ipv4Addr2.AddressWithPrefix.Address
// Obtain a route to a neighbor.
r, err := host1Stack.FindRoute(host1NICID, localAddr, neighborAddr, header.IPv4ProtocolNumber, false)
if err != nil {
t.Fatalf("host1Stack.FindRoute(...): %s", err)
}
// Send packet to neighbor (start link resolution & resolve, then send
// packet). Send twice to use cached address the second time.
for i := 0; i < 2; i++ {
go writePacket(t, r)
if err := host1NICMonitorable.waitForLinkAddress(utils.LinkAddr2, time.Second); err != nil {
t.Fatalf("host1NIC.waitForLinkAddress(%s): %s", utils.LinkAddr2, err)
}
}
// Neighbor no longer reachable, deleted from the neighbor cache.
host1Stack.RemoveNeighbor(host1NICID, header.IPv4ProtocolNumber, neighborAddr)
host2Stack.DisableNIC(host2NICID)
// Send a packet to the neighbor that's no longer reachable (should fail).
go writePacket(t, r)
if err := host1NICMonitorable.waitForLinkAddress(utils.LinkAddr2, time.Second); err == nil {
t.Fatalf("got host1NIC.waitForLinkAddress(%s) = nil, want err", utils.LinkAddr2)
}
// Neighbor reachable again with new MAC address.
host2Stack.EnableNIC(host2NICID)
host2NICSettable.setLinkAddress(utils.LinkAddr3)
// Pending packet should eventually reach the new neighbor.
if err := host1NICMonitorable.waitForLinkAddress(utils.LinkAddr3, 5*time.Second); err != nil {
t.Fatalf("host1NIC.waitForLinkAddress(%s): %s", utils.LinkAddr3, err)
}
}