blob: 48e3754dac6441b1f4789dc8581d8b1dbaafb5a8 [file] [log] [blame]
// Copyright 2019 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/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/fpromise/single_threaded_executor.h>
#include <lib/syslog/cpp/macros.h>
#include <sys/socket.h>
#include <future>
#include <optional>
#include <fbl/unique_fd.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/fxl/strings/trim.h"
#include "src/virtualization/tests/lib/enclosed_guest.h"
#include "src/virtualization/tests/lib/guest_test.h"
using ::testing::HasSubstr;
static constexpr char kVirtioNetUtil[] = "virtio_net_test_util";
static constexpr size_t kTestPacketSize = 1000;
// Includes ethernet + IPv4 + UDP headers.
static constexpr size_t kHeadersSize = 42;
static constexpr fuchsia::net::MacAddress kDefaultMacAddress = {
.octets = {0x02, 0x1a, 0x11, 0x00, 0x01, 0x00},
};
static constexpr fuchsia::net::MacAddress kSecondNicMacAddress = {
.octets = {0x02, 0x1a, 0x11, 0x00, 0x01, 0x01},
};
static constexpr fuchsia::virtualization::NetSpec kSecondNicNetSpec = {
.mac_address = kSecondNicMacAddress,
.enable_bridge = true,
};
static constexpr char kDefaultMacString[] = "02:1a:11:00:01:00";
static constexpr char kSecondNicMacString[] = "02:1a:11:00:01:01";
[[maybe_unused]] static constexpr char kHostMacString[] = "02:1a:11:00:00:00";
// Run the two promises, returning the result of the first that completes.
//
// TODO(https://fxbug.dev/42139156): When a library version becomes available, use that
// instead.
template <typename V, typename E>
fpromise::promise<V, E> SelectPromise(fpromise::promise<V, E>& a, fpromise::promise<V, E>& b) {
return fpromise::make_promise(
[&a, &b](fpromise::context& context) mutable -> fpromise::result<V, E> {
fpromise::result<V, E> a_result = a(context);
if (!a_result.is_pending()) {
return std::move(a_result);
}
fpromise::result<V, E> b_result = b(context);
if (!b_result.is_pending()) {
return std::move(b_result);
}
return fpromise::pending();
});
}
static void TestThread(fuchsia::net::MacAddress mac_addr, FakeNetstack* netstack,
uint8_t receive_byte, uint8_t send_byte, bool rotate_bytes,
bool use_raw_packets, fpromise::consumer<void, void> consumer) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
// This thread will loop indefinitely until it receives the correct packet.
// The test will time out via RunUtil in the test fixture if we fail to
// receive the correct packet.
using Promise = fpromise::promise<std::optional<std::vector<uint8_t>>, zx_status_t>;
using PromiseResult = fpromise::result<std::optional<std::vector<uint8_t>>, zx_status_t>;
Promise consumer_promise =
consumer.promise().then([](fpromise::result<void, void>&) -> PromiseResult {
return fpromise::ok(std::optional<std::vector<uint8_t>>());
});
while (true) {
Promise netstack_promise =
netstack->ReceivePacket(mac_addr).and_then([](std::vector<uint8_t>& v) {
return fpromise::ok(std::optional<std::vector<uint8_t>>(std::move(v)));
});
auto result = fpromise::run_single_threaded(SelectPromise(netstack_promise, consumer_promise));
ASSERT_TRUE(result.is_ok());
std::optional opt_value = result.take_value();
if (!opt_value.has_value()) {
// The consumer promise has completed, so we can break from the while loop here.
break;
}
std::vector<uint8_t> packet = std::move(opt_value.value());
bool match_test_packet = false;
size_t headers_size = use_raw_packets ? 0 : kHeadersSize;
if (packet.size() == headers_size + kTestPacketSize) {
match_test_packet = true;
for (size_t i = headers_size; i != packet.size(); ++i) {
if (packet[i] != receive_byte) {
match_test_packet = false;
break;
}
}
}
if (!match_test_packet) {
// The packet is incorrect, don't echo it back to the target
continue;
}
std::vector<uint8_t> send_packet(kTestPacketSize);
memset(send_packet.data(), send_byte, kTestPacketSize);
if (rotate_bytes) {
receive_byte++;
send_byte++;
}
fpromise::promise<void, zx_status_t> promise;
if (use_raw_packets) {
promise = netstack->SendPacket(mac_addr, std::move(send_packet));
} else {
promise = netstack->SendUdpPacket(mac_addr, std::move(send_packet));
}
auto send_result = fpromise::run_single_threaded(std::move(promise));
ASSERT_TRUE(send_result.is_ok());
}
}
class VirtioNetMultipleInterfacesZirconGuest : public ZirconEnclosedGuest {
public:
VirtioNetMultipleInterfacesZirconGuest(async_dispatcher_t* dispatcher,
RunLoopUntilFunc run_loop_until)
: ZirconEnclosedGuest(dispatcher, std::move(run_loop_until)) {}
zx_status_t BuildLaunchInfo(GuestLaunchInfo* launch_info) override {
zx_status_t status = ZirconEnclosedGuest::BuildLaunchInfo(launch_info);
if (status != ZX_OK) {
return status;
}
launch_info->config.set_virtio_gpu(false);
launch_info->config.mutable_net_devices()->emplace_back(kSecondNicNetSpec);
return ZX_OK;
}
};
using VirtioNetMultipleInterfacesZirconGuestTest =
GuestTest<VirtioNetMultipleInterfacesZirconGuest>;
TEST_F(VirtioNetMultipleInterfacesZirconGuestTest, ReceiveAndSend) {
// Both the value and error are void here, as we're only using the bridge as a signal.
fpromise::bridge<void, void> bridge;
// Loop back some data over the default network interface to verify that it is functional.
auto handle = std::async(std::launch::async, [this, &bridge] {
FakeNetstack* netstack = this->GetEnclosedGuest().GetNetstack();
// Pass the consumer into the test thread here.
TestThread(kDefaultMacAddress, netstack, 0xab, 0xba, /*rotate_bytes=*/false,
/*use_raw_packets=*/true, std::move(bridge.consumer));
});
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioNetUtil,
{fxl::StringPrintf("%u", 0xab), fxl::StringPrintf("%u", 0xba),
fxl::StringPrintf("%zu", kTestPacketSize), kDefaultMacString},
&result),
ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
bridge.completer.complete_ok();
handle.wait();
bridge = fpromise::bridge<void, void>();
// Ensure that the guest's second NIC works as well.
handle = std::async(std::launch::async, [this, &bridge] {
FakeNetstack* netstack = this->GetEnclosedGuest().GetNetstack();
TestThread(kSecondNicMacAddress, netstack, 0xcd, 0xdc, /*rotate_bytes=*/false,
/*use_raw_packets=*/true, std::move(bridge.consumer));
});
EXPECT_EQ(this->RunUtil(kVirtioNetUtil,
{fxl::StringPrintf("%u", 0xcd), fxl::StringPrintf("%u", 0xdc),
fxl::StringPrintf("%zu", kTestPacketSize), kSecondNicMacString},
&result),
ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
bridge.completer.complete_ok();
handle.wait();
}
#if __x86_64__
class VirtioNetMultipleInterfacesDebianGuest : public DebianEnclosedGuest {
public:
VirtioNetMultipleInterfacesDebianGuest(async_dispatcher_t* dispatcher,
RunLoopUntilFunc run_loop_until)
: DebianEnclosedGuest(dispatcher, std::move(run_loop_until)) {}
zx_status_t BuildLaunchInfo(struct GuestLaunchInfo* launch_info) override {
zx_status_t status = DebianEnclosedGuest::BuildLaunchInfo(launch_info);
if (status != ZX_OK) {
return status;
}
launch_info->config.set_virtio_gpu(false);
launch_info->config.mutable_net_devices()->emplace_back(kSecondNicNetSpec);
return ZX_OK;
}
};
using VirtioNetMultipleInterfacesDebianGuestTest =
GuestTest<VirtioNetMultipleInterfacesDebianGuest>;
TEST_F(VirtioNetMultipleInterfacesDebianGuestTest, ReceiveAndEchoMultiple) {
fpromise::bridge<void, void> bridge;
auto handle = std::async(std::launch::async, [this, &bridge] {
FakeNetstack* netstack = this->GetEnclosedGuest().GetNetstack();
TestThread(kDefaultMacAddress, netstack, 0xab, 0xab, /*rotate_bytes=*/true,
/*use_raw_packets=*/false, std::move(bridge.consumer));
});
// The NetworkManager service tries to detect whether there is a working network, and as we
// simply drop all unexpected packets this times out and fails during longer running tests.
// We can just disable this since we don't actually want our networks managed, but longer term
// we need to improve the fake network logic.
//
// TODO(https://fxbug.dev/42065907): Improve fake network to reply to NetworkManager.
EXPECT_EQ(this->Execute({"sudo", "systemctl", "mask", "NetworkManager.service"}), ZX_OK);
EXPECT_EQ(this->Execute({"sudo", "systemctl", "stop", "NetworkManager.service"}), ZX_OK);
// Find the network interface corresponding to the guest's first ethernet device MAC address.
std::string network_interface;
ASSERT_EQ(this->RunUtil(kVirtioNetUtil,
{
"Find",
kDefaultMacString,
},
&network_interface),
ZX_OK);
network_interface = std::string(fxl::TrimString(network_interface, "\n"));
ASSERT_FALSE(network_interface.empty());
// Configure the guest IPv4 address.
EXPECT_EQ(this->Execute({"ifconfig", network_interface, "192.168.0.10"}), ZX_OK);
// Manually add a route to the host.
EXPECT_EQ(this->Execute({"arp", "-s", "192.168.0.1", kHostMacString}), ZX_OK);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioNetUtil,
{
"RotatingTransfer",
fxl::StringPrintf("%u", 0xab),
fxl::StringPrintf("%u", 50000), // Roughly 50MB of data.
fxl::StringPrintf("%zu", kTestPacketSize),
},
&result),
ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
bridge.completer.complete_ok();
handle.wait();
}
TEST_F(VirtioNetMultipleInterfacesDebianGuestTest, ReceiveAndSend) {
fpromise::bridge<void, void> bridge;
auto handle = std::async(std::launch::async, [this, &bridge] {
FakeNetstack* netstack = this->GetEnclosedGuest().GetNetstack();
TestThread(kDefaultMacAddress, netstack, 0xab, 0xba, /*rotate_bytes=*/false,
/*use_raw_packets=*/false, std::move(bridge.consumer));
});
// Find the network interface corresponding to the guest's first ethernet device MAC address.
std::string network_interface;
ASSERT_EQ(this->RunUtil(kVirtioNetUtil,
{
"Find",
kDefaultMacString,
},
&network_interface),
ZX_OK);
network_interface = std::string(fxl::TrimString(network_interface, "\n"));
ASSERT_FALSE(network_interface.empty());
// Configure the guest IPv4 address.
EXPECT_EQ(this->Execute({"ifconfig", network_interface, "192.168.0.10"}), ZX_OK);
// Manually add a route to the host.
EXPECT_EQ(this->Execute({"arp", "-s", "192.168.0.1", kHostMacString}), ZX_OK);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioNetUtil,
{
"Transfer",
fxl::StringPrintf("%u", 0xab),
fxl::StringPrintf("%u", 0xba),
fxl::StringPrintf("%zu", kTestPacketSize),
},
&result),
ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
bridge.completer.complete_ok();
handle.wait();
bridge = fpromise::bridge<void, void>();
// Bring down the first interface
EXPECT_EQ(this->Execute({"ifconfig", network_interface, "down"}), ZX_OK);
// Find the network interface corresponding to the guest's second ethernet device MAC address.
ASSERT_EQ(this->RunUtil(kVirtioNetUtil,
{
"Find",
kSecondNicMacString,
},
&network_interface),
ZX_OK);
network_interface = std::string(fxl::TrimString(network_interface, "\n"));
ASSERT_FALSE(network_interface.empty());
// Configure the guest's second interface with the same settings as the first interface.
EXPECT_EQ(this->Execute({"ifconfig", network_interface, "192.168.0.10"}), ZX_OK);
EXPECT_EQ(this->Execute({"arp", "-s", "192.168.0.1", kHostMacString}), ZX_OK);
// Start a new handler thread to validate the data sent over the second NIC.
handle = std::async(std::launch::async, [this, &bridge] {
FakeNetstack* netstack = this->GetEnclosedGuest().GetNetstack();
TestThread(kSecondNicMacAddress, netstack, 0xcd, 0xdc, /*rotate_bytes=*/false,
/*use_raw_packets=*/false, std::move(bridge.consumer));
});
// Run the net util to generate and validate the data
EXPECT_EQ(this->RunUtil(kVirtioNetUtil,
{
"Transfer",
fxl::StringPrintf("%u", 0xcd),
fxl::StringPrintf("%u", 0xdc),
fxl::StringPrintf("%zu", kTestPacketSize),
},
&result),
ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
bridge.completer.complete_ok();
handle.wait();
}
#endif // __x86_64__