blob: cc6e36017df90632872c7b4b6693a4cbaace5f6c [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.
package netstack
import (
"context"
"net"
"sort"
"syscall/zx"
"syscall/zx/dispatch"
"syscall/zx/fidl"
"testing"
"time"
"fidl/fuchsia/hardware/ethernet"
fidlnet "fidl/fuchsia/net"
"fidl/fuchsia/net/stack"
"fidl/fuchsia/netstack"
ethernetext "fidlext/fuchsia/hardware/ethernet"
"netstack/dhcp"
"netstack/dns"
"netstack/fidlconv"
"netstack/routes"
"netstack/util"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
tcpipstack "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"
)
const (
testDeviceName string = "testdevice"
testTopoPath string = "/fake/ethernet/device"
testV4Address tcpip.Address = tcpip.Address("\xc0\xa8\x2a\x10")
testV6Address tcpip.Address = tcpip.Address("\xc0\xa8\x2a\x10\xc0\xa8\x2a\x10\xc0\xa8\x2a\x10\xc0\xa8\x2a\x10")
testLinkLocalV6Addr1 tcpip.Address = "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
testLinkLocalV6Addr2 tcpip.Address = "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
dadResolutionTimeout = dadRetransmitTimer*dadTransmits + time.Second
)
func TestDelRouteErrors(t *testing.T) {
ns := newNetstack(t)
eth := deviceForAddEth(ethernet.Info{}, t)
ifs, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName}, &eth)
if err != nil {
t.Fatalf("addEth(%q, _): %s", testTopoPath, err)
}
rt := tcpip.Route{
Destination: header.IPv4EmptySubnet,
Gateway: "\x01\x02\x03\x04",
NIC: ifs.nicid,
}
// Deleting a route we never added should result in an error.
if err := ns.DelRoute(rt); err != routes.ErrNoSuchRoute {
t.Errorf("got DelRoute(%s) = %v, want = %s", rt, err, routes.ErrNoSuchRoute)
}
if err := ns.AddRoute(rt, metricNotSet, false); err != nil {
t.Fatalf("AddRoute(%s, metricNotSet, false): %s", rt, err)
}
// Deleting a route we added should not result in an error.
if err := ns.DelRoute(rt); err != nil {
t.Fatalf("got DelRoute(%s) = %s, want = nil", rt, err)
}
// Deleting a route we just deleted should result in an error.
if err := ns.DelRoute(rt); err != routes.ErrNoSuchRoute {
t.Errorf("got DelRoute(%s) = %v, want = %s", rt, err, routes.ErrNoSuchRoute)
}
}
// TestStackNICEnableDisable tests that the NIC in stack.Stack is enabled or
// disabled when the underlying link is brought up or down, respectively.
func TestStackNICEnableDisable(t *testing.T) {
ns := newNetstack(t)
eth := deviceForAddEth(ethernet.Info{}, t)
eth.StopImpl = func() error { return nil }
config := netstack.InterfaceConfig{Name: testDeviceName}
ifs, err := ns.addEth(testTopoPath, config, &eth)
if err != nil {
t.Fatalf("ns.addEth(%q, %+v, _): %s", testTopoPath, config, err)
}
// The NIC should initially be disabled in stack.Stack.
if enabled := ns.stack.CheckNIC(ifs.nicid); enabled {
t.Fatalf("got ns.stack.CheckNIC(%d) = true, want = false", ifs.nicid)
}
// Bringing the link up should enable the NIC in stack.Stack.
if err := ifs.controller.Up(); err != nil {
t.Fatal("ifs.controller.Up(): ", err)
}
if enabled := ns.stack.CheckNIC(ifs.nicid); !enabled {
t.Fatalf("got ns.stack.CheckNIC(%d) = false, want = true", ifs.nicid)
}
// Bringing the link down should disable the NIC in stack.Stack.
if err := ifs.controller.Down(); err != nil {
t.Fatal("ifs.controller.Down(): ", err)
}
if enabled := ns.stack.CheckNIC(ifs.nicid); enabled {
t.Fatalf("got ns.stack.CheckNIC(%d) = true, want = false", ifs.nicid)
}
}
// TestStackNICRemove tests that the NIC in stack.Stack is removed when the
// underlying link is closed.
func TestStackNICRemove(t *testing.T) {
ns := newNetstack(t)
eth := deviceForAddEth(ethernet.Info{}, t)
eth.StopImpl = func() error { return nil }
config := netstack.InterfaceConfig{Name: testDeviceName}
ifs, err := ns.addEth(testTopoPath, config, &eth)
if err != nil {
t.Fatalf("ns.addEth(%q, %+v, _): %s", testTopoPath, config, err)
}
// The NIC should initially be disabled in stack.Stack.
if enabled := ns.stack.CheckNIC(ifs.nicid); enabled {
t.Errorf("got ns.stack.CheckNIC(%d) = true, want = false", ifs.nicid)
}
if _, ok := ns.stack.NICInfo()[ifs.nicid]; !ok {
t.Errorf("missing NICInfo for NIC %d", ifs.nicid)
}
if _, err := ns.stack.GetMainNICAddress(ifs.nicid, header.IPv6ProtocolNumber); err != nil {
t.Errorf("GetMainNICAddress(%d, %d): %s", ifs.nicid, header.IPv6ProtocolNumber, err)
}
if t.Failed() {
t.FailNow()
}
// Closing the link should remove the NIC from stack.Stack.
if err := ifs.controller.Close(); err != nil {
t.Fatal("ifs.controller.Close(): ", err)
}
if enabled := ns.stack.CheckNIC(ifs.nicid); enabled {
t.Errorf("got ns.stack.CheckNIC(%d) = false, want = true", ifs.nicid)
}
if nicInfo, ok := ns.stack.NICInfo()[ifs.nicid]; ok {
t.Errorf("unexpected NICInfo found for NIC %d = %+v", ifs.nicid, nicInfo)
}
if addr, err := ns.stack.GetMainNICAddress(ifs.nicid, header.IPv6ProtocolNumber); err != tcpip.ErrUnknownNICID {
t.Errorf("got GetMainNICAddress(%d, %d) = (%s, %v), want = (_, %s)", ifs.nicid, header.IPv6ProtocolNumber, addr, err, tcpip.ErrUnknownNICID)
}
// Wait for the controller to stop and free up its resources.
ep, ok := ifs.controller.(tcpipstack.LinkEndpoint)
if !ok {
t.Fatalf("ep (= %T) does not implement tcpipstack.LinkEndpoint", ep)
}
ep.Wait()
}
func TestBindingSetCounterStat_Value(t *testing.T) {
d, err := dispatch.NewDispatcher()
if err != nil {
t.Fatalf("couldn't initialize dispatcher: %s", err)
}
defer d.Close()
s := bindingSetCounterStat{bindingSets: []*fidl.BindingSet{
new(fidl.BindingSet),
new(fidl.BindingSet),
}}
defer func() {
for _, b := range s.bindingSets {
b.Close()
}
}()
// Create a pair of channels for each call to (*fidl.BindingSet).Add;
// (*fidl.BindingSet).Remove closes the channel being removed, which causes
// its peer to return ZX_ERR_PEER_CLOSED, which in turn would cause the peer
// to be removed from any fidl.BindingSet to which it has been added, which
// would cause this test to flake (if some other test has started the FIDL
// dispatcher).
type Pair struct {
ch, peer zx.Channel
}
var pairs []Pair
defer func() {
for _, pair := range pairs {
// Explicitly ignore the errors; if the test runs past the
// (*fidl.BindingSet).Remove calls below, these channels will have been
// closed, and these Close calls will return ErrBadHandle. If the test
// does not, then these Close calls will return nil. Either way, that's
// not what we're testing here.
_ = pair.ch.Close()
_ = pair.peer.Close()
}
}()
var want uint64
if got := s.Value(); got != want {
t.Errorf("got s.Value() = %d want = %d", got, want)
}
for _, b := range s.bindingSets {
for i := 0; i < 2; i++ {
ch, peer, err := zx.NewChannel(0)
if err != nil {
t.Fatal("zx.NewChannel(...) failed:", err)
}
pairs = append(pairs, Pair{ch: ch, peer: peer})
if _, err := b.AddToDispatcher(nil, ch, d, nil); err != nil {
t.Fatalf("%T.Add(...) failed: %s", b, err)
}
want++
if got := s.Value(); got != want {
t.Errorf("got s.Value() = %d want = %d", got, want)
}
}
}
for _, b := range s.bindingSets {
for _, key := range b.BindingKeys() {
if !b.Remove(key) {
t.Fatalf("got %T.Remove(...) = false want = true", b)
}
want--
if got := s.Value(); got != want {
t.Errorf("got s.Value() = %d want = %d", got, want)
}
}
}
if got := s.Value(); got != want {
t.Errorf("got s.Value() = %d want = %d", got, want)
}
// Ensure the test is self-consistent.
if want != 0 {
t.Errorf("got `want` = %d want = 0", want)
}
}
func containsRoute(rs []tcpip.Route, r tcpip.Route) bool {
for _, i := range rs {
if i == r {
return true
}
}
return false
}
// TestEndpointDoubleClose tests that closing an endpoint that has already been
// closed once will not panic. This is in response to a bug where a socketImpl
// (whose endpoint had already been closed) was cloned, resulting in a second
// socketImpl with a reference to the already closed endpoint. When this second
// socketImpl closes, it will attempt to close the endpoint (which is already
// closed) resulting in a panic.
func TestEndpointDoubleClose(t *testing.T) {
ns := newNetstack(t)
wq := &waiter.Queue{}
ep, err := ns.stack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, wq)
if err != nil {
t.Fatalf("NewEndpoint = %s", err)
}
ios := endpointWithSocket{
endpoint: endpoint{
netProto: ipv6.ProtocolNumber,
transProto: tcp.ProtocolNumber,
wq: wq,
ep: ep,
ns: ns,
},
loopReadDone: make(chan struct{}),
loopWriteDone: make(chan struct{}),
closing: make(chan struct{}),
}
{
localS, peerS, err := zx.NewSocket(uint32(zx.SocketStream))
if err != nil {
t.Fatalf("zx.NewSocket = %s", err)
}
ios.local = localS
ios.peer = peerS
}
// We set clones to 2 initially to make sure that resources only get
// cleaned up when the ref count drops from 1 to 0.
ios.clones = 2
if refcount := ios.close(); refcount != 1 {
t.Fatalf("got refcount = %d, want = 1", refcount)
}
select {
case <-ios.closing:
t.Fatal("ios.closing is closed")
default:
}
// Ref count is now 1 so when we call close again, it will cleanup
// associated resources.
if refcount := ios.close(); refcount != 0 {
t.Fatalf("got refcount = %d, want = 0", refcount)
}
select {
case <-ios.closing:
default:
t.Fatal("ios.closing is not closed")
}
// Set ref count to 1 so it drops to 0 and make sure we do not
// do the work of closing again, and therefore should not panic.
ios.clones = 1
if refcount := ios.close(); refcount != 0 {
t.Fatalf("got refcount = %d, want = 0", refcount)
}
}
// Test whether ios.close will delete the reference to the
// endpoint created here from the netstack`s endpoints.
func TestCloseEndpointsMap(t *testing.T) {
ns := newNetstack(t)
wq := &waiter.Queue{}
ep, err := ns.stack.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, wq)
if err != nil {
t.Fatalf("NewEndpoint = %s", err)
}
ios := endpointWithSocket{
endpoint: endpoint{
netProto: ipv6.ProtocolNumber,
transProto: tcp.ProtocolNumber,
wq: wq,
ep: ep,
ns: ns,
},
loopReadDone: make(chan struct{}),
loopWriteDone: make(chan struct{}),
closing: make(chan struct{}),
}
{
localS, peerS, err := zx.NewSocket(uint32(zx.SocketStream))
if err != nil {
t.Fatalf("zx.NewSocket = %s", err)
}
ios.local = localS
ios.peer = peerS
}
if _, loaded := ns.endpoints.LoadOrStore(zx.Handle(ios.local), ios.ep); loaded {
t.Fatalf("endpoint map store error, key %d exists", ios.local)
}
ios.clones = 1
if refcount := ios.close(); refcount != 0 {
t.Fatalf("got refcount = %d, want = 0", refcount)
}
select {
case <-ios.closing:
default:
t.Fatal("ios.closing is not closed")
}
// Check if the reference to the endpoint is deleted from endpoints.
if _, ok := ios.ns.endpoints.Load(zx.Handle(ios.local)); ok {
t.Fatal("endpoint map not updated on ios.close")
}
}
func TestNicName(t *testing.T) {
ns := newNetstack(t)
eth := deviceForAddEth(ethernet.Info{}, t)
ifs, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName}, &eth)
if err != nil {
t.Fatal(err)
}
name := ifs.ns.name(ifs.nicid)
if name != testDeviceName {
t.Fatalf("ifs.mu.name = %v, want = %v", name, testDeviceName)
}
}
func TestNotStartedByDefault(t *testing.T) {
ns := newNetstack(t)
startCalled := false
eth := deviceForAddEth(ethernet.Info{}, t)
eth.StartImpl = func() (int32, error) {
startCalled = true
return int32(zx.ErrOk), nil
}
if _, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName}, &eth); err != nil {
t.Fatal(err)
}
if startCalled {
t.Error("expected no calls to ethernet.Device.Start by addEth")
}
}
type ndpDADEvent struct {
nicID tcpip.NICID
addr tcpip.Address
resolved bool
err *tcpip.Error
}
// testNDPDispatcher is a tcpip.NDPDispatcher that sends an NDP DAD event on
// dadC when OnDuplicateAddressDetectionStatus gets called.
type testNDPDispatcher struct {
dadC chan ndpDADEvent
}
// OnDuplicateAddressDetectionStatus implements
// stack.NDPDispatcher.OnDuplicateAddressDetectionStatus.
func (n *testNDPDispatcher) OnDuplicateAddressDetectionStatus(nicID tcpip.NICID, addr tcpip.Address, resolved bool, err *tcpip.Error) {
if c := n.dadC; c != nil {
c <- ndpDADEvent{
nicID: nicID,
addr: addr,
resolved: resolved,
err: err,
}
}
}
// OnDefaultRouterDiscovered implements stack.NDPDispatcher.OnDefaultRouterDiscovered.
//
// Adds the event to the event queue and returns true so Stack remembers the
// discovered default router.
func (*testNDPDispatcher) OnDefaultRouterDiscovered(tcpip.NICID, tcpip.Address) bool {
return false
}
// OnDefaultRouterInvalidated implements stack.NDPDispatcher.OnDefaultRouterInvalidated.
func (*testNDPDispatcher) OnDefaultRouterInvalidated(tcpip.NICID, tcpip.Address) {
}
// OnOnLinkPrefixDiscovered implements stack.NDPDispatcher.OnOnLinkPrefixDiscovered.
func (*testNDPDispatcher) OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet) bool {
return false
}
// OnOnLinkPrefixInvalidated implements stack.NDPDispatcher.OnOnLinkPrefixInvalidated.
func (*testNDPDispatcher) OnOnLinkPrefixInvalidated(tcpip.NICID, tcpip.Subnet) {
}
// OnAutoGenAddress implements stack.NDPDispatcher.OnAutoGenAddress.
func (*testNDPDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) bool {
return false
}
// OnAutoGenAddressDeprecated implements
// stack.NDPDispatcher.OnAutoGenAddressDeprecated.
func (*testNDPDispatcher) OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) {
}
// OnAutoGenAddressInvalidated implements stack.NDPDispatcher.OnAutoGenAddressInvalidated.
func (*testNDPDispatcher) OnAutoGenAddressInvalidated(tcpip.NICID, tcpip.AddressWithPrefix) {
}
// OnRecursiveDNSServerOption implements stack.NDPDispatcher.OnRecursiveDNSServerOption.
func (*testNDPDispatcher) OnRecursiveDNSServerOption(tcpip.NICID, []tcpip.Address, time.Duration) {
}
// OnDNSSearchListOption implements stack.NDPDispatcher.OnDNSSearchListOption.
func (*testNDPDispatcher) OnDNSSearchListOption(tcpip.NICID, []string, time.Duration) {
}
// OnDHCPv6Configuration implements stack.NDPDispatcher.OnDHCPv6Configuration.
func (*testNDPDispatcher) OnDHCPv6Configuration(tcpip.NICID, tcpipstack.DHCPv6ConfigurationFromNDPRA) {
}
// Test that NICs get an IPv6 link-local address using the same algorithm that
// netsvc uses. It does not matter whether the address is generated
// automatically by the netstack or manually by the bindings (Netstack).
func TestIpv6LinkLocalAddr(t *testing.T) {
t.Parallel()
ndpDisp := testNDPDispatcher{
dadC: make(chan ndpDADEvent, 1),
}
ns := newNetstackWithStackNDPDispatcher(t, &ndpDisp)
eth := deviceForAddEth(ethernet.Info{
Mac: ethernet.MacAddress{
Octets: [6]byte{2, 3, 4, 5, 6, 7},
},
}, t)
ifs, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName}, &eth)
if err != nil {
t.Fatalf("addEth(_, _, _): %s", err)
}
if err := ifs.controller.Up(); err != nil {
t.Fatal("ifs.controller.Up(): ", err)
}
want := tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x03\x04\xff\xfe\x05\x06\x07",
},
}
select {
case d := <-ndpDisp.dadC:
if diff := cmp.Diff(ndpDADEvent{nicID: ifs.nicid, addr: want.AddressWithPrefix.Address, resolved: true, err: nil}, d, cmp.AllowUnexported(d)); diff != "" {
t.Fatalf("ndp DAD event mismatch (-want +got):\n%s", diff)
}
case <-time.After(dadResolutionTimeout):
t.Fatal("timed out waiting for DAD event")
}
nicInfos := ns.stack.NICInfo()
nicInfo, ok := nicInfos[ifs.nicid]
if !ok {
t.Fatalf("stack.NICInfo()[%d]: %s", ifs.nicid, tcpip.ErrUnknownNICID)
}
if _, found := findAddress(nicInfo.ProtocolAddresses, want); !found {
t.Fatalf("got NIC addrs = %+v, want = %+v", nicInfo.ProtocolAddresses, want)
}
}
func TestIpv6LinkLocalOnLinkRoute(t *testing.T) {
if got, want := ipv6LinkLocalOnLinkRoute(6), (tcpip.Route{Destination: header.IPv6LinkLocalPrefix.Subnet(), NIC: 6}); got != want {
t.Fatalf("got ipv6LinkLocalOnLinkRoute(6) = %s, want = %s", got, want)
}
}
// Test that NICs get an on-link route to the IPv6 link-local subnet when it is
// brought up.
func TestIpv6LinkLocalOnLinkRouteOnUp(t *testing.T) {
ns := newNetstack(t)
eth := deviceForAddEth(ethernet.Info{
Mac: ethernet.MacAddress{
Octets: [6]byte{2, 3, 4, 5, 6, 7},
},
}, t)
eth.StopImpl = func() error { return nil }
ifs, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName}, &eth)
if err != nil {
t.Fatalf("addEth(_, _, _): %s", err)
}
linkLocalRoute := ipv6LinkLocalOnLinkRoute(ifs.nicid)
// Initially should not have the link-local route.
rt := ns.stack.GetRouteTable()
if containsRoute(rt, linkLocalRoute) {
t.Fatalf("got GetRouteTable() = %+v, don't want = %s", rt, linkLocalRoute)
}
// Bringing the ethernet device up should result in the link-local
// route being added.
if err := ifs.controller.Up(); err != nil {
t.Fatalf("eth.Up(): %s", err)
}
rt = ns.stack.GetRouteTable()
if !containsRoute(rt, linkLocalRoute) {
t.Fatalf("got GetRouteTable() = %+v, want = %s", rt, linkLocalRoute)
}
// Bringing the ethernet device down should result in the link-local
// route being removed.
if err := ifs.controller.Down(); err != nil {
t.Fatalf("eth.Down(): %s", err)
}
rt = ns.stack.GetRouteTable()
if containsRoute(rt, linkLocalRoute) {
t.Fatalf("got GetRouteTable() = %+v, don't want = %s", rt, linkLocalRoute)
}
}
func TestDefaultV6Route(t *testing.T) {
if got, want := defaultV6Route(6, testLinkLocalV6Addr1), (tcpip.Route{Destination: header.IPv6EmptySubnet, Gateway: testLinkLocalV6Addr1, NIC: 6}); got != want {
t.Fatalf("got defaultV6Route(6, %s) = %s, want = %s", testLinkLocalV6Addr1, got, want)
}
}
func TestOnLinkV6Route(t *testing.T) {
subAddr := util.Parse("abcd:1234::")
subMask := tcpip.AddressMask(util.Parse("ffff:ffff::"))
subnet, err := tcpip.NewSubnet(subAddr, subMask)
if err != nil {
t.Fatalf("NewSubnet(%s, %s): %s", subAddr, subMask, err)
}
if got, want := onLinkV6Route(6, subnet), (tcpip.Route{Destination: subnet, NIC: 6}); got != want {
t.Fatalf("got onLinkV6Route(6, %s) = %s, want = %s", subnet, got, want)
}
}
func TestMulticastPromiscuousModeEnabledByDefault(t *testing.T) {
ns := newNetstack(t)
multicastPromiscuousModeEnabled := false
eth := deviceForAddEth(ethernet.Info{}, t)
eth.ConfigMulticastSetPromiscuousModeImpl = func(enabled bool) (int32, error) {
multicastPromiscuousModeEnabled = enabled
return int32(zx.ErrOk), nil
}
if _, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName}, &eth); err != nil {
t.Fatal(err)
}
if !multicastPromiscuousModeEnabled {
t.Error("expected a call to ConfigMulticastSetPromiscuousMode(true) by addEth")
}
}
func TestDhcpConfiguration(t *testing.T) {
ns := newNetstack(t)
ipAddressConfig := netstack.IpAddressConfig{}
ipAddressConfig.SetDhcp(true)
d := deviceForAddEth(ethernet.Info{}, t)
d.StopImpl = func() error { return nil }
ifs, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName, IpAddressConfig: ipAddressConfig}, &d)
if err != nil {
t.Fatal(err)
}
name := ifs.ns.name(ifs.nicid)
ifs.mu.Lock()
if ifs.mu.dhcp.Client == nil {
t.Error("no dhcp client")
}
if ifs.mu.dhcp.enabled {
t.Error("expected dhcp to be disabled")
}
if ifs.mu.dhcp.running() {
t.Error("expected dhcp client to be stopped initially")
}
ifs.mu.Unlock()
ifs.setDHCPStatus(name, true)
ifs.controller.Up()
ifs.mu.Lock()
if !ifs.mu.dhcp.enabled {
t.Error("expected dhcp to be enabled")
}
if !ifs.mu.dhcp.running() {
t.Error("expected dhcp client to be running")
}
ifs.mu.Unlock()
ifs.controller.Down()
ifs.mu.Lock()
if ifs.mu.dhcp.running() {
t.Error("expected dhcp client to be stopped on eth down")
}
if !ifs.mu.dhcp.enabled {
t.Error("expected dhcp configuration to be preserved on eth down")
}
ifs.mu.Unlock()
ifs.controller.Up()
ifs.mu.Lock()
if !ifs.mu.dhcp.running() {
t.Error("expected dhcp client to be running on eth restart")
}
if !ifs.mu.dhcp.enabled {
t.Error("expected dhcp configuration to be preserved on eth restart")
}
ifs.mu.Unlock()
}
func TestUniqueFallbackNICNames(t *testing.T) {
ns := newNetstack(t)
d1 := deviceForAddEth(ethernet.Info{}, t)
ifs1, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{}, &d1)
if err != nil {
t.Fatal(err)
}
d2 := deviceForAddEth(ethernet.Info{}, t)
ifs2, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{}, &d2)
if err != nil {
t.Fatal(err)
}
nicInfos := ns.stack.NICInfo()
nicInfo1, ok := nicInfos[ifs1.nicid]
if !ok {
t.Fatalf("stack.NICInfo()[%d]: %s", ifs1.nicid, tcpip.ErrUnknownNICID)
}
nicInfo2, ok := nicInfos[ifs2.nicid]
if !ok {
t.Fatalf("stack.NICInfo()[%d]: %s", ifs2.nicid, tcpip.ErrUnknownNICID)
}
if nicInfo1.Name == nicInfo2.Name {
t.Fatalf("got (%+v).Name == (%+v).Name, want non-equal", nicInfo1, nicInfo2)
}
}
func TestStaticIPConfiguration(t *testing.T) {
ns := newNetstack(t)
addr := fidlconv.ToNetIpAddress(testV4Address)
ifAddr := stack.InterfaceAddress{IpAddress: addr, PrefixLen: 32}
d := deviceForAddEth(ethernet.Info{}, t)
d.StopImpl = func() error { return nil }
ifs, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName}, &d)
if err != nil {
t.Fatal(err)
}
name := ifs.ns.name(ifs.nicid)
result := ns.addInterfaceAddr(uint64(ifs.nicid), ifAddr)
if result != stack.StackAddInterfaceAddressResultWithResponse(stack.StackAddInterfaceAddressResponse{}) {
t.Fatalf("got ns.addInterfaceAddr(%d, %#v) = %#v, want = Response()", ifs.nicid, ifAddr, result)
}
if mainAddr, err := ns.stack.GetMainNICAddress(ifs.nicid, ipv4.ProtocolNumber); err != nil {
t.Errorf("stack.GetMainNICAddress(%d, %d): %s", ifs.nicid, ipv4.ProtocolNumber, err)
} else if got := mainAddr.Address; got != testV4Address {
t.Errorf("got stack.GetMainNICAddress(%d, %d).Addr = %#v, want = %#v", ifs.nicid, ipv4.ProtocolNumber, got, testV4Address)
}
ifs.mu.Lock()
if ifs.mu.dhcp.enabled {
t.Error("expected dhcp state to be disabled initially")
}
ifs.mu.Unlock()
ifs.controller.Down()
ifs.mu.Lock()
if ifs.mu.dhcp.enabled {
t.Error("expected dhcp state to remain disabled after bringing interface down")
}
if ifs.mu.dhcp.running() {
t.Error("expected dhcp state to remain stopped after bringing interface down")
}
ifs.mu.Unlock()
ifs.controller.Up()
ifs.mu.Lock()
if ifs.mu.dhcp.enabled {
t.Error("expected dhcp state to remain disabled after restarting interface")
}
ifs.mu.Unlock()
ifs.setDHCPStatus(name, true)
ifs.mu.Lock()
if !ifs.mu.dhcp.enabled {
t.Error("expected dhcp state to become enabled after manually enabling it")
}
if !ifs.mu.dhcp.running() {
t.Error("expected dhcp state running")
}
ifs.mu.Unlock()
}
func TestWLANStaticIPConfiguration(t *testing.T) {
ns := Netstack{
stack: tcpipstack.New(tcpipstack.Options{
NetworkProtocols: []tcpipstack.NetworkProtocol{
arp.NewProtocol(),
ipv4.NewProtocol(),
ipv6.NewProtocol(),
},
}),
}
addr := fidlconv.ToNetIpAddress(testV4Address)
ifAddr := stack.InterfaceAddress{IpAddress: addr, PrefixLen: 32}
d := deviceForAddEth(ethernet.Info{Features: ethernet.InfoFeatureWlan}, t)
ifs, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{Name: testDeviceName}, &d)
if err != nil {
t.Fatal(err)
}
result := ns.addInterfaceAddr(uint64(ifs.nicid), ifAddr)
if result != stack.StackAddInterfaceAddressResultWithResponse(stack.StackAddInterfaceAddressResponse{}) {
t.Fatalf("got ns.addInterfaceAddr(%d, %#v) = %#v, want = Response()", ifs.nicid, ifAddr, result)
}
if mainAddr, err := ns.stack.GetMainNICAddress(ifs.nicid, ipv4.ProtocolNumber); err != nil {
t.Errorf("stack.GetMainNICAddress(%d, %d): %s", ifs.nicid, ipv4.ProtocolNumber, err)
} else if got := mainAddr.Address; got != testV4Address {
t.Errorf("got stack.GetMainNICAddress(%d, %d).Addr = %#v, want = %#v", ifs.nicid, ipv4.ProtocolNumber, got, testV4Address)
}
}
func newNetstack(t *testing.T) *Netstack {
t.Helper()
return newNetstackWithNDPDispatcher(t, nil)
}
func newNetstackWithNDPDispatcher(t *testing.T, ndpDisp *ndpDispatcher) *Netstack {
t.Helper()
// ndpDispatcher should never be called with a nil receiver.
//
// From https://golang.org/doc/faq#nil_error:
//
// Under the covers, interfaces are implemented as two elements, a type T and
// a value V.
//
// An interface value is nil only if the V and T are both unset, (T=nil, V is
// not set), In particular, a nil interface will always hold a nil type. If we
// store a nil pointer of type *int inside an interface value, the inner type
// will be *int regardless of the value of the pointer: (T=*int, V=nil). Such
// an interface value will therefore be non-nil even when the pointer value V
// inside is nil.
if ndpDisp == nil {
return newNetstackWithStackNDPDispatcher(t, nil)
}
ns := newNetstackWithStackNDPDispatcher(t, ndpDisp)
ndpDisp.ns = ns
return ns
}
func newNetstackWithStackNDPDispatcher(t *testing.T, ndpDisp tcpipstack.NDPDispatcher) *Netstack {
t.Helper()
stk := tcpipstack.New(tcpipstack.Options{
NetworkProtocols: []tcpipstack.NetworkProtocol{
arp.NewProtocol(),
ipv4.NewProtocol(),
ipv6.NewProtocol(),
},
TransportProtocols: []tcpipstack.TransportProtocol{
tcp.NewProtocol(),
udp.NewProtocol(),
},
NDPDisp: ndpDisp,
})
return &Netstack{
stack: stk,
// We need to initialize the DNS client, since adding/removing interfaces
// sets the DNS servers on that interface, which requires that dnsClient
// exist.
dnsClient: dns.NewClient(stk),
}
}
func getInterfaceAddresses(t *testing.T, ni *stackImpl, nicid tcpip.NICID) []tcpip.AddressWithPrefix {
t.Helper()
interfaces, err := ni.ListInterfaces(context.Background())
if err != nil {
t.Fatalf("ni.ListInterfaces() failed: %s", err)
}
info, found := stack.InterfaceInfo{}, false
for _, i := range interfaces {
if tcpip.NICID(i.Id) == nicid {
info = i
found = true
break
}
}
if !found {
t.Fatalf("couldn't find NICID=%d in %+v", nicid, interfaces)
}
addrs := make([]tcpip.AddressWithPrefix, 0, len(info.Properties.Addresses))
for _, a := range info.Properties.Addresses {
addrs = append(addrs, tcpip.AddressWithPrefix{
Address: fidlconv.ToTCPIPAddress(a.IpAddress),
PrefixLen: int(a.PrefixLen),
})
}
return addrs
}
func compareInterfaceAddresses(t *testing.T, got, want []tcpip.AddressWithPrefix) {
t.Helper()
sort.Slice(got, func(i, j int) bool { return got[i].Address < got[j].Address })
sort.Slice(want, func(i, j int) bool { return want[i].Address < want[j].Address })
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("Interface addresses mismatch (-want +got):\n%s", diff)
}
}
func TestNetstackImpl_GetInterfaces2(t *testing.T) {
ns := newNetstack(t)
ni := &netstackImpl{ns: ns}
d := deviceForAddEth(ethernet.Info{}, t)
if _, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{}, &d); err != nil {
t.Fatal(err)
}
interfaces, err := ni.GetInterfaces2(context.Background())
if err != nil {
t.Fatal(err)
}
if l := len(interfaces); l == 0 {
t.Fatalf("got len(GetInterfaces2()) = %d, want != %d", l, l)
}
var expectedAddr fidlnet.IpAddress
expectedAddr.SetIpv4(fidlnet.Ipv4Address{})
for _, iface := range interfaces {
if iface.Addr != expectedAddr {
t.Errorf("got interface %+v, want Addr = %+v", iface, expectedAddr)
}
if iface.Netmask != expectedAddr {
t.Errorf("got interface %+v, want NetMask = %+v", iface, expectedAddr)
}
}
}
// Test adding a list of both IPV4 and IPV6 addresses and then removing them
// again one-by-one.
func TestListInterfaceAddresses(t *testing.T) {
ndpDisp := testNDPDispatcher{
dadC: make(chan ndpDADEvent, 1),
}
ns := newNetstackWithStackNDPDispatcher(t, &ndpDisp)
ni := &stackImpl{ns: ns}
d := deviceForAddEth(ethernet.Info{
Mac: ethernet.MacAddress{
Octets: [6]byte{2, 3, 4, 5, 6, 7},
},
}, t)
ifState, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{}, &d)
if err != nil {
t.Fatal(err)
}
if err := ifState.controller.Up(); err != nil {
t.Fatal("ifState.controller.Up(): ", err)
}
waitForDAD := func(addr tcpip.Address) {
t.Helper()
select {
case d := <-ndpDisp.dadC:
if diff := cmp.Diff(ndpDADEvent{nicID: ifState.nicid, addr: addr, resolved: true, err: nil}, d, cmp.AllowUnexported(d)); diff != "" {
t.Fatalf("ndp DAD event mismatch (-want +got):\n%s", diff)
}
case <-time.After(dadResolutionTimeout):
t.Fatal("timed out waiting for DAD event")
}
}
waitForDAD("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x03\x04\xff\xfe\x05\x06\x07")
// The call to ns.addEth() added addresses to the stack. Make sure we include
// those in our want list.
wantAddrs := getInterfaceAddresses(t, ni, ifState.nicid)
testAddresses := []tcpip.AddressWithPrefix{
{"\x01\x01\x01\x01", 32},
{"\x02\x02\x02\x02", 24},
{"\x03\x03\x03\x03", 16},
{"\x04\x04\x04\x04", 8},
{"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01", 128},
{"\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02", 64},
{"\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03", 32},
{"\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04", 8},
}
t.Run("Add", func(t *testing.T) {
for _, addr := range testAddresses {
t.Run(addr.String(), func(t *testing.T) {
ifAddr := stack.InterfaceAddress{
IpAddress: fidlconv.ToNetIpAddress(addr.Address),
PrefixLen: uint8(addr.PrefixLen),
}
result, err := ni.AddInterfaceAddress(context.Background(), uint64(ifState.nicid), ifAddr)
AssertNoError(t, err)
if result != stack.StackAddInterfaceAddressResultWithResponse(stack.StackAddInterfaceAddressResponse{}) {
t.Fatalf("got ni.AddInterfaceAddress(%d, %#v) = %#v, want = Response()", ifState.nicid, ifAddr, result)
}
if addr := addr.Address; header.IsV6UnicastAddress(addr) {
waitForDAD(addr)
}
wantAddrs = append(wantAddrs, addr)
gotAddrs := getInterfaceAddresses(t, ni, ifState.nicid)
compareInterfaceAddresses(t, gotAddrs, wantAddrs)
})
}
})
t.Run("Remove", func(t *testing.T) {
for _, addr := range testAddresses {
t.Run(addr.String(), func(t *testing.T) {
ifAddr := stack.InterfaceAddress{
IpAddress: fidlconv.ToNetIpAddress(addr.Address),
PrefixLen: uint8(addr.PrefixLen),
}
result, err := ni.DelInterfaceAddress(context.Background(), uint64(ifState.nicid), ifAddr)
AssertNoError(t, err)
if result != stack.StackDelInterfaceAddressResultWithResponse(stack.StackDelInterfaceAddressResponse{}) {
t.Fatalf("got ni.DelInterfaceAddress(%d, %#v) = %#v, want = Response()", ifState.nicid, ifAddr, result)
}
// Remove address from list.
for i, a := range wantAddrs {
if a == addr {
wantAddrs = append(wantAddrs[:i], wantAddrs[i+1:]...)
break
}
}
gotAddrs := getInterfaceAddresses(t, ni, ifState.nicid)
compareInterfaceAddresses(t, gotAddrs, wantAddrs)
})
}
})
}
// Test that adding an address with one prefix and then adding the same address
// but with a different prefix will simply replace the first address.
func TestAddAddressesThenChangePrefix(t *testing.T) {
ns := newNetstack(t)
ni := &stackImpl{ns: ns}
d := deviceForAddEth(ethernet.Info{}, t)
ifState, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{}, &d)
if err != nil {
t.Fatal(err)
}
// The call to ns.addEth() added addresses to the stack. Make sure we include
// those in our want list.
initialAddrs := getInterfaceAddresses(t, ni, ifState.nicid)
// Add address.
addr := tcpip.AddressWithPrefix{"\x01\x01\x01\x01", 8}
ifAddr := stack.InterfaceAddress{
IpAddress: fidlconv.ToNetIpAddress(addr.Address),
PrefixLen: uint8(addr.PrefixLen),
}
result, err := ni.AddInterfaceAddress(context.Background(), uint64(ifState.nicid), ifAddr)
AssertNoError(t, err)
if result != stack.StackAddInterfaceAddressResultWithResponse(stack.StackAddInterfaceAddressResponse{}) {
t.Fatalf("got ni.AddInterfaceAddress(%d, %#v) = %#v, want = Response()", ifState.nicid, ifAddr, result)
}
wantAddrs := append(initialAddrs, addr)
gotAddrs := getInterfaceAddresses(t, ni, ifState.nicid)
compareInterfaceAddresses(t, gotAddrs, wantAddrs)
// Add the same address with a different prefix.
addr.PrefixLen *= 2
ifAddr.PrefixLen *= 2
result, err = ni.AddInterfaceAddress(context.Background(), uint64(ifState.nicid), ifAddr)
AssertNoError(t, err)
if result != stack.StackAddInterfaceAddressResultWithResponse(stack.StackAddInterfaceAddressResponse{}) {
t.Fatalf("got ni.AddInterfaceAddress(%d, %#v) = %#v, want = Response()", ifState.nicid, ifAddr, result)
}
wantAddrs = append(initialAddrs, addr)
gotAddrs = getInterfaceAddresses(t, ni, ifState.nicid)
compareInterfaceAddresses(t, gotAddrs, wantAddrs)
}
func TestAddRouteParameterValidation(t *testing.T) {
ns := newNetstack(t)
d := deviceForAddEth(ethernet.Info{}, t)
addr := tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address("\xf0\xf0\xf0\xf0"),
PrefixLen: 24,
},
}
subnetLocalAddress := tcpip.Address("\xf0\xf0\xf0\xf1")
ifState, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{}, &d)
if err != nil {
t.Fatalf("got ns.addEth(_) = _, %s want = _, nil", err)
}
found, err := ns.addInterfaceAddress(ifState.nicid, addr)
if err != nil {
t.Fatalf("ns.addInterfaceAddress(%d, %s) = _, %s", ifState.nicid, addr.AddressWithPrefix, err)
}
if !found {
t.Fatalf("ns.addInterfaceAddress(%d, %s) = %t, _", ifState.nicid, addr.AddressWithPrefix, found)
}
tests := []struct {
name string
route tcpip.Route
metric routes.Metric
dynamic bool
shouldError bool
}{
{
name: "IPv4 destination no NIC invalid gateway",
route: tcpip.Route{
Destination: util.PointSubnet(testV4Address),
Gateway: testV4Address,
NIC: 0,
},
metric: routes.Metric(0),
shouldError: true,
},
{
name: "IPv6 destination no NIC invalid gateway",
route: tcpip.Route{
Destination: util.PointSubnet(testV6Address),
Gateway: testV6Address,
NIC: 0,
},
metric: routes.Metric(0),
shouldError: true,
},
{
name: "IPv4 destination no NIC valid gateway",
route: tcpip.Route{
Destination: util.PointSubnet(testV4Address),
Gateway: subnetLocalAddress,
NIC: 0,
},
},
{
name: "zero length gateway",
route: tcpip.Route{
Destination: util.PointSubnet(testV4Address),
NIC: ifState.nicid,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := ns.AddRoute(test.route, test.metric, test.dynamic)
if got := err != nil; got != test.shouldError {
t.Logf("err = %v", err)
t.Errorf("got (ns.AddRoute(_) != nil) = %t, want = %t", got, test.shouldError)
}
})
}
}
func TestDHCPAcquired(t *testing.T) {
ns := newNetstack(t)
d := deviceForAddEth(ethernet.Info{}, t)
ifState, err := ns.addEth(testTopoPath, netstack.InterfaceConfig{}, &d)
if err != nil {
t.Fatal(err)
}
serverAddress := []byte(testV4Address)
serverAddress[len(serverAddress)-1]++
gatewayAddress := serverAddress
gatewayAddress[len(gatewayAddress)-1]++
defaultMask := net.IP(testV4Address).DefaultMask()
prefixLen, _ := defaultMask.Size()
destination1, err := tcpip.NewSubnet(util.Parse("192.168.42.0"), tcpip.AddressMask(util.Parse("255.255.255.0")))
if err != nil {
t.Fatal(err)
}
destination2, err := tcpip.NewSubnet(util.Parse("0.0.0.0"), tcpip.AddressMask(util.Parse("0.0.0.0")))
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
oldAddr, newAddr tcpip.AddressWithPrefix
config dhcp.Config
expectedRouteTable []routes.ExtendedRoute
}{
{
name: "subnet mask provided",
oldAddr: tcpip.AddressWithPrefix{},
newAddr: tcpip.AddressWithPrefix{
Address: testV4Address,
PrefixLen: prefixLen,
},
config: dhcp.Config{
ServerAddress: tcpip.Address(serverAddress),
Gateway: tcpip.Address(serverAddress),
SubnetMask: tcpip.AddressMask(defaultMask),
DNS: []tcpip.Address{tcpip.Address(gatewayAddress)},
LeaseLength: dhcp.Seconds(60),
},
expectedRouteTable: []routes.ExtendedRoute{
{
Route: tcpip.Route{
Destination: destination1,
NIC: 1,
},
Metric: 0,
MetricTracksInterface: true,
Dynamic: true,
Enabled: false,
},
{
Route: tcpip.Route{
Destination: destination2,
Gateway: util.Parse("192.168.42.18"),
NIC: 1,
},
Metric: 0,
MetricTracksInterface: true,
Dynamic: true,
Enabled: false,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// save current route table for later
originalRouteTable := ifState.ns.GetExtendedRouteTable()
// Update the DHCP address to the given test values and verify it took
// effect.
ifState.dhcpAcquired(test.oldAddr, test.newAddr, test.config)
if diff := cmp.Diff(ifState.mu.dnsServers, test.config.DNS); diff != "" {
t.Errorf("ifState.mu.dnsServers mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(ifState.ns.GetExtendedRouteTable(), test.expectedRouteTable, cmp.AllowUnexported(tcpip.Subnet{})); diff != "" {
t.Errorf("GetExtendedRouteTable() mismatch (-want +got):\n%s", diff)
}
infoMap := ns.stack.NICInfo()
if info, ok := infoMap[ifState.nicid]; ok {
found := false
for _, address := range info.ProtocolAddresses {
if address.Protocol == ipv4.ProtocolNumber {
switch address.AddressWithPrefix {
case test.oldAddr:
t.Errorf("expired address %s was not removed from NIC addresses %v", test.oldAddr, info.ProtocolAddresses)
case test.newAddr:
found = true
}
}
}
if !found {
t.Errorf("new address %s was not added to NIC addresses %v", test.newAddr, info.ProtocolAddresses)
}
} else {
t.Errorf("NIC %d not found in %v", ifState.nicid, infoMap)
}
// Remove the address and verify everything is cleaned up correctly.
remAddr := test.newAddr
ifState.dhcpAcquired(remAddr, tcpip.AddressWithPrefix{}, dhcp.Config{})
if diff := cmp.Diff(ifState.mu.dnsServers, ifState.mu.dnsServers[:0]); diff != "" {
t.Errorf("ifState.mu.dnsServers mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(ifState.ns.GetExtendedRouteTable(), originalRouteTable); diff != "" {
t.Errorf("GetExtendedRouteTable() mismatch (-want +got):\n%s", diff)
}
infoMap = ns.stack.NICInfo()
if info, ok := infoMap[ifState.nicid]; ok {
for _, address := range info.ProtocolAddresses {
if address.Protocol == ipv4.ProtocolNumber {
if address.AddressWithPrefix == remAddr {
t.Errorf("address %s/%d was not removed from NIC addresses %v", remAddr.Address, remAddr.PrefixLen, info.ProtocolAddresses)
}
}
}
} else {
t.Errorf("NIC %d not found in %v", ifState.nicid, infoMap)
}
})
}
}
// Returns an ethernetext.Device struct that implements
// ethernet.Device and can be started and stopped.
//
// Reports the passed in ethernet.Info when Device#GetInfo is called.
func deviceForAddEth(info ethernet.Info, t *testing.T) ethernetext.Device {
return ethernetext.Device{
TB: t,
GetInfoImpl: func() (ethernet.Info, error) { return info, nil },
SetClientNameImpl: func(string) (int32, error) { return 0, nil },
GetStatusImpl: func() (ethernet.DeviceStatus, error) {
return ethernet.DeviceStatusOnline, nil
},
GetFifosImpl: func() (int32, *ethernet.Fifos, error) {
return int32(zx.ErrOk), &ethernet.Fifos{
TxDepth: 1,
}, nil
},
SetIoBufferImpl: func(zx.VMO) (int32, error) {
return int32(zx.ErrOk), nil
},
StartImpl: func() (int32, error) {
return int32(zx.ErrOk), nil
},
ConfigMulticastSetPromiscuousModeImpl: func(bool) (int32, error) {
return int32(zx.ErrOk), nil
},
}
}