blob: 463761dd9c93fc0713050078a4186dd4880d8b0c [file] [log] [blame]
// Copyright 2018 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/component/cpp/testing/test_with_environment.h"
#include "lib/netemul/network/ethernet_client.h"
#include "lib/netemul/network/fake_endpoint.h"
#include "lib/netemul/network/network_context.h"
#define ASSERT_OK(st) ASSERT_EQ(ZX_OK, (st))
#define ASSERT_NOK(st) ASSERT_NE(ZX_OK, (st))
#define TEST_BUF_SIZE (512ul)
#define WAIT_FOR_OK(ok) \
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&ok]() { return ok; }, zx::sec(2)))
#define WAIT_FOR_OK_AND_RESET(ok) \
WAIT_FOR_OK(ok); \
ok = false
namespace netemul {
namespace testing {
static const EthernetConfig TestEthBuffConfig = {.buff_size = 2048, .nbufs = 4};
using component::testing::EnclosingEnvironment;
using component::testing::EnvironmentServices;
using component::testing::TestWithEnvironment;
class NetworkServiceTest : public TestWithEnvironment {
public:
using FNetworkManager = NetworkManager::FNetworkManager;
using FEndpointManager = EndpointManager::FEndpointManager;
using FNetworkContext = NetworkContext::FNetworkContext;
using FNetwork = Network::FNetwork;
using FEndpoint = Endpoint::FEndpoint;
using FFakeEndpoint = FakeEndpoint::FFakeEndpoint;
protected:
void SetUp() override {
fuchsia::sys::EnvironmentPtr parent_env;
real_services()->ConnectToService(parent_env.NewRequest());
svc_loop_ =
std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread);
ASSERT_OK(svc_loop_->StartThread("testloop"));
svc_ = std::make_unique<NetworkContext>(svc_loop_->dispatcher());
auto services =
EnvironmentServices::Create(parent_env, svc_loop_->dispatcher());
services->AddService(svc_->GetHandler());
test_env_ = CreateNewEnclosingEnvironment("env", std::move(services));
ASSERT_TRUE(WaitForEnclosingEnvToStart(test_env_.get()));
}
void GetNetworkManager(fidl::InterfaceRequest<FNetworkManager> nm) {
fidl::InterfacePtr<FNetworkContext> netc;
test_env_->ConnectToService(netc.NewRequest());
netc->GetNetworkManager(std::move(nm));
}
void GetEndpointManager(fidl::InterfaceRequest<FEndpointManager> epm) {
fidl::InterfacePtr<FNetworkContext> netc;
test_env_->ConnectToService(netc.NewRequest());
netc->GetEndpointManager(std::move(epm));
}
Endpoint::Config GetDefaultEndpointConfig() {
Endpoint::Config ret;
ret.mtu = 1500;
ret.backing = fuchsia::netemul::network::EndpointBacking::ETHERTAP;
return ret;
}
void GetServices(fidl::InterfaceRequest<FNetworkManager> nm,
fidl::InterfaceRequest<FEndpointManager> epm) {
fidl::InterfacePtr<FNetworkContext> netc;
test_env_->ConnectToService(netc.NewRequest());
netc->GetNetworkManager(std::move(nm));
netc->GetEndpointManager(std::move(epm));
}
void StartServices() {
GetServices(net_manager_.NewRequest(), endp_manager_.NewRequest());
}
void CreateNetwork(const char* name,
fidl::SynchronousInterfacePtr<FNetwork>* netout,
Network::Config config = Network::Config()) {
ASSERT_TRUE(net_manager_.is_bound());
zx_status_t status;
fidl::InterfaceHandle<FNetwork> neth;
ASSERT_OK(
net_manager_->CreateNetwork(name, std::move(config), &status, &neth));
ASSERT_OK(status);
ASSERT_TRUE(neth.is_valid());
*netout = neth.BindSync();
}
void CreateEndpoint(const char* name,
fidl::SynchronousInterfacePtr<FEndpoint>* netout,
Endpoint::Config config) {
ASSERT_TRUE(net_manager_.is_bound());
zx_status_t status;
fidl::InterfaceHandle<FEndpoint> eph;
ASSERT_OK(
endp_manager_->CreateEndpoint(name, std::move(config), &status, &eph));
ASSERT_OK(status);
ASSERT_TRUE(eph.is_valid());
*netout = eph.BindSync();
}
void CreateEndpoint(const char* name,
fidl::SynchronousInterfacePtr<FEndpoint>* netout) {
CreateEndpoint(name, netout, GetDefaultEndpointConfig());
}
void TearDown() override {
async::PostTask(svc_loop_->dispatcher(), [this]() {
svc_.reset();
svc_loop_->Quit();
});
svc_loop_->JoinThreads();
}
std::unique_ptr<EnclosingEnvironment> test_env_;
std::unique_ptr<async::Loop> svc_loop_;
std::unique_ptr<NetworkContext> svc_;
fidl::SynchronousInterfacePtr<FNetworkManager> net_manager_;
fidl::SynchronousInterfacePtr<FEndpointManager> endp_manager_;
};
TEST_F(NetworkServiceTest, NetworkLifecycle) {
fidl::SynchronousInterfacePtr<FNetworkManager> netm;
GetNetworkManager(netm.NewRequest());
const char* netname = "mynet";
std::vector<std::string> nets;
ASSERT_OK(netm->ListNetworks(&nets));
ASSERT_EQ(0ul, nets.size());
Network::Config config;
zx_status_t status;
fidl::InterfaceHandle<FNetwork> neth;
// can create network ok
ASSERT_OK(netm->CreateNetwork(fidl::StringPtr(netname), std::move(config),
&status, &neth));
auto net = neth.BindSync();
ASSERT_OK(status);
ASSERT_TRUE(net.is_bound());
// list nets again and make sure it's there:
ASSERT_OK(netm->ListNetworks(&nets));
ASSERT_EQ(1ul, nets.size());
ASSERT_EQ(netname, nets.at(0));
// check network name matches:
std::string outname;
ASSERT_OK(net->GetName(&outname));
ASSERT_EQ(netname, outname);
// check that we can fetch the network by name:
fidl::InterfaceHandle<FNetwork> ohandle;
ASSERT_OK(netm->GetNetwork(netname, &ohandle));
ASSERT_TRUE(ohandle.is_valid());
// dispose of second handle
ohandle.TakeChannel().reset();
// check that network still exists:
ASSERT_OK(netm->ListNetworks(&nets));
ASSERT_EQ(1ul, nets.size());
// destroy original network handle:
net.Unbind().TakeChannel().reset();
// make sure network is deleted afterwards:
ASSERT_OK(netm->ListNetworks(&nets));
ASSERT_EQ(0ul, nets.size());
// trying to get the network again without creating it fails:
ASSERT_OK(netm->GetNetwork(netname, &ohandle));
ASSERT_FALSE(ohandle.is_valid());
}
TEST_F(NetworkServiceTest, EndpointLifecycle) {
fidl::SynchronousInterfacePtr<FEndpointManager> epm;
GetEndpointManager(epm.NewRequest());
const char* epname = "myendpoint";
std::vector<std::string> eps;
ASSERT_OK(epm->ListEndpoints(&eps));
ASSERT_EQ(0ul, eps.size());
auto config = GetDefaultEndpointConfig();
zx_status_t status;
fidl::InterfaceHandle<FEndpoint> eph;
// can create endpoint ok
ASSERT_OK(epm->CreateEndpoint(fidl::StringPtr(epname), std::move(config),
&status, &eph));
auto ep = eph.BindSync();
ASSERT_OK(status);
ASSERT_TRUE(ep.is_bound());
// list endpoints again and make sure it's there:
ASSERT_OK(epm->ListEndpoints(&eps));
ASSERT_EQ(1ul, eps.size());
ASSERT_EQ(epname, eps.at(0));
// check endpoint name matches:
std::string outname;
ASSERT_OK(ep->GetName(&outname));
ASSERT_EQ(epname, outname);
// check that we can fetch the endpoint by name:
fidl::InterfaceHandle<FEndpoint> ohandle;
ASSERT_OK(epm->GetEndpoint(epname, &ohandle));
ASSERT_TRUE(ohandle.is_valid());
// dispose of second handle
ohandle.TakeChannel().reset();
// check that endpoint still exists:
ASSERT_OK(epm->ListEndpoints(&eps));
ASSERT_EQ(1ul, eps.size());
// destroy original endpoint handle:
ep.Unbind().TakeChannel().reset();
// make sure endpoint is deleted afterwards:
ASSERT_OK(epm->ListEndpoints(&eps));
ASSERT_EQ(0ul, eps.size());
// trying to get the endpoint again without creating it fails:
ASSERT_OK(epm->GetEndpoint(epname, &ohandle));
ASSERT_FALSE(ohandle.is_valid());
}
TEST_F(NetworkServiceTest, BadEndpointConfigurations) {
fidl::SynchronousInterfacePtr<FEndpointManager> epm;
GetEndpointManager(epm.NewRequest());
const char* epname = "myendpoint";
zx_status_t status;
fidl::InterfaceHandle<FEndpoint> eph;
// can't create endpoint with empty name
ASSERT_OK(epm->CreateEndpoint("", GetDefaultEndpointConfig(), &status, &eph));
ASSERT_NOK(status);
ASSERT_FALSE(eph.is_valid());
// can't create endpoint with unexisting backing
auto badBacking = GetDefaultEndpointConfig();
badBacking.backing =
static_cast<fuchsia::netemul::network::EndpointBacking>(-1);
ASSERT_OK(epm->CreateEndpoint(epname, std::move(badBacking), &status, &eph));
ASSERT_NOK(status);
ASSERT_FALSE(eph.is_valid());
// can't create endpoint which violates maximum MTU
auto badMtu = GetDefaultEndpointConfig();
badMtu.mtu = 65535; // 65k too large
ASSERT_OK(epm->CreateEndpoint(epname, std::move(badMtu), &status, &eph));
ASSERT_NOK(status);
ASSERT_FALSE(eph.is_valid());
// create a good endpoint:
fidl::InterfaceHandle<FEndpoint> good_eph;
ASSERT_OK(epm->CreateEndpoint(epname, GetDefaultEndpointConfig(), &status,
&good_eph));
ASSERT_OK(status);
ASSERT_TRUE(good_eph.is_valid());
// can't create another endpoint with same name:
ASSERT_OK(
epm->CreateEndpoint(epname, GetDefaultEndpointConfig(), &status, &eph));
ASSERT_NOK(status);
ASSERT_FALSE(eph.is_valid());
}
TEST_F(NetworkServiceTest, BadNetworkConfigurations) {
fidl::SynchronousInterfacePtr<FNetworkManager> netm;
GetNetworkManager(netm.NewRequest());
zx_status_t status;
fidl::InterfaceHandle<FNetwork> neth;
// can't create network with empty name
ASSERT_OK(netm->CreateNetwork("", Network::Config(), &status, &neth));
ASSERT_NOK(status);
ASSERT_FALSE(neth.is_valid());
const char* netname = "mynet";
// create a good network
fidl::InterfaceHandle<FNetwork> good_neth;
ASSERT_OK(
netm->CreateNetwork(netname, Network::Config(), &status, &good_neth));
ASSERT_OK(status);
ASSERT_TRUE(good_neth.is_valid());
// can't create another network with same name:
ASSERT_OK(netm->CreateNetwork(netname, Network::Config(), &status, &neth));
ASSERT_NOK(status);
ASSERT_FALSE(neth.is_valid());
}
TEST_F(NetworkServiceTest, TransitData) {
const char* netname = "mynet";
const char* ep1name = "ep1";
const char* ep2name = "ep2";
StartServices();
// create a network:
fidl::SynchronousInterfacePtr<FNetwork> net;
CreateNetwork(netname, &net);
// create first endpoint:
fidl::SynchronousInterfacePtr<FEndpoint> ep1;
CreateEndpoint(ep1name, &ep1);
// create second endpoint:
fidl::SynchronousInterfacePtr<FEndpoint> ep2;
CreateEndpoint(ep2name, &ep2);
// attach both endpoints:
zx_status_t status;
ASSERT_OK(net->AttachEndpoint(ep1name, &status));
ASSERT_OK(status);
ASSERT_OK(net->AttachEndpoint(ep2name, &status));
ASSERT_OK(status);
// start ethernet clients on both endpoints:
fidl::InterfaceHandle<fuchsia::hardware::ethernet::Device> eth1_h;
fidl::InterfaceHandle<fuchsia::hardware::ethernet::Device> eth2_h;
ASSERT_OK(ep1->GetEthernetDevice(&eth1_h));
ASSERT_TRUE(eth1_h.is_valid());
ASSERT_OK(ep2->GetEthernetDevice(&eth2_h));
ASSERT_TRUE(eth2_h.is_valid());
// create both ethernet clients
EthernetClient eth1(dispatcher(), eth1_h.Bind());
EthernetClient eth2(dispatcher(), eth2_h.Bind());
bool ok = false;
// configure both ethernet clients:
eth1.Setup(TestEthBuffConfig, [&ok](zx_status_t status) {
ASSERT_OK(status);
ok = true;
});
WAIT_FOR_OK_AND_RESET(ok);
eth2.Setup(TestEthBuffConfig, [&ok](zx_status_t status) {
ASSERT_OK(status);
ok = true;
});
WAIT_FOR_OK_AND_RESET(ok);
// create some test buffs
uint8_t test_buff1[TEST_BUF_SIZE];
uint8_t test_buff2[TEST_BUF_SIZE];
for (size_t i = 0; i < TEST_BUF_SIZE; i++) {
test_buff1[i] = static_cast<uint8_t>(i);
test_buff2[i] = ~static_cast<uint8_t>(i);
}
// install callbacks on the ethernet interfaces:
eth1.SetDataCallback([&ok, &test_buff1](const void* data, size_t len) {
ASSERT_EQ(TEST_BUF_SIZE, len);
ASSERT_EQ(0, memcmp(data, test_buff1, len));
ok = true;
});
eth2.SetDataCallback([&ok, &test_buff2](const void* data, size_t len) {
ASSERT_EQ(TEST_BUF_SIZE, len);
ASSERT_EQ(0, memcmp(data, test_buff2, len));
ok = true;
});
// send data from eth2 to eth1
ASSERT_OK(eth2.Send(test_buff1, TEST_BUF_SIZE));
WAIT_FOR_OK_AND_RESET(ok);
// send data from eth1 to eth2
ASSERT_OK(eth1.Send(test_buff2, TEST_BUF_SIZE));
WAIT_FOR_OK_AND_RESET(ok);
// try removing an endpoint:
ASSERT_OK(net->RemoveEndpoint(ep2name, &status));
ASSERT_OK(status);
// can still send, but should not trigger anything on the other side:
ASSERT_OK(eth1.Send(test_buff1, TEST_BUF_SIZE));
RunLoopUntilIdle();
ASSERT_FALSE(ok);
}
TEST_F(NetworkServiceTest, Flooding) {
const char* netname = "mynet";
const char* ep1name = "ep1";
const char* ep2name = "ep2";
const char* ep3name = "ep3";
StartServices();
// create a network:
fidl::SynchronousInterfacePtr<FNetwork> net;
CreateNetwork(netname, &net);
// create first endpoint:
fidl::SynchronousInterfacePtr<FEndpoint> ep1;
CreateEndpoint(ep1name, &ep1);
// create second endpoint:
fidl::SynchronousInterfacePtr<FEndpoint> ep2;
CreateEndpoint(ep2name, &ep2);
// create a third:
fidl::SynchronousInterfacePtr<FEndpoint> ep3;
CreateEndpoint(ep3name, &ep3);
// attach all three endpoints:
zx_status_t status;
ASSERT_OK(net->AttachEndpoint(ep1name, &status));
ASSERT_OK(status);
ASSERT_OK(net->AttachEndpoint(ep2name, &status));
ASSERT_OK(status);
ASSERT_OK(net->AttachEndpoint(ep3name, &status));
ASSERT_OK(status);
// start ethernet clients on all endpoints:
fidl::InterfaceHandle<fuchsia::hardware::ethernet::Device> eth1_h;
fidl::InterfaceHandle<fuchsia::hardware::ethernet::Device> eth2_h;
fidl::InterfaceHandle<fuchsia::hardware::ethernet::Device> eth3_h;
ASSERT_OK(ep1->GetEthernetDevice(&eth1_h));
ASSERT_TRUE(eth1_h.is_valid());
ASSERT_OK(ep2->GetEthernetDevice(&eth2_h));
ASSERT_TRUE(eth2_h.is_valid());
ASSERT_OK(ep3->GetEthernetDevice(&eth3_h));
ASSERT_TRUE(eth3_h.is_valid());
// create all ethernet clients
EthernetClient eth1(dispatcher(), eth1_h.Bind());
EthernetClient eth2(dispatcher(), eth2_h.Bind());
EthernetClient eth3(dispatcher(), eth3_h.Bind());
bool ok = false;
// configure all ethernet clients:
eth1.Setup(TestEthBuffConfig, [&ok](zx_status_t status) {
ASSERT_OK(status);
ok = true;
});
WAIT_FOR_OK_AND_RESET(ok);
eth2.Setup(TestEthBuffConfig, [&ok](zx_status_t status) {
ASSERT_OK(status);
ok = true;
});
WAIT_FOR_OK_AND_RESET(ok);
eth3.Setup(TestEthBuffConfig, [&ok](zx_status_t status) {
ASSERT_OK(status);
ok = true;
});
WAIT_FOR_OK_AND_RESET(ok);
// create a test buff
uint8_t test_buff[TEST_BUF_SIZE];
for (size_t i = 0; i < TEST_BUF_SIZE; i++) {
test_buff[i] = static_cast<uint8_t>(i);
}
// install callbacks on the ethernet interfaces:
bool ok_eth1 = false;
bool ok_eth2 = false;
bool ok_eth3 = false;
eth1.SetDataCallback([&ok_eth1, &test_buff](const void* data, size_t len) {
ASSERT_EQ(TEST_BUF_SIZE, len);
ASSERT_EQ(0, memcmp(data, test_buff, len));
ok_eth1 = true;
});
eth2.SetDataCallback([&ok_eth2, &test_buff](const void* data, size_t len) {
ASSERT_EQ(TEST_BUF_SIZE, len);
ASSERT_EQ(0, memcmp(data, test_buff, len));
ok_eth2 = true;
});
eth3.SetDataCallback([&ok_eth3, &test_buff](const void* data, size_t len) {
ASSERT_EQ(TEST_BUF_SIZE, len);
ASSERT_EQ(0, memcmp(data, test_buff, len));
ok_eth3 = true;
});
for (int i = 0; i < 3; i++) {
// flood network from eth1:
ASSERT_OK(eth1.Send(test_buff, TEST_BUF_SIZE));
// wait for corrrect data on both endpoints:
WAIT_FOR_OK_AND_RESET(ok_eth2);
WAIT_FOR_OK_AND_RESET(ok_eth3);
// eth1 should have received NO data at this point:
ASSERT_FALSE(ok_eth1);
// now flood from eth2:
ASSERT_OK(eth2.Send(test_buff, TEST_BUF_SIZE));
// wait for corrrect data on both endpoints:
WAIT_FOR_OK_AND_RESET(ok_eth1);
WAIT_FOR_OK_AND_RESET(ok_eth3);
ASSERT_FALSE(ok_eth2);
}
}
TEST_F(NetworkServiceTest, AttachRemove) {
const char* netname = "mynet";
const char* epname = "ep1";
StartServices();
// create a network:
fidl::SynchronousInterfacePtr<FNetwork> net;
CreateNetwork(netname, &net);
// create an endpoint:
fidl::SynchronousInterfacePtr<FEndpoint> ep1;
CreateEndpoint(epname, &ep1);
// attach endpoint:
zx_status_t status;
ASSERT_OK(net->AttachEndpoint(epname, &status));
ASSERT_OK(status);
// try to attach again:
ASSERT_OK(net->AttachEndpoint(epname, &status));
// should return not OK cause endpoint was already attached:
ASSERT_NOK(status);
// remove endpoint:
ASSERT_OK(net->RemoveEndpoint(epname, &status));
ASSERT_OK(status);
// remove endpoint again:
ASSERT_OK(net->RemoveEndpoint(epname, &status));
// should return not OK cause endpoint was not attached
ASSERT_NOK(status);
}
TEST_F(NetworkServiceTest, FakeEndpoints) {
const char* netname = "mynet";
const char* epname = "ep1";
StartServices();
// create a network:
fidl::SynchronousInterfacePtr<FNetwork> net;
CreateNetwork(netname, &net);
// create first endpoint:
fidl::SynchronousInterfacePtr<FEndpoint> ep1;
CreateEndpoint(epname, &ep1);
// attach endpoint:
zx_status_t status;
ASSERT_OK(net->AttachEndpoint(epname, &status));
ASSERT_OK(status);
// start ethernet clients on endpoint:
fidl::InterfaceHandle<fuchsia::hardware::ethernet::Device> eth1_h;
ASSERT_OK(ep1->GetEthernetDevice(&eth1_h));
ASSERT_TRUE(eth1_h.is_valid());
// create ethernet client
EthernetClient eth1(dispatcher(), eth1_h.Bind());
bool ok = false;
// configure ethernet client:
eth1.Setup(TestEthBuffConfig, [&ok](zx_status_t status) {
ASSERT_OK(status);
ok = true;
});
WAIT_FOR_OK_AND_RESET(ok);
// create some test buffs
std::vector<uint8_t> test_buff1(TEST_BUF_SIZE);
std::vector<uint8_t> test_buff2(TEST_BUF_SIZE);
test_buff2.reserve(TEST_BUF_SIZE);
for (size_t i = 0; i < TEST_BUF_SIZE; i++) {
test_buff1[i] = static_cast<uint8_t>(i);
test_buff2[i] = ~static_cast<uint8_t>(i);
}
// install callbacks on the ethernet interface:
eth1.SetDataCallback([&ok, &test_buff1](const void* data, size_t len) {
ASSERT_EQ(TEST_BUF_SIZE, len);
ASSERT_EQ(0, memcmp(data, test_buff1.data(), len));
ok = true;
});
// create and inject a fake endpoint:
fidl::InterfacePtr<FFakeEndpoint> fake_ep;
ASSERT_OK(net->CreateFakeEndpoint(fake_ep.NewRequest()));
ASSERT_TRUE(fake_ep.is_bound());
// install on data callback:
fake_ep.events().OnData = [&ok, &test_buff2](std::vector<uint8_t> data) {
ASSERT_EQ(TEST_BUF_SIZE, data.size());
ASSERT_EQ(0, memcmp(data.data(), test_buff2.data(), data.size()));
ok = true;
};
for (int i = 0; i < 3; i++) {
// send buff 2 from eth endpoint:
eth1.Send(test_buff2.data(), test_buff2.size());
WAIT_FOR_OK_AND_RESET(ok);
// send buff 1 from fake endpoint:
fake_ep->Write(fidl::VectorPtr<uint8_t>(test_buff1));
WAIT_FOR_OK_AND_RESET(ok);
}
}
} // namespace testing
} // namespace netemul