blob: fb230143817e3084cc1651e2c588ea2dd368738e [file] [log] [blame]
// Copyright 2021 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"
"fmt"
"syscall/zx"
"syscall/zx/fidl"
"syscall/zx/zxwait"
"testing"
"time"
"unsafe"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/netdevice"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/sync"
"go.fuchsia.dev/fuchsia/src/lib/component"
"fidl/fuchsia/hardware/network"
"fidl/fuchsia/net/interfaces/admin"
)
var _ network.DeviceWithCtx = (*fakeNetworkDeviceImpl)(nil)
type fakeNetworkDeviceImpl struct {
t *testing.T
rx, tx zx.Handle
data, descriptors zx.VMO
sessionReq network.SessionWithCtxInterfaceRequest
}
const fakeDepth = 128
func (f *fakeNetworkDeviceImpl) GetInfo(fidl.Context) (network.DeviceInfo, error) {
var baseInfo network.DeviceBaseInfo
baseInfo.SetRxDepth(fakeDepth)
baseInfo.SetTxDepth(fakeDepth)
baseInfo.SetBufferAlignment(1)
baseInfo.SetMaxBufferLength(2048)
baseInfo.SetMinRxBufferLength(2048)
baseInfo.SetMinTxBufferLength(0)
baseInfo.SetMinTxBufferHead(0)
baseInfo.SetMinTxBufferTail(0)
baseInfo.SetMaxBufferParts(1)
var info network.DeviceInfo
info.SetMinDescriptorLength(uint8(netdevice.DescriptorLength / 8))
info.SetDescriptorVersion(netdevice.DescriptorVersion)
info.SetBaseInfo(baseInfo)
return info, nil
}
func (f *fakeNetworkDeviceImpl) OpenSession(_ fidl.Context, sessionName string, sessionInfo network.SessionInfo) (network.DeviceOpenSessionResult, error) {
const elemCount = fakeDepth * 2
const elemSize = 2
f.data = sessionInfo.Data
f.descriptors = sessionInfo.Descriptors
t := f.t
var rx, tx zx.Handle
if status := zx.Sys_fifo_create(elemCount, elemSize, 0, &rx, &f.rx); status != zx.ErrOk {
err := &zx.Error{Status: status, Text: "FIFO create"}
t.Errorf("zx.Sys_fifo_create(_, _, _, _) = %s", err)
return network.DeviceOpenSessionResult{}, err
}
t.Cleanup(func() {
if err := rx.Close(); err != nil {
t.Errorf("rx.Close() = %s", err)
}
})
if status := zx.Sys_fifo_create(elemCount, elemSize, 0, &tx, &f.tx); status != zx.ErrOk {
err := &zx.Error{Status: status, Text: "FIFO create"}
t.Errorf("zx.Sys_fifo_create(_, _, _, _) = %s", err)
return network.DeviceOpenSessionResult{}, err
}
t.Cleanup(func() {
if err := tx.Close(); err != nil {
t.Errorf("tx.Close() = %s", err)
}
})
sessionReq, session, err := network.NewSessionWithCtxInterfaceRequest()
if err != nil {
t.Errorf("network.NewSessionWithCtxInterfaceRequest() = %s", err)
return network.DeviceOpenSessionResult{}, err
}
f.sessionReq = sessionReq
result := network.DeviceOpenSessionResultWithResponse(
network.DeviceOpenSessionResponse{
Session: *session,
Fifos: network.Fifos{Rx: rx, Tx: tx},
})
// Prevent test cleanup.
rx = zx.HandleInvalid
tx = zx.HandleInvalid
return result, nil
}
func (f *fakeNetworkDeviceImpl) GetPort(_ fidl.Context, id network.PortId, _ network.PortWithCtxInterfaceRequest) error {
f.t.Fatalf("unexpected GetPort(_, %#v, _) call", id)
return nil
}
func (f *fakeNetworkDeviceImpl) GetPortWatcher(fidl.Context, network.PortWatcherWithCtxInterfaceRequest) error {
f.t.Fatalf("unexpected GetPortWatcher(_, _) call")
return nil
}
func (f *fakeNetworkDeviceImpl) Clone(fidl.Context, network.DeviceWithCtxInterfaceRequest) error {
f.t.Fatalf("unexpected Clone(_, _) call")
return nil
}
func TestClosesNetworkDevice(t *testing.T) {
ns, _ := newNetstack(t, netstackTestOptions{})
installer := &interfacesAdminInstallerImpl{ns: ns}
const (
dropDevice = iota
dropControl
)
for _, drop := range []int{dropDevice, dropControl} {
for _, detach := range []bool{true, false} {
if drop == dropControl && detach {
// Dropping control with detach is not a valid use case since it doesn't
// cause the device to close.
continue
}
name := fmt.Sprintf("drop%s%s", func() string {
switch drop {
case dropDevice:
return "Device"
case dropControl:
return "Control"
default:
panic(fmt.Sprintf("unknown drop value %d", drop))
}
}(), func() string {
if detach {
return "Detach"
}
return "NoDetach"
}())
t.Run(name, func(t *testing.T) {
deviceReq, device, err := network.NewDeviceWithCtxInterfaceRequest()
if err != nil {
t.Fatalf("network.NewDeviceWithCtxInterfaceRequest() = %s", err)
}
t.Cleanup(func() {
if err := deviceReq.Close(); err != nil {
t.Errorf("deviceReq.Close() = %s", err)
}
if err := device.Close(); err != nil {
t.Errorf("device.Close() = %s", err)
}
})
controlReq, control, err := admin.NewDeviceControlWithCtxInterfaceRequest()
if err != nil {
t.Fatalf("admin.NewDeviceControlWithCtxInterfaceRequest() = %s", err)
}
t.Cleanup(func() {
if err := controlReq.Close(); err != nil {
t.Errorf("controlReq.Close() = %s", err)
}
if err := control.Close(); err != nil {
t.Errorf("control.Close() = %s", err)
}
})
if detach {
if err := control.Detach(context.Background()); err != nil {
t.Fatalf("control.Detach(_) = %s", err)
}
}
fakeDevice := &fakeNetworkDeviceImpl{t: t}
t.Cleanup(func() {
if err := fakeDevice.tx.Close(); err != nil {
t.Errorf("fakeDevice.tx.Close() = %s", err)
}
if err := fakeDevice.rx.Close(); err != nil {
t.Errorf("fakeDevice.rx.Close() = %s", err)
}
if err := fakeDevice.sessionReq.Close(); err != nil {
t.Errorf("fakeDevice.sessionReq.Close() = %s", err)
}
if err := fakeDevice.data.Close(); err != nil {
t.Errorf("fakeDevice.data.Close() = %s", err)
}
if err := fakeDevice.descriptors.Close(); err != nil {
t.Errorf("fakeDevice.descriptors.Close() = %s", err)
}
})
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
defer func() {
cancel()
wg.Wait()
}()
go func() {
defer wg.Done()
component.Serve(ctx, &network.DeviceWithCtxStub{Impl: fakeDevice}, deviceReq.Channel, component.ServeOptions{
OnError: func(err error) {
t.Errorf("OnError: %s", err)
},
// Channel is closed by test cleanup.
KeepChannelAlive: true,
})
}()
if err := installer.InstallDevice(context.Background(), *device, controlReq); err != nil {
t.Fatalf("installer.InstallDevice(_, _, _) = %s", err)
}
// Prevent test cleanup.
*device.Handle() = zx.HandleInvalid
*controlReq.Handle() = zx.HandleInvalid
channelHandle := func() *zx.Handle {
switch drop {
case dropDevice:
// Drop the device and observe control handle close.
if err := fakeDevice.rx.Close(); err != nil {
t.Fatalf("fakeDevice.rx.Close() = %s", err)
}
return control.Handle()
case dropControl:
// Drop control and observe the session handle close.
if err := control.Close(); err != nil {
t.Fatalf("control.Close() = %s", err)
}
return fakeDevice.sessionReq.Handle()
default:
panic(fmt.Sprintf("unknown drop value %d", drop))
}
}()
signals, err := zxwait.WaitContext(context.Background(), *channelHandle, zx.SignalChannelPeerClosed)
if err != nil {
t.Fatalf("zxwait.Waitcontext(_, _, _) = %s", err)
}
if got, want := signals, zx.Signals(zx.SignalChannelPeerClosed); got != want {
t.Errorf("got zxwait.WaitContext(_, _, _) = %d, want %d", got, want)
}
// There's no synchronization guaranteeing that the VMOs are already unmapped.
// We must poll until we observe zero mappings.
waitVmoUnmapped := func(name string, vmo zx.VMO) {
for {
var info zx.InfoVmo
if err := vmo.Handle().GetInfo(zx.ObjectInfoVMO, unsafe.Pointer(&info), uint(unsafe.Sizeof(info))); err != nil {
t.Fatalf("%s vmo.Handle().GetInfo(_, _, _) = %s", name, err)
}
switch n := info.NumMappings; n {
case 0:
return
case 1:
t.Logf("%s VMO still has one mapping, waiting...", name)
time.Sleep(10 * time.Millisecond)
default:
t.Fatalf("%s info.NumMappings = %d, expected <= 1", name, n)
}
}
}
waitVmoUnmapped("data", fakeDevice.data)
waitVmoUnmapped("descriptors", fakeDevice.descriptors)
})
}
}
}