blob: baaa741cd644d1eed0b3fbc4e8ef7298489c6611 [file] [log] [blame]
// Copyright 2020 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 integration_test
import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"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/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
)
var _ ipv6.NDPDispatcher = (*ndpDispatcher)(nil)
type ndpDispatcher struct{}
func (*ndpDispatcher) OnDuplicateAddressDetectionStatus(tcpip.NICID, tcpip.Address, bool, *tcpip.Error) {
}
func (*ndpDispatcher) OnDefaultRouterDiscovered(tcpip.NICID, tcpip.Address) bool {
return false
}
func (*ndpDispatcher) OnDefaultRouterInvalidated(tcpip.NICID, tcpip.Address) {}
func (*ndpDispatcher) OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet) bool {
return false
}
func (*ndpDispatcher) OnOnLinkPrefixInvalidated(tcpip.NICID, tcpip.Subnet) {}
func (*ndpDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) bool {
return true
}
func (*ndpDispatcher) OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) {}
func (*ndpDispatcher) OnAutoGenAddressInvalidated(tcpip.NICID, tcpip.AddressWithPrefix) {}
func (*ndpDispatcher) OnRecursiveDNSServerOption(tcpip.NICID, []tcpip.Address, time.Duration) {}
func (*ndpDispatcher) OnDNSSearchListOption(tcpip.NICID, []string, time.Duration) {}
func (*ndpDispatcher) OnDHCPv6Configuration(tcpip.NICID, ipv6.DHCPv6ConfigurationFromNDPRA) {}
// TestInitialLoopbackAddresses tests that the loopback interface does not
// auto-generate a link-local address when it is brought up.
func TestInitialLoopbackAddresses(t *testing.T) {
const nicID = 1
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocolWithOptions(ipv6.Options{
NDPDisp: &ndpDispatcher{},
AutoGenLinkLocal: true,
OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
NICNameFromID: func(nicID tcpip.NICID, nicName string) string {
t.Fatalf("should not attempt to get name for NIC with ID = %d; nicName = %s", nicID, nicName)
return ""
},
},
})},
})
if err := s.CreateNIC(nicID, loopback.New()); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
nicsInfo := s.NICInfo()
if nicInfo, ok := nicsInfo[nicID]; !ok {
t.Fatalf("did not find NIC with ID = %d in s.NICInfo() = %#v", nicID, nicsInfo)
} else if got := len(nicInfo.ProtocolAddresses); got != 0 {
t.Fatalf("got len(nicInfo.ProtocolAddresses) = %d, want = 0; nicInfo.ProtocolAddresses = %#v", got, nicInfo.ProtocolAddresses)
}
}
// TestLoopbackAcceptAllInSubnetUDP tests that a loopback interface considers
// itself bound to all addresses in the subnet of an assigned address and UDP
// traffic is sent/received correctly.
func TestLoopbackAcceptAllInSubnetUDP(t *testing.T) {
const (
nicID = 1
localPort = 80
)
data := []byte{1, 2, 3, 4}
ipv4ProtocolAddress := tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4Addr,
}
ipv4Bytes := []byte(ipv4ProtocolAddress.AddressWithPrefix.Address)
ipv4Bytes[len(ipv4Bytes)-1]++
otherIPv4Address := tcpip.Address(ipv4Bytes)
ipv6ProtocolAddress := tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: ipv6Addr,
}
ipv6Bytes := []byte(ipv6Addr.Address)
ipv6Bytes[len(ipv6Bytes)-1]++
otherIPv6Address := tcpip.Address(ipv6Bytes)
tests := []struct {
name string
addAddress tcpip.ProtocolAddress
bindAddr tcpip.Address
dstAddr tcpip.Address
expectRx bool
}{
{
name: "IPv4 bind to wildcard and send to assigned address",
addAddress: ipv4ProtocolAddress,
dstAddr: ipv4ProtocolAddress.AddressWithPrefix.Address,
expectRx: true,
},
{
name: "IPv4 bind to wildcard and send to other subnet-local address",
addAddress: ipv4ProtocolAddress,
dstAddr: otherIPv4Address,
expectRx: true,
},
{
name: "IPv4 bind to wildcard send to other address",
addAddress: ipv4ProtocolAddress,
dstAddr: remoteIPv4Addr,
expectRx: false,
},
{
name: "IPv4 bind to other subnet-local address and send to assigned address",
addAddress: ipv4ProtocolAddress,
bindAddr: otherIPv4Address,
dstAddr: ipv4ProtocolAddress.AddressWithPrefix.Address,
expectRx: false,
},
{
name: "IPv4 bind and send to other subnet-local address",
addAddress: ipv4ProtocolAddress,
bindAddr: otherIPv4Address,
dstAddr: otherIPv4Address,
expectRx: true,
},
{
name: "IPv4 bind to assigned address and send to other subnet-local address",
addAddress: ipv4ProtocolAddress,
bindAddr: ipv4ProtocolAddress.AddressWithPrefix.Address,
dstAddr: otherIPv4Address,
expectRx: false,
},
{
name: "IPv6 bind and send to assigned address",
addAddress: ipv6ProtocolAddress,
bindAddr: ipv6Addr.Address,
dstAddr: ipv6Addr.Address,
expectRx: true,
},
{
name: "IPv6 bind to wildcard and send to other subnet-local address",
addAddress: ipv6ProtocolAddress,
dstAddr: otherIPv6Address,
expectRx: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
})
if err := s.CreateNIC(nicID, loopback.New()); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
if err := s.AddProtocolAddress(nicID, test.addAddress); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v): %s", nicID, test.addAddress, err)
}
s.SetRouteTable([]tcpip.Route{
tcpip.Route{
Destination: header.IPv4EmptySubnet,
NIC: nicID,
},
tcpip.Route{
Destination: header.IPv6EmptySubnet,
NIC: nicID,
},
})
var wq waiter.Queue
rep, err := s.NewEndpoint(udp.ProtocolNumber, test.addAddress.Protocol, &wq)
if err != nil {
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, test.addAddress.Protocol, err)
}
defer rep.Close()
bindAddr := tcpip.FullAddress{Addr: test.bindAddr, Port: localPort}
if err := rep.Bind(bindAddr); err != nil {
t.Fatalf("rep.Bind(%+v): %s", bindAddr, err)
}
sep, err := s.NewEndpoint(udp.ProtocolNumber, test.addAddress.Protocol, &wq)
if err != nil {
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, test.addAddress.Protocol, err)
}
defer sep.Close()
wopts := tcpip.WriteOptions{
To: &tcpip.FullAddress{
Addr: test.dstAddr,
Port: localPort,
},
}
n, _, err := sep.Write(tcpip.SlicePayload(data), wopts)
if err != nil {
t.Fatalf("sep.Write(_, _): %s", err)
}
if want := int64(len(data)); n != want {
t.Fatalf("got sep.Write(_, _) = (%d, _, nil), want = (%d, _, nil)", n, want)
}
var addr tcpip.FullAddress
if gotPayload, _, err := rep.Read(&addr); test.expectRx {
if err != nil {
t.Fatalf("reep.Read(_): %s", err)
}
if diff := cmp.Diff(buffer.View(data), gotPayload); diff != "" {
t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
}
if addr.Addr != test.addAddress.AddressWithPrefix.Address {
t.Errorf("got addr.Addr = %s, want = %s", addr.Addr, test.addAddress.AddressWithPrefix.Address)
}
} else {
if err != tcpip.ErrWouldBlock {
t.Fatalf("got rep.Read(nil) = (%x, _, %s), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
}
}
})
}
}
// TestLoopbackSubnetLifetimeBoundToAddr tests that the lifetime of an address
// in a loopback interface's associated subnet is bound to the permanently bound
// address.
func TestLoopbackSubnetLifetimeBoundToAddr(t *testing.T) {
const nicID = 1
protoAddr := tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: ipv4Addr,
}
addrBytes := []byte(ipv4Addr.Address)
addrBytes[len(addrBytes)-1]++
otherAddr := tcpip.Address(addrBytes)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
})
if err := s.CreateNIC(nicID, loopback.New()); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
}
if err := s.AddProtocolAddress(nicID, protoAddr); err != nil {
t.Fatalf("s.AddProtocolAddress(%d, %#v): %s", nicID, protoAddr, err)
}
s.SetRouteTable([]tcpip.Route{
tcpip.Route{
Destination: header.IPv4EmptySubnet,
NIC: nicID,
},
})
r, err := s.FindRoute(nicID, otherAddr, remoteIPv4Addr, ipv4.ProtocolNumber, false /* multicastLoop */)
if err != nil {
t.Fatalf("s.FindRoute(%d, %s, %s, %d, false): %s", nicID, otherAddr, remoteIPv4Addr, ipv4.ProtocolNumber, err)
}
defer r.Release()
params := stack.NetworkHeaderParams{
Protocol: 111,
TTL: 64,
TOS: stack.DefaultTOS,
}
data := buffer.View([]byte{1, 2, 3, 4})
if err := r.WritePacket(nil /* gso */, params, stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()),
Data: data.ToVectorisedView(),
})); err != nil {
t.Fatalf("r.WritePacket(nil, %#v, _): %s", params, err)
}
// Removing the address should make the endpoint invalid.
if err := s.RemoveAddress(nicID, protoAddr.AddressWithPrefix.Address); err != nil {
t.Fatalf("s.RemoveAddress(%d, %s): %s", nicID, protoAddr.AddressWithPrefix.Address, err)
}
if err := r.WritePacket(nil /* gso */, params, stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()),
Data: data.ToVectorisedView(),
})); err != tcpip.ErrInvalidEndpointState {
t.Fatalf("got r.WritePacket(nil, %#v, _) = %s, want = %s", params, err, tcpip.ErrInvalidEndpointState)
}
}
// TestLoopbackAcceptAllInSubnetTCP tests that a loopback interface considers
// itself bound to all addresses in the subnet of an assigned address and TCP
// traffic is sent/received correctly.
func TestLoopbackAcceptAllInSubnetTCP(t *testing.T) {
const (
nicID = 1
localPort = 80
)
ipv4ProtocolAddress := tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: ipv4Addr,
}
ipv4ProtocolAddress.AddressWithPrefix.PrefixLen = 8
ipv4Bytes := []byte(ipv4ProtocolAddress.AddressWithPrefix.Address)
ipv4Bytes[len(ipv4Bytes)-1]++
otherIPv4Address := tcpip.Address(ipv4Bytes)
ipv6ProtocolAddress := tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: ipv6Addr,
}
ipv6Bytes := []byte(ipv6Addr.Address)
ipv6Bytes[len(ipv6Bytes)-1]++
otherIPv6Address := tcpip.Address(ipv6Bytes)
tests := []struct {
name string
addAddress tcpip.ProtocolAddress
bindAddr tcpip.Address
dstAddr tcpip.Address
expectAccept bool
}{
{
name: "IPv4 bind to wildcard and send to assigned address",
addAddress: ipv4ProtocolAddress,
dstAddr: ipv4ProtocolAddress.AddressWithPrefix.Address,
expectAccept: true,
},
{
name: "IPv4 bind to wildcard and send to other subnet-local address",
addAddress: ipv4ProtocolAddress,
dstAddr: otherIPv4Address,
expectAccept: true,
},
{
name: "IPv4 bind to wildcard send to other address",
addAddress: ipv4ProtocolAddress,
dstAddr: remoteIPv4Addr,
expectAccept: false,
},
{
name: "IPv4 bind to other subnet-local address and send to assigned address",
addAddress: ipv4ProtocolAddress,
bindAddr: otherIPv4Address,
dstAddr: ipv4ProtocolAddress.AddressWithPrefix.Address,
expectAccept: false,
},
{
name: "IPv4 bind and send to other subnet-local address",
addAddress: ipv4ProtocolAddress,
bindAddr: otherIPv4Address,
dstAddr: otherIPv4Address,
expectAccept: true,
},
{
name: "IPv4 bind to assigned address and send to other subnet-local address",
addAddress: ipv4ProtocolAddress,
bindAddr: ipv4ProtocolAddress.AddressWithPrefix.Address,
dstAddr: otherIPv4Address,
expectAccept: false,
},
{
name: "IPv6 bind and send to assigned address",
addAddress: ipv6ProtocolAddress,
bindAddr: ipv6Addr.Address,
dstAddr: ipv6Addr.Address,
expectAccept: true,
},
{
name: "IPv6 bind to wildcard and send to other subnet-local address",
addAddress: ipv6ProtocolAddress,
dstAddr: otherIPv6Address,
expectAccept: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
})
if err := s.CreateNIC(nicID, loopback.New()); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
if err := s.AddProtocolAddress(nicID, test.addAddress); err != nil {
t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID, test.addAddress, err)
}
s.SetRouteTable([]tcpip.Route{
tcpip.Route{
Destination: header.IPv4EmptySubnet,
NIC: nicID,
},
tcpip.Route{
Destination: header.IPv6EmptySubnet,
NIC: nicID,
},
})
var wq waiter.Queue
we, ch := waiter.NewChannelEntry(nil)
wq.EventRegister(&we, waiter.EventIn)
defer wq.EventUnregister(&we)
listeningEndpoint, err := s.NewEndpoint(tcp.ProtocolNumber, test.addAddress.Protocol, &wq)
if err != nil {
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, test.addAddress.Protocol, err)
}
defer listeningEndpoint.Close()
bindAddr := tcpip.FullAddress{Addr: test.bindAddr, Port: localPort}
if err := listeningEndpoint.Bind(bindAddr); err != nil {
t.Fatalf("listeningEndpoint.Bind(%#v): %s", bindAddr, err)
}
if err := listeningEndpoint.Listen(1); err != nil {
t.Fatalf("listeningEndpoint.Listen(1): %s", err)
}
connectingEndpoint, err := s.NewEndpoint(tcp.ProtocolNumber, test.addAddress.Protocol, &wq)
if err != nil {
t.Fatalf("s.NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, test.addAddress.Protocol, err)
}
defer connectingEndpoint.Close()
connectAddr := tcpip.FullAddress{
Addr: test.dstAddr,
Port: localPort,
}
if err := connectingEndpoint.Connect(connectAddr); err != tcpip.ErrConnectStarted {
t.Fatalf("connectingEndpoint.Connect(%#v): %s", connectAddr, err)
}
if !test.expectAccept {
if _, _, err := listeningEndpoint.Accept(nil); err != tcpip.ErrWouldBlock {
t.Fatalf("got listeningEndpoint.Accept(nil) = %s, want = %s", err, tcpip.ErrWouldBlock)
}
return
}
// Wait for the listening endpoint to be "readable". That is, wait for a
// new connection.
<-ch
var addr tcpip.FullAddress
if _, _, err := listeningEndpoint.Accept(&addr); err != nil {
t.Fatalf("listeningEndpoint.Accept(nil): %s", err)
}
if addr.Addr != test.addAddress.AddressWithPrefix.Address {
t.Errorf("got addr.Addr = %s, want = %s", addr.Addr, test.addAddress.AddressWithPrefix.Address)
}
})
}
}