1930 lines
60 KiB
Go
1930 lines
60 KiB
Go
// Copyright 2018 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 ip_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
"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/link/loopback"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
)
|
|
|
|
const (
|
|
localIPv4Addr = tcpip.Address("\x0a\x00\x00\x01")
|
|
remoteIPv4Addr = tcpip.Address("\x0a\x00\x00\x02")
|
|
ipv4SubnetAddr = tcpip.Address("\x0a\x00\x00\x00")
|
|
ipv4SubnetMask = tcpip.Address("\xff\xff\xff\x00")
|
|
ipv4Gateway = tcpip.Address("\x0a\x00\x00\x03")
|
|
localIPv6Addr = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
|
|
remoteIPv6Addr = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
|
|
ipv6SubnetAddr = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
|
ipv6SubnetMask = tcpip.Address("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
|
|
ipv6Gateway = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03")
|
|
nicID = 1
|
|
)
|
|
|
|
var localIPv4AddrWithPrefix = tcpip.AddressWithPrefix{
|
|
Address: localIPv4Addr,
|
|
PrefixLen: 24,
|
|
}
|
|
|
|
var localIPv6AddrWithPrefix = tcpip.AddressWithPrefix{
|
|
Address: localIPv6Addr,
|
|
PrefixLen: 120,
|
|
}
|
|
|
|
type transportError struct {
|
|
origin tcpip.SockErrOrigin
|
|
typ uint8
|
|
code uint8
|
|
info uint32
|
|
kind stack.TransportErrorKind
|
|
}
|
|
|
|
// testObject implements two interfaces: LinkEndpoint and TransportDispatcher.
|
|
// The former is used to pretend that it's a link endpoint so that we can
|
|
// inspect packets written by the network endpoints. The latter is used to
|
|
// pretend that it's the network stack so that it can inspect incoming packets
|
|
// that have been handled by the network endpoints.
|
|
//
|
|
// Packets are checked by comparing their fields/values against the expected
|
|
// values stored in the test object itself.
|
|
type testObject struct {
|
|
t *testing.T
|
|
protocol tcpip.TransportProtocolNumber
|
|
contents []byte
|
|
srcAddr tcpip.Address
|
|
dstAddr tcpip.Address
|
|
v4 bool
|
|
transErr transportError
|
|
|
|
dataCalls int
|
|
controlCalls int
|
|
}
|
|
|
|
// checkValues verifies that the transport protocol, data contents, src & dst
|
|
// addresses of a packet match what's expected. If any field doesn't match, the
|
|
// test fails.
|
|
func (t *testObject) checkValues(protocol tcpip.TransportProtocolNumber, vv buffer.VectorisedView, srcAddr, dstAddr tcpip.Address) {
|
|
v := vv.ToView()
|
|
if protocol != t.protocol {
|
|
t.t.Errorf("protocol = %v, want %v", protocol, t.protocol)
|
|
}
|
|
|
|
if srcAddr != t.srcAddr {
|
|
t.t.Errorf("srcAddr = %v, want %v", srcAddr, t.srcAddr)
|
|
}
|
|
|
|
if dstAddr != t.dstAddr {
|
|
t.t.Errorf("dstAddr = %v, want %v", dstAddr, t.dstAddr)
|
|
}
|
|
|
|
if len(v) != len(t.contents) {
|
|
t.t.Fatalf("len(payload) = %v, want %v", len(v), len(t.contents))
|
|
}
|
|
|
|
for i := range t.contents {
|
|
if t.contents[i] != v[i] {
|
|
t.t.Fatalf("payload[%v] = %v, want %v", i, v[i], t.contents[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// DeliverTransportPacket is called by network endpoints after parsing incoming
|
|
// packets. This is used by the test object to verify that the results of the
|
|
// parsing are expected.
|
|
func (t *testObject) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt *stack.PacketBuffer) stack.TransportPacketDisposition {
|
|
netHdr := pkt.Network()
|
|
t.checkValues(protocol, pkt.Data, netHdr.SourceAddress(), netHdr.DestinationAddress())
|
|
t.dataCalls++
|
|
return stack.TransportPacketHandled
|
|
}
|
|
|
|
// DeliverTransportError is called by network endpoints after parsing
|
|
// incoming control (ICMP) packets. This is used by the test object to verify
|
|
// that the results of the parsing are expected.
|
|
func (t *testObject) DeliverTransportError(local, remote tcpip.Address, net tcpip.NetworkProtocolNumber, trans tcpip.TransportProtocolNumber, transErr stack.TransportError, pkt *stack.PacketBuffer) {
|
|
t.checkValues(trans, pkt.Data, remote, local)
|
|
if diff := cmp.Diff(
|
|
t.transErr,
|
|
transportError{
|
|
origin: transErr.Origin(),
|
|
typ: transErr.Type(),
|
|
code: transErr.Code(),
|
|
info: transErr.Info(),
|
|
kind: transErr.Kind(),
|
|
},
|
|
cmp.AllowUnexported(transportError{}),
|
|
); diff != "" {
|
|
t.t.Errorf("transport error mismatch (-want +got):\n%s", diff)
|
|
}
|
|
t.controlCalls++
|
|
}
|
|
|
|
// Attach is only implemented to satisfy the LinkEndpoint interface.
|
|
func (*testObject) Attach(stack.NetworkDispatcher) {}
|
|
|
|
// IsAttached implements stack.LinkEndpoint.IsAttached.
|
|
func (*testObject) IsAttached() bool {
|
|
return true
|
|
}
|
|
|
|
// MTU implements stack.LinkEndpoint.MTU. It just returns a constant that
|
|
// matches the linux loopback MTU.
|
|
func (*testObject) MTU() uint32 {
|
|
return 65536
|
|
}
|
|
|
|
// Capabilities implements stack.LinkEndpoint.Capabilities.
|
|
func (*testObject) Capabilities() stack.LinkEndpointCapabilities {
|
|
return 0
|
|
}
|
|
|
|
// MaxHeaderLength is only implemented to satisfy the LinkEndpoint interface.
|
|
func (*testObject) MaxHeaderLength() uint16 {
|
|
return 0
|
|
}
|
|
|
|
// LinkAddress returns the link address of this endpoint.
|
|
func (*testObject) LinkAddress() tcpip.LinkAddress {
|
|
return ""
|
|
}
|
|
|
|
// Wait implements stack.LinkEndpoint.Wait.
|
|
func (*testObject) Wait() {}
|
|
|
|
// WritePacket is called by network endpoints after producing a packet and
|
|
// writing it to the link endpoint. This is used by the test object to verify
|
|
// that the produced packet is as expected.
|
|
func (t *testObject) WritePacket(_ *stack.Route, _ *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
|
|
var prot tcpip.TransportProtocolNumber
|
|
var srcAddr tcpip.Address
|
|
var dstAddr tcpip.Address
|
|
|
|
if t.v4 {
|
|
h := header.IPv4(pkt.NetworkHeader().View())
|
|
prot = tcpip.TransportProtocolNumber(h.Protocol())
|
|
srcAddr = h.SourceAddress()
|
|
dstAddr = h.DestinationAddress()
|
|
|
|
} else {
|
|
h := header.IPv6(pkt.NetworkHeader().View())
|
|
prot = tcpip.TransportProtocolNumber(h.NextHeader())
|
|
srcAddr = h.SourceAddress()
|
|
dstAddr = h.DestinationAddress()
|
|
}
|
|
t.checkValues(prot, pkt.Data, srcAddr, dstAddr)
|
|
return nil
|
|
}
|
|
|
|
// WritePackets implements stack.LinkEndpoint.WritePackets.
|
|
func (*testObject) WritePackets(_ *stack.Route, _ *stack.GSO, pkt stack.PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
|
|
panic("not implemented")
|
|
}
|
|
|
|
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
|
|
func (*testObject) ARPHardwareType() header.ARPHardwareType {
|
|
panic("not implemented")
|
|
}
|
|
|
|
// AddHeader implements stack.LinkEndpoint.AddHeader.
|
|
func (*testObject) AddHeader(local, remote tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func buildIPv4Route(local, remote tcpip.Address) (*stack.Route, tcpip.Error) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
|
|
})
|
|
s.CreateNIC(nicID, loopback.New())
|
|
s.AddAddress(nicID, ipv4.ProtocolNumber, local)
|
|
s.SetRouteTable([]tcpip.Route{{
|
|
Destination: header.IPv4EmptySubnet,
|
|
Gateway: ipv4Gateway,
|
|
NIC: 1,
|
|
}})
|
|
|
|
return s.FindRoute(nicID, local, remote, ipv4.ProtocolNumber, false /* multicastLoop */)
|
|
}
|
|
|
|
func buildIPv6Route(local, remote tcpip.Address) (*stack.Route, tcpip.Error) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
|
|
})
|
|
s.CreateNIC(nicID, loopback.New())
|
|
s.AddAddress(nicID, ipv6.ProtocolNumber, local)
|
|
s.SetRouteTable([]tcpip.Route{{
|
|
Destination: header.IPv6EmptySubnet,
|
|
Gateway: ipv6Gateway,
|
|
NIC: 1,
|
|
}})
|
|
|
|
return s.FindRoute(nicID, local, remote, ipv6.ProtocolNumber, false /* multicastLoop */)
|
|
}
|
|
|
|
func buildDummyStackWithLinkEndpoint(t *testing.T, mtu uint32) (*stack.Stack, *channel.Endpoint) {
|
|
t.Helper()
|
|
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
|
|
})
|
|
e := channel.New(1, mtu, "")
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
|
}
|
|
|
|
v4Addr := tcpip.ProtocolAddress{Protocol: header.IPv4ProtocolNumber, AddressWithPrefix: localIPv4AddrWithPrefix}
|
|
if err := s.AddProtocolAddress(nicID, v4Addr); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %#v) = %s", nicID, v4Addr, err)
|
|
}
|
|
|
|
v6Addr := tcpip.ProtocolAddress{Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: localIPv6AddrWithPrefix}
|
|
if err := s.AddProtocolAddress(nicID, v6Addr); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %#v) = %s", nicID, v6Addr, err)
|
|
}
|
|
|
|
return s, e
|
|
}
|
|
|
|
func buildDummyStack(t *testing.T) *stack.Stack {
|
|
t.Helper()
|
|
|
|
s, _ := buildDummyStackWithLinkEndpoint(t, header.IPv6MinimumMTU)
|
|
return s
|
|
}
|
|
|
|
var _ stack.NetworkInterface = (*testInterface)(nil)
|
|
|
|
type testInterface struct {
|
|
testObject
|
|
|
|
mu struct {
|
|
sync.RWMutex
|
|
disabled bool
|
|
}
|
|
}
|
|
|
|
func (*testInterface) ID() tcpip.NICID {
|
|
return nicID
|
|
}
|
|
|
|
func (*testInterface) IsLoopback() bool {
|
|
return false
|
|
}
|
|
|
|
func (*testInterface) Name() string {
|
|
return ""
|
|
}
|
|
|
|
func (t *testInterface) Enabled() bool {
|
|
t.mu.RLock()
|
|
defer t.mu.RUnlock()
|
|
return !t.mu.disabled
|
|
}
|
|
|
|
func (*testInterface) Promiscuous() bool {
|
|
return false
|
|
}
|
|
|
|
func (*testInterface) Spoofing() bool {
|
|
return false
|
|
}
|
|
|
|
func (t *testInterface) setEnabled(v bool) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
t.mu.disabled = !v
|
|
}
|
|
|
|
func (*testInterface) WritePacketToRemote(tcpip.LinkAddress, *stack.GSO, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) tcpip.Error {
|
|
return &tcpip.ErrNotSupported{}
|
|
}
|
|
|
|
func (*testInterface) HandleNeighborProbe(tcpip.NetworkProtocolNumber, tcpip.Address, tcpip.LinkAddress) tcpip.Error {
|
|
return nil
|
|
}
|
|
|
|
func (*testInterface) HandleNeighborConfirmation(tcpip.NetworkProtocolNumber, tcpip.Address, tcpip.LinkAddress, stack.ReachabilityConfirmationFlags) tcpip.Error {
|
|
return nil
|
|
}
|
|
|
|
func (*testInterface) CheckLocalAddress(tcpip.NetworkProtocolNumber, tcpip.Address) bool {
|
|
return false
|
|
}
|
|
|
|
func TestSourceAddressValidation(t *testing.T) {
|
|
rxIPv4ICMP := func(e *channel.Endpoint, src tcpip.Address) {
|
|
totalLen := header.IPv4MinimumSize + header.ICMPv4MinimumSize
|
|
hdr := buffer.NewPrependable(totalLen)
|
|
pkt := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
|
|
pkt.SetType(header.ICMPv4Echo)
|
|
pkt.SetCode(0)
|
|
pkt.SetChecksum(0)
|
|
pkt.SetChecksum(^header.Checksum(pkt, 0))
|
|
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
|
|
ip.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(totalLen),
|
|
Protocol: uint8(icmp.ProtocolNumber4),
|
|
TTL: ipv4.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: localIPv4Addr,
|
|
})
|
|
ip.SetChecksum(^ip.CalculateChecksum())
|
|
|
|
e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: hdr.View().ToVectorisedView(),
|
|
}))
|
|
}
|
|
|
|
rxIPv6ICMP := func(e *channel.Endpoint, src tcpip.Address) {
|
|
totalLen := header.IPv6MinimumSize + header.ICMPv6MinimumSize
|
|
hdr := buffer.NewPrependable(totalLen)
|
|
pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6MinimumSize))
|
|
pkt.SetType(header.ICMPv6EchoRequest)
|
|
pkt.SetCode(0)
|
|
pkt.SetChecksum(0)
|
|
pkt.SetChecksum(header.ICMPv6Checksum(pkt, src, localIPv6Addr, buffer.VectorisedView{}))
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: header.ICMPv6MinimumSize,
|
|
TransportProtocol: icmp.ProtocolNumber6,
|
|
HopLimit: ipv6.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: localIPv6Addr,
|
|
})
|
|
e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: hdr.View().ToVectorisedView(),
|
|
}))
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
srcAddress tcpip.Address
|
|
rxICMP func(*channel.Endpoint, tcpip.Address)
|
|
valid bool
|
|
}{
|
|
{
|
|
name: "IPv4 valid",
|
|
srcAddress: "\x01\x02\x03\x04",
|
|
rxICMP: rxIPv4ICMP,
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "IPv6 valid",
|
|
srcAddress: "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10",
|
|
rxICMP: rxIPv6ICMP,
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "IPv4 unspecified",
|
|
srcAddress: header.IPv4Any,
|
|
rxICMP: rxIPv4ICMP,
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "IPv6 unspecified",
|
|
srcAddress: header.IPv4Any,
|
|
rxICMP: rxIPv6ICMP,
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "IPv4 multicast",
|
|
srcAddress: "\xe0\x00\x00\x01",
|
|
rxICMP: rxIPv4ICMP,
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "IPv6 multicast",
|
|
srcAddress: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
|
|
rxICMP: rxIPv6ICMP,
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "IPv4 broadcast",
|
|
srcAddress: header.IPv4Broadcast,
|
|
rxICMP: rxIPv4ICMP,
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "IPv4 subnet broadcast",
|
|
srcAddress: func() tcpip.Address {
|
|
subnet := localIPv4AddrWithPrefix.Subnet()
|
|
return subnet.Broadcast()
|
|
}(),
|
|
rxICMP: rxIPv4ICMP,
|
|
valid: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s, e := buildDummyStackWithLinkEndpoint(t, header.IPv6MinimumMTU)
|
|
test.rxICMP(e, test.srcAddress)
|
|
|
|
var wantValid uint64
|
|
if test.valid {
|
|
wantValid = 1
|
|
}
|
|
|
|
if got, want := s.Stats().IP.InvalidSourceAddressesReceived.Value(), 1-wantValid; got != want {
|
|
t.Errorf("got s.Stats().IP.InvalidSourceAddressesReceived.Value() = %d, want = %d", got, want)
|
|
}
|
|
if got := s.Stats().IP.PacketsDelivered.Value(); got != wantValid {
|
|
t.Errorf("got s.Stats().IP.PacketsDelivered.Value() = %d, want = %d", got, wantValid)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnableWhenNICDisabled(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
protocolFactory stack.NetworkProtocolFactory
|
|
protoNum tcpip.NetworkProtocolNumber
|
|
}{
|
|
{
|
|
name: "IPv4",
|
|
protocolFactory: ipv4.NewProtocol,
|
|
protoNum: ipv4.ProtocolNumber,
|
|
},
|
|
{
|
|
name: "IPv6",
|
|
protocolFactory: ipv6.NewProtocol,
|
|
protoNum: ipv6.ProtocolNumber,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var nic testInterface
|
|
nic.setEnabled(false)
|
|
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{test.protocolFactory},
|
|
})
|
|
p := s.NetworkProtocolInstance(test.protoNum)
|
|
|
|
// We pass nil for all parameters except the NetworkInterface and Stack
|
|
// since Enable only depends on these.
|
|
ep := p.NewEndpoint(&nic, nil)
|
|
|
|
// The endpoint should initially be disabled, regardless the NIC's enabled
|
|
// status.
|
|
if ep.Enabled() {
|
|
t.Fatal("got ep.Enabled() = true, want = false")
|
|
}
|
|
nic.setEnabled(true)
|
|
if ep.Enabled() {
|
|
t.Fatal("got ep.Enabled() = true, want = false")
|
|
}
|
|
|
|
// Attempting to enable the endpoint while the NIC is disabled should
|
|
// fail.
|
|
nic.setEnabled(false)
|
|
err := ep.Enable()
|
|
if _, ok := err.(*tcpip.ErrNotPermitted); !ok {
|
|
t.Fatalf("got ep.Enable() = %s, want = %s", err, &tcpip.ErrNotPermitted{})
|
|
}
|
|
// ep should consider the NIC's enabled status when determining its own
|
|
// enabled status so we "enable" the NIC to read just the endpoint's
|
|
// enabled status.
|
|
nic.setEnabled(true)
|
|
if ep.Enabled() {
|
|
t.Fatal("got ep.Enabled() = true, want = false")
|
|
}
|
|
|
|
// Enabling the interface after the NIC has been enabled should succeed.
|
|
if err := ep.Enable(); err != nil {
|
|
t.Fatalf("ep.Enable(): %s", err)
|
|
}
|
|
if !ep.Enabled() {
|
|
t.Fatal("got ep.Enabled() = false, want = true")
|
|
}
|
|
|
|
// ep should consider the NIC's enabled status when determining its own
|
|
// enabled status.
|
|
nic.setEnabled(false)
|
|
if ep.Enabled() {
|
|
t.Fatal("got ep.Enabled() = true, want = false")
|
|
}
|
|
|
|
// Disabling the endpoint when the NIC is enabled should make the endpoint
|
|
// disabled.
|
|
nic.setEnabled(true)
|
|
ep.Disable()
|
|
if ep.Enabled() {
|
|
t.Fatal("got ep.Enabled() = true, want = false")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIPv4Send(t *testing.T) {
|
|
s := buildDummyStack(t)
|
|
proto := s.NetworkProtocolInstance(ipv4.ProtocolNumber)
|
|
nic := testInterface{
|
|
testObject: testObject{
|
|
t: t,
|
|
v4: true,
|
|
},
|
|
}
|
|
ep := proto.NewEndpoint(&nic, nil)
|
|
defer ep.Close()
|
|
|
|
// Allocate and initialize the payload view.
|
|
payload := buffer.NewView(100)
|
|
for i := 0; i < len(payload); i++ {
|
|
payload[i] = uint8(i)
|
|
}
|
|
|
|
// Setup the packet buffer.
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
ReserveHeaderBytes: int(ep.MaxHeaderLength()),
|
|
Data: payload.ToVectorisedView(),
|
|
})
|
|
|
|
// Issue the write.
|
|
nic.testObject.protocol = 123
|
|
nic.testObject.srcAddr = localIPv4Addr
|
|
nic.testObject.dstAddr = remoteIPv4Addr
|
|
nic.testObject.contents = payload
|
|
|
|
r, err := buildIPv4Route(localIPv4Addr, remoteIPv4Addr)
|
|
if err != nil {
|
|
t.Fatalf("could not find route: %v", err)
|
|
}
|
|
if err := ep.WritePacket(r, nil /* gso */, stack.NetworkHeaderParams{
|
|
Protocol: 123,
|
|
TTL: 123,
|
|
TOS: stack.DefaultTOS,
|
|
}, pkt); err != nil {
|
|
t.Fatalf("WritePacket failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReceive(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
protoFactory stack.NetworkProtocolFactory
|
|
protoNum tcpip.NetworkProtocolNumber
|
|
v4 bool
|
|
epAddr tcpip.AddressWithPrefix
|
|
handlePacket func(*testing.T, stack.NetworkEndpoint, *testInterface)
|
|
}{
|
|
{
|
|
name: "IPv4",
|
|
protoFactory: ipv4.NewProtocol,
|
|
protoNum: ipv4.ProtocolNumber,
|
|
v4: true,
|
|
epAddr: localIPv4Addr.WithPrefix(),
|
|
handlePacket: func(t *testing.T, ep stack.NetworkEndpoint, nic *testInterface) {
|
|
const totalLen = header.IPv4MinimumSize + 30 /* payload length */
|
|
|
|
view := buffer.NewView(totalLen)
|
|
ip := header.IPv4(view)
|
|
ip.Encode(&header.IPv4Fields{
|
|
TotalLength: totalLen,
|
|
TTL: ipv4.DefaultTTL,
|
|
Protocol: 10,
|
|
SrcAddr: remoteIPv4Addr,
|
|
DstAddr: localIPv4Addr,
|
|
})
|
|
ip.SetChecksum(^ip.CalculateChecksum())
|
|
|
|
// Make payload be non-zero.
|
|
for i := header.IPv4MinimumSize; i < len(view); i++ {
|
|
view[i] = uint8(i)
|
|
}
|
|
|
|
// Give packet to ipv4 endpoint, dispatcher will validate that it's ok.
|
|
nic.testObject.protocol = 10
|
|
nic.testObject.srcAddr = remoteIPv4Addr
|
|
nic.testObject.dstAddr = localIPv4Addr
|
|
nic.testObject.contents = view[header.IPv4MinimumSize:totalLen]
|
|
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: view.ToVectorisedView(),
|
|
})
|
|
ep.HandlePacket(pkt)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6",
|
|
protoFactory: ipv6.NewProtocol,
|
|
protoNum: ipv6.ProtocolNumber,
|
|
v4: false,
|
|
epAddr: localIPv6Addr.WithPrefix(),
|
|
handlePacket: func(t *testing.T, ep stack.NetworkEndpoint, nic *testInterface) {
|
|
const payloadLen = 30
|
|
view := buffer.NewView(header.IPv6MinimumSize + payloadLen)
|
|
ip := header.IPv6(view)
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: payloadLen,
|
|
TransportProtocol: 10,
|
|
HopLimit: ipv6.DefaultTTL,
|
|
SrcAddr: remoteIPv6Addr,
|
|
DstAddr: localIPv6Addr,
|
|
})
|
|
|
|
// Make payload be non-zero.
|
|
for i := header.IPv6MinimumSize; i < len(view); i++ {
|
|
view[i] = uint8(i)
|
|
}
|
|
|
|
// Give packet to ipv6 endpoint, dispatcher will validate that it's ok.
|
|
nic.testObject.protocol = 10
|
|
nic.testObject.srcAddr = remoteIPv6Addr
|
|
nic.testObject.dstAddr = localIPv6Addr
|
|
nic.testObject.contents = view[header.IPv6MinimumSize:][:payloadLen]
|
|
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: view.ToVectorisedView(),
|
|
})
|
|
ep.HandlePacket(pkt)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{test.protoFactory},
|
|
})
|
|
nic := testInterface{
|
|
testObject: testObject{
|
|
t: t,
|
|
v4: test.v4,
|
|
},
|
|
}
|
|
ep := s.NetworkProtocolInstance(test.protoNum).NewEndpoint(&nic, &nic.testObject)
|
|
defer ep.Close()
|
|
|
|
if err := ep.Enable(); err != nil {
|
|
t.Fatalf("ep.Enable(): %s", err)
|
|
}
|
|
|
|
addressableEndpoint, ok := ep.(stack.AddressableEndpoint)
|
|
if !ok {
|
|
t.Fatalf("expected network endpoint with number = %d to implement stack.AddressableEndpoint", test.protoNum)
|
|
}
|
|
if ep, err := addressableEndpoint.AddAndAcquirePermanentAddress(test.epAddr, stack.CanBePrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */); err != nil {
|
|
t.Fatalf("addressableEndpoint.AddAndAcquirePermanentAddress(%s, CanBePrimaryEndpoint, AddressConfigStatic, false): %s", test.epAddr, err)
|
|
} else {
|
|
ep.DecRef()
|
|
}
|
|
|
|
stat := s.Stats().IP.PacketsReceived
|
|
if got := stat.Value(); got != 0 {
|
|
t.Fatalf("got s.Stats().IP.PacketsReceived.Value() = %d, want = 0", got)
|
|
}
|
|
test.handlePacket(t, ep, &nic)
|
|
if nic.testObject.dataCalls != 1 {
|
|
t.Errorf("Bad number of data calls: got %x, want 1", nic.testObject.dataCalls)
|
|
}
|
|
if got := stat.Value(); got != 1 {
|
|
t.Errorf("got s.Stats().IP.PacketsReceived.Value() = %d, want = 1", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIPv4ReceiveControl(t *testing.T) {
|
|
const (
|
|
mtu = 0xbeef - header.IPv4MinimumSize
|
|
dataLen = 8
|
|
)
|
|
|
|
cases := []struct {
|
|
name string
|
|
expectedCount int
|
|
fragmentOffset uint16
|
|
code header.ICMPv4Code
|
|
transErr transportError
|
|
trunc int
|
|
}{
|
|
{
|
|
name: "FragmentationNeeded",
|
|
expectedCount: 1,
|
|
fragmentOffset: 0,
|
|
code: header.ICMPv4FragmentationNeeded,
|
|
transErr: transportError{
|
|
origin: tcpip.SockExtErrorOriginICMP,
|
|
typ: uint8(header.ICMPv4DstUnreachable),
|
|
code: uint8(header.ICMPv4FragmentationNeeded),
|
|
info: mtu,
|
|
kind: stack.PacketTooBigTransportError,
|
|
},
|
|
trunc: 0,
|
|
},
|
|
{
|
|
name: "Truncated (missing IPv4 header)",
|
|
expectedCount: 0,
|
|
fragmentOffset: 0,
|
|
code: header.ICMPv4FragmentationNeeded,
|
|
trunc: header.IPv4MinimumSize + header.ICMPv4MinimumSize,
|
|
},
|
|
{
|
|
name: "Truncated (partial offending packet's IP header)",
|
|
expectedCount: 0,
|
|
fragmentOffset: 0,
|
|
code: header.ICMPv4FragmentationNeeded,
|
|
trunc: header.IPv4MinimumSize + header.ICMPv4MinimumSize + header.IPv4MinimumSize - 1,
|
|
},
|
|
{
|
|
name: "Truncated (partial offending packet's data)",
|
|
expectedCount: 0,
|
|
fragmentOffset: 0,
|
|
code: header.ICMPv4FragmentationNeeded,
|
|
trunc: header.ICMPv4MinimumSize + header.ICMPv4MinimumSize + header.IPv4MinimumSize + dataLen - 1,
|
|
},
|
|
{
|
|
name: "Port unreachable",
|
|
expectedCount: 1,
|
|
fragmentOffset: 0,
|
|
code: header.ICMPv4PortUnreachable,
|
|
transErr: transportError{
|
|
origin: tcpip.SockExtErrorOriginICMP,
|
|
typ: uint8(header.ICMPv4DstUnreachable),
|
|
code: uint8(header.ICMPv4PortUnreachable),
|
|
kind: stack.DestinationPortUnreachableTransportError,
|
|
},
|
|
trunc: 0,
|
|
},
|
|
{
|
|
name: "Non-zero fragment offset",
|
|
expectedCount: 0,
|
|
fragmentOffset: 100,
|
|
code: header.ICMPv4PortUnreachable,
|
|
trunc: 0,
|
|
},
|
|
{
|
|
name: "Zero-length packet",
|
|
expectedCount: 0,
|
|
fragmentOffset: 100,
|
|
code: header.ICMPv4PortUnreachable,
|
|
trunc: 2*header.IPv4MinimumSize + header.ICMPv4MinimumSize + dataLen,
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := buildDummyStack(t)
|
|
proto := s.NetworkProtocolInstance(ipv4.ProtocolNumber)
|
|
nic := testInterface{
|
|
testObject: testObject{
|
|
t: t,
|
|
},
|
|
}
|
|
ep := proto.NewEndpoint(&nic, &nic.testObject)
|
|
defer ep.Close()
|
|
|
|
if err := ep.Enable(); err != nil {
|
|
t.Fatalf("ep.Enable(): %s", err)
|
|
}
|
|
|
|
const dataOffset = header.IPv4MinimumSize*2 + header.ICMPv4MinimumSize
|
|
view := buffer.NewView(dataOffset + dataLen)
|
|
|
|
// Create the outer IPv4 header.
|
|
ip := header.IPv4(view)
|
|
ip.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(len(view) - c.trunc),
|
|
TTL: 20,
|
|
Protocol: uint8(header.ICMPv4ProtocolNumber),
|
|
SrcAddr: "\x0a\x00\x00\xbb",
|
|
DstAddr: localIPv4Addr,
|
|
})
|
|
ip.SetChecksum(^ip.CalculateChecksum())
|
|
|
|
// Create the ICMP header.
|
|
icmp := header.ICMPv4(view[header.IPv4MinimumSize:])
|
|
icmp.SetType(header.ICMPv4DstUnreachable)
|
|
icmp.SetCode(c.code)
|
|
icmp.SetIdent(0xdead)
|
|
icmp.SetSequence(0xbeef)
|
|
|
|
// Create the inner IPv4 header.
|
|
ip = header.IPv4(view[header.IPv4MinimumSize+header.ICMPv4MinimumSize:])
|
|
ip.Encode(&header.IPv4Fields{
|
|
TotalLength: 100,
|
|
TTL: 20,
|
|
Protocol: 10,
|
|
FragmentOffset: c.fragmentOffset,
|
|
SrcAddr: localIPv4Addr,
|
|
DstAddr: remoteIPv4Addr,
|
|
})
|
|
ip.SetChecksum(^ip.CalculateChecksum())
|
|
|
|
// Make payload be non-zero.
|
|
for i := dataOffset; i < len(view); i++ {
|
|
view[i] = uint8(i)
|
|
}
|
|
|
|
icmp.SetChecksum(0)
|
|
checksum := ^header.Checksum(icmp, 0 /* initial */)
|
|
icmp.SetChecksum(checksum)
|
|
|
|
// Give packet to IPv4 endpoint, dispatcher will validate that
|
|
// it's ok.
|
|
nic.testObject.protocol = 10
|
|
nic.testObject.srcAddr = remoteIPv4Addr
|
|
nic.testObject.dstAddr = localIPv4Addr
|
|
nic.testObject.contents = view[dataOffset:]
|
|
nic.testObject.transErr = c.transErr
|
|
|
|
addressableEndpoint, ok := ep.(stack.AddressableEndpoint)
|
|
if !ok {
|
|
t.Fatal("expected IPv4 network endpoint to implement stack.AddressableEndpoint")
|
|
}
|
|
addr := localIPv4Addr.WithPrefix()
|
|
if ep, err := addressableEndpoint.AddAndAcquirePermanentAddress(addr, stack.CanBePrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */); err != nil {
|
|
t.Fatalf("addressableEndpoint.AddAndAcquirePermanentAddress(%s, CanBePrimaryEndpoint, AddressConfigStatic, false): %s", addr, err)
|
|
} else {
|
|
ep.DecRef()
|
|
}
|
|
|
|
pkt := truncatedPacket(view, c.trunc, header.IPv4MinimumSize)
|
|
ep.HandlePacket(pkt)
|
|
if want := c.expectedCount; nic.testObject.controlCalls != want {
|
|
t.Fatalf("Bad number of control calls for %q case: got %v, want %v", c.name, nic.testObject.controlCalls, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIPv4FragmentationReceive(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
|
|
})
|
|
proto := s.NetworkProtocolInstance(ipv4.ProtocolNumber)
|
|
nic := testInterface{
|
|
testObject: testObject{
|
|
t: t,
|
|
v4: true,
|
|
},
|
|
}
|
|
ep := proto.NewEndpoint(&nic, &nic.testObject)
|
|
defer ep.Close()
|
|
|
|
if err := ep.Enable(); err != nil {
|
|
t.Fatalf("ep.Enable(): %s", err)
|
|
}
|
|
|
|
totalLen := header.IPv4MinimumSize + 24
|
|
|
|
frag1 := buffer.NewView(totalLen)
|
|
ip1 := header.IPv4(frag1)
|
|
ip1.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(totalLen),
|
|
TTL: 20,
|
|
Protocol: 10,
|
|
FragmentOffset: 0,
|
|
Flags: header.IPv4FlagMoreFragments,
|
|
SrcAddr: remoteIPv4Addr,
|
|
DstAddr: localIPv4Addr,
|
|
})
|
|
ip1.SetChecksum(^ip1.CalculateChecksum())
|
|
|
|
// Make payload be non-zero.
|
|
for i := header.IPv4MinimumSize; i < totalLen; i++ {
|
|
frag1[i] = uint8(i)
|
|
}
|
|
|
|
frag2 := buffer.NewView(totalLen)
|
|
ip2 := header.IPv4(frag2)
|
|
ip2.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(totalLen),
|
|
TTL: 20,
|
|
Protocol: 10,
|
|
FragmentOffset: 24,
|
|
SrcAddr: remoteIPv4Addr,
|
|
DstAddr: localIPv4Addr,
|
|
})
|
|
ip2.SetChecksum(^ip2.CalculateChecksum())
|
|
|
|
// Make payload be non-zero.
|
|
for i := header.IPv4MinimumSize; i < totalLen; i++ {
|
|
frag2[i] = uint8(i)
|
|
}
|
|
|
|
// Give packet to ipv4 endpoint, dispatcher will validate that it's ok.
|
|
nic.testObject.protocol = 10
|
|
nic.testObject.srcAddr = remoteIPv4Addr
|
|
nic.testObject.dstAddr = localIPv4Addr
|
|
nic.testObject.contents = append(frag1[header.IPv4MinimumSize:totalLen], frag2[header.IPv4MinimumSize:totalLen]...)
|
|
|
|
// Send first segment.
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: frag1.ToVectorisedView(),
|
|
})
|
|
|
|
addressableEndpoint, ok := ep.(stack.AddressableEndpoint)
|
|
if !ok {
|
|
t.Fatal("expected IPv4 network endpoint to implement stack.AddressableEndpoint")
|
|
}
|
|
addr := localIPv4Addr.WithPrefix()
|
|
if ep, err := addressableEndpoint.AddAndAcquirePermanentAddress(addr, stack.CanBePrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */); err != nil {
|
|
t.Fatalf("addressableEndpoint.AddAndAcquirePermanentAddress(%s, CanBePrimaryEndpoint, AddressConfigStatic, false): %s", addr, err)
|
|
} else {
|
|
ep.DecRef()
|
|
}
|
|
|
|
ep.HandlePacket(pkt)
|
|
if nic.testObject.dataCalls != 0 {
|
|
t.Fatalf("Bad number of data calls: got %x, want 0", nic.testObject.dataCalls)
|
|
}
|
|
|
|
// Send second segment.
|
|
pkt = stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: frag2.ToVectorisedView(),
|
|
})
|
|
ep.HandlePacket(pkt)
|
|
if nic.testObject.dataCalls != 1 {
|
|
t.Fatalf("Bad number of data calls: got %x, want 1", nic.testObject.dataCalls)
|
|
}
|
|
}
|
|
|
|
func TestIPv6Send(t *testing.T) {
|
|
s := buildDummyStack(t)
|
|
proto := s.NetworkProtocolInstance(ipv6.ProtocolNumber)
|
|
nic := testInterface{
|
|
testObject: testObject{
|
|
t: t,
|
|
},
|
|
}
|
|
ep := proto.NewEndpoint(&nic, nil)
|
|
defer ep.Close()
|
|
|
|
if err := ep.Enable(); err != nil {
|
|
t.Fatalf("ep.Enable(): %s", err)
|
|
}
|
|
|
|
// Allocate and initialize the payload view.
|
|
payload := buffer.NewView(100)
|
|
for i := 0; i < len(payload); i++ {
|
|
payload[i] = uint8(i)
|
|
}
|
|
|
|
// Setup the packet buffer.
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
ReserveHeaderBytes: int(ep.MaxHeaderLength()),
|
|
Data: payload.ToVectorisedView(),
|
|
})
|
|
|
|
// Issue the write.
|
|
nic.testObject.protocol = 123
|
|
nic.testObject.srcAddr = localIPv6Addr
|
|
nic.testObject.dstAddr = remoteIPv6Addr
|
|
nic.testObject.contents = payload
|
|
|
|
r, err := buildIPv6Route(localIPv6Addr, remoteIPv6Addr)
|
|
if err != nil {
|
|
t.Fatalf("could not find route: %v", err)
|
|
}
|
|
if err := ep.WritePacket(r, nil /* gso */, stack.NetworkHeaderParams{
|
|
Protocol: 123,
|
|
TTL: 123,
|
|
TOS: stack.DefaultTOS,
|
|
}, pkt); err != nil {
|
|
t.Fatalf("WritePacket failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIPv6ReceiveControl(t *testing.T) {
|
|
const (
|
|
mtu = 0xffff
|
|
outerSrcAddr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa"
|
|
dataLen = 8
|
|
)
|
|
|
|
newUint16 := func(v uint16) *uint16 { return &v }
|
|
|
|
portUnreachableTransErr := transportError{
|
|
origin: tcpip.SockExtErrorOriginICMP6,
|
|
typ: uint8(header.ICMPv6DstUnreachable),
|
|
code: uint8(header.ICMPv6PortUnreachable),
|
|
kind: stack.DestinationPortUnreachableTransportError,
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
expectedCount int
|
|
fragmentOffset *uint16
|
|
typ header.ICMPv6Type
|
|
code header.ICMPv6Code
|
|
transErr transportError
|
|
trunc int
|
|
}{
|
|
{
|
|
name: "PacketTooBig",
|
|
expectedCount: 1,
|
|
fragmentOffset: nil,
|
|
typ: header.ICMPv6PacketTooBig,
|
|
code: header.ICMPv6UnusedCode,
|
|
transErr: transportError{
|
|
origin: tcpip.SockExtErrorOriginICMP6,
|
|
typ: uint8(header.ICMPv6PacketTooBig),
|
|
code: uint8(header.ICMPv6UnusedCode),
|
|
info: mtu,
|
|
kind: stack.PacketTooBigTransportError,
|
|
},
|
|
trunc: 0,
|
|
},
|
|
{
|
|
name: "Truncated (missing offending packet's IPv6 header)",
|
|
expectedCount: 0,
|
|
fragmentOffset: nil,
|
|
typ: header.ICMPv6PacketTooBig,
|
|
code: header.ICMPv6UnusedCode,
|
|
trunc: header.IPv6MinimumSize + header.ICMPv6PacketTooBigMinimumSize,
|
|
},
|
|
{
|
|
name: "Truncated PacketTooBig (partial offending packet's IPv6 header)",
|
|
expectedCount: 0,
|
|
fragmentOffset: nil,
|
|
typ: header.ICMPv6PacketTooBig,
|
|
code: header.ICMPv6UnusedCode,
|
|
trunc: header.IPv6MinimumSize + header.ICMPv6PacketTooBigMinimumSize + header.IPv6MinimumSize - 1,
|
|
},
|
|
{
|
|
name: "Truncated (partial offending packet's data)",
|
|
expectedCount: 0,
|
|
fragmentOffset: nil,
|
|
typ: header.ICMPv6PacketTooBig,
|
|
code: header.ICMPv6UnusedCode,
|
|
trunc: header.IPv6MinimumSize + header.ICMPv6PacketTooBigMinimumSize + header.IPv6MinimumSize + dataLen - 1,
|
|
},
|
|
{
|
|
name: "Port unreachable",
|
|
expectedCount: 1,
|
|
fragmentOffset: nil,
|
|
typ: header.ICMPv6DstUnreachable,
|
|
code: header.ICMPv6PortUnreachable,
|
|
transErr: portUnreachableTransErr,
|
|
trunc: 0,
|
|
},
|
|
{
|
|
name: "Truncated DstPortUnreachable (partial offending packet's IP header)",
|
|
expectedCount: 0,
|
|
fragmentOffset: nil,
|
|
typ: header.ICMPv6DstUnreachable,
|
|
code: header.ICMPv6PortUnreachable,
|
|
trunc: header.IPv6MinimumSize + header.ICMPv6DstUnreachableMinimumSize + header.IPv6MinimumSize - 1,
|
|
},
|
|
{
|
|
name: "DstPortUnreachable for Fragmented, zero offset",
|
|
expectedCount: 1,
|
|
fragmentOffset: newUint16(0),
|
|
typ: header.ICMPv6DstUnreachable,
|
|
code: header.ICMPv6PortUnreachable,
|
|
transErr: portUnreachableTransErr,
|
|
trunc: 0,
|
|
},
|
|
{
|
|
name: "DstPortUnreachable for Non-zero fragment offset",
|
|
expectedCount: 0,
|
|
fragmentOffset: newUint16(100),
|
|
typ: header.ICMPv6DstUnreachable,
|
|
code: header.ICMPv6PortUnreachable,
|
|
transErr: portUnreachableTransErr,
|
|
trunc: 0,
|
|
},
|
|
{
|
|
name: "Zero-length packet",
|
|
expectedCount: 0,
|
|
fragmentOffset: nil,
|
|
typ: header.ICMPv6DstUnreachable,
|
|
code: header.ICMPv6PortUnreachable,
|
|
trunc: 2*header.IPv6MinimumSize + header.ICMPv6DstUnreachableMinimumSize + dataLen,
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := buildDummyStack(t)
|
|
proto := s.NetworkProtocolInstance(ipv6.ProtocolNumber)
|
|
nic := testInterface{
|
|
testObject: testObject{
|
|
t: t,
|
|
},
|
|
}
|
|
ep := proto.NewEndpoint(&nic, &nic.testObject)
|
|
defer ep.Close()
|
|
|
|
if err := ep.Enable(); err != nil {
|
|
t.Fatalf("ep.Enable(): %s", err)
|
|
}
|
|
|
|
dataOffset := header.IPv6MinimumSize*2 + header.ICMPv6MinimumSize
|
|
if c.fragmentOffset != nil {
|
|
dataOffset += header.IPv6FragmentHeaderSize
|
|
}
|
|
view := buffer.NewView(dataOffset + dataLen)
|
|
|
|
// Create the outer IPv6 header.
|
|
ip := header.IPv6(view)
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(len(view) - header.IPv6MinimumSize - c.trunc),
|
|
TransportProtocol: header.ICMPv6ProtocolNumber,
|
|
HopLimit: 20,
|
|
SrcAddr: outerSrcAddr,
|
|
DstAddr: localIPv6Addr,
|
|
})
|
|
|
|
// Create the ICMP header.
|
|
icmp := header.ICMPv6(view[header.IPv6MinimumSize:])
|
|
icmp.SetType(c.typ)
|
|
icmp.SetCode(c.code)
|
|
icmp.SetIdent(0xdead)
|
|
icmp.SetSequence(0xbeef)
|
|
|
|
var extHdrs header.IPv6ExtHdrSerializer
|
|
// Build the fragmentation header if needed.
|
|
if c.fragmentOffset != nil {
|
|
extHdrs = append(extHdrs, &header.IPv6SerializableFragmentExtHdr{
|
|
FragmentOffset: *c.fragmentOffset,
|
|
M: true,
|
|
Identification: 0x12345678,
|
|
})
|
|
}
|
|
|
|
// Create the inner IPv6 header.
|
|
ip = header.IPv6(view[header.IPv6MinimumSize+header.ICMPv6PayloadOffset:])
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: 100,
|
|
TransportProtocol: 10,
|
|
HopLimit: 20,
|
|
SrcAddr: localIPv6Addr,
|
|
DstAddr: remoteIPv6Addr,
|
|
ExtensionHeaders: extHdrs,
|
|
})
|
|
|
|
// Make payload be non-zero.
|
|
for i := dataOffset; i < len(view); i++ {
|
|
view[i] = uint8(i)
|
|
}
|
|
|
|
// Give packet to IPv6 endpoint, dispatcher will validate that
|
|
// it's ok.
|
|
nic.testObject.protocol = 10
|
|
nic.testObject.srcAddr = remoteIPv6Addr
|
|
nic.testObject.dstAddr = localIPv6Addr
|
|
nic.testObject.contents = view[dataOffset:]
|
|
nic.testObject.transErr = c.transErr
|
|
|
|
// Set ICMPv6 checksum.
|
|
icmp.SetChecksum(header.ICMPv6Checksum(icmp, outerSrcAddr, localIPv6Addr, buffer.VectorisedView{}))
|
|
|
|
addressableEndpoint, ok := ep.(stack.AddressableEndpoint)
|
|
if !ok {
|
|
t.Fatal("expected IPv6 network endpoint to implement stack.AddressableEndpoint")
|
|
}
|
|
addr := localIPv6Addr.WithPrefix()
|
|
if ep, err := addressableEndpoint.AddAndAcquirePermanentAddress(addr, stack.CanBePrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */); err != nil {
|
|
t.Fatalf("addressableEndpoint.AddAndAcquirePermanentAddress(%s, CanBePrimaryEndpoint, AddressConfigStatic, false): %s", addr, err)
|
|
} else {
|
|
ep.DecRef()
|
|
}
|
|
pkt := truncatedPacket(view, c.trunc, header.IPv6MinimumSize)
|
|
ep.HandlePacket(pkt)
|
|
if want := c.expectedCount; nic.testObject.controlCalls != want {
|
|
t.Fatalf("Bad number of control calls for %q case: got %v, want %v", c.name, nic.testObject.controlCalls, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// truncatedPacket returns a PacketBuffer based on a truncated view. If view,
|
|
// after truncation, is large enough to hold a network header, it makes part of
|
|
// view the packet's NetworkHeader and the rest its Data. Otherwise all of view
|
|
// becomes Data.
|
|
func truncatedPacket(view buffer.View, trunc, netHdrLen int) *stack.PacketBuffer {
|
|
v := view[:len(view)-trunc]
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: v.ToVectorisedView(),
|
|
})
|
|
return pkt
|
|
}
|
|
|
|
func TestWriteHeaderIncludedPacket(t *testing.T) {
|
|
const (
|
|
nicID = 1
|
|
transportProto = 5
|
|
|
|
dataLen = 4
|
|
)
|
|
|
|
dataBuf := [dataLen]byte{1, 2, 3, 4}
|
|
data := dataBuf[:]
|
|
|
|
ipv4Options := header.IPv4OptionsSerializer{
|
|
&header.IPv4SerializableListEndOption{},
|
|
&header.IPv4SerializableNOPOption{},
|
|
&header.IPv4SerializableListEndOption{},
|
|
&header.IPv4SerializableNOPOption{},
|
|
}
|
|
|
|
expectOptions := header.IPv4Options{
|
|
byte(header.IPv4OptionListEndType),
|
|
byte(header.IPv4OptionNOPType),
|
|
byte(header.IPv4OptionListEndType),
|
|
byte(header.IPv4OptionNOPType),
|
|
}
|
|
|
|
ipv6FragmentExtHdrBuf := [header.IPv6FragmentExtHdrLength]byte{transportProto, 0, 62, 4, 1, 2, 3, 4}
|
|
ipv6FragmentExtHdr := ipv6FragmentExtHdrBuf[:]
|
|
|
|
var ipv6PayloadWithExtHdrBuf [dataLen + header.IPv6FragmentExtHdrLength]byte
|
|
ipv6PayloadWithExtHdr := ipv6PayloadWithExtHdrBuf[:]
|
|
if n := copy(ipv6PayloadWithExtHdr, ipv6FragmentExtHdr); n != len(ipv6FragmentExtHdr) {
|
|
t.Fatalf("copied %d bytes, expected %d bytes", n, len(ipv6FragmentExtHdr))
|
|
}
|
|
if n := copy(ipv6PayloadWithExtHdr[header.IPv6FragmentExtHdrLength:], data); n != len(data) {
|
|
t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
protoFactory stack.NetworkProtocolFactory
|
|
protoNum tcpip.NetworkProtocolNumber
|
|
nicAddr tcpip.Address
|
|
remoteAddr tcpip.Address
|
|
pktGen func(*testing.T, tcpip.Address) buffer.VectorisedView
|
|
checker func(*testing.T, *stack.PacketBuffer, tcpip.Address)
|
|
expectedErr tcpip.Error
|
|
}{
|
|
{
|
|
name: "IPv4",
|
|
protoFactory: ipv4.NewProtocol,
|
|
protoNum: ipv4.ProtocolNumber,
|
|
nicAddr: localIPv4Addr,
|
|
remoteAddr: remoteIPv4Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
totalLen := header.IPv4MinimumSize + len(data)
|
|
hdr := buffer.NewPrependable(totalLen)
|
|
if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
|
|
t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
|
|
}
|
|
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
|
|
ip.Encode(&header.IPv4Fields{
|
|
Protocol: transportProto,
|
|
TTL: ipv4.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
})
|
|
return hdr.View().ToVectorisedView()
|
|
},
|
|
checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
|
|
if src == header.IPv4Any {
|
|
src = localIPv4Addr
|
|
}
|
|
|
|
netHdr := pkt.NetworkHeader()
|
|
|
|
if len(netHdr.View()) != header.IPv4MinimumSize {
|
|
t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), header.IPv4MinimumSize)
|
|
}
|
|
|
|
checker.IPv4(t, stack.PayloadSince(netHdr),
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(remoteIPv4Addr),
|
|
checker.IPv4HeaderLength(header.IPv4MinimumSize),
|
|
checker.IPFullLength(uint16(header.IPv4MinimumSize+len(data))),
|
|
checker.IPPayload(data),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv4 with IHL too small",
|
|
protoFactory: ipv4.NewProtocol,
|
|
protoNum: ipv4.ProtocolNumber,
|
|
nicAddr: localIPv4Addr,
|
|
remoteAddr: remoteIPv4Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
totalLen := header.IPv4MinimumSize + len(data)
|
|
hdr := buffer.NewPrependable(totalLen)
|
|
if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
|
|
t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
|
|
}
|
|
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
|
|
ip.Encode(&header.IPv4Fields{
|
|
Protocol: transportProto,
|
|
TTL: ipv4.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
})
|
|
ip.SetHeaderLength(header.IPv4MinimumSize - 1)
|
|
return hdr.View().ToVectorisedView()
|
|
},
|
|
expectedErr: &tcpip.ErrMalformedHeader{},
|
|
},
|
|
{
|
|
name: "IPv4 too small",
|
|
protoFactory: ipv4.NewProtocol,
|
|
protoNum: ipv4.ProtocolNumber,
|
|
nicAddr: localIPv4Addr,
|
|
remoteAddr: remoteIPv4Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
ip := header.IPv4(make([]byte, header.IPv4MinimumSize))
|
|
ip.Encode(&header.IPv4Fields{
|
|
Protocol: transportProto,
|
|
TTL: ipv4.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
})
|
|
return buffer.View(ip[:len(ip)-1]).ToVectorisedView()
|
|
},
|
|
expectedErr: &tcpip.ErrMalformedHeader{},
|
|
},
|
|
{
|
|
name: "IPv4 minimum size",
|
|
protoFactory: ipv4.NewProtocol,
|
|
protoNum: ipv4.ProtocolNumber,
|
|
nicAddr: localIPv4Addr,
|
|
remoteAddr: remoteIPv4Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
ip := header.IPv4(make([]byte, header.IPv4MinimumSize))
|
|
ip.Encode(&header.IPv4Fields{
|
|
Protocol: transportProto,
|
|
TTL: ipv4.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
})
|
|
return buffer.View(ip).ToVectorisedView()
|
|
},
|
|
checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
|
|
if src == header.IPv4Any {
|
|
src = localIPv4Addr
|
|
}
|
|
|
|
netHdr := pkt.NetworkHeader()
|
|
|
|
if len(netHdr.View()) != header.IPv4MinimumSize {
|
|
t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), header.IPv4MinimumSize)
|
|
}
|
|
|
|
checker.IPv4(t, stack.PayloadSince(netHdr),
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(remoteIPv4Addr),
|
|
checker.IPv4HeaderLength(header.IPv4MinimumSize),
|
|
checker.IPFullLength(header.IPv4MinimumSize),
|
|
checker.IPPayload(nil),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv4 with options",
|
|
protoFactory: ipv4.NewProtocol,
|
|
protoNum: ipv4.ProtocolNumber,
|
|
nicAddr: localIPv4Addr,
|
|
remoteAddr: remoteIPv4Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
ipHdrLen := int(header.IPv4MinimumSize + ipv4Options.Length())
|
|
totalLen := ipHdrLen + len(data)
|
|
hdr := buffer.NewPrependable(totalLen)
|
|
if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
|
|
t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
|
|
}
|
|
ip := header.IPv4(hdr.Prepend(ipHdrLen))
|
|
ip.Encode(&header.IPv4Fields{
|
|
Protocol: transportProto,
|
|
TTL: ipv4.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
Options: ipv4Options,
|
|
})
|
|
return hdr.View().ToVectorisedView()
|
|
},
|
|
checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
|
|
if src == header.IPv4Any {
|
|
src = localIPv4Addr
|
|
}
|
|
|
|
netHdr := pkt.NetworkHeader()
|
|
|
|
hdrLen := int(header.IPv4MinimumSize + ipv4Options.Length())
|
|
if len(netHdr.View()) != hdrLen {
|
|
t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), hdrLen)
|
|
}
|
|
|
|
checker.IPv4(t, stack.PayloadSince(netHdr),
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(remoteIPv4Addr),
|
|
checker.IPv4HeaderLength(hdrLen),
|
|
checker.IPFullLength(uint16(hdrLen+len(data))),
|
|
checker.IPv4Options(expectOptions),
|
|
checker.IPPayload(data),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv4 with options and data across views",
|
|
protoFactory: ipv4.NewProtocol,
|
|
protoNum: ipv4.ProtocolNumber,
|
|
nicAddr: localIPv4Addr,
|
|
remoteAddr: remoteIPv4Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
ip := header.IPv4(make([]byte, header.IPv4MinimumSize+ipv4Options.Length()))
|
|
ip.Encode(&header.IPv4Fields{
|
|
Protocol: transportProto,
|
|
TTL: ipv4.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
Options: ipv4Options,
|
|
})
|
|
vv := buffer.View(ip).ToVectorisedView()
|
|
vv.AppendView(data)
|
|
return vv
|
|
},
|
|
checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
|
|
if src == header.IPv4Any {
|
|
src = localIPv4Addr
|
|
}
|
|
|
|
netHdr := pkt.NetworkHeader()
|
|
|
|
hdrLen := int(header.IPv4MinimumSize + ipv4Options.Length())
|
|
if len(netHdr.View()) != hdrLen {
|
|
t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), hdrLen)
|
|
}
|
|
|
|
checker.IPv4(t, stack.PayloadSince(netHdr),
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(remoteIPv4Addr),
|
|
checker.IPv4HeaderLength(hdrLen),
|
|
checker.IPFullLength(uint16(hdrLen+len(data))),
|
|
checker.IPv4Options(expectOptions),
|
|
checker.IPPayload(data),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6",
|
|
protoFactory: ipv6.NewProtocol,
|
|
protoNum: ipv6.ProtocolNumber,
|
|
nicAddr: localIPv6Addr,
|
|
remoteAddr: remoteIPv6Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
totalLen := header.IPv6MinimumSize + len(data)
|
|
hdr := buffer.NewPrependable(totalLen)
|
|
if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
|
|
t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
|
|
}
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
TransportProtocol: transportProto,
|
|
HopLimit: ipv6.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
})
|
|
return hdr.View().ToVectorisedView()
|
|
},
|
|
checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
|
|
if src == header.IPv6Any {
|
|
src = localIPv6Addr
|
|
}
|
|
|
|
netHdr := pkt.NetworkHeader()
|
|
|
|
if len(netHdr.View()) != header.IPv6MinimumSize {
|
|
t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), header.IPv6MinimumSize)
|
|
}
|
|
|
|
checker.IPv6(t, stack.PayloadSince(netHdr),
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(remoteIPv6Addr),
|
|
checker.IPFullLength(uint16(header.IPv6MinimumSize+len(data))),
|
|
checker.IPPayload(data),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6 with extension header",
|
|
protoFactory: ipv6.NewProtocol,
|
|
protoNum: ipv6.ProtocolNumber,
|
|
nicAddr: localIPv6Addr,
|
|
remoteAddr: remoteIPv6Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
totalLen := header.IPv6MinimumSize + len(ipv6FragmentExtHdr) + len(data)
|
|
hdr := buffer.NewPrependable(totalLen)
|
|
if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
|
|
t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
|
|
}
|
|
if n := copy(hdr.Prepend(len(ipv6FragmentExtHdr)), ipv6FragmentExtHdr); n != len(ipv6FragmentExtHdr) {
|
|
t.Fatalf("copied %d bytes, expected %d bytes", n, len(ipv6FragmentExtHdr))
|
|
}
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
// NB: we're lying about transport protocol here to verify the raw
|
|
// fragment header bytes.
|
|
TransportProtocol: tcpip.TransportProtocolNumber(header.IPv6FragmentExtHdrIdentifier),
|
|
HopLimit: ipv6.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
})
|
|
return hdr.View().ToVectorisedView()
|
|
},
|
|
checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
|
|
if src == header.IPv6Any {
|
|
src = localIPv6Addr
|
|
}
|
|
|
|
netHdr := pkt.NetworkHeader()
|
|
|
|
if want := header.IPv6MinimumSize + len(ipv6FragmentExtHdr); len(netHdr.View()) != want {
|
|
t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), want)
|
|
}
|
|
|
|
checker.IPv6(t, stack.PayloadSince(netHdr),
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(remoteIPv6Addr),
|
|
checker.IPFullLength(uint16(header.IPv6MinimumSize+len(ipv6PayloadWithExtHdr))),
|
|
checker.IPPayload(ipv6PayloadWithExtHdr),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6 minimum size",
|
|
protoFactory: ipv6.NewProtocol,
|
|
protoNum: ipv6.ProtocolNumber,
|
|
nicAddr: localIPv6Addr,
|
|
remoteAddr: remoteIPv6Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
ip := header.IPv6(make([]byte, header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
TransportProtocol: transportProto,
|
|
HopLimit: ipv6.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
})
|
|
return buffer.View(ip).ToVectorisedView()
|
|
},
|
|
checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
|
|
if src == header.IPv6Any {
|
|
src = localIPv6Addr
|
|
}
|
|
|
|
netHdr := pkt.NetworkHeader()
|
|
|
|
if len(netHdr.View()) != header.IPv6MinimumSize {
|
|
t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), header.IPv6MinimumSize)
|
|
}
|
|
|
|
checker.IPv6(t, stack.PayloadSince(netHdr),
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(remoteIPv6Addr),
|
|
checker.IPFullLength(header.IPv6MinimumSize),
|
|
checker.IPPayload(nil),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6 too small",
|
|
protoFactory: ipv6.NewProtocol,
|
|
protoNum: ipv6.ProtocolNumber,
|
|
nicAddr: localIPv6Addr,
|
|
remoteAddr: remoteIPv6Addr,
|
|
pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
|
|
ip := header.IPv6(make([]byte, header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
TransportProtocol: transportProto,
|
|
HopLimit: ipv6.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: header.IPv4Any,
|
|
})
|
|
return buffer.View(ip[:len(ip)-1]).ToVectorisedView()
|
|
},
|
|
expectedErr: &tcpip.ErrMalformedHeader{},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
subTests := []struct {
|
|
name string
|
|
srcAddr tcpip.Address
|
|
}{
|
|
{
|
|
name: "unspecified source",
|
|
srcAddr: tcpip.Address(strings.Repeat("\x00", len(test.nicAddr))),
|
|
},
|
|
{
|
|
name: "random source",
|
|
srcAddr: tcpip.Address(strings.Repeat("\xab", len(test.nicAddr))),
|
|
},
|
|
}
|
|
|
|
for _, subTest := range subTests {
|
|
t.Run(subTest.name, func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{test.protoFactory},
|
|
})
|
|
e := channel.New(1, header.IPv6MinimumMTU, "")
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
|
|
}
|
|
if err := s.AddAddress(nicID, test.protoNum, test.nicAddr); err != nil {
|
|
t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, test.protoNum, test.nicAddr, err)
|
|
}
|
|
|
|
s.SetRouteTable([]tcpip.Route{{Destination: test.remoteAddr.WithPrefix().Subnet(), NIC: nicID}})
|
|
|
|
r, err := s.FindRoute(nicID, test.nicAddr, test.remoteAddr, test.protoNum, false /* multicastLoop */)
|
|
if err != nil {
|
|
t.Fatalf("s.FindRoute(%d, %s, %s, %d, false): %s", nicID, test.remoteAddr, test.nicAddr, test.protoNum, err)
|
|
}
|
|
defer r.Release()
|
|
|
|
{
|
|
err := r.WriteHeaderIncludedPacket(stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: test.pktGen(t, subTest.srcAddr),
|
|
}))
|
|
if diff := cmp.Diff(test.expectedErr, err); diff != "" {
|
|
t.Fatalf("unexpected error from r.WriteHeaderIncludedPacket(_), (-want, +got):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
if test.expectedErr != nil {
|
|
return
|
|
}
|
|
|
|
pkt, ok := e.Read()
|
|
if !ok {
|
|
t.Fatal("expected a packet to be written")
|
|
}
|
|
test.checker(t, pkt.Pkt, subTest.srcAddr)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that the included data in an ICMP error packet conforms to the
|
|
// requirements of RFC 972, RFC 4443 section 2.4 and RFC 1812 Section 4.3.2.3
|
|
func TestICMPInclusionSize(t *testing.T) {
|
|
const (
|
|
replyHeaderLength4 = header.IPv4MinimumSize + header.IPv4MinimumSize + header.ICMPv4MinimumSize
|
|
replyHeaderLength6 = header.IPv6MinimumSize + header.IPv6MinimumSize + header.ICMPv6MinimumSize
|
|
targetSize4 = header.IPv4MinimumProcessableDatagramSize
|
|
targetSize6 = header.IPv6MinimumMTU
|
|
// A protocol number that will cause an error response.
|
|
reservedProtocol = 254
|
|
)
|
|
|
|
// IPv4 function to create a IP packet and send it to the stack.
|
|
// The packet should generate an error response. We can do that by using an
|
|
// unknown transport protocol (254).
|
|
rxIPv4Bad := func(e *channel.Endpoint, src tcpip.Address, payload []byte) buffer.View {
|
|
totalLen := header.IPv4MinimumSize + len(payload)
|
|
hdr := buffer.NewPrependable(header.IPv4MinimumSize)
|
|
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
|
|
ip.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(totalLen),
|
|
Protocol: reservedProtocol,
|
|
TTL: ipv4.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: localIPv4Addr,
|
|
})
|
|
ip.SetChecksum(^ip.CalculateChecksum())
|
|
vv := hdr.View().ToVectorisedView()
|
|
vv.AppendView(buffer.View(payload))
|
|
// Take a copy before InjectInbound takes ownership of vv
|
|
// as vv may be changed during the call.
|
|
v := vv.ToView()
|
|
e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: vv,
|
|
}))
|
|
return v
|
|
}
|
|
|
|
// IPv6 function to create a packet and send it to the stack.
|
|
// The packet should be errant in a way that causes the stack to send an
|
|
// ICMP error response and have enough data to allow the testing of the
|
|
// inclusion of the errant packet. Use `unknown next header' to generate
|
|
// the error.
|
|
rxIPv6Bad := func(e *channel.Endpoint, src tcpip.Address, payload []byte) buffer.View {
|
|
hdr := buffer.NewPrependable(header.IPv6MinimumSize)
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(len(payload)),
|
|
TransportProtocol: reservedProtocol,
|
|
HopLimit: ipv6.DefaultTTL,
|
|
SrcAddr: src,
|
|
DstAddr: localIPv6Addr,
|
|
})
|
|
vv := hdr.View().ToVectorisedView()
|
|
vv.AppendView(buffer.View(payload))
|
|
// Take a copy before InjectInbound takes ownership of vv
|
|
// as vv may be changed during the call.
|
|
v := vv.ToView()
|
|
|
|
e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Data: vv,
|
|
}))
|
|
return v
|
|
}
|
|
|
|
v4Checker := func(t *testing.T, pkt *stack.PacketBuffer, payload buffer.View) {
|
|
// We already know the entire packet is the right size so we can use its
|
|
// length to calculate the right payload size to check.
|
|
expectedPayloadLength := pkt.Size() - header.IPv4MinimumSize - header.ICMPv4MinimumSize
|
|
checker.IPv4(t, stack.PayloadSince(pkt.NetworkHeader()),
|
|
checker.SrcAddr(localIPv4Addr),
|
|
checker.DstAddr(remoteIPv4Addr),
|
|
checker.IPv4HeaderLength(header.IPv4MinimumSize),
|
|
checker.IPFullLength(uint16(header.IPv4MinimumSize+header.ICMPv4MinimumSize+expectedPayloadLength)),
|
|
checker.ICMPv4(
|
|
checker.ICMPv4Checksum(),
|
|
checker.ICMPv4Type(header.ICMPv4DstUnreachable),
|
|
checker.ICMPv4Code(header.ICMPv4ProtoUnreachable),
|
|
checker.ICMPv4Payload(payload[:expectedPayloadLength]),
|
|
),
|
|
)
|
|
}
|
|
|
|
v6Checker := func(t *testing.T, pkt *stack.PacketBuffer, payload buffer.View) {
|
|
// We already know the entire packet is the right size so we can use its
|
|
// length to calculate the right payload size to check.
|
|
expectedPayloadLength := pkt.Size() - header.IPv6MinimumSize - header.ICMPv6MinimumSize
|
|
checker.IPv6(t, stack.PayloadSince(pkt.NetworkHeader()),
|
|
checker.SrcAddr(localIPv6Addr),
|
|
checker.DstAddr(remoteIPv6Addr),
|
|
checker.IPFullLength(uint16(header.IPv6MinimumSize+header.ICMPv6MinimumSize+expectedPayloadLength)),
|
|
checker.ICMPv6(
|
|
checker.ICMPv6Type(header.ICMPv6ParamProblem),
|
|
checker.ICMPv6Code(header.ICMPv6UnknownHeader),
|
|
checker.ICMPv6Payload(payload[:expectedPayloadLength]),
|
|
),
|
|
)
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
srcAddress tcpip.Address
|
|
injector func(*channel.Endpoint, tcpip.Address, []byte) buffer.View
|
|
checker func(*testing.T, *stack.PacketBuffer, buffer.View)
|
|
payloadLength int // Not including IP header.
|
|
linkMTU uint32 // Largest IP packet that the link can send as payload.
|
|
replyLength int // Total size of IP/ICMP packet expected back.
|
|
}{
|
|
{
|
|
name: "IPv4 exact match",
|
|
srcAddress: remoteIPv4Addr,
|
|
injector: rxIPv4Bad,
|
|
checker: v4Checker,
|
|
payloadLength: targetSize4 - replyHeaderLength4,
|
|
linkMTU: targetSize4,
|
|
replyLength: targetSize4,
|
|
},
|
|
{
|
|
name: "IPv4 larger MTU",
|
|
srcAddress: remoteIPv4Addr,
|
|
injector: rxIPv4Bad,
|
|
checker: v4Checker,
|
|
payloadLength: targetSize4,
|
|
linkMTU: targetSize4 + 1000,
|
|
replyLength: targetSize4,
|
|
},
|
|
{
|
|
name: "IPv4 smaller MTU",
|
|
srcAddress: remoteIPv4Addr,
|
|
injector: rxIPv4Bad,
|
|
checker: v4Checker,
|
|
payloadLength: targetSize4,
|
|
linkMTU: targetSize4 - 50,
|
|
replyLength: targetSize4 - 50,
|
|
},
|
|
{
|
|
name: "IPv4 payload exceeds",
|
|
srcAddress: remoteIPv4Addr,
|
|
injector: rxIPv4Bad,
|
|
checker: v4Checker,
|
|
payloadLength: targetSize4 + 10,
|
|
linkMTU: targetSize4,
|
|
replyLength: targetSize4,
|
|
},
|
|
{
|
|
name: "IPv4 1 byte less",
|
|
srcAddress: remoteIPv4Addr,
|
|
injector: rxIPv4Bad,
|
|
checker: v4Checker,
|
|
payloadLength: targetSize4 - replyHeaderLength4 - 1,
|
|
linkMTU: targetSize4,
|
|
replyLength: targetSize4 - 1,
|
|
},
|
|
{
|
|
name: "IPv4 No payload",
|
|
srcAddress: remoteIPv4Addr,
|
|
injector: rxIPv4Bad,
|
|
checker: v4Checker,
|
|
payloadLength: 0,
|
|
linkMTU: targetSize4,
|
|
replyLength: replyHeaderLength4,
|
|
},
|
|
{
|
|
name: "IPv6 exact match",
|
|
srcAddress: remoteIPv6Addr,
|
|
injector: rxIPv6Bad,
|
|
checker: v6Checker,
|
|
payloadLength: targetSize6 - replyHeaderLength6,
|
|
linkMTU: targetSize6,
|
|
replyLength: targetSize6,
|
|
},
|
|
{
|
|
name: "IPv6 larger MTU",
|
|
srcAddress: remoteIPv6Addr,
|
|
injector: rxIPv6Bad,
|
|
checker: v6Checker,
|
|
payloadLength: targetSize6,
|
|
linkMTU: targetSize6 + 400,
|
|
replyLength: targetSize6,
|
|
},
|
|
// NB. No "smaller MTU" test here as less than 1280 is not permitted
|
|
// in IPv6.
|
|
{
|
|
name: "IPv6 payload exceeds",
|
|
srcAddress: remoteIPv6Addr,
|
|
injector: rxIPv6Bad,
|
|
checker: v6Checker,
|
|
payloadLength: targetSize6,
|
|
linkMTU: targetSize6,
|
|
replyLength: targetSize6,
|
|
},
|
|
{
|
|
name: "IPv6 1 byte less",
|
|
srcAddress: remoteIPv6Addr,
|
|
injector: rxIPv6Bad,
|
|
checker: v6Checker,
|
|
payloadLength: targetSize6 - replyHeaderLength6 - 1,
|
|
linkMTU: targetSize6,
|
|
replyLength: targetSize6 - 1,
|
|
},
|
|
{
|
|
name: "IPv6 no payload",
|
|
srcAddress: remoteIPv6Addr,
|
|
injector: rxIPv6Bad,
|
|
checker: v6Checker,
|
|
payloadLength: 0,
|
|
linkMTU: targetSize6,
|
|
replyLength: replyHeaderLength6,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s, e := buildDummyStackWithLinkEndpoint(t, test.linkMTU)
|
|
// Allocate and initialize the payload view.
|
|
payload := buffer.NewView(test.payloadLength)
|
|
for i := 0; i < len(payload); i++ {
|
|
payload[i] = uint8(i)
|
|
}
|
|
// Default routes for IPv4&6 so ICMP can find a route to the remote
|
|
// node when attempting to send the ICMP error Reply.
|
|
s.SetRouteTable([]tcpip.Route{
|
|
{
|
|
Destination: header.IPv4EmptySubnet,
|
|
NIC: nicID,
|
|
},
|
|
{
|
|
Destination: header.IPv6EmptySubnet,
|
|
NIC: nicID,
|
|
},
|
|
})
|
|
v := test.injector(e, test.srcAddress, payload)
|
|
pkt, ok := e.Read()
|
|
if !ok {
|
|
t.Fatal("expected a packet to be written")
|
|
}
|
|
if got, want := pkt.Pkt.Size(), test.replyLength; got != want {
|
|
t.Fatalf("got %d bytes of icmp error packet, want %d", got, want)
|
|
}
|
|
test.checker(t, pkt.Pkt, v)
|
|
})
|
|
}
|
|
}
|