mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
The change was originally motivated by ticket references (cl/450976957), which were never implemented. PiperOrigin-RevId: 617480960
1398 lines
41 KiB
Go
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)
|
|
}
|
|
}
|