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