// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package stack_test contains tests for the stack. It is in its own package so
// that the tests can also validate that all definitions needed to implement
// transport and network protocols are properly exported by the stack package.
package stack_test

import (
	"bytes"
	"fmt"
	"math"
	"net"
	"sort"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"gvisor.dev/gvisor/pkg/buffer"
	"gvisor.dev/gvisor/pkg/rand"
	"gvisor.dev/gvisor/pkg/sync"
	"gvisor.dev/gvisor/pkg/tcpip"
	"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/loopback"
	"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/testutil"
	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)

const (
	fakeNetNumber        tcpip.NetworkProtocolNumber = math.MaxUint32
	fakeNetHeaderLen                                 = 12
	fakeDefaultPrefixLen                             = 32

	// fakeControlProtocol is used for control packets that represent
	// destination port unreachable.
	fakeControlProtocol tcpip.TransportProtocolNumber = 2

	// defaultMTU is the MTU, in bytes, used throughout the tests, except
	// where another value is explicitly used. It is chosen to match the MTU
	// of loopback interfaces on linux systems.
	defaultMTU = 65536

	dstAddrOffset        = 0
	srcAddrOffset        = 4
	protocolNumberOffset = 8
)

func checkGetMainNICAddress(s *stack.Stack, nicID tcpip.NICID, proto tcpip.NetworkProtocolNumber, want tcpip.AddressWithPrefix) error {
	if addr, err := s.GetMainNICAddress(nicID, proto); err != nil {
		return fmt.Errorf("stack.GetMainNICAddress(%d, %d): %s", nicID, proto, err)
	} else if addr != want {
		return fmt.Errorf("got stack.GetMainNICAddress(%d, %d) = %s, want = %s", nicID, proto, addr, want)
	}
	return nil
}

// fakeNetworkEndpoint is a network-layer protocol endpoint. It counts sent and
// received packets; the counts of all endpoints are aggregated in the protocol
// descriptor.
//
// Headers of this protocol are fakeNetHeaderLen bytes. Addresses are 4 bytes,
// but we only use the first byte.
type fakeNetworkEndpoint struct {
	stack.AddressableEndpointState

	mu struct {
		sync.RWMutex

		enabled             bool
		forwarding          bool
		multicastForwarding bool
	}

	nic        stack.NetworkInterface
	proto      *fakeNetworkProtocol
	dispatcher stack.TransportDispatcher
}

func (f *fakeNetworkEndpoint) Enable() tcpip.Error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.mu.enabled = true
	return nil
}

func (f *fakeNetworkEndpoint) Enabled() bool {
	f.mu.RLock()
	defer f.mu.RUnlock()
	return f.mu.enabled
}

func (f *fakeNetworkEndpoint) Disable() {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.mu.enabled = false
}

func (f *fakeNetworkEndpoint) MTU() uint32 {
	return f.nic.MTU() - uint32(f.MaxHeaderLength())
}

func (*fakeNetworkEndpoint) DefaultTTL() uint8 {
	return 123
}

func (f *fakeNetworkEndpoint) HandlePacket(pkt *stack.PacketBuffer) {
	if _, _, ok := f.proto.Parse(pkt); !ok {
		return
	}

	// Increment the received packet count in the protocol descriptor.
	netHdr := pkt.NetworkHeader().Slice()

	dst := tcpip.AddrFromSlice(netHdr[dstAddrOffset:][:header.IPv4AddressSize])
	addressEndpoint := f.AcquireAssignedAddress(dst, f.nic.Promiscuous(), stack.CanBePrimaryEndpoint, true /* readOnly */)
	if addressEndpoint == nil {
		return
	}

	f.proto.packetCount[int(dst.AsSlice()[0])%len(f.proto.packetCount)]++

	// Handle control packets.
	if netHdr[protocolNumberOffset] == uint8(fakeControlProtocol) {
		hdr, ok := pkt.Data().Consume(fakeNetHeaderLen)
		if !ok {
			return
		}
		f.dispatcher.DeliverTransportError(
			tcpip.AddrFrom4Slice(hdr[srcAddrOffset:srcAddrOffset+header.IPv4AddressSize]),
			tcpip.AddrFrom4Slice(hdr[dstAddrOffset:dstAddrOffset+header.IPv4AddressSize]),
			fakeNetNumber,
			tcpip.TransportProtocolNumber(hdr[protocolNumberOffset]),
			// Nothing checks the error.
			nil, /* transport error */
			pkt,
		)
		return
	}

	transProtoNum := tcpip.TransportProtocolNumber(netHdr[protocolNumberOffset])
	switch err := f.proto.stack.ParsePacketBufferTransport(transProtoNum, pkt); err {
	case stack.ParsedOK:
	case stack.UnknownTransportProtocol, stack.TransportLayerParseError:
		// The transport layer will handle unknown protocols and transport layer
		// parsing errors.
	default:
		panic(fmt.Sprintf("unexpected error parsing transport header = %d", err))
	}

	// Dispatch the packet to the transport protocol.
	f.dispatcher.DeliverTransportPacket(transProtoNum, pkt)
}

func (f *fakeNetworkEndpoint) EndpointHeaderSize() uint32 {
	return fakeNetHeaderLen
}

func (f *fakeNetworkEndpoint) MaxHeaderLength() uint16 {
	return f.nic.MaxHeaderLength() + fakeNetHeaderLen
}

func (f *fakeNetworkEndpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber {
	return f.proto.Number()
}

func (f *fakeNetworkEndpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams, pkt *stack.PacketBuffer) tcpip.Error {
	// Increment the sent packet count in the protocol descriptor.
	remote := r.RemoteAddress()
	f.proto.sendPacketCount[int(remote.AsSlice()[0])%len(f.proto.sendPacketCount)]++

	// Add the protocol's header to the packet and send it to the link
	// endpoint.
	hdr := pkt.NetworkHeader().Push(fakeNetHeaderLen)
	pkt.NetworkProtocolNumber = fakeNetNumber
	copy(hdr[dstAddrOffset:], remote.AsSlice())
	local := r.LocalAddress()
	copy(hdr[srcAddrOffset:], local.AsSlice())
	hdr[protocolNumberOffset] = byte(params.Protocol)

	if r.Loop()&stack.PacketLoop != 0 {
		f.HandlePacket(pkt.Clone())
	}
	if r.Loop()&stack.PacketOut == 0 {
		return nil
	}

	return f.nic.WritePacket(r, pkt)
}

// WritePackets implements stack.LinkEndpoint.WritePackets.
func (*fakeNetworkEndpoint) WritePackets(*stack.Route, stack.PacketBufferList, stack.NetworkHeaderParams) (int, tcpip.Error) {
	panic("not implemented")
}

func (*fakeNetworkEndpoint) WriteHeaderIncludedPacket(*stack.Route, *stack.PacketBuffer) tcpip.Error {
	return &tcpip.ErrNotSupported{}
}

func (f *fakeNetworkEndpoint) Close() {
	f.AddressableEndpointState.Cleanup()
}

// Stats implements NetworkEndpoint.
func (*fakeNetworkEndpoint) Stats() stack.NetworkEndpointStats {
	return &fakeNetworkEndpointStats{}
}

var _ stack.NetworkEndpointStats = (*fakeNetworkEndpointStats)(nil)

type fakeNetworkEndpointStats struct{}

// IsNetworkEndpointStats implements stack.NetworkEndpointStats.
func (*fakeNetworkEndpointStats) IsNetworkEndpointStats() {}

type addMulticastRouteData struct {
	addresses stack.UnicastSourceAndMulticastDestination
	route     stack.MulticastRoute
}

type enableMulticastForwardingForProtocolResult struct {
	AlreadyEnabled bool
	Err            tcpip.Error
}

// fakeNetworkProtocol is a network-layer protocol descriptor. It aggregates the
// number of packets sent and received via endpoints of this protocol. The index
// where packets are added is given by the packet's destination address MOD 10.
type fakeNetworkProtocol struct {
	stack *stack.Stack

	packetCount     [10]int
	sendPacketCount [10]int
	defaultTTL      uint8

	addMulticastRouteData          addMulticastRouteData
	multicastRouteLastUsedTimeData stack.UnicastSourceAndMulticastDestination
	removeMulticastRouteData       stack.UnicastSourceAndMulticastDestination

	enableMulticastForwardingForProtocolResult  enableMulticastForwardingForProtocolResult
	disableMulticastForwardingForProtocolCalled bool
}

func (*fakeNetworkProtocol) Number() tcpip.NetworkProtocolNumber {
	return fakeNetNumber
}

func (*fakeNetworkProtocol) MinimumPacketSize() int {
	return fakeNetHeaderLen
}

func (f *fakeNetworkProtocol) PacketCount(intfAddr byte) int {
	return f.packetCount[int(intfAddr)%len(f.packetCount)]
}

func (*fakeNetworkProtocol) ParseAddresses(v []byte) (src, dst tcpip.Address) {
	return tcpip.AddrFrom4Slice(v[srcAddrOffset:][:header.IPv4AddressSize]), tcpip.AddrFrom4Slice(v[dstAddrOffset:][:header.IPv4AddressSize])
}

func (f *fakeNetworkProtocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.TransportDispatcher) stack.NetworkEndpoint {
	e := &fakeNetworkEndpoint{
		nic:        nic,
		proto:      f,
		dispatcher: dispatcher,
	}
	e.AddressableEndpointState.Init(e, stack.AddressableEndpointStateOptions{HiddenWhileDisabled: false})
	return e
}

func (f *fakeNetworkProtocol) SetOption(option tcpip.SettableNetworkProtocolOption) tcpip.Error {
	switch v := option.(type) {
	case *tcpip.DefaultTTLOption:
		f.defaultTTL = uint8(*v)
		return nil
	default:
		return &tcpip.ErrUnknownProtocolOption{}
	}
}

func (f *fakeNetworkProtocol) Option(option tcpip.GettableNetworkProtocolOption) tcpip.Error {
	switch v := option.(type) {
	case *tcpip.DefaultTTLOption:
		*v = tcpip.DefaultTTLOption(f.defaultTTL)
		return nil
	default:
		return &tcpip.ErrUnknownProtocolOption{}
	}
}

// Close implements NetworkProtocol.Close.
func (*fakeNetworkProtocol) Close() {}

// Wait implements NetworkProtocol.Wait.
func (*fakeNetworkProtocol) Wait() {}

// Parse implements NetworkProtocol.Parse.
func (*fakeNetworkProtocol) Parse(pkt *stack.PacketBuffer) (tcpip.TransportProtocolNumber, bool, bool) {
	hdr, ok := pkt.NetworkHeader().Consume(fakeNetHeaderLen)
	if !ok {
		return 0, false, false
	}
	pkt.NetworkProtocolNumber = fakeNetNumber
	return tcpip.TransportProtocolNumber(hdr[protocolNumberOffset]), true, true
}

// AddMulticastRoute implements
// MulticastForwardingNetworkProtocol.AddMulticastRoute.
func (f *fakeNetworkProtocol) AddMulticastRoute(addresses stack.UnicastSourceAndMulticastDestination, route stack.MulticastRoute) tcpip.Error {
	f.addMulticastRouteData = addMulticastRouteData{addresses, route}
	return nil
}

// RemoveMulticastRoute implements
// MulticastForwardingNetworkProtocol.RemoveMulticastRoute.
func (f *fakeNetworkProtocol) RemoveMulticastRoute(addresses stack.UnicastSourceAndMulticastDestination) tcpip.Error {
	f.removeMulticastRouteData = addresses
	return nil
}

// MulticastRouteLastUsedTime implements
// MulticastForwardingNetworkProtocol.MulticastRouteLastUsedTime.
func (f *fakeNetworkProtocol) MulticastRouteLastUsedTime(addresses stack.UnicastSourceAndMulticastDestination) (tcpip.MonotonicTime, tcpip.Error) {
	f.multicastRouteLastUsedTimeData = addresses
	return tcpip.MonotonicTime{}, nil
}

// EnableMulticastForwarding implements
// MulticastForwardingNetworkProtocol.EnableMulticastForwarding.
func (f *fakeNetworkProtocol) EnableMulticastForwarding(stack.MulticastForwardingEventDispatcher) (bool, tcpip.Error) {
	return f.enableMulticastForwardingForProtocolResult.AlreadyEnabled, f.enableMulticastForwardingForProtocolResult.Err
}

// DisableMulticastForwarding implements
// MulticastForwardingNetworkProtocol.DisableMulticastForwarding.
func (f *fakeNetworkProtocol) DisableMulticastForwarding() {
	f.disableMulticastForwardingForProtocolCalled = true
}

// Forwarding implements stack.ForwardingNetworkEndpoint.
func (f *fakeNetworkEndpoint) Forwarding() bool {
	f.mu.RLock()
	defer f.mu.RUnlock()
	return f.mu.forwarding
}

// SetForwarding implements stack.ForwardingNetworkEndpoint.
func (f *fakeNetworkEndpoint) SetForwarding(v bool) bool {
	f.mu.Lock()
	defer f.mu.Unlock()
	prev := f.mu.forwarding
	f.mu.forwarding = v
	return prev
}

// MulticastForwarding implements stack.MulticastForwardingNetworkEndpoint.
func (f *fakeNetworkEndpoint) MulticastForwarding() bool {
	f.mu.RLock()
	defer f.mu.RUnlock()
	return f.mu.multicastForwarding
}

// SetMulticastForwarding implements stack.MulticastForwardingNetworkEndpoint.
func (f *fakeNetworkEndpoint) SetMulticastForwarding(v bool) bool {
	f.mu.Lock()
	defer f.mu.Unlock()
	prev := f.mu.multicastForwarding
	f.mu.multicastForwarding = v
	return prev
}

func fakeNetFactory(s *stack.Stack) stack.NetworkProtocol {
	return &fakeNetworkProtocol{stack: s}
}

// linkEPWithMockedAttach is a stack.LinkEndpoint that tests can use to verify
// that LinkEndpoint.Attach was called.
type linkEPWithMockedAttach struct {
	stack.LinkEndpoint
	attached bool
}

// Attach implements stack.LinkEndpoint.Attach.
func (l *linkEPWithMockedAttach) Attach(d stack.NetworkDispatcher) {
	l.LinkEndpoint.Attach(d)
	l.attached = d != nil
}

func (l *linkEPWithMockedAttach) isAttached() bool {
	return l.attached
}

var _ stack.MulticastForwardingEventDispatcher = (*fakeMulticastEventDispatcher)(nil)

type fakeMulticastEventDispatcher struct {
}

func (m *fakeMulticastEventDispatcher) OnMissingRoute(context stack.MulticastPacketContext) {
}

func (m *fakeMulticastEventDispatcher) OnUnexpectedInputInterface(context stack.MulticastPacketContext, expectedInputInterface tcpip.NICID) {
}

// Checks to see if list contains an address.
func containsAddr(list []tcpip.ProtocolAddress, item tcpip.ProtocolAddress) bool {
	for _, i := range list {
		if i == item {
			return true
		}
	}

	return false
}

type addressChangedEvent struct {
	lifetimes stack.AddressLifetimes
	state     stack.AddressAssignmentState
}

// An implementation of AddressDispatcher which forwards data from callbacks
// to channels to be asserted against in tests.
type addressDispatcher struct {
	changedCh chan addressChangedEvent
	removedCh chan stack.AddressRemovalReason
	nicid     tcpip.NICID
	addr      tcpip.AddressWithPrefix
	lifetimes stack.AddressLifetimes
	state     stack.AddressAssignmentState
}

var _ stack.AddressDispatcher = (*addressDispatcher)(nil)

// OnChanged implements stack.AddressDispatcher.
func (ad *addressDispatcher) OnChanged(lifetimes stack.AddressLifetimes, state stack.AddressAssignmentState) {
	if ad.changedCh != nil {
		ad.changedCh <- addressChangedEvent{
			lifetimes: lifetimes,
			state:     state,
		}
	}
}

// OnRemoved implements stack.AddressDispatcher.
func (ad *addressDispatcher) OnRemoved(reason stack.AddressRemovalReason) {
	if ad.removedCh != nil {
		ad.removedCh <- reason
	}
}

func (ad *addressDispatcher) disable() {
	ad.changedCh = nil
	ad.removedCh = nil
}

func (ad *addressDispatcher) expectNoEvent() error {
	select {
	case e := <-ad.changedCh:
		return fmt.Errorf("dispatcher for nic=%d addr=%s unexpectedly received changed event: %#v", ad.nicid, ad.addr, e)
	case e := <-ad.removedCh:
		return fmt.Errorf("dispatcher for nic=%d addr=%s unexpectedly received removed event: %#v", ad.nicid, ad.addr, e)
	default:
		return nil
	}
}

func (ad *addressDispatcher) expectChanged(lifetimes stack.AddressLifetimes, state stack.AddressAssignmentState) error {
	select {
	case e := <-ad.changedCh:
		ad.lifetimes = e.lifetimes
		ad.state = e.state
		if diff := cmp.Diff(e, addressChangedEvent{
			lifetimes: lifetimes,
			state:     state,
		}, cmp.AllowUnexported(e, tcpip.MonotonicTime{})); diff != "" {
			return fmt.Errorf("dispatcher for nic=%d addr=%s address changed event mismatch (-got +want):\n%s", ad.nicid, ad.addr, diff)
		}
	default:
		return fmt.Errorf("dispatcher for nic=%d addr=%s address changed event not immediately ready", ad.nicid, ad.addr)
	}
	return nil
}

func (ad *addressDispatcher) expectDeprecated() error {
	return ad.expectChanged(stack.AddressLifetimes{
		Deprecated: true,
		ValidUntil: ad.lifetimes.ValidUntil,
	}, ad.state)
}

func (ad *addressDispatcher) expectValidUntilChanged(validUntil tcpip.MonotonicTime) error {
	return ad.expectChanged(stack.AddressLifetimes{
		Deprecated:     ad.lifetimes.Deprecated,
		PreferredUntil: ad.lifetimes.PreferredUntil,
		ValidUntil:     validUntil,
	}, ad.state)
}

func (ad *addressDispatcher) expectLifetimesChanged(lifetimes stack.AddressLifetimes) error {
	return ad.expectChanged(lifetimes, ad.state)
}

func (ad *addressDispatcher) expectStateChanged(state stack.AddressAssignmentState) error {
	return ad.expectChanged(ad.lifetimes, state)
}

func (ad *addressDispatcher) expectRemoved(want stack.AddressRemovalReason) error {
	select {
	case got := <-ad.removedCh:
		if want != got {
			return fmt.Errorf("dispatcher for nic=%d addr=%s got removal reason = %s, want = %s", ad.nicid, ad.addr, got, want)
		}
	default:
		return fmt.Errorf("dispatcher for nic=%d addr=%s address removed event not immediately ready", ad.nicid, ad.addr)
	}
	return nil
}

func infiniteLifetimes() stack.AddressLifetimes {
	return stack.AddressLifetimes{
		Deprecated:     false,
		ValidUntil:     tcpip.MonotonicTimeInfinite(),
		PreferredUntil: tcpip.MonotonicTimeInfinite(),
	}
}

func TestNetworkReceive(t *testing.T) {
	// Create a stack with the fake network protocol, one nic, and two
	// addresses attached to it: 1 & 2.
	ep := channel.New(10, defaultMTU, "")
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	protocolAddr1 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFrom4Slice([]byte("\x01\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr1, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr1, err)
	}

	protocolAddr2 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFrom4Slice([]byte("\x02\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr2, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr2, err)
	}

	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)

	buf := make([]byte, 30)

	// Make sure packet with wrong address is not delivered.
	buf[dstAddrOffset] = 3
	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(buf),
	}))
	if fakeNet.packetCount[1] != 0 {
		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 0)
	}
	if fakeNet.packetCount[2] != 0 {
		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 0)
	}

	// Make sure packet is delivered to first endpoint.
	buf[dstAddrOffset] = 1
	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(buf),
	}))
	if fakeNet.packetCount[1] != 1 {
		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
	}
	if fakeNet.packetCount[2] != 0 {
		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 0)
	}

	// Make sure packet is delivered to second endpoint.
	buf[dstAddrOffset] = 2
	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(buf),
	}))
	if fakeNet.packetCount[1] != 1 {
		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
	}
	if fakeNet.packetCount[2] != 1 {
		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
	}

	// Make sure packet is not delivered if protocol number is wrong.
	ep.InjectInbound(fakeNetNumber-1, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(buf),
	}))
	if fakeNet.packetCount[1] != 1 {
		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
	}
	if fakeNet.packetCount[2] != 1 {
		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
	}

	// Make sure packet that is too small is dropped.
	buf = buf[:2]
	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(buf),
	}))
	if fakeNet.packetCount[1] != 1 {
		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
	}
	if fakeNet.packetCount[2] != 1 {
		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
	}
}

func sendTo(s *stack.Stack, addr tcpip.Address, payload []byte) tcpip.Error {
	r, err := s.FindRoute(0, tcpip.Address{}, addr, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		return err
	}
	defer r.Release()
	return send(r, payload)
}

func send(r *stack.Route, payload []byte) tcpip.Error {
	return r.WritePacket(stack.NetworkHeaderParams{Protocol: fakeTransNumber, TTL: 123, TOS: stack.DefaultTOS}, stack.NewPacketBuffer(stack.PacketBufferOptions{
		ReserveHeaderBytes: int(r.MaxHeaderLength()),
		Payload:            buffer.MakeWithData(payload),
	}))
}

func testSendTo(t *testing.T, s *stack.Stack, addrStr string, ep *channel.Endpoint, payload []byte) {
	t.Helper()
	ep.Drain()
	addr := tcpip.AddrFromSlice([]byte(addrStr))
	if err := sendTo(s, addr, payload); err != nil {
		t.Error("sendTo failed:", err)
	}
	if got, want := ep.Drain(), 1; got != want {
		t.Errorf("sendTo packet count: got = %d, want %d", got, want)
	}
}

func testSend(t *testing.T, r *stack.Route, ep *channel.Endpoint, payload []byte) {
	t.Helper()
	ep.Drain()
	if err := send(r, payload); err != nil {
		t.Error("send failed:", err)
	}
	if got, want := ep.Drain(), 1; got != want {
		t.Errorf("send packet count: got = %d, want %d", got, want)
	}
}

func testFailingSend(t *testing.T, r *stack.Route, payload []byte, wantErr tcpip.Error) {
	t.Helper()
	if gotErr := send(r, payload); gotErr != wantErr {
		t.Errorf("send failed: got = %s, want = %s ", gotErr, wantErr)
	}
}

func testFailingSendTo(t *testing.T, s *stack.Stack, addr tcpip.Address, payload []byte, wantErr tcpip.Error) {
	t.Helper()
	if gotErr := sendTo(s, addr, payload); gotErr != wantErr {
		t.Errorf("sendto failed: got = %s, want = %s ", gotErr, wantErr)
	}
}

func testRecv(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf []byte) {
	t.Helper()
	// testRecvInternal injects one packet, and we expect to receive it.
	want := fakeNet.PacketCount(localAddrByte) + 1
	testRecvInternal(t, fakeNet, localAddrByte, ep, buf, want)
}

func testFailingRecv(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf []byte) {
	t.Helper()
	// testRecvInternal injects one packet, and we do NOT expect to receive it.
	want := fakeNet.PacketCount(localAddrByte)
	testRecvInternal(t, fakeNet, localAddrByte, ep, buf, want)
}

func testRecvInternal(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf []byte, want int) {
	t.Helper()
	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(buf),
	}))
	if got := fakeNet.PacketCount(localAddrByte); got != want {
		t.Errorf("receive packet count: got = %d, want %d", got, want)
	}
}

func TestNetworkSend(t *testing.T) {
	// Create a stack with the fake network protocol, one nic, and one
	// address: 1. The route table sends all packets through the only
	// existing nic.
	ep := channel.New(10, defaultMTU, "")
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatal("NewNIC failed:", err)
	}

	{
		subnet, err := tcpip.NewSubnet(tcpip.AddrFrom4Slice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFrom4Slice([]byte("\x00\x00\x00\x00")), NIC: 1}})
	}

	protocolAddr := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr, err)
	}

	// Make sure that the link-layer endpoint received the outbound packet.
	testSendTo(t, s, "\x03\x00\x00\x00", ep, nil)
}

func TestNetworkSendMultiRoute(t *testing.T) {
	// Create a stack with the fake network protocol, two nics, and two
	// addresses per nic, the first nic has odd address, the second one has
	// even addresses.
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep1 := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep1); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	protocolAddr1 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr1, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr1, err)
	}

	protocolAddr3 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr3, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr3, err)
	}

	ep2 := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(2, ep2); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	protocolAddr2 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(2, protocolAddr2, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 2, protocolAddr2, err)
	}

	protocolAddr4 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(2, protocolAddr4, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 2, protocolAddr4, err)
	}

	// Set a route table that sends all packets with odd destination
	// addresses through the first NIC, and all even destination address
	// through the second one.
	{
		subnet0, err := tcpip.NewSubnet(tcpip.AddrFrom4Slice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x01\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		subnet1, err := tcpip.NewSubnet(tcpip.AddrFrom4Slice([]byte("\x01\x00\x00\x00")), tcpip.MaskFrom("\x01\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		s.SetRouteTable([]tcpip.Route{
			{Destination: subnet1, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
			{Destination: subnet0, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 2},
		})
	}

	// Send a packet to an odd destination.
	testSendTo(t, s, "\x05\x00\x00\x00", ep1, nil)

	// Send a packet to an even destination.
	testSendTo(t, s, "\x06\x00\x00\x00", ep2, nil)
}

func testRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr, expectedSrcAddr tcpip.Address, expectedRouteMTU int) {
	t.Helper()

	r, err := s.FindRoute(nic, srcAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatal("FindRoute failed:", err)
	}

	defer r.Release()

	if r.LocalAddress() != expectedSrcAddr {
		t.Errorf("got Route.LocalAddress() = %s, want = %s", expectedSrcAddr, r.LocalAddress())
	}

	if r.RemoteAddress() != dstAddr {
		t.Errorf("got Route.RemoteAddress() = %s, want = %s", dstAddr, r.RemoteAddress())
	}

	if int(r.MTU()) != expectedRouteMTU {
		t.Errorf("got Route.MTU() = %d, want = %d", r.MTU(), expectedRouteMTU)
	}
}

func testNoRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr tcpip.Address) {
	t.Helper()

	_, err := s.FindRoute(nic, srcAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
	if _, ok := err.(*tcpip.ErrNetworkUnreachable); !ok {
		t.Fatalf("FindRoute returned unexpected error, got = %v, want = %s", err, &tcpip.ErrNetworkUnreachable{})
	}
}

// TestAttachToLinkEndpointImmediately tests that a LinkEndpoint is attached to
// a NetworkDispatcher when the NIC is created.
func TestAttachToLinkEndpointImmediately(t *testing.T) {
	const nicID = 1

	tests := []struct {
		name    string
		nicOpts stack.NICOptions
	}{
		{
			name:    "Create enabled NIC",
			nicOpts: stack.NICOptions{Disabled: false},
		},
		{
			name:    "Create disabled NIC",
			nicOpts: stack.NICOptions{Disabled: true},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
			})

			e := linkEPWithMockedAttach{
				LinkEndpoint: loopback.New(),
			}

			if err := s.CreateNICWithOptions(nicID, &e, test.nicOpts); err != nil {
				t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, test.nicOpts, err)
			}
			if !e.isAttached() {
				t.Fatal("link endpoint not attached to a network dispatcher")
			}
		})
	}
}

func TestDisableUnknownNIC(t *testing.T) {
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	err := s.DisableNIC(1)
	if _, ok := err.(*tcpip.ErrUnknownNICID); !ok {
		t.Fatalf("got s.DisableNIC(1) = %v, want = %s", err, &tcpip.ErrUnknownNICID{})
	}
}

func TestDisabledNICsNICInfoAndCheckNIC(t *testing.T) {
	const nicID = 1

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	e := loopback.New()
	nicOpts := stack.NICOptions{Disabled: true}
	if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
		t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, nicOpts, err)
	}

	checkNIC := func(enabled bool) {
		t.Helper()

		allNICInfo := s.NICInfo()
		nicInfo, ok := allNICInfo[nicID]
		if !ok {
			t.Errorf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo)
		} else if nicInfo.Flags.Running != enabled {
			t.Errorf("got nicInfo.Flags.Running = %t, want = %t", nicInfo.Flags.Running, enabled)
		}

		if got := s.CheckNIC(nicID); got != enabled {
			t.Errorf("got s.CheckNIC(%d) = %t, want = %t", nicID, got, enabled)
		}
	}

	// NIC should initially report itself as disabled.
	checkNIC(false)

	if err := s.EnableNIC(nicID); err != nil {
		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
	}
	checkNIC(true)

	// If the NIC is not reporting a correct enabled status, we cannot trust the
	// next check so end the test here.
	if t.Failed() {
		t.FailNow()
	}

	if err := s.DisableNIC(nicID); err != nil {
		t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
	}
	checkNIC(false)
}

func TestRemoveUnknownNIC(t *testing.T) {
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	err := s.RemoveNIC(1)
	if _, ok := err.(*tcpip.ErrUnknownNICID); !ok {
		t.Fatalf("got s.RemoveNIC(1) = %v, want = %s", err, &tcpip.ErrUnknownNICID{})
	}
}

func TestRemoveNIC(t *testing.T) {
	for _, tt := range []struct {
		name   string
		linkep stack.LinkEndpoint
	}{
		{
			name:   "loopback",
			linkep: loopback.New(),
		},
		{
			name:   "channel",
			linkep: channel.New(0, defaultMTU, ""),
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			const nicID = 1

			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
			})

			e := linkEPWithMockedAttach{
				LinkEndpoint: tt.linkep,
			}
			if err := s.CreateNIC(nicID, &e); err != nil {
				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
			}

			// NIC should be present in NICInfo and attached to a NetworkDispatcher.
			allNICInfo := s.NICInfo()
			if _, ok := allNICInfo[nicID]; !ok {
				t.Errorf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo)
			}
			if !e.isAttached() {
				t.Fatal("link endpoint not attached to a network dispatcher")
			}

			// Removing a NIC should remove it from NICInfo and e should be detached from
			// the NetworkDispatcher.
			if err := s.RemoveNIC(nicID); err != nil {
				t.Fatalf("s.RemoveNIC(%d): %s", nicID, err)
			}
			if nicInfo, ok := s.NICInfo()[nicID]; ok {
				t.Errorf("got unexpected NICInfo entry for deleted NIC %d = %+v", nicID, nicInfo)
			}
			if e.isAttached() {
				t.Error("link endpoint for removed NIC still attached to a network dispatcher")
			}
		})
	}
}

func TestRouteWithDownNIC(t *testing.T) {
	tests := []struct {
		name   string
		downFn func(s *stack.Stack, nicID tcpip.NICID) tcpip.Error
		upFn   func(s *stack.Stack, nicID tcpip.NICID) tcpip.Error
	}{
		{
			name:   "Disabled NIC",
			downFn: (*stack.Stack).DisableNIC,
			upFn:   (*stack.Stack).EnableNIC,
		},

		// Once a NIC is removed, it cannot be brought up.
		{
			name:   "Removed NIC",
			downFn: (*stack.Stack).RemoveNIC,
		},
	}

	const unspecifiedNIC = 0
	const nicID1 = 1
	const nicID2 = 2
	addr1 := tcpip.AddrFrom4Slice([]byte("\x01\x00\x00\x00"))
	addr2 := tcpip.AddrFrom4Slice([]byte("\x02\x00\x00\x00"))
	nic1Dst := tcpip.AddrFrom4Slice([]byte("\x05\x00\x00\x00"))
	nic2Dst := tcpip.AddrFrom4Slice([]byte("\x06\x00\x00\x00"))
	nic1RouteMTU := 1500
	nic2RouteMTU := 1460
	// These are set in setup function, because they depend on protocl being used.
	nic1RouteMTUAtNetworkLayer := 0
	nic2RouteMTUAtNetworkLayer := 0

	setup := func(t *testing.T) (*stack.Stack, *channel.Endpoint, *channel.Endpoint) {
		s := stack.New(stack.Options{
			NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
		})
		nic1RouteMTUAtNetworkLayer = nic1RouteMTU - fakeNetHeaderLen
		nic2RouteMTUAtNetworkLayer = nic2RouteMTU - fakeNetHeaderLen

		ep1 := channel.New(1, defaultMTU, "")
		if err := s.CreateNIC(nicID1, ep1); err != nil {
			t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
		}

		protocolAddr1 := tcpip.ProtocolAddress{
			Protocol: fakeNetNumber,
			AddressWithPrefix: tcpip.AddressWithPrefix{
				Address:   addr1,
				PrefixLen: fakeDefaultPrefixLen,
			},
		}
		if err := s.AddProtocolAddress(nicID1, protocolAddr1, stack.AddressProperties{}); err != nil {
			t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID1, protocolAddr1, err)
		}

		ep2 := channel.New(1, defaultMTU, "")
		if err := s.CreateNIC(nicID2, ep2); err != nil {
			t.Fatalf("CreateNIC(%d, _): %s", nicID2, err)
		}

		protocolAddr2 := tcpip.ProtocolAddress{
			Protocol: fakeNetNumber,
			AddressWithPrefix: tcpip.AddressWithPrefix{
				Address:   addr2,
				PrefixLen: fakeDefaultPrefixLen,
			},
		}
		if err := s.AddProtocolAddress(nicID2, protocolAddr2, stack.AddressProperties{}); err != nil {
			t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddr2, err)
		}

		// Set a route table that sends all packets with odd destination
		// addresses through the first NIC, and all even destination address
		// through the second one.
		{
			subnet0, err := tcpip.NewSubnet(tcpip.AddrFrom4Slice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x01\x00\x00\x00"))
			if err != nil {
				t.Fatal(err)
			}
			subnet1, err := tcpip.NewSubnet(tcpip.AddrFrom4Slice([]byte("\x01\x00\x00\x00")), tcpip.MaskFrom("\x01\x00\x00\x00"))
			if err != nil {
				t.Fatal(err)
			}
			s.SetRouteTable([]tcpip.Route{
				{Destination: subnet1, Gateway: tcpip.AddrFrom4Slice([]byte("\x00\x00\x00\x00")), NIC: nicID1, MTU: uint32(nic1RouteMTU)},
				{Destination: subnet0, Gateway: tcpip.AddrFrom4Slice([]byte("\x00\x00\x00\x00")), NIC: nicID2, MTU: uint32(nic2RouteMTU)},
			})
		}

		return s, ep1, ep2
	}

	// Tests that routes through a down NIC are not used when looking up a route
	// for a destination.
	t.Run("Find", func(t *testing.T) {
		for _, test := range tests {
			t.Run(test.name, func(t *testing.T) {
				s, _, _ := setup(t)

				// Test routes to odd address.
				testRoute(t, s, unspecifiedNIC, tcpip.Address{}, tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")), addr1, nic1RouteMTUAtNetworkLayer)
				testRoute(t, s, unspecifiedNIC, addr1, tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")), addr1, nic1RouteMTUAtNetworkLayer)
				testRoute(t, s, nicID1, addr1, tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")), addr1, nic1RouteMTUAtNetworkLayer)

				// Test routes to even address.
				testRoute(t, s, unspecifiedNIC, tcpip.Address{}, tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")), addr2, nic2RouteMTUAtNetworkLayer)
				testRoute(t, s, unspecifiedNIC, addr2, tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")), addr2, nic2RouteMTUAtNetworkLayer)
				testRoute(t, s, nicID2, addr2, tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")), addr2, nic2RouteMTUAtNetworkLayer)

				// Bringing NIC1 down should result in no routes to odd addresses. Routes to
				// even addresses should continue to be available as NIC2 is still up.
				if err := test.downFn(s, nicID1); err != nil {
					t.Fatalf("test.downFn(_, %d): %s", nicID1, err)
				}
				testNoRoute(t, s, unspecifiedNIC, tcpip.Address{}, nic1Dst)
				testNoRoute(t, s, unspecifiedNIC, addr1, nic1Dst)
				testNoRoute(t, s, nicID1, addr1, nic1Dst)
				testRoute(t, s, unspecifiedNIC, tcpip.Address{}, nic2Dst, addr2, nic2RouteMTUAtNetworkLayer)
				testRoute(t, s, unspecifiedNIC, addr2, nic2Dst, addr2, nic2RouteMTUAtNetworkLayer)
				testRoute(t, s, nicID2, addr2, nic2Dst, addr2, nic2RouteMTUAtNetworkLayer)

				// Bringing NIC2 down should result in no routes to even addresses. No
				// route should be available to any address as routes to odd addresses
				// were made unavailable by bringing NIC1 down above.
				if err := test.downFn(s, nicID2); err != nil {
					t.Fatalf("test.downFn(_, %d): %s", nicID2, err)
				}
				testNoRoute(t, s, unspecifiedNIC, tcpip.Address{}, nic1Dst)
				testNoRoute(t, s, unspecifiedNIC, addr1, nic1Dst)
				testNoRoute(t, s, nicID1, addr1, nic1Dst)
				testNoRoute(t, s, unspecifiedNIC, tcpip.Address{}, nic2Dst)
				testNoRoute(t, s, unspecifiedNIC, addr2, nic2Dst)
				testNoRoute(t, s, nicID2, addr2, nic2Dst)

				if upFn := test.upFn; upFn != nil {
					// Bringing NIC1 up should make routes to odd addresses available
					// again. Routes to even addresses should continue to be unavailable
					// as NIC2 is still down.
					if err := upFn(s, nicID1); err != nil {
						t.Fatalf("test.upFn(_, %d): %s", nicID1, err)
					}
					testRoute(t, s, unspecifiedNIC, tcpip.Address{}, nic1Dst, addr1, nic1RouteMTUAtNetworkLayer)
					testRoute(t, s, unspecifiedNIC, addr1, nic1Dst, addr1, nic1RouteMTUAtNetworkLayer)
					testRoute(t, s, nicID1, addr1, nic1Dst, addr1, nic1RouteMTUAtNetworkLayer)
					testNoRoute(t, s, unspecifiedNIC, tcpip.Address{}, nic2Dst)
					testNoRoute(t, s, unspecifiedNIC, addr2, nic2Dst)
					testNoRoute(t, s, nicID2, addr2, nic2Dst)
				}
			})
		}
	})

	// Tests that writing a packet using a Route through a down NIC fails.
	t.Run("WritePacket", func(t *testing.T) {
		for _, test := range tests {
			t.Run(test.name, func(t *testing.T) {
				s, ep1, ep2 := setup(t)

				r1, err := s.FindRoute(nicID1, addr1, nic1Dst, fakeNetNumber, false /* multicastLoop */)
				if err != nil {
					t.Errorf("FindRoute(%d, %s, %s, %d, false): %s", nicID1, addr1, nic1Dst, fakeNetNumber, err)
				}
				defer r1.Release()

				r2, err := s.FindRoute(nicID2, addr2, nic2Dst, fakeNetNumber, false /* multicastLoop */)
				if err != nil {
					t.Errorf("FindRoute(%d, %s, %s, %d, false): %s", nicID2, addr2, nic2Dst, fakeNetNumber, err)
				}
				defer r2.Release()

				// If we failed to get routes r1 or r2, we cannot proceed with the test.
				if t.Failed() {
					t.FailNow()
				}

				buf := []byte{1}
				testSend(t, r1, ep1, buf)
				testSend(t, r2, ep2, buf)

				// Writes with Routes that use NIC1 after being brought down should fail.
				if err := test.downFn(s, nicID1); err != nil {
					t.Fatalf("test.downFn(_, %d): %s", nicID1, err)
				}
				testFailingSend(t, r1, buf, &tcpip.ErrInvalidEndpointState{})
				testSend(t, r2, ep2, buf)

				// Writes with Routes that use NIC2 after being brought down should fail.
				if err := test.downFn(s, nicID2); err != nil {
					t.Fatalf("test.downFn(_, %d): %s", nicID2, err)
				}
				testFailingSend(t, r1, buf, &tcpip.ErrInvalidEndpointState{})
				testFailingSend(t, r2, buf, &tcpip.ErrInvalidEndpointState{})

				if upFn := test.upFn; upFn != nil {
					// Writes with Routes that use NIC1 after being brought up should
					// succeed.
					//
					// TODO(gvisor.dev/issue/1491): Should we instead completely
					// invalidate all Routes that were bound to a NIC that was brought
					// down at some point?
					if err := upFn(s, nicID1); err != nil {
						t.Fatalf("test.upFn(_, %d): %s", nicID1, err)
					}
					testSend(t, r1, ep1, buf)
					testFailingSend(t, r2, buf, &tcpip.ErrInvalidEndpointState{})
				}
			})
		}
	})
}

func TestRoutes(t *testing.T) {
	// Create a stack with the fake network protocol, two nics, and two
	// addresses per nic, the first nic has odd address, the second one has
	// even addresses.
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep1 := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep1); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	protocolAddr1 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr1, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr1, err)
	}

	protocolAddr3 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr3, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr3, err)
	}

	ep2 := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(2, ep2); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	protocolAddr2 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(2, protocolAddr2, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 2, protocolAddr2, err)
	}

	protocolAddr4 := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")),
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(2, protocolAddr4, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 2, protocolAddr4, err)
	}

	// Set a route table that sends all packets with odd destination
	// addresses through the first NIC, and all even destination address
	// through the second one.
	nic1RouteMTU := 1500
	nic1RouteMTUAtNetworkLayer := nic1RouteMTU - fakeNetHeaderLen
	nic2RouteMTU := 1460
	nic2RouteMTUAtNetworkLayer := nic2RouteMTU - fakeNetHeaderLen
	{
		subnet0, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x01\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		subnet1, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), tcpip.MaskFrom("\x01\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}

		s.SetRouteTable([]tcpip.Route{
			{Destination: subnet1, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1, MTU: uint32(nic1RouteMTU)},
			{Destination: subnet0, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 2, MTU: uint32(nic2RouteMTU)},
		})
	}

	// Test routes to odd address.
	testRoute(t, s, 0, tcpip.Address{}, tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), nic1RouteMTUAtNetworkLayer)
	testRoute(t, s, 0, tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), nic1RouteMTUAtNetworkLayer)
	testRoute(t, s, 1, tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), nic1RouteMTUAtNetworkLayer)
	testRoute(t, s, 0, tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")), nic1RouteMTUAtNetworkLayer)
	testRoute(t, s, 1, tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")), nic1RouteMTUAtNetworkLayer)

	// Test routes to even address.
	testRoute(t, s, 0, tcpip.Address{}, tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")), nic2RouteMTUAtNetworkLayer)
	testRoute(t, s, 0, tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")), nic2RouteMTUAtNetworkLayer)
	testRoute(t, s, 2, tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")), nic2RouteMTUAtNetworkLayer)
	testRoute(t, s, 0, tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")), nic2RouteMTUAtNetworkLayer)
	testRoute(t, s, 2, tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")), nic2RouteMTUAtNetworkLayer)

	// Try to send to odd numbered address from even numbered ones, then
	// vice-versa.
	testNoRoute(t, s, 0, tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")))
	testNoRoute(t, s, 2, tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")))
	testNoRoute(t, s, 0, tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")))
	testNoRoute(t, s, 2, tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x05\x00\x00\x00")))

	testNoRoute(t, s, 0, tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")))
	testNoRoute(t, s, 1, tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")))
	testNoRoute(t, s, 0, tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")))
	testNoRoute(t, s, 1, tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")), tcpip.AddrFromSlice([]byte("\x06\x00\x00\x00")))
}

func TestAddressRemoval(t *testing.T) {
	const localAddrByte byte = 0x01
	localAddr := tcpip.AddrFromSlice([]byte{localAddrByte, 0, 0, 0})
	remoteAddr := tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	protocolAddr := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   localAddr,
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr, err)
	}
	{
		subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}})
	}

	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)

	buf := make([]byte, 30)

	// Send and receive packets, and verify they are received.
	buf[dstAddrOffset] = localAddrByte
	testRecv(t, fakeNet, localAddrByte, ep, buf)
	testSendTo(t, s, string(remoteAddr.AsSlice()), ep, nil)

	// Remove the address, then check that send/receive doesn't work anymore.
	if err := s.RemoveAddress(1, localAddr); err != nil {
		t.Fatal("RemoveAddress failed:", err)
	}
	testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
	testFailingSendTo(t, s, remoteAddr, nil, &tcpip.ErrNetworkUnreachable{})

	// Check that removing the same address fails.
	err := s.RemoveAddress(1, localAddr)
	if _, ok := err.(*tcpip.ErrBadLocalAddress); !ok {
		t.Fatalf("RemoveAddress returned unexpected error, got = %v, want = %s", err, &tcpip.ErrBadLocalAddress{})
	}
}

func TestAddressRemovalWithRouteHeld(t *testing.T) {
	const localAddrByte byte = 0x01
	localAddr := tcpip.AddrFromSlice([]byte{localAddrByte, 0, 0, 0})
	remoteAddr := tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatalf("CreateNIC failed: %v", err)
	}
	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
	buf := make([]byte, 30)

	protocolAddr := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   localAddr,
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr, err)
	}
	{
		subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}})
	}

	r, err := s.FindRoute(0, tcpip.Address{}, remoteAddr, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatal("FindRoute failed:", err)
	}

	// Send and receive packets, and verify they are received.
	buf[dstAddrOffset] = localAddrByte
	testRecv(t, fakeNet, localAddrByte, ep, buf)
	testSend(t, r, ep, nil)
	testSendTo(t, s, string(remoteAddr.AsSlice()), ep, nil)

	// Remove the address, then check that send/receive doesn't work anymore.
	if err := s.RemoveAddress(1, localAddr); err != nil {
		t.Fatal("RemoveAddress failed:", err)
	}
	testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
	testFailingSend(t, r, nil, &tcpip.ErrInvalidEndpointState{})
	testFailingSendTo(t, s, remoteAddr, nil, &tcpip.ErrNetworkUnreachable{})

	// Check that removing the same address fails.
	{
		err := s.RemoveAddress(1, localAddr)
		if _, ok := err.(*tcpip.ErrBadLocalAddress); !ok {
			t.Fatalf("RemoveAddress returned unexpected error, got = %v, want = %s", err, &tcpip.ErrBadLocalAddress{})
		}
	}
}

func verifyAddress(t *testing.T, s *stack.Stack, nicID tcpip.NICID, addr tcpip.Address) {
	t.Helper()
	info, ok := s.NICInfo()[nicID]
	if !ok {
		t.Fatalf("NICInfo() failed to find nicID=%d", nicID)
	}
	if addr.Len() == 0 {
		// No address given, verify that there is no address assigned to the NIC.
		for _, a := range info.ProtocolAddresses {
			if a.Protocol == fakeNetNumber && a.AddressWithPrefix != (tcpip.AddressWithPrefix{}) {
				t.Errorf("verify no-address: got = %s, want = %s", a.AddressWithPrefix, tcpip.AddressWithPrefix{})
			}
		}
		return
	}
	// Address given, verify the address is assigned to the NIC and no other
	// address is.
	found := false
	for _, a := range info.ProtocolAddresses {
		if a.Protocol == fakeNetNumber {
			if a.AddressWithPrefix.Address == addr {
				found = true
			} else {
				t.Errorf("verify address: got = %s, want = %s", a.AddressWithPrefix.Address, addr)
			}
		}
	}
	if !found {
		t.Errorf("verify address: couldn't find %s on the NIC", addr)
	}
}

func TestEndpointExpiration(t *testing.T) {
	const (
		localAddrByte byte        = 0x01
		nicID         tcpip.NICID = 1
	)
	var (
		noAddr     = tcpip.Address{}
		remoteAddr = tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00"))
	)
	localAddr := tcpip.AddrFromSlice([]byte{localAddrByte, 0, 0, 0})

	for _, promiscuous := range []bool{true, false} {
		for _, spoofing := range []bool{true, false} {
			t.Run(fmt.Sprintf("promiscuous=%t spoofing=%t", promiscuous, spoofing), func(t *testing.T) {
				s := stack.New(stack.Options{
					NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
				})

				ep := channel.New(10, defaultMTU, "")
				if err := s.CreateNIC(nicID, ep); err != nil {
					t.Fatal("CreateNIC failed:", err)
				}

				{
					subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
					if err != nil {
						t.Fatal(err)
					}
					s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}})
				}

				fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
				buf := make([]byte, 30)
				buf[dstAddrOffset] = localAddrByte

				if promiscuous {
					if err := s.SetPromiscuousMode(nicID, true); err != nil {
						t.Fatal("SetPromiscuousMode failed:", err)
					}
				}

				if spoofing {
					if err := s.SetSpoofing(nicID, true); err != nil {
						t.Fatal("SetSpoofing failed:", err)
					}
				}

				// 1. No Address yet, send should only work for spoofing, receive for
				// promiscuous mode.
				//-----------------------
				verifyAddress(t, s, nicID, noAddr)
				if promiscuous {
					testRecv(t, fakeNet, localAddrByte, ep, buf)
				} else {
					testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
				}
				if spoofing {
					// FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
					// testSendTo(t, s, remoteAddr, ep, nil)
				} else {
					testFailingSendTo(t, s, remoteAddr, nil, &tcpip.ErrNetworkUnreachable{})
				}

				// 2. Add Address, everything should work.
				//-----------------------
				protocolAddr := tcpip.ProtocolAddress{
					Protocol: fakeNetNumber,
					AddressWithPrefix: tcpip.AddressWithPrefix{
						Address:   localAddr,
						PrefixLen: fakeDefaultPrefixLen,
					},
				}
				if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
					t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
				}
				verifyAddress(t, s, nicID, localAddr)
				testRecv(t, fakeNet, localAddrByte, ep, buf)
				testSendTo(t, s, string(remoteAddr.AsSlice()), ep, nil)

				// 3. Remove the address, send should only work for spoofing, receive
				// for promiscuous mode.
				//-----------------------
				if err := s.RemoveAddress(nicID, localAddr); err != nil {
					t.Fatal("RemoveAddress failed:", err)
				}
				verifyAddress(t, s, nicID, noAddr)
				if promiscuous {
					testRecv(t, fakeNet, localAddrByte, ep, buf)
				} else {
					testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
				}
				if spoofing {
					// FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
					// testSendTo(t, s, remoteAddr, ep, nil)
				} else {
					testFailingSendTo(t, s, remoteAddr, nil, &tcpip.ErrNetworkUnreachable{})
				}

				// 4. Add Address back, everything should work again.
				//-----------------------
				if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
					t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
				}
				verifyAddress(t, s, nicID, localAddr)
				testRecv(t, fakeNet, localAddrByte, ep, buf)
				testSendTo(t, s, string(remoteAddr.AsSlice()), ep, nil)

				// 5. Take a reference to the endpoint by getting a route. Verify that
				// we can still send/receive, including sending using the route.
				//-----------------------
				r, err := s.FindRoute(0, tcpip.Address{}, remoteAddr, fakeNetNumber, false /* multicastLoop */)
				if err != nil {
					t.Fatal("FindRoute failed:", err)
				}
				testRecv(t, fakeNet, localAddrByte, ep, buf)
				testSendTo(t, s, string(remoteAddr.AsSlice()), ep, nil)
				testSend(t, r, ep, nil)

				// 6. Remove the address. Send should only work for spoofing, receive
				// for promiscuous mode.
				//-----------------------
				if err := s.RemoveAddress(nicID, localAddr); err != nil {
					t.Fatal("RemoveAddress failed:", err)
				}
				verifyAddress(t, s, nicID, noAddr)
				if promiscuous {
					testRecv(t, fakeNet, localAddrByte, ep, buf)
				} else {
					testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
				}
				if spoofing {
					testSend(t, r, ep, nil)
					testSendTo(t, s, string(remoteAddr.AsSlice()), ep, nil)
				} else {
					testFailingSend(t, r, nil, &tcpip.ErrInvalidEndpointState{})
					testFailingSendTo(t, s, remoteAddr, nil, &tcpip.ErrNetworkUnreachable{})
				}

				// 7. Add Address back, everything should work again.
				//-----------------------
				if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
					t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
				}
				verifyAddress(t, s, nicID, localAddr)
				testRecv(t, fakeNet, localAddrByte, ep, buf)
				testSendTo(t, s, string(remoteAddr.AsSlice()), ep, nil)
				testSend(t, r, ep, nil)

				// 8. Remove the route, sendTo/recv should still work.
				//-----------------------
				r.Release()
				verifyAddress(t, s, nicID, localAddr)
				testRecv(t, fakeNet, localAddrByte, ep, buf)
				testSendTo(t, s, string(remoteAddr.AsSlice()), ep, nil)

				// 9. Remove the address. Send should only work for spoofing, receive
				// for promiscuous mode.
				//-----------------------
				if err := s.RemoveAddress(nicID, localAddr); err != nil {
					t.Fatal("RemoveAddress failed:", err)
				}
				verifyAddress(t, s, nicID, noAddr)
				if promiscuous {
					testRecv(t, fakeNet, localAddrByte, ep, buf)
				} else {
					testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
				}
				if spoofing {
					// FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
					// testSendTo(t, s, remoteAddr, ep, nil)
				} else {
					testFailingSendTo(t, s, remoteAddr, nil, &tcpip.ErrNetworkUnreachable{})
				}
			})
		}
	}
}

func TestPromiscuousMode(t *testing.T) {
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	{
		subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}})
	}

	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)

	buf := make([]byte, 30)

	// Write a packet, and check that it doesn't get delivered as we don't
	// have a matching endpoint.
	const localAddrByte byte = 0x01
	buf[dstAddrOffset] = localAddrByte
	testFailingRecv(t, fakeNet, localAddrByte, ep, buf)

	// Set promiscuous mode, then check that packet is delivered.
	if err := s.SetPromiscuousMode(1, true); err != nil {
		t.Fatal("SetPromiscuousMode failed:", err)
	}
	testRecv(t, fakeNet, localAddrByte, ep, buf)

	// Check that we can't get a route as there is no local address.
	_, err := s.FindRoute(0, tcpip.Address{}, tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")), fakeNetNumber, false /* multicastLoop */)
	if _, ok := err.(*tcpip.ErrNetworkUnreachable); !ok {
		t.Fatalf("FindRoute returned unexpected error: got = %v, want = %s", err, &tcpip.ErrNetworkUnreachable{})
	}

	// Set promiscuous mode to false, then check that packet can't be
	// delivered anymore.
	if err := s.SetPromiscuousMode(1, false); err != nil {
		t.Fatal("SetPromiscuousMode failed:", err)
	}
	testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
}

// TestExternalSendWithHandleLocal tests that the stack creates a non-local
// route when spoofing or promiscuous mode are enabled.
//
// This test makes sure that packets are transmitted from the stack.
func TestExternalSendWithHandleLocal(t *testing.T) {
	const (
		unspecifiedNICID = 0
		nicID            = 1
	)
	var (
		localAddr = tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
		dstAddr   = tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00"))
	)

	subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
	if err != nil {
		t.Fatal(err)
	}

	tests := []struct {
		name           string
		configureStack func(*testing.T, *stack.Stack)
	}{
		{
			name:           "Default",
			configureStack: func(*testing.T, *stack.Stack) {},
		},
		{
			name: "Spoofing",
			configureStack: func(t *testing.T, s *stack.Stack) {
				if err := s.SetSpoofing(nicID, true); err != nil {
					t.Fatalf("s.SetSpoofing(%d, true): %s", nicID, err)
				}
			},
		},
		{
			name: "Promiscuous",
			configureStack: func(t *testing.T, s *stack.Stack) {
				if err := s.SetPromiscuousMode(nicID, true); err != nil {
					t.Fatalf("s.SetPromiscuousMode(%d, true): %s", nicID, err)
				}
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, handleLocal := range []bool{true, false} {
				t.Run(fmt.Sprintf("HandleLocal=%t", handleLocal), func(t *testing.T) {
					s := stack.New(stack.Options{
						NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
						HandleLocal:      handleLocal,
					})

					ep := channel.New(1, defaultMTU, "")
					if err := s.CreateNIC(nicID, ep); err != nil {
						t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
					}
					protocolAddr := tcpip.ProtocolAddress{
						Protocol: fakeNetNumber,
						AddressWithPrefix: tcpip.AddressWithPrefix{
							Address:   localAddr,
							PrefixLen: fakeDefaultPrefixLen,
						},
					}
					if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
						t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
					}

					s.SetRouteTable([]tcpip.Route{{Destination: subnet, NIC: nicID}})

					test.configureStack(t, s)

					r, err := s.FindRoute(unspecifiedNICID, localAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
					if err != nil {
						t.Fatalf("s.FindRoute(%d, %s, %s, %d, false): %s", unspecifiedNICID, localAddr, dstAddr, fakeNetNumber, err)
					}
					defer r.Release()

					if r.LocalAddress() != localAddr {
						t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), localAddr)
					}
					if r.RemoteAddress() != dstAddr {
						t.Errorf("got r.RemoteAddress() = %s, want = %s", r.RemoteAddress(), dstAddr)
					}

					if n := ep.Drain(); n != 0 {
						t.Fatalf("got ep.Drain() = %d, want = 0", n)
					}
					if err := r.WritePacket(stack.NetworkHeaderParams{
						Protocol: fakeTransNumber,
						TTL:      123,
						TOS:      stack.DefaultTOS,
					}, stack.NewPacketBuffer(stack.PacketBufferOptions{
						ReserveHeaderBytes: int(r.MaxHeaderLength()),
						Payload:            buffer.MakeWithData(make([]byte, 10)),
					})); err != nil {
						t.Fatalf("r.WritePacket(nil, _, _): %s", err)
					}
					if n := ep.Drain(); n != 1 {
						t.Fatalf("got ep.Drain() = %d, want = 1", n)
					}
				})
			}
		})
	}
}

func TestSpoofingWithAddress(t *testing.T) {
	localAddr := tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
	nonExistentLocalAddr := tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))
	dstAddr := tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00"))

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	protocolAddr := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   localAddr,
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr, err)
	}

	{
		subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}})
	}

	// With address spoofing disabled, FindRoute does not permit an address
	// that was not added to the NIC to be used as the source.
	r, err := s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
	if err == nil {
		t.Errorf("FindRoute succeeded with route %+v when it should have failed", r)
	}

	// With address spoofing enabled, FindRoute permits any address to be used
	// as the source.
	if err := s.SetSpoofing(1, true); err != nil {
		t.Fatal("SetSpoofing failed:", err)
	}
	r, err = s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatal("FindRoute failed:", err)
	}
	if r.LocalAddress() != nonExistentLocalAddr {
		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nonExistentLocalAddr)
	}
	if r.RemoteAddress() != dstAddr {
		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), dstAddr)
	}
	// Sending a packet works.
	testSendTo(t, s, string(dstAddr.AsSlice()), ep, nil)
	testSend(t, r, ep, nil)

	// FindRoute should also work with a local address that exists on the NIC.
	r, err = s.FindRoute(0, localAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatal("FindRoute failed:", err)
	}
	if r.LocalAddress() != localAddr {
		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nonExistentLocalAddr)
	}
	if r.RemoteAddress() != dstAddr {
		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), dstAddr)
	}
	// Sending a packet using the route works.
	testSend(t, r, ep, nil)
}

func TestSpoofingNoAddress(t *testing.T) {
	nonExistentLocalAddr := tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
	dstAddr := tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	{
		subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}})
	}

	// With address spoofing disabled, FindRoute does not permit an address
	// that was not added to the NIC to be used as the source.
	r, err := s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
	if err == nil {
		t.Errorf("FindRoute succeeded with route %+v when it should have failed", r)
	}
	// Sending a packet fails.
	testFailingSendTo(t, s, dstAddr, nil, &tcpip.ErrNetworkUnreachable{})

	// With address spoofing enabled, FindRoute permits any address to be used
	// as the source.
	if err := s.SetSpoofing(1, true); err != nil {
		t.Fatal("SetSpoofing failed:", err)
	}
	r, err = s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatal("FindRoute failed:", err)
	}
	if r.LocalAddress() != nonExistentLocalAddr {
		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nonExistentLocalAddr)
	}
	if r.RemoteAddress() != dstAddr {
		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), dstAddr)
	}
	// Sending a packet works.
	// FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
	// testSendTo(t, s, remoteAddr, ep, nil)
}

func TestOutgoingBroadcastWithEmptyRouteTable(t *testing.T) {
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}
	s.SetRouteTable([]tcpip.Route{})

	// If there is no endpoint, it won't work.
	{
		_, err := s.FindRoute(1, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
		if _, ok := err.(*tcpip.ErrNetworkUnreachable); !ok {
			t.Fatalf("got FindRoute(1, %s, %s, %d) = %s, want = %s", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err, &tcpip.ErrNetworkUnreachable{})
		}
	}

	protoAddr := tcpip.ProtocolAddress{Protocol: fakeNetNumber, AddressWithPrefix: tcpip.AddressWithPrefix{Address: header.IPv4Any}}
	if err := s.AddProtocolAddress(1, protoAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(1, %+v, {}) failed: %s", protoAddr, err)
	}
	r, err := s.FindRoute(1, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatalf("FindRoute(1, %v, %v, %d) failed: %v", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err)
	}
	if r.LocalAddress() != header.IPv4Any {
		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), header.IPv4Any)
	}

	if r.RemoteAddress() != header.IPv4Broadcast {
		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), header.IPv4Broadcast)
	}

	// If the NIC doesn't exist, it won't work.
	{
		_, err := s.FindRoute(2, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
		if _, ok := err.(*tcpip.ErrNetworkUnreachable); !ok {
			t.Fatalf("got FindRoute(2, %v, %v, %d) = %v want = %v", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err, &tcpip.ErrNetworkUnreachable{})
		}
	}
}

func TestOutgoingBroadcastWithRouteTable(t *testing.T) {
	defaultAddr := tcpip.AddressWithPrefix{Address: header.IPv4Any}
	// Local subnet on NIC1: 192.168.1.58/24, gateway 192.168.1.1.
	nic1Addr := tcpip.AddressWithPrefix{Address: tcpip.AddrFromSlice([]byte("\xc0\xa8\x01\x3a")), PrefixLen: 24}
	nic1Gateway := testutil.MustParse4("192.168.1.1")
	// Local subnet on NIC2: 10.10.10.5/24, gateway 10.10.10.1.
	nic2Addr := tcpip.AddressWithPrefix{Address: tcpip.AddrFromSlice([]byte("\x0a\x0a\x0a\x05")), PrefixLen: 24}
	nic2Gateway := testutil.MustParse4("10.10.10.1")

	// Create a new stack with two NICs.
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})
	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatalf("CreateNIC failed: %s", err)
	}
	if err := s.CreateNIC(2, ep); err != nil {
		t.Fatalf("CreateNIC failed: %s", err)
	}
	nic1ProtoAddr := tcpip.ProtocolAddress{Protocol: fakeNetNumber, AddressWithPrefix: nic1Addr}
	if err := s.AddProtocolAddress(1, nic1ProtoAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(1, %+v, {}) failed: %s", nic1ProtoAddr, err)
	}

	nic2ProtoAddr := tcpip.ProtocolAddress{Protocol: fakeNetNumber, AddressWithPrefix: nic2Addr}
	if err := s.AddProtocolAddress(2, nic2ProtoAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(2, %+v, {}) failed: %s", nic2ProtoAddr, err)
	}

	// Set the initial route table.
	rt := []tcpip.Route{
		{Destination: nic1Addr.Subnet(), NIC: 1},
		{Destination: nic2Addr.Subnet(), NIC: 2},
		{Destination: defaultAddr.Subnet(), Gateway: nic2Gateway, NIC: 2},
		{Destination: defaultAddr.Subnet(), Gateway: nic1Gateway, NIC: 1},
	}
	s.SetRouteTable(rt)

	// When an interface is given, the route for a broadcast goes through it.
	r, err := s.FindRoute(1, nic1Addr.Address, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatalf("FindRoute(1, %v, %v, %d) failed: %v", nic1Addr.Address, header.IPv4Broadcast, fakeNetNumber, err)
	}
	if r.LocalAddress() != nic1Addr.Address {
		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nic1Addr.Address)
	}

	if r.RemoteAddress() != header.IPv4Broadcast {
		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), header.IPv4Broadcast)
	}

	// When an interface is not given, it consults the route table.
	// 1. Case: Using the default route.
	r, err = s.FindRoute(0, tcpip.Address{}, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatalf("FindRoute(0, \"\", %s, %d) failed: %s", header.IPv4Broadcast, fakeNetNumber, err)
	}
	if r.LocalAddress() != nic2Addr.Address {
		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nic2Addr.Address)
	}

	if r.RemoteAddress() != header.IPv4Broadcast {
		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), header.IPv4Broadcast)
	}

	// 2. Case: Having an explicit route for broadcast will select that one.
	rt = append(
		[]tcpip.Route{
			{Destination: header.IPv4Broadcast.WithPrefix().Subnet(), NIC: 1},
		},
		rt...,
	)
	s.SetRouteTable(rt)
	r, err = s.FindRoute(0, tcpip.Address{}, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatalf("FindRoute(0, \"\", %s, %d) failed: %s", header.IPv4Broadcast, fakeNetNumber, err)
	}
	if r.LocalAddress() != nic1Addr.Address {
		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nic1Addr.Address)
	}

	if r.RemoteAddress() != header.IPv4Broadcast {
		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), header.IPv4Broadcast)
	}
}

func TestMulticastOrIPv6LinkLocalNeedsNoRoute(t *testing.T) {
	for _, tc := range []struct {
		name        string
		routeNeeded bool
		address     string
	}{
		// IPv4 multicast address range: 224.0.0.0 - 239.255.255.255
		//                <=>  0xe0.0x00.0x00.0x00 - 0xef.0xff.0xff.0xff
		{"IPv4 Multicast 1", false, "\xe0\x00\x00\x00"},
		{"IPv4 Multicast 2", false, "\xef\xff\xff\xff"},
		{"IPv4 Unicast 1", true, "\xdf\xff\xff\xff"},
		{"IPv4 Unicast 2", true, "\xf0\x00\x00\x00"},
		{"IPv4 Unicast 3", true, "\x00\x00\x00\x00"},

		// IPv6 multicast address is 0xff[8] + flags[4] + scope[4] + groupId[112]
		{"IPv6 Multicast 1", false, "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Multicast 2", false, "\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Multicast 3", false, "\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},

		// IPv6 link-local address starts with fe80::/10.
		{"IPv6 Unicast Link-Local 1", false, "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Unicast Link-Local 2", false, "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"},
		{"IPv6 Unicast Link-Local 3", false, "\xfe\x80\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff"},
		{"IPv6 Unicast Link-Local 4", false, "\xfe\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Unicast Link-Local 5", false, "\xfe\xbf\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},

		// IPv6 addresses that are neither multicast nor link-local.
		{"IPv6 Unicast Not Link-Local 1", true, "\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Unicast Not Link-Local 2", true, "\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},
		{"IPv6 Unicast Not Link-local 3", true, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Unicast Not Link-Local 4", true, "\xfe\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Unicast Not Link-Local 5", true, "\xfe\xdf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Unicast Not Link-Local 6", true, "\xfd\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
		{"IPv6 Unicast Not Link-Local 7", true, "\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
			})

			ep := channel.New(10, defaultMTU, "")
			if err := s.CreateNIC(1, ep); err != nil {
				t.Fatal("CreateNIC failed:", err)
			}

			s.SetRouteTable([]tcpip.Route{})

			var anyAddr tcpip.Address
			if len(tc.address) == header.IPv4AddressSize {
				anyAddr = header.IPv4Any
			} else {
				anyAddr = header.IPv6Any
			}

			var want tcpip.Error = &tcpip.ErrNetworkUnreachable{}

			// If there is no endpoint, it won't work.
			address := tcpip.AddrFromSlice([]byte(tc.address))
			if _, err := s.FindRoute(1, anyAddr, address, fakeNetNumber, false /* multicastLoop */); err != want {
				t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %v", anyAddr, address, fakeNetNumber, err, want)
			}

			protocolAddr := tcpip.ProtocolAddress{
				Protocol: fakeNetNumber,
				AddressWithPrefix: tcpip.AddressWithPrefix{
					Address:   anyAddr,
					PrefixLen: fakeDefaultPrefixLen,
				},
			}
			if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr, err)
			}

			if r, err := s.FindRoute(1, anyAddr, address, fakeNetNumber, false /* multicastLoop */); tc.routeNeeded {
				// Route table is empty but we need a route, this should cause an error.
				if _, ok := err.(*tcpip.ErrNetworkUnreachable); !ok {
					t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %v", anyAddr, address, fakeNetNumber, err, &tcpip.ErrNetworkUnreachable{})
				}
			} else {
				if err != nil {
					t.Fatalf("FindRoute(1, %v, %v, %v) failed: %v", anyAddr, address, fakeNetNumber, err)
				}
				if r.LocalAddress() != anyAddr {
					t.Errorf("Bad local address: got %v, want = %v", r.LocalAddress(), anyAddr)
				}
				if r.RemoteAddress() != address {
					t.Errorf("Bad remote address: got %v, want = %v", r.RemoteAddress(), address)
				}
			}
			// If the NIC doesn't exist, it won't work.
			if _, err := s.FindRoute(2, anyAddr, address, fakeNetNumber, false /* multicastLoop */); err != want {
				t.Fatalf("got FindRoute(2, %v, %v, %v) = %v want = %v", anyAddr, address, fakeNetNumber, err, want)
			}
		})
	}
}

func TestNetworkOption(t *testing.T) {
	s := stack.New(stack.Options{
		NetworkProtocols:   []stack.NetworkProtocolFactory{fakeNetFactory},
		TransportProtocols: []stack.TransportProtocolFactory{},
	})

	opt := tcpip.DefaultTTLOption(5)
	if err := s.SetNetworkProtocolOption(fakeNetNumber, &opt); err != nil {
		t.Fatalf("s.SetNetworkProtocolOption(%d, &%T(%d)): %s", fakeNetNumber, opt, opt, err)
	}

	var optGot tcpip.DefaultTTLOption
	if err := s.NetworkProtocolOption(fakeNetNumber, &optGot); err != nil {
		t.Fatalf("s.NetworkProtocolOption(%d, &%T): %s", fakeNetNumber, optGot, err)
	}

	if opt != optGot {
		t.Errorf("got optGot = %d, want = %d", optGot, opt)
	}
}

func TestGetMainNICAddressAddPrimaryNonPrimary(t *testing.T) {
	const nicID = 1

	for _, addrLen := range []int{4, 16} {
		t.Run(fmt.Sprintf("addrLen=%d", addrLen), func(t *testing.T) {
			for canBe := 0; canBe < 3; canBe++ {
				t.Run(fmt.Sprintf("canBe=%d", canBe), func(t *testing.T) {
					for never := 0; never < 3; never++ {
						t.Run(fmt.Sprintf("never=%d", never), func(t *testing.T) {
							s := stack.New(stack.Options{
								NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
							})
							ep := channel.New(10, defaultMTU, "")
							if err := s.CreateNIC(nicID, ep); err != nil {
								t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
							}
							// Insert <canBe> primary and <never> never-primary addresses.
							// Each one will add a network endpoint to the NIC.
							primaryAddrAdded := make(map[tcpip.AddressWithPrefix]struct{})
							for i := 0; i < canBe+never; i++ {
								var behavior stack.PrimaryEndpointBehavior
								if i < canBe {
									behavior = stack.CanBePrimaryEndpoint
								} else {
									behavior = stack.NeverPrimaryEndpoint
								}
								// Add an address and in case of a primary one include a
								// prefixLen.
								address := tcpip.AddrFromSlice(bytes.Repeat([]byte{byte(i)}, addrLen))
								properties := stack.AddressProperties{PEB: behavior}
								if behavior == stack.CanBePrimaryEndpoint {
									protocolAddress := tcpip.ProtocolAddress{
										Protocol:          fakeNetNumber,
										AddressWithPrefix: address.WithPrefix(),
									}
									if err := s.AddProtocolAddress(nicID, protocolAddress, properties); err != nil {
										t.Fatalf("AddProtocolAddress(%d, %+v, %+v): %s", nicID, protocolAddress, properties, err)
									}
									// Remember the address/prefix.
									primaryAddrAdded[protocolAddress.AddressWithPrefix] = struct{}{}
								} else {
									protocolAddress := tcpip.ProtocolAddress{
										Protocol: fakeNetNumber,
										AddressWithPrefix: tcpip.AddressWithPrefix{
											Address:   address,
											PrefixLen: fakeDefaultPrefixLen,
										},
									}
									if err := s.AddProtocolAddress(nicID, protocolAddress, properties); err != nil {
										t.Fatalf("AddProtocolAddress(%d, %+v, %+v): %s", nicID, protocolAddress, properties, err)
									}
								}
							}
							// Check that GetMainNICAddress returns an address if at least
							// one primary address was added. In that case make sure the
							// address/prefixLen matches what we added.
							gotAddr, err := s.GetMainNICAddress(nicID, fakeNetNumber)
							if err != nil {
								t.Fatalf("GetMainNICAddress(%d, %d): %s", nicID, fakeNetNumber, err)
							}
							if len(primaryAddrAdded) == 0 {
								// No primary addresses present.
								if wantAddr := (tcpip.AddressWithPrefix{}); gotAddr != wantAddr {
									t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, gotAddr, wantAddr)
								}
							} else {
								// At least one primary address was added, verify the returned
								// address is in the list of primary addresses we added.
								if _, ok := primaryAddrAdded[gotAddr]; !ok {
									t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, gotAddr, primaryAddrAdded)
								}
							}
						})
					}
				})
			}
		})
	}
}

func TestGetMainNICAddressErrors(t *testing.T) {
	const nicID = 1

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, arp.NewProtocol},
	})
	if err := s.CreateNIC(nicID, loopback.New()); err != nil {
		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
	}

	// Sanity check with a successful call.
	if addr, err := s.GetMainNICAddress(nicID, ipv4.ProtocolNumber); err != nil {
		t.Errorf("s.GetMainNICAddress(%d, %d): %s", nicID, ipv4.ProtocolNumber, err)
	} else if want := (tcpip.AddressWithPrefix{}); addr != want {
		t.Errorf("got s.GetMainNICAddress(%d, %d) = %s, want = %s", nicID, ipv4.ProtocolNumber, addr, want)
	}

	const unknownNICID = nicID + 1
	switch addr, err := s.GetMainNICAddress(unknownNICID, ipv4.ProtocolNumber); err.(type) {
	case *tcpip.ErrUnknownNICID:
	default:
		t.Errorf("got s.GetMainNICAddress(%d, %d) = (%s, %T), want = (_, tcpip.ErrUnknownNICID)", unknownNICID, ipv4.ProtocolNumber, addr, err)
	}

	// ARP is not an addressable network endpoint.
	switch addr, err := s.GetMainNICAddress(nicID, arp.ProtocolNumber); err.(type) {
	case *tcpip.ErrNotSupported:
	default:
		t.Errorf("got s.GetMainNICAddress(%d, %d) = (%s, %T), want = (_, tcpip.ErrNotSupported)", nicID, arp.ProtocolNumber, addr, err)
	}

	const unknownProtocolNumber = 1234
	switch addr, err := s.GetMainNICAddress(nicID, unknownProtocolNumber); err.(type) {
	case *tcpip.ErrUnknownProtocol:
	default:
		t.Errorf("got s.GetMainNICAddress(%d, %d) = (%s, %T), want = (_, tcpip.ErrUnknownProtocol)", nicID, unknownProtocolNumber, addr, err)
	}
}

func TestGetMainNICAddressAddRemove(t *testing.T) {
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})
	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(1, ep); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	for _, tc := range []struct {
		name      string
		address   tcpip.Address
		prefixLen int
	}{
		{"IPv4", tcpip.AddrFromSlice([]byte("\x01\x01\x01\x01")), 24},
		{"IPv6", tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")), 116},
	} {
		t.Run(tc.name, func(t *testing.T) {
			protocolAddress := tcpip.ProtocolAddress{
				Protocol: fakeNetNumber,
				AddressWithPrefix: tcpip.AddressWithPrefix{
					Address:   tc.address,
					PrefixLen: tc.prefixLen,
				},
			}
			if err := s.AddProtocolAddress(1, protocolAddress, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(1, %+v, {}): %s", protocolAddress, err)
			}

			// Check that we get the right initial address and prefix length.
			if err := checkGetMainNICAddress(s, 1, fakeNetNumber, protocolAddress.AddressWithPrefix); err != nil {
				t.Fatal(err)
			}

			if err := s.RemoveAddress(1, protocolAddress.AddressWithPrefix.Address); err != nil {
				t.Fatal("RemoveAddress failed:", err)
			}

			// Check that we get no address after removal.
			if err := checkGetMainNICAddress(s, 1, fakeNetNumber, tcpip.AddressWithPrefix{}); err != nil {
				t.Fatal(err)
			}
		})
	}
}

// Simple network address generator. Good for 255 addresses.
type addressGenerator struct{ cnt byte }

func (g *addressGenerator) next(addrLen int) tcpip.Address {
	g.cnt++
	return tcpip.AddrFromSlice(bytes.Repeat([]byte{g.cnt}, addrLen))
}

func verifyAddresses(t *testing.T, expectedAddresses, gotAddresses []tcpip.ProtocolAddress) {
	t.Helper()

	if len(gotAddresses) != len(expectedAddresses) {
		t.Fatalf("got len(addresses) = %d, want = %d", len(gotAddresses), len(expectedAddresses))
	}

	sort.Slice(gotAddresses, func(i, j int) bool {
		return string(gotAddresses[i].AddressWithPrefix.Address.AsSlice()) < string(gotAddresses[j].AddressWithPrefix.Address.AsSlice())
	})
	sort.Slice(expectedAddresses, func(i, j int) bool {
		return string(expectedAddresses[i].AddressWithPrefix.Address.AsSlice()) < string(expectedAddresses[j].AddressWithPrefix.Address.AsSlice())
	})

	for i, gotAddr := range gotAddresses {
		expectedAddr := expectedAddresses[i]
		if gotAddr != expectedAddr {
			t.Errorf("got address = %+v, wanted = %+v", gotAddr, expectedAddr)
		}
	}
}

func TestAddProtocolAddress(t *testing.T) {
	const nicID = 1
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})
	ep := channel.New(10, defaultMTU, "")
	if err := s.CreateNIC(nicID, ep); err != nil {
		t.Fatal("CreateNIC failed:", err)
	}

	addrLenRange := []int{4, 16}
	behaviorRange := []stack.PrimaryEndpointBehavior{stack.CanBePrimaryEndpoint, stack.FirstPrimaryEndpoint, stack.NeverPrimaryEndpoint}
	configTypeRange := []stack.AddressConfigType{stack.AddressConfigStatic, stack.AddressConfigSlaac}
	temporaryRange := []bool{false, true}
	deprecatedRange := []bool{false, true}
	wantAddresses := make([]tcpip.ProtocolAddress, 0, len(addrLenRange)*len(behaviorRange)*len(configTypeRange)*len(deprecatedRange))
	var addrGen addressGenerator
	for _, addrLen := range addrLenRange {
		for _, behavior := range behaviorRange {
			for _, configType := range configTypeRange {
				for _, temporary := range temporaryRange {
					for _, deprecated := range deprecatedRange {
						address := addrGen.next(addrLen)
						properties := stack.AddressProperties{
							PEB:        behavior,
							ConfigType: configType,
							Lifetimes:  stack.AddressLifetimes{Deprecated: deprecated},
							Temporary:  temporary,
						}
						protocolAddr := tcpip.ProtocolAddress{
							Protocol: fakeNetNumber,
							AddressWithPrefix: tcpip.AddressWithPrefix{
								Address:   address,
								PrefixLen: fakeDefaultPrefixLen,
							},
						}
						if err := s.AddProtocolAddress(nicID, protocolAddr, properties); err != nil {
							t.Fatalf("AddProtocolAddress(%d, %+v, %+v) failed: %s", nicID, protocolAddr, properties, err)
						}
						wantAddresses = append(wantAddresses, tcpip.ProtocolAddress{
							Protocol:          fakeNetNumber,
							AddressWithPrefix: tcpip.AddressWithPrefix{Address: address, PrefixLen: fakeDefaultPrefixLen},
						})
					}
				}
			}
		}
	}

	gotAddresses := s.AllAddresses()[nicID]
	verifyAddresses(t, wantAddresses, gotAddresses)
}

func TestCreateNICWithOptions(t *testing.T) {
	type callArgsAndExpect struct {
		nicID tcpip.NICID
		opts  stack.NICOptions
		err   tcpip.Error
	}

	tests := []struct {
		desc  string
		calls []callArgsAndExpect
	}{
		{
			desc: "InvalidNICID",
			calls: []callArgsAndExpect{
				{
					nicID: tcpip.NICID(0),
					opts:  stack.NICOptions{Name: "eth0"},
					err:   &tcpip.ErrInvalidNICID{},
				},
			},
		},
		{
			desc: "DuplicateNICID",
			calls: []callArgsAndExpect{
				{
					nicID: tcpip.NICID(1),
					opts:  stack.NICOptions{Name: "eth1"},
					err:   nil,
				},
				{
					nicID: tcpip.NICID(1),
					opts:  stack.NICOptions{Name: "eth2"},
					err:   &tcpip.ErrDuplicateNICID{},
				},
			},
		},
		{
			desc: "DuplicateName",
			calls: []callArgsAndExpect{
				{
					nicID: tcpip.NICID(1),
					opts:  stack.NICOptions{Name: "lo"},
					err:   nil,
				},
				{
					nicID: tcpip.NICID(2),
					opts:  stack.NICOptions{Name: "lo"},
					err:   &tcpip.ErrDuplicateNICID{},
				},
			},
		},
		{
			desc: "Unnamed",
			calls: []callArgsAndExpect{
				{
					nicID: tcpip.NICID(1),
					opts:  stack.NICOptions{},
					err:   nil,
				},
				{
					nicID: tcpip.NICID(2),
					opts:  stack.NICOptions{},
					err:   nil,
				},
			},
		},
		{
			desc: "UnnamedDuplicateNICID",
			calls: []callArgsAndExpect{
				{
					nicID: tcpip.NICID(1),
					opts:  stack.NICOptions{},
					err:   nil,
				},
				{
					nicID: tcpip.NICID(1),
					opts:  stack.NICOptions{},
					err:   &tcpip.ErrDuplicateNICID{},
				},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.desc, func(t *testing.T) {
			s := stack.New(stack.Options{})
			ep := channel.New(0, 0, "\x00\x00\x00\x00\x00\x00")
			for _, call := range test.calls {
				if got, want := s.CreateNICWithOptions(call.nicID, ep, call.opts), call.err; got != want {
					t.Fatalf("CreateNICWithOptions(%v, _, %+v) = %v, want %v", call.nicID, call.opts, got, want)
				}
			}
		})
	}
}

func TestNICStats(t *testing.T) {
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	nics := []struct {
		addr        tcpip.Address
		txByteCount int
		rxByteCount int
	}{
		{
			addr:        tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")),
			txByteCount: 30,
			rxByteCount: 10,
		},
		{
			addr:        tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")),
			txByteCount: 50,
			rxByteCount: 20,
		},
	}

	var txBytesTotal, rxBytesTotal, txPacketsTotal, rxPacketsTotal int
	for i, nic := range nics {
		nicid := tcpip.NICID(i + 1)
		ep := channel.New(1, defaultMTU, "")
		if err := s.CreateNIC(nicid, ep); err != nil {
			t.Fatal("CreateNIC failed: ", err)
		}
		protocolAddr := tcpip.ProtocolAddress{
			Protocol: fakeNetNumber,
			AddressWithPrefix: tcpip.AddressWithPrefix{
				Address:   nic.addr,
				PrefixLen: fakeDefaultPrefixLen,
			},
		}
		if err := s.AddProtocolAddress(nicid, protocolAddr, stack.AddressProperties{}); err != nil {
			t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicid, protocolAddr, err)
		}

		{
			subnet, err := tcpip.NewSubnet(nic.addr, tcpip.MaskFrom("\xff\x00\x00\x00"))
			if err != nil {
				t.Fatal(err)
			}
			s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: nicid}})
		}

		nicStats := s.NICInfo()[nicid].Stats

		// Inbound packet.
		rxBuffer := make([]byte, nic.rxByteCount)
		ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
			Payload: buffer.MakeWithData(rxBuffer),
		}))
		if got, want := nicStats.Rx.Packets.Value(), uint64(1); got != want {
			t.Errorf("got Rx.Packets.Value() = %d, want = %d", got, want)
		}
		if got, want := nicStats.Rx.Bytes.Value(), uint64(nic.rxByteCount); got != want {
			t.Errorf("got Rx.Bytes.Value() = %d, want = %d", got, want)
		}
		rxPacketsTotal++
		rxBytesTotal += nic.rxByteCount

		// Outbound packet.
		txBuffer := make([]byte, nic.txByteCount)
		actualTxLength := nic.txByteCount + fakeNetHeaderLen
		if err := sendTo(s, nic.addr, txBuffer); err != nil {
			t.Fatal("sendTo failed: ", err)
		}
		want := ep.Drain()
		if got := nicStats.Tx.Packets.Value(); got != uint64(want) {
			t.Errorf("got Tx.Packets.Value() = %d, ep.Drain() = %d", got, want)
		}
		if got, want := nicStats.Tx.Bytes.Value(), uint64(actualTxLength); got != want {
			t.Errorf("got Tx.Bytes.Value() = %d, want = %d", got, want)
		}
		txPacketsTotal += want
		txBytesTotal += actualTxLength
	}

	// Now verify that each NIC stats was correctly aggregated at the stack level.
	if got, want := s.Stats().NICs.Rx.Packets.Value(), uint64(rxPacketsTotal); got != want {
		t.Errorf("got s.Stats().NIC.Rx.Packets.Value() = %d, want = %d", got, want)
	}
	if got, want := s.Stats().NICs.Rx.Bytes.Value(), uint64(rxBytesTotal); got != want {
		t.Errorf("got s.Stats().Rx.Bytes.Value() = %d, want = %d", got, want)
	}
	if got, want := s.Stats().NICs.Tx.Packets.Value(), uint64(txPacketsTotal); got != want {
		t.Errorf("got Tx.Packets.Value() = %d, ep.Drain() = %d", got, want)
	}
	if got, want := s.Stats().NICs.Tx.Bytes.Value(), uint64(txBytesTotal); got != want {
		t.Errorf("got Tx.Bytes.Value() = %d, want = %d", got, want)
	}
}

// TestNICContextPreservation tests that you can read out via stack.NICInfo the
// Context data you pass via NICContext.Context in stack.CreateNICWithOptions.
func TestNICContextPreservation(t *testing.T) {
	var ctx *int
	tests := []struct {
		name string
		opts stack.NICOptions
		want stack.NICContext
	}{
		{
			"context_set",
			stack.NICOptions{Context: ctx},
			ctx,
		},
		{
			"context_not_set",
			stack.NICOptions{},
			nil,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{})
			id := tcpip.NICID(1)
			ep := channel.New(0, 0, "\x00\x00\x00\x00\x00\x00")
			if err := s.CreateNICWithOptions(id, ep, test.opts); err != nil {
				t.Fatalf("got stack.CreateNICWithOptions(%d, %+v, %+v) = %s, want nil", id, ep, test.opts, err)
			}
			nicinfos := s.NICInfo()
			nicinfo, ok := nicinfos[id]
			if !ok {
				t.Fatalf("got nicinfos[%d] = _, %t, want _, true; nicinfos = %+v", id, ok, nicinfos)
			}
			if got, want := nicinfo.Context == test.want, true; got != want {
				t.Fatalf("got nicinfo.Context == ctx = %t, want %t; nicinfo.Context = %p, ctx = %p", got, want, nicinfo.Context, test.want)
			}
		})
	}
}

// TestNICKindPreservation tests the NIC kind is present in NICInfo.
func TestNICKindPreservation(t *testing.T) {
	tests := []struct {
		name string
		opts stack.NICOptions
		want string
	}{
		{
			"veth",
			stack.NICOptions{Kind: "veth"},
			"veth",
		},
		{
			"bridge",
			stack.NICOptions{Kind: "bridge"},
			"bridge",
		},
		{
			"not_set",
			stack.NICOptions{},
			"",
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{})
			id := tcpip.NICID(1)
			ep := channel.New(0, 0, "\x00\x00\x00\x00\x00\x00")
			if err := s.CreateNICWithOptions(id, ep, test.opts); err != nil {
				t.Fatalf("got stack.CreateNICWithOptions(%d, %+v, %+v) = %s, want nil", id, ep, test.opts, err)
			}
			nicinfos := s.NICInfo()
			nicinfo, ok := nicinfos[id]
			if !ok {
				t.Fatalf("id: %d not found in NICInfo: %+v", id, nicinfos)
			}
			if got, want := nicinfo.Kind, test.want; got != want {
				t.Fatalf("got nicinfo.Kind = %q, want %q", got, want)
			}
		})
	}
}

// TestNICAutoGenLinkLocalAddr tests the auto-generation of IPv6 link-local
// addresses.
func TestNICAutoGenLinkLocalAddr(t *testing.T) {
	const nicID = 1

	var secretKey [header.OpaqueIIDSecretKeyMinBytes]byte
	n, err := rand.Read(secretKey[:])
	if err != nil {
		t.Fatalf("rand.Read(_): %s", err)
	}
	if n != header.OpaqueIIDSecretKeyMinBytes {
		t.Fatalf("expected rand.Read to read %d bytes, read %d bytes", header.OpaqueIIDSecretKeyMinBytes, n)
	}

	nicNameFunc := func(_ tcpip.NICID, name string) string {
		return name
	}

	tests := []struct {
		name         string
		nicName      string
		autoGen      bool
		linkAddr     tcpip.LinkAddress
		iidOpts      ipv6.OpaqueInterfaceIdentifierOptions
		shouldGen    bool
		expectedAddr tcpip.Address
	}{
		{
			name:      "Disabled",
			nicName:   "nic1",
			autoGen:   false,
			linkAddr:  linkAddr1,
			shouldGen: false,
		},
		{
			name:     "Disabled without OIID options",
			nicName:  "nic1",
			autoGen:  false,
			linkAddr: linkAddr1,
			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
				NICNameFromID: nicNameFunc,
				SecretKey:     secretKey[:],
			},
			shouldGen: false,
		},

		// Tests for EUI64 based addresses.
		{
			name:         "EUI64 Enabled",
			autoGen:      true,
			linkAddr:     linkAddr1,
			shouldGen:    true,
			expectedAddr: header.LinkLocalAddr(linkAddr1),
		},
		{
			name:      "EUI64 Empty MAC",
			autoGen:   true,
			shouldGen: false,
		},
		{
			name:      "EUI64 Invalid MAC",
			autoGen:   true,
			linkAddr:  "\x01\x02\x03",
			shouldGen: false,
		},
		{
			name:      "EUI64 Multicast MAC",
			autoGen:   true,
			linkAddr:  "\x01\x02\x03\x04\x05\x06",
			shouldGen: false,
		},
		{
			name:      "EUI64 Unspecified MAC",
			autoGen:   true,
			linkAddr:  "\x00\x00\x00\x00\x00\x00",
			shouldGen: false,
		},

		// Tests for Opaque IID based addresses.
		{
			name:     "OIID Enabled",
			nicName:  "nic1",
			autoGen:  true,
			linkAddr: linkAddr1,
			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
				NICNameFromID: nicNameFunc,
				SecretKey:     secretKey[:],
			},
			shouldGen:    true,
			expectedAddr: header.LinkLocalAddrWithOpaqueIID("nic1", 0, secretKey[:]),
		},
		// These are all cases where we would not have generated a
		// link-local address if opaque IIDs were disabled.
		{
			name:    "OIID Empty MAC and empty nicName",
			autoGen: true,
			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
				NICNameFromID: nicNameFunc,
				SecretKey:     secretKey[:1],
			},
			shouldGen:    true,
			expectedAddr: header.LinkLocalAddrWithOpaqueIID("", 0, secretKey[:1]),
		},
		{
			name:     "OIID Invalid MAC",
			nicName:  "test",
			autoGen:  true,
			linkAddr: "\x01\x02\x03",
			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
				NICNameFromID: nicNameFunc,
				SecretKey:     secretKey[:2],
			},
			shouldGen:    true,
			expectedAddr: header.LinkLocalAddrWithOpaqueIID("test", 0, secretKey[:2]),
		},
		{
			name:     "OIID Multicast MAC",
			nicName:  "test2",
			autoGen:  true,
			linkAddr: "\x01\x02\x03\x04\x05\x06",
			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
				NICNameFromID: nicNameFunc,
				SecretKey:     secretKey[:3],
			},
			shouldGen:    true,
			expectedAddr: header.LinkLocalAddrWithOpaqueIID("test2", 0, secretKey[:3]),
		},
		{
			name:     "OIID Unspecified MAC and nil SecretKey",
			nicName:  "test3",
			autoGen:  true,
			linkAddr: "\x00\x00\x00\x00\x00\x00",
			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
				NICNameFromID: nicNameFunc,
			},
			shouldGen:    true,
			expectedAddr: header.LinkLocalAddrWithOpaqueIID("test3", 0, nil),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			const autoGenAddrCount = 1
			ndpDisp := ndpDispatcher{
				autoGenAddrC:    make(chan ndpAutoGenAddrEvent, autoGenAddrCount),
				autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount),
			}
			opts := stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
					AutoGenLinkLocal: test.autoGen,
					NDPDisp:          &ndpDisp,
					OpaqueIIDOpts:    test.iidOpts,
				})},
			}

			e := channel.New(0, 1280, test.linkAddr)
			s := stack.New(opts)
			nicOpts := stack.NICOptions{Name: test.nicName, Disabled: true}
			if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
				t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, err)
			}

			// A new disabled NIC should not have any address, even if auto generation
			// was enabled.
			allStackAddrs := s.AllAddresses()
			allNICAddrs, ok := allStackAddrs[nicID]
			if !ok {
				t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
			}
			if l := len(allNICAddrs); l != 0 {
				t.Fatalf("got len(allNICAddrs) = %d, want = 0", l)
			}

			// Enabling the NIC should attempt auto-generation of a link-local
			// address.
			if err := s.EnableNIC(nicID); err != nil {
				t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
			}

			var expectedMainAddr tcpip.AddressWithPrefix
			if test.shouldGen {
				expectedMainAddr = tcpip.AddressWithPrefix{
					Address:   test.expectedAddr,
					PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen,
				}

				// Should have auto-generated an address and resolved immediately (DAD
				// is disabled).
				if _, err := expectAutoGenAddrNewEvent(&ndpDisp, expectedMainAddr); err != nil {
					t.Fatalf("error expecting link-local auto-gen address generated event: %s", err)
				}
			} else {
				// Should not have auto-generated an address.
				select {
				case <-ndpDisp.autoGenAddrC:
					t.Fatal("unexpectedly auto-generated an address")
				default:
				}
			}

			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, expectedMainAddr); err != nil {
				t.Fatal(err)
			}

			// Disabling the NIC should remove the auto-generated address.
			if err := s.DisableNIC(nicID); err != nil {
				t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
			}
			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
				t.Fatal(err)
			}
		})
	}
}

// TestNoLinkLocalAutoGenForLoopbackNIC tests that IPv6 link-local addresses are
// not auto-generated for loopback NICs.
func TestNoLinkLocalAutoGenForLoopbackNIC(t *testing.T) {
	const nicID = 1
	const nicName = "nicName"

	tests := []struct {
		name          string
		opaqueIIDOpts ipv6.OpaqueInterfaceIdentifierOptions
	}{
		{
			name:          "IID From MAC",
			opaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{},
		},
		{
			name: "Opaque IID",
			opaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
				NICNameFromID: func(_ tcpip.NICID, nicName string) string {
					return nicName
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			opts := stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
					AutoGenLinkLocal: true,
					OpaqueIIDOpts:    test.opaqueIIDOpts,
				})},
			}

			e := loopback.New()
			s := stack.New(opts)
			nicOpts := stack.NICOptions{Name: nicName}
			if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
				t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, nicOpts, err)
			}

			if err := checkGetMainNICAddress(s, 1, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
				t.Fatal(err)
			}
		})
	}
}

// TestNICAutoGenAddrDoesDAD tests that the successful auto-generation of IPv6
// link-local addresses will only be assigned after the DAD process resolves.
func TestNICAutoGenAddrDoesDAD(t *testing.T) {
	const nicID = 1

	ndpDisp := ndpDispatcher{
		dadC: make(chan ndpDADEvent, 1),
	}
	dadConfigs := stack.DefaultDADConfigurations()
	clock := faketime.NewManualClock()
	opts := stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
			AutoGenLinkLocal: true,
			NDPDisp:          &ndpDisp,
			DADConfigs:       dadConfigs,
		})},
		Clock: clock,
	}

	e := channel.New(int(dadConfigs.DupAddrDetectTransmits), 1280, linkAddr1)
	s := stack.New(opts)
	if err := s.CreateNIC(nicID, e); err != nil {
		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
	}

	// Address should not be considered bound to the
	// NIC yet (DAD ongoing).
	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
		t.Fatal(err)
	}

	linkLocalAddr := header.LinkLocalAddr(linkAddr1)

	// Wait for DAD to resolve.
	clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer)
	select {
	case e := <-ndpDisp.dadC:
		if diff := checkDADEvent(e, nicID, linkLocalAddr, &stack.DADSucceeded{}); diff != "" {
			t.Errorf("dad event mismatch (-want +got):\n%s", diff)
		}
	default:
		// We should get a resolution event after 1s (default time to
		// resolve as per default NDP configurations). Waiting for that
		// resolution time + an extra 1s without a resolution event
		// means something is wrong.
		t.Fatal("timed out waiting for DAD resolution")
	}
	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{Address: linkLocalAddr, PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen}); err != nil {
		t.Fatal(err)
	}
}

// TestNewPEB tests that a new PrimaryEndpointBehavior value (peb) is respected
// when an address's kind gets "promoted" to permanent from permanentExpired.
func TestNewPEBOnPromotionToPermanent(t *testing.T) {
	const nicID = 1

	pebs := []stack.PrimaryEndpointBehavior{
		stack.NeverPrimaryEndpoint,
		stack.CanBePrimaryEndpoint,
		stack.FirstPrimaryEndpoint,
	}

	for _, pi := range pebs {
		for _, ps := range pebs {
			t.Run(fmt.Sprintf("%d-to-%d", pi, ps), func(t *testing.T) {
				s := stack.New(stack.Options{
					NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
				})
				ep1 := channel.New(10, defaultMTU, "")
				if err := s.CreateNIC(nicID, ep1); err != nil {
					t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
				}

				// Add a permanent address with initial
				// PrimaryEndpointBehavior (peb), pi. If pi is
				// NeverPrimaryEndpoint, the address should not
				// be returned by a call to GetMainNICAddress;
				// else, it should.
				address1 := tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
				properties := stack.AddressProperties{PEB: pi}
				protocolAddr := tcpip.ProtocolAddress{
					Protocol: fakeNetNumber,
					AddressWithPrefix: tcpip.AddressWithPrefix{
						Address:   address1,
						PrefixLen: fakeDefaultPrefixLen,
					},
				}
				if err := s.AddProtocolAddress(nicID, protocolAddr, properties); err != nil {
					t.Fatalf("AddProtocolAddress(%d, %+v, %+v): %s", nicID, protocolAddr, properties, err)
				}
				addr, err := s.GetMainNICAddress(nicID, fakeNetNumber)
				if err != nil {
					t.Fatalf("GetMainNICAddress(%d, %d): %s", nicID, fakeNetNumber, err)
				}
				if pi == stack.NeverPrimaryEndpoint {
					if want := (tcpip.AddressWithPrefix{}); addr != want {
						t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, addr, want)

					}
				} else if addr.Address != address1 {
					t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, addr.Address, address1)
				}

				{
					subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
					if err != nil {
						t.Fatalf("NewSubnet failed: %v", err)
					}
					s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}})
				}

				// Take a route through the address so its ref
				// count gets incremented and does not actually
				// get deleted when RemoveAddress is called
				// below. This is because we want to test that a
				// new peb is respected when an address gets
				// "promoted" to permanent from a
				// permanentExpired kind.
				address2 := tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))
				r, err := s.FindRoute(nicID, address1, address2, fakeNetNumber, false)
				if err != nil {
					t.Fatalf("FindRoute(%d, %s, %s, %d, false): %s", nicID, address1, address2, fakeNetNumber, err)
				}
				defer r.Release()
				if err := s.RemoveAddress(nicID, address1); err != nil {
					t.Fatalf("RemoveAddress(%d, %s): %s", nicID, address1, err)
				}

				//
				// At this point, the address should still be
				// known by the NIC, but have its
				// kind = permanentExpired.
				//

				// Add some other address with peb set to
				// FirstPrimaryEndpoint.
				address3 := tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00"))
				protocolAddr3 := tcpip.ProtocolAddress{
					Protocol: fakeNetNumber,
					AddressWithPrefix: tcpip.AddressWithPrefix{
						Address:   address3,
						PrefixLen: fakeDefaultPrefixLen,
					},
				}
				properties = stack.AddressProperties{PEB: stack.FirstPrimaryEndpoint}
				if err := s.AddProtocolAddress(nicID, protocolAddr3, properties); err != nil {
					t.Fatalf("AddProtocolAddress(%d, %+v, %+v): %s", nicID, protocolAddr3, properties, err)
				}

				// Add back the address we removed earlier and
				// make sure the new peb was respected.
				// (The address should just be promoted now).
				protocolAddr1 := tcpip.ProtocolAddress{
					Protocol: fakeNetNumber,
					AddressWithPrefix: tcpip.AddressWithPrefix{
						Address:   address1,
						PrefixLen: fakeDefaultPrefixLen,
					},
				}
				properties = stack.AddressProperties{PEB: ps}
				if err := s.AddProtocolAddress(nicID, protocolAddr1, properties); err != nil {
					t.Fatalf("AddProtocolAddress(%d, %+v, %+v): %s", nicID, protocolAddr1, properties, err)
				}
				var primaryAddrs []tcpip.Address
				for _, pa := range s.NICInfo()[nicID].ProtocolAddresses {
					primaryAddrs = append(primaryAddrs, pa.AddressWithPrefix.Address)
				}
				var expectedList []tcpip.Address
				switch ps {
				case stack.FirstPrimaryEndpoint:
					expectedList = []tcpip.Address{
						tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")),
						tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")),
					}
				case stack.CanBePrimaryEndpoint:
					expectedList = []tcpip.Address{
						tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")),
						tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")),
					}
				case stack.NeverPrimaryEndpoint:
					expectedList = []tcpip.Address{
						tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00")),
					}
				}
				if !cmp.Equal(primaryAddrs, expectedList) {
					t.Fatalf("got NIC's primary addresses = %v, want = %v", primaryAddrs, expectedList)
				}

				// Once we remove the other address, if the new
				// peb, ps, was NeverPrimaryEndpoint, no address
				// should be returned by a call to
				// GetMainNICAddress; else, our original address
				// should be returned.
				if err := s.RemoveAddress(nicID, address3); err != nil {
					t.Fatalf("RemoveAddress(%d, %s): %s", nicID, address3, err)
				}
				addr, err = s.GetMainNICAddress(nicID, fakeNetNumber)
				if err != nil {
					t.Fatalf("GetMainNICAddress(%d, %d): %s", nicID, fakeNetNumber, err)
				}
				if ps == stack.NeverPrimaryEndpoint {
					if want := (tcpip.AddressWithPrefix{}); addr != want {
						t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, addr, want)
					}
				} else {
					if addr.Address != address1 {
						t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, addr.Address, address1)
					}
				}
			})
		}
	}
}

func TestIPv6SourceAddressSelectionScopeAndSameAddress(t *testing.T) {
	const (
		nicID           = 1
		lifetimeSeconds = 9999
	)

	var (
		linkLocalAddr1         = testutil.MustParse6("fe80::1")
		linkLocalAddr2         = testutil.MustParse6("fe80::2")
		linkLocalMulticastAddr = testutil.MustParse6("ff02::1")
		uniqueLocalAddr1       = testutil.MustParse6("fc00::1")
		uniqueLocalAddr2       = testutil.MustParse6("fd00::2")
		globalAddr1            = testutil.MustParse6("a000::1")
		globalAddr2            = testutil.MustParse6("a000::2")
		globalAddr3            = testutil.MustParse6("a000::3")
		ipv4MappedIPv6Addr1    = testutil.MustParse6("::ffff:0.0.0.1")
		ipv4MappedIPv6Addr2    = testutil.MustParse6("::ffff:0.0.0.2")
		toredoAddr1            = testutil.MustParse6("2001::1")
		toredoAddr2            = testutil.MustParse6("2001::2")
		ipv6ToIPv4Addr1        = testutil.MustParse6("2002::1")
		ipv6ToIPv4Addr2        = testutil.MustParse6("2002::2")
	)

	prefix1, _, stableGlobalAddr1 := prefixSubnetAddr(0, linkAddr1)
	prefix2, _, stableGlobalAddr2 := prefixSubnetAddr(1, linkAddr1)

	var tempIIDHistory [header.IIDSize]byte
	header.InitialTempIID(tempIIDHistory[:], nil, nicID)
	tempGlobalAddr1 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], stableGlobalAddr1.Address).Address
	tempGlobalAddr2 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], stableGlobalAddr2.Address).Address

	type addressWithProperties struct {
		addr       tcpip.Address
		properties stack.AddressProperties
	}

	// Rule 3 is also tested by NDP's AutoGenAddr test.
	tests := []struct {
		name                                   string
		slaacPrefixForTempAddrBeforeNICAddrAdd tcpip.AddressWithPrefix
		nicAddrs                               []addressWithProperties
		slaacPrefixForTempAddrAfterNICAddrAdd  tcpip.AddressWithPrefix
		remoteAddr                             tcpip.Address
		expectedLocalAddr                      tcpip.Address
	}{
		// Test Rule 1 of RFC 6724 section 5 (prefer same address).
		{
			name: "Same Global most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: globalAddr1},
			},
			remoteAddr:        globalAddr1,
			expectedLocalAddr: globalAddr1,
		},
		{
			name: "Same Global most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: uniqueLocalAddr1},
			},
			remoteAddr:        globalAddr1,
			expectedLocalAddr: globalAddr1,
		},
		{
			name: "Same Link Local most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: linkLocalAddr1},
			},
			remoteAddr:        linkLocalAddr1,
			expectedLocalAddr: linkLocalAddr1,
		},
		{
			name: "Same Link Local most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: globalAddr1},
			},
			remoteAddr:        linkLocalAddr1,
			expectedLocalAddr: linkLocalAddr1,
		},
		{
			name: "Same Unique Local most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: uniqueLocalAddr1},
				{addr: globalAddr1},
			},
			remoteAddr:        uniqueLocalAddr1,
			expectedLocalAddr: uniqueLocalAddr1,
		},
		{
			name: "Same Unique Local most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: uniqueLocalAddr1},
			},
			remoteAddr:        uniqueLocalAddr1,
			expectedLocalAddr: uniqueLocalAddr1,
		},

		// Test Rule 2 of RFC 6724 section 5 (prefer appropriate scope).
		{
			name: "Global most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: globalAddr1},
			},
			remoteAddr:        globalAddr2,
			expectedLocalAddr: globalAddr1,
		},
		{
			name: "Global most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: linkLocalAddr1},
			},
			remoteAddr:        globalAddr2,
			expectedLocalAddr: globalAddr1,
		},
		{
			name: "Link Local most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: linkLocalAddr1},
			},
			remoteAddr:        linkLocalAddr2,
			expectedLocalAddr: linkLocalAddr1,
		},
		{
			name: "Link Local most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: globalAddr1},
			},
			remoteAddr:        linkLocalAddr2,
			expectedLocalAddr: linkLocalAddr1,
		},
		{
			name: "Link Local most preferred for link local multicast (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: linkLocalAddr1},
			},
			remoteAddr:        linkLocalMulticastAddr,
			expectedLocalAddr: linkLocalAddr1,
		},
		{
			name: "Link Local most preferred for link local multicast (first address)",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: globalAddr1},
			},
			remoteAddr:        linkLocalMulticastAddr,
			expectedLocalAddr: linkLocalAddr1,
		},

		// Test Rule 3 of RFC 6724 section 5 (avoid deprecated addresses).
		{
			name: "Deprecated least preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{
					addr: globalAddr2,
					properties: stack.AddressProperties{
						Lifetimes: stack.AddressLifetimes{Deprecated: true},
					},
				},
			},
			remoteAddr:        globalAddr3,
			expectedLocalAddr: globalAddr1,
		},
		{
			name: "Deprecated least preferred (first address)",
			nicAddrs: []addressWithProperties{
				{
					addr: globalAddr2,
					properties: stack.AddressProperties{
						Lifetimes: stack.AddressLifetimes{Deprecated: true},
					},
				},
				{addr: globalAddr1},
			},
			remoteAddr:        globalAddr3,
			expectedLocalAddr: globalAddr1,
		},
		// Test Rule 6 of 6724 section 5 (prefer matching label).
		{
			name: "Unique Local most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: uniqueLocalAddr1},
				{addr: globalAddr1},
				{addr: ipv4MappedIPv6Addr1},
				{addr: toredoAddr1},
				{addr: ipv6ToIPv4Addr1},
			},
			remoteAddr:        uniqueLocalAddr2,
			expectedLocalAddr: uniqueLocalAddr1,
		},
		{
			name: "Unique Local most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: ipv4MappedIPv6Addr1},
				{addr: toredoAddr1},
				{addr: ipv6ToIPv4Addr1},
				{addr: uniqueLocalAddr1},
			},
			remoteAddr:        uniqueLocalAddr2,
			expectedLocalAddr: uniqueLocalAddr1,
		},
		{
			name: "Toredo most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: toredoAddr1},
				{addr: uniqueLocalAddr1},
				{addr: globalAddr1},
				{addr: ipv4MappedIPv6Addr1},
				{addr: ipv6ToIPv4Addr1},
			},
			remoteAddr:        toredoAddr2,
			expectedLocalAddr: toredoAddr1,
		},
		{
			name: "Toredo most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: ipv4MappedIPv6Addr1},
				{addr: ipv6ToIPv4Addr1},
				{addr: uniqueLocalAddr1},
				{addr: toredoAddr1},
			},
			remoteAddr:        toredoAddr2,
			expectedLocalAddr: toredoAddr1,
		},
		{
			name: "6To4 most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: ipv6ToIPv4Addr1},
				{addr: toredoAddr1},
				{addr: uniqueLocalAddr1},
				{addr: globalAddr1},
				{addr: ipv4MappedIPv6Addr1},
			},
			remoteAddr:        ipv6ToIPv4Addr2,
			expectedLocalAddr: ipv6ToIPv4Addr1,
		},
		{
			name: "6To4 most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: ipv4MappedIPv6Addr1},
				{addr: uniqueLocalAddr1},
				{addr: toredoAddr1},
				{addr: ipv6ToIPv4Addr1},
			},
			remoteAddr:        ipv6ToIPv4Addr2,
			expectedLocalAddr: ipv6ToIPv4Addr1,
		},
		{
			name: "IPv4 mapped IPv6 most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: ipv4MappedIPv6Addr1},
				{addr: ipv6ToIPv4Addr1},
				{addr: toredoAddr1},
				{addr: uniqueLocalAddr1},
				{addr: globalAddr1},
			},
			remoteAddr:        ipv4MappedIPv6Addr2,
			expectedLocalAddr: ipv4MappedIPv6Addr1,
		},
		{
			name: "IPv4 mapped IPv6 most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: ipv6ToIPv4Addr1},
				{addr: uniqueLocalAddr1},
				{addr: toredoAddr1},
				{addr: ipv4MappedIPv6Addr1},
			},
			remoteAddr:        ipv4MappedIPv6Addr2,
			expectedLocalAddr: ipv4MappedIPv6Addr1,
		},

		// Test Rule 7 of RFC 6724 section 5 (prefer temporary addresses).
		{
			name:                                   "Temp Global most preferred (prefix before addr add)",
			slaacPrefixForTempAddrBeforeNICAddrAdd: prefix1,
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: uniqueLocalAddr1},
				{addr: globalAddr1},
			},
			remoteAddr:        globalAddr2,
			expectedLocalAddr: tempGlobalAddr1,
		},
		{
			name: "Temp Global most preferred (prefix after addr add)",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: uniqueLocalAddr1},
				{addr: globalAddr1},
			},
			slaacPrefixForTempAddrAfterNICAddrAdd: prefix1,
			remoteAddr:                            globalAddr2,
			expectedLocalAddr:                     tempGlobalAddr1,
		},
		{
			name: "Temp Static most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr2},
				{
					addr: globalAddr1,
					properties: stack.AddressProperties{
						ConfigType: stack.AddressConfigStatic,
						Temporary:  true,
					},
				},
			},
			remoteAddr:        globalAddr3,
			expectedLocalAddr: globalAddr1,
		},
		{
			name: "Temp Static most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{
					addr: globalAddr1,
					properties: stack.AddressProperties{
						ConfigType: stack.AddressConfigStatic,
						Temporary:  true,
					},
				},
				{addr: globalAddr2},
			},
			remoteAddr:        globalAddr3,
			expectedLocalAddr: globalAddr1,
		},

		// Test Rule 8 of RFC 6724 section 5 (use longest matching prefix).
		{
			name: "Longest prefix matched most preferred (first address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr2},
				{addr: globalAddr1},
			},
			remoteAddr:        globalAddr3,
			expectedLocalAddr: globalAddr2,
		},
		{
			name: "Longest prefix matched most preferred (last address)",
			nicAddrs: []addressWithProperties{
				{addr: globalAddr1},
				{addr: globalAddr2},
			},
			remoteAddr:        globalAddr3,
			expectedLocalAddr: globalAddr2,
		},

		// Test returning the endpoint that is closest to the front when
		// candidate addresses are "equal" from the perspective of RFC 6724
		// section 5.
		{
			name: "Unique Local for Global",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: uniqueLocalAddr1},
				{addr: uniqueLocalAddr2},
			},
			remoteAddr:        globalAddr2,
			expectedLocalAddr: uniqueLocalAddr1,
		},
		{
			name: "Link Local for Global",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: linkLocalAddr2},
			},
			remoteAddr:        globalAddr2,
			expectedLocalAddr: linkLocalAddr1,
		},
		{
			name: "Link Local for Unique Local",
			nicAddrs: []addressWithProperties{
				{addr: linkLocalAddr1},
				{addr: linkLocalAddr2},
			},
			remoteAddr:        uniqueLocalAddr2,
			expectedLocalAddr: linkLocalAddr1,
		},
		{
			name:                                   "Temp Global for Global",
			slaacPrefixForTempAddrBeforeNICAddrAdd: prefix1,
			slaacPrefixForTempAddrAfterNICAddrAdd:  prefix2,
			remoteAddr:                             globalAddr1,
			expectedLocalAddr:                      tempGlobalAddr2,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			e := channel.New(0, 1280, linkAddr1)
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
					NDPConfigs: ipv6.NDPConfigurations{
						HandleRAs:                  ipv6.HandlingRAsEnabledWhenForwardingDisabled,
						AutoGenGlobalAddresses:     true,
						AutoGenTempGlobalAddresses: true,
					},
					NDPDisp: &ndpDispatcher{},
				})},
				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
			})
			if err := s.CreateNIC(nicID, e); err != nil {
				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
			}

			if test.slaacPrefixForTempAddrBeforeNICAddrAdd != (tcpip.AddressWithPrefix{}) {
				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, test.slaacPrefixForTempAddrBeforeNICAddrAdd, true, true, lifetimeSeconds, lifetimeSeconds))
			}

			for _, a := range test.nicAddrs {
				protocolAddr := tcpip.ProtocolAddress{
					Protocol:          ipv6.ProtocolNumber,
					AddressWithPrefix: a.addr.WithPrefix(),
				}
				if err := s.AddProtocolAddress(nicID, protocolAddr, a.properties); err != nil {
					t.Fatalf("AddProtocolAddress(%d, %+v, %+v): %s", nicID, protocolAddr, a.properties, err)
				}
			}

			if test.slaacPrefixForTempAddrAfterNICAddrAdd != (tcpip.AddressWithPrefix{}) {
				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, test.slaacPrefixForTempAddrAfterNICAddrAdd, true, true, lifetimeSeconds, lifetimeSeconds))
			}

			if t.Failed() {
				t.FailNow()
			}

			netEP, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber)
			if err != nil {
				t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
			}

			addressableEndpoint, ok := netEP.(stack.AddressableEndpoint)
			if !ok {
				t.Fatal("network endpoint is not addressable")
			}

			addressEP := addressableEndpoint.AcquireOutgoingPrimaryAddress(test.remoteAddr, tcpip.Address{} /* srcHint */, false /* allowExpired */)
			if addressEP == nil {
				t.Fatal("expected a non-nil address endpoint")
			}
			defer addressEP.DecRef()

			if got := addressEP.AddressWithPrefix().Address; got != test.expectedLocalAddr {
				t.Errorf("got local address = %s, want = %s", got, test.expectedLocalAddr)
			}
		})
	}
}

func TestAddRemoveIPv4BroadcastAddressOnNICEnableDisable(t *testing.T) {
	const nicID = 1
	broadcastAddr := tcpip.ProtocolAddress{
		Protocol: header.IPv4ProtocolNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   header.IPv4Broadcast,
			PrefixLen: 32,
		},
	}

	e := loopback.New()
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
	})
	nicOpts := stack.NICOptions{Disabled: true}
	if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
		t.Fatalf("CreateNIC(%d, _, %+v) = %s", nicID, nicOpts, err)
	}

	{
		allStackAddrs := s.AllAddresses()
		if allNICAddrs, ok := allStackAddrs[nicID]; !ok {
			t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
		} else if containsAddr(allNICAddrs, broadcastAddr) {
			t.Fatalf("got allNICAddrs = %+v, don't want = %+v", allNICAddrs, broadcastAddr)
		}
	}

	// Enabling the NIC should add the IPv4 broadcast address.
	if err := s.EnableNIC(nicID); err != nil {
		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
	}

	{
		allStackAddrs := s.AllAddresses()
		if allNICAddrs, ok := allStackAddrs[nicID]; !ok {
			t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
		} else if !containsAddr(allNICAddrs, broadcastAddr) {
			t.Fatalf("got allNICAddrs = %+v, want = %+v", allNICAddrs, broadcastAddr)
		}
	}

	// Disabling the NIC should remove the IPv4 broadcast address.
	if err := s.DisableNIC(nicID); err != nil {
		t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
	}

	{
		allStackAddrs := s.AllAddresses()
		if allNICAddrs, ok := allStackAddrs[nicID]; !ok {
			t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
		} else if containsAddr(allNICAddrs, broadcastAddr) {
			t.Fatalf("got allNICAddrs = %+v, don't want = %+v", allNICAddrs, broadcastAddr)
		}
	}
}

// TestLeaveIPv6SolicitedNodeAddrBeforeAddrRemoval tests that removing an IPv6
// address after leaving its solicited node multicast address does not result in
// an error.
func TestLeaveIPv6SolicitedNodeAddrBeforeAddrRemoval(t *testing.T) {
	const nicID = 1

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
	})
	e := channel.New(10, 1280, linkAddr1)
	if err := s.CreateNIC(1, e); err != nil {
		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
	}

	protocolAddr := tcpip.ProtocolAddress{
		Protocol:          ipv6.ProtocolNumber,
		AddressWithPrefix: addr1.WithPrefix(),
	}
	if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
	}

	// The NIC should have joined addr1's solicited node multicast address.
	snmc := header.SolicitedNodeAddr(addr1)
	in, err := s.IsInGroup(nicID, snmc)
	if err != nil {
		t.Fatalf("IsInGroup(%d, %s): %s", nicID, snmc, err)
	}
	if !in {
		t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, snmc)
	}

	if err := s.LeaveGroup(ipv6.ProtocolNumber, nicID, snmc); err != nil {
		t.Fatalf("LeaveGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, snmc, err)
	}
	in, err = s.IsInGroup(nicID, snmc)
	if err != nil {
		t.Fatalf("IsInGroup(%d, %s): %s", nicID, snmc, err)
	}
	if in {
		t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, snmc)
	}

	if err := s.RemoveAddress(nicID, addr1); err != nil {
		t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr1, err)
	}
}

func TestJoinLeaveMulticastOnNICEnableDisable(t *testing.T) {
	const nicID = 1

	tests := []struct {
		name  string
		proto tcpip.NetworkProtocolNumber
		addr  tcpip.Address
	}{
		{
			name:  "IPv6 All-Nodes",
			proto: header.IPv6ProtocolNumber,
			addr:  header.IPv6AllNodesMulticastAddress,
		},
		{
			name:  "IPv4 All-Systems",
			proto: header.IPv4ProtocolNumber,
			addr:  header.IPv4AllSystems,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			e := loopback.New()
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
			})
			nicOpts := stack.NICOptions{Disabled: true}
			if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
				t.Fatalf("CreateNIC(%d, _, %+v) = %s", nicID, nicOpts, err)
			}

			// Should not be in the multicast group yet because the NIC has not been
			// enabled yet.
			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
			} else if isInGroup {
				t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, test.addr)
			}

			// The all-nodes multicast group should be joined when the NIC is enabled.
			if err := s.EnableNIC(nicID); err != nil {
				t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
			}

			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
			} else if !isInGroup {
				t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, test.addr)
			}

			// The multicast group should be left when the NIC is disabled.
			if err := s.DisableNIC(nicID); err != nil {
				t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
			}

			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
			} else if isInGroup {
				t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, test.addr)
			}

			// The all-nodes multicast group should be joined when the NIC is enabled.
			if err := s.EnableNIC(nicID); err != nil {
				t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
			}

			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
			} else if !isInGroup {
				t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, test.addr)
			}

			// Leaving the group before disabling the NIC should not cause an error.
			if err := s.LeaveGroup(test.proto, nicID, test.addr); err != nil {
				t.Fatalf("s.LeaveGroup(%d, %d, %s): %s", test.proto, nicID, test.addr, err)
			}

			if err := s.DisableNIC(nicID); err != nil {
				t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
			}

			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
			} else if isInGroup {
				t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, test.addr)
			}
		})
	}
}

// TestDoDADWhenNICEnabled tests that IPv6 endpoints that were added while a NIC
// was disabled have DAD performed on them when the NIC is enabled.
func TestDoDADWhenNICEnabled(t *testing.T) {
	const dadTransmits = 1
	const retransmitTimer = time.Second
	const nicID = 1

	ndpDisp := ndpDispatcher{
		dadC: make(chan ndpDADEvent, 1),
	}
	clock := faketime.NewManualClock()
	opts := stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
			DADConfigs: stack.DADConfigurations{
				DupAddrDetectTransmits: dadTransmits,
				RetransmitTimer:        retransmitTimer,
			},
			NDPDisp: &ndpDisp,
		})},
		Clock: clock,
	}

	e := channel.New(dadTransmits, 1280, linkAddr1)
	s := stack.New(opts)
	nicOpts := stack.NICOptions{Disabled: true}
	if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
		t.Fatalf("CreateNIC(%d, _, %+v) = %s", nicID, nicOpts, err)
	}

	addr := tcpip.ProtocolAddress{
		Protocol: header.IPv6ProtocolNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   llAddr1,
			PrefixLen: 128,
		},
	}
	if err := s.AddProtocolAddress(nicID, addr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, addr, err)
	}

	// Address should be in the list of all addresses.
	if addrs := s.AllAddresses()[nicID]; !containsV6Addr(addrs, addr.AddressWithPrefix) {
		t.Fatalf("got s.AllAddresses()[%d] = %+v, want = %+v", nicID, addrs, addr)
	}

	// Address should be tentative so it should not be a main address.
	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
		t.Fatal(err)
	}

	// Enabling the NIC should start DAD for the address.
	if err := s.EnableNIC(nicID); err != nil {
		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
	}
	if addrs := s.AllAddresses()[nicID]; !containsV6Addr(addrs, addr.AddressWithPrefix) {
		t.Fatalf("got s.AllAddresses()[%d] = %+v, want = %+v", nicID, addrs, addr)
	}

	// Address should not be considered bound to the NIC yet (DAD ongoing).
	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
		t.Fatal(err)
	}

	// Wait for DAD to resolve.
	clock.Advance(dadTransmits * retransmitTimer)
	select {
	case e := <-ndpDisp.dadC:
		if diff := checkDADEvent(e, nicID, addr.AddressWithPrefix.Address, &stack.DADSucceeded{}); diff != "" {
			t.Errorf("dad event mismatch (-want +got):\n%s", diff)
		}
	default:
		t.Fatal("timed out waiting for DAD resolution")
	}
	if addrs := s.AllAddresses()[nicID]; !containsV6Addr(addrs, addr.AddressWithPrefix) {
		t.Fatalf("got s.AllAddresses()[%d] = %+v, want = %+v", nicID, addrs, addr)
	}
	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr.AddressWithPrefix); err != nil {
		t.Fatal(err)
	}

	// Enabling the NIC again should be a no-op.
	if err := s.EnableNIC(nicID); err != nil {
		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
	}
	if addrs := s.AllAddresses()[nicID]; !containsV6Addr(addrs, addr.AddressWithPrefix) {
		t.Fatalf("got s.AllAddresses()[%d] = %+v, want = %+v", nicID, addrs, addr)
	}
	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr.AddressWithPrefix); err != nil {
		t.Fatal(err)
	}
}

func TestStackReceiveBufferSizeOption(t *testing.T) {
	const sMin = stack.MinBufferSize
	testCases := []struct {
		name string
		rs   tcpip.ReceiveBufferSizeOption
		err  tcpip.Error
	}{
		// Invalid configurations.
		{"min_below_zero", tcpip.ReceiveBufferSizeOption{Min: -1, Default: sMin, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
		{"min_zero", tcpip.ReceiveBufferSizeOption{Min: 0, Default: sMin, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
		{"default_below_min", tcpip.ReceiveBufferSizeOption{Min: sMin, Default: sMin - 1, Max: sMin - 1}, &tcpip.ErrInvalidOptionValue{}},
		{"default_above_max", tcpip.ReceiveBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
		{"max_below_min", tcpip.ReceiveBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin - 1}, &tcpip.ErrInvalidOptionValue{}},

		// Valid Configurations
		{"in_ascending_order", tcpip.ReceiveBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin + 2}, nil},
		{"all_equal", tcpip.ReceiveBufferSizeOption{Min: sMin, Default: sMin, Max: sMin}, nil},
		{"min_default_equal", tcpip.ReceiveBufferSizeOption{Min: sMin, Default: sMin, Max: sMin + 1}, nil},
		{"default_max_equal", tcpip.ReceiveBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin + 1}, nil},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			s := stack.New(stack.Options{})
			defer s.Close()
			if err := s.SetOption(tc.rs); err != tc.err {
				t.Fatalf("s.SetOption(%#v) = %v, want: %v", tc.rs, err, tc.err)
			}
			var rs tcpip.ReceiveBufferSizeOption
			if tc.err == nil {
				if err := s.Option(&rs); err != nil {
					t.Fatalf("s.Option(%#v) = %v, want: nil", rs, err)
				}
				if got, want := rs, tc.rs; got != want {
					t.Fatalf("s.Option(..) returned unexpected value got: %#v, want: %#v", got, want)
				}
			}
		})
	}
}

func TestStackSendBufferSizeOption(t *testing.T) {
	const sMin = stack.MinBufferSize
	testCases := []struct {
		name string
		ss   tcpip.SendBufferSizeOption
		err  tcpip.Error
	}{
		// Invalid configurations.
		{"min_below_zero", tcpip.SendBufferSizeOption{Min: -1, Default: sMin, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
		{"min_zero", tcpip.SendBufferSizeOption{Min: 0, Default: sMin, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
		{"default_below_min", tcpip.SendBufferSizeOption{Min: 0, Default: sMin - 1, Max: sMin - 1}, &tcpip.ErrInvalidOptionValue{}},
		{"default_above_max", tcpip.SendBufferSizeOption{Min: 0, Default: sMin + 1, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
		{"max_below_min", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin - 1}, &tcpip.ErrInvalidOptionValue{}},

		// Valid Configurations
		{"in_ascending_order", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin + 2}, nil},
		{"all_equal", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin, Max: sMin}, nil},
		{"min_default_equal", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin, Max: sMin + 1}, nil},
		{"default_max_equal", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin + 1}, nil},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			s := stack.New(stack.Options{})
			defer s.Close()
			err := s.SetOption(tc.ss)
			if diff := cmp.Diff(tc.err, err); diff != "" {
				t.Fatalf("unexpected error from s.SetOption(%+v), (-want, +got):\n%s", tc.ss, diff)
			}
			if tc.err == nil {
				var ss tcpip.SendBufferSizeOption
				if err := s.Option(&ss); err != nil {
					t.Fatalf("s.Option(%+v) = %v, want: nil", ss, err)
				}
				if got, want := ss, tc.ss; got != want {
					t.Fatalf("s.Option(..) returned unexpected value got: %#v, want: %#v", got, want)
				}
			}
		})
	}
}

func TestOutgoingSubnetBroadcast(t *testing.T) {
	const (
		unspecifiedNICID = 0
		nicID1           = 1
	)

	defaultAddr := tcpip.AddressWithPrefix{
		Address:   header.IPv4Any,
		PrefixLen: 0,
	}
	defaultSubnet := defaultAddr.Subnet()
	ipv4Addr := tcpip.AddressWithPrefix{
		Address:   tcpip.AddrFromSlice([]byte("\xc0\xa8\x01\x3a")),
		PrefixLen: 24,
	}
	ipv4Subnet := ipv4Addr.Subnet()
	ipv4SubnetBcast := ipv4Subnet.Broadcast()
	ipv4Gateway := testutil.MustParse4("192.168.1.1")
	ipv4AddrPrefix31 := tcpip.AddressWithPrefix{
		Address:   tcpip.AddrFromSlice([]byte("\xc0\xa8\x01\x3a")),
		PrefixLen: 31,
	}
	ipv4Subnet31 := ipv4AddrPrefix31.Subnet()
	ipv4Subnet31Bcast := ipv4Subnet31.Broadcast()
	ipv4AddrPrefix32 := tcpip.AddressWithPrefix{
		Address:   tcpip.AddrFromSlice([]byte("\xc0\xa8\x01\x3a")),
		PrefixLen: 32,
	}
	ipv4Subnet32 := ipv4AddrPrefix32.Subnet()
	ipv4Subnet32Bcast := ipv4Subnet32.Broadcast()
	ipv6Addr := tcpip.AddressWithPrefix{
		Address:   tcpip.AddrFromSlice([]byte("\x20\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")),
		PrefixLen: 64,
	}
	ipv6Subnet := ipv6Addr.Subnet()
	ipv6SubnetBcast := ipv6Subnet.Broadcast()
	remNetAddr := tcpip.AddressWithPrefix{
		Address:   tcpip.AddrFromSlice([]byte("\x64\x0a\x7b\x18")),
		PrefixLen: 24,
	}
	remNetSubnet := remNetAddr.Subnet()
	remNetSubnetBcast := remNetSubnet.Broadcast()

	tests := []struct {
		name                      string
		nicAddr                   tcpip.ProtocolAddress
		routes                    []tcpip.Route
		remoteAddr                tcpip.Address
		expectedLocalAddress      tcpip.Address
		expectedRemoteAddress     tcpip.Address
		expectedRemoteLinkAddress tcpip.LinkAddress
		expectedNextHop           tcpip.Address
		expectedNetProto          tcpip.NetworkProtocolNumber
		expectedLoop              stack.PacketLooping
	}{
		// Broadcast to a locally attached subnet populates the broadcast MAC.
		{
			name: "IPv4 Broadcast to local subnet",
			nicAddr: tcpip.ProtocolAddress{
				Protocol:          header.IPv4ProtocolNumber,
				AddressWithPrefix: ipv4Addr,
			},
			routes: []tcpip.Route{
				{
					Destination: ipv4Subnet,
					NIC:         nicID1,
				},
			},
			remoteAddr:                ipv4SubnetBcast,
			expectedLocalAddress:      ipv4Addr.Address,
			expectedRemoteAddress:     ipv4SubnetBcast,
			expectedRemoteLinkAddress: header.EthernetBroadcastAddress,
			expectedNetProto:          header.IPv4ProtocolNumber,
			expectedLoop:              stack.PacketOut | stack.PacketLoop,
		},
		// Broadcast to a locally attached /31 subnet does not populate the
		// broadcast MAC.
		{
			name: "IPv4 Broadcast to local /31 subnet",
			nicAddr: tcpip.ProtocolAddress{
				Protocol:          header.IPv4ProtocolNumber,
				AddressWithPrefix: ipv4AddrPrefix31,
			},
			routes: []tcpip.Route{
				{
					Destination: ipv4Subnet31,
					NIC:         nicID1,
				},
			},
			remoteAddr:            ipv4Subnet31Bcast,
			expectedLocalAddress:  ipv4AddrPrefix31.Address,
			expectedRemoteAddress: ipv4Subnet31Bcast,
			expectedNetProto:      header.IPv4ProtocolNumber,
			expectedLoop:          stack.PacketOut,
		},
		// Broadcast to a locally attached /32 subnet does not populate the
		// broadcast MAC.
		{
			name: "IPv4 Broadcast to local /32 subnet",
			nicAddr: tcpip.ProtocolAddress{
				Protocol:          header.IPv4ProtocolNumber,
				AddressWithPrefix: ipv4AddrPrefix32,
			},
			routes: []tcpip.Route{
				{
					Destination: ipv4Subnet32,
					NIC:         nicID1,
				},
			},
			remoteAddr:            ipv4Subnet32Bcast,
			expectedLocalAddress:  ipv4AddrPrefix32.Address,
			expectedRemoteAddress: ipv4Subnet32Bcast,
			expectedNetProto:      header.IPv4ProtocolNumber,
			expectedLoop:          stack.PacketOut,
		},
		// IPv6 has no notion of a broadcast.
		{
			name: "IPv6 'Broadcast' to local subnet",
			nicAddr: tcpip.ProtocolAddress{
				Protocol:          header.IPv6ProtocolNumber,
				AddressWithPrefix: ipv6Addr,
			},
			routes: []tcpip.Route{
				{
					Destination: ipv6Subnet,
					NIC:         nicID1,
				},
			},
			remoteAddr:            ipv6SubnetBcast,
			expectedLocalAddress:  ipv6Addr.Address,
			expectedRemoteAddress: ipv6SubnetBcast,
			expectedNetProto:      header.IPv6ProtocolNumber,
			expectedLoop:          stack.PacketOut,
		},
		// Broadcast to a remote subnet in the route table is send to the next-hop
		// gateway.
		{
			name: "IPv4 Broadcast to remote subnet",
			nicAddr: tcpip.ProtocolAddress{
				Protocol:          header.IPv4ProtocolNumber,
				AddressWithPrefix: ipv4Addr,
			},
			routes: []tcpip.Route{
				{
					Destination: remNetSubnet,
					Gateway:     ipv4Gateway,
					NIC:         nicID1,
				},
			},
			remoteAddr:            remNetSubnetBcast,
			expectedLocalAddress:  ipv4Addr.Address,
			expectedRemoteAddress: remNetSubnetBcast,
			expectedNextHop:       ipv4Gateway,
			expectedNetProto:      header.IPv4ProtocolNumber,
			expectedLoop:          stack.PacketOut,
		},
		// Broadcast to an unknown subnet follows the default route. Note that this
		// is essentially just routing an unknown destination IP, because w/o any
		// subnet prefix information a subnet broadcast address is just a normal IP.
		{
			name: "IPv4 Broadcast to unknown subnet",
			nicAddr: tcpip.ProtocolAddress{
				Protocol:          header.IPv4ProtocolNumber,
				AddressWithPrefix: ipv4Addr,
			},
			routes: []tcpip.Route{
				{
					Destination: defaultSubnet,
					Gateway:     ipv4Gateway,
					NIC:         nicID1,
				},
			},
			remoteAddr:            remNetSubnetBcast,
			expectedLocalAddress:  ipv4Addr.Address,
			expectedRemoteAddress: remNetSubnetBcast,
			expectedNextHop:       ipv4Gateway,
			expectedNetProto:      header.IPv4ProtocolNumber,
			expectedLoop:          stack.PacketOut,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
			})
			ep := channel.New(0, defaultMTU, "")
			ep.LinkEPCapabilities |= stack.CapabilityResolutionRequired
			if err := s.CreateNIC(nicID1, ep); err != nil {
				t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
			}
			if err := s.AddProtocolAddress(nicID1, test.nicAddr, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID1, test.nicAddr, err)
			}

			s.SetRouteTable(test.routes)

			var netProto tcpip.NetworkProtocolNumber
			switch l := test.remoteAddr.Len(); l {
			case header.IPv4AddressSize:
				netProto = header.IPv4ProtocolNumber
			case header.IPv6AddressSize:
				netProto = header.IPv6ProtocolNumber
			default:
				t.Fatalf("got unexpected address length = %d bytes", l)
			}

			r, err := s.FindRoute(unspecifiedNICID, tcpip.Address{} /* localAddr */, test.remoteAddr, netProto, false /* multicastLoop */)
			if err != nil {
				t.Fatalf("FindRoute(%d, '', %s, %d): %s", unspecifiedNICID, test.remoteAddr, netProto, err)
			}
			if r.LocalAddress() != test.expectedLocalAddress {
				t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), test.expectedLocalAddress)
			}
			if r.RemoteAddress() != test.expectedRemoteAddress {
				t.Errorf("got r.RemoteAddress = %s, want = %s", r.RemoteAddress(), test.expectedRemoteAddress)
			}
			if got := r.RemoteLinkAddress(); got != test.expectedRemoteLinkAddress {
				t.Errorf("got r.RemoteLinkAddress() = %s, want = %s", got, test.expectedRemoteLinkAddress)
			}
			if r.NextHop() != test.expectedNextHop {
				t.Errorf("got r.NextHop() = %s, want = %s", r.NextHop(), test.expectedNextHop)
			}
			if r.NetProto() != test.expectedNetProto {
				t.Errorf("got r.NetProto() = %d, want = %d", r.NetProto(), test.expectedNetProto)
			}
			if r.Loop() != test.expectedLoop {
				t.Errorf("got r.Loop() = %x, want = %x", r.Loop(), test.expectedLoop)
			}
		})
	}
}

func TestResolveWith(t *testing.T) {
	const (
		unspecifiedNICID = 0
		nicID            = 1
	)

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, arp.NewProtocol},
	})
	ep := channel.New(0, defaultMTU, "")
	ep.LinkEPCapabilities |= stack.CapabilityResolutionRequired
	if err := s.CreateNIC(nicID, ep); err != nil {
		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
	}
	addr := tcpip.ProtocolAddress{
		Protocol: header.IPv4ProtocolNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFrom4Slice([]byte{192, 168, 1, 58}),
			PrefixLen: 24,
		},
	}
	if err := s.AddProtocolAddress(nicID, addr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, addr, err)
	}

	s.SetRouteTable([]tcpip.Route{{Destination: header.IPv4EmptySubnet, NIC: nicID}})

	remoteAddr := tcpip.AddrFrom4Slice([]byte{192, 168, 1, 59})
	r, err := s.FindRoute(unspecifiedNICID, tcpip.Address{} /* localAddr */, remoteAddr, header.IPv4ProtocolNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatalf("FindRoute(%d, '', %s, %d): %s", unspecifiedNICID, remoteAddr, header.IPv4ProtocolNumber, err)
	}
	defer r.Release()

	// Should initially require resolution.
	if !r.IsResolutionRequired() {
		t.Fatal("got r.IsResolutionRequired() = false, want = true")
	}

	// Manually resolving the route should no longer require resolution.
	r.ResolveWith("\x01")
	if r.IsResolutionRequired() {
		t.Fatal("got r.IsResolutionRequired() = true, want = false")
	}
}

// TestRouteReleaseAfterAddrRemoval tests that releasing a Route after its
// associated address is removed should not cause a panic.
func TestRouteReleaseAfterAddrRemoval(t *testing.T) {
	const (
		nicID = 1
	)
	var (
		localAddr  = tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
		remoteAddr = tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))
	)

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	ep := channel.New(0, defaultMTU, "")
	if err := s.CreateNIC(nicID, ep); err != nil {
		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
	}
	protocolAddr := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   localAddr,
			PrefixLen: fakeDefaultPrefixLen,
		},
	}
	if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
	}
	{
		subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
		if err != nil {
			t.Fatal(err)
		}
		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}})
	}

	r, err := s.FindRoute(nicID, localAddr, remoteAddr, fakeNetNumber, false /* multicastLoop */)
	if err != nil {
		t.Fatalf("s.FindRoute(%d, %s, %s, %d, false): %s", nicID, localAddr, remoteAddr, fakeNetNumber, err)
	}
	// Should not panic.
	defer r.Release()

	// Check that removing the same address fails.
	if err := s.RemoveAddress(nicID, localAddr); err != nil {
		t.Fatalf("s.RemoveAddress(%d, %s): %s", nicID, localAddr, err)
	}
}

func TestGetNetworkEndpoint(t *testing.T) {
	const nicID = 1

	tests := []struct {
		name         string
		protoFactory stack.NetworkProtocolFactory
		protoNum     tcpip.NetworkProtocolNumber
	}{
		{
			name:         "IPv4",
			protoFactory: ipv4.NewProtocol,
			protoNum:     ipv4.ProtocolNumber,
		},
		{
			name:         "IPv6",
			protoFactory: ipv6.NewProtocol,
			protoNum:     ipv6.ProtocolNumber,
		},
	}

	factories := make([]stack.NetworkProtocolFactory, 0, len(tests))
	for _, test := range tests {
		factories = append(factories, test.protoFactory)
	}

	s := stack.New(stack.Options{
		NetworkProtocols: factories,
	})

	if err := s.CreateNIC(nicID, channel.New(0, defaultMTU, "")); err != nil {
		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ep, err := s.GetNetworkEndpoint(nicID, test.protoNum)
			if err != nil {
				t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, test.protoNum, err)
			}

			if got := ep.NetworkProtocolNumber(); got != test.protoNum {
				t.Fatalf("got ep.NetworkProtocolNumber() = %d, want = %d", got, test.protoNum)
			}
		})
	}
}

func TestGetMainNICAddressWhenNICDisabled(t *testing.T) {
	const nicID = 1

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
	})

	if err := s.CreateNIC(nicID, channel.New(0, defaultMTU, "")); err != nil {
		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
	}

	protocolAddress := tcpip.ProtocolAddress{
		Protocol: fakeNetNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")),
			PrefixLen: 32,
		},
	}
	if err := s.AddProtocolAddress(nicID, protocolAddress, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddress, err)
	}

	// Check that we get the right initial address and prefix length.
	if err := checkGetMainNICAddress(s, nicID, fakeNetNumber, protocolAddress.AddressWithPrefix); err != nil {
		t.Fatal(err)
	}

	// Should still get the address when the NIC is disabled.
	if err := s.DisableNIC(nicID); err != nil {
		t.Fatalf("DisableNIC(%d): %s", nicID, err)
	}
	if err := checkGetMainNICAddress(s, nicID, fakeNetNumber, protocolAddress.AddressWithPrefix); err != nil {
		t.Fatal(err)
	}
}

// TestAddRoute tests Stack.AddRoute
func TestAddRoute(t *testing.T) {
	s := stack.New(stack.Options{})

	subnet1, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
	if err != nil {
		t.Fatal(err)
	}

	subnet2, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00")), tcpip.MaskFrom("\xff\x00\x00\x00"))
	if err != nil {
		t.Fatal(err)
	}

	expected := []tcpip.Route{
		{Destination: subnet2, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
		{Destination: subnet1, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
	}

	// Initialize the route table with one route.
	s.SetRouteTable([]tcpip.Route{expected[0]})

	// Add another route.
	s.AddRoute(expected[1])

	rt := s.GetRouteTable()
	if got, want := len(rt), len(expected); got != want {
		t.Fatalf("Unexpected route table length got = %d, want = %d", got, want)
	}
	for i, route := range rt {
		if got, want := route, expected[i]; !got.Equal(want) {
			t.Fatalf("Unexpected route got = %#v, want = %#v", got, want)
		}
	}
}

// TestRemoveRoutes tests Stack.RemoveRoutes
func TestRemoveRoutes(t *testing.T) {
	s := stack.New(stack.Options{})

	addressToRemove := tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
	subnet1, err := tcpip.NewSubnet(addressToRemove, tcpip.MaskFrom("\x01\x00\x00\x00"))
	if err != nil {
		t.Fatal(err)
	}

	subnet2, err := tcpip.NewSubnet(addressToRemove, tcpip.MaskFrom("\x01\x00\x00\x00"))
	if err != nil {
		t.Fatal(err)
	}

	subnet3, err := tcpip.NewSubnet(tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00")), tcpip.MaskFrom("\x02\x00\x00\x00"))
	if err != nil {
		t.Fatal(err)
	}

	routeList := []tcpip.Route{
		{Destination: subnet1, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
		{Destination: subnet2, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
		{Destination: subnet3, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
	}
	// Initialize the route table with three routes.
	s.SetRouteTable(routeList)

	// Remove routes with the specific address.
	removed := s.RemoveRoutes(func(r tcpip.Route) bool {
		return r.Destination.ID() == addressToRemove
	})

	expected := []tcpip.Route{{Destination: subnet3, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1}}
	rt := s.GetRouteTable()
	if got, want := len(rt), len(expected); got != want {
		t.Fatalf("Unexpected route table length got = %d, want = %d", got, want)
	}
	for i, route := range rt {
		if got, want := route, expected[i]; got != want {
			t.Fatalf("Unexpected route got = %#v, want = %#v", got, want)
		}
	}

	if got, want := removed, len(routeList)-len(expected); want != removed {
		t.Fatalf("stack.RemoveRoutes(_) removes %v routes, want = %v", got, want)
	}
}

func TestFindRouteWithForwarding(t *testing.T) {
	const (
		nicID1 = 1
		nicID2 = 2
	)
	var (
		nic1Addr   = tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
		nic2Addr   = tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))
		remoteAddr = tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00"))
	)

	type netCfg struct {
		proto              tcpip.NetworkProtocolNumber
		factory            stack.NetworkProtocolFactory
		nic1AddrWithPrefix tcpip.AddressWithPrefix
		nic2AddrWithPrefix tcpip.AddressWithPrefix
		remoteAddr         tcpip.Address
	}

	fakeNetCfg := netCfg{
		proto:              fakeNetNumber,
		factory:            fakeNetFactory,
		nic1AddrWithPrefix: tcpip.AddressWithPrefix{Address: nic1Addr, PrefixLen: fakeDefaultPrefixLen},
		nic2AddrWithPrefix: tcpip.AddressWithPrefix{Address: nic2Addr, PrefixLen: fakeDefaultPrefixLen},
		remoteAddr:         remoteAddr,
	}

	globalIPv6Addr1 := tcpip.AddrFrom16Slice(net.ParseIP("a::1").To16())
	globalIPv6Addr2 := tcpip.AddrFrom16Slice(net.ParseIP("a::2").To16())

	ipv6LinkLocalNIC1WithGlobalRemote := netCfg{
		proto:              ipv6.ProtocolNumber,
		factory:            ipv6.NewProtocol,
		nic1AddrWithPrefix: llAddr1.WithPrefix(),
		nic2AddrWithPrefix: globalIPv6Addr2.WithPrefix(),
		remoteAddr:         globalIPv6Addr1,
	}
	ipv6GlobalNIC1WithLinkLocalRemote := netCfg{
		proto:              ipv6.ProtocolNumber,
		factory:            ipv6.NewProtocol,
		nic1AddrWithPrefix: globalIPv6Addr1.WithPrefix(),
		nic2AddrWithPrefix: llAddr1.WithPrefix(),
		remoteAddr:         llAddr2,
	}
	ipv6GlobalNIC1WithLinkLocalMulticastRemote := netCfg{
		proto:              ipv6.ProtocolNumber,
		factory:            ipv6.NewProtocol,
		nic1AddrWithPrefix: globalIPv6Addr1.WithPrefix(),
		nic2AddrWithPrefix: globalIPv6Addr2.WithPrefix(),
		remoteAddr:         tcpip.AddrFromSlice([]byte("\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")),
	}

	tests := []struct {
		name string

		netCfg            netCfg
		forwardingEnabled bool

		addrNIC             tcpip.NICID
		localAddrWithPrefix tcpip.AddressWithPrefix

		findRouteErr          tcpip.Error
		dependentOnForwarding bool
	}{
		{
			name:                  "forwarding disabled and localAddr not on specified NIC but route from different NIC",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     false,
			addrNIC:               nicID1,
			localAddrWithPrefix:   fakeNetCfg.nic2AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and localAddr not on specified NIC but route from different NIC",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     true,
			addrNIC:               nicID1,
			localAddrWithPrefix:   fakeNetCfg.nic2AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and localAddr on specified NIC but route from different NIC",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     false,
			addrNIC:               nicID1,
			localAddrWithPrefix:   fakeNetCfg.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and localAddr on specified NIC but route from different NIC",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     true,
			addrNIC:               nicID1,
			localAddrWithPrefix:   fakeNetCfg.nic1AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: true,
		},
		{
			name:                  "forwarding disabled and localAddr on specified NIC and route from same NIC",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     false,
			addrNIC:               nicID2,
			localAddrWithPrefix:   fakeNetCfg.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and localAddr on specified NIC and route from same NIC",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     true,
			addrNIC:               nicID2,
			localAddrWithPrefix:   fakeNetCfg.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and localAddr not on specified NIC but route from same NIC",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     false,
			addrNIC:               nicID2,
			localAddrWithPrefix:   fakeNetCfg.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and localAddr not on specified NIC but route from same NIC",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     true,
			addrNIC:               nicID2,
			localAddrWithPrefix:   fakeNetCfg.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and localAddr on same NIC as route",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     false,
			localAddrWithPrefix:   fakeNetCfg.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and localAddr on same NIC as route",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     false,
			localAddrWithPrefix:   fakeNetCfg.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and localAddr on different NIC as route",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     false,
			localAddrWithPrefix:   fakeNetCfg.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and localAddr on different NIC as route",
			netCfg:                fakeNetCfg,
			forwardingEnabled:     true,
			localAddrWithPrefix:   fakeNetCfg.nic1AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: true,
		},
		{
			name:                  "forwarding disabled and specified NIC only has link-local addr with route on different NIC",
			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
			forwardingEnabled:     false,
			addrNIC:               nicID1,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and specified NIC only has link-local addr with route on different NIC",
			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
			forwardingEnabled:     true,
			addrNIC:               nicID1,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and link-local local addr with route on different NIC",
			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
			forwardingEnabled:     false,
			localAddrWithPrefix:   ipv6LinkLocalNIC1WithGlobalRemote.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and link-local local addr with route on same NIC",
			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
			forwardingEnabled:     true,
			localAddrWithPrefix:   ipv6LinkLocalNIC1WithGlobalRemote.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and global local addr with route on same NIC",
			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
			forwardingEnabled:     true,
			localAddrWithPrefix:   ipv6LinkLocalNIC1WithGlobalRemote.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and link-local local addr with route on same NIC",
			netCfg:                ipv6GlobalNIC1WithLinkLocalRemote,
			forwardingEnabled:     false,
			localAddrWithPrefix:   ipv6GlobalNIC1WithLinkLocalRemote.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and link-local local addr with route on same NIC",
			netCfg:                ipv6GlobalNIC1WithLinkLocalRemote,
			forwardingEnabled:     true,
			localAddrWithPrefix:   ipv6GlobalNIC1WithLinkLocalRemote.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and global local addr with link-local remote on different NIC",
			netCfg:                ipv6GlobalNIC1WithLinkLocalRemote,
			forwardingEnabled:     false,
			localAddrWithPrefix:   ipv6GlobalNIC1WithLinkLocalRemote.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and global local addr with link-local remote on different NIC",
			netCfg:                ipv6GlobalNIC1WithLinkLocalRemote,
			forwardingEnabled:     true,
			localAddrWithPrefix:   ipv6GlobalNIC1WithLinkLocalRemote.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and global local addr with link-local multicast remote on different NIC",
			netCfg:                ipv6GlobalNIC1WithLinkLocalMulticastRemote,
			forwardingEnabled:     false,
			localAddrWithPrefix:   ipv6GlobalNIC1WithLinkLocalMulticastRemote.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and global local addr with link-local multicast remote on different NIC",
			netCfg:                ipv6GlobalNIC1WithLinkLocalMulticastRemote,
			forwardingEnabled:     true,
			localAddrWithPrefix:   ipv6GlobalNIC1WithLinkLocalMulticastRemote.nic1AddrWithPrefix,
			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding disabled and global local addr with link-local multicast remote on same NIC",
			netCfg:                ipv6GlobalNIC1WithLinkLocalMulticastRemote,
			forwardingEnabled:     false,
			localAddrWithPrefix:   ipv6GlobalNIC1WithLinkLocalMulticastRemote.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
		{
			name:                  "forwarding enabled and global local addr with link-local multicast remote on same NIC",
			netCfg:                ipv6GlobalNIC1WithLinkLocalMulticastRemote,
			forwardingEnabled:     true,
			localAddrWithPrefix:   ipv6GlobalNIC1WithLinkLocalMulticastRemote.nic2AddrWithPrefix,
			findRouteErr:          nil,
			dependentOnForwarding: false,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{test.netCfg.factory},
			})

			ep1 := channel.New(1, defaultMTU, "")
			if err := s.CreateNIC(nicID1, ep1); err != nil {
				t.Fatalf("CreateNIC(%d, _): %s:", nicID1, err)
			}

			ep2 := channel.New(1, defaultMTU, "")
			if err := s.CreateNIC(nicID2, ep2); err != nil {
				t.Fatalf("CreateNIC(%d, _): %s:", nicID2, err)
			}

			protocolAddr1 := tcpip.ProtocolAddress{
				Protocol:          test.netCfg.proto,
				AddressWithPrefix: test.netCfg.nic1AddrWithPrefix,
			}
			if err := s.AddProtocolAddress(nicID1, protocolAddr1, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID1, protocolAddr1, err)
			}

			protocolAddr2 := tcpip.ProtocolAddress{
				Protocol:          test.netCfg.proto,
				AddressWithPrefix: test.netCfg.nic2AddrWithPrefix,
			}
			if err := s.AddProtocolAddress(nicID2, protocolAddr2, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddr2, err)
			}

			if err := s.SetForwardingDefaultAndAllNICs(test.netCfg.proto, test.forwardingEnabled); err != nil {
				t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", test.netCfg.proto, test.forwardingEnabled, err)
			}

			s.SetRouteTable([]tcpip.Route{{Destination: test.netCfg.remoteAddr.WithPrefix().Subnet(), NIC: nicID2}})

			r, err := s.FindRoute(test.addrNIC, test.localAddrWithPrefix.Address, test.netCfg.remoteAddr, test.netCfg.proto, false /* multicastLoop */)
			if err == nil {
				defer r.Release()
			}
			if diff := cmp.Diff(test.findRouteErr, err); diff != "" {
				t.Fatalf("unexpected error from FindRoute(%d, %s, %s, %d, false), (-want, +got):\n%s", test.addrNIC, test.localAddrWithPrefix.Address, test.netCfg.remoteAddr, test.netCfg.proto, diff)
			}

			if test.findRouteErr != nil {
				return
			}

			if r.LocalAddress() != test.localAddrWithPrefix.Address {
				t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), test.localAddrWithPrefix.Address)
			}
			if r.RemoteAddress() != test.netCfg.remoteAddr {
				t.Errorf("got r.RemoteAddress() = %s, want = %s", r.RemoteAddress(), test.netCfg.remoteAddr)
			}

			if t.Failed() {
				t.FailNow()
			}

			// Sending a packet should always go through NIC2 since we only install a
			// route to test.netCfg.remoteAddr through NIC2.
			data := []byte{1, 2, 3, 4}
			if err := send(r, data); err != nil {
				t.Fatalf("send(_, _): %s", err)
			}
			if n := ep1.Drain(); n != 0 {
				t.Errorf("got %d unexpected packets from ep1", n)
			}
			pkt := ep2.Read()
			if pkt == nil {
				t.Fatal("packet not sent through ep2")
			}
			defer pkt.DecRef()
			if pkt.EgressRoute.LocalAddress != test.localAddrWithPrefix.Address {
				t.Errorf("got pkt.EgressRoute.LocalAddress = %s, want = %s", pkt.EgressRoute.LocalAddress, test.localAddrWithPrefix.Address)
			}
			if pkt.EgressRoute.RemoteAddress != test.netCfg.remoteAddr {
				t.Errorf("got pkt.EgressRoute.RemoteAddress = %s, want = %s", pkt.EgressRoute.RemoteAddress, test.netCfg.remoteAddr)
			}

			if !test.forwardingEnabled || !test.dependentOnForwarding {
				return
			}

			// Disabling forwarding when the route is dependent on forwarding being
			// enabled should make the route invalid.
			if err := s.SetForwardingDefaultAndAllNICs(test.netCfg.proto, false); err != nil {
				t.Fatalf("SetForwardingDefaultAndAllNICs(%d, false): %s", test.netCfg.proto, err)
			}
			{
				err := send(r, data)
				if _, ok := err.(*tcpip.ErrInvalidEndpointState); !ok {
					t.Fatalf("got send(_, _) = %s, want = %s", err, &tcpip.ErrInvalidEndpointState{})
				}
			}
			if n := ep1.Drain(); n != 0 {
				t.Errorf("got %d unexpected packets from ep1", n)
			}
			if n := ep2.Drain(); n != 0 {
				t.Errorf("got %d unexpected packets from ep2", n)
			}
		})
	}
}

func TestFindRoutePrefersLocalAddrOnlyForLocallyGeneratedTraffic(t *testing.T) {
	const (
		nicID1 = 1
		nicID2 = 2
	)
	var (
		nic1Addr    = tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
		nic2Addr    = tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))
		gatewayAddr = tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00"))
	)

	tests := []struct {
		name            string
		localAddr       tcpip.Address
		remoteAddr      tcpip.Address
		wantOutgoingNIC tcpip.NICID
	}{
		{
			name:            "locally generated traffic routed through default gateway because we prefer local address on outgoing interface",
			localAddr:       nic1Addr,
			remoteAddr:      nic2Addr,
			wantOutgoingNIC: nicID1,
		},
		{
			name:            "forwarded traffic routed through NIC 2 because local address preference only applies to locally generated traffic",
			localAddr:       tcpip.Address{},
			remoteAddr:      nic2Addr,
			wantOutgoingNIC: nicID2,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
			})

			ep1 := channel.New(1, defaultMTU, "")
			if err := s.CreateNIC(nicID1, ep1); err != nil {
				t.Fatalf("CreateNIC(%d, _): %s:", nicID1, err)
			}

			ep2 := channel.New(1, defaultMTU, "")
			if err := s.CreateNIC(nicID2, ep2); err != nil {
				t.Fatalf("CreateNIC(%d, _): %s:", nicID2, err)
			}

			// NB: we do *not* assign nic2Addr on NIC 2. We are exercising the scenario when we are forwarding
			// traffic to an address that we do not own.
			protocolAddr1 := tcpip.ProtocolAddress{
				Protocol:          fakeNetNumber,
				AddressWithPrefix: tcpip.AddressWithPrefix{Address: nic1Addr, PrefixLen: fakeDefaultPrefixLen},
			}
			if err := s.AddProtocolAddress(nicID1, protocolAddr1, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID1, protocolAddr1, err)
			}

			if err := s.SetForwardingDefaultAndAllNICs(fakeNetNumber, true); err != nil {
				t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", fakeNetNumber, true, err)
			}

			unspecifiedSubnet := func() tcpip.Subnet {
				unspecifiedSubnet, err := tcpip.NewSubnet(tcpip.AddrFrom4Slice([]byte("\x00\x00\x00\x00")), tcpip.MaskFrom("\x00\x00\x00\x00"))
				if err != nil {
					t.Fatal(err)
				}
				return unspecifiedSubnet
			}()
			s.SetRouteTable([]tcpip.Route{{Destination: nic2Addr.WithPrefix().Subnet(), NIC: nicID2}, {Destination: unspecifiedSubnet, Gateway: gatewayAddr, NIC: nicID1}})

			r, err := s.FindRoute(0, test.localAddr, test.remoteAddr, fakeNetNumber, false /* multicastLoop */)
			if err != nil {
				t.Fatalf("FindRoute(0, %s, %s, %d, false): got %s, want nil", test.localAddr, test.remoteAddr, fakeNetNumber, err)
			}
			if r.NICID() != test.wantOutgoingNIC {
				t.Errorf("got r.NICID() = %d, want = %d", r.NICID(), test.wantOutgoingNIC)
			}

			if t.Failed() {
				t.FailNow()
			}
		})
	}
}

func TestAddMulticastRoute(t *testing.T) {
	const (
		incomingNICID = 1
		outgoingNICID = 2
	)
	address := testutil.MustParse4("192.168.1.1")
	outgoingInterfaces := []stack.MulticastRouteOutgoingInterface{{ID: outgoingNICID, MinTTL: 3}}
	addresses := stack.UnicastSourceAndMulticastDestination{Source: address, Destination: address}

	tests := []struct {
		name     string
		netProto tcpip.NetworkProtocolNumber
		factory  stack.NetworkProtocolFactory
		wantErr  tcpip.Error
	}{
		{
			name:     "valid",
			netProto: fakeNetNumber,
			factory:  fakeNetFactory,
			wantErr:  nil,
		},
		{
			name:     "unknown protocol",
			factory:  fakeNetFactory,
			netProto: arp.ProtocolNumber,
			wantErr:  &tcpip.ErrUnknownProtocol{},
		},
		{
			name:     "not supported",
			factory:  arp.NewProtocol,
			netProto: arp.ProtocolNumber,
			wantErr:  &tcpip.ErrNotSupported{},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{test.factory},
			})

			route := stack.MulticastRoute{
				ExpectedInputInterface: incomingNICID,
				OutgoingInterfaces:     outgoingInterfaces,
			}

			err := s.AddMulticastRoute(test.netProto, addresses, route)

			if !cmp.Equal(err, test.wantErr, cmpopts.EquateErrors()) {
				t.Errorf("s.AddMulticastRoute(%d, %#v, %#v) = %s, want %s", test.netProto, addresses, route, err, test.wantErr)
			}

			if test.wantErr == nil {
				fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)

				expectedAddMulticastRouteData := addMulticastRouteData{addresses, route}
				if !cmp.Equal(fakeNet.addMulticastRouteData, expectedAddMulticastRouteData, cmp.AllowUnexported(addMulticastRouteData{}, stack.MulticastRoute{})) {
					t.Errorf("fakeNet.addMulticastRouteData = %#v, want = %#v", fakeNet.addMulticastRouteData, expectedAddMulticastRouteData)
				}
			}
		})
	}
}

func TestRemoveMulticastRoute(t *testing.T) {
	const nicID = 1
	address := testutil.MustParse4("192.168.1.1")
	addresses := stack.UnicastSourceAndMulticastDestination{Source: address, Destination: address}

	tests := []struct {
		name     string
		netProto tcpip.NetworkProtocolNumber
		factory  stack.NetworkProtocolFactory
		wantErr  tcpip.Error
	}{
		{
			name:     "valid",
			netProto: fakeNetNumber,
			factory:  fakeNetFactory,
			wantErr:  nil,
		},
		{
			name:     "unknown protocol",
			factory:  fakeNetFactory,
			netProto: arp.ProtocolNumber,
			wantErr:  &tcpip.ErrUnknownProtocol{},
		},
		{
			name:     "not supported",
			factory:  arp.NewProtocol,
			netProto: arp.ProtocolNumber,
			wantErr:  &tcpip.ErrNotSupported{},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{test.factory},
			})

			err := s.RemoveMulticastRoute(test.netProto, addresses)

			if !cmp.Equal(err, test.wantErr, cmpopts.EquateErrors()) {
				t.Errorf("s.RemoveMulticastRoute(%d, %#v) = %s, want %s", test.netProto, addresses, err, test.wantErr)
			}

			if test.wantErr == nil {
				fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
				if !cmp.Equal(fakeNet.removeMulticastRouteData, addresses) {
					t.Errorf("fakeNet.removeMulticastRouteData = %#v, want = %#v", fakeNet.removeMulticastRouteData, addresses)
				}
			}
		})
	}
}

func TestMulticastRouteLastUsedTime(t *testing.T) {
	address := testutil.MustParse4("192.168.1.1")
	addresses := stack.UnicastSourceAndMulticastDestination{Source: address, Destination: address}

	tests := []struct {
		name     string
		netProto tcpip.NetworkProtocolNumber
		factory  stack.NetworkProtocolFactory
		wantErr  tcpip.Error
	}{
		{
			name:     "valid",
			netProto: fakeNetNumber,
			factory:  fakeNetFactory,
			wantErr:  nil,
		},
		{
			name:     "unknown protocol",
			factory:  fakeNetFactory,
			netProto: arp.ProtocolNumber,
			wantErr:  &tcpip.ErrUnknownProtocol{},
		},
		{
			name:     "not supported",
			factory:  arp.NewProtocol,
			netProto: arp.ProtocolNumber,
			wantErr:  &tcpip.ErrNotSupported{},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{test.factory},
			})

			_, err := s.MulticastRouteLastUsedTime(test.netProto, addresses)

			if !cmp.Equal(err, test.wantErr, cmpopts.EquateErrors()) {
				t.Errorf("s.MulticastRouteLastUsedTime(%d, %#v) = %v, want %v", test.netProto, addresses, err, test.wantErr)
			}

			if test.wantErr == nil {
				fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)

				if !cmp.Equal(fakeNet.multicastRouteLastUsedTimeData, addresses) {
					t.Errorf("fakeNet.multicastRouteLastUsedTimeData = %#v, want = %#v", fakeNet.multicastRouteLastUsedTimeData, addresses)
				}
			}
		})
	}
}

func TestEnableMulticastForwardingForProtocol(t *testing.T) {
	tests := []struct {
		name           string
		netProto       tcpip.NetworkProtocolNumber
		factory        stack.NetworkProtocolFactory
		delegateOutput enableMulticastForwardingForProtocolResult
		wantResult     enableMulticastForwardingForProtocolResult
	}{
		{
			name:           "impl returns previously enabled",
			netProto:       fakeNetNumber,
			factory:        fakeNetFactory,
			delegateOutput: enableMulticastForwardingForProtocolResult{true, nil},
			wantResult:     enableMulticastForwardingForProtocolResult{true, nil},
		},
		{
			name:           "impl returns previously disabled",
			netProto:       fakeNetNumber,
			factory:        fakeNetFactory,
			delegateOutput: enableMulticastForwardingForProtocolResult{false, nil},
			wantResult:     enableMulticastForwardingForProtocolResult{false, nil},
		},
		{
			name:           "impl returns error",
			netProto:       fakeNetNumber,
			factory:        fakeNetFactory,
			delegateOutput: enableMulticastForwardingForProtocolResult{false, &tcpip.ErrUnknownDevice{}},
			wantResult:     enableMulticastForwardingForProtocolResult{false, &tcpip.ErrUnknownDevice{}},
		},
		{
			name:       "unknown protocol",
			factory:    fakeNetFactory,
			netProto:   arp.ProtocolNumber,
			wantResult: enableMulticastForwardingForProtocolResult{false, &tcpip.ErrUnknownProtocol{}},
		},
		{
			name:       "not supported",
			factory:    arp.NewProtocol,
			netProto:   arp.ProtocolNumber,
			wantResult: enableMulticastForwardingForProtocolResult{false, &tcpip.ErrNotSupported{}},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{test.factory},
			})

			if test.netProto == fakeNetNumber {
				fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
				fakeNet.enableMulticastForwardingForProtocolResult = test.delegateOutput
			}

			alreadyEnabled, err := s.EnableMulticastForwardingForProtocol(test.netProto, &fakeMulticastEventDispatcher{})

			if !cmp.Equal(enableMulticastForwardingForProtocolResult{alreadyEnabled, err}, test.wantResult, cmpopts.EquateErrors()) {
				t.Errorf("s.EnableMulticastForwardingForProtocol(%d, _) = (%t, %s), want = (%t, %s)", test.netProto, alreadyEnabled, err, test.wantResult.AlreadyEnabled, test.wantResult.Err)
			}
		})
	}
}

func TestDisableMulticastForwardingForProtocol(t *testing.T) {
	tests := []struct {
		name     string
		netProto tcpip.NetworkProtocolNumber
		factory  stack.NetworkProtocolFactory
		wantErr  tcpip.Error
	}{
		{
			name:     "valid",
			netProto: fakeNetNumber,
			factory:  fakeNetFactory,
			wantErr:  nil,
		},
		{
			name:     "unknown protocol",
			factory:  fakeNetFactory,
			netProto: arp.ProtocolNumber,
			wantErr:  &tcpip.ErrUnknownProtocol{},
		},
		{
			name:     "not supported",
			factory:  arp.NewProtocol,
			netProto: arp.ProtocolNumber,
			wantErr:  &tcpip.ErrNotSupported{},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{test.factory},
			})

			err := s.DisableMulticastForwardingForProtocol(test.netProto)

			if !cmp.Equal(err, test.wantErr, cmpopts.EquateErrors()) {
				t.Errorf("s.DisableMulticastForwardingForProtocol(%d) = %s, want = %s", test.netProto, err, test.wantErr)
			}

			if err == nil {
				fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
				if !fakeNet.disableMulticastForwardingForProtocolCalled {
					t.Errorf("fakeNet.disableMulticastForwardingForProtocolCalled = false, want = true")
				}
			}
		})
	}
}

func TestNICForwarding(t *testing.T) {
	const nicID = 1

	tests := []struct {
		name     string
		factory  stack.NetworkProtocolFactory
		netProto tcpip.NetworkProtocolNumber
	}{
		{
			name:     "Fake Network",
			factory:  fakeNetFactory,
			netProto: fakeNetNumber,
		},
		{
			name:     "IPv4",
			factory:  ipv4.NewProtocol,
			netProto: ipv4.ProtocolNumber,
		},
		{
			name:     "IPv6",
			factory:  ipv6.NewProtocol,
			netProto: ipv6.ProtocolNumber,
		},
	}

	subTests := []struct {
		name                     string
		getForwardingFunc        func(*stack.Stack, tcpip.NICID, tcpip.NetworkProtocolNumber) (bool, tcpip.Error)
		getForwardingFuncName    string
		setForwardingFunc        func(*stack.Stack, tcpip.NICID, tcpip.NetworkProtocolNumber, bool) (bool, tcpip.Error)
		setForwardingFuncName    string
		getNicInfoForwardingMap  func(stack.NICInfo) map[tcpip.NetworkProtocolNumber]bool
		nicInfoForwardingMapName string
	}{
		{
			name:                     "unicast",
			getForwardingFunc:        (*stack.Stack).NICForwarding,
			getForwardingFuncName:    "NICForwarding",
			setForwardingFunc:        (*stack.Stack).SetNICForwarding,
			setForwardingFuncName:    "SetNICForwarding",
			getNicInfoForwardingMap:  func(info stack.NICInfo) map[tcpip.NetworkProtocolNumber]bool { return info.Forwarding },
			nicInfoForwardingMapName: "Forwarding",
		},
		{
			name:                     "multicast",
			getForwardingFunc:        (*stack.Stack).NICMulticastForwarding,
			getForwardingFuncName:    "NICMulticastForwarding",
			setForwardingFunc:        (*stack.Stack).SetNICMulticastForwarding,
			setForwardingFuncName:    "SetNICMulticastForwarding",
			getNicInfoForwardingMap:  func(info stack.NICInfo) map[tcpip.NetworkProtocolNumber]bool { return info.MulticastForwarding },
			nicInfoForwardingMapName: "MulticastForwarding",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, subTest := range subTests {
				t.Run(subTest.name, func(t *testing.T) {
					s := stack.New(stack.Options{
						NetworkProtocols: []stack.NetworkProtocolFactory{test.factory},
					})
					if err := s.CreateNIC(nicID, channel.New(0, defaultMTU, "")); err != nil {
						t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
					}

					// Forwarding should initially be disabled.
					if forwarding, err := subTest.getForwardingFunc(s, nicID, test.netProto); err != nil {
						t.Fatalf("s.%s(%d, %d): %s", subTest.getForwardingFuncName, nicID, test.netProto, err)
					} else if forwarding {
						t.Errorf("got s.%s(%d, %d) = true, want = false", subTest.getForwardingFuncName, nicID, test.netProto)
					}

					// Setting forwarding to be enabled should return the previous
					// configuration of false. Enabling it a second time should be a
					// no-op.
					for _, wantPrevForwarding := range [...]bool{false, true} {
						if prevForwarding, err := subTest.setForwardingFunc(s, nicID, test.netProto, true); err != nil {
							t.Fatalf("s.%s(%d, %d, true): %s", subTest.setForwardingFuncName, nicID, test.netProto, err)
						} else if prevForwarding != wantPrevForwarding {
							t.Errorf("got s.%s(%d, %d, true) = %t, want = %t", subTest.setForwardingFuncName, nicID, test.netProto, prevForwarding, wantPrevForwarding)
						}
						if forwarding, err := subTest.getForwardingFunc(s, nicID, test.netProto); err != nil {
							t.Fatalf("s.%s(%d, %d): %s", subTest.getForwardingFuncName, nicID, test.netProto, err)
						} else if !forwarding {
							t.Errorf("got s.%s(%d, %d) = false, want = true", subTest.getForwardingFuncName, nicID, test.netProto)
						}
						// Verify that the NICInfo also contains the expected value.
						allNICInfo := s.NICInfo()
						if info, ok := allNICInfo[nicID]; !ok {
							t.Fatalf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo)
						} else {
							forwardingMap := subTest.getNicInfoForwardingMap(info)
							if forward, ok := forwardingMap[test.netProto]; !ok {
								t.Fatalf("entry for %d missing from info.%s = %+v", test.netProto, subTest.nicInfoForwardingMapName, forwardingMap)
							} else if !forward {
								t.Errorf("got info.%s[%d] = %t, want = true", subTest.nicInfoForwardingMapName, test.netProto, forward)
							}
						}
					}

					// Setting forwarding to be disabled should return the previous
					// configuration of true. Disabling it a second time should be a
					// no-op.
					for _, wantPrevForwarding := range [...]bool{true, false} {
						if prevForwarding, err := subTest.setForwardingFunc(s, nicID, test.netProto, false); err != nil {
							t.Fatalf("s.%s(%d, %d, false): %s", subTest.setForwardingFuncName, nicID, test.netProto, err)
						} else if prevForwarding != wantPrevForwarding {
							t.Errorf("got s.%s(%d, %d, false) = %t, want = %t", subTest.setForwardingFuncName, nicID, test.netProto, prevForwarding, wantPrevForwarding)
						}
						if forwarding, err := subTest.getForwardingFunc(s, nicID, test.netProto); err != nil {
							t.Fatalf("s.%s(%d, %d): %s", subTest.getForwardingFuncName, nicID, test.netProto, err)
						} else if forwarding {
							t.Errorf("got s.%s(%d, %d) = true, want = false", subTest.getForwardingFuncName, nicID, test.netProto)
						}
						// Verify that the NICInfo also contains the expected value.
						allNICInfo := s.NICInfo()
						if info, ok := allNICInfo[nicID]; !ok {
							t.Fatalf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo)
						} else {
							forwardingMap := subTest.getNicInfoForwardingMap(info)
							if forward, ok := forwardingMap[test.netProto]; !ok {
								t.Fatalf("entry for %d missing from info.%s = %+v", test.netProto, subTest.nicInfoForwardingMapName, forwardingMap)
							} else if forward {
								t.Errorf("got info.%s[%d] = %t, want = false", subTest.nicInfoForwardingMapName, test.netProto, forward)
							}
						}
					}

				})
			}
		})
	}
}

func TestWritePacketToRemote(t *testing.T) {
	const nicID = 1
	const MTU = 1280
	e := channel.New(1, MTU, linkAddr1)
	s := stack.New(stack.Options{})
	if err := s.CreateNIC(nicID, e); err != nil {
		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
	}
	if err := s.EnableNIC(nicID); err != nil {
		t.Fatalf("CreateNIC(%d) = %s", nicID, err)
	}
	tests := []struct {
		name     string
		protocol tcpip.NetworkProtocolNumber
		payload  []byte
	}{
		{
			name:     "SuccessIPv4",
			protocol: header.IPv4ProtocolNumber,
			payload:  []byte{1, 2, 3, 4},
		},
		{
			name:     "SuccessIPv6",
			protocol: header.IPv6ProtocolNumber,
			payload:  []byte{5, 6, 7, 8},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if err := s.WritePacketToRemote(nicID, linkAddr2, test.protocol, buffer.MakeWithData(test.payload)); err != nil {
				t.Fatalf("s.WritePacketToRemote(_, _, _, _) = %s", err)
			}

			pkt := e.Read()
			if got, want := pkt != nil, true; got != want {
				t.Fatalf("e.Read() = %t, want %t", got, want)
			}
			defer pkt.DecRef()
			if got, want := pkt.NetworkProtocolNumber, test.protocol; got != want {
				t.Fatalf("pkt.NetworkProtocolNumber = %d, want %d", got, want)
			}
			if pkt.EgressRoute.RemoteLinkAddress != linkAddr2 {
				t.Fatalf("pkt.EgressRoute.RemoteAddress = %s, want %s", pkt.EgressRoute.RemoteLinkAddress, linkAddr2)
			}
			if diff := cmp.Diff(pkt.Data().AsRange().ToSlice(), test.payload); diff != "" {
				t.Errorf("pkt.Data mismatch (-want +got):\n%s", diff)
			}
		})
	}

	t.Run("InvalidNICID", func(t *testing.T) {
		err := s.WritePacketToRemote(234, linkAddr2, header.IPv4ProtocolNumber, buffer.MakeWithData([]byte{1}))
		if _, ok := err.(*tcpip.ErrUnknownDevice); !ok {
			t.Fatalf("s.WritePacketToRemote(_, _, _, _) = %s, want = %s", err, &tcpip.ErrUnknownDevice{})
		}
		pkt := e.Read()
		if got, want := pkt != nil, false; got != want {
			t.Fatalf("e.Read() = %t, %v; want %t", got, pkt, want)
		}
	})
}

func TestClearNeighborCacheOnNICDisable(t *testing.T) {
	const (
		nicID    = 1
		linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
	)

	var (
		ipv4Addr = testutil.MustParse4("1.2.3.4")
		ipv6Addr = testutil.MustParse6("102:304:102:304:102:304:102:304")
	)

	clock := faketime.NewManualClock()
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
		Clock:            clock,
	})
	e := channel.New(0, 0, "")
	e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
	if err := s.CreateNIC(nicID, e); err != nil {
		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
	}

	addrs := []struct {
		proto tcpip.NetworkProtocolNumber
		addr  tcpip.Address
	}{
		{
			proto: ipv4.ProtocolNumber,
			addr:  ipv4Addr,
		},
		{
			proto: ipv6.ProtocolNumber,
			addr:  ipv6Addr,
		},
	}
	for _, addr := range addrs {
		if err := s.AddStaticNeighbor(nicID, addr.proto, addr.addr, linkAddr); err != nil {
			t.Fatalf("s.AddStaticNeighbor(%d, %d, %s, %s): %s", nicID, addr.proto, addr.addr, linkAddr, err)
		}

		if neighbors, err := s.Neighbors(nicID, addr.proto); err != nil {
			t.Fatalf("s.Neighbors(%d, %d): %s", nicID, addr.proto, err)
		} else if diff := cmp.Diff(
			[]stack.NeighborEntry{{Addr: addr.addr, LinkAddr: linkAddr, State: stack.Static, UpdatedAt: clock.NowMonotonic()}},
			neighbors,
			cmp.AllowUnexported(tcpip.MonotonicTime{}),
		); diff != "" {
			t.Fatalf("proto=%d neighbors mismatch (-want +got):\n%s", addr.proto, diff)
		}
	}

	// Disabling the NIC should clear the neighbor table.
	if err := s.DisableNIC(nicID); err != nil {
		t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
	}
	for _, addr := range addrs {
		if neighbors, err := s.Neighbors(nicID, addr.proto); err != nil {
			t.Fatalf("s.Neighbors(%d, %d): %s", nicID, addr.proto, err)
		} else if len(neighbors) != 0 {
			t.Fatalf("got proto=%d len(neighbors) = %d, want = 0; neighbors = %#v", addr.proto, len(neighbors), neighbors)
		}
	}

	// Enabling the NIC should have an empty neighbor table.
	if err := s.EnableNIC(nicID); err != nil {
		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
	}
	for _, addr := range addrs {
		if neighbors, err := s.Neighbors(nicID, addr.proto); err != nil {
			t.Fatalf("s.Neighbors(%d, %d): %s", nicID, addr.proto, err)
		} else if len(neighbors) != 0 {
			t.Fatalf("got proto=%d len(neighbors) = %d, want = 0; neighbors = %#v", addr.proto, len(neighbors), neighbors)
		}
	}
}

func TestGetLinkAddressErrors(t *testing.T) {
	const (
		nicID        = 1
		unknownNICID = nicID + 1
	)

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
	})
	if err := s.CreateNIC(nicID, channel.New(0, 0, "")); err != nil {
		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
	}

	{
		err := s.GetLinkAddress(unknownNICID, tcpip.Address{}, tcpip.Address{}, ipv4.ProtocolNumber, nil)
		if _, ok := err.(*tcpip.ErrUnknownNICID); !ok {
			t.Errorf("got s.GetLinkAddress(%d, '', '', %d, nil) = %s, want = %s", unknownNICID, ipv4.ProtocolNumber, err, &tcpip.ErrUnknownNICID{})
		}
	}
	{
		err := s.GetLinkAddress(nicID, tcpip.Address{}, tcpip.Address{}, ipv4.ProtocolNumber, nil)
		if _, ok := err.(*tcpip.ErrNotSupported); !ok {
			t.Errorf("got s.GetLinkAddress(%d, '', '', %d, nil) = %s, want = %s", unknownNICID, ipv4.ProtocolNumber, err, &tcpip.ErrNotSupported{})
		}
	}
}

func TestStaticGetLinkAddress(t *testing.T) {
	const (
		nicID = 1
	)

	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
	})
	e := channel.New(0, 0, "")
	e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
	if err := s.CreateNIC(nicID, e); err != nil {
		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
	}

	tests := []struct {
		name             string
		proto            tcpip.NetworkProtocolNumber
		addr             tcpip.Address
		expectedLinkAddr tcpip.LinkAddress
	}{
		{
			name:             "IPv4",
			proto:            ipv4.ProtocolNumber,
			addr:             header.IPv4Broadcast,
			expectedLinkAddr: header.EthernetBroadcastAddress,
		},
		{
			name:             "IPv6",
			proto:            ipv6.ProtocolNumber,
			addr:             header.IPv6AllNodesMulticastAddress,
			expectedLinkAddr: header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllNodesMulticastAddress),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ch := make(chan stack.LinkResolutionResult, 1)
			if err := s.GetLinkAddress(nicID, test.addr, tcpip.Address{}, test.proto, func(r stack.LinkResolutionResult) {
				ch <- r
			}); err != nil {
				t.Fatalf("s.GetLinkAddress(%d, %s, '', %d, _): %s", nicID, test.addr, test.proto, err)
			}

			if diff := cmp.Diff(stack.LinkResolutionResult{LinkAddress: test.expectedLinkAddr, Err: nil}, <-ch); diff != "" {
				t.Fatalf("link resolution result mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

// TODO(b/221146133): Test with:
//   - Multiple NICs
//   - Gateway first route tables
//   - IPv6
//   - Set local address
//   - Set NIC
func TestFindRoute(t *testing.T) {
	// Just use a consistent prefix length throughout tests for simplicity.
	const prefixLen = 24

	type nic struct {
		id        tcpip.NICID
		addresses []string
	}
	type route struct {
		gateway string
		subnet  string
		nic     tcpip.NICID
		srcHint string
	}
	type query struct {
		name        string
		remote      string
		wantID      tcpip.NICID
		wantLocal   string
		wantNextHop string
		wantErr     bool
	}
	stacks := []struct {
		name    string
		nics    []nic
		routes  []route
		queries []query
	}{
		{
			name: "one NIC, multiple addresses, partially overlapping addresses",
			nics: []nic{{id: 1, addresses: []string{"169.254.9.1", "169.254.169.1"}}},
			routes: []route{
				{gateway: "1.1.1.1", subnet: "0.0.0.0/0", nic: 1},
				{gateway: "1.1.1.1", subnet: "169.254.169.0/25", nic: 1},
			},
			queries: []query{
				{
					name:        "match default only",
					remote:      "2.2.2.2",
					wantID:      1,
					wantLocal:   "169.254.9.1",
					wantNextHop: "1.1.1.1",
				},
				{
					name:        "match both, but prefer non-default",
					remote:      "169.254.169.2",
					wantID:      1,
					wantLocal:   "169.254.169.1",
					wantNextHop: "1.1.1.1",
				},
			},
		},
		{
			name: "one NIC, multiple addresses, addresses swapped",
			nics: []nic{{id: 1, addresses: []string{"192.168.2.1", "192.168.1.1"}}},
			routes: []route{
				{gateway: "192.168.2.22", subnet: "192.168.2.0/24", nic: 1},
				{gateway: "192.168.1.11", subnet: "0.0.0.0/0", nic: 1},
			},
			queries: []query{
				{
					name:        "match default only",
					remote:      "1.1.1.1",
					wantID:      1,
					wantLocal:   "192.168.2.1",
					wantNextHop: "192.168.1.11",
				},
				{
					name:        "match both, but prefer non-default",
					remote:      "192.168.2.2",
					wantID:      1,
					wantLocal:   "192.168.2.1",
					wantNextHop: "192.168.2.22",
				},
			},
		},
		{
			name: "one NIC, multiple addresses, gateway last",
			nics: []nic{{id: 1, addresses: []string{"192.168.1.1", "192.168.2.1"}}},
			routes: []route{
				{gateway: "192.168.2.22", subnet: "192.168.2.0/24", nic: 1},
				{gateway: "192.168.1.11", subnet: "0.0.0.0/0", nic: 1},
			},
			queries: []query{
				{
					name:        "match default only",
					remote:      "1.1.1.1",
					wantID:      1,
					wantLocal:   "192.168.1.1",
					wantNextHop: "192.168.1.11",
				},
				{
					name:        "match both, but prefer non-default",
					remote:      "192.168.2.2",
					wantID:      1,
					wantLocal:   "192.168.2.1",
					wantNextHop: "192.168.2.22",
				},
			},
		},
		{
			name: "one NIC, multiple addresses, no default gateway",
			nics: []nic{{id: 1, addresses: []string{"192.168.1.1", "192.168.2.1"}}},
			routes: []route{
				{gateway: "192.168.2.22", subnet: "192.168.2.0/24", nic: 1},
				{gateway: "192.168.1.11", subnet: "192.168.1.0/24", nic: 1},
			},
			queries: []query{
				{
					name:        "match single A",
					remote:      "192.168.1.2",
					wantID:      1,
					wantLocal:   "192.168.1.1",
					wantNextHop: "192.168.1.11",
				},
				{
					name:        "match single B",
					remote:      "192.168.2.2",
					wantID:      1,
					wantLocal:   "192.168.2.1",
					wantNextHop: "192.168.2.22",
				},
				{
					name:    "match none",
					remote:  "3.3.3.3",
					wantErr: true,
				},
			},
		},
		{
			name: "one NIC, multiple addresses, no gateways",
			nics: []nic{{id: 1, addresses: []string{"169.254.169.1", "169.254.9.1"}}},
			routes: []route{
				{subnet: "10.0.0.0/8", nic: 1},
			},
			queries: []query{
				{
					name:        "match first",
					remote:      "10.132.0.4",
					wantID:      1,
					wantLocal:   "169.254.169.1",
					wantNextHop: "", // No gateway, so no next hop.
				},
			},
		},
		{
			name: "one NIC, multiple addresses, source hints, no gateways",
			nics: []nic{{id: 1, addresses: []string{"169.254.169.1", "169.254.9.1", "10.132.1.1"}}},
			routes: []route{
				{subnet: "10.0.0.0/8", nic: 1, srcHint: "169.254.9.1"},
			},
			queries: []query{
				{
					name:        "match hint",
					remote:      "10.132.0.4",
					wantID:      1,
					wantLocal:   "169.254.9.1",
					wantNextHop: "", // No gateway, so no next hop.
				},
			},
		},
	}

	for _, stackConfig := range stacks {
		t.Run(stackConfig.name, func(t *testing.T) {
			// Create the stack. The channel endpoint is unused, but necessary for creation.
			ep := channel.New(1, defaultMTU, "")
			stk := stack.New(stack.Options{
				NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol /*, arp.NewProtocol*/},
			})

			// Create NICs and assign addresses to them.
			for _, nic := range stackConfig.nics {
				if err := stk.CreateNIC(nic.id, ep); err != nil {
					t.Fatal("NewNIC failed:", err)
				}
				for _, addr := range nic.addresses {
					protocolAddr := tcpip.ProtocolAddress{
						Protocol: header.IPv4ProtocolNumber,
						AddressWithPrefix: tcpip.AddressWithPrefix{
							Address:   testutil.MustParse4(addr),
							PrefixLen: prefixLen,
						},
					}
					if err := stk.AddProtocolAddress(nic.id, protocolAddr, stack.AddressProperties{}); err != nil {
						t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr, err)
					}
				}
			}

			// Setup the route table.
			var routeTable []tcpip.Route
			for _, route := range stackConfig.routes {
				rt := tcpip.Route{
					Destination: testutil.MustParseSubnet4(route.subnet),
					NIC:         route.nic,
				}
				if len(route.gateway) > 0 {
					rt.Gateway = testutil.MustParse4(route.gateway)
				}
				if len(route.srcHint) > 0 {
					rt.SourceHint = testutil.MustParse4(route.srcHint)
				}
				routeTable = append(routeTable, rt)
			}
			stk.SetRouteTable(routeTable)

			for _, query := range stackConfig.queries {
				t.Run(query.name, func(t *testing.T) {
					route, err := stk.FindRoute(
						0,
						tcpip.Address{},
						testutil.MustParse4(query.remote),
						header.IPv4ProtocolNumber,
						false, /* multicastLoop */
					)
					if err != nil {
						if _, ok := err.(*tcpip.ErrNetworkUnreachable); query.wantErr && ok {
							return
						}
						t.Fatalf("FoundRoute failed: %v", err)
					}
					if got, want := route.OutgoingNIC(), query.wantID; got != want {
						t.Errorf("got outgoing NIC %d, but wanted %d", got, want)
					}
					if got, want := route.LocalAddress(), testutil.MustParse4(query.wantLocal); got != want {
						t.Errorf("got local address %s, but wanted %s", got, want)
					}
					var nextHop tcpip.Address
					if len(query.wantNextHop) > 0 {
						nextHop = testutil.MustParse4(query.wantNextHop)
					}
					if got, want := route.NextHop(), nextHop; got != want {
						t.Errorf("got next hop %s, but wanted %s", got, want)
					}
				})
			}
		})
	}
}
