| // 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. |
| |
| // +build !build_with_native_toolchain |
| |
| package netstack |
| |
| import ( |
| "context" |
| "fmt" |
| "net" |
| "runtime" |
| "syscall/zx" |
| "testing" |
| "time" |
| |
| fidlnet "fidl/fuchsia/net" |
| "fidl/fuchsia/net/interfaces" |
| "fidl/fuchsia/netstack" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| ) |
| |
| func TestDiffInterfaceProperties(t *testing.T) { |
| var addr1 interfaces.Address |
| addr1.SetAddr(fidlnet.Subnet{ |
| Addr: toIpAddress(net.IPv4(192, 168, 0, 1).To4()), |
| PrefixLen: 16, |
| }) |
| var addr2 interfaces.Address |
| addr2.SetAddr(fidlnet.Subnet{ |
| Addr: toIpAddress(net.IPv4(192, 168, 0, 2).To4()), |
| PrefixLen: 16, |
| }) |
| |
| var p interfaces.Properties |
| p.SetId(1) |
| p.SetAddresses([]interfaces.Address{addr1}) |
| p.SetOnline(false) |
| p.SetHasDefaultIpv4Route(true) |
| p.SetHasDefaultIpv6Route(false) |
| |
| var diff interfaces.Properties |
| diff.SetId(1) |
| |
| online := p |
| online.SetOnline(true) |
| onlineDiff := diff |
| onlineDiff.SetOnline(true) |
| |
| defaultRouteAdded := p |
| defaultRouteAdded.SetHasDefaultIpv6Route(true) |
| defaultRouteAddedDiff := diff |
| defaultRouteAddedDiff.SetHasDefaultIpv6Route(true) |
| |
| defaultRouteRemoved := p |
| defaultRouteRemoved.SetHasDefaultIpv4Route(false) |
| defaultRouteRemovedDiff := diff |
| defaultRouteRemovedDiff.SetHasDefaultIpv4Route(false) |
| |
| addressRemoved := p |
| addressRemoved.SetAddresses(nil) |
| addressRemovedDiff := diff |
| addressRemovedDiff.SetAddresses(nil) |
| |
| addressAdded := p |
| addressAdded.SetAddresses([]interfaces.Address{addr1, addr2}) |
| addressAddedDiff := diff |
| addressAddedDiff.SetAddresses([]interfaces.Address{addr1, addr2}) |
| |
| addressChanged := p |
| addressChanged.SetAddresses([]interfaces.Address{addr2}) |
| addressChangedDiff := diff |
| addressChangedDiff.SetAddresses([]interfaces.Address{addr2}) |
| |
| for _, tc := range []struct { |
| name string |
| before interfaces.Properties |
| after interfaces.Properties |
| wantDiff interfaces.Properties |
| }{ |
| {"Online", p, online, onlineDiff}, |
| {"DefaultRouteAdded", p, defaultRouteAdded, defaultRouteAddedDiff}, |
| {"DefaultRouteRemoved", p, defaultRouteRemoved, defaultRouteRemovedDiff}, |
| {"AddressRemoved", p, addressRemoved, addressRemovedDiff}, |
| {"AddressAdded", p, addressAdded, addressAddedDiff}, |
| {"AddressChanged", p, addressChanged, addressChangedDiff}, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| gotDiff := diffInterfaceProperties(tc.before, tc.after) |
| if diff := cmp.Diff(tc.wantDiff, gotDiff, cmpopts.IgnoreTypes(struct{}{}), cmpopts.EquateEmpty()); diff != "" { |
| t.Fatalf("(-want +got)\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| type watchResult struct { |
| event interfaces.Event |
| err error |
| } |
| |
| func assertWatchResult(gotEvent interfaces.Event, gotErr error, wantEvent interfaces.Event) error { |
| if gotErr != nil { |
| return fmt.Errorf("Watch failed: %s", gotErr) |
| } |
| if diff := cmp.Diff(wantEvent, gotEvent, cmpopts.IgnoreTypes(struct{}{}), cmpopts.EquateEmpty()); diff != "" { |
| return fmt.Errorf("(-want +got)\n%s", diff) |
| } |
| return nil |
| } |
| |
| func wantInterfaceProperties(ns *Netstack, nicid tcpip.NICID) interfaces.Properties { |
| var hasDefaultIpv4Route, hasDefaultIpv6Route bool |
| for _, er := range ns.GetExtendedRouteTable() { |
| if er.Enabled && er.Route.NIC == nicid { |
| if er.Route.Destination.Equal(header.IPv4EmptySubnet) { |
| hasDefaultIpv4Route = true |
| } else if er.Route.Destination.Equal(header.IPv6EmptySubnet) { |
| hasDefaultIpv6Route = true |
| } |
| } |
| } |
| return interfaceProperties(ns.stack.NICInfo()[nicid], hasDefaultIpv4Route, hasDefaultIpv6Route) |
| } |
| |
| func TestInterfacesWatcher(t *testing.T) { |
| ns := newNetstack(t) |
| ni := &netstackImpl{ns: ns} |
| si := &interfaceStateImpl{ns: ns} |
| |
| nicid := func() tcpip.NICID { |
| ifs := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs.Remove) |
| return ifs.nicid |
| }() |
| |
| // The first watcher will always block, while the second watcher should never block. |
| initWatcher := func() *interfaces.WatcherWithCtxInterface { |
| 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) |
| } |
| event, err := watcher.Watch(context.Background()) |
| if err := assertWatchResult(event, err, interfaces.EventWithExisting(wantInterfaceProperties(ns, nicid))); err != nil { |
| t.Fatal(err) |
| } |
| event, err = watcher.Watch(context.Background()) |
| if err := assertWatchResult(event, err, interfaces.EventWithIdle(interfaces.Empty{})); err != nil { |
| t.Fatal(err) |
| } |
| return watcher |
| } |
| blockingWatcher, nonBlockingWatcher := initWatcher(), initWatcher() |
| |
| ch := make(chan watchResult) |
| blockingWatch := func() { |
| go func() { |
| event, err := blockingWatcher.Watch(context.Background()) |
| ch <- watchResult{ |
| event: event, |
| err: err, |
| } |
| }() |
| select { |
| case got := <-ch: |
| t.Fatalf("WatchServers did not block and completed with: %+v", got) |
| case <-time.After(50 * time.Millisecond): |
| } |
| } |
| |
| defer func() { |
| getSize := func() int { |
| ns.interfaceWatchers.mu.Lock() |
| defer ns.interfaceWatchers.mu.Unlock() |
| return len(ns.interfaceWatchers.mu.watchers) |
| } |
| |
| blockingWatch() |
| |
| before := getSize() |
| |
| if err := blockingWatcher.Close(); err != nil { |
| t.Errorf("failed to close blocking Watcher client proxy: %s", err) |
| } |
| |
| func() { |
| got, want := <-ch, zx.ErrCanceled |
| switch err := got.err.(type) { |
| case nil: |
| t.Errorf("got Watch() = (%#v, %v), want = (_, %s)", got.event, got.err, want) |
| case *zx.Error: |
| if err.Status == want { |
| return |
| } |
| } |
| t.Errorf("got Watch() = (_, %s), want = (_, %s)", got.err, want) |
| }() |
| close(ch) |
| |
| // The handler should terminate after the client channel closes. |
| for getSize() != before-1 { |
| runtime.Gosched() |
| } |
| |
| if err := nonBlockingWatcher.Close(); err != nil { |
| t.Errorf("failed to close non-blocking Watcher client proxy: %s", err) |
| } |
| }() |
| |
| blockingWatch() |
| |
| // Add an interface. |
| ifs := addNoopEndpoint(t, ns, "") |
| |
| verifyWatchResults := func(wantEvent interfaces.Event) { |
| got := <-ch |
| event, err := nonBlockingWatcher.Watch(context.Background()) |
| if err := assertWatchResult(got.event, got.err, wantEvent); err != nil { |
| t.Fatalf("blocked watch failed: %s", err) |
| } |
| if err := assertWatchResult(event, err, wantEvent); err != nil { |
| t.Fatalf("non-blocked watch failed: %s", err) |
| } |
| } |
| verifyWatchResults(interfaces.EventWithAdded(wantInterfaceProperties(ns, ifs.nicid))) |
| |
| // Set interface up. |
| blockingWatch() |
| 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(interfaces.EventWithChanged(online)) |
| |
| // Add an address. |
| blockingWatch() |
| addr := toIpAddress(net.IPv4(192, 168, 0, 1).To4()) |
| prefixLen := uint8(16) |
| if netErr, err := ni.SetInterfaceAddress(context.Background(), uint32(ifs.nicid), addr, prefixLen); netErr.Status != netstack.StatusOk || err != nil { |
| t.Fatalf("failed to call SetInterfaceAddress: netErr=%s, err=%s", netErr, err) |
| } |
| addressAdded := id |
| properties := wantInterfaceProperties(ns, ifs.nicid) |
| addressAdded.SetAddresses(properties.GetAddresses()) |
| verifyWatchResults(interfaces.EventWithChanged(addressAdded)) |
| |
| // Add a default route. |
| blockingWatch() |
| r := defaultV4Route(ifs.nicid, tcpip.Address(net.IPv4(192, 168, 0, 2).To4())) |
| if err := ns.AddRoute(r, metricNotSet, false); err != nil { |
| t.Fatalf("failed to add default route: %s", err) |
| } |
| defaultIpv4RouteAdded := id |
| defaultIpv4RouteAdded.SetHasDefaultIpv4Route(true) |
| verifyWatchResults(interfaces.EventWithChanged(defaultIpv4RouteAdded)) |
| |
| // Remove the default route. |
| blockingWatch() |
| if err := ns.DelRoute(r); err != nil { |
| t.Fatalf("failed to delete default route: %s", err) |
| } |
| defaultIpv4RouteRemoved := id |
| defaultIpv4RouteRemoved.SetHasDefaultIpv4Route(false) |
| verifyWatchResults(interfaces.EventWithChanged(defaultIpv4RouteRemoved)) |
| |
| // Remove an address. |
| blockingWatch() |
| if netErr, err := ni.RemoveInterfaceAddress(context.Background(), uint32(ifs.nicid), addr, prefixLen); netErr.Status != netstack.StatusOk || err != nil { |
| t.Fatalf("failed to call RemoveInterfaceAddress: netErr=%s, err=%s", netErr, err) |
| } |
| addressRemoved := id |
| properties = wantInterfaceProperties(ns, ifs.nicid) |
| addressRemoved.SetAddresses(properties.GetAddresses()) |
| verifyWatchResults(interfaces.EventWithChanged(addressRemoved)) |
| |
| // Set interface down. |
| blockingWatch() |
| if err := ifs.Down(); err != nil { |
| t.Fatalf("failed to set interface down: %s", err) |
| } |
| offline := id |
| offline.SetOnline(false) |
| verifyWatchResults(interfaces.EventWithChanged(offline)) |
| |
| // Remove the interface. |
| blockingWatch() |
| ifs.Remove() |
| verifyWatchResults(interfaces.EventWithRemoved(uint64(ifs.nicid))) |
| } |