blob: 657c8e7cf0f82dd9ce4ea7f9a097c326e6b5a29c [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//go:build !build_with_native_toolchain
package netstack
import (
"context"
"testing"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/util"
"fidl/fuchsia/net"
"fidl/fuchsia/net/stack"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
)
func TestValidateIPAddressMask(t *testing.T) {
var (
addr1 = util.Parse("10.11.224.0")
addr2 = util.Parse("10.11.255.0")
)
for _, tc := range []struct {
addr tcpip.Address
prefixLen uint8
want bool
}{
{addr: addr1, prefixLen: 32, want: true},
{addr: addr1, prefixLen: 20, want: true},
{addr: addr1, prefixLen: 19, want: true},
{addr: addr1, prefixLen: 18, want: false},
{addr: addr2, prefixLen: 25, want: true},
{addr: addr2, prefixLen: 24, want: true},
{addr: addr2, prefixLen: 23, want: false},
{addr: header.IPv4Any, prefixLen: 0, want: true},
{addr: header.IPv4Any, prefixLen: 32, want: true},
{addr: header.IPv4Any, prefixLen: 33, want: false},
} {
addr := fidlconv.ToNetIpAddress(tc.addr)
if got := validateSubnet(net.Subnet{Addr: addr, PrefixLen: tc.prefixLen}); got != tc.want {
t.Errorf("got validateSubnet(%v) = %t, want = %t", addr, got, tc.want)
}
}
}
func AssertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Errorf("Received unexpected error:\n%+v", err)
}
}
func TestFuchsiaNetStack(t *testing.T) {
t.Run("Add and Delete Forwarding Entries", func(t *testing.T) {
ns, _ := newNetstack(t, netstackTestOptions{})
ifs := addNoopEndpoint(t, ns, "")
device := uint64(ifs.nicid)
t.Cleanup(ifs.RemoveByUser)
ni := stackImpl{ns: ns}
getTable := func(ns *Netstack) []stack.ForwardingEntry {
ert := ns.GetExtendedRouteTable()
entries := make([]stack.ForwardingEntry, 0, len(ert))
for _, er := range ert {
entry := fidlconv.TCPIPRouteToForwardingEntry(er.Route)
entry.Metric = uint32(er.Metric)
entries = append(entries, entry)
}
return entries
}
// The multicast subnet routes that are implicitly installed on all devices.
ipv4MulticastSubnetRoute := stack.ForwardingEntry{
Subnet: fidlconv.ToNetSubnet(ipv4MulticastSubnet()),
DeviceId: device,
Metric: uint32(defaultInterfaceMetric),
}
ipv6MulticastSubnetRoute := stack.ForwardingEntry{
Subnet: fidlconv.ToNetSubnet(ipv6MulticastSubnet()),
DeviceId: device,
Metric: uint32(defaultInterfaceMetric),
}
nonexistentSubnet := net.Subnet{
Addr: fidlconv.ToNetIpAddress(util.Parse("170.11.224.0")),
PrefixLen: 19,
}
badMaskSubnet := net.Subnet{
Addr: fidlconv.ToNetIpAddress(util.Parse("192.168.17.1")),
PrefixLen: 19,
}
localSubnet := net.Subnet{
Addr: fidlconv.ToNetIpAddress(util.Parse("192.168.32.0")),
PrefixLen: 19,
}
defaultSubnet := net.Subnet{
Addr: fidlconv.ToNetIpAddress(header.IPv4Any),
PrefixLen: 0,
}
invalidRoute := stack.ForwardingEntry{
Subnet: localSubnet,
DeviceId: device + 1,
}
localRoute := stack.ForwardingEntry{
Subnet: localSubnet,
DeviceId: device,
}
wantLocalRoute := localRoute
wantLocalRoute.Metric = uint32(defaultInterfaceMetric)
nextHop := fidlconv.ToNetIpAddress(util.Parse("192.168.32.1"))
defaultRoute := stack.ForwardingEntry{
Subnet: defaultSubnet,
NextHop: &nextHop,
DeviceId: device,
}
wantDefaultRoute := defaultRoute
wantDefaultRoute.Metric = uint32(defaultInterfaceMetric)
// Add an invalid entry.
addResult, err := ni.AddForwardingEntry(context.Background(), invalidRoute)
AssertNoError(t, err)
if addResult != stack.StackAddForwardingEntryResultWithErr(stack.ErrorInvalidArgs) {
t.Errorf("got ni.AddForwardingEntry(%#v) = %#v, want = Err(ErrorInvalidArgs)", invalidRoute, addResult)
}
// Add a local subnet route.
addResult, err = ni.AddForwardingEntry(context.Background(), localRoute)
AssertNoError(t, err)
if addResult != stack.StackAddForwardingEntryResultWithResponse(stack.StackAddForwardingEntryResponse{}) {
t.Fatalf("got ni.AddForwardingEntry(%#v) = %#v, want = Response()", localRoute, addResult)
}
table := getTable(ns)
expectedTable := []stack.ForwardingEntry{
wantLocalRoute,
ipv4MulticastSubnetRoute,
ipv6MulticastSubnetRoute,
}
if diff := cmp.Diff(expectedTable, table, cmpopts.IgnoreTypes(struct{}{})); diff != "" {
t.Fatalf("forwarding table mismatch (-want +got):\n%s", diff)
}
// Add the same entry again.
addResult, err = ni.AddForwardingEntry(context.Background(), localRoute)
AssertNoError(t, err)
if addResult != stack.StackAddForwardingEntryResultWithResponse(stack.StackAddForwardingEntryResponse{}) {
t.Errorf("got ni.AddForwardingEntry(%#v) = %#v, want = Response()", localRoute, addResult)
}
table = getTable(ns)
AssertNoError(t, err)
if diff := cmp.Diff(expectedTable, table, cmpopts.IgnoreTypes(struct{}{})); diff != "" {
t.Fatalf("forwarding table mismatch (-want +got):\n%s", diff)
}
// Add default route.
addResult, err = ni.AddForwardingEntry(context.Background(), defaultRoute)
AssertNoError(t, err)
if addResult != stack.StackAddForwardingEntryResultWithResponse(stack.StackAddForwardingEntryResponse{}) {
t.Errorf("got ni.AddForwardingEntry(%#v) = %#v, want = Response()", defaultRoute, addResult)
}
table = getTable(ns)
AssertNoError(t, err)
expectedTable = []stack.ForwardingEntry{
wantLocalRoute,
ipv4MulticastSubnetRoute,
ipv6MulticastSubnetRoute,
wantDefaultRoute,
}
if diff := cmp.Diff(expectedTable, table, cmpopts.IgnoreTypes(struct{}{})); diff != "" {
t.Fatalf("forwarding table mismatch (-want +got):\n%s", diff)
}
// Remove nonexistent subnet.
delResult, err := ni.DelForwardingEntry(context.Background(), stack.ForwardingEntry{Subnet: nonexistentSubnet})
AssertNoError(t, err)
if delResult != stack.StackDelForwardingEntryResultWithErr(stack.ErrorNotFound) {
t.Errorf("got ni.DelForwardingEntry(%#v) = %#v, want = Err(ErrorNotFound)", nonexistentSubnet, delResult)
}
// Remove subnet with bad subnet mask.
delResult, err = ni.DelForwardingEntry(context.Background(), stack.ForwardingEntry{Subnet: badMaskSubnet})
AssertNoError(t, err)
if delResult != stack.StackDelForwardingEntryResultWithErr(stack.ErrorInvalidArgs) {
t.Errorf("got ni.DelForwardingEntry(%#v) = %#v, want = Err(ErrorInvalidArgs)", badMaskSubnet, delResult)
}
// Remove local route.
delResult, err = ni.DelForwardingEntry(context.Background(), stack.ForwardingEntry{Subnet: localSubnet})
AssertNoError(t, err)
if delResult != stack.StackDelForwardingEntryResultWithResponse(stack.StackDelForwardingEntryResponse{}) {
t.Fatalf("got ni.DelForwardingEntry(%#v) = Err(%s), want = Response()", localRoute, delResult.Err)
}
table = getTable(ns)
AssertNoError(t, err)
expectedTable = []stack.ForwardingEntry{
ipv4MulticastSubnetRoute,
ipv6MulticastSubnetRoute,
wantDefaultRoute,
}
if diff := cmp.Diff(expectedTable, table, cmpopts.IgnoreTypes(struct{}{})); diff != "" {
t.Fatalf("forwarding table mismatch (-want +got):\n%s", diff)
}
// Remove default route.
delResult, err = ni.DelForwardingEntry(context.Background(), stack.ForwardingEntry{Subnet: defaultSubnet})
AssertNoError(t, err)
if delResult != stack.StackDelForwardingEntryResultWithResponse(stack.StackDelForwardingEntryResponse{}) {
t.Fatalf("got ni.DelForwardingEntry(%#v) = Err(%s), want = Response()", localRoute, delResult.Err)
}
table = getTable(ns)
AssertNoError(t, err)
expectedTable = []stack.ForwardingEntry{
ipv4MulticastSubnetRoute,
ipv6MulticastSubnetRoute,
}
if diff := cmp.Diff(expectedTable, table, cmpopts.IgnoreTypes(struct{}{})); diff != "" {
t.Fatalf("forwarding table mismatch (-want +got):\n%s", diff)
}
})
t.Run("Enable and Disable IP Forwarding", func(t *testing.T) {
ns, _ := newNetstack(t, netstackTestOptions{})
ifs1 := addNoopEndpoint(t, ns, "")
t.Cleanup(ifs1.RemoveByUser)
ifs2 := addNoopEndpoint(t, ns, "")
t.Cleanup(ifs2.RemoveByUser)
ni := stackImpl{ns: ns}
protocols := [...]struct {
tcpip tcpip.NetworkProtocolNumber
fidl net.IpVersion
}{
{
tcpip: ipv4.ProtocolNumber,
fidl: net.IpVersionV4,
},
{
tcpip: ipv6.ProtocolNumber,
fidl: net.IpVersionV6,
},
}
nicIDs := [...]tcpip.NICID{ifs1.nicid, ifs2.nicid}
checkForwarding := func(nicID tcpip.NICID, netProto tcpip.NetworkProtocolNumber, ipVersion net.IpVersion, want bool) {
t.Helper()
if got, err := ns.stack.NICForwarding(nicID, netProto); err != nil {
t.Errorf("ns.stack.NICForwarding(%d, %d): %s", nicID, netProto, err)
} else if got != want {
t.Errorf("got ns.stack.NICForwarding(%d, %d) = %t, want = %t", nicID, netProto, got, want)
}
}
checkAllForwarding := func(want bool) {
t.Helper()
for _, protocol := range protocols {
for _, nicID := range nicIDs {
checkForwarding(nicID, protocol.tcpip, protocol.fidl, want)
}
}
if t.Failed() {
t.FailNow()
}
}
checkAllForwardingExcept := func(want bool, exceptNICID tcpip.NICID, exceptProtocol net.IpVersion) {
t.Helper()
for _, protocol := range protocols {
for _, nicID := range nicIDs {
want := want
if nicID == exceptNICID && protocol.fidl == exceptProtocol {
want = !want
}
checkForwarding(nicID, protocol.tcpip, protocol.fidl, want)
}
}
if t.Failed() {
t.FailNow()
}
}
setInterfaceForwarding := func(nicID tcpip.NICID, ipVersion net.IpVersion, enabled bool) {
t.Helper()
resp, err := ni.SetInterfaceIpForwardingDeprecated(context.Background(), uint64(nicID), ipVersion, enabled)
if err != nil {
t.Fatalf("ni.SetInterfaceIpForwardingDeprecated(_, uint64(%d), %d, %t): %s", nicID, ipVersion, enabled, err)
}
if diff := cmp.Diff(stack.StackSetInterfaceIpForwardingDeprecatedResultWithResponse(stack.StackSetInterfaceIpForwardingDeprecatedResponse{}), resp); diff != "" {
t.Fatalf("ni.SetInterfaceIpForwardingDeprecated(_, %d, %d, %t) mismatch (-want +got):\n%s", nicID, ipVersion, enabled, diff)
}
}
// Forwarding should initially be disabled.
checkAllForwarding(false)
// We should be able to enable forwarding on a single interface.
setInterfaceForwarding(ifs1.nicid, net.IpVersionV4, true)
checkAllForwardingExcept(false, ifs1.nicid, net.IpVersionV4)
setInterfaceForwarding(ifs1.nicid, net.IpVersionV6, true)
setInterfaceForwarding(ifs2.nicid, net.IpVersionV4, true)
setInterfaceForwarding(ifs2.nicid, net.IpVersionV6, true)
// We should be able to disable forwarding on a single interface.
setInterfaceForwarding(ifs1.nicid, net.IpVersionV4, false)
checkAllForwardingExcept(true, ifs1.nicid, net.IpVersionV4)
})
}
func TestSetDhcpClientEnabled(t *testing.T) {
t.Run("bad NIC", func(t *testing.T) {
ns, _ := newNetstack(t, netstackTestOptions{})
stackServiceImpl := stackImpl{ns: ns}
result, err := stackServiceImpl.SetDhcpClientEnabled(context.Background(), 1234, true)
if err != nil {
t.Fatalf("stackServiceImpl.StartDhcpClient(...) = %s", err)
}
if got, want := result.Which(), stack.I_stackSetDhcpClientEnabledResultTag(stack.StackSetDhcpClientEnabledResultErr); got != want {
t.Fatalf("got result.Which() = %d, want = %d", got, want)
}
if got, want := result.Err, stack.ErrorNotFound; got != want {
t.Fatalf("got result.Err = %s, want = %s", got, want)
}
})
t.Run("good NIC", func(t *testing.T) {
ns, _ := newNetstack(t, netstackTestOptions{})
ifs1 := addNoopEndpoint(t, ns, "")
t.Cleanup(ifs1.RemoveByUser)
stackServiceImpl := stackImpl{ns: ns}
result, err := stackServiceImpl.SetDhcpClientEnabled(context.Background(), uint64(ifs1.nicid), true)
if err != nil {
t.Fatalf("stackServiceImpl.StartDhcpClient(...) = %s", err)
}
if got, want := result.Which(), stack.I_stackSetDhcpClientEnabledResultTag(stack.StackSetDhcpClientEnabledResultResponse); got != want {
t.Fatalf("got result.Which() = %d, want = %d", got, want)
}
})
}