Remove linkAddrCache

It was replaced by NUD/neighborCache.

Fixes #4658.

PiperOrigin-RevId: 356085221
This commit is contained in:
Ghanan Gowripalan
2021-02-06 21:37:15 -08:00
committed by gVisor bot
parent 554c405e87
commit 3853a94f10
17 changed files with 1219 additions and 2677 deletions
+6 -95
View File
@@ -17,7 +17,6 @@ package arp_test
import (
"context"
"fmt"
"strconv"
"testing"
"time"
@@ -155,7 +154,7 @@ type testContext struct {
nudDisp *arpDispatcher
}
func newTestContext(t *testing.T, useNeighborCache bool) *testContext {
func newTestContext(t *testing.T) *testContext {
c := stack.DefaultNUDConfigurations()
// Transition from Reachable to Stale almost immediately to test if receiving
// probes refreshes positive reachability.
@@ -173,7 +172,6 @@ func newTestContext(t *testing.T, useNeighborCache bool) *testContext {
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4},
NUDConfigs: c,
NUDDisp: &d,
UseNeighborCache: useNeighborCache,
})
ep := channel.New(defaultChannelSize, defaultMTU, stackLinkAddr)
@@ -191,15 +189,6 @@ func newTestContext(t *testing.T, useNeighborCache bool) *testContext {
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, stackAddr); err != nil {
t.Fatalf("AddAddress for ipv4 failed: %v", err)
}
if !useNeighborCache {
// The remote address needs to be assigned to the NIC so we can receive and
// verify outgoing ARP packets. The neighbor cache isn't concerned with
// this; the tests that use linkAddrCache expect the ARP responses to be
// received by the same NIC.
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, remoteAddr); err != nil {
t.Fatalf("AddAddress for ipv4 failed: %v", err)
}
}
s.SetRouteTable([]tcpip.Route{{
Destination: header.IPv4EmptySubnet,
@@ -217,86 +206,8 @@ func (c *testContext) cleanup() {
c.linkEP.Close()
}
func TestDirectRequest(t *testing.T) {
c := newTestContext(t, false /* useNeighborCache */)
defer c.cleanup()
const senderMAC = "\x01\x02\x03\x04\x05\x06"
const senderIPv4 = "\x0a\x00\x00\x02"
v := make(buffer.View, header.ARPSize)
h := header.ARP(v)
h.SetIPv4OverEthernet()
h.SetOp(header.ARPRequest)
copy(h.HardwareAddressSender(), senderMAC)
copy(h.ProtocolAddressSender(), senderIPv4)
inject := func(addr tcpip.Address) {
copy(h.ProtocolAddressTarget(), addr)
c.linkEP.InjectInbound(arp.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: v.ToVectorisedView(),
}))
}
for i, address := range []tcpip.Address{stackAddr, remoteAddr} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
expectedPacketsReceived := c.s.Stats().ARP.PacketsReceived.Value() + 1
expectedRequestsReceived := c.s.Stats().ARP.RequestsReceived.Value() + 1
expectedRepliesSent := c.s.Stats().ARP.OutgoingRepliesSent.Value() + 1
inject(address)
pi, _ := c.linkEP.ReadContext(context.Background())
if pi.Proto != arp.ProtocolNumber {
t.Fatalf("expected ARP response, got network protocol number %d", pi.Proto)
}
rep := header.ARP(pi.Pkt.NetworkHeader().View())
if !rep.IsValid() {
t.Fatalf("invalid ARP response: len = %d; response = %x", len(rep), rep)
}
if got := rep.Op(); got != header.ARPReply {
t.Fatalf("got Op = %d, want = %d", got, header.ARPReply)
}
if got, want := tcpip.LinkAddress(rep.HardwareAddressSender()), stackLinkAddr; got != want {
t.Errorf("got HardwareAddressSender = %s, want = %s", got, want)
}
if got, want := tcpip.Address(rep.ProtocolAddressSender()), tcpip.Address(h.ProtocolAddressTarget()); got != want {
t.Errorf("got ProtocolAddressSender = %s, want = %s", got, want)
}
if got, want := tcpip.LinkAddress(rep.HardwareAddressTarget()), tcpip.LinkAddress(h.HardwareAddressSender()); got != want {
t.Errorf("got HardwareAddressTarget = %s, want = %s", got, want)
}
if got, want := tcpip.Address(rep.ProtocolAddressTarget()), tcpip.Address(h.ProtocolAddressSender()); got != want {
t.Errorf("got ProtocolAddressTarget = %s, want = %s", got, want)
}
if got := c.s.Stats().ARP.PacketsReceived.Value(); got != expectedPacketsReceived {
t.Errorf("got c.s.Stats().ARP.PacketsReceived.Value() = %d, want = %d", got, expectedPacketsReceived)
}
if got := c.s.Stats().ARP.RequestsReceived.Value(); got != expectedRequestsReceived {
t.Errorf("got c.s.Stats().ARP.PacketsReceived.Value() = %d, want = %d", got, expectedRequestsReceived)
}
if got := c.s.Stats().ARP.OutgoingRepliesSent.Value(); got != expectedRepliesSent {
t.Errorf("got c.s.Stats().ARP.OutgoingRepliesSent.Value() = %d, want = %d", got, expectedRepliesSent)
}
})
}
inject(unknownAddr)
// Sleep tests are gross, but this will only potentially flake
// if there's a bug. If there is no bug this will reliably
// succeed.
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if pkt, ok := c.linkEP.ReadContext(ctx); ok {
t.Errorf("stackAddrBad: unexpected packet sent, Proto=%v", pkt.Proto)
}
if got := c.s.Stats().ARP.RequestsReceivedUnknownTargetAddress.Value(); got != 1 {
t.Errorf("got c.s.Stats().ARP.RequestsReceivedUnKnownTargetAddress.Value() = %d, want = 1", got)
}
}
func TestMalformedPacket(t *testing.T) {
c := newTestContext(t, false)
c := newTestContext(t)
defer c.cleanup()
v := make(buffer.View, header.ARPSize)
@@ -315,7 +226,7 @@ func TestMalformedPacket(t *testing.T) {
}
func TestDisabledEndpoint(t *testing.T) {
c := newTestContext(t, false)
c := newTestContext(t)
defer c.cleanup()
ep, err := c.s.GetNetworkEndpoint(nicID, header.ARPProtocolNumber)
@@ -340,7 +251,7 @@ func TestDisabledEndpoint(t *testing.T) {
}
func TestDirectReply(t *testing.T) {
c := newTestContext(t, false)
c := newTestContext(t)
defer c.cleanup()
const senderMAC = "\x01\x02\x03\x04\x05\x06"
@@ -370,8 +281,8 @@ func TestDirectReply(t *testing.T) {
}
}
func TestDirectRequestWithNeighborCache(t *testing.T) {
c := newTestContext(t, true /* useNeighborCache */)
func TestDirectRequest(t *testing.T) {
c := newTestContext(t)
defer c.cleanup()
tests := []struct {
+109 -288
View File
@@ -175,168 +175,9 @@ func handleICMPInIPv6(ep stack.NetworkEndpoint, src, dst tcpip.Address, icmp hea
}
func TestICMPCounts(t *testing.T) {
tests := []struct {
name string
useNeighborCache bool
}{
{
name: "linkAddrCache",
useNeighborCache: false,
},
{
name: "neighborCache",
useNeighborCache: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
UseNeighborCache: test.useNeighborCache,
})
if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
t.Fatalf("CreateNIC(_, _) = %s", err)
}
{
subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
if err != nil {
t.Fatal(err)
}
s.SetRouteTable(
[]tcpip.Route{{
Destination: subnet,
NIC: nicID,
}},
)
}
netProto := s.NetworkProtocolInstance(ProtocolNumber)
if netProto == nil {
t.Fatalf("cannot find protocol instance for network protocol %d", ProtocolNumber)
}
ep := netProto.NewEndpoint(&testInterface{}, &stubDispatcher{})
defer ep.Close()
if err := ep.Enable(); err != nil {
t.Fatalf("ep.Enable(): %s", err)
}
addressableEndpoint, ok := ep.(stack.AddressableEndpoint)
if !ok {
t.Fatalf("expected network endpoint to implement stack.AddressableEndpoint")
}
addr := lladdr0.WithPrefix()
if ep, err := addressableEndpoint.AddAndAcquirePermanentAddress(addr, stack.CanBePrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */); err != nil {
t.Fatalf("addressableEndpoint.AddAndAcquirePermanentAddress(%s, CanBePrimaryEndpoint, AddressConfigStatic, false): %s", addr, err)
} else {
ep.DecRef()
}
var tllData [header.NDPLinkLayerAddressSize]byte
header.NDPOptions(tllData[:]).Serialize(header.NDPOptionsSerializer{
header.NDPTargetLinkLayerAddressOption(linkAddr1),
})
types := []struct {
typ header.ICMPv6Type
size int
extraData []byte
}{
{
typ: header.ICMPv6DstUnreachable,
size: header.ICMPv6DstUnreachableMinimumSize,
},
{
typ: header.ICMPv6PacketTooBig,
size: header.ICMPv6PacketTooBigMinimumSize,
},
{
typ: header.ICMPv6TimeExceeded,
size: header.ICMPv6MinimumSize,
},
{
typ: header.ICMPv6ParamProblem,
size: header.ICMPv6MinimumSize,
},
{
typ: header.ICMPv6EchoRequest,
size: header.ICMPv6EchoMinimumSize,
},
{
typ: header.ICMPv6EchoReply,
size: header.ICMPv6EchoMinimumSize,
},
{
typ: header.ICMPv6RouterSolicit,
size: header.ICMPv6MinimumSize,
},
{
typ: header.ICMPv6RouterAdvert,
size: header.ICMPv6HeaderSize + header.NDPRAMinimumSize,
},
{
typ: header.ICMPv6NeighborSolicit,
size: header.ICMPv6NeighborSolicitMinimumSize,
},
{
typ: header.ICMPv6NeighborAdvert,
size: header.ICMPv6NeighborAdvertMinimumSize,
extraData: tllData[:],
},
{
typ: header.ICMPv6RedirectMsg,
size: header.ICMPv6MinimumSize,
},
{
typ: header.ICMPv6MulticastListenerQuery,
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
},
{
typ: header.ICMPv6MulticastListenerReport,
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
},
{
typ: header.ICMPv6MulticastListenerDone,
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
},
{
typ: 255, /* Unrecognized */
size: 50,
},
}
for _, typ := range types {
icmp := header.ICMPv6(buffer.NewView(typ.size + len(typ.extraData)))
copy(icmp[typ.size:], typ.extraData)
icmp.SetType(typ.typ)
icmp.SetChecksum(header.ICMPv6Checksum(icmp[:typ.size], lladdr0, lladdr1, buffer.View(typ.extraData).ToVectorisedView()))
handleICMPInIPv6(ep, lladdr1, lladdr0, icmp)
}
// Construct an empty ICMP packet so that
// Stats().ICMP.ICMPv6ReceivedPacketStats.Invalid is incremented.
handleICMPInIPv6(ep, lladdr1, lladdr0, header.ICMPv6(buffer.NewView(header.IPv6MinimumSize)))
icmpv6Stats := s.Stats().ICMP.V6.PacketsReceived
visitStats(reflect.ValueOf(&icmpv6Stats).Elem(), func(name string, s *tcpip.StatCounter) {
if got, want := s.Value(), uint64(1); got != want {
t.Errorf("got %s = %d, want = %d", name, got, want)
}
})
if t.Failed() {
t.Logf("stats:\n%+v", s.Stats())
}
})
}
}
func TestICMPCountsWithNeighborCache(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
t.Fatalf("CreateNIC(_, _) = %s", err)
@@ -770,135 +611,116 @@ func TestICMPChecksumValidationSimple(t *testing.T) {
},
}
tests := []struct {
name string
useNeighborCache bool
}{
{
name: "linkAddrCache",
useNeighborCache: false,
},
{
name: "neighborCache",
useNeighborCache: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, typ := range types {
for _, isRouter := range []bool{false, true} {
name := typ.name
if isRouter {
name += " (Router)"
}
t.Run(name, func(t *testing.T) {
e := channel.New(0, 1280, linkAddr0)
// Indicate that resolution for link layer addresses is required to
// send packets over this link. This is needed so the NIC knows to
// allocate a neighbor table.
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
UseNeighborCache: test.useNeighborCache,
})
if isRouter {
// Enabling forwarding makes the stack act as a router.
s.SetForwarding(ProtocolNumber, true)
}
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(_, _) = %s", err)
}
if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err)
}
{
subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
if err != nil {
t.Fatal(err)
}
s.SetRouteTable(
[]tcpip.Route{{
Destination: subnet,
NIC: nicID,
}},
)
}
handleIPv6Payload := func(checksum bool) {
icmp := header.ICMPv6(buffer.NewView(typ.size + len(typ.extraData)))
copy(icmp[typ.size:], typ.extraData)
icmp.SetType(typ.typ)
if checksum {
icmp.SetChecksum(header.ICMPv6Checksum(icmp, lladdr1, lladdr0, buffer.View{}.ToVectorisedView()))
}
ip := header.IPv6(buffer.NewView(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(len(icmp)),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: header.NDPHopLimit,
SrcAddr: lladdr1,
DstAddr: lladdr0,
})
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: buffer.NewVectorisedView(len(ip)+len(icmp), []buffer.View{buffer.View(ip), buffer.View(icmp)}),
})
e.InjectInbound(ProtocolNumber, pkt)
}
stats := s.Stats().ICMP.V6.PacketsReceived
invalid := stats.Invalid
routerOnly := stats.RouterOnlyPacketsDroppedByHost
typStat := typ.statCounter(stats)
// Initial stat counts should be 0.
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
if got := routerOnly.Value(); got != 0 {
t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 0", got)
}
if got := typStat.Value(); got != 0 {
t.Fatalf("got %s = %d, want = 0", typ.name, got)
}
// Without setting checksum, the incoming packet should
// be invalid.
handleIPv6Payload(false)
if got := invalid.Value(); got != 1 {
t.Fatalf("got invalid = %d, want = 1", got)
}
// Router only count should not have increased.
if got := routerOnly.Value(); got != 0 {
t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 0", got)
}
// Rx count of type typ.typ should not have increased.
if got := typStat.Value(); got != 0 {
t.Fatalf("got %s = %d, want = 0", typ.name, got)
}
// When checksum is set, it should be received.
handleIPv6Payload(true)
if got := typStat.Value(); got != 1 {
t.Fatalf("got %s = %d, want = 1", typ.name, got)
}
// Invalid count should not have increased again.
if got := invalid.Value(); got != 1 {
t.Fatalf("got invalid = %d, want = 1", got)
}
if !isRouter && typ.routerOnly && test.useNeighborCache {
// Router only count should have increased.
if got := routerOnly.Value(); got != 1 {
t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 1", got)
}
}
})
}
for _, typ := range types {
for _, isRouter := range []bool{false, true} {
name := typ.name
if isRouter {
name += " (Router)"
}
})
t.Run(name, func(t *testing.T) {
e := channel.New(0, 1280, linkAddr0)
// Indicate that resolution for link layer addresses is required to
// send packets over this link. This is needed so the NIC knows to
// allocate a neighbor table.
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
})
if isRouter {
// Enabling forwarding makes the stack act as a router.
s.SetForwarding(ProtocolNumber, true)
}
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(_, _) = %s", err)
}
if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err)
}
{
subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
if err != nil {
t.Fatal(err)
}
s.SetRouteTable(
[]tcpip.Route{{
Destination: subnet,
NIC: nicID,
}},
)
}
handleIPv6Payload := func(checksum bool) {
icmp := header.ICMPv6(buffer.NewView(typ.size + len(typ.extraData)))
copy(icmp[typ.size:], typ.extraData)
icmp.SetType(typ.typ)
if checksum {
icmp.SetChecksum(header.ICMPv6Checksum(icmp, lladdr1, lladdr0, buffer.View{}.ToVectorisedView()))
}
ip := header.IPv6(buffer.NewView(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(len(icmp)),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: header.NDPHopLimit,
SrcAddr: lladdr1,
DstAddr: lladdr0,
})
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: buffer.NewVectorisedView(len(ip)+len(icmp), []buffer.View{buffer.View(ip), buffer.View(icmp)}),
})
e.InjectInbound(ProtocolNumber, pkt)
}
stats := s.Stats().ICMP.V6.PacketsReceived
invalid := stats.Invalid
routerOnly := stats.RouterOnlyPacketsDroppedByHost
typStat := typ.statCounter(stats)
// Initial stat counts should be 0.
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
if got := routerOnly.Value(); got != 0 {
t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 0", got)
}
if got := typStat.Value(); got != 0 {
t.Fatalf("got %s = %d, want = 0", typ.name, got)
}
// Without setting checksum, the incoming packet should
// be invalid.
handleIPv6Payload(false)
if got := invalid.Value(); got != 1 {
t.Fatalf("got invalid = %d, want = 1", got)
}
// Router only count should not have increased.
if got := routerOnly.Value(); got != 0 {
t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 0", got)
}
// Rx count of type typ.typ should not have increased.
if got := typStat.Value(); got != 0 {
t.Fatalf("got %s = %d, want = 0", typ.name, got)
}
// When checksum is set, it should be received.
handleIPv6Payload(true)
if got := typStat.Value(); got != 1 {
t.Fatalf("got %s = %d, want = 1", typ.name, got)
}
// Invalid count should not have increased again.
if got := invalid.Value(); got != 1 {
t.Fatalf("got invalid = %d, want = 1", got)
}
if !isRouter && typ.routerOnly {
// Router only count should have increased.
if got := routerOnly.Value(); got != 1 {
t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 1", got)
}
}
})
}
}
}
@@ -1762,7 +1584,6 @@ func TestCallsToNeighborCache(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
UseNeighborCache: true,
})
{
if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
File diff suppressed because it is too large Load Diff
-15
View File
@@ -3,18 +3,6 @@ load("//tools/go_generics:defs.bzl", "go_template_instance")
package(licenses = ["notice"])
go_template_instance(
name = "linkaddrentry_list",
out = "linkaddrentry_list.go",
package = "stack",
prefix = "linkAddrEntry",
template = "//pkg/ilist:generic_list",
types = {
"Element": "*linkAddrEntry",
"Linker": "*linkAddrEntry",
},
)
go_template_instance(
name = "neighbor_entry_list",
out = "neighbor_entry_list.go",
@@ -62,8 +50,6 @@ go_library(
"iptables_state.go",
"iptables_targets.go",
"iptables_types.go",
"linkaddrcache.go",
"linkaddrentry_list.go",
"neighbor_cache.go",
"neighbor_entry.go",
"neighbor_entry_list.go",
@@ -141,7 +127,6 @@ go_test(
size = "small",
srcs = [
"forwarding_test.go",
"linkaddrcache_test.go",
"neighbor_cache_test.go",
"neighbor_entry_test.go",
"nic_test.go",
File diff suppressed because it is too large Load Diff
-359
View File
@@ -1,359 +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
import (
"fmt"
"time"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
)
const linkAddrCacheSize = 512 // max cache entries
// linkAddrCache is a fixed-sized cache mapping IP addresses to link addresses.
//
// The entries are stored in a ring buffer, oldest entry replaced first.
//
// This struct is safe for concurrent use.
type linkAddrCache struct {
nic *nic
linkRes LinkAddressResolver
// ageLimit is how long a cache entry is valid for.
ageLimit time.Duration
// resolutionTimeout is the amount of time to wait for a link request to
// resolve an address.
resolutionTimeout time.Duration
// resolutionAttempts is the number of times an address is attempted to be
// resolved before failing.
resolutionAttempts int
mu struct {
sync.Mutex
table map[tcpip.Address]*linkAddrEntry
lru linkAddrEntryList
}
}
// entryState controls the state of a single entry in the cache.
type entryState int
const (
// incomplete means that there is an outstanding request to resolve the
// address. This is the initial state.
incomplete entryState = iota
// ready means that the address has been resolved and can be used.
ready
)
// String implements Stringer.
func (s entryState) String() string {
switch s {
case incomplete:
return "incomplete"
case ready:
return "ready"
default:
return fmt.Sprintf("unknown(%d)", s)
}
}
// A linkAddrEntry is an entry in the linkAddrCache.
// This struct is thread-compatible.
type linkAddrEntry struct {
// linkAddrEntryEntry access is synchronized by the linkAddrCache lock.
linkAddrEntryEntry
cache *linkAddrCache
mu struct {
sync.RWMutex
addr tcpip.Address
linkAddr tcpip.LinkAddress
expiration time.Time
s entryState
// done is closed when address resolution is complete. It is nil iff s is
// incomplete and resolution is not yet in progress.
done chan struct{}
// onResolve is called with the result of address resolution.
onResolve []func(LinkResolutionResult)
}
}
func (e *linkAddrEntry) notifyCompletionLocked(linkAddr tcpip.LinkAddress) {
res := LinkResolutionResult{LinkAddress: linkAddr, Success: len(linkAddr) != 0}
for _, callback := range e.mu.onResolve {
callback(res)
}
e.mu.onResolve = nil
if ch := e.mu.done; ch != nil {
close(ch)
e.mu.done = nil
// Dequeue the pending packets in a new goroutine to not hold up the current
// goroutine as writing packets may be a costly operation.
//
// At the time of writing, when writing packets, a neighbor's link address
// is resolved (which ends up obtaining the entry's lock) while holding the
// link resolution queue's lock. Dequeuing packets in a new goroutine avoids
// a lock ordering violation.
go e.cache.nic.linkResQueue.dequeue(ch, linkAddr, len(linkAddr) != 0)
}
}
// changeStateLocked sets the entry's state to ns.
//
// The entry's expiration is bumped up to the greater of itself and the passed
// expiration; the zero value indicates immediate expiration, and is set
// unconditionally - this is an implementation detail that allows for entries
// to be reused.
//
// Precondition: e.mu must be locked
func (e *linkAddrEntry) changeStateLocked(ns entryState, expiration time.Time) {
if e.mu.s == incomplete && ns == ready {
e.notifyCompletionLocked(e.mu.linkAddr)
}
if expiration.IsZero() || expiration.After(e.mu.expiration) {
e.mu.expiration = expiration
}
e.mu.s = ns
}
// add adds a k -> v mapping to the cache.
func (c *linkAddrCache) add(k tcpip.Address, v tcpip.LinkAddress) {
// Calculate expiration time before acquiring the lock, since expiration is
// relative to the time when information was learned, rather than when it
// happened to be inserted into the cache.
expiration := time.Now().Add(c.ageLimit)
c.mu.Lock()
entry := c.getOrCreateEntryLocked(k)
entry.mu.Lock()
defer entry.mu.Unlock()
c.mu.Unlock()
entry.mu.linkAddr = v
entry.changeStateLocked(ready, expiration)
}
// getOrCreateEntryLocked retrieves a cache entry associated with k. The
// returned entry is always refreshed in the cache (it is reachable via the
// map, and its place is bumped in LRU).
//
// If a matching entry exists in the cache, it is returned. If no matching
// entry exists and the cache is full, an existing entry is evicted via LRU,
// reset to state incomplete, and returned. If no matching entry exists and the
// cache is not full, a new entry with state incomplete is allocated and
// returned.
func (c *linkAddrCache) getOrCreateEntryLocked(k tcpip.Address) *linkAddrEntry {
if entry, ok := c.mu.table[k]; ok {
c.mu.lru.Remove(entry)
c.mu.lru.PushFront(entry)
return entry
}
var entry *linkAddrEntry
if len(c.mu.table) == linkAddrCacheSize {
entry = c.mu.lru.Back()
entry.mu.Lock()
delete(c.mu.table, entry.mu.addr)
c.mu.lru.Remove(entry)
// Wake waiters and mark the soon-to-be-reused entry as expired.
entry.notifyCompletionLocked("" /* linkAddr */)
entry.mu.Unlock()
} else {
entry = new(linkAddrEntry)
}
*entry = linkAddrEntry{
cache: c,
}
entry.mu.Lock()
entry.mu.addr = k
entry.mu.s = incomplete
entry.mu.Unlock()
c.mu.table[k] = entry
c.mu.lru.PushFront(entry)
return entry
}
// get reports any known link address for addr.
func (c *linkAddrCache) get(addr, localAddr tcpip.Address, onResolve func(LinkResolutionResult)) (tcpip.LinkAddress, <-chan struct{}, tcpip.Error) {
c.mu.Lock()
entry := c.getOrCreateEntryLocked(addr)
entry.mu.Lock()
defer entry.mu.Unlock()
c.mu.Unlock()
switch s := entry.mu.s; s {
case ready:
if !time.Now().After(entry.mu.expiration) {
// Not expired.
if onResolve != nil {
onResolve(LinkResolutionResult{LinkAddress: entry.mu.linkAddr, Success: true})
}
return entry.mu.linkAddr, nil, nil
}
entry.changeStateLocked(incomplete, time.Time{})
fallthrough
case incomplete:
if onResolve != nil {
entry.mu.onResolve = append(entry.mu.onResolve, onResolve)
}
if entry.mu.done == nil {
entry.mu.done = make(chan struct{})
go c.startAddressResolution(addr, localAddr, entry.mu.done) // S/R-SAFE: link non-savable; wakers dropped synchronously.
}
return entry.mu.linkAddr, entry.mu.done, &tcpip.ErrWouldBlock{}
default:
panic(fmt.Sprintf("invalid cache entry state: %s", s))
}
}
func (c *linkAddrCache) startAddressResolution(k tcpip.Address, localAddr tcpip.Address, done <-chan struct{}) {
for i := 0; ; i++ {
// Send link request, then wait for the timeout limit and check
// whether the request succeeded.
c.linkRes.LinkAddressRequest(k, localAddr, "" /* linkAddr */)
select {
case now := <-time.After(c.resolutionTimeout):
if stop := c.checkLinkRequest(now, k, i); stop {
return
}
case <-done:
return
}
}
}
// checkLinkRequest checks whether previous attempt to resolve address has
// succeeded and mark the entry accordingly. Returns true if request can stop,
// false if another request should be sent.
func (c *linkAddrCache) checkLinkRequest(now time.Time, k tcpip.Address, attempt int) bool {
c.mu.Lock()
defer c.mu.Unlock()
entry, ok := c.mu.table[k]
if !ok {
// Entry was evicted from the cache.
return true
}
entry.mu.Lock()
defer entry.mu.Unlock()
switch s := entry.mu.s; s {
case ready:
// Entry was made ready by resolver.
case incomplete:
if attempt+1 < c.resolutionAttempts {
// No response yet, need to send another ARP request.
return false
}
// Max number of retries reached, delete entry.
entry.notifyCompletionLocked("" /* linkAddr */)
delete(c.mu.table, k)
default:
panic(fmt.Sprintf("invalid cache entry state: %s", s))
}
return true
}
func (c *linkAddrCache) init(nic *nic, ageLimit, resolutionTimeout time.Duration, resolutionAttempts int, linkRes LinkAddressResolver) {
*c = linkAddrCache{
nic: nic,
linkRes: linkRes,
ageLimit: ageLimit,
resolutionTimeout: resolutionTimeout,
resolutionAttempts: resolutionAttempts,
}
c.mu.Lock()
c.mu.table = make(map[tcpip.Address]*linkAddrEntry, linkAddrCacheSize)
c.mu.Unlock()
}
var _ neighborTable = (*linkAddrCache)(nil)
func (*linkAddrCache) neighbors() ([]NeighborEntry, tcpip.Error) {
return nil, &tcpip.ErrNotSupported{}
}
func (c *linkAddrCache) addStaticEntry(addr tcpip.Address, linkAddr tcpip.LinkAddress) {
c.add(addr, linkAddr)
}
func (*linkAddrCache) remove(addr tcpip.Address) tcpip.Error {
return &tcpip.ErrNotSupported{}
}
func (*linkAddrCache) removeAll() tcpip.Error {
return &tcpip.ErrNotSupported{}
}
func (c *linkAddrCache) handleProbe(addr tcpip.Address, linkAddr tcpip.LinkAddress) {
if len(linkAddr) != 0 {
// NUD allows probes without a link address but linkAddrCache
// is a simple neighbor table which does not implement NUD.
//
// As per RFC 4861 section 4.3,
//
// Source link-layer address
// The link-layer address for the sender. MUST NOT be
// included when the source IP address is the
// unspecified address. Otherwise, on link layers
// that have addresses this option MUST be included in
// multicast solicitations and SHOULD be included in
// unicast solicitations.
c.add(addr, linkAddr)
}
}
func (c *linkAddrCache) handleConfirmation(addr tcpip.Address, linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) {
if len(linkAddr) != 0 {
// NUD allows confirmations without a link address but linkAddrCache
// is a simple neighbor table which does not implement NUD.
//
// As per RFC 4861 section 4.4,
//
// Target link-layer address
// The link-layer address for the target, i.e., the
// sender of the advertisement. This option MUST be
// included on link layers that have addresses when
// responding to multicast solicitations. When
// responding to a unicast Neighbor Solicitation this
// option SHOULD be included.
c.add(addr, linkAddr)
}
}
func (c *linkAddrCache) handleUpperLevelConfirmation(tcpip.Address) {}
func (*linkAddrCache) nudConfig() (NUDConfigurations, tcpip.Error) {
return NUDConfigurations{}, &tcpip.ErrNotSupported{}
}
func (*linkAddrCache) setNUDConfig(NUDConfigurations) tcpip.Error {
return &tcpip.ErrNotSupported{}
}
-291
View File
@@ -1,291 +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
import (
"fmt"
"math"
"sync/atomic"
"testing"
"time"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
)
type testaddr struct {
addr tcpip.Address
linkAddr tcpip.LinkAddress
}
var testAddrs = func() []testaddr {
var addrs []testaddr
for i := 0; i < 4*linkAddrCacheSize; i++ {
addr := fmt.Sprintf("Addr%06d", i)
addrs = append(addrs, testaddr{
addr: tcpip.Address(addr),
linkAddr: tcpip.LinkAddress("Link" + addr),
})
}
return addrs
}()
type testLinkAddressResolver struct {
cache *linkAddrCache
delay time.Duration
onLinkAddressRequest func()
}
func (r *testLinkAddressResolver) LinkAddressRequest(targetAddr, _ tcpip.Address, _ tcpip.LinkAddress) tcpip.Error {
// TODO(gvisor.dev/issue/5141): Use a fake clock.
time.AfterFunc(r.delay, func() { r.fakeRequest(targetAddr) })
if f := r.onLinkAddressRequest; f != nil {
f()
}
return nil
}
func (r *testLinkAddressResolver) fakeRequest(addr tcpip.Address) {
for _, ta := range testAddrs {
if ta.addr == addr {
r.cache.add(ta.addr, ta.linkAddr)
break
}
}
}
func (*testLinkAddressResolver) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) {
if addr == "broadcast" {
return "mac_broadcast", true
}
return "", false
}
func (*testLinkAddressResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
return 1
}
func getBlocking(c *linkAddrCache, addr tcpip.Address) (tcpip.LinkAddress, tcpip.Error) {
var attemptedResolution bool
for {
got, ch, err := c.get(addr, "", nil)
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
if attemptedResolution {
return got, &tcpip.ErrTimeout{}
}
attemptedResolution = true
<-ch
continue
}
return got, err
}
}
func newEmptyNIC() *nic {
n := &nic{}
n.linkResQueue.init(n)
return n
}
func TestCacheOverflow(t *testing.T) {
var c linkAddrCache
c.init(newEmptyNIC(), 1<<63-1, 1*time.Second, 3, nil)
for i := len(testAddrs) - 1; i >= 0; i-- {
e := testAddrs[i]
c.add(e.addr, e.linkAddr)
got, _, err := c.get(e.addr, "", nil)
if err != nil {
t.Errorf("insert %d, c.get(%s, '', nil): %s", i, e.addr, err)
}
if got != e.linkAddr {
t.Errorf("insert %d, got c.get(%s, '', nil) = %s, want = %s", i, e.addr, got, e.linkAddr)
}
}
// Expect to find at least half of the most recent entries.
for i := 0; i < linkAddrCacheSize/2; i++ {
e := testAddrs[i]
got, _, err := c.get(e.addr, "", nil)
if err != nil {
t.Errorf("check %d, c.get(%s, '', nil): %s", i, e.addr, err)
}
if got != e.linkAddr {
t.Errorf("check %d, got c.get(%s, '', nil) = %s, want = %s", i, e.addr, got, e.linkAddr)
}
}
// The earliest entries should no longer be in the cache.
c.mu.Lock()
defer c.mu.Unlock()
for i := len(testAddrs) - 1; i >= len(testAddrs)-linkAddrCacheSize; i-- {
e := testAddrs[i]
if entry, ok := c.mu.table[e.addr]; ok {
t.Errorf("unexpected entry at c.mu.table[%s]: %#v", e.addr, entry)
}
}
}
func TestCacheConcurrent(t *testing.T) {
var c linkAddrCache
linkRes := &testLinkAddressResolver{cache: &c}
c.init(newEmptyNIC(), 1<<63-1, 1*time.Second, 3, linkRes)
var wg sync.WaitGroup
for r := 0; r < 16; r++ {
wg.Add(1)
go func() {
for _, e := range testAddrs {
c.add(e.addr, e.linkAddr)
}
wg.Done()
}()
}
wg.Wait()
// All goroutines add in the same order and add more values than
// can fit in the cache, so our eviction strategy requires that
// the last entry be present and the first be missing.
e := testAddrs[len(testAddrs)-1]
got, _, err := c.get(e.addr, "", nil)
if err != nil {
t.Errorf("c.get(%s, '', nil): %s", e.addr, err)
}
if got != e.linkAddr {
t.Errorf("got c.get(%s, '', nil) = %s, want = %s", e.addr, got, e.linkAddr)
}
e = testAddrs[0]
c.mu.Lock()
defer c.mu.Unlock()
if entry, ok := c.mu.table[e.addr]; ok {
t.Errorf("unexpected entry at c.mu.table[%s]: %#v", e.addr, entry)
}
}
func TestCacheAgeLimit(t *testing.T) {
var c linkAddrCache
linkRes := &testLinkAddressResolver{cache: &c}
c.init(newEmptyNIC(), 1*time.Millisecond, 1*time.Second, 3, linkRes)
e := testAddrs[0]
c.add(e.addr, e.linkAddr)
time.Sleep(50 * time.Millisecond)
_, _, err := c.get(e.addr, "", nil)
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
t.Errorf("got c.get(%s, '', nil) = %s, want = ErrWouldBlock", e.addr, err)
}
}
func TestCacheReplace(t *testing.T) {
var c linkAddrCache
c.init(newEmptyNIC(), 1<<63-1, 1*time.Second, 3, nil)
e := testAddrs[0]
l2 := e.linkAddr + "2"
c.add(e.addr, e.linkAddr)
got, _, err := c.get(e.addr, "", nil)
if err != nil {
t.Errorf("c.get(%s, '', nil): %s", e.addr, err)
}
if got != e.linkAddr {
t.Errorf("got c.get(%s, '', nil) = %s, want = %s", e.addr, got, e.linkAddr)
}
c.add(e.addr, l2)
got, _, err = c.get(e.addr, "", nil)
if err != nil {
t.Errorf("c.get(%s, '', nil): %s", e.addr, err)
}
if got != l2 {
t.Errorf("got c.get(%s, '', nil) = %s, want = %s", e.addr, got, l2)
}
}
func TestCacheResolution(t *testing.T) {
// There is a race condition causing this test to fail when the executor
// takes longer than the resolution timeout to call linkAddrCache.get. This
// is especially common when this test is run with gotsan.
//
// Using a large resolution timeout decreases the probability of experiencing
// this race condition and does not affect how long this test takes to run.
var c linkAddrCache
linkRes := &testLinkAddressResolver{cache: &c}
c.init(newEmptyNIC(), 1<<63-1, math.MaxInt64, 1, linkRes)
for i, ta := range testAddrs {
got, err := getBlocking(&c, ta.addr)
if err != nil {
t.Errorf("check %d, getBlocking(_, %s): %s", i, ta.addr, err)
}
if got != ta.linkAddr {
t.Errorf("check %d, got getBlocking(_, %s) = %s, want = %s", i, ta.addr, got, ta.linkAddr)
}
}
// Check that after resolved, address stays in the cache and never returns WouldBlock.
for i := 0; i < 10; i++ {
e := testAddrs[len(testAddrs)-1]
got, _, err := c.get(e.addr, "", nil)
if err != nil {
t.Errorf("c.get(%s, '', nil): %s", e.addr, err)
}
if got != e.linkAddr {
t.Errorf("got c.get(%s, '', nil) = %s, want = %s", e.addr, got, e.linkAddr)
}
}
}
func TestCacheResolutionFailed(t *testing.T) {
var c linkAddrCache
linkRes := &testLinkAddressResolver{cache: &c}
c.init(newEmptyNIC(), 1<<63-1, 10*time.Millisecond, 5, linkRes)
var requestCount uint32
linkRes.onLinkAddressRequest = func() {
atomic.AddUint32(&requestCount, 1)
}
// First, sanity check that resolution is working...
e := testAddrs[0]
got, err := getBlocking(&c, e.addr)
if err != nil {
t.Errorf("getBlocking(_, %s): %s", e.addr, err)
}
if got != e.linkAddr {
t.Errorf("got getBlocking(_, %s) = %s, want = %s", e.addr, got, e.linkAddr)
}
before := atomic.LoadUint32(&requestCount)
e.addr += "2"
a, err := getBlocking(&c, e.addr)
if _, ok := err.(*tcpip.ErrTimeout); !ok {
t.Errorf("got getBlocking(_, %s) = (%s, %s), want = (_, %s)", e.addr, a, err, &tcpip.ErrTimeout{})
}
if got, want := int(atomic.LoadUint32(&requestCount)-before), c.resolutionAttempts; got != want {
t.Errorf("got link address request count = %d, want = %d", got, want)
}
}
func TestCacheResolutionTimeout(t *testing.T) {
resolverDelay := 500 * time.Millisecond
expiration := resolverDelay / 10
var c linkAddrCache
linkRes := &testLinkAddressResolver{cache: &c, delay: resolverDelay}
c.init(newEmptyNIC(), expiration, 1*time.Millisecond, 3, linkRes)
e := testAddrs[0]
a, err := getBlocking(&c, e.addr)
if _, ok := err.(*tcpip.ErrTimeout); !ok {
t.Errorf("got getBlocking(_, %s) = (%s, %s), want = (_, %s)", e.addr, a, err, &tcpip.ErrTimeout{})
}
}
File diff suppressed because it is too large Load Diff
+2 -36
View File
@@ -266,30 +266,6 @@ func (n *neighborCache) setConfig(config NUDConfigurations) {
n.state.SetConfig(config)
}
var _ neighborTable = (*neighborCache)(nil)
func (n *neighborCache) neighbors() ([]NeighborEntry, tcpip.Error) {
return n.entries(), nil
}
func (n *neighborCache) get(addr, localAddr tcpip.Address, onResolve func(LinkResolutionResult)) (tcpip.LinkAddress, <-chan struct{}, tcpip.Error) {
entry, ch, err := n.entry(addr, localAddr, onResolve)
return entry.LinkAddr, ch, err
}
func (n *neighborCache) remove(addr tcpip.Address) tcpip.Error {
if !n.removeEntry(addr) {
return &tcpip.ErrBadAddress{}
}
return nil
}
func (n *neighborCache) removeAll() tcpip.Error {
n.clear()
return nil
}
// handleProbe handles a neighbor probe as defined by RFC 4861 section 7.2.3.
//
// Validation of the probe is expected to be handled by the caller.
@@ -331,17 +307,8 @@ func (n *neighborCache) handleUpperLevelConfirmation(addr tcpip.Address) {
}
}
func (n *neighborCache) nudConfig() (NUDConfigurations, tcpip.Error) {
return n.config(), nil
}
func (n *neighborCache) setNUDConfig(c NUDConfigurations) tcpip.Error {
n.setConfig(c)
return nil
}
func newNeighborCache(nic *nic, r LinkAddressResolver) *neighborCache {
n := &neighborCache{
func (n *neighborCache) init(nic *nic, r LinkAddressResolver) {
*n = neighborCache{
nic: nic,
state: NewNUDState(nic.stack.nudConfigs, nic.stack.randomGenerator),
linkRes: r,
@@ -349,5 +316,4 @@ func newNeighborCache(nic *nic, r LinkAddressResolver) *neighborCache {
n.mu.Lock()
n.mu.cache = make(map[tcpip.Address]*neighborEntry, neighborCacheSize)
n.mu.Unlock()
return n
}
+2 -2
View File
@@ -84,7 +84,7 @@ func newTestNeighborResolver(nudDisp NUDDispatcher, config NUDConfigurations, cl
entries: newTestEntryStore(),
delay: typicalLatency,
}
linkRes.neigh = newNeighborCache(&nic{
linkRes.neigh.init(&nic{
stack: &Stack{
clock: clock,
nudDisp: nudDisp,
@@ -187,7 +187,7 @@ func (s *testEntryStore) set(i int, linkAddr tcpip.LinkAddress) {
// neighbor probe.
type testNeighborResolver struct {
clock tcpip.Clock
neigh *neighborCache
neigh neighborCache
entries *testEntryStore
delay time.Duration
onLinkAddressRequest func()
+9 -9
View File
@@ -239,16 +239,16 @@ func entryTestSetup(c NUDConfigurations) (*neighborEntry, *testNUDDispatcher, *e
var linkRes entryTestLinkResolver
// Stub out the neighbor cache to verify deletion from the cache.
neigh := newNeighborCache(&nic, &linkRes)
l := linkResolver{
resolver: &linkRes,
neighborTable: neigh,
l := &linkResolver{
resolver: &linkRes,
}
entry := newNeighborEntry(neigh, entryTestAddr1 /* remoteAddr */, neigh.state)
neigh.mu.Lock()
neigh.mu.cache[entryTestAddr1] = entry
neigh.mu.Unlock()
nic.linkAddrResolvers = map[tcpip.NetworkProtocolNumber]linkResolver{
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,
}
+23 -41
View File
@@ -24,37 +24,23 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/header"
)
type neighborTable interface {
neighbors() ([]NeighborEntry, tcpip.Error)
addStaticEntry(tcpip.Address, tcpip.LinkAddress)
get(addr, localAddr tcpip.Address, onResolve func(LinkResolutionResult)) (tcpip.LinkAddress, <-chan struct{}, tcpip.Error)
remove(tcpip.Address) tcpip.Error
removeAll() tcpip.Error
handleProbe(tcpip.Address, tcpip.LinkAddress)
handleConfirmation(tcpip.Address, tcpip.LinkAddress, ReachabilityConfirmationFlags)
handleUpperLevelConfirmation(tcpip.Address)
nudConfig() (NUDConfigurations, tcpip.Error)
setNUDConfig(NUDConfigurations) tcpip.Error
}
var _ NetworkInterface = (*nic)(nil)
type linkResolver struct {
resolver LinkAddressResolver
neighborTable neighborTable
neigh neighborCache
}
func (l *linkResolver) getNeighborLinkAddress(addr, localAddr tcpip.Address, onResolve func(LinkResolutionResult)) (tcpip.LinkAddress, <-chan struct{}, tcpip.Error) {
return l.neighborTable.get(addr, localAddr, onResolve)
entry, ch, err := l.neigh.entry(addr, localAddr, onResolve)
return entry.LinkAddr, ch, err
}
func (l *linkResolver) confirmReachable(addr tcpip.Address) {
l.neighborTable.handleUpperLevelConfirmation(addr)
l.neigh.handleUpperLevelConfirmation(addr)
}
var _ NetworkInterface = (*nic)(nil)
// nic represents a "network interface card" to which the networking stack is
// attached.
type nic struct {
@@ -70,7 +56,7 @@ type nic struct {
// The network endpoints themselves may be modified by calling the interface's
// methods, but the map reference and entries must be constant.
networkEndpoints map[tcpip.NetworkProtocolNumber]NetworkEndpoint
linkAddrResolvers map[tcpip.NetworkProtocolNumber]linkResolver
linkAddrResolvers map[tcpip.NetworkProtocolNumber]*linkResolver
// enabled is set to 1 when the NIC is enabled and 0 when it is disabled.
//
@@ -165,7 +151,7 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC
context: ctx,
stats: makeNICStats(),
networkEndpoints: make(map[tcpip.NetworkProtocolNumber]NetworkEndpoint),
linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]linkResolver),
linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]*linkResolver),
}
nic.linkResQueue.init(nic)
nic.mu.packetEPs = make(map[tcpip.NetworkProtocolNumber]*packetEndpointList)
@@ -185,17 +171,8 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC
if resolutionRequired {
if r, ok := netEP.(LinkAddressResolver); ok {
l := linkResolver{
resolver: r,
}
if stack.useNeighborCache {
l.neighborTable = newNeighborCache(nic, r)
} else {
cache := new(linkAddrCache)
cache.init(nic, ageLimit, resolutionTimeout, resolutionAttempts, r)
l.neighborTable = cache
}
l := &linkResolver{resolver: r}
l.neigh.init(nic, r)
nic.linkAddrResolvers[r.LinkAddressProtocol()] = l
}
}
@@ -640,7 +617,7 @@ func (n *nic) getLinkAddress(addr, localAddr tcpip.Address, protocol tcpip.Netwo
func (n *nic) neighbors(protocol tcpip.NetworkProtocolNumber) ([]NeighborEntry, tcpip.Error) {
if linkRes, ok := n.linkAddrResolvers[protocol]; ok {
return linkRes.neighborTable.neighbors()
return linkRes.neigh.entries(), nil
}
return nil, &tcpip.ErrNotSupported{}
@@ -648,7 +625,7 @@ func (n *nic) neighbors(protocol tcpip.NetworkProtocolNumber) ([]NeighborEntry,
func (n *nic) addStaticNeighbor(addr tcpip.Address, protocol tcpip.NetworkProtocolNumber, linkAddress tcpip.LinkAddress) tcpip.Error {
if linkRes, ok := n.linkAddrResolvers[protocol]; ok {
linkRes.neighborTable.addStaticEntry(addr, linkAddress)
linkRes.neigh.addStaticEntry(addr, linkAddress)
return nil
}
@@ -657,7 +634,10 @@ func (n *nic) addStaticNeighbor(addr tcpip.Address, protocol tcpip.NetworkProtoc
func (n *nic) removeNeighbor(protocol tcpip.NetworkProtocolNumber, addr tcpip.Address) tcpip.Error {
if linkRes, ok := n.linkAddrResolvers[protocol]; ok {
return linkRes.neighborTable.remove(addr)
if !linkRes.neigh.removeEntry(addr) {
return &tcpip.ErrBadAddress{}
}
return nil
}
return &tcpip.ErrNotSupported{}
@@ -665,7 +645,8 @@ func (n *nic) removeNeighbor(protocol tcpip.NetworkProtocolNumber, addr tcpip.Ad
func (n *nic) clearNeighbors(protocol tcpip.NetworkProtocolNumber) tcpip.Error {
if linkRes, ok := n.linkAddrResolvers[protocol]; ok {
return linkRes.neighborTable.removeAll()
linkRes.neigh.clear()
return nil
}
return &tcpip.ErrNotSupported{}
@@ -923,7 +904,7 @@ func (n *nic) Name() string {
// nudConfigs gets the NUD configurations for n.
func (n *nic) nudConfigs(protocol tcpip.NetworkProtocolNumber) (NUDConfigurations, tcpip.Error) {
if linkRes, ok := n.linkAddrResolvers[protocol]; ok {
return linkRes.neighborTable.nudConfig()
return linkRes.neigh.config(), nil
}
return NUDConfigurations{}, &tcpip.ErrNotSupported{}
@@ -936,7 +917,8 @@ func (n *nic) nudConfigs(protocol tcpip.NetworkProtocolNumber) (NUDConfiguration
func (n *nic) setNUDConfigs(protocol tcpip.NetworkProtocolNumber, c NUDConfigurations) tcpip.Error {
if linkRes, ok := n.linkAddrResolvers[protocol]; ok {
c.resetInvalidFields()
return linkRes.neighborTable.setNUDConfig(c)
linkRes.neigh.setConfig(c)
return nil
}
return &tcpip.ErrNotSupported{}
@@ -979,7 +961,7 @@ func (n *nic) isValidForOutgoing(ep AssignableAddressEndpoint) bool {
// HandleNeighborProbe implements NetworkInterface.
func (n *nic) HandleNeighborProbe(protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, linkAddr tcpip.LinkAddress) tcpip.Error {
if l, ok := n.linkAddrResolvers[protocol]; ok {
l.neighborTable.handleProbe(addr, linkAddr)
l.neigh.handleProbe(addr, linkAddr)
return nil
}
@@ -989,7 +971,7 @@ func (n *nic) HandleNeighborProbe(protocol tcpip.NetworkProtocolNumber, addr tcp
// HandleNeighborConfirmation implements NetworkInterface.
func (n *nic) HandleNeighborConfirmation(protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) tcpip.Error {
if l, ok := n.linkAddrResolvers[protocol]; ok {
l.neighborTable.handleConfirmation(addr, linkAddr, flags)
l.neigh.handleConfirmation(addr, linkAddr, flags)
return nil
}
-9
View File
@@ -101,7 +101,6 @@ func TestNUDFunctions(t *testing.T) {
clock := faketime.NewManualClock()
s := stack.New(stack.Options{
NUDConfigs: stack.DefaultNUDConfigurations(),
UseNeighborCache: true,
NetworkProtocols: test.netProtoFactory,
Clock: clock,
})
@@ -206,7 +205,6 @@ func TestDefaultNUDConfigurations(t *testing.T) {
// address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: stack.DefaultNUDConfigurations(),
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -261,7 +259,6 @@ func TestNUDConfigurationsBaseReachableTime(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -318,7 +315,6 @@ func TestNUDConfigurationsMinRandomFactor(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -398,7 +394,6 @@ func TestNUDConfigurationsMaxRandomFactor(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -460,7 +455,6 @@ func TestNUDConfigurationsRetransmitTimer(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -512,7 +506,6 @@ func TestNUDConfigurationsDelayFirstProbeTime(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -564,7 +557,6 @@ func TestNUDConfigurationsMaxMulticastProbes(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -616,7 +608,6 @@ func TestNUDConfigurationsMaxUnicastProbes(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
+4 -4
View File
@@ -53,7 +53,7 @@ type Route struct {
// linkRes is set if link address resolution is enabled for this protocol on
// the route's NIC.
linkRes linkResolver
linkRes *linkResolver
}
type routeInfo struct {
@@ -184,7 +184,7 @@ func makeRoute(netProto tcpip.NetworkProtocolNumber, gateway, localAddr, remoteA
return r
}
if r.linkRes.resolver == nil {
if r.linkRes == nil {
return r
}
@@ -400,7 +400,7 @@ func (r *Route) IsResolutionRequired() bool {
}
func (r *Route) isResolutionRequiredRLocked() bool {
return len(r.mu.remoteLinkAddress) == 0 && r.linkRes.resolver != nil && r.isValidForOutgoingRLocked() && !r.local()
return len(r.mu.remoteLinkAddress) == 0 && r.linkRes != nil && r.isValidForOutgoingRLocked() && !r.local()
}
func (r *Route) isValidForOutgoing() bool {
@@ -528,7 +528,7 @@ func (r *Route) IsOutboundBroadcast() bool {
// "Reachable" is defined as having full-duplex communication between the
// local and remote ends of the route.
func (r *Route) ConfirmReachable() {
if r.linkRes.resolver != nil {
if r.linkRes != nil {
r.linkRes.confirmReachable(r.nextHop())
}
}
-18
View File
@@ -434,12 +434,6 @@ type Stack struct {
// nudConfigs is the default NUD configurations used by interfaces.
nudConfigs NUDConfigurations
// useNeighborCache indicates whether ARP and NDP packets should be handled
// by the NIC's neighborCache instead of linkAddrCache.
//
// TODO(gvisor.dev/issue/4658): Remove this field.
useNeighborCache bool
// nudDisp is the NUD event dispatcher that is used to send the netstack
// integrator NUD related events.
nudDisp NUDDispatcher
@@ -516,17 +510,6 @@ type Options struct {
// NUDConfigs is the default NUD configurations used by interfaces.
NUDConfigs NUDConfigurations
// UseNeighborCache is unused.
//
// TODO(gvisor.dev/issue/4658): Remove this field.
UseNeighborCache bool
// UseLinkAddrCache indicates that the legacy link address cache should be
// used for link resolution.
//
// TODO(gvisor.dev/issue/4658): Remove this field.
UseLinkAddrCache bool
// NUDDisp is the NUD event dispatcher that an integrator can provide to
// receive NUD related events.
NUDDisp NUDDispatcher
@@ -666,7 +649,6 @@ func New(opts Options) *Stack {
icmpRateLimiter: NewICMPRateLimiter(),
seed: generateRandUint32(),
nudConfigs: opts.NUDConfigs,
useNeighborCache: !opts.UseLinkAddrCache,
uniqueIDGenerator: opts.UniqueID,
nudDisp: opts.NUDDisp,
randomGenerator: mathrand.New(randSrc),
-1
View File
@@ -4324,7 +4324,6 @@ func TestClearNeighborCacheOnNICDisable(t *testing.T) {
clock := faketime.NewManualClock()
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
UseNeighborCache: true,
Clock: clock,
})
e := channel.New(0, 0, "")
@@ -487,30 +487,25 @@ func TestGetLinkAddress(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, useNeighborCache := range []bool{true, false} {
t.Run(fmt.Sprintf("UseNeighborCache=%t", useNeighborCache), func(t *testing.T) {
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
UseNeighborCache: useNeighborCache,
}
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
}
host1Stack, _ := setupStack(t, stackOpts, host1NICID, host2NICID)
host1Stack, _ := setupStack(t, stackOpts, host1NICID, host2NICID)
ch := make(chan stack.LinkResolutionResult, 1)
err := host1Stack.GetLinkAddress(host1NICID, test.remoteAddr, "", test.netProto, func(r stack.LinkResolutionResult) {
ch <- r
})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
t.Fatalf("got host1Stack.GetLinkAddress(%d, %s, '', %d, _) = %s, want = %s", host1NICID, test.remoteAddr, test.netProto, err, &tcpip.ErrWouldBlock{})
}
wantRes := stack.LinkResolutionResult{Success: test.expectedOk}
if test.expectedOk {
wantRes.LinkAddress = linkAddr2
}
if diff := cmp.Diff(wantRes, <-ch); diff != "" {
t.Fatalf("link resolution result mismatch (-want +got):\n%s", diff)
}
})
ch := make(chan stack.LinkResolutionResult, 1)
err := host1Stack.GetLinkAddress(host1NICID, test.remoteAddr, "", test.netProto, func(r stack.LinkResolutionResult) {
ch <- r
})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
t.Fatalf("got host1Stack.GetLinkAddress(%d, %s, '', %d, _) = %s, want = %s", host1NICID, test.remoteAddr, test.netProto, err, &tcpip.ErrWouldBlock{})
}
wantRes := stack.LinkResolutionResult{Success: test.expectedOk}
if test.expectedOk {
wantRes.LinkAddress = linkAddr2
}
if diff := cmp.Diff(wantRes, <-ch); diff != "" {
t.Fatalf("link resolution result mismatch (-want +got):\n%s", diff)
}
})
}
@@ -587,66 +582,61 @@ func TestRouteResolvedFields(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, useNeighborCache := range []bool{true, false} {
t.Run(fmt.Sprintf("UseNeighborCache=%t", useNeighborCache), func(t *testing.T) {
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
UseNeighborCache: useNeighborCache,
}
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
}
host1Stack, _ := setupStack(t, stackOpts, host1NICID, host2NICID)
r, err := host1Stack.FindRoute(host1NICID, "", test.remoteAddr, test.netProto, false /* multicastLoop */)
if err != nil {
t.Fatalf("host1Stack.FindRoute(%d, '', %s, %d, false): %s", host1NICID, test.remoteAddr, test.netProto, err)
}
defer r.Release()
host1Stack, _ := setupStack(t, stackOpts, host1NICID, host2NICID)
r, err := host1Stack.FindRoute(host1NICID, "", test.remoteAddr, test.netProto, false /* multicastLoop */)
if err != nil {
t.Fatalf("host1Stack.FindRoute(%d, '', %s, %d, false): %s", host1NICID, test.remoteAddr, test.netProto, err)
}
defer r.Release()
var wantRouteInfo stack.RouteInfo
wantRouteInfo.LocalLinkAddress = linkAddr1
wantRouteInfo.LocalAddress = test.localAddr
wantRouteInfo.RemoteAddress = test.remoteAddr
wantRouteInfo.NetProto = test.netProto
wantRouteInfo.Loop = stack.PacketOut
wantRouteInfo.RemoteLinkAddress = test.expectedLinkAddr
var wantRouteInfo stack.RouteInfo
wantRouteInfo.LocalLinkAddress = linkAddr1
wantRouteInfo.LocalAddress = test.localAddr
wantRouteInfo.RemoteAddress = test.remoteAddr
wantRouteInfo.NetProto = test.netProto
wantRouteInfo.Loop = stack.PacketOut
wantRouteInfo.RemoteLinkAddress = test.expectedLinkAddr
ch := make(chan stack.ResolvedFieldsResult, 1)
ch := make(chan stack.ResolvedFieldsResult, 1)
if !test.immediatelyResolvable {
wantUnresolvedRouteInfo := wantRouteInfo
wantUnresolvedRouteInfo.RemoteLinkAddress = ""
if !test.immediatelyResolvable {
wantUnresolvedRouteInfo := wantRouteInfo
wantUnresolvedRouteInfo.RemoteLinkAddress = ""
err := r.ResolvedFields(func(r stack.ResolvedFieldsResult) {
ch <- r
})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
t.Errorf("got r.ResolvedFields(_) = %s, want = %s", err, &tcpip.ErrWouldBlock{})
}
if diff := cmp.Diff(stack.ResolvedFieldsResult{RouteInfo: wantRouteInfo, Success: test.expectedSuccess}, <-ch, cmp.AllowUnexported(stack.RouteInfo{})); diff != "" {
t.Errorf("route resolve result mismatch (-want +got):\n%s", diff)
}
if !test.expectedSuccess {
return
}
// At this point the neighbor table should be populated so the route
// should be immediately resolvable.
}
if err := r.ResolvedFields(func(r stack.ResolvedFieldsResult) {
ch <- r
}); err != nil {
t.Errorf("r.ResolvedFields(_): %s", err)
}
select {
case routeResolveRes := <-ch:
if diff := cmp.Diff(stack.ResolvedFieldsResult{RouteInfo: wantRouteInfo, Success: true}, routeResolveRes, cmp.AllowUnexported(stack.RouteInfo{})); diff != "" {
t.Errorf("route resolve result from resolved route mismatch (-want +got):\n%s", diff)
}
default:
t.Fatal("expected route to be immediately resolvable")
}
err := r.ResolvedFields(func(r stack.ResolvedFieldsResult) {
ch <- r
})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
t.Errorf("got r.ResolvedFields(_) = %s, want = %s", err, &tcpip.ErrWouldBlock{})
}
if diff := cmp.Diff(stack.ResolvedFieldsResult{RouteInfo: wantRouteInfo, Success: test.expectedSuccess}, <-ch, cmp.AllowUnexported(stack.RouteInfo{})); diff != "" {
t.Errorf("route resolve result mismatch (-want +got):\n%s", diff)
}
if !test.expectedSuccess {
return
}
// At this point the neighbor table should be populated so the route
// should be immediately resolvable.
}
if err := r.ResolvedFields(func(r stack.ResolvedFieldsResult) {
ch <- r
}); err != nil {
t.Errorf("r.ResolvedFields(_): %s", err)
}
select {
case routeResolveRes := <-ch:
if diff := cmp.Diff(stack.ResolvedFieldsResult{RouteInfo: wantRouteInfo, Success: true}, routeResolveRes, cmp.AllowUnexported(stack.RouteInfo{})); diff != "" {
t.Errorf("route resolve result from resolved route mismatch (-want +got):\n%s", diff)
}
default:
t.Fatal("expected route to be immediately resolvable")
}
})
}
@@ -1065,7 +1055,6 @@ func TestTCPConfirmNeighborReachability(t *testing.T) {
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
Clock: clock,
UseNeighborCache: true,
}
host1StackOpts := stackOpts
host1StackOpts.NUDDisp = &nudDisp