summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/stack
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/tcpip/stack')
-rw-r--r--pkg/tcpip/stack/BUILD167
-rw-r--r--pkg/tcpip/stack/addressable_endpoint_state_test.go61
-rw-r--r--pkg/tcpip/stack/conntrack_test.go132
-rw-r--r--pkg/tcpip/stack/forwarding_test.go804
-rw-r--r--pkg/tcpip/stack/iptables_test.go220
-rw-r--r--pkg/tcpip/stack/ndp_test.go5614
-rw-r--r--pkg/tcpip/stack/neighbor_cache_test.go1584
-rw-r--r--pkg/tcpip/stack/neighbor_entry_list.go221
-rw-r--r--pkg/tcpip/stack/neighbor_entry_test.go2269
-rw-r--r--pkg/tcpip/stack/nic_test.go261
-rw-r--r--pkg/tcpip/stack/nud_test.go816
-rw-r--r--pkg/tcpip/stack/packet_buffer_list.go221
-rw-r--r--pkg/tcpip/stack/packet_buffer_refs.go140
-rw-r--r--pkg/tcpip/stack/packet_buffer_test.go655
-rw-r--r--pkg/tcpip/stack/stack_state_autogen.go1299
-rw-r--r--pkg/tcpip/stack/stack_test.go4680
-rw-r--r--pkg/tcpip/stack/stack_unsafe_state_autogen.go3
-rw-r--r--pkg/tcpip/stack/transport_demuxer_test.go454
-rw-r--r--pkg/tcpip/stack/transport_test.go579
-rw-r--r--pkg/tcpip/stack/tuple_list.go221
20 files changed, 2105 insertions, 18296 deletions
diff --git a/pkg/tcpip/stack/BUILD b/pkg/tcpip/stack/BUILD
deleted file mode 100644
index 81eed5b11..000000000
--- a/pkg/tcpip/stack/BUILD
+++ /dev/null
@@ -1,167 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test", "most_shards")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-package(licenses = ["notice"])
-
-go_template_instance(
- name = "neighbor_entry_list",
- out = "neighbor_entry_list.go",
- package = "stack",
- prefix = "neighborEntry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*neighborEntry",
- "Linker": "*neighborEntry",
- },
-)
-
-go_template_instance(
- name = "packet_buffer_list",
- out = "packet_buffer_list.go",
- package = "stack",
- prefix = "PacketBuffer",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*PacketBuffer",
- "Linker": "*PacketBuffer",
- },
-)
-
-go_template_instance(
- name = "tuple_list",
- out = "tuple_list.go",
- package = "stack",
- prefix = "tuple",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*tuple",
- "Linker": "*tuple",
- },
-)
-
-go_template_instance(
- name = "packet_buffer_refs",
- out = "packet_buffer_refs.go",
- package = "stack",
- prefix = "packetBuffer",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "PacketBuffer",
- },
-)
-
-go_library(
- name = "stack",
- srcs = [
- "addressable_endpoint_state.go",
- "conntrack.go",
- "headertype_string.go",
- "hook_string.go",
- "icmp_rate_limit.go",
- "iptables.go",
- "iptables_targets.go",
- "iptables_types.go",
- "neighbor_cache.go",
- "neighbor_entry.go",
- "neighbor_entry_list.go",
- "neighborstate_string.go",
- "nic.go",
- "nic_stats.go",
- "nud.go",
- "packet_buffer.go",
- "packet_buffer_list.go",
- "packet_buffer_refs.go",
- "packet_buffer_unsafe.go",
- "pending_packets.go",
- "rand.go",
- "registration.go",
- "route.go",
- "stack.go",
- "stack_global_state.go",
- "stack_options.go",
- "tcp.go",
- "transport_demuxer.go",
- "tuple_list.go",
- ],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/atomicbitops",
- "//pkg/buffer",
- "//pkg/ilist",
- "//pkg/log",
- "//pkg/rand",
- "//pkg/refsvfs2",
- "//pkg/sleep",
- "//pkg/sync",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/hash/jenkins",
- "//pkg/tcpip/header",
- "//pkg/tcpip/internal/tcp",
- "//pkg/tcpip/ports",
- "//pkg/tcpip/seqnum",
- "//pkg/tcpip/transport/tcpconntrack",
- "//pkg/waiter",
- "@org_golang_x_time//rate:go_default_library",
- ],
-)
-
-go_test(
- name = "stack_x_test",
- size = "small",
- srcs = [
- "addressable_endpoint_state_test.go",
- "ndp_test.go",
- "nud_test.go",
- "stack_test.go",
- "transport_demuxer_test.go",
- "transport_test.go",
- ],
- shard_count = most_shards,
- deps = [
- ":stack",
- "//pkg/rand",
- "//pkg/sync",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/checker",
- "//pkg/tcpip/faketime",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/loopback",
- "//pkg/tcpip/network/arp",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/ports",
- "//pkg/tcpip/testutil",
- "//pkg/tcpip/transport/icmp",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- "@com_github_google_go_cmp//cmp:go_default_library",
- ],
-)
-
-go_test(
- name = "stack_test",
- size = "small",
- srcs = [
- "conntrack_test.go",
- "forwarding_test.go",
- "iptables_test.go",
- "neighbor_cache_test.go",
- "neighbor_entry_test.go",
- "nic_test.go",
- "packet_buffer_test.go",
- ],
- library = ":stack",
- deps = [
- "//pkg/sync",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/faketime",
- "//pkg/tcpip/header",
- "//pkg/tcpip/testutil",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@com_github_google_go_cmp//cmp/cmpopts:go_default_library",
- ],
-)
diff --git a/pkg/tcpip/stack/addressable_endpoint_state_test.go b/pkg/tcpip/stack/addressable_endpoint_state_test.go
deleted file mode 100644
index c55f85743..000000000
--- a/pkg/tcpip/stack/addressable_endpoint_state_test.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-// TestAddressableEndpointStateCleanup tests that cleaning up an addressable
-// endpoint state removes permanent addresses and leaves groups.
-func TestAddressableEndpointStateCleanup(t *testing.T) {
- var ep fakeNetworkEndpoint
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- var s stack.AddressableEndpointState
- s.Init(&ep)
-
- addr := tcpip.AddressWithPrefix{
- Address: "\x01",
- PrefixLen: 8,
- }
-
- {
- ep, err := s.AddAndAcquirePermanentAddress(addr, stack.AddressProperties{PEB: stack.NeverPrimaryEndpoint})
- if err != nil {
- t.Fatalf("s.AddAndAcquirePermanentAddress(%s, AddressProperties{PEB: NeverPrimaryEndpoint}): %s", addr, err)
- }
- // We don't need the address endpoint.
- ep.DecRef()
- }
- {
- ep := s.AcquireAssignedAddress(addr.Address, false /* allowTemp */, stack.NeverPrimaryEndpoint)
- if ep == nil {
- t.Fatalf("got s.AcquireAssignedAddress(%s, false, NeverPrimaryEndpoint) = nil, want = non-nil", addr.Address)
- }
- ep.DecRef()
- }
-
- s.Cleanup()
- if ep := s.AcquireAssignedAddress(addr.Address, false /* allowTemp */, stack.NeverPrimaryEndpoint); ep != nil {
- ep.DecRef()
- t.Fatalf("got s.AcquireAssignedAddress(%s, false, NeverPrimaryEndpoint) = %s, want = nil", addr.Address, ep.AddressWithPrefix())
- }
-}
diff --git a/pkg/tcpip/stack/conntrack_test.go b/pkg/tcpip/stack/conntrack_test.go
deleted file mode 100644
index fb0645ed1..000000000
--- a/pkg/tcpip/stack/conntrack_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2021 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/faketime"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/testutil"
-)
-
-func TestReap(t *testing.T) {
- // Initialize conntrack.
- clock := faketime.NewManualClock()
- ct := ConnTrack{
- clock: clock,
- }
- ct.init()
- ct.checkNumTuples(t, 0)
-
- // Simulate sending a SYN. This will get the connection into conntrack, but
- // the connection won't be considered established. Thus the timeout for
- // reaping is unestablishedTimeout.
- pkt1 := genTCPPacket()
- pkt1.tuple = ct.getConnOrMaybeInsertNoop(pkt1)
- // We set rt.routeInfo.Loop to avoid a panic when handlePacket calls
- // rt.RequiresTXTransportChecksum.
- var rt Route
- rt.routeInfo.Loop = PacketLoop
- if pkt1.tuple.conn.handlePacket(pkt1, Output, &rt) {
- t.Fatal("handlePacket() shouldn't perform any NAT")
- }
- ct.checkNumTuples(t, 1)
-
- // Travel a little into the future and send the same SYN. This should update
- // lastUsed, but per #6748 didn't.
- clock.Advance(unestablishedTimeout / 2)
- pkt2 := genTCPPacket()
- pkt2.tuple = ct.getConnOrMaybeInsertNoop(pkt2)
- if pkt2.tuple.conn.handlePacket(pkt2, Output, &rt) {
- t.Fatal("handlePacket() shouldn't perform any NAT")
- }
- ct.checkNumTuples(t, 1)
-
- // Travel farther into the future - enough that failing to update lastUsed
- // would cause a reaping - and reap the whole table. Make sure the connection
- // hasn't been reaped.
- clock.Advance(unestablishedTimeout * 3 / 4)
- ct.reapEverything()
- ct.checkNumTuples(t, 1)
-
- // Travel past unestablishedTimeout to confirm the tuple is gone.
- clock.Advance(unestablishedTimeout / 2)
- ct.reapEverything()
- ct.checkNumTuples(t, 0)
-}
-
-// genTCPPacket returns an initialized IPv4 TCP packet.
-func genTCPPacket() *PacketBuffer {
- const packetLen = header.IPv4MinimumSize + header.TCPMinimumSize
- pkt := NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: packetLen,
- })
- pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber
- pkt.TransportProtocolNumber = header.TCPProtocolNumber
- tcpHdr := header.TCP(pkt.TransportHeader().Push(header.TCPMinimumSize))
- tcpHdr.Encode(&header.TCPFields{
- SrcPort: 5555,
- DstPort: 6666,
- SeqNum: 7777,
- AckNum: 8888,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 50000,
- Checksum: 0, // Conntrack doesn't verify the checksum.
- })
- ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
- ipHdr.Encode(&header.IPv4Fields{
- TotalLength: packetLen,
- Protocol: uint8(header.TCPProtocolNumber),
- SrcAddr: testutil.MustParse4("1.0.0.1"),
- DstAddr: testutil.MustParse4("1.0.0.2"),
- Checksum: 0, // Conntrack doesn't verify the checksum.
- })
-
- return pkt
-}
-
-// checkNumTuples checks that there are exactly want tuples tracked by
-// conntrack.
-func (ct *ConnTrack) checkNumTuples(t *testing.T, want int) {
- t.Helper()
- ct.mu.RLock()
- defer ct.mu.RUnlock()
-
- var total int
- for idx := range ct.buckets {
- ct.buckets[idx].mu.RLock()
- total += ct.buckets[idx].tuples.Len()
- ct.buckets[idx].mu.RUnlock()
- }
-
- if total != want {
- t.Fatalf("checkNumTuples: got %d, wanted %d", total, want)
- }
-}
-
-func (ct *ConnTrack) reapEverything() {
- var bucket int
- for {
- newBucket, _ := ct.reapUnused(bucket, 0 /* ignored */)
- // We started reaping at bucket 0. If the next bucket isn't after our
- // current bucket, we've gone through them all.
- if newBucket <= bucket {
- break
- }
- bucket = newBucket
- }
-}
diff --git a/pkg/tcpip/stack/forwarding_test.go b/pkg/tcpip/stack/forwarding_test.go
deleted file mode 100644
index c2f1f4798..000000000
--- a/pkg/tcpip/stack/forwarding_test.go
+++ /dev/null
@@ -1,804 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack
-
-import (
- "encoding/binary"
- "math"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/sync"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/faketime"
- "gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-const (
- fwdTestNetNumber tcpip.NetworkProtocolNumber = math.MaxUint32
- fwdTestNetHeaderLen = 12
- fwdTestNetDefaultPrefixLen = 8
-
- // fwdTestNetDefaultMTU 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.
- fwdTestNetDefaultMTU = 65536
-
- dstAddrOffset = 0
- srcAddrOffset = 1
- protocolNumberOffset = 2
-)
-
-var _ LinkAddressResolver = (*fwdTestNetworkEndpoint)(nil)
-var _ NetworkEndpoint = (*fwdTestNetworkEndpoint)(nil)
-
-// fwdTestNetworkEndpoint is a network-layer protocol endpoint.
-// Headers of this protocol are fwdTestNetHeaderLen bytes, but we currently only
-// use the first three: destination address, source address, and transport
-// protocol. They're all one byte fields to simplify parsing.
-type fwdTestNetworkEndpoint struct {
- AddressableEndpointState
-
- nic NetworkInterface
- proto *fwdTestNetworkProtocol
- dispatcher TransportDispatcher
-
- mu struct {
- sync.RWMutex
- forwarding bool
- }
-}
-
-func (*fwdTestNetworkEndpoint) Enable() tcpip.Error {
- return nil
-}
-
-func (*fwdTestNetworkEndpoint) Enabled() bool {
- return true
-}
-
-func (*fwdTestNetworkEndpoint) Disable() {}
-
-func (f *fwdTestNetworkEndpoint) MTU() uint32 {
- return f.nic.MTU() - uint32(f.MaxHeaderLength())
-}
-
-func (*fwdTestNetworkEndpoint) DefaultTTL() uint8 {
- return 123
-}
-
-func (f *fwdTestNetworkEndpoint) HandlePacket(pkt *PacketBuffer) {
- if _, _, ok := f.proto.Parse(pkt); !ok {
- return
- }
-
- netHdr := pkt.NetworkHeader().View()
- _, dst := f.proto.ParseAddresses(netHdr)
-
- addressEndpoint := f.AcquireAssignedAddress(dst, f.nic.Promiscuous(), CanBePrimaryEndpoint)
- if addressEndpoint != nil {
- addressEndpoint.DecRef()
- // Dispatch the packet to the transport protocol.
- f.dispatcher.DeliverTransportPacket(tcpip.TransportProtocolNumber(netHdr[protocolNumberOffset]), pkt)
- return
- }
-
- r, err := f.proto.stack.FindRoute(0, "", dst, fwdTestNetNumber, false /* multicastLoop */)
- if err != nil {
- return
- }
- defer r.Release()
-
- vv := buffer.NewVectorisedView(pkt.Size(), pkt.Views())
- pkt = NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: int(r.MaxHeaderLength()),
- Data: vv.ToView().ToVectorisedView(),
- })
- // TODO(gvisor.dev/issue/1085) Decrease the TTL field in forwarded packets.
- _ = r.WriteHeaderIncludedPacket(pkt)
-}
-
-func (f *fwdTestNetworkEndpoint) MaxHeaderLength() uint16 {
- return f.nic.MaxHeaderLength() + fwdTestNetHeaderLen
-}
-
-func (f *fwdTestNetworkEndpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber {
- return f.proto.Number()
-}
-
-func (f *fwdTestNetworkEndpoint) WritePacket(r *Route, params NetworkHeaderParams, pkt *PacketBuffer) tcpip.Error {
- // Add the protocol's header to the packet and send it to the link
- // endpoint.
- b := pkt.NetworkHeader().Push(fwdTestNetHeaderLen)
- b[dstAddrOffset] = r.RemoteAddress()[0]
- b[srcAddrOffset] = r.LocalAddress()[0]
- b[protocolNumberOffset] = byte(params.Protocol)
-
- return f.nic.WritePacket(r, fwdTestNetNumber, pkt)
-}
-
-// WritePackets implements LinkEndpoint.WritePackets.
-func (*fwdTestNetworkEndpoint) WritePackets(*Route, PacketBufferList, NetworkHeaderParams) (int, tcpip.Error) {
- panic("not implemented")
-}
-
-func (f *fwdTestNetworkEndpoint) WriteHeaderIncludedPacket(r *Route, pkt *PacketBuffer) tcpip.Error {
- // The network header should not already be populated.
- if _, ok := pkt.NetworkHeader().Consume(fwdTestNetHeaderLen); !ok {
- return &tcpip.ErrMalformedHeader{}
- }
-
- return f.nic.WritePacket(r, fwdTestNetNumber, pkt)
-}
-
-func (f *fwdTestNetworkEndpoint) Close() {
- f.AddressableEndpointState.Cleanup()
-}
-
-// Stats implements stack.NetworkEndpoint.
-func (*fwdTestNetworkEndpoint) Stats() NetworkEndpointStats {
- return &fwdTestNetworkEndpointStats{}
-}
-
-var _ NetworkEndpointStats = (*fwdTestNetworkEndpointStats)(nil)
-
-type fwdTestNetworkEndpointStats struct{}
-
-// IsNetworkEndpointStats implements stack.NetworkEndpointStats.
-func (*fwdTestNetworkEndpointStats) IsNetworkEndpointStats() {}
-
-var _ NetworkProtocol = (*fwdTestNetworkProtocol)(nil)
-
-// fwdTestNetworkProtocol is a network-layer protocol that implements Address
-// resolution.
-type fwdTestNetworkProtocol struct {
- stack *Stack
-
- neigh *neighborCache
- addrResolveDelay time.Duration
- onLinkAddressResolved func(*neighborCache, tcpip.Address, tcpip.LinkAddress)
- onResolveStaticAddress func(tcpip.Address) (tcpip.LinkAddress, bool)
-}
-
-func (*fwdTestNetworkProtocol) Number() tcpip.NetworkProtocolNumber {
- return fwdTestNetNumber
-}
-
-func (*fwdTestNetworkProtocol) MinimumPacketSize() int {
- return fwdTestNetHeaderLen
-}
-
-func (*fwdTestNetworkProtocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) {
- return tcpip.Address(v[srcAddrOffset : srcAddrOffset+1]), tcpip.Address(v[dstAddrOffset : dstAddrOffset+1])
-}
-
-func (*fwdTestNetworkProtocol) Parse(pkt *PacketBuffer) (tcpip.TransportProtocolNumber, bool, bool) {
- netHeader, ok := pkt.NetworkHeader().Consume(fwdTestNetHeaderLen)
- if !ok {
- return 0, false, false
- }
- return tcpip.TransportProtocolNumber(netHeader[protocolNumberOffset]), true, true
-}
-
-func (f *fwdTestNetworkProtocol) NewEndpoint(nic NetworkInterface, dispatcher TransportDispatcher) NetworkEndpoint {
- e := &fwdTestNetworkEndpoint{
- nic: nic,
- proto: f,
- dispatcher: dispatcher,
- }
- e.AddressableEndpointState.Init(e)
- return e
-}
-
-func (*fwdTestNetworkProtocol) SetOption(tcpip.SettableNetworkProtocolOption) tcpip.Error {
- return &tcpip.ErrUnknownProtocolOption{}
-}
-
-func (*fwdTestNetworkProtocol) Option(tcpip.GettableNetworkProtocolOption) tcpip.Error {
- return &tcpip.ErrUnknownProtocolOption{}
-}
-
-func (*fwdTestNetworkProtocol) Close() {}
-
-func (*fwdTestNetworkProtocol) Wait() {}
-
-func (f *fwdTestNetworkEndpoint) LinkAddressRequest(addr, _ tcpip.Address, remoteLinkAddr tcpip.LinkAddress) tcpip.Error {
- if fn := f.proto.onLinkAddressResolved; fn != nil {
- f.proto.stack.clock.AfterFunc(f.proto.addrResolveDelay, func() {
- fn(f.proto.neigh, addr, remoteLinkAddr)
- })
- }
- return nil
-}
-
-func (f *fwdTestNetworkEndpoint) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) {
- if fn := f.proto.onResolveStaticAddress; fn != nil {
- return fn(addr)
- }
- return "", false
-}
-
-func (*fwdTestNetworkEndpoint) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
- return fwdTestNetNumber
-}
-
-// Forwarding implements stack.ForwardingNetworkEndpoint.
-func (f *fwdTestNetworkEndpoint) Forwarding() bool {
- f.mu.RLock()
- defer f.mu.RUnlock()
- return f.mu.forwarding
-
-}
-
-// SetForwarding implements stack.ForwardingNetworkEndpoint.
-func (f *fwdTestNetworkEndpoint) SetForwarding(v bool) {
- f.mu.Lock()
- defer f.mu.Unlock()
- f.mu.forwarding = v
-}
-
-// fwdTestPacketInfo holds all the information about an outbound packet.
-type fwdTestPacketInfo struct {
- RemoteLinkAddress tcpip.LinkAddress
- LocalLinkAddress tcpip.LinkAddress
- Pkt *PacketBuffer
-}
-
-var _ LinkEndpoint = (*fwdTestLinkEndpoint)(nil)
-
-type fwdTestLinkEndpoint struct {
- dispatcher NetworkDispatcher
- mtu uint32
- linkAddr tcpip.LinkAddress
-
- // C is where outbound packets are queued.
- C chan fwdTestPacketInfo
-}
-
-// InjectInbound injects an inbound packet.
-func (e *fwdTestLinkEndpoint) InjectInbound(protocol tcpip.NetworkProtocolNumber, pkt *PacketBuffer) {
- e.InjectLinkAddr(protocol, "", pkt)
-}
-
-// InjectLinkAddr injects an inbound packet with a remote link address.
-func (e *fwdTestLinkEndpoint) InjectLinkAddr(protocol tcpip.NetworkProtocolNumber, remote tcpip.LinkAddress, pkt *PacketBuffer) {
- e.dispatcher.DeliverNetworkPacket(remote, "" /* local */, protocol, pkt)
-}
-
-// Attach saves the stack network-layer dispatcher for use later when packets
-// are injected.
-func (e *fwdTestLinkEndpoint) Attach(dispatcher NetworkDispatcher) {
- e.dispatcher = dispatcher
-}
-
-// IsAttached implements stack.LinkEndpoint.IsAttached.
-func (e *fwdTestLinkEndpoint) IsAttached() bool {
- return e.dispatcher != nil
-}
-
-// MTU implements stack.LinkEndpoint.MTU. It returns the value initialized
-// during construction.
-func (e *fwdTestLinkEndpoint) MTU() uint32 {
- return e.mtu
-}
-
-// Capabilities implements stack.LinkEndpoint.Capabilities.
-func (e fwdTestLinkEndpoint) Capabilities() LinkEndpointCapabilities {
- caps := LinkEndpointCapabilities(0)
- return caps | CapabilityResolutionRequired
-}
-
-// MaxHeaderLength returns the maximum size of the link layer header. Given it
-// doesn't have a header, it just returns 0.
-func (*fwdTestLinkEndpoint) MaxHeaderLength() uint16 {
- return 0
-}
-
-// LinkAddress returns the link address of this endpoint.
-func (e *fwdTestLinkEndpoint) LinkAddress() tcpip.LinkAddress {
- return e.linkAddr
-}
-
-func (e fwdTestLinkEndpoint) WritePacket(r RouteInfo, _ tcpip.NetworkProtocolNumber, pkt *PacketBuffer) tcpip.Error {
- p := fwdTestPacketInfo{
- RemoteLinkAddress: r.RemoteLinkAddress,
- LocalLinkAddress: r.LocalLinkAddress,
- Pkt: pkt,
- }
-
- select {
- case e.C <- p:
- default:
- }
-
- return nil
-}
-
-// WritePackets stores outbound packets into the channel.
-func (e *fwdTestLinkEndpoint) WritePackets(r RouteInfo, pkts PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
- n := 0
- for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
- e.WritePacket(r, protocol, pkt)
- n++
- }
-
- return n, nil
-}
-
-func (*fwdTestLinkEndpoint) WriteRawPacket(*PacketBuffer) tcpip.Error {
- return &tcpip.ErrNotSupported{}
-}
-
-// Wait implements stack.LinkEndpoint.Wait.
-func (*fwdTestLinkEndpoint) Wait() {}
-
-// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
-func (*fwdTestLinkEndpoint) ARPHardwareType() header.ARPHardwareType {
- panic("not implemented")
-}
-
-// AddHeader implements stack.LinkEndpoint.AddHeader.
-func (e *fwdTestLinkEndpoint) AddHeader(tcpip.LinkAddress, tcpip.LinkAddress, tcpip.NetworkProtocolNumber, *PacketBuffer) {
- panic("not implemented")
-}
-
-func fwdTestNetFactory(t *testing.T, proto *fwdTestNetworkProtocol) (*faketime.ManualClock, *fwdTestLinkEndpoint, *fwdTestLinkEndpoint) {
- clock := faketime.NewManualClock()
- // Create a stack with the network protocol and two NICs.
- s := New(Options{
- NetworkProtocols: []NetworkProtocolFactory{func(s *Stack) NetworkProtocol {
- proto.stack = s
- return proto
- }},
- Clock: clock,
- })
-
- protoNum := proto.Number()
- if err := s.SetForwardingDefaultAndAllNICs(protoNum, true); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, true): %s", protoNum, err)
- }
-
- // NIC 1 has the link address "a", and added the network address 1.
- ep1 := &fwdTestLinkEndpoint{
- C: make(chan fwdTestPacketInfo, 300),
- mtu: fwdTestNetDefaultMTU,
- linkAddr: "a",
- }
- if err := s.CreateNIC(1, ep1); err != nil {
- t.Fatal("CreateNIC #1 failed:", err)
- }
- protocolAddr1 := tcpip.ProtocolAddress{
- Protocol: fwdTestNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: "\x01",
- PrefixLen: fwdTestNetDefaultPrefixLen,
- },
- }
- if err := s.AddProtocolAddress(1, protocolAddr1, AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr1, err)
- }
-
- // NIC 2 has the link address "b", and added the network address 2.
- ep2 := &fwdTestLinkEndpoint{
- C: make(chan fwdTestPacketInfo, 300),
- mtu: fwdTestNetDefaultMTU,
- linkAddr: "b",
- }
- if err := s.CreateNIC(2, ep2); err != nil {
- t.Fatal("CreateNIC #2 failed:", err)
- }
- protocolAddr2 := tcpip.ProtocolAddress{
- Protocol: fwdTestNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: "\x02",
- PrefixLen: fwdTestNetDefaultPrefixLen,
- },
- }
- if err := s.AddProtocolAddress(2, protocolAddr2, AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 2, protocolAddr2, err)
- }
-
- nic, ok := s.nics[2]
- if !ok {
- t.Fatal("NIC 2 does not exist")
- }
-
- if l, ok := nic.linkAddrResolvers[fwdTestNetNumber]; ok {
- proto.neigh = &l.neigh
- }
-
- // Route all packets to NIC 2.
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, NIC: 2}})
- }
-
- return clock, ep1, ep2
-}
-
-func TestForwardingWithStaticResolver(t *testing.T) {
- // Create a network protocol with a static resolver.
- proto := &fwdTestNetworkProtocol{
- onResolveStaticAddress:
- // The network address 3 is resolved to the link address "c".
- func(addr tcpip.Address) (tcpip.LinkAddress, bool) {
- if addr == "\x03" {
- return "c", true
- }
- return "", false
- },
- }
-
- clock, ep1, ep2 := fwdTestNetFactory(t, proto)
-
- // Inject an inbound packet to address 3 on NIC 1, and see if it is
- // forwarded to NIC 2.
- buf := buffer.NewView(30)
- buf[dstAddrOffset] = 3
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
-
- var p fwdTestPacketInfo
-
- clock.Advance(proto.addrResolveDelay)
- select {
- case p = <-ep2.C:
- default:
- t.Fatal("packet not forwarded")
- }
-
- // Test that the static address resolution happened correctly.
- if p.RemoteLinkAddress != "c" {
- t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
- }
- if p.LocalLinkAddress != "b" {
- t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
- }
-}
-
-func TestForwardingWithFakeResolver(t *testing.T) {
- proto := fwdTestNetworkProtocol{
- addrResolveDelay: 500 * time.Millisecond,
- onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
- t.Helper()
- if len(linkAddr) != 0 {
- t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
- }
- // Any address will be resolved to the link address "c".
- neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
- },
- }
- clock, ep1, ep2 := fwdTestNetFactory(t, &proto)
-
- // Inject an inbound packet to address 3 on NIC 1, and see if it is
- // forwarded to NIC 2.
- buf := buffer.NewView(30)
- buf[dstAddrOffset] = 3
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
-
- var p fwdTestPacketInfo
-
- clock.Advance(proto.addrResolveDelay)
- select {
- case p = <-ep2.C:
- default:
- t.Fatal("packet not forwarded")
- }
-
- // Test that the address resolution happened correctly.
- if p.RemoteLinkAddress != "c" {
- t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
- }
- if p.LocalLinkAddress != "b" {
- t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
- }
-}
-
-func TestForwardingWithNoResolver(t *testing.T) {
- // Create a network protocol without a resolver.
- proto := &fwdTestNetworkProtocol{}
-
- // Whether or not we use the neighbor cache here does not matter since
- // neither linkAddrCache nor neighborCache will be used.
- clock, ep1, ep2 := fwdTestNetFactory(t, proto)
-
- // inject an inbound packet to address 3 on NIC 1, and see if it is
- // forwarded to NIC 2.
- buf := buffer.NewView(30)
- buf[dstAddrOffset] = 3
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
-
- clock.Advance(proto.addrResolveDelay)
- select {
- case <-ep2.C:
- t.Fatal("Packet should not be forwarded")
- default:
- }
-}
-
-func TestForwardingResolutionFailsForQueuedPackets(t *testing.T) {
- proto := &fwdTestNetworkProtocol{
- addrResolveDelay: 50 * time.Millisecond,
- onLinkAddressResolved: func(*neighborCache, tcpip.Address, tcpip.LinkAddress) {
- // Don't resolve the link address.
- },
- }
-
- clock, ep1, ep2 := fwdTestNetFactory(t, proto)
-
- const numPackets int = 5
- // These packets will all be enqueued in the packet queue to wait for link
- // address resolution.
- for i := 0; i < numPackets; i++ {
- buf := buffer.NewView(30)
- buf[dstAddrOffset] = 3
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- }
-
- // All packets should fail resolution.
- for i := 0; i < numPackets; i++ {
- clock.Advance(proto.addrResolveDelay)
- select {
- case got := <-ep2.C:
- t.Fatalf("got %#v; packets should have failed resolution and not been forwarded", got)
- default:
- }
- }
-}
-
-func TestForwardingWithFakeResolverPartialTimeout(t *testing.T) {
- proto := fwdTestNetworkProtocol{
- addrResolveDelay: 500 * time.Millisecond,
- onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
- t.Helper()
- if len(linkAddr) != 0 {
- t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
- }
- // Only packets to address 3 will be resolved to the
- // link address "c".
- if addr == "\x03" {
- neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
- }
- },
- }
- clock, ep1, ep2 := fwdTestNetFactory(t, &proto)
-
- // Inject an inbound packet to address 4 on NIC 1. This packet should
- // not be forwarded.
- buf := buffer.NewView(30)
- buf[dstAddrOffset] = 4
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
-
- // Inject an inbound packet to address 3 on NIC 1, and see if it is
- // forwarded to NIC 2.
- buf = buffer.NewView(30)
- buf[dstAddrOffset] = 3
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
-
- var p fwdTestPacketInfo
-
- clock.Advance(proto.addrResolveDelay)
- select {
- case p = <-ep2.C:
- default:
- t.Fatal("packet not forwarded")
- }
-
- if nh := PayloadSince(p.Pkt.NetworkHeader()); nh[dstAddrOffset] != 3 {
- t.Fatalf("got p.Pkt.NetworkHeader[dstAddrOffset] = %d, want = 3", nh[dstAddrOffset])
- }
-
- // Test that the address resolution happened correctly.
- if p.RemoteLinkAddress != "c" {
- t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
- }
- if p.LocalLinkAddress != "b" {
- t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
- }
-}
-
-func TestForwardingWithFakeResolverTwoPackets(t *testing.T) {
- proto := fwdTestNetworkProtocol{
- addrResolveDelay: 500 * time.Millisecond,
- onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
- t.Helper()
- if len(linkAddr) != 0 {
- t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
- }
- // Any packets will be resolved to the link address "c".
- neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
- },
- }
- clock, ep1, ep2 := fwdTestNetFactory(t, &proto)
-
- // Inject two inbound packets to address 3 on NIC 1.
- for i := 0; i < 2; i++ {
- buf := buffer.NewView(30)
- buf[dstAddrOffset] = 3
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- }
-
- for i := 0; i < 2; i++ {
- var p fwdTestPacketInfo
-
- clock.Advance(proto.addrResolveDelay)
- select {
- case p = <-ep2.C:
- default:
- t.Fatal("packet not forwarded")
- }
-
- if nh := PayloadSince(p.Pkt.NetworkHeader()); nh[dstAddrOffset] != 3 {
- t.Fatalf("got p.Pkt.NetworkHeader[dstAddrOffset] = %d, want = 3", nh[dstAddrOffset])
- }
-
- // Test that the address resolution happened correctly.
- if p.RemoteLinkAddress != "c" {
- t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
- }
- if p.LocalLinkAddress != "b" {
- t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
- }
- }
-}
-
-func TestForwardingWithFakeResolverManyPackets(t *testing.T) {
- proto := fwdTestNetworkProtocol{
- addrResolveDelay: 500 * time.Millisecond,
- onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
- t.Helper()
- if len(linkAddr) != 0 {
- t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
- }
- // Any packets will be resolved to the link address "c".
- neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
- },
- }
- clock, ep1, ep2 := fwdTestNetFactory(t, &proto)
-
- for i := 0; i < maxPendingPacketsPerResolution+5; i++ {
- // Inject inbound 'maxPendingPacketsPerResolution + 5' packets on NIC 1.
- buf := buffer.NewView(30)
- buf[dstAddrOffset] = 3
- // Set the packet sequence number.
- binary.BigEndian.PutUint16(buf[fwdTestNetHeaderLen:], uint16(i))
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- }
-
- for i := 0; i < maxPendingPacketsPerResolution; i++ {
- var p fwdTestPacketInfo
-
- clock.Advance(proto.addrResolveDelay)
- select {
- case p = <-ep2.C:
- default:
- t.Fatal("packet not forwarded")
- }
-
- b := PayloadSince(p.Pkt.NetworkHeader())
- if b[dstAddrOffset] != 3 {
- t.Fatalf("got b[dstAddrOffset] = %d, want = 3", b[dstAddrOffset])
- }
- if len(b) < fwdTestNetHeaderLen+2 {
- t.Fatalf("packet is too short to hold a sequence number: len(b) = %d", b)
- }
- seqNumBuf := b[fwdTestNetHeaderLen:]
-
- // The first 5 packets should not be forwarded so the sequence number should
- // start with 5.
- want := uint16(i + 5)
- if n := binary.BigEndian.Uint16(seqNumBuf); n != want {
- t.Fatalf("got the packet #%d, want = #%d", n, want)
- }
-
- // Test that the address resolution happened correctly.
- if p.RemoteLinkAddress != "c" {
- t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
- }
- if p.LocalLinkAddress != "b" {
- t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
- }
- }
-}
-
-func TestForwardingWithFakeResolverManyResolutions(t *testing.T) {
- proto := fwdTestNetworkProtocol{
- addrResolveDelay: 500 * time.Millisecond,
- onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
- t.Helper()
- if len(linkAddr) != 0 {
- t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
- }
- // Any packets will be resolved to the link address "c".
- neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
- },
- }
- clock, ep1, ep2 := fwdTestNetFactory(t, &proto)
-
- for i := 0; i < maxPendingResolutions+5; i++ {
- // Inject inbound 'maxPendingResolutions + 5' packets on NIC 1.
- // Each packet has a different destination address (3 to
- // maxPendingResolutions + 7).
- buf := buffer.NewView(30)
- buf[dstAddrOffset] = byte(3 + i)
- ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- }
-
- for i := 0; i < maxPendingResolutions; i++ {
- var p fwdTestPacketInfo
-
- clock.Advance(proto.addrResolveDelay)
- select {
- case p = <-ep2.C:
- default:
- t.Fatal("packet not forwarded")
- }
-
- // The first 5 packets (address 3 to 7) should not be forwarded
- // because their address resolutions are interrupted.
- if nh := PayloadSince(p.Pkt.NetworkHeader()); nh[dstAddrOffset] < 8 {
- t.Fatalf("got p.Pkt.NetworkHeader[dstAddrOffset] = %d, want p.Pkt.NetworkHeader[dstAddrOffset] >= 8", nh[dstAddrOffset])
- }
-
- // Test that the address resolution happened correctly.
- if p.RemoteLinkAddress != "c" {
- t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
- }
- if p.LocalLinkAddress != "b" {
- t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
- }
- }
-}
diff --git a/pkg/tcpip/stack/iptables_test.go b/pkg/tcpip/stack/iptables_test.go
deleted file mode 100644
index 1788e98c9..000000000
--- a/pkg/tcpip/stack/iptables_test.go
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright 2021 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/faketime"
- "gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-// TestNATedConnectionReap tests that NATed connections are properly reaped.
-func TestNATedConnectionReap(t *testing.T) {
- // Note that the network protocol used for this test doesn't matter as this
- // test focuses on reaping, not anything related to a specific network
- // protocol.
-
- const (
- nattedDstPort = 1
- srcPort = 2
- dstPort = 3
-
- nattedDstAddr = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
- srcAddr = tcpip.Address("\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
- dstAddr = tcpip.Address("\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03")
- )
-
- clock := faketime.NewManualClock()
- iptables := DefaultTables(0 /* seed */, clock)
-
- table := Table{
- Rules: []Rule{
- // Prerouting
- {
- Target: &DNATTarget{NetworkProtocol: header.IPv6ProtocolNumber, Addr: nattedDstAddr, Port: nattedDstPort},
- },
- {
- Target: &AcceptTarget{},
- },
-
- // Input
- {
- Target: &AcceptTarget{},
- },
-
- // Forward
- {
- Target: &AcceptTarget{},
- },
-
- // Output
- {
- Target: &AcceptTarget{},
- },
-
- // Postrouting
- {
- Target: &AcceptTarget{},
- },
- },
- BuiltinChains: [NumHooks]int{
- Prerouting: 0,
- Input: 2,
- Forward: 3,
- Output: 4,
- Postrouting: 5,
- },
- }
- if err := iptables.ReplaceTable(NATID, table, true /* ipv6 */); err != nil {
- t.Fatalf("ipt.ReplaceTable(%d, _, true): %s", NATID, err)
- }
-
- // Stop the reaper if it is running so we can reap manually as it is started
- // on the first change to IPTables.
- iptables.reaperDone <- struct{}{}
-
- pkt := NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: header.IPv6MinimumSize + header.UDPMinimumSize,
- })
- udp := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
- udp.SetSourcePort(srcPort)
- udp.SetDestinationPort(dstPort)
- udp.SetChecksum(0)
- udp.SetChecksum(^udp.CalculateChecksum(header.PseudoHeaderChecksum(
- header.UDPProtocolNumber,
- srcAddr,
- dstAddr,
- uint16(len(udp)),
- )))
- pkt.TransportProtocolNumber = header.UDPProtocolNumber
- ip := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(len(udp)),
- TransportProtocol: header.UDPProtocolNumber,
- HopLimit: 64,
- SrcAddr: srcAddr,
- DstAddr: dstAddr,
- })
- pkt.NetworkProtocolNumber = header.IPv6ProtocolNumber
-
- originalTID, _, ok := getTupleID(pkt)
- if !ok {
- t.Fatal("failed to get original tuple ID")
- }
-
- if !iptables.CheckPrerouting(pkt, nil /* addressEP */, "" /* inNicName */) {
- t.Fatal("got ipt.CheckPrerouting(...) = false, want = true")
- }
- if !iptables.CheckInput(pkt, "" /* inNicName */) {
- t.Fatal("got ipt.CheckInput(...) = false, want = true")
- }
-
- invertedReplyTID, _, ok := getTupleID(pkt)
- if !ok {
- t.Fatal("failed to get NATed packet's tuple ID")
- }
- if invertedReplyTID == originalTID {
- t.Fatalf("NAT not performed; got invertedReplyTID = %#v", invertedReplyTID)
- }
- replyTID := invertedReplyTID.reply()
-
- originalBktID := iptables.connections.bucket(originalTID)
- replyBktID := iptables.connections.bucket(replyTID)
-
- // This test depends on the original and reply tuples mapping to different
- // buckets.
- if originalBktID == replyBktID {
- t.Fatalf("expected bucket IDs to be different; got = %d", originalBktID)
- }
-
- lowerBktID := originalBktID
- if lowerBktID > replyBktID {
- lowerBktID = replyBktID
- }
-
- runReaper := func() {
- // Reaping the bucket with the lower ID should reap both tuples of the
- // connection if it has timed out.
- //
- // We will manually pick the next start bucket ID and don't use the
- // interval so we ignore the return values.
- _, _ = iptables.connections.reapUnused(lowerBktID, 0 /* prevInterval */)
- }
-
- iptables.connections.mu.RLock()
- buckets := iptables.connections.buckets
- iptables.connections.mu.RUnlock()
-
- originalBkt := &buckets[originalBktID]
- replyBkt := &buckets[replyBktID]
-
- // Run the reaper and make sure the tuples were not reaped.
- reapAndCheckForConnections := func() {
- t.Helper()
-
- runReaper()
-
- now := clock.NowMonotonic()
- if originalTuple := originalBkt.connForTID(originalTID, now); originalTuple == nil {
- t.Error("expected to get original tuple")
- }
-
- if replyTuple := replyBkt.connForTID(replyTID, now); replyTuple == nil {
- t.Error("expected to get reply tuple")
- }
-
- if t.Failed() {
- t.FailNow()
- }
- }
-
- // Connection was just added and no time has passed - it should not be reaped.
- reapAndCheckForConnections()
-
- // Time must advance past the unestablished timeout for a connection to be
- // reaped.
- clock.Advance(unestablishedTimeout)
- reapAndCheckForConnections()
-
- // Connection should now be reaped.
- clock.Advance(1)
- runReaper()
- now := clock.NowMonotonic()
- if originalTuple := originalBkt.connForTID(originalTID, now); originalTuple != nil {
- t.Errorf("got originalBkt.connForTID(%#v, %#v) = %#v, want = nil", originalTID, now, originalTuple)
- }
- if replyTuple := replyBkt.connForTID(replyTID, now); replyTuple != nil {
- t.Errorf("got replyBkt.connForTID(%#v, %#v) = %#v, want = nil", replyTID, now, replyTuple)
- }
- // Make sure we don't have stale tuples just lying around.
- //
- // We manually check the buckets as connForTID will skip over tuples that
- // have timed out.
- checkNoTupleInBucket := func(bkt *bucket, tid tupleID, reply bool) {
- t.Helper()
-
- bkt.mu.RLock()
- defer bkt.mu.RUnlock()
- for tuple := bkt.tuples.Front(); tuple != nil; tuple = tuple.Next() {
- if tuple.id() == originalTID {
- t.Errorf("unexpectedly found tuple with ID = %#v; reply = %t", tid, reply)
- }
- }
- }
- checkNoTupleInBucket(originalBkt, originalTID, false /* reply */)
- checkNoTupleInBucket(replyBkt, replyTID, true /* reply */)
-}
diff --git a/pkg/tcpip/stack/ndp_test.go b/pkg/tcpip/stack/ndp_test.go
deleted file mode 100644
index 40b33b6b5..000000000
--- a/pkg/tcpip/stack/ndp_test.go
+++ /dev/null
@@ -1,5614 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack_test
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "math/rand"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- cryptorand "gvisor.dev/gvisor/pkg/rand"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "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/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/testutil"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-var (
- addr1 = testutil.MustParse6("a00::1")
- addr2 = testutil.MustParse6("a00::2")
- addr3 = testutil.MustParse6("a00::3")
-)
-
-const (
- linkAddr1 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
- linkAddr2 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x07")
- linkAddr3 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x08")
- linkAddr4 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x09")
-
- defaultPrefixLen = 128
-)
-
-var (
- llAddr1 = header.LinkLocalAddr(linkAddr1)
- llAddr2 = header.LinkLocalAddr(linkAddr2)
- llAddr3 = header.LinkLocalAddr(linkAddr3)
- llAddr4 = header.LinkLocalAddr(linkAddr4)
- dstAddr = tcpip.FullAddress{
- Addr: "\x0a\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
- Port: 25,
- }
-)
-
-func addrForSubnet(subnet tcpip.Subnet, linkAddr tcpip.LinkAddress) tcpip.AddressWithPrefix {
- if !header.IsValidUnicastEthernetAddress(linkAddr) {
- return tcpip.AddressWithPrefix{}
- }
-
- addrBytes := []byte(subnet.ID())
- header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, addrBytes[header.IIDOffsetInIPv6Address:])
- return tcpip.AddressWithPrefix{
- Address: tcpip.Address(addrBytes),
- PrefixLen: 64,
- }
-}
-
-// prefixSubnetAddr returns a prefix (Address + Length), the prefix's equivalent
-// tcpip.Subnet, and an address where the lower half of the address is composed
-// of the EUI-64 of linkAddr if it is a valid unicast ethernet address.
-func prefixSubnetAddr(offset uint8, linkAddr tcpip.LinkAddress) (tcpip.AddressWithPrefix, tcpip.Subnet, tcpip.AddressWithPrefix) {
- prefixBytes := []byte{1, 2, 3, 4, 5, 6, 7, 8 + offset, 0, 0, 0, 0, 0, 0, 0, 0}
- prefix := tcpip.AddressWithPrefix{
- Address: tcpip.Address(prefixBytes),
- PrefixLen: 64,
- }
-
- subnet := prefix.Subnet()
-
- return prefix, subnet, addrForSubnet(subnet, linkAddr)
-}
-
-// ndpDADEvent is a set of parameters that was passed to
-// ndpDispatcher.OnDuplicateAddressDetectionResult.
-type ndpDADEvent struct {
- nicID tcpip.NICID
- addr tcpip.Address
- res stack.DADResult
-}
-
-type ndpOffLinkRouteEvent struct {
- nicID tcpip.NICID
- subnet tcpip.Subnet
- router tcpip.Address
- prf header.NDPRoutePreference
- // true if route was updated, false if invalidated.
- updated bool
-}
-
-type ndpPrefixEvent struct {
- nicID tcpip.NICID
- prefix tcpip.Subnet
- // true if prefix was discovered, false if invalidated.
- discovered bool
-}
-
-type ndpAutoGenAddrEventType int
-
-const (
- newAddr ndpAutoGenAddrEventType = iota
- deprecatedAddr
- invalidatedAddr
-)
-
-type ndpAutoGenAddrEvent struct {
- nicID tcpip.NICID
- addr tcpip.AddressWithPrefix
- eventType ndpAutoGenAddrEventType
-}
-
-func (e ndpAutoGenAddrEvent) String() string {
- return fmt.Sprintf("%T{nicID=%d addr=%s eventType=%d}", e, e.nicID, e.addr, e.eventType)
-}
-
-type ndpRDNSS struct {
- addrs []tcpip.Address
- lifetime time.Duration
-}
-
-type ndpRDNSSEvent struct {
- nicID tcpip.NICID
- rdnss ndpRDNSS
-}
-
-type ndpDNSSLEvent struct {
- nicID tcpip.NICID
- domainNames []string
- lifetime time.Duration
-}
-
-type ndpDHCPv6Event struct {
- nicID tcpip.NICID
- configuration ipv6.DHCPv6ConfigurationFromNDPRA
-}
-
-var _ ipv6.NDPDispatcher = (*ndpDispatcher)(nil)
-
-// ndpDispatcher implements NDPDispatcher so tests can know when various NDP
-// related events happen for test purposes.
-type ndpDispatcher struct {
- dadC chan ndpDADEvent
- offLinkRouteC chan ndpOffLinkRouteEvent
- prefixC chan ndpPrefixEvent
- autoGenAddrC chan ndpAutoGenAddrEvent
- rdnssC chan ndpRDNSSEvent
- dnsslC chan ndpDNSSLEvent
- routeTable []tcpip.Route
- dhcpv6ConfigurationC chan ndpDHCPv6Event
-}
-
-// Implements ipv6.NDPDispatcher.OnDuplicateAddressDetectionResult.
-func (n *ndpDispatcher) OnDuplicateAddressDetectionResult(nicID tcpip.NICID, addr tcpip.Address, res stack.DADResult) {
- if n.dadC != nil {
- n.dadC <- ndpDADEvent{
- nicID,
- addr,
- res,
- }
- }
-}
-
-// Implements ipv6.NDPDispatcher.OnOffLinkRouteUpdated.
-func (n *ndpDispatcher) OnOffLinkRouteUpdated(nicID tcpip.NICID, subnet tcpip.Subnet, router tcpip.Address, prf header.NDPRoutePreference) {
- if c := n.offLinkRouteC; c != nil {
- c <- ndpOffLinkRouteEvent{
- nicID,
- subnet,
- router,
- prf,
- true,
- }
- }
-}
-
-// Implements ipv6.NDPDispatcher.OnOffLinkRouteInvalidated.
-func (n *ndpDispatcher) OnOffLinkRouteInvalidated(nicID tcpip.NICID, subnet tcpip.Subnet, router tcpip.Address) {
- if c := n.offLinkRouteC; c != nil {
- var prf header.NDPRoutePreference
- c <- ndpOffLinkRouteEvent{
- nicID,
- subnet,
- router,
- prf,
- false,
- }
- }
-}
-
-// Implements ipv6.NDPDispatcher.OnOnLinkPrefixDiscovered.
-func (n *ndpDispatcher) OnOnLinkPrefixDiscovered(nicID tcpip.NICID, prefix tcpip.Subnet) {
- if c := n.prefixC; c != nil {
- c <- ndpPrefixEvent{
- nicID,
- prefix,
- true,
- }
- }
-}
-
-// Implements ipv6.NDPDispatcher.OnOnLinkPrefixInvalidated.
-func (n *ndpDispatcher) OnOnLinkPrefixInvalidated(nicID tcpip.NICID, prefix tcpip.Subnet) {
- if c := n.prefixC; c != nil {
- c <- ndpPrefixEvent{
- nicID,
- prefix,
- false,
- }
- }
-}
-
-func (n *ndpDispatcher) OnAutoGenAddress(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) {
- if c := n.autoGenAddrC; c != nil {
- c <- ndpAutoGenAddrEvent{
- nicID,
- addr,
- newAddr,
- }
- }
-}
-
-func (n *ndpDispatcher) OnAutoGenAddressDeprecated(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) {
- if c := n.autoGenAddrC; c != nil {
- c <- ndpAutoGenAddrEvent{
- nicID,
- addr,
- deprecatedAddr,
- }
- }
-}
-
-func (n *ndpDispatcher) OnAutoGenAddressInvalidated(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) {
- if c := n.autoGenAddrC; c != nil {
- c <- ndpAutoGenAddrEvent{
- nicID,
- addr,
- invalidatedAddr,
- }
- }
-}
-
-// Implements ipv6.NDPDispatcher.OnRecursiveDNSServerOption.
-func (n *ndpDispatcher) OnRecursiveDNSServerOption(nicID tcpip.NICID, addrs []tcpip.Address, lifetime time.Duration) {
- if c := n.rdnssC; c != nil {
- c <- ndpRDNSSEvent{
- nicID,
- ndpRDNSS{
- addrs,
- lifetime,
- },
- }
- }
-}
-
-// Implements ipv6.NDPDispatcher.OnDNSSearchListOption.
-func (n *ndpDispatcher) OnDNSSearchListOption(nicID tcpip.NICID, domainNames []string, lifetime time.Duration) {
- if n.dnsslC != nil {
- n.dnsslC <- ndpDNSSLEvent{
- nicID,
- domainNames,
- lifetime,
- }
- }
-}
-
-// Implements ipv6.NDPDispatcher.OnDHCPv6Configuration.
-func (n *ndpDispatcher) OnDHCPv6Configuration(nicID tcpip.NICID, configuration ipv6.DHCPv6ConfigurationFromNDPRA) {
- if c := n.dhcpv6ConfigurationC; c != nil {
- c <- ndpDHCPv6Event{
- nicID,
- configuration,
- }
- }
-}
-
-// channelLinkWithHeaderLength is a channel.Endpoint with a configurable
-// header length.
-type channelLinkWithHeaderLength struct {
- *channel.Endpoint
- headerLength uint16
-}
-
-func (l *channelLinkWithHeaderLength) MaxHeaderLength() uint16 {
- return l.headerLength
-}
-
-// Check e to make sure that the event is for addr on nic with ID 1, and the
-// resolved flag set to resolved with the specified err.
-func checkDADEvent(e ndpDADEvent, nicID tcpip.NICID, addr tcpip.Address, res stack.DADResult) string {
- return cmp.Diff(ndpDADEvent{nicID: nicID, addr: addr, res: res}, e, cmp.AllowUnexported(e))
-}
-
-// TestDADDisabled tests that an address successfully resolves immediately
-// when DAD is not enabled (the default for an empty stack.Options).
-func TestDADDisabled(t *testing.T) {
- const nicID = 1
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPDisp: &ndpDisp,
- })},
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- addrWithPrefix := tcpip.AddressWithPrefix{
- Address: addr1,
- PrefixLen: defaultPrefixLen,
- }
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addrWithPrefix,
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, err)
- }
-
- // Should get the address immediately since we should not have performed
- // DAD on it.
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr1, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected DAD event")
- }
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil {
- t.Fatal(err)
- }
-
- // We should not have sent any NDP NS messages.
- if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got != 0 {
- t.Fatalf("got NeighborSolicit = %d, want = 0", got)
- }
-}
-
-func TestDADResolveLoopback(t *testing.T) {
- const nicID = 1
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- }
-
- dadConfigs := stack.DADConfigurations{
- RetransmitTimer: time.Second,
- DupAddrDetectTransmits: 1,
- }
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- Clock: clock,
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPDisp: &ndpDisp,
- DADConfigs: dadConfigs,
- })},
- })
- if err := s.CreateNIC(nicID, loopback.New()); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: addr1,
- PrefixLen: defaultPrefixLen,
- },
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, 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)
- }
-
- // DAD should not resolve after the normal resolution time since our DAD
- // message was looped back - we should extend our DAD process.
- dadResolutionTime := time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer
- clock.Advance(dadResolutionTime)
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
- t.Error(err)
- }
-
- // Make sure the address does not resolve before the extended resolution time
- // has passed.
- const delta = time.Nanosecond
- // DAD will send extra NS probes if an NS message is looped back.
- const extraTransmits = 3
- clock.Advance(dadResolutionTime*extraTransmits - delta)
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
- t.Error(err)
- }
-
- // DAD should now resolve.
- clock.Advance(delta)
- if diff := checkDADEvent(<-ndpDisp.dadC, nicID, addr1, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
-}
-
-// TestDADResolve tests that an address successfully resolves after performing
-// DAD for various values of DupAddrDetectTransmits and RetransmitTimer.
-// Included in the subtests is a test to make sure that an invalid
-// RetransmitTimer (<1ms) values get fixed to the default RetransmitTimer of 1s.
-// This tests also validates the NDP NS packet that is transmitted.
-func TestDADResolve(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- linkHeaderLen uint16
- dupAddrDetectTransmits uint8
- retransTimer time.Duration
- expectedRetransmitTimer time.Duration
- }{
- {
- name: "1:1s:1s",
- dupAddrDetectTransmits: 1,
- retransTimer: time.Second,
- expectedRetransmitTimer: time.Second,
- },
- {
- name: "2:1s:1s",
- linkHeaderLen: 1,
- dupAddrDetectTransmits: 2,
- retransTimer: time.Second,
- expectedRetransmitTimer: time.Second,
- },
- {
- name: "1:2s:2s",
- linkHeaderLen: 2,
- dupAddrDetectTransmits: 1,
- retransTimer: 2 * time.Second,
- expectedRetransmitTimer: 2 * time.Second,
- },
- // 0s is an invalid RetransmitTimer timer and will be fixed to
- // the default RetransmitTimer value of 1s.
- {
- name: "1:0s:1s",
- linkHeaderLen: 3,
- dupAddrDetectTransmits: 1,
- retransTimer: 0,
- expectedRetransmitTimer: time.Second,
- },
- }
-
- nonces := [][]byte{
- {1, 2, 3, 4, 5, 6},
- {7, 8, 9, 10, 11, 12},
- }
-
- var secureRNGBytes []byte
- for _, n := range nonces {
- secureRNGBytes = append(secureRNGBytes, n...)
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- }
-
- e := channelLinkWithHeaderLength{
- Endpoint: channel.New(int(test.dupAddrDetectTransmits), 1280, linkAddr1),
- headerLength: test.linkHeaderLen,
- }
- e.Endpoint.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- var secureRNG bytes.Reader
- secureRNG.Reset(secureRNGBytes)
-
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- Clock: clock,
- RandSource: rand.NewSource(time.Now().UnixNano()),
- SecureRNG: &secureRNG,
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPDisp: &ndpDisp,
- DADConfigs: stack.DADConfigurations{
- RetransmitTimer: test.retransTimer,
- DupAddrDetectTransmits: test.dupAddrDetectTransmits,
- },
- })},
- })
- if err := s.CreateNIC(nicID, &e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- // We add a default route so the call to FindRoute below will succeed
- // once we have an assigned address.
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv6EmptySubnet,
- Gateway: addr3,
- NIC: nicID,
- }})
-
- addrWithPrefix := tcpip.AddressWithPrefix{
- Address: addr1,
- PrefixLen: defaultPrefixLen,
- }
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addrWithPrefix,
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, err)
- }
-
- // Make sure the address does not resolve before the resolution time has
- // passed.
- const delta = time.Nanosecond
- clock.Advance(test.expectedRetransmitTimer*time.Duration(test.dupAddrDetectTransmits) - delta)
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
- t.Error(err)
- }
- // Should not get a route even if we specify the local address as the
- // tentative address.
- {
- r, err := s.FindRoute(nicID, "", addr2, header.IPv6ProtocolNumber, false)
- if _, ok := err.(*tcpip.ErrNoRoute); !ok {
- t.Errorf("got FindRoute(%d, '', %s, %d, false) = (%+v, %v), want = (_, %s)", nicID, addr2, header.IPv6ProtocolNumber, r, err, &tcpip.ErrNoRoute{})
- }
- if r != nil {
- r.Release()
- }
- }
- {
- r, err := s.FindRoute(nicID, addr1, addr2, header.IPv6ProtocolNumber, false)
- if _, ok := err.(*tcpip.ErrNoRoute); !ok {
- t.Errorf("got FindRoute(%d, %s, %s, %d, false) = (%+v, %v), want = (_, %s)", nicID, addr1, addr2, header.IPv6ProtocolNumber, r, err, &tcpip.ErrNoRoute{})
- }
- if r != nil {
- r.Release()
- }
- }
-
- if t.Failed() {
- t.FailNow()
- }
-
- // Wait for DAD to resolve.
- clock.Advance(delta)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr1, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatalf("expected DAD event for %s on NIC(%d)", addr1, nicID)
- }
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil {
- t.Error(err)
- }
- // Should get a route using the address now that it is resolved.
- {
- r, err := s.FindRoute(nicID, "", addr2, header.IPv6ProtocolNumber, false)
- if err != nil {
- t.Errorf("got FindRoute(%d, '', %s, %d, false): %s", nicID, addr2, header.IPv6ProtocolNumber, err)
- } else if r.LocalAddress() != addr1 {
- t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), addr1)
- }
- r.Release()
- }
- {
- r, err := s.FindRoute(nicID, addr1, addr2, header.IPv6ProtocolNumber, false)
- if err != nil {
- t.Errorf("got FindRoute(%d, %s, %s, %d, false): %s", nicID, addr1, addr2, header.IPv6ProtocolNumber, err)
- } else if r.LocalAddress() != addr1 {
- t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), addr1)
- }
- if r != nil {
- r.Release()
- }
- }
-
- if t.Failed() {
- t.FailNow()
- }
-
- // Should not have sent any more NS messages.
- if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got != uint64(test.dupAddrDetectTransmits) {
- t.Fatalf("got NeighborSolicit = %d, want = %d", got, test.dupAddrDetectTransmits)
- }
-
- // Validate the sent Neighbor Solicitation messages.
- for i := uint8(0); i < test.dupAddrDetectTransmits; i++ {
- p, ok := e.Read()
- if !ok {
- t.Fatal("packet didn't arrive")
- }
-
- // Make sure its an IPv6 packet.
- if p.Proto != header.IPv6ProtocolNumber {
- t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber)
- }
-
- // Make sure the right remote link address is used.
- snmc := header.SolicitedNodeAddr(addr1)
- if want := header.EthernetAddressFromMulticastIPv6Address(snmc); p.Route.RemoteLinkAddress != want {
- t.Errorf("got remote link address = %s, want = %s", p.Route.RemoteLinkAddress, want)
- }
-
- // Check NDP NS packet.
- //
- // As per RFC 4861 section 4.3, a possible option is the Source Link
- // Layer option, but this option MUST NOT be included when the source
- // address of the packet is the unspecified address.
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(header.IPv6Any),
- checker.DstAddr(snmc),
- checker.TTL(header.NDPHopLimit),
- checker.NDPNS(
- checker.NDPNSTargetAddress(addr1),
- checker.NDPNSOptions([]header.NDPOption{header.NDPNonceOption(nonces[i])}),
- ))
-
- if l, want := p.Pkt.AvailableHeaderBytes(), int(test.linkHeaderLen); l != want {
- t.Errorf("got p.Pkt.AvailableHeaderBytes() = %d; want = %d", l, want)
- }
- }
- })
- }
-}
-
-func rxNDPSolicit(e *channel.Endpoint, tgt tcpip.Address) {
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.ICMPv6NeighborSolicitMinimumSize)
- pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborSolicitMinimumSize))
- pkt.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(pkt.MessageBody())
- ns.SetTargetAddress(tgt)
- snmc := header.SolicitedNodeAddr(tgt)
- pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
- Header: pkt,
- Src: header.IPv6Any,
- Dst: snmc,
- }))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- TransportProtocol: icmp.ProtocolNumber6,
- HopLimit: 255,
- SrcAddr: header.IPv6Any,
- DstAddr: snmc,
- })
- e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{Data: hdr.View().ToVectorisedView()}))
-}
-
-// TestDADFail tests to make sure that the DAD process fails if another node is
-// detected to be performing DAD on the same address (receive an NS message from
-// a node doing DAD for the same address), or if another node is detected to own
-// the address already (receive an NA message for the tentative address).
-func TestDADFail(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- rxPkt func(e *channel.Endpoint, tgt tcpip.Address)
- getStat func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter
- expectedHolderLinkAddress tcpip.LinkAddress
- }{
- {
- name: "RxSolicit",
- rxPkt: rxNDPSolicit,
- getStat: func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return s.NeighborSolicit
- },
- expectedHolderLinkAddress: "",
- },
- {
- name: "RxAdvert",
- rxPkt: func(e *channel.Endpoint, tgt tcpip.Address) {
- naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + naSize)
- pkt := header.ICMPv6(hdr.Prepend(naSize))
- pkt.SetType(header.ICMPv6NeighborAdvert)
- na := header.NDPNeighborAdvert(pkt.MessageBody())
- na.SetSolicitedFlag(true)
- na.SetOverrideFlag(true)
- na.SetTargetAddress(tgt)
- na.Options().Serialize(header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(linkAddr1),
- })
- pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
- Header: pkt,
- Src: tgt,
- Dst: header.IPv6AllNodesMulticastAddress,
- }))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- TransportProtocol: icmp.ProtocolNumber6,
- HopLimit: 255,
- SrcAddr: tgt,
- DstAddr: header.IPv6AllNodesMulticastAddress,
- })
- e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{Data: hdr.View().ToVectorisedView()}))
- },
- getStat: func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return s.NeighborAdvert
- },
- expectedHolderLinkAddress: linkAddr1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- }
- dadConfigs := stack.DefaultDADConfigurations()
- dadConfigs.RetransmitTimer = time.Second * 2
-
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPDisp: &ndpDisp,
- DADConfigs: dadConfigs,
- })},
- Clock: clock,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addr1.WithPrefix(),
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, 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)
- }
-
- // Receive a packet to simulate an address conflict.
- test.rxPkt(e, addr1)
-
- stat := test.getStat(s.Stats().ICMP.V6.PacketsReceived)
- if got := stat.Value(); got != 1 {
- t.Fatalf("got stat = %d, want = 1", got)
- }
-
- // Wait for DAD to fail and make sure the address did
- // not get resolved.
- clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr1, &stack.DADDupAddrDetected{HolderLinkAddress: test.expectedHolderLinkAddress}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- // If we don't get a failure event after the
- // expected resolution time + extra 1s buffer,
- // something is wrong.
- t.Fatal("timed out waiting for DAD failure")
- }
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
- t.Fatal(err)
- }
-
- // Attempting to add the address again should not fail if the address's
- // state was cleaned up when DAD failed.
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
- }
- })
- }
-}
-
-func TestDADStop(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- stopFn func(t *testing.T, s *stack.Stack)
- skipFinalAddrCheck bool
- }{
- // Tests to make sure that DAD stops when an address is removed.
- {
- name: "Remove address",
- stopFn: func(t *testing.T, s *stack.Stack) {
- if err := s.RemoveAddress(nicID, addr1); err != nil {
- t.Fatalf("RemoveAddress(%d, %s): %s", nicID, addr1, err)
- }
- },
- },
-
- // Tests to make sure that DAD stops when the NIC is disabled.
- {
- name: "Disable NIC",
- stopFn: func(t *testing.T, s *stack.Stack) {
- if err := s.DisableNIC(nicID); err != nil {
- t.Fatalf("DisableNIC(%d): %s", nicID, err)
- }
- },
- },
-
- // Tests to make sure that DAD stops when the NIC is removed.
- {
- name: "Remove NIC",
- stopFn: func(t *testing.T, s *stack.Stack) {
- if err := s.RemoveNIC(nicID); err != nil {
- t.Fatalf("RemoveNIC(%d): %s", nicID, err)
- }
- },
- // The NIC is removed so we can't check its addresses after calling
- // stopFn.
- skipFinalAddrCheck: true,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- }
-
- dadConfigs := stack.DADConfigurations{
- RetransmitTimer: time.Second,
- DupAddrDetectTransmits: 2,
- }
-
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPDisp: &ndpDisp,
- DADConfigs: dadConfigs,
- })},
- Clock: clock,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
- }
-
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addr1.WithPrefix(),
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, 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)
- }
-
- test.stopFn(t, s)
-
- // Wait for DAD to fail (since the address was removed during DAD).
- clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr1, &stack.DADAborted{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- // If we don't get a failure event after the expected resolution
- // time + extra 1s buffer, something is wrong.
- t.Fatal("timed out waiting for DAD failure")
- }
-
- if !test.skipFinalAddrCheck {
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
- t.Fatal(err)
- }
- }
-
- // Should not have sent more than 1 NS message.
- if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got > 1 {
- t.Errorf("got NeighborSolicit = %d, want <= 1", got)
- }
- })
- }
-}
-
-// TestSetNDPConfigurations tests that we can update and use per-interface NDP
-// configurations without affecting the default NDP configurations or other
-// interfaces' configurations.
-func TestSetNDPConfigurations(t *testing.T) {
- const nicID1 = 1
- const nicID2 = 2
- const nicID3 = 3
-
- tests := []struct {
- name string
- dupAddrDetectTransmits uint8
- retransmitTimer time.Duration
- expectedRetransmitTimer time.Duration
- }{
- {
- "OK",
- 1,
- time.Second,
- time.Second,
- },
- {
- "Invalid Retransmit Timer",
- 1,
- 0,
- time.Second,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- expectDADEvent := func(nicID tcpip.NICID, addr tcpip.Address) {
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatalf("expected DAD event for %s", addr)
- }
- }
-
- // This NIC(1)'s NDP configurations will be updated to
- // be different from the default.
- if err := s.CreateNIC(nicID1, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID1, err)
- }
-
- // Created before updating NIC(1)'s NDP configurations
- // but updating NIC(1)'s NDP configurations should not
- // affect other existing NICs.
- if err := s.CreateNIC(nicID2, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err)
- }
-
- // Update the configurations on NIC(1) to use DAD.
- if ipv6Ep, err := s.GetNetworkEndpoint(nicID1, header.IPv6ProtocolNumber); err != nil {
- t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID1, header.IPv6ProtocolNumber, err)
- } else {
- dad := ipv6Ep.(stack.DuplicateAddressDetector)
- dad.SetDADConfigurations(stack.DADConfigurations{
- DupAddrDetectTransmits: test.dupAddrDetectTransmits,
- RetransmitTimer: test.retransmitTimer,
- })
- }
-
- // Created after updating NIC(1)'s NDP configurations
- // but the stack's default NDP configurations should not
- // have been updated.
- if err := s.CreateNIC(nicID3, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID3, err)
- }
-
- // Add addresses for each NIC.
- addrWithPrefix1 := tcpip.AddressWithPrefix{Address: addr1, PrefixLen: defaultPrefixLen}
- protocolAddr1 := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addrWithPrefix1,
- }
- if err := s.AddProtocolAddress(nicID1, protocolAddr1, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID1, protocolAddr1, err)
- }
- addrWithPrefix2 := tcpip.AddressWithPrefix{Address: addr2, PrefixLen: defaultPrefixLen}
- protocolAddr2 := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addrWithPrefix2,
- }
- if err := s.AddProtocolAddress(nicID2, protocolAddr2, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID2, protocolAddr2, err)
- }
- expectDADEvent(nicID2, addr2)
- addrWithPrefix3 := tcpip.AddressWithPrefix{Address: addr3, PrefixLen: defaultPrefixLen}
- protocolAddr3 := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addrWithPrefix3,
- }
- if err := s.AddProtocolAddress(nicID3, protocolAddr3, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID3, protocolAddr3, err)
- }
- expectDADEvent(nicID3, addr3)
-
- // Address should not be considered bound to NIC(1) yet
- // (DAD ongoing).
- if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
- t.Fatal(err)
- }
-
- // Should get the address on NIC(2) and NIC(3)
- // immediately since we should not have performed DAD on
- // it as the stack was configured to not do DAD by
- // default and we only updated the NDP configurations on
- // NIC(1).
- if err := checkGetMainNICAddress(s, nicID2, header.IPv6ProtocolNumber, addrWithPrefix2); err != nil {
- t.Fatal(err)
- }
- if err := checkGetMainNICAddress(s, nicID3, header.IPv6ProtocolNumber, addrWithPrefix3); err != nil {
- t.Fatal(err)
- }
-
- // Sleep until right before resolution to make sure the address didn't
- // resolve on NIC(1) yet.
- const delta = 1
- clock.Advance(time.Duration(test.dupAddrDetectTransmits)*test.expectedRetransmitTimer - delta)
- if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
- t.Fatal(err)
- }
-
- // Wait for DAD to resolve.
- clock.Advance(delta)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID1, addr1, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for DAD resolution")
- }
- if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, addrWithPrefix1); err != nil {
- t.Fatal(err)
- }
- })
- }
-}
-
-// raBuf returns a valid NDP Router Advertisement with options, router
-// preference and DHCPv6 configurations specified.
-func raBuf(ip tcpip.Address, rl uint16, managedAddress, otherConfigurations bool, prf header.NDPRoutePreference, optSer header.NDPOptionsSerializer) *stack.PacketBuffer {
- const flagsByte = 1
- const routerLifetimeOffset = 2
-
- icmpSize := header.ICMPv6HeaderSize + header.NDPRAMinimumSize + optSer.Length()
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + icmpSize)
- pkt := header.ICMPv6(hdr.Prepend(icmpSize))
- pkt.SetType(header.ICMPv6RouterAdvert)
- pkt.SetCode(0)
- raPayload := pkt.MessageBody()
- ra := header.NDPRouterAdvert(raPayload)
- // Populate the Router Lifetime.
- binary.BigEndian.PutUint16(raPayload[routerLifetimeOffset:], rl)
- // Populate the Managed Address flag field.
- if managedAddress {
- // The Managed Addresses flag field is the 7th bit of the flags byte.
- raPayload[flagsByte] |= 1 << 7
- }
- // Populate the Other Configurations flag field.
- if otherConfigurations {
- // The Other Configurations flag field is the 6th bit of the flags byte.
- raPayload[flagsByte] |= 1 << 6
- }
- // The Prf field is held in the flags byte.
- raPayload[flagsByte] |= byte(prf) << 3
- opts := ra.Options()
- opts.Serialize(optSer)
- pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
- Header: pkt,
- Src: ip,
- Dst: header.IPv6AllNodesMulticastAddress,
- }))
- payloadLength := hdr.UsedLength()
- iph := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- iph.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- TransportProtocol: icmp.ProtocolNumber6,
- HopLimit: header.NDPHopLimit,
- SrcAddr: ip,
- DstAddr: header.IPv6AllNodesMulticastAddress,
- })
-
- return stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- })
-}
-
-// raBufWithOpts returns a valid NDP Router Advertisement with options.
-//
-// Note, raBufWithOpts does not populate any of the RA fields other than the
-// Router Lifetime.
-func raBufWithOpts(ip tcpip.Address, rl uint16, optSer header.NDPOptionsSerializer) *stack.PacketBuffer {
- return raBuf(ip, rl, false /* managedAddress */, false /* otherConfigurations */, 0 /* prf */, optSer)
-}
-
-// raBufWithDHCPv6 returns a valid NDP Router Advertisement with DHCPv6 related
-// fields set.
-//
-// Note, raBufWithDHCPv6 does not populate any of the RA fields other than the
-// DHCPv6 related ones.
-func raBufWithDHCPv6(ip tcpip.Address, managedAddresses, otherConfigurations bool) *stack.PacketBuffer {
- return raBuf(ip, 0, managedAddresses, otherConfigurations, 0 /* prf */, header.NDPOptionsSerializer{})
-}
-
-// raBuf returns a valid NDP Router Advertisement.
-//
-// Note, raBuf does not populate any of the RA fields other than the
-// Router Lifetime.
-func raBufSimple(ip tcpip.Address, rl uint16) *stack.PacketBuffer {
- return raBufWithOpts(ip, rl, header.NDPOptionsSerializer{})
-}
-
-// raBufWithPrf returns a valid NDP Router Advertisement with a preference.
-//
-// Note, raBufWithPrf does not populate any of the RA fields other than the
-// Router Lifetime and Default Router Preference fields.
-func raBufWithPrf(ip tcpip.Address, rl uint16, prf header.NDPRoutePreference) *stack.PacketBuffer {
- return raBuf(ip, rl, false /* managedAddress */, false /* otherConfigurations */, prf, header.NDPOptionsSerializer{})
-}
-
-// raBufWithPI returns a valid NDP Router Advertisement with a single Prefix
-// Information option.
-//
-// Note, raBufWithPI does not populate any of the RA fields other than the
-// Router Lifetime.
-func raBufWithPI(ip tcpip.Address, rl uint16, prefix tcpip.AddressWithPrefix, onLink, auto bool, vl, pl uint32) *stack.PacketBuffer {
- flags := uint8(0)
- if onLink {
- // The OnLink flag is the 7th bit in the flags byte.
- flags |= 1 << 7
- }
- if auto {
- // The Address Auto-Configuration flag is the 6th bit in the
- // flags byte.
- flags |= 1 << 6
- }
-
- // A valid header.NDPPrefixInformation must be 30 bytes.
- buf := [30]byte{}
- // The first byte in a header.NDPPrefixInformation is the Prefix Length
- // field.
- buf[0] = uint8(prefix.PrefixLen)
- // The 2nd byte within a header.NDPPrefixInformation is the Flags field.
- buf[1] = flags
- // The Valid Lifetime field starts after the 2nd byte within a
- // header.NDPPrefixInformation.
- binary.BigEndian.PutUint32(buf[2:], vl)
- // The Preferred Lifetime field starts after the 6th byte within a
- // header.NDPPrefixInformation.
- binary.BigEndian.PutUint32(buf[6:], pl)
- // The Prefix Address field starts after the 14th byte within a
- // header.NDPPrefixInformation.
- copy(buf[14:], prefix.Address)
- return raBufWithOpts(ip, rl, header.NDPOptionsSerializer{
- header.NDPPrefixInformation(buf[:]),
- })
-}
-
-// raBufWithRIO returns a valid NDP Router Advertisement with a single Route
-// Information option.
-//
-// All fields in the RA will be zero except the RIO option.
-func raBufWithRIO(t *testing.T, ip tcpip.Address, prefix tcpip.AddressWithPrefix, lifetimeSeconds uint32, prf header.NDPRoutePreference) *stack.PacketBuffer {
- // buf will hold the route information option after the Type and Length
- // fields.
- //
- // 2.3. Route Information Option
- //
- // 0 1 2 3
- // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- // | Type | Length | Prefix Length |Resvd|Prf|Resvd|
- // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- // | Route Lifetime |
- // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- // | Prefix (Variable Length) |
- // . .
- // . .
- // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- var buf [22]byte
- buf[0] = uint8(prefix.PrefixLen)
- buf[1] = byte(prf) << 3
- binary.BigEndian.PutUint32(buf[2:], lifetimeSeconds)
- if n := copy(buf[6:], prefix.Address); n != len(prefix.Address) {
- t.Fatalf("got copy(...) = %d, want = %d", n, len(prefix.Address))
- }
- return raBufWithOpts(ip, 0 /* router lifetime */, header.NDPOptionsSerializer{
- header.NDPRouteInformation(buf[:]),
- })
-}
-
-func TestDynamicConfigurationsDisabled(t *testing.T) {
- const (
- nicID = 1
- maxRtrSolicitDelay = time.Second
- )
-
- prefix := tcpip.AddressWithPrefix{
- Address: testutil.MustParse6("102:304:506:708::"),
- PrefixLen: 64,
- }
-
- tests := []struct {
- name string
- config func(bool) ipv6.NDPConfigurations
- ra *stack.PacketBuffer
- }{
- {
- name: "No Router Discovery",
- config: func(enable bool) ipv6.NDPConfigurations {
- return ipv6.NDPConfigurations{DiscoverDefaultRouters: enable}
- },
- ra: raBufSimple(llAddr2, 1000),
- },
- {
- name: "No Prefix Discovery",
- config: func(enable bool) ipv6.NDPConfigurations {
- return ipv6.NDPConfigurations{DiscoverOnLinkPrefixes: enable}
- },
- ra: raBufWithPI(llAddr2, 0, prefix, true, false, 10, 0),
- },
- {
- name: "No Autogenerate Addresses",
- config: func(enable bool) ipv6.NDPConfigurations {
- return ipv6.NDPConfigurations{AutoGenGlobalAddresses: enable}
- },
- ra: raBufWithPI(llAddr2, 0, prefix, false, true, 10, 0),
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- // Being configured to discover routers/prefixes or auto-generate
- // addresses means RAs must be handled, and router/prefix discovery or
- // SLAAC must be enabled.
- //
- // This tests all possible combinations of the configurations where
- // router/prefix discovery or SLAAC are disabled.
- for i := 0; i < 7; i++ {
- handle := ipv6.HandlingRAsDisabled
- if i&1 != 0 {
- handle = ipv6.HandlingRAsEnabledWhenForwardingDisabled
- }
- enable := i&2 != 0
- forwarding := i&4 == 0
-
- t.Run(fmt.Sprintf("HandleRAs(%s), Forwarding(%t), Enabled(%t)", handle, forwarding, enable), func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1),
- prefixC: make(chan ndpPrefixEvent, 1),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- ndpConfigs := test.config(enable)
- ndpConfigs.HandleRAs = handle
- ndpConfigs.MaxRtrSolicitations = 1
- ndpConfigs.RtrSolicitationInterval = maxRtrSolicitDelay
- ndpConfigs.MaxRtrSolicitationDelay = maxRtrSolicitDelay
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- Clock: clock,
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ndpConfigs,
- NDPDisp: &ndpDisp,
- })},
- })
- if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err)
- }
-
- e := channel.New(1, 1280, linkAddr1)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
- }
-
- handleRAsDisabled := handle == ipv6.HandlingRAsDisabled || forwarding
- ep, err := s.GetNetworkEndpoint(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, ipv6.ProtocolNumber, err)
- }
- stats := ep.Stats()
- v6Stats, ok := stats.(*ipv6.Stats)
- if !ok {
- t.Fatalf("got v6Stats = %T, expected = %T", stats, v6Stats)
- }
-
- // Make sure that when handling RAs are enabled, we solicit routers.
- clock.Advance(maxRtrSolicitDelay)
- if got, want := v6Stats.ICMP.PacketsSent.RouterSolicit.Value(), boolToUint64(!handleRAsDisabled); got != want {
- t.Errorf("got v6Stats.ICMP.PacketsSent.RouterSolicit.Value() = %d, want = %d", got, want)
- }
- if handleRAsDisabled {
- if p, ok := e.Read(); ok {
- t.Errorf("unexpectedly got a packet = %#v", p)
- }
- } else if p, ok := e.Read(); !ok {
- t.Error("expected router solicitation packet")
- } else if p.Proto != header.IPv6ProtocolNumber {
- t.Errorf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber)
- } else {
- if want := header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersLinkLocalMulticastAddress); p.Route.RemoteLinkAddress != want {
- t.Errorf("got remote link address = %s, want = %s", p.Route.RemoteLinkAddress, want)
- }
-
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(header.IPv6Any),
- checker.DstAddr(header.IPv6AllRoutersLinkLocalMulticastAddress),
- checker.TTL(header.NDPHopLimit),
- checker.NDPRS(checker.NDPRSOptions(nil)),
- )
- }
-
- // Make sure we do not discover any routers or prefixes, or perform
- // SLAAC on reception of an RA.
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra.Clone())
- // Make sure that the unhandled RA stat is only incremented when
- // handling RAs is disabled.
- if got, want := v6Stats.UnhandledRouterAdvertisements.Value(), boolToUint64(handleRAsDisabled); got != want {
- t.Errorf("got v6Stats.UnhandledRouterAdvertisements.Value() = %d, want = %d", got, want)
- }
- select {
- case e := <-ndpDisp.offLinkRouteC:
- t.Errorf("unexpectedly updated an off-link route when configured not to: %#v", e)
- default:
- }
- select {
- case e := <-ndpDisp.prefixC:
- t.Errorf("unexpectedly discovered a prefix when configured not to: %#v", e)
- default:
- }
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Errorf("unexpectedly auto-generated an address when configured not to: %#v", e)
- default:
- }
- })
- }
- })
- }
-}
-
-func boolToUint64(v bool) uint64 {
- if v {
- return 1
- }
- return 0
-}
-
-func checkOffLinkRouteEvent(e ndpOffLinkRouteEvent, nicID tcpip.NICID, subnet tcpip.Subnet, router tcpip.Address, prf header.NDPRoutePreference, updated bool) string {
- return cmp.Diff(ndpOffLinkRouteEvent{nicID: nicID, subnet: subnet, router: router, prf: prf, updated: updated}, e, cmp.AllowUnexported(e))
-}
-
-func testWithRAs(t *testing.T, f func(*testing.T, ipv6.HandleRAsConfiguration, bool)) {
- tests := [...]struct {
- name string
- handleRAs ipv6.HandleRAsConfiguration
- forwarding bool
- }{
- {
- name: "Handle RAs when forwarding disabled",
- handleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- forwarding: false,
- },
- {
- name: "Always Handle RAs with forwarding disabled",
- handleRAs: ipv6.HandlingRAsAlwaysEnabled,
- forwarding: false,
- },
- {
- name: "Always Handle RAs with forwarding enabled",
- handleRAs: ipv6.HandlingRAsAlwaysEnabled,
- forwarding: true,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- f(t, test.handleRAs, test.forwarding)
- })
- }
-}
-
-func TestOffLinkRouteDiscovery(t *testing.T) {
- const nicID = 1
-
- moreSpecificPrefix := tcpip.AddressWithPrefix{Address: testutil.MustParse6("a00::"), PrefixLen: 16}
- tests := []struct {
- name string
-
- discoverDefaultRouters bool
- discoverMoreSpecificRoutes bool
-
- dest tcpip.Subnet
- ra func(*testing.T, tcpip.Address, uint16, header.NDPRoutePreference) *stack.PacketBuffer
- }{
- {
- name: "Default router discovery",
- discoverDefaultRouters: true,
- discoverMoreSpecificRoutes: false,
- dest: header.IPv6EmptySubnet,
- ra: func(_ *testing.T, router tcpip.Address, lifetimeSeconds uint16, prf header.NDPRoutePreference) *stack.PacketBuffer {
- return raBufWithPrf(router, lifetimeSeconds, prf)
- },
- },
- {
- name: "More-specific route discovery",
- discoverDefaultRouters: false,
- discoverMoreSpecificRoutes: true,
- dest: moreSpecificPrefix.Subnet(),
- ra: func(t *testing.T, router tcpip.Address, lifetimeSeconds uint16, prf header.NDPRoutePreference) *stack.PacketBuffer {
- return raBufWithRIO(t, router, moreSpecificPrefix, uint32(lifetimeSeconds), prf)
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- testWithRAs(t, func(t *testing.T, handleRAs ipv6.HandleRAsConfiguration, forwarding bool) {
- ndpDisp := ndpDispatcher{
- offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: handleRAs,
- DiscoverDefaultRouters: test.discoverDefaultRouters,
- DiscoverMoreSpecificRoutes: test.discoverMoreSpecificRoutes,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- expectOffLinkRouteEvent := func(addr tcpip.Address, prf header.NDPRoutePreference, updated bool) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.offLinkRouteC:
- if diff := checkOffLinkRouteEvent(e, nicID, test.dest, addr, prf, updated); diff != "" {
- t.Errorf("off-link route event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected router discovery event")
- }
- }
-
- expectAsyncOffLinkRouteInvalidationEvent := func(addr tcpip.Address, timeout time.Duration) {
- t.Helper()
-
- clock.Advance(timeout)
- select {
- case e := <-ndpDisp.offLinkRouteC:
- var prf header.NDPRoutePreference
- if diff := checkOffLinkRouteEvent(e, nicID, test.dest, addr, prf, false); diff != "" {
- t.Errorf("off-link route event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for router discovery event")
- }
- }
-
- if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err)
- }
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
- }
-
- // Rx an RA from lladdr2 with zero lifetime. It should not be
- // remembered.
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 0, header.MediumRoutePreference))
- select {
- case <-ndpDisp.offLinkRouteC:
- t.Fatal("unexpectedly updated an off-link route with 0 lifetime")
- default:
- }
-
- // Discover an off-link route through llAddr2.
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 1000, header.ReservedRoutePreference))
- if test.discoverMoreSpecificRoutes {
- // The reserved value is considered invalid with more-specific route
- // discovery so we inject the same packet but with the default
- // (medium) preference value.
- select {
- case <-ndpDisp.offLinkRouteC:
- t.Fatal("unexpectedly updated an off-link route with a reserved preference value")
- default:
- }
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 1000, header.MediumRoutePreference))
- }
- expectOffLinkRouteEvent(llAddr2, header.MediumRoutePreference, true)
-
- // Rx an RA from another router (lladdr3) with non-zero lifetime and
- // non-default preference value.
- const l3LifetimeSeconds = 6
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr3, l3LifetimeSeconds, header.HighRoutePreference))
- expectOffLinkRouteEvent(llAddr3, header.HighRoutePreference, true)
-
- // Rx an RA from lladdr2 with lesser lifetime and default (medium)
- // preference value.
- const l2LifetimeSeconds = 2
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, l2LifetimeSeconds, header.MediumRoutePreference))
- select {
- case <-ndpDisp.offLinkRouteC:
- t.Fatal("should not receive a off-link route event when updating lifetimes for known routers")
- default:
- }
-
- // Rx an RA from lladdr2 with a different preference.
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, l2LifetimeSeconds, header.LowRoutePreference))
- expectOffLinkRouteEvent(llAddr2, header.LowRoutePreference, true)
-
- // Wait for lladdr2's router invalidation job to execute. The lifetime
- // of the router should have been updated to the most recent (smaller)
- // lifetime.
- //
- // Wait for the normal lifetime plus an extra bit for the
- // router to get invalidated. If we don't get an invalidation
- // event after this time, then something is wrong.
- expectAsyncOffLinkRouteInvalidationEvent(llAddr2, l2LifetimeSeconds*time.Second)
-
- // Rx an RA from lladdr2 with huge lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 1000, header.MediumRoutePreference))
- expectOffLinkRouteEvent(llAddr2, header.MediumRoutePreference, true)
-
- // Rx an RA from lladdr2 with zero lifetime. It should be invalidated.
- e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 0, header.MediumRoutePreference))
- expectOffLinkRouteEvent(llAddr2, header.MediumRoutePreference, false)
-
- // Wait for lladdr3's router invalidation job to execute. The lifetime
- // of the router should have been updated to the most recent (smaller)
- // lifetime.
- //
- // Wait for the normal lifetime plus an extra bit for the
- // router to get invalidated. If we don't get an invalidation
- // event after this time, then something is wrong.
- expectAsyncOffLinkRouteInvalidationEvent(llAddr3, l3LifetimeSeconds*time.Second)
- })
- })
- }
-}
-
-// TestRouterDiscoveryMaxRouters tests that only
-// ipv6.MaxDiscoveredOffLinkRoutes discovered routers are remembered.
-func TestRouterDiscoveryMaxRouters(t *testing.T) {
- const nicID = 1
-
- ndpDisp := ndpDispatcher{
- offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- DiscoverDefaultRouters: true,
- },
- NDPDisp: &ndpDisp,
- })},
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
- }
-
- // Receive an RA from 2 more than the max number of discovered routers.
- for i := 1; i <= ipv6.MaxDiscoveredOffLinkRoutes+2; i++ {
- linkAddr := []byte{2, 2, 3, 4, 5, 0}
- linkAddr[5] = byte(i)
- llAddr := header.LinkLocalAddr(tcpip.LinkAddress(linkAddr))
-
- e.InjectInbound(header.IPv6ProtocolNumber, raBufSimple(llAddr, 5))
-
- if i <= ipv6.MaxDiscoveredOffLinkRoutes {
- select {
- case e := <-ndpDisp.offLinkRouteC:
- if diff := checkOffLinkRouteEvent(e, nicID, header.IPv6EmptySubnet, llAddr, header.MediumRoutePreference, true); diff != "" {
- t.Errorf("off-link route event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected router discovery event")
- }
-
- } else {
- select {
- case <-ndpDisp.offLinkRouteC:
- t.Fatal("should not have discovered a new router after we already discovered the max number of routers")
- default:
- }
- }
- }
-}
-
-// Check e to make sure that the event is for prefix on nic with ID 1, and the
-// discovered flag set to discovered.
-func checkPrefixEvent(e ndpPrefixEvent, prefix tcpip.Subnet, discovered bool) string {
- return cmp.Diff(ndpPrefixEvent{nicID: 1, prefix: prefix, discovered: discovered}, e, cmp.AllowUnexported(e))
-}
-
-func TestPrefixDiscovery(t *testing.T) {
- prefix1, subnet1, _ := prefixSubnetAddr(0, "")
- prefix2, subnet2, _ := prefixSubnetAddr(1, "")
- prefix3, subnet3, _ := prefixSubnetAddr(2, "")
-
- testWithRAs(t, func(t *testing.T, handleRAs ipv6.HandleRAsConfiguration, forwarding bool) {
- ndpDisp := ndpDispatcher{
- prefixC: make(chan ndpPrefixEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: handleRAs,
- DiscoverOnLinkPrefixes: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- expectPrefixEvent := func(prefix tcpip.Subnet, discovered bool) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.prefixC:
- if diff := checkPrefixEvent(e, prefix, discovered); diff != "" {
- t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected prefix discovery event")
- }
- }
-
- if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err)
- }
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with zero valid lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 0, 0))
- select {
- case <-ndpDisp.prefixC:
- t.Fatal("unexpectedly discovered a prefix with 0 lifetime")
- default:
- }
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with non-zero lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 100, 0))
- expectPrefixEvent(subnet1, true)
-
- // Receive an RA with prefix2 in a PI.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, false, 100, 0))
- expectPrefixEvent(subnet2, true)
-
- // Receive an RA with prefix3 in a PI.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix3, true, false, 100, 0))
- expectPrefixEvent(subnet3, true)
-
- // Receive an RA with prefix1 in a PI with lifetime = 0.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 0, 0))
- expectPrefixEvent(subnet1, false)
-
- // Receive an RA with prefix2 in a PI with lesser lifetime.
- lifetime := uint32(2)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, false, lifetime, 0))
- select {
- case <-ndpDisp.prefixC:
- t.Fatal("unexpectedly received prefix event when updating lifetime")
- default:
- }
-
- // Wait for prefix2's most recent invalidation job plus some buffer to
- // expire.
- clock.Advance(time.Duration(lifetime) * time.Second)
- select {
- case e := <-ndpDisp.prefixC:
- if diff := checkPrefixEvent(e, subnet2, false); diff != "" {
- t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for prefix discovery event")
- }
-
- // Receive RA to invalidate prefix3.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix3, true, false, 0, 0))
- expectPrefixEvent(subnet3, false)
- })
-}
-
-func TestPrefixDiscoveryWithInfiniteLifetime(t *testing.T) {
- prefix := tcpip.AddressWithPrefix{
- Address: testutil.MustParse6("102:304:506:708::"),
- PrefixLen: 64,
- }
- subnet := prefix.Subnet()
-
- ndpDisp := ndpDispatcher{
- prefixC: make(chan ndpPrefixEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- DiscoverOnLinkPrefixes: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- expectPrefixEvent := func(prefix tcpip.Subnet, discovered bool) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.prefixC:
- if diff := checkPrefixEvent(e, prefix, discovered); diff != "" {
- t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected prefix discovery event")
- }
- }
-
- // Receive an RA with prefix in an NDP Prefix Information option (PI)
- // with infinite valid lifetime which should not get invalidated.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, infiniteLifetimeSeconds, 0))
- expectPrefixEvent(subnet, true)
- clock.Advance(header.NDPInfiniteLifetime)
- select {
- case <-ndpDisp.prefixC:
- t.Fatal("unexpectedly invalidated a prefix with infinite lifetime")
- default:
- }
-
- // Receive an RA with finite lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, infiniteLifetimeSeconds-1, 0))
- clock.Advance(header.NDPInfiniteLifetime - time.Second)
- select {
- case e := <-ndpDisp.prefixC:
- if diff := checkPrefixEvent(e, subnet, false); diff != "" {
- t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for prefix discovery event")
- }
-
- // Receive an RA with finite lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, infiniteLifetimeSeconds-1, 0))
- expectPrefixEvent(subnet, true)
-
- // Receive an RA with prefix with an infinite lifetime.
- // The prefix should not be invalidated.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, infiniteLifetimeSeconds, 0))
- clock.Advance(header.NDPInfiniteLifetime)
- select {
- case <-ndpDisp.prefixC:
- t.Fatal("unexpectedly invalidated a prefix with infinite lifetime")
- default:
- }
-
- // Receive an RA with 0 lifetime.
- // The prefix should get invalidated.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, 0, 0))
- expectPrefixEvent(subnet, false)
-}
-
-// TestPrefixDiscoveryMaxRouters tests that only
-// ipv6.MaxDiscoveredOnLinkPrefixes discovered on-link prefixes are remembered.
-func TestPrefixDiscoveryMaxOnLinkPrefixes(t *testing.T) {
- ndpDisp := ndpDispatcher{
- prefixC: make(chan ndpPrefixEvent, ipv6.MaxDiscoveredOnLinkPrefixes+3),
- }
- e := channel.New(0, 1280, linkAddr1)
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- DiscoverDefaultRouters: false,
- DiscoverOnLinkPrefixes: true,
- },
- NDPDisp: &ndpDisp,
- })},
- })
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- optSer := make(header.NDPOptionsSerializer, ipv6.MaxDiscoveredOnLinkPrefixes+2)
- prefixes := [ipv6.MaxDiscoveredOnLinkPrefixes + 2]tcpip.Subnet{}
-
- // Receive an RA with 2 more than the max number of discovered on-link
- // prefixes.
- for i := 0; i < ipv6.MaxDiscoveredOnLinkPrefixes+2; i++ {
- prefixAddr := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0}
- prefixAddr[7] = byte(i)
- prefix := tcpip.AddressWithPrefix{
- Address: tcpip.Address(prefixAddr[:]),
- PrefixLen: 64,
- }
- prefixes[i] = prefix.Subnet()
- buf := [30]byte{}
- buf[0] = uint8(prefix.PrefixLen)
- buf[1] = 128
- binary.BigEndian.PutUint32(buf[2:], 10)
- copy(buf[14:], prefix.Address)
-
- optSer[i] = header.NDPPrefixInformation(buf[:])
- }
-
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, optSer))
- for i := 0; i < ipv6.MaxDiscoveredOnLinkPrefixes+2; i++ {
- if i < ipv6.MaxDiscoveredOnLinkPrefixes {
- select {
- case e := <-ndpDisp.prefixC:
- if diff := checkPrefixEvent(e, prefixes[i], true); diff != "" {
- t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected prefix discovery event")
- }
- } else {
- select {
- case <-ndpDisp.prefixC:
- t.Fatal("should not have discovered a new prefix after we already discovered the max number of prefixes")
- default:
- }
- }
- }
-}
-
-// Checks to see if list contains an IPv6 address, item.
-func containsV6Addr(list []tcpip.ProtocolAddress, item tcpip.AddressWithPrefix) bool {
- protocolAddress := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: item,
- }
-
- return containsAddr(list, protocolAddress)
-}
-
-// Check e to make sure that the event is for addr on nic with ID 1, and the
-// event type is set to eventType.
-func checkAutoGenAddrEvent(e ndpAutoGenAddrEvent, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) string {
- return cmp.Diff(ndpAutoGenAddrEvent{nicID: 1, addr: addr, eventType: eventType}, e, cmp.AllowUnexported(e))
-}
-
-const minVLSeconds = uint32(ipv6.MinPrefixInformationValidLifetimeForUpdate / time.Second)
-const infiniteLifetimeSeconds = uint32(header.NDPInfiniteLifetime / time.Second)
-
-// TestAutoGenAddr tests that an address is properly generated and invalidated
-// when configured to do so.
-func TestAutoGenAddr(t *testing.T) {
- prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
- prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-
- testWithRAs(t, func(t *testing.T, handleRAs ipv6.HandleRAsConfiguration, forwarding bool) {
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: handleRAs,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err)
- }
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with zero valid lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 0, 0))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly auto-generated an address with 0 lifetime")
- default:
- }
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with non-zero lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
- expectAutoGenAddrEvent(addr1, newAddr)
- if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) {
- t.Fatalf("Should have %s in the list of addresses", addr1)
- }
-
- // Receive an RA with prefix2 in an NDP Prefix Information option (PI)
- // with preferred lifetime > valid lifetime
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 5, 6))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly auto-generated an address with preferred lifetime > valid lifetime")
- default:
- }
-
- // Receive an RA with prefix2 in a PI with a valid lifetime that exceeds
- // the minimum.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, minVLSeconds+1, 0))
- expectAutoGenAddrEvent(addr2, newAddr)
- if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) {
- t.Fatalf("Should have %s in the list of addresses", addr1)
- }
- if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr2) {
- t.Fatalf("Should have %s in the list of addresses", addr2)
- }
-
- // Refresh valid lifetime for addr of prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly auto-generated an address when we already have an address for a prefix")
- default:
- }
-
- // Wait for addr of prefix1 to be invalidated.
- clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- if containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) {
- t.Fatalf("Should not have %s in the list of addresses", addr1)
- }
- if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr2) {
- t.Fatalf("Should have %s in the list of addresses", addr2)
- }
- })
-}
-
-func addressCheck(addrs []tcpip.ProtocolAddress, containList, notContainList []tcpip.AddressWithPrefix) string {
- ret := ""
- for _, c := range containList {
- if !containsV6Addr(addrs, c) {
- ret += fmt.Sprintf("should have %s in the list of addresses\n", c)
- }
- }
- for _, c := range notContainList {
- if containsV6Addr(addrs, c) {
- ret += fmt.Sprintf("should not have %s in the list of addresses\n", c)
- }
- }
- return ret
-}
-
-// TestAutoGenTempAddr tests that temporary SLAAC addresses are generated when
-// configured to do so as part of IPv6 Privacy Extensions.
-func TestAutoGenTempAddr(t *testing.T) {
- const nicID = 1
-
- prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
- prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-
- tests := []struct {
- name string
- dupAddrTransmits uint8
- retransmitTimer time.Duration
- }{
- {
- name: "DAD disabled",
- },
- {
- name: "DAD enabled",
- dupAddrTransmits: 1,
- retransmitTimer: time.Second,
- },
- }
-
- for i, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- seed := []byte{uint8(i)}
- var tempIIDHistory [header.IIDSize]byte
- header.InitialTempIID(tempIIDHistory[:], seed, nicID)
- newTempAddr := func(stableAddr tcpip.Address) tcpip.AddressWithPrefix {
- return header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], stableAddr)
- }
-
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 2),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- DADConfigs: stack.DADConfigurations{
- DupAddrDetectTransmits: test.dupAddrTransmits,
- RetransmitTimer: test.retransmitTimer,
- },
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- AutoGenTempGlobalAddresses: true,
- MaxTempAddrValidLifetime: 2 * ipv6.MinPrefixInformationValidLifetimeForUpdate,
- MaxTempAddrPreferredLifetime: 2 * ipv6.MinPrefixInformationValidLifetimeForUpdate,
- },
- NDPDisp: &ndpDisp,
- TempIIDSeed: seed,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- clock.RunImmediatelyScheduledJobs()
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- }
-
- expectDADEventAsync := func(addr tcpip.Address) {
- t.Helper()
-
- clock.Advance(time.Duration(test.dupAddrTransmits) * test.retransmitTimer)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for DAD event")
- }
- }
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with zero valid lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 0, 0))
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpectedly auto-generated an address with 0 lifetime; event = %+v", e)
- default:
- }
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with non-zero valid lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
- expectAutoGenAddrEvent(addr1, newAddr)
- expectDADEventAsync(addr1.Address)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpectedly got an auto gen addr event = %+v", e)
- default:
- }
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with non-zero valid & preferred lifetimes.
- tempAddr1 := newTempAddr(addr1.Address)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100))
- expectAutoGenAddrEvent(tempAddr1, newAddr)
- expectDADEventAsync(tempAddr1.Address)
- if mismatch := addressCheck(s.NICInfo()[1].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Receive an RA with prefix2 in an NDP Prefix Information option (PI)
- // with preferred lifetime > valid lifetime
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 5, 6))
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpectedly auto-generated an address with preferred lifetime > valid lifetime; event = %+v", e)
- default:
- }
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Receive an RA with prefix2 in a PI with a valid lifetime that exceeds
- // the minimum and won't be reached in this test.
- tempAddr2 := newTempAddr(addr2.Address)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 2*minVLSeconds, 2*minVLSeconds))
- expectAutoGenAddrEvent(addr2, newAddr)
- expectDADEventAsync(addr2.Address)
- expectAutoGenAddrEventAsync(tempAddr2, newAddr)
- expectDADEventAsync(tempAddr2.Address)
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Deprecate prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
- expectAutoGenAddrEvent(addr1, deprecatedAddr)
- expectAutoGenAddrEvent(tempAddr1, deprecatedAddr)
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Refresh lifetimes for prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100))
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Reduce valid lifetime and deprecate addresses of prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, minVLSeconds, 0))
- expectAutoGenAddrEvent(addr1, deprecatedAddr)
- expectAutoGenAddrEvent(tempAddr1, deprecatedAddr)
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Wait for addrs of prefix1 to be invalidated. They should be
- // invalidated at the same time.
- clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- var nextAddr tcpip.AddressWithPrefix
- if e.addr == addr1 {
- if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- nextAddr = tempAddr1
- } else {
- if diff := checkAutoGenAddrEvent(e, tempAddr1, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- nextAddr = addr1
- }
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, nextAddr, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr2, tempAddr2}, []tcpip.AddressWithPrefix{addr1, tempAddr1}); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Receive an RA with prefix2 in a PI w/ 0 lifetimes.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 0, 0))
- expectAutoGenAddrEvent(addr2, deprecatedAddr)
- expectAutoGenAddrEvent(tempAddr2, deprecatedAddr)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Errorf("got unexpected auto gen addr event = %+v", e)
- default:
- }
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr2, tempAddr2}, []tcpip.AddressWithPrefix{addr1, tempAddr1}); mismatch != "" {
- t.Fatal(mismatch)
- }
- })
- }
-}
-
-// TestNoAutoGenTempAddrForLinkLocal test that temporary SLAAC addresses are not
-// generated for auto generated link-local addresses.
-func TestNoAutoGenTempAddrForLinkLocal(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- dupAddrTransmits uint8
- retransmitTimer time.Duration
- }{
- {
- name: "DAD disabled",
- },
- {
- name: "DAD enabled",
- dupAddrTransmits: 1,
- retransmitTimer: time.Second,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- AutoGenTempGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- AutoGenLinkLocal: true,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- // The stable link-local address should auto-generate and resolve DAD.
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, tcpip.AddressWithPrefix{Address: llAddr1, PrefixLen: header.IIDOffsetInIPv6Address * 8}, newAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- clock.Advance(time.Duration(test.dupAddrTransmits) * test.retransmitTimer)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, llAddr1, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for DAD event")
- }
-
- // No new addresses should be generated.
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Errorf("got unxpected auto gen addr event = %+v", e)
- default:
- }
- })
- }
-}
-
-// TestNoAutoGenTempAddrWithoutStableAddr tests that a temporary SLAAC address
-// will not be generated until after DAD completes, even if a new Router
-// Advertisement is received to refresh lifetimes.
-func TestNoAutoGenTempAddrWithoutStableAddr(t *testing.T) {
- const (
- nicID = 1
- dadTransmits = 1
- retransmitTimer = 2 * time.Second
- )
-
- prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
- var tempIIDHistory [header.IIDSize]byte
- header.InitialTempIID(tempIIDHistory[:], nil, nicID)
- tempAddr := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
-
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- DADConfigs: stack.DADConfigurations{
- DupAddrDetectTransmits: dadTransmits,
- RetransmitTimer: retransmitTimer,
- },
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- AutoGenTempGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- // Receive an RA to trigger SLAAC for prefix.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
-
- // DAD on the stable address for prefix has not yet completed. Receiving a new
- // RA that would refresh lifetimes should not generate a temporary SLAAC
- // address for the prefix.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpected auto gen addr event = %+v", e)
- default:
- }
-
- // Wait for DAD to complete for the stable address then expect the temporary
- // address to be generated.
- clock.Advance(dadTransmits * retransmitTimer)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for DAD event")
- }
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, tempAddr, newAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
-}
-
-// TestAutoGenTempAddrRegen tests that temporary SLAAC addresses are
-// regenerated.
-func TestAutoGenTempAddrRegen(t *testing.T) {
- const (
- nicID = 1
- regenAdv = 2 * time.Second
-
- numTempAddrs = 3
- maxTempAddrValidLifetime = numTempAddrs * ipv6.MinPrefixInformationValidLifetimeForUpdate
- )
-
- prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
- var tempIIDHistory [header.IIDSize]byte
- header.InitialTempIID(tempIIDHistory[:], nil, nicID)
- var tempAddrs [numTempAddrs]tcpip.AddressWithPrefix
- for i := 0; i < len(tempAddrs); i++ {
- tempAddrs[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
- }
-
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
- }
- e := channel.New(0, 1280, linkAddr1)
- ndpConfigs := ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- AutoGenTempGlobalAddresses: true,
- RegenAdvanceDuration: regenAdv,
- MaxTempAddrValidLifetime: maxTempAddrValidLifetime,
- MaxTempAddrPreferredLifetime: ipv6.MinPrefixInformationValidLifetimeForUpdate,
- }
- clock := faketime.NewManualClock()
- randSource := savingRandSource{
- s: rand.NewSource(time.Now().UnixNano()),
- }
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ndpConfigs,
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- RandSource: &randSource,
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) {
- t.Helper()
-
- clock.Advance(timeout)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- }
-
- tempDesyncFactor := time.Duration(randSource.lastInt63) % ipv6.MaxDesyncFactor
- effectiveMaxTempAddrPL := ipv6.MinPrefixInformationValidLifetimeForUpdate - tempDesyncFactor
- // The time since the last regeneration before a new temporary address is
- // generated.
- tempAddrRegenenerationTime := effectiveMaxTempAddrPL - regenAdv
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with non-zero valid & preferred lifetimes.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, minVLSeconds))
- expectAutoGenAddrEvent(addr, newAddr)
- expectAutoGenAddrEvent(tempAddrs[0], newAddr)
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0]}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Wait for regeneration
- expectAutoGenAddrEventAsync(tempAddrs[1], newAddr, tempAddrRegenenerationTime)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, minVLSeconds))
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0], tempAddrs[1]}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
- expectAutoGenAddrEventAsync(tempAddrs[0], deprecatedAddr, regenAdv)
-
- // Wait for regeneration
- expectAutoGenAddrEventAsync(tempAddrs[2], newAddr, tempAddrRegenenerationTime-regenAdv)
- expectAutoGenAddrEventAsync(tempAddrs[1], deprecatedAddr, regenAdv)
-
- // Stop generating temporary addresses
- ndpConfigs.AutoGenTempGlobalAddresses = false
- if ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber); err != nil {
- t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
- } else {
- ndpEP := ipv6Ep.(ipv6.NDPEndpoint)
- ndpEP.SetNDPConfigurations(ndpConfigs)
- }
-
- // Refresh lifetimes and wait for the last temporary address to be deprecated.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, minVLSeconds))
- expectAutoGenAddrEventAsync(tempAddrs[2], deprecatedAddr, effectiveMaxTempAddrPL-regenAdv)
-
- // Refresh lifetimes such that the prefix is valid and preferred forever.
- //
- // This should not affect the lifetimes of temporary addresses because they
- // are capped by the maximum valid and preferred lifetimes for temporary
- // addresses.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, infiniteLifetimeSeconds, infiniteLifetimeSeconds))
-
- // Wait for all the temporary addresses to get invalidated.
- invalidateAfter := maxTempAddrValidLifetime - clock.NowMonotonic().Sub(tcpip.MonotonicTime{})
- for _, addr := range tempAddrs {
- expectAutoGenAddrEventAsync(addr, invalidatedAddr, invalidateAfter)
- invalidateAfter = tempAddrRegenenerationTime
- }
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr}, tempAddrs[:]); mismatch != "" {
- t.Fatal(mismatch)
- }
-}
-
-// TestAutoGenTempAddrRegenJobUpdates tests that a temporary address's
-// regeneration job gets updated when refreshing the address's lifetimes.
-func TestAutoGenTempAddrRegenJobUpdates(t *testing.T) {
- const (
- nicID = 1
- regenAdv = 2 * time.Second
-
- numTempAddrs = 3
- maxTempAddrPreferredLifetime = ipv6.MinPrefixInformationValidLifetimeForUpdate
- maxTempAddrPreferredLifetimeSeconds = uint32(maxTempAddrPreferredLifetime / time.Second)
- )
-
- prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
- var tempIIDHistory [header.IIDSize]byte
- header.InitialTempIID(tempIIDHistory[:], nil, nicID)
- var tempAddrs [numTempAddrs]tcpip.AddressWithPrefix
- for i := 0; i < len(tempAddrs); i++ {
- tempAddrs[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
- }
-
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
- }
- e := channel.New(0, 1280, linkAddr1)
- ndpConfigs := ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- AutoGenTempGlobalAddresses: true,
- RegenAdvanceDuration: regenAdv,
- MaxTempAddrPreferredLifetime: maxTempAddrPreferredLifetime,
- MaxTempAddrValidLifetime: maxTempAddrPreferredLifetime * 2,
- }
- clock := faketime.NewManualClock()
- initialTime := clock.NowMonotonic()
- randSource := savingRandSource{
- s: rand.NewSource(time.Now().UnixNano()),
- }
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ndpConfigs,
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- RandSource: &randSource,
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- tempDesyncFactor := time.Duration(randSource.lastInt63) % ipv6.MaxDesyncFactor
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) {
- t.Helper()
-
- clock.Advance(timeout)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- }
-
- // Receive an RA with prefix1 in an NDP Prefix Information option (PI)
- // with non-zero valid & preferred lifetimes.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, maxTempAddrPreferredLifetimeSeconds, maxTempAddrPreferredLifetimeSeconds))
- expectAutoGenAddrEvent(addr, newAddr)
- expectAutoGenAddrEvent(tempAddrs[0], newAddr)
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0]}, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Deprecate the prefix.
- //
- // A new temporary address should be generated after the regeneration
- // time has passed since the prefix is deprecated.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, maxTempAddrPreferredLifetimeSeconds, 0))
- expectAutoGenAddrEvent(addr, deprecatedAddr)
- expectAutoGenAddrEvent(tempAddrs[0], deprecatedAddr)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpected auto gen addr event = %#v", e)
- default:
- }
-
- effectiveMaxTempAddrPL := maxTempAddrPreferredLifetime - tempDesyncFactor
- // The time since the last regeneration before a new temporary address is
- // generated.
- tempAddrRegenenerationTime := effectiveMaxTempAddrPL - regenAdv
-
- // Advance the clock by the regeneration time but don't expect a new temporary
- // address as the prefix is deprecated.
- clock.Advance(tempAddrRegenenerationTime)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpected auto gen addr event = %#v", e)
- default:
- }
-
- // Prefer the prefix again.
- //
- // A new temporary address should immediately be generated since the
- // regeneration time has already passed since the last address was generated
- // - this regeneration does not depend on a job.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, maxTempAddrPreferredLifetimeSeconds, maxTempAddrPreferredLifetimeSeconds))
- expectAutoGenAddrEvent(tempAddrs[1], newAddr)
- // Wait for the first temporary address to be deprecated.
- expectAutoGenAddrEventAsync(tempAddrs[0], deprecatedAddr, regenAdv)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpected auto gen addr event = %s", e)
- default:
- }
-
- // Increase the maximum lifetimes for temporary addresses to large values
- // then refresh the lifetimes of the prefix.
- //
- // A new address should not be generated after the regeneration time that was
- // expected for the previous check. This is because the preferred lifetime for
- // the temporary addresses has increased, so it will take more time to
- // regenerate a new temporary address. Note, new addresses are only
- // regenerated after the preferred lifetime - the regenerate advance duration
- // as paased.
- const largeLifetimeSeconds = minVLSeconds * 2
- const largeLifetime = time.Duration(largeLifetimeSeconds) * time.Second
- ndpConfigs.MaxTempAddrValidLifetime = 2 * largeLifetime
- ndpConfigs.MaxTempAddrPreferredLifetime = largeLifetime
- ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber)
- if err != nil {
- t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
- }
- ndpEP := ipv6Ep.(ipv6.NDPEndpoint)
- ndpEP.SetNDPConfigurations(ndpConfigs)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
- timeSinceInitialTime := clock.NowMonotonic().Sub(initialTime)
- clock.Advance(largeLifetime - timeSinceInitialTime)
- expectAutoGenAddrEvent(tempAddrs[0], deprecatedAddr)
- // to offset the advement of time to test the first temporary address's
- // deprecation after the second was generated
- advLess := regenAdv
- expectAutoGenAddrEventAsync(tempAddrs[2], newAddr, timeSinceInitialTime-advLess-(tempDesyncFactor+regenAdv))
- expectAutoGenAddrEventAsync(tempAddrs[1], deprecatedAddr, regenAdv)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpected auto gen addr event = %+v", e)
- default:
- }
-}
-
-// TestMixedSLAACAddrConflictRegen tests SLAAC address regeneration in response
-// to a mix of DAD conflicts and NIC-local conflicts.
-func TestMixedSLAACAddrConflictRegen(t *testing.T) {
- const (
- nicID = 1
- nicName = "nic"
- lifetimeSeconds = 9999
- // From stack.maxSLAACAddrLocalRegenAttempts
- maxSLAACAddrLocalRegenAttempts = 10
- // We use 2 more addreses than the maximum local regeneration attempts
- // because we want to also trigger regeneration in response to a DAD
- // conflicts for this test.
- maxAddrs = maxSLAACAddrLocalRegenAttempts + 2
- dupAddrTransmits = 1
- retransmitTimer = time.Second
- )
-
- var tempIIDHistoryWithModifiedEUI64 [header.IIDSize]byte
- header.InitialTempIID(tempIIDHistoryWithModifiedEUI64[:], nil, nicID)
-
- var tempIIDHistoryWithOpaqueIID [header.IIDSize]byte
- header.InitialTempIID(tempIIDHistoryWithOpaqueIID[:], nil, nicID)
-
- prefix, subnet, stableAddrWithModifiedEUI64 := prefixSubnetAddr(0, linkAddr1)
- var stableAddrsWithOpaqueIID [maxAddrs]tcpip.AddressWithPrefix
- var tempAddrsWithOpaqueIID [maxAddrs]tcpip.AddressWithPrefix
- var tempAddrsWithModifiedEUI64 [maxAddrs]tcpip.AddressWithPrefix
- addrBytes := []byte(subnet.ID())
- for i := 0; i < maxAddrs; i++ {
- stableAddrsWithOpaqueIID[i] = tcpip.AddressWithPrefix{
- Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, uint8(i), nil)),
- PrefixLen: header.IIDOffsetInIPv6Address * 8,
- }
- // When generating temporary addresses, the resolved stable address for the
- // SLAAC prefix will be the first address stable address generated for the
- // prefix as we will not simulate address conflicts for the stable addresses
- // in tests involving temporary addresses. Address conflicts for stable
- // addresses will be done in their own tests.
- tempAddrsWithOpaqueIID[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistoryWithOpaqueIID[:], stableAddrsWithOpaqueIID[0].Address)
- tempAddrsWithModifiedEUI64[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistoryWithModifiedEUI64[:], stableAddrWithModifiedEUI64.Address)
- }
-
- tests := []struct {
- name string
- addrs []tcpip.AddressWithPrefix
- tempAddrs bool
- initialExpect tcpip.AddressWithPrefix
- maxAddrs int
- nicNameFromID func(tcpip.NICID, string) string
- }{
- {
- name: "Stable addresses with opaque IIDs",
- addrs: stableAddrsWithOpaqueIID[:],
- maxAddrs: 1,
- nicNameFromID: func(tcpip.NICID, string) string {
- return nicName
- },
- },
- {
- name: "Temporary addresses with opaque IIDs",
- addrs: tempAddrsWithOpaqueIID[:],
- tempAddrs: true,
- initialExpect: stableAddrsWithOpaqueIID[0],
- maxAddrs: 1 /* initial (stable) address */ + maxSLAACAddrLocalRegenAttempts,
- nicNameFromID: func(tcpip.NICID, string) string {
- return nicName
- },
- },
- {
- name: "Temporary addresses with modified EUI64",
- addrs: tempAddrsWithModifiedEUI64[:],
- tempAddrs: true,
- maxAddrs: 1 /* initial (stable) address */ + maxSLAACAddrLocalRegenAttempts,
- initialExpect: stableAddrWithModifiedEUI64,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- // We may receive a deprecated and invalidated event for each SLAAC
- // address that is assigned.
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAddrs*2),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- Clock: clock,
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- AutoGenTempGlobalAddresses: test.tempAddrs,
- AutoGenAddressConflictRetries: 1,
- },
- NDPDisp: &ndpDisp,
- OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
- NICNameFromID: test.nicNameFromID,
- },
- })},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
-
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv6EmptySubnet,
- Gateway: llAddr2,
- NIC: nicID,
- }})
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- manuallyAssignedAddresses := make(map[tcpip.Address]struct{})
- for j := 0; j < len(test.addrs)-1; j++ {
- // The NIC will not attempt to generate an address in response to a
- // NIC-local conflict after some maximum number of attempts. We skip
- // creating a conflict for the address that would be generated as part
- // of the last attempt so we can simulate a DAD conflict for this
- // address and restart the NIC-local generation process.
- if j == maxSLAACAddrLocalRegenAttempts-1 {
- continue
- }
-
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: ipv6.ProtocolNumber,
- AddressWithPrefix: test.addrs[j].Address.WithPrefix(),
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
- }
-
- manuallyAssignedAddresses[test.addrs[j].Address] = struct{}{}
- }
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- expectAutoGenAddrAsyncEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- if diff := checkAutoGenAddrEvent(<-ndpDisp.autoGenAddrC, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- }
-
- expectDADEventAsync := func(addr tcpip.Address) {
- t.Helper()
-
- clock.Advance(dupAddrTransmits * retransmitTimer)
- if diff := checkDADEvent(<-ndpDisp.dadC, nicID, addr, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- }
-
- // Enable DAD.
- ndpDisp.dadC = make(chan ndpDADEvent, 2)
- if ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber); err != nil {
- t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
- } else {
- ndpEP := ipv6Ep.(stack.DuplicateAddressDetector)
- ndpEP.SetDADConfigurations(stack.DADConfigurations{
- DupAddrDetectTransmits: dupAddrTransmits,
- RetransmitTimer: retransmitTimer,
- })
- }
-
- // Do SLAAC for prefix.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
- if test.initialExpect != (tcpip.AddressWithPrefix{}) {
- expectAutoGenAddrEvent(test.initialExpect, newAddr)
- expectDADEventAsync(test.initialExpect.Address)
- }
-
- // The last local generation attempt should succeed, but we introduce a
- // DAD failure to restart the local generation process.
- addr := test.addrs[maxSLAACAddrLocalRegenAttempts-1]
- expectAutoGenAddrAsyncEvent(addr, newAddr)
- rxNDPSolicit(e, addr.Address)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected DAD event")
- }
- expectAutoGenAddrEvent(addr, invalidatedAddr)
-
- // The last address generated should resolve DAD.
- addr = test.addrs[len(test.addrs)-1]
- expectAutoGenAddrAsyncEvent(addr, newAddr)
- expectDADEventAsync(addr.Address)
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpected auto gen addr event = %+v", e)
- default:
- }
-
- // Wait for all the SLAAC addresses to be invalidated.
- clock.Advance(lifetimeSeconds * time.Second)
- gotAddresses := make(map[tcpip.Address]struct{})
- for _, a := range s.NICInfo()[nicID].ProtocolAddresses {
- gotAddresses[a.AddressWithPrefix.Address] = struct{}{}
- }
- if diff := cmp.Diff(manuallyAssignedAddresses, gotAddresses); diff != "" {
- t.Fatalf("assigned addresses mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-// stackAndNdpDispatcherWithDefaultRoute returns an ndpDispatcher,
-// channel.Endpoint and stack.Stack.
-//
-// stack.Stack will have a default route through the router (llAddr3) installed
-// and a static link-address (linkAddr3) added to the link address cache for the
-// router.
-func stackAndNdpDispatcherWithDefaultRoute(t *testing.T, nicID tcpip.NICID) (*ndpDispatcher, *channel.Endpoint, *stack.Stack, *faketime.ManualClock) {
- t.Helper()
- ndpDisp := &ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: ndpDisp,
- })},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- Clock: clock,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv6EmptySubnet,
- Gateway: llAddr3,
- NIC: nicID,
- }})
-
- if err := s.AddStaticNeighbor(nicID, ipv6.ProtocolNumber, llAddr3, linkAddr3); err != nil {
- t.Fatalf("s.AddStaticNeighbor(%d, %d, %s, %s): %s", nicID, ipv6.ProtocolNumber, llAddr3, linkAddr3, err)
- }
- return ndpDisp, e, s, clock
-}
-
-// addrForNewConnectionTo returns the local address used when creating a new
-// connection to addr.
-func addrForNewConnectionTo(t *testing.T, s *stack.Stack, addr tcpip.FullAddress) tcpip.Address {
- t.Helper()
-
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.ReadableEvents)
- defer wq.EventUnregister(&we)
- defer close(ch)
- ep, err := s.NewEndpoint(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err)
- }
- defer ep.Close()
- ep.SocketOptions().SetV6Only(true)
- if err := ep.Connect(addr); err != nil {
- t.Fatalf("ep.Connect(%+v): %s", addr, err)
- }
- got, err := ep.GetLocalAddress()
- if err != nil {
- t.Fatalf("ep.GetLocalAddress(): %s", err)
- }
- return got.Addr
-}
-
-// addrForNewConnection returns the local address used when creating a new
-// connection.
-func addrForNewConnection(t *testing.T, s *stack.Stack) tcpip.Address {
- t.Helper()
-
- return addrForNewConnectionTo(t, s, dstAddr)
-}
-
-// addrForNewConnectionWithAddr returns the local address used when creating a
-// new connection with a specific local address.
-func addrForNewConnectionWithAddr(t *testing.T, s *stack.Stack, addr tcpip.FullAddress) tcpip.Address {
- t.Helper()
-
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.ReadableEvents)
- defer wq.EventUnregister(&we)
- defer close(ch)
- ep, err := s.NewEndpoint(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err)
- }
- defer ep.Close()
- ep.SocketOptions().SetV6Only(true)
- if err := ep.Bind(addr); err != nil {
- t.Fatalf("ep.Bind(%+v): %s", addr, err)
- }
- if err := ep.Connect(dstAddr); err != nil {
- t.Fatalf("ep.Connect(%+v): %s", dstAddr, err)
- }
- got, err := ep.GetLocalAddress()
- if err != nil {
- t.Fatalf("ep.GetLocalAddress(): %s", err)
- }
- return got.Addr
-}
-
-// TestAutoGenAddrDeprecateFromPI tests deprecating a SLAAC address when
-// receiving a PI with 0 preferred lifetime.
-func TestAutoGenAddrDeprecateFromPI(t *testing.T) {
- const nicID = 1
-
- prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
- prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-
- ndpDisp, e, s, _ := stackAndNdpDispatcherWithDefaultRoute(t, nicID)
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) {
- t.Helper()
-
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil {
- t.Fatal(err)
- }
-
- if got := addrForNewConnection(t, s); got != addr.Address {
- t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address)
- }
- }
-
- // Receive PI for prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100))
- expectAutoGenAddrEvent(addr1, newAddr)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should have %s in the list of addresses", addr1)
- }
- expectPrimaryAddr(addr1)
-
- // Deprecate addr for prefix1 immedaitely.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
- expectAutoGenAddrEvent(addr1, deprecatedAddr)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should have %s in the list of addresses", addr1)
- }
- // addr should still be the primary endpoint as there are no other addresses.
- expectPrimaryAddr(addr1)
-
- // Refresh lifetimes of addr generated from prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto-generated event")
- default:
- }
- expectPrimaryAddr(addr1)
-
- // Receive PI for prefix2.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100))
- expectAutoGenAddrEvent(addr2, newAddr)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
- expectPrimaryAddr(addr2)
-
- // Deprecate addr for prefix2 immedaitely.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0))
- expectAutoGenAddrEvent(addr2, deprecatedAddr)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
- // addr1 should be the primary endpoint now since addr2 is deprecated but
- // addr1 is not.
- expectPrimaryAddr(addr1)
- // addr2 is deprecated but if explicitly requested, it should be used.
- fullAddr2 := tcpip.FullAddress{Addr: addr2.Address, NIC: nicID}
- if got := addrForNewConnectionWithAddr(t, s, fullAddr2); got != addr2.Address {
- t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr2, got, addr2.Address)
- }
-
- // Another PI w/ 0 preferred lifetime should not result in a deprecation
- // event.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto-generated event")
- default:
- }
- expectPrimaryAddr(addr1)
- if got := addrForNewConnectionWithAddr(t, s, fullAddr2); got != addr2.Address {
- t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr2, got, addr2.Address)
- }
-
- // Refresh lifetimes of addr generated from prefix2.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto-generated event")
- default:
- }
- expectPrimaryAddr(addr2)
-}
-
-// TestAutoGenAddrJobDeprecation tests that an address is properly deprecated
-// when its preferred lifetime expires.
-func TestAutoGenAddrJobDeprecation(t *testing.T) {
- const nicID = 1
-
- prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
- prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-
- ndpDisp, e, s, clock := stackAndNdpDispatcherWithDefaultRoute(t, nicID)
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- expectAutoGenAddrEventAfter := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) {
- t.Helper()
-
- clock.Advance(timeout)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- }
-
- expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) {
- t.Helper()
-
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil {
- t.Fatal(err)
- }
-
- if got := addrForNewConnection(t, s); got != addr.Address {
- t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address)
- }
- }
-
- // Receive PI for prefix2.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, infiniteLifetimeSeconds, infiniteLifetimeSeconds))
- expectAutoGenAddrEvent(addr2, newAddr)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
- expectPrimaryAddr(addr2)
-
- // Receive a PI for prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 90))
- expectAutoGenAddrEvent(addr1, newAddr)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should have %s in the list of addresses", addr1)
- }
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
- expectPrimaryAddr(addr1)
-
- // Refresh lifetime for addr of prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, minVLSeconds, minVLSeconds-1))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto-generated event")
- default:
- }
- expectPrimaryAddr(addr1)
-
- // Wait for addr of prefix1 to be deprecated.
- expectAutoGenAddrEventAfter(addr1, deprecatedAddr, ipv6.MinPrefixInformationValidLifetimeForUpdate-time.Second)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should not have %s in the list of addresses", addr1)
- }
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
- // addr2 should be the primary endpoint now since addr1 is deprecated but
- // addr2 is not.
- expectPrimaryAddr(addr2)
-
- // addr1 is deprecated but if explicitly requested, it should be used.
- fullAddr1 := tcpip.FullAddress{Addr: addr1.Address, NIC: nicID}
- if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address {
- t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address)
- }
-
- // Refresh valid lifetime for addr of prefix1, w/ 0 preferred lifetime to make
- // sure we do not get a deprecation event again.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, minVLSeconds, 0))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto-generated event")
- default:
- }
- expectPrimaryAddr(addr2)
- if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address {
- t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address)
- }
-
- // Refresh lifetimes for addr of prefix1.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, minVLSeconds, minVLSeconds-1))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto-generated event")
- default:
- }
- // addr1 is the primary endpoint again since it is non-deprecated now.
- expectPrimaryAddr(addr1)
-
- // Wait for addr of prefix1 to be deprecated.
- expectAutoGenAddrEventAfter(addr1, deprecatedAddr, ipv6.MinPrefixInformationValidLifetimeForUpdate-time.Second)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should not have %s in the list of addresses", addr1)
- }
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
- // addr2 should be the primary endpoint now since it is not deprecated.
- expectPrimaryAddr(addr2)
- if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address {
- t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address)
- }
-
- // Wait for addr of prefix1 to be invalidated.
- expectAutoGenAddrEventAfter(addr1, invalidatedAddr, time.Second)
- if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should not have %s in the list of addresses", addr1)
- }
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
- expectPrimaryAddr(addr2)
-
- // Refresh both lifetimes for addr of prefix2 to the same value.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, minVLSeconds, minVLSeconds))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto-generated event")
- default:
- }
-
- // Wait for a deprecation then invalidation events, or just an invalidation
- // event. We need to cover both cases but cannot deterministically hit both
- // cases because the deprecation and invalidation handlers could be handled in
- // either deprecation then invalidation, or invalidation then deprecation
- // (which should be cancelled by the invalidation handler).
- //
- // Since we're about to cause both events to fire, we need the dispatcher
- // channel to be able to hold both.
- if got, want := len(ndpDisp.autoGenAddrC), 0; got != want {
- t.Fatalf("got len(ndpDisp.autoGenAddrC) = %d, want %d", got, want)
- }
- if got, want := cap(ndpDisp.autoGenAddrC), 1; got != want {
- t.Fatalf("got cap(ndpDisp.autoGenAddrC) = %d, want %d", got, want)
- }
- ndpDisp.autoGenAddrC = make(chan ndpAutoGenAddrEvent, 2)
- clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr2, deprecatedAddr); diff == "" {
- // If we get a deprecation event first, we should get an invalidation
- // event almost immediately after.
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr2, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- } else if diff := checkAutoGenAddrEvent(e, addr2, invalidatedAddr); diff == "" {
- // If we get an invalidation event first, we should not get a deprecation
- // event after.
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto-generated event")
- default:
- }
- } else {
- t.Fatalf("got unexpected auto-generated event")
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should not have %s in the list of addresses", addr1)
- }
- if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should not have %s in the list of addresses", addr2)
- }
- // Should not have any primary endpoints.
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
- t.Fatal(err)
- }
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.ReadableEvents)
- defer wq.EventUnregister(&we)
- defer close(ch)
- ep, err := s.NewEndpoint(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err)
- }
- defer ep.Close()
- ep.SocketOptions().SetV6Only(true)
-
- {
- err := ep.Connect(dstAddr)
- if _, ok := err.(*tcpip.ErrNoRoute); !ok {
- t.Errorf("got ep.Connect(%+v) = %s, want = %s", dstAddr, err, &tcpip.ErrNoRoute{})
- }
- }
-}
-
-// Tests transitioning a SLAAC address's valid lifetime between finite and
-// infinite values.
-func TestAutoGenAddrFiniteToInfiniteToFiniteVL(t *testing.T) {
- const infiniteVLSeconds = 2
-
- prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-
- tests := []struct {
- name string
- infiniteVL uint32
- }{
- {
- name: "EqualToInfiniteVL",
- infiniteVL: infiniteVLSeconds,
- },
- // Our implementation supports changing header.NDPInfiniteLifetime for tests
- // such that a packet can be received where the lifetime field has a value
- // greater than header.NDPInfiniteLifetime. Because of this, we test to make
- // sure that receiving a value greater than header.NDPInfiniteLifetime is
- // handled the same as when receiving a value equal to
- // header.NDPInfiniteLifetime.
- {
- name: "MoreThanInfiniteVL",
- infiniteVL: infiniteVLSeconds + 1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- // Receive an RA with finite prefix.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0))
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
-
- default:
- t.Fatal("expected addr auto gen event")
- }
-
- // Receive an new RA with prefix with infinite VL.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.infiniteVL, 0))
-
- // Receive a new RA with prefix with finite VL.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0))
-
- clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
-
- default:
- t.Fatal("timeout waiting for addr auto gen event")
- }
- })
- }
-}
-
-// TestAutoGenAddrValidLifetimeUpdates tests that the valid lifetime of an
-// auto-generated address only gets updated when required to, as specified in
-// RFC 4862 section 5.5.3.e.
-func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) {
- const infiniteVL = 4294967295
-
- prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-
- tests := []struct {
- name string
- ovl uint32
- nvl uint32
- evl uint32
- }{
- // Should update the VL to the minimum VL for updating if the
- // new VL is less than minVLSeconds but was originally greater than
- // it.
- {
- "LargeVLToVLLessThanMinVLForUpdate",
- 9999,
- 1,
- minVLSeconds,
- },
- {
- "LargeVLTo0",
- 9999,
- 0,
- minVLSeconds,
- },
- {
- "InfiniteVLToVLLessThanMinVLForUpdate",
- infiniteVL,
- 1,
- minVLSeconds,
- },
- {
- "InfiniteVLTo0",
- infiniteVL,
- 0,
- minVLSeconds,
- },
-
- // Should not update VL if original VL was less than minVLSeconds
- // and the new VL is also less than minVLSeconds.
- {
- "ShouldNotUpdateWhenBothOldAndNewAreLessThanMinVLForUpdate",
- minVLSeconds - 1,
- minVLSeconds - 3,
- minVLSeconds - 1,
- },
-
- // Should take the new VL if the new VL is greater than the
- // remaining time or is greater than minVLSeconds.
- {
- "MorethanMinVLToLesserButStillMoreThanMinVLForUpdate",
- minVLSeconds + 5,
- minVLSeconds + 3,
- minVLSeconds + 3,
- },
- {
- "SmallVLToGreaterVLButStillLessThanMinVLForUpdate",
- minVLSeconds - 3,
- minVLSeconds - 1,
- minVLSeconds - 1,
- },
- {
- "SmallVLToGreaterVLThatIsMoreThaMinVLForUpdate",
- minVLSeconds - 3,
- minVLSeconds + 1,
- minVLSeconds + 1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 10),
- }
- e := channel.New(10, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- // Receive an RA with prefix with initial VL,
- // test.ovl.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.ovl, 0))
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
-
- // Receive an new RA with prefix with new VL,
- // test.nvl.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.nvl, 0))
-
- //
- // Validate that the VL for the address got set
- // to test.evl.
- //
-
- // The address should not be invalidated until the effective valid
- // lifetime has passed.
- const delta = 1
- clock.Advance(time.Duration(test.evl)*time.Second - delta)
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly received an auto gen addr event")
- default:
- }
-
- // Wait for the invalidation event.
- clock.Advance(delta)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timeout waiting for addr auto gen event")
- }
- })
- }
-}
-
-// TestAutoGenAddrRemoval tests that when auto-generated addresses are removed
-// by the user, its resources will be cleaned up and an invalidation event will
-// be sent to the integrator.
-func TestAutoGenAddrRemoval(t *testing.T) {
- prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- // Receive a PI to auto-generate an address.
- const lifetimeSeconds = 1
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, 0))
- expectAutoGenAddrEvent(addr, newAddr)
-
- // Removing the address should result in an invalidation event
- // immediately.
- if err := s.RemoveAddress(1, addr.Address); err != nil {
- t.Fatalf("RemoveAddress(_, %s) = %s", addr.Address, err)
- }
- expectAutoGenAddrEvent(addr, invalidatedAddr)
-
- // Wait for the original valid lifetime to make sure the original job got
- // cancelled/cleaned up.
- clock.Advance(lifetimeSeconds * time.Second)
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly received an auto gen addr event")
- default:
- }
-}
-
-// TestAutoGenAddrAfterRemoval tests adding a SLAAC address that was previously
-// assigned to the NIC but is in the permanentExpired state.
-func TestAutoGenAddrAfterRemoval(t *testing.T) {
- const nicID = 1
-
- prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
- prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
- ndpDisp, e, s, _ := stackAndNdpDispatcherWithDefaultRoute(t, nicID)
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) {
- t.Helper()
-
- if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil {
- t.Fatal(err)
- }
-
- if got := addrForNewConnection(t, s); got != addr.Address {
- t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address)
- }
- }
-
- // Receive a PI to auto-generate addr1 with a large valid and preferred
- // lifetime.
- const largeLifetimeSeconds = 999
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix1, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
- expectAutoGenAddrEvent(addr1, newAddr)
- expectPrimaryAddr(addr1)
-
- // Add addr2 as a static address.
- protoAddr2 := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addr2,
- }
- properties := stack.AddressProperties{PEB: stack.FirstPrimaryEndpoint}
- if err := s.AddProtocolAddress(nicID, protoAddr2, properties); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, %+v) = %s", nicID, protoAddr2, properties, err)
- }
- // addr2 should be more preferred now since it is at the front of the primary
- // list.
- expectPrimaryAddr(addr2)
-
- // Get a route using addr2 to increment its reference count then remove it
- // to leave it in the permanentExpired state.
- r, err := s.FindRoute(nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, false)
- if err != nil {
- t.Fatalf("FindRoute(%d, %s, %s, %d, false): %s", nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, err)
- }
- defer r.Release()
- if err := s.RemoveAddress(nicID, addr2.Address); err != nil {
- t.Fatalf("s.RemoveAddress(%d, %s): %s", nicID, addr2.Address, err)
- }
- // addr1 should be preferred again since addr2 is in the expired state.
- expectPrimaryAddr(addr1)
-
- // Receive a PI to auto-generate addr2 as valid and preferred.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
- expectAutoGenAddrEvent(addr2, newAddr)
- // addr2 should be more preferred now that it is closer to the front of the
- // primary list and not deprecated.
- expectPrimaryAddr(addr2)
-
- // Removing the address should result in an invalidation event immediately.
- // It should still be in the permanentExpired state because r is still held.
- //
- // We remove addr2 here to make sure addr2 was marked as a SLAAC address
- // (it was previously marked as a static address).
- if err := s.RemoveAddress(1, addr2.Address); err != nil {
- t.Fatalf("RemoveAddress(_, %s) = %s", addr2.Address, err)
- }
- expectAutoGenAddrEvent(addr2, invalidatedAddr)
- // addr1 should be more preferred since addr2 is in the expired state.
- expectPrimaryAddr(addr1)
-
- // Receive a PI to auto-generate addr2 as valid and deprecated.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, 0))
- expectAutoGenAddrEvent(addr2, newAddr)
- // addr1 should still be more preferred since addr2 is deprecated, even though
- // it is closer to the front of the primary list.
- expectPrimaryAddr(addr1)
-
- // Receive a PI to refresh addr2's preferred lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly got an auto gen addr event")
- default:
- }
- // addr2 should be more preferred now that it is not deprecated.
- expectPrimaryAddr(addr2)
-
- if err := s.RemoveAddress(1, addr2.Address); err != nil {
- t.Fatalf("RemoveAddress(_, %s) = %s", addr2.Address, err)
- }
- expectAutoGenAddrEvent(addr2, invalidatedAddr)
- expectPrimaryAddr(addr1)
-}
-
-// TestAutoGenAddrStaticConflict tests that if SLAAC generates an address that
-// is already assigned to the NIC, the static address remains.
-func TestAutoGenAddrStaticConflict(t *testing.T) {
- prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- // Add the address as a static address before SLAAC tries to add it.
- protocolAddr := tcpip.ProtocolAddress{Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr}
- if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(1, %+v, {}) = %s", protocolAddr, err)
- }
- if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) {
- t.Fatalf("Should have %s in the list of addresses", addr1)
- }
-
- // Receive a PI where the generated address will be the same as the one
- // that we already have assigned statically.
- const lifetimeSeconds = 1
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, 0))
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly received an auto gen addr event for an address we already have statically")
- default:
- }
- if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) {
- t.Fatalf("Should have %s in the list of addresses", addr1)
- }
-
- // Should not get an invalidation event after the PI's invalidation
- // time.
- clock.Advance(lifetimeSeconds * time.Second)
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Fatal("unexpectedly received an auto gen addr event")
- default:
- }
- if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) {
- t.Fatalf("Should have %s in the list of addresses", addr1)
- }
-}
-
-func makeSecretKey(t *testing.T) []byte {
- secretKey := make([]byte, header.OpaqueIIDSecretKeyMinBytes)
- n, err := cryptorand.Read(secretKey)
- if err != nil {
- t.Fatalf("cryptorand.Read(_): %s", err)
- }
- if l := len(secretKey); n != l {
- t.Fatalf("got cryptorand.Read(_) = (%d, nil), want = (%d, nil)", n, l)
- }
- return secretKey
-}
-
-// TestAutoGenAddrWithOpaqueIID tests that SLAAC generated addresses will use
-// opaque interface identifiers when configured to do so.
-func TestAutoGenAddrWithOpaqueIID(t *testing.T) {
- const nicID = 1
- const nicName = "nic1"
-
- secretKey := makeSecretKey(t)
-
- prefix1, subnet1, _ := prefixSubnetAddr(0, linkAddr1)
- prefix2, subnet2, _ := prefixSubnetAddr(1, linkAddr1)
- // addr1 and addr2 are the addresses that are expected to be generated when
- // stack.Stack is configured to generate opaque interface identifiers as
- // defined by RFC 7217.
- addrBytes := []byte(subnet1.ID())
- addr1 := tcpip.AddressWithPrefix{
- Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet1, nicName, 0, secretKey)),
- PrefixLen: 64,
- }
- addrBytes = []byte(subnet2.ID())
- addr2 := tcpip.AddressWithPrefix{
- Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet2, nicName, 0, secretKey)),
- PrefixLen: 64,
- }
-
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
- NICNameFromID: func(_ tcpip.NICID, nicName string) string {
- return nicName
- },
- SecretKey: secretKey,
- },
- })},
- Clock: clock,
- })
- opts := stack.NICOptions{Name: nicName}
- if err := s.CreateNICWithOptions(nicID, e, opts); err != nil {
- t.Fatalf("CreateNICWithOptions(%d, _, %+v, _) = %s", nicID, opts, err)
- }
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- // Receive an RA with prefix1 in a PI.
- const validLifetimeSecondPrefix1 = 1
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, validLifetimeSecondPrefix1, 0))
- expectAutoGenAddrEvent(addr1, newAddr)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should have %s in the list of addresses", addr1)
- }
-
- // Receive an RA with prefix2 in a PI with a large valid lifetime.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0))
- expectAutoGenAddrEvent(addr2, newAddr)
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should have %s in the list of addresses", addr1)
- }
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
-
- // Wait for addr of prefix1 to be invalidated.
- clock.Advance(validLifetimeSecondPrefix1 * time.Second)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
- t.Fatalf("should not have %s in the list of addresses", addr1)
- }
- if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
- t.Fatalf("should have %s in the list of addresses", addr2)
- }
-}
-
-func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
- const nicID = 1
- const nicName = "nic"
- const dadTransmits = 1
- const retransmitTimer = time.Second
- const maxMaxRetries = 3
- const lifetimeSeconds = 10
-
- secretKey := makeSecretKey(t)
-
- prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
-
- addrForSubnet := func(subnet tcpip.Subnet, dadCounter uint8) tcpip.AddressWithPrefix {
- addrBytes := []byte(subnet.ID())
- return tcpip.AddressWithPrefix{
- Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, dadCounter, secretKey)),
- PrefixLen: 64,
- }
- }
-
- expectAutoGenAddrEvent := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- expectAutoGenAddrEventAsync := func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- clock.RunImmediatelyScheduledJobs()
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for addr auto gen event")
- }
- }
-
- expectDADEvent := func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) {
- t.Helper()
-
- clock.RunImmediatelyScheduledJobs()
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr, res); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected DAD event")
- }
- }
-
- expectDADEventAsync := func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) {
- t.Helper()
-
- clock.Advance(dadTransmits * retransmitTimer)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr, res); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for DAD event")
- }
- }
-
- stableAddrForTempAddrTest := addrForSubnet(subnet, 0)
-
- addrTypes := []struct {
- name string
- ndpConfigs ipv6.NDPConfigurations
- autoGenLinkLocal bool
- prepareFn func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, e *channel.Endpoint, tempIIDHistory []byte) []tcpip.AddressWithPrefix
- addrGenFn func(dadCounter uint8, tempIIDHistory []byte) tcpip.AddressWithPrefix
- }{
- {
- name: "Global address",
- ndpConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- },
- prepareFn: func(_ *testing.T, _ *faketime.ManualClock, _ *ndpDispatcher, e *channel.Endpoint, _ []byte) []tcpip.AddressWithPrefix {
- // Receive an RA with prefix1 in a PI.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
- return nil
-
- },
- addrGenFn: func(dadCounter uint8, _ []byte) tcpip.AddressWithPrefix {
- return addrForSubnet(subnet, dadCounter)
- },
- },
- {
- name: "LinkLocal address",
- ndpConfigs: ipv6.NDPConfigurations{},
- autoGenLinkLocal: true,
- prepareFn: func(*testing.T, *faketime.ManualClock, *ndpDispatcher, *channel.Endpoint, []byte) []tcpip.AddressWithPrefix {
- return nil
- },
- addrGenFn: func(dadCounter uint8, _ []byte) tcpip.AddressWithPrefix {
- return addrForSubnet(header.IPv6LinkLocalPrefix.Subnet(), dadCounter)
- },
- },
- {
- name: "Temporary address",
- ndpConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- AutoGenTempGlobalAddresses: true,
- },
- prepareFn: func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, e *channel.Endpoint, tempIIDHistory []byte) []tcpip.AddressWithPrefix {
- header.InitialTempIID(tempIIDHistory, nil, nicID)
-
- // Generate a stable SLAAC address so temporary addresses will be
- // generated.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
- expectAutoGenAddrEvent(t, ndpDisp, stableAddrForTempAddrTest, newAddr)
- expectDADEventAsync(t, clock, ndpDisp, stableAddrForTempAddrTest.Address, &stack.DADSucceeded{})
-
- // The stable address will be assigned throughout the test.
- return []tcpip.AddressWithPrefix{stableAddrForTempAddrTest}
- },
- addrGenFn: func(_ uint8, tempIIDHistory []byte) tcpip.AddressWithPrefix {
- return header.GenerateTempIPv6SLAACAddr(tempIIDHistory, stableAddrForTempAddrTest.Address)
- },
- },
- }
-
- for _, addrType := range addrTypes {
- t.Run(addrType.name, func(t *testing.T) {
- for maxRetries := uint8(0); maxRetries <= maxMaxRetries; maxRetries++ {
- for numFailures := uint8(0); numFailures <= maxRetries+1; numFailures++ {
- maxRetries := maxRetries
- numFailures := numFailures
- addrType := addrType
-
- t.Run(fmt.Sprintf("%d max retries and %d failures", maxRetries, numFailures), func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
- }
- e := channel.New(0, 1280, linkAddr1)
- ndpConfigs := addrType.ndpConfigs
- ndpConfigs.AutoGenAddressConflictRetries = maxRetries
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- AutoGenLinkLocal: addrType.autoGenLinkLocal,
- DADConfigs: stack.DADConfigurations{
- DupAddrDetectTransmits: dadTransmits,
- RetransmitTimer: retransmitTimer,
- },
- NDPConfigs: ndpConfigs,
- NDPDisp: &ndpDisp,
- OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
- NICNameFromID: func(_ tcpip.NICID, nicName string) string {
- return nicName
- },
- SecretKey: secretKey,
- },
- })},
- Clock: clock,
- })
- opts := stack.NICOptions{Name: nicName}
- if err := s.CreateNICWithOptions(nicID, e, opts); err != nil {
- t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, err)
- }
-
- var tempIIDHistory [header.IIDSize]byte
- stableAddrs := addrType.prepareFn(t, clock, &ndpDisp, e, tempIIDHistory[:])
-
- // Simulate DAD conflicts so the address is regenerated.
- for i := uint8(0); i < numFailures; i++ {
- addr := addrType.addrGenFn(i, tempIIDHistory[:])
- expectAutoGenAddrEventAsync(t, clock, &ndpDisp, addr, newAddr)
-
- // Should not have any new addresses assigned to the NIC.
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, stableAddrs, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // Simulate a DAD conflict.
- rxNDPSolicit(e, addr.Address)
- expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr)
- expectDADEvent(t, clock, &ndpDisp, addr.Address, &stack.DADDupAddrDetected{})
-
- // Attempting to add the address manually should not fail if the
- // address's state was cleaned up when DAD failed.
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addr,
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
- }
- if err := s.RemoveAddress(nicID, addr.Address); err != nil {
- t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr.Address, err)
- }
- expectDADEvent(t, clock, &ndpDisp, addr.Address, &stack.DADAborted{})
- }
-
- // Should not have any new addresses assigned to the NIC.
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, stableAddrs, nil); mismatch != "" {
- t.Fatal(mismatch)
- }
-
- // If we had less failures than generation attempts, we should have
- // an address after DAD resolves.
- if maxRetries+1 > numFailures {
- addr := addrType.addrGenFn(numFailures, tempIIDHistory[:])
- expectAutoGenAddrEventAsync(t, clock, &ndpDisp, addr, newAddr)
- expectDADEventAsync(t, clock, &ndpDisp, addr.Address, &stack.DADSucceeded{})
- if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, append(stableAddrs, addr), nil); mismatch != "" {
- t.Fatal(mismatch)
- }
- }
-
- // Should not attempt address generation again.
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpectedly got an auto-generated address event = %+v", e)
- default:
- }
- })
- }
- }
- })
- }
-}
-
-// TestAutoGenAddrWithEUI64IIDNoDADRetries tests that a regeneration attempt is
-// not made for SLAAC addresses generated with an IID based on the NIC's link
-// address.
-func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) {
- const nicID = 1
- const dadTransmits = 1
- const retransmitTimer = time.Second
- const maxRetries = 3
- const lifetimeSeconds = 10
-
- prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
-
- addrTypes := []struct {
- name string
- ndpConfigs ipv6.NDPConfigurations
- autoGenLinkLocal bool
- subnet tcpip.Subnet
- triggerSLAACFn func(e *channel.Endpoint)
- }{
- {
- name: "Global address",
- ndpConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- AutoGenAddressConflictRetries: maxRetries,
- },
- subnet: subnet,
- triggerSLAACFn: func(e *channel.Endpoint) {
- // Receive an RA with prefix1 in a PI.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
-
- },
- },
- {
- name: "LinkLocal address",
- ndpConfigs: ipv6.NDPConfigurations{
- AutoGenAddressConflictRetries: maxRetries,
- },
- autoGenLinkLocal: true,
- subnet: header.IPv6LinkLocalPrefix.Subnet(),
- triggerSLAACFn: func(e *channel.Endpoint) {},
- },
- }
-
- for _, addrType := range addrTypes {
- addrType := addrType
-
- t.Run(addrType.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- AutoGenLinkLocal: addrType.autoGenLinkLocal,
- NDPConfigs: addrType.ndpConfigs,
- NDPDisp: &ndpDisp,
- DADConfigs: stack.DADConfigurations{
- DupAddrDetectTransmits: dadTransmits,
- RetransmitTimer: retransmitTimer,
- },
- })},
- Clock: clock,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- addrType.triggerSLAACFn(e)
-
- addrBytes := []byte(addrType.subnet.ID())
- header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr1, addrBytes[header.IIDOffsetInIPv6Address:])
- addr := tcpip.AddressWithPrefix{
- Address: tcpip.Address(addrBytes),
- PrefixLen: 64,
- }
- expectAutoGenAddrEvent(addr, newAddr)
-
- // Simulate a DAD conflict.
- rxNDPSolicit(e, addr.Address)
- expectAutoGenAddrEvent(addr, invalidatedAddr)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected DAD event")
- }
-
- // Should not attempt address regeneration.
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Fatalf("unexpectedly got an auto-generated address event = %+v", e)
- default:
- }
- })
- }
-}
-
-// TestAutoGenAddrContinuesLifetimesAfterRetry tests that retrying address
-// generation in response to DAD conflicts does not refresh the lifetimes.
-func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) {
- const nicID = 1
- const nicName = "nic"
- const dadTransmits = 1
- const retransmitTimer = 2 * time.Second
- const failureTimer = time.Second
- const maxRetries = 1
- const lifetimeSeconds = 5
-
- secretKey := makeSecretKey(t)
-
- prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
-
- ndpDisp := ndpDispatcher{
- dadC: make(chan ndpDADEvent, 1),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
- }
- e := channel.New(0, 1280, linkAddr1)
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- DADConfigs: stack.DADConfigurations{
- DupAddrDetectTransmits: dadTransmits,
- RetransmitTimer: retransmitTimer,
- },
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- AutoGenGlobalAddresses: true,
- AutoGenAddressConflictRetries: maxRetries,
- },
- NDPDisp: &ndpDisp,
- OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
- NICNameFromID: func(_ tcpip.NICID, nicName string) string {
- return nicName
- },
- SecretKey: secretKey,
- },
- })},
- Clock: clock,
- })
- opts := stack.NICOptions{Name: nicName}
- if err := s.CreateNICWithOptions(nicID, e, opts); err != nil {
- t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, err)
- }
-
- expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
- t.Helper()
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- }
-
- // Receive an RA with prefix in a PI.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
-
- addrBytes := []byte(subnet.ID())
- addr := tcpip.AddressWithPrefix{
- Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, 0, secretKey)),
- PrefixLen: 64,
- }
- expectAutoGenAddrEvent(addr, newAddr)
-
- // Simulate a DAD conflict after some time has passed.
- clock.Advance(failureTimer)
- rxNDPSolicit(e, addr.Address)
- expectAutoGenAddrEvent(addr, invalidatedAddr)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected DAD event")
- }
-
- // Let the next address resolve.
- addr.Address = tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, 1, secretKey))
- expectAutoGenAddrEvent(addr, newAddr)
- clock.Advance(dadTransmits * retransmitTimer)
- select {
- case e := <-ndpDisp.dadC:
- if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADSucceeded{}); diff != "" {
- t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for DAD event")
- }
-
- // Address should be deprecated/invalidated after the lifetime expires.
- //
- // Note, the remaining lifetime is calculated from when the PI was first
- // processed. Since we wait for some time before simulating a DAD conflict
- // and more time for the new address to resolve, the new address is only
- // expected to be valid for the remaining time. The DAD conflict should
- // not have reset the lifetimes.
- //
- // We expect either just the invalidation event or the deprecation event
- // followed by the invalidation event.
- clock.Advance(lifetimeSeconds*time.Second - failureTimer - dadTransmits*retransmitTimer)
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if e.eventType == deprecatedAddr {
- if diff := checkAutoGenAddrEvent(e, addr, deprecatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
-
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("timed out waiting for invalidated auto gen addr event after deprecation")
- }
- } else {
- if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- }
- default:
- t.Fatal("timed out waiting for auto gen addr event")
- }
-}
-
-// TestNDPRecursiveDNSServerDispatch tests that we properly dispatch an event
-// to the integrator when an RA is received with the NDP Recursive DNS Server
-// option with at least one valid address.
-func TestNDPRecursiveDNSServerDispatch(t *testing.T) {
- tests := []struct {
- name string
- opt header.NDPRecursiveDNSServer
- expected *ndpRDNSS
- }{
- {
- "Unspecified",
- header.NDPRecursiveDNSServer([]byte{
- 0, 0,
- 0, 0, 0, 2,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- }),
- nil,
- },
- {
- "Multicast",
- header.NDPRecursiveDNSServer([]byte{
- 0, 0,
- 0, 0, 0, 2,
- 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
- }),
- nil,
- },
- {
- "OptionTooSmall",
- header.NDPRecursiveDNSServer([]byte{
- 0, 0,
- 0, 0, 0, 2,
- 1, 2, 3, 4, 5, 6, 7, 8,
- }),
- nil,
- },
- {
- "0Addresses",
- header.NDPRecursiveDNSServer([]byte{
- 0, 0,
- 0, 0, 0, 2,
- }),
- nil,
- },
- {
- "Valid1Address",
- header.NDPRecursiveDNSServer([]byte{
- 0, 0,
- 0, 0, 0, 2,
- 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1,
- }),
- &ndpRDNSS{
- []tcpip.Address{
- "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01",
- },
- 2 * time.Second,
- },
- },
- {
- "Valid2Addresses",
- header.NDPRecursiveDNSServer([]byte{
- 0, 0,
- 0, 0, 0, 1,
- 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1,
- 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 2,
- }),
- &ndpRDNSS{
- []tcpip.Address{
- "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01",
- "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x02",
- },
- time.Second,
- },
- },
- {
- "Valid3Addresses",
- header.NDPRecursiveDNSServer([]byte{
- 0, 0,
- 0, 0, 0, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1,
- 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 2,
- 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 3,
- }),
- &ndpRDNSS{
- []tcpip.Address{
- "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01",
- "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x02",
- "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x03",
- },
- 0,
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- // We do not expect more than a single RDNSS
- // event at any time for this test.
- rdnssC: make(chan ndpRDNSSEvent, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- },
- NDPDisp: &ndpDisp,
- })},
- })
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(1) = %s", err)
- }
-
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, header.NDPOptionsSerializer{test.opt}))
-
- if test.expected != nil {
- select {
- case e := <-ndpDisp.rdnssC:
- if e.nicID != 1 {
- t.Errorf("got rdnss nicID = %d, want = 1", e.nicID)
- }
- if diff := cmp.Diff(e.rdnss.addrs, test.expected.addrs); diff != "" {
- t.Errorf("rdnss addrs mismatch (-want +got):\n%s", diff)
- }
- if e.rdnss.lifetime != test.expected.lifetime {
- t.Errorf("got rdnss lifetime = %s, want = %s", e.rdnss.lifetime, test.expected.lifetime)
- }
- default:
- t.Fatal("expected an RDNSS option event")
- }
- }
-
- // Should have no more RDNSS options.
- select {
- case e := <-ndpDisp.rdnssC:
- t.Fatalf("unexpectedly got a new RDNSS option event: %+v", e)
- default:
- }
- })
- }
-}
-
-// TestNDPDNSSearchListDispatch tests that the integrator is informed when an
-// NDP DNS Search List option is received with at least one domain name in the
-// search list.
-func TestNDPDNSSearchListDispatch(t *testing.T) {
- const nicID = 1
-
- ndpDisp := ndpDispatcher{
- dnsslC: make(chan ndpDNSSLEvent, 3),
- }
- e := channel.New(0, 1280, linkAddr1)
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- },
- NDPDisp: &ndpDisp,
- })},
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- optSer := header.NDPOptionsSerializer{
- header.NDPDNSSearchList([]byte{
- 0, 0,
- 0, 0, 0, 0,
- 2, 'h', 'i',
- 0,
- }),
- header.NDPDNSSearchList([]byte{
- 0, 0,
- 0, 0, 0, 1,
- 1, 'i',
- 0,
- 2, 'a', 'm',
- 2, 'm', 'e',
- 0,
- }),
- header.NDPDNSSearchList([]byte{
- 0, 0,
- 0, 0, 1, 0,
- 3, 'x', 'y', 'z',
- 0,
- 5, 'h', 'e', 'l', 'l', 'o',
- 5, 'w', 'o', 'r', 'l', 'd',
- 0,
- 4, 't', 'h', 'i', 's',
- 2, 'i', 's',
- 1, 'a',
- 4, 't', 'e', 's', 't',
- 0,
- }),
- }
- expected := []struct {
- domainNames []string
- lifetime time.Duration
- }{
- {
- domainNames: []string{
- "hi",
- },
- lifetime: 0,
- },
- {
- domainNames: []string{
- "i",
- "am.me",
- },
- lifetime: time.Second,
- },
- {
- domainNames: []string{
- "xyz",
- "hello.world",
- "this.is.a.test",
- },
- lifetime: 256 * time.Second,
- },
- }
-
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, optSer))
-
- for i, expected := range expected {
- select {
- case dnssl := <-ndpDisp.dnsslC:
- if dnssl.nicID != nicID {
- t.Errorf("got %d-th dnssl nicID = %d, want = %d", i, dnssl.nicID, nicID)
- }
- if diff := cmp.Diff(dnssl.domainNames, expected.domainNames); diff != "" {
- t.Errorf("%d-th dnssl domain names mismatch (-want +got):\n%s", i, diff)
- }
- if dnssl.lifetime != expected.lifetime {
- t.Errorf("got %d-th dnssl lifetime = %s, want = %s", i, dnssl.lifetime, expected.lifetime)
- }
- default:
- t.Fatal("expected a DNSSL event")
- }
- }
-
- // Should have no more DNSSL options.
- select {
- case <-ndpDisp.dnsslC:
- t.Fatal("unexpectedly got a DNSSL event")
- default:
- }
-}
-
-func TestNoCleanupNDPStateWhenForwardingEnabled(t *testing.T) {
- const (
- lifetimeSeconds = 999
- nicID = 1
- )
-
- ndpDisp := ndpDispatcher{
- offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1),
- prefixC: make(chan ndpPrefixEvent, 1),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- AutoGenLinkLocal: true,
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- DiscoverDefaultRouters: true,
- DiscoverOnLinkPrefixes: true,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- })},
- })
-
- e1 := channel.New(0, header.IPv6MinimumMTU, linkAddr1)
- if err := s.CreateNIC(nicID, e1); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- llAddr := tcpip.AddressWithPrefix{Address: llAddr1, PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen}
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, llAddr, newAddr); diff != "" {
- t.Errorf("auto-gen addr mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Errorf("expected auto-gen addr event for %s on NIC(%d)", llAddr, nicID)
- }
-
- prefix, subnet, addr := prefixSubnetAddr(0, linkAddr1)
- e1.InjectInbound(
- header.IPv6ProtocolNumber,
- raBufWithPI(
- llAddr3,
- lifetimeSeconds,
- prefix,
- true, /* onLink */
- true, /* auto */
- lifetimeSeconds,
- lifetimeSeconds,
- ),
- )
- select {
- case e := <-ndpDisp.offLinkRouteC:
- if diff := checkOffLinkRouteEvent(e, nicID, header.IPv6EmptySubnet, llAddr3, header.MediumRoutePreference, true /* discovered */); diff != "" {
- t.Errorf("off-link route event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Errorf("expected off-link route event for %s on NIC(%d)", llAddr3, nicID)
- }
- select {
- case e := <-ndpDisp.prefixC:
- if diff := checkPrefixEvent(e, subnet, true /* discovered */); diff != "" {
- t.Errorf("off-link route event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Errorf("expected prefix event for %s on NIC(%d)", prefix, nicID)
- }
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" {
- t.Errorf("auto-gen addr mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Errorf("expected auto-gen addr event for %s on NIC(%d)", addr, nicID)
- }
-
- // Enabling or disabling forwarding should not invalidate discovered prefixes
- // or routers, or auto-generated address.
- for _, forwarding := range [...]bool{true, false} {
- t.Run(fmt.Sprintf("Transition forwarding to %t", forwarding), func(t *testing.T) {
- if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err)
- }
- select {
- case e := <-ndpDisp.offLinkRouteC:
- t.Errorf("unexpected off-link route event = %#v", e)
- default:
- }
- select {
- case e := <-ndpDisp.prefixC:
- t.Errorf("unexpected prefix event = %#v", e)
- default:
- }
- select {
- case e := <-ndpDisp.autoGenAddrC:
- t.Errorf("unexpected auto-gen addr event = %#v", e)
- default:
- }
- })
- }
-}
-
-func TestCleanupNDPState(t *testing.T) {
- const (
- lifetimeSeconds = 5
- maxRouterAndPrefixEvents = 4
- nicID1 = 1
- nicID2 = 2
- )
-
- prefix1, subnet1, e1Addr1 := prefixSubnetAddr(0, linkAddr1)
- prefix2, subnet2, e1Addr2 := prefixSubnetAddr(1, linkAddr1)
- e2Addr1 := addrForSubnet(subnet1, linkAddr2)
- e2Addr2 := addrForSubnet(subnet2, linkAddr2)
- llAddrWithPrefix1 := tcpip.AddressWithPrefix{
- Address: llAddr1,
- PrefixLen: 64,
- }
- llAddrWithPrefix2 := tcpip.AddressWithPrefix{
- Address: llAddr2,
- PrefixLen: 64,
- }
-
- tests := []struct {
- name string
- cleanupFn func(t *testing.T, s *stack.Stack)
- keepAutoGenLinkLocal bool
- maxAutoGenAddrEvents int
- skipFinalAddrCheck bool
- }{
- // A NIC should cleanup all NDP state when it is disabled.
- {
- name: "Disable NIC",
- cleanupFn: func(t *testing.T, s *stack.Stack) {
- t.Helper()
-
- if err := s.DisableNIC(nicID1); err != nil {
- t.Fatalf("s.DisableNIC(%d): %s", nicID1, err)
- }
- if err := s.DisableNIC(nicID2); err != nil {
- t.Fatalf("s.DisableNIC(%d): %s", nicID2, err)
- }
- },
- keepAutoGenLinkLocal: false,
- maxAutoGenAddrEvents: 6,
- },
-
- // A NIC should cleanup all NDP state when it is removed.
- {
- name: "Remove NIC",
- cleanupFn: func(t *testing.T, s *stack.Stack) {
- t.Helper()
-
- if err := s.RemoveNIC(nicID1); err != nil {
- t.Fatalf("s.RemoveNIC(%d): %s", nicID1, err)
- }
- if err := s.RemoveNIC(nicID2); err != nil {
- t.Fatalf("s.RemoveNIC(%d): %s", nicID2, err)
- }
- },
- keepAutoGenLinkLocal: false,
- maxAutoGenAddrEvents: 6,
- // The NICs are removed so we can't check their addresses after calling
- // stopFn.
- skipFinalAddrCheck: true,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ndpDisp := ndpDispatcher{
- offLinkRouteC: make(chan ndpOffLinkRouteEvent, maxRouterAndPrefixEvents),
- prefixC: make(chan ndpPrefixEvent, maxRouterAndPrefixEvents),
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAutoGenAddrEvents),
- }
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- AutoGenLinkLocal: true,
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- DiscoverDefaultRouters: true,
- DiscoverOnLinkPrefixes: true,
- AutoGenGlobalAddresses: true,
- },
- NDPDisp: &ndpDisp,
- })},
- Clock: clock,
- })
-
- expectOffLinkRouteEvent := func() (bool, ndpOffLinkRouteEvent) {
- select {
- case e := <-ndpDisp.offLinkRouteC:
- return true, e
- default:
- }
-
- return false, ndpOffLinkRouteEvent{}
- }
-
- expectPrefixEvent := func() (bool, ndpPrefixEvent) {
- select {
- case e := <-ndpDisp.prefixC:
- return true, e
- default:
- }
-
- return false, ndpPrefixEvent{}
- }
-
- expectAutoGenAddrEvent := func() (bool, ndpAutoGenAddrEvent) {
- select {
- case e := <-ndpDisp.autoGenAddrC:
- return true, e
- default:
- }
-
- return false, ndpAutoGenAddrEvent{}
- }
-
- e1 := channel.New(0, 1280, linkAddr1)
- if err := s.CreateNIC(nicID1, e1); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID1, err)
- }
- // We have other tests that make sure we receive the *correct* events
- // on normal discovery of routers/prefixes, and auto-generated
- // addresses. Here we just make sure we get an event and let other tests
- // handle the correctness check.
- expectAutoGenAddrEvent()
-
- e2 := channel.New(0, 1280, linkAddr2)
- if err := s.CreateNIC(nicID2, e2); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err)
- }
- expectAutoGenAddrEvent()
-
- // Receive RAs on NIC(1) and NIC(2) from default routers (llAddr3 and
- // llAddr4) w/ PI (for prefix1 in RA from llAddr3 and prefix2 in RA from
- // llAddr4) to discover multiple routers and prefixes, and auto-gen
- // multiple addresses.
-
- e1.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, lifetimeSeconds, prefix1, true, true, lifetimeSeconds, lifetimeSeconds))
- if ok, _ := expectOffLinkRouteEvent(); !ok {
- t.Errorf("expected off-link route event for %s on NIC(%d)", llAddr3, nicID1)
- }
- if ok, _ := expectPrefixEvent(); !ok {
- t.Errorf("expected prefix event for %s on NIC(%d)", prefix1, nicID1)
- }
- if ok, _ := expectAutoGenAddrEvent(); !ok {
- t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr1, nicID1)
- }
-
- e1.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr4, lifetimeSeconds, prefix2, true, true, lifetimeSeconds, lifetimeSeconds))
- if ok, _ := expectOffLinkRouteEvent(); !ok {
- t.Errorf("expected off-link route event for %s on NIC(%d)", llAddr4, nicID1)
- }
- if ok, _ := expectPrefixEvent(); !ok {
- t.Errorf("expected prefix event for %s on NIC(%d)", prefix2, nicID1)
- }
- if ok, _ := expectAutoGenAddrEvent(); !ok {
- t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr2, nicID1)
- }
-
- e2.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, lifetimeSeconds, prefix1, true, true, lifetimeSeconds, lifetimeSeconds))
- if ok, _ := expectOffLinkRouteEvent(); !ok {
- t.Errorf("expected off-link route event for %s on NIC(%d)", llAddr3, nicID2)
- }
- if ok, _ := expectPrefixEvent(); !ok {
- t.Errorf("expected prefix event for %s on NIC(%d)", prefix1, nicID2)
- }
- if ok, _ := expectAutoGenAddrEvent(); !ok {
- t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr2, nicID2)
- }
-
- e2.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr4, lifetimeSeconds, prefix2, true, true, lifetimeSeconds, lifetimeSeconds))
- if ok, _ := expectOffLinkRouteEvent(); !ok {
- t.Errorf("expected off-link route event for %s on NIC(%d)", llAddr4, nicID2)
- }
- if ok, _ := expectPrefixEvent(); !ok {
- t.Errorf("expected prefix event for %s on NIC(%d)", prefix2, nicID2)
- }
- if ok, _ := expectAutoGenAddrEvent(); !ok {
- t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e2Addr2, nicID2)
- }
-
- // We should have the auto-generated addresses added.
- nicinfo := s.NICInfo()
- nic1Addrs := nicinfo[nicID1].ProtocolAddresses
- nic2Addrs := nicinfo[nicID2].ProtocolAddresses
- if !containsV6Addr(nic1Addrs, llAddrWithPrefix1) {
- t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs)
- }
- if !containsV6Addr(nic1Addrs, e1Addr1) {
- t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e1Addr1, nicID1, nic1Addrs)
- }
- if !containsV6Addr(nic1Addrs, e1Addr2) {
- t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e1Addr2, nicID1, nic1Addrs)
- }
- if !containsV6Addr(nic2Addrs, llAddrWithPrefix2) {
- t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs)
- }
- if !containsV6Addr(nic2Addrs, e2Addr1) {
- t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e2Addr1, nicID2, nic2Addrs)
- }
- if !containsV6Addr(nic2Addrs, e2Addr2) {
- t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e2Addr2, nicID2, nic2Addrs)
- }
-
- // We can't proceed any further if we already failed the test (missing
- // some discovery/auto-generated address events or addresses).
- if t.Failed() {
- t.FailNow()
- }
-
- test.cleanupFn(t, s)
-
- // Collect invalidation events after having NDP state cleaned up.
- gotOffLinkRouteEvents := make(map[ndpOffLinkRouteEvent]int)
- for i := 0; i < maxRouterAndPrefixEvents; i++ {
- ok, e := expectOffLinkRouteEvent()
- if !ok {
- t.Errorf("expected %d off-link route events after becoming a router; got = %d", maxRouterAndPrefixEvents, i)
- break
- }
- gotOffLinkRouteEvents[e]++
- }
- gotPrefixEvents := make(map[ndpPrefixEvent]int)
- for i := 0; i < maxRouterAndPrefixEvents; i++ {
- ok, e := expectPrefixEvent()
- if !ok {
- t.Errorf("expected %d prefix events after becoming a router; got = %d", maxRouterAndPrefixEvents, i)
- break
- }
- gotPrefixEvents[e]++
- }
- gotAutoGenAddrEvents := make(map[ndpAutoGenAddrEvent]int)
- for i := 0; i < test.maxAutoGenAddrEvents; i++ {
- ok, e := expectAutoGenAddrEvent()
- if !ok {
- t.Errorf("expected %d auto-generated address events after becoming a router; got = %d", test.maxAutoGenAddrEvents, i)
- break
- }
- gotAutoGenAddrEvents[e]++
- }
-
- // No need to proceed any further if we already failed the test (missing
- // some invalidation events).
- if t.Failed() {
- t.FailNow()
- }
-
- expectedOffLinkRouteEvents := map[ndpOffLinkRouteEvent]int{
- {nicID: nicID1, subnet: header.IPv6EmptySubnet, router: llAddr3, updated: false}: 1,
- {nicID: nicID1, subnet: header.IPv6EmptySubnet, router: llAddr4, updated: false}: 1,
- {nicID: nicID2, subnet: header.IPv6EmptySubnet, router: llAddr3, updated: false}: 1,
- {nicID: nicID2, subnet: header.IPv6EmptySubnet, router: llAddr4, updated: false}: 1,
- }
- if diff := cmp.Diff(expectedOffLinkRouteEvents, gotOffLinkRouteEvents); diff != "" {
- t.Errorf("off-link route events mismatch (-want +got):\n%s", diff)
- }
- expectedPrefixEvents := map[ndpPrefixEvent]int{
- {nicID: nicID1, prefix: subnet1, discovered: false}: 1,
- {nicID: nicID1, prefix: subnet2, discovered: false}: 1,
- {nicID: nicID2, prefix: subnet1, discovered: false}: 1,
- {nicID: nicID2, prefix: subnet2, discovered: false}: 1,
- }
- if diff := cmp.Diff(expectedPrefixEvents, gotPrefixEvents); diff != "" {
- t.Errorf("prefix events mismatch (-want +got):\n%s", diff)
- }
- expectedAutoGenAddrEvents := map[ndpAutoGenAddrEvent]int{
- {nicID: nicID1, addr: e1Addr1, eventType: invalidatedAddr}: 1,
- {nicID: nicID1, addr: e1Addr2, eventType: invalidatedAddr}: 1,
- {nicID: nicID2, addr: e2Addr1, eventType: invalidatedAddr}: 1,
- {nicID: nicID2, addr: e2Addr2, eventType: invalidatedAddr}: 1,
- }
-
- if !test.keepAutoGenLinkLocal {
- expectedAutoGenAddrEvents[ndpAutoGenAddrEvent{nicID: nicID1, addr: llAddrWithPrefix1, eventType: invalidatedAddr}] = 1
- expectedAutoGenAddrEvents[ndpAutoGenAddrEvent{nicID: nicID2, addr: llAddrWithPrefix2, eventType: invalidatedAddr}] = 1
- }
-
- if diff := cmp.Diff(expectedAutoGenAddrEvents, gotAutoGenAddrEvents); diff != "" {
- t.Errorf("auto-generated address events mismatch (-want +got):\n%s", diff)
- }
-
- if !test.skipFinalAddrCheck {
- // Make sure the auto-generated addresses got removed.
- nicinfo = s.NICInfo()
- nic1Addrs = nicinfo[nicID1].ProtocolAddresses
- nic2Addrs = nicinfo[nicID2].ProtocolAddresses
- if containsV6Addr(nic1Addrs, llAddrWithPrefix1) != test.keepAutoGenLinkLocal {
- if test.keepAutoGenLinkLocal {
- t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs)
- } else {
- t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs)
- }
- }
- if containsV6Addr(nic1Addrs, e1Addr1) {
- t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e1Addr1, nicID1, nic1Addrs)
- }
- if containsV6Addr(nic1Addrs, e1Addr2) {
- t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e1Addr2, nicID1, nic1Addrs)
- }
- if containsV6Addr(nic2Addrs, llAddrWithPrefix2) != test.keepAutoGenLinkLocal {
- if test.keepAutoGenLinkLocal {
- t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs)
- } else {
- t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs)
- }
- }
- if containsV6Addr(nic2Addrs, e2Addr1) {
- t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e2Addr1, nicID2, nic2Addrs)
- }
- if containsV6Addr(nic2Addrs, e2Addr2) {
- t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e2Addr2, nicID2, nic2Addrs)
- }
- }
-
- // Should not get any more events (invalidation timers should have been
- // cancelled when the NDP state was cleaned up).
- clock.Advance(lifetimeSeconds * time.Second)
- select {
- case <-ndpDisp.offLinkRouteC:
- t.Error("unexpected off-link route event")
- default:
- }
- select {
- case <-ndpDisp.prefixC:
- t.Error("unexpected prefix event")
- default:
- }
- select {
- case <-ndpDisp.autoGenAddrC:
- t.Error("unexpected auto-generated address event")
- default:
- }
- })
- }
-}
-
-// TestDHCPv6ConfigurationFromNDPDA tests that the NDPDispatcher is properly
-// informed when new information about what configurations are available via
-// DHCPv6 is learned.
-func TestDHCPv6ConfigurationFromNDPDA(t *testing.T) {
- const nicID = 1
-
- ndpDisp := ndpDispatcher{
- dhcpv6ConfigurationC: make(chan ndpDHCPv6Event, 1),
- }
- e := channel.New(0, 1280, linkAddr1)
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- },
- NDPDisp: &ndpDisp,
- })},
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- expectDHCPv6Event := func(configuration ipv6.DHCPv6ConfigurationFromNDPRA) {
- t.Helper()
- select {
- case e := <-ndpDisp.dhcpv6ConfigurationC:
- if diff := cmp.Diff(ndpDHCPv6Event{nicID: nicID, configuration: configuration}, e, cmp.AllowUnexported(e)); diff != "" {
- t.Errorf("dhcpv6 event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected DHCPv6 configuration event")
- }
- }
-
- expectNoDHCPv6Event := func() {
- t.Helper()
- select {
- case <-ndpDisp.dhcpv6ConfigurationC:
- t.Fatal("unexpected DHCPv6 configuration event")
- default:
- }
- }
-
- // Even if the first RA reports no DHCPv6 configurations are available, the
- // dispatcher should get an event.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false))
- expectDHCPv6Event(ipv6.DHCPv6NoConfiguration)
- // Receiving the same update again should not result in an event to the
- // dispatcher.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false))
- expectNoDHCPv6Event()
-
- // Receive an RA that updates the DHCPv6 configuration to Other
- // Configurations.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
- expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
- expectNoDHCPv6Event()
-
- // Receive an RA that updates the DHCPv6 configuration to Managed Address.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false))
- expectDHCPv6Event(ipv6.DHCPv6ManagedAddress)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false))
- expectNoDHCPv6Event()
-
- // Receive an RA that updates the DHCPv6 configuration to none.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false))
- expectDHCPv6Event(ipv6.DHCPv6NoConfiguration)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false))
- expectNoDHCPv6Event()
-
- // Receive an RA that updates the DHCPv6 configuration to Managed Address.
- //
- // Note, when the M flag is set, the O flag is redundant.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true))
- expectDHCPv6Event(ipv6.DHCPv6ManagedAddress)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true))
- expectNoDHCPv6Event()
- // Even though the DHCPv6 flags are different, the effective configuration is
- // the same so we should not receive a new event.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false))
- expectNoDHCPv6Event()
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true))
- expectNoDHCPv6Event()
-
- // Receive an RA that updates the DHCPv6 configuration to Other
- // Configurations.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
- expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
- expectNoDHCPv6Event()
-
- // Cycling the NIC should cause the last DHCPv6 configuration to be cleared.
- if err := s.DisableNIC(nicID); err != nil {
- t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
- }
- if err := s.EnableNIC(nicID); err != nil {
- t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
- }
-
- // Receive an RA that updates the DHCPv6 configuration to Other
- // Configurations.
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
- expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations)
- e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
- expectNoDHCPv6Event()
-}
-
-var _ rand.Source = (*savingRandSource)(nil)
-
-type savingRandSource struct {
- s rand.Source
-
- lastInt63 int64
-}
-
-func (d *savingRandSource) Int63() int64 {
- i := d.s.Int63()
- d.lastInt63 = i
- return i
-}
-func (d *savingRandSource) Seed(seed int64) {
- d.s.Seed(seed)
-}
-
-// TestRouterSolicitation tests the initial Router Solicitations that are sent
-// when a NIC newly becomes enabled.
-func TestRouterSolicitation(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- linkHeaderLen uint16
- linkAddr tcpip.LinkAddress
- nicAddr tcpip.Address
- expectedSrcAddr tcpip.Address
- expectedNDPOpts []header.NDPOption
- maxRtrSolicit uint8
- rtrSolicitInt time.Duration
- effectiveRtrSolicitInt time.Duration
- maxRtrSolicitDelay time.Duration
- effectiveMaxRtrSolicitDelay time.Duration
- }{
- {
- name: "Single RS with 2s delay and interval",
- expectedSrcAddr: header.IPv6Any,
- maxRtrSolicit: 1,
- rtrSolicitInt: 2 * time.Second,
- effectiveRtrSolicitInt: 2 * time.Second,
- maxRtrSolicitDelay: 2 * time.Second,
- effectiveMaxRtrSolicitDelay: 2 * time.Second,
- },
- {
- name: "Single RS with 4s delay and interval",
- expectedSrcAddr: header.IPv6Any,
- maxRtrSolicit: 1,
- rtrSolicitInt: 4 * time.Second,
- effectiveRtrSolicitInt: 4 * time.Second,
- maxRtrSolicitDelay: 4 * time.Second,
- effectiveMaxRtrSolicitDelay: 4 * time.Second,
- },
- {
- name: "Two RS with delay",
- linkHeaderLen: 1,
- nicAddr: llAddr1,
- expectedSrcAddr: llAddr1,
- maxRtrSolicit: 2,
- rtrSolicitInt: 2 * time.Second,
- effectiveRtrSolicitInt: 2 * time.Second,
- maxRtrSolicitDelay: 500 * time.Millisecond,
- effectiveMaxRtrSolicitDelay: 500 * time.Millisecond,
- },
- {
- name: "Single RS without delay",
- linkHeaderLen: 2,
- linkAddr: linkAddr1,
- nicAddr: llAddr1,
- expectedSrcAddr: llAddr1,
- expectedNDPOpts: []header.NDPOption{
- header.NDPSourceLinkLayerAddressOption(linkAddr1),
- },
- maxRtrSolicit: 1,
- rtrSolicitInt: 2 * time.Second,
- effectiveRtrSolicitInt: 2 * time.Second,
- maxRtrSolicitDelay: 0,
- effectiveMaxRtrSolicitDelay: 0,
- },
- {
- name: "Two RS without delay and invalid zero interval",
- linkHeaderLen: 3,
- linkAddr: linkAddr1,
- expectedSrcAddr: header.IPv6Any,
- maxRtrSolicit: 2,
- rtrSolicitInt: 0,
- effectiveRtrSolicitInt: 4 * time.Second,
- maxRtrSolicitDelay: 0,
- effectiveMaxRtrSolicitDelay: 0,
- },
- {
- name: "Three RS without delay",
- linkAddr: linkAddr1,
- expectedSrcAddr: header.IPv6Any,
- maxRtrSolicit: 3,
- rtrSolicitInt: 500 * time.Millisecond,
- effectiveRtrSolicitInt: 500 * time.Millisecond,
- maxRtrSolicitDelay: 0,
- effectiveMaxRtrSolicitDelay: 0,
- },
- {
- name: "Two RS with invalid negative delay",
- linkAddr: linkAddr1,
- expectedSrcAddr: header.IPv6Any,
- maxRtrSolicit: 2,
- rtrSolicitInt: time.Second,
- effectiveRtrSolicitInt: time.Second,
- maxRtrSolicitDelay: -3 * time.Second,
- effectiveMaxRtrSolicitDelay: time.Second,
- },
- }
-
- subTests := []struct {
- name string
- handleRAs ipv6.HandleRAsConfiguration
- afterFirstRS func(*testing.T, *stack.Stack)
- }{
- {
- name: "Handle RAs when forwarding disabled",
- handleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- afterFirstRS: func(*testing.T, *stack.Stack) {},
- },
-
- // Enabling forwarding when RAs are always configured to be handled
- // should not stop router solicitations.
- {
- name: "Handle RAs always",
- handleRAs: ipv6.HandlingRAsAlwaysEnabled,
- afterFirstRS: func(t *testing.T, s *stack.Stack) {
- if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err)
- }
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- for _, subTest := range subTests {
- t.Run(subTest.name, func(t *testing.T) {
- clock := faketime.NewManualClock()
- e := channelLinkWithHeaderLength{
- Endpoint: channel.New(int(test.maxRtrSolicit), 1280, test.linkAddr),
- headerLength: test.linkHeaderLen,
- }
- e.Endpoint.LinkEPCapabilities |= stack.CapabilityResolutionRequired
- waitForPkt := func(timeout time.Duration) {
- t.Helper()
-
- clock.Advance(timeout)
- p, ok := e.Read()
- if !ok {
- t.Fatal("expected router solicitation packet")
- }
-
- if p.Proto != header.IPv6ProtocolNumber {
- t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber)
- }
-
- // Make sure the right remote link address is used.
- if want := header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersLinkLocalMulticastAddress); p.Route.RemoteLinkAddress != want {
- t.Errorf("got remote link address = %s, want = %s", p.Route.RemoteLinkAddress, want)
- }
-
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(test.expectedSrcAddr),
- checker.DstAddr(header.IPv6AllRoutersLinkLocalMulticastAddress),
- checker.TTL(header.NDPHopLimit),
- checker.NDPRS(checker.NDPRSOptions(test.expectedNDPOpts)),
- )
-
- if l, want := p.Pkt.AvailableHeaderBytes(), int(test.linkHeaderLen); l != want {
- t.Errorf("got p.Pkt.AvailableHeaderBytes() = %d; want = %d", l, want)
- }
- }
- waitForNothing := func(timeout time.Duration) {
- t.Helper()
-
- clock.Advance(timeout)
- if p, ok := e.Read(); ok {
- t.Fatalf("unexpectedly got a packet = %#v", p)
- }
- }
- randSource := savingRandSource{
- s: rand.NewSource(time.Now().UnixNano()),
- }
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: subTest.handleRAs,
- MaxRtrSolicitations: test.maxRtrSolicit,
- RtrSolicitationInterval: test.rtrSolicitInt,
- MaxRtrSolicitationDelay: test.maxRtrSolicitDelay,
- },
- })},
- Clock: clock,
- RandSource: &randSource,
- })
-
- opts := stack.NICOptions{Disabled: true}
- if err := s.CreateNICWithOptions(nicID, &e, opts); err != nil {
- t.Fatalf("CreateNICWithOptions(%d, _, %#v) = %s", nicID, opts, err)
- }
-
- if addr := test.nicAddr; addr != "" {
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: header.IPv6ProtocolNumber,
- AddressWithPrefix: addr.WithPrefix(),
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
- }
- }
-
- if err := s.EnableNIC(nicID); err != nil {
- t.Fatalf("EnableNIC(%d): %s", nicID, err)
- }
-
- // Make sure each RS is sent at the right time.
- remaining := test.maxRtrSolicit
- if remaining != 0 {
- maxRtrSolicitDelay := test.maxRtrSolicitDelay
- if maxRtrSolicitDelay < 0 {
- maxRtrSolicitDelay = ipv6.DefaultNDPConfigurations().MaxRtrSolicitationDelay
- }
- var actualRtrSolicitDelay time.Duration
- if maxRtrSolicitDelay != 0 {
- actualRtrSolicitDelay = time.Duration(randSource.lastInt63) % maxRtrSolicitDelay
- }
- waitForPkt(actualRtrSolicitDelay)
- remaining--
- }
-
- subTest.afterFirstRS(t, s)
-
- for ; remaining != 0; remaining-- {
- if test.effectiveRtrSolicitInt != 0 {
- waitForNothing(test.effectiveRtrSolicitInt - time.Nanosecond)
- waitForPkt(time.Nanosecond)
- } else {
- waitForPkt(0)
- }
- }
-
- // Make sure no more RS.
- if test.effectiveRtrSolicitInt > test.effectiveMaxRtrSolicitDelay {
- waitForNothing(test.effectiveRtrSolicitInt)
- } else {
- waitForNothing(test.effectiveMaxRtrSolicitDelay)
- }
-
- if got, want := s.Stats().ICMP.V6.PacketsSent.RouterSolicit.Value(), uint64(test.maxRtrSolicit); got != want {
- t.Fatalf("got sent RouterSolicit = %d, want = %d", got, want)
- }
- })
- }
- })
- }
-}
-
-func TestStopStartSolicitingRouters(t *testing.T) {
- const nicID = 1
- const delay = 0
- const interval = 500 * time.Millisecond
- const maxRtrSolicitations = 3
-
- tests := []struct {
- name string
- startFn func(t *testing.T, s *stack.Stack)
- // first is used to tell stopFn that it is being called for the first time
- // after router solicitations were last enabled.
- stopFn func(t *testing.T, s *stack.Stack, first bool)
- }{
- // Tests that when forwarding is enabled or disabled, router solicitations
- // are stopped or started, respectively.
- {
- name: "Enable and disable forwarding",
- startFn: func(t *testing.T, s *stack.Stack) {
- t.Helper()
-
- if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, false); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, false): %s", ipv6.ProtocolNumber, err)
- }
- },
- stopFn: func(t *testing.T, s *stack.Stack, _ bool) {
- t.Helper()
-
- if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil {
- t.Fatalf("SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err)
- }
- },
- },
-
- // Tests that when a NIC is enabled or disabled, router solicitations
- // are started or stopped, respectively.
- {
- name: "Enable and disable NIC",
- startFn: func(t *testing.T, s *stack.Stack) {
- t.Helper()
-
- if err := s.EnableNIC(nicID); err != nil {
- t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
- }
- },
- stopFn: func(t *testing.T, s *stack.Stack, _ bool) {
- t.Helper()
-
- if err := s.DisableNIC(nicID); err != nil {
- t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
- }
- },
- },
-
- // Tests that when a NIC is removed, router solicitations are stopped. We
- // cannot start router solications on a removed NIC.
- {
- name: "Remove NIC",
- stopFn: func(t *testing.T, s *stack.Stack, first bool) {
- t.Helper()
-
- // Only try to remove the NIC the first time stopFn is called since it's
- // impossible to remove an already removed NIC.
- if !first {
- return
- }
-
- if err := s.RemoveNIC(nicID); err != nil {
- t.Fatalf("s.RemoveNIC(%d): %s", nicID, err)
- }
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- e := channel.New(maxRtrSolicitations, 1280, linkAddr1)
- waitForPkt := func(clock *faketime.ManualClock, timeout time.Duration) {
- t.Helper()
-
- clock.Advance(timeout)
- p, ok := e.Read()
- if !ok {
- t.Fatal("timed out waiting for packet")
- }
-
- if p.Proto != header.IPv6ProtocolNumber {
- t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber)
- }
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(header.IPv6Any),
- checker.DstAddr(header.IPv6AllRoutersLinkLocalMulticastAddress),
- checker.TTL(header.NDPHopLimit),
- checker.NDPRS())
- }
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
- HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled,
- MaxRtrSolicitations: maxRtrSolicitations,
- RtrSolicitationInterval: interval,
- MaxRtrSolicitationDelay: delay,
- },
- })},
- Clock: clock,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- // Stop soliciting routers.
- test.stopFn(t, s, true /* first */)
- clock.Advance(delay)
- if _, ok := e.Read(); ok {
- // A single RS may have been sent before solicitations were stopped.
- clock.Advance(interval)
- if _, ok = e.Read(); ok {
- t.Fatal("should not have sent more than one RS message")
- }
- }
-
- // Stopping router solicitations after it has already been stopped should
- // do nothing.
- test.stopFn(t, s, false /* first */)
- clock.Advance(delay)
- if _, ok := e.Read(); ok {
- t.Fatal("unexpectedly got a packet after router solicitation has been stopepd")
- }
-
- // If test.startFn is nil, there is no way to restart router solications.
- if test.startFn == nil {
- return
- }
-
- // Start soliciting routers.
- test.startFn(t, s)
- waitForPkt(clock, delay)
- waitForPkt(clock, interval)
- waitForPkt(clock, interval)
- clock.Advance(interval)
- if _, ok := e.Read(); ok {
- t.Fatal("unexpectedly got an extra packet after sending out the expected RSs")
- }
-
- // Starting router solicitations after it has already completed should do
- // nothing.
- test.startFn(t, s)
- clock.Advance(interval)
- if _, ok := e.Read(); ok {
- t.Fatal("unexpectedly got a packet after finishing router solicitations")
- }
- })
- }
-}
diff --git a/pkg/tcpip/stack/neighbor_cache_test.go b/pkg/tcpip/stack/neighbor_cache_test.go
deleted file mode 100644
index 7de25fe37..000000000
--- a/pkg/tcpip/stack/neighbor_cache_test.go
+++ /dev/null
@@ -1,1584 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack
-
-import (
- "fmt"
- "math"
- "math/rand"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/faketime"
-)
-
-const (
- // entryStoreSize is the default number of entries that will be generated and
- // added to the entry store. This number needs to be larger than the size of
- // the neighbor cache to give ample opportunity for verifying behavior during
- // cache overflows. Four times the size of the neighbor cache allows for
- // three complete cache overflows.
- entryStoreSize = 4 * neighborCacheSize
-
- // typicalLatency is the typical latency for an ARP or NDP packet to travel
- // to a router and back.
- typicalLatency = time.Millisecond
-
- // testEntryBroadcastAddr is a special address that indicates a packet should
- // be sent to all nodes.
- testEntryBroadcastAddr = tcpip.Address("broadcast")
-
- // testEntryBroadcastLinkAddr is a special link address sent back to
- // multicast neighbor probes.
- testEntryBroadcastLinkAddr = tcpip.LinkAddress("mac_broadcast")
-
- // infiniteDuration indicates that a task will not occur in our lifetime.
- infiniteDuration = time.Duration(math.MaxInt64)
-)
-
-// unorderedEventsDiffOpts returns options passed to cmp.Diff to sort slices of
-// events for cases where ordering must be ignored.
-func unorderedEventsDiffOpts() []cmp.Option {
- return []cmp.Option{
- cmpopts.SortSlices(func(a, b testEntryEventInfo) bool {
- return strings.Compare(string(a.Entry.Addr), string(b.Entry.Addr)) < 0
- }),
- }
-}
-
-// unorderedEntriesDiffOpts returns options passed to cmp.Diff to sort slices of
-// entries for cases where ordering must be ignored.
-func unorderedEntriesDiffOpts() []cmp.Option {
- return []cmp.Option{
- cmpopts.SortSlices(func(a, b NeighborEntry) bool {
- return strings.Compare(string(a.Addr), string(b.Addr)) < 0
- }),
- }
-}
-
-func newTestNeighborResolver(nudDisp NUDDispatcher, config NUDConfigurations, clock tcpip.Clock) *testNeighborResolver {
- config.resetInvalidFields()
- rng := rand.New(rand.NewSource(time.Now().UnixNano()))
- linkRes := &testNeighborResolver{
- clock: clock,
- entries: newTestEntryStore(),
- delay: typicalLatency,
- }
- linkRes.neigh.init(&nic{
- stack: &Stack{
- clock: clock,
- nudDisp: nudDisp,
- nudConfigs: config,
- randomGenerator: rng,
- },
- id: 1,
- stats: makeNICStats(tcpip.NICStats{}.FillIn()),
- }, linkRes)
- return linkRes
-}
-
-// testEntryStore contains a set of IP to NeighborEntry mappings.
-type testEntryStore struct {
- mu sync.RWMutex
- entriesMap map[tcpip.Address]NeighborEntry
-}
-
-func toAddress(i uint16) tcpip.Address {
- return tcpip.Address([]byte{
- 1,
- 0,
- byte(i >> 8),
- byte(i),
- })
-}
-
-func toLinkAddress(i uint16) tcpip.LinkAddress {
- return tcpip.LinkAddress([]byte{
- 1,
- 0,
- 0,
- 0,
- byte(i >> 8),
- byte(i),
- })
-}
-
-// newTestEntryStore returns a testEntryStore pre-populated with entries.
-func newTestEntryStore() *testEntryStore {
- store := &testEntryStore{
- entriesMap: make(map[tcpip.Address]NeighborEntry),
- }
- for i := uint16(0); i < entryStoreSize; i++ {
- addr := toAddress(i)
- linkAddr := toLinkAddress(i)
-
- store.entriesMap[addr] = NeighborEntry{
- Addr: addr,
- LinkAddr: linkAddr,
- }
- }
- return store
-}
-
-// size returns the number of entries in the store.
-func (s *testEntryStore) size() uint16 {
- s.mu.RLock()
- defer s.mu.RUnlock()
- return uint16(len(s.entriesMap))
-}
-
-// entry returns the entry at index i. Returns an empty entry and false if i is
-// out of bounds.
-func (s *testEntryStore) entry(i uint16) (NeighborEntry, bool) {
- return s.entryByAddr(toAddress(i))
-}
-
-// entryByAddr returns the entry matching addr for situations when the index is
-// not available. Returns an empty entry and false if no entries match addr.
-func (s *testEntryStore) entryByAddr(addr tcpip.Address) (NeighborEntry, bool) {
- s.mu.RLock()
- defer s.mu.RUnlock()
- entry, ok := s.entriesMap[addr]
- return entry, ok
-}
-
-// entries returns all entries in the store.
-func (s *testEntryStore) entries() []NeighborEntry {
- entries := make([]NeighborEntry, 0, len(s.entriesMap))
- s.mu.RLock()
- defer s.mu.RUnlock()
- for i := uint16(0); i < entryStoreSize; i++ {
- addr := toAddress(i)
- if entry, ok := s.entriesMap[addr]; ok {
- entries = append(entries, entry)
- }
- }
- return entries
-}
-
-// set modifies the link addresses of an entry.
-func (s *testEntryStore) set(i uint16, linkAddr tcpip.LinkAddress) {
- addr := toAddress(i)
- s.mu.Lock()
- defer s.mu.Unlock()
- if entry, ok := s.entriesMap[addr]; ok {
- entry.LinkAddr = linkAddr
- s.entriesMap[addr] = entry
- }
-}
-
-// testNeighborResolver implements LinkAddressResolver to emulate sending a
-// neighbor probe.
-type testNeighborResolver struct {
- clock tcpip.Clock
- neigh neighborCache
- entries *testEntryStore
- delay time.Duration
- onLinkAddressRequest func()
- dropReplies bool
-}
-
-var _ LinkAddressResolver = (*testNeighborResolver)(nil)
-
-func (r *testNeighborResolver) LinkAddressRequest(targetAddr, _ tcpip.Address, _ tcpip.LinkAddress) tcpip.Error {
- if !r.dropReplies {
- // Delay handling the request to emulate network latency.
- r.clock.AfterFunc(r.delay, func() {
- r.fakeRequest(targetAddr)
- })
- }
-
- // Execute post address resolution action, if available.
- if f := r.onLinkAddressRequest; f != nil {
- f()
- }
- return nil
-}
-
-// fakeRequest emulates handling a response for a link address request.
-func (r *testNeighborResolver) fakeRequest(addr tcpip.Address) {
- if entry, ok := r.entries.entryByAddr(addr); ok {
- r.neigh.handleConfirmation(addr, entry.LinkAddr, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
- }
-}
-
-func (*testNeighborResolver) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) {
- if addr == testEntryBroadcastAddr {
- return testEntryBroadcastLinkAddr, true
- }
- return "", false
-}
-
-func (*testNeighborResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
- return 0
-}
-
-func TestNeighborCacheGetConfig(t *testing.T) {
- nudDisp := testNUDDispatcher{}
- c := DefaultNUDConfigurations()
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, c, clock)
-
- if got, want := linkRes.neigh.config(), c; got != want {
- t.Errorf("got linkRes.neigh.config() = %+v, want = %+v", got, want)
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- defer nudDisp.mu.Unlock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-func TestNeighborCacheSetConfig(t *testing.T) {
- nudDisp := testNUDDispatcher{}
- c := DefaultNUDConfigurations()
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, c, clock)
-
- c.MinRandomFactor = 1
- c.MaxRandomFactor = 1
- linkRes.neigh.setConfig(c)
-
- if got, want := linkRes.neigh.config(), c; got != want {
- t.Errorf("got linkRes.neigh.config() = %+v, want = %+v", got, want)
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- defer nudDisp.mu.Unlock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-func addReachableEntryWithRemoved(nudDisp *testNUDDispatcher, clock *faketime.ManualClock, linkRes *testNeighborResolver, entry NeighborEntry, removed []NeighborEntry) error {
- var gotLinkResolutionResult LinkResolutionResult
-
- _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
- gotLinkResolutionResult = r
- })
- if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
- return fmt.Errorf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
- }
-
- {
- var wantEvents []testEntryEventInfo
-
- for _, removedEntry := range removed {
- wantEvents = append(wantEvents, testEntryEventInfo{
- EventType: entryTestRemoved,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: removedEntry.Addr,
- LinkAddr: removedEntry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- })
- }
-
- wantEvents = append(wantEvents, testEntryEventInfo{
- EventType: entryTestAdded,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: "",
- State: Incomplete,
- UpdatedAt: clock.Now(),
- },
- })
-
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- clock.Advance(typicalLatency)
-
- select {
- case <-ch:
- default:
- return fmt.Errorf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
- }
- wantLinkResolutionResult := LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil}
- if diff := cmp.Diff(wantLinkResolutionResult, gotLinkResolutionResult); diff != "" {
- return fmt.Errorf("got link resolution result mismatch (-want +got):\n%s", diff)
- }
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- return nil
-}
-
-func addReachableEntry(nudDisp *testNUDDispatcher, clock *faketime.ManualClock, linkRes *testNeighborResolver, entry NeighborEntry) error {
- return addReachableEntryWithRemoved(nudDisp, clock, linkRes, entry, nil /* removed */)
-}
-
-func TestNeighborCacheEntry(t *testing.T) {
- c := DefaultNUDConfigurations()
- nudDisp := testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, c, clock)
-
- entry, ok := linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
- }
- if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
-
- if _, _, err := linkRes.neigh.entry(entry.Addr, "", nil); err != nil {
- t.Fatalf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err)
- }
-
- // No more events should have been dispatched.
- nudDisp.mu.Lock()
- defer nudDisp.mu.Unlock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-func TestNeighborCacheRemoveEntry(t *testing.T) {
- config := DefaultNUDConfigurations()
-
- nudDisp := testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
- entry, ok := linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
- }
- if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
-
- linkRes.neigh.removeEntry(entry.Addr)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestRemoved,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- {
- _, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
- if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
- t.Errorf("got linkRes.neigh.entry(%s, '', nil) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
- }
- }
-}
-
-type testContext struct {
- clock *faketime.ManualClock
- linkRes *testNeighborResolver
- nudDisp *testNUDDispatcher
-}
-
-func newTestContext(c NUDConfigurations) testContext {
- nudDisp := &testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(nudDisp, c, clock)
-
- return testContext{
- clock: clock,
- linkRes: linkRes,
- nudDisp: nudDisp,
- }
-}
-
-type overflowOptions struct {
- startAtEntryIndex uint16
- wantStaticEntries []NeighborEntry
-}
-
-func (c *testContext) overflowCache(opts overflowOptions) error {
- // Fill the neighbor cache to capacity to verify the LRU eviction strategy is
- // working properly after the entry removal.
- for i := opts.startAtEntryIndex; i < c.linkRes.entries.size(); i++ {
- var removedEntries []NeighborEntry
-
- // When beyond the full capacity, the cache will evict an entry as per the
- // LRU eviction strategy. Note that the number of static entries should not
- // affect the total number of dynamic entries that can be added.
- if i >= neighborCacheSize+opts.startAtEntryIndex {
- removedEntry, ok := c.linkRes.entries.entry(i - neighborCacheSize)
- if !ok {
- return fmt.Errorf("got linkRes.entries.entry(%d) = _, false, want = true", i-neighborCacheSize)
- }
- removedEntries = append(removedEntries, removedEntry)
- }
-
- entry, ok := c.linkRes.entries.entry(i)
- if !ok {
- return fmt.Errorf("got c.linkRes.entries.entry(%d) = _, false, want = true", i)
- }
- if err := addReachableEntryWithRemoved(c.nudDisp, c.clock, c.linkRes, entry, removedEntries); err != nil {
- return fmt.Errorf("addReachableEntryWithRemoved(...) = %s", err)
- }
- }
-
- // Expect to find only the most recent entries. The order of entries reported
- // by entries() is nondeterministic, so entries have to be sorted before
- // comparison.
- wantUnorderedEntries := opts.wantStaticEntries
- for i := c.linkRes.entries.size() - neighborCacheSize; i < c.linkRes.entries.size(); i++ {
- entry, ok := c.linkRes.entries.entry(i)
- if !ok {
- return fmt.Errorf("got c.linkRes.entries.entry(%d) = _, false, want = true", i)
- }
- durationReachableNanos := time.Duration(c.linkRes.entries.size()-i-1) * typicalLatency
- wantEntry := NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: c.clock.Now().Add(-durationReachableNanos),
- }
- wantUnorderedEntries = append(wantUnorderedEntries, wantEntry)
- }
-
- if diff := cmp.Diff(wantUnorderedEntries, c.linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" {
- return fmt.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff)
- }
-
- // No more events should have been dispatched.
- c.nudDisp.mu.Lock()
- defer c.nudDisp.mu.Unlock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), c.nudDisp.mu.events); diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-
- return nil
-}
-
-// TestNeighborCacheOverflow verifies that the LRU cache eviction strategy
-// respects the dynamic entry count.
-func TestNeighborCacheOverflow(t *testing.T) {
- config := DefaultNUDConfigurations()
- // Stay in Reachable so the cache can overflow
- config.BaseReachableTime = infiniteDuration
- config.MinRandomFactor = 1
- config.MaxRandomFactor = 1
-
- c := newTestContext(config)
- opts := overflowOptions{
- startAtEntryIndex: 0,
- }
- if err := c.overflowCache(opts); err != nil {
- t.Errorf("c.overflowCache(%+v): %s", opts, err)
- }
-}
-
-// TestNeighborCacheRemoveEntryThenOverflow verifies that the LRU cache
-// eviction strategy respects the dynamic entry count when an entry is removed.
-func TestNeighborCacheRemoveEntryThenOverflow(t *testing.T) {
- config := DefaultNUDConfigurations()
- // Stay in Reachable so the cache can overflow
- config.BaseReachableTime = infiniteDuration
- config.MinRandomFactor = 1
- config.MaxRandomFactor = 1
-
- c := newTestContext(config)
-
- // Add a dynamic entry
- entry, ok := c.linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
- }
- if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
-
- // Remove the entry
- c.linkRes.neigh.removeEntry(entry.Addr)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestRemoved,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- opts := overflowOptions{
- startAtEntryIndex: 0,
- }
- if err := c.overflowCache(opts); err != nil {
- t.Errorf("c.overflowCache(%+v): %s", opts, err)
- }
-}
-
-// TestNeighborCacheDuplicateStaticEntryWithSameLinkAddress verifies that
-// adding a duplicate static entry with the same link address does not dispatch
-// any events.
-func TestNeighborCacheDuplicateStaticEntryWithSameLinkAddress(t *testing.T) {
- config := DefaultNUDConfigurations()
- c := newTestContext(config)
-
- // Add a static entry
- entry, ok := c.linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
- }
- staticLinkAddr := entry.LinkAddr + "static"
- c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestAdded,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: staticLinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // Add a duplicate static entry with the same link address.
- c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
- c.nudDisp.mu.Lock()
- defer c.nudDisp.mu.Unlock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), c.nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-// TestNeighborCacheDuplicateStaticEntryWithDifferentLinkAddress verifies that
-// adding a duplicate static entry with a different link address dispatches a
-// change event.
-func TestNeighborCacheDuplicateStaticEntryWithDifferentLinkAddress(t *testing.T) {
- config := DefaultNUDConfigurations()
- c := newTestContext(config)
-
- // Add a static entry
- entry, ok := c.linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
- }
- staticLinkAddr := entry.LinkAddr + "static"
- c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestAdded,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: staticLinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // Add a duplicate entry with a different link address
- staticLinkAddr += "duplicate"
- c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: staticLinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-}
-
-// TestNeighborCacheRemoveStaticEntryThenOverflow verifies that the LRU cache
-// eviction strategy respects the dynamic entry count when a static entry is
-// added then removed. In this case, the dynamic entry count shouldn't have
-// been touched.
-func TestNeighborCacheRemoveStaticEntryThenOverflow(t *testing.T) {
- config := DefaultNUDConfigurations()
- // Stay in Reachable so the cache can overflow
- config.BaseReachableTime = infiniteDuration
- config.MinRandomFactor = 1
- config.MaxRandomFactor = 1
-
- c := newTestContext(config)
-
- // Add a static entry
- entry, ok := c.linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
- }
- staticLinkAddr := entry.LinkAddr + "static"
- c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestAdded,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: staticLinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // Remove the static entry that was just added
- c.linkRes.neigh.removeEntry(entry.Addr)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestRemoved,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: staticLinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- opts := overflowOptions{
- startAtEntryIndex: 0,
- }
- if err := c.overflowCache(opts); err != nil {
- t.Errorf("c.overflowCache(%+v): %s", opts, err)
- }
-}
-
-// TestNeighborCacheOverwriteWithStaticEntryThenOverflow verifies that the LRU
-// cache eviction strategy keeps count of the dynamic entry count when an entry
-// is overwritten by a static entry. Static entries should not count towards
-// the size of the LRU cache.
-func TestNeighborCacheOverwriteWithStaticEntryThenOverflow(t *testing.T) {
- config := DefaultNUDConfigurations()
- // Stay in Reachable so the cache can overflow
- config.BaseReachableTime = infiniteDuration
- config.MinRandomFactor = 1
- config.MaxRandomFactor = 1
-
- c := newTestContext(config)
-
- // Add a dynamic entry
- entry, ok := c.linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
- }
- if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
-
- // Override the entry with a static one using the same address
- staticLinkAddr := entry.LinkAddr + "static"
- c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestRemoved,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: c.clock.Now(),
- },
- },
- {
- EventType: entryTestAdded,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: staticLinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- opts := overflowOptions{
- startAtEntryIndex: 1,
- wantStaticEntries: []NeighborEntry{
- {
- Addr: entry.Addr,
- LinkAddr: staticLinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- if err := c.overflowCache(opts); err != nil {
- t.Errorf("c.overflowCache(%+v): %s", opts, err)
- }
-}
-
-func TestNeighborCacheAddStaticEntryThenOverflow(t *testing.T) {
- config := DefaultNUDConfigurations()
- // Stay in Reachable so the cache can overflow
- config.BaseReachableTime = infiniteDuration
- config.MinRandomFactor = 1
- config.MaxRandomFactor = 1
-
- c := newTestContext(config)
-
- entry, ok := c.linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
- }
- c.linkRes.neigh.addStaticEntry(entry.Addr, entry.LinkAddr)
- e, _, err := c.linkRes.neigh.entry(entry.Addr, "", nil)
- if err != nil {
- t.Errorf("unexpected error from c.linkRes.neigh.entry(%s, \"\", nil): %s", entry.Addr, err)
- }
- want := NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- }
- if diff := cmp.Diff(want, e); diff != "" {
- t.Errorf("c.linkRes.neigh.entry(%s, \"\", nil) mismatch (-want, +got):\n%s", entry.Addr, diff)
- }
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestAdded,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- opts := overflowOptions{
- startAtEntryIndex: 1,
- wantStaticEntries: []NeighborEntry{
- {
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Static,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- if err := c.overflowCache(opts); err != nil {
- t.Errorf("c.overflowCache(%+v): %s", opts, err)
- }
-}
-
-func TestNeighborCacheClear(t *testing.T) {
- config := DefaultNUDConfigurations()
-
- nudDisp := testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
- // Add a dynamic entry.
- entry, ok := linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
- }
- if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
-
- // Add a static entry.
- linkRes.neigh.addStaticEntry(entryTestAddr1, entryTestLinkAddr1)
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestAdded,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Static,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // Clear should remove both dynamic and static entries.
- linkRes.neigh.clear()
-
- // Remove events dispatched from clear() have no deterministic order so they
- // need to be sorted before comparison.
- wantUnorderedEvents := []testEntryEventInfo{
- {
- EventType: entryTestRemoved,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- {
- EventType: entryTestRemoved,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Static,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- defer nudDisp.mu.Unlock()
- if diff := cmp.Diff(wantUnorderedEvents, nudDisp.mu.events, unorderedEventsDiffOpts()...); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-// TestNeighborCacheClearThenOverflow verifies that the LRU cache eviction
-// strategy keeps count of the dynamic entry count when all entries are
-// cleared.
-func TestNeighborCacheClearThenOverflow(t *testing.T) {
- config := DefaultNUDConfigurations()
- // Stay in Reachable so the cache can overflow
- config.BaseReachableTime = infiniteDuration
- config.MinRandomFactor = 1
- config.MaxRandomFactor = 1
-
- c := newTestContext(config)
-
- // Add a dynamic entry
- entry, ok := c.linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
- }
- if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
-
- // Clear the cache.
- c.linkRes.neigh.clear()
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestRemoved,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: c.clock.Now(),
- },
- },
- }
- c.nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
- c.nudDisp.mu.events = nil
- c.nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- opts := overflowOptions{
- startAtEntryIndex: 0,
- }
- if err := c.overflowCache(opts); err != nil {
- t.Errorf("c.overflowCache(%+v): %s", opts, err)
- }
-}
-
-func TestNeighborCacheKeepFrequentlyUsed(t *testing.T) {
- config := DefaultNUDConfigurations()
- // Stay in Reachable so the cache can overflow
- config.BaseReachableTime = infiniteDuration
- config.MinRandomFactor = 1
- config.MaxRandomFactor = 1
-
- nudDisp := testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
- startedAt := clock.Now()
-
- // The following logic is very similar to overflowCache, but
- // periodically refreshes the frequently used entry.
-
- // Fill the neighbor cache to capacity
- for i := uint16(0); i < neighborCacheSize; i++ {
- entry, ok := linkRes.entries.entry(i)
- if !ok {
- t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i)
- }
- if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
- }
-
- frequentlyUsedEntry, ok := linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
- }
-
- // Keep adding more entries
- for i := uint16(neighborCacheSize); i < linkRes.entries.size(); i++ {
- // Periodically refresh the frequently used entry
- if i%(neighborCacheSize/2) == 0 {
- if _, _, err := linkRes.neigh.entry(frequentlyUsedEntry.Addr, "", nil); err != nil {
- t.Errorf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", frequentlyUsedEntry.Addr, err)
- }
- }
-
- entry, ok := linkRes.entries.entry(i)
- if !ok {
- t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i)
- }
-
- // An entry should have been removed, as per the LRU eviction strategy
- removedEntry, ok := linkRes.entries.entry(i - neighborCacheSize + 1)
- if !ok {
- t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i-neighborCacheSize+1)
- }
-
- if err := addReachableEntryWithRemoved(&nudDisp, clock, linkRes, entry, []NeighborEntry{removedEntry}); err != nil {
- t.Fatalf("addReachableEntryWithRemoved(...) = %s", err)
- }
- }
-
- // Expect to find only the frequently used entry and the most recent entries.
- // The order of entries reported by entries() is nondeterministic, so entries
- // have to be sorted before comparison.
- wantUnsortedEntries := []NeighborEntry{
- {
- Addr: frequentlyUsedEntry.Addr,
- LinkAddr: frequentlyUsedEntry.LinkAddr,
- State: Reachable,
- // Can be inferred since the frequently used entry is the first to
- // be created and transitioned to Reachable.
- UpdatedAt: startedAt.Add(typicalLatency),
- },
- }
-
- for i := linkRes.entries.size() - neighborCacheSize + 1; i < linkRes.entries.size(); i++ {
- entry, ok := linkRes.entries.entry(i)
- if !ok {
- t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i)
- }
- durationReachableNanos := time.Duration(linkRes.entries.size()-i-1) * typicalLatency
- wantUnsortedEntries = append(wantUnsortedEntries, NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now().Add(-durationReachableNanos),
- })
- }
-
- if diff := cmp.Diff(wantUnsortedEntries, linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" {
- t.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff)
- }
-
- // No more events should have been dispatched.
- nudDisp.mu.Lock()
- defer nudDisp.mu.Unlock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-func TestNeighborCacheConcurrent(t *testing.T) {
- const concurrentProcesses = 16
-
- config := DefaultNUDConfigurations()
-
- nudDisp := testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
- storeEntries := linkRes.entries.entries()
- for _, entry := range storeEntries {
- var wg sync.WaitGroup
- for r := 0; r < concurrentProcesses; r++ {
- wg.Add(1)
- go func(entry NeighborEntry) {
- defer wg.Done()
- switch e, _, err := linkRes.neigh.entry(entry.Addr, "", nil); err.(type) {
- case nil, *tcpip.ErrWouldBlock:
- default:
- t.Errorf("got linkRes.neigh.entry(%s, '', nil) = (%+v, _, %s), want (_, _, nil) or (_, _, %s)", entry.Addr, e, err, &tcpip.ErrWouldBlock{})
- }
- }(entry)
- }
-
- // Wait for all goroutines to send a request
- wg.Wait()
-
- // Process all the requests for a single entry concurrently
- clock.Advance(typicalLatency)
- }
-
- // All goroutines add in the same order and add more values than can fit in
- // the cache. Our eviction strategy requires that the last entries are
- // present, up to the size of the neighbor cache, and the rest are missing.
- // The order of entries reported by entries() is nondeterministic, so entries
- // have to be sorted before comparison.
- var wantUnsortedEntries []NeighborEntry
- for i := linkRes.entries.size() - neighborCacheSize; i < linkRes.entries.size(); i++ {
- entry, ok := linkRes.entries.entry(i)
- if !ok {
- t.Errorf("got linkRes.entries.entry(%d) = _, false, want = true", i)
- }
- durationReachableNanos := time.Duration(linkRes.entries.size()-i-1) * typicalLatency
- wantUnsortedEntries = append(wantUnsortedEntries, NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now().Add(-durationReachableNanos),
- })
- }
-
- if diff := cmp.Diff(wantUnsortedEntries, linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" {
- t.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff)
- }
-}
-
-func TestNeighborCacheReplace(t *testing.T) {
- config := DefaultNUDConfigurations()
-
- nudDisp := testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
- entry, ok := linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
- }
- if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
-
- // Notify of a link address change
- var updatedLinkAddr tcpip.LinkAddress
- {
- entry, ok := linkRes.entries.entry(1)
- if !ok {
- t.Fatal("got linkRes.entries.entry(1) = _, false, want = true")
- }
- updatedLinkAddr = entry.LinkAddr
- }
- linkRes.entries.set(0, updatedLinkAddr)
- linkRes.neigh.handleConfirmation(entry.Addr, updatedLinkAddr, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: true,
- IsRouter: false,
- })
-
- // Requesting the entry again should start neighbor reachability confirmation.
- //
- // Verify the entry's new link address and the new state.
- {
- e, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
- if err != nil {
- t.Fatalf("linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err)
- }
- want := NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: updatedLinkAddr,
- State: Delay,
- UpdatedAt: clock.Now(),
- }
- if diff := cmp.Diff(want, e); diff != "" {
- t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff)
- }
- }
-
- clock.Advance(config.DelayFirstProbeTime + typicalLatency)
-
- // Verify that the neighbor is now reachable.
- {
- e, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
- if err != nil {
- t.Errorf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err)
- }
- want := NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: updatedLinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- }
- if diff := cmp.Diff(want, e); diff != "" {
- t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff)
- }
- }
-}
-
-func TestNeighborCacheResolutionFailed(t *testing.T) {
- config := DefaultNUDConfigurations()
-
- nudDisp := testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
- var requestCount uint32
- linkRes.onLinkAddressRequest = func() {
- atomic.AddUint32(&requestCount, 1)
- }
-
- entry, ok := linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
- }
-
- // First, sanity check that resolution is working
- if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
- t.Fatalf("addReachableEntry(...) = %s", err)
- }
-
- got, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
- if err != nil {
- t.Fatalf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err)
- }
- want := NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- }
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff)
- }
-
- // Verify address resolution fails for an unknown address.
- before := atomic.LoadUint32(&requestCount)
-
- entry.Addr += "2"
- {
- _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
- if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" {
- t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
- }
- })
- if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
- t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
- }
- waitFor := config.DelayFirstProbeTime + typicalLatency*time.Duration(config.MaxMulticastProbes)
- clock.Advance(waitFor)
- select {
- case <-ch:
- default:
- t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
- }
- }
-
- maxAttempts := linkRes.neigh.config().MaxUnicastProbes
- if got, want := atomic.LoadUint32(&requestCount)-before, maxAttempts; got != want {
- t.Errorf("got link address request count = %d, want = %d", got, want)
- }
-}
-
-// TestNeighborCacheResolutionTimeout simulates sending MaxMulticastProbes
-// probes and not retrieving a confirmation before the duration defined by
-// MaxMulticastProbes * RetransmitTimer.
-func TestNeighborCacheResolutionTimeout(t *testing.T) {
- config := DefaultNUDConfigurations()
- config.RetransmitTimer = time.Millisecond // small enough to cause timeout
-
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(nil, config, clock)
- // large enough to cause timeout
- linkRes.delay = time.Minute
-
- entry, ok := linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
- }
-
- _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
- if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" {
- t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
- }
- })
- if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
- t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
- }
- waitFor := config.RetransmitTimer * time.Duration(config.MaxMulticastProbes)
- clock.Advance(waitFor)
-
- select {
- case <-ch:
- default:
- t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
- }
-}
-
-// TestNeighborCacheRetryResolution simulates retrying communication after
-// failing to perform address resolution.
-func TestNeighborCacheRetryResolution(t *testing.T) {
- config := DefaultNUDConfigurations()
- nudDisp := testNUDDispatcher{}
- clock := faketime.NewManualClock()
- linkRes := newTestNeighborResolver(&nudDisp, config, clock)
- // Simulate a faulty link.
- linkRes.dropReplies = true
-
- entry, ok := linkRes.entries.entry(0)
- if !ok {
- t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
- }
-
- // Perform address resolution with a faulty link, which will fail.
- {
- _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
- if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" {
- t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
- }
- })
- if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
- t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
- }
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestAdded,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: "",
- State: Incomplete,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- waitFor := config.RetransmitTimer * time.Duration(config.MaxMulticastProbes)
- clock.Advance(waitFor)
-
- select {
- case <-ch:
- default:
- t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
- }
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: "",
- State: Unreachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- {
- wantEntries := []NeighborEntry{
- {
- Addr: entry.Addr,
- LinkAddr: "",
- State: Unreachable,
- UpdatedAt: clock.Now(),
- },
- }
- if diff := cmp.Diff(linkRes.neigh.entries(), wantEntries, unorderedEntriesDiffOpts()...); diff != "" {
- t.Fatalf("neighbor entries mismatch (-got, +want):\n%s", diff)
- }
- }
- }
-
- // Retry address resolution with a working link.
- linkRes.dropReplies = false
- {
- incompleteEntry, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
- if diff := cmp.Diff(LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil}, r); diff != "" {
- t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
- }
- })
- if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
- t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
- }
- if incompleteEntry.State != Incomplete {
- t.Fatalf("got entry.State = %s, want = %s", incompleteEntry.State, Incomplete)
- }
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: "",
- State: Incomplete,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- clock.Advance(typicalLatency)
-
- select {
- case <-ch:
- default:
- t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
- }
-
- {
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: 1,
- Entry: NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- {
- gotEntry, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
- if err != nil {
- t.Fatalf("linkRes.neigh.entry(%s, '', _): %s", entry.Addr, err)
- }
-
- wantEntry := NeighborEntry{
- Addr: entry.Addr,
- LinkAddr: entry.LinkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- }
- if diff := cmp.Diff(gotEntry, wantEntry); diff != "" {
- t.Fatalf("neighbor entry mismatch (-got, +want):\n%s", diff)
- }
- }
- }
-}
-
-func BenchmarkCacheClear(b *testing.B) {
- b.StopTimer()
- config := DefaultNUDConfigurations()
- clock := tcpip.NewStdClock()
- linkRes := newTestNeighborResolver(nil, config, clock)
- linkRes.delay = 0
-
- // Clear for every possible size of the cache
- for cacheSize := uint16(0); cacheSize < neighborCacheSize; cacheSize++ {
- // Fill the neighbor cache to capacity.
- for i := uint16(0); i < cacheSize; i++ {
- entry, ok := linkRes.entries.entry(i)
- if !ok {
- b.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i)
- }
-
- _, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
- if diff := cmp.Diff(LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil}, r); diff != "" {
- b.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
- }
- })
- if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
- b.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
- }
-
- select {
- case <-ch:
- default:
- b.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
- }
- }
-
- b.StartTimer()
- linkRes.neigh.clear()
- b.StopTimer()
- }
-}
diff --git a/pkg/tcpip/stack/neighbor_entry_list.go b/pkg/tcpip/stack/neighbor_entry_list.go
new file mode 100644
index 000000000..d78430080
--- /dev/null
+++ b/pkg/tcpip/stack/neighbor_entry_list.go
@@ -0,0 +1,221 @@
+package stack
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type neighborEntryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (neighborEntryElementMapper) linkerFor(elem *neighborEntry) *neighborEntry { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type neighborEntryList struct {
+ head *neighborEntry
+ tail *neighborEntry
+}
+
+// Reset resets list l to the empty state.
+func (l *neighborEntryList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *neighborEntryList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *neighborEntryList) Front() *neighborEntry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *neighborEntryList) Back() *neighborEntry {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *neighborEntryList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (neighborEntryElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *neighborEntryList) PushFront(e *neighborEntry) {
+ linker := neighborEntryElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ neighborEntryElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *neighborEntryList) PushBack(e *neighborEntry) {
+ linker := neighborEntryElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ neighborEntryElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *neighborEntryList) PushBackList(m *neighborEntryList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ neighborEntryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ neighborEntryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *neighborEntryList) InsertAfter(b, e *neighborEntry) {
+ bLinker := neighborEntryElementMapper{}.linkerFor(b)
+ eLinker := neighborEntryElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ neighborEntryElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *neighborEntryList) InsertBefore(a, e *neighborEntry) {
+ aLinker := neighborEntryElementMapper{}.linkerFor(a)
+ eLinker := neighborEntryElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ neighborEntryElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *neighborEntryList) Remove(e *neighborEntry) {
+ linker := neighborEntryElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ neighborEntryElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ neighborEntryElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else if l.tail == e {
+ l.tail = prev
+ }
+
+ linker.SetNext(nil)
+ linker.SetPrev(nil)
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type neighborEntryEntry struct {
+ next *neighborEntry
+ prev *neighborEntry
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *neighborEntryEntry) Next() *neighborEntry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *neighborEntryEntry) Prev() *neighborEntry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *neighborEntryEntry) SetNext(elem *neighborEntry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *neighborEntryEntry) SetPrev(elem *neighborEntry) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/stack/neighbor_entry_test.go b/pkg/tcpip/stack/neighbor_entry_test.go
deleted file mode 100644
index 59d86d6d4..000000000
--- a/pkg/tcpip/stack/neighbor_entry_test.go
+++ /dev/null
@@ -1,2269 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack
-
-import (
- "fmt"
- "math"
- "math/rand"
- "sync"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/faketime"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/testutil"
-)
-
-const (
- entryTestNetNumber tcpip.NetworkProtocolNumber = math.MaxUint32
-
- entryTestNICID tcpip.NICID = 1
-
- entryTestLinkAddr1 = tcpip.LinkAddress("\x0a\x00\x00\x00\x00\x01")
- entryTestLinkAddr2 = tcpip.LinkAddress("\x0a\x00\x00\x00\x00\x02")
-)
-
-var (
- entryTestAddr1 = testutil.MustParse6("a::1")
- entryTestAddr2 = testutil.MustParse6("a::2")
-)
-
-// runImmediatelyScheduledJobs runs all jobs scheduled to run at the current
-// time.
-func runImmediatelyScheduledJobs(clock *faketime.ManualClock) {
- clock.Advance(immediateDuration)
-}
-
-// The following unit tests exercise every state transition and verify its
-// behavior with RFC 4681 and RFC 7048.
-//
-// | From | To | Cause | Update | Action | Event |
-// | =========== | =========== | ========================================== | ======== | ===========| ======= |
-// | Unknown | Unknown | Confirmation w/ unknown address | | | Added |
-// | Unknown | Incomplete | Packet queued to unknown address | | Send probe | Added |
-// | Unknown | Stale | Probe | | | Added |
-// | Incomplete | Incomplete | Retransmit timer expired | | Send probe | Changed |
-// | Incomplete | Reachable | Solicited confirmation | LinkAddr | Notify | Changed |
-// | Incomplete | Stale | Unsolicited confirmation | LinkAddr | Notify | Changed |
-// | Incomplete | Stale | Probe | LinkAddr | Notify | Changed |
-// | Incomplete | Unreachable | Max probes sent without reply | | Notify | Changed |
-// | Reachable | Reachable | Confirmation w/ different isRouter flag | IsRouter | | |
-// | Reachable | Stale | Reachable timer expired | | | Changed |
-// | Reachable | Stale | Probe or confirmation w/ different address | | | Changed |
-// | Stale | Reachable | Solicited override confirmation | LinkAddr | | Changed |
-// | Stale | Reachable | Solicited confirmation w/o address | | Notify | Changed |
-// | Stale | Stale | Override confirmation | LinkAddr | | Changed |
-// | Stale | Stale | Probe w/ different address | LinkAddr | | Changed |
-// | Stale | Delay | Packet sent | | | Changed |
-// | Delay | Reachable | Upper-layer confirmation | | | Changed |
-// | Delay | Reachable | Solicited override confirmation | LinkAddr | | Changed |
-// | Delay | Reachable | Solicited confirmation w/o address | | Notify | Changed |
-// | Delay | Stale | Probe or confirmation w/ different address | | | Changed |
-// | Delay | Probe | Delay timer expired | | Send probe | Changed |
-// | Probe | Reachable | Solicited override confirmation | LinkAddr | | Changed |
-// | Probe | Reachable | Solicited confirmation w/ same address | | Notify | Changed |
-// | Probe | Reachable | Solicited confirmation w/o address | | Notify | Changed |
-// | Probe | Stale | Probe or confirmation w/ different address | | | Changed |
-// | Probe | Probe | Retransmit timer expired | | | Changed |
-// | Probe | Unreachable | Max probes sent without reply | | Notify | Changed |
-// | Unreachable | Incomplete | Packet queued | | Send probe | Changed |
-// | Unreachable | Stale | Probe w/ different address | LinkAddr | | Changed |
-
-type testEntryEventType uint8
-
-const (
- entryTestAdded testEntryEventType = iota
- entryTestChanged
- entryTestRemoved
-)
-
-func (t testEntryEventType) String() string {
- switch t {
- case entryTestAdded:
- return "add"
- case entryTestChanged:
- return "change"
- case entryTestRemoved:
- return "remove"
- default:
- return fmt.Sprintf("unknown (%d)", t)
- }
-}
-
-// Fields are exported for use with cmp.Diff.
-type testEntryEventInfo struct {
- EventType testEntryEventType
- NICID tcpip.NICID
- Entry NeighborEntry
-}
-
-func (e testEntryEventInfo) String() string {
- return fmt.Sprintf("%s event for NIC #%d, %#v", e.EventType, e.NICID, e.Entry)
-}
-
-// testNUDDispatcher implements NUDDispatcher to validate the dispatching of
-// events upon certain NUD state machine events.
-type testNUDDispatcher struct {
- mu struct {
- sync.Mutex
- events []testEntryEventInfo
- }
-}
-
-var _ NUDDispatcher = (*testNUDDispatcher)(nil)
-
-func (d *testNUDDispatcher) queueEvent(e testEntryEventInfo) {
- d.mu.Lock()
- defer d.mu.Unlock()
- d.mu.events = append(d.mu.events, e)
-}
-
-func (d *testNUDDispatcher) OnNeighborAdded(nicID tcpip.NICID, entry NeighborEntry) {
- d.queueEvent(testEntryEventInfo{
- EventType: entryTestAdded,
- NICID: nicID,
- Entry: entry,
- })
-}
-
-func (d *testNUDDispatcher) OnNeighborChanged(nicID tcpip.NICID, entry NeighborEntry) {
- d.queueEvent(testEntryEventInfo{
- EventType: entryTestChanged,
- NICID: nicID,
- Entry: entry,
- })
-}
-
-func (d *testNUDDispatcher) OnNeighborRemoved(nicID tcpip.NICID, entry NeighborEntry) {
- d.queueEvent(testEntryEventInfo{
- EventType: entryTestRemoved,
- NICID: nicID,
- Entry: entry,
- })
-}
-
-type entryTestLinkResolver struct {
- mu struct {
- sync.Mutex
- probes []entryTestProbeInfo
- }
-}
-
-var _ LinkAddressResolver = (*entryTestLinkResolver)(nil)
-
-type entryTestProbeInfo struct {
- RemoteAddress tcpip.Address
- RemoteLinkAddress tcpip.LinkAddress
- LocalAddress tcpip.Address
-}
-
-func (p entryTestProbeInfo) String() string {
- return fmt.Sprintf("probe with RemoteAddress=%q, RemoteLinkAddress=%q, LocalAddress=%q", p.RemoteAddress, p.RemoteLinkAddress, p.LocalAddress)
-}
-
-// LinkAddressRequest sends a request for the LinkAddress of addr. Broadcasts
-// to the local network if linkAddr is the zero value.
-func (r *entryTestLinkResolver) LinkAddressRequest(targetAddr, localAddr tcpip.Address, linkAddr tcpip.LinkAddress) tcpip.Error {
- r.mu.Lock()
- defer r.mu.Unlock()
- r.mu.probes = append(r.mu.probes, entryTestProbeInfo{
- RemoteAddress: targetAddr,
- RemoteLinkAddress: linkAddr,
- LocalAddress: localAddr,
- })
- return nil
-}
-
-// ResolveStaticAddress attempts to resolve address without sending requests.
-// It either resolves the name immediately or returns the empty LinkAddress.
-func (*entryTestLinkResolver) ResolveStaticAddress(tcpip.Address) (tcpip.LinkAddress, bool) {
- return "", false
-}
-
-// LinkAddressProtocol returns the network protocol of the addresses this
-// resolver can resolve.
-func (*entryTestLinkResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
- return entryTestNetNumber
-}
-
-func entryTestSetup(c NUDConfigurations) (*neighborEntry, *testNUDDispatcher, *entryTestLinkResolver, *faketime.ManualClock) {
- clock := faketime.NewManualClock()
- disp := testNUDDispatcher{}
- nic := nic{
- LinkEndpoint: nil, // entryTestLinkResolver doesn't use a LinkEndpoint
-
- id: entryTestNICID,
- stack: &Stack{
- clock: clock,
- nudDisp: &disp,
- nudConfigs: c,
- randomGenerator: rand.New(rand.NewSource(time.Now().UnixNano())),
- },
- stats: makeNICStats(tcpip.NICStats{}.FillIn()),
- }
- netEP := (&testIPv6Protocol{}).NewEndpoint(&nic, nil)
- nic.networkEndpoints = map[tcpip.NetworkProtocolNumber]NetworkEndpoint{
- header.IPv6ProtocolNumber: netEP,
- }
-
- var linkRes entryTestLinkResolver
- // Stub out the neighbor cache to verify deletion from the cache.
- l := &linkResolver{
- resolver: &linkRes,
- }
- l.neigh.init(&nic, &linkRes)
-
- entry := newNeighborEntry(&l.neigh, entryTestAddr1 /* remoteAddr */, l.neigh.state)
- l.neigh.mu.Lock()
- l.neigh.mu.cache[entryTestAddr1] = entry
- l.neigh.mu.Unlock()
- nic.linkAddrResolvers = map[tcpip.NetworkProtocolNumber]*linkResolver{
- header.IPv6ProtocolNumber: l,
- }
-
- return entry, &disp, &linkRes, clock
-}
-
-// TestEntryInitiallyUnknown verifies that the state of a newly created
-// neighborEntry is Unknown.
-func TestEntryInitiallyUnknown(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- e.mu.Lock()
- if e.mu.neigh.State != Unknown {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
- }
- e.mu.Unlock()
-
- clock.Advance(c.RetransmitTimer)
-
- // No probes should have been sent.
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryUnknownToUnknownWhenConfirmationWithUnknownAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: false,
- IsRouter: false,
- })
- if e.mu.neigh.State != Unknown {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
- }
- e.mu.Unlock()
-
- clock.Advance(time.Hour)
-
- // No probes should have been sent.
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryUnknownToIncomplete(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
-}
-
-func unknownToIncomplete(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- if err := func() error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- if e.mu.neigh.State != Unknown {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
- }
- e.handlePacketQueuedLocked(entryTestAddr2)
- if e.mu.neigh.State != Incomplete {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
- }
- return nil
- }(); err != nil {
- return err
- }
-
- runImmediatelyScheduledJobs(clock)
- wantProbes := []entryTestProbeInfo{
- {
- RemoteAddress: entryTestAddr1,
- RemoteLinkAddress: tcpip.LinkAddress(""),
- LocalAddress: entryTestAddr2,
- },
- }
- linkRes.mu.Lock()
- diff := cmp.Diff(wantProbes, linkRes.mu.probes)
- linkRes.mu.probes = nil
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestAdded,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: tcpip.LinkAddress(""),
- State: Incomplete,
- UpdatedAt: clock.Now(),
- },
- },
- }
- {
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
- return nil
-}
-
-func TestEntryUnknownToStale(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
-}
-
-func unknownToStale(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- if err := func() error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- if e.mu.neigh.State != Unknown {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
- }
- e.handleProbeLocked(entryTestLinkAddr1)
- if e.mu.neigh.State != Stale {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- return nil
- }(); err != nil {
- return err
- }
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestAdded,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- {
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- return nil
-}
-
-func TestEntryIncompleteToIncompleteDoesNotChangeUpdatedAt(t *testing.T) {
- c := DefaultNUDConfigurations()
- c.MaxMulticastProbes = 3
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
-
- // UpdatedAt should remain the same during address resolution.
- e.mu.Lock()
- startedAt := e.mu.neigh.UpdatedAt
- e.mu.Unlock()
-
- // Wait for the rest of the reachability probe transmissions, signifying
- // Incomplete to Incomplete transitions.
- for i := uint32(1); i < c.MaxMulticastProbes; i++ {
- clock.Advance(c.RetransmitTimer)
-
- wantProbes := []entryTestProbeInfo{
- {
- RemoteAddress: entryTestAddr1,
- RemoteLinkAddress: tcpip.LinkAddress(""),
- LocalAddress: entryTestAddr2,
- },
- }
- linkRes.mu.Lock()
- diff := cmp.Diff(wantProbes, linkRes.mu.probes)
- linkRes.mu.probes = nil
- linkRes.mu.Unlock()
- if diff != "" {
- t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
-
- e.mu.Lock()
- if got, want := e.mu.neigh.UpdatedAt, startedAt; got != want {
- t.Errorf("got e.mu.neigh.UpdatedAt = %q, want = %q", got, want)
- }
- e.mu.Unlock()
- }
-
- // UpdatedAt should change after failing address resolution. Timing out after
- // sending the last probe transitions the entry to Unreachable.
- clock.Advance(c.RetransmitTimer)
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: tcpip.LinkAddress(""),
- State: Unreachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryIncompleteToReachable(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachable(...) = %s", err)
- }
-}
-
-func incompleteToReachableWithFlags(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, flags ReachabilityConfirmationFlags) error {
- if err := func() error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- if e.mu.neigh.State != Incomplete {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
- }
- e.handleConfirmationLocked(entryTestLinkAddr1, flags)
- if e.mu.neigh.State != Reachable {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
- return fmt.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
- }
- return nil
- }(); err != nil {
- return err
- }
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- {
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- return nil
-}
-
-func incompleteToReachable(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- if err := incompleteToReachableWithFlags(e, nudDisp, linkRes, clock, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- }); err != nil {
- return err
- }
-
- e.mu.Lock()
- isRouter := e.mu.isRouter
- e.mu.Unlock()
- if isRouter {
- return fmt.Errorf("got e.mu.isRouter = %t, want = false", isRouter)
- }
-
- return nil
-}
-
-func TestEntryIncompleteToReachableWithRouterFlag(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachableWithRouterFlag(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachableWithRouterFlag(...) = %s", err)
- }
-}
-
-func incompleteToReachableWithRouterFlag(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- if err := incompleteToReachableWithFlags(e, nudDisp, linkRes, clock, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: true,
- }); err != nil {
- return err
- }
-
- e.mu.Lock()
- isRouter := e.mu.isRouter
- e.mu.Unlock()
- if !isRouter {
- return fmt.Errorf("got e.mu.isRouter = %t, want = true", isRouter)
- }
-
- return nil
-}
-
-func TestEntryIncompleteToStaleWhenUnsolicitedConfirmation(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: false,
- IsRouter: false,
- })
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- if e.mu.isRouter {
- t.Errorf("got e.mu.isRouter = %t, want = false", e.mu.isRouter)
- }
- e.mu.Unlock()
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryIncompleteToStaleWhenProbe(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleProbeLocked(entryTestLinkAddr1)
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.mu.Unlock()
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryIncompleteToUnreachable(t *testing.T) {
- c := DefaultNUDConfigurations()
- c.MaxMulticastProbes = 3
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToUnreachable(...) = %s", err)
- }
-}
-
-func incompleteToUnreachable(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- {
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Incomplete {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Incomplete)
- }
- }
-
- // The first probe was sent in the transition from Unknown to Incomplete.
- clock.Advance(c.RetransmitTimer)
-
- // Observe each subsequent multicast probe transmitted.
- for i := uint32(1); i < c.MaxMulticastProbes; i++ {
- wantProbes := []entryTestProbeInfo{{
- RemoteAddress: entryTestAddr1,
- RemoteLinkAddress: "",
- LocalAddress: entryTestAddr2,
- }}
- linkRes.mu.Lock()
- diff := cmp.Diff(wantProbes, linkRes.mu.probes)
- linkRes.mu.probes = nil
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probe #%d mismatch (-want, +got):\n%s", i+1, diff)
- }
-
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Incomplete {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Incomplete)
- }
-
- clock.Advance(c.RetransmitTimer)
- }
-
- {
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Unreachable {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Unreachable)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: tcpip.LinkAddress(""),
- State: Unreachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-
- return nil
-}
-
-type testLocker struct{}
-
-var _ sync.Locker = (*testLocker)(nil)
-
-func (*testLocker) Lock() {}
-func (*testLocker) Unlock() {}
-
-func TestEntryReachableToReachableClearsRouterWhenConfirmationWithoutRouter(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachableWithRouterFlag(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachableWithRouterFlag(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: false,
- IsRouter: false,
- })
- if e.mu.neigh.State != Reachable {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- if got, want := e.mu.isRouter, false; got != want {
- t.Errorf("got e.mu.isRouter = %t, want = %t", got, want)
- }
- ipv6EP := e.cache.nic.networkEndpoints[header.IPv6ProtocolNumber].(*testIPv6Endpoint)
- if ipv6EP.invalidatedRtr != e.mu.neigh.Addr {
- t.Errorf("got ipv6EP.invalidatedRtr = %s, want = %s", ipv6EP.invalidatedRtr, e.mu.neigh.Addr)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-func TestEntryReachableToReachableWhenProbeWithSameAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachable(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleProbeLocked(entryTestLinkAddr1)
- if e.mu.neigh.State != Reachable {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-func TestEntryReachableToStaleWhenTimeout(t *testing.T) {
- c := DefaultNUDConfigurations()
- // Eliminate random factors from ReachableTime computation so the transition
- // from Stale to Reachable will only take BaseReachableTime duration.
- c.MinRandomFactor = 1
- c.MaxRandomFactor = 1
-
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachable(...) = %s", err)
- }
- if err := reachableToStale(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("reachableToStale(...) = %s", err)
- }
-}
-
-// reachableToStale transitions a neighborEntry in Reachable state to Stale
-// state. Depends on the elimination of random factors in the ReachableTime
-// computation.
-//
-// c.MinRandomFactor = 1
-// c.MaxRandomFactor = 1
-func reachableToStale(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- // Ensure there are no random factors in the ReachableTime computation.
- if c.MinRandomFactor != 1 {
- return fmt.Errorf("got c.MinRandomFactor = %f, want = 1", c.MinRandomFactor)
- }
- if c.MaxRandomFactor != 1 {
- return fmt.Errorf("got c.MaxRandomFactor = %f, want = 1", c.MaxRandomFactor)
- }
-
- {
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Reachable {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Reachable)
- }
- }
-
- clock.Advance(c.BaseReachableTime)
-
- {
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Stale {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Stale)
- }
- }
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- {
-
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- return nil
-}
-
-func TestEntryReachableToStaleWhenProbeWithDifferentAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachable(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleProbeLocked(entryTestLinkAddr2)
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryReachableToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachable(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: false,
- IsRouter: false,
- })
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryReachableToStaleWhenConfirmationWithDifferentAddressAndOverride(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachable(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: true,
- IsRouter: false,
- })
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToStaleWhenProbeWithSameAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleProbeLocked(entryTestLinkAddr1)
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: true,
- IsRouter: false,
- })
- if e.mu.neigh.State != Reachable {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked("" /* linkAddr */, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
- if e.mu.neigh.State != Reachable {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToStaleWhenOverrideConfirmation(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: true,
- IsRouter: false,
- })
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
- }
- e.mu.Unlock()
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToStaleWhenProbeUpdateAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleProbeLocked(entryTestLinkAddr2)
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToDelay(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
-}
-
-func staleToDelay(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- if err := func() error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- if e.mu.neigh.State != Stale {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.handlePacketQueuedLocked(entryTestAddr2)
- if e.mu.neigh.State != Delay {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Delay)
- }
- return nil
- }(); err != nil {
- return err
- }
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Delay,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-
- return nil
-}
-
-func TestEntryDelayToReachableWhenUpperLevelConfirmation(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleUpperLevelConfirmationLocked()
- if e.mu.neigh.State != Reachable {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: true,
- IsRouter: false,
- })
- if e.mu.neigh.State != Reachable {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked("" /* linkAddr */, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
- if e.mu.neigh.State != Reachable {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToDelayWhenOverrideConfirmationWithSameAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: true,
- IsRouter: false,
- })
- if e.mu.neigh.State != Delay {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Delay)
- }
- if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToStaleWhenProbeWithDifferentAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleProbeLocked(entryTestLinkAddr2)
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: true,
- IsRouter: false,
- })
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToProbe(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
- if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("delayToProbe(...) = %s", err)
- }
-}
-
-func delayToProbe(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- {
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Delay {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Delay)
- }
- }
-
- // Wait for the first unicast probe to be transmitted, marking the
- // transition from Delay to Probe.
- clock.Advance(c.DelayFirstProbeTime)
-
- {
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Probe {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
- }
- }
-
- wantProbes := []entryTestProbeInfo{
- {
- RemoteAddress: entryTestAddr1,
- RemoteLinkAddress: entryTestLinkAddr1,
- },
- }
- {
- linkRes.mu.Lock()
- diff := cmp.Diff(wantProbes, linkRes.mu.probes)
- linkRes.mu.probes = nil
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Probe,
- UpdatedAt: clock.Now(),
- },
- },
- }
- {
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- return nil
-}
-
-func TestEntryProbeToStaleWhenProbeWithDifferentAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
- if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("delayToProbe(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleProbeLocked(entryTestLinkAddr2)
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryProbeToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
- if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("delayToProbe(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: true,
- IsRouter: false,
- })
- if e.mu.neigh.State != Stale {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr2,
- State: Stale,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- nudDisp.mu.Unlock()
-}
-
-func TestEntryProbeToProbeWhenOverrideConfirmationWithSameAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
- if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("delayToProbe(...) = %s", err)
- }
-
- e.mu.Lock()
- e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
- Solicited: false,
- Override: true,
- IsRouter: false,
- })
- if e.mu.neigh.State != Probe {
- t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Probe)
- }
- if got, want := e.mu.neigh.LinkAddr, entryTestLinkAddr1; got != want {
- t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", got, want)
- }
- e.mu.Unlock()
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- // No events should have been dispatched.
- nudDisp.mu.Lock()
- diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
- nudDisp.mu.Unlock()
- if diff != "" {
- t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-}
-
-func TestEntryProbeToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
- if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("delayToProbe(...) = %s", err)
- }
- if err := probeToReachableWithOverride(e, nudDisp, linkRes, clock, entryTestLinkAddr2); err != nil {
- t.Fatalf("probeToReachableWithOverride(...) = %s", err)
- }
-}
-
-func probeToReachableWithFlags(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) error {
- if err := func() error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- prevLinkAddr := e.mu.neigh.LinkAddr
- if e.mu.neigh.State != Probe {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Probe)
- }
- e.handleConfirmationLocked(linkAddr, flags)
-
- if e.mu.neigh.State != Reachable {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
- }
- if linkAddr == "" {
- linkAddr = prevLinkAddr
- }
- if e.mu.neigh.LinkAddr != linkAddr {
- return fmt.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, linkAddr)
- }
- return nil
- }(); err != nil {
- return err
- }
-
- // No probes should have been sent.
- runImmediatelyScheduledJobs(clock)
- {
- linkRes.mu.Lock()
- diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: linkAddr,
- State: Reachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- {
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
-
- return nil
-}
-
-func probeToReachableWithOverride(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, linkAddr tcpip.LinkAddress) error {
- return probeToReachableWithFlags(e, nudDisp, linkRes, clock, linkAddr, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: true,
- IsRouter: false,
- })
-}
-
-func TestEntryProbeToReachableWhenSolicitedConfirmationWithSameAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
- if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("delayToProbe(...) = %s", err)
- }
- if err := probeToReachable(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("probeToReachable(...) = %s", err)
- }
-}
-
-func probeToReachable(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- return probeToReachableWithFlags(e, nudDisp, linkRes, clock, entryTestLinkAddr1, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
-}
-
-func TestEntryProbeToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
- c := DefaultNUDConfigurations()
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
- if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("delayToProbe(...) = %s", err)
- }
- if err := probeToReachableWithoutAddress(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("probeToReachableWithoutAddress(...) = %s", err)
- }
-}
-
-func probeToReachableWithoutAddress(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- return probeToReachableWithFlags(e, nudDisp, linkRes, clock, "" /* linkAddr */, ReachabilityConfirmationFlags{
- Solicited: true,
- Override: false,
- IsRouter: false,
- })
-}
-
-func TestEntryProbeToUnreachable(t *testing.T) {
- c := DefaultNUDConfigurations()
- c.MaxMulticastProbes = 3
- c.MaxUnicastProbes = 3
- c.DelayFirstProbeTime = c.RetransmitTimer
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToStale(...) = %s", err)
- }
- if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("staleToDelay(...) = %s", err)
- }
- if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("delayToProbe(...) = %s", err)
- }
- if err := probeToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("probeToUnreachable(...) = %s", err)
- }
-}
-
-func probeToUnreachable(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- {
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Probe {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
- }
- }
-
- // The first probe was sent in the transition from Delay to Probe.
- clock.Advance(c.RetransmitTimer)
-
- // Observe each subsequent unicast probe transmitted.
- for i := uint32(1); i < c.MaxUnicastProbes; i++ {
- wantProbes := []entryTestProbeInfo{{
- RemoteAddress: entryTestAddr1,
- RemoteLinkAddress: entryTestLinkAddr1,
- }}
- linkRes.mu.Lock()
- diff := cmp.Diff(wantProbes, linkRes.mu.probes)
- linkRes.mu.probes = nil
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probe #%d mismatch (-want, +got):\n%s", i+1, diff)
- }
-
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Probe {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
- }
-
- clock.Advance(c.RetransmitTimer)
- }
-
- {
- e.mu.Lock()
- state := e.mu.neigh.State
- e.mu.Unlock()
- if state != Unreachable {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Unreachable)
- }
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: entryTestLinkAddr1,
- State: Unreachable,
- UpdatedAt: clock.Now(),
- },
- },
- }
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
-
- return nil
-}
-
-func TestEntryUnreachableToIncomplete(t *testing.T) {
- c := DefaultNUDConfigurations()
- c.MaxMulticastProbes = 3
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToUnreachable(...) = %s", err)
- }
- if err := unreachableToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unreachableToIncomplete(...) = %s", err)
- }
-}
-
-func unreachableToIncomplete(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
- if err := func() error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- if e.mu.neigh.State != Unreachable {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unreachable)
- }
- e.handlePacketQueuedLocked(entryTestAddr2)
- if e.mu.neigh.State != Incomplete {
- return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
- }
- return nil
- }(); err != nil {
- return err
- }
-
- runImmediatelyScheduledJobs(clock)
- wantProbes := []entryTestProbeInfo{
- {
- RemoteAddress: entryTestAddr1,
- RemoteLinkAddress: tcpip.LinkAddress(""),
- LocalAddress: entryTestAddr2,
- },
- }
- linkRes.mu.Lock()
- diff := cmp.Diff(wantProbes, linkRes.mu.probes)
- linkRes.mu.probes = nil
- linkRes.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
- }
-
- wantEvents := []testEntryEventInfo{
- {
- EventType: entryTestChanged,
- NICID: entryTestNICID,
- Entry: NeighborEntry{
- Addr: entryTestAddr1,
- LinkAddr: tcpip.LinkAddress(""),
- State: Incomplete,
- UpdatedAt: clock.Now(),
- },
- },
- }
- {
- nudDisp.mu.Lock()
- diff := cmp.Diff(wantEvents, nudDisp.mu.events)
- nudDisp.mu.events = nil
- nudDisp.mu.Unlock()
- if diff != "" {
- return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
- }
- }
- return nil
-}
-
-func TestEntryUnreachableToStale(t *testing.T) {
- c := DefaultNUDConfigurations()
- c.MaxMulticastProbes = 3
- // Eliminate random factors from ReachableTime computation so the transition
- // from Stale to Reachable will only take BaseReachableTime duration.
- c.MinRandomFactor = 1
- c.MaxRandomFactor = 1
-
- e, nudDisp, linkRes, clock := entryTestSetup(c)
-
- if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unknownToIncomplete(...) = %s", err)
- }
- if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToUnreachable(...) = %s", err)
- }
- if err := unreachableToIncomplete(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("unreachableToIncomplete(...) = %s", err)
- }
- if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("incompleteToReachable(...) = %s", err)
- }
- if err := reachableToStale(c, e, nudDisp, linkRes, clock); err != nil {
- t.Fatalf("reachableToStale(...) = %s", err)
- }
-}
diff --git a/pkg/tcpip/stack/nic_test.go b/pkg/tcpip/stack/nic_test.go
deleted file mode 100644
index 88ca9b076..000000000
--- a/pkg/tcpip/stack/nic_test.go
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/testutil"
-)
-
-var _ AddressableEndpoint = (*testIPv6Endpoint)(nil)
-var _ NetworkEndpoint = (*testIPv6Endpoint)(nil)
-var _ NDPEndpoint = (*testIPv6Endpoint)(nil)
-
-// An IPv6 NetworkEndpoint that throws away outgoing packets.
-//
-// We use this instead of ipv6.endpoint because the ipv6 package depends on
-// the stack package which this test lives in, causing a cyclic dependency.
-type testIPv6Endpoint struct {
- AddressableEndpointState
-
- nic NetworkInterface
- protocol *testIPv6Protocol
-
- invalidatedRtr tcpip.Address
-}
-
-func (*testIPv6Endpoint) Enable() tcpip.Error {
- return nil
-}
-
-func (*testIPv6Endpoint) Enabled() bool {
- return true
-}
-
-func (*testIPv6Endpoint) Disable() {}
-
-// DefaultTTL implements NetworkEndpoint.DefaultTTL.
-func (*testIPv6Endpoint) DefaultTTL() uint8 {
- return 0
-}
-
-// MTU implements NetworkEndpoint.MTU.
-func (e *testIPv6Endpoint) MTU() uint32 {
- return e.nic.MTU() - header.IPv6MinimumSize
-}
-
-// MaxHeaderLength implements NetworkEndpoint.MaxHeaderLength.
-func (e *testIPv6Endpoint) MaxHeaderLength() uint16 {
- return e.nic.MaxHeaderLength() + header.IPv6MinimumSize
-}
-
-// WritePacket implements NetworkEndpoint.WritePacket.
-func (*testIPv6Endpoint) WritePacket(*Route, NetworkHeaderParams, *PacketBuffer) tcpip.Error {
- return nil
-}
-
-// WritePackets implements NetworkEndpoint.WritePackets.
-func (*testIPv6Endpoint) WritePackets(*Route, PacketBufferList, NetworkHeaderParams) (int, tcpip.Error) {
- // Our tests don't use this so we don't support it.
- return 0, &tcpip.ErrNotSupported{}
-}
-
-// WriteHeaderIncludedPacket implements
-// NetworkEndpoint.WriteHeaderIncludedPacket.
-func (*testIPv6Endpoint) WriteHeaderIncludedPacket(*Route, *PacketBuffer) tcpip.Error {
- // Our tests don't use this so we don't support it.
- return &tcpip.ErrNotSupported{}
-}
-
-// HandlePacket implements NetworkEndpoint.HandlePacket.
-func (*testIPv6Endpoint) HandlePacket(*PacketBuffer) {}
-
-// Close implements NetworkEndpoint.Close.
-func (e *testIPv6Endpoint) Close() {
- e.AddressableEndpointState.Cleanup()
-}
-
-// NetworkProtocolNumber implements NetworkEndpoint.NetworkProtocolNumber.
-func (*testIPv6Endpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber {
- return header.IPv6ProtocolNumber
-}
-
-func (e *testIPv6Endpoint) InvalidateDefaultRouter(rtr tcpip.Address) {
- e.invalidatedRtr = rtr
-}
-
-// Stats implements NetworkEndpoint.
-func (*testIPv6Endpoint) Stats() NetworkEndpointStats {
- return &testIPv6EndpointStats{}
-}
-
-var _ NetworkEndpointStats = (*testIPv6EndpointStats)(nil)
-
-type testIPv6EndpointStats struct{}
-
-// IsNetworkEndpointStats implements stack.NetworkEndpointStats.
-func (*testIPv6EndpointStats) IsNetworkEndpointStats() {}
-
-// We use this instead of ipv6.protocol because the ipv6 package depends on
-// the stack package which this test lives in, causing a cyclic dependency.
-type testIPv6Protocol struct{}
-
-// Number implements NetworkProtocol.Number.
-func (*testIPv6Protocol) Number() tcpip.NetworkProtocolNumber {
- return header.IPv6ProtocolNumber
-}
-
-// MinimumPacketSize implements NetworkProtocol.MinimumPacketSize.
-func (*testIPv6Protocol) MinimumPacketSize() int {
- return header.IPv6MinimumSize
-}
-
-// ParseAddresses implements NetworkProtocol.ParseAddresses.
-func (*testIPv6Protocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) {
- h := header.IPv6(v)
- return h.SourceAddress(), h.DestinationAddress()
-}
-
-// NewEndpoint implements NetworkProtocol.NewEndpoint.
-func (p *testIPv6Protocol) NewEndpoint(nic NetworkInterface, _ TransportDispatcher) NetworkEndpoint {
- e := &testIPv6Endpoint{
- nic: nic,
- protocol: p,
- }
- e.AddressableEndpointState.Init(e)
- return e
-}
-
-// SetOption implements NetworkProtocol.SetOption.
-func (*testIPv6Protocol) SetOption(tcpip.SettableNetworkProtocolOption) tcpip.Error {
- return nil
-}
-
-// Option implements NetworkProtocol.Option.
-func (*testIPv6Protocol) Option(tcpip.GettableNetworkProtocolOption) tcpip.Error {
- return nil
-}
-
-// Close implements NetworkProtocol.Close.
-func (*testIPv6Protocol) Close() {}
-
-// Wait implements NetworkProtocol.Wait.
-func (*testIPv6Protocol) Wait() {}
-
-// Parse implements NetworkProtocol.Parse.
-func (*testIPv6Protocol) Parse(*PacketBuffer) (tcpip.TransportProtocolNumber, bool, bool) {
- return 0, false, false
-}
-
-func TestDisabledRxStatsWhenNICDisabled(t *testing.T) {
- // When the NIC is disabled, the only field that matters is the stats field.
- // This test is limited to stats counter checks.
- nic := nic{
- stats: makeNICStats(tcpip.NICStats{}.FillIn()),
- }
-
- if got := nic.stats.local.DisabledRx.Packets.Value(); got != 0 {
- t.Errorf("got DisabledRx.Packets = %d, want = 0", got)
- }
- if got := nic.stats.local.DisabledRx.Bytes.Value(); got != 0 {
- t.Errorf("got DisabledRx.Bytes = %d, want = 0", got)
- }
- if got := nic.stats.local.Rx.Packets.Value(); got != 0 {
- t.Errorf("got Rx.Packets = %d, want = 0", got)
- }
- if got := nic.stats.local.Rx.Bytes.Value(); got != 0 {
- t.Errorf("got Rx.Bytes = %d, want = 0", got)
- }
-
- if t.Failed() {
- t.FailNow()
- }
-
- nic.DeliverNetworkPacket("", "", 0, NewPacketBuffer(PacketBufferOptions{
- Data: buffer.View([]byte{1, 2, 3, 4}).ToVectorisedView(),
- }))
-
- if got := nic.stats.local.DisabledRx.Packets.Value(); got != 1 {
- t.Errorf("got DisabledRx.Packets = %d, want = 1", got)
- }
- if got := nic.stats.local.DisabledRx.Bytes.Value(); got != 4 {
- t.Errorf("got DisabledRx.Bytes = %d, want = 4", got)
- }
- if got := nic.stats.local.Rx.Packets.Value(); got != 0 {
- t.Errorf("got Rx.Packets = %d, want = 0", got)
- }
- if got := nic.stats.local.Rx.Bytes.Value(); got != 0 {
- t.Errorf("got Rx.Bytes = %d, want = 0", got)
- }
-}
-
-func TestPacketWithUnknownNetworkProtocolNumber(t *testing.T) {
- nic := nic{
- stats: makeNICStats(tcpip.NICStats{}.FillIn()),
- enabled: 1,
- }
- // IPv4 isn't recognized since we haven't initialized the NIC with an IPv4
- // endpoint.
- nic.DeliverNetworkPacket("", "", header.IPv4ProtocolNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buffer.View([]byte{1, 2, 3, 4}).ToVectorisedView(),
- }))
- var count uint64
- if got, ok := nic.stats.local.UnknownL3ProtocolRcvdPacketCounts.Get(uint64(header.IPv4ProtocolNumber)); ok {
- count = got.Value()
- }
- if count != 1 {
- t.Errorf("got UnknownL3ProtocolRcvdPacketCounts[header.IPv4ProtocolNumber] = %d, want = 1", count)
- }
-}
-
-func TestPacketWithUnknownTransportProtocolNumber(t *testing.T) {
- nic := nic{
- stack: &Stack{},
- stats: makeNICStats(tcpip.NICStats{}.FillIn()),
- enabled: 1,
- }
- // UDP isn't recognized since we haven't initialized the NIC with a UDP
- // protocol.
- nic.DeliverTransportPacket(header.UDPProtocolNumber, NewPacketBuffer(PacketBufferOptions{
- Data: buffer.View([]byte{1, 2, 3, 4}).ToVectorisedView(),
- }))
- var count uint64
- if got, ok := nic.stats.local.UnknownL4ProtocolRcvdPacketCounts.Get(uint64(header.UDPProtocolNumber)); ok {
- count = got.Value()
- }
- if count != 1 {
- t.Errorf("got UnknownL4ProtocolRcvdPacketCounts[header.UDPProtocolNumber] = %d, want = 1", count)
- }
-}
-
-func TestMultiCounterStatsInitialization(t *testing.T) {
- global := tcpip.NICStats{}.FillIn()
- nic := nic{
- stats: makeNICStats(global),
- }
- multi := nic.stats.multiCounterNICStats
- local := nic.stats.local
- if err := testutil.ValidateMultiCounterStats(reflect.ValueOf(&multi).Elem(), []reflect.Value{reflect.ValueOf(&local).Elem(), reflect.ValueOf(&global).Elem()}, testutil.ValidateMultiCounterStatsOptions{
- ExpectMultiCounterStat: true,
- ExpectMultiIntegralStatCounterMap: true,
- }); err != nil {
- t.Error(err)
- }
-}
diff --git a/pkg/tcpip/stack/nud_test.go b/pkg/tcpip/stack/nud_test.go
deleted file mode 100644
index 1aeb2f8a5..000000000
--- a/pkg/tcpip/stack/nud_test.go
+++ /dev/null
@@ -1,816 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack_test
-
-import (
- "math"
- "math/rand"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/faketime"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-const (
- defaultBaseReachableTime = 30 * time.Second
- minimumBaseReachableTime = time.Millisecond
- defaultMinRandomFactor = 0.5
- defaultMaxRandomFactor = 1.5
- defaultRetransmitTimer = time.Second
- minimumRetransmitTimer = time.Millisecond
- defaultDelayFirstProbeTime = 5 * time.Second
- defaultMaxMulticastProbes = 3
- defaultMaxUnicastProbes = 3
-
- defaultFakeRandomNum = 0.5
-)
-
-// fakeRand is a deterministic random number generator.
-type fakeRand struct {
- num float32
-}
-
-var _ rand.Source = (*fakeRand)(nil)
-
-func (f *fakeRand) Int63() int64 {
- return int64(f.num * float32(1<<63))
-}
-
-func (*fakeRand) Seed(int64) {}
-
-func TestNUDFunctions(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- nicID tcpip.NICID
- netProtoFactory []stack.NetworkProtocolFactory
- extraLinkCapabilities stack.LinkEndpointCapabilities
- expectedErr tcpip.Error
- }{
- {
- name: "Invalid NICID",
- nicID: nicID + 1,
- netProtoFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- extraLinkCapabilities: stack.CapabilityResolutionRequired,
- expectedErr: &tcpip.ErrUnknownNICID{},
- },
- {
- name: "No network protocol",
- nicID: nicID,
- expectedErr: &tcpip.ErrNotSupported{},
- },
- {
- name: "With IPv6",
- nicID: nicID,
- netProtoFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- expectedErr: &tcpip.ErrNotSupported{},
- },
- {
- name: "With resolution capability",
- nicID: nicID,
- extraLinkCapabilities: stack.CapabilityResolutionRequired,
- expectedErr: &tcpip.ErrNotSupported{},
- },
- {
- name: "With IPv6 and resolution capability",
- nicID: nicID,
- netProtoFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- extraLinkCapabilities: stack.CapabilityResolutionRequired,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NUDConfigs: stack.DefaultNUDConfigurations(),
- NetworkProtocols: test.netProtoFactory,
- Clock: clock,
- })
-
- e := channel.New(0, 0, linkAddr1)
- e.LinkEPCapabilities &^= stack.CapabilityResolutionRequired
- e.LinkEPCapabilities |= test.extraLinkCapabilities
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- configs := stack.DefaultNUDConfigurations()
- configs.BaseReachableTime = time.Hour
-
- {
- err := s.SetNUDConfigurations(test.nicID, ipv6.ProtocolNumber, configs)
- if diff := cmp.Diff(test.expectedErr, err); diff != "" {
- t.Errorf("s.SetNUDConfigurations(%d, %d, _) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
- }
- }
-
- {
- gotConfigs, err := s.NUDConfigurations(test.nicID, ipv6.ProtocolNumber)
- if diff := cmp.Diff(test.expectedErr, err); diff != "" {
- t.Errorf("s.NUDConfigurations(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
- } else if test.expectedErr == nil {
- if diff := cmp.Diff(configs, gotConfigs); diff != "" {
- t.Errorf("got configs mismatch (-want +got):\n%s", diff)
- }
- }
- }
-
- for _, addr := range []tcpip.Address{llAddr1, llAddr2} {
- {
- err := s.AddStaticNeighbor(test.nicID, ipv6.ProtocolNumber, addr, linkAddr1)
- if diff := cmp.Diff(test.expectedErr, err); diff != "" {
- t.Errorf("s.AddStaticNeighbor(%d, %d, %s, %s) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, addr, linkAddr1, diff)
- }
- }
- }
-
- {
- wantErr := test.expectedErr
- for i := 0; i < 2; i++ {
- {
- err := s.RemoveNeighbor(test.nicID, ipv6.ProtocolNumber, llAddr1)
- if diff := cmp.Diff(wantErr, err); diff != "" {
- t.Errorf("s.RemoveNeighbor(%d, %d, '') error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
- }
- }
-
- if test.expectedErr != nil {
- break
- }
-
- // Removing a neighbor that does not exist should give us a bad address
- // error.
- wantErr = &tcpip.ErrBadAddress{}
- }
- }
-
- {
- neighbors, err := s.Neighbors(test.nicID, ipv6.ProtocolNumber)
- if diff := cmp.Diff(test.expectedErr, err); diff != "" {
- t.Errorf("s.Neigbors(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
- } else if test.expectedErr == nil {
- if diff := cmp.Diff(
- []stack.NeighborEntry{{Addr: llAddr2, LinkAddr: linkAddr1, State: stack.Static, UpdatedAt: clock.Now()}},
- neighbors,
- ); diff != "" {
- t.Errorf("neighbors mismatch (-want +got):\n%s", diff)
- }
- }
- }
-
- {
- err := s.ClearNeighbors(test.nicID, ipv6.ProtocolNumber)
- if diff := cmp.Diff(test.expectedErr, err); diff != "" {
- t.Errorf("s.ClearNeigbors(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
- } else if test.expectedErr == nil {
- if neighbors, err := s.Neighbors(test.nicID, ipv6.ProtocolNumber); err != nil {
- t.Errorf("s.Neighbors(%d, %d): %s", test.nicID, ipv6.ProtocolNumber, err)
- } else if len(neighbors) != 0 {
- t.Errorf("got len(neighbors) = %d, want = 0; neighbors = %#v", len(neighbors), neighbors)
- }
- }
- }
- })
- }
-}
-
-func TestDefaultNUDConfigurations(t *testing.T) {
- const nicID = 1
-
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- // A neighbor cache is required to store NUDConfigurations. The networking
- // stack will only allocate neighbor caches if a protocol providing link
- // address resolution is specified (e.g. ARP or IPv6).
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- NUDConfigs: stack.DefaultNUDConfigurations(),
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- c, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
- }
- if got, want := c, stack.DefaultNUDConfigurations(); got != want {
- t.Errorf("got stack.NUDConfigurations(%d, %d) = %+v, want = %+v", nicID, ipv6.ProtocolNumber, got, want)
- }
-}
-
-func TestNUDConfigurationsBaseReachableTime(t *testing.T) {
- tests := []struct {
- name string
- baseReachableTime time.Duration
- want time.Duration
- }{
- // Invalid cases
- {
- name: "EqualToZero",
- baseReachableTime: 0,
- want: defaultBaseReachableTime,
- },
- // Valid cases
- {
- name: "MoreThanZero",
- baseReachableTime: time.Millisecond,
- want: time.Millisecond,
- },
- {
- name: "MoreThanDefaultBaseReachableTime",
- baseReachableTime: 2 * defaultBaseReachableTime,
- want: 2 * defaultBaseReachableTime,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- const nicID = 1
-
- c := stack.DefaultNUDConfigurations()
- c.BaseReachableTime = test.baseReachableTime
-
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- // A neighbor cache is required to store NUDConfigurations. The
- // networking stack will only allocate neighbor caches if a protocol
- // providing link address resolution is specified (e.g. ARP or IPv6).
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- NUDConfigs: c,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
- }
- if got := sc.BaseReachableTime; got != test.want {
- t.Errorf("got BaseReachableTime = %q, want = %q", got, test.want)
- }
- })
- }
-}
-
-func TestNUDConfigurationsMinRandomFactor(t *testing.T) {
- tests := []struct {
- name string
- minRandomFactor float32
- want float32
- }{
- // Invalid cases
- {
- name: "LessThanZero",
- minRandomFactor: -1,
- want: defaultMinRandomFactor,
- },
- {
- name: "EqualToZero",
- minRandomFactor: 0,
- want: defaultMinRandomFactor,
- },
- // Valid cases
- {
- name: "MoreThanZero",
- minRandomFactor: 1,
- want: 1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- const nicID = 1
-
- c := stack.DefaultNUDConfigurations()
- c.MinRandomFactor = test.minRandomFactor
-
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- // A neighbor cache is required to store NUDConfigurations. The
- // networking stack will only allocate neighbor caches if a protocol
- // providing link address resolution is specified (e.g. ARP or IPv6).
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- NUDConfigs: c,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
- }
- if got := sc.MinRandomFactor; got != test.want {
- t.Errorf("got MinRandomFactor = %f, want = %f", got, test.want)
- }
- })
- }
-}
-
-func TestNUDConfigurationsMaxRandomFactor(t *testing.T) {
- tests := []struct {
- name string
- minRandomFactor float32
- maxRandomFactor float32
- want float32
- }{
- // Invalid cases
- {
- name: "LessThanZero",
- minRandomFactor: defaultMinRandomFactor,
- maxRandomFactor: -1,
- want: defaultMaxRandomFactor,
- },
- {
- name: "EqualToZero",
- minRandomFactor: defaultMinRandomFactor,
- maxRandomFactor: 0,
- want: defaultMaxRandomFactor,
- },
- {
- name: "LessThanMinRandomFactor",
- minRandomFactor: defaultMinRandomFactor,
- maxRandomFactor: defaultMinRandomFactor * 0.99,
- want: defaultMaxRandomFactor,
- },
- {
- name: "MoreThanMinRandomFactorWhenMinRandomFactorIsLargerThanMaxRandomFactorDefault",
- minRandomFactor: defaultMaxRandomFactor * 2,
- maxRandomFactor: defaultMaxRandomFactor,
- want: defaultMaxRandomFactor * 6,
- },
- // Valid cases
- {
- name: "EqualToMinRandomFactor",
- minRandomFactor: defaultMinRandomFactor,
- maxRandomFactor: defaultMinRandomFactor,
- want: defaultMinRandomFactor,
- },
- {
- name: "MoreThanMinRandomFactor",
- minRandomFactor: defaultMinRandomFactor,
- maxRandomFactor: defaultMinRandomFactor * 1.1,
- want: defaultMinRandomFactor * 1.1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- const nicID = 1
-
- c := stack.DefaultNUDConfigurations()
- c.MinRandomFactor = test.minRandomFactor
- c.MaxRandomFactor = test.maxRandomFactor
-
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- // A neighbor cache is required to store NUDConfigurations. The
- // networking stack will only allocate neighbor caches if a protocol
- // providing link address resolution is specified (e.g. ARP or IPv6).
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- NUDConfigs: c,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
- }
- if got := sc.MaxRandomFactor; got != test.want {
- t.Errorf("got MaxRandomFactor = %f, want = %f", got, test.want)
- }
- })
- }
-}
-
-func TestNUDConfigurationsRetransmitTimer(t *testing.T) {
- tests := []struct {
- name string
- retransmitTimer time.Duration
- want time.Duration
- }{
- // Invalid cases
- {
- name: "EqualToZero",
- retransmitTimer: 0,
- want: defaultRetransmitTimer,
- },
- {
- name: "LessThanMinimumRetransmitTimer",
- retransmitTimer: minimumRetransmitTimer - time.Nanosecond,
- want: defaultRetransmitTimer,
- },
- // Valid cases
- {
- name: "EqualToMinimumRetransmitTimer",
- retransmitTimer: minimumRetransmitTimer,
- want: minimumBaseReachableTime,
- },
- {
- name: "LargetThanMinimumRetransmitTimer",
- retransmitTimer: 2 * minimumBaseReachableTime,
- want: 2 * minimumBaseReachableTime,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- const nicID = 1
-
- c := stack.DefaultNUDConfigurations()
- c.RetransmitTimer = test.retransmitTimer
-
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- // A neighbor cache is required to store NUDConfigurations. The
- // networking stack will only allocate neighbor caches if a protocol
- // providing link address resolution is specified (e.g. ARP or IPv6).
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- NUDConfigs: c,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
- }
- if got := sc.RetransmitTimer; got != test.want {
- t.Errorf("got RetransmitTimer = %q, want = %q", got, test.want)
- }
- })
- }
-}
-
-func TestNUDConfigurationsDelayFirstProbeTime(t *testing.T) {
- tests := []struct {
- name string
- delayFirstProbeTime time.Duration
- want time.Duration
- }{
- // Invalid cases
- {
- name: "EqualToZero",
- delayFirstProbeTime: 0,
- want: defaultDelayFirstProbeTime,
- },
- // Valid cases
- {
- name: "MoreThanZero",
- delayFirstProbeTime: time.Millisecond,
- want: time.Millisecond,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- const nicID = 1
-
- c := stack.DefaultNUDConfigurations()
- c.DelayFirstProbeTime = test.delayFirstProbeTime
-
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- // A neighbor cache is required to store NUDConfigurations. The
- // networking stack will only allocate neighbor caches if a protocol
- // providing link address resolution is specified (e.g. ARP or IPv6).
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- NUDConfigs: c,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
- }
- if got := sc.DelayFirstProbeTime; got != test.want {
- t.Errorf("got DelayFirstProbeTime = %q, want = %q", got, test.want)
- }
- })
- }
-}
-
-func TestNUDConfigurationsMaxMulticastProbes(t *testing.T) {
- tests := []struct {
- name string
- maxMulticastProbes uint32
- want uint32
- }{
- // Invalid cases
- {
- name: "EqualToZero",
- maxMulticastProbes: 0,
- want: defaultMaxMulticastProbes,
- },
- // Valid cases
- {
- name: "MoreThanZero",
- maxMulticastProbes: 1,
- want: 1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- const nicID = 1
-
- c := stack.DefaultNUDConfigurations()
- c.MaxMulticastProbes = test.maxMulticastProbes
-
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- // A neighbor cache is required to store NUDConfigurations. The
- // networking stack will only allocate neighbor caches if a protocol
- // providing link address resolution is specified (e.g. ARP or IPv6).
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- NUDConfigs: c,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
- }
- if got := sc.MaxMulticastProbes; got != test.want {
- t.Errorf("got MaxMulticastProbes = %q, want = %q", got, test.want)
- }
- })
- }
-}
-
-func TestNUDConfigurationsMaxUnicastProbes(t *testing.T) {
- tests := []struct {
- name string
- maxUnicastProbes uint32
- want uint32
- }{
- // Invalid cases
- {
- name: "EqualToZero",
- maxUnicastProbes: 0,
- want: defaultMaxUnicastProbes,
- },
- // Valid cases
- {
- name: "MoreThanZero",
- maxUnicastProbes: 1,
- want: 1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- const nicID = 1
-
- c := stack.DefaultNUDConfigurations()
- c.MaxUnicastProbes = test.maxUnicastProbes
-
- e := channel.New(0, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- // A neighbor cache is required to store NUDConfigurations. The
- // networking stack will only allocate neighbor caches if a protocol
- // providing link address resolution is specified (e.g. ARP or IPv6).
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- NUDConfigs: c,
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
- if err != nil {
- t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
- }
- if got := sc.MaxUnicastProbes; got != test.want {
- t.Errorf("got MaxUnicastProbes = %q, want = %q", got, test.want)
- }
- })
- }
-}
-
-// TestNUDStateReachableTime verifies the correctness of the ReachableTime
-// computation.
-func TestNUDStateReachableTime(t *testing.T) {
- tests := []struct {
- name string
- baseReachableTime time.Duration
- minRandomFactor float32
- maxRandomFactor float32
- want time.Duration
- }{
- {
- name: "AllZeros",
- baseReachableTime: 0,
- minRandomFactor: 0,
- maxRandomFactor: 0,
- want: 0,
- },
- {
- name: "ZeroMaxRandomFactor",
- baseReachableTime: time.Second,
- minRandomFactor: 0,
- maxRandomFactor: 0,
- want: 0,
- },
- {
- name: "ZeroMinRandomFactor",
- baseReachableTime: time.Second,
- minRandomFactor: 0,
- maxRandomFactor: 1,
- want: time.Duration(defaultFakeRandomNum * float32(time.Second)),
- },
- {
- name: "FractionalRandomFactor",
- baseReachableTime: time.Duration(math.MaxInt64),
- minRandomFactor: 0.001,
- maxRandomFactor: 0.002,
- want: time.Duration((0.001 + (0.001 * defaultFakeRandomNum)) * float32(math.MaxInt64)),
- },
- {
- name: "MinAndMaxRandomFactorsEqual",
- baseReachableTime: time.Second,
- minRandomFactor: 1,
- maxRandomFactor: 1,
- want: time.Second,
- },
- {
- name: "MinAndMaxRandomFactorsDifferent",
- baseReachableTime: time.Second,
- minRandomFactor: 1,
- maxRandomFactor: 2,
- want: time.Duration((1.0 + defaultFakeRandomNum) * float32(time.Second)),
- },
- {
- name: "MaxInt64",
- baseReachableTime: time.Duration(math.MaxInt64),
- minRandomFactor: 1,
- maxRandomFactor: 1,
- want: time.Duration(math.MaxInt64),
- },
- {
- name: "Overflow",
- baseReachableTime: time.Duration(math.MaxInt64),
- minRandomFactor: 1.5,
- maxRandomFactor: 1.5,
- want: time.Duration(math.MaxInt64),
- },
- {
- name: "DoubleOverflow",
- baseReachableTime: time.Duration(math.MaxInt64),
- minRandomFactor: 2.5,
- maxRandomFactor: 2.5,
- want: time.Duration(math.MaxInt64),
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- c := stack.NUDConfigurations{
- BaseReachableTime: test.baseReachableTime,
- MinRandomFactor: test.minRandomFactor,
- MaxRandomFactor: test.maxRandomFactor,
- }
- // A fake random number generator is used to ensure deterministic
- // results.
- rng := fakeRand{
- num: defaultFakeRandomNum,
- }
- var clock faketime.NullClock
- s := stack.NewNUDState(c, &clock, rand.New(&rng))
- if got, want := s.ReachableTime(), test.want; got != want {
- t.Errorf("got ReachableTime = %q, want = %q", got, want)
- }
- })
- }
-}
-
-// TestNUDStateRecomputeReachableTime exercises the ReachableTime function
-// twice to verify recomputation of reachable time when the min random factor,
-// max random factor, or base reachable time changes.
-func TestNUDStateRecomputeReachableTime(t *testing.T) {
- const defaultBase = time.Second
- const defaultMin = 2.0 * defaultMaxRandomFactor
- const defaultMax = 3.0 * defaultMaxRandomFactor
-
- tests := []struct {
- name string
- baseReachableTime time.Duration
- minRandomFactor float32
- maxRandomFactor float32
- want time.Duration
- }{
- {
- name: "BaseReachableTime",
- baseReachableTime: 2 * defaultBase,
- minRandomFactor: defaultMin,
- maxRandomFactor: defaultMax,
- want: time.Duration((defaultMin + (defaultMax-defaultMin)*defaultFakeRandomNum) * float32(2*defaultBase)),
- },
- {
- name: "MinRandomFactor",
- baseReachableTime: defaultBase,
- minRandomFactor: defaultMax,
- maxRandomFactor: defaultMax,
- want: time.Duration(defaultMax * float32(defaultBase)),
- },
- {
- name: "MaxRandomFactor",
- baseReachableTime: defaultBase,
- minRandomFactor: defaultMin,
- maxRandomFactor: defaultMin,
- want: time.Duration(defaultMin * float32(defaultBase)),
- },
- {
- name: "BothRandomFactor",
- baseReachableTime: defaultBase,
- minRandomFactor: 2 * defaultMin,
- maxRandomFactor: 2 * defaultMax,
- want: time.Duration((2*defaultMin + (2*defaultMax-2*defaultMin)*defaultFakeRandomNum) * float32(defaultBase)),
- },
- {
- name: "BaseReachableTimeAndBothRandomFactors",
- baseReachableTime: 2 * defaultBase,
- minRandomFactor: 2 * defaultMin,
- maxRandomFactor: 2 * defaultMax,
- want: time.Duration((2*defaultMin + (2*defaultMax-2*defaultMin)*defaultFakeRandomNum) * float32(2*defaultBase)),
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- c := stack.DefaultNUDConfigurations()
- c.BaseReachableTime = defaultBase
- c.MinRandomFactor = defaultMin
- c.MaxRandomFactor = defaultMax
-
- // A fake random number generator is used to ensure deterministic
- // results.
- rng := fakeRand{
- num: defaultFakeRandomNum,
- }
- var clock faketime.NullClock
- s := stack.NewNUDState(c, &clock, rand.New(&rng))
- old := s.ReachableTime()
-
- if got, want := s.ReachableTime(), old; got != want {
- t.Errorf("got ReachableTime = %q, want = %q", got, want)
- }
-
- // Check for recomputation when changing the min random factor, the max
- // random factor, the base reachability time, or any permutation of those
- // three options.
- c.BaseReachableTime = test.baseReachableTime
- c.MinRandomFactor = test.minRandomFactor
- c.MaxRandomFactor = test.maxRandomFactor
- s.SetConfig(c)
-
- if got, want := s.ReachableTime(), test.want; got != want {
- t.Errorf("got ReachableTime = %q, want = %q", got, want)
- }
-
- // Verify that ReachableTime isn't recomputed when none of the
- // configuration options change. The random factor is changed so that if
- // a recompution were to occur, ReachableTime would change.
- rng.num = defaultFakeRandomNum / 2.0
- if got, want := s.ReachableTime(), test.want; got != want {
- t.Errorf("got ReachableTime = %q, want = %q", got, want)
- }
- })
- }
-}
diff --git a/pkg/tcpip/stack/packet_buffer_list.go b/pkg/tcpip/stack/packet_buffer_list.go
new file mode 100644
index 000000000..ce7057d6b
--- /dev/null
+++ b/pkg/tcpip/stack/packet_buffer_list.go
@@ -0,0 +1,221 @@
+package stack
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type PacketBufferElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (PacketBufferElementMapper) linkerFor(elem *PacketBuffer) *PacketBuffer { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type PacketBufferList struct {
+ head *PacketBuffer
+ tail *PacketBuffer
+}
+
+// Reset resets list l to the empty state.
+func (l *PacketBufferList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *PacketBufferList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *PacketBufferList) Front() *PacketBuffer {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *PacketBufferList) Back() *PacketBuffer {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *PacketBufferList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (PacketBufferElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *PacketBufferList) PushFront(e *PacketBuffer) {
+ linker := PacketBufferElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ PacketBufferElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *PacketBufferList) PushBack(e *PacketBuffer) {
+ linker := PacketBufferElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ PacketBufferElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *PacketBufferList) PushBackList(m *PacketBufferList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ PacketBufferElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ PacketBufferElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *PacketBufferList) InsertAfter(b, e *PacketBuffer) {
+ bLinker := PacketBufferElementMapper{}.linkerFor(b)
+ eLinker := PacketBufferElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ PacketBufferElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *PacketBufferList) InsertBefore(a, e *PacketBuffer) {
+ aLinker := PacketBufferElementMapper{}.linkerFor(a)
+ eLinker := PacketBufferElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ PacketBufferElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *PacketBufferList) Remove(e *PacketBuffer) {
+ linker := PacketBufferElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ PacketBufferElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ PacketBufferElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else if l.tail == e {
+ l.tail = prev
+ }
+
+ linker.SetNext(nil)
+ linker.SetPrev(nil)
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type PacketBufferEntry struct {
+ next *PacketBuffer
+ prev *PacketBuffer
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *PacketBufferEntry) Next() *PacketBuffer {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *PacketBufferEntry) Prev() *PacketBuffer {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *PacketBufferEntry) SetNext(elem *PacketBuffer) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *PacketBufferEntry) SetPrev(elem *PacketBuffer) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/stack/packet_buffer_refs.go b/pkg/tcpip/stack/packet_buffer_refs.go
new file mode 100644
index 000000000..c756afd64
--- /dev/null
+++ b/pkg/tcpip/stack/packet_buffer_refs.go
@@ -0,0 +1,140 @@
+package stack
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const packetBufferenableLogging = false
+
+// obj is used to customize logging. Note that we use a pointer to T so that
+// we do not copy the entire object when passed as a format parameter.
+var packetBufferobj *PacketBuffer
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type packetBufferRefs struct {
+ // refCount is composed of two fields:
+ //
+ // [32-bit speculative references]:[32-bit real references]
+ //
+ // Speculative references are used for TryIncRef, to avoid a CompareAndSwap
+ // loop. See IncRef, DecRef and TryIncRef for details of how these fields are
+ // used.
+ refCount int64
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *packetBufferRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *packetBufferRefs) RefType() string {
+ return fmt.Sprintf("%T", packetBufferobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *packetBufferRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *packetBufferRefs) LogRefs() bool {
+ return packetBufferenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *packetBufferRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *packetBufferRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if packetBufferenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.TryIncRef.
+//
+// To do this safely without a loop, a speculative reference is first acquired
+// on the object. This allows multiple concurrent TryIncRef calls to distinguish
+// other TryIncRef calls from genuine references held.
+//
+//go:nosplit
+func (r *packetBufferRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if packetBufferenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ return true
+}
+
+// DecRef implements refs.RefCounter.DecRef.
+//
+// Note that speculative references are counted here. Since they were added
+// prior to real references reaching zero, they will successfully convert to
+// real references. In other words, we see speculative references only in the
+// following case:
+//
+// A: TryIncRef [speculative increase => sees non-negative references]
+// B: DecRef [real decrease]
+// A: TryIncRef [transform speculative to real]
+//
+//go:nosplit
+func (r *packetBufferRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if packetBufferenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *packetBufferRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/tcpip/stack/packet_buffer_test.go b/pkg/tcpip/stack/packet_buffer_test.go
deleted file mode 100644
index c376ed1a1..000000000
--- a/pkg/tcpip/stack/packet_buffer_test.go
+++ /dev/null
@@ -1,655 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at //
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack
-
-import (
- "bytes"
- "fmt"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-func TestPacketHeaderPush(t *testing.T) {
- for _, test := range []struct {
- name string
- reserved int
- link []byte
- network []byte
- transport []byte
- data []byte
- }{
- {
- name: "construct empty packet",
- },
- {
- name: "construct link header only packet",
- reserved: 60,
- link: makeView(10),
- },
- {
- name: "construct link and network header only packet",
- reserved: 60,
- link: makeView(10),
- network: makeView(20),
- },
- {
- name: "construct header only packet",
- reserved: 60,
- link: makeView(10),
- network: makeView(20),
- transport: makeView(30),
- },
- {
- name: "construct data only packet",
- data: makeView(40),
- },
- {
- name: "construct L3 packet",
- reserved: 60,
- network: makeView(20),
- transport: makeView(30),
- data: makeView(40),
- },
- {
- name: "construct L2 packet",
- reserved: 60,
- link: makeView(10),
- network: makeView(20),
- transport: makeView(30),
- data: makeView(40),
- },
- } {
- t.Run(test.name, func(t *testing.T) {
- pk := NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: test.reserved,
- // Make a copy of data to make sure our truth data won't be taint by
- // PacketBuffer.
- Data: buffer.NewViewFromBytes(test.data).ToVectorisedView(),
- })
-
- allHdrSize := len(test.link) + len(test.network) + len(test.transport)
-
- // Check the initial values for packet.
- checkInitialPacketBuffer(t, pk, PacketBufferOptions{
- ReserveHeaderBytes: test.reserved,
- Data: buffer.View(test.data).ToVectorisedView(),
- })
-
- // Push headers.
- if v := test.transport; len(v) > 0 {
- copy(pk.TransportHeader().Push(len(v)), v)
- }
- if v := test.network; len(v) > 0 {
- copy(pk.NetworkHeader().Push(len(v)), v)
- }
- if v := test.link; len(v) > 0 {
- copy(pk.LinkHeader().Push(len(v)), v)
- }
-
- // Check the after values for packet.
- if got, want := pk.ReservedHeaderBytes(), test.reserved; got != want {
- t.Errorf("After pk.ReservedHeaderBytes() = %d, want %d", got, want)
- }
- if got, want := pk.AvailableHeaderBytes(), test.reserved-allHdrSize; got != want {
- t.Errorf("After pk.AvailableHeaderBytes() = %d, want %d", got, want)
- }
- if got, want := pk.HeaderSize(), allHdrSize; got != want {
- t.Errorf("After pk.HeaderSize() = %d, want %d", got, want)
- }
- if got, want := pk.Size(), allHdrSize+len(test.data); got != want {
- t.Errorf("After pk.Size() = %d, want %d", got, want)
- }
- // Check the after state.
- checkPacketContents(t, "After ", pk, packetContents{
- link: test.link,
- network: test.network,
- transport: test.transport,
- data: test.data,
- })
- })
- }
-}
-
-func TestPacketHeaderConsume(t *testing.T) {
- for _, test := range []struct {
- name string
- data []byte
- link int
- network int
- transport int
- }{
- {
- name: "parse L2 packet",
- data: concatViews(makeView(10), makeView(20), makeView(30), makeView(40)),
- link: 10,
- network: 20,
- transport: 30,
- },
- {
- name: "parse L3 packet",
- data: concatViews(makeView(20), makeView(30), makeView(40)),
- network: 20,
- transport: 30,
- },
- } {
- t.Run(test.name, func(t *testing.T) {
- pk := NewPacketBuffer(PacketBufferOptions{
- // Make a copy of data to make sure our truth data won't be taint by
- // PacketBuffer.
- Data: buffer.NewViewFromBytes(test.data).ToVectorisedView(),
- })
-
- // Check the initial values for packet.
- checkInitialPacketBuffer(t, pk, PacketBufferOptions{
- Data: buffer.View(test.data).ToVectorisedView(),
- })
-
- // Consume headers.
- if size := test.link; size > 0 {
- if _, ok := pk.LinkHeader().Consume(size); !ok {
- t.Fatalf("pk.LinkHeader().Consume() = false, want true")
- }
- }
- if size := test.network; size > 0 {
- if _, ok := pk.NetworkHeader().Consume(size); !ok {
- t.Fatalf("pk.NetworkHeader().Consume() = false, want true")
- }
- }
- if size := test.transport; size > 0 {
- if _, ok := pk.TransportHeader().Consume(size); !ok {
- t.Fatalf("pk.TransportHeader().Consume() = false, want true")
- }
- }
-
- allHdrSize := test.link + test.network + test.transport
-
- // Check the after values for packet.
- if got, want := pk.ReservedHeaderBytes(), 0; got != want {
- t.Errorf("After pk.ReservedHeaderBytes() = %d, want %d", got, want)
- }
- if got, want := pk.AvailableHeaderBytes(), 0; got != want {
- t.Errorf("After pk.AvailableHeaderBytes() = %d, want %d", got, want)
- }
- if got, want := pk.HeaderSize(), allHdrSize; got != want {
- t.Errorf("After pk.HeaderSize() = %d, want %d", got, want)
- }
- if got, want := pk.Size(), len(test.data); got != want {
- t.Errorf("After pk.Size() = %d, want %d", got, want)
- }
- // Check the after state of pk.
- checkPacketContents(t, "After ", pk, packetContents{
- link: test.data[:test.link],
- network: test.data[test.link:][:test.network],
- transport: test.data[test.link+test.network:][:test.transport],
- data: test.data[allHdrSize:],
- })
- })
- }
-}
-
-func TestPacketHeaderConsumeDataTooShort(t *testing.T) {
- data := makeView(10)
-
- pk := NewPacketBuffer(PacketBufferOptions{
- // Make a copy of data to make sure our truth data won't be taint by
- // PacketBuffer.
- Data: buffer.NewViewFromBytes(data).ToVectorisedView(),
- })
-
- // Consume should fail if pkt.Data is too short.
- if _, ok := pk.LinkHeader().Consume(11); ok {
- t.Fatalf("pk.LinkHeader().Consume() = _, true; want _, false")
- }
- if _, ok := pk.NetworkHeader().Consume(11); ok {
- t.Fatalf("pk.NetworkHeader().Consume() = _, true; want _, false")
- }
- if _, ok := pk.TransportHeader().Consume(11); ok {
- t.Fatalf("pk.TransportHeader().Consume() = _, true; want _, false")
- }
-
- // Check packet should look the same as initial packet.
- checkInitialPacketBuffer(t, pk, PacketBufferOptions{
- Data: buffer.View(data).ToVectorisedView(),
- })
-}
-
-// This is a very obscure use-case seen in the code that verifies packets
-// before sending them out. It tries to parse the headers to verify.
-// PacketHeader was initially not designed to mix Push() and Consume(), but it
-// works and it's been relied upon. Include a test here.
-func TestPacketHeaderPushConsumeMixed(t *testing.T) {
- link := makeView(10)
- network := makeView(20)
- data := makeView(30)
-
- initData := append([]byte(nil), network...)
- initData = append(initData, data...)
- pk := NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: len(link),
- Data: buffer.NewViewFromBytes(initData).ToVectorisedView(),
- })
-
- // 1. Consume network header
- gotNetwork, ok := pk.NetworkHeader().Consume(len(network))
- if !ok {
- t.Fatalf("pk.NetworkHeader().Consume(%d) = _, false; want _, true", len(network))
- }
- checkViewEqual(t, "gotNetwork", gotNetwork, network)
-
- // 2. Push link header
- copy(pk.LinkHeader().Push(len(link)), link)
-
- checkPacketContents(t, "" /* prefix */, pk, packetContents{
- link: link,
- network: network,
- data: data,
- })
-}
-
-func TestPacketHeaderPushConsumeMixedTooLong(t *testing.T) {
- link := makeView(10)
- network := makeView(20)
- data := makeView(30)
-
- initData := concatViews(network, data)
- pk := NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: len(link),
- Data: buffer.NewViewFromBytes(initData).ToVectorisedView(),
- })
-
- // 1. Push link header
- copy(pk.LinkHeader().Push(len(link)), link)
-
- checkPacketContents(t, "" /* prefix */, pk, packetContents{
- link: link,
- data: initData,
- })
-
- // 2. Consume network header, with a number of bytes too large.
- gotNetwork, ok := pk.NetworkHeader().Consume(len(initData) + 1)
- if ok {
- t.Fatalf("pk.NetworkHeader().Consume(%d) = %q, true; want _, false", len(initData)+1, gotNetwork)
- }
-
- checkPacketContents(t, "" /* prefix */, pk, packetContents{
- link: link,
- data: initData,
- })
-}
-
-func TestPacketHeaderPushCalledAtMostOnce(t *testing.T) {
- const headerSize = 10
-
- pk := NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: headerSize * int(numHeaderType),
- })
-
- for _, h := range []PacketHeader{
- pk.TransportHeader(),
- pk.NetworkHeader(),
- pk.LinkHeader(),
- } {
- t.Run("PushedTwice/"+h.typ.String(), func(t *testing.T) {
- h.Push(headerSize)
-
- defer func() { recover() }()
- h.Push(headerSize)
- t.Fatal("Second push should have panicked")
- })
- }
-}
-
-func TestPacketHeaderConsumeCalledAtMostOnce(t *testing.T) {
- const headerSize = 10
-
- pk := NewPacketBuffer(PacketBufferOptions{
- Data: makeView(headerSize * int(numHeaderType)).ToVectorisedView(),
- })
-
- for _, h := range []PacketHeader{
- pk.LinkHeader(),
- pk.NetworkHeader(),
- pk.TransportHeader(),
- } {
- t.Run("ConsumedTwice/"+h.typ.String(), func(t *testing.T) {
- if _, ok := h.Consume(headerSize); !ok {
- t.Fatal("First consume should succeed")
- }
-
- defer func() { recover() }()
- h.Consume(headerSize)
- t.Fatal("Second consume should have panicked")
- })
- }
-}
-
-func TestPacketHeaderPushThenConsumePanics(t *testing.T) {
- const headerSize = 10
-
- pk := NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: headerSize * int(numHeaderType),
- })
-
- for _, h := range []PacketHeader{
- pk.TransportHeader(),
- pk.NetworkHeader(),
- pk.LinkHeader(),
- } {
- t.Run(h.typ.String(), func(t *testing.T) {
- h.Push(headerSize)
-
- defer func() { recover() }()
- h.Consume(headerSize)
- t.Fatal("Consume should have panicked")
- })
- }
-}
-
-func TestPacketHeaderConsumeThenPushPanics(t *testing.T) {
- const headerSize = 10
-
- pk := NewPacketBuffer(PacketBufferOptions{
- Data: makeView(headerSize * int(numHeaderType)).ToVectorisedView(),
- })
-
- for _, h := range []PacketHeader{
- pk.LinkHeader(),
- pk.NetworkHeader(),
- pk.TransportHeader(),
- } {
- t.Run(h.typ.String(), func(t *testing.T) {
- h.Consume(headerSize)
-
- defer func() { recover() }()
- h.Push(headerSize)
- t.Fatal("Push should have panicked")
- })
- }
-}
-
-func TestPacketBufferData(t *testing.T) {
- for _, tc := range []struct {
- name string
- makePkt func(*testing.T) *PacketBuffer
- data string
- }{
- {
- name: "inbound packet",
- makePkt: func(*testing.T) *PacketBuffer {
- pkt := NewPacketBuffer(PacketBufferOptions{
- Data: vv("aabbbbccccccDATA"),
- })
- pkt.LinkHeader().Consume(2)
- pkt.NetworkHeader().Consume(4)
- pkt.TransportHeader().Consume(6)
- return pkt
- },
- data: "DATA",
- },
- {
- name: "outbound packet",
- makePkt: func(*testing.T) *PacketBuffer {
- pkt := NewPacketBuffer(PacketBufferOptions{
- ReserveHeaderBytes: 12,
- Data: vv("DATA"),
- })
- copy(pkt.TransportHeader().Push(6), []byte("cccccc"))
- copy(pkt.NetworkHeader().Push(4), []byte("bbbb"))
- copy(pkt.LinkHeader().Push(2), []byte("aa"))
- return pkt
- },
- data: "DATA",
- },
- } {
- t.Run(tc.name, func(t *testing.T) {
- // PullUp
- for _, n := range []int{1, len(tc.data)} {
- t.Run(fmt.Sprintf("PullUp%d", n), func(t *testing.T) {
- pkt := tc.makePkt(t)
- v, ok := pkt.Data().PullUp(n)
- wantV := []byte(tc.data)[:n]
- if !ok || !bytes.Equal(v, wantV) {
- t.Errorf("pkt.Data().PullUp(%d) = %q, %t; want %q, true", n, v, ok, wantV)
- }
- })
- }
- t.Run("PullUpOutOfBounds", func(t *testing.T) {
- n := len(tc.data) + 1
- pkt := tc.makePkt(t)
- v, ok := pkt.Data().PullUp(n)
- if ok || v != nil {
- t.Errorf("pkt.Data().PullUp(%d) = %q, %t; want nil, false", n, v, ok)
- }
- })
-
- // Consume.
- for _, n := range []int{1, len(tc.data)} {
- t.Run(fmt.Sprintf("Consume%d", n), func(t *testing.T) {
- pkt := tc.makePkt(t)
- v, ok := pkt.Data().Consume(n)
- if !ok {
- t.Fatalf("Consume failed")
- }
- if want := []byte(tc.data)[:n]; !bytes.Equal(v, want) {
- t.Fatalf("pkt.Data().Consume(n) = 0x%x, want 0x%x", v, want)
- }
-
- checkData(t, pkt, []byte(tc.data)[n:])
- })
- }
-
- // CapLength
- for _, n := range []int{0, 1, len(tc.data)} {
- t.Run(fmt.Sprintf("CapLength%d", n), func(t *testing.T) {
- pkt := tc.makePkt(t)
- pkt.Data().CapLength(n)
-
- want := []byte(tc.data)
- if n < len(want) {
- want = want[:n]
- }
- checkData(t, pkt, want)
- })
- }
-
- // Views
- t.Run("Views", func(t *testing.T) {
- pkt := tc.makePkt(t)
- checkData(t, pkt, []byte(tc.data))
- })
-
- // AppendView
- t.Run("AppendView", func(t *testing.T) {
- s := "APPEND"
-
- pkt := tc.makePkt(t)
- pkt.Data().AppendView(buffer.View(s))
-
- checkData(t, pkt, []byte(tc.data+s))
- })
-
- // ReadFromVV
- for _, n := range []int{0, 1, 2, 7, 10, 14, 20} {
- t.Run(fmt.Sprintf("ReadFromVV%d", n), func(t *testing.T) {
- s := "TO READ"
- srcVV := vv(s, s)
- s += s
-
- pkt := tc.makePkt(t)
- pkt.Data().ReadFromVV(&srcVV, n)
-
- if n < len(s) {
- s = s[:n]
- }
- checkData(t, pkt, []byte(tc.data+s))
- })
- }
-
- // ExtractVV
- t.Run("ExtractVV", func(t *testing.T) {
- pkt := tc.makePkt(t)
- extractedVV := pkt.Data().ExtractVV()
-
- got := extractedVV.ToOwnedView()
- want := []byte(tc.data)
- if !bytes.Equal(got, want) {
- t.Errorf("pkt.Data().ExtractVV().ToOwnedView() = %q, want %q", got, want)
- }
- })
- })
- }
-}
-
-type packetContents struct {
- link buffer.View
- network buffer.View
- transport buffer.View
- data buffer.View
-}
-
-func checkPacketContents(t *testing.T, prefix string, pk *PacketBuffer, want packetContents) {
- t.Helper()
- // Headers.
- checkPacketHeader(t, prefix+"pk.LinkHeader", pk.LinkHeader(), want.link)
- checkPacketHeader(t, prefix+"pk.NetworkHeader", pk.NetworkHeader(), want.network)
- checkPacketHeader(t, prefix+"pk.TransportHeader", pk.TransportHeader(), want.transport)
- // Data.
- checkData(t, pk, want.data)
- // Whole packet.
- checkViewEqual(t, prefix+"pk.Views()",
- concatViews(pk.Views()...),
- concatViews(want.link, want.network, want.transport, want.data))
- // PayloadSince.
- checkViewEqual(t, prefix+"PayloadSince(LinkHeader)",
- PayloadSince(pk.LinkHeader()),
- concatViews(want.link, want.network, want.transport, want.data))
- checkViewEqual(t, prefix+"PayloadSince(NetworkHeader)",
- PayloadSince(pk.NetworkHeader()),
- concatViews(want.network, want.transport, want.data))
- checkViewEqual(t, prefix+"PayloadSince(TransportHeader)",
- PayloadSince(pk.TransportHeader()),
- concatViews(want.transport, want.data))
-}
-
-func checkInitialPacketBuffer(t *testing.T, pk *PacketBuffer, opts PacketBufferOptions) {
- t.Helper()
- reserved := opts.ReserveHeaderBytes
- if got, want := pk.ReservedHeaderBytes(), reserved; got != want {
- t.Errorf("Initial pk.ReservedHeaderBytes() = %d, want %d", got, want)
- }
- if got, want := pk.AvailableHeaderBytes(), reserved; got != want {
- t.Errorf("Initial pk.AvailableHeaderBytes() = %d, want %d", got, want)
- }
- if got, want := pk.HeaderSize(), 0; got != want {
- t.Errorf("Initial pk.HeaderSize() = %d, want %d", got, want)
- }
- data := opts.Data.ToView()
- if got, want := pk.Size(), len(data); got != want {
- t.Errorf("Initial pk.Size() = %d, want %d", got, want)
- }
- checkPacketContents(t, "Initial ", pk, packetContents{
- data: data,
- })
-}
-
-func checkPacketHeader(t *testing.T, name string, h PacketHeader, want []byte) {
- t.Helper()
- checkViewEqual(t, name+".View()", h.View(), want)
-}
-
-func checkViewEqual(t *testing.T, what string, got, want buffer.View) {
- t.Helper()
- if !bytes.Equal(got, want) {
- t.Errorf("%s = %x, want %x", what, got, want)
- }
-}
-
-func checkData(t *testing.T, pkt *PacketBuffer, want []byte) {
- t.Helper()
- if got := concatViews(pkt.Data().Views()...); !bytes.Equal(got, want) {
- t.Errorf("pkt.Data().Views() = 0x%x, want 0x%x", got, want)
- }
- if got := pkt.Data().Size(); got != len(want) {
- t.Errorf("pkt.Data().Size() = %d, want %d", got, len(want))
- }
-
- t.Run("AsRange", func(t *testing.T) {
- // Full range
- checkRange(t, pkt.Data().AsRange(), want)
-
- // SubRange
- for _, off := range []int{0, 1, len(want), len(want) + 1} {
- t.Run(fmt.Sprintf("SubRange%d", off), func(t *testing.T) {
- // Empty when off is greater than the size of range.
- var sub []byte
- if off < len(want) {
- sub = want[off:]
- }
- checkRange(t, pkt.Data().AsRange().SubRange(off), sub)
- })
- }
-
- // Capped
- for _, n := range []int{0, 1, len(want), len(want) + 1} {
- t.Run(fmt.Sprintf("Capped%d", n), func(t *testing.T) {
- sub := want
- if n < len(sub) {
- sub = sub[:n]
- }
- checkRange(t, pkt.Data().AsRange().Capped(n), sub)
- })
- }
- })
-}
-
-func checkRange(t *testing.T, r Range, data []byte) {
- if got, want := r.Size(), len(data); got != want {
- t.Errorf("r.Size() = %d, want %d", got, want)
- }
- if got := r.AsView(); !bytes.Equal(got, data) {
- t.Errorf("r.AsView() = %x, want %x", got, data)
- }
- if got := r.ToOwnedView(); !bytes.Equal(got, data) {
- t.Errorf("r.ToOwnedView() = %x, want %x", got, data)
- }
- if got, want := r.Checksum(), header.Checksum(data, 0 /* initial */); got != want {
- t.Errorf("r.Checksum() = %x, want %x", got, want)
- }
-}
-
-func vv(pieces ...string) buffer.VectorisedView {
- var views []buffer.View
- var size int
- for _, p := range pieces {
- v := buffer.View([]byte(p))
- size += len(v)
- views = append(views, v)
- }
- return buffer.NewVectorisedView(size, views)
-}
-
-func makeView(size int) buffer.View {
- b := byte(size)
- return bytes.Repeat([]byte{b}, size)
-}
-
-func concatViews(views ...buffer.View) buffer.View {
- var all buffer.View
- for _, v := range views {
- all = append(all, v...)
- }
- return all
-}
diff --git a/pkg/tcpip/stack/stack_state_autogen.go b/pkg/tcpip/stack/stack_state_autogen.go
new file mode 100644
index 000000000..b6ecac560
--- /dev/null
+++ b/pkg/tcpip/stack/stack_state_autogen.go
@@ -0,0 +1,1299 @@
+// automatically generated by stateify.
+
+package stack
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (t *tuple) StateTypeName() string {
+ return "pkg/tcpip/stack.tuple"
+}
+
+func (t *tuple) StateFields() []string {
+ return []string{
+ "tupleEntry",
+ "conn",
+ "reply",
+ "tupleID",
+ }
+}
+
+func (t *tuple) beforeSave() {}
+
+// +checklocksignore
+func (t *tuple) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.tupleEntry)
+ stateSinkObject.Save(1, &t.conn)
+ stateSinkObject.Save(2, &t.reply)
+ stateSinkObject.Save(3, &t.tupleID)
+}
+
+func (t *tuple) afterLoad() {}
+
+// +checklocksignore
+func (t *tuple) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.tupleEntry)
+ stateSourceObject.Load(1, &t.conn)
+ stateSourceObject.Load(2, &t.reply)
+ stateSourceObject.Load(3, &t.tupleID)
+}
+
+func (ti *tupleID) StateTypeName() string {
+ return "pkg/tcpip/stack.tupleID"
+}
+
+func (ti *tupleID) StateFields() []string {
+ return []string{
+ "srcAddr",
+ "srcPort",
+ "dstAddr",
+ "dstPort",
+ "transProto",
+ "netProto",
+ }
+}
+
+func (ti *tupleID) beforeSave() {}
+
+// +checklocksignore
+func (ti *tupleID) StateSave(stateSinkObject state.Sink) {
+ ti.beforeSave()
+ stateSinkObject.Save(0, &ti.srcAddr)
+ stateSinkObject.Save(1, &ti.srcPort)
+ stateSinkObject.Save(2, &ti.dstAddr)
+ stateSinkObject.Save(3, &ti.dstPort)
+ stateSinkObject.Save(4, &ti.transProto)
+ stateSinkObject.Save(5, &ti.netProto)
+}
+
+func (ti *tupleID) afterLoad() {}
+
+// +checklocksignore
+func (ti *tupleID) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &ti.srcAddr)
+ stateSourceObject.Load(1, &ti.srcPort)
+ stateSourceObject.Load(2, &ti.dstAddr)
+ stateSourceObject.Load(3, &ti.dstPort)
+ stateSourceObject.Load(4, &ti.transProto)
+ stateSourceObject.Load(5, &ti.netProto)
+}
+
+func (cn *conn) StateTypeName() string {
+ return "pkg/tcpip/stack.conn"
+}
+
+func (cn *conn) StateFields() []string {
+ return []string{
+ "ct",
+ "original",
+ "reply",
+ "finalized",
+ "sourceManip",
+ "destinationManip",
+ "tcb",
+ "lastUsed",
+ }
+}
+
+func (cn *conn) beforeSave() {}
+
+// +checklocksignore
+func (cn *conn) StateSave(stateSinkObject state.Sink) {
+ cn.beforeSave()
+ stateSinkObject.Save(0, &cn.ct)
+ stateSinkObject.Save(1, &cn.original)
+ stateSinkObject.Save(2, &cn.reply)
+ stateSinkObject.Save(3, &cn.finalized)
+ stateSinkObject.Save(4, &cn.sourceManip)
+ stateSinkObject.Save(5, &cn.destinationManip)
+ stateSinkObject.Save(6, &cn.tcb)
+ stateSinkObject.Save(7, &cn.lastUsed)
+}
+
+func (cn *conn) afterLoad() {}
+
+// +checklocksignore
+func (cn *conn) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &cn.ct)
+ stateSourceObject.Load(1, &cn.original)
+ stateSourceObject.Load(2, &cn.reply)
+ stateSourceObject.Load(3, &cn.finalized)
+ stateSourceObject.Load(4, &cn.sourceManip)
+ stateSourceObject.Load(5, &cn.destinationManip)
+ stateSourceObject.Load(6, &cn.tcb)
+ stateSourceObject.Load(7, &cn.lastUsed)
+}
+
+func (ct *ConnTrack) StateTypeName() string {
+ return "pkg/tcpip/stack.ConnTrack"
+}
+
+func (ct *ConnTrack) StateFields() []string {
+ return []string{
+ "seed",
+ "clock",
+ "buckets",
+ }
+}
+
+func (ct *ConnTrack) beforeSave() {}
+
+// +checklocksignore
+func (ct *ConnTrack) StateSave(stateSinkObject state.Sink) {
+ ct.beforeSave()
+ stateSinkObject.Save(0, &ct.seed)
+ stateSinkObject.Save(1, &ct.clock)
+ stateSinkObject.Save(2, &ct.buckets)
+}
+
+func (ct *ConnTrack) afterLoad() {}
+
+// +checklocksignore
+func (ct *ConnTrack) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &ct.seed)
+ stateSourceObject.Load(1, &ct.clock)
+ stateSourceObject.Load(2, &ct.buckets)
+}
+
+func (bkt *bucket) StateTypeName() string {
+ return "pkg/tcpip/stack.bucket"
+}
+
+func (bkt *bucket) StateFields() []string {
+ return []string{
+ "tuples",
+ }
+}
+
+func (bkt *bucket) beforeSave() {}
+
+// +checklocksignore
+func (bkt *bucket) StateSave(stateSinkObject state.Sink) {
+ bkt.beforeSave()
+ stateSinkObject.Save(0, &bkt.tuples)
+}
+
+func (bkt *bucket) afterLoad() {}
+
+// +checklocksignore
+func (bkt *bucket) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &bkt.tuples)
+}
+
+func (it *IPTables) StateTypeName() string {
+ return "pkg/tcpip/stack.IPTables"
+}
+
+func (it *IPTables) StateFields() []string {
+ return []string{
+ "priorities",
+ "connections",
+ "reaperDone",
+ "mu",
+ "v4Tables",
+ "v6Tables",
+ "modified",
+ }
+}
+
+// +checklocksignore
+func (it *IPTables) StateSave(stateSinkObject state.Sink) {
+ it.beforeSave()
+ stateSinkObject.Save(0, &it.priorities)
+ stateSinkObject.Save(1, &it.connections)
+ stateSinkObject.Save(2, &it.reaperDone)
+ stateSinkObject.Save(3, &it.mu)
+ stateSinkObject.Save(4, &it.v4Tables)
+ stateSinkObject.Save(5, &it.v6Tables)
+ stateSinkObject.Save(6, &it.modified)
+}
+
+// +checklocksignore
+func (it *IPTables) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &it.priorities)
+ stateSourceObject.Load(1, &it.connections)
+ stateSourceObject.Load(2, &it.reaperDone)
+ stateSourceObject.Load(3, &it.mu)
+ stateSourceObject.Load(4, &it.v4Tables)
+ stateSourceObject.Load(5, &it.v6Tables)
+ stateSourceObject.Load(6, &it.modified)
+ stateSourceObject.AfterLoad(it.afterLoad)
+}
+
+func (table *Table) StateTypeName() string {
+ return "pkg/tcpip/stack.Table"
+}
+
+func (table *Table) StateFields() []string {
+ return []string{
+ "Rules",
+ "BuiltinChains",
+ "Underflows",
+ }
+}
+
+func (table *Table) beforeSave() {}
+
+// +checklocksignore
+func (table *Table) StateSave(stateSinkObject state.Sink) {
+ table.beforeSave()
+ stateSinkObject.Save(0, &table.Rules)
+ stateSinkObject.Save(1, &table.BuiltinChains)
+ stateSinkObject.Save(2, &table.Underflows)
+}
+
+func (table *Table) afterLoad() {}
+
+// +checklocksignore
+func (table *Table) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &table.Rules)
+ stateSourceObject.Load(1, &table.BuiltinChains)
+ stateSourceObject.Load(2, &table.Underflows)
+}
+
+func (r *Rule) StateTypeName() string {
+ return "pkg/tcpip/stack.Rule"
+}
+
+func (r *Rule) StateFields() []string {
+ return []string{
+ "Filter",
+ "Matchers",
+ "Target",
+ }
+}
+
+func (r *Rule) beforeSave() {}
+
+// +checklocksignore
+func (r *Rule) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.Filter)
+ stateSinkObject.Save(1, &r.Matchers)
+ stateSinkObject.Save(2, &r.Target)
+}
+
+func (r *Rule) afterLoad() {}
+
+// +checklocksignore
+func (r *Rule) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.Filter)
+ stateSourceObject.Load(1, &r.Matchers)
+ stateSourceObject.Load(2, &r.Target)
+}
+
+func (fl *IPHeaderFilter) StateTypeName() string {
+ return "pkg/tcpip/stack.IPHeaderFilter"
+}
+
+func (fl *IPHeaderFilter) StateFields() []string {
+ return []string{
+ "Protocol",
+ "CheckProtocol",
+ "Dst",
+ "DstMask",
+ "DstInvert",
+ "Src",
+ "SrcMask",
+ "SrcInvert",
+ "InputInterface",
+ "InputInterfaceMask",
+ "InputInterfaceInvert",
+ "OutputInterface",
+ "OutputInterfaceMask",
+ "OutputInterfaceInvert",
+ }
+}
+
+func (fl *IPHeaderFilter) beforeSave() {}
+
+// +checklocksignore
+func (fl *IPHeaderFilter) StateSave(stateSinkObject state.Sink) {
+ fl.beforeSave()
+ stateSinkObject.Save(0, &fl.Protocol)
+ stateSinkObject.Save(1, &fl.CheckProtocol)
+ stateSinkObject.Save(2, &fl.Dst)
+ stateSinkObject.Save(3, &fl.DstMask)
+ stateSinkObject.Save(4, &fl.DstInvert)
+ stateSinkObject.Save(5, &fl.Src)
+ stateSinkObject.Save(6, &fl.SrcMask)
+ stateSinkObject.Save(7, &fl.SrcInvert)
+ stateSinkObject.Save(8, &fl.InputInterface)
+ stateSinkObject.Save(9, &fl.InputInterfaceMask)
+ stateSinkObject.Save(10, &fl.InputInterfaceInvert)
+ stateSinkObject.Save(11, &fl.OutputInterface)
+ stateSinkObject.Save(12, &fl.OutputInterfaceMask)
+ stateSinkObject.Save(13, &fl.OutputInterfaceInvert)
+}
+
+func (fl *IPHeaderFilter) afterLoad() {}
+
+// +checklocksignore
+func (fl *IPHeaderFilter) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fl.Protocol)
+ stateSourceObject.Load(1, &fl.CheckProtocol)
+ stateSourceObject.Load(2, &fl.Dst)
+ stateSourceObject.Load(3, &fl.DstMask)
+ stateSourceObject.Load(4, &fl.DstInvert)
+ stateSourceObject.Load(5, &fl.Src)
+ stateSourceObject.Load(6, &fl.SrcMask)
+ stateSourceObject.Load(7, &fl.SrcInvert)
+ stateSourceObject.Load(8, &fl.InputInterface)
+ stateSourceObject.Load(9, &fl.InputInterfaceMask)
+ stateSourceObject.Load(10, &fl.InputInterfaceInvert)
+ stateSourceObject.Load(11, &fl.OutputInterface)
+ stateSourceObject.Load(12, &fl.OutputInterfaceMask)
+ stateSourceObject.Load(13, &fl.OutputInterfaceInvert)
+}
+
+func (l *neighborEntryList) StateTypeName() string {
+ return "pkg/tcpip/stack.neighborEntryList"
+}
+
+func (l *neighborEntryList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *neighborEntryList) beforeSave() {}
+
+// +checklocksignore
+func (l *neighborEntryList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *neighborEntryList) afterLoad() {}
+
+// +checklocksignore
+func (l *neighborEntryList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *neighborEntryEntry) StateTypeName() string {
+ return "pkg/tcpip/stack.neighborEntryEntry"
+}
+
+func (e *neighborEntryEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *neighborEntryEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *neighborEntryEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *neighborEntryEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *neighborEntryEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (p *PacketBufferList) StateTypeName() string {
+ return "pkg/tcpip/stack.PacketBufferList"
+}
+
+func (p *PacketBufferList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (p *PacketBufferList) beforeSave() {}
+
+// +checklocksignore
+func (p *PacketBufferList) StateSave(stateSinkObject state.Sink) {
+ p.beforeSave()
+ stateSinkObject.Save(0, &p.head)
+ stateSinkObject.Save(1, &p.tail)
+}
+
+func (p *PacketBufferList) afterLoad() {}
+
+// +checklocksignore
+func (p *PacketBufferList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &p.head)
+ stateSourceObject.Load(1, &p.tail)
+}
+
+func (e *PacketBufferEntry) StateTypeName() string {
+ return "pkg/tcpip/stack.PacketBufferEntry"
+}
+
+func (e *PacketBufferEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *PacketBufferEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *PacketBufferEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *PacketBufferEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *PacketBufferEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (r *packetBufferRefs) StateTypeName() string {
+ return "pkg/tcpip/stack.packetBufferRefs"
+}
+
+func (r *packetBufferRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *packetBufferRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *packetBufferRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *packetBufferRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (t *TransportEndpointID) StateTypeName() string {
+ return "pkg/tcpip/stack.TransportEndpointID"
+}
+
+func (t *TransportEndpointID) StateFields() []string {
+ return []string{
+ "LocalPort",
+ "LocalAddress",
+ "RemotePort",
+ "RemoteAddress",
+ }
+}
+
+func (t *TransportEndpointID) beforeSave() {}
+
+// +checklocksignore
+func (t *TransportEndpointID) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.LocalPort)
+ stateSinkObject.Save(1, &t.LocalAddress)
+ stateSinkObject.Save(2, &t.RemotePort)
+ stateSinkObject.Save(3, &t.RemoteAddress)
+}
+
+func (t *TransportEndpointID) afterLoad() {}
+
+// +checklocksignore
+func (t *TransportEndpointID) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.LocalPort)
+ stateSourceObject.Load(1, &t.LocalAddress)
+ stateSourceObject.Load(2, &t.RemotePort)
+ stateSourceObject.Load(3, &t.RemoteAddress)
+}
+
+func (g *GSOType) StateTypeName() string {
+ return "pkg/tcpip/stack.GSOType"
+}
+
+func (g *GSOType) StateFields() []string {
+ return nil
+}
+
+func (g *GSO) StateTypeName() string {
+ return "pkg/tcpip/stack.GSO"
+}
+
+func (g *GSO) StateFields() []string {
+ return []string{
+ "Type",
+ "NeedsCsum",
+ "CsumOffset",
+ "MSS",
+ "L3HdrLen",
+ "MaxSize",
+ }
+}
+
+func (g *GSO) beforeSave() {}
+
+// +checklocksignore
+func (g *GSO) StateSave(stateSinkObject state.Sink) {
+ g.beforeSave()
+ stateSinkObject.Save(0, &g.Type)
+ stateSinkObject.Save(1, &g.NeedsCsum)
+ stateSinkObject.Save(2, &g.CsumOffset)
+ stateSinkObject.Save(3, &g.MSS)
+ stateSinkObject.Save(4, &g.L3HdrLen)
+ stateSinkObject.Save(5, &g.MaxSize)
+}
+
+func (g *GSO) afterLoad() {}
+
+// +checklocksignore
+func (g *GSO) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &g.Type)
+ stateSourceObject.Load(1, &g.NeedsCsum)
+ stateSourceObject.Load(2, &g.CsumOffset)
+ stateSourceObject.Load(3, &g.MSS)
+ stateSourceObject.Load(4, &g.L3HdrLen)
+ stateSourceObject.Load(5, &g.MaxSize)
+}
+
+func (t *TransportEndpointInfo) StateTypeName() string {
+ return "pkg/tcpip/stack.TransportEndpointInfo"
+}
+
+func (t *TransportEndpointInfo) StateFields() []string {
+ return []string{
+ "NetProto",
+ "TransProto",
+ "ID",
+ "BindNICID",
+ "BindAddr",
+ "RegisterNICID",
+ }
+}
+
+func (t *TransportEndpointInfo) beforeSave() {}
+
+// +checklocksignore
+func (t *TransportEndpointInfo) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.NetProto)
+ stateSinkObject.Save(1, &t.TransProto)
+ stateSinkObject.Save(2, &t.ID)
+ stateSinkObject.Save(3, &t.BindNICID)
+ stateSinkObject.Save(4, &t.BindAddr)
+ stateSinkObject.Save(5, &t.RegisterNICID)
+}
+
+func (t *TransportEndpointInfo) afterLoad() {}
+
+// +checklocksignore
+func (t *TransportEndpointInfo) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.NetProto)
+ stateSourceObject.Load(1, &t.TransProto)
+ stateSourceObject.Load(2, &t.ID)
+ stateSourceObject.Load(3, &t.BindNICID)
+ stateSourceObject.Load(4, &t.BindAddr)
+ stateSourceObject.Load(5, &t.RegisterNICID)
+}
+
+func (t *TCPCubicState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPCubicState"
+}
+
+func (t *TCPCubicState) StateFields() []string {
+ return []string{
+ "WLastMax",
+ "WMax",
+ "T",
+ "TimeSinceLastCongestion",
+ "C",
+ "K",
+ "Beta",
+ "WC",
+ "WEst",
+ }
+}
+
+func (t *TCPCubicState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPCubicState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.WLastMax)
+ stateSinkObject.Save(1, &t.WMax)
+ stateSinkObject.Save(2, &t.T)
+ stateSinkObject.Save(3, &t.TimeSinceLastCongestion)
+ stateSinkObject.Save(4, &t.C)
+ stateSinkObject.Save(5, &t.K)
+ stateSinkObject.Save(6, &t.Beta)
+ stateSinkObject.Save(7, &t.WC)
+ stateSinkObject.Save(8, &t.WEst)
+}
+
+func (t *TCPCubicState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPCubicState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.WLastMax)
+ stateSourceObject.Load(1, &t.WMax)
+ stateSourceObject.Load(2, &t.T)
+ stateSourceObject.Load(3, &t.TimeSinceLastCongestion)
+ stateSourceObject.Load(4, &t.C)
+ stateSourceObject.Load(5, &t.K)
+ stateSourceObject.Load(6, &t.Beta)
+ stateSourceObject.Load(7, &t.WC)
+ stateSourceObject.Load(8, &t.WEst)
+}
+
+func (t *TCPRACKState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPRACKState"
+}
+
+func (t *TCPRACKState) StateFields() []string {
+ return []string{
+ "XmitTime",
+ "EndSequence",
+ "FACK",
+ "RTT",
+ "Reord",
+ "DSACKSeen",
+ "ReoWnd",
+ "ReoWndIncr",
+ "ReoWndPersist",
+ "RTTSeq",
+ }
+}
+
+func (t *TCPRACKState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPRACKState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.XmitTime)
+ stateSinkObject.Save(1, &t.EndSequence)
+ stateSinkObject.Save(2, &t.FACK)
+ stateSinkObject.Save(3, &t.RTT)
+ stateSinkObject.Save(4, &t.Reord)
+ stateSinkObject.Save(5, &t.DSACKSeen)
+ stateSinkObject.Save(6, &t.ReoWnd)
+ stateSinkObject.Save(7, &t.ReoWndIncr)
+ stateSinkObject.Save(8, &t.ReoWndPersist)
+ stateSinkObject.Save(9, &t.RTTSeq)
+}
+
+func (t *TCPRACKState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPRACKState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.XmitTime)
+ stateSourceObject.Load(1, &t.EndSequence)
+ stateSourceObject.Load(2, &t.FACK)
+ stateSourceObject.Load(3, &t.RTT)
+ stateSourceObject.Load(4, &t.Reord)
+ stateSourceObject.Load(5, &t.DSACKSeen)
+ stateSourceObject.Load(6, &t.ReoWnd)
+ stateSourceObject.Load(7, &t.ReoWndIncr)
+ stateSourceObject.Load(8, &t.ReoWndPersist)
+ stateSourceObject.Load(9, &t.RTTSeq)
+}
+
+func (t *TCPEndpointID) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPEndpointID"
+}
+
+func (t *TCPEndpointID) StateFields() []string {
+ return []string{
+ "LocalPort",
+ "LocalAddress",
+ "RemotePort",
+ "RemoteAddress",
+ }
+}
+
+func (t *TCPEndpointID) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPEndpointID) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.LocalPort)
+ stateSinkObject.Save(1, &t.LocalAddress)
+ stateSinkObject.Save(2, &t.RemotePort)
+ stateSinkObject.Save(3, &t.RemoteAddress)
+}
+
+func (t *TCPEndpointID) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPEndpointID) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.LocalPort)
+ stateSourceObject.Load(1, &t.LocalAddress)
+ stateSourceObject.Load(2, &t.RemotePort)
+ stateSourceObject.Load(3, &t.RemoteAddress)
+}
+
+func (t *TCPFastRecoveryState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPFastRecoveryState"
+}
+
+func (t *TCPFastRecoveryState) StateFields() []string {
+ return []string{
+ "Active",
+ "First",
+ "Last",
+ "MaxCwnd",
+ "HighRxt",
+ "RescueRxt",
+ }
+}
+
+func (t *TCPFastRecoveryState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPFastRecoveryState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.Active)
+ stateSinkObject.Save(1, &t.First)
+ stateSinkObject.Save(2, &t.Last)
+ stateSinkObject.Save(3, &t.MaxCwnd)
+ stateSinkObject.Save(4, &t.HighRxt)
+ stateSinkObject.Save(5, &t.RescueRxt)
+}
+
+func (t *TCPFastRecoveryState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPFastRecoveryState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.Active)
+ stateSourceObject.Load(1, &t.First)
+ stateSourceObject.Load(2, &t.Last)
+ stateSourceObject.Load(3, &t.MaxCwnd)
+ stateSourceObject.Load(4, &t.HighRxt)
+ stateSourceObject.Load(5, &t.RescueRxt)
+}
+
+func (t *TCPReceiverState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPReceiverState"
+}
+
+func (t *TCPReceiverState) StateFields() []string {
+ return []string{
+ "RcvNxt",
+ "RcvAcc",
+ "RcvWndScale",
+ "PendingBufUsed",
+ }
+}
+
+func (t *TCPReceiverState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPReceiverState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.RcvNxt)
+ stateSinkObject.Save(1, &t.RcvAcc)
+ stateSinkObject.Save(2, &t.RcvWndScale)
+ stateSinkObject.Save(3, &t.PendingBufUsed)
+}
+
+func (t *TCPReceiverState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPReceiverState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.RcvNxt)
+ stateSourceObject.Load(1, &t.RcvAcc)
+ stateSourceObject.Load(2, &t.RcvWndScale)
+ stateSourceObject.Load(3, &t.PendingBufUsed)
+}
+
+func (t *TCPRTTState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPRTTState"
+}
+
+func (t *TCPRTTState) StateFields() []string {
+ return []string{
+ "SRTT",
+ "RTTVar",
+ "SRTTInited",
+ }
+}
+
+func (t *TCPRTTState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPRTTState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.SRTT)
+ stateSinkObject.Save(1, &t.RTTVar)
+ stateSinkObject.Save(2, &t.SRTTInited)
+}
+
+func (t *TCPRTTState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPRTTState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.SRTT)
+ stateSourceObject.Load(1, &t.RTTVar)
+ stateSourceObject.Load(2, &t.SRTTInited)
+}
+
+func (t *TCPSenderState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPSenderState"
+}
+
+func (t *TCPSenderState) StateFields() []string {
+ return []string{
+ "LastSendTime",
+ "DupAckCount",
+ "SndCwnd",
+ "Ssthresh",
+ "SndCAAckCount",
+ "Outstanding",
+ "SackedOut",
+ "SndWnd",
+ "SndUna",
+ "SndNxt",
+ "RTTMeasureSeqNum",
+ "RTTMeasureTime",
+ "Closed",
+ "RTO",
+ "RTTState",
+ "MaxPayloadSize",
+ "SndWndScale",
+ "MaxSentAck",
+ "FastRecovery",
+ "Cubic",
+ "RACKState",
+ "RetransmitTS",
+ "SpuriousRecovery",
+ }
+}
+
+func (t *TCPSenderState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPSenderState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.LastSendTime)
+ stateSinkObject.Save(1, &t.DupAckCount)
+ stateSinkObject.Save(2, &t.SndCwnd)
+ stateSinkObject.Save(3, &t.Ssthresh)
+ stateSinkObject.Save(4, &t.SndCAAckCount)
+ stateSinkObject.Save(5, &t.Outstanding)
+ stateSinkObject.Save(6, &t.SackedOut)
+ stateSinkObject.Save(7, &t.SndWnd)
+ stateSinkObject.Save(8, &t.SndUna)
+ stateSinkObject.Save(9, &t.SndNxt)
+ stateSinkObject.Save(10, &t.RTTMeasureSeqNum)
+ stateSinkObject.Save(11, &t.RTTMeasureTime)
+ stateSinkObject.Save(12, &t.Closed)
+ stateSinkObject.Save(13, &t.RTO)
+ stateSinkObject.Save(14, &t.RTTState)
+ stateSinkObject.Save(15, &t.MaxPayloadSize)
+ stateSinkObject.Save(16, &t.SndWndScale)
+ stateSinkObject.Save(17, &t.MaxSentAck)
+ stateSinkObject.Save(18, &t.FastRecovery)
+ stateSinkObject.Save(19, &t.Cubic)
+ stateSinkObject.Save(20, &t.RACKState)
+ stateSinkObject.Save(21, &t.RetransmitTS)
+ stateSinkObject.Save(22, &t.SpuriousRecovery)
+}
+
+func (t *TCPSenderState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPSenderState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.LastSendTime)
+ stateSourceObject.Load(1, &t.DupAckCount)
+ stateSourceObject.Load(2, &t.SndCwnd)
+ stateSourceObject.Load(3, &t.Ssthresh)
+ stateSourceObject.Load(4, &t.SndCAAckCount)
+ stateSourceObject.Load(5, &t.Outstanding)
+ stateSourceObject.Load(6, &t.SackedOut)
+ stateSourceObject.Load(7, &t.SndWnd)
+ stateSourceObject.Load(8, &t.SndUna)
+ stateSourceObject.Load(9, &t.SndNxt)
+ stateSourceObject.Load(10, &t.RTTMeasureSeqNum)
+ stateSourceObject.Load(11, &t.RTTMeasureTime)
+ stateSourceObject.Load(12, &t.Closed)
+ stateSourceObject.Load(13, &t.RTO)
+ stateSourceObject.Load(14, &t.RTTState)
+ stateSourceObject.Load(15, &t.MaxPayloadSize)
+ stateSourceObject.Load(16, &t.SndWndScale)
+ stateSourceObject.Load(17, &t.MaxSentAck)
+ stateSourceObject.Load(18, &t.FastRecovery)
+ stateSourceObject.Load(19, &t.Cubic)
+ stateSourceObject.Load(20, &t.RACKState)
+ stateSourceObject.Load(21, &t.RetransmitTS)
+ stateSourceObject.Load(22, &t.SpuriousRecovery)
+}
+
+func (t *TCPSACKInfo) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPSACKInfo"
+}
+
+func (t *TCPSACKInfo) StateFields() []string {
+ return []string{
+ "Blocks",
+ "ReceivedBlocks",
+ "MaxSACKED",
+ }
+}
+
+func (t *TCPSACKInfo) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPSACKInfo) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.Blocks)
+ stateSinkObject.Save(1, &t.ReceivedBlocks)
+ stateSinkObject.Save(2, &t.MaxSACKED)
+}
+
+func (t *TCPSACKInfo) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPSACKInfo) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.Blocks)
+ stateSourceObject.Load(1, &t.ReceivedBlocks)
+ stateSourceObject.Load(2, &t.MaxSACKED)
+}
+
+func (r *RcvBufAutoTuneParams) StateTypeName() string {
+ return "pkg/tcpip/stack.RcvBufAutoTuneParams"
+}
+
+func (r *RcvBufAutoTuneParams) StateFields() []string {
+ return []string{
+ "MeasureTime",
+ "CopiedBytes",
+ "PrevCopiedBytes",
+ "RcvBufSize",
+ "RTT",
+ "RTTVar",
+ "RTTMeasureSeqNumber",
+ "RTTMeasureTime",
+ "Disabled",
+ }
+}
+
+func (r *RcvBufAutoTuneParams) beforeSave() {}
+
+// +checklocksignore
+func (r *RcvBufAutoTuneParams) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.MeasureTime)
+ stateSinkObject.Save(1, &r.CopiedBytes)
+ stateSinkObject.Save(2, &r.PrevCopiedBytes)
+ stateSinkObject.Save(3, &r.RcvBufSize)
+ stateSinkObject.Save(4, &r.RTT)
+ stateSinkObject.Save(5, &r.RTTVar)
+ stateSinkObject.Save(6, &r.RTTMeasureSeqNumber)
+ stateSinkObject.Save(7, &r.RTTMeasureTime)
+ stateSinkObject.Save(8, &r.Disabled)
+}
+
+func (r *RcvBufAutoTuneParams) afterLoad() {}
+
+// +checklocksignore
+func (r *RcvBufAutoTuneParams) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.MeasureTime)
+ stateSourceObject.Load(1, &r.CopiedBytes)
+ stateSourceObject.Load(2, &r.PrevCopiedBytes)
+ stateSourceObject.Load(3, &r.RcvBufSize)
+ stateSourceObject.Load(4, &r.RTT)
+ stateSourceObject.Load(5, &r.RTTVar)
+ stateSourceObject.Load(6, &r.RTTMeasureSeqNumber)
+ stateSourceObject.Load(7, &r.RTTMeasureTime)
+ stateSourceObject.Load(8, &r.Disabled)
+}
+
+func (t *TCPRcvBufState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPRcvBufState"
+}
+
+func (t *TCPRcvBufState) StateFields() []string {
+ return []string{
+ "RcvBufUsed",
+ "RcvAutoParams",
+ "RcvClosed",
+ }
+}
+
+func (t *TCPRcvBufState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPRcvBufState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.RcvBufUsed)
+ stateSinkObject.Save(1, &t.RcvAutoParams)
+ stateSinkObject.Save(2, &t.RcvClosed)
+}
+
+func (t *TCPRcvBufState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPRcvBufState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.RcvBufUsed)
+ stateSourceObject.Load(1, &t.RcvAutoParams)
+ stateSourceObject.Load(2, &t.RcvClosed)
+}
+
+func (t *TCPSndBufState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPSndBufState"
+}
+
+func (t *TCPSndBufState) StateFields() []string {
+ return []string{
+ "SndBufSize",
+ "SndBufUsed",
+ "SndClosed",
+ "PacketTooBigCount",
+ "SndMTU",
+ "AutoTuneSndBufDisabled",
+ }
+}
+
+func (t *TCPSndBufState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPSndBufState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.SndBufSize)
+ stateSinkObject.Save(1, &t.SndBufUsed)
+ stateSinkObject.Save(2, &t.SndClosed)
+ stateSinkObject.Save(3, &t.PacketTooBigCount)
+ stateSinkObject.Save(4, &t.SndMTU)
+ stateSinkObject.Save(5, &t.AutoTuneSndBufDisabled)
+}
+
+func (t *TCPSndBufState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPSndBufState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.SndBufSize)
+ stateSourceObject.Load(1, &t.SndBufUsed)
+ stateSourceObject.Load(2, &t.SndClosed)
+ stateSourceObject.Load(3, &t.PacketTooBigCount)
+ stateSourceObject.Load(4, &t.SndMTU)
+ stateSourceObject.Load(5, &t.AutoTuneSndBufDisabled)
+}
+
+func (t *TCPEndpointStateInner) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPEndpointStateInner"
+}
+
+func (t *TCPEndpointStateInner) StateFields() []string {
+ return []string{
+ "TSOffset",
+ "SACKPermitted",
+ "SendTSOk",
+ "RecentTS",
+ }
+}
+
+func (t *TCPEndpointStateInner) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPEndpointStateInner) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.TSOffset)
+ stateSinkObject.Save(1, &t.SACKPermitted)
+ stateSinkObject.Save(2, &t.SendTSOk)
+ stateSinkObject.Save(3, &t.RecentTS)
+}
+
+func (t *TCPEndpointStateInner) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPEndpointStateInner) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.TSOffset)
+ stateSourceObject.Load(1, &t.SACKPermitted)
+ stateSourceObject.Load(2, &t.SendTSOk)
+ stateSourceObject.Load(3, &t.RecentTS)
+}
+
+func (t *TCPEndpointState) StateTypeName() string {
+ return "pkg/tcpip/stack.TCPEndpointState"
+}
+
+func (t *TCPEndpointState) StateFields() []string {
+ return []string{
+ "TCPEndpointStateInner",
+ "ID",
+ "SegTime",
+ "RcvBufState",
+ "SndBufState",
+ "SACK",
+ "Receiver",
+ "Sender",
+ }
+}
+
+func (t *TCPEndpointState) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPEndpointState) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.TCPEndpointStateInner)
+ stateSinkObject.Save(1, &t.ID)
+ stateSinkObject.Save(2, &t.SegTime)
+ stateSinkObject.Save(3, &t.RcvBufState)
+ stateSinkObject.Save(4, &t.SndBufState)
+ stateSinkObject.Save(5, &t.SACK)
+ stateSinkObject.Save(6, &t.Receiver)
+ stateSinkObject.Save(7, &t.Sender)
+}
+
+func (t *TCPEndpointState) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPEndpointState) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.TCPEndpointStateInner)
+ stateSourceObject.Load(1, &t.ID)
+ stateSourceObject.Load(2, &t.SegTime)
+ stateSourceObject.Load(3, &t.RcvBufState)
+ stateSourceObject.Load(4, &t.SndBufState)
+ stateSourceObject.Load(5, &t.SACK)
+ stateSourceObject.Load(6, &t.Receiver)
+ stateSourceObject.Load(7, &t.Sender)
+}
+
+func (ep *multiPortEndpoint) StateTypeName() string {
+ return "pkg/tcpip/stack.multiPortEndpoint"
+}
+
+func (ep *multiPortEndpoint) StateFields() []string {
+ return []string{
+ "demux",
+ "netProto",
+ "transProto",
+ "flags",
+ "endpoints",
+ }
+}
+
+func (ep *multiPortEndpoint) beforeSave() {}
+
+// +checklocksignore
+func (ep *multiPortEndpoint) StateSave(stateSinkObject state.Sink) {
+ ep.beforeSave()
+ stateSinkObject.Save(0, &ep.demux)
+ stateSinkObject.Save(1, &ep.netProto)
+ stateSinkObject.Save(2, &ep.transProto)
+ stateSinkObject.Save(3, &ep.flags)
+ stateSinkObject.Save(4, &ep.endpoints)
+}
+
+func (ep *multiPortEndpoint) afterLoad() {}
+
+// +checklocksignore
+func (ep *multiPortEndpoint) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &ep.demux)
+ stateSourceObject.Load(1, &ep.netProto)
+ stateSourceObject.Load(2, &ep.transProto)
+ stateSourceObject.Load(3, &ep.flags)
+ stateSourceObject.Load(4, &ep.endpoints)
+}
+
+func (l *tupleList) StateTypeName() string {
+ return "pkg/tcpip/stack.tupleList"
+}
+
+func (l *tupleList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *tupleList) beforeSave() {}
+
+// +checklocksignore
+func (l *tupleList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *tupleList) afterLoad() {}
+
+// +checklocksignore
+func (l *tupleList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *tupleEntry) StateTypeName() string {
+ return "pkg/tcpip/stack.tupleEntry"
+}
+
+func (e *tupleEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *tupleEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *tupleEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *tupleEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *tupleEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+ state.Register((*tuple)(nil))
+ state.Register((*tupleID)(nil))
+ state.Register((*conn)(nil))
+ state.Register((*ConnTrack)(nil))
+ state.Register((*bucket)(nil))
+ state.Register((*IPTables)(nil))
+ state.Register((*Table)(nil))
+ state.Register((*Rule)(nil))
+ state.Register((*IPHeaderFilter)(nil))
+ state.Register((*neighborEntryList)(nil))
+ state.Register((*neighborEntryEntry)(nil))
+ state.Register((*PacketBufferList)(nil))
+ state.Register((*PacketBufferEntry)(nil))
+ state.Register((*packetBufferRefs)(nil))
+ state.Register((*TransportEndpointID)(nil))
+ state.Register((*GSOType)(nil))
+ state.Register((*GSO)(nil))
+ state.Register((*TransportEndpointInfo)(nil))
+ state.Register((*TCPCubicState)(nil))
+ state.Register((*TCPRACKState)(nil))
+ state.Register((*TCPEndpointID)(nil))
+ state.Register((*TCPFastRecoveryState)(nil))
+ state.Register((*TCPReceiverState)(nil))
+ state.Register((*TCPRTTState)(nil))
+ state.Register((*TCPSenderState)(nil))
+ state.Register((*TCPSACKInfo)(nil))
+ state.Register((*RcvBufAutoTuneParams)(nil))
+ state.Register((*TCPRcvBufState)(nil))
+ state.Register((*TCPSndBufState)(nil))
+ state.Register((*TCPEndpointStateInner)(nil))
+ state.Register((*TCPEndpointState)(nil))
+ state.Register((*multiPortEndpoint)(nil))
+ state.Register((*tupleList)(nil))
+ state.Register((*tupleEntry)(nil))
+}
diff --git a/pkg/tcpip/stack/stack_test.go b/pkg/tcpip/stack/stack_test.go
deleted file mode 100644
index f5a35eac4..000000000
--- a/pkg/tcpip/stack/stack_test.go
+++ /dev/null
@@ -1,4680 +0,0 @@
-// 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"
- "gvisor.dev/gvisor/pkg/rand"
- "gvisor.dev/gvisor/pkg/sync"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "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 = 8
-
- // 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 = 1
- protocolNumberOffset = 2
-)
-
-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, but we currently only
-// use the first three: destination address, source address, and transport
-// protocol. They're all one byte fields to simplify parsing.
-type fakeNetworkEndpoint struct {
- stack.AddressableEndpointState
-
- mu struct {
- sync.RWMutex
-
- enabled bool
- forwarding 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().View()
-
- dst := tcpip.Address(netHdr[dstAddrOffset:][:1])
- addressEndpoint := f.AcquireAssignedAddress(dst, f.nic.Promiscuous(), stack.CanBePrimaryEndpoint)
- if addressEndpoint == nil {
- return
- }
- addressEndpoint.DecRef()
-
- f.proto.packetCount[int(dst[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.Address(hdr[srcAddrOffset:srcAddrOffset+1]),
- tcpip.Address(hdr[dstAddrOffset:dstAddrOffset+1]),
- 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) 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.
- f.proto.sendPacketCount[int(r.RemoteAddress()[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
- hdr[dstAddrOffset] = r.RemoteAddress()[0]
- hdr[srcAddrOffset] = r.LocalAddress()[0]
- 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, fakeNetNumber, 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() {}
-
-// 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
-}
-
-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 buffer.View) (src, dst tcpip.Address) {
- return tcpip.Address(v[srcAddrOffset : srcAddrOffset+1]), tcpip.Address(v[dstAddrOffset : dstAddrOffset+1])
-}
-
-func (f *fakeNetworkProtocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.TransportDispatcher) stack.NetworkEndpoint {
- e := &fakeNetworkEndpoint{
- nic: nic,
- proto: f,
- dispatcher: dispatcher,
- }
- e.AddressableEndpointState.Init(e)
- 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
-}
-
-// 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) {
- f.mu.Lock()
- defer f.mu.Unlock()
- f.mu.forwarding = v
-}
-
-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
-}
-
-// 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
-}
-
-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: "\x01",
- 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: "\x02",
- 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 := buffer.NewView(30)
-
- // Make sure packet with wrong address is not delivered.
- buf[dstAddrOffset] = 3
- ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- 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{
- Data: buf.ToVectorisedView(),
- }))
- 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{
- Data: buf.ToVectorisedView(),
- }))
- 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{
- Data: buf.ToVectorisedView(),
- }))
- 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.CapLength(2)
- ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- 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 buffer.View) tcpip.Error {
- r, err := s.FindRoute(0, "", addr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- return err
- }
- defer r.Release()
- return send(r, payload)
-}
-
-func send(r *stack.Route, payload buffer.View) tcpip.Error {
- return r.WritePacket(stack.NetworkHeaderParams{Protocol: fakeTransNumber, TTL: 123, TOS: stack.DefaultTOS}, stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: int(r.MaxHeaderLength()),
- Data: payload.ToVectorisedView(),
- }))
-}
-
-func testSendTo(t *testing.T, s *stack.Stack, addr tcpip.Address, ep *channel.Endpoint, payload buffer.View) {
- t.Helper()
- ep.Drain()
- 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 buffer.View) {
- 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 buffer.View, 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 buffer.View, 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 buffer.View) {
- 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 buffer.View) {
- 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 buffer.View, want int) {
- t.Helper()
- ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- 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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: "\x01",
- 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", 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: "\x01",
- 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: "\x03",
- 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: "\x02",
- 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: "\x04",
- 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("\x00", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- subnet1, err := tcpip.NewSubnet("\x01", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{
- {Destination: subnet1, Gateway: "\x00", NIC: 1},
- {Destination: subnet0, Gateway: "\x00", NIC: 2},
- })
- }
-
- // Send a packet to an odd destination.
- testSendTo(t, s, "\x05", ep1, nil)
-
- // Send a packet to an even destination.
- testSendTo(t, s, "\x06", ep2, nil)
-}
-
-func testRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr, expectedSrcAddr tcpip.Address) {
- 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.Fatalf("got Route.LocalAddress() = %s, want = %s", expectedSrcAddr, r.LocalAddress())
- }
-
- if r.RemoteAddress() != dstAddr {
- t.Fatalf("got Route.RemoteAddress() = %s, want = %s", dstAddr, r.RemoteAddress())
- }
-}
-
-func testNoRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr tcpip.Address) {
- _, err := s.FindRoute(nic, srcAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
- if _, ok := err.(*tcpip.ErrNoRoute); !ok {
- t.Fatalf("FindRoute returned unexpected error, got = %v, want = %s", err, &tcpip.ErrNoRoute{})
- }
-}
-
-// 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
- expectErr tcpip.Error
- }{
- {
- name: "loopback",
- linkep: loopback.New(),
- expectErr: &tcpip.ErrNotSupported{},
- },
- {
- name: "channel",
- linkep: channel.New(0, defaultMTU, ""),
- expectErr: nil,
- },
- } {
- 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 got, want := s.RemoveNIC(nicID), tt.expectErr; got != want {
- t.Fatalf("got s.RemoveNIC(%d) = %s, want %s", nicID, got, want)
- }
- if tt.expectErr == nil {
- 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
- const addr1 = tcpip.Address("\x01")
- const addr2 = tcpip.Address("\x02")
- const nic1Dst = tcpip.Address("\x05")
- const nic2Dst = tcpip.Address("\x06")
-
- setup := func(t *testing.T) (*stack.Stack, *channel.Endpoint, *channel.Endpoint) {
- 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)
- }
-
- 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("\x00", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- subnet1, err := tcpip.NewSubnet("\x01", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{
- {Destination: subnet1, Gateway: "\x00", NIC: nicID1},
- {Destination: subnet0, Gateway: "\x00", NIC: nicID2},
- })
- }
-
- 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, "", "\x05", addr1)
- testRoute(t, s, unspecifiedNIC, addr1, "\x05", addr1)
- testRoute(t, s, nicID1, addr1, "\x05", addr1)
-
- // Test routes to even address.
- testRoute(t, s, unspecifiedNIC, "", "\x06", addr2)
- testRoute(t, s, unspecifiedNIC, addr2, "\x06", addr2)
- testRoute(t, s, nicID2, addr2, "\x06", addr2)
-
- // 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, "", nic1Dst)
- testNoRoute(t, s, unspecifiedNIC, addr1, nic1Dst)
- testNoRoute(t, s, nicID1, addr1, nic1Dst)
- testRoute(t, s, unspecifiedNIC, "", nic2Dst, addr2)
- testRoute(t, s, unspecifiedNIC, addr2, nic2Dst, addr2)
- testRoute(t, s, nicID2, addr2, nic2Dst, addr2)
-
- // 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, "", nic1Dst)
- testNoRoute(t, s, unspecifiedNIC, addr1, nic1Dst)
- testNoRoute(t, s, nicID1, addr1, nic1Dst)
- testNoRoute(t, s, unspecifiedNIC, "", 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, "", nic1Dst, addr1)
- testRoute(t, s, unspecifiedNIC, addr1, nic1Dst, addr1)
- testRoute(t, s, nicID1, addr1, nic1Dst, addr1)
- testNoRoute(t, s, unspecifiedNIC, "", 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 := buffer.View([]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: "\x01",
- 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: "\x03",
- 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: "\x02",
- 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: "\x04",
- 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("\x00", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- subnet1, err := tcpip.NewSubnet("\x01", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{
- {Destination: subnet1, Gateway: "\x00", NIC: 1},
- {Destination: subnet0, Gateway: "\x00", NIC: 2},
- })
- }
-
- // Test routes to odd address.
- testRoute(t, s, 0, "", "\x05", "\x01")
- testRoute(t, s, 0, "\x01", "\x05", "\x01")
- testRoute(t, s, 1, "\x01", "\x05", "\x01")
- testRoute(t, s, 0, "\x03", "\x05", "\x03")
- testRoute(t, s, 1, "\x03", "\x05", "\x03")
-
- // Test routes to even address.
- testRoute(t, s, 0, "", "\x06", "\x02")
- testRoute(t, s, 0, "\x02", "\x06", "\x02")
- testRoute(t, s, 2, "\x02", "\x06", "\x02")
- testRoute(t, s, 0, "\x04", "\x06", "\x04")
- testRoute(t, s, 2, "\x04", "\x06", "\x04")
-
- // Try to send to odd numbered address from even numbered ones, then
- // vice-versa.
- testNoRoute(t, s, 0, "\x02", "\x05")
- testNoRoute(t, s, 2, "\x02", "\x05")
- testNoRoute(t, s, 0, "\x04", "\x05")
- testNoRoute(t, s, 2, "\x04", "\x05")
-
- testNoRoute(t, s, 0, "\x01", "\x06")
- testNoRoute(t, s, 1, "\x01", "\x06")
- testNoRoute(t, s, 0, "\x03", "\x06")
- testNoRoute(t, s, 1, "\x03", "\x06")
-}
-
-func TestAddressRemoval(t *testing.T) {
- const localAddrByte byte = 0x01
- localAddr := tcpip.Address([]byte{localAddrByte})
- remoteAddr := tcpip.Address("\x02")
-
- 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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- buf := buffer.NewView(30)
-
- // Send and receive packets, and verify they are received.
- buf[dstAddrOffset] = localAddrByte
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSendTo(t, s, remoteAddr, 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.ErrNoRoute{})
-
- // 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.Address([]byte{localAddrByte})
- remoteAddr := tcpip.Address("\x02")
-
- 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 := buffer.NewView(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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- r, err := s.FindRoute(0, "", 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, remoteAddr, 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.ErrNoRoute{})
-
- // 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 len(addr) == 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
- remoteAddr tcpip.Address = "\x03"
- noAddr tcpip.Address = ""
- nicID tcpip.NICID = 1
- )
- localAddr := tcpip.Address([]byte{localAddrByte})
-
- 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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
- buf := buffer.NewView(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.ErrNoRoute{})
- }
-
- // 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, remoteAddr, 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.ErrNoRoute{})
- }
-
- // 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, remoteAddr, 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, "", remoteAddr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatal("FindRoute failed:", err)
- }
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSendTo(t, s, remoteAddr, 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, remoteAddr, ep, nil)
- } else {
- testFailingSend(t, r, nil, &tcpip.ErrInvalidEndpointState{})
- testFailingSendTo(t, s, remoteAddr, nil, &tcpip.ErrNoRoute{})
- }
-
- // 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, remoteAddr, 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, remoteAddr, 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.ErrNoRoute{})
- }
- })
- }
- }
-}
-
-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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- buf := buffer.NewView(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, "", "\x02", fakeNetNumber, false /* multicastLoop */)
- if _, ok := err.(*tcpip.ErrNoRoute); !ok {
- t.Fatalf("FindRoute returned unexpected error: got = %v, want = %s", err, &tcpip.ErrNoRoute{})
- }
-
- // 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
-
- localAddr = tcpip.Address("\x01")
- dstAddr = tcpip.Address("\x03")
- )
-
- subnet, err := tcpip.NewSubnet("\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()),
- Data: buffer.NewView(10).ToVectorisedView(),
- })); 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.Address("\x01")
- nonExistentLocalAddr := tcpip.Address("\x02")
- dstAddr := tcpip.Address("\x03")
-
- 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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\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, dstAddr, 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.Address("\x01")
- dstAddr := tcpip.Address("\x02")
-
- 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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\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.ErrNoRoute{})
-
- // 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: "\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: "\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, "", 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, "", 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 tcpip.Address
- }{
- // 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 tc.routeNeeded {
- want = &tcpip.ErrNoRoute{}
- }
-
- // If there is no endpoint, it won't work.
- if _, err := s.FindRoute(1, anyAddr, tc.address, fakeNetNumber, false /* multicastLoop */); err != want {
- t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %v", anyAddr, tc.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, tc.address, fakeNetNumber, false /* multicastLoop */); tc.routeNeeded {
- // Route table is empty but we need a route, this should cause an error.
- if _, ok := err.(*tcpip.ErrNoRoute); !ok {
- t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %v", anyAddr, tc.address, fakeNetNumber, err, &tcpip.ErrNoRoute{})
- }
- } else {
- if err != nil {
- t.Fatalf("FindRoute(1, %v, %v, %v) failed: %v", anyAddr, tc.address, fakeNetNumber, err)
- }
- if r.LocalAddress() != anyAddr {
- t.Errorf("Bad local address: got %v, want = %v", r.LocalAddress(), anyAddr)
- }
- if r.RemoteAddress() != tc.address {
- t.Errorf("Bad remote address: got %v, want = %v", r.RemoteAddress(), tc.address)
- }
- }
- // If the NIC doesn't exist, it won't work.
- if _, err := s.FindRoute(2, anyAddr, tc.address, fakeNetNumber, false /* multicastLoop */); err != want {
- t.Fatalf("got FindRoute(2, %v, %v, %v) = %v want = %v", anyAddr, tc.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.Address(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", "\x01\x01\x01\x01", 24},
- {"IPv6", "\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.Address(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 gotAddresses[i].AddressWithPrefix.Address < gotAddresses[j].AddressWithPrefix.Address
- })
- sort.Slice(expectedAddresses, func(i, j int) bool {
- return expectedAddresses[i].AddressWithPrefix.Address < expectedAddresses[j].AddressWithPrefix.Address
- })
-
- 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, stack.AddressConfigSlaacTemp}
- 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 _, deprecated := range deprecatedRange {
- address := addrGen.next(addrLen)
- properties := stack.AddressProperties{
- PEB: behavior,
- ConfigType: configType,
- Deprecated: deprecated,
- }
- 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: "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: "\x01",
- txByteCount: 30,
- rxByteCount: 10,
- },
- {
- addr: "\x02",
- txByteCount: 50,
- rxByteCount: 20,
- },
- }
-
- var txBytesTotal, rxBytesTotal, txPacketsTotal, rxPacketsTotal int
- for i, nic := range nics {
- nicid := tcpip.NICID(i)
- 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, "\xff")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: nicid}})
- }
-
- nicStats := s.NICInfo()[nicid].Stats
-
- // Inbound packet.
- rxBuffer := buffer.NewView(nic.rxByteCount)
- ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: rxBuffer.ToVectorisedView(),
- }))
- 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 := buffer.NewView(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)
- }
- })
- }
-}
-
-// 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) {
- ndpDisp := ndpDispatcher{
- autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
- }
- 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).
- select {
- case e := <-ndpDisp.autoGenAddrC:
- if diff := checkAutoGenAddrEvent(e, expectedMainAddr, newAddr); diff != "" {
- t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
- }
- default:
- t.Fatal("expected addr auto gen event")
- }
- } 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.
- const address1 = tcpip.Address("\x01")
- 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("\x00", "\x00")
- if err != nil {
- t.Fatalf("NewSubnet failed: %v", err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\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.
- const address2 = tcpip.Address("\x02")
- 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.
- const address3 = tcpip.Address("\x03")
- 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{
- "\x01",
- "\x03",
- }
- case stack.CanBePrimaryEndpoint:
- expectedList = []tcpip.Address{
- "\x03",
- "\x01",
- }
- case stack.NeverPrimaryEndpoint:
- expectedList = []tcpip.Address{
- "\x03",
- }
- }
- 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
-
- // Rule 3 is not tested here, and is instead tested by NDP's AutoGenAddr test.
- tests := []struct {
- name string
- slaacPrefixForTempAddrBeforeNICAddrAdd tcpip.AddressWithPrefix
- nicAddrs []tcpip.Address
- 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: []tcpip.Address{linkLocalAddr1, globalAddr1},
- remoteAddr: globalAddr1,
- expectedLocalAddr: globalAddr1,
- },
- {
- name: "Same Global most preferred (first address)",
- nicAddrs: []tcpip.Address{globalAddr1, uniqueLocalAddr1},
- remoteAddr: globalAddr1,
- expectedLocalAddr: globalAddr1,
- },
- {
- name: "Same Link Local most preferred (last address)",
- nicAddrs: []tcpip.Address{globalAddr1, linkLocalAddr1},
- remoteAddr: linkLocalAddr1,
- expectedLocalAddr: linkLocalAddr1,
- },
- {
- name: "Same Link Local most preferred (first address)",
- nicAddrs: []tcpip.Address{linkLocalAddr1, globalAddr1},
- remoteAddr: linkLocalAddr1,
- expectedLocalAddr: linkLocalAddr1,
- },
- {
- name: "Same Unique Local most preferred (last address)",
- nicAddrs: []tcpip.Address{uniqueLocalAddr1, globalAddr1},
- remoteAddr: uniqueLocalAddr1,
- expectedLocalAddr: uniqueLocalAddr1,
- },
- {
- name: "Same Unique Local most preferred (first address)",
- nicAddrs: []tcpip.Address{globalAddr1, uniqueLocalAddr1},
- remoteAddr: uniqueLocalAddr1,
- expectedLocalAddr: uniqueLocalAddr1,
- },
-
- // Test Rule 2 of RFC 6724 section 5 (prefer appropriate scope).
- {
- name: "Global most preferred (last address)",
- nicAddrs: []tcpip.Address{linkLocalAddr1, globalAddr1},
- remoteAddr: globalAddr2,
- expectedLocalAddr: globalAddr1,
- },
- {
- name: "Global most preferred (first address)",
- nicAddrs: []tcpip.Address{globalAddr1, linkLocalAddr1},
- remoteAddr: globalAddr2,
- expectedLocalAddr: globalAddr1,
- },
- {
- name: "Link Local most preferred (last address)",
- nicAddrs: []tcpip.Address{globalAddr1, linkLocalAddr1},
- remoteAddr: linkLocalAddr2,
- expectedLocalAddr: linkLocalAddr1,
- },
- {
- name: "Link Local most preferred (first address)",
- nicAddrs: []tcpip.Address{linkLocalAddr1, globalAddr1},
- remoteAddr: linkLocalAddr2,
- expectedLocalAddr: linkLocalAddr1,
- },
- {
- name: "Link Local most preferred for link local multicast (last address)",
- nicAddrs: []tcpip.Address{globalAddr1, linkLocalAddr1},
- remoteAddr: linkLocalMulticastAddr,
- expectedLocalAddr: linkLocalAddr1,
- },
- {
- name: "Link Local most preferred for link local multicast (first address)",
- nicAddrs: []tcpip.Address{linkLocalAddr1, globalAddr1},
- remoteAddr: linkLocalMulticastAddr,
- expectedLocalAddr: linkLocalAddr1,
- },
-
- // Test Rule 6 of 6724 section 5 (prefer matching label).
- {
- name: "Unique Local most preferred (last address)",
- nicAddrs: []tcpip.Address{uniqueLocalAddr1, globalAddr1, ipv4MappedIPv6Addr1, toredoAddr1, ipv6ToIPv4Addr1},
- remoteAddr: uniqueLocalAddr2,
- expectedLocalAddr: uniqueLocalAddr1,
- },
- {
- name: "Unique Local most preferred (first address)",
- nicAddrs: []tcpip.Address{globalAddr1, ipv4MappedIPv6Addr1, toredoAddr1, ipv6ToIPv4Addr1, uniqueLocalAddr1},
- remoteAddr: uniqueLocalAddr2,
- expectedLocalAddr: uniqueLocalAddr1,
- },
- {
- name: "Toredo most preferred (first address)",
- nicAddrs: []tcpip.Address{toredoAddr1, uniqueLocalAddr1, globalAddr1, ipv4MappedIPv6Addr1, ipv6ToIPv4Addr1},
- remoteAddr: toredoAddr2,
- expectedLocalAddr: toredoAddr1,
- },
- {
- name: "Toredo most preferred (last address)",
- nicAddrs: []tcpip.Address{globalAddr1, ipv4MappedIPv6Addr1, ipv6ToIPv4Addr1, uniqueLocalAddr1, toredoAddr1},
- remoteAddr: toredoAddr2,
- expectedLocalAddr: toredoAddr1,
- },
- {
- name: "6To4 most preferred (first address)",
- nicAddrs: []tcpip.Address{ipv6ToIPv4Addr1, toredoAddr1, uniqueLocalAddr1, globalAddr1, ipv4MappedIPv6Addr1},
- remoteAddr: ipv6ToIPv4Addr2,
- expectedLocalAddr: ipv6ToIPv4Addr1,
- },
- {
- name: "6To4 most preferred (last address)",
- nicAddrs: []tcpip.Address{globalAddr1, ipv4MappedIPv6Addr1, uniqueLocalAddr1, toredoAddr1, ipv6ToIPv4Addr1},
- remoteAddr: ipv6ToIPv4Addr2,
- expectedLocalAddr: ipv6ToIPv4Addr1,
- },
- {
- name: "IPv4 mapped IPv6 most preferred (first address)",
- nicAddrs: []tcpip.Address{ipv4MappedIPv6Addr1, ipv6ToIPv4Addr1, toredoAddr1, uniqueLocalAddr1, globalAddr1},
- remoteAddr: ipv4MappedIPv6Addr2,
- expectedLocalAddr: ipv4MappedIPv6Addr1,
- },
- {
- name: "IPv4 mapped IPv6 most preferred (last address)",
- nicAddrs: []tcpip.Address{globalAddr1, ipv6ToIPv4Addr1, uniqueLocalAddr1, toredoAddr1, ipv4MappedIPv6Addr1},
- remoteAddr: ipv4MappedIPv6Addr2,
- expectedLocalAddr: ipv4MappedIPv6Addr1,
- },
-
- // Test Rule 7 of RFC 6724 section 5 (prefer temporary addresses).
- {
- name: "Temp Global most preferred (last address)",
- slaacPrefixForTempAddrBeforeNICAddrAdd: prefix1,
- nicAddrs: []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, globalAddr1},
- remoteAddr: globalAddr2,
- expectedLocalAddr: tempGlobalAddr1,
- },
- {
- name: "Temp Global most preferred (first address)",
- nicAddrs: []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, globalAddr1},
- slaacPrefixForTempAddrAfterNICAddrAdd: prefix1,
- remoteAddr: globalAddr2,
- expectedLocalAddr: tempGlobalAddr1,
- },
-
- // Test Rule 8 of RFC 6724 section 5 (use longest matching prefix).
- {
- name: "Longest prefix matched most preferred (first address)",
- nicAddrs: []tcpip.Address{globalAddr2, globalAddr1},
- remoteAddr: globalAddr3,
- expectedLocalAddr: globalAddr2,
- },
- {
- name: "Longest prefix matched most preferred (last address)",
- nicAddrs: []tcpip.Address{globalAddr1, 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: []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, uniqueLocalAddr2},
- remoteAddr: globalAddr2,
- expectedLocalAddr: uniqueLocalAddr1,
- },
- {
- name: "Link Local for Global",
- nicAddrs: []tcpip.Address{linkLocalAddr1, linkLocalAddr2},
- remoteAddr: globalAddr2,
- expectedLocalAddr: linkLocalAddr1,
- },
- {
- name: "Link Local for Unique Local",
- nicAddrs: []tcpip.Address{linkLocalAddr1, 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.WithPrefix(),
- }
- if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, 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, 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: "\xc0\xa8\x01\x3a",
- PrefixLen: 24,
- }
- ipv4Subnet := ipv4Addr.Subnet()
- ipv4SubnetBcast := ipv4Subnet.Broadcast()
- ipv4Gateway := testutil.MustParse4("192.168.1.1")
- ipv4AddrPrefix31 := tcpip.AddressWithPrefix{
- Address: "\xc0\xa8\x01\x3a",
- PrefixLen: 31,
- }
- ipv4Subnet31 := ipv4AddrPrefix31.Subnet()
- ipv4Subnet31Bcast := ipv4Subnet31.Broadcast()
- ipv4AddrPrefix32 := tcpip.AddressWithPrefix{
- Address: "\xc0\xa8\x01\x3a",
- PrefixLen: 32,
- }
- ipv4Subnet32 := ipv4AddrPrefix32.Subnet()
- ipv4Subnet32Bcast := ipv4Subnet32.Broadcast()
- ipv6Addr := tcpip.AddressWithPrefix{
- Address: "\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: "\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 := len(test.remoteAddr); 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, "" /* 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.Address([]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.Address([]byte{192, 168, 1, 59})
- r, err := s.FindRoute(unspecifiedNICID, "" /* 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
- localAddr = tcpip.Address("\x01")
- remoteAddr = tcpip.Address("\x02")
- )
-
- 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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\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: "\x01",
- PrefixLen: 8,
- },
- }
- 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 diabled.
- 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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
-
- subnet2, err := tcpip.NewSubnet("\x01", "\x01")
- if err != nil {
- t.Fatal(err)
- }
-
- expected := []tcpip.Route{
- {Destination: subnet1, Gateway: "\x00", NIC: 1},
- {Destination: subnet2, Gateway: "\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 != 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.Address("\x01")
- subnet1, err := tcpip.NewSubnet(addressToRemove, "\x01")
- if err != nil {
- t.Fatal(err)
- }
-
- subnet2, err := tcpip.NewSubnet(addressToRemove, "\x01")
- if err != nil {
- t.Fatal(err)
- }
-
- subnet3, err := tcpip.NewSubnet("\x02", "\x02")
- if err != nil {
- t.Fatal(err)
- }
-
- // Initialize the route table with three routes.
- s.SetRouteTable([]tcpip.Route{
- {Destination: subnet1, Gateway: "\x00", NIC: 1},
- {Destination: subnet2, Gateway: "\x00", NIC: 1},
- {Destination: subnet3, Gateway: "\x00", NIC: 1},
- })
-
- // Remove routes with the specific address.
- s.RemoveRoutes(func(r tcpip.Route) bool {
- return r.Destination.ID() == addressToRemove
- })
-
- expected := []tcpip.Route{{Destination: subnet3, Gateway: "\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)
- }
- }
-}
-
-func TestFindRouteWithForwarding(t *testing.T) {
- const (
- nicID1 = 1
- nicID2 = 2
-
- nic1Addr = tcpip.Address("\x01")
- nic2Addr = tcpip.Address("\x02")
- remoteAddr = tcpip.Address("\x03")
- )
-
- 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.Address(net.ParseIP("a::1").To16())
- globalIPv6Addr2 := tcpip.Address(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: "\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.ErrNoRoute{},
- 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.ErrNoRoute{},
- 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.ErrNoRoute{},
- 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.ErrNoRoute{},
- 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.ErrNoRoute{},
- 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.ErrNoRoute{},
- 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.ErrNoRoute{},
- 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.ErrNoRoute{},
- dependentOnForwarding: false,
- },
- {
- name: "forwarding disabled and link-local local addr with route on different NIC",
- netCfg: ipv6LinkLocalNIC1WithGlobalRemote,
- forwardingEnabled: false,
- localAddrWithPrefix: ipv6LinkLocalNIC1WithGlobalRemote.nic1AddrWithPrefix,
- findRouteErr: &tcpip.ErrNoRoute{},
- dependentOnForwarding: false,
- },
- {
- name: "forwarding enabled and link-local local addr with route on same NIC",
- netCfg: ipv6LinkLocalNIC1WithGlobalRemote,
- forwardingEnabled: true,
- localAddrWithPrefix: ipv6LinkLocalNIC1WithGlobalRemote.nic1AddrWithPrefix,
- findRouteErr: &tcpip.ErrNoRoute{},
- 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 := buffer.View([]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, ok := ep2.Read()
- if !ok {
- t.Fatal("packet not sent through ep2")
- }
- if pkt.Route.LocalAddress != test.localAddrWithPrefix.Address {
- t.Errorf("got pkt.Route.LocalAddress = %s, want = %s", pkt.Route.LocalAddress, test.localAddrWithPrefix.Address)
- }
- if pkt.Route.RemoteAddress != test.netCfg.remoteAddr {
- t.Errorf("got pkt.Route.RemoteAddress = %s, want = %s", pkt.Route.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 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.View(test.payload).ToVectorisedView()); err != nil {
- t.Fatalf("s.WritePacketToRemote(_, _, _, _) = %s", err)
- }
-
- pkt, ok := e.Read()
- if got, want := ok, true; got != want {
- t.Fatalf("e.Read() = %t, want %t", got, want)
- }
- if got, want := pkt.Proto, test.protocol; got != want {
- t.Fatalf("pkt.Proto = %d, want %d", got, want)
- }
- if pkt.Route.RemoteLinkAddress != linkAddr2 {
- t.Fatalf("pkt.Route.RemoteAddress = %s, want %s", pkt.Route.RemoteLinkAddress, linkAddr2)
- }
- if diff := cmp.Diff(pkt.Pkt.Data().AsRange().ToOwnedView(), buffer.View(test.payload)); diff != "" {
- t.Errorf("pkt.Pkt.Data mismatch (-want +got):\n%s", diff)
- }
- })
- }
-
- t.Run("InvalidNICID", func(t *testing.T) {
- err := s.WritePacketToRemote(234, linkAddr2, header.IPv4ProtocolNumber, buffer.View([]byte{1}).ToVectorisedView())
- if _, ok := err.(*tcpip.ErrUnknownDevice); !ok {
- t.Fatalf("s.WritePacketToRemote(_, _, _, _) = %s, want = %s", err, &tcpip.ErrUnknownDevice{})
- }
- pkt, ok := e.Read()
- if got, want := ok, 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.Now()}},
- neighbors,
- ); 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, "", "", 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, "", "", 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, "", 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)
- }
- })
- }
-}
diff --git a/pkg/tcpip/stack/stack_unsafe_state_autogen.go b/pkg/tcpip/stack/stack_unsafe_state_autogen.go
new file mode 100644
index 000000000..758ab3457
--- /dev/null
+++ b/pkg/tcpip/stack/stack_unsafe_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package stack
diff --git a/pkg/tcpip/stack/transport_demuxer_test.go b/pkg/tcpip/stack/transport_demuxer_test.go
deleted file mode 100644
index cd3a8c25a..000000000
--- a/pkg/tcpip/stack/transport_demuxer_test.go
+++ /dev/null
@@ -1,454 +0,0 @@
-// 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
-
-import (
- "io/ioutil"
- "math"
- "math/rand"
- "strconv"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/ports"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- testSrcAddrV6 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
- testDstAddrV6 = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-
- testSrcAddrV4 = "\x0a\x00\x00\x01"
- testDstAddrV4 = "\x0a\x00\x00\x02"
-
- testDstPort = 1234
- testSrcPort = 4096
-)
-
-type testContext struct {
- linkEps map[tcpip.NICID]*channel.Endpoint
- s *stack.Stack
- wq waiter.Queue
-}
-
-// newDualTestContextMultiNIC creates the testing context and also linkEpIDs NICs.
-func newDualTestContextMultiNIC(t *testing.T, mtu uint32, linkEpIDs []tcpip.NICID) *testContext {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
- linkEps := make(map[tcpip.NICID]*channel.Endpoint)
- for _, linkEpID := range linkEpIDs {
- channelEp := channel.New(256, mtu, "")
- if err := s.CreateNIC(linkEpID, channelEp); err != nil {
- t.Fatalf("CreateNIC failed: %s", err)
- }
- linkEps[linkEpID] = channelEp
-
- protocolAddrV4 := tcpip.ProtocolAddress{
- Protocol: ipv4.ProtocolNumber,
- AddressWithPrefix: tcpip.Address(testDstAddrV4).WithPrefix(),
- }
- if err := s.AddProtocolAddress(linkEpID, protocolAddrV4, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", linkEpID, protocolAddrV4, err)
- }
-
- protocolAddrV6 := tcpip.ProtocolAddress{
- Protocol: ipv6.ProtocolNumber,
- AddressWithPrefix: testDstAddrV6.WithPrefix(),
- }
- if err := s.AddProtocolAddress(linkEpID, protocolAddrV6, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", linkEpID, protocolAddrV6, err)
- }
- }
-
- s.SetRouteTable([]tcpip.Route{
- {Destination: header.IPv4EmptySubnet, NIC: 1},
- {Destination: header.IPv6EmptySubnet, NIC: 1},
- })
-
- return &testContext{
- s: s,
- linkEps: linkEps,
- }
-}
-
-type headers struct {
- srcPort uint16
- dstPort uint16
-}
-
-func newPayload() []byte {
- b := make([]byte, 30+rand.Intn(100))
- for i := range b {
- b[i] = byte(rand.Intn(256))
- }
- return b
-}
-
-func (c *testContext) sendV4Packet(payload []byte, h *headers, linkEpID tcpip.NICID) {
- buf := buffer.NewView(header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
- payloadStart := len(buf) - len(payload)
- copy(buf[payloadStart:], payload)
-
- // Initialize the IP header.
- ip := header.IPv4(buf)
- ip.Encode(&header.IPv4Fields{
- TOS: 0x80,
- TotalLength: uint16(len(buf)),
- TTL: 65,
- Protocol: uint8(udp.ProtocolNumber),
- SrcAddr: testSrcAddrV4,
- DstAddr: testDstAddrV4,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- // Initialize the UDP header.
- u := header.UDP(buf[header.IPv4MinimumSize:])
- u.Encode(&header.UDPFields{
- SrcPort: h.srcPort,
- DstPort: h.dstPort,
- Length: uint16(header.UDPMinimumSize + len(payload)),
- })
-
- // Calculate the UDP pseudo-header checksum.
- xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, testSrcAddrV4, testDstAddrV4, uint16(len(u)))
-
- // Calculate the UDP checksum and set it.
- xsum = header.Checksum(payload, xsum)
- u.SetChecksum(^u.CalculateChecksum(xsum))
-
- // Inject packet.
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- })
- c.linkEps[linkEpID].InjectInbound(ipv4.ProtocolNumber, pkt)
-}
-
-func (c *testContext) sendV6Packet(payload []byte, h *headers, linkEpID tcpip.NICID) {
- // Allocate a buffer for data and headers.
- buf := buffer.NewView(header.UDPMinimumSize + header.IPv6MinimumSize + len(payload))
- copy(buf[len(buf)-len(payload):], payload)
-
- // Initialize the IP header.
- ip := header.IPv6(buf)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(header.UDPMinimumSize + len(payload)),
- TransportProtocol: udp.ProtocolNumber,
- HopLimit: 65,
- SrcAddr: testSrcAddrV6,
- DstAddr: testDstAddrV6,
- })
-
- // Initialize the UDP header.
- u := header.UDP(buf[header.IPv6MinimumSize:])
- u.Encode(&header.UDPFields{
- SrcPort: h.srcPort,
- DstPort: h.dstPort,
- Length: uint16(header.UDPMinimumSize + len(payload)),
- })
-
- // Calculate the UDP pseudo-header checksum.
- xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, testSrcAddrV6, testDstAddrV6, uint16(len(u)))
-
- // Calculate the UDP checksum and set it.
- xsum = header.Checksum(payload, xsum)
- u.SetChecksum(^u.CalculateChecksum(xsum))
-
- // Inject packet.
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- })
- c.linkEps[linkEpID].InjectInbound(ipv6.ProtocolNumber, pkt)
-}
-
-func TestTransportDemuxerRegister(t *testing.T) {
- for _, test := range []struct {
- name string
- proto tcpip.NetworkProtocolNumber
- want tcpip.Error
- }{
- {"failure", ipv6.ProtocolNumber, &tcpip.ErrUnknownProtocol{}},
- {"success", ipv4.ProtocolNumber, nil},
- } {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
- var wq waiter.Queue
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatal(err)
- }
- tEP, ok := ep.(stack.TransportEndpoint)
- if !ok {
- t.Fatalf("%T does not implement stack.TransportEndpoint", ep)
- }
- if got, want := s.RegisterTransportEndpoint([]tcpip.NetworkProtocolNumber{test.proto}, udp.ProtocolNumber, stack.TransportEndpointID{}, tEP, ports.Flags{}, 0), test.want; got != want {
- t.Fatalf("s.RegisterTransportEndpoint(...) = %s, want %s", got, want)
- }
- })
- }
-}
-
-func TestTransportDemuxerRegisterMultiple(t *testing.T) {
- type test struct {
- flags ports.Flags
- want tcpip.Error
- }
- for _, subtest := range []struct {
- name string
- tests []test
- }{
- {"zeroFlags", []test{
- {ports.Flags{}, nil},
- {ports.Flags{}, &tcpip.ErrPortInUse{}},
- }},
- {"multibindFlags", []test{
- // Allow multiple registrations same TransportEndpointID with multibind flags.
- {ports.Flags{LoadBalanced: true, MostRecent: true}, nil},
- {ports.Flags{LoadBalanced: true, MostRecent: true}, nil},
- // Disallow registration w/same ID for a non-multibindflag.
- {ports.Flags{TupleOnly: true}, &tcpip.ErrPortInUse{}},
- }},
- } {
- t.Run(subtest.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
- var eps []tcpip.Endpoint
- for idx, test := range subtest.tests {
- var wq waiter.Queue
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatal(err)
- }
- eps = append(eps, ep)
- tEP, ok := ep.(stack.TransportEndpoint)
- if !ok {
- t.Fatalf("%T does not implement stack.TransportEndpoint", ep)
- }
- id := stack.TransportEndpointID{LocalPort: 1}
- if got, want := s.RegisterTransportEndpoint([]tcpip.NetworkProtocolNumber{ipv4.ProtocolNumber}, udp.ProtocolNumber, id, tEP, test.flags, 0), test.want; got != want {
- t.Fatalf("test index: %d, s.RegisterTransportEndpoint(ipv4.ProtocolNumber, udp.ProtocolNumber, _, _, %+v, 0) = %s, want %s", idx, test.flags, got, want)
- }
- }
- for _, ep := range eps {
- ep.Close()
- }
- })
- }
-}
-
-// TestBindToDeviceDistribution injects varied packets on input devices and checks that
-// the distribution of packets received matches expectations.
-func TestBindToDeviceDistribution(t *testing.T) {
- type endpointSockopts struct {
- reuse bool
- bindToDevice tcpip.NICID
- }
- tcs := []struct {
- name string
- // endpoints will received the inject packets.
- endpoints []endpointSockopts
- // wantDistributions is the want ratio of packets received on each
- // endpoint for each NIC on which packets are injected.
- wantDistributions map[tcpip.NICID][]float64
- }{
- {
- name: "BindPortReuse",
- // 5 endpoints that all have reuse set.
- endpoints: []endpointSockopts{
- {reuse: true, bindToDevice: 0},
- {reuse: true, bindToDevice: 0},
- {reuse: true, bindToDevice: 0},
- {reuse: true, bindToDevice: 0},
- {reuse: true, bindToDevice: 0},
- },
- wantDistributions: map[tcpip.NICID][]float64{
- // Injected packets on dev0 get distributed evenly.
- 1: {0.2, 0.2, 0.2, 0.2, 0.2},
- },
- },
- {
- name: "BindToDevice",
- // 3 endpoints with various bindings.
- endpoints: []endpointSockopts{
- {reuse: false, bindToDevice: 1},
- {reuse: false, bindToDevice: 2},
- {reuse: false, bindToDevice: 3},
- },
- wantDistributions: map[tcpip.NICID][]float64{
- // Injected packets on dev0 go only to the endpoint bound to dev0.
- 1: {1, 0, 0},
- // Injected packets on dev1 go only to the endpoint bound to dev1.
- 2: {0, 1, 0},
- // Injected packets on dev2 go only to the endpoint bound to dev2.
- 3: {0, 0, 1},
- },
- },
- {
- name: "ReuseAndBindToDevice",
- // 6 endpoints with various bindings.
- endpoints: []endpointSockopts{
- {reuse: true, bindToDevice: 1},
- {reuse: true, bindToDevice: 1},
- {reuse: true, bindToDevice: 2},
- {reuse: true, bindToDevice: 2},
- {reuse: true, bindToDevice: 2},
- {reuse: true, bindToDevice: 0},
- },
- wantDistributions: map[tcpip.NICID][]float64{
- // Injected packets on dev0 get distributed among endpoints bound to
- // dev0.
- 1: {0.5, 0.5, 0, 0, 0, 0},
- // Injected packets on dev1 get distributed among endpoints bound to
- // dev1 or unbound.
- 2: {0, 0, 1. / 3, 1. / 3, 1. / 3, 0},
- // Injected packets on dev999 go only to the unbound.
- 1000: {0, 0, 0, 0, 0, 1},
- },
- },
- }
- protos := map[string]tcpip.NetworkProtocolNumber{
- "IPv4": ipv4.ProtocolNumber,
- "IPv6": ipv6.ProtocolNumber,
- }
-
- for _, test := range tcs {
- for protoName, protoNum := range protos {
- for device, wantDistribution := range test.wantDistributions {
- t.Run(test.name+protoName+"-"+strconv.Itoa(int(device)), func(t *testing.T) {
- // Create the NICs.
- var devices []tcpip.NICID
- for d := range test.wantDistributions {
- devices = append(devices, d)
- }
- c := newDualTestContextMultiNIC(t, defaultMTU, devices)
-
- // Create endpoints and bind each to a NIC, sometimes reusing ports.
- eps := make(map[tcpip.Endpoint]int)
- pollChannel := make(chan tcpip.Endpoint)
- for i, endpoint := range test.endpoints {
- // Try to receive the data.
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.ReadableEvents)
- t.Cleanup(func() {
- wq.EventUnregister(&we)
- close(ch)
- })
-
- var err tcpip.Error
- ep, err := c.s.NewEndpoint(udp.ProtocolNumber, protoNum, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %s", err)
- }
- t.Cleanup(ep.Close)
- eps[ep] = i
-
- go func(ep tcpip.Endpoint) {
- for range ch {
- pollChannel <- ep
- }
- }(ep)
-
- ep.SocketOptions().SetReusePort(endpoint.reuse)
- if err := ep.SocketOptions().SetBindToDevice(int32(endpoint.bindToDevice)); err != nil {
- t.Fatalf("SetSockOpt(&%T(%d)) on endpoint %d failed: %s", endpoint.bindToDevice, endpoint.bindToDevice, i, err)
- }
-
- var dstAddr tcpip.Address
- switch protoNum {
- case ipv4.ProtocolNumber:
- dstAddr = testDstAddrV4
- case ipv6.ProtocolNumber:
- dstAddr = testDstAddrV6
- default:
- t.Fatalf("unexpected protocol number: %d", protoNum)
- }
- if err := ep.Bind(tcpip.FullAddress{Addr: dstAddr, Port: testDstPort}); err != nil {
- t.Fatalf("ep.Bind(...) on endpoint %d failed: %s", i, err)
- }
- }
-
- // Send packets across a range of ports, checking that packets from
- // the same source port are always demultiplexed to the same
- // destination endpoint.
- npackets := 10_000
- nports := 1_000
- if got, want := len(test.endpoints), len(wantDistribution); got != want {
- t.Fatalf("got len(test.endpoints) = %d, want %d", got, want)
- }
- endpoints := make(map[uint16]tcpip.Endpoint)
- stats := make(map[tcpip.Endpoint]int)
- for i := 0; i < npackets; i++ {
- // Send a packet.
- port := uint16(i % nports)
- payload := newPayload()
- hdrs := &headers{
- srcPort: testSrcPort + port,
- dstPort: testDstPort,
- }
- switch protoNum {
- case ipv4.ProtocolNumber:
- c.sendV4Packet(payload, hdrs, device)
- case ipv6.ProtocolNumber:
- c.sendV6Packet(payload, hdrs, device)
- default:
- t.Fatalf("unexpected protocol number: %d", protoNum)
- }
-
- ep := <-pollChannel
- if _, err := ep.Read(ioutil.Discard, tcpip.ReadOptions{}); err != nil {
- t.Fatalf("Read on endpoint %d failed: %s", eps[ep], err)
- }
- stats[ep]++
- if i < nports {
- endpoints[uint16(i)] = ep
- } else {
- // Check that all packets from one client are handled by the same
- // socket.
- if want, got := endpoints[port], ep; want != got {
- t.Fatalf("Packet sent on port %d expected on endpoint %d but received on endpoint %d", port, eps[want], eps[got])
- }
- }
- }
-
- // Check that a packet distribution is as expected.
- for ep, i := range eps {
- wantRatio := wantDistribution[i]
- wantRecv := wantRatio * float64(npackets)
- actualRecv := stats[ep]
- actualRatio := float64(stats[ep]) / float64(npackets)
- // The deviation is less than 10%.
- if math.Abs(actualRatio-wantRatio) > 0.05 {
- t.Errorf("want about %.0f%% (%.0f of %d) packets to arrive on endpoint %d, got %.0f%% (%d of %d)", wantRatio*100, wantRecv, npackets, i, actualRatio*100, actualRecv, npackets)
- }
- }
- })
- }
- }
- }
-}
diff --git a/pkg/tcpip/stack/transport_test.go b/pkg/tcpip/stack/transport_test.go
deleted file mode 100644
index 51870d03f..000000000
--- a/pkg/tcpip/stack/transport_test.go
+++ /dev/null
@@ -1,579 +0,0 @@
-// 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
-
-import (
- "bytes"
- "io"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/ports"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- fakeTransNumber tcpip.TransportProtocolNumber = 1
- fakeTransHeaderLen int = 3
-)
-
-// fakeTransportEndpoint is a transport-layer protocol endpoint. It counts
-// received packets; the counts of all endpoints are aggregated in the protocol
-// descriptor.
-//
-// Headers of this protocol are fakeTransHeaderLen bytes, but we currently don't
-// use it.
-type fakeTransportEndpoint struct {
- stack.TransportEndpointInfo
- tcpip.DefaultSocketOptionsHandler
-
- proto *fakeTransportProtocol
- peerAddr tcpip.Address
- route *stack.Route
- uniqueID uint64
-
- // acceptQueue is non-nil iff bound.
- acceptQueue []*fakeTransportEndpoint
-
- // ops is used to set and get socket options.
- ops tcpip.SocketOptions
-}
-
-func (f *fakeTransportEndpoint) Info() tcpip.EndpointInfo {
- return &f.TransportEndpointInfo
-}
-
-func (*fakeTransportEndpoint) Stats() tcpip.EndpointStats {
- return nil
-}
-
-func (*fakeTransportEndpoint) SetOwner(owner tcpip.PacketOwner) {}
-
-func (f *fakeTransportEndpoint) SocketOptions() *tcpip.SocketOptions {
- return &f.ops
-}
-
-func newFakeTransportEndpoint(proto *fakeTransportProtocol, netProto tcpip.NetworkProtocolNumber, s *stack.Stack) tcpip.Endpoint {
- ep := &fakeTransportEndpoint{TransportEndpointInfo: stack.TransportEndpointInfo{NetProto: netProto}, proto: proto, uniqueID: s.UniqueID()}
- ep.ops.InitHandler(ep, s, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits)
- return ep
-}
-
-func (f *fakeTransportEndpoint) Abort() {
- f.Close()
-}
-
-func (f *fakeTransportEndpoint) Close() {
- // TODO(gvisor.dev/issue/5153): Consider retaining the route.
- f.route.Release()
-}
-
-func (*fakeTransportEndpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
- return mask
-}
-
-func (*fakeTransportEndpoint) Read(io.Writer, tcpip.ReadOptions) (tcpip.ReadResult, tcpip.Error) {
- return tcpip.ReadResult{}, nil
-}
-
-func (f *fakeTransportEndpoint) Write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, tcpip.Error) {
- if len(f.route.RemoteAddress()) == 0 {
- return 0, &tcpip.ErrNoRoute{}
- }
-
- v := make([]byte, p.Len())
- if _, err := io.ReadFull(p, v); err != nil {
- return 0, &tcpip.ErrBadBuffer{}
- }
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: int(f.route.MaxHeaderLength()) + fakeTransHeaderLen,
- Data: buffer.View(v).ToVectorisedView(),
- })
- _ = pkt.TransportHeader().Push(fakeTransHeaderLen)
- if err := f.route.WritePacket(stack.NetworkHeaderParams{Protocol: fakeTransNumber, TTL: 123, TOS: stack.DefaultTOS}, pkt); err != nil {
- return 0, err
- }
-
- return int64(len(v)), nil
-}
-
-// SetSockOpt sets a socket option. Currently not supported.
-func (*fakeTransportEndpoint) SetSockOpt(tcpip.SettableSocketOption) tcpip.Error {
- return &tcpip.ErrInvalidEndpointState{}
-}
-
-// SetSockOptInt sets a socket option. Currently not supported.
-func (*fakeTransportEndpoint) SetSockOptInt(tcpip.SockOptInt, int) tcpip.Error {
- return &tcpip.ErrInvalidEndpointState{}
-}
-
-// GetSockOptInt implements tcpip.Endpoint.GetSockOptInt.
-func (*fakeTransportEndpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
- return -1, &tcpip.ErrUnknownProtocolOption{}
-}
-
-// GetSockOpt implements tcpip.Endpoint.GetSockOpt.
-func (*fakeTransportEndpoint) GetSockOpt(tcpip.GettableSocketOption) tcpip.Error {
- return &tcpip.ErrInvalidEndpointState{}
-}
-
-// Disconnect implements tcpip.Endpoint.Disconnect.
-func (*fakeTransportEndpoint) Disconnect() tcpip.Error {
- return &tcpip.ErrNotSupported{}
-}
-
-func (f *fakeTransportEndpoint) Connect(addr tcpip.FullAddress) tcpip.Error {
- f.peerAddr = addr.Addr
-
- // Find the route.
- r, err := f.proto.stack.FindRoute(addr.NIC, "", addr.Addr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- return &tcpip.ErrNoRoute{}
- }
-
- // Try to register so that we can start receiving packets.
- f.ID.RemoteAddress = addr.Addr
- err = f.proto.stack.RegisterTransportEndpoint([]tcpip.NetworkProtocolNumber{fakeNetNumber}, fakeTransNumber, f.ID, f, ports.Flags{}, 0 /* bindToDevice */)
- if err != nil {
- r.Release()
- return err
- }
-
- f.route = r
-
- return nil
-}
-
-func (f *fakeTransportEndpoint) UniqueID() uint64 {
- return f.uniqueID
-}
-
-func (*fakeTransportEndpoint) ConnectEndpoint(e tcpip.Endpoint) tcpip.Error {
- return nil
-}
-
-func (*fakeTransportEndpoint) Shutdown(tcpip.ShutdownFlags) tcpip.Error {
- return nil
-}
-
-func (*fakeTransportEndpoint) Reset() {
-}
-
-func (*fakeTransportEndpoint) Listen(int) tcpip.Error {
- return nil
-}
-
-func (f *fakeTransportEndpoint) Accept(*tcpip.FullAddress) (tcpip.Endpoint, *waiter.Queue, tcpip.Error) {
- if len(f.acceptQueue) == 0 {
- return nil, nil, nil
- }
- a := f.acceptQueue[0]
- f.acceptQueue = f.acceptQueue[1:]
- return a, nil, nil
-}
-
-func (f *fakeTransportEndpoint) Bind(a tcpip.FullAddress) tcpip.Error {
- if err := f.proto.stack.RegisterTransportEndpoint(
- []tcpip.NetworkProtocolNumber{fakeNetNumber},
- fakeTransNumber,
- stack.TransportEndpointID{LocalAddress: a.Addr},
- f,
- ports.Flags{},
- 0, /* bindtoDevice */
- ); err != nil {
- return err
- }
- f.acceptQueue = []*fakeTransportEndpoint{}
- return nil
-}
-
-func (*fakeTransportEndpoint) GetLocalAddress() (tcpip.FullAddress, tcpip.Error) {
- return tcpip.FullAddress{}, nil
-}
-
-func (*fakeTransportEndpoint) GetRemoteAddress() (tcpip.FullAddress, tcpip.Error) {
- return tcpip.FullAddress{}, nil
-}
-
-func (f *fakeTransportEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) {
- // Increment the number of received packets.
- f.proto.packetCount++
- if f.acceptQueue == nil {
- return
- }
-
- netHdr := pkt.NetworkHeader().View()
- route, err := f.proto.stack.FindRoute(pkt.NICID, tcpip.Address(netHdr[dstAddrOffset]), tcpip.Address(netHdr[srcAddrOffset]), pkt.NetworkProtocolNumber, false /* multicastLoop */)
- if err != nil {
- return
- }
-
- ep := &fakeTransportEndpoint{
- TransportEndpointInfo: stack.TransportEndpointInfo{
- ID: f.ID,
- NetProto: f.NetProto,
- },
- proto: f.proto,
- peerAddr: route.RemoteAddress(),
- route: route,
- }
- ep.ops.InitHandler(ep, f.proto.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits)
- f.acceptQueue = append(f.acceptQueue, ep)
-}
-
-func (f *fakeTransportEndpoint) HandleError(stack.TransportError, *stack.PacketBuffer) {
- // Increment the number of received control packets.
- f.proto.controlCount++
-}
-
-func (*fakeTransportEndpoint) State() uint32 {
- return 0
-}
-
-func (*fakeTransportEndpoint) ModerateRecvBuf(copied int) {}
-
-func (*fakeTransportEndpoint) Resume(*stack.Stack) {}
-
-func (*fakeTransportEndpoint) Wait() {}
-
-func (*fakeTransportEndpoint) LastError() tcpip.Error {
- return nil
-}
-
-type fakeTransportGoodOption bool
-
-type fakeTransportBadOption bool
-
-type fakeTransportInvalidValueOption int
-
-type fakeTransportProtocolOptions struct {
- good bool
-}
-
-// fakeTransportProtocol is a transport-layer protocol descriptor. It
-// aggregates the number of packets received via endpoints of this protocol.
-type fakeTransportProtocol struct {
- stack *stack.Stack
-
- packetCount int
- controlCount int
- opts fakeTransportProtocolOptions
-}
-
-func (*fakeTransportProtocol) Number() tcpip.TransportProtocolNumber {
- return fakeTransNumber
-}
-
-func (f *fakeTransportProtocol) NewEndpoint(netProto tcpip.NetworkProtocolNumber, _ *waiter.Queue) (tcpip.Endpoint, tcpip.Error) {
- return newFakeTransportEndpoint(f, netProto, f.stack), nil
-}
-
-func (*fakeTransportProtocol) NewRawEndpoint(tcpip.NetworkProtocolNumber, *waiter.Queue) (tcpip.Endpoint, tcpip.Error) {
- return nil, &tcpip.ErrUnknownProtocol{}
-}
-
-func (*fakeTransportProtocol) MinimumPacketSize() int {
- return fakeTransHeaderLen
-}
-
-func (*fakeTransportProtocol) ParsePorts(buffer.View) (src, dst uint16, err tcpip.Error) {
- return 0, 0, nil
-}
-
-func (*fakeTransportProtocol) HandleUnknownDestinationPacket(stack.TransportEndpointID, *stack.PacketBuffer) stack.UnknownDestinationPacketDisposition {
- return stack.UnknownDestinationPacketHandled
-}
-
-func (f *fakeTransportProtocol) SetOption(option tcpip.SettableTransportProtocolOption) tcpip.Error {
- switch v := option.(type) {
- case *tcpip.TCPModerateReceiveBufferOption:
- f.opts.good = bool(*v)
- return nil
- default:
- return &tcpip.ErrUnknownProtocolOption{}
- }
-}
-
-func (f *fakeTransportProtocol) Option(option tcpip.GettableTransportProtocolOption) tcpip.Error {
- switch v := option.(type) {
- case *tcpip.TCPModerateReceiveBufferOption:
- *v = tcpip.TCPModerateReceiveBufferOption(f.opts.good)
- return nil
- default:
- return &tcpip.ErrUnknownProtocolOption{}
- }
-}
-
-// Abort implements TransportProtocol.Abort.
-func (*fakeTransportProtocol) Abort() {}
-
-// Close implements tcpip.Endpoint.Close.
-func (*fakeTransportProtocol) Close() {}
-
-// Wait implements TransportProtocol.Wait.
-func (*fakeTransportProtocol) Wait() {}
-
-// Parse implements TransportProtocol.Parse.
-func (*fakeTransportProtocol) Parse(pkt *stack.PacketBuffer) bool {
- if _, ok := pkt.TransportHeader().Consume(fakeTransHeaderLen); ok {
- pkt.TransportProtocolNumber = fakeTransNumber
- return true
- }
- return false
-}
-
-func fakeTransFactory(s *stack.Stack) stack.TransportProtocol {
- return &fakeTransportProtocol{stack: s}
-}
-
-func TestTransportReceive(t *testing.T) {
- linkEP := channel.New(10, defaultMTU, "")
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
- TransportProtocols: []stack.TransportProtocolFactory{fakeTransFactory},
- })
- if err := s.CreateNIC(1, linkEP); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: "\x01",
- PrefixLen: fakeDefaultPrefixLen,
- },
- }
- if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr, err)
- }
-
- // Create endpoint and connect to remote address.
- wq := waiter.Queue{}
- ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
- t.Fatalf("Connect failed: %v", err)
- }
-
- fakeTrans := s.TransportProtocolInstance(fakeTransNumber).(*fakeTransportProtocol)
-
- // Create buffer that will hold the packet.
- buf := buffer.NewView(30)
-
- // Make sure packet with wrong protocol is not delivered.
- buf[0] = 1
- buf[2] = 0
- linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- if fakeTrans.packetCount != 0 {
- t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 0)
- }
-
- // Make sure packet from the wrong source is not delivered.
- buf[0] = 1
- buf[1] = 3
- buf[2] = byte(fakeTransNumber)
- linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- if fakeTrans.packetCount != 0 {
- t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 0)
- }
-
- // Make sure packet is delivered.
- buf[0] = 1
- buf[1] = 2
- buf[2] = byte(fakeTransNumber)
- linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- if fakeTrans.packetCount != 1 {
- t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 1)
- }
-}
-
-func TestTransportControlReceive(t *testing.T) {
- linkEP := channel.New(10, defaultMTU, "")
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
- TransportProtocols: []stack.TransportProtocolFactory{fakeTransFactory},
- })
- if err := s.CreateNIC(1, linkEP); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: "\x01",
- PrefixLen: fakeDefaultPrefixLen,
- },
- }
- if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", 1, protocolAddr, err)
- }
-
- // Create endpoint and connect to remote address.
- wq := waiter.Queue{}
- ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
- t.Fatalf("Connect failed: %v", err)
- }
-
- fakeTrans := s.TransportProtocolInstance(fakeTransNumber).(*fakeTransportProtocol)
-
- // Create buffer that will hold the control packet.
- buf := buffer.NewView(2*fakeNetHeaderLen + 30)
-
- // Outer packet contains the control protocol number.
- buf[0] = 1
- buf[1] = 0xfe
- buf[2] = uint8(fakeControlProtocol)
-
- // Make sure packet with wrong protocol is not delivered.
- buf[fakeNetHeaderLen+0] = 0
- buf[fakeNetHeaderLen+1] = 1
- buf[fakeNetHeaderLen+2] = 0
- linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- if fakeTrans.controlCount != 0 {
- t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 0)
- }
-
- // Make sure packet from the wrong source is not delivered.
- buf[fakeNetHeaderLen+0] = 3
- buf[fakeNetHeaderLen+1] = 1
- buf[fakeNetHeaderLen+2] = byte(fakeTransNumber)
- linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- if fakeTrans.controlCount != 0 {
- t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 0)
- }
-
- // Make sure packet is delivered.
- buf[fakeNetHeaderLen+0] = 2
- buf[fakeNetHeaderLen+1] = 1
- buf[fakeNetHeaderLen+2] = byte(fakeTransNumber)
- linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buf.ToVectorisedView(),
- }))
- if fakeTrans.controlCount != 1 {
- t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 1)
- }
-}
-
-func TestTransportSend(t *testing.T) {
- linkEP := channel.New(10, defaultMTU, "")
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
- TransportProtocols: []stack.TransportProtocolFactory{fakeTransFactory},
- })
- if err := s.CreateNIC(1, linkEP); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- protocolAddr := tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: "\x01",
- 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("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- // Create endpoint and bind it.
- wq := waiter.Queue{}
- ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
- t.Fatalf("Connect failed: %v", err)
- }
-
- // Create buffer that will hold the payload.
- b := make([]byte, 30)
- var r bytes.Reader
- r.Reset(b)
- if _, err := ep.Write(&r, tcpip.WriteOptions{}); err != nil {
- t.Fatalf("write failed: %v", err)
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- if fakeNet.sendPacketCount[2] != 1 {
- t.Errorf("sendPacketCount = %d, want %d", fakeNet.sendPacketCount[2], 1)
- }
-}
-
-func TestTransportOptions(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
- TransportProtocols: []stack.TransportProtocolFactory{fakeTransFactory},
- })
-
- v := tcpip.TCPModerateReceiveBufferOption(true)
- if err := s.SetTransportProtocolOption(fakeTransNumber, &v); err != nil {
- t.Errorf("s.SetTransportProtocolOption(fakeTrans, &%T(%t)): %s", v, v, err)
- }
- v = false
- if err := s.TransportProtocolOption(fakeTransNumber, &v); err != nil {
- t.Fatalf("s.TransportProtocolOption(fakeTransNumber, &%T): %s", v, err)
- }
- if !v {
- t.Fatalf("got tcpip.TCPModerateReceiveBufferOption = false, want = true")
- }
-}
diff --git a/pkg/tcpip/stack/tuple_list.go b/pkg/tcpip/stack/tuple_list.go
new file mode 100644
index 000000000..31d0feefa
--- /dev/null
+++ b/pkg/tcpip/stack/tuple_list.go
@@ -0,0 +1,221 @@
+package stack
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type tupleElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (tupleElementMapper) linkerFor(elem *tuple) *tuple { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type tupleList struct {
+ head *tuple
+ tail *tuple
+}
+
+// Reset resets list l to the empty state.
+func (l *tupleList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *tupleList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *tupleList) Front() *tuple {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *tupleList) Back() *tuple {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *tupleList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (tupleElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *tupleList) PushFront(e *tuple) {
+ linker := tupleElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ tupleElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *tupleList) PushBack(e *tuple) {
+ linker := tupleElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ tupleElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *tupleList) PushBackList(m *tupleList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ tupleElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ tupleElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *tupleList) InsertAfter(b, e *tuple) {
+ bLinker := tupleElementMapper{}.linkerFor(b)
+ eLinker := tupleElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ tupleElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *tupleList) InsertBefore(a, e *tuple) {
+ aLinker := tupleElementMapper{}.linkerFor(a)
+ eLinker := tupleElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ tupleElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *tupleList) Remove(e *tuple) {
+ linker := tupleElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ tupleElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ tupleElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else if l.tail == e {
+ l.tail = prev
+ }
+
+ linker.SetNext(nil)
+ linker.SetPrev(nil)
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type tupleEntry struct {
+ next *tuple
+ prev *tuple
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *tupleEntry) Next() *tuple {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *tupleEntry) Prev() *tuple {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *tupleEntry) SetNext(elem *tuple) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *tupleEntry) SetPrev(elem *tuple) {
+ e.prev = elem
+}