1314 lines
38 KiB
Go
1314 lines
38 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 (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
|
)
|
|
|
|
// setupStackAndEndpoint creates a stack with a single NIC with a link-local
|
|
// address llladdr and an IPv6 endpoint to a remote with link-local address
|
|
// rlladdr
|
|
func setupStackAndEndpoint(t *testing.T, llladdr, rlladdr tcpip.Address, useNeighborCache bool) (*stack.Stack, stack.NetworkEndpoint) {
|
|
t.Helper()
|
|
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
|
|
UseNeighborCache: useNeighborCache,
|
|
})
|
|
|
|
if err := s.CreateNIC(1, &stubLinkEndpoint{}); err != nil {
|
|
t.Fatalf("CreateNIC(_) = %s", err)
|
|
}
|
|
if err := s.AddAddress(1, ProtocolNumber, llladdr); err != nil {
|
|
t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, llladdr, err)
|
|
}
|
|
|
|
{
|
|
subnet, err := tcpip.NewSubnet(rlladdr, tcpip.AddressMask(strings.Repeat("\xff", len(rlladdr))))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s.SetRouteTable(
|
|
[]tcpip.Route{{
|
|
Destination: subnet,
|
|
NIC: 1,
|
|
}},
|
|
)
|
|
}
|
|
|
|
netProto := s.NetworkProtocolInstance(ProtocolNumber)
|
|
if netProto == nil {
|
|
t.Fatalf("cannot find protocol instance for network protocol %d", ProtocolNumber)
|
|
}
|
|
|
|
ep := netProto.NewEndpoint(&testInterface{}, &stubLinkAddressCache{}, &stubNUDHandler{}, &stubDispatcher{})
|
|
if err := ep.Enable(); err != nil {
|
|
t.Fatalf("ep.Enable(): %s", err)
|
|
}
|
|
t.Cleanup(ep.Close)
|
|
|
|
return s, ep
|
|
}
|
|
|
|
var _ NDPDispatcher = (*testNDPDispatcher)(nil)
|
|
|
|
// testNDPDispatcher is an NDPDispatcher only allows default router discovery.
|
|
type testNDPDispatcher struct {
|
|
addr tcpip.Address
|
|
}
|
|
|
|
func (*testNDPDispatcher) OnDuplicateAddressDetectionStatus(tcpip.NICID, tcpip.Address, bool, *tcpip.Error) {
|
|
}
|
|
|
|
func (t *testNDPDispatcher) OnDefaultRouterDiscovered(_ tcpip.NICID, addr tcpip.Address) bool {
|
|
t.addr = addr
|
|
return true
|
|
}
|
|
|
|
func (t *testNDPDispatcher) OnDefaultRouterInvalidated(_ tcpip.NICID, addr tcpip.Address) {
|
|
t.addr = addr
|
|
}
|
|
|
|
func (*testNDPDispatcher) OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet) bool {
|
|
return false
|
|
}
|
|
|
|
func (*testNDPDispatcher) OnOnLinkPrefixInvalidated(tcpip.NICID, tcpip.Subnet) {
|
|
}
|
|
|
|
func (*testNDPDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) bool {
|
|
return false
|
|
}
|
|
|
|
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,
|
|
})},
|
|
})
|
|
|
|
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.rememberDefaultRouter(lladdr1, time.Hour)
|
|
ipv6EP.mu.Unlock()
|
|
|
|
if ndpDisp.addr != lladdr1 {
|
|
t.Fatalf("got ndpDisp.addr = %s, want = %s", ndpDisp.addr, lladdr1)
|
|
}
|
|
|
|
ndpDisp.addr = ""
|
|
ndpEP := ep.(stack.NDPEndpoint)
|
|
ndpEP.InvalidateDefaultRouter(lladdr1)
|
|
if ndpDisp.addr != lladdr1 {
|
|
t.Fatalf("got ndpDisp.addr = %s, want = %s", ndpDisp.addr, lladdr1)
|
|
}
|
|
}
|
|
|
|
// TestNeighorSolicitationWithSourceLinkLayerOption 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 TestNeighorSolicitationWithSourceLinkLayerOption(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) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
|
|
})
|
|
e := channel.New(0, 1280, linkAddr0)
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
|
}
|
|
if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
|
|
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
|
|
}
|
|
|
|
ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize + len(test.optsBuf)
|
|
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
|
|
pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
|
|
pkt.SetType(header.ICMPv6NeighborSolicit)
|
|
ns := header.NDPNeighborSolicit(pkt.NDPPayload())
|
|
ns.SetTargetAddress(lladdr0)
|
|
opts := ns.Options()
|
|
copy(opts, test.optsBuf)
|
|
pkt.SetChecksum(header.ICMPv6Checksum(pkt, lladdr1, lladdr0, buffer.VectorisedView{}))
|
|
payloadLength := hdr.UsedLength()
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(payloadLength),
|
|
NextHeader: uint8(header.ICMPv6ProtocolNumber),
|
|
HopLimit: 255,
|
|
SrcAddr: lladdr1,
|
|
DstAddr: lladdr0,
|
|
})
|
|
|
|
invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
|
|
|
|
// Invalid count should initially be 0.
|
|
if got := invalid.Value(); got != 0 {
|
|
t.Fatalf("got invalid = %d, want = 0", got)
|
|
}
|
|
|
|
e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: hdr.View().ToVectorisedView(),
|
|
}))
|
|
|
|
linkAddr, c, err := s.GetLinkAddress(nicID, lladdr1, lladdr0, ProtocolNumber, nil)
|
|
if linkAddr != test.expectedLinkAddr {
|
|
t.Errorf("got link address = %s, want = %s", linkAddr, test.expectedLinkAddr)
|
|
}
|
|
|
|
if test.expectedLinkAddr != "" {
|
|
if err != nil {
|
|
t.Errorf("s.GetLinkAddress(%d, %s, %s, %d, nil): %s", nicID, lladdr1, lladdr0, ProtocolNumber, err)
|
|
}
|
|
if c != nil {
|
|
t.Errorf("got unexpected channel")
|
|
}
|
|
|
|
// Invalid count should not have increased.
|
|
if got := invalid.Value(); got != 0 {
|
|
t.Errorf("got invalid = %d, want = 0", got)
|
|
}
|
|
} else {
|
|
if err != tcpip.ErrWouldBlock {
|
|
t.Errorf("got s.GetLinkAddress(%d, %s, %s, %d, nil) = (_, _, %v), want = (_, _, %s)", nicID, lladdr1, lladdr0, ProtocolNumber, err, tcpip.ErrWouldBlock)
|
|
}
|
|
if c == nil {
|
|
t.Errorf("expected channel from call to s.GetLinkAddress(%d, %s, %s, %d, nil)", nicID, lladdr1, lladdr0, ProtocolNumber)
|
|
}
|
|
|
|
// Invalid count should have increased.
|
|
if got := invalid.Value(); got != 1 {
|
|
t.Errorf("got invalid = %d, want = 1", got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNeighorSolicitationWithSourceLinkLayerOptionUsingNeighborCache 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 TestNeighorSolicitationWithSourceLinkLayerOptionUsingNeighborCache(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) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
|
|
UseNeighborCache: true,
|
|
})
|
|
e := channel.New(0, 1280, linkAddr0)
|
|
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
|
}
|
|
if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
|
|
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
|
|
}
|
|
|
|
ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize + len(test.optsBuf)
|
|
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
|
|
pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
|
|
pkt.SetType(header.ICMPv6NeighborSolicit)
|
|
ns := header.NDPNeighborSolicit(pkt.NDPPayload())
|
|
ns.SetTargetAddress(lladdr0)
|
|
opts := ns.Options()
|
|
copy(opts, test.optsBuf)
|
|
pkt.SetChecksum(header.ICMPv6Checksum(pkt, lladdr1, lladdr0, buffer.VectorisedView{}))
|
|
payloadLength := hdr.UsedLength()
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(payloadLength),
|
|
NextHeader: uint8(header.ICMPv6ProtocolNumber),
|
|
HopLimit: 255,
|
|
SrcAddr: lladdr1,
|
|
DstAddr: lladdr0,
|
|
})
|
|
|
|
invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
|
|
|
|
// Invalid count should initially be 0.
|
|
if got := invalid.Value(); got != 0 {
|
|
t.Fatalf("got invalid = %d, want = 0", got)
|
|
}
|
|
|
|
e.InjectInbound(ProtocolNumber, &stack.PacketBuffer{
|
|
Data: hdr.View().ToVectorisedView(),
|
|
})
|
|
|
|
neighbors, err := s.Neighbors(nicID)
|
|
if err != nil {
|
|
t.Fatalf("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("s.Neighbors(%d) returned unexpected duplicate neighbor entry (-existing +got):\n%s", nicID, diff)
|
|
}
|
|
t.Fatalf("s.Neighbors(%d) returned unexpected duplicate neighbor entry: %s", nicID, 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: %s", neigh)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNeighorSolicitationResponse(t *testing.T) {
|
|
const nicID = 1
|
|
nicAddr := lladdr0
|
|
remoteAddr := lladdr1
|
|
nicAddrSNMC := header.SolicitedNodeAddr(nicAddr)
|
|
nicLinkAddr := linkAddr0
|
|
remoteLinkAddr0 := linkAddr1
|
|
remoteLinkAddr1 := linkAddr2
|
|
|
|
stacks := []struct {
|
|
name string
|
|
useNeighborCache bool
|
|
}{
|
|
{
|
|
name: "linkAddrCache",
|
|
useNeighborCache: false,
|
|
},
|
|
{
|
|
name: "neighborCache",
|
|
useNeighborCache: true,
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
nsOpts header.NDPOptionsSerializer
|
|
nsSrcLinkAddr tcpip.LinkAddress
|
|
nsSrc tcpip.Address
|
|
nsDst tcpip.Address
|
|
nsInvalid bool
|
|
naDstLinkAddr tcpip.LinkAddress
|
|
naSolicited bool
|
|
naSrc tcpip.Address
|
|
naDst tcpip.Address
|
|
}{
|
|
{
|
|
name: "Unspecified source to solicited-node multicast destination",
|
|
nsOpts: nil,
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
nsSrc: header.IPv6Any,
|
|
nsDst: nicAddrSNMC,
|
|
nsInvalid: false,
|
|
naDstLinkAddr: remoteLinkAddr0,
|
|
naSolicited: false,
|
|
naSrc: nicAddr,
|
|
naDst: header.IPv6AllNodesMulticastAddress,
|
|
},
|
|
{
|
|
name: "Unspecified source with source ll option to multicast destination",
|
|
nsOpts: header.NDPOptionsSerializer{
|
|
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
|
|
},
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
nsSrc: header.IPv6Any,
|
|
nsDst: nicAddrSNMC,
|
|
nsInvalid: true,
|
|
},
|
|
{
|
|
name: "Unspecified source to unicast destination",
|
|
nsOpts: nil,
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
nsSrc: header.IPv6Any,
|
|
nsDst: nicAddr,
|
|
nsInvalid: true,
|
|
},
|
|
{
|
|
name: "Unspecified source with source ll option to unicast destination",
|
|
nsOpts: header.NDPOptionsSerializer{
|
|
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
|
|
},
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
nsSrc: header.IPv6Any,
|
|
nsDst: nicAddr,
|
|
nsInvalid: true,
|
|
},
|
|
|
|
{
|
|
name: "Specified source with 1 source ll to multicast destination",
|
|
nsOpts: header.NDPOptionsSerializer{
|
|
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
|
|
},
|
|
nsSrcLinkAddr: 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[:]),
|
|
},
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
nsSrc: remoteAddr,
|
|
nsDst: nicAddrSNMC,
|
|
nsInvalid: false,
|
|
naDstLinkAddr: remoteLinkAddr1,
|
|
naSolicited: true,
|
|
naSrc: nicAddr,
|
|
naDst: remoteAddr,
|
|
},
|
|
{
|
|
name: "Specified source to multicast destination",
|
|
nsOpts: nil,
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
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[:]),
|
|
},
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
nsSrc: remoteAddr,
|
|
nsDst: nicAddrSNMC,
|
|
nsInvalid: true,
|
|
},
|
|
|
|
{
|
|
name: "Specified source to unicast destination",
|
|
nsOpts: nil,
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
nsSrc: remoteAddr,
|
|
nsDst: nicAddr,
|
|
nsInvalid: false,
|
|
naDstLinkAddr: remoteLinkAddr0,
|
|
naSolicited: true,
|
|
naSrc: nicAddr,
|
|
naDst: remoteAddr,
|
|
},
|
|
{
|
|
name: "Specified source with 1 source ll to unicast destination",
|
|
nsOpts: header.NDPOptionsSerializer{
|
|
header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
|
|
},
|
|
nsSrcLinkAddr: 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[:]),
|
|
},
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
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[:]),
|
|
},
|
|
nsSrcLinkAddr: remoteLinkAddr0,
|
|
nsSrc: remoteAddr,
|
|
nsDst: nicAddr,
|
|
nsInvalid: true,
|
|
},
|
|
}
|
|
|
|
for _, stackTyp := range stacks {
|
|
t.Run(stackTyp.name, func(t *testing.T) {
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
|
|
UseNeighborCache: stackTyp.useNeighborCache,
|
|
})
|
|
e := channel.New(1, 1280, nicLinkAddr)
|
|
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
|
}
|
|
if err := s.AddAddress(nicID, ProtocolNumber, nicAddr); err != nil {
|
|
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, nicAddr, err)
|
|
}
|
|
|
|
ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize + test.nsOpts.Length()
|
|
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
|
|
pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
|
|
pkt.SetType(header.ICMPv6NeighborSolicit)
|
|
ns := header.NDPNeighborSolicit(pkt.NDPPayload())
|
|
ns.SetTargetAddress(nicAddr)
|
|
opts := ns.Options()
|
|
opts.Serialize(test.nsOpts)
|
|
pkt.SetChecksum(header.ICMPv6Checksum(pkt, test.nsSrc, test.nsDst, buffer.VectorisedView{}))
|
|
payloadLength := hdr.UsedLength()
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(payloadLength),
|
|
NextHeader: uint8(header.ICMPv6ProtocolNumber),
|
|
HopLimit: 255,
|
|
SrcAddr: test.nsSrc,
|
|
DstAddr: test.nsDst,
|
|
})
|
|
|
|
invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
|
|
|
|
// Invalid count should initially be 0.
|
|
if got := invalid.Value(); got != 0 {
|
|
t.Fatalf("got invalid = %d, want = 0", got)
|
|
}
|
|
|
|
e.InjectLinkAddr(ProtocolNumber, test.nsSrcLinkAddr, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: hdr.View().ToVectorisedView(),
|
|
}))
|
|
|
|
if test.nsInvalid {
|
|
if got := invalid.Value(); got != 1 {
|
|
t.Fatalf("got invalid = %d, want = 1", got)
|
|
}
|
|
|
|
if p, got := e.Read(); got {
|
|
t.Fatalf("unexpected response to an invalid NS = %+v", p.Pkt)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
p, got := e.Read()
|
|
if !got {
|
|
t.Fatal("expected an NDP NA response")
|
|
}
|
|
|
|
if p.Route.RemoteLinkAddress != test.naDstLinkAddr {
|
|
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, test.naDstLinkAddr)
|
|
}
|
|
|
|
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
|
|
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[:]),
|
|
}),
|
|
))
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNeighorAdvertisementWithTargetLinkLayerOption tests that receiving a
|
|
// valid NDP NA message with the Target Link Layer Address option results in a
|
|
// new entry in the link address cache for the target of the message.
|
|
func TestNeighorAdvertisementWithTargetLinkLayerOption(t *testing.T) {
|
|
const nicID = 1
|
|
|
|
tests := []struct {
|
|
name string
|
|
optsBuf []byte
|
|
expectedLinkAddr tcpip.LinkAddress
|
|
}{
|
|
{
|
|
name: "Valid",
|
|
optsBuf: []byte{2, 1, 2, 3, 4, 5, 6, 7},
|
|
expectedLinkAddr: "\x02\x03\x04\x05\x06\x07",
|
|
},
|
|
{
|
|
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) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
|
|
})
|
|
e := channel.New(0, 1280, linkAddr0)
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
|
}
|
|
if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
|
|
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
|
|
}
|
|
|
|
ndpNASize := header.ICMPv6NeighborAdvertMinimumSize + len(test.optsBuf)
|
|
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
|
|
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
|
|
pkt.SetType(header.ICMPv6NeighborAdvert)
|
|
ns := header.NDPNeighborAdvert(pkt.NDPPayload())
|
|
ns.SetTargetAddress(lladdr1)
|
|
opts := ns.Options()
|
|
copy(opts, test.optsBuf)
|
|
pkt.SetChecksum(header.ICMPv6Checksum(pkt, lladdr1, lladdr0, buffer.VectorisedView{}))
|
|
payloadLength := hdr.UsedLength()
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(payloadLength),
|
|
NextHeader: uint8(header.ICMPv6ProtocolNumber),
|
|
HopLimit: 255,
|
|
SrcAddr: lladdr1,
|
|
DstAddr: lladdr0,
|
|
})
|
|
|
|
invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
|
|
|
|
// Invalid count should initially be 0.
|
|
if got := invalid.Value(); got != 0 {
|
|
t.Fatalf("got invalid = %d, want = 0", got)
|
|
}
|
|
|
|
e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: hdr.View().ToVectorisedView(),
|
|
}))
|
|
|
|
linkAddr, c, err := s.GetLinkAddress(nicID, lladdr1, lladdr0, ProtocolNumber, nil)
|
|
if linkAddr != test.expectedLinkAddr {
|
|
t.Errorf("got link address = %s, want = %s", linkAddr, test.expectedLinkAddr)
|
|
}
|
|
|
|
if test.expectedLinkAddr != "" {
|
|
if err != nil {
|
|
t.Errorf("s.GetLinkAddress(%d, %s, %s, %d, nil): %s", nicID, lladdr1, lladdr0, ProtocolNumber, err)
|
|
}
|
|
if c != nil {
|
|
t.Errorf("got unexpected channel")
|
|
}
|
|
|
|
// Invalid count should not have increased.
|
|
if got := invalid.Value(); got != 0 {
|
|
t.Errorf("got invalid = %d, want = 0", got)
|
|
}
|
|
} else {
|
|
if err != tcpip.ErrWouldBlock {
|
|
t.Errorf("got s.GetLinkAddress(%d, %s, %s, %d, nil) = (_, _, %v), want = (_, _, %s)", nicID, lladdr1, lladdr0, ProtocolNumber, err, tcpip.ErrWouldBlock)
|
|
}
|
|
if c == nil {
|
|
t.Errorf("expected channel from call to s.GetLinkAddress(%d, %s, %s, %d, nil)", nicID, lladdr1, lladdr0, ProtocolNumber)
|
|
}
|
|
|
|
// Invalid count should have increased.
|
|
if got := invalid.Value(); got != 1 {
|
|
t.Errorf("got invalid = %d, want = 1", got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNeighorAdvertisementWithTargetLinkLayerOptionUsingNeighborCache 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 TestNeighorAdvertisementWithTargetLinkLayerOptionUsingNeighborCache(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) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
|
|
UseNeighborCache: true,
|
|
})
|
|
e := channel.New(0, 1280, linkAddr0)
|
|
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
|
}
|
|
if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
|
|
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
|
|
}
|
|
|
|
ndpNASize := header.ICMPv6NeighborAdvertMinimumSize + len(test.optsBuf)
|
|
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
|
|
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
|
|
pkt.SetType(header.ICMPv6NeighborAdvert)
|
|
ns := header.NDPNeighborAdvert(pkt.NDPPayload())
|
|
ns.SetTargetAddress(lladdr1)
|
|
opts := ns.Options()
|
|
copy(opts, test.optsBuf)
|
|
pkt.SetChecksum(header.ICMPv6Checksum(pkt, lladdr1, lladdr0, buffer.VectorisedView{}))
|
|
payloadLength := hdr.UsedLength()
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(payloadLength),
|
|
NextHeader: uint8(header.ICMPv6ProtocolNumber),
|
|
HopLimit: 255,
|
|
SrcAddr: lladdr1,
|
|
DstAddr: lladdr0,
|
|
})
|
|
|
|
invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
|
|
|
|
// Invalid count should initially be 0.
|
|
if got := invalid.Value(); got != 0 {
|
|
t.Fatalf("got invalid = %d, want = 0", got)
|
|
}
|
|
|
|
e.InjectInbound(ProtocolNumber, &stack.PacketBuffer{
|
|
Data: hdr.View().ToVectorisedView(),
|
|
})
|
|
|
|
neighbors, err := s.Neighbors(nicID)
|
|
if err != nil {
|
|
t.Fatalf("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("s.Neighbors(%d) returned unexpected duplicate neighbor entry (-existing +got):\n%s", nicID, diff)
|
|
}
|
|
t.Fatalf("s.Neighbors(%d) returned unexpected duplicate neighbor entry: %s", nicID, existing)
|
|
}
|
|
neighborByAddr[n.Addr] = n
|
|
}
|
|
|
|
if neigh, ok := neighborByAddr[lladdr1]; ok {
|
|
t.Fatalf("unexpectedly got neighbor entry: %s", 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) {
|
|
stacks := []struct {
|
|
name string
|
|
useNeighborCache bool
|
|
}{
|
|
{
|
|
name: "linkAddrCache",
|
|
useNeighborCache: false,
|
|
},
|
|
{
|
|
name: "neighborCache",
|
|
useNeighborCache: true,
|
|
},
|
|
}
|
|
|
|
for _, stackTyp := range stacks {
|
|
t.Run(stackTyp.name, func(t *testing.T) {
|
|
setup := func(t *testing.T) (*stack.Stack, stack.NetworkEndpoint, stack.Route) {
|
|
t.Helper()
|
|
|
|
// Create a stack with the assigned link-local address lladdr0
|
|
// and an endpoint to lladdr1.
|
|
s, ep := setupStackAndEndpoint(t, lladdr0, lladdr1, stackTyp.useNeighborCache)
|
|
|
|
r, err := s.FindRoute(1, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */)
|
|
if err != nil {
|
|
t.Fatalf("FindRoute(_) = _, %s, want = _, nil", err)
|
|
}
|
|
|
|
return s, ep, r
|
|
}
|
|
|
|
handleIPv6Payload := func(payload buffer.View, hopLimit uint8, atomicFragment bool, ep stack.NetworkEndpoint, r *stack.Route) {
|
|
nextHdr := uint8(header.ICMPv6ProtocolNumber)
|
|
var extensions buffer.View
|
|
if atomicFragment {
|
|
extensions = buffer.NewView(header.IPv6FragmentExtHdrLength)
|
|
extensions[0] = nextHdr
|
|
nextHdr = uint8(header.IPv6FragmentExtHdrIdentifier)
|
|
}
|
|
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
ReserveHeaderBytes: header.IPv6MinimumSize + len(extensions),
|
|
Data: payload.ToVectorisedView(),
|
|
})
|
|
ip := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize + len(extensions)))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(len(payload) + len(extensions)),
|
|
NextHeader: nextHdr,
|
|
HopLimit: hopLimit,
|
|
SrcAddr: r.LocalAddress,
|
|
DstAddr: r.RemoteAddress,
|
|
})
|
|
if n := copy(ip[header.IPv6MinimumSize:], extensions); n != len(extensions) {
|
|
t.Fatalf("expected to write %d bytes of extensions, but wrote %d", len(extensions), n)
|
|
}
|
|
ep.HandlePacket(r, pkt)
|
|
}
|
|
|
|
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,
|
|
},
|
|
}
|
|
|
|
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) {
|
|
s, ep, r := setup(t)
|
|
defer r.Release()
|
|
|
|
if isRouter {
|
|
// Enabling forwarding makes the stack act as a router.
|
|
s.SetForwarding(ProtocolNumber, true)
|
|
}
|
|
|
|
stats := s.Stats().ICMP.V6PacketsReceived
|
|
invalid := stats.Invalid
|
|
routerOnly := stats.RouterOnlyPacketsDroppedByHost
|
|
typStat := typ.statCounter(stats)
|
|
|
|
icmp := header.ICMPv6(buffer.NewView(typ.size + len(typ.extraData)))
|
|
copy(icmp[typ.size:], typ.extraData)
|
|
icmp.SetType(typ.typ)
|
|
icmp.SetCode(test.code)
|
|
icmp.SetChecksum(header.ICMPv6Checksum(icmp[:typ.size], r.LocalAddress, r.RemoteAddress, buffer.View(typ.extraData).ToVectorisedView()))
|
|
|
|
// 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 = %d, want = 0", got)
|
|
}
|
|
|
|
// RouterOnlyPacketsReceivedByHost count should initially be 0.
|
|
if got := routerOnly.Value(); got != 0 {
|
|
t.Errorf("got RouterOnlyPacketsReceivedByHost = %d, want = 0", got)
|
|
}
|
|
|
|
if t.Failed() {
|
|
t.FailNow()
|
|
}
|
|
|
|
handleIPv6Payload(buffer.View(icmp), test.hopLimit, test.atomicFragment, ep, &r)
|
|
|
|
// 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 = %d, want = %d", got, want)
|
|
}
|
|
|
|
want = 0
|
|
if test.valid && !isRouter && typ.routerOnly {
|
|
// RouterOnlyPacketsReceivedByHost count should have increased.
|
|
want = 1
|
|
}
|
|
if got := routerOnly.Value(); got != want {
|
|
t.Errorf("got RouterOnlyPacketsReceivedByHost = %d, want = %d", got, want)
|
|
}
|
|
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
// 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) {
|
|
stacks := []struct {
|
|
name string
|
|
useNeighborCache bool
|
|
}{
|
|
{
|
|
name: "linkAddrCache",
|
|
useNeighborCache: false,
|
|
},
|
|
{
|
|
name: "neighborCache",
|
|
useNeighborCache: true,
|
|
},
|
|
}
|
|
|
|
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 _, stackTyp := range stacks {
|
|
t.Run(stackTyp.name, func(t *testing.T) {
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
e := channel.New(10, 1280, linkAddr1)
|
|
e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
|
|
UseNeighborCache: stackTyp.useNeighborCache,
|
|
})
|
|
|
|
if err := s.CreateNIC(1, e); err != nil {
|
|
t.Fatalf("CreateNIC(_) = %s", err)
|
|
}
|
|
|
|
icmpSize := header.ICMPv6HeaderSize + len(test.ndpPayload)
|
|
hdr := buffer.NewPrependable(header.IPv6MinimumSize + icmpSize)
|
|
pkt := header.ICMPv6(hdr.Prepend(icmpSize))
|
|
pkt.SetType(header.ICMPv6RouterAdvert)
|
|
pkt.SetCode(test.code)
|
|
copy(pkt.NDPPayload(), test.ndpPayload)
|
|
payloadLength := hdr.UsedLength()
|
|
pkt.SetChecksum(header.ICMPv6Checksum(pkt, test.src, header.IPv6AllNodesMulticastAddress, buffer.VectorisedView{}))
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(payloadLength),
|
|
NextHeader: uint8(icmp.ProtocolNumber6),
|
|
HopLimit: test.hopLimit,
|
|
SrcAddr: test.src,
|
|
DstAddr: header.IPv6AllNodesMulticastAddress,
|
|
})
|
|
|
|
stats := s.Stats().ICMP.V6PacketsReceived
|
|
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)
|
|
}
|
|
|
|
e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: hdr.View().ToVectorisedView(),
|
|
}))
|
|
|
|
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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|