| // 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)) |
| } |