Files
Kevin KrakauerandgVisor bot 3c75945fd6 netstack: remove PacketBuffer.IsNil()
The change was originally motivated by ticket references (cl/450976957), which
were never implemented.

PiperOrigin-RevId: 617480960
2024-03-20 05:31:48 -07:00

1398 lines
41 KiB
Go

// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ipv6
import (
"math/rand"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checker"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/faketime"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/prependable"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
)
var _ NDPDispatcher = (*testNDPDispatcher)(nil)
// testNDPDispatcher is an NDPDispatcher only allows default router discovery.
type testNDPDispatcher struct {
addr tcpip.Address
}
func (*testNDPDispatcher) OnDuplicateAddressDetectionResult(tcpip.NICID, tcpip.Address, stack.DADResult) {
}
func (t *testNDPDispatcher) OnOffLinkRouteUpdated(_ tcpip.NICID, _ tcpip.Subnet, addr tcpip.Address, _ header.NDPRoutePreference) {
t.addr = addr
}
func (t *testNDPDispatcher) OnOffLinkRouteInvalidated(_ tcpip.NICID, _ tcpip.Subnet, addr tcpip.Address) {
t.addr = addr
}
func (*testNDPDispatcher) OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet) {
}
func (*testNDPDispatcher) OnOnLinkPrefixInvalidated(tcpip.NICID, tcpip.Subnet) {
}
func (*testNDPDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) stack.AddressDispatcher {
return nil
}
func (*testNDPDispatcher) OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) {
}
func (*testNDPDispatcher) OnAutoGenAddressInvalidated(tcpip.NICID, tcpip.AddressWithPrefix) {
}
func (*testNDPDispatcher) OnRecursiveDNSServerOption(tcpip.NICID, []tcpip.Address, time.Duration) {
}
func (*testNDPDispatcher) OnDNSSearchListOption(tcpip.NICID, []string, time.Duration) {
}
func (*testNDPDispatcher) OnDHCPv6Configuration(tcpip.NICID, DHCPv6ConfigurationFromNDPRA) {
}
func TestStackNDPEndpointInvalidateDefaultRouter(t *testing.T) {
var ndpDisp testNDPDispatcher
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocolWithOptions(Options{
NDPDisp: &ndpDisp,
})},
})
defer s.Close()
if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
}
ep, err := s.GetNetworkEndpoint(nicID, ProtocolNumber)
if err != nil {
t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, ProtocolNumber, err)
}
ipv6EP := ep.(*endpoint)
ipv6EP.mu.Lock()
ipv6EP.mu.ndp.handleOffLinkRouteDiscovery(offLinkRoute{dest: header.IPv6EmptySubnet, router: lladdr1}, time.Hour, header.MediumRoutePreference)
ipv6EP.mu.Unlock()
if ndpDisp.addr != lladdr1 {
t.Fatalf("got ndpDisp.addr = %s, want = %s", ndpDisp.addr, lladdr1)
}
ndpDisp.addr = tcpip.Address{}
ndpEP := ep.(stack.NDPEndpoint)
ndpEP.InvalidateDefaultRouter(lladdr1)
if ndpDisp.addr != lladdr1 {
t.Fatalf("got ndpDisp.addr = %s, want = %s", ndpDisp.addr, lladdr1)
}
}
// TestNeighborSolicitationWithSourceLinkLayerOption tests that receiving a
// valid NDP NS message with the Source Link Layer Address option results in a
// new entry in the link address cache for the sender of the message.
func TestNeighborSolicitationWithSourceLinkLayerOption(t *testing.T) {
const nicID = 1
tests := []struct {
name string
optsBuf []byte
expectedLinkAddr tcpip.LinkAddress
}{
{
name: "Valid",
optsBuf: []byte{1, 1, 2, 3, 4, 5, 6, 7},
expectedLinkAddr: "\x02\x03\x04\x05\x06\x07",
},
{
name: "Too Small",
optsBuf: []byte{1, 1, 2, 3, 4, 5, 6},
},
{
name: "Invalid Length",
optsBuf: []byte{1, 2, 2, 3, 4, 5, 6, 7},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := newTestContext()
defer c.cleanup()
s := c.s
e := channel.New(0, 1280, linkAddr0)
defer e.Close()
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: ProtocolNumber,
AddressWithPrefix: lladdr0.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize + len(test.optsBuf)
hdr := prependable.New(header.IPv6MinimumSize + ndpNSSize)
pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
pkt.SetType(header.ICMPv6NeighborSolicit)
ns := header.NDPNeighborSolicit(pkt.MessageBody())
ns.SetTargetAddress(lladdr0)
opts := ns.Options()
copy(opts, test.optsBuf)
pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: pkt,
Src: lladdr1,
Dst: lladdr0,
}))
payloadLength := hdr.UsedLength()
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: 255,
SrcAddr: lladdr1,
DstAddr: lladdr0,
})
invalid := s.Stats().ICMP.V6.PacketsReceived.Invalid
// Invalid count should initially be 0.
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
pktBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(hdr.View()),
})
e.InjectInbound(ProtocolNumber, pktBuf)
pktBuf.DecRef()
neighbors, err := s.Neighbors(nicID, ProtocolNumber)
if err != nil {
t.Fatalf("s.Neighbors(%d, %d): %s", nicID, ProtocolNumber, 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("s.Neighbors(%d, %d) returned unexpected duplicate neighbor entry (-existing +got):\n%s", nicID, ProtocolNumber, diff)
}
t.Fatalf("s.Neighbors(%d, %d) returned unexpected duplicate neighbor entry: %#v", nicID, ProtocolNumber, existing)
}
neighborByAddr[n.Addr] = n
}
if neigh, ok := neighborByAddr[lladdr1]; len(test.expectedLinkAddr) != 0 {
// Invalid count should not have increased.
if got := invalid.Value(); got != 0 {
t.Errorf("got invalid = %d, want = 0", got)
}
if !ok {
t.Fatalf("expected a neighbor entry for %q", lladdr1)
}
if neigh.LinkAddr != test.expectedLinkAddr {
t.Errorf("got link address = %s, want = %s", neigh.LinkAddr, test.expectedLinkAddr)
}
if neigh.State != stack.Stale {
t.Errorf("got NUD state = %s, want = %s", neigh.State, stack.Stale)
}
} else {
// Invalid count should have increased.
if got := invalid.Value(); got != 1 {
t.Errorf("got invalid = %d, want = 1", got)
}
if ok {
t.Fatalf("unexpectedly got neighbor entry: %#v", neigh)
}
}
})
}
}
func TestNeighborSolicitationResponse(t *testing.T) {
const nicID = 1
nicAddr := lladdr0
remoteAddr := lladdr1
nicAddrSNMC := header.SolicitedNodeAddr(nicAddr)
nicLinkAddr := linkAddr0
remoteLinkAddr0 := linkAddr1
remoteLinkAddr1 := linkAddr2
tests := []struct {
name string
nsOpts header.NDPOptionsSerializer
nsSrc tcpip.Address
nsDst tcpip.Address
nsInvalid bool
naDstLinkAddr tcpip.LinkAddress
naSolicited bool
naSrc tcpip.Address
naDst tcpip.Address
performsLinkResolution bool
forwardingEnabled bool
}{
{
name: "Unspecified source to solicited-node multicast destination",
nsOpts: nil,
nsSrc: header.IPv6Any,
nsDst: nicAddrSNMC,
nsInvalid: false,
naDstLinkAddr: header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllNodesMulticastAddress),
naSolicited: false,
naSrc: nicAddr,
naDst: header.IPv6AllNodesMulticastAddress,
},
{
name: "Unspecified source with source ll option to multicast destination",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
},
nsSrc: header.IPv6Any,
nsDst: nicAddrSNMC,
nsInvalid: true,
},
{
name: "Unspecified source to unicast destination",
nsOpts: nil,
nsSrc: header.IPv6Any,
nsDst: nicAddr,
nsInvalid: true,
},
{
name: "Unspecified source with source ll option to unicast destination",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
},
nsSrc: header.IPv6Any,
nsDst: nicAddr,
nsInvalid: true,
},
{
name: "Specified source with 1 source ll to multicast destination",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
},
nsSrc: remoteAddr,
nsDst: nicAddrSNMC,
nsInvalid: false,
naDstLinkAddr: remoteLinkAddr0,
naSolicited: true,
naSrc: nicAddr,
naDst: remoteAddr,
},
{
name: "Specified source with 1 source ll different from route to multicast destination",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr1[:]),
},
nsSrc: remoteAddr,
nsDst: nicAddrSNMC,
nsInvalid: false,
naDstLinkAddr: remoteLinkAddr1,
naSolicited: true,
naSrc: nicAddr,
naDst: remoteAddr,
},
{
name: "Specified source to multicast destination",
nsOpts: nil,
nsSrc: remoteAddr,
nsDst: nicAddrSNMC,
nsInvalid: true,
},
{
name: "Specified source with 2 source ll to multicast destination",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr1[:]),
},
nsSrc: remoteAddr,
nsDst: nicAddrSNMC,
nsInvalid: true,
},
{
name: "Specified source to unicast destination",
nsOpts: nil,
nsSrc: remoteAddr,
nsDst: nicAddr,
nsInvalid: false,
naDstLinkAddr: remoteLinkAddr0,
naSolicited: true,
naSrc: nicAddr,
naDst: remoteAddr,
// Since we send a unicast solicitations to a node without an entry for
// the remote, the node needs to perform neighbor discovery to get the
// remote's link address to send the advertisement response.
performsLinkResolution: true,
},
{
name: "Specified source with 1 source ll to unicast destination",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
},
nsSrc: remoteAddr,
nsDst: nicAddr,
nsInvalid: false,
naDstLinkAddr: remoteLinkAddr0,
naSolicited: true,
naSrc: nicAddr,
naDst: remoteAddr,
},
{
name: "Specified source with 1 source ll different from route to unicast destination",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr1[:]),
},
nsSrc: remoteAddr,
nsDst: nicAddr,
nsInvalid: false,
naDstLinkAddr: remoteLinkAddr1,
naSolicited: true,
naSrc: nicAddr,
naDst: remoteAddr,
},
{
name: "Specified source with 2 source ll to unicast destination",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr1[:]),
},
nsSrc: remoteAddr,
nsDst: nicAddr,
nsInvalid: true,
},
{
name: "Specified source with 1 source ll to multicast destination with forwarding enabled",
nsOpts: header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
},
nsSrc: remoteAddr,
nsDst: nicAddrSNMC,
nsInvalid: false,
naDstLinkAddr: remoteLinkAddr0,
naSolicited: true,
naSrc: nicAddr,
naDst: remoteAddr,
forwardingEnabled: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := newTestContext()
defer c.cleanup()
s := c.s
if err := s.SetForwardingDefaultAndAllNICs(header.IPv6ProtocolNumber, test.forwardingEnabled); err != nil {
t.Fatalf("SetForwardingDefaultAndAllNICs(%t): %s", test.forwardingEnabled, err)
}
e := channel.New(1, 1280, nicLinkAddr)
defer e.Close()
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: ProtocolNumber,
AddressWithPrefix: nicAddr.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
s.SetRouteTable([]tcpip.Route{
{
Destination: header.IPv6EmptySubnet,
NIC: 1,
},
})
ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize + test.nsOpts.Length()
hdr := prependable.New(header.IPv6MinimumSize + ndpNSSize)
pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
pkt.SetType(header.ICMPv6NeighborSolicit)
ns := header.NDPNeighborSolicit(pkt.MessageBody())
ns.SetTargetAddress(nicAddr)
opts := ns.Options()
opts.Serialize(test.nsOpts)
pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: pkt,
Src: test.nsSrc,
Dst: test.nsDst,
}))
payloadLength := hdr.UsedLength()
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: 255,
SrcAddr: test.nsSrc,
DstAddr: test.nsDst,
})
invalid := s.Stats().ICMP.V6.PacketsReceived.Invalid
// Invalid count should initially be 0.
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
pktBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(hdr.View()),
})
e.InjectInbound(ProtocolNumber, pktBuf)
pktBuf.DecRef()
if test.nsInvalid {
if got := invalid.Value(); got != 1 {
t.Fatalf("got invalid = %d, want = 1", got)
}
if p := e.Read(); p != nil {
t.Fatalf("unexpected response to an invalid NS = %+v", p)
}
// If we expected the NS to be invalid, we have nothing else to check.
return
}
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
if test.performsLinkResolution {
c.clock.RunImmediatelyScheduledJobs()
p := e.Read()
if p == nil {
t.Fatal("expected an NDP NS response")
}
respNSDst := header.SolicitedNodeAddr(test.nsSrc)
var want stack.RouteInfo
want.NetProto = ProtocolNumber
want.LocalLinkAddress = nicLinkAddr
want.RemoteLinkAddress = header.EthernetAddressFromMulticastIPv6Address(respNSDst)
if diff := cmp.Diff(want, p.EgressRoute, cmp.AllowUnexported(want)); diff != "" {
t.Errorf("route info mismatch (-want +got):\n%s", diff)
}
payload := stack.PayloadSince(p.NetworkHeader())
defer payload.Release()
checker.IPv6(t, payload,
checker.SrcAddr(nicAddr),
checker.DstAddr(respNSDst),
checker.TTL(header.NDPHopLimit),
checker.NDPNS(
checker.NDPNSTargetAddress(test.nsSrc),
checker.NDPNSOptions([]header.NDPOption{
header.NDPSourceLinkLayerAddressOption(nicLinkAddr),
}),
))
p.DecRef()
ser := header.NDPOptionsSerializer{
header.NDPTargetLinkLayerAddressOption(linkAddr1),
}
ndpNASize := header.ICMPv6NeighborAdvertMinimumSize + ser.Length()
hdr := prependable.New(header.IPv6MinimumSize + ndpNASize)
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
pkt.SetType(header.ICMPv6NeighborAdvert)
na := header.NDPNeighborAdvert(pkt.MessageBody())
na.SetSolicitedFlag(true)
na.SetOverrideFlag(true)
na.SetRouterFlag(test.forwardingEnabled)
na.SetTargetAddress(test.nsSrc)
na.Options().Serialize(ser)
pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: pkt,
Src: test.nsSrc,
Dst: nicAddr,
}))
payloadLength := hdr.UsedLength()
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: header.NDPHopLimit,
SrcAddr: test.nsSrc,
DstAddr: nicAddr,
})
pktBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(hdr.View()),
})
e.InjectInbound(ProtocolNumber, pktBuf)
pktBuf.DecRef()
}
c.clock.RunImmediatelyScheduledJobs()
p := e.Read()
if p == nil {
t.Fatal("expected an NDP NA response")
}
defer p.DecRef()
if p.EgressRoute.LocalAddress != test.naSrc {
t.Errorf("got p.EgressRoute.LocalAddress = %s, want = %s", p.EgressRoute.LocalAddress, test.naSrc)
}
if p.EgressRoute.LocalLinkAddress != nicLinkAddr {
t.Errorf("p.EgressRoute.LocalLinkAddress = %s, want = %s", p.EgressRoute.LocalLinkAddress, nicLinkAddr)
}
if p.EgressRoute.RemoteAddress != test.naDst {
t.Errorf("got p.EgressRoute.RemoteAddress = %s, want = %s", p.EgressRoute.RemoteAddress, test.naDst)
}
if p.EgressRoute.RemoteLinkAddress != test.naDstLinkAddr {
t.Errorf("got p.EgressRoute.RemoteLinkAddress = %s, want = %s", p.EgressRoute.RemoteLinkAddress, test.naDstLinkAddr)
}
payload := stack.PayloadSince(p.NetworkHeader())
defer payload.Release()
checker.IPv6(t, payload,
checker.SrcAddr(test.naSrc),
checker.DstAddr(test.naDst),
checker.TTL(header.NDPHopLimit),
checker.NDPNA(
checker.NDPNASolicitedFlag(test.naSolicited),
checker.NDPNATargetAddress(nicAddr),
checker.NDPNAOptions([]header.NDPOption{
header.NDPTargetLinkLayerAddressOption(nicLinkAddr[:]),
}),
))
})
}
}
// TestNeighborAdvertisementWithTargetLinkLayerOption tests that receiving a
// valid NDP NA message with the Target Link Layer Address option does not
// result in a new entry in the neighbor cache for the target of the message.
func TestNeighborAdvertisementWithTargetLinkLayerOption(t *testing.T) {
const nicID = 1
tests := []struct {
name string
optsBuf []byte
isValid bool
}{
{
name: "Valid",
optsBuf: []byte{2, 1, 2, 3, 4, 5, 6, 7},
isValid: true,
},
{
name: "Too Small",
optsBuf: []byte{2, 1, 2, 3, 4, 5, 6},
},
{
name: "Invalid Length",
optsBuf: []byte{2, 2, 2, 3, 4, 5, 6, 7},
},
{
name: "Multiple",
optsBuf: []byte{
2, 1, 2, 3, 4, 5, 6, 7,
2, 1, 2, 3, 4, 5, 6, 8,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := newTestContext()
defer c.cleanup()
s := c.s
e := channel.New(0, 1280, linkAddr0)
defer e.Close()
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: ProtocolNumber,
AddressWithPrefix: lladdr0.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
ndpNASize := header.ICMPv6NeighborAdvertMinimumSize + len(test.optsBuf)
hdr := prependable.New(header.IPv6MinimumSize + ndpNASize)
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
pkt.SetType(header.ICMPv6NeighborAdvert)
ns := header.NDPNeighborAdvert(pkt.MessageBody())
ns.SetTargetAddress(lladdr1)
opts := ns.Options()
copy(opts, test.optsBuf)
pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: pkt,
Src: lladdr1,
Dst: lladdr0,
}))
payloadLength := hdr.UsedLength()
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: 255,
SrcAddr: lladdr1,
DstAddr: lladdr0,
})
invalid := s.Stats().ICMP.V6.PacketsReceived.Invalid
// Invalid count should initially be 0.
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
pktBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(hdr.View()),
})
e.InjectInbound(ProtocolNumber, pktBuf)
pktBuf.DecRef()
neighbors, err := s.Neighbors(nicID, ProtocolNumber)
if err != nil {
t.Fatalf("s.Neighbors(%d, %d): %s", nicID, ProtocolNumber, 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("s.Neighbors(%d, %d) returned unexpected duplicate neighbor entry (-existing +got):\n%s", nicID, ProtocolNumber, diff)
}
t.Fatalf("s.Neighbors(%d, %d) returned unexpected duplicate neighbor entry: %#v", nicID, ProtocolNumber, existing)
}
neighborByAddr[n.Addr] = n
}
if neigh, ok := neighborByAddr[lladdr1]; ok {
t.Fatalf("unexpectedly got neighbor entry: %#v", neigh)
}
if test.isValid {
// Invalid count should not have increased.
if got := invalid.Value(); got != 0 {
t.Errorf("got invalid = %d, want = 0", got)
}
} else {
// Invalid count should have increased.
if got := invalid.Value(); got != 1 {
t.Errorf("got invalid = %d, want = 1", got)
}
}
})
}
}
func TestNDPValidation(t *testing.T) {
const nicID = 1
handleIPv6Payload := func(payload []byte, hopLimit uint8, atomicFragment bool, ep stack.NetworkEndpoint) {
var extHdrs header.IPv6ExtHdrSerializer
if atomicFragment {
extHdrs = append(extHdrs, &header.IPv6SerializableFragmentExtHdr{})
}
extHdrsLen := extHdrs.Length()
ip := make([]byte, header.IPv6MinimumSize+extHdrsLen)
header.IPv6(ip).Encode(&header.IPv6Fields{
PayloadLength: uint16(len(payload) + extHdrsLen),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: hopLimit,
SrcAddr: lladdr1,
DstAddr: lladdr0,
ExtensionHeaders: extHdrs,
})
buf := buffer.MakeWithData(ip)
buf.Append(buffer.NewViewWithData(payload))
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buf,
})
ep.HandlePacket(pkt)
pkt.DecRef()
}
var tllData [header.NDPLinkLayerAddressSize]byte
header.NDPOptions(tllData[:]).Serialize(header.NDPOptionsSerializer{
header.NDPTargetLinkLayerAddressOption(linkAddr1),
})
var sllData [header.NDPLinkLayerAddressSize]byte
header.NDPOptions(sllData[:]).Serialize(header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(linkAddr1),
})
types := []struct {
name string
typ header.ICMPv6Type
size int
extraData []byte
statCounter func(tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter
routerOnly bool
}{
{
name: "RouterSolicit",
typ: header.ICMPv6RouterSolicit,
size: header.ICMPv6MinimumSize,
statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
return stats.RouterSolicit
},
routerOnly: true,
},
{
name: "RouterAdvert",
typ: header.ICMPv6RouterAdvert,
size: header.ICMPv6HeaderSize + header.NDPRAMinimumSize,
statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
return stats.RouterAdvert
},
},
{
name: "NeighborSolicit",
typ: header.ICMPv6NeighborSolicit,
size: header.ICMPv6NeighborSolicitMinimumSize,
extraData: sllData[:],
statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
return stats.NeighborSolicit
},
},
{
name: "NeighborAdvert",
typ: header.ICMPv6NeighborAdvert,
size: header.ICMPv6NeighborAdvertMinimumSize,
extraData: tllData[:],
statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
return stats.NeighborAdvert
},
},
{
name: "RedirectMsg",
typ: header.ICMPv6RedirectMsg,
size: header.ICMPv6MinimumSize,
statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
return stats.RedirectMsg
},
},
}
subTests := []struct {
name string
atomicFragment bool
hopLimit uint8
code header.ICMPv6Code
valid bool
}{
{
name: "Valid",
atomicFragment: false,
hopLimit: header.NDPHopLimit,
code: 0,
valid: true,
},
{
name: "Fragmented",
atomicFragment: true,
hopLimit: header.NDPHopLimit,
code: 0,
valid: false,
},
{
name: "Invalid hop limit",
atomicFragment: false,
hopLimit: header.NDPHopLimit - 1,
code: 0,
valid: false,
},
{
name: "Invalid ICMPv6 code",
atomicFragment: false,
hopLimit: header.NDPHopLimit,
code: 1,
valid: false,
},
}
subnet, err := tcpip.NewSubnet(lladdr1, tcpip.MaskFrom(strings.Repeat("\xff", lladdr0.Len())))
if err != nil {
t.Fatal(err)
}
for _, typ := range types {
for _, isRouter := range []bool{false, true} {
name := typ.name
if isRouter {
name += " (Router)"
}
t.Run(name, func(t *testing.T) {
for _, test := range subTests {
t.Run(test.name, func(t *testing.T) {
c := newTestContext()
defer c.cleanup()
s := c.s
if isRouter {
if err := s.SetForwardingDefaultAndAllNICs(ProtocolNumber, true); err != nil {
t.Fatalf("SetForwardingDefaultAndAllNICs(%d, true): %s", ProtocolNumber, err)
}
}
if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: ProtocolNumber,
AddressWithPrefix: lladdr0.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
ep, err := s.GetNetworkEndpoint(nicID, ProtocolNumber)
if err != nil {
t.Fatal("cannot find network endpoint instance for IPv6")
}
s.SetRouteTable([]tcpip.Route{{
Destination: subnet,
NIC: nicID,
}})
stats := s.Stats().ICMP.V6.PacketsReceived
invalid := stats.Invalid
routerOnly := stats.RouterOnlyPacketsDroppedByHost
typStat := typ.statCounter(stats)
icmpH := header.ICMPv6(make([]byte, typ.size+len(typ.extraData)))
copy(icmpH[typ.size:], typ.extraData)
icmpH.SetType(typ.typ)
icmpH.SetCode(test.code)
icmpH.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpH[:typ.size],
Src: lladdr0,
Dst: lladdr1,
PayloadCsum: checksum.Checksum(typ.extraData /* initial */, 0),
PayloadLen: len(typ.extraData),
}))
// Rx count of the NDP message should initially be 0.
if got := typStat.Value(); got != 0 {
t.Errorf("got %s = %d, want = 0", typ.name, got)
}
// Invalid count should initially be 0.
if got := invalid.Value(); got != 0 {
t.Errorf("got invalid.Value() = %d, want = 0", got)
}
// Should initially not have dropped any packets.
if got := routerOnly.Value(); got != 0 {
t.Errorf("got routerOnly.Value() = %d, want = 0", got)
}
if t.Failed() {
t.FailNow()
}
handleIPv6Payload(icmpH, test.hopLimit, test.atomicFragment, ep)
// Rx count of the NDP packet should have increased.
if got := typStat.Value(); got != 1 {
t.Errorf("got %s = %d, want = 1", typ.name, got)
}
want := uint64(0)
if !test.valid {
// Invalid count should have increased.
want = 1
}
if got := invalid.Value(); got != want {
t.Errorf("got invalid.Value() = %d, want = %d", got, want)
}
want = 0
if test.valid && !isRouter && typ.routerOnly {
// Router only packets are expected to be dropped when operating
// as a host.
want = 1
}
if got := routerOnly.Value(); got != want {
t.Errorf("got routerOnly.Value() = %d, want = %d", got, want)
}
})
}
})
}
}
}
// TestNeighborAdvertisementValidation tests that the NIC validates received
// Neighbor Advertisements.
//
// In particular, if the IP Destination Address is a multicast address, and the
// Solicited flag is not zero, the Neighbor Advertisement is invalid and should
// be discarded.
func TestNeighborAdvertisementValidation(t *testing.T) {
tests := []struct {
name string
ipDstAddr tcpip.Address
solicitedFlag bool
valid bool
}{
{
name: "Multicast IP destination address with Solicited flag set",
ipDstAddr: header.IPv6AllNodesMulticastAddress,
solicitedFlag: true,
valid: false,
},
{
name: "Multicast IP destination address with Solicited flag unset",
ipDstAddr: header.IPv6AllNodesMulticastAddress,
solicitedFlag: false,
valid: true,
},
{
name: "Unicast IP destination address with Solicited flag set",
ipDstAddr: lladdr0,
solicitedFlag: true,
valid: true,
},
{
name: "Unicast IP destination address with Solicited flag unset",
ipDstAddr: lladdr0,
solicitedFlag: false,
valid: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := newTestContext()
defer c.cleanup()
s := c.s
e := channel.New(0, header.IPv6MinimumMTU, linkAddr0)
defer e.Close()
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: ProtocolNumber,
AddressWithPrefix: lladdr0.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
ndpNASize := header.ICMPv6NeighborAdvertMinimumSize
hdr := prependable.New(header.IPv6MinimumSize + ndpNASize)
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
pkt.SetType(header.ICMPv6NeighborAdvert)
na := header.NDPNeighborAdvert(pkt.MessageBody())
na.SetTargetAddress(lladdr1)
na.SetSolicitedFlag(test.solicitedFlag)
pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: pkt,
Src: lladdr1,
Dst: test.ipDstAddr,
}))
payloadLength := hdr.UsedLength()
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: 255,
SrcAddr: lladdr1,
DstAddr: test.ipDstAddr,
})
stats := s.Stats().ICMP.V6.PacketsReceived
invalid := stats.Invalid
rxNA := stats.NeighborAdvert
if got := rxNA.Value(); got != 0 {
t.Fatalf("got rxNA = %d, want = 0", got)
}
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
pktBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(hdr.View()),
})
e.InjectInbound(header.IPv6ProtocolNumber, pktBuf)
pktBuf.DecRef()
if got := rxNA.Value(); got != 1 {
t.Fatalf("got rxNA = %d, want = 1", got)
}
var wantInvalid uint64 = 1
if test.valid {
wantInvalid = 0
}
if got := invalid.Value(); got != wantInvalid {
t.Fatalf("got invalid = %d, want = %d", got, wantInvalid)
}
// As per RFC 4861 section 7.2.5:
// When a valid Neighbor Advertisement is received ...
// If no entry exists, the advertisement SHOULD be silently discarded.
// There is no need to create an entry if none exists, since the
// recipient has apparently not initiated any communication with the
// target.
if neighbors, err := s.Neighbors(nicID, ProtocolNumber); err != nil {
t.Fatalf("s.Neighbors(%d, %d): %s", nicID, ProtocolNumber, err)
} else if len(neighbors) != 0 {
t.Fatalf("got len(neighbors) = %d, want = 0; neighbors = %#v", len(neighbors), neighbors)
}
})
}
}
// TestRouterAdvertValidation tests that when the NIC is configured to handle
// NDP Router Advertisement packets, it validates the Router Advertisement
// properly before handling them.
func TestRouterAdvertValidation(t *testing.T) {
tests := []struct {
name string
src tcpip.Address
hopLimit uint8
code header.ICMPv6Code
ndpPayload []byte
expectedSuccess bool
}{
{
"OK",
lladdr0,
255,
0,
[]byte{
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
},
true,
},
{
"NonLinkLocalSourceAddr",
addr1,
255,
0,
[]byte{
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
},
false,
},
{
"HopLimitNot255",
lladdr0,
254,
0,
[]byte{
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
},
false,
},
{
"NonZeroCode",
lladdr0,
255,
1,
[]byte{
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
},
false,
},
{
"NDPPayloadTooSmall",
lladdr0,
255,
0,
[]byte{
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0,
},
false,
},
{
"OKWithOptions",
lladdr0,
255,
0,
[]byte{
// RA payload
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
// Option #1 (TargetLinkLayerAddress)
2, 1, 0, 0, 0, 0, 0, 0,
// Option #2 (unrecognized)
255, 1, 0, 0, 0, 0, 0, 0,
// Option #3 (PrefixInformation)
3, 4, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
true,
},
{
"OptionWithZeroLength",
lladdr0,
255,
0,
[]byte{
// RA payload
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
// Option #1 (TargetLinkLayerAddress)
// Invalid as it has 0 length.
2, 0, 0, 0, 0, 0, 0, 0,
// Option #2 (unrecognized)
255, 1, 0, 0, 0, 0, 0, 0,
// Option #3 (PrefixInformation)
3, 4, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := newTestContext()
defer c.cleanup()
s := c.s
e := channel.New(10, 1280, linkAddr1)
defer e.Close()
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
if err := s.CreateNIC(1, e); err != nil {
t.Fatalf("CreateNIC(_) = %s", err)
}
icmpSize := header.ICMPv6HeaderSize + len(test.ndpPayload)
hdr := prependable.New(header.IPv6MinimumSize + icmpSize)
pkt := header.ICMPv6(hdr.Prepend(icmpSize))
pkt.SetType(header.ICMPv6RouterAdvert)
pkt.SetCode(test.code)
copy(pkt.MessageBody(), test.ndpPayload)
payloadLength := hdr.UsedLength()
pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: pkt,
Src: test.src,
Dst: header.IPv6AllNodesMulticastAddress,
}))
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
TransportProtocol: icmp.ProtocolNumber6,
HopLimit: test.hopLimit,
SrcAddr: test.src,
DstAddr: header.IPv6AllNodesMulticastAddress,
})
stats := s.Stats().ICMP.V6.PacketsReceived
invalid := stats.Invalid
rxRA := stats.RouterAdvert
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
if got := rxRA.Value(); got != 0 {
t.Fatalf("got rxRA = %d, want = 0", got)
}
pktBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(hdr.View()),
})
e.InjectInbound(header.IPv6ProtocolNumber, pktBuf)
pktBuf.DecRef()
if got := rxRA.Value(); got != 1 {
t.Fatalf("got rxRA = %d, want = 1", got)
}
if test.expectedSuccess {
if got := invalid.Value(); got != 0 {
t.Fatalf("got invalid = %d, want = 0", got)
}
} else {
if got := invalid.Value(); got != 1 {
t.Fatalf("got invalid = %d, want = 1", got)
}
}
})
}
}
// TestCheckDuplicateAddress checks that calls to CheckDuplicateAddress and DAD
// performed when adding new addresses do not interfere with each other.
func TestCheckDuplicateAddress(t *testing.T) {
const nicID = 1
clock := faketime.NewManualClock()
dadConfigs := stack.DADConfigurations{
DupAddrDetectTransmits: 1,
RetransmitTimer: time.Second,
}
s := stack.New(stack.Options{
Clock: clock,
RandSource: rand.NewSource(time.Now().UnixNano()),
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocolWithOptions(Options{
DADConfigs: dadConfigs,
})},
})
defer func() {
s.Close()
s.Wait()
}()
// This test is expected to send at max 2 DAD messages. We allow an extra
// packet to be stored to catch unexpected packets.
e := channel.New(3, header.IPv6MinimumMTU, linkAddr0)
defer e.Close()
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
dadPacketsSent := 0
snmc := header.SolicitedNodeAddr(lladdr0)
remoteLinkAddr := header.EthernetAddressFromMulticastIPv6Address(snmc)
checkDADMsg := func() {
clock.RunImmediatelyScheduledJobs()
p := e.Read()
if p == nil {
t.Fatalf("expected %d-th DAD message", dadPacketsSent)
}
defer p.DecRef()
if p.NetworkProtocolNumber != header.IPv6ProtocolNumber {
t.Errorf("(i=%d) got p.NetworkProtocolNumber = %d, want = %d", dadPacketsSent, p.NetworkProtocolNumber, header.IPv6ProtocolNumber)
}
if p.EgressRoute.RemoteLinkAddress != remoteLinkAddr {
t.Errorf("(i=%d) got p.EgressRoute.RemoteLinkAddress = %s, want = %s", dadPacketsSent, p.EgressRoute.RemoteLinkAddress, remoteLinkAddr)
}
payload := stack.PayloadSince(p.NetworkHeader())
defer payload.Release()
checker.IPv6(t, payload,
checker.SrcAddr(header.IPv6Any),
checker.DstAddr(snmc),
checker.TTL(header.NDPHopLimit),
checker.NDPNS(
checker.NDPNSTargetAddress(lladdr0),
))
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: ProtocolNumber,
AddressWithPrefix: lladdr0.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
checkDADMsg()
// Start DAD for the address we just added.
//
// Even though the stack will perform DAD before the added address transitions
// from tentative to assigned, this DAD request should be independent of that.
ch := make(chan stack.DADResult, 3)
dadRequestsMade := 1
dadPacketsSent++
if res, err := s.CheckDuplicateAddress(nicID, ProtocolNumber, lladdr0, func(r stack.DADResult) {
ch <- r
}); err != nil {
t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, ProtocolNumber, lladdr0, err)
} else if res != stack.DADStarting {
t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADStarting)
}
checkDADMsg()
// Remove the address and make sure our DAD request was not stopped.
if err := s.RemoveAddress(nicID, lladdr0); err != nil {
t.Fatalf("RemoveAddress(%d, %s): %s", nicID, lladdr0, err)
}
// Should not restart DAD since we already requested DAD above - the handler
// should be called when the original request completes so we should not send
// an extra DAD message here.
dadRequestsMade++
if res, err := s.CheckDuplicateAddress(nicID, ProtocolNumber, lladdr0, func(r stack.DADResult) {
ch <- r
}); err != nil {
t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, ProtocolNumber, lladdr0, err)
} else if res != stack.DADAlreadyRunning {
t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADAlreadyRunning)
}
// Wait for DAD to complete.
clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer)
for i := 0; i < dadRequestsMade; i++ {
if diff := cmp.Diff(&stack.DADSucceeded{}, <-ch); diff != "" {
t.Errorf("(i=%d) DAD result mismatch (-want +got):\n%s", i, diff)
}
}
// Should have no more results.
select {
case r := <-ch:
t.Errorf("unexpectedly got an extra DAD result; r = %#v", r)
default:
}
// Should have no more packets.
if p := e.Read(); p != nil {
t.Errorf("got unexpected packet = %#v", p)
}
}