Add option to replace linkAddrCache with neighborCache

This change adds an option to replace the current implementation of ARP through
linkAddrCache, with an implementation of NUD through neighborCache. Switching
to using NUD for both ARP and NDP is beneficial for the reasons described by
RFC 4861 Section 3.1:

  "[Using NUD] significantly improves the robustness of packet delivery in the
  presence of failing routers, partially failing or partitioned links, or nodes
  that change their link-layer addresses. For instance, mobile nodes can move
  off-link without losing any connectivity due to stale ARP caches."

  "Unlike ARP, Neighbor Unreachability Detection detects half-link failures and
  avoids sending traffic to neighbors with which two-way connectivity is
  absent."

Along with these changes exposes the API for querying and operating the
neighbor cache. Operations include:
  - Create a static entry
  - List all entries
  - Delete all entries
  - Remove an entry by address

This also exposes the API to change the NUD protocol constants on a per-NIC
basis to allow Neighbor Discovery to operate over links with widely varying
performance characteristics. See [RFC 4861 Section 10][1] for the list of
constants.

Finally, an API for subscribing to NUD state changes is exposed through
NUDDispatcher. See [RFC 4861 Appendix C][3] for the list of edges.

Tests:
 pkg/tcpip/network/arp:arp_test
 + TestDirectRequest

 pkg/tcpip/network/ipv6:ipv6_test
 + TestLinkResolution
 + TestNDPValidation
 + TestNeighorAdvertisementWithTargetLinkLayerOption
 + TestNeighorSolicitationResponse
 + TestNeighorSolicitationWithSourceLinkLayerOption
 + TestRouterAdvertValidation

 pkg/tcpip/stack:stack_test
 + TestCacheWaker
 + TestForwardingWithFakeResolver
 + TestForwardingWithFakeResolverManyPackets
 + TestForwardingWithFakeResolverManyResolutions
 + TestForwardingWithFakeResolverPartialTimeout
 + TestForwardingWithFakeResolverTwoPackets
 + TestIPv6SourceAddressSelectionScopeAndSameAddress

[1]: https://tools.ietf.org/html/rfc4861#section-10
[2]: https://tools.ietf.org/html/rfc4861#appendix-C

Fixes #1889
Fixes #1894
Fixes #1895
Fixes #1947
Fixes #1948
Fixes #1949
Fixes #1950

PiperOrigin-RevId: 328365034
This commit is contained in:
Sam Balana
2020-08-25 11:09:33 -07:00
committed by gVisor bot
parent b26f7503b5
commit a174aa7597
21 changed files with 2640 additions and 1177 deletions
+1
View File
@@ -28,5 +28,6 @@ go_test(
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/stack",
"//pkg/tcpip/transport/icmp",
"@com_github_google_go_cmp//cmp:go_default_library",
],
)
+41 -6
View File
@@ -46,6 +46,7 @@ type endpoint struct {
nicID tcpip.NICID
linkEP stack.LinkEndpoint
linkAddrCache stack.LinkAddressCache
nud stack.NUDHandler
}
// DefaultTTL is unused for ARP. It implements stack.NetworkEndpoint.
@@ -78,7 +79,7 @@ func (e *endpoint) WritePacket(*stack.Route, *stack.GSO, stack.NetworkHeaderPara
// NetworkProtocolNumber implements stack.NetworkEndpoint.NetworkProtocolNumber.
func (e *endpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber {
return e.protocol.Number()
return ProtocolNumber
}
// WritePackets implements stack.NetworkEndpoint.WritePackets.
@@ -99,9 +100,25 @@ func (e *endpoint) HandlePacket(r *stack.Route, pkt *stack.PacketBuffer) {
switch h.Op() {
case header.ARPRequest:
localAddr := tcpip.Address(h.ProtocolAddressTarget())
if e.linkAddrCache.CheckLocalAddress(e.nicID, header.IPv4ProtocolNumber, localAddr) == 0 {
return // we have no useful answer, ignore the request
if e.nud == nil {
if e.linkAddrCache.CheckLocalAddress(e.nicID, header.IPv4ProtocolNumber, localAddr) == 0 {
return // we have no useful answer, ignore the request
}
addr := tcpip.Address(h.ProtocolAddressSender())
linkAddr := tcpip.LinkAddress(h.HardwareAddressSender())
e.linkAddrCache.AddLinkAddress(e.nicID, addr, linkAddr)
} else {
if r.Stack().CheckLocalAddress(e.nicID, header.IPv4ProtocolNumber, localAddr) == 0 {
return // we have no useful answer, ignore the request
}
remoteAddr := tcpip.Address(h.ProtocolAddressSender())
remoteLinkAddr := tcpip.LinkAddress(h.HardwareAddressSender())
e.nud.HandleProbe(remoteAddr, localAddr, ProtocolNumber, remoteLinkAddr, e.protocol)
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(e.linkEP.MaxHeaderLength()) + header.ARPSize,
})
@@ -113,11 +130,28 @@ func (e *endpoint) HandlePacket(r *stack.Route, pkt *stack.PacketBuffer) {
copy(packet.HardwareAddressTarget(), h.HardwareAddressSender())
copy(packet.ProtocolAddressTarget(), h.ProtocolAddressSender())
_ = e.linkEP.WritePacket(r, nil /* gso */, ProtocolNumber, pkt)
fallthrough // also fill the cache from requests
case header.ARPReply:
addr := tcpip.Address(h.ProtocolAddressSender())
linkAddr := tcpip.LinkAddress(h.HardwareAddressSender())
e.linkAddrCache.AddLinkAddress(e.nicID, addr, linkAddr)
if e.nud == nil {
e.linkAddrCache.AddLinkAddress(e.nicID, addr, linkAddr)
return
}
// The solicited, override, and isRouter flags are not available for ARP;
// they are only available for IPv6 Neighbor Advertisements.
e.nud.HandleConfirmation(addr, linkAddr, stack.ReachabilityConfirmationFlags{
// Solicited and unsolicited (also referred to as gratuitous) ARP Replies
// are handled equivalently to a solicited Neighbor Advertisement.
Solicited: true,
// If a different link address is received than the one cached, the entry
// should always go to Stale.
Override: false,
// ARP does not distinguish between router and non-router hosts.
IsRouter: false,
})
}
}
@@ -134,12 +168,13 @@ func (*protocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) {
return tcpip.Address(h.ProtocolAddressSender()), ProtocolAddress
}
func (p *protocol) NewEndpoint(nicID tcpip.NICID, linkAddrCache stack.LinkAddressCache, dispatcher stack.TransportDispatcher, sender stack.LinkEndpoint, st *stack.Stack) stack.NetworkEndpoint {
func (p *protocol) NewEndpoint(nicID tcpip.NICID, linkAddrCache stack.LinkAddressCache, nud stack.NUDHandler, dispatcher stack.TransportDispatcher, sender stack.LinkEndpoint, st *stack.Stack) stack.NetworkEndpoint {
return &endpoint{
protocol: p,
nicID: nicID,
linkEP: sender,
linkAddrCache: linkAddrCache,
nud: nud,
}
}
+304 -29
View File
@@ -16,10 +16,12 @@ package arp_test
import (
"context"
"fmt"
"strconv"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
@@ -32,57 +34,192 @@ import (
)
const (
stackLinkAddr1 = tcpip.LinkAddress("\x0a\x0a\x0b\x0b\x0c\x0c")
stackLinkAddr2 = tcpip.LinkAddress("\x0b\x0b\x0c\x0c\x0d\x0d")
stackAddr1 = tcpip.Address("\x0a\x00\x00\x01")
stackAddr2 = tcpip.Address("\x0a\x00\x00\x02")
stackAddrBad = tcpip.Address("\x0a\x00\x00\x03")
nicID = 1
stackAddr = tcpip.Address("\x0a\x00\x00\x01")
stackLinkAddr = tcpip.LinkAddress("\x0a\x0a\x0b\x0b\x0c\x0c")
remoteAddr = tcpip.Address("\x0a\x00\x00\x02")
remoteLinkAddr = tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06")
unknownAddr = tcpip.Address("\x0a\x00\x00\x03")
defaultChannelSize = 1
defaultMTU = 65536
// eventChanSize defines the size of event channels used by the neighbor
// cache's event dispatcher. The size chosen here needs to be sufficient to
// queue all the events received during tests before consumption.
// If eventChanSize is too small, the tests may deadlock.
eventChanSize = 32
)
type testContext struct {
t *testing.T
linkEP *channel.Endpoint
s *stack.Stack
type eventType uint8
const (
entryAdded eventType = iota
entryChanged
entryRemoved
)
func (t eventType) String() string {
switch t {
case entryAdded:
return "add"
case entryChanged:
return "change"
case entryRemoved:
return "remove"
default:
return fmt.Sprintf("unknown (%d)", t)
}
}
func newTestContext(t *testing.T) *testContext {
type eventInfo struct {
eventType eventType
nicID tcpip.NICID
addr tcpip.Address
linkAddr tcpip.LinkAddress
state stack.NeighborState
}
func (e eventInfo) String() string {
return fmt.Sprintf("%s event for NIC #%d, addr=%q, linkAddr=%q, state=%q", e.eventType, e.nicID, e.addr, e.linkAddr, e.state)
}
// arpDispatcher implements NUDDispatcher to validate the dispatching of
// events upon certain NUD state machine events.
type arpDispatcher struct {
// C is where events are queued
C chan eventInfo
}
var _ stack.NUDDispatcher = (*arpDispatcher)(nil)
func (d *arpDispatcher) OnNeighborAdded(nicID tcpip.NICID, addr tcpip.Address, linkAddr tcpip.LinkAddress, state stack.NeighborState, updatedAt time.Time) {
e := eventInfo{
eventType: entryAdded,
nicID: nicID,
addr: addr,
linkAddr: linkAddr,
state: state,
}
d.C <- e
}
func (d *arpDispatcher) OnNeighborChanged(nicID tcpip.NICID, addr tcpip.Address, linkAddr tcpip.LinkAddress, state stack.NeighborState, updatedAt time.Time) {
e := eventInfo{
eventType: entryChanged,
nicID: nicID,
addr: addr,
linkAddr: linkAddr,
state: state,
}
d.C <- e
}
func (d *arpDispatcher) OnNeighborRemoved(nicID tcpip.NICID, addr tcpip.Address, linkAddr tcpip.LinkAddress, state stack.NeighborState, updatedAt time.Time) {
e := eventInfo{
eventType: entryRemoved,
nicID: nicID,
addr: addr,
linkAddr: linkAddr,
state: state,
}
d.C <- e
}
func (d *arpDispatcher) waitForEvent(ctx context.Context, want eventInfo) error {
select {
case got := <-d.C:
if diff := cmp.Diff(got, want, cmp.AllowUnexported(got)); diff != "" {
return fmt.Errorf("got invalid event (-got +want):\n%s", diff)
}
case <-ctx.Done():
return fmt.Errorf("%s for %s", ctx.Err(), want)
}
return nil
}
func (d *arpDispatcher) waitForEventWithTimeout(want eventInfo, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return d.waitForEvent(ctx, want)
}
func (d *arpDispatcher) nextEvent() (eventInfo, bool) {
select {
case event := <-d.C:
return event, true
default:
return eventInfo{}, false
}
}
type testContext struct {
s *stack.Stack
linkEP *channel.Endpoint
nudDisp *arpDispatcher
}
func newTestContext(t *testing.T, useNeighborCache bool) *testContext {
c := stack.DefaultNUDConfigurations()
// Transition from Reachable to Stale almost immediately to test if receiving
// probes refreshes positive reachability.
c.BaseReachableTime = time.Microsecond
d := arpDispatcher{
// Create an event channel large enough so the neighbor cache doesn't block
// while dispatching events. Blocking could interfere with the timing of
// NUD transitions.
C: make(chan eventInfo, eventChanSize),
}
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol(), arp.NewProtocol()},
TransportProtocols: []stack.TransportProtocol{icmp.NewProtocol4()},
NUDConfigs: c,
NUDDisp: &d,
UseNeighborCache: useNeighborCache,
})
ep := channel.New(defaultChannelSize, defaultMTU, stackLinkAddr1)
ep := channel.New(defaultChannelSize, defaultMTU, stackLinkAddr)
ep.LinkEPCapabilities |= stack.CapabilityResolutionRequired
wep := stack.LinkEndpoint(ep)
if testing.Verbose() {
wep = sniffer.New(ep)
}
if err := s.CreateNIC(1, wep); err != nil {
if err := s.CreateNIC(nicID, wep); err != nil {
t.Fatalf("CreateNIC failed: %v", err)
}
if err := s.AddAddress(1, ipv4.ProtocolNumber, stackAddr1); err != nil {
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, stackAddr); err != nil {
t.Fatalf("AddAddress for ipv4 failed: %v", err)
}
if err := s.AddAddress(1, ipv4.ProtocolNumber, stackAddr2); 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)
}
}
if err := s.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
if err := s.AddAddress(nicID, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
t.Fatalf("AddAddress for arp failed: %v", err)
}
s.SetRouteTable([]tcpip.Route{{
Destination: header.IPv4EmptySubnet,
NIC: 1,
NIC: nicID,
}})
return &testContext{
t: t,
s: s,
linkEP: ep,
s: s,
linkEP: ep,
nudDisp: &d,
}
}
@@ -91,7 +228,7 @@ func (c *testContext) cleanup() {
}
func TestDirectRequest(t *testing.T) {
c := newTestContext(t)
c := newTestContext(t, false /* useNeighborCache */)
defer c.cleanup()
const senderMAC = "\x01\x02\x03\x04\x05\x06"
@@ -111,7 +248,7 @@ func TestDirectRequest(t *testing.T) {
}))
}
for i, address := range []tcpip.Address{stackAddr1, stackAddr2} {
for i, address := range []tcpip.Address{stackAddr, remoteAddr} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
inject(address)
pi, _ := c.linkEP.ReadContext(context.Background())
@@ -122,7 +259,7 @@ func TestDirectRequest(t *testing.T) {
if !rep.IsValid() {
t.Fatalf("invalid ARP response: len = %d; response = %x", len(rep), rep)
}
if got, want := tcpip.LinkAddress(rep.HardwareAddressSender()), stackLinkAddr1; got != want {
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 {
@@ -137,7 +274,7 @@ func TestDirectRequest(t *testing.T) {
})
}
inject(stackAddrBad)
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.
@@ -148,6 +285,144 @@ func TestDirectRequest(t *testing.T) {
}
}
func TestDirectRequestWithNeighborCache(t *testing.T) {
c := newTestContext(t, true /* useNeighborCache */)
defer c.cleanup()
tests := []struct {
name string
senderAddr tcpip.Address
senderLinkAddr tcpip.LinkAddress
targetAddr tcpip.Address
isValid bool
}{
{
name: "Loopback",
senderAddr: stackAddr,
senderLinkAddr: stackLinkAddr,
targetAddr: stackAddr,
isValid: true,
},
{
name: "Remote",
senderAddr: remoteAddr,
senderLinkAddr: remoteLinkAddr,
targetAddr: stackAddr,
isValid: true,
},
{
name: "RemoteInvalidTarget",
senderAddr: remoteAddr,
senderLinkAddr: remoteLinkAddr,
targetAddr: unknownAddr,
isValid: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Inject an incoming ARP request.
v := make(buffer.View, header.ARPSize)
h := header.ARP(v)
h.SetIPv4OverEthernet()
h.SetOp(header.ARPRequest)
copy(h.HardwareAddressSender(), test.senderLinkAddr)
copy(h.ProtocolAddressSender(), test.senderAddr)
copy(h.ProtocolAddressTarget(), test.targetAddr)
c.linkEP.InjectInbound(arp.ProtocolNumber, &stack.PacketBuffer{
Data: v.ToVectorisedView(),
})
if !test.isValid {
// No packets should be sent after receiving an invalid ARP request.
// There is no need to perform a blocking read here, since packets are
// sent in the same function that handles ARP requests.
if pkt, ok := c.linkEP.Read(); ok {
t.Errorf("unexpected packet sent with network protocol number %d", pkt.Proto)
}
return
}
// Verify an ARP response was sent.
pi, ok := c.linkEP.Read()
if !ok {
t.Fatal("expected ARP response to be sent, got none")
}
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, 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)
}
// Verify the sender was saved in the neighbor cache.
wantEvent := eventInfo{
eventType: entryAdded,
nicID: nicID,
addr: test.senderAddr,
linkAddr: tcpip.LinkAddress(test.senderLinkAddr),
state: stack.Stale,
}
if err := c.nudDisp.waitForEventWithTimeout(wantEvent, time.Second); err != nil {
t.Fatal(err)
}
neighbors, err := c.s.Neighbors(nicID)
if err != nil {
t.Fatalf("c.s.Neighbors(%d): %s", nicID, err)
}
neighborByAddr := make(map[tcpip.Address]stack.NeighborEntry)
for _, n := range neighbors {
if existing, ok := neighborByAddr[n.Addr]; ok {
if diff := cmp.Diff(existing, n); diff != "" {
t.Fatalf("duplicate neighbor entry found (-existing +got):\n%s", diff)
}
t.Fatalf("exact neighbor entry duplicate found for addr=%s", n.Addr)
}
neighborByAddr[n.Addr] = n
}
neigh, ok := neighborByAddr[test.senderAddr]
if !ok {
t.Fatalf("expected neighbor entry with Addr = %s", test.senderAddr)
}
if got, want := neigh.LinkAddr, test.senderLinkAddr; got != want {
t.Errorf("got neighbor LinkAddr = %s, want = %s", got, want)
}
if got, want := neigh.LocalAddr, stackAddr; got != want {
t.Errorf("got neighbor LocalAddr = %s, want = %s", got, want)
}
if got, want := neigh.State, stack.Stale; got != want {
t.Errorf("got neighbor State = %s, want = %s", got, want)
}
// No more events should be dispatched
for {
event, ok := c.nudDisp.nextEvent()
if !ok {
break
}
t.Errorf("unexpected %s", event)
}
})
}
}
func TestLinkAddressRequest(t *testing.T) {
tests := []struct {
name string
@@ -156,8 +431,8 @@ func TestLinkAddressRequest(t *testing.T) {
}{
{
name: "Unicast",
remoteLinkAddr: stackLinkAddr2,
expectLinkAddr: stackLinkAddr2,
remoteLinkAddr: remoteLinkAddr,
expectLinkAddr: remoteLinkAddr,
},
{
name: "Multicast",
@@ -173,9 +448,9 @@ func TestLinkAddressRequest(t *testing.T) {
t.Fatal("expected ARP protocol to implement stack.LinkAddressResolver")
}
linkEP := channel.New(defaultChannelSize, defaultMTU, stackLinkAddr1)
if err := linkRes.LinkAddressRequest(stackAddr1, stackAddr2, test.remoteLinkAddr, linkEP); err != nil {
t.Errorf("got p.LinkAddressRequest(%s, %s, %s, _) = %s", stackAddr1, stackAddr2, test.remoteLinkAddr, err)
linkEP := channel.New(defaultChannelSize, defaultMTU, stackLinkAddr)
if err := linkRes.LinkAddressRequest(stackAddr, remoteAddr, test.remoteLinkAddr, linkEP); err != nil {
t.Errorf("got p.LinkAddressRequest(%s, %s, %s, _) = %s", stackAddr, remoteAddr, test.remoteLinkAddr, err)
}
pkt, ok := linkEP.Read()
+7 -7
View File
@@ -250,7 +250,7 @@ func buildDummyStack(t *testing.T) *stack.Stack {
func TestIPv4Send(t *testing.T) {
o := testObject{t: t, v4: true}
proto := ipv4.NewProtocol()
ep := proto.NewEndpoint(nicID, nil, nil, &o, buildDummyStack(t))
ep := proto.NewEndpoint(nicID, nil, nil, nil, &o, buildDummyStack(t))
defer ep.Close()
// Allocate and initialize the payload view.
@@ -287,7 +287,7 @@ func TestIPv4Send(t *testing.T) {
func TestIPv4Receive(t *testing.T) {
o := testObject{t: t, v4: true}
proto := ipv4.NewProtocol()
ep := proto.NewEndpoint(nicID, nil, &o, nil, buildDummyStack(t))
ep := proto.NewEndpoint(nicID, nil, nil, &o, nil, buildDummyStack(t))
defer ep.Close()
totalLen := header.IPv4MinimumSize + 30
@@ -357,7 +357,7 @@ func TestIPv4ReceiveControl(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
o := testObject{t: t}
proto := ipv4.NewProtocol()
ep := proto.NewEndpoint(nicID, nil, &o, nil, buildDummyStack(t))
ep := proto.NewEndpoint(nicID, nil, nil, &o, nil, buildDummyStack(t))
defer ep.Close()
const dataOffset = header.IPv4MinimumSize*2 + header.ICMPv4MinimumSize
@@ -418,7 +418,7 @@ func TestIPv4ReceiveControl(t *testing.T) {
func TestIPv4FragmentationReceive(t *testing.T) {
o := testObject{t: t, v4: true}
proto := ipv4.NewProtocol()
ep := proto.NewEndpoint(nicID, nil, &o, nil, buildDummyStack(t))
ep := proto.NewEndpoint(nicID, nil, nil, &o, nil, buildDummyStack(t))
defer ep.Close()
totalLen := header.IPv4MinimumSize + 24
@@ -495,7 +495,7 @@ func TestIPv4FragmentationReceive(t *testing.T) {
func TestIPv6Send(t *testing.T) {
o := testObject{t: t}
proto := ipv6.NewProtocol()
ep := proto.NewEndpoint(nicID, nil, &o, channel.New(0, 1280, ""), buildDummyStack(t))
ep := proto.NewEndpoint(nicID, nil, nil, &o, channel.New(0, 1280, ""), buildDummyStack(t))
defer ep.Close()
// Allocate and initialize the payload view.
@@ -532,7 +532,7 @@ func TestIPv6Send(t *testing.T) {
func TestIPv6Receive(t *testing.T) {
o := testObject{t: t}
proto := ipv6.NewProtocol()
ep := proto.NewEndpoint(nicID, nil, &o, nil, buildDummyStack(t))
ep := proto.NewEndpoint(nicID, nil, nil, &o, nil, buildDummyStack(t))
defer ep.Close()
totalLen := header.IPv6MinimumSize + 30
@@ -611,7 +611,7 @@ func TestIPv6ReceiveControl(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
o := testObject{t: t}
proto := ipv6.NewProtocol()
ep := proto.NewEndpoint(nicID, nil, &o, nil, buildDummyStack(t))
ep := proto.NewEndpoint(nicID, nil, nil, &o, nil, buildDummyStack(t))
defer ep.Close()
dataOffset := header.IPv6MinimumSize*2 + header.ICMPv6MinimumSize
+1 -1
View File
@@ -59,7 +59,7 @@ type endpoint struct {
}
// NewEndpoint creates a new ipv4 endpoint.
func (p *protocol) NewEndpoint(nicID tcpip.NICID, linkAddrCache stack.LinkAddressCache, dispatcher stack.TransportDispatcher, linkEP stack.LinkEndpoint, st *stack.Stack) stack.NetworkEndpoint {
func (p *protocol) NewEndpoint(nicID tcpip.NICID, _ stack.LinkAddressCache, _ stack.NUDHandler, dispatcher stack.TransportDispatcher, linkEP stack.LinkEndpoint, st *stack.Stack) stack.NetworkEndpoint {
return &endpoint{
nicID: nicID,
linkEP: linkEP,
+188 -94
View File
@@ -15,8 +15,6 @@
package ipv6
import (
"fmt"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
@@ -71,6 +69,59 @@ func (e *endpoint) handleControl(typ stack.ControlType, extra uint32, pkt *stack
e.dispatcher.DeliverTransportControlPacket(src, hdr.DestinationAddress(), ProtocolNumber, p, typ, extra, pkt)
}
// getLinkAddrOption searches NDP options for a given link address option using
// the provided getAddr function as a filter. Returns the link address if
// found; otherwise, returns the zero link address value. Also returns true if
// the options are valid as per the wire format, false otherwise.
func getLinkAddrOption(it header.NDPOptionIterator, getAddr func(header.NDPOption) tcpip.LinkAddress) (tcpip.LinkAddress, bool) {
var linkAddr tcpip.LinkAddress
for {
opt, done, err := it.Next()
if err != nil {
return "", false
}
if done {
break
}
if addr := getAddr(opt); len(addr) != 0 {
// No RFCs define what to do when an NDP message has multiple Link-Layer
// Address options. Since no interface can have multiple link-layer
// addresses, we consider such messages invalid.
if len(linkAddr) != 0 {
return "", false
}
linkAddr = addr
}
}
return linkAddr, true
}
// getSourceLinkAddr searches NDP options for the source link address option.
// Returns the link address if found; otherwise, returns the zero link address
// value. Also returns true if the options are valid as per the wire format,
// false otherwise.
func getSourceLinkAddr(it header.NDPOptionIterator) (tcpip.LinkAddress, bool) {
return getLinkAddrOption(it, func(opt header.NDPOption) tcpip.LinkAddress {
if src, ok := opt.(header.NDPSourceLinkLayerAddressOption); ok {
return src.EthernetAddress()
}
return ""
})
}
// getTargetLinkAddr searches NDP options for the target link address option.
// Returns the link address if found; otherwise, returns the zero link address
// value. Also returns true if the options are valid as per the wire format,
// false otherwise.
func getTargetLinkAddr(it header.NDPOptionIterator) (tcpip.LinkAddress, bool) {
return getLinkAddrOption(it, func(opt header.NDPOption) tcpip.LinkAddress {
if dst, ok := opt.(header.NDPTargetLinkLayerAddressOption); ok {
return dst.EthernetAddress()
}
return ""
})
}
func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragmentHeader bool) {
stats := r.Stats().ICMP
sent := stats.V6PacketsSent
@@ -137,7 +188,7 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
case header.ICMPv6NeighborSolicit:
received.NeighborSolicit.Increment()
if pkt.Data.Size() < header.ICMPv6NeighborSolicitMinimumSize || !isNDPValid() {
if !isNDPValid() || pkt.Data.Size() < header.ICMPv6NeighborSolicitMinimumSize {
received.Invalid.Increment()
return
}
@@ -147,14 +198,15 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
// NDP messages cannot be fragmented. Also note that in the common case NDP
// datagrams are very small and ToView() will not incur allocations.
ns := header.NDPNeighborSolicit(payload.ToView())
it, err := ns.Options().Iter(true)
if err != nil {
// If we have a malformed NDP NS option, drop the packet.
targetAddr := ns.TargetAddress()
// As per RFC 4861 section 4.3, the Target Address MUST NOT be a multicast
// address.
if header.IsV6MulticastAddress(targetAddr) {
received.Invalid.Increment()
return
}
targetAddr := ns.TargetAddress()
s := r.Stack()
if isTentative, err := s.IsAddrTentative(e.nicID, targetAddr); err != nil {
// We will only get an error if the NIC is unrecognized, which should not
@@ -187,39 +239,22 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
// so the packet is processed as defined in RFC 4861, as per RFC 4862
// section 5.4.3.
// Is the NS targetting us?
if e.linkAddrCache.CheckLocalAddress(e.nicID, ProtocolNumber, targetAddr) == 0 {
// Is the NS targeting us?
if s.CheckLocalAddress(e.nicID, ProtocolNumber, targetAddr) == 0 {
return
}
// If the NS message contains the Source Link-Layer Address option, update
// the link address cache with the value of the option.
//
// TODO(b/148429853): Properly process the NS message and do Neighbor
// Unreachability Detection.
var sourceLinkAddr tcpip.LinkAddress
for {
opt, done, err := it.Next()
if err != nil {
// This should never happen as Iter(true) above did not return an error.
panic(fmt.Sprintf("unexpected error when iterating over NDP options: %s", err))
}
if done {
break
}
it, err := ns.Options().Iter(false /* check */)
if err != nil {
// Options are not valid as per the wire format, silently drop the packet.
received.Invalid.Increment()
return
}
switch opt := opt.(type) {
case header.NDPSourceLinkLayerAddressOption:
// No RFCs define what to do when an NS message has multiple Source
// Link-Layer Address options. Since no interface can have multiple
// link-layer addresses, we consider such messages invalid.
if len(sourceLinkAddr) != 0 {
received.Invalid.Increment()
return
}
sourceLinkAddr = opt.EthernetAddress()
}
sourceLinkAddr, ok := getSourceLinkAddr(it)
if !ok {
received.Invalid.Increment()
return
}
unspecifiedSource := r.RemoteAddress == header.IPv6Any
@@ -237,6 +272,8 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
} else if unspecifiedSource {
received.Invalid.Increment()
return
} else if e.nud != nil {
e.nud.HandleProbe(r.RemoteAddress, r.LocalAddress, header.IPv6ProtocolNumber, sourceLinkAddr, e.protocol)
} else {
e.linkAddrCache.AddLinkAddress(e.nicID, r.RemoteAddress, sourceLinkAddr)
}
@@ -304,7 +341,7 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
case header.ICMPv6NeighborAdvert:
received.NeighborAdvert.Increment()
if pkt.Data.Size() < header.ICMPv6NeighborAdvertSize || !isNDPValid() {
if !isNDPValid() || pkt.Data.Size() < header.ICMPv6NeighborAdvertSize {
received.Invalid.Increment()
return
}
@@ -314,17 +351,10 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
// 5, NDP messages cannot be fragmented. Also note that in the common case
// NDP datagrams are very small and ToView() will not incur allocations.
na := header.NDPNeighborAdvert(payload.ToView())
it, err := na.Options().Iter(true)
if err != nil {
// If we have a malformed NDP NA option, drop the packet.
received.Invalid.Increment()
return
}
targetAddr := na.TargetAddress()
stack := r.Stack()
s := r.Stack()
if isTentative, err := stack.IsAddrTentative(e.nicID, targetAddr); err != nil {
if isTentative, err := s.IsAddrTentative(e.nicID, targetAddr); err != nil {
// We will only get an error if the NIC is unrecognized, which should not
// happen. For now short-circuit this packet.
//
@@ -335,7 +365,14 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
// DAD on, implying the address is not unique. In this case we let the
// stack know so it can handle such a scenario and do nothing furthur with
// the NDP NA.
stack.DupTentativeAddrDetected(e.nicID, targetAddr)
s.DupTentativeAddrDetected(e.nicID, targetAddr)
return
}
it, err := na.Options().Iter(false /* check */)
if err != nil {
// If we have a malformed NDP NA option, drop the packet.
received.Invalid.Increment()
return
}
@@ -348,39 +385,25 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
// TODO(b/143147598): Handle the scenario described above. Also inform the
// netstack integration that a duplicate address was detected outside of
// DAD.
targetLinkAddr, ok := getTargetLinkAddr(it)
if !ok {
received.Invalid.Increment()
return
}
// If the NA message has the target link layer option, update the link
// address cache with the link address for the target of the message.
//
// TODO(b/148429853): Properly process the NA message and do Neighbor
// Unreachability Detection.
var targetLinkAddr tcpip.LinkAddress
for {
opt, done, err := it.Next()
if err != nil {
// This should never happen as Iter(true) above did not return an error.
panic(fmt.Sprintf("unexpected error when iterating over NDP options: %s", err))
}
if done {
break
}
switch opt := opt.(type) {
case header.NDPTargetLinkLayerAddressOption:
// No RFCs define what to do when an NA message has multiple Target
// Link-Layer Address options. Since no interface can have multiple
// link-layer addresses, we consider such messages invalid.
if len(targetLinkAddr) != 0 {
received.Invalid.Increment()
return
}
targetLinkAddr = opt.EthernetAddress()
}
}
if len(targetLinkAddr) != 0 {
e.linkAddrCache.AddLinkAddress(e.nicID, targetAddr, targetLinkAddr)
if e.nud == nil {
e.linkAddrCache.AddLinkAddress(e.nicID, targetAddr, targetLinkAddr)
return
}
e.nud.HandleConfirmation(targetAddr, targetLinkAddr, stack.ReachabilityConfirmationFlags{
Solicited: na.SolicitedFlag(),
Override: na.OverrideFlag(),
IsRouter: na.RouterFlag(),
})
}
case header.ICMPv6EchoRequest:
@@ -440,27 +463,75 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
case header.ICMPv6RouterSolicit:
received.RouterSolicit.Increment()
if !isNDPValid() {
//
// Validate the RS as per RFC 4861 section 6.1.1.
//
// Is the NDP payload of sufficient size to hold a Router Solictation?
if !isNDPValid() || pkt.Data.Size()-header.ICMPv6HeaderSize < header.NDPRSMinimumSize {
received.Invalid.Increment()
return
}
stack := r.Stack()
// Is the networking stack operating as a router?
if !stack.Forwarding() {
// ... No, silently drop the packet.
received.RouterOnlyPacketsDroppedByHost.Increment()
return
}
// Note that in the common case NDP datagrams are very small and ToView()
// will not incur allocations.
rs := header.NDPRouterSolicit(payload.ToView())
it, err := rs.Options().Iter(false /* check */)
if err != nil {
// Options are not valid as per the wire format, silently drop the packet.
received.Invalid.Increment()
return
}
sourceLinkAddr, ok := getSourceLinkAddr(it)
if !ok {
received.Invalid.Increment()
return
}
// If the RS message has the source link layer option, update the link
// address cache with the link address for the source of the message.
if len(sourceLinkAddr) != 0 {
// As per RFC 4861 section 4.1, the Source Link-Layer Address Option MUST
// NOT be included when the source IP address is the unspecified address.
// Otherwise, it SHOULD be included on link layers that have addresses.
if r.RemoteAddress == header.IPv6Any {
received.Invalid.Increment()
return
}
if e.nud != nil {
// A RS with a specified source IP address modifies the NUD state
// machine in the same way a reachability probe would.
e.nud.HandleProbe(r.RemoteAddress, r.LocalAddress, header.IPv6ProtocolNumber, sourceLinkAddr, e.protocol)
}
}
case header.ICMPv6RouterAdvert:
received.RouterAdvert.Increment()
// Is the NDP payload of sufficient size to hold a Router
// Advertisement?
if pkt.Data.Size()-header.ICMPv6HeaderSize < header.NDPRAMinimumSize || !isNDPValid() {
//
// Validate the RA as per RFC 4861 section 6.1.2.
//
// Is the NDP payload of sufficient size to hold a Router Advertisement?
if !isNDPValid() || pkt.Data.Size()-header.ICMPv6HeaderSize < header.NDPRAMinimumSize {
received.Invalid.Increment()
return
}
routerAddr := iph.SourceAddress()
//
// Validate the RA as per RFC 4861 section 6.1.2.
//
// Is the IP Source Address a link-local address?
if !header.IsV6LinkLocalAddress(routerAddr) {
// ...No, silently drop the packet.
@@ -468,16 +539,18 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
return
}
// The remainder of payload must be only the router advertisement, so
// payload.ToView() always returns the advertisement. Per RFC 6980 section
// 5, NDP messages cannot be fragmented. Also note that in the common case
// NDP datagrams are very small and ToView() will not incur allocations.
// Note that in the common case NDP datagrams are very small and ToView()
// will not incur allocations.
ra := header.NDPRouterAdvert(payload.ToView())
opts := ra.Options()
it, err := ra.Options().Iter(false /* check */)
if err != nil {
// Options are not valid as per the wire format, silently drop the packet.
received.Invalid.Increment()
return
}
// Are options valid as per the wire format?
if _, err := opts.Iter(true); err != nil {
// ...No, silently drop the packet.
sourceLinkAddr, ok := getSourceLinkAddr(it)
if !ok {
received.Invalid.Increment()
return
}
@@ -487,12 +560,33 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
// as RFC 4861 section 6.1.2 is concerned.
//
// If the RA has the source link layer option, update the link address
// cache with the link address for the advertised router.
if len(sourceLinkAddr) != 0 && e.nud != nil {
e.nud.HandleProbe(routerAddr, r.LocalAddress, header.IPv6ProtocolNumber, sourceLinkAddr, e.protocol)
}
// Tell the NIC to handle the RA.
stack := r.Stack()
rxNICID := r.NICID()
stack.HandleNDPRA(rxNICID, routerAddr, ra)
stack.HandleNDPRA(e.nicID, routerAddr, ra)
case header.ICMPv6RedirectMsg:
// TODO(gvisor.dev/issue/2285): Call `e.nud.HandleProbe` after validating
// this redirect message, as per RFC 4871 section 7.3.3:
//
// "A Neighbor Cache entry enters the STALE state when created as a
// result of receiving packets other than solicited Neighbor
// Advertisements (i.e., Router Solicitations, Router Advertisements,
// Redirects, and Neighbor Solicitations). These packets contain the
// link-layer address of either the sender or, in the case of Redirect,
// the redirection target. However, receipt of these link-layer
// addresses does not confirm reachability of the forward-direction path
// to that node. Placing a newly created Neighbor Cache entry for which
// the link-layer address is known in the STALE state provides assurance
// that path failures are detected quickly. In addition, should a cached
// link-layer address be modified due to receiving one of the above
// messages, the state SHOULD also be set to STALE to provide prompt
// verification that the path to the new link-layer address is working."
received.RedirectMsg.Increment()
if !isNDPValid() {
received.Invalid.Increment()
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -48,6 +48,7 @@ type endpoint struct {
nicID tcpip.NICID
linkEP stack.LinkEndpoint
linkAddrCache stack.LinkAddressCache
nud stack.NUDHandler
dispatcher stack.TransportDispatcher
protocol *protocol
stack *stack.Stack
@@ -455,11 +456,12 @@ func (*protocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) {
}
// NewEndpoint creates a new ipv6 endpoint.
func (p *protocol) NewEndpoint(nicID tcpip.NICID, linkAddrCache stack.LinkAddressCache, dispatcher stack.TransportDispatcher, linkEP stack.LinkEndpoint, st *stack.Stack) stack.NetworkEndpoint {
func (p *protocol) NewEndpoint(nicID tcpip.NICID, linkAddrCache stack.LinkAddressCache, nud stack.NUDHandler, dispatcher stack.TransportDispatcher, linkEP stack.LinkEndpoint, st *stack.Stack) stack.NetworkEndpoint {
return &endpoint{
nicID: nicID,
linkEP: linkEP,
linkAddrCache: linkAddrCache,
nud: nud,
dispatcher: dispatcher,
protocol: p,
stack: st,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+68
View File
@@ -275,3 +275,71 @@ 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)
}
}
}
File diff suppressed because it is too large Load Diff
+79 -15
View File
@@ -21,6 +21,7 @@ import (
"sort"
"sync/atomic"
"gvisor.dev/gvisor/pkg/sleep"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
@@ -135,6 +136,16 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC
}
nic.mu.ndp.initializeTempAddrState()
// Check for Neighbor Unreachability Detection support.
if ep.Capabilities()&CapabilityResolutionRequired != 0 && len(stack.linkAddrResolvers) != 0 && stack.useNeighborCache {
rng := rand.New(rand.NewSource(stack.clock.NowNanoseconds()))
nic.neigh = &neighborCache{
nic: nic,
state: NewNUDState(stack.nudConfigs, rng),
cache: make(map[tcpip.Address]*neighborEntry, neighborCacheSize),
}
}
// Register supported packet endpoint protocols.
for _, netProto := range header.Ethertypes {
nic.mu.packetEPs[netProto] = []PacketEndpoint{}
@@ -142,17 +153,7 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC
for _, netProto := range stack.networkProtocols {
netNum := netProto.Number()
nic.mu.packetEPs[netNum] = nil
nic.networkEndpoints[netNum] = netProto.NewEndpoint(id, stack, nic, ep, stack)
}
// Check for Neighbor Unreachability Detection support.
if ep.Capabilities()&CapabilityResolutionRequired != 0 && len(stack.linkAddrResolvers) != 0 {
rng := rand.New(rand.NewSource(stack.clock.NowNanoseconds()))
nic.neigh = &neighborCache{
nic: nic,
state: NewNUDState(stack.nudConfigs, rng),
cache: make(map[tcpip.Address]*neighborEntry, neighborCacheSize),
}
nic.networkEndpoints[netNum] = netProto.NewEndpoint(id, stack, nic.neigh, nic, ep, stack)
}
nic.linkEP.Attach(nic)
@@ -431,7 +432,7 @@ func (n *NIC) setSpoofing(enable bool) {
// If an IPv6 primary endpoint is requested, Source Address Selection (as
// defined by RFC 6724 section 5) will be performed.
func (n *NIC) primaryEndpoint(protocol tcpip.NetworkProtocolNumber, remoteAddr tcpip.Address) *referencedNetworkEndpoint {
if protocol == header.IPv6ProtocolNumber && remoteAddr != "" {
if protocol == header.IPv6ProtocolNumber && len(remoteAddr) != 0 {
return n.primaryIPv6Endpoint(remoteAddr)
}
@@ -818,11 +819,24 @@ func (n *NIC) addAddressLocked(protocolAddress tcpip.ProtocolAddress, peb Primar
}
}
ep, ok := n.networkEndpoints[protocolAddress.Protocol]
netProto, ok := n.stack.networkProtocols[protocolAddress.Protocol]
if !ok {
return nil, tcpip.ErrUnknownProtocol
}
var nud NUDHandler
if n.neigh != nil {
// An interface value that holds a nil concrete value is itself non-nil.
// For this reason, n.neigh cannot be passed directly to NewEndpoint so
// NetworkEndpoints don't confuse it for non-nil.
//
// See https://golang.org/doc/faq#nil_error for more information.
nud = n.neigh
}
// Create the new network endpoint.
ep := netProto.NewEndpoint(n.id, n.stack, nud, n, n.linkEP, n.stack)
isIPv6Unicast := protocolAddress.Protocol == header.IPv6ProtocolNumber && header.IsV6UnicastAddress(protocolAddress.AddressWithPrefix.Address)
// If the address is an IPv6 address and it is a permanent address,
@@ -844,10 +858,11 @@ func (n *NIC) addAddressLocked(protocolAddress tcpip.ProtocolAddress, peb Primar
deprecated: deprecated,
}
// Set up cache if link address resolution exists for this protocol.
// Set up resolver if link address resolution exists for this protocol.
if n.linkEP.Capabilities()&CapabilityResolutionRequired != 0 {
if _, ok := n.stack.linkAddrResolvers[protocolAddress.Protocol]; ok {
if linkRes, ok := n.stack.linkAddrResolvers[protocolAddress.Protocol]; ok {
ref.linkCache = n.stack
ref.linkRes = linkRes
}
}
@@ -1082,6 +1097,51 @@ func (n *NIC) RemoveAddress(addr tcpip.Address) *tcpip.Error {
return n.removePermanentAddressLocked(addr)
}
func (n *NIC) neighbors() ([]NeighborEntry, *tcpip.Error) {
if n.neigh == nil {
return nil, tcpip.ErrNotSupported
}
return n.neigh.entries(), nil
}
func (n *NIC) removeWaker(addr tcpip.Address, w *sleep.Waker) {
if n.neigh == nil {
return
}
n.neigh.removeWaker(addr, w)
}
func (n *NIC) addStaticNeighbor(addr tcpip.Address, linkAddress tcpip.LinkAddress) *tcpip.Error {
if n.neigh == nil {
return tcpip.ErrNotSupported
}
n.neigh.addStaticEntry(addr, linkAddress)
return nil
}
func (n *NIC) removeNeighbor(addr tcpip.Address) *tcpip.Error {
if n.neigh == nil {
return tcpip.ErrNotSupported
}
if !n.neigh.removeEntry(addr) {
return tcpip.ErrBadAddress
}
return nil
}
func (n *NIC) clearNeighbors() *tcpip.Error {
if n.neigh == nil {
return tcpip.ErrNotSupported
}
n.neigh.clear()
return nil
}
// joinGroup adds a new endpoint for the given multicast address, if none
// exists yet. Otherwise it just increments its count.
func (n *NIC) joinGroup(protocol tcpip.NetworkProtocolNumber, addr tcpip.Address) *tcpip.Error {
@@ -1662,6 +1722,10 @@ type referencedNetworkEndpoint struct {
// protocol. Set to nil otherwise.
linkCache LinkAddressCache
// linkRes is set if link address resolution is enabled for this protocol.
// Set to nil otherwise.
linkRes LinkAddressResolver
// refs is counting references held for this endpoint. When refs hits zero it
// triggers the automatic removal of the endpoint from the NIC.
refs int32
+1 -1
View File
@@ -192,7 +192,7 @@ func (*testIPv6Protocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address)
}
// NewEndpoint implements NetworkProtocol.NewEndpoint.
func (p *testIPv6Protocol) NewEndpoint(nicID tcpip.NICID, _ LinkAddressCache, _ TransportDispatcher, linkEP LinkEndpoint, _ *Stack) NetworkEndpoint {
func (p *testIPv6Protocol) NewEndpoint(nicID tcpip.NICID, _ LinkAddressCache, _ NUDHandler, _ TransportDispatcher, linkEP LinkEndpoint, _ *Stack) NetworkEndpoint {
return &testIPv6Endpoint{
nicID: nicID,
linkEP: linkEP,
+14 -2
View File
@@ -61,6 +61,7 @@ func TestSetNUDConfigurationFailsForBadNICID(t *testing.T) {
// stack will only allocate neighbor caches if a protocol providing link
// address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
UseNeighborCache: true,
})
// No NIC with ID 1 yet.
@@ -84,7 +85,8 @@ func TestNUDConfigurationFailsForNotSupported(t *testing.T) {
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
s := stack.New(stack.Options{
NUDConfigs: stack.DefaultNUDConfigurations(),
NUDConfigs: stack.DefaultNUDConfigurations(),
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -108,7 +110,8 @@ func TestSetNUDConfigurationFailsForNotSupported(t *testing.T) {
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
s := stack.New(stack.Options{
NUDConfigs: stack.DefaultNUDConfigurations(),
NUDConfigs: stack.DefaultNUDConfigurations(),
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -136,6 +139,7 @@ func TestDefaultNUDConfigurations(t *testing.T) {
// address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: stack.DefaultNUDConfigurations(),
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -190,6 +194,7 @@ func TestNUDConfigurationsBaseReachableTime(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -246,6 +251,7 @@ func TestNUDConfigurationsMinRandomFactor(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -325,6 +331,7 @@ func TestNUDConfigurationsMaxRandomFactor(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -386,6 +393,7 @@ func TestNUDConfigurationsRetransmitTimer(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -437,6 +445,7 @@ func TestNUDConfigurationsDelayFirstProbeTime(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -488,6 +497,7 @@ func TestNUDConfigurationsMaxMulticastProbes(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -539,6 +549,7 @@ func TestNUDConfigurationsMaxUnicastProbes(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@@ -590,6 +601,7 @@ func TestNUDConfigurationsUnreachableTime(t *testing.T) {
// providing link address resolution is specified (e.g. ARP or IPv6).
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NUDConfigs: c,
UseNeighborCache: true,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
+2 -2
View File
@@ -298,7 +298,7 @@ type NetworkProtocol interface {
ParseAddresses(v buffer.View) (src, dst tcpip.Address)
// NewEndpoint creates a new endpoint of this protocol.
NewEndpoint(nicID tcpip.NICID, linkAddrCache LinkAddressCache, dispatcher TransportDispatcher, sender LinkEndpoint, st *Stack) NetworkEndpoint
NewEndpoint(nicID tcpip.NICID, linkAddrCache LinkAddressCache, nud NUDHandler, dispatcher TransportDispatcher, sender LinkEndpoint, st *Stack) NetworkEndpoint
// SetOption allows enabling/disabling protocol specific features.
// SetOption returns an error if the option is not supported or the
@@ -488,7 +488,7 @@ type LinkAddressResolver interface {
ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool)
// LinkAddressProtocol returns the network protocol of the
// addresses this this resolver can resolve.
// addresses this resolver can resolve.
LinkAddressProtocol() tcpip.NetworkProtocolNumber
}
+19
View File
@@ -141,6 +141,16 @@ func (r *Route) Resolve(waker *sleep.Waker) (<-chan struct{}, *tcpip.Error) {
}
nextAddr = r.RemoteAddress
}
if r.ref.nic.neigh != nil {
entry, ch, err := r.ref.nic.neigh.entry(nextAddr, r.LocalAddress, r.ref.linkRes, waker)
if err != nil {
return ch, err
}
r.RemoteLinkAddress = entry.LinkAddr
return nil, nil
}
linkAddr, ch, err := r.ref.linkCache.GetLinkAddress(r.ref.nic.ID(), nextAddr, r.LocalAddress, r.NetProto, waker)
if err != nil {
return ch, err
@@ -155,6 +165,12 @@ func (r *Route) RemoveWaker(waker *sleep.Waker) {
if nextAddr == "" {
nextAddr = r.RemoteAddress
}
if r.ref.nic.neigh != nil {
r.ref.nic.neigh.removeWaker(nextAddr, waker)
return
}
r.ref.linkCache.RemoveWaker(r.ref.nic.ID(), nextAddr, waker)
}
@@ -163,6 +179,9 @@ func (r *Route) RemoveWaker(waker *sleep.Waker) {
//
// The NIC r uses must not be locked.
func (r *Route) IsResolutionRequired() bool {
if r.ref.nic.neigh != nil {
return r.ref.isValidForOutgoing() && r.ref.linkRes != nil && r.RemoteLinkAddress == ""
}
return r.ref.isValidForOutgoing() && r.ref.linkCache != nil && r.RemoteLinkAddress == ""
}
+89 -11
View File
@@ -248,7 +248,7 @@ type RcvBufAutoTuneParams struct {
// was started.
MeasureTime time.Time
// CopiedBytes is the number of bytes copied to userspace since
// CopiedBytes is the number of bytes copied to user space since
// this measure began.
CopiedBytes int
@@ -461,6 +461,10 @@ 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.
useNeighborCache bool
// autoGenIPv6LinkLocal determines whether or not the stack will attempt
// to auto-generate an IPv6 link-local address for newly enabled non-loopback
// NICs. See the AutoGenIPv6LinkLocal field of Options for more details.
@@ -541,6 +545,13 @@ type Options struct {
// NUDConfigs is the default NUD configurations used by interfaces.
NUDConfigs NUDConfigurations
// UseNeighborCache indicates whether ARP and NDP packets should be handled
// by the Neighbor Unreachability Detection (NUD) state machine. This flag
// also enables the APIs for inspecting and modifying the neighbor table via
// NUDDispatcher and the following Stack methods: Neighbors, RemoveNeighbor,
// and ClearNeighbors.
UseNeighborCache bool
// AutoGenIPv6LinkLocal determines whether or not the stack will attempt to
// auto-generate an IPv6 link-local address for newly enabled non-loopback
// NICs.
@@ -715,6 +726,7 @@ func New(opts Options) *Stack {
seed: generateRandUint32(),
ndpConfigs: opts.NDPConfigs,
nudConfigs: opts.NUDConfigs,
useNeighborCache: opts.UseNeighborCache,
autoGenIPv6LinkLocal: opts.AutoGenIPv6LinkLocal,
uniqueIDGenerator: opts.UniqueID,
ndpDisp: opts.NDPDisp,
@@ -1209,8 +1221,8 @@ func (s *Stack) AddProtocolAddressWithOptions(id tcpip.NICID, protocolAddress tc
s.mu.RLock()
defer s.mu.RUnlock()
nic := s.nics[id]
if nic == nil {
nic, ok := s.nics[id]
if !ok {
return tcpip.ErrUnknownNICID
}
@@ -1335,8 +1347,8 @@ func (s *Stack) CheckLocalAddress(nicID tcpip.NICID, protocol tcpip.NetworkProto
// If a NIC is specified, we try to find the address there only.
if nicID != 0 {
nic := s.nics[nicID]
if nic == nil {
nic, ok := s.nics[nicID]
if !ok {
return 0
}
@@ -1367,8 +1379,8 @@ func (s *Stack) SetPromiscuousMode(nicID tcpip.NICID, enable bool) *tcpip.Error
s.mu.RLock()
defer s.mu.RUnlock()
nic := s.nics[nicID]
if nic == nil {
nic, ok := s.nics[nicID]
if !ok {
return tcpip.ErrUnknownNICID
}
@@ -1383,8 +1395,8 @@ func (s *Stack) SetSpoofing(nicID tcpip.NICID, enable bool) *tcpip.Error {
s.mu.RLock()
defer s.mu.RUnlock()
nic := s.nics[nicID]
if nic == nil {
nic, ok := s.nics[nicID]
if !ok {
return tcpip.ErrUnknownNICID
}
@@ -1416,8 +1428,33 @@ func (s *Stack) GetLinkAddress(nicID tcpip.NICID, addr, localAddr tcpip.Address,
return s.linkAddrCache.get(fullAddr, linkRes, localAddr, nic.linkEP, waker)
}
// RemoveWaker implements LinkAddressCache.RemoveWaker.
// Neighbors returns all IP to MAC address associations.
func (s *Stack) Neighbors(nicID tcpip.NICID) ([]NeighborEntry, *tcpip.Error) {
s.mu.RLock()
nic, ok := s.nics[nicID]
s.mu.RUnlock()
if !ok {
return nil, tcpip.ErrUnknownNICID
}
return nic.neighbors()
}
// RemoveWaker removes a waker that has been added when link resolution for
// addr was requested.
func (s *Stack) RemoveWaker(nicID tcpip.NICID, addr tcpip.Address, waker *sleep.Waker) {
if s.useNeighborCache {
s.mu.RLock()
nic, ok := s.nics[nicID]
s.mu.RUnlock()
if ok {
nic.removeWaker(addr, waker)
}
return
}
s.mu.RLock()
defer s.mu.RUnlock()
@@ -1427,6 +1464,47 @@ func (s *Stack) RemoveWaker(nicID tcpip.NICID, addr tcpip.Address, waker *sleep.
}
}
// AddStaticNeighbor statically associates an IP address to a MAC address.
func (s *Stack) AddStaticNeighbor(nicID tcpip.NICID, addr tcpip.Address, linkAddr tcpip.LinkAddress) *tcpip.Error {
s.mu.RLock()
nic, ok := s.nics[nicID]
s.mu.RUnlock()
if !ok {
return tcpip.ErrUnknownNICID
}
return nic.addStaticNeighbor(addr, linkAddr)
}
// RemoveNeighbor removes an IP to MAC address association previously created
// either automically or by AddStaticNeighbor. Returns ErrBadAddress if there
// is no association with the provided address.
func (s *Stack) RemoveNeighbor(nicID tcpip.NICID, addr tcpip.Address) *tcpip.Error {
s.mu.RLock()
nic, ok := s.nics[nicID]
s.mu.RUnlock()
if !ok {
return tcpip.ErrUnknownNICID
}
return nic.removeNeighbor(addr)
}
// ClearNeighbors removes all IP to MAC address associations.
func (s *Stack) ClearNeighbors(nicID tcpip.NICID) *tcpip.Error {
s.mu.RLock()
nic, ok := s.nics[nicID]
s.mu.RUnlock()
if !ok {
return tcpip.ErrUnknownNICID
}
return nic.clearNeighbors()
}
// RegisterTransportEndpoint registers the given endpoint with the stack
// transport dispatcher. Received packets that match the provided id will be
// delivered to the given endpoint; specifying a nic is optional, but
@@ -1961,7 +2039,7 @@ func (s *Stack) FindNetworkEndpoint(netProto tcpip.NetworkProtocolNumber, addres
return nil, tcpip.ErrBadAddress
}
// FindNICNameFromID returns the name of the nic for the given NICID.
// FindNICNameFromID returns the name of the NIC for the given NICID.
func (s *Stack) FindNICNameFromID(id tcpip.NICID) string {
s.mu.RLock()
defer s.mu.RUnlock()
+1 -1
View File
@@ -197,7 +197,7 @@ func (*fakeNetworkProtocol) ParseAddresses(v buffer.View) (src, dst tcpip.Addres
return tcpip.Address(v[srcAddrOffset : srcAddrOffset+1]), tcpip.Address(v[dstAddrOffset : dstAddrOffset+1])
}
func (f *fakeNetworkProtocol) NewEndpoint(nicID tcpip.NICID, linkAddrCache stack.LinkAddressCache, dispatcher stack.TransportDispatcher, ep stack.LinkEndpoint, _ *stack.Stack) stack.NetworkEndpoint {
func (f *fakeNetworkProtocol) NewEndpoint(nicID tcpip.NICID, _ stack.LinkAddressCache, _ stack.NUDHandler, dispatcher stack.TransportDispatcher, ep stack.LinkEndpoint, _ *stack.Stack) stack.NetworkEndpoint {
return &fakeNetworkEndpoint{
nicID: nicID,
proto: f,
+4
View File
@@ -1192,6 +1192,10 @@ type ICMPv6ReceivedPacketStats struct {
// Invalid is the total number of ICMPv6 packets received that the
// transport layer could not parse.
Invalid *StatCounter
// RouterOnlyPacketsDroppedByHost is the total number of ICMPv6 packets
// dropped due to being router-specific packets.
RouterOnlyPacketsDroppedByHost *StatCounter
}
// ICMPStats collects ICMP-specific stats (both v4 and v6).

Some files were not shown because too many files have changed in this diff Show More