|  | // 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__ |