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
705 lines
22 KiB
Go
705 lines
22 KiB
Go
// 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 forward_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"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/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
|
|
"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/tests/utils"
|
|
"gvisor.dev/gvisor/pkg/tcpip/testutil"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
)
|
|
|
|
const ttl = 64
|
|
|
|
func rxICMPv4EchoRequest(e *channel.Endpoint, src, dst tcpip.Address) {
|
|
utils.RxICMPv4EchoRequest(e, src, dst, ttl)
|
|
}
|
|
|
|
func rxICMPv6EchoRequest(e *channel.Endpoint, src, dst tcpip.Address) {
|
|
utils.RxICMPv6EchoRequest(e, src, dst, ttl)
|
|
}
|
|
|
|
func forwardedICMPv4EchoRequestChecker(t *testing.T, v *buffer.View, src, dst tcpip.Address) {
|
|
checker.IPv4(t, v,
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(dst),
|
|
checker.TTL(ttl-1),
|
|
checker.ICMPv4(
|
|
checker.ICMPv4Type(header.ICMPv4Echo)))
|
|
}
|
|
|
|
func forwardedICMPv6EchoRequestChecker(t *testing.T, v *buffer.View, src, dst tcpip.Address) {
|
|
checker.IPv6(t, v,
|
|
checker.SrcAddr(src),
|
|
checker.DstAddr(dst),
|
|
checker.TTL(ttl-1),
|
|
checker.ICMPv6(
|
|
checker.ICMPv6Type(header.ICMPv6EchoRequest)))
|
|
}
|
|
|
|
func TestForwarding(t *testing.T) {
|
|
const listenPort = 8080
|
|
|
|
type endpointAndAddresses struct {
|
|
serverEP tcpip.Endpoint
|
|
serverAddr tcpip.Address
|
|
serverReadableCH chan struct{}
|
|
|
|
clientEP tcpip.Endpoint
|
|
clientAddr tcpip.Address
|
|
clientReadableCH chan struct{}
|
|
}
|
|
|
|
newEP := func(t *testing.T, s *stack.Stack, transProto tcpip.TransportProtocolNumber, netProto tcpip.NetworkProtocolNumber) (tcpip.Endpoint, chan struct{}) {
|
|
t.Helper()
|
|
var wq waiter.Queue
|
|
we, ch := waiter.NewChannelEntry(waiter.ReadableEvents)
|
|
wq.EventRegister(&we)
|
|
ep, err := s.NewEndpoint(transProto, netProto, &wq)
|
|
if err != nil {
|
|
t.Fatalf("s.NewEndpoint(%d, %d, _): %s", transProto, netProto, err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
wq.EventUnregister(&we)
|
|
})
|
|
|
|
return ep, ch
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
epAndAddrs func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses
|
|
}{
|
|
{
|
|
name: "IPv4 host1 server with host2 client",
|
|
epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
|
|
ep1, ep1WECH := newEP(t, host1Stack, proto, ipv4.ProtocolNumber)
|
|
ep2, ep2WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber)
|
|
return endpointAndAddresses{
|
|
serverEP: ep1,
|
|
serverAddr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
|
|
serverReadableCH: ep1WECH,
|
|
|
|
clientEP: ep2,
|
|
clientAddr: utils.Host2IPv4Addr.AddressWithPrefix.Address,
|
|
clientReadableCH: ep2WECH,
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6 host2 server with host1 client",
|
|
epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
|
|
ep1, ep1WECH := newEP(t, host2Stack, proto, ipv6.ProtocolNumber)
|
|
ep2, ep2WECH := newEP(t, host1Stack, proto, ipv6.ProtocolNumber)
|
|
return endpointAndAddresses{
|
|
serverEP: ep1,
|
|
serverAddr: utils.Host2IPv6Addr.AddressWithPrefix.Address,
|
|
serverReadableCH: ep1WECH,
|
|
|
|
clientEP: ep2,
|
|
clientAddr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
|
|
clientReadableCH: ep2WECH,
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "IPv4 host2 server with routerNIC1 client",
|
|
epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
|
|
ep1, ep1WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber)
|
|
ep2, ep2WECH := newEP(t, routerStack, proto, ipv4.ProtocolNumber)
|
|
return endpointAndAddresses{
|
|
serverEP: ep1,
|
|
serverAddr: utils.Host2IPv4Addr.AddressWithPrefix.Address,
|
|
serverReadableCH: ep1WECH,
|
|
|
|
clientEP: ep2,
|
|
clientAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
|
|
clientReadableCH: ep2WECH,
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6 routerNIC2 server with host1 client",
|
|
epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
|
|
ep1, ep1WECH := newEP(t, routerStack, proto, ipv6.ProtocolNumber)
|
|
ep2, ep2WECH := newEP(t, host1Stack, proto, ipv6.ProtocolNumber)
|
|
return endpointAndAddresses{
|
|
serverEP: ep1,
|
|
serverAddr: utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address,
|
|
serverReadableCH: ep1WECH,
|
|
|
|
clientEP: ep2,
|
|
clientAddr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
|
|
clientReadableCH: ep2WECH,
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
subTests := []struct {
|
|
name string
|
|
proto tcpip.TransportProtocolNumber
|
|
expectedConnectErr tcpip.Error
|
|
setupServer func(t *testing.T, ep tcpip.Endpoint)
|
|
setupServerConn func(t *testing.T, ep tcpip.Endpoint, ch <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{})
|
|
needRemoteAddr bool
|
|
}{
|
|
{
|
|
name: "UDP",
|
|
proto: udp.ProtocolNumber,
|
|
expectedConnectErr: nil,
|
|
setupServerConn: func(t *testing.T, ep tcpip.Endpoint, _ <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{}) {
|
|
t.Helper()
|
|
|
|
if err := ep.Connect(clientAddr); err != nil {
|
|
t.Fatalf("ep.Connect(%#v): %s", clientAddr, err)
|
|
}
|
|
return nil, nil
|
|
},
|
|
needRemoteAddr: true,
|
|
},
|
|
{
|
|
name: "TCP",
|
|
proto: tcp.ProtocolNumber,
|
|
expectedConnectErr: &tcpip.ErrConnectStarted{},
|
|
setupServer: func(t *testing.T, ep tcpip.Endpoint) {
|
|
t.Helper()
|
|
|
|
if err := ep.Listen(1); err != nil {
|
|
t.Fatalf("ep.Listen(1): %s", err)
|
|
}
|
|
},
|
|
setupServerConn: func(t *testing.T, ep tcpip.Endpoint, ch <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{}) {
|
|
t.Helper()
|
|
|
|
var addr tcpip.FullAddress
|
|
for {
|
|
newEP, wq, err := ep.Accept(&addr)
|
|
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
|
|
<-ch
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("ep.Accept(_): %s", err)
|
|
}
|
|
if diff := cmp.Diff(clientAddr, addr, checker.IgnoreCmpPath(
|
|
"NIC",
|
|
)); diff != "" {
|
|
t.Errorf("accepted address mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
we, newCH := waiter.NewChannelEntry(waiter.ReadableEvents)
|
|
wq.EventRegister(&we)
|
|
return newEP, newCH
|
|
}
|
|
},
|
|
needRemoteAddr: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
for _, subTest := range subTests {
|
|
t.Run(subTest.name, func(t *testing.T) {
|
|
stackOpts := stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
|
|
}
|
|
|
|
host1Stack := stack.New(stackOpts)
|
|
defer host1Stack.Destroy()
|
|
routerStack := stack.New(stackOpts)
|
|
defer routerStack.Destroy()
|
|
host2Stack := stack.New(stackOpts)
|
|
defer host2Stack.Destroy()
|
|
utils.SetupRoutedStacks(t, host1Stack, routerStack, host2Stack)
|
|
|
|
epsAndAddrs := test.epAndAddrs(t, host1Stack, routerStack, host2Stack, subTest.proto)
|
|
defer epsAndAddrs.serverEP.Close()
|
|
defer epsAndAddrs.clientEP.Close()
|
|
|
|
serverAddr := tcpip.FullAddress{Addr: epsAndAddrs.serverAddr, Port: listenPort}
|
|
if err := epsAndAddrs.serverEP.Bind(serverAddr); err != nil {
|
|
t.Fatalf("epsAndAddrs.serverEP.Bind(%#v): %s", serverAddr, err)
|
|
}
|
|
clientAddr := tcpip.FullAddress{Addr: epsAndAddrs.clientAddr}
|
|
if err := epsAndAddrs.clientEP.Bind(clientAddr); err != nil {
|
|
t.Fatalf("epsAndAddrs.clientEP.Bind(%#v): %s", clientAddr, err)
|
|
}
|
|
|
|
if subTest.setupServer != nil {
|
|
subTest.setupServer(t, epsAndAddrs.serverEP)
|
|
}
|
|
{
|
|
err := epsAndAddrs.clientEP.Connect(serverAddr)
|
|
if diff := cmp.Diff(subTest.expectedConnectErr, err); diff != "" {
|
|
t.Fatalf("unexpected error from epsAndAddrs.clientEP.Connect(%#v), (-want, +got):\n%s", serverAddr, diff)
|
|
}
|
|
}
|
|
if addr, err := epsAndAddrs.clientEP.GetLocalAddress(); err != nil {
|
|
t.Fatalf("epsAndAddrs.clientEP.GetLocalAddress(): %s", err)
|
|
} else {
|
|
clientAddr = addr
|
|
clientAddr.NIC = 0
|
|
}
|
|
|
|
serverEP := epsAndAddrs.serverEP
|
|
serverCH := epsAndAddrs.serverReadableCH
|
|
if ep, ch := subTest.setupServerConn(t, serverEP, serverCH, clientAddr); ep != nil {
|
|
defer ep.Close()
|
|
serverEP = ep
|
|
serverCH = ch
|
|
}
|
|
|
|
write := func(ep tcpip.Endpoint, data []byte) {
|
|
t.Helper()
|
|
|
|
var r bytes.Reader
|
|
r.Reset(data)
|
|
var wOpts tcpip.WriteOptions
|
|
n, err := ep.Write(&r, wOpts)
|
|
if err != nil {
|
|
t.Fatalf("ep.Write(_, %#v): %s", wOpts, err)
|
|
}
|
|
if want := int64(len(data)); n != want {
|
|
t.Fatalf("got ep.Write(_, %#v) = (%d, _), want = (%d, _)", wOpts, n, want)
|
|
}
|
|
}
|
|
|
|
data := []byte{1, 2, 3, 4}
|
|
write(epsAndAddrs.clientEP, data)
|
|
|
|
read := func(ch chan struct{}, ep tcpip.Endpoint, data []byte, expectedFrom tcpip.FullAddress) {
|
|
t.Helper()
|
|
|
|
var buf bytes.Buffer
|
|
var res tcpip.ReadResult
|
|
for {
|
|
var err tcpip.Error
|
|
opts := tcpip.ReadOptions{NeedRemoteAddr: subTest.needRemoteAddr}
|
|
res, err = ep.Read(&buf, opts)
|
|
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
|
|
<-ch
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("ep.Read(_, %d, %#v): %s", len(data), opts, err)
|
|
}
|
|
break
|
|
}
|
|
|
|
readResult := tcpip.ReadResult{
|
|
Count: len(data),
|
|
Total: len(data),
|
|
}
|
|
if subTest.needRemoteAddr {
|
|
readResult.RemoteAddr = expectedFrom
|
|
}
|
|
if diff := cmp.Diff(readResult, res, checker.IgnoreCmpPath(
|
|
"ControlMessages",
|
|
"RemoteAddr.NIC",
|
|
)); diff != "" {
|
|
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
|
|
}
|
|
if diff := cmp.Diff(buf.Bytes(), data); diff != "" {
|
|
t.Errorf("received data mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
if t.Failed() {
|
|
t.FailNow()
|
|
}
|
|
}
|
|
|
|
read(serverCH, serverEP, data, clientAddr)
|
|
|
|
data = []byte{5, 6, 7, 8, 9, 10, 11, 12}
|
|
write(serverEP, data)
|
|
read(epsAndAddrs.clientReadableCH, epsAndAddrs.clientEP, data, serverAddr)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type fillableLinkEndpoint struct {
|
|
*channel.Endpoint
|
|
full bool
|
|
}
|
|
|
|
func (e *fillableLinkEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
|
if e.full {
|
|
return 0, &tcpip.ErrNoBufferSpace{}
|
|
}
|
|
|
|
return e.Endpoint.WritePackets(pkts)
|
|
}
|
|
|
|
func TestUnicastForwarding(t *testing.T) {
|
|
const (
|
|
nicID1 = 1
|
|
nicID2 = 2
|
|
)
|
|
|
|
var (
|
|
ipv4LinkLocalUnicastAddr = testutil.MustParse4("169.254.0.10")
|
|
ipv6LinkLocalUnicastAddr = testutil.MustParse6("fe80::a")
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
netProto tcpip.NetworkProtocolNumber
|
|
srcAddr, dstAddr tcpip.Address
|
|
rx func(*channel.Endpoint, tcpip.Address, tcpip.Address)
|
|
expectForward bool
|
|
checker func(*testing.T, *buffer.View)
|
|
}{
|
|
{
|
|
name: "IPv4 link-local source",
|
|
netProto: ipv4.ProtocolNumber,
|
|
srcAddr: ipv4LinkLocalUnicastAddr,
|
|
dstAddr: utils.RemoteIPv4Addr,
|
|
rx: rxICMPv4EchoRequest,
|
|
expectForward: false,
|
|
},
|
|
{
|
|
name: "IPv4 link-local destination",
|
|
netProto: ipv4.ProtocolNumber,
|
|
srcAddr: utils.RemoteIPv4Addr,
|
|
dstAddr: ipv4LinkLocalUnicastAddr,
|
|
rx: rxICMPv4EchoRequest,
|
|
expectForward: false,
|
|
},
|
|
{
|
|
name: "IPv4 non-link-local unicast",
|
|
netProto: ipv4.ProtocolNumber,
|
|
srcAddr: utils.RemoteIPv4Addr,
|
|
dstAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
|
|
rx: rxICMPv4EchoRequest,
|
|
expectForward: true,
|
|
checker: func(t *testing.T, v *buffer.View) {
|
|
forwardedICMPv4EchoRequestChecker(t, v, utils.RemoteIPv4Addr, utils.Ipv4Addr2.AddressWithPrefix.Address)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6 link-local source",
|
|
netProto: ipv6.ProtocolNumber,
|
|
srcAddr: ipv6LinkLocalUnicastAddr,
|
|
dstAddr: utils.RemoteIPv6Addr,
|
|
rx: rxICMPv6EchoRequest,
|
|
expectForward: false,
|
|
},
|
|
{
|
|
name: "IPv6 link-local destination",
|
|
netProto: ipv6.ProtocolNumber,
|
|
srcAddr: utils.RemoteIPv6Addr,
|
|
dstAddr: ipv6LinkLocalUnicastAddr,
|
|
rx: rxICMPv6EchoRequest,
|
|
expectForward: false,
|
|
},
|
|
{
|
|
name: "IPv6 non-link-local unicast",
|
|
netProto: ipv6.ProtocolNumber,
|
|
srcAddr: utils.RemoteIPv6Addr,
|
|
dstAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
|
|
rx: rxICMPv6EchoRequest,
|
|
expectForward: true,
|
|
checker: func(t *testing.T, v *buffer.View) {
|
|
forwardedICMPv6EchoRequestChecker(t, v, utils.RemoteIPv6Addr, utils.Ipv6Addr2.AddressWithPrefix.Address)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
for _, full := range []bool{true, false} {
|
|
t.Run(fmt.Sprintf("Full=%t", full), func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
|
|
})
|
|
|
|
e1 := channel.New(1, header.IPv6MinimumMTU, "")
|
|
defer e1.Close()
|
|
if err := s.CreateNIC(nicID1, e1); err != nil {
|
|
t.Fatalf("s.CreateNIC(%d, _): %s", nicID1, err)
|
|
}
|
|
|
|
e2 := fillableLinkEndpoint{Endpoint: channel.New(1, header.IPv6MinimumMTU, ""), full: full}
|
|
defer e2.Close()
|
|
if err := s.CreateNIC(nicID2, &e2); err != nil {
|
|
t.Fatalf("s.CreateNIC(%d, _): %s", nicID2, err)
|
|
}
|
|
|
|
protocolAddrV4 := tcpip.ProtocolAddress{
|
|
Protocol: ipv4.ProtocolNumber,
|
|
AddressWithPrefix: utils.Ipv4Addr,
|
|
}
|
|
if err := s.AddProtocolAddress(nicID2, protocolAddrV4, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddrV4, err)
|
|
}
|
|
protocolAddrV6 := tcpip.ProtocolAddress{
|
|
Protocol: ipv6.ProtocolNumber,
|
|
AddressWithPrefix: utils.Ipv6Addr,
|
|
}
|
|
if err := s.AddProtocolAddress(nicID2, protocolAddrV6, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddrV6, err)
|
|
}
|
|
|
|
if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil {
|
|
t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv4.ProtocolNumber, err)
|
|
}
|
|
if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil {
|
|
t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err)
|
|
}
|
|
|
|
s.SetRouteTable([]tcpip.Route{
|
|
{
|
|
Destination: header.IPv4EmptySubnet,
|
|
NIC: nicID2,
|
|
},
|
|
{
|
|
Destination: header.IPv6EmptySubnet,
|
|
NIC: nicID2,
|
|
},
|
|
})
|
|
|
|
test.rx(e1, test.srcAddr, test.dstAddr)
|
|
|
|
expectForward := test.expectForward && !full
|
|
p := e2.Read()
|
|
if (p != nil) != expectForward {
|
|
t.Fatalf("got e2.Read() = %#v, want = (_ == nil) = %t", p, expectForward)
|
|
}
|
|
|
|
if expectForward {
|
|
payload := stack.PayloadSince(p.NetworkHeader())
|
|
defer payload.Release()
|
|
test.checker(t, payload)
|
|
p.DecRef()
|
|
}
|
|
|
|
checkOutgoingDeviceNoBufferSpaceCounter := func(nicID tcpip.NICID, expectErr bool) {
|
|
t.Helper()
|
|
|
|
expectCounter := uint64(0)
|
|
if expectErr {
|
|
expectCounter = 1
|
|
}
|
|
|
|
netEP, err := s.GetNetworkEndpoint(nicID, test.netProto)
|
|
if err != nil {
|
|
t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, test.netProto, err)
|
|
}
|
|
|
|
stats := netEP.Stats()
|
|
ipStats, ok := stats.(stack.IPNetworkEndpointStats)
|
|
if !ok {
|
|
t.Fatalf("%#v is not a %T", stats, ipStats)
|
|
}
|
|
|
|
if got := ipStats.IPStats().Forwarding.OutgoingDeviceNoBufferSpace.Value(); got != expectCounter {
|
|
t.Errorf("got ipStats.IPStats().Forwarding.OutgoingDeviceNoBufferSpace.Value() = %d, want = %d", got, expectCounter)
|
|
}
|
|
}
|
|
checkOutgoingDeviceNoBufferSpaceCounter(nicID1, test.expectForward && full)
|
|
checkOutgoingDeviceNoBufferSpaceCounter(nicID2, false)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPerInterfaceForwarding(t *testing.T) {
|
|
const (
|
|
nicID1 = 1
|
|
nicID2 = 2
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
srcAddr, dstAddr tcpip.Address
|
|
rx func(*channel.Endpoint, tcpip.Address, tcpip.Address)
|
|
checker func(*testing.T, *buffer.View)
|
|
}{
|
|
{
|
|
name: "IPv4 unicast",
|
|
srcAddr: utils.RemoteIPv4Addr,
|
|
dstAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
|
|
rx: rxICMPv4EchoRequest,
|
|
checker: func(t *testing.T, v *buffer.View) {
|
|
forwardedICMPv4EchoRequestChecker(t, v, utils.RemoteIPv4Addr, utils.Ipv4Addr2.AddressWithPrefix.Address)
|
|
},
|
|
},
|
|
{
|
|
name: "IPv6 unicast",
|
|
srcAddr: utils.RemoteIPv6Addr,
|
|
dstAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
|
|
rx: rxICMPv6EchoRequest,
|
|
checker: func(t *testing.T, v *buffer.View) {
|
|
forwardedICMPv6EchoRequestChecker(t, v, utils.RemoteIPv6Addr, utils.Ipv6Addr2.AddressWithPrefix.Address)
|
|
},
|
|
},
|
|
}
|
|
|
|
netProtos := [...]tcpip.NetworkProtocolNumber{ipv4.ProtocolNumber, ipv6.ProtocolNumber}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{
|
|
// ARP is not used in this test but it is a network protocol that does
|
|
// not support forwarding. We install the protocol to make sure that
|
|
// forwarding information for a NIC is only reported for network
|
|
// protocols that support forwarding.
|
|
arp.NewProtocol,
|
|
|
|
ipv4.NewProtocol,
|
|
ipv6.NewProtocol,
|
|
},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
|
|
})
|
|
|
|
e1 := channel.New(1, header.IPv6MinimumMTU, "")
|
|
defer e1.Close()
|
|
if err := s.CreateNIC(nicID1, e1); err != nil {
|
|
t.Fatalf("s.CreateNIC(%d, _): %s", nicID1, err)
|
|
}
|
|
|
|
e2 := channel.New(1, header.IPv6MinimumMTU, "")
|
|
defer e2.Close()
|
|
if err := s.CreateNIC(nicID2, e2); err != nil {
|
|
t.Fatalf("s.CreateNIC(%d, _): %s", nicID2, err)
|
|
}
|
|
|
|
for _, add := range [...]struct {
|
|
nicID tcpip.NICID
|
|
addr tcpip.ProtocolAddress
|
|
}{
|
|
{
|
|
nicID: nicID1,
|
|
addr: utils.RouterNIC1IPv4Addr,
|
|
},
|
|
{
|
|
nicID: nicID1,
|
|
addr: utils.RouterNIC1IPv6Addr,
|
|
},
|
|
{
|
|
nicID: nicID2,
|
|
addr: utils.RouterNIC2IPv4Addr,
|
|
},
|
|
{
|
|
nicID: nicID2,
|
|
addr: utils.RouterNIC2IPv6Addr,
|
|
},
|
|
} {
|
|
if err := s.AddProtocolAddress(add.nicID, add.addr, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("s.AddProtocolAddress(%d, %+v, {}): %s", add.nicID, add.addr, err)
|
|
}
|
|
}
|
|
|
|
// Only enable forwarding on NIC1 and make sure that only packets arriving
|
|
// on NIC1 are forwarded.
|
|
for _, netProto := range netProtos {
|
|
if _, err := s.SetNICForwarding(nicID1, netProto, true); err != nil {
|
|
t.Fatalf("s.SetNICForwarding(%d, %d, true): %s", nicID1, netProtos, err)
|
|
}
|
|
}
|
|
|
|
nicsInfo := s.NICInfo()
|
|
for _, subTest := range [...]struct {
|
|
nicID tcpip.NICID
|
|
nicEP *channel.Endpoint
|
|
otherNICID tcpip.NICID
|
|
otherNICEP *channel.Endpoint
|
|
expectForwarding bool
|
|
}{
|
|
{
|
|
nicID: nicID1,
|
|
nicEP: e1,
|
|
otherNICID: nicID2,
|
|
otherNICEP: e2,
|
|
expectForwarding: true,
|
|
},
|
|
{
|
|
nicID: nicID2,
|
|
nicEP: e2,
|
|
otherNICID: nicID1,
|
|
otherNICEP: e1,
|
|
expectForwarding: false,
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprintf("Packet arriving at NIC%d", subTest.nicID), func(t *testing.T) {
|
|
nicInfo, ok := nicsInfo[subTest.nicID]
|
|
if !ok {
|
|
t.Errorf("expected NIC info for NIC %d; got = %#v", subTest.nicID, nicsInfo)
|
|
} else {
|
|
forwarding := make(map[tcpip.NetworkProtocolNumber]bool)
|
|
for _, netProto := range netProtos {
|
|
forwarding[netProto] = subTest.expectForwarding
|
|
}
|
|
|
|
if diff := cmp.Diff(forwarding, nicInfo.Forwarding); diff != "" {
|
|
t.Errorf("nicsInfo[%d].Forwarding mismatch (-want +got):\n%s", subTest.nicID, diff)
|
|
}
|
|
}
|
|
|
|
s.SetRouteTable([]tcpip.Route{
|
|
{
|
|
Destination: header.IPv4EmptySubnet,
|
|
NIC: subTest.otherNICID,
|
|
},
|
|
{
|
|
Destination: header.IPv6EmptySubnet,
|
|
NIC: subTest.otherNICID,
|
|
},
|
|
})
|
|
|
|
test.rx(subTest.nicEP, test.srcAddr, test.dstAddr)
|
|
if p := subTest.nicEP.Read(); p != nil {
|
|
t.Errorf("unexpectedly got a response from the interface the packet arrived on: %#v", p)
|
|
p.DecRef()
|
|
}
|
|
p := subTest.otherNICEP.Read()
|
|
if (p != nil) != subTest.expectForwarding {
|
|
t.Errorf("got otherNICEP.Read() = (%#v, %t), want = (_, %t)", p, ok, subTest.expectForwarding)
|
|
}
|
|
if p != nil {
|
|
payload := stack.PayloadSince(p.NetworkHeader())
|
|
defer payload.Release()
|
|
test.checker(t, payload)
|
|
p.DecRef()
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|