test: add simple virtio proxy networking tests and move mirrored tests to their own class (#13838)

* test: add simple virtio proxy networking tests * move mirrored tests to their own class * fix failing mirrored variation * use different port for HostUdpBindDoesNotPreventGuestTcpBind --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>

Ben Hillis committed Dec 10, 2025 at 09:41 UTC 25572cc168b5a54e18a63104d5cebc478fd6677d
1 file changed +1172 -973
test/windows/NetworkTests.cpp
+1172 -973
@@ -105,6 +105,11 @@ bool TryLoadWinhttpProxyMethods() noexcept
105 } \
106 }
107
108 +#define VIRTIOPROXY_TEST_ONLY() \
109 + { \
110 + WSL2_TEST_ONLY(); \
111 + }
112 +
113 static constexpr auto c_wslVmCreatorId = L"\'{40e0ac32-46a5-438a-A0B2-2B479E8F2E90}\'";
114 static constexpr auto c_wsaVmCreatorId = L"\'{9E288F02-CE00-4D9E-BE2B-14CE463B0298}\'";
115 static constexpr auto c_anyVmCreatorId = L"\'{00000000-0000-0000-0000-000000000000}\'";
@@ -115,6 +120,9 @@ static const std::wstring c_firewallTrafficTestPort = L"80";
120 static const std::wstring c_firewallTestOtherPort = L"443";
121 static const std::wstring c_dnsTunnelingDefaultIp = L"10.255.255.254";
122
123 +// Set ManualConnectivityValidation to true to manually check stdout from the test to verify the correct calls are made in Linux/Init
124 +static constexpr bool ManualConnectivityValidation = false;
125 +
126 namespace {
127
128 std::wstring GetMacAddress(const std::wstring& adapter = L"eth0")
@@ -160,10 +168,15 @@ public:
168
169 namespace NetworkTests {
170
171 +class VirtioProxyTests;
172 +
173 class NetworkTests
174 {
175 WSL_TEST_CLASS(NetworkTests)
176
177 + friend class MirroredTests;
178 + friend class VirtioProxyTests;
179 +
180 static std::wstring SockaddrToString(const SOCKADDR_INET* sockAddr)
181 {
182 constexpr auto ipv4AddressStringLength = 16;
@@ -308,14 +321,7 @@ class NetworkTests
321 return true;
322 }
323
311 - auto [out, _] = LxsstuLaunchWslAndCaptureOutput(
312 - L"readlink /sys/class/net/eth0 | grep -o -E '[[:xdigit:]]{8}(-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}'", 0);
313 - out.pop_back();
314 -
315 - const auto guid = wsl::shared::string::ToGuid(out);
316 - VERIFY_IS_TRUE(guid.has_value());
317 -
318 - AdapterId = guid.value();
324 + AdapterId = NetworkTests::QueryAdapterId();
325 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ln -f -s /init /gns"), (DWORD)0);
326
327 return true;
@@ -680,7 +686,7 @@ class NetworkTests
686 LogInfo("'ip route get 2001::5' - '%ls'", out.c_str());
687
688 auto [out5, _5] = LxsstuLaunchWslAndCaptureOutput(L"ip addr show eth0");
683 - LogInfo("[TemporaryAddress] ip addr show output: '%ls'", out5.c_str());
689 + LogInfo("[TemporaryAddress] ip addr show output:\r\n%ls", FixLineEndings(out5).c_str());
690
691 std::wsmatch match;
692 std::wregex pattern(L"2001::5 from :: via fc00::1 dev eth0 proto kernel src ([a-f,A-F,0-9,:]+)");
@@ -954,50 +960,6 @@ class NetworkTests
960 VerifyDnsSuffixes();
961 }
962
957 - TEST_METHOD(MirroredDnsTunneling)
958 - {
959 - DNS_TUNNELING_TEST_ONLY();
960 - MIRRORED_NETWORKING_TEST_ONLY();
961 -
962 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true}));
963 - WaitForMirroredStateInLinux();
964 -
965 - VerifyDnsTunneling(c_dnsTunnelingDefaultIp);
966 - }
967 -
968 - TEST_METHOD(MirroredDnsTunnelingWithSpecificIp)
969 - {
970 - DNS_TUNNELING_TEST_ONLY();
971 - MIRRORED_NETWORKING_TEST_ONLY();
972 -
973 - WslConfigChange config(LxssGenerateTestConfig(
974 - {.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true, .dnsTunnelingIpAddress = L"10.255.255.1"}));
975 - WaitForMirroredStateInLinux();
976 -
977 - VerifyDnsTunneling(L"10.255.255.1");
978 - }
979 -
980 - TEST_METHOD(MirroredDnsTunnelingVerifySuffixes)
981 - {
982 - DNS_TUNNELING_TEST_ONLY();
983 - MIRRORED_NETWORKING_TEST_ONLY();
984 -
985 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true}));
986 - WaitForMirroredStateInLinux();
987 -
988 - VerifyDnsSuffixes();
989 - }
990 -
991 - TEST_METHOD(MirroredWithoutTunnelingVerifySuffixes)
992 - {
993 - MIRRORED_NETWORKING_TEST_ONLY();
994 -
995 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = false}));
996 - WaitForMirroredStateInLinux();
997 -
998 - VerifyDnsSuffixes();
999 - }
1000 -
963 TEST_METHOD(NatWithoutIcsDnsProxy)
964 {
965 WSL2_TEST_ONLY();
@@ -1128,7 +1090,7 @@ class NetworkTests
1090 static void VerifyHttpProxyEnvVariables(const std::wstring& proxyString, const std::wstring& bypassString, const std::wstring& pacUrl)
1091 {
1092 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"printenv");
1131 - LogInfo("VerifyHttpProxyEnvVariables %ls", out.c_str());
1093 + LogInfo("VerifyHttpProxyEnvVariables:\r\n%ls", FixLineEndings(out).c_str());
1094
1095 VerifyHttpProxyStringMirrored(proxyString);
1096 VerifyHttpProxyBypassesMirrored(bypassString);
@@ -1335,88 +1297,6 @@ class NetworkTests
1297 VerifyHttpProxyFilterByNetworkConfigurationNAT();
1298 }
1299
1338 - TEST_METHOD(MirroredHttpProxyVerifyConfigDisabled)
1339 - {
1340 - MIRRORED_NETWORKING_TEST_ONLY();
1341 - WINHTTP_PROXY_TEST_ONLY();
1342 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = false}));
1343 - WaitForMirroredStateInLinux();
1344 -
1345 - auto restoreProxySettings = wil::scope_exit([&] { ClearHttpProxySettings(true); });
1346 - SetHttpProxySettings(c_httpProxyString, L"", L"", true);
1347 - VerifyHttpProxyEnvVariables(L"", L"", L"");
1348 - }
1349 -
1350 - TEST_METHOD(MirroredHttpProxySimple)
1351 - {
1352 - MIRRORED_NETWORKING_TEST_ONLY();
1353 - WINHTTP_PROXY_TEST_ONLY();
1354 -
1355 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
1356 - WaitForMirroredStateInLinux();
1357 - VerifyHttpProxySimple();
1358 - }
1359 -
1360 - TEST_METHOD(MirroredHttpProxySimpleMachineScope)
1361 - {
1362 - MIRRORED_NETWORKING_TEST_ONLY();
1363 - WINHTTP_PROXY_TEST_ONLY();
1364 -
1365 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
1366 - WaitForMirroredStateInLinux();
1367 -
1368 - // verify with machine scope
1369 - VerifyHttpProxySimple(false);
1370 - }
1371 -
1372 - TEST_METHOD(MirroredNoHttpProxyConfigured)
1373 - {
1374 - MIRRORED_NETWORKING_TEST_ONLY();
1375 - WINHTTP_PROXY_TEST_ONLY();
1376 -
1377 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
1378 - WaitForMirroredStateInLinux();
1379 - VerifyNoHttpProxyConfigured();
1380 - }
1381 -
1382 - TEST_METHOD(MirroredHttpProxyWithBypassesConfigured)
1383 - {
1384 - MIRRORED_NETWORKING_TEST_ONLY();
1385 - WINHTTP_PROXY_TEST_ONLY();
1386 -
1387 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
1388 - WaitForMirroredStateInLinux();
1389 - VerifyHttpProxyWithBypassesConfigured();
1390 - }
1391 -
1392 - TEST_METHOD(MirroredHttpProxyChange)
1393 - {
1394 - MIRRORED_NETWORKING_TEST_ONLY();
1395 - WINHTTP_PROXY_TEST_ONLY();
1396 -
1397 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
1398 - WaitForMirroredStateInLinux();
1399 - VerifyHttpProxyChange();
1400 - }
1401 -
1402 - TEST_METHOD(MirroredHttpProxyAndWslEnv)
1403 - {
1404 - MIRRORED_NETWORKING_TEST_ONLY();
1405 - WINHTTP_PROXY_TEST_ONLY();
1406 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
1407 - WaitForMirroredStateInLinux();
1408 - VerifyHttpProxyAndWslEnv();
1409 - }
1410 -
1411 - TEST_METHOD(MirroredHttpProxyFilterByNetworkConfiguration)
1412 - {
1413 - MIRRORED_NETWORKING_TEST_ONLY();
1414 - WINHTTP_PROXY_TEST_ONLY();
1415 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
1416 -
1417 - VerifyHttpProxyFilterByNetworkConfigurationMirrored();
1418 - }
1419 -
1300 TEST_METHOD(RenameInterface)
1301 {
1302 WSL2_TEST_ONLY();
@@ -1914,49 +1794,6 @@ class NetworkTests
1794 VerifyLoopbackHostToGuest(L"0.0.0.0", IPPROTO_TCP);
1795 }
1796
1917 - TEST_METHOD(MirroredSmokeTest)
1918 - {
1919 - MIRRORED_NETWORKING_TEST_ONLY();
1920 -
1921 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
1922 - WaitForMirroredStateInLinux();
1923 -
1924 - // Verify that we have a working connection
1925 - GuestClient(L"tcp-connect:bing.com:80");
1926 - }
1927 -
1928 - TEST_METHOD(MirroredInternetConnectivityV4)
1929 - {
1930 - MIRRORED_NETWORKING_TEST_ONLY();
1931 -
1932 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
1933 - WaitForMirroredStateInLinux();
1934 -
1935 - if (!HostHasInternetConnectivity(AF_INET))
1936 - {
1937 - LogSkipped("Host does not have IPv4 internet connectivity. Skipping...");
1938 - return;
1939 - }
1940 -
1941 - GuestClient(L"tcp4-connect:bing.com:80");
1942 - }
1943 -
1944 - TEST_METHOD(MirroredInternetConnectivityV6)
1945 - {
1946 - MIRRORED_NETWORKING_TEST_ONLY();
1947 -
1948 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
1949 - WaitForMirroredStateInLinux();
1950 -
1951 - if (!HostHasInternetConnectivity(AF_INET6))
1952 - {
1953 - LogSkipped("Host does not have IPv6 internet connectivity. Skipping...");
1954 - return;
1955 - }
1956 -
1957 - GuestClient(L"tcp6-connect:bing.com:80");
1958 - }
1959 -
1797 static void VerifyLoopbackGuestToHost(const std::wstring& address, int protocol)
1798 {
1799 LogInfo("VerifyLoopbackGuestToHost(address=%ls, protocol=%d)", address.c_str(), protocol);
@@ -2046,73 +1883,6 @@ class NetworkTests
1883 VerifyLoopbackGuestToGuest(address, IPPROTO_TCP);
1884 }
1885
2049 - TEST_METHOD(MirroredLoopbackLocal)
2050 - {
2051 - MIRRORED_NETWORKING_TEST_ONLY();
2052 -
2053 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
2054 - WaitForMirroredStateInLinux();
2055 -
2056 - std::vector<InterfaceState> interfaceStates = GetAllInterfaceStates();
2057 -
2058 - // Verify loopback connectivity on assigned unicast addresses
2059 - for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
2060 - {
2061 - for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
2062 - {
2063 - // The IP used for DNS tunneling is not intended for guest<->host communication
2064 - if (j->Address != c_dnsTunnelingDefaultIp)
2065 - {
2066 - VerifyLoopbackConnectivity(j->Address);
2067 - }
2068 - }
2069 - for (auto j = i->V6Addresses.begin(); j != i->V6Addresses.end(); ++j)
2070 - {
2071 - // TODO: enable when v6 loopback is supported
2072 - // VerifyLoopbackConnectivity(j->Address);
2073 - }
2074 - }
2075 - }
2076 -
2077 - TEST_METHOD(MirroredLoopbackExplicit)
2078 - {
2079 - MIRRORED_NETWORKING_TEST_ONLY();
2080 -
2081 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2082 - WaitForMirroredStateInLinux();
2083 -
2084 - // Verify loopback connectivity on loopback addresses
2085 - VerifyLoopbackConnectivity(L"127.0.0.1");
2086 - // TODO: enable when v6 loopback is supported
2087 - // VerifyLoopbackConnectivity(L"::1");
2088 - }
2089 -
2090 - TEST_METHOD(MirroredLoopbackSystemd)
2091 - {
2092 - MIRRORED_NETWORKING_TEST_ONLY();
2093 -
2094 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2095 - WaitForMirroredStateInLinux();
2096 -
2097 - // Write a .conf file to conflict with loopback settings.
2098 -#define CONFIG_FILE_PATH L"/etc/sysctl.d/MirroredLoopbackSystemd.conf"
2099 - auto revertConfigFile = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [] {
2100 - const std::wstring deleteConfigFileCmd(L"-u root -e rm " CONFIG_FILE_PATH);
2101 - LxsstuLaunchWsl(deleteConfigFileCmd.data());
2102 - });
2103 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"echo \"net.ipv4.conf.*.rp_filter=2\" > " CONFIG_FILE_PATH), static_cast<DWORD>(0));
2104 -
2105 - // Enable systemd which will apply the .conf file.
2106 - auto revertSystemd = EnableSystemd();
2107 -
2108 - // Verify the settings configured in the systemd hardening logic.
2109 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl net.ipv4.conf.all.rp_filter | grep -w 0"), 0);
2110 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl net.ipv4.conf." TEXT(LX_INIT_LOOPBACK_DEVICE_NAME) L".rp_filter | grep -w 0"), 0);
2111 -
2112 - // Verify an E2E loopback scenario.
2113 - VerifyLoopbackGuestToHost(L"127.0.0.1", IPPROTO_TCP);
2114 - }
2115 -
1886 static wil::unique_socket BindHostPort(uint16_t Port, int Type, int Protocol, bool ExpectSuccess, bool Ipv6 = false, bool Localhost = false)
1887 {
1888 int AddressFamily{};
@@ -2394,231 +2164,53 @@ class NetworkTests
2164 ValidateLocalhostRelayTraffic(false);
2165 }
2166
2397 - TEST_METHOD(MirroredGuestPortCantBeBoundByHost)
2167 + static void TestNonRootNamespaceEphemeralBind()
2168 {
2399 - MIRRORED_NETWORKING_TEST_ONLY();
2169 + // Get the forwarding state.
2170 + auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
2171 + std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
2172
2401 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2402 - WaitForMirroredStateInLinux();
2173 + // Ensure the ephemeral port range configured in the non-root networking namespace does not
2174 + // overlap with the ephemeral port range in the root networking namespace (use the 300 ports
2175 + // preceding the root networking namespace ephemeral port range).
2176 + auto [start, _2] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f1", 0);
2177 + start.pop_back();
2178 + int ephemeralRangeStart = std::stoi(start);
2179
2404 - {
2405 - auto guestProcess = BindGuestPort(L"TCP4-LISTEN:1234", true);
2406 - BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
2407 - }
2180 + int ephemeralRangeEnd = ephemeralRangeStart - 1;
2181 + ephemeralRangeStart = ephemeralRangeEnd - 299;
2182 + VERIFY_IS_GREATER_THAN(ephemeralRangeStart, 1024);
2183 + VERIFY_IS_LESS_THAN_OR_EQUAL(ephemeralRangeEnd, UINT16_MAX);
2184 + const std::wstring ephemeralRangeCommand =
2185 + std::format(L"ip netns exec testns sysctl -w net.ipv4.ip_local_port_range=\"{} {}\"", ephemeralRangeStart, ephemeralRangeEnd);
2186
2409 - {
2410 - auto guestProcess = BindGuestPort(L"UDP4-LISTEN:1234", true);
2411 - BindHostPort(1234, SOCK_DGRAM, IPPROTO_UDP, false);
2412 - }
2413 - }
2187 + // Clean up the below configurations.
2188 + auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
2189 + LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
2190 + LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
2191 + LxsstuLaunchWsl(L"ip link delete veth-test-br");
2192 + LxsstuLaunchWsl(L"ip link delete testbridge");
2193 + LxsstuLaunchWsl(L"ip netns delete testns");
2194 + });
2195
2415 - TEST_METHOD(MirroredGuestPortIsReleased)
2416 - {
2417 - MIRRORED_NETWORKING_TEST_ONLY();
2418 -
2419 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2420 - WaitForMirroredStateInLinux();
2421 -
2422 - // Make sure the VM doesn't time out
2423 - WslKeepAlive keepAlive;
2424 -
2425 - {
2426 - auto guestProcess = BindGuestPort(L"TCP4-LISTEN:1234", true);
2427 - BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
2428 - }
2429 -
2430 - const wil::unique_socket listenSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2431 - VERIFY_IS_TRUE(!!listenSocket);
2432 -
2433 - SOCKADDR_IN Address{};
2434 - Address.sin_family = AF_INET;
2435 - Address.sin_port = htons(1234);
2436 -
2437 - const auto timeout = std::chrono::steady_clock::now() + std::chrono::minutes(2);
2438 -
2439 - bool bound = false;
2440 - while (!bound && std::chrono::steady_clock::now() < timeout)
2441 - {
2442 - bound = bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) != SOCKET_ERROR;
2443 - std::this_thread::sleep_for(std::chrono::seconds(1));
2444 - }
2445 -
2446 - VERIFY_IS_TRUE(bound);
2447 - }
2448 -
2449 - TEST_METHOD(MirroredHostPortCantBeBoundByGuest)
2450 - {
2451 - MIRRORED_NETWORKING_TEST_ONLY();
2452 -
2453 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2454 - WaitForMirroredStateInLinux();
2455 -
2456 - {
2457 - auto hostPort = BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, true);
2458 - BindGuestPort(L"TCP4-LISTEN:1234", false);
2459 - }
2460 -
2461 - {
2462 - auto hostPort = BindHostPort(1234, SOCK_DGRAM, IPPROTO_UDP, true);
2463 - BindGuestPort(L"UDP4-LISTEN:1234", false);
2464 - }
2465 - }
2466 -
2467 - TEST_METHOD(MirroredUdpBindDoesNotPreventTcpBind)
2468 - {
2469 - MIRRORED_NETWORKING_TEST_ONLY();
2470 -
2471 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2472 - WaitForMirroredStateInLinux();
2473 -
2474 - auto tcpPort = BindGuestPort(L"TCP4-LISTEN:1234", true);
2475 - auto udpPort = BindGuestPort(L"UDP4-LISTEN:1234", true);
2476 - }
2477 -
2478 - TEST_METHOD(MirroredHostUdpBindDoesNotPreventGuestTcpBind)
2479 - {
2480 - MIRRORED_NETWORKING_TEST_ONLY();
2481 -
2482 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2483 - WaitForMirroredStateInLinux();
2484 -
2485 - auto tcpPort = BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, true);
2486 - auto udpPort = BindGuestPort(L"UDP4-LISTEN:1234", true);
2487 - }
2488 -
2489 - TEST_METHOD(MirroredMultipleGuestBindOnSameTuple)
2490 - {
2491 - MIRRORED_NETWORKING_TEST_ONLY();
2492 -
2493 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2494 - WaitForMirroredStateInLinux();
2495 -
2496 - auto bind1 = BindGuestPort(L"TCP4-LISTEN:1234,bind=127.0.0.1", true);
2497 - {
2498 - auto bind2 = BindGuestPort(L"TCP6-LISTEN:1234,bind=::1", true);
2499 -
2500 - // Allow time for this second bind to be viewed as "in use" by the init port tracker
2501 - // before closing the socket. If the socket is closed before the init port tracker sees
2502 - // that the port allocation was in use, then the init port tracker will hold onto the
2503 - // allocation for a considerable amount of time (through the duration of this test case)
2504 - // before releasing it.
2505 - std::this_thread::sleep_for(std::chrono::seconds(3));
2506 - }
2507 -
2508 - // Allow time for the init port tracker to detect the second port allocation as no longer in
2509 - // use and perform its cleanup of the second port allocation.
2510 - const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(3);
2511 - while (std::chrono::steady_clock::now() < timeout)
2512 - {
2513 - // {TCP, 1234} should still be reserved for the guest from the first bind.
2514 - auto hostPort = BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
2515 - std::this_thread::sleep_for(std::chrono::seconds(1));
2516 - }
2517 - }
2518 -
2519 - TEST_METHOD(MirroredEphemeralBind)
2520 - {
2521 - MIRRORED_NETWORKING_TEST_ONLY();
2522 -
2523 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2524 - WaitForMirroredStateInLinux();
2525 -
2526 - auto tcpPort = BindGuestPort(L"TCP4-LISTEN:0", true);
2527 - auto udpPort = BindGuestPort(L"UDP4-LISTEN:0", true);
2528 - }
2529 -
2530 - TEST_METHOD(MirroredExplicitEphemeralBind)
2531 - {
2532 - MIRRORED_NETWORKING_TEST_ONLY();
2533 -
2534 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2535 - WaitForMirroredStateInLinux();
2536 -
2537 - // Get ephemeral port range
2538 - auto [start, err1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f1", 0);
2539 - start.pop_back();
2540 - const auto ephemeralRangeStart = std::stoi(start);
2541 -
2542 - auto [end, err2] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f2", 0);
2543 - end.pop_back();
2544 - const auto ephemeralRangeEnd = std::stoi(end);
2545 -
2546 - // Walk the ephemeral port range and verify we can bind to at least one port (some might be already taken, but the test
2547 - // assumes there should be at least one free).
2548 - bool canBindTcp = false;
2549 - bool canBindUdp = false;
2550 -
2551 - for (int port = ephemeralRangeStart; port <= ephemeralRangeEnd; port++)
2552 - {
2553 - auto [tcpListener, tcpSuccess, read] = BindGuestPortHelper(L"TCP4-LISTEN:" + std::to_wstring(port));
2554 - if (tcpSuccess)
2555 - {
2556 - canBindTcp = true;
2557 - break;
2558 - }
2559 - }
2560 -
2561 - for (int port = ephemeralRangeStart; port <= ephemeralRangeEnd; port++)
2562 - {
2563 - auto [udpListener, udpSuccess, read] = BindGuestPortHelper(L"UDP4-LISTEN:" + std::to_wstring(port));
2564 - if (udpSuccess)
2565 - {
2566 - canBindUdp = true;
2567 - break;
2568 - }
2569 - }
2570 -
2571 - VERIFY_IS_TRUE(canBindTcp);
2572 - VERIFY_IS_TRUE(canBindUdp);
2573 - }
2574 -
2575 - static void TestNonRootNamespaceEphemeralBind()
2576 - {
2577 - // Get the forwarding state.
2578 - auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
2579 - std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
2580 -
2581 - // Ensure the ephemeral port range configured in the non-root networking namespace does not
2582 - // overlap with the ephemeral port range in the root networking namespace (use the 300 ports
2583 - // preceding the root networking namespace ephemeral port range).
2584 - auto [start, _2] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f1", 0);
2585 - start.pop_back();
2586 - int ephemeralRangeStart = std::stoi(start);
2587 -
2588 - int ephemeralRangeEnd = ephemeralRangeStart - 1;
2589 - ephemeralRangeStart = ephemeralRangeEnd - 299;
2590 - VERIFY_IS_GREATER_THAN(ephemeralRangeStart, 1024);
2591 - VERIFY_IS_LESS_THAN_OR_EQUAL(ephemeralRangeEnd, UINT16_MAX);
2592 - const std::wstring ephemeralRangeCommand =
2593 - std::format(L"ip netns exec testns sysctl -w net.ipv4.ip_local_port_range=\"{} {}\"", ephemeralRangeStart, ephemeralRangeEnd);
2594 -
2595 - // Clean up the below configurations.
2596 - auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
2597 - LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
2598 - LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
2599 - LxsstuLaunchWsl(L"ip link delete veth-test-br");
2600 - LxsstuLaunchWsl(L"ip link delete testbridge");
2601 - LxsstuLaunchWsl(L"ip netns delete testns");
2602 - });
2603 -
2604 - // Set up a networking namespace and provide it external network access via a bridge, veth
2605 - // pair, SRCNAT iptables rule and forwarding.
2606 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
2607 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(ephemeralRangeCommand.c_str()), 0);
2608 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
2609 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
2610 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
2611 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
2612 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
2613 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
2614 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
2615 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
2616 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
2617 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
2618 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
2619 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
2620 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
2621 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
2196 + // Set up a networking namespace and provide it external network access via a bridge, veth
2197 + // pair, SRCNAT iptables rule and forwarding.
2198 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
2199 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(ephemeralRangeCommand.c_str()), 0);
2200 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
2201 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
2202 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
2203 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
2204 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
2205 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
2206 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
2207 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
2208 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
2209 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
2210 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
2211 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
2212 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
2213 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
2214
2215 // Verify we have connectivity from the networking namespace when using ephemeral port selection.
2216 auto [output, warnings] =
@@ -2656,298 +2248,78 @@ class NetworkTests
2248 TestNonRootNamespaceEphemeralBind();
2249 }
2250
2659 - TEST_METHOD(MirroredNonRootNamespaceEphemeralBind)
2251 + enum class FirewallObjects
2252 {
2661 - MIRRORED_NETWORKING_TEST_ONLY();
2253 + Required,
2254 + NotRequired
2255 + };
2256
2663 - // Because the test creates a new network namespace, the resolv.conf from the root network namespace
2664 - // is copied in the resolv.conf of the new network namespace. The DNS tunneling listener running in the root namespace
2665 - // needs to be accessible from the new namespace, so it can't use a 127* IP
2666 - WslConfigChange config(LxssGenerateTestConfig(
2667 - {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true, .dnsTunnelingIpAddress = L"10.255.255.254"}));
2668 - WaitForMirroredStateInLinux();
2257 + static void ValidateInitialFirewallState(FirewallObjects expectHyperVFirewallObjects)
2258 + {
2259 + // Verify that we have an initially working connection.
2260 + // This also ensures that WSL is started to allow for
2261 + // validating the initial Hyper-V port state
2262 + GuestClient(L"tcp-connect:bing.com:80");
2263
2670 - TestNonRootNamespaceEphemeralBind();
2264 + if (expectHyperVFirewallObjects == FirewallObjects::Required)
2265 + {
2266 + // Query for Hyper-V objects. At least one Hyper-V port is expected
2267 + auto [out, err] = LxsstuLaunchPowershellAndCaptureOutput(L"Get-NetFirewallHyperVPort");
2268 + LogInfo("out:[%ls] err:[%ls]", out.c_str(), err.c_str());
2269 + VERIFY_IS_TRUE(!out.empty());
2270 + }
2271 }
2272
2673 - // Verifies that in mirrored mode, Windows can connect to a listener running in a Linux network namespace different from
2674 - // the Linux root network namespace.
2675 - TEST_METHOD(MirroredPortForwardingToNonRootNamespace)
2273 + static auto AddFirewallRule(const FirewallRule& rule)
2274 {
2677 - MIRRORED_NETWORKING_TEST_ONLY();
2275 + try
2276 + {
2277 + std::wstring cmdPrefix;
2278 + if (rule.Type == FirewallType::HyperV)
2279 + {
2280 + cmdPrefix = L"New-NetFirewallHyperVRule -VmCreatorId " + rule.VmCreatorId + L" -RemotePorts " + rule.RemotePorts;
2281 + }
2282 + else
2283 + {
2284 + cmdPrefix = L"New-NetFirewallRule -Protocol TCP -RemotePort " + rule.RemotePorts;
2285 + }
2286
2679 - WslConfigChange config(LxssGenerateTestConfig(
2680 - {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
2681 - WaitForMirroredStateInLinux();
2287 + auto [out, _] = LxsstuLaunchPowershellAndCaptureOutput(
2288 + cmdPrefix + L" -Name " + rule.Name + L" -DisplayName " + rule.Name + L" -Action " + rule.Action +
2289 + L" -Direction Outbound");
2290
2683 - // We list the IPv4 addresses mirrored in Linux and use the first one we find in the test
2684 - std::vector<InterfaceState> interfaceStates = GetAllInterfaceStates();
2685 - std::wstring ipAddress;
2291 + LogInfo("AddRule output:\r\n%ls", FixLineEndings(out).c_str());
2292
2687 - for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
2688 - {
2689 - for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
2293 + // output what, if any, Hyper-V Firewall rules were created in response to the above
2294 + auto [query_output, __] = LxsstuLaunchPowershellAndCaptureOutput(L"Get-NetFirewallHyperVRule -Name " + rule.Name);
2295 + LogInfo("Get-NetFirewallHyperVRule output:\r\n%ls", FixLineEndings(query_output).c_str());
2296 + }
2297 + CATCH_LOG()
2298 +
2299 + return wil::scope_exit([rule]() {
2300 + try
2301 {
2691 - // The IP used for DNS tunneling is not intended for guest<->host communication
2692 - if (j->Address != c_dnsTunnelingDefaultIp)
2302 + LogInfo("Removing the test rule %ls\n", rule.Name.c_str());
2303 + std::wstring cmdPrefix;
2304 + if (rule.Type == FirewallType::HyperV)
2305 {
2694 - ipAddress = j->Address;
2695 - break;
2306 + cmdPrefix = L"Remove-NetFirewallHyperVRule";
2307 + }
2308 + else
2309 + {
2310 + cmdPrefix = L"Remove-NetFirewallRule";
2311 }
2312 + LxsstuLaunchPowershellAndCaptureOutput(cmdPrefix + L" -Name " + rule.Name);
2313 }
2698 - }
2314 + CATCH_LOG()
2315 + });
2316 + }
2317
2700 - // Get the forwarding state.
2701 - auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
2702 - std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
2703 -
2704 - // Clean up the below configurations.
2705 - auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
2706 - LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
2707 - LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
2708 - LxsstuLaunchWsl(L"--system --user root nft flush chain nat PREROUTING");
2709 - LxsstuLaunchWsl(L"ip link delete veth-test-br");
2710 - LxsstuLaunchWsl(L"ip link delete testbridge");
2711 - LxsstuLaunchWsl(L"ip netns delete testns");
2712 - });
2713 -
2714 - // Set up a networking namespace and provide it external network access via a bridge, veth
2715 - // pair, SRCNAT iptables rule and forwarding.
2716 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
2717 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
2718 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
2719 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
2720 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
2721 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
2722 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
2723 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
2724 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
2725 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
2726 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
2727 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
2728 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
2729 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
2730 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
2731 -
2732 - // Add rule for port forwarding traffic with destination port 8080 to port 80 in the new namespace
2733 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat PREROUTING { type nat hook prerouting priority dstnat; }\""), 0);
2734 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat PREROUTING tcp dport 8080 dnat to 192.168.15.2:80"), 0);
2735 -
2736 - // Start listeners in root namespace on port 8080 and new namespace on port 80
2737 - SOCKADDR_INET rootListenerAddr = wsl::windows::common::string::StringToSockAddrInet(L"0.0.0.0");
2738 - SS_PORT(&rootListenerAddr) = htons(8080);
2739 - GuestListener rootListener(rootListenerAddr, IPPROTO_TCP);
2740 -
2741 - SOCKADDR_INET namespaceListenerAddr = wsl::windows::common::string::StringToSockAddrInet(L"0.0.0.0");
2742 - SS_PORT(&namespaceListenerAddr) = htons(80);
2743 - GuestListener namespaceListener(namespaceListenerAddr, IPPROTO_TCP, L"testns");
2744 -
2745 - // Verify Windows can connect to port 8080
2746 - SOCKADDR_INET serverAddr = wsl::windows::common::string::StringToSockAddrInet(ipAddress);
2747 - SS_PORT(&serverAddr) = htons(8080);
2748 -
2749 - wil::unique_socket clientSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2750 - VERIFY_ARE_NOT_EQUAL(clientSocket.get(), INVALID_SOCKET);
2751 -
2752 - VERIFY_ARE_EQUAL(connect(clientSocket.get(), reinterpret_cast<SOCKADDR*>(&serverAddr), sizeof(serverAddr)), 0);
2753 - }
2754 -
2755 - TEST_METHOD(MirroredLinuxNonRootNamespaceConnectToWindowsHost)
2756 - {
2757 - MIRRORED_NETWORKING_TEST_ONLY();
2758 -
2759 - WslConfigChange config(LxssGenerateTestConfig(
2760 - {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
2761 - WaitForMirroredStateInLinux();
2762 -
2763 - // We list the IPv4 addresses mirrored in Linux and use the first one we find in the test
2764 - std::vector<InterfaceState> interfaceStates = GetAllInterfaceStates();
2765 - std::wstring ipAddress;
2766 -
2767 - for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
2768 - {
2769 - for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
2770 - {
2771 - // The IP used for DNS tunneling is not intended for guest<->host communication
2772 - if (j->Address != c_dnsTunnelingDefaultIp)
2773 - {
2774 - ipAddress = j->Address;
2775 - break;
2776 - }
2777 - }
2778 - }
2779 -
2780 - // Get the forwarding state.
2781 - auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
2782 - std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
2783 -
2784 - // Clean up the below configurations.
2785 - auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
2786 - LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
2787 - LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
2788 - LxsstuLaunchWsl(L"ip link delete veth-test-br");
2789 - LxsstuLaunchWsl(L"ip link delete testbridge");
2790 - LxsstuLaunchWsl(L"ip netns delete testns");
2791 - });
2792 -
2793 - // Set up a networking namespace and provide it external network access via a bridge, veth
2794 - // pair, SRCNAT iptables rule and forwarding.
2795 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
2796 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
2797 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
2798 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
2799 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
2800 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
2801 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
2802 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
2803 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
2804 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
2805 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
2806 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
2807 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
2808 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
2809 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
2810 -
2811 - // Create a listener on the Windows host on port 1234
2812 - SOCKADDR_INET addr = wsl::windows::common::string::StringToSockAddrInet(ipAddress);
2813 - SS_PORT(&addr) = htons(1234);
2814 -
2815 - const wil::unique_socket listenSocket(socket(addr.si_family, SOCK_STREAM, IPPROTO_TCP));
2816 - VERIFY_ARE_NOT_EQUAL(listenSocket.get(), INVALID_SOCKET);
2817 - VERIFY_ARE_NOT_EQUAL(bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)), SOCKET_ERROR);
2818 - VERIFY_ARE_NOT_EQUAL(listen(listenSocket.get(), SOMAXCONN), SOCKET_ERROR);
2819 -
2820 - // Verify the new network namespace can connect to the Windows host listener
2821 - auto [output, warnings] = LxsstuLaunchWslAndCaptureOutput(
2822 - L"ip netns exec testns socat -dd tcp-connect:" + ipAddress + L":1234 create:/tmp/nonexistent", 1);
2823 - LogInfo("output %s", output.c_str());
2824 - LogInfo("warnings %s", warnings.c_str());
2825 - VERIFY_ARE_NOT_EQUAL(warnings.find(L"starting data transfer loop"), std::string::npos);
2826 - }
2827 -
2828 - TEST_METHOD(MirroredResolvConf)
2829 - {
2830 - MIRRORED_NETWORKING_TEST_ONLY();
2831 -
2832 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2833 - WaitForMirroredStateInLinux();
2834 -
2835 - auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
2836 - const std::wregex pattern(L"(.|\n)*nameserver [0-9\\. ]+(.|\n)*", std::regex::extended);
2837 -
2838 - VERIFY_IS_TRUE(std::regex_match(out, pattern));
2839 - }
2840 -
2841 - TEST_METHOD(MirroredNetworkSettings)
2842 - {
2843 - MIRRORED_NETWORKING_TEST_ONLY();
2844 -
2845 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
2846 - WaitForMirroredStateInLinux();
2847 -
2848 - struct NetworkSetting
2849 - {
2850 - const std::wstring Path;
2851 - const std::wstring ExpectedValue;
2852 - };
2853 -
2854 - std::vector<NetworkSetting> settings{
2855 - {L"/proc/sys/net/ipv6/conf/all/accept_ra", L"0\n"},
2856 - {L"/proc/sys/net/ipv6/conf/default/accept_ra", L"0\n"},
2857 - {L"/proc/sys/net/ipv6/conf/all/dad_transmits", L"0\n"},
2858 - {L"/proc/sys/net/ipv6/conf/default/dad_transmits", L"0\n"},
2859 - {L"/proc/sys/net/ipv6/conf/all/autoconf", L"0\n"},
2860 - {L"/proc/sys/net/ipv6/conf/default/autoconf", L"0\n"},
2861 - {L"/proc/sys/net/ipv6/conf/all/addr_gen_mode", L"1\n"},
2862 - {L"/proc/sys/net/ipv6/conf/default/addr_gen_mode", L"1\n"},
2863 - {L"/proc/sys/net/ipv6/conf/all/use_tempaddr", L"0\n"},
2864 - {L"/proc/sys/net/ipv6/conf/default/use_tempaddr", L"0\n"},
2865 - {L"/proc/sys/net/ipv4/conf/all/arp_filter", L"1\n"},
2866 - {L"/proc/sys/net/ipv4/conf/all/rp_filter", L"0\n"},
2867 - };
2868 -
2869 - settings.push_back({L"/proc/sys/net/ipv4/conf/" + GetGelNicDeviceName() + L"/rp_filter", L"0\n"});
2870 -
2871 - for (const auto& setting : settings)
2872 - {
2873 - auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat " + setting.Path);
2874 - LogInfo("%ls", (setting.Path + L" : " + out).c_str());
2875 - VERIFY_ARE_EQUAL(setting.ExpectedValue, out);
2876 - }
2877 - }
2878 -
2879 - enum class FirewallObjects
2880 - {
2881 - Required,
2882 - NotRequired
2883 - };
2884 -
2885 - static void ValidateInitialFirewallState(FirewallObjects expectHyperVFirewallObjects)
2886 - {
2887 - // Verify that we have an initially working connection.
2888 - // This also ensures that WSL is started to allow for
2889 - // validating the initial Hyper-V port state
2890 - GuestClient(L"tcp-connect:bing.com:80");
2891 -
2892 - if (expectHyperVFirewallObjects == FirewallObjects::Required)
2893 - {
2894 - // Query for Hyper-V objects. At least one Hyper-V port is expected
2895 - auto [out, err] = LxsstuLaunchPowershellAndCaptureOutput(L"Get-NetFirewallHyperVPort");
2896 - LogInfo("out:[%ls] err:[%ls]", out.c_str(), err.c_str());
2897 - VERIFY_IS_TRUE(!out.empty());
2898 - }
2899 - }
2900 -
2901 - static auto AddFirewallRule(const FirewallRule& rule)
2902 - {
2903 - try
2904 - {
2905 - std::wstring cmdPrefix;
2906 - if (rule.Type == FirewallType::HyperV)
2907 - {
2908 - cmdPrefix = L"New-NetFirewallHyperVRule -VmCreatorId " + rule.VmCreatorId + L" -RemotePorts " + rule.RemotePorts;
2909 - }
2910 - else
2911 - {
2912 - cmdPrefix = L"New-NetFirewallRule -Protocol TCP -RemotePort " + rule.RemotePorts;
2913 - }
2914 -
2915 - auto [out, _] = LxsstuLaunchPowershellAndCaptureOutput(
2916 - cmdPrefix + L" -Name " + rule.Name + L" -DisplayName " + rule.Name + L" -Action " + rule.Action +
2917 - L" -Direction Outbound");
2918 -
2919 - LogInfo("AddRule output:[\n %ls]", out.c_str());
2920 -
2921 - // output what, if any, Hyper-V Firewall rules were created in response to the above
2922 - auto [query_output, __] = LxsstuLaunchPowershellAndCaptureOutput(L"Get-NetFirewallHyperVRule -Name " + rule.Name);
2923 - LogInfo("Get-NetFirewallHyperVRule output:[\n %ls]", query_output.c_str());
2924 - }
2925 - CATCH_LOG()
2926 -
2927 - return wil::scope_exit([rule]() {
2928 - try
2929 - {
2930 - LogInfo("Removing the test rule %ls\n", rule.Name.c_str());
2931 - std::wstring cmdPrefix;
2932 - if (rule.Type == FirewallType::HyperV)
2933 - {
2934 - cmdPrefix = L"Remove-NetFirewallHyperVRule";
2935 - }
2936 - else
2937 - {
2938 - cmdPrefix = L"Remove-NetFirewallRule";
2939 - }
2940 - LxsstuLaunchPowershellAndCaptureOutput(cmdPrefix + L" -Name " + rule.Name);
2941 - }
2942 - CATCH_LOG()
2943 - });
2944 - }
2945 -
2946 - enum class FirewallTestConnectivity
2947 - {
2948 - Allowed,
2949 - Blocked
2950 - };
2318 + enum class FirewallTestConnectivity
2319 + {
2320 + Allowed,
2321 + Blocked
2322 + };
2323
2324 static auto AddFirewallRuleAndValidateTraffic(const FirewallRule& rule, FirewallTestConnectivity expectedConnectivityAfterRule)
2325 {
@@ -3092,20 +2464,6 @@ class NetworkTests
2464 FirewallRuleBlockedTests(FirewallTestConnectivity::Blocked);
2465 }
2466
3095 - TEST_METHOD(MirroredFirewallRulesExpectedBlock)
3096 - {
3097 - HYPERV_FIREWALL_TEST_ONLY();
3098 - MIRRORED_NETWORKING_TEST_ONLY();
3099 -
3100 - SKIP_TEST_UNSTABLE();
3101 -
3102 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3103 - WaitForMirroredStateInLinux();
3104 -
3105 - ValidateInitialFirewallState(FirewallObjects::Required);
3106 - FirewallRuleBlockedTests(FirewallTestConnectivity::Blocked);
3107 - }
3108 -
2467 static void FirewallRuleAllowedTests(FirewallTestConnectivity expectedConnectivity)
2468 {
2469 // A host rule with different IP address should not affect traffic
@@ -3152,24 +2510,10 @@ class NetworkTests
2510 FirewallRuleAllowedTests(FirewallTestConnectivity::Allowed);
2511 }
2512
3155 - TEST_METHOD(MirroredFirewallRulesExpectedAllow)
2513 + static void FirewallSettingEnabledTests(bool isHyperVFirewallEnabled)
2514 {
3157 - HYPERV_FIREWALL_TEST_ONLY();
3158 - MIRRORED_NETWORKING_TEST_ONLY();
3159 -
3160 - SKIP_TEST_UNSTABLE();
3161 -
3162 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3163 - WaitForMirroredStateInLinux();
3164 -
3165 - ValidateInitialFirewallState(FirewallObjects::Required);
3166 - FirewallRuleAllowedTests(FirewallTestConnectivity::Allowed);
3167 - }
3168 -
3169 - static void FirewallSettingEnabledTests(bool isHyperVFirewallEnabled)
3170 - {
3171 - // Configure Firewall disabled
3172 - auto hostDisabledCleanup = ConfigureFirewallEnabled(FirewallType::Host, false);
2515 + // Configure Firewall disabled
2516 + auto hostDisabledCleanup = ConfigureFirewallEnabled(FirewallType::Host, false);
2517
2518 // Add host block rule, which is expected to be enforced
2519 FirewallRule blockRule = {FirewallType::Host, L"WSLTestBlockRule", c_firewallTrafficTestPort, c_firewallRuleActionBlock, c_wslVmCreatorId};
@@ -3252,22 +2596,20 @@ class NetworkTests
2596 FirewallSettingEnabledTests(false);
2597 }
2598
3255 - TEST_METHOD(MirroredFirewallRulesEnabledSetting)
3256 - {
3257 - HYPERV_FIREWALL_TEST_ONLY();
3258 - MIRRORED_NETWORKING_TEST_ONLY();
2599 + /* Network Tests Helper Methods */
2600
3260 - SKIP_TEST_UNSTABLE();
2601 + static GUID QueryAdapterId()
2602 + {
2603 + auto [out, _] = LxsstuLaunchWslAndCaptureOutput(
2604 + L"readlink /sys/class/net/eth0 | grep -o -E '[[:xdigit:]]{8}(-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}'", 0);
2605 + out.pop_back();
2606
3262 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3263 - WaitForMirroredStateInLinux();
2607 + const auto guid = wsl::shared::string::ToGuid(out);
2608 + VERIFY_IS_TRUE(guid.has_value());
2609
3265 - ValidateInitialFirewallState(FirewallObjects::Required);
3266 - FirewallSettingEnabledTests(true);
2610 + return guid.value();
2611 }
2612
3269 - /* Network Tests Helper Methods */
3270 -
2613 static void RunGns(const std::string& input, const std::optional<GUID>& adapter = {}, const std::optional<LX_MESSAGE_TYPE>& messageType = {}, int expectedErrorCode = 0)
2614 {
2615 constexpr auto InheritOnReadHandle = true;
@@ -3316,6 +2658,25 @@ class NetworkTests
2658 RunGns(request, LxGnsMessageDeviceSettingRequest);
2659 }
2660
2661 + // Convert Unix line endings (\n) to Windows line endings (\r\n) for proper console display
2662 + static std::wstring FixLineEndings(const std::wstring& input)
2663 + {
2664 + std::wstring output;
2665 + for (size_t i = 0; i < input.length(); ++i)
2666 + {
2667 + if (input[i] == L'\n')
2668 + {
2669 + output += L"\r\n";
2670 + }
2671 + else if (input[i] != L'\r')
2672 + {
2673 + output += input[i];
2674 + }
2675 + }
2676 +
2677 + return output;
2678 + }
2679 +
2680 static RoutingTableState GetRoutingTableState(std::wstring& out, std::wregex& defaultRoutePattern, std::wregex& routePattern)
2681 {
2682 RoutingTableState state;
@@ -3344,7 +2705,7 @@ class NetworkTests
2705 static RoutingTableState GetIpv4RoutingTableState()
2706 {
2707 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip route show");
3347 - LogInfo("Ip route output: '%ls'", out.c_str());
2708 + LogInfo("Ip route output:\r\n%ls", FixLineEndings(out).c_str());
2709
2710 std::wregex defaultRoutePattern(L"default via ([0-9,.]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
2711 std::wregex routePattern(L"([0-9,.,/]+) via ([0-9,.]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
@@ -3355,7 +2716,7 @@ class NetworkTests
2716 static RoutingTableState GetIpv6RoutingTableState()
2717 {
2718 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip -6 route show");
3358 - LogInfo("Ip -6 route output: '%ls'", out.c_str());
2719 + LogInfo("Ip -6 route output:\r\n%ls", FixLineEndings(out).c_str());
2720
2721 RoutingTableState state;
2722 std::wregex defaultRoutePattern(L"default via ([a-f,A-F,0-9,:]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
@@ -3374,7 +2735,7 @@ class NetworkTests
2735 // inet6 2001::1:2:3:4/64 scope global
2736 // valid_lft forever preferred_lft 0sec
2737 auto [out, warnings] = LxsstuLaunchWslAndCaptureOutput(L"ip addr show " + name);
3377 - LogInfo("ip addr show output: '%ls'", out.c_str());
2738 + LogInfo("ip addr show output:\r\n%ls", FixLineEndings(out).c_str());
2739
2740 if (expectedWarnings.empty())
2741 {
@@ -3439,7 +2800,7 @@ class NetworkTests
2800 }
2801 else
2802 {
3442 - LogInfo("Ip addr output: '%ls'", out.c_str());
2803 + LogInfo("Ip addr output:\r\n%ls", FixLineEndings(out).c_str());
2804 LogInfo("Current line: \"%ls\"", line.c_str());
2805 VERIFY_FAIL(L"Failed to extract interface state");
2806 }
@@ -3482,7 +2843,7 @@ class NetworkTests
2843 {
2844 // Result output is a list of interface names with newline as the delimiter
2845 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip -brief link show | awk -F '[@ ]' '{print $1}'");
3485 - LogInfo("parsed ip link output:'%ls'", out.c_str());
2846 + LogInfo("parsed ip link output:\r\n%ls", FixLineEndings(out).c_str());
2847
2848 std::wistringstream input(out);
2849
@@ -3689,7 +3050,23 @@ class NetworkTests
3050 SetEvent(event.get());
3051 watchdogThread.wait();
3052
3692 - LogInfo("output=\n %S", output.c_str());
3053 + // Convert narrow string output to wide string for logging, and fix line endings
3054 + std::wstring wideOutput;
3055 + wideOutput.reserve(output.length());
3056 + for (char c : output)
3057 + {
3058 + if (c == '\n')
3059 + {
3060 + wideOutput += L'\r';
3061 + wideOutput += L'\n';
3062 + }
3063 + else if (c != '\r')
3064 + {
3065 + wideOutput += static_cast<wchar_t>(static_cast<unsigned char>(c));
3066 + }
3067 + }
3068 + LogInfo("output=\r\n%ls", wideOutput.c_str());
3069 +
3070 return (output.find(substr) != std::string::npos);
3071 }
3072
@@ -3868,67 +3245,6 @@ class NetworkTests
3245 return buffer;
3246 }
3247
3871 - // Due to VM creation performance requirements, VM creation is allowed to finish even if all
3872 - // networking state has not been mirrored yet. This introduces a race condition between the
3873 - // mirroring of networking state and mirrored mode test case execution that relies on the
3874 - // networking state being mirrored.
3875 - //
3876 - // This routine resolves the race condition by waiting for networking state to be mirrored into
3877 - // the VM. Tracking all mirrored networking state is complicated, so we use a heuristic to
3878 - // simplify: default routes have been observed to be mirrored last, so if they are present in
3879 - // the VM then we consider mirroring to be completed.
3880 - static void WaitForMirroredStateInLinux()
3881 - {
3882 - const bool hostConnectivityV4 = HostHasInternetConnectivity(AF_INET);
3883 - const bool hostConnectivityV6 = HostHasInternetConnectivity(AF_INET6);
3884 -
3885 - Stopwatch<std::chrono::seconds> Watchdog(std::chrono::seconds(30));
3886 -
3887 - do
3888 - {
3889 - // Count how many interfaces have v4/v6 connectivity, as defined by having a gateway and at least 1 preferred address.
3890 - int interfacesWithV4Connectivity = 0;
3891 - int interfacesWithV6Connectivity = 0;
3892 -
3893 - // Get all interface info from the VM.
3894 - for (const auto& i : GetAllInterfaceStates())
3895 - {
3896 - if (i.Gateway.has_value())
3897 - {
3898 - for (const auto& j : i.V4Addresses)
3899 - {
3900 - if (j.Preferred)
3901 - {
3902 - interfacesWithV4Connectivity++;
3903 - break;
3904 - }
3905 - }
3906 - }
3907 - if (i.V6Gateway.has_value())
3908 - {
3909 - for (const auto& j : i.V6Addresses)
3910 - {
3911 - if (j.Preferred)
3912 - {
3913 - interfacesWithV6Connectivity++;
3914 - break;
3915 - }
3916 - }
3917 - }
3918 - }
3919 -
3920 - // Consider mirroring to be complete if we have the same v4/v6 connectivity in the VM as the host.
3921 - if ((!hostConnectivityV4 || interfacesWithV4Connectivity > 0) && (!hostConnectivityV6 || interfacesWithV6Connectivity > 0))
3922 - {
3923 - break;
3924 - }
3925 -
3926 - LogInfo("Waiting for mirrored state...");
3927 - } while (Sleep(1000), !Watchdog.IsExpired());
3928 -
3929 - VERIFY_IS_FALSE(Watchdog.IsExpired());
3930 - }
3931 -
3248 static void WaitForNATStateInLinux()
3249 {
3250 Stopwatch<std::chrono::seconds> Watchdog(std::chrono::seconds(30));
@@ -3976,21 +3292,841 @@ class NetworkTests
3292 break;
3293 }
3294
3979 - LogInfo("Waiting for NAT state...");
3980 - } while (Sleep(1000), !Watchdog.IsExpired());
3981 - VERIFY_IS_FALSE(Watchdog.IsExpired());
3295 + LogInfo("Waiting for NAT state...");
3296 + } while (Sleep(1000), !Watchdog.IsExpired());
3297 + VERIFY_IS_FALSE(Watchdog.IsExpired());
3298 + }
3299 +
3300 + TEST_METHOD(ConnectivityCheckTestNATDefaultSuccess)
3301 + {
3302 + WSL2_TEST_ONLY();
3303 +
3304 + WslConfigChange config(LxssGenerateTestConfig());
3305 + WaitForNATStateInLinux();
3306 +
3307 + const auto coInit = wil::CoInitializeEx();
3308 + const wil::com_ptr<INetworkListManager> networkListManager = wil::CoCreateInstance<NetworkListManager, INetworkListManager>();
3309 + VERIFY_IS_NOT_NULL(networkListManager.get());
3310 + NLM_CONNECTIVITY hostConnectivity{};
3311 + VERIFY_SUCCEEDED(networkListManager->GetConnectivity(&hostConnectivity));
3312 +
3313 + // Windows
3314 + const wsl::shared::conncheck::ConnCheckResult hostResult =
3315 + wsl::shared::conncheck::CheckConnection("www.msftconnecttest.com", "ipv6.msftconnecttest.com", "80");
3316 +
3317 + if (hostConnectivity & NLM_CONNECTIVITY_IPV4_INTERNET)
3318 + {
3319 + VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::Success, hostResult.Ipv4Status);
3320 + }
3321 + else
3322 + {
3323 + // one of the 2 expected runtime failures
3324 + VERIFY_IS_TRUE(
3325 + hostResult.Ipv4Status == wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo ||
3326 + hostResult.Ipv4Status == wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
3327 + }
3328 + if (hostConnectivity & NLM_CONNECTIVITY_IPV6_INTERNET)
3329 + {
3330 + VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::Success, hostResult.Ipv4Status);
3331 + }
3332 + else
3333 + {
3334 + // one of the 2 expected runtime failures (sometimes v6 name resolution will fail, depending on the configuration)
3335 + VERIFY_IS_TRUE(
3336 + hostResult.Ipv6Status == wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo ||
3337 + hostResult.Ipv6Status == wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
3338 + }
3339 +
3340 + // www.msftconnecttest.com will always fail IPv6 name resolution - it doesn't have any AAAA records registered for it
3341 + const int expectedErrorCode = static_cast<int>(hostResult.Ipv4Status) |
3342 + (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) << 16);
3343 + LogInfo("RunGns(www.msftconnecttest.com, 0x%x)", expectedErrorCode);
3344 + // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
3345 + // it returns 1 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::Success)
3346 + // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
3347 + // if ManualConnectivityValidation is set true, one can confirm from the stdout captured that the correct result was determined and returned by init.
3348 + constexpr auto testErrorCode =
3349 + ManualConnectivityValidation ? expectedErrorCode : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::Success);
3350 + RunGns("www.msftconnecttest.com", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
3351 + }
3352 +
3353 + TEST_METHOD(ConnectivityCheckTestNATNameResolutionFailure)
3354 + {
3355 + WSL2_TEST_ONLY();
3356 +
3357 + WslConfigChange config(LxssGenerateTestConfig());
3358 + WaitForNATStateInLinux();
3359 +
3360 + // Windows
3361 + const wsl::shared::conncheck::ConnCheckResult result =
3362 + wsl::shared::conncheck::CheckConnection("asdlkfadsf.bbcxzncvb", nullptr, "80");
3363 +
3364 + VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo, result.Ipv4Status);
3365 + VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo, result.Ipv6Status);
3366 +
3367 + constexpr int expectedErrorCode = static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) |
3368 + (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) << 16);
3369 + LogInfo("RunGns(asdlkfadsf.bbcxzncvb, 0x%x)", expectedErrorCode);
3370 + // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
3371 + // it returns 2 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo))
3372 + // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
3373 + // if temporarily change this back to expectedErrorCode, one can confirm from the stdout captured that the correct result was determined and returned by init.
3374 + constexpr auto testErrorCode = ManualConnectivityValidation
3375 + ? expectedErrorCode
3376 + : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo);
3377 + RunGns("asdlkfadsf.bbcxzncvb", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
3378 + }
3379 +
3380 + TEST_METHOD(ConnectivityCheckTestNATNameResolvesButConnectivityFails)
3381 + {
3382 + WSL2_TEST_ONLY();
3383 +
3384 + WslConfigChange config(LxssGenerateTestConfig());
3385 + WaitForNATStateInLinux();
3386 +
3387 + const auto* ncsiDnsOnlyName = "dns.msftncsi.com";
3388 + // v4 and v6 should succeed to resolve the name, but fail to connect,
3389 + // as this NCSI name is registered in global DNS, but there's not HTTP endpoint for it
3390 +
3391 + // Windows
3392 + const wsl::shared::conncheck::ConnCheckResult result =
3393 + wsl::shared::conncheck::CheckConnection(ncsiDnsOnlyName, nullptr, "80");
3394 +
3395 + VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect, result.Ipv4Status);
3396 + // v6 name resolution might fail, depending on the configuration
3397 + VERIFY_IS_TRUE(
3398 + (wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo == result.Ipv6Status) ||
3399 + (wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect == result.Ipv6Status));
3400 +
3401 + constexpr int expectedErrorCode = static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect) |
3402 + (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect) << 16);
3403 + LogInfo("RunGns(%hs, 0x%x)", ncsiDnsOnlyName, expectedErrorCode);
3404 + // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
3405 + // it returns 4 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect))
3406 + // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
3407 + // if ManualConnectivityValidation is set true, one can confirm from the stdout captured that the correct result was determined and returned by init.
3408 + constexpr auto testErrorCode = ManualConnectivityValidation
3409 + ? expectedErrorCode
3410 + : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
3411 + RunGns(ncsiDnsOnlyName, AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
3412 + }
3413 +};
3414 +
3415 +class MirroredTests
3416 +{
3417 + WSL_TEST_CLASS(MirroredTests)
3418 +
3419 + std::optional<WslConfigChange> m_config;
3420 + GUID AdapterId;
3421 +
3422 + TEST_CLASS_SETUP(TestClassSetup)
3423 + {
3424 + VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
3425 +
3426 + if (LxsstuVmMode())
3427 + {
3428 + m_config.emplace(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3429 +
3430 + AdapterId = NetworkTests::QueryAdapterId();
3431 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ln -f -s /init /gns"), (DWORD)0);
3432 + }
3433 +
3434 + return true;
3435 + }
3436 +
3437 + TEST_CLASS_CLEANUP(TestClassCleanup)
3438 + {
3439 + m_config.reset();
3440 +
3441 + VERIFY_NO_THROW(LxsstuUninitialize(false));
3442 +
3443 + return true;
3444 + }
3445 +
3446 + TEST_METHOD(DnsTunneling)
3447 + {
3448 + DNS_TUNNELING_TEST_ONLY();
3449 + MIRRORED_NETWORKING_TEST_ONLY();
3450 +
3451 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true}));
3452 + WaitForMirroredStateInLinux();
3453 +
3454 + NetworkTests::VerifyDnsTunneling(c_dnsTunnelingDefaultIp);
3455 + }
3456 +
3457 + TEST_METHOD(DnsTunnelingWithSpecificIp)
3458 + {
3459 + DNS_TUNNELING_TEST_ONLY();
3460 + MIRRORED_NETWORKING_TEST_ONLY();
3461 +
3462 + m_config->Update(LxssGenerateTestConfig(
3463 + {.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true, .dnsTunnelingIpAddress = L"10.255.255.1"}));
3464 + WaitForMirroredStateInLinux();
3465 +
3466 + NetworkTests::VerifyDnsTunneling(L"10.255.255.1");
3467 + }
3468 +
3469 + TEST_METHOD(DnsTunnelingVerifySuffixes)
3470 + {
3471 + DNS_TUNNELING_TEST_ONLY();
3472 + MIRRORED_NETWORKING_TEST_ONLY();
3473 +
3474 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true}));
3475 + WaitForMirroredStateInLinux();
3476 +
3477 + NetworkTests::VerifyDnsSuffixes();
3478 + }
3479 +
3480 + TEST_METHOD(WithoutTunnelingVerifySuffixes)
3481 + {
3482 + MIRRORED_NETWORKING_TEST_ONLY();
3483 +
3484 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = false}));
3485 + WaitForMirroredStateInLinux();
3486 +
3487 + NetworkTests::VerifyDnsSuffixes();
3488 + }
3489 +
3490 + TEST_METHOD(HttpProxyVerifyConfigDisabled)
3491 + {
3492 + MIRRORED_NETWORKING_TEST_ONLY();
3493 + WINHTTP_PROXY_TEST_ONLY();
3494 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = false}));
3495 + WaitForMirroredStateInLinux();
3496 +
3497 + auto restoreProxySettings = wil::scope_exit([&] { NetworkTests::ClearHttpProxySettings(true); });
3498 + NetworkTests::SetHttpProxySettings(NetworkTests::c_httpProxyString, L"", L"", true);
3499 + NetworkTests::VerifyHttpProxyEnvVariables(L"", L"", L"");
3500 + }
3501 +
3502 + TEST_METHOD(HttpProxySimple)
3503 + {
3504 + MIRRORED_NETWORKING_TEST_ONLY();
3505 + WINHTTP_PROXY_TEST_ONLY();
3506 +
3507 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
3508 + WaitForMirroredStateInLinux();
3509 + NetworkTests::VerifyHttpProxySimple();
3510 + }
3511 +
3512 + TEST_METHOD(HttpProxySimpleMachineScope)
3513 + {
3514 + MIRRORED_NETWORKING_TEST_ONLY();
3515 + WINHTTP_PROXY_TEST_ONLY();
3516 +
3517 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
3518 + WaitForMirroredStateInLinux();
3519 +
3520 + // verify with machine scope
3521 + NetworkTests::VerifyHttpProxySimple(false);
3522 + }
3523 +
3524 + TEST_METHOD(NoHttpProxyConfigured)
3525 + {
3526 + MIRRORED_NETWORKING_TEST_ONLY();
3527 + WINHTTP_PROXY_TEST_ONLY();
3528 +
3529 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
3530 + WaitForMirroredStateInLinux();
3531 + NetworkTests::VerifyNoHttpProxyConfigured();
3532 + }
3533 +
3534 + TEST_METHOD(HttpProxyWithBypassesConfigured)
3535 + {
3536 + MIRRORED_NETWORKING_TEST_ONLY();
3537 + WINHTTP_PROXY_TEST_ONLY();
3538 +
3539 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
3540 + WaitForMirroredStateInLinux();
3541 + NetworkTests::VerifyHttpProxyWithBypassesConfigured();
3542 + }
3543 +
3544 + TEST_METHOD(HttpProxyChange)
3545 + {
3546 + MIRRORED_NETWORKING_TEST_ONLY();
3547 + WINHTTP_PROXY_TEST_ONLY();
3548 +
3549 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
3550 + WaitForMirroredStateInLinux();
3551 + NetworkTests::VerifyHttpProxyChange();
3552 + }
3553 +
3554 + TEST_METHOD(HttpProxyAndWslEnv)
3555 + {
3556 + MIRRORED_NETWORKING_TEST_ONLY();
3557 + WINHTTP_PROXY_TEST_ONLY();
3558 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
3559 + WaitForMirroredStateInLinux();
3560 + NetworkTests::VerifyHttpProxyAndWslEnv();
3561 + }
3562 +
3563 + TEST_METHOD(HttpProxyFilterByNetworkConfiguration)
3564 + {
3565 + MIRRORED_NETWORKING_TEST_ONLY();
3566 + WINHTTP_PROXY_TEST_ONLY();
3567 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
3568 +
3569 + NetworkTests::VerifyHttpProxyFilterByNetworkConfigurationMirrored();
3570 + }
3571 +
3572 + TEST_METHOD(SmokeTest)
3573 + {
3574 + MIRRORED_NETWORKING_TEST_ONLY();
3575 +
3576 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3577 + WaitForMirroredStateInLinux();
3578 +
3579 + // Verify that we have a working connection
3580 + NetworkTests::GuestClient(L"tcp-connect:bing.com:80");
3581 + }
3582 +
3583 + TEST_METHOD(InternetConnectivityV4)
3584 + {
3585 + MIRRORED_NETWORKING_TEST_ONLY();
3586 +
3587 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3588 + WaitForMirroredStateInLinux();
3589 +
3590 + if (!NetworkTests::HostHasInternetConnectivity(AF_INET))
3591 + {
3592 + LogSkipped("Host does not have IPv4 internet connectivity. Skipping...");
3593 + return;
3594 + }
3595 +
3596 + NetworkTests::GuestClient(L"tcp4-connect:bing.com:80");
3597 + }
3598 +
3599 + TEST_METHOD(InternetConnectivityV6)
3600 + {
3601 + MIRRORED_NETWORKING_TEST_ONLY();
3602 +
3603 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3604 + WaitForMirroredStateInLinux();
3605 +
3606 + if (!NetworkTests::HostHasInternetConnectivity(AF_INET6))
3607 + {
3608 + LogSkipped("Host does not have IPv6 internet connectivity. Skipping...");
3609 + return;
3610 + }
3611 +
3612 + NetworkTests::GuestClient(L"tcp6-connect:bing.com:80");
3613 + }
3614 +
3615 + TEST_METHOD(LoopbackLocal)
3616 + {
3617 + MIRRORED_NETWORKING_TEST_ONLY();
3618 +
3619 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
3620 + WaitForMirroredStateInLinux();
3621 +
3622 + std::vector<NetworkTests::InterfaceState> interfaceStates = NetworkTests::GetAllInterfaceStates();
3623 +
3624 + // Verify loopback connectivity on assigned unicast addresses
3625 + for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
3626 + {
3627 + for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
3628 + {
3629 + // The IP used for DNS tunneling is not intended for guest<->host communication
3630 + if (j->Address != c_dnsTunnelingDefaultIp)
3631 + {
3632 + NetworkTests::VerifyLoopbackConnectivity(j->Address);
3633 + }
3634 + }
3635 + for (auto j = i->V6Addresses.begin(); j != i->V6Addresses.end(); ++j)
3636 + {
3637 + // TODO: enable when v6 loopback is supported
3638 + // VerifyLoopbackConnectivity(j->Address);
3639 + }
3640 + }
3641 + }
3642 +
3643 + TEST_METHOD(LoopbackExplicit)
3644 + {
3645 + MIRRORED_NETWORKING_TEST_ONLY();
3646 +
3647 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3648 + WaitForMirroredStateInLinux();
3649 +
3650 + // Verify loopback connectivity on loopback addresses
3651 + NetworkTests::VerifyLoopbackConnectivity(L"127.0.0.1");
3652 + // TODO: enable when v6 loopback is supported
3653 + // VerifyLoopbackConnectivity(L"::1");
3654 + }
3655 +
3656 + TEST_METHOD(LoopbackSystemd)
3657 + {
3658 + MIRRORED_NETWORKING_TEST_ONLY();
3659 +
3660 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3661 + WaitForMirroredStateInLinux();
3662 +
3663 + // Write a .conf file to conflict with loopback settings.
3664 +#define CONFIG_FILE_PATH L"/etc/sysctl.d/MirroredLoopbackSystemd.conf"
3665 + auto revertConfigFile = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [] {
3666 + const std::wstring deleteConfigFileCmd(L"-u root -e rm " CONFIG_FILE_PATH);
3667 + LxsstuLaunchWsl(deleteConfigFileCmd.data());
3668 + });
3669 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"echo \"net.ipv4.conf.*.rp_filter=2\" > " CONFIG_FILE_PATH), static_cast<DWORD>(0));
3670 +
3671 + // Enable systemd which will apply the .conf file.
3672 + auto revertSystemd = EnableSystemd();
3673 +
3674 + // Verify the settings configured in the systemd hardening logic.
3675 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl net.ipv4.conf.all.rp_filter | grep -w 0"), 0);
3676 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl net.ipv4.conf." TEXT(LX_INIT_LOOPBACK_DEVICE_NAME) L".rp_filter | grep -w 0"), 0);
3677 +
3678 + // Verify an E2E loopback scenario.
3679 + NetworkTests::VerifyLoopbackGuestToHost(L"127.0.0.1", IPPROTO_TCP);
3680 + }
3681 +
3682 + TEST_METHOD(GuestPortCantBeBoundByHost)
3683 + {
3684 + MIRRORED_NETWORKING_TEST_ONLY();
3685 +
3686 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3687 + WaitForMirroredStateInLinux();
3688 +
3689 + {
3690 + auto guestProcess = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", true);
3691 + NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
3692 + }
3693 +
3694 + {
3695 + auto guestProcess = NetworkTests::BindGuestPort(L"UDP4-LISTEN:1234", true);
3696 + NetworkTests::BindHostPort(1234, SOCK_DGRAM, IPPROTO_UDP, false);
3697 + }
3698 + }
3699 +
3700 + TEST_METHOD(GuestPortIsReleased)
3701 + {
3702 + MIRRORED_NETWORKING_TEST_ONLY();
3703 +
3704 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3705 + WaitForMirroredStateInLinux();
3706 +
3707 + // Make sure the VM doesn't time out
3708 + WslKeepAlive keepAlive;
3709 +
3710 + {
3711 + auto guestProcess = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", true);
3712 + NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
3713 + }
3714 +
3715 + const wil::unique_socket listenSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
3716 + VERIFY_IS_TRUE(!!listenSocket);
3717 +
3718 + SOCKADDR_IN Address{};
3719 + Address.sin_family = AF_INET;
3720 + Address.sin_port = htons(1234);
3721 +
3722 + const auto timeout = std::chrono::steady_clock::now() + std::chrono::minutes(2);
3723 +
3724 + bool bound = false;
3725 + while (!bound && std::chrono::steady_clock::now() < timeout)
3726 + {
3727 + bound = bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) != SOCKET_ERROR;
3728 + std::this_thread::sleep_for(std::chrono::seconds(1));
3729 + }
3730 +
3731 + VERIFY_IS_TRUE(bound);
3732 + }
3733 +
3734 + TEST_METHOD(HostPortCantBeBoundByGuest)
3735 + {
3736 + MIRRORED_NETWORKING_TEST_ONLY();
3737 +
3738 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3739 + WaitForMirroredStateInLinux();
3740 +
3741 + {
3742 + auto hostPort = NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, true);
3743 + NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", false);
3744 + }
3745 +
3746 + {
3747 + auto hostPort = NetworkTests::BindHostPort(1234, SOCK_DGRAM, IPPROTO_UDP, true);
3748 + NetworkTests::BindGuestPort(L"UDP4-LISTEN:1234", false);
3749 + }
3750 + }
3751 +
3752 + TEST_METHOD(UdpBindDoesNotPreventTcpBind)
3753 + {
3754 + MIRRORED_NETWORKING_TEST_ONLY();
3755 +
3756 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3757 + WaitForMirroredStateInLinux();
3758 +
3759 + auto tcpPort = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", true);
3760 + auto udpPort = NetworkTests::BindGuestPort(L"UDP4-LISTEN:1234", true);
3761 + }
3762 +
3763 + TEST_METHOD(HostUdpBindDoesNotPreventGuestTcpBind)
3764 + {
3765 + MIRRORED_NETWORKING_TEST_ONLY();
3766 +
3767 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3768 + WaitForMirroredStateInLinux();
3769 +
3770 + auto udpPort = NetworkTests::BindHostPort(2345, SOCK_DGRAM, IPPROTO_UDP, true);
3771 + auto tcpPort = NetworkTests::BindGuestPort(L"TCP4-LISTEN:2345", true);
3772 + }
3773 +
3774 + TEST_METHOD(MultipleGuestBindOnSameTuple)
3775 + {
3776 + MIRRORED_NETWORKING_TEST_ONLY();
3777 +
3778 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3779 + WaitForMirroredStateInLinux();
3780 +
3781 + auto bind1 = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234,bind=127.0.0.1", true);
3782 + {
3783 + auto bind2 = NetworkTests::BindGuestPort(L"TCP6-LISTEN:1234,bind=::1", true);
3784 +
3785 + // Allow time for this second bind to be viewed as "in use" by the init port tracker
3786 + // before closing the socket. If the socket is closed before the init port tracker sees
3787 + // that the port allocation was in use, then the init port tracker will hold onto the
3788 + // allocation for a considerable amount of time (through the duration of this test case)
3789 + // before releasing it.
3790 + std::this_thread::sleep_for(std::chrono::seconds(3));
3791 + }
3792 +
3793 + // Allow time for the init port tracker to detect the second port allocation as no longer in
3794 + // use and perform its cleanup of the second port allocation.
3795 + const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(3);
3796 + while (std::chrono::steady_clock::now() < timeout)
3797 + {
3798 + // {TCP, 1234} should still be reserved for the guest from the first bind.
3799 + auto hostPort = NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
3800 + std::this_thread::sleep_for(std::chrono::seconds(1));
3801 + }
3802 + }
3803 +
3804 + TEST_METHOD(EphemeralBind)
3805 + {
3806 + MIRRORED_NETWORKING_TEST_ONLY();
3807 +
3808 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3809 + WaitForMirroredStateInLinux();
3810 +
3811 + auto tcpPort = NetworkTests::BindGuestPort(L"TCP4-LISTEN:0", true);
3812 + auto udpPort = NetworkTests::BindGuestPort(L"UDP4-LISTEN:0", true);
3813 + }
3814 +
3815 + TEST_METHOD(ExplicitEphemeralBind)
3816 + {
3817 + MIRRORED_NETWORKING_TEST_ONLY();
3818 +
3819 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
3820 + WaitForMirroredStateInLinux();
3821 +
3822 + // Get ephemeral port range
3823 + auto [start, err1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f1", 0);
3824 + start.pop_back();
3825 + const auto ephemeralRangeStart = std::stoi(start);
3826 +
3827 + auto [end, err2] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f2", 0);
3828 + end.pop_back();
3829 + const auto ephemeralRangeEnd = std::stoi(end);
3830 +
3831 + // Walk the ephemeral port range and verify we can bind to at least one port (some might be already taken, but the test
3832 + // assumes there should be at least one free).
3833 + bool canBindTcp = false;
3834 + bool canBindUdp = false;
3835 +
3836 + for (int port = ephemeralRangeStart; port <= ephemeralRangeEnd; port++)
3837 + {
3838 + auto [tcpListener, tcpSuccess, read] = NetworkTests::BindGuestPortHelper(L"TCP4-LISTEN:" + std::to_wstring(port));
3839 + if (tcpSuccess)
3840 + {
3841 + canBindTcp = true;
3842 + break;
3843 + }
3844 + }
3845 +
3846 + for (int port = ephemeralRangeStart; port <= ephemeralRangeEnd; port++)
3847 + {
3848 + auto [udpListener, udpSuccess, read] = NetworkTests::BindGuestPortHelper(L"UDP4-LISTEN:" + std::to_wstring(port));
3849 + if (udpSuccess)
3850 + {
3851 + canBindUdp = true;
3852 + break;
3853 + }
3854 + }
3855 +
3856 + VERIFY_IS_TRUE(canBindTcp);
3857 + VERIFY_IS_TRUE(canBindUdp);
3858 + }
3859 +
3860 + TEST_METHOD(NonRootNamespaceEphemeralBind)
3861 + {
3862 + MIRRORED_NETWORKING_TEST_ONLY();
3863 +
3864 + // Because the test creates a new network namespace, the resolv.conf from the root network namespace
3865 + // is copied in the resolv.conf of the new network namespace. The DNS tunneling listener running in the root namespace
3866 + // needs to be accessible from the new namespace, so it can't use a 127* IP
3867 + m_config->Update(LxssGenerateTestConfig(
3868 + {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true, .dnsTunnelingIpAddress = L"10.255.255.254"}));
3869 + WaitForMirroredStateInLinux();
3870 +
3871 + NetworkTests::TestNonRootNamespaceEphemeralBind();
3872 + }
3873 +
3874 + // Verifies that in mirrored mode, Windows can connect to a listener running in a Linux network namespace different from
3875 + // the Linux root network namespace.
3876 + TEST_METHOD(PortForwardingToNonRootNamespace)
3877 + {
3878 + MIRRORED_NETWORKING_TEST_ONLY();
3879 +
3880 + m_config->Update(LxssGenerateTestConfig(
3881 + {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
3882 + WaitForMirroredStateInLinux();
3883 +
3884 + // We list the IPv4 addresses mirrored in Linux and use the first one we find in the test
3885 + std::vector<NetworkTests::InterfaceState> interfaceStates = NetworkTests::GetAllInterfaceStates();
3886 + std::wstring ipAddress;
3887 +
3888 + for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
3889 + {
3890 + for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
3891 + {
3892 + // The IP used for DNS tunneling is not intended for guest<->host communication
3893 + if (j->Address != c_dnsTunnelingDefaultIp)
3894 + {
3895 + ipAddress = j->Address;
3896 + break;
3897 + }
3898 + }
3899 + }
3900 +
3901 + // Get the forwarding state.
3902 + auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
3903 + std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
3904 +
3905 + // Clean up the below configurations.
3906 + auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
3907 + LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
3908 + LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
3909 + LxsstuLaunchWsl(L"--system --user root nft flush chain nat PREROUTING");
3910 + LxsstuLaunchWsl(L"ip link delete veth-test-br");
3911 + LxsstuLaunchWsl(L"ip link delete testbridge");
3912 + LxsstuLaunchWsl(L"ip netns delete testns");
3913 + });
3914 +
3915 + // Set up a networking namespace and provide it external network access via a bridge, veth
3916 + // pair, SRCNAT iptables rule and forwarding.
3917 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
3918 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
3919 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
3920 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
3921 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
3922 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
3923 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
3924 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
3925 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
3926 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
3927 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
3928 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
3929 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
3930 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
3931 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
3932 +
3933 + // Add rule for port forwarding traffic with destination port 8080 to port 80 in the new namespace
3934 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat PREROUTING { type nat hook prerouting priority dstnat; }\""), 0);
3935 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat PREROUTING tcp dport 8080 dnat to 192.168.15.2:80"), 0);
3936 +
3937 + // Start listeners in root namespace on port 8080 and new namespace on port 80
3938 + SOCKADDR_INET rootListenerAddr = wsl::windows::common::string::StringToSockAddrInet(L"0.0.0.0");
3939 + SS_PORT(&rootListenerAddr) = htons(8080);
3940 + NetworkTests::GuestListener rootListener(rootListenerAddr, IPPROTO_TCP);
3941 +
3942 + SOCKADDR_INET namespaceListenerAddr = wsl::windows::common::string::StringToSockAddrInet(L"0.0.0.0");
3943 + SS_PORT(&namespaceListenerAddr) = htons(80);
3944 + NetworkTests::GuestListener namespaceListener(namespaceListenerAddr, IPPROTO_TCP, L"testns");
3945 +
3946 + // Verify Windows can connect to port 8080
3947 + SOCKADDR_INET serverAddr = wsl::windows::common::string::StringToSockAddrInet(ipAddress);
3948 + SS_PORT(&serverAddr) = htons(8080);
3949 +
3950 + wil::unique_socket clientSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
3951 + VERIFY_ARE_NOT_EQUAL(clientSocket.get(), INVALID_SOCKET);
3952 +
3953 + VERIFY_ARE_EQUAL(connect(clientSocket.get(), reinterpret_cast<SOCKADDR*>(&serverAddr), sizeof(serverAddr)), 0);
3954 + }
3955 +
3956 + TEST_METHOD(LinuxNonRootNamespaceConnectToWindowsHost)
3957 + {
3958 + MIRRORED_NETWORKING_TEST_ONLY();
3959 +
3960 + m_config->Update(LxssGenerateTestConfig(
3961 + {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
3962 + WaitForMirroredStateInLinux();
3963 +
3964 + // We list the IPv4 addresses mirrored in Linux and use the first one we find in the test
3965 + std::vector<NetworkTests::InterfaceState> interfaceStates = NetworkTests::GetAllInterfaceStates();
3966 + std::wstring ipAddress;
3967 +
3968 + for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
3969 + {
3970 + for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
3971 + {
3972 + // The IP used for DNS tunneling is not intended for guest<->host communication
3973 + if (j->Address != c_dnsTunnelingDefaultIp)
3974 + {
3975 + ipAddress = j->Address;
3976 + break;
3977 + }
3978 + }
3979 + }
3980 +
3981 + // Get the forwarding state.
3982 + auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
3983 + std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
3984 +
3985 + // Clean up the below configurations.
3986 + auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
3987 + LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
3988 + LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
3989 + LxsstuLaunchWsl(L"ip link delete veth-test-br");
3990 + LxsstuLaunchWsl(L"ip link delete testbridge");
3991 + LxsstuLaunchWsl(L"ip netns delete testns");
3992 + });
3993 +
3994 + // Set up a networking namespace and provide it external network access via a bridge, veth
3995 + // pair, SRCNAT iptables rule and forwarding.
3996 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
3997 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
3998 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
3999 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
4000 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
4001 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
4002 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
4003 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
4004 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
4005 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
4006 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
4007 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
4008 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
4009 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
4010 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
4011 +
4012 + // Create a listener on the Windows host on port 1234
4013 + SOCKADDR_INET addr = wsl::windows::common::string::StringToSockAddrInet(ipAddress);
4014 + SS_PORT(&addr) = htons(1234);
4015 +
4016 + const wil::unique_socket listenSocket(socket(addr.si_family, SOCK_STREAM, IPPROTO_TCP));
4017 + VERIFY_ARE_NOT_EQUAL(listenSocket.get(), INVALID_SOCKET);
4018 + VERIFY_ARE_NOT_EQUAL(bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)), SOCKET_ERROR);
4019 + VERIFY_ARE_NOT_EQUAL(listen(listenSocket.get(), SOMAXCONN), SOCKET_ERROR);
4020 +
4021 + // Verify the new network namespace can connect to the Windows host listener
4022 + auto [output, warnings] = LxsstuLaunchWslAndCaptureOutput(
4023 + L"ip netns exec testns socat -dd tcp-connect:" + ipAddress + L":1234 create:/tmp/nonexistent", 1);
4024 + LogInfo("output %s", output.c_str());
4025 + LogInfo("warnings %s", warnings.c_str());
4026 + VERIFY_ARE_NOT_EQUAL(warnings.find(L"starting data transfer loop"), std::string::npos);
4027 + }
4028 +
4029 + TEST_METHOD(ResolvConf)
4030 + {
4031 + MIRRORED_NETWORKING_TEST_ONLY();
4032 +
4033 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4034 + WaitForMirroredStateInLinux();
4035 +
4036 + auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
4037 + const std::wregex pattern(L"(.|\n)*nameserver [0-9\\. ]+(.|\n)*", std::regex::extended);
4038 +
4039 + VERIFY_IS_TRUE(std::regex_match(out, pattern));
4040 + }
4041 +
4042 + TEST_METHOD(NetworkSettings)
4043 + {
4044 + MIRRORED_NETWORKING_TEST_ONLY();
4045 +
4046 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4047 + WaitForMirroredStateInLinux();
4048 +
4049 + struct NetworkSetting
4050 + {
4051 + const std::wstring Path;
4052 + const std::wstring ExpectedValue;
4053 + };
4054 +
4055 + std::vector<NetworkSetting> settings{
4056 + {L"/proc/sys/net/ipv6/conf/all/accept_ra", L"0\n"},
4057 + {L"/proc/sys/net/ipv6/conf/default/accept_ra", L"0\n"},
4058 + {L"/proc/sys/net/ipv6/conf/all/dad_transmits", L"0\n"},
4059 + {L"/proc/sys/net/ipv6/conf/default/dad_transmits", L"0\n"},
4060 + {L"/proc/sys/net/ipv6/conf/all/autoconf", L"0\n"},
4061 + {L"/proc/sys/net/ipv6/conf/default/autoconf", L"0\n"},
4062 + {L"/proc/sys/net/ipv6/conf/all/addr_gen_mode", L"1\n"},
4063 + {L"/proc/sys/net/ipv6/conf/default/addr_gen_mode", L"1\n"},
4064 + {L"/proc/sys/net/ipv6/conf/all/use_tempaddr", L"0\n"},
4065 + {L"/proc/sys/net/ipv6/conf/default/use_tempaddr", L"0\n"},
4066 + {L"/proc/sys/net/ipv4/conf/all/arp_filter", L"1\n"},
4067 + {L"/proc/sys/net/ipv4/conf/all/rp_filter", L"0\n"},
4068 + };
4069 +
4070 + settings.push_back({L"/proc/sys/net/ipv4/conf/" + NetworkTests::GetGelNicDeviceName() + L"/rp_filter", L"0\n"});
4071 +
4072 + for (const auto& setting : settings)
4073 + {
4074 + auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat " + setting.Path);
4075 + LogInfo("%ls", (setting.Path + L" : " + out).c_str());
4076 + VERIFY_ARE_EQUAL(setting.ExpectedValue, out);
4077 + }
4078 + }
4079 +
4080 + TEST_METHOD(FirewallRulesExpectedBlock)
4081 + {
4082 + HYPERV_FIREWALL_TEST_ONLY();
4083 + MIRRORED_NETWORKING_TEST_ONLY();
4084 +
4085 + SKIP_TEST_UNSTABLE();
4086 +
4087 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4088 + WaitForMirroredStateInLinux();
4089 +
4090 + NetworkTests::ValidateInitialFirewallState(NetworkTests::FirewallObjects::Required);
4091 + NetworkTests::FirewallRuleBlockedTests(NetworkTests::FirewallTestConnectivity::Blocked);
4092 + }
4093 +
4094 + TEST_METHOD(FirewallRulesExpectedAllow)
4095 + {
4096 + HYPERV_FIREWALL_TEST_ONLY();
4097 + MIRRORED_NETWORKING_TEST_ONLY();
4098 +
4099 + SKIP_TEST_UNSTABLE();
4100 +
4101 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4102 + WaitForMirroredStateInLinux();
4103 +
4104 + NetworkTests::ValidateInitialFirewallState(NetworkTests::FirewallObjects::Required);
4105 + NetworkTests::FirewallRuleAllowedTests(NetworkTests::FirewallTestConnectivity::Allowed);
4106 + }
4107 +
4108 + TEST_METHOD(FirewallRulesEnabledSetting)
4109 + {
4110 + HYPERV_FIREWALL_TEST_ONLY();
4111 + MIRRORED_NETWORKING_TEST_ONLY();
4112 +
4113 + SKIP_TEST_UNSTABLE();
4114 +
4115 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4116 + WaitForMirroredStateInLinux();
4117 +
4118 + NetworkTests::ValidateInitialFirewallState(NetworkTests::FirewallObjects::Required);
4119 + NetworkTests::FirewallSettingEnabledTests(true);
4120 }
4121
3984 - // Set ManualConnectivityValidation to true to manually check stdout from the test to verify the correct calls are made in Linux/Init
3985 - static constexpr bool ManualConnectivityValidation = false;
3986 - TEST_METHOD(ConnectivityCheckTestMirroredDefaultSuccess)
4122 + TEST_METHOD(ConnectivityCheckTestDefaultSuccess)
4123 {
4124 WSL2_TEST_ONLY();
4125 MIRRORED_NETWORKING_TEST_ONLY();
4126
4127 SKIP_TEST_UNSTABLE();
4128
3993 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4129 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4130 WaitForMirroredStateInLinux();
4131
4132 const auto coInit = wil::CoInitializeEx();
@@ -4035,68 +4171,15 @@ class NetworkTests
4171 // it returns 1 as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
4172 // if ManualConnectivityValidation is set true, one can confirm from the stdout captured that the correct result was determined and returned by init.
4173 constexpr auto testErrorCode = ManualConnectivityValidation ? expectedErrorCode : 1;
4038 - RunGns("www.msftconnecttest.com", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4039 - }
4040 -
4041 - TEST_METHOD(ConnectivityCheckTestNATDefaultSuccess)
4042 - {
4043 - WSL2_TEST_ONLY();
4044 -
4045 - WslConfigChange config(LxssGenerateTestConfig());
4046 - WaitForNATStateInLinux();
4047 -
4048 - const auto coInit = wil::CoInitializeEx();
4049 - const wil::com_ptr<INetworkListManager> networkListManager = wil::CoCreateInstance<NetworkListManager, INetworkListManager>();
4050 - VERIFY_IS_NOT_NULL(networkListManager.get());
4051 - NLM_CONNECTIVITY hostConnectivity{};
4052 - VERIFY_SUCCEEDED(networkListManager->GetConnectivity(&hostConnectivity));
4053 -
4054 - // Windows
4055 - const wsl::shared::conncheck::ConnCheckResult hostResult =
4056 - wsl::shared::conncheck::CheckConnection("www.msftconnecttest.com", "ipv6.msftconnecttest.com", "80");
4057 -
4058 - if (hostConnectivity & NLM_CONNECTIVITY_IPV4_INTERNET)
4059 - {
4060 - VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::Success, hostResult.Ipv4Status);
4061 - }
4062 - else
4063 - {
4064 - // one of the 2 expected runtime failures
4065 - VERIFY_IS_TRUE(
4066 - hostResult.Ipv4Status == wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo ||
4067 - hostResult.Ipv4Status == wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
4068 - }
4069 - if (hostConnectivity & NLM_CONNECTIVITY_IPV6_INTERNET)
4070 - {
4071 - VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::Success, hostResult.Ipv4Status);
4072 - }
4073 - else
4074 - {
4075 - // one of the 2 expected runtime failures (sometimes v6 name resolution will fail, depending on the configuration)
4076 - VERIFY_IS_TRUE(
4077 - hostResult.Ipv6Status == wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo ||
4078 - hostResult.Ipv6Status == wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
4079 - }
4080 -
4081 - // www.msftconnecttest.com will always fail IPv6 name resolution - it doesn't have any AAAA records registered for it
4082 - const int expectedErrorCode = static_cast<int>(hostResult.Ipv4Status) |
4083 - (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) << 16);
4084 - LogInfo("RunGns(www.msftconnecttest.com, 0x%x)", expectedErrorCode);
4085 - // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
4086 - // it returns 1 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::Success)
4087 - // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
4088 - // if ManualConnectivityValidation is set true, one can confirm from the stdout captured that the correct result was determined and returned by init.
4089 - constexpr auto testErrorCode =
4090 - ManualConnectivityValidation ? expectedErrorCode : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::Success);
4091 - RunGns("www.msftconnecttest.com", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4174 + NetworkTests::RunGns("www.msftconnecttest.com", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4175 }
4176
4094 - TEST_METHOD(ConnectivityCheckTestMirroredNameResolutionFailure)
4177 + TEST_METHOD(ConnectivityCheckTestNameResolutionFailure)
4178 {
4179 WSL2_TEST_ONLY();
4180 MIRRORED_NETWORKING_TEST_ONLY();
4181
4099 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4182 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4183 WaitForMirroredStateInLinux();
4184
4185 // Windows
@@ -4116,44 +4199,17 @@ class NetworkTests
4199 constexpr auto testErrorCode = ManualConnectivityValidation
4200 ? expectedErrorCode
4201 : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo);
4119 - RunGns("asdlkfadsf.bbcxzncvb", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4120 - }
4121 -
4122 - TEST_METHOD(ConnectivityCheckTestNATNameResolutionFailure)
4123 - {
4124 - WSL2_TEST_ONLY();
4125 -
4126 - WslConfigChange config(LxssGenerateTestConfig());
4127 - WaitForNATStateInLinux();
4128 -
4129 - // Windows
4130 - const wsl::shared::conncheck::ConnCheckResult result =
4131 - wsl::shared::conncheck::CheckConnection("asdlkfadsf.bbcxzncvb", nullptr, "80");
4132 -
4133 - VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo, result.Ipv4Status);
4134 - VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo, result.Ipv6Status);
4135 -
4136 - constexpr int expectedErrorCode = static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) |
4137 - (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) << 16);
4138 - LogInfo("RunGns(asdlkfadsf.bbcxzncvb, 0x%x)", expectedErrorCode);
4139 - // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
4140 - // it returns 2 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo))
4141 - // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
4142 - // if temporarily change this back to expectedErrorCode, one can confirm from the stdout captured that the correct result was determined and returned by init.
4143 - constexpr auto testErrorCode = ManualConnectivityValidation
4144 - ? expectedErrorCode
4145 - : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo);
4146 - RunGns("asdlkfadsf.bbcxzncvb", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4202 + NetworkTests::RunGns("asdlkfadsf.bbcxzncvb", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4203 }
4204
4149 - TEST_METHOD(ConnectivityCheckTestMirroredNameResolvesButConnectivityFails)
4205 + TEST_METHOD(ConnectivityCheckTestNameResolvesButConnectivityFails)
4206 {
4207 WSL2_TEST_ONLY();
4208 MIRRORED_NETWORKING_TEST_ONLY();
4209
4210 SKIP_TEST_UNSTABLE();
4211
4156 - WslConfigChange config(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4212 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4213 WaitForMirroredStateInLinux();
4214
4215 const auto* ncsiDnsOnlyName = "dns.msftncsi.com";
@@ -4180,41 +4236,68 @@ class NetworkTests
4236 constexpr auto testErrorCode = ManualConnectivityValidation
4237 ? expectedErrorCode
4238 : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
4183 - RunGns(ncsiDnsOnlyName, AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4239 + NetworkTests::RunGns(ncsiDnsOnlyName, AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4240 }
4241
4186 - TEST_METHOD(ConnectivityCheckTestNATNameResolvesButConnectivityFails)
4242 + // Due to VM creation performance requirements, VM creation is allowed to finish even if all
4243 + // networking state has not been mirrored yet. This introduces a race condition between the
4244 + // mirroring of networking state and mirrored mode test case execution that relies on the
4245 + // networking state being mirrored.
4246 + //
4247 + // This routine resolves the race condition by waiting for networking state to be mirrored into
4248 + // the VM. Tracking all mirrored networking state is complicated, so we use a heuristic to
4249 + // simplify: default routes have been observed to be mirrored last, so if they are present in
4250 + // the VM then we consider mirroring to be completed.
4251 + static void WaitForMirroredStateInLinux()
4252 {
4188 - WSL2_TEST_ONLY();
4253 + const bool hostConnectivityV4 = NetworkTests::HostHasInternetConnectivity(AF_INET);
4254 + const bool hostConnectivityV6 = NetworkTests::HostHasInternetConnectivity(AF_INET6);
4255
4190 - WslConfigChange config(LxssGenerateTestConfig());
4191 - WaitForNATStateInLinux();
4256 + Stopwatch<std::chrono::seconds> Watchdog(std::chrono::seconds(30));
4257
4193 - const auto* ncsiDnsOnlyName = "dns.msftncsi.com";
4194 - // v4 and v6 should succeed to resolve the name, but fail to connect,
4195 - // as this NCSI name is registered in global DNS, but there's not HTTP endpoint for it
4258 + do
4259 + {
4260 + // Count how many interfaces have v4/v6 connectivity, as defined by having a gateway and at least 1 preferred address.
4261 + int interfacesWithV4Connectivity = 0;
4262 + int interfacesWithV6Connectivity = 0;
4263
4197 - // Windows
4198 - const wsl::shared::conncheck::ConnCheckResult result =
4199 - wsl::shared::conncheck::CheckConnection(ncsiDnsOnlyName, nullptr, "80");
4264 + // Get all interface info from the VM.
4265 + for (const auto& i : NetworkTests::GetAllInterfaceStates())
4266 + {
4267 + if (i.Gateway.has_value())
4268 + {
4269 + for (const auto& j : i.V4Addresses)
4270 + {
4271 + if (j.Preferred)
4272 + {
4273 + interfacesWithV4Connectivity++;
4274 + break;
4275 + }
4276 + }
4277 + }
4278 + if (i.V6Gateway.has_value())
4279 + {
4280 + for (const auto& j : i.V6Addresses)
4281 + {
4282 + if (j.Preferred)
4283 + {
4284 + interfacesWithV6Connectivity++;
4285 + break;
4286 + }
4287 + }
4288 + }
4289 + }
4290
4201 - VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect, result.Ipv4Status);
4202 - // v6 name resolution might fail, depending on the configuration
4203 - VERIFY_IS_TRUE(
4204 - (wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo == result.Ipv6Status) ||
4205 - (wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect == result.Ipv6Status));
4291 + // Consider mirroring to be complete if we have the same v4/v6 connectivity in the VM as the host.
4292 + if ((!hostConnectivityV4 || interfacesWithV4Connectivity > 0) && (!hostConnectivityV6 || interfacesWithV6Connectivity > 0))
4293 + {
4294 + break;
4295 + }
4296
4207 - constexpr int expectedErrorCode = static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect) |
4208 - (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect) << 16);
4209 - LogInfo("RunGns(%hs, 0x%x)", ncsiDnsOnlyName, expectedErrorCode);
4210 - // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
4211 - // it returns 4 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect))
4212 - // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
4213 - // if ManualConnectivityValidation is set true, one can confirm from the stdout captured that the correct result was determined and returned by init.
4214 - constexpr auto testErrorCode = ManualConnectivityValidation
4215 - ? expectedErrorCode
4216 - : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
4217 - RunGns(ncsiDnsOnlyName, AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
4297 + LogInfo("Waiting for mirrored state...");
4298 + } while (Sleep(1000), !Watchdog.IsExpired());
4299 +
4300 + VERIFY_IS_FALSE(Watchdog.IsExpired());
4301 }
4302 };
4303
@@ -4298,4 +4381,120 @@ class BridgedTests
4381 VERIFY_ARE_EQUAL(L"0\n", out);
4382 }
4383 };
4384 +
4385 +class VirtioProxyTests
4386 +{
4387 + WSL_TEST_CLASS(VirtioProxyTests)
4388 +
4389 + std::optional<WslConfigChange> m_config;
4390 +
4391 + TEST_CLASS_SETUP(TestClassSetup)
4392 + {
4393 + VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
4394 +
4395 + if (LxsstuVmMode())
4396 + {
4397 + m_config.emplace(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy}));
4398 + }
4399 +
4400 + return true;
4401 + }
4402 +
4403 + TEST_CLASS_CLEANUP(TestClassCleanup)
4404 + {
4405 + m_config.reset();
4406 +
4407 + VERIFY_NO_THROW(LxsstuUninitialize(false));
4408 +
4409 + return true;
4410 + }
4411 +
4412 + TEST_METHOD(SmokeTest)
4413 + {
4414 + VIRTIOPROXY_TEST_ONLY();
4415 +
4416 + // Verify that we have a working connection
4417 + NetworkTests::GuestClient(L"tcp-connect:bing.com:80");
4418 + }
4419 +
4420 + TEST_METHOD(InternetConnectivityV4)
4421 + {
4422 + VIRTIOPROXY_TEST_ONLY();
4423 +
4424 + if (!NetworkTests::HostHasInternetConnectivity(AF_INET))
4425 + {
4426 + LogSkipped("Host does not have IPv4 internet connectivity. Skipping...");
4427 + return;
4428 + }
4429 +
4430 + NetworkTests::GuestClient(L"tcp4-connect:bing.com:80");
4431 + }
4432 +
4433 + TEST_METHOD(InternetConnectivityV6)
4434 + {
4435 + VIRTIOPROXY_TEST_ONLY();
4436 +
4437 + if (!NetworkTests::HostHasInternetConnectivity(AF_INET6))
4438 + {
4439 + LogSkipped("Host does not have IPv6 internet connectivity. Skipping...");
4440 + return;
4441 + }
4442 +
4443 + NetworkTests::GuestClient(L"tcp6-connect:bing.com:80");
4444 + }
4445 +
4446 + TEST_METHOD(Configuration)
4447 + {
4448 + VIRTIOPROXY_TEST_ONLY();
4449 +
4450 + const auto state = NetworkTests::GetInterfaceState(L"eth0");
4451 + VERIFY_IS_FALSE(state.V4Addresses.empty());
4452 + VERIFY_IS_TRUE(state.Gateway.has_value());
4453 +
4454 + auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
4455 + const std::wregex pattern(L"(.|\\n)*nameserver [0-9. ]+(.|\\n)*");
4456 +
4457 + VERIFY_IS_TRUE(std::regex_match(out, pattern));
4458 + }
4459 +
4460 + TEST_METHOD(GuestPortIsReleased)
4461 + {
4462 + VIRTIOPROXY_TEST_ONLY();
4463 +
4464 + // Make sure the VM doesn't time out
4465 + WslKeepAlive keepAlive;
4466 +
4467 + {
4468 + auto guestProcess = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", true);
4469 + NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
4470 + }
4471 +
4472 + const wil::unique_socket listenSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
4473 + VERIFY_IS_TRUE(!!listenSocket);
4474 +
4475 + SOCKADDR_IN Address{};
4476 + Address.sin_family = AF_INET;
4477 + Address.sin_port = htons(1234);
4478 +
4479 + const auto timeout = std::chrono::steady_clock::now() + std::chrono::minutes(2);
4480 +
4481 + bool bound = false;
4482 + while (!bound && std::chrono::steady_clock::now() < timeout)
4483 + {
4484 + bound = bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) != SOCKET_ERROR;
4485 + std::this_thread::sleep_for(std::chrono::seconds(1));
4486 + }
4487 +
4488 + VERIFY_IS_TRUE(bound);
4489 + }
4490 +
4491 + TEST_METHOD(HttpProxySimple)
4492 + {
4493 + VIRTIOPROXY_TEST_ONLY();
4494 + WINHTTP_PROXY_TEST_ONLY();
4495 +
4496 + m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .autoProxy = true}));
4497 + NetworkTests::VerifyHttpProxySimple();
4498 + }
4499 +};
4500 } // namespace NetworkTests