| // 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. |
| |
| package netdevice |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/binary" |
| "fmt" |
| "math/rand" |
| "os" |
| "runtime" |
| "sync" |
| "testing" |
| "time" |
| |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/eth" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/testutil" |
| "go.fuchsia.dev/fuchsia/src/lib/component" |
| syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go" |
| |
| "fidl/fuchsia/hardware/network" |
| "fidl/fuchsia/net" |
| "fidl/fuchsia/net/tun" |
| |
| "github.com/google/go-cmp/cmp" |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/buffer" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "gvisor.dev/gvisor/pkg/tcpip/stack" |
| ) |
| |
| type DeliverNetworkPacketArgs struct { |
| SrcLinkAddr, DstLinkAddr tcpip.LinkAddress |
| Protocol tcpip.NetworkProtocolNumber |
| Pkt *stack.PacketBuffer |
| } |
| |
| type dispatcherChan chan DeliverNetworkPacketArgs |
| |
| var _ stack.NetworkDispatcher = (*dispatcherChan)(nil) |
| |
| func (ch *dispatcherChan) DeliverNetworkPacket(srcLinkAddr, dstLinkAddr tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { |
| *ch <- DeliverNetworkPacketArgs{ |
| SrcLinkAddr: srcLinkAddr, |
| DstLinkAddr: dstLinkAddr, |
| Protocol: protocol, |
| Pkt: pkt, |
| } |
| } |
| |
| func (*dispatcherChan) DeliverOutboundPacket(_, _ tcpip.LinkAddress, _ tcpip.NetworkProtocolNumber, _ *stack.PacketBuffer) { |
| } |
| |
| const TunMtu uint32 = 2048 |
| const TunMinTxLength int = 60 |
| |
| func getTunMac() net.MacAddress { |
| return net.MacAddress{Octets: [6]uint8{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}} |
| } |
| func getOtherMac() net.MacAddress { |
| return net.MacAddress{Octets: [6]uint8{0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE}} |
| } |
| |
| var lazyComponentContext struct { |
| once sync.Once |
| ctx *component.Context |
| } |
| |
| func componentCtx() *component.Context { |
| lazyComponentContext.once.Do(func() { lazyComponentContext.ctx = component.NewContextFromStartupInfo() }) |
| return lazyComponentContext.ctx |
| } |
| |
| func tunCtl(t *testing.T) *tun.ControlWithCtxInterface { |
| t.Helper() |
| req, tunCtl, err := tun.NewControlWithCtxInterfaceRequest() |
| if err != nil { |
| t.Fatalf("failed to create tunctl request: %s", err) |
| } |
| componentCtx().ConnectToEnvService(req) |
| t.Cleanup(func() { |
| if err := tunCtl.Close(); err != nil { |
| t.Errorf("failed to close tunctl: %s", err) |
| } |
| }) |
| return tunCtl |
| } |
| |
| func newTunDeviceRequest(t *testing.T) (tun.DeviceWithCtxInterfaceRequest, *tun.DeviceWithCtxInterface) { |
| t.Helper() |
| deviceRequest, device, err := tun.NewDeviceWithCtxInterfaceRequest() |
| if err != nil { |
| t.Fatalf("failed to create tun device request: %s", err) |
| } |
| t.Cleanup(func() { |
| if err := device.Close(); err != nil { |
| t.Errorf("tun device close failed: %s", err) |
| } |
| }) |
| return deviceRequest, device |
| } |
| |
| func newNetworkDeviceRequest(t *testing.T) (network.DeviceWithCtxInterfaceRequest, *network.DeviceWithCtxInterface) { |
| t.Helper() |
| deviceRequest, device, err := network.NewDeviceWithCtxInterfaceRequest() |
| if err != nil { |
| t.Fatalf("failed to create network device request: %s", err) |
| } |
| t.Cleanup(func() { |
| if err := device.Close(); err != nil { |
| t.Errorf("network device close failed: %s", err) |
| } |
| }) |
| return deviceRequest, device |
| } |
| |
| func newMacAddressingRequest(t *testing.T) (network.MacAddressingWithCtxInterfaceRequest, *network.MacAddressingWithCtxInterface) { |
| t.Helper() |
| macRequest, mac, err := network.NewMacAddressingWithCtxInterfaceRequest() |
| if err != nil { |
| t.Fatalf("failed to create mac addressing request: %s", err) |
| } |
| t.Cleanup(func() { |
| if err := mac.Close(); err != nil { |
| t.Errorf("mac addressing close failed: %s", err) |
| } |
| }) |
| return macRequest, mac |
| } |
| |
| func newTunDevicePairRequest(t *testing.T) (tun.DevicePairWithCtxInterfaceRequest, *tun.DevicePairWithCtxInterface) { |
| t.Helper() |
| deviceRequest, device, err := tun.NewDevicePairWithCtxInterfaceRequest() |
| if err != nil { |
| t.Fatalf("failed to create tun device pair request: %s", err) |
| } |
| t.Cleanup(func() { |
| if err := device.Close(); err != nil { |
| t.Errorf("tun device pair close failed: %s", err) |
| } |
| }) |
| return deviceRequest, device |
| } |
| |
| func createTunWithConfig(t *testing.T, ctx context.Context, config tun.DeviceConfig) *tun.DeviceWithCtxInterface { |
| t.Helper() |
| deviceRequest, device := newTunDeviceRequest(t) |
| if err := tunCtl(t).CreateDevice(ctx, config, deviceRequest); err != nil { |
| t.Fatalf("tunCtl.CreateDevice failed: %s", err) |
| } |
| return device |
| } |
| |
| func createTunWithOnline(t *testing.T, ctx context.Context, online bool) *tun.DeviceWithCtxInterface { |
| t.Helper() |
| var config tun.DeviceConfig |
| config.SetOnline(online) |
| config.SetBlocking(true) |
| config.SetMac(getTunMac()) |
| |
| var base tun.BaseConfig |
| base.SetMtu(TunMtu) |
| base.SetRxTypes([]network.FrameType{network.FrameTypeEthernet}) |
| base.SetTxTypes([]network.FrameTypeSupport{{ |
| Type: network.FrameTypeEthernet, |
| Features: network.FrameFeaturesRaw, |
| SupportedFlags: 0, |
| }}) |
| base.SetMinTxBufferLength(uint32(TunMinTxLength)) |
| |
| config.SetBase(base) |
| return createTunWithConfig(t, ctx, config) |
| } |
| |
| func createTunPair(t *testing.T, ctx context.Context, frameTypes []network.FrameType) *tun.DevicePairWithCtxInterface { |
| t.Helper() |
| txTypes := make([]network.FrameTypeSupport, 0, len(frameTypes)) |
| for _, t := range frameTypes { |
| txTypes = append(txTypes, network.FrameTypeSupport{ |
| Type: t, |
| Features: network.FrameFeaturesRaw, |
| SupportedFlags: 0, |
| }) |
| } |
| var config tun.DevicePairConfig |
| config.SetMacLeft(getTunMac()) |
| config.SetMacRight(getOtherMac()) |
| |
| var base tun.BaseConfig |
| base.SetMtu(TunMtu) |
| base.SetRxTypes(frameTypes) |
| base.SetTxTypes(txTypes) |
| config.SetBase(base) |
| |
| deviceRequest, device := newTunDevicePairRequest(t) |
| if err := tunCtl(t).CreatePair(ctx, config, deviceRequest); err != nil { |
| t.Fatalf("tunCtl.CreatePair failed: %s", err) |
| } |
| return device |
| } |
| |
| func createTunClientPair(t *testing.T, ctx context.Context) (*tun.DeviceWithCtxInterface, *MacAddressingClient) { |
| return createTunClientPairWithOnline(t, ctx, true) |
| } |
| |
| func createTunClientPairWithOnline(t *testing.T, ctx context.Context, online bool) (*tun.DeviceWithCtxInterface, *MacAddressingClient) { |
| t.Helper() |
| tundev := createTunWithOnline(t, ctx, online) |
| netdev, mac := connectProtos(t, ctx, tundev) |
| |
| client, err := NewMacAddressingClient(ctx, netdev, mac, &SimpleSessionConfigFactory{}) |
| if err != nil { |
| t.Fatalf("NewMacAddressingClient failed: %s", err) |
| } |
| |
| t.Cleanup(func() { |
| if err := client.Close(); err != nil { |
| t.Errorf("client close failed: %s", err) |
| } |
| }) |
| return tundev, client |
| } |
| |
| func createDeviceProtocolsRequest(t *testing.T) (*network.DeviceWithCtxInterface, tun.Protocols) { |
| t.Helper() |
| var protocols tun.Protocols |
| devReq, dev := newNetworkDeviceRequest(t) |
| protocols.SetNetworkDevice(devReq) |
| return dev, protocols |
| } |
| |
| func connectProtos(t *testing.T, ctx context.Context, tunDevice *tun.DeviceWithCtxInterface) (*network.DeviceWithCtxInterface, *network.MacAddressingWithCtxInterface) { |
| t.Helper() |
| devReq, dev := newNetworkDeviceRequest(t) |
| macReq, mac := newMacAddressingRequest(t) |
| |
| var protocols tun.Protocols |
| protocols.SetNetworkDevice(devReq) |
| protocols.SetMacAddressing(macReq) |
| |
| if err := tunDevice.ConnectProtocols(ctx, protocols); err != nil { |
| t.Fatalf("tunDevice.ConnectProtocols failed: %s", err) |
| } |
| return dev, mac |
| } |
| |
| func TestMain(m *testing.M) { |
| syslog.SetVerbosity(syslog.DebugVerbosity) |
| os.Exit(m.Run()) |
| } |
| |
| func TestClient_WritePacket(t *testing.T) { |
| ctx := context.Background() |
| |
| tunDev, client := createTunClientPairWithOnline(t, ctx, false) |
| defer func() { |
| if err := tunDev.Close(); err != nil { |
| t.Fatalf("tunDev.Close() failed: %s", err) |
| } |
| client.Wait() |
| }() |
| |
| linkEndpoint := eth.NewLinkEndpoint(client) |
| |
| client.SetOnLinkClosed(func() {}) |
| client.SetOnLinkOnlineChanged(func(bool) {}) |
| |
| dispatcher := make(dispatcherChan) |
| close(dispatcher) |
| linkEndpoint.Attach(&dispatcher) |
| |
| if err := client.Up(); err != nil { |
| t.Fatalf("failed to start client %s", err) |
| } |
| |
| if err := linkEndpoint.WritePacket(&stack.Route{}, nil, header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{ |
| ReserveHeaderBytes: int(linkEndpoint.MaxHeaderLength()), |
| })); err != nil { |
| t.Fatalf("WritePacket failed: %s", err) |
| } |
| |
| // This is unfortunate, but we don't have a way of being notified. |
| now := time.Now() |
| for { |
| if client.handler.Stats.Tx.Drops.Value() == 0 { |
| if time.Since(now) < 10*time.Second { |
| runtime.Gosched() |
| continue |
| } |
| t.Error("drop not incremented") |
| } |
| break |
| } |
| } |
| |
| func TestWritePacket(t *testing.T) { |
| ctx := context.Background() |
| |
| tunDev, client := createTunClientPair(t, ctx) |
| |
| client.SetOnLinkClosed(func() {}) |
| client.SetOnLinkOnlineChanged(func(bool) {}) |
| |
| linkEndpoint := eth.NewLinkEndpoint(client) |
| |
| dispatcher := make(dispatcherChan) |
| linkEndpoint.Attach(&dispatcher) |
| |
| if err := client.Up(); err != nil { |
| t.Fatalf("failed to start client %s", err) |
| } |
| tunMac := getTunMac() |
| otherMac := getOtherMac() |
| const protocol = tcpip.NetworkProtocolNumber(45) |
| const pktBody = "bar" |
| if err := linkEndpoint.WritePacket(&stack.Route{ |
| LocalLinkAddress: tcpip.LinkAddress(tunMac.Octets[:]), |
| RemoteLinkAddress: tcpip.LinkAddress(otherMac.Octets[:]), |
| }, |
| nil, |
| protocol, |
| stack.NewPacketBuffer(stack.PacketBufferOptions{ |
| ReserveHeaderBytes: int(linkEndpoint.MaxHeaderLength()), |
| Data: buffer.View(pktBody).ToVectorisedView(), |
| }), |
| ); err != nil { |
| t.Fatalf("WritePacket failed: %s", err) |
| } |
| readFrameResult, err := tunDev.ReadFrame(ctx) |
| if err != nil { |
| t.Fatalf("failed to read frame from tun device: %s", err) |
| } |
| if readFrameResult.Which() == tun.DeviceReadFrameResultErr { |
| t.Fatalf("failed to read frame from tun: %v", readFrameResult.Err) |
| } |
| if readFrameResult.Response.Frame.FrameType != network.FrameTypeEthernet { |
| t.Errorf("unexpected response frame type: got %v, want: %v", readFrameResult.Response.Frame.FrameType, network.FrameTypeEthernet) |
| } |
| data := readFrameResult.Response.Frame.Data |
| |
| expect := func() []byte { |
| b := make([]byte, 0, TunMinTxLength) |
| b = append(b, otherMac.Octets[:]...) |
| b = append(b, tunMac.Octets[:]...) |
| ethType := [2]byte{0, 0} |
| binary.BigEndian.PutUint16(ethType[:], uint16(protocol)) |
| b = append(b, ethType[:]...) |
| b = append(b, []byte(pktBody)...) |
| if len(b) < TunMinTxLength { |
| b = b[:TunMinTxLength] |
| } |
| return b |
| }() |
| if !bytes.Equal(data, expect) { |
| t.Fatalf("delivered packet mismatch. Wanted %v, got: %v", expect, data) |
| } |
| |
| if err := tunDev.Close(); err != nil { |
| t.Fatalf("tunDev.Close() failed: %s", err) |
| } |
| client.Wait() |
| } |
| |
| func TestReceivePacket(t *testing.T) { |
| ctx := context.Background() |
| |
| tunDev, client := createTunClientPair(t, ctx) |
| |
| client.SetOnLinkClosed(func() {}) |
| client.SetOnLinkOnlineChanged(func(bool) {}) |
| |
| linkEndpoint := eth.NewLinkEndpoint(client) |
| |
| if err := client.Up(); err != nil { |
| t.Fatalf("failed to start client %s", err) |
| } |
| |
| dispatcher := make(dispatcherChan, 1) |
| linkEndpoint.Attach(&dispatcher) |
| |
| tunMac := getTunMac() |
| otherMac := getOtherMac() |
| const protocol = tcpip.NetworkProtocolNumber(45) |
| const pktPayload = "foobarbazfoobar" |
| referenceFrame := func() []uint8 { |
| b := tunMac.Octets[:] |
| b = append(b, otherMac.Octets[:]...) |
| ethType := [2]byte{0, 0} |
| binary.BigEndian.PutUint16(ethType[:], uint16(protocol)) |
| b = append(b, ethType[:]...) |
| b = append(b, []byte(pktPayload)...) |
| return b |
| }() |
| send := func(sendSize int) { |
| var frame tun.Frame |
| frame.SetFrameType(network.FrameTypeEthernet) |
| frame.SetData(referenceFrame[:sendSize]) |
| status, err := tunDev.WriteFrame(ctx, frame) |
| if err != nil { |
| t.Fatalf("WriteFrame failed: %s", err) |
| } |
| if status.Which() == tun.DeviceWriteFrameResultErr { |
| t.Fatalf("unexpected error on WriteFrame: %v", status) |
| } |
| } |
| // First test that if we send something smaller than the minimum Ethernet frame size will not get dispatched. |
| send(header.EthernetMinimumSize - 1) |
| select { |
| case <-time.After(200 * time.Millisecond): |
| case args := <-dispatcher: |
| t.Fatalf("unexpected packet received: %v", args) |
| } |
| |
| ethFields := header.EthernetFields{ |
| SrcAddr: tcpip.LinkAddress(otherMac.Octets[:]), |
| DstAddr: tcpip.LinkAddress(tunMac.Octets[:]), |
| Type: protocol, |
| } |
| wantLinkHdr := make(buffer.View, header.EthernetMinimumSize) |
| header.Ethernet(wantLinkHdr).Encode(ðFields) |
| |
| // Then test some valid configurations. |
| for _, extra := range []int{ |
| // Test receiving a frame that is equal to the minimum frame size. |
| 0, |
| // Test receiving a frame that is just greater than the minimum frame size. |
| 1, |
| // Test receiving the full frame. |
| len(pktPayload), |
| } { |
| send(header.EthernetMinimumSize + extra) |
| args := <-dispatcher |
| if diff := cmp.Diff(args, DeliverNetworkPacketArgs{ |
| SrcLinkAddr: tcpip.LinkAddress(otherMac.Octets[:]), |
| DstLinkAddr: tcpip.LinkAddress(tunMac.Octets[:]), |
| Protocol: protocol, |
| Pkt: func() *stack.PacketBuffer { |
| vv := wantLinkHdr.ToVectorisedView() |
| vv.AppendView(buffer.View(pktPayload[:extra])) |
| pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ |
| Data: vv, |
| }) |
| _, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) |
| if !ok { |
| t.Fatalf("failed to consume %d bytes for link header", header.EthernetMinimumSize) |
| } |
| return pkt |
| }(), |
| }, testutil.PacketBufferCmpTransformer); diff != "" { |
| t.Fatalf("delivered network packet mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| if err := tunDev.Close(); err != nil { |
| t.Fatalf("tunDev.Close() failed: %s", err) |
| } |
| linkEndpoint.Wait() |
| } |
| |
| func TestUpDown(t *testing.T) { |
| ctx := context.Background() |
| |
| tunDev, client := createTunClientPair(t, ctx) |
| |
| state, err := tunDev.WatchState(ctx) |
| if err != nil { |
| t.Fatalf("failed to get tun device state: %s", err) |
| } |
| |
| // On start, HasSession should be false. |
| if state.HasSession { |
| t.Fatalf("unexpected initial state: got %t, want false", state.HasSession) |
| } |
| |
| // Call Up and retrieve the updated state from TunDev, checking if it is |
| // powered now. |
| if err := client.Up(); err != nil { |
| t.Fatalf("client.Up failed: %s", err) |
| } |
| state, err = tunDev.WatchState(ctx) |
| if err != nil { |
| t.Fatalf("failed to get tun device state: %s", err) |
| } |
| if !state.HasSession { |
| t.Fatalf("unexpected state after Up: got %t, want true", state.HasSession) |
| } |
| |
| // Call Down and retrieve the updated state from TunDev, checking if it is |
| // not powered again. |
| if err := client.Down(); err != nil { |
| t.Fatalf("client.Down failed: %s", err) |
| } |
| state, err = tunDev.WatchState(ctx) |
| if err != nil { |
| t.Fatalf("failed to get tun device state: %s", err) |
| } |
| if state.HasSession { |
| t.Fatalf("unexpected state after Down: got %t, want false", state.HasSession) |
| } |
| } |
| |
| func TestSetPromiscuousMode(t *testing.T) { |
| ctx := context.Background() |
| |
| tunDev, client := createTunClientPair(t, ctx) |
| |
| state, err := tunDev.WatchState(ctx) |
| if err != nil { |
| t.Fatalf("failed to get tun device state: %s", err) |
| } |
| // We always set the interface to multicast promiscuous when we create the |
| // device. That might not be true once we have fine grained multicast filter |
| // control. |
| if !state.MacPresent || state.Mac.Mode != network.MacFilterModeMulticastPromiscuous { |
| t.Fatalf("unexpected initial state %v, expected state.Mac.Mode=%v", state, state.Mac.Mode) |
| } |
| |
| // Set promiscuous mode to true and check that the mode changed with |
| // tunDevice. |
| if err := client.SetPromiscuousMode(true); err != nil { |
| t.Fatalf("failed to enable promiscuous mode: %s", err) |
| } |
| state, err = tunDev.WatchState(ctx) |
| if err != nil { |
| t.Fatalf("failed to get tun device state: %s", err) |
| } |
| if !state.MacPresent || state.Mac.Mode != network.MacFilterModePromiscuous { |
| t.Fatalf("unexpected state after setting promiscuous mode ON %+v, expected state.Mac.Mode=%v", state, network.MacFilterModePromiscuous) |
| } |
| |
| // Set promiscuous mode to false and check that the mode changed with |
| // tunDevice. |
| if err := client.SetPromiscuousMode(false); err != nil { |
| t.Fatalf("failed to disable promiscuous mode: %s", err) |
| } |
| state, err = tunDev.WatchState(ctx) |
| if err != nil { |
| t.Fatalf("failed to get tun device state: %s", err) |
| } |
| if !state.MacPresent || state.Mac.Mode != network.MacFilterModeMulticastPromiscuous { |
| t.Fatalf("unexpected state after setting promiscuous mode OFF %+v, expected state.Mac.Mode=%v", state, network.MacFilterModeMulticastPromiscuous) |
| } |
| } |
| |
| func TestStateChange(t *testing.T) { |
| ctx := context.Background() |
| |
| tunDev, client := createTunClientPair(t, ctx) |
| |
| closed := make(chan struct{}) |
| client.SetOnLinkClosed(func() { close(closed) }) |
| |
| defer func() { |
| // Close and expect callback to fire. |
| if err := client.Close(); err != nil { |
| t.Fatalf("failed to close client: %s", err) |
| } |
| <-closed |
| }() |
| |
| ch := make(chan bool, 1) |
| client.SetOnLinkOnlineChanged(func(linkOnline bool) { |
| ch <- linkOnline |
| }) |
| |
| dispatcher := make(dispatcherChan) |
| client.Attach(&dispatcher) |
| |
| // First link state should be Started, because we set tunDev to online by |
| // default. |
| if !<-ch { |
| t.Error("initial link state down, want up") |
| } |
| |
| // Set offline and expect link state Down. |
| if err := tunDev.SetOnline(ctx, false); err != nil { |
| t.Fatalf("failed to set device online: %s", err) |
| } |
| if <-ch { |
| t.Error("post-down link state up, want down") |
| } |
| |
| // Set online and expect link state Started again. |
| if err := tunDev.SetOnline(ctx, true); err != nil { |
| t.Fatalf("failed to set device offline: %s", err) |
| } |
| if !<-ch { |
| t.Error("post-up link state down, want up") |
| } |
| } |
| |
| func TestDestroyDeviceCausesClose(t *testing.T) { |
| ctx := context.Background() |
| |
| tunDev, client := createTunClientPair(t, ctx) |
| |
| closed := make(chan struct{}) |
| client.SetOnLinkClosed(func() { close(closed) }) |
| client.SetOnLinkOnlineChanged(func(bool) {}) |
| |
| dispatcher := make(dispatcherChan) |
| client.Attach(&dispatcher) |
| |
| // Close and expect callback to fire. |
| if err := tunDev.Close(); err != nil { |
| t.Fatalf("tunDev.Close() failed: %s", err) |
| } |
| <-closed |
| } |
| |
| func TestCreationFailsIBadFrameType(t *testing.T) { |
| ctx := context.Background() |
| |
| tunDev := createTunWithOnline(t, ctx, true) |
| |
| dev, mac := connectProtos(t, ctx, tunDev) |
| |
| // Try to use IPv4 frames, but tun device is created only with Ethernet |
| // frame support. |
| if _, err := NewMacAddressingClient(ctx, dev, mac, &SimpleSessionConfigFactory{ |
| FrameTypes: []network.FrameType{network.FrameTypeIpv4}, |
| }); err == nil { |
| t.Fatal("creating link endpoint with bad frame support should fail") |
| } |
| } |
| |
| func TestPairExchangePackets(t *testing.T) { |
| ctx := context.Background() |
| pair := createTunPair(t, ctx, []network.FrameType{network.FrameTypeIpv4}) |
| |
| left, leftProtos := createDeviceProtocolsRequest(t) |
| right, rightProtos := createDeviceProtocolsRequest(t) |
| |
| var pairEnds tun.DevicePairEnds |
| pairEnds.SetLeft(leftProtos) |
| pairEnds.SetRight(rightProtos) |
| |
| if err := pair.ConnectProtocols(ctx, pairEnds); err != nil { |
| t.Fatalf("failed to connect to protocols: %s", err) |
| } |
| |
| sessionConfig := SimpleSessionConfigFactory{FrameTypes: []network.FrameType{network.FrameTypeIpv4}} |
| lClient, err := NewClient(ctx, left, &sessionConfig) |
| if err != nil { |
| t.Fatalf("failed to create left client: %s", err) |
| } |
| t.Cleanup(func() { |
| if err := lClient.Close(); err != nil { |
| t.Errorf("left client close failed: %s", err) |
| } |
| }) |
| |
| rClient, err := NewClient(ctx, right, &sessionConfig) |
| if err != nil { |
| t.Fatalf("failed to create right client: %s", err) |
| } |
| t.Cleanup(func() { |
| if err := rClient.Close(); err != nil { |
| t.Errorf("right client close failed: %s", err) |
| } |
| }) |
| |
| for _, client := range []*Client{lClient, rClient} { |
| client.SetOnLinkClosed(func() {}) |
| client.SetOnLinkOnlineChanged(func(bool) {}) |
| } |
| |
| lDispatcher := make(dispatcherChan, 1) |
| rDispatcher := make(dispatcherChan, 1) |
| lClient.Attach(&lDispatcher) |
| rClient.Attach(&rDispatcher) |
| packetCount := lClient.info.RxDepth * 4 |
| |
| if err := lClient.Up(); err != nil { |
| t.Fatalf("failed to start left client: %s", err) |
| } |
| if err := rClient.Up(); err != nil { |
| t.Fatalf("failed to start right client: %s", err) |
| } |
| |
| req, watcher, err := network.NewStatusWatcherWithCtxInterfaceRequest() |
| if err != nil { |
| t.Fatalf("failed to create status watcher request: %s", err) |
| } |
| t.Cleanup(func() { |
| if err := watcher.Close(); err != nil { |
| t.Errorf("watcher.Close() failed: %s", err) |
| } |
| }) |
| if err := lClient.device.GetStatusWatcher(ctx, req, network.MaxStatusBuffer); err != nil { |
| t.Fatalf("failed to get status watcher: %s", err) |
| } |
| |
| for { |
| status, err := watcher.WatchStatus(context.Background()) |
| if err != nil { |
| t.Fatalf("failed to get status: %s", err) |
| } |
| if status.GetFlags()&network.StatusFlagsOnline != 0 { |
| break |
| } |
| } |
| |
| makeTestPacket := func(prefix byte, index uint16) *stack.PacketBuffer { |
| rng := rand.New(rand.NewSource(int64(index))) |
| |
| view := []byte{prefix} |
| |
| var indexBuffer [2]byte |
| binary.LittleEndian.PutUint16(indexBuffer[:], 0) |
| view = append(view, indexBuffer[:]...) |
| |
| // Use randomized payload lengths so resetting descriptors is exercised |
| // and verified. |
| payloadLength := rng.Uint32() % (DefaultBufferLength - uint32(len(view))) |
| for i := uint32(0); i < payloadLength; i++ { |
| view = append(view, byte(rng.Uint32())) |
| } |
| return &stack.PacketBuffer{ |
| Data: buffer.View(view).ToVectorisedView(), |
| } |
| } |
| |
| send := func(endpoint stack.LinkEndpoint, prefix byte, errs chan error) { |
| for i := uint16(0); i < packetCount; i++ { |
| if err := endpoint.WritePacket(&stack.Route{}, nil, header.IPv4ProtocolNumber, makeTestPacket(prefix, i)); err != nil { |
| errs <- fmt.Errorf("WritePacket error: %v", err) |
| return |
| } |
| } |
| errs <- nil |
| } |
| |
| validate := func(pkt DeliverNetworkPacketArgs, prefix uint8, index uint16) { |
| if diff := cmp.Diff(pkt, DeliverNetworkPacketArgs{ |
| Protocol: header.IPv4ProtocolNumber, |
| Pkt: makeTestPacket(prefix, index), |
| }, testutil.PacketBufferCmpTransformer); diff != "" { |
| t.Fatalf("delivered network packet mismatch (prefix=%d, index=%d) (-want +got):\n%s", prefix, index, diff) |
| } |
| } |
| |
| lSendErrs := make(chan error, 1) |
| rSendErrs := make(chan error, 1) |
| |
| const lPrefix = 1 |
| const rPrefix = 2 |
| |
| go send(lClient, lPrefix, lSendErrs) |
| go send(rClient, rPrefix, rSendErrs) |
| |
| var rReceived, lReceived uint16 |
| for lReceived < packetCount || rReceived < packetCount { |
| select { |
| case err := <-lSendErrs: |
| if err != nil { |
| t.Fatalf("left send failed: %s", err) |
| } |
| case err := <-rSendErrs: |
| if err != nil { |
| t.Fatalf("left send failed: %s", err) |
| } |
| case pkt := <-lDispatcher: |
| validate(pkt, rPrefix, lReceived) |
| lReceived++ |
| case pkt := <-rDispatcher: |
| validate(pkt, lPrefix, rReceived) |
| rReceived++ |
| } |
| |
| } |
| |
| if err := pair.Close(); err != nil { |
| t.Fatalf("tun pair close failed: %s", err) |
| } |
| lClient.Wait() |
| rClient.Wait() |
| } |