blob: 790dc7323e375e58e489fb7a8b66970a3adeaa61 [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.
#include <lib/async-loop/cpp/loop.h>
#include <lib/sync/completion.h>
#include <lib/syslog/global.h>
#include <lib/zx/clock.h>
#include <zircon/device/network.h>
#include <zircon/status.h>
#include <fbl/span.h>
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
#include "src/lib/testing/predicates/status.h"
#include "tun_ctl.h"
namespace network {
namespace tun {
namespace testing {
namespace {
// Enable timeouts only to test things locally, committed code should not use timeouts.
constexpr zx::duration kTimeout = zx::duration::infinite();
} // namespace
constexpr uint32_t kDefaultMtu = 1500;
// A very simple client to fuchsia.hardware.network.Device to run data path
// tests against.
class SimpleClient {
public:
static constexpr uint64_t kBufferSize = 2048;
SimpleClient() = default;
fuchsia::net::tun::Protocols NewRequest() {
fuchsia::net::tun::Protocols protos;
protos.set_network_device(device_.NewRequest());
return protos;
}
zx_status_t OpenSession() {
fuchsia::hardware::network::Info device_info;
zx_status_t status = device_->GetInfo(&device_info);
if (status != ZX_OK) {
return status;
}
auto total_buffers = device_info.tx_depth + device_info.rx_depth;
if ((status = data_.CreateAndMap(total_buffers * kBufferSize,
ZX_VM_PERM_WRITE | ZX_VM_PERM_READ, nullptr, &data_vmo_)) !=
ZX_OK) {
return status;
}
if ((status = descriptors_.CreateAndMap(total_buffers * sizeof(buffer_descriptor_t),
ZX_VM_PERM_WRITE | ZX_VM_PERM_READ, nullptr,
&descriptors_vmo_)) != ZX_OK) {
return status;
}
descriptor_count_ = total_buffers;
rx_depth_ = device_info.rx_depth;
tx_depth_ = device_info.tx_depth;
fuchsia::hardware::network::SessionInfo session_info;
session_info.options = fuchsia::hardware::network::SessionFlags::PRIMARY;
session_info.descriptor_count = descriptor_count_;
session_info.descriptor_version = NETWORK_DEVICE_DESCRIPTOR_VERSION;
session_info.descriptor_length = sizeof(buffer_descriptor_t) / sizeof(uint64_t);
session_info.rx_frames.push_back(fuchsia::hardware::network::FrameType::ETHERNET);
if ((status = data_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &session_info.data)) != ZX_OK) {
return status;
}
if ((status = descriptors_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &session_info.descriptors)) !=
ZX_OK) {
return status;
}
fuchsia::hardware::network::Device_OpenSession_Result open_session_result;
status = device_->OpenSession("tun-test", std::move(session_info), &open_session_result);
if (status != ZX_OK) {
return status;
}
if (open_session_result.is_err()) {
return open_session_result.err();
}
session_ = open_session_result.response().session.BindSync();
rx_ = std::move(open_session_result.response().fifos.rx);
tx_ = std::move(open_session_result.response().fifos.tx);
return ZX_OK;
}
buffer_descriptor_t* descriptor(uint16_t index) {
if (index > descriptor_count_) {
return nullptr;
}
return static_cast<buffer_descriptor_t*>(descriptors_.start()) + index;
}
fbl::Span<uint8_t> data(const buffer_descriptor_t* desc) {
return fbl::Span(static_cast<uint8_t*>(data_.start()) + desc->offset, desc->data_length);
}
void MintData(uint16_t didx, uint32_t len = 0) {
auto* desc = descriptor(didx);
if (len == 0) {
len = desc->data_length;
} else {
desc->data_length = 4;
}
auto desc_data = data(desc);
uint16_t i = 0;
for (auto& b : desc_data) {
b = static_cast<uint8_t>(i++ + didx);
}
}
void ValidateDataInPlace(uint16_t desc, uint16_t mint_idx, uint32_t size = kBufferSize) {
auto* d = descriptor(desc);
ASSERT_EQ(d->data_length, size);
auto desc_data = data(d).begin();
for (uint32_t i = 0; i < size; i++) {
ASSERT_EQ(*desc_data, static_cast<uint8_t>(i + mint_idx))
<< "Data mismatch at position " << i;
desc_data++;
}
}
static void ValidateData(const std::vector<uint8_t>& data, uint16_t didx) {
ASSERT_EQ(data.size(), kBufferSize);
for (uint32_t i = 0; i < data.size(); i++) {
ASSERT_EQ(data[i], static_cast<uint8_t>(i + didx)) << "Data mismatch at position " << i;
}
}
buffer_descriptor_t* ResetDescriptor(uint16_t index) {
auto* desc = descriptor(index);
*desc = {
static_cast<uint8_t>(fuchsia::hardware::network::FrameType::ETHERNET), // frame_type
0, // chain_length
0, // nxt
static_cast<uint32_t>(fuchsia::hardware::network::InfoType::NO_INFO), // info_type
static_cast<uint64_t>(index) * kBufferSize, // offset
0, // head_length
0, // tail_length
kBufferSize, // data_length
0, // inbound_flags
0, // return_flags
};
return desc;
}
zx_status_t SendDescriptors(zx::fifo* fifo, const std::vector<uint16_t>& descs, bool reset,
size_t count) {
if (count == 0) {
count = descs.size();
}
if (reset) {
for (size_t i = 0; i < count; i++) {
ResetDescriptor(descs[i]);
MintData(descs[i]);
}
}
return fifo->write(sizeof(uint16_t), &descs[0], count, nullptr);
}
zx_status_t SendTx(const std::vector<uint16_t>& descs, bool reset = false, size_t count = 0) {
return SendDescriptors(&tx_, descs, reset, count);
}
zx_status_t SendRx(const std::vector<uint16_t>& descs, bool reset = true, size_t count = 0) {
return SendDescriptors(&rx_, descs, reset, count);
}
zx_status_t FetchDescriptors(zx::fifo* fifo, uint16_t* out, size_t* count, bool wait) {
size_t c = 1;
if (!count) {
count = &c;
}
if (wait) {
auto status = fifo->wait_one(ZX_FIFO_READABLE, zx::deadline_after(kTimeout), nullptr);
if (status != ZX_OK) {
return status;
}
}
return fifo->read(sizeof(uint16_t), out, *count, count);
}
zx_status_t FetchTx(uint16_t* out, size_t* count = nullptr, bool wait = true) {
return FetchDescriptors(&tx_, out, count, wait);
}
zx_status_t FetchRx(uint16_t* out, size_t* count = nullptr, bool wait = true) {
return FetchDescriptors(&rx_, out, count, wait);
}
zx_status_t WaitOnline() {
fuchsia::hardware::network::StatusWatcherSyncPtr watcher;
zx_status_t status = device_->GetStatusWatcher(watcher.NewRequest(), 5);
if (status != ZX_OK) {
return status;
}
bool online = false;
while (!online) {
fuchsia::hardware::network::Status device_status;
status = watcher->WatchStatus(&device_status);
if (status != ZX_OK) {
return status;
}
online = static_cast<bool>(device_status.flags() &
fuchsia::hardware::network::StatusFlags::ONLINE);
}
return ZX_OK;
}
fuchsia::hardware::network::SessionSyncPtr& session() { return session_; }
fuchsia::hardware::network::DeviceSyncPtr& device() { return device_; }
zx_status_t WaitRx() {
return rx_.wait_one(ZX_FIFO_READABLE, zx::deadline_after(kTimeout), nullptr);
}
zx_status_t WaitTx() {
return tx_.wait_one(ZX_FIFO_READABLE, zx::deadline_after(kTimeout), nullptr);
}
uint32_t rx_depth() const { return rx_depth_; }
uint32_t tx_depth() const { return tx_depth_; }
private:
fuchsia::hardware::network::DeviceSyncPtr device_;
zx::vmo data_vmo_;
zx::vmo descriptors_vmo_;
uint32_t descriptor_count_;
fzl::VmoMapper data_;
fzl::VmoMapper descriptors_;
zx::fifo rx_;
zx::fifo tx_;
uint32_t tx_depth_;
uint32_t rx_depth_;
fuchsia::hardware::network::SessionSyncPtr session_;
};
class TunTest : public gtest::RealLoopFixture {
public:
TunTest()
: gtest::RealLoopFixture(),
loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
tun_ctl_(loop_.dispatcher()) {}
void SetUp() override {
fx_logger_config_t log_cfg = {
.min_severity = -2,
.console_fd = dup(STDOUT_FILENO),
.log_service_channel = ZX_HANDLE_INVALID,
.tags = nullptr,
.num_tags = 0,
};
fx_log_reconfigure(&log_cfg);
ASSERT_OK(loop_.StartThread("tun-test"));
}
void TearDown() override {
// At the end of every test, all Device and DevicePair instances must be destroyed. We wait for
// tun_ctl_ to observe all of them before destroying it and the async loop.
sync_completion_t completion;
tun_ctl_.SetSafeShutdownCallback([&completion]() { sync_completion_signal(&completion); });
ASSERT_OK(sync_completion_wait(&completion, kTimeout.get()));
}
fuchsia::net::tun::ControlSyncPtr Connect() {
fuchsia::net::tun::ControlSyncPtr ret;
tun_ctl_.Connect(ret.NewRequest());
return ret;
}
static fuchsia::net::tun::BaseConfig DefaultBaseConfig() {
fuchsia::net::tun::BaseConfig config;
config.set_mtu(kDefaultMtu);
config.set_rx_types({fuchsia::hardware::network::FrameType::ETHERNET});
config.set_tx_types({fuchsia::hardware::network::FrameTypeSupport{
fuchsia::hardware::network::FrameType::ETHERNET, 0,
static_cast<fuchsia::hardware::network::TxFlags>(0)}});
return config;
}
static fuchsia::net::tun::DeviceConfig DefaultDeviceConfig() {
fuchsia::net::tun::DeviceConfig config;
config.set_base(DefaultBaseConfig());
config.set_mac(fuchsia::net::MacAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
config.set_blocking(true);
return config;
}
static fuchsia::net::tun::DevicePairConfig DefaultDevicePairConfig() {
fuchsia::net::tun::DevicePairConfig config;
config.set_base(DefaultBaseConfig());
config.set_mac_left(fuchsia::net::MacAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
config.set_mac_right(fuchsia::net::MacAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x07});
return config;
}
fidl::InterfaceHandle<fuchsia::net::tun::Device> CreateDevice(
fuchsia::net::tun::DeviceConfig config) {
auto tun = Connect();
fidl::InterfaceHandle<fuchsia::net::tun::Device> device;
EXPECT_OK(tun->CreateDevice(std::move(config), device.NewRequest()));
return device;
}
fidl::InterfaceHandle<fuchsia::net::tun::DevicePair> CreatePair(
fuchsia::net::tun::DevicePairConfig config) {
auto tun = Connect();
fidl::InterfaceHandle<fuchsia::net::tun::DevicePair> device;
EXPECT_OK(tun->CreatePair(std::move(config), device.NewRequest()));
return device;
}
protected:
async::Loop loop_;
TunCtl tun_ctl_;
};
TEST_F(TunTest, InvalidConfigs) {
auto wait_for_error = [this](fuchsia::net::tun::DeviceConfig config) -> zx_status_t {
auto device = CreateDevice(std::move(config)).Bind();
zx_status_t epitaph = ZX_OK;
device.set_error_handler([&epitaph](zx_status_t err) { epitaph = err; });
if (!RunLoopWithTimeoutOrUntil([&epitaph] { return epitaph != ZX_OK; }, kTimeout)) {
return ZX_ERR_TIMED_OUT;
}
return epitaph;
};
// Zero MTU
auto config = DefaultDeviceConfig();
config.mutable_base()->set_mtu(0);
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
// MTU too large
config = DefaultDeviceConfig();
config.mutable_base()->set_mtu(fuchsia::net::tun::MAX_MTU + 1);
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
// No Rx frames
config = DefaultDeviceConfig();
config.mutable_base()->clear_rx_types();
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
// No Tx frames
config = DefaultDeviceConfig();
config.mutable_base()->clear_tx_types();
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
// Empty Rx frames
config = DefaultDeviceConfig();
config.mutable_base()->set_rx_types({});
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
// Empty Tx frames
config = DefaultDeviceConfig();
config.mutable_base()->set_tx_types({});
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
}
TEST_F(TunTest, ConnectNetworkDevice) {
auto tun = CreateDevice(DefaultDeviceConfig()).BindSync();
fuchsia::hardware::network::DeviceSyncPtr device;
fuchsia::net::tun::Protocols protos;
protos.set_network_device(device.NewRequest());
ASSERT_OK(tun->ConnectProtocols(std::move(protos)));
fuchsia::hardware::network::Info info;
ASSERT_OK(device->GetInfo(&info));
}
TEST_F(TunTest, Teardown) {
auto tun = CreateDevice(DefaultDeviceConfig()).BindSync();
fuchsia::hardware::network::DevicePtr device;
fuchsia::hardware::network::MacAddressingPtr mac;
fuchsia::net::tun::Protocols protos;
protos.set_mac_addressing(mac.NewRequest());
protos.set_network_device(device.NewRequest());
ASSERT_OK(tun->ConnectProtocols(std::move(protos)));
bool device_dead = false;
bool mac_dead = false;
device.set_error_handler([&device_dead](zx_status_t status) {
EXPECT_STATUS(status, ZX_ERR_PEER_CLOSED);
device_dead = true;
});
mac.set_error_handler([&mac_dead](zx_status_t status) {
EXPECT_STATUS(status, ZX_ERR_PEER_CLOSED);
mac_dead = true;
});
// get rid of tun.
tun.Unbind().TakeChannel().reset();
ASSERT_TRUE(
RunLoopWithTimeoutOrUntil([&device_dead, &mac_dead]() { return device_dead && mac_dead; },
kTimeout, zx::duration::infinite()))
<< "Timed out waiting for channels to close; device_dead=" << device_dead
<< ", mac_dead=" << mac_dead;
}
TEST_F(TunTest, Status) {
auto tun = CreateDevice(DefaultDeviceConfig()).BindSync();
fuchsia::hardware::network::DeviceSyncPtr device;
fuchsia::net::tun::Protocols protos;
protos.set_network_device(device.NewRequest());
ASSERT_OK(tun->ConnectProtocols(std::move(protos)));
fuchsia::hardware::network::Status device_status;
ASSERT_OK(device->GetStatus(&device_status));
ASSERT_EQ(device_status.mtu(), kDefaultMtu);
ASSERT_EQ(device_status.flags(), fuchsia::hardware::network::StatusFlags());
fuchsia::hardware::network::StatusWatcherSyncPtr watcher;
ASSERT_OK(device->GetStatusWatcher(watcher.NewRequest(), 5));
ASSERT_OK(watcher->WatchStatus(&device_status));
ASSERT_EQ(device_status.mtu(), kDefaultMtu);
ASSERT_EQ(device_status.flags(), fuchsia::hardware::network::StatusFlags());
ASSERT_OK(tun->SetOnline(true));
ASSERT_OK(watcher->WatchStatus(&device_status));
ASSERT_EQ(device_status.mtu(), kDefaultMtu);
ASSERT_EQ(device_status.flags(), fuchsia::hardware::network::StatusFlags::ONLINE);
}
TEST_F(TunTest, Mac) {
auto config = DefaultDeviceConfig();
fuchsia::net::MacAddress ref_mac;
config.mac().Clone(&ref_mac);
auto tun = CreateDevice(std::move(config)).BindSync();
fuchsia::hardware::network::MacAddressingSyncPtr mac;
fuchsia::net::tun::Protocols protos;
protos.set_mac_addressing(mac.NewRequest());
ASSERT_OK(tun->ConnectProtocols(std::move(protos)));
fuchsia::net::MacAddress unicast;
ASSERT_OK(mac->GetUnicastAddress(&unicast));
fuchsia::net::tun::InternalState internal_state;
ASSERT_OK(tun->WatchState(&internal_state));
ASSERT_TRUE(internal_state.has_mac());
ASSERT_EQ(internal_state.mac().mode(),
fuchsia::hardware::network::MacFilterMode::MULTICAST_FILTER);
zx_status_t status;
ASSERT_OK(mac->AddMulticastAddress(fuchsia::net::MacAddress{1, 10, 20, 30, 40, 50}, &status));
ASSERT_OK(status);
ASSERT_OK(tun->WatchState(&internal_state));
ASSERT_EQ(internal_state.mac().mode(),
fuchsia::hardware::network::MacFilterMode::MULTICAST_FILTER);
ASSERT_EQ(internal_state.mac().multicast_filters().size(), 1ul);
std::array<uint8_t, 6> cmp{1, 10, 20, 30, 40, 50};
ASSERT_EQ(internal_state.mac().multicast_filters()[0].octets, cmp);
ASSERT_OK(mac->SetMode(fuchsia::hardware::network::MacFilterMode::PROMISCUOUS, &status));
ASSERT_OK(status);
ASSERT_OK(tun->WatchState(&internal_state));
ASSERT_EQ(internal_state.mac().mode(), fuchsia::hardware::network::MacFilterMode::PROMISCUOUS);
ASSERT_TRUE(internal_state.mac().multicast_filters().empty())
<< "Mac filters should be empty, but has " << internal_state.mac().multicast_filters().size()
<< " entries";
}
TEST_F(TunTest, NoMac) {
auto config = DefaultDeviceConfig();
// remove mac information
config.clear_mac();
auto tun = CreateDevice(std::move(config)).BindSync();
fuchsia::hardware::network::MacAddressingPtr mac;
fuchsia::net::tun::Protocols protos;
protos.set_mac_addressing(mac.NewRequest());
ASSERT_OK(tun->ConnectProtocols(std::move(protos)));
bool done = false;
// mac should be closed because we created tun without a mac information.
// Wait for the error handler to report that back to us.
mac.set_error_handler([&done](zx_status_t error) {
EXPECT_STATUS(error, ZX_ERR_PEER_CLOSED);
done = true;
});
ASSERT_TRUE(
RunLoopWithTimeoutOrUntil([&done]() { return done; }, kTimeout, zx::duration::infinite()));
fuchsia::net::tun::InternalState internal_state;
ASSERT_OK(tun->GetState(&internal_state));
ASSERT_FALSE(internal_state.has_mac()) << "Mac state should be unset";
}
TEST_F(TunTest, SimpleRxTx) {
auto config = DefaultDeviceConfig();
config.set_online(true);
config.set_blocking(false);
auto tun = CreateDevice(std::move(config)).BindSync();
SimpleClient client;
ASSERT_OK(tun->ConnectProtocols(client.NewRequest()));
ASSERT_OK(client.OpenSession());
ASSERT_OK(client.session()->SetPaused(false));
zx::eventpair signals;
ASSERT_OK(tun->GetSignals(&signals));
// Attempting to read frame without any available buffers should fail with should_wait and the
// readable signal should not be set.
fuchsia::net::tun::Device_ReadFrame_Result read_frame_result;
ASSERT_OK(tun->ReadFrame(&read_frame_result));
ASSERT_TRUE(read_frame_result.is_err())
<< "Got unexpected frame with " << read_frame_result.response().frame.data().size()
<< "bytes, should've errored";
ASSERT_STATUS(read_frame_result.err(), ZX_ERR_SHOULD_WAIT);
ASSERT_STATUS(signals.wait_one(static_cast<uint32_t>(fuchsia::net::tun::Signals::READABLE),
zx::time::infinite_past(), nullptr),
ZX_ERR_TIMED_OUT);
ASSERT_OK(client.SendTx({0x00, 0x01}, true));
ASSERT_OK(signals.wait_one(static_cast<uint32_t>(fuchsia::net::tun::Signals::READABLE),
zx::deadline_after(kTimeout), nullptr));
ASSERT_OK(tun->ReadFrame(&read_frame_result));
ASSERT_TRUE(read_frame_result.is_response())
<< "ReadFrame failed: " << zx_status_get_string(read_frame_result.err());
ASSERT_EQ(read_frame_result.response().frame.frame_type(),
fuchsia::hardware::network::FrameType::ETHERNET);
ASSERT_NO_FATAL_FAILURE(
SimpleClient::ValidateData(read_frame_result.response().frame.data(), 0x00));
ASSERT_FALSE(read_frame_result.response().frame.has_meta());
// After read frame, the first descriptor must've been returned.
uint16_t desc;
ASSERT_OK(client.FetchTx(&desc));
EXPECT_EQ(desc, 0x00);
// Attempting to send a frame without any available buffers should fail with should_wait and the
// writable signal should not be set.
fuchsia::net::tun::Device_WriteFrame_Result write_frame_result;
{
fuchsia::net::tun::Frame frame;
frame.set_frame_type(fuchsia::hardware::network::FrameType::ETHERNET);
frame.set_data({0xAA, 0xBB});
ASSERT_OK(tun->WriteFrame(std::move(frame), &write_frame_result));
ASSERT_TRUE(write_frame_result.is_err());
ASSERT_STATUS(write_frame_result.err(), ZX_ERR_SHOULD_WAIT);
ASSERT_STATUS(signals.wait_one(static_cast<uint32_t>(fuchsia::net::tun::Signals::WRITABLE),
zx::time::infinite_past(), nullptr),
ZX_ERR_TIMED_OUT);
}
ASSERT_OK(client.SendRx({0x02}, true));
// But if we sent stuff out, now it should work after waiting for the available signal.
ASSERT_OK(signals.wait_one(static_cast<uint32_t>(fuchsia::net::tun::Signals::WRITABLE),
zx::deadline_after(kTimeout), nullptr));
{
fuchsia::net::tun::Frame frame;
frame.set_frame_type(fuchsia::hardware::network::FrameType::ETHERNET);
frame.set_data({0xAA, 0xBB});
ASSERT_OK(tun->WriteFrame(std::move(frame), &write_frame_result));
ASSERT_TRUE(write_frame_result.is_response())
<< "Write frame failed with " << zx_status_get_string(write_frame_result.err());
}
// Check that data was correctly written to descriptor.
ASSERT_OK(client.FetchRx(&desc));
ASSERT_EQ(desc, 0x02);
auto* d = client.descriptor(desc);
EXPECT_EQ(d->data_length, 2u);
auto data = client.data(d);
EXPECT_EQ(data[0], 0xAA);
EXPECT_EQ(data[1], 0xBB);
}
TEST_F(TunTest, PairRxTx) {
auto dpair = CreatePair(DefaultDevicePairConfig()).BindSync();
fuchsia::net::tun::DevicePairEnds ends;
SimpleClient left;
SimpleClient right;
ends.set_left(left.NewRequest());
ends.set_right(right.NewRequest());
ASSERT_OK(dpair->ConnectProtocols(std::move(ends)));
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
ASSERT_OK(right.SendRx({0x05, 0x06, 0x07}, true));
right.session()->SetPaused(false);
left.session()->SetPaused(false);
ASSERT_OK(left.WaitOnline());
ASSERT_OK(right.WaitOnline());
ASSERT_OK(left.SendTx({0x00, 0x01, 0x02}, true));
ASSERT_OK(right.WaitRx());
uint16_t ds[3];
size_t dcount = 3;
ASSERT_OK(right.FetchRx(ds, &dcount));
ASSERT_EQ(dcount, 3u);
// Check that the data was copied correctly for all three descriptors.
uint16_t ref_d = 0x00;
for (const auto& d : ds) {
ASSERT_NO_FATAL_FAILURE(right.ValidateDataInPlace(d, ref_d))
<< "Invalid in place data for " << d << " <-> " << ref_d;
ref_d++;
}
ASSERT_OK(left.WaitTx());
ASSERT_OK(left.FetchTx(ds, &dcount));
EXPECT_EQ(dcount, 3u);
}
TEST_F(TunTest, PairOnlineSignal) {
auto dpair = CreatePair(DefaultDevicePairConfig()).BindSync();
fuchsia::net::tun::DevicePairEnds ends;
SimpleClient left;
SimpleClient right;
ends.set_left(left.NewRequest());
ends.set_right(right.NewRequest());
ASSERT_OK(dpair->ConnectProtocols(std::move(ends)));
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
// Online status should be false for both sides before a session is opened.
fuchsia::hardware::network::StatusWatcherSyncPtr wleft, wright;
left.device()->GetStatusWatcher(wleft.NewRequest(), 2);
right.device()->GetStatusWatcher(wright.NewRequest(), 2);
fuchsia::hardware::network::Status sleft, sright;
ASSERT_OK(wleft->WatchStatus(&sleft));
ASSERT_OK(wright->WatchStatus(&sright));
EXPECT_EQ(sleft.flags() & fuchsia::hardware::network::StatusFlags::ONLINE,
fuchsia::hardware::network::StatusFlags());
EXPECT_EQ(sright.flags() & fuchsia::hardware::network::StatusFlags::ONLINE,
fuchsia::hardware::network::StatusFlags());
// When both sessions are unpaused, online signal must come up.
ASSERT_OK(left.session()->SetPaused(false));
ASSERT_OK(right.session()->SetPaused(false));
ASSERT_OK(wleft->WatchStatus(&sleft));
ASSERT_OK(wright->WatchStatus(&sright));
EXPECT_EQ(sleft.flags() & fuchsia::hardware::network::StatusFlags::ONLINE,
fuchsia::hardware::network::StatusFlags::ONLINE);
EXPECT_EQ(sright.flags() & fuchsia::hardware::network::StatusFlags::ONLINE,
fuchsia::hardware::network::StatusFlags::ONLINE);
}
TEST_F(TunTest, PairFallibleWrites) {
auto config = DefaultDevicePairConfig();
config.set_fallible_transmit_left(true);
auto dpair = CreatePair(std::move(config)).BindSync();
fuchsia::net::tun::DevicePairEnds ends;
SimpleClient left;
SimpleClient right;
ends.set_left(left.NewRequest());
ends.set_right(right.NewRequest());
ASSERT_OK(dpair->ConnectProtocols(std::move(ends)));
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
ASSERT_OK(left.session()->SetPaused(false));
ASSERT_OK(right.session()->SetPaused(false));
ASSERT_OK(left.WaitOnline());
ASSERT_OK(right.WaitOnline());
ASSERT_OK(left.SendTx({0x00}, true));
ASSERT_OK(left.WaitTx());
uint16_t desc;
ASSERT_OK(left.FetchTx(&desc));
ASSERT_EQ(desc, 0x00);
auto* d = left.descriptor(desc);
auto flags = static_cast<fuchsia::hardware::network::TxReturnFlags>(d->return_flags);
EXPECT_EQ(flags & fuchsia::hardware::network::TxReturnFlags::TX_RET_ERROR,
fuchsia::hardware::network::TxReturnFlags::TX_RET_ERROR);
}
TEST_F(TunTest, PairInfallibleWrites) {
auto dpair = CreatePair(DefaultDevicePairConfig()).BindSync();
fuchsia::net::tun::DevicePairEnds ends;
SimpleClient left;
SimpleClient right;
ends.set_left(left.NewRequest());
ends.set_right(right.NewRequest());
ASSERT_OK(dpair->ConnectProtocols(std::move(ends)));
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
ASSERT_OK(left.session()->SetPaused(false));
ASSERT_OK(right.session()->SetPaused(false));
ASSERT_OK(left.WaitOnline());
ASSERT_OK(right.WaitOnline());
ASSERT_OK(left.SendTx({0x00}, true));
uint16_t desc;
ASSERT_STATUS(left.FetchTx(&desc, nullptr, false), ZX_ERR_SHOULD_WAIT);
ASSERT_OK(right.SendRx({0x01}, true));
ASSERT_OK(right.WaitRx());
ASSERT_OK(left.WaitTx());
ASSERT_OK(left.FetchTx(&desc));
EXPECT_EQ(desc, 0x00);
ASSERT_OK(right.FetchRx(&desc));
EXPECT_EQ(desc, 0x01);
}
TEST_F(TunTest, RejectsIfOffline) {
auto tun = CreateDevice(DefaultDeviceConfig()).BindSync();
SimpleClient cli;
ASSERT_OK(tun->ConnectProtocols(cli.NewRequest()));
ASSERT_OK(cli.OpenSession());
ASSERT_OK(cli.session()->SetPaused(false));
// Can't send from the tun end.
{
fuchsia::net::tun::Frame frame;
frame.set_frame_type(fuchsia::hardware::network::FrameType::ETHERNET);
frame.set_data({0x01, 0x02, 0x03});
fuchsia::net::tun::Device_WriteFrame_Result result;
ASSERT_OK(tun->WriteFrame(std::move(frame), &result));
ASSERT_TRUE(result.is_err());
ASSERT_STATUS(result.err(), ZX_ERR_BAD_STATE);
}
// Can't send from client end.
{
ASSERT_OK(cli.SendTx({0x00}, true));
ASSERT_OK(cli.WaitTx());
uint16_t desc;
ASSERT_OK(cli.FetchTx(&desc));
ASSERT_EQ(desc, 0x00);
ASSERT_TRUE(static_cast<bool>(
fuchsia::hardware::network::TxReturnFlags(cli.descriptor(desc)->return_flags) &
fuchsia::hardware::network::TxReturnFlags::TX_RET_ERROR))
<< "Bad return flags " << cli.descriptor(desc)->return_flags;
}
// If we set online we'll be able to send.
ASSERT_OK(tun->SetOnline(true));
// Send from client end once more and read a single frame.
{
ASSERT_OK(cli.SendTx({0x00, 0x01}, true));
fuchsia::net::tun::Device_ReadFrame_Result result;
ASSERT_OK(tun->ReadFrame(&result));
ASSERT_TRUE(result.is_response())
<< "ReadFrame failed with " << zx_status_get_string(result.err());
}
// Set offline and see if client received their tx buffers back.
ASSERT_OK(tun->SetOnline(false));
uint16_t desc;
ASSERT_OK(cli.WaitTx());
// No error on first descriptor.
ASSERT_OK(cli.FetchTx(&desc));
ASSERT_EQ(desc, 0x00);
EXPECT_FALSE(static_cast<bool>(
fuchsia::hardware::network::TxReturnFlags(cli.descriptor(desc)->return_flags) &
fuchsia::hardware::network::TxReturnFlags::TX_RET_ERROR))
<< "Bad return flags " << cli.descriptor(desc)->return_flags;
// Error on second.
ASSERT_OK(cli.FetchTx(&desc));
ASSERT_EQ(desc, 0x01);
EXPECT_TRUE(static_cast<bool>(
fuchsia::hardware::network::TxReturnFlags(cli.descriptor(desc)->return_flags) &
fuchsia::hardware::network::TxReturnFlags::TX_RET_ERROR))
<< "Bad return flags " << cli.descriptor(desc)->return_flags;
}
TEST_F(TunTest, PairEcho) {
auto dpair = CreatePair(DefaultDevicePairConfig()).BindSync();
fuchsia::net::tun::DevicePairEnds ends;
SimpleClient left;
SimpleClient right;
ends.set_left(left.NewRequest());
ends.set_right(right.NewRequest());
ASSERT_OK(dpair->ConnectProtocols(std::move(ends)));
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
ASSERT_OK(left.session()->SetPaused(false));
ASSERT_OK(right.session()->SetPaused(false));
ASSERT_OK(left.WaitOnline());
ASSERT_OK(right.WaitOnline());
auto rx_depth = left.rx_depth();
auto tx_depth = left.tx_depth();
std::vector<uint16_t> tx_descriptors;
std::vector<uint16_t> rx_descriptors;
tx_descriptors.reserve(tx_depth);
rx_descriptors.reserve(rx_depth);
for (uint16_t i = 0; i < static_cast<uint16_t>(tx_depth); i++) {
tx_descriptors.push_back(i);
left.ResetDescriptor(i);
left.MintData(i, 4);
right.ResetDescriptor(i);
}
for (uint16_t i = tx_depth; i < static_cast<uint16_t>(tx_depth + rx_depth); i++) {
rx_descriptors.push_back(i);
left.ResetDescriptor(i);
right.ResetDescriptor(i);
}
ASSERT_OK(right.SendRx(rx_descriptors));
ASSERT_OK(left.SendRx(rx_descriptors));
ASSERT_OK(left.SendTx(tx_descriptors));
uint32_t echoed = 0;
std::vector<uint16_t> rx_buffer(rx_depth, 0);
std::vector<uint16_t> tx_buffer(tx_depth, 0);
while (echoed < tx_depth) {
ASSERT_OK(right.WaitRx());
size_t count = rx_depth;
ASSERT_OK(right.FetchRx(&rx_buffer[0], &count));
for (size_t i = 0; i < count; i++) {
tx_buffer[i] = tx_descriptors[i + echoed];
auto* rx_desc = right.descriptor(rx_buffer[i]);
auto* tx_desc = right.descriptor(tx_buffer[i]);
auto rx_data = right.data(rx_desc);
auto tx_data = right.data(tx_desc);
std::copy(rx_data.begin(), rx_data.end(), tx_data.begin());
tx_desc->frame_type = rx_desc->frame_type;
tx_desc->data_length = rx_desc->data_length;
}
echoed += count;
ASSERT_OK(right.SendTx(tx_buffer, false, count));
ASSERT_OK(right.SendRx(rx_buffer, false, count));
}
uint32_t received = 0;
while (received < tx_depth) {
ASSERT_OK(left.WaitRx());
size_t count = rx_depth;
ASSERT_OK(left.FetchRx(&rx_buffer[0], &count));
for (size_t i = 0; i < count; i++) {
auto orig_desc = tx_descriptors[received + i];
ASSERT_NO_FATAL_FAILURE(left.ValidateDataInPlace(rx_buffer[i], orig_desc, 4));
}
received += count;
}
}
} // namespace testing
} // namespace tun
} // namespace network