mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Invoke address resolution upon subsequent traffic to Failed neighbor
Removes the period of time in which subseqeuent traffic to a Failed neighbor immediately fails with ErrNoLinkAddress. A Failed neighbor is one in which address resolution fails; or in other words, the neighbor's IP address cannot be translated to a MAC address. This means removing the Failed state for linkAddrCache and allowing transitiong out of Failed into Incomplete for neighborCache. Previously, both caches would transition entries to Failed after address resolution fails. In this state, any subsequent traffic requested within an unreachable time would immediately fail with ErrNoLinkAddress. This does not follow RFC 4861 section 7.3.3: If address resolution fails, the entry SHOULD be deleted, so that subsequent traffic to that neighbor invokes the next-hop determination procedure again. Invoking next-hop determination at this point ensures that alternate default routers are tried. The API for getting a link address for a given address, whether through the link address cache or the neighbor table, is updated to optionally take a callback which will be called when address resolution completes. This allows `Route` to handle completing link resolution internally, so callers of (*Route).Resolve (e.g. endpoints) don’t have to keep track of when it completes and update the Route accordingly. This change also removes the wakers from LinkAddressCache, NeighborCache, and Route in favor of the callbacks, and callers that previously used a waker can now just pass a callback to (*Route).Resolve that will notify the waker on resolution completion. Fixes #4796 Startblock: has LGTM from sbalana and then add reviewer ghanan PiperOrigin-RevId: 348597478
This commit is contained in:
committed by
gVisor bot
parent
620de250a4
commit
fee2cd640f
@@ -31,7 +31,7 @@ type PacketInfo struct {
|
||||
Pkt *stack.PacketBuffer
|
||||
Proto tcpip.NetworkProtocolNumber
|
||||
GSO *stack.GSO
|
||||
Route *stack.Route
|
||||
Route stack.RouteInfo
|
||||
}
|
||||
|
||||
// Notification is the interface for receiving notification from the packet
|
||||
@@ -230,15 +230,11 @@ func (e *Endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
|
||||
// WritePacket stores outbound packets into the channel.
|
||||
func (e *Endpoint) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
|
||||
// Clone r then release its resource so we only get the relevant fields from
|
||||
// stack.Route without holding a reference to a NIC's endpoint.
|
||||
route := r.Clone()
|
||||
route.Release()
|
||||
p := PacketInfo{
|
||||
Pkt: pkt,
|
||||
Proto: protocol,
|
||||
GSO: gso,
|
||||
Route: route,
|
||||
Route: r.GetFields(),
|
||||
}
|
||||
|
||||
e.q.Write(p)
|
||||
@@ -248,17 +244,13 @@ func (e *Endpoint) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.Ne
|
||||
|
||||
// WritePackets stores outbound packets into the channel.
|
||||
func (e *Endpoint) WritePackets(r *stack.Route, gso *stack.GSO, pkts stack.PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, *tcpip.Error) {
|
||||
// Clone r then release its resource so we only get the relevant fields from
|
||||
// stack.Route without holding a reference to a NIC's endpoint.
|
||||
route := r.Clone()
|
||||
route.Release()
|
||||
n := 0
|
||||
for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
|
||||
p := PacketInfo{
|
||||
Pkt: pkt,
|
||||
Proto: protocol,
|
||||
GSO: gso,
|
||||
Route: route,
|
||||
Route: r.GetFields(),
|
||||
}
|
||||
|
||||
if !e.q.Write(p) {
|
||||
|
||||
@@ -323,9 +323,8 @@ func TestPreserveSrcAddress(t *testing.T) {
|
||||
defer c.cleanup()
|
||||
|
||||
// Set LocalLinkAddress in route to the value of the bridged address.
|
||||
r := &stack.Route{
|
||||
LocalLinkAddress: baddr,
|
||||
}
|
||||
var r stack.Route
|
||||
r.LocalLinkAddress = baddr
|
||||
r.ResolveWith(raddr)
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
@@ -335,7 +334,7 @@ func TestPreserveSrcAddress(t *testing.T) {
|
||||
ReserveHeaderBytes: header.EthernetMinimumSize,
|
||||
Data: buffer.VectorisedView{},
|
||||
})
|
||||
if err := c.ep.WritePacket(r, nil /* gso */, proto, pkt); err != nil {
|
||||
if err := c.ep.WritePacket(&r, nil /* gso */, proto, pkt); err != nil {
|
||||
t.Fatalf("WritePacket failed: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ func TestInjectableEndpointDispatch(t *testing.T) {
|
||||
Data: buffer.NewViewFromBytes([]byte{0xFB}).ToVectorisedView(),
|
||||
})
|
||||
pkt.TransportHeader().Push(1)[0] = 0xFA
|
||||
packetRoute := stack.Route{RemoteAddress: dstIP}
|
||||
var packetRoute stack.Route
|
||||
packetRoute.RemoteAddress = dstIP
|
||||
|
||||
endpoint.WritePacket(&packetRoute, nil /* gso */, ipv4.ProtocolNumber, pkt)
|
||||
|
||||
@@ -73,7 +74,8 @@ func TestInjectableEndpointDispatchHdrOnly(t *testing.T) {
|
||||
Data: buffer.NewView(0).ToVectorisedView(),
|
||||
})
|
||||
pkt.TransportHeader().Push(1)[0] = 0xFA
|
||||
packetRoute := stack.Route{RemoteAddress: dstIP}
|
||||
var packetRoute stack.Route
|
||||
packetRoute.RemoteAddress = dstIP
|
||||
endpoint.WritePacket(&packetRoute, nil /* gso */, ipv4.ProtocolNumber, pkt)
|
||||
buf := make([]byte, 6500)
|
||||
bytesRead, err := sock.Read(buf)
|
||||
|
||||
@@ -154,8 +154,7 @@ func (e *endpoint) GSOMaxSize() uint32 {
|
||||
func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
|
||||
// WritePacket caller's do not set the following fields in PacketBuffer
|
||||
// so we populate them here.
|
||||
newRoute := r.Clone()
|
||||
pkt.EgressRoute = newRoute
|
||||
pkt.EgressRoute = r
|
||||
pkt.GSOOptions = gso
|
||||
pkt.NetworkProtocolNumber = protocol
|
||||
d := e.dispatchers[int(pkt.Hash)%len(e.dispatchers)]
|
||||
@@ -178,11 +177,6 @@ func (e *endpoint) WritePackets(_ *stack.Route, _ *stack.GSO, pkts stack.PacketB
|
||||
for pkt := pkts.Front(); pkt != nil; {
|
||||
d := e.dispatchers[int(pkt.Hash)%len(e.dispatchers)]
|
||||
nxt := pkt.Next()
|
||||
// Since qdisc can hold onto a packet for long we should Clone
|
||||
// the route here to ensure it doesn't get released while the
|
||||
// packet is still in our queue.
|
||||
newRoute := pkt.EgressRoute.Clone()
|
||||
pkt.EgressRoute = newRoute
|
||||
if !d.q.enqueue(pkt) {
|
||||
if enqueued > 0 {
|
||||
d.newPacketWaker.Assert()
|
||||
|
||||
@@ -61,6 +61,7 @@ func (q *packetBufferQueue) enqueue(s *stack.PacketBuffer) bool {
|
||||
q.mu.Lock()
|
||||
r := q.used < q.limit
|
||||
if r {
|
||||
s.EgressRoute.Acquire()
|
||||
q.list.PushBack(s)
|
||||
q.used++
|
||||
}
|
||||
|
||||
@@ -340,9 +340,8 @@ func TestPreserveSrcAddressInSend(t *testing.T) {
|
||||
|
||||
newLocalLinkAddress := tcpip.LinkAddress(strings.Repeat("0xFE", 6))
|
||||
// Set both remote and local link address in route.
|
||||
r := stack.Route{
|
||||
LocalLinkAddress: newLocalLinkAddress,
|
||||
}
|
||||
var r stack.Route
|
||||
r.LocalLinkAddress = newLocalLinkAddress
|
||||
r.ResolveWith(remoteLinkAddr)
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
|
||||
@@ -264,7 +264,7 @@ func (d *Device) encodePkt(info *channel.PacketInfo) (buffer.View, bool) {
|
||||
// If the packet does not already have link layer header, and the route
|
||||
// does not exist, we can't compute it. This is possibly a raw packet, tun
|
||||
// device doesn't support this at the moment.
|
||||
if info.Pkt.LinkHeader().View().IsEmpty() && info.Route.RemoteLinkAddress() == "" {
|
||||
if info.Pkt.LinkHeader().View().IsEmpty() && len(info.Route.RemoteLinkAddress) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ func (d *Device) encodePkt(info *channel.PacketInfo) (buffer.View, bool) {
|
||||
if d.hasFlags(linux.IFF_TAP) {
|
||||
// Add ethernet header if not provided.
|
||||
if info.Pkt.LinkHeader().View().IsEmpty() {
|
||||
d.endpoint.AddHeader(info.Route.LocalLinkAddress, info.Route.RemoteLinkAddress(), info.Proto, info.Pkt)
|
||||
d.endpoint.AddHeader(info.Route.LocalLinkAddress, info.Route.RemoteLinkAddress, info.Proto, info.Pkt)
|
||||
}
|
||||
vv.AppendView(info.Pkt.LinkHeader().View())
|
||||
}
|
||||
|
||||
@@ -441,9 +441,8 @@ func (*testInterface) Promiscuous() bool {
|
||||
}
|
||||
|
||||
func (t *testInterface) WritePacketToRemote(remoteLinkAddr tcpip.LinkAddress, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
|
||||
r := stack.Route{
|
||||
NetProto: protocol,
|
||||
}
|
||||
var r stack.Route
|
||||
r.NetProto = protocol
|
||||
r.ResolveWith(remoteLinkAddr)
|
||||
return t.LinkEndpoint.WritePacket(&r, gso, protocol, pkt)
|
||||
}
|
||||
@@ -557,8 +556,8 @@ func TestLinkAddressRequest(t *testing.T) {
|
||||
t.Fatal("expected to send a link address request")
|
||||
}
|
||||
|
||||
if got := pkt.Route.RemoteLinkAddress(); got != test.expectedRemoteLinkAddr {
|
||||
t.Errorf("got pkt.Route.RemoteLinkAddress() = %s, want = %s", got, test.expectedRemoteLinkAddr)
|
||||
if pkt.Route.RemoteLinkAddress != test.expectedRemoteLinkAddr {
|
||||
t.Errorf("got pkt.Route.RemoteLinkAddress = %s, want = %s", pkt.Route.RemoteLinkAddress, test.expectedRemoteLinkAddr)
|
||||
}
|
||||
|
||||
rep := header.ARP(stack.PayloadSince(pkt.Pkt.NetworkHeader()))
|
||||
|
||||
@@ -2669,8 +2669,8 @@ func TestPacketQueing(t *testing.T) {
|
||||
if p.Proto != header.IPv4ProtocolNumber {
|
||||
t.Errorf("got p.Proto = %d, want = %d", p.Proto, header.IPv4ProtocolNumber)
|
||||
}
|
||||
if got := p.Route.RemoteLinkAddress(); got != host2NICLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress() = %s, want = %s", got, host2NICLinkAddr)
|
||||
if p.Route.RemoteLinkAddress != host2NICLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, host2NICLinkAddr)
|
||||
}
|
||||
checker.IPv4(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
|
||||
checker.SrcAddr(host1IPv4Addr.AddressWithPrefix.Address),
|
||||
@@ -2712,8 +2712,8 @@ func TestPacketQueing(t *testing.T) {
|
||||
if p.Proto != header.IPv4ProtocolNumber {
|
||||
t.Errorf("got p.Proto = %d, want = %d", p.Proto, header.IPv4ProtocolNumber)
|
||||
}
|
||||
if got := p.Route.RemoteLinkAddress(); got != host2NICLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress() = %s, want = %s", got, host2NICLinkAddr)
|
||||
if p.Route.RemoteLinkAddress != host2NICLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, host2NICLinkAddr)
|
||||
}
|
||||
checker.IPv4(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
|
||||
checker.SrcAddr(host1IPv4Addr.AddressWithPrefix.Address),
|
||||
@@ -2761,8 +2761,8 @@ func TestPacketQueing(t *testing.T) {
|
||||
if p.Proto != arp.ProtocolNumber {
|
||||
t.Errorf("got p.Proto = %d, want = %d", p.Proto, arp.ProtocolNumber)
|
||||
}
|
||||
if got := p.Route.RemoteLinkAddress(); got != header.EthernetBroadcastAddress {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress() = %s, want = %s", got, header.EthernetBroadcastAddress)
|
||||
if p.Route.RemoteLinkAddress != header.EthernetBroadcastAddress {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, header.EthernetBroadcastAddress)
|
||||
}
|
||||
rep := header.ARP(p.Pkt.NetworkHeader().View())
|
||||
if got := rep.Op(); got != header.ARPRequest {
|
||||
|
||||
@@ -149,9 +149,8 @@ func (*testInterface) Promiscuous() bool {
|
||||
}
|
||||
|
||||
func (t *testInterface) WritePacketToRemote(remoteLinkAddr tcpip.LinkAddress, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
|
||||
r := stack.Route{
|
||||
NetProto: protocol,
|
||||
}
|
||||
var r stack.Route
|
||||
r.NetProto = protocol
|
||||
r.ResolveWith(remoteLinkAddr)
|
||||
return t.LinkEndpoint.WritePacket(&r, gso, protocol, pkt)
|
||||
}
|
||||
@@ -600,8 +599,8 @@ func routeICMPv6Packet(t *testing.T, args routeArgs, fn func(*testing.T, header.
|
||||
return
|
||||
}
|
||||
|
||||
if got := pi.Route.RemoteLinkAddress(); len(args.remoteLinkAddr) != 0 && got != args.remoteLinkAddr {
|
||||
t.Errorf("got remote link address = %s, want = %s", got, args.remoteLinkAddr)
|
||||
if len(args.remoteLinkAddr) != 0 && pi.Route.RemoteLinkAddress != args.remoteLinkAddr {
|
||||
t.Errorf("got remote link address = %s, want = %s", pi.Route.RemoteLinkAddress, args.remoteLinkAddr)
|
||||
}
|
||||
|
||||
// Pull the full payload since network header. Needed for header.IPv6 to
|
||||
@@ -1381,8 +1380,8 @@ func TestLinkAddressRequest(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("expected to send a link address request")
|
||||
}
|
||||
if got := pkt.Route.RemoteLinkAddress(); got != test.expectedRemoteLinkAddr {
|
||||
t.Errorf("got pkt.Route.RemoteLinkAddress() = %s, want = %s", got, test.expectedRemoteLinkAddr)
|
||||
if pkt.Route.RemoteLinkAddress != test.expectedRemoteLinkAddr {
|
||||
t.Errorf("got pkt.Route.RemoteLinkAddress = %s, want = %s", pkt.Route.RemoteLinkAddress, test.expectedRemoteLinkAddr)
|
||||
}
|
||||
if pkt.Route.RemoteAddress != test.expectedRemoteAddr {
|
||||
t.Errorf("got pkt.Route.RemoteAddress = %s, want = %s", pkt.Route.RemoteAddress, test.expectedRemoteAddr)
|
||||
@@ -1463,8 +1462,8 @@ func TestPacketQueing(t *testing.T) {
|
||||
if p.Proto != ProtocolNumber {
|
||||
t.Errorf("got p.Proto = %d, want = %d", p.Proto, ProtocolNumber)
|
||||
}
|
||||
if got := p.Route.RemoteLinkAddress(); got != host2NICLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress() = %s, want = %s", got, host2NICLinkAddr)
|
||||
if p.Route.RemoteLinkAddress != host2NICLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, host2NICLinkAddr)
|
||||
}
|
||||
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
|
||||
checker.SrcAddr(host1IPv6Addr.AddressWithPrefix.Address),
|
||||
@@ -1505,8 +1504,8 @@ func TestPacketQueing(t *testing.T) {
|
||||
if p.Proto != ProtocolNumber {
|
||||
t.Errorf("got p.Proto = %d, want = %d", p.Proto, ProtocolNumber)
|
||||
}
|
||||
if got := p.Route.RemoteLinkAddress(); got != host2NICLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress() = %s, want = %s", got, host2NICLinkAddr)
|
||||
if p.Route.RemoteLinkAddress != host2NICLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, host2NICLinkAddr)
|
||||
}
|
||||
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
|
||||
checker.SrcAddr(host1IPv6Addr.AddressWithPrefix.Address),
|
||||
@@ -1556,8 +1555,8 @@ func TestPacketQueing(t *testing.T) {
|
||||
t.Errorf("got Proto = %d, want = %d", p.Proto, ProtocolNumber)
|
||||
}
|
||||
snmc := header.SolicitedNodeAddr(host2IPv6Addr.AddressWithPrefix.Address)
|
||||
if got, want := p.Route.RemoteLinkAddress(), header.EthernetAddressFromMulticastIPv6Address(snmc); got != want {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress() = %s, want = %s", got, want)
|
||||
if want := header.EthernetAddressFromMulticastIPv6Address(snmc); p.Route.RemoteLinkAddress != want {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, want)
|
||||
}
|
||||
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
|
||||
checker.SrcAddr(host1IPv6Addr.AddressWithPrefix.Address),
|
||||
|
||||
@@ -650,8 +650,8 @@ func TestNeighorSolicitationResponse(t *testing.T) {
|
||||
if p.Route.RemoteAddress != respNSDst {
|
||||
t.Errorf("got p.Route.RemoteAddress = %s, want = %s", p.Route.RemoteAddress, respNSDst)
|
||||
}
|
||||
if got, want := p.Route.RemoteLinkAddress(), header.EthernetAddressFromMulticastIPv6Address(respNSDst); got != want {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress() = %s, want = %s", got, want)
|
||||
if want := header.EthernetAddressFromMulticastIPv6Address(respNSDst); p.Route.RemoteLinkAddress != want {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, want)
|
||||
}
|
||||
|
||||
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
|
||||
@@ -706,8 +706,8 @@ func TestNeighorSolicitationResponse(t *testing.T) {
|
||||
if p.Route.RemoteAddress != test.naDst {
|
||||
t.Errorf("got p.Route.RemoteAddress = %s, want = %s", p.Route.RemoteAddress, test.naDst)
|
||||
}
|
||||
if got := p.Route.RemoteLinkAddress(); got != test.naDstLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress() = %s, want = %s", got, test.naDstLinkAddr)
|
||||
if p.Route.RemoteLinkAddress != test.naDstLinkAddr {
|
||||
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, test.naDstLinkAddr)
|
||||
}
|
||||
|
||||
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
|
||||
|
||||
@@ -148,7 +148,6 @@ go_test(
|
||||
],
|
||||
library = ":stack",
|
||||
deps = [
|
||||
"//pkg/sleep",
|
||||
"//pkg/sync",
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/buffer",
|
||||
|
||||
@@ -560,6 +560,38 @@ func TestForwardingWithNoResolver(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingResolutionFailsForQueuedPackets(t *testing.T) {
|
||||
proto := &fwdTestNetworkProtocol{
|
||||
addrResolveDelay: 50 * time.Millisecond,
|
||||
onLinkAddressResolved: func(*linkAddrCache, *neighborCache, tcpip.Address, tcpip.LinkAddress) {
|
||||
// Don't resolve the link address.
|
||||
},
|
||||
}
|
||||
|
||||
ep1, ep2 := fwdTestNetFactory(t, proto, true /* useNeighborCache */)
|
||||
|
||||
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.
|
||||
// TODO(gvisor.dev/issue/5141): Use a fake clock.
|
||||
for i := 0; i < numPackets; i++ {
|
||||
select {
|
||||
case got := <-ep2.C:
|
||||
t.Fatalf("got %#v; packets should have failed resolution and not been forwarded", got)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingWithFakeResolverPartialTimeout(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sleep"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
)
|
||||
@@ -58,9 +57,6 @@ const (
|
||||
incomplete entryState = iota
|
||||
// ready means that the address has been resolved and can be used.
|
||||
ready
|
||||
// failed means that address resolution timed out and the address
|
||||
// could not be resolved.
|
||||
failed
|
||||
)
|
||||
|
||||
// String implements Stringer.
|
||||
@@ -70,8 +66,6 @@ func (s entryState) String() string {
|
||||
return "incomplete"
|
||||
case ready:
|
||||
return "ready"
|
||||
case failed:
|
||||
return "failed"
|
||||
default:
|
||||
return fmt.Sprintf("unknown(%d)", s)
|
||||
}
|
||||
@@ -80,40 +74,48 @@ func (s entryState) String() string {
|
||||
// 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
|
||||
|
||||
// TODO(gvisor.dev/issue/5150): move these fields under mu.
|
||||
// mu protects the fields below.
|
||||
mu sync.RWMutex
|
||||
|
||||
addr tcpip.FullAddress
|
||||
linkAddr tcpip.LinkAddress
|
||||
expiration time.Time
|
||||
s entryState
|
||||
|
||||
// wakers is a set of waiters for address resolution result. Anytime
|
||||
// state transitions out of incomplete these waiters are notified.
|
||||
wakers map[*sleep.Waker]struct{}
|
||||
|
||||
// done is used to allow callers to wait on address resolution. It is nil iff
|
||||
// s is incomplete and resolution is not yet in progress.
|
||||
// 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(tcpip.LinkAddress, bool)
|
||||
}
|
||||
|
||||
// changeState sets the entry's state to ns, notifying any waiters.
|
||||
func (e *linkAddrEntry) notifyCompletionLocked(linkAddr tcpip.LinkAddress) {
|
||||
for _, callback := range e.onResolve {
|
||||
callback(linkAddr, len(linkAddr) != 0)
|
||||
}
|
||||
e.onResolve = nil
|
||||
if ch := e.done; ch != nil {
|
||||
close(ch)
|
||||
e.done = nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (e *linkAddrEntry) changeState(ns entryState, expiration time.Time) {
|
||||
// Notify whoever is waiting on address resolution when transitioning
|
||||
// out of incomplete.
|
||||
if e.s == incomplete && ns != incomplete {
|
||||
for w := range e.wakers {
|
||||
w.Assert()
|
||||
}
|
||||
e.wakers = nil
|
||||
if ch := e.done; ch != nil {
|
||||
close(ch)
|
||||
}
|
||||
e.done = nil
|
||||
//
|
||||
// Precondition: e.mu must be locked
|
||||
func (e *linkAddrEntry) changeStateLocked(ns entryState, expiration time.Time) {
|
||||
if e.s == incomplete && ns == ready {
|
||||
e.notifyCompletionLocked(e.linkAddr)
|
||||
}
|
||||
|
||||
if expiration.IsZero() || expiration.After(e.expiration) {
|
||||
@@ -122,10 +124,6 @@ func (e *linkAddrEntry) changeState(ns entryState, expiration time.Time) {
|
||||
e.s = ns
|
||||
}
|
||||
|
||||
func (e *linkAddrEntry) removeWaker(w *sleep.Waker) {
|
||||
delete(e.wakers, w)
|
||||
}
|
||||
|
||||
// add adds a k -> v mapping to the cache.
|
||||
func (c *linkAddrCache) add(k tcpip.FullAddress, v tcpip.LinkAddress) {
|
||||
// Calculate expiration time before acquiring the lock, since expiration is
|
||||
@@ -135,10 +133,12 @@ func (c *linkAddrCache) add(k tcpip.FullAddress, v tcpip.LinkAddress) {
|
||||
|
||||
c.cache.Lock()
|
||||
entry := c.getOrCreateEntryLocked(k)
|
||||
entry.linkAddr = v
|
||||
|
||||
entry.changeState(ready, expiration)
|
||||
c.cache.Unlock()
|
||||
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
entry.linkAddr = v
|
||||
entry.changeStateLocked(ready, expiration)
|
||||
}
|
||||
|
||||
// getOrCreateEntryLocked retrieves a cache entry associated with k. The
|
||||
@@ -159,13 +159,14 @@ func (c *linkAddrCache) getOrCreateEntryLocked(k tcpip.FullAddress) *linkAddrEnt
|
||||
var entry *linkAddrEntry
|
||||
if len(c.cache.table) == linkAddrCacheSize {
|
||||
entry = c.cache.lru.Back()
|
||||
entry.mu.Lock()
|
||||
|
||||
delete(c.cache.table, entry.addr)
|
||||
c.cache.lru.Remove(entry)
|
||||
|
||||
// Wake waiters and mark the soon-to-be-reused entry as expired. Note
|
||||
// that the state passed doesn't matter when the zero time is passed.
|
||||
entry.changeState(failed, time.Time{})
|
||||
// Wake waiters and mark the soon-to-be-reused entry as expired.
|
||||
entry.notifyCompletionLocked("" /* linkAddr */)
|
||||
entry.mu.Unlock()
|
||||
} else {
|
||||
entry = new(linkAddrEntry)
|
||||
}
|
||||
@@ -180,9 +181,12 @@ func (c *linkAddrCache) getOrCreateEntryLocked(k tcpip.FullAddress) *linkAddrEnt
|
||||
}
|
||||
|
||||
// get reports any known link address for k.
|
||||
func (c *linkAddrCache) get(k tcpip.FullAddress, linkRes LinkAddressResolver, localAddr tcpip.Address, nic NetworkInterface, waker *sleep.Waker) (tcpip.LinkAddress, <-chan struct{}, *tcpip.Error) {
|
||||
func (c *linkAddrCache) get(k tcpip.FullAddress, linkRes LinkAddressResolver, localAddr tcpip.Address, nic NetworkInterface, onResolve func(tcpip.LinkAddress, bool)) (tcpip.LinkAddress, <-chan struct{}, *tcpip.Error) {
|
||||
if linkRes != nil {
|
||||
if addr, ok := linkRes.ResolveStaticAddress(k.Addr); ok {
|
||||
if onResolve != nil {
|
||||
onResolve(addr, true)
|
||||
}
|
||||
return addr, nil, nil
|
||||
}
|
||||
}
|
||||
@@ -190,56 +194,35 @@ func (c *linkAddrCache) get(k tcpip.FullAddress, linkRes LinkAddressResolver, lo
|
||||
c.cache.Lock()
|
||||
defer c.cache.Unlock()
|
||||
entry := c.getOrCreateEntryLocked(k)
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
switch s := entry.s; s {
|
||||
case ready, failed:
|
||||
case ready:
|
||||
if !time.Now().After(entry.expiration) {
|
||||
// Not expired.
|
||||
switch s {
|
||||
case ready:
|
||||
return entry.linkAddr, nil, nil
|
||||
case failed:
|
||||
return entry.linkAddr, nil, tcpip.ErrNoLinkAddress
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid cache entry state: %s", s))
|
||||
if onResolve != nil {
|
||||
onResolve(entry.linkAddr, true)
|
||||
}
|
||||
return entry.linkAddr, nil, nil
|
||||
}
|
||||
|
||||
entry.changeState(incomplete, time.Time{})
|
||||
entry.changeStateLocked(incomplete, time.Time{})
|
||||
fallthrough
|
||||
case incomplete:
|
||||
if waker != nil {
|
||||
if entry.wakers == nil {
|
||||
entry.wakers = make(map[*sleep.Waker]struct{})
|
||||
}
|
||||
entry.wakers[waker] = struct{}{}
|
||||
if onResolve != nil {
|
||||
entry.onResolve = append(entry.onResolve, onResolve)
|
||||
}
|
||||
|
||||
if entry.done == nil {
|
||||
// Address resolution needs to be initiated.
|
||||
if linkRes == nil {
|
||||
return entry.linkAddr, nil, tcpip.ErrNoLinkAddress
|
||||
}
|
||||
|
||||
entry.done = make(chan struct{})
|
||||
go c.startAddressResolution(k, linkRes, localAddr, nic, entry.done) // S/R-SAFE: link non-savable; wakers dropped synchronously.
|
||||
}
|
||||
|
||||
return entry.linkAddr, entry.done, tcpip.ErrWouldBlock
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid cache entry state: %s", s))
|
||||
}
|
||||
}
|
||||
|
||||
// removeWaker removes a waker previously added through get().
|
||||
func (c *linkAddrCache) removeWaker(k tcpip.FullAddress, waker *sleep.Waker) {
|
||||
c.cache.Lock()
|
||||
defer c.cache.Unlock()
|
||||
|
||||
if entry, ok := c.cache.table[k]; ok {
|
||||
entry.removeWaker(waker)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *linkAddrCache) startAddressResolution(k tcpip.FullAddress, linkRes LinkAddressResolver, localAddr tcpip.Address, nic NetworkInterface, done <-chan struct{}) {
|
||||
for i := 0; ; i++ {
|
||||
// Send link request, then wait for the timeout limit and check
|
||||
@@ -257,9 +240,9 @@ func (c *linkAddrCache) startAddressResolution(k tcpip.FullAddress, linkRes Link
|
||||
}
|
||||
}
|
||||
|
||||
// checkLinkRequest checks whether previous attempt to resolve address has succeeded
|
||||
// and mark the entry accordingly, e.g. ready, failed, etc. Return true if request
|
||||
// can stop, false if another request should be sent.
|
||||
// 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.FullAddress, attempt int) bool {
|
||||
c.cache.Lock()
|
||||
defer c.cache.Unlock()
|
||||
@@ -268,16 +251,20 @@ func (c *linkAddrCache) checkLinkRequest(now time.Time, k tcpip.FullAddress, att
|
||||
// Entry was evicted from the cache.
|
||||
return true
|
||||
}
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
switch s := entry.s; s {
|
||||
case ready, failed:
|
||||
// Entry was made ready by resolver or failed. Either way we're done.
|
||||
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, mark entry as failed.
|
||||
entry.changeState(failed, now.Add(c.ageLimit))
|
||||
// Max number of retries reached, delete entry.
|
||||
entry.notifyCompletionLocked("" /* linkAddr */)
|
||||
delete(c.cache.table, k)
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid cache entry state: %s", s))
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sleep"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
)
|
||||
@@ -50,6 +49,7 @@ type testLinkAddressResolver struct {
|
||||
}
|
||||
|
||||
func (r *testLinkAddressResolver) LinkAddressRequest(targetAddr, _ tcpip.Address, _ tcpip.LinkAddress, _ NetworkInterface) *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()
|
||||
@@ -78,16 +78,18 @@ func (*testLinkAddressResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumbe
|
||||
}
|
||||
|
||||
func getBlocking(c *linkAddrCache, addr tcpip.FullAddress, linkRes LinkAddressResolver) (tcpip.LinkAddress, *tcpip.Error) {
|
||||
w := sleep.Waker{}
|
||||
s := sleep.Sleeper{}
|
||||
s.AddWaker(&w, 123)
|
||||
defer s.Done()
|
||||
|
||||
var attemptedResolution bool
|
||||
for {
|
||||
if got, _, err := c.get(addr, linkRes, "", nil, &w); err != tcpip.ErrWouldBlock {
|
||||
return got, err
|
||||
got, ch, err := c.get(addr, linkRes, "", nil, nil)
|
||||
if err == tcpip.ErrWouldBlock {
|
||||
if attemptedResolution {
|
||||
return got, tcpip.ErrNoLinkAddress
|
||||
}
|
||||
attemptedResolution = true
|
||||
<-ch
|
||||
continue
|
||||
}
|
||||
s.Fetch(true)
|
||||
return got, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,16 +118,19 @@ func TestCacheOverflow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// The earliest entries should no longer be in the cache.
|
||||
c.cache.Lock()
|
||||
defer c.cache.Unlock()
|
||||
for i := len(testAddrs) - 1; i >= len(testAddrs)-linkAddrCacheSize; i-- {
|
||||
e := testAddrs[i]
|
||||
if _, _, err := c.get(e.addr, nil, "", nil, nil); err != tcpip.ErrNoLinkAddress {
|
||||
t.Errorf("check %d, c.get(%q), got error: %v, want: error ErrNoLinkAddress", i, string(e.addr.Addr), err)
|
||||
if entry, ok := c.cache.table[e.addr]; ok {
|
||||
t.Errorf("unexpected entry at c.cache.table[%q]: %#v", string(e.addr.Addr), entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheConcurrent(t *testing.T) {
|
||||
c := newLinkAddrCache(1<<63-1, 1*time.Second, 3)
|
||||
linkRes := &testLinkAddressResolver{cache: c}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for r := 0; r < 16; r++ {
|
||||
@@ -133,7 +138,6 @@ func TestCacheConcurrent(t *testing.T) {
|
||||
go func() {
|
||||
for _, e := range testAddrs {
|
||||
c.add(e.addr, e.linkAddr)
|
||||
c.get(e.addr, nil, "", nil, nil) // make work for gotsan
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
@@ -144,7 +148,7 @@ func TestCacheConcurrent(t *testing.T) {
|
||||
// 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, "", nil, nil)
|
||||
got, _, err := c.get(e.addr, linkRes, "", nil, nil)
|
||||
if err != nil {
|
||||
t.Errorf("c.get(%q)=%q, got error: %v", string(e.addr.Addr), got, err)
|
||||
}
|
||||
@@ -153,18 +157,22 @@ func TestCacheConcurrent(t *testing.T) {
|
||||
}
|
||||
|
||||
e = testAddrs[0]
|
||||
if _, _, err := c.get(e.addr, nil, "", nil, nil); err != tcpip.ErrNoLinkAddress {
|
||||
t.Errorf("c.get(%q), got error: %v, want: error ErrNoLinkAddress", string(e.addr.Addr), err)
|
||||
c.cache.Lock()
|
||||
defer c.cache.Unlock()
|
||||
if entry, ok := c.cache.table[e.addr]; ok {
|
||||
t.Errorf("unexpected entry at c.cache.table[%q]: %#v", string(e.addr.Addr), entry)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheAgeLimit(t *testing.T) {
|
||||
c := newLinkAddrCache(1*time.Millisecond, 1*time.Second, 3)
|
||||
linkRes := &testLinkAddressResolver{cache: c}
|
||||
|
||||
e := testAddrs[0]
|
||||
c.add(e.addr, e.linkAddr)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if _, _, err := c.get(e.addr, nil, "", nil, nil); err != tcpip.ErrNoLinkAddress {
|
||||
t.Errorf("c.get(%q), got error: %v, want: error ErrNoLinkAddress", string(e.addr.Addr), err)
|
||||
if _, _, err := c.get(e.addr, linkRes, "", nil, nil); err != tcpip.ErrWouldBlock {
|
||||
t.Errorf("got c.get(%q) = %s, want = ErrWouldBlock", string(e.addr.Addr), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,71 +290,3 @@ func TestStaticResolution(t *testing.T) {
|
||||
t.Errorf("c.get(%q)=%q, want %q", string(addr), string(got), string(want))
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheWaker verifies that RemoveWaker removes a waker previously added
|
||||
// through get().
|
||||
func TestCacheWaker(t *testing.T) {
|
||||
c := newLinkAddrCache(1<<63-1, 1*time.Second, 3)
|
||||
|
||||
// First, sanity check that wakers are working.
|
||||
{
|
||||
linkRes := &testLinkAddressResolver{cache: c}
|
||||
s := sleep.Sleeper{}
|
||||
defer s.Done()
|
||||
|
||||
const wakerID = 1
|
||||
w := sleep.Waker{}
|
||||
s.AddWaker(&w, wakerID)
|
||||
|
||||
e := testAddrs[0]
|
||||
|
||||
if _, _, err := c.get(e.addr, linkRes, "", nil, &w); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got c.get(%q, _, _, _, _) = %s, want = %s", e.addr.Addr, err, tcpip.ErrWouldBlock)
|
||||
}
|
||||
id, ok := s.Fetch(true /* block */)
|
||||
if !ok {
|
||||
t.Fatal("got s.Fetch(true) = (_, false), want = (_, true)")
|
||||
}
|
||||
if id != wakerID {
|
||||
t.Fatalf("got s.Fetch(true) = (%d, %t), want = (%d, true)", id, ok, wakerID)
|
||||
}
|
||||
|
||||
if got, _, err := c.get(e.addr, linkRes, "", nil, nil); err != nil {
|
||||
t.Fatalf("c.get(%q, _, _, _, _): %s", e.addr.Addr, err)
|
||||
} else if got != e.linkAddr {
|
||||
t.Fatalf("got c.get(%q) = %q, want = %q", e.addr.Addr, got, e.linkAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that RemoveWaker works.
|
||||
{
|
||||
linkRes := &testLinkAddressResolver{cache: c}
|
||||
s := sleep.Sleeper{}
|
||||
defer s.Done()
|
||||
|
||||
const wakerID = 2 // different than the ID used in the sanity check
|
||||
w := sleep.Waker{}
|
||||
s.AddWaker(&w, wakerID)
|
||||
|
||||
e := testAddrs[1]
|
||||
linkRes.onLinkAddressRequest = func() {
|
||||
// Remove the waker before the linkAddrCache has the opportunity to send
|
||||
// a notification.
|
||||
c.removeWaker(e.addr, &w)
|
||||
}
|
||||
|
||||
if _, _, err := c.get(e.addr, linkRes, "", nil, &w); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got c.get(%q, _, _, _, _) = %s, want = %s", e.addr.Addr, err, tcpip.ErrWouldBlock)
|
||||
}
|
||||
|
||||
if got, err := getBlocking(c, e.addr, linkRes); err != nil {
|
||||
t.Fatalf("c.get(%q, _, _, _, _): %s", e.addr.Addr, err)
|
||||
} else if got != e.linkAddr {
|
||||
t.Fatalf("c.get(%q) = %q, want = %q", e.addr.Addr, got, e.linkAddr)
|
||||
}
|
||||
|
||||
if id, ok := s.Fetch(false /* block */); ok {
|
||||
t.Fatalf("unexpected notification from waker with id %d", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,8 +540,8 @@ func TestDADResolve(t *testing.T) {
|
||||
|
||||
// Make sure the right remote link address is used.
|
||||
snmc := header.SolicitedNodeAddr(addr1)
|
||||
if got, want := p.Route.RemoteLinkAddress(), header.EthernetAddressFromMulticastIPv6Address(snmc); got != want {
|
||||
t.Errorf("got remote link address = %s, want = %s", got, want)
|
||||
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.
|
||||
@@ -5197,8 +5197,8 @@ func TestRouterSolicitation(t *testing.T) {
|
||||
}
|
||||
|
||||
// Make sure the right remote link address is used.
|
||||
if got, want := p.Route.RemoteLinkAddress(), header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersMulticastAddress); got != want {
|
||||
t.Errorf("got remote link address = %s, want = %s", got, want)
|
||||
if want := header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersMulticastAddress); 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()),
|
||||
|
||||
@@ -17,7 +17,6 @@ package stack
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sleep"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
)
|
||||
@@ -99,9 +98,7 @@ func (n *neighborCache) getOrCreateEntry(remoteAddr tcpip.Address, linkRes LinkA
|
||||
n.dynamic.lru.Remove(e)
|
||||
n.dynamic.count--
|
||||
|
||||
e.dispatchRemoveEventLocked()
|
||||
e.setStateLocked(Unknown)
|
||||
e.notifyWakersLocked()
|
||||
e.removeLocked()
|
||||
e.mu.Unlock()
|
||||
}
|
||||
n.cache[remoteAddr] = entry
|
||||
@@ -110,21 +107,27 @@ func (n *neighborCache) getOrCreateEntry(remoteAddr tcpip.Address, linkRes LinkA
|
||||
return entry
|
||||
}
|
||||
|
||||
// entry looks up the neighbor cache for translating address to link address
|
||||
// (e.g. IP -> MAC). If the LinkEndpoint requests address resolution and there
|
||||
// is a LinkAddressResolver registered with the network protocol, the cache
|
||||
// attempts to resolve the address and returns ErrWouldBlock. If a Waker is
|
||||
// provided, it will be notified when address resolution is complete (success
|
||||
// or not).
|
||||
// entry looks up neighbor information matching the remote address, and returns
|
||||
// it if readily available.
|
||||
//
|
||||
// Returns ErrWouldBlock if the link address is not readily available, along
|
||||
// with a notification channel for the caller to block on. Triggers address
|
||||
// resolution asynchronously.
|
||||
//
|
||||
// If onResolve is provided, it will be called either immediately, if resolution
|
||||
// is not required, or when address resolution is complete, with the resolved
|
||||
// link address and whether resolution succeeded. After any callbacks have been
|
||||
// called, the returned notification channel is closed.
|
||||
//
|
||||
// NB: if a callback is provided, it should not call into the neighbor cache.
|
||||
//
|
||||
// If specified, the local address must be an address local to the interface the
|
||||
// neighbor cache belongs to. The local address is the source address of a
|
||||
// packet prompting NUD/link address resolution.
|
||||
//
|
||||
// If address resolution is required, ErrNoLinkAddress and a notification
|
||||
// channel is returned for the top level caller to block. Channel is closed
|
||||
// once address resolution is complete (success or not).
|
||||
func (n *neighborCache) entry(remoteAddr, localAddr tcpip.Address, linkRes LinkAddressResolver, w *sleep.Waker) (NeighborEntry, <-chan struct{}, *tcpip.Error) {
|
||||
// TODO(gvisor.dev/issue/5151): Don't return the neighbor entry.
|
||||
func (n *neighborCache) entry(remoteAddr, localAddr tcpip.Address, linkRes LinkAddressResolver, onResolve func(tcpip.LinkAddress, bool)) (NeighborEntry, <-chan struct{}, *tcpip.Error) {
|
||||
// TODO(gvisor.dev/issue/5149): Handle static resolution in route.Resolve.
|
||||
if linkAddr, ok := linkRes.ResolveStaticAddress(remoteAddr); ok {
|
||||
e := NeighborEntry{
|
||||
Addr: remoteAddr,
|
||||
@@ -132,6 +135,9 @@ func (n *neighborCache) entry(remoteAddr, localAddr tcpip.Address, linkRes LinkA
|
||||
State: Static,
|
||||
UpdatedAtNanos: 0,
|
||||
}
|
||||
if onResolve != nil {
|
||||
onResolve(linkAddr, true)
|
||||
}
|
||||
return e, nil, nil
|
||||
}
|
||||
|
||||
@@ -149,37 +155,25 @@ func (n *neighborCache) entry(remoteAddr, localAddr tcpip.Address, linkRes LinkA
|
||||
// of packets to a neighbor. While reasserting a neighbor's reachability,
|
||||
// a node continues sending packets to that neighbor using the cached
|
||||
// link-layer address."
|
||||
if onResolve != nil {
|
||||
onResolve(entry.neigh.LinkAddr, true)
|
||||
}
|
||||
return entry.neigh, nil, nil
|
||||
case Unknown, Incomplete:
|
||||
entry.addWakerLocked(w)
|
||||
|
||||
case Unknown, Incomplete, Failed:
|
||||
if onResolve != nil {
|
||||
entry.onResolve = append(entry.onResolve, onResolve)
|
||||
}
|
||||
if entry.done == nil {
|
||||
// Address resolution needs to be initiated.
|
||||
if linkRes == nil {
|
||||
return entry.neigh, nil, tcpip.ErrNoLinkAddress
|
||||
}
|
||||
entry.done = make(chan struct{})
|
||||
}
|
||||
|
||||
entry.handlePacketQueuedLocked(localAddr)
|
||||
return entry.neigh, entry.done, tcpip.ErrWouldBlock
|
||||
case Failed:
|
||||
return entry.neigh, nil, tcpip.ErrNoLinkAddress
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid cache entry state: %s", s))
|
||||
}
|
||||
}
|
||||
|
||||
// removeWaker removes a waker that has been added when link resolution for
|
||||
// addr was requested.
|
||||
func (n *neighborCache) removeWaker(addr tcpip.Address, waker *sleep.Waker) {
|
||||
n.mu.Lock()
|
||||
if entry, ok := n.cache[addr]; ok {
|
||||
delete(entry.wakers, waker)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
}
|
||||
|
||||
// entries returns all entries in the neighbor cache.
|
||||
func (n *neighborCache) entries() []NeighborEntry {
|
||||
n.mu.RLock()
|
||||
@@ -222,34 +216,13 @@ func (n *neighborCache) addStaticEntry(addr tcpip.Address, linkAddr tcpip.LinkAd
|
||||
return
|
||||
}
|
||||
|
||||
// Notify that resolution has been interrupted, just in case the entry was
|
||||
// in the Incomplete or Probe state.
|
||||
entry.dispatchRemoveEventLocked()
|
||||
entry.setStateLocked(Unknown)
|
||||
entry.notifyWakersLocked()
|
||||
entry.removeLocked()
|
||||
entry.mu.Unlock()
|
||||
}
|
||||
|
||||
n.cache[addr] = newStaticNeighborEntry(n.nic, addr, linkAddr, n.state)
|
||||
}
|
||||
|
||||
// removeEntryLocked removes the specified entry from the neighbor cache.
|
||||
//
|
||||
// Prerequisite: n.mu and entry.mu MUST be locked.
|
||||
func (n *neighborCache) removeEntryLocked(entry *neighborEntry) {
|
||||
if entry.neigh.State != Static {
|
||||
n.dynamic.lru.Remove(entry)
|
||||
n.dynamic.count--
|
||||
}
|
||||
if entry.neigh.State != Failed {
|
||||
entry.dispatchRemoveEventLocked()
|
||||
}
|
||||
entry.setStateLocked(Unknown)
|
||||
entry.notifyWakersLocked()
|
||||
|
||||
delete(n.cache, entry.neigh.Addr)
|
||||
}
|
||||
|
||||
// removeEntry removes a dynamic or static entry by address from the neighbor
|
||||
// cache. Returns true if the entry was found and deleted.
|
||||
func (n *neighborCache) removeEntry(addr tcpip.Address) bool {
|
||||
@@ -264,7 +237,13 @@ func (n *neighborCache) removeEntry(addr tcpip.Address) bool {
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
n.removeEntryLocked(entry)
|
||||
if entry.neigh.State != Static {
|
||||
n.dynamic.lru.Remove(entry)
|
||||
n.dynamic.count--
|
||||
}
|
||||
|
||||
entry.removeLocked()
|
||||
delete(n.cache, entry.neigh.Addr)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -275,9 +254,7 @@ func (n *neighborCache) clear() {
|
||||
|
||||
for _, entry := range n.cache {
|
||||
entry.mu.Lock()
|
||||
entry.dispatchRemoveEventLocked()
|
||||
entry.setStateLocked(Unknown)
|
||||
entry.notifyWakersLocked()
|
||||
entry.removeLocked()
|
||||
entry.mu.Unlock()
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sleep"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
)
|
||||
@@ -67,8 +66,7 @@ const (
|
||||
// Static describes entries that have been explicitly added by the user. They
|
||||
// do not expire and are not deleted until explicitly removed.
|
||||
Static
|
||||
// Failed means traffic should not be sent to this neighbor since attempts of
|
||||
// reachability have returned inconclusive.
|
||||
// Failed means recent attempts of reachability have returned inconclusive.
|
||||
Failed
|
||||
)
|
||||
|
||||
@@ -93,16 +91,13 @@ type neighborEntry struct {
|
||||
|
||||
neigh NeighborEntry
|
||||
|
||||
// wakers is a set of waiters for address resolution result. Anytime state
|
||||
// transitions out of incomplete these waiters are notified. It is nil iff
|
||||
// address resolution is ongoing and no clients are waiting for the result.
|
||||
wakers map[*sleep.Waker]struct{}
|
||||
|
||||
// done is used to allow callers to wait on address resolution. It is nil
|
||||
// iff nudState is not Reachable and address resolution is not yet in
|
||||
// progress.
|
||||
// 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(tcpip.LinkAddress, bool)
|
||||
|
||||
isRouter bool
|
||||
job *tcpip.Job
|
||||
}
|
||||
@@ -143,25 +138,15 @@ func newStaticNeighborEntry(nic *NIC, addr tcpip.Address, linkAddr tcpip.LinkAdd
|
||||
}
|
||||
}
|
||||
|
||||
// addWaker adds w to the list of wakers waiting for address resolution.
|
||||
// Assumes the entry has already been appropriately locked.
|
||||
func (e *neighborEntry) addWakerLocked(w *sleep.Waker) {
|
||||
if w == nil {
|
||||
return
|
||||
// notifyCompletionLocked notifies those waiting for address resolution, with
|
||||
// the link address if resolution completed successfully.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) notifyCompletionLocked(succeeded bool) {
|
||||
for _, callback := range e.onResolve {
|
||||
callback(e.neigh.LinkAddr, succeeded)
|
||||
}
|
||||
if e.wakers == nil {
|
||||
e.wakers = make(map[*sleep.Waker]struct{})
|
||||
}
|
||||
e.wakers[w] = struct{}{}
|
||||
}
|
||||
|
||||
// notifyWakersLocked notifies those waiting for address resolution, whether it
|
||||
// succeeded or failed. Assumes the entry has already been appropriately locked.
|
||||
func (e *neighborEntry) notifyWakersLocked() {
|
||||
for w := range e.wakers {
|
||||
w.Assert()
|
||||
}
|
||||
e.wakers = nil
|
||||
e.onResolve = nil
|
||||
if ch := e.done; ch != nil {
|
||||
close(ch)
|
||||
e.done = nil
|
||||
@@ -170,6 +155,8 @@ func (e *neighborEntry) notifyWakersLocked() {
|
||||
|
||||
// dispatchAddEventLocked signals to stack's NUD Dispatcher that the entry has
|
||||
// been added.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) dispatchAddEventLocked() {
|
||||
if nudDisp := e.nic.stack.nudDisp; nudDisp != nil {
|
||||
nudDisp.OnNeighborAdded(e.nic.id, e.neigh)
|
||||
@@ -178,6 +165,8 @@ func (e *neighborEntry) dispatchAddEventLocked() {
|
||||
|
||||
// dispatchChangeEventLocked signals to stack's NUD Dispatcher that the entry
|
||||
// has changed state or link-layer address.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) dispatchChangeEventLocked() {
|
||||
if nudDisp := e.nic.stack.nudDisp; nudDisp != nil {
|
||||
nudDisp.OnNeighborChanged(e.nic.id, e.neigh)
|
||||
@@ -186,23 +175,41 @@ func (e *neighborEntry) dispatchChangeEventLocked() {
|
||||
|
||||
// dispatchRemoveEventLocked signals to stack's NUD Dispatcher that the entry
|
||||
// has been removed.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) dispatchRemoveEventLocked() {
|
||||
if nudDisp := e.nic.stack.nudDisp; nudDisp != nil {
|
||||
nudDisp.OnNeighborRemoved(e.nic.id, e.neigh)
|
||||
}
|
||||
}
|
||||
|
||||
// cancelJobLocked cancels the currently scheduled action, if there is one.
|
||||
// Entries in Unknown, Stale, or Static state do not have a scheduled action.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) cancelJobLocked() {
|
||||
if job := e.job; job != nil {
|
||||
job.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// removeLocked prepares the entry for removal.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) removeLocked() {
|
||||
e.neigh.UpdatedAtNanos = e.nic.stack.clock.NowNanoseconds()
|
||||
e.dispatchRemoveEventLocked()
|
||||
e.cancelJobLocked()
|
||||
e.notifyCompletionLocked(false /* succeeded */)
|
||||
}
|
||||
|
||||
// setStateLocked transitions the entry to the specified state immediately.
|
||||
//
|
||||
// Follows the logic defined in RFC 4861 section 7.3.3.
|
||||
//
|
||||
// e.mu MUST be locked.
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) setStateLocked(next NeighborState) {
|
||||
// Cancel the previously scheduled action, if there is one. Entries in
|
||||
// Unknown, Stale, or Static state do not have scheduled actions.
|
||||
if timer := e.job; timer != nil {
|
||||
timer.Cancel()
|
||||
}
|
||||
e.cancelJobLocked()
|
||||
|
||||
prev := e.neigh.State
|
||||
e.neigh.State = next
|
||||
@@ -257,11 +264,7 @@ func (e *neighborEntry) setStateLocked(next NeighborState) {
|
||||
e.job.Schedule(immediateDuration)
|
||||
|
||||
case Failed:
|
||||
e.notifyWakersLocked()
|
||||
e.job = e.nic.stack.newJob(&doubleLock{first: &e.nic.neigh.mu, second: &e.mu}, func() {
|
||||
e.nic.neigh.removeEntryLocked(e)
|
||||
})
|
||||
e.job.Schedule(config.UnreachableTime)
|
||||
e.notifyCompletionLocked(false /* succeeded */)
|
||||
|
||||
case Unknown, Stale, Static:
|
||||
// Do nothing
|
||||
@@ -275,8 +278,14 @@ func (e *neighborEntry) setStateLocked(next NeighborState) {
|
||||
// being queued for outgoing transmission.
|
||||
//
|
||||
// Follows the logic defined in RFC 4861 section 7.3.3.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
|
||||
switch e.neigh.State {
|
||||
case Failed:
|
||||
e.nic.stats.Neighbor.FailedEntryLookups.Increment()
|
||||
|
||||
fallthrough
|
||||
case Unknown:
|
||||
e.neigh.State = Incomplete
|
||||
e.neigh.UpdatedAtNanos = e.nic.stack.clock.NowNanoseconds()
|
||||
@@ -309,7 +318,7 @@ func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
|
||||
// implementation may find it convenient in some cases to return errors
|
||||
// to the sender by taking the offending packet, generating an ICMP
|
||||
// error message, and then delivering it (locally) through the generic
|
||||
// error-handling routines.' - RFC 4861 section 2.1
|
||||
// error-handling routines." - RFC 4861 section 2.1
|
||||
e.dispatchRemoveEventLocked()
|
||||
e.setStateLocked(Failed)
|
||||
return
|
||||
@@ -349,8 +358,6 @@ func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
|
||||
|
||||
case Incomplete, Reachable, Delay, Probe, Static:
|
||||
// Do nothing
|
||||
case Failed:
|
||||
e.nic.stats.Neighbor.FailedEntryLookups.Increment()
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid cache entry state: %s", e.neigh.State))
|
||||
}
|
||||
@@ -360,18 +367,30 @@ func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
|
||||
// Neighbor Solicitation for ARP or NDP, respectively).
|
||||
//
|
||||
// Follows the logic defined in RFC 4861 section 7.2.3.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) handleProbeLocked(remoteLinkAddr tcpip.LinkAddress) {
|
||||
// Probes MUST be silently discarded if the target address is tentative, does
|
||||
// not exist, or not bound to the NIC as per RFC 4861 section 7.2.3. These
|
||||
// checks MUST be done by the NetworkEndpoint.
|
||||
|
||||
switch e.neigh.State {
|
||||
case Unknown, Incomplete, Failed:
|
||||
case Unknown, Failed:
|
||||
e.neigh.LinkAddr = remoteLinkAddr
|
||||
e.setStateLocked(Stale)
|
||||
e.notifyWakersLocked()
|
||||
e.dispatchAddEventLocked()
|
||||
|
||||
case Incomplete:
|
||||
// "If an entry already exists, and the cached link-layer address
|
||||
// differs from the one in the received Source Link-Layer option, the
|
||||
// cached address should be replaced by the received address, and the
|
||||
// entry's reachability state MUST be set to STALE."
|
||||
// - RFC 4861 section 7.2.3
|
||||
e.neigh.LinkAddr = remoteLinkAddr
|
||||
e.setStateLocked(Stale)
|
||||
e.notifyCompletionLocked(true /* succeeded */)
|
||||
e.dispatchChangeEventLocked()
|
||||
|
||||
case Reachable, Delay, Probe:
|
||||
if e.neigh.LinkAddr != remoteLinkAddr {
|
||||
e.neigh.LinkAddr = remoteLinkAddr
|
||||
@@ -404,6 +423,8 @@ func (e *neighborEntry) handleProbeLocked(remoteLinkAddr tcpip.LinkAddress) {
|
||||
// not be possible. SEND uses RSA key pairs to produce Cryptographically
|
||||
// Generated Addresses (CGA), as defined in RFC 3972. This ensures that the
|
||||
// claimed source of an NDP message is the owner of the claimed address.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) handleConfirmationLocked(linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) {
|
||||
switch e.neigh.State {
|
||||
case Incomplete:
|
||||
@@ -422,7 +443,7 @@ func (e *neighborEntry) handleConfirmationLocked(linkAddr tcpip.LinkAddress, fla
|
||||
}
|
||||
e.dispatchChangeEventLocked()
|
||||
e.isRouter = flags.IsRouter
|
||||
e.notifyWakersLocked()
|
||||
e.notifyCompletionLocked(true /* succeeded */)
|
||||
|
||||
// "Note that the Override flag is ignored if the entry is in the
|
||||
// INCOMPLETE state." - RFC 4861 section 7.2.5
|
||||
@@ -457,7 +478,7 @@ func (e *neighborEntry) handleConfirmationLocked(linkAddr tcpip.LinkAddress, fla
|
||||
wasReachable := e.neigh.State == Reachable
|
||||
// Set state to Reachable again to refresh timers.
|
||||
e.setStateLocked(Reachable)
|
||||
e.notifyWakersLocked()
|
||||
e.notifyCompletionLocked(true /* succeeded */)
|
||||
if !wasReachable {
|
||||
e.dispatchChangeEventLocked()
|
||||
}
|
||||
@@ -495,6 +516,8 @@ func (e *neighborEntry) handleConfirmationLocked(linkAddr tcpip.LinkAddress, fla
|
||||
|
||||
// handleUpperLevelConfirmationLocked processes an incoming upper-level protocol
|
||||
// (e.g. TCP acknowledgements) reachability confirmation.
|
||||
//
|
||||
// Precondition: e.mu MUST be locked.
|
||||
func (e *neighborEntry) handleUpperLevelConfirmationLocked() {
|
||||
switch e.neigh.State {
|
||||
case Reachable, Stale, Delay, Probe:
|
||||
@@ -512,23 +535,3 @@ func (e *neighborEntry) handleUpperLevelConfirmationLocked() {
|
||||
panic(fmt.Sprintf("Invalid cache entry state: %s", e.neigh.State))
|
||||
}
|
||||
}
|
||||
|
||||
// doubleLock combines two locks into one while maintaining lock ordering.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/4796): Remove this once subsequent traffic to a Failed
|
||||
// neighbor is allowed.
|
||||
type doubleLock struct {
|
||||
first, second sync.Locker
|
||||
}
|
||||
|
||||
// Lock locks both locks in order: first then second.
|
||||
func (l *doubleLock) Lock() {
|
||||
l.first.Lock()
|
||||
l.second.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks both locks in reverse order: second then first.
|
||||
func (l *doubleLock) Unlock() {
|
||||
l.second.Unlock()
|
||||
l.first.Unlock()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user