blob: 966b47314f3cacb8e3f8438c0ef71c931e93de60 [file] [log] [blame]
// Copyright 2020 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
// +build !build_with_native_toolchain
package netstack
import (
"context"
"errors"
"fmt"
"net"
"syscall/zx"
"testing"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/dhcp"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/sync"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/time"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/util"
fidlnet "fidl/fuchsia/net"
"fidl/fuchsia/net/interfaces"
"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/stack"
)
func assertWatchResult(gotEvent interfaces.Event, gotErr error, wantEvent interfaces.Event) error {
if gotErr != nil {
return fmt.Errorf("Watch failed: %w", gotErr)
}
if diff := cmp.Diff(wantEvent, gotEvent, cmpopts.IgnoreTypes(struct{}{}), cmpopts.EquateEmpty()); diff != "" {
return fmt.Errorf("(-want +got)\n%s", diff)
}
return nil
}
type watchResult struct {
event interfaces.Event
err error
}
type watcherHelper struct {
*interfaces.WatcherWithCtxInterface
}
func initWatcher(t *testing.T, si *interfaceStateImpl) watcherHelper {
request, watcher, err := interfaces.NewWatcherWithCtxInterfaceRequest()
if err != nil {
t.Fatalf("failed to create Watcher protocol channel pair: %s", err)
}
if err := si.GetWatcher(context.Background(), interfaces.WatcherOptions{}, request); err != nil {
t.Fatalf("failed to call GetWatcher: %s", err)
}
return watcherHelper{
WatcherWithCtxInterface: watcher,
}
}
func (w *watcherHelper) expectIdleEvent(t *testing.T) {
w.expectEvent(t, interfaces.EventWithIdle(interfaces.Empty{}))
}
func (w *watcherHelper) expectEvent(t *testing.T, want interfaces.Event) {
event, err := w.Watch(context.Background())
if err := assertWatchResult(event, err, want); err != nil {
t.Fatal(err)
}
}
func (w *watcherHelper) blockingWatch(t *testing.T, ch chan watchResult) {
go func() {
event, err := w.Watch(context.Background())
ch <- watchResult{
event: event,
err: err,
}
}()
select {
case got := <-ch:
t.Fatalf("Watch did not block and completed with: %#v", got)
case <-time.After(50 * time.Millisecond):
}
}
func TestInterfacesWatcherDisallowMultiplePending(t *testing.T) {
addGoleakCheck(t)
eventChan := make(chan interfaceEvent)
watcherChan := make(chan interfaces.WatcherWithCtxInterfaceRequest)
go interfaceWatcherEventLoop(eventChan, watcherChan)
si := &interfaceStateImpl{watcherChan: watcherChan}
watcher := initWatcher(t, si)
watcher.expectIdleEvent(t)
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err := watcher.Watch(context.Background())
var gotErr *zx.Error
if !(errors.As(err, &gotErr) && gotErr.Status == zx.ErrPeerClosed) {
t.Errorf("got watcher.Watch() = (_, %s), want %s", err, zx.ErrPeerClosed)
}
}()
}
}
func TestInterfacesWatcherExisting(t *testing.T) {
addGoleakCheck(t)
eventChan := make(chan interfaceEvent)
watcherChan := make(chan interfaces.WatcherWithCtxInterfaceRequest)
go interfaceWatcherEventLoop(eventChan, watcherChan)
ns, _ := newNetstack(t, netstackTestOptions{interfaceEventChan: eventChan})
si := &interfaceStateImpl{watcherChan: watcherChan}
ifs := addNoopEndpoint(t, ns, "")
watcher := initWatcher(t, si)
defer func() {
if err := watcher.Close(); err != nil {
t.Fatalf("failed to close watcher: %s", err)
}
}()
watcher.expectEvent(t, interfaces.EventWithExisting(initialProperties(ifs, ns.name(ifs.nicid))))
watcher.expectIdleEvent(t)
}
func TestInterfacesWatcher(t *testing.T) {
addGoleakCheck(t)
eventChan := make(chan interfaceEvent)
watcherChan := make(chan interfaces.WatcherWithCtxInterfaceRequest)
go interfaceWatcherEventLoop(eventChan, watcherChan)
ns, _ := newNetstack(t, netstackTestOptions{interfaceEventChan: eventChan})
si := &interfaceStateImpl{watcherChan: watcherChan}
// The first watcher will always block, while the second watcher should never block.
blockingWatcher, nonBlockingWatcher := initWatcher(t, si), initWatcher(t, si)
ch := make(chan watchResult)
defer func() {
// NB: The blocking watcher closed at the end of the test instead of deferred as
// additional assertions are made with it.
if err := nonBlockingWatcher.Close(); err != nil {
t.Fatalf("failed to close non-blocking watcher: %s", err)
}
close(ch)
}()
blockingWatcher.expectIdleEvent(t)
nonBlockingWatcher.expectIdleEvent(t)
blockingWatcher.blockingWatch(t, ch)
// Add an interface.
ifs := addNoopEndpoint(t, ns, "")
verifyWatchResults := func(t *testing.T, wantEvent interfaces.Event) {
t.Helper()
nonBlockingWatcher.expectEvent(t, wantEvent)
got := <-ch
if err := assertWatchResult(got.event, got.err, wantEvent); err != nil {
t.Fatalf("blocked watch failed: %s", err)
}
}
verifyWatchResults(t, interfaces.EventWithAdded(initialProperties(ifs, ns.name(ifs.nicid))))
// Set interface up.
blockingWatcher.blockingWatch(t, ch)
if err := ifs.Up(); err != nil {
t.Fatalf("failed to set interface up: %s", err)
}
var id interfaces.Properties
id.SetId(uint64(ifs.nicid))
online := id
online.SetOnline(true)
verifyWatchResults(t, interfaces.EventWithChanged(online))
// Add an address.
blockingWatcher.blockingWatch(t, ch)
protocolAddr := tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(net.IPv4(192, 168, 0, 1).To4()),
PrefixLen: 16,
},
}
if ok, reason := ifs.addAddress(protocolAddr, stack.AddressProperties{}); !ok {
t.Fatalf("ifs.addAddress(%s, {}): %s", protocolAddr.AddressWithPrefix, reason)
}
{
addressAdded := id
var address interfaces.Address
address.SetAddr(fidlconv.ToNetSubnet(protocolAddr.AddressWithPrefix))
address.SetValidUntil(int64(zx.TimensecInfinite))
addressAdded.SetAddresses([]interfaces.Address{address})
verifyWatchResults(t, interfaces.EventWithChanged(addressAdded))
}
// Add a default route.
blockingWatcher.blockingWatch(t, ch)
r := defaultV4Route(ifs.nicid, "\x01\x02\x03\x05")
if err := ns.AddRoute(r, metricNotSet, false); err != nil {
t.Fatalf("failed to add default route: %s", err)
}
defaultIpv4RouteAdded := id
defaultIpv4RouteAdded.SetHasDefaultIpv4Route(true)
verifyWatchResults(t, interfaces.EventWithChanged(defaultIpv4RouteAdded))
// Remove the default route.
blockingWatcher.blockingWatch(t, ch)
_ = ns.DelRoute(r)
defaultIpv4RouteRemoved := id
defaultIpv4RouteRemoved.SetHasDefaultIpv4Route(false)
verifyWatchResults(t, interfaces.EventWithChanged(defaultIpv4RouteRemoved))
// Remove an address.
blockingWatcher.blockingWatch(t, ch)
if zxStatus := ifs.removeAddress(protocolAddr); zxStatus != zx.ErrOk {
t.Fatalf("ifs.removeAddress(%s): %s", protocolAddr.AddressWithPrefix, zxStatus)
}
addressRemoved := id
addressRemoved.SetAddresses([]interfaces.Address{})
verifyWatchResults(t, interfaces.EventWithChanged(addressRemoved))
// DHCP Acquired on the interface.
blockingWatcher.blockingWatch(t, ch)
addr := fidlnet.Ipv4Address{Addr: [4]uint8{192, 168, 0, 4}}
acquiredAddr := tcpip.AddressWithPrefix{Address: tcpip.Address(addr.Addr[:]), PrefixLen: 24}
leaseLength := dhcp.Seconds(10)
initUpdatedAt := time.Monotonic(42)
ifs.dhcpAcquired(context.Background(), tcpip.AddressWithPrefix{}, acquiredAddr, dhcp.Config{UpdatedAt: initUpdatedAt, LeaseLength: leaseLength})
dhcpAddressAdded := id
var address interfaces.Address
address.SetAddr(fidlnet.Subnet{
Addr: fidlnet.IpAddressWithIpv4(addr),
PrefixLen: uint8(acquiredAddr.PrefixLen),
})
address.SetValidUntil(int64(zx.TimensecInfinite))
dhcpAddressAdded.SetAddresses([]interfaces.Address{address})
verifyWatchResults(t, interfaces.EventWithChanged(dhcpAddressAdded))
address.SetValidUntil(initUpdatedAt.Add(leaseLength.Duration()).MonotonicNano())
dhcpValidUntil := id
dhcpValidUntil.SetAddresses([]interfaces.Address{address})
blockingWatcher.expectEvent(t, interfaces.EventWithChanged(dhcpValidUntil))
nonBlockingWatcher.expectEvent(t, interfaces.EventWithChanged(dhcpValidUntil))
// DHCP Acquired with same valid_until does not produce event.
ifs.dhcpAcquired(context.Background(), acquiredAddr, acquiredAddr, dhcp.Config{UpdatedAt: initUpdatedAt, LeaseLength: leaseLength})
blockingWatcher.blockingWatch(t, ch)
// DHCP Acquired with different valid_until.
updatedAt := time.Monotonic(100)
ifs.dhcpAcquired(context.Background(), acquiredAddr, acquiredAddr, dhcp.Config{UpdatedAt: updatedAt, LeaseLength: leaseLength})
dhcpAddressRenewed := id
address.SetValidUntil(updatedAt.Add(leaseLength.Duration()).MonotonicNano())
dhcpAddressRenewed.SetAddresses([]interfaces.Address{address})
verifyWatchResults(t, interfaces.EventWithChanged(dhcpAddressRenewed))
// DHCP Acquired on empty address signaling end of lease.
blockingWatcher.blockingWatch(t, ch)
ifs.dhcpAcquired(context.Background(), acquiredAddr, tcpip.AddressWithPrefix{}, dhcp.Config{})
dhcpExpired := id
dhcpExpired.SetAddresses([]interfaces.Address{})
verifyWatchResults(t, interfaces.EventWithChanged(dhcpExpired))
// Set interface down.
blockingWatcher.blockingWatch(t, ch)
if err := ifs.Down(); err != nil {
t.Fatalf("failed to set interface down: %s", err)
}
offline := id
offline.SetOnline(false)
verifyWatchResults(t, interfaces.EventWithChanged(offline))
// Remove the interface.
blockingWatcher.blockingWatch(t, ch)
ifs.RemoveByUser()
verifyWatchResults(t, interfaces.EventWithRemoved(uint64(ifs.nicid)))
blockingWatcher.blockingWatch(t, ch)
if err := blockingWatcher.Close(); err != nil {
t.Errorf("failed to close blocking Watcher client proxy: %s", err)
}
got, want := <-ch, zx.ErrCanceled
switch err := got.err.(type) {
case *zx.Error:
if err.Status != want {
t.Fatalf("unexpected zxStatus; got: %s, want: %s", err.Status, want)
}
default:
t.Fatalf("unexpected return value from Watch resolved due to channel closure; got: %#v, want: %s", got, want)
}
}
func TestInterfacesWatcherDuplicateAddress(t *testing.T) {
eventChan := make(chan interfaceEvent)
watcherChan := make(chan interfaces.WatcherWithCtxInterfaceRequest)
go interfaceWatcherEventLoop(eventChan, watcherChan)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
protocolAddr := tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: util.Parse("abcd::1"),
PrefixLen: 64,
},
}
ndpDisp := newNDPDispatcherForTest()
ndpDisp.getAddressPrefix = func(_ *stack.NICInfo, addr tcpip.Address) (int, bool) {
if want := protocolAddr.AddressWithPrefix.Address; addr != want {
t.Fatalf("getAddressPrefix got addr=%s, want addr=%s", addr, want)
return 0, false
}
return protocolAddr.AddressWithPrefix.PrefixLen, true
}
ns, _ := newNetstack(t, netstackTestOptions{interfaceEventChan: eventChan, ndpDisp: ndpDisp})
ndpDisp.start(ctx)
si := &interfaceStateImpl{watcherChan: watcherChan}
ifs := addNoopEndpoint(t, ns, "")
watcher := initWatcher(t, si)
defer func() {
if err := watcher.Close(); err != nil {
t.Fatalf("failed to close watcher: %s", err)
}
}()
watcher.expectEvent(t, interfaces.EventWithExisting(initialProperties(ifs, ns.name(ifs.nicid))))
watcher.expectIdleEvent(t)
// Add an IPv6 address, should not observe the address until DAD success.
ifs.addAddress(protocolAddr, stack.AddressProperties{})
resultCh := make(chan watchResult, 1)
watcher.blockingWatch(t, resultCh)
// Fake a DAD succeeded event and observe the address.
ndpDisp.OnDuplicateAddressDetectionResult(ifs.nicid, protocolAddr.AddressWithPrefix.Address, &stack.DADSucceeded{})
var wantAddress interfaces.Address
wantAddress.SetAddr(fidlconv.ToNetSubnet(protocolAddr.AddressWithPrefix))
wantAddress.SetValidUntil(int64(zx.TimensecInfinite))
{
var wantProperties interfaces.Properties
wantProperties.SetId(uint64(ifs.nicid))
wantProperties.SetAddresses([]interfaces.Address{wantAddress})
watchResult := <-resultCh
if err := assertWatchResult(watchResult.event, watchResult.err, interfaces.EventWithChanged(wantProperties)); err != nil {
t.Fatal(err)
}
}
// Fake another DAD completion event. Note that DAD is normally only re-run
// after an interface goes down and back up, but since that is not relevant
// to this test we forego that step. Expect nothing on the watcher.
ndpDisp.OnDuplicateAddressDetectionResult(ifs.nicid, protocolAddr.AddressWithPrefix.Address, &stack.DADSucceeded{})
watcher.blockingWatch(t, resultCh)
// Remove the address and observe removal.
if status := ifs.removeAddress(protocolAddr); status != zx.ErrOk {
t.Fatalf("ifs.removeAddress(%#v) = %s", protocolAddr, status)
}
{
var wantProperties interfaces.Properties
wantProperties.SetId(uint64(ifs.nicid))
wantProperties.SetAddresses([]interfaces.Address{})
watchResult := <-resultCh
if err := assertWatchResult(watchResult.event, watchResult.err, interfaces.EventWithChanged(wantProperties)); err != nil {
t.Fatal(err)
}
}
}
// TestInterfacesWatcherDeepCopyAddresses ensures that changes to address
// properties do not get retroactively applied to events enqueued in the past.
func TestInterfacesWatcherDeepCopyAddresses(t *testing.T) {
eventChan := make(chan interfaceEvent)
watcherChan := make(chan interfaces.WatcherWithCtxInterfaceRequest)
go interfaceWatcherEventLoop(eventChan, watcherChan)
ns, _ := newNetstack(t, netstackTestOptions{interfaceEventChan: eventChan})
si := &interfaceStateImpl{watcherChan: watcherChan}
ifs := addNoopEndpoint(t, ns, "")
// Add an address.
protocolAddr := tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(net.IPv4(1, 2, 3, 4).To4()),
PrefixLen: 16,
},
}
if ok, reason := ifs.addAddress(protocolAddr, stack.AddressProperties{}); !ok {
t.Fatalf("ifs.addAddress(%s, {}): %s", protocolAddr.AddressWithPrefix, reason)
}
// Initialize a watcher so that there is a queued Existing event with the
// address.
watcher := initWatcher(t, si)
defer func() {
if err := watcher.Close(); err != nil {
t.Fatalf("failed to close watcher: %s", err)
}
}()
validUntil1 := time.Hour.Nanoseconds()
validUntil2 := 2 * validUntil1
ifs.mu.Lock()
ifs.ns.onAddressValidUntilChangeLocked(ifs.nicid, protocolAddr.AddressWithPrefix, time.Monotonic(validUntil1))
ifs.ns.onAddressValidUntilChangeLocked(ifs.nicid, protocolAddr.AddressWithPrefix, time.Monotonic(validUntil2))
ifs.mu.Unlock()
// Read all the queued events.
var wantAddr interfaces.Address
wantAddr.SetAddr(fidlconv.ToNetSubnet(protocolAddr.AddressWithPrefix))
wantAddr.SetValidUntil(int64(zx.TimensecInfinite))
wantProperties := initialProperties(ifs, ns.name(ifs.nicid))
wantProperties.SetAddresses([]interfaces.Address{wantAddr})
watcher.expectEvent(t, interfaces.EventWithExisting(wantProperties))
watcher.expectIdleEvent(t)
var wantChange interfaces.Properties
wantChange.SetId(uint64(ifs.nicid))
wantAddr.SetValidUntil(validUntil1)
wantChange.SetAddresses([]interfaces.Address{wantAddr})
watcher.expectEvent(t, interfaces.EventWithChanged(wantChange))
wantAddr.SetValidUntil(validUntil2)
wantChange.SetAddresses([]interfaces.Address{wantAddr})
watcher.expectEvent(t, interfaces.EventWithChanged(wantChange))
}