test: add simple virtio proxy networking tests

This commit is contained in:
Ben Hillis 2025-12-05 14:17:40 -08:00
parent caf38dcad2
commit ee3134afab

View File

@ -105,6 +105,11 @@ bool TryLoadWinhttpProxyMethods() noexcept
} \
}
#define VIRTIOPROXY_TEST_ONLY() \
{ \
WSL2_TEST_ONLY(); \
}
static constexpr auto c_wslVmCreatorId = L"\'{40e0ac32-46a5-438a-A0B2-2B479E8F2E90}\'";
static constexpr auto c_wsaVmCreatorId = L"\'{9E288F02-CE00-4D9E-BE2B-14CE463B0298}\'";
static constexpr auto c_anyVmCreatorId = L"\'{00000000-0000-0000-0000-000000000000}\'";
@ -160,10 +165,14 @@ public:
namespace NetworkTests {
class VirtioProxyTests;
class NetworkTests
{
WSL_TEST_CLASS(NetworkTests)
friend class VirtioProxyTests;
static std::wstring SockaddrToString(const SOCKADDR_INET* sockAddr)
{
constexpr auto ipv4AddressStringLength = 16;
@ -680,7 +689,7 @@ class NetworkTests
LogInfo("'ip route get 2001::5' - '%ls'", out.c_str());
auto [out5, _5] = LxsstuLaunchWslAndCaptureOutput(L"ip addr show eth0");
LogInfo("[TemporaryAddress] ip addr show output: '%ls'", out5.c_str());
LogInfo("[TemporaryAddress] ip addr show output:\r\n'%ls'", out5.c_str());
std::wsmatch match;
std::wregex pattern(L"2001::5 from :: via fc00::1 dev eth0 proto kernel src ([a-f,A-F,0-9,:]+)");
@ -1128,7 +1137,7 @@ class NetworkTests
static void VerifyHttpProxyEnvVariables(const std::wstring& proxyString, const std::wstring& bypassString, const std::wstring& pacUrl)
{
auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"printenv");
LogInfo("VerifyHttpProxyEnvVariables %ls", out.c_str());
LogInfo("VerifyHttpProxyEnvVariables:\r\n%ls", FixLineEndings(out).c_str());
VerifyHttpProxyStringMirrored(proxyString);
VerifyHttpProxyBypassesMirrored(bypassString);
@ -3316,6 +3325,25 @@ class NetworkTests
RunGns(request, LxGnsMessageDeviceSettingRequest);
}
// Convert Unix line endings (\n) to Windows line endings (\r\n) for proper console display
static std::wstring FixLineEndings(const std::wstring& input)
{
std::wstring output;
for (size_t i = 0; i < input.length(); ++i)
{
if (input[i] == L'\n')
{
output += L"\r\n";
}
else if (input[i] != L'\r')
{
output += input[i];
}
}
return output;
}
static RoutingTableState GetRoutingTableState(std::wstring& out, std::wregex& defaultRoutePattern, std::wregex& routePattern)
{
RoutingTableState state;
@ -3344,7 +3372,7 @@ class NetworkTests
static RoutingTableState GetIpv4RoutingTableState()
{
auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip route show");
LogInfo("Ip route output: '%ls'", out.c_str());
LogInfo("Ip route output:\r\n%ls", FixLineEndings(out).c_str());
std::wregex defaultRoutePattern(L"default via ([0-9,.]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
std::wregex routePattern(L"([0-9,.,/]+) via ([0-9,.]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
@ -3355,7 +3383,7 @@ class NetworkTests
static RoutingTableState GetIpv6RoutingTableState()
{
auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip -6 route show");
LogInfo("Ip -6 route output: '%ls'", out.c_str());
LogInfo("Ip -6 route output:\r\n%ls", FixLineEndings(out).c_str());
RoutingTableState state;
std::wregex defaultRoutePattern(L"default via ([a-f,A-F,0-9,:]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
@ -3374,7 +3402,7 @@ class NetworkTests
// inet6 2001::1:2:3:4/64 scope global
// valid_lft forever preferred_lft 0sec
auto [out, warnings] = LxsstuLaunchWslAndCaptureOutput(L"ip addr show " + name);
LogInfo("ip addr show output: '%ls'", out.c_str());
LogInfo("ip addr show output:\r\n%ls", FixLineEndings(out).c_str());
if (expectedWarnings.empty())
{
@ -3689,7 +3717,23 @@ class NetworkTests
SetEvent(event.get());
watchdogThread.wait();
LogInfo("output=\n %S", output.c_str());
// Convert narrow string output to wide string for logging, and fix line endings
std::wstring wideOutput;
wideOutput.reserve(output.length());
for (char c : output)
{
if (c == '\n')
{
wideOutput += L'\r';
wideOutput += L'\n';
}
else if (c != '\r')
{
wideOutput += static_cast<wchar_t>(static_cast<unsigned char>(c));
}
}
LogInfo("output=\r\n%ls", wideOutput.c_str());
return (output.find(substr) != std::string::npos);
}
@ -4298,4 +4342,120 @@ class BridgedTests
VERIFY_ARE_EQUAL(L"0\n", out);
}
};
class VirtioProxyTests
{
WSL_TEST_CLASS(VirtioProxyTests)
std::optional<WslConfigChange> m_config;
TEST_CLASS_SETUP(TestClassSetup)
{
VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
if (LxsstuVmMode())
{
m_config.emplace(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy}));
}
return true;
}
TEST_CLASS_CLEANUP(TestClassCleanup)
{
m_config.reset();
VERIFY_NO_THROW(LxsstuUninitialize(false));
return true;
}
TEST_METHOD(SmokeTest)
{
VIRTIOPROXY_TEST_ONLY();
// Verify that we have a working connection
NetworkTests::GuestClient(L"tcp-connect:bing.com:80");
}
TEST_METHOD(InternetConnectivityV4)
{
VIRTIOPROXY_TEST_ONLY();
if (!NetworkTests::HostHasInternetConnectivity(AF_INET))
{
LogSkipped("Host does not have IPv4 internet connectivity. Skipping...");
return;
}
NetworkTests::GuestClient(L"tcp4-connect:bing.com:80");
}
TEST_METHOD(InternetConnectivityV6)
{
VIRTIOPROXY_TEST_ONLY();
if (!NetworkTests::HostHasInternetConnectivity(AF_INET6))
{
LogSkipped("Host does not have IPv6 internet connectivity. Skipping...");
return;
}
NetworkTests::GuestClient(L"tcp6-connect:bing.com:80");
}
TEST_METHOD(Configuration)
{
VIRTIOPROXY_TEST_ONLY();
const auto state = NetworkTests::GetInterfaceState(L"eth0");
VERIFY_IS_FALSE(state.V4Addresses.empty());
VERIFY_IS_TRUE(state.Gateway.has_value());
auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
const std::wregex pattern(L"(.|\\n)*nameserver [0-9. ]+(.|\\n)*");
VERIFY_IS_TRUE(std::regex_match(out, pattern));
}
TEST_METHOD(GuestPortIsReleased)
{
VIRTIOPROXY_TEST_ONLY();
// Make sure the VM doesn't time out
WslKeepAlive keepAlive;
{
auto guestProcess = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", true);
NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
}
const wil::unique_socket listenSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
VERIFY_IS_TRUE(!!listenSocket);
SOCKADDR_IN Address{};
Address.sin_family = AF_INET;
Address.sin_port = htons(1234);
const auto timeout = std::chrono::steady_clock::now() + std::chrono::minutes(2);
bool bound = false;
while (!bound && std::chrono::steady_clock::now() < timeout)
{
bound = bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) != SOCKET_ERROR;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
VERIFY_IS_TRUE(bound);
}
TEST_METHOD(HttpProxySimple)
{
VIRTIOPROXY_TEST_ONLY();
WINHTTP_PROXY_TEST_ONLY();
WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .autoProxy = true}));
NetworkTests::VerifyHttpProxySimple();
}
};
} // namespace NetworkTests