master
cpp 5,726 lines 242 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 NetworkTests.cpp
8
9 Abstract:
10
11 This file contains test cases for the networking logic.
12
13 --*/
14
15 #include "precomp.h"
16 #include "computenetwork.h"
17 #include "Common.h"
18 #include "wslpolicies.h"
19 #include "hns_schema.h"
20 #include "WslCoreNetworkEndpointSettings.h"
21 #include "WslCoreNetworkingSupport.h"
22 #include "WslCoreTcpIpStateTracking.h"
23
24 #include <mstcpip.h>
25 #include <winhttp.h>
26 #include <winsock2.h>
27 #include <netlistmgr.h>
28
29 using wsl::shared::hns::GuestEndpointResourceType;
30 using wsl::shared::hns::ModifyGuestEndpointSettingRequest;
31 using wsl::shared::hns::ModifyRequestType;
32
33 bool TryLoadWinhttpProxyMethods() noexcept
34 {
35 constexpr auto winhttpModuleName = L"Winhttp.dll";
36 const wil::shared_hmodule winhttpModule{LoadLibraryEx(winhttpModuleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)};
37 if (!winhttpModule)
38 {
39 return false;
40 }
41
42 try
43 {
44 // attempt to find the functions for the Winhttp proxy APIs.
45 static LxssDynamicFunction<decltype(WinHttpRegisterProxyChangeNotification)> WinHttpRegisterProxyChangeNotification{
46 winhttpModule, "WinHttpRegisterProxyChangeNotification"};
47 static LxssDynamicFunction<decltype(WinHttpUnregisterProxyChangeNotification)> WinHttpUnregisterProxyChangeNotification{
48 winhttpModule, "WinHttpUnregisterProxyChangeNotification"};
49 static LxssDynamicFunction<decltype(WinHttpGetProxySettingsEx)> WinHttpGetProxySettingsEx{
50 winhttpModule, "WinHttpGetProxySettingsEx"};
51 static LxssDynamicFunction<decltype(WinHttpGetProxySettingsResultEx)> WinHttpGetProxySettingsResultEx{
52 winhttpModule, "WinHttpGetProxySettingsResultEx"};
53 static LxssDynamicFunction<decltype(WinHttpFreeProxySettingsEx)> WinHttpFreeProxySettingsEx{
54 winhttpModule, "WinHttpFreeProxySettingsEx"};
55 }
56 catch (...)
57 {
58 return false;
59 }
60 return true;
61 }
62
63 #define HYPERV_FIREWALL_TEST_ONLY() \
64 { \
65 WINDOWS_11_TEST_ONLY(); \
66 if (!AreExperimentalNetworkingFeaturesSupported() || !IsHyperVFirewallSupported()) \
67 { \
68 LogSkipped("Hyper-V Firewall not supported on this OS. Skipping test..."); \
69 return; \
70 } \
71 }
72
73 #define MIRRORED_NETWORKING_TEST_ONLY() \
74 { \
75 WINDOWS_11_TEST_ONLY(); \
76 if (!AreExperimentalNetworkingFeaturesSupported() || !IsHyperVFirewallSupported()) \
77 { \
78 LogSkipped("Mirrored networking not supported on this OS. Skipping test.."); \
79 return; \
80 } \
81 }
82
83 #define DNS_TUNNELING_TEST_ONLY() \
84 { \
85 WINDOWS_11_TEST_ONLY(); \
86 if (!AreExperimentalNetworkingFeaturesSupported()) \
87 { \
88 LogSkipped("DNS tunneling not supported on this OS. Skipping test..."); \
89 return; \
90 } \
91 if (!TryLoadDnsResolverMethods()) \
92 { \
93 LogSkipped("DNS tunneling APIs not present on this OS. Skipping test..."); \
94 return; \
95 } \
96 }
97
98 #define WINHTTP_PROXY_TEST_ONLY() \
99 { \
100 if (!TryLoadWinhttpProxyMethods()) \
101 { \
102 LogSkipped("Winhttp proxy APIs not present on this OS. Skipping test..."); \
103 return; \
104 } \
105 }
106
107 #define CONSOMME_TEST_ONLY() \
108 { \
109 }
110
111 static constexpr auto c_wslVmCreatorId = L"\'{40e0ac32-46a5-438a-A0B2-2B479E8F2E90}\'";
112 static constexpr auto c_wsaVmCreatorId = L"\'{9E288F02-CE00-4D9E-BE2B-14CE463B0298}\'";
113 static constexpr auto c_anyVmCreatorId = L"\'{00000000-0000-0000-0000-000000000000}\'";
114 static constexpr auto c_firewallRuleActionBlock = L"Block";
115 static constexpr auto c_firewallRuleActionAllow = L"Allow";
116 static constexpr auto c_firewallTrafficTestCmd = L"ping -c 3 -W 5 1.1.1.1";
117 static const std::wstring c_firewallTrafficTestPort = L"80";
118 static const std::wstring c_firewallTestOtherPort = L"443";
119 static const std::wstring c_dnsTunnelingDefaultIp = L"10.255.255.254";
120
121 // Set ManualConnectivityValidation to true to manually check stdout from the test to verify the correct calls are made in Linux/Init
122 static constexpr bool ManualConnectivityValidation = false;
123
124 namespace {
125
126 std::wstring GetMacAddress(const std::wstring& adapter = L"eth0")
127 {
128 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /sys/class/net/" + adapter + L"/address", 0);
129 out.pop_back(); // remove LF
130 return out;
131 }
132
133 template <class T>
134 class Stopwatch
135 {
136 private:
137 LARGE_INTEGER m_startQpc;
138 LARGE_INTEGER m_frequencyQpc;
139 T m_timeoutInterval;
140
141 public:
142 Stopwatch(_In_opt_ T TimeoutInterval = T::max()) : m_timeoutInterval(TimeoutInterval)
143 {
144 QueryPerformanceFrequency(&m_frequencyQpc);
145 QueryPerformanceCounter(&m_startQpc);
146 }
147
148 T Elapsed()
149 {
150 LARGE_INTEGER End;
151 UINT64 ElapsedQpc;
152
153 QueryPerformanceCounter(&End);
154 ElapsedQpc = End.QuadPart - m_startQpc.QuadPart;
155
156 return T((ElapsedQpc * T::period::den) / T::period::num / m_frequencyQpc.QuadPart);
157 }
158
159 bool IsExpired()
160 {
161 return Elapsed() >= m_timeoutInterval;
162 }
163 };
164
165 } // namespace
166
167 namespace NetworkTests {
168
169 class ConsommeTests;
170
171 class NetworkTests
172 {
173 WSL_TEST_CLASS(NetworkTests)
174
175 friend class MirroredTests;
176 friend class BridgedTests;
177 friend class ConsommeTests;
178
179 struct IpAddress
180 {
181 std::wstring Address;
182 uint8_t PrefixLength;
183 bool Preferred = false;
184
185 bool operator==(const IpAddress& other) const
186 {
187 return Address == other.Address && PrefixLength == other.PrefixLength;
188 }
189
190 std::wstring GetPrefix() const
191 {
192 DWORD status = ERROR_INVALID_FUNCTION;
193 SOCKADDR_INET* address = nullptr;
194 unsigned char* addressPointer{};
195
196 NET_ADDRESS_INFO netAddrInfo{};
197 status = ParseNetworkString(Address.c_str(), NET_STRING_IP_ADDRESS, &netAddrInfo, nullptr, nullptr);
198 if (status != NO_ERROR)
199 {
200 return std::wstring(L"");
201 }
202
203 address = reinterpret_cast<SOCKADDR_INET*>(&netAddrInfo.IpAddress);
204 addressPointer = (address->si_family == AF_INET) ? reinterpret_cast<unsigned char*>(&address->Ipv4.sin_addr)
205 : address->Ipv6.sin6_addr.u.Byte;
206
207 constexpr int c_numBitsPerByte = 8;
208 for (int i = 0, currPrefixLength = PrefixLength; i < INET_ADDR_LENGTH(address->si_family); i++, currPrefixLength -= c_numBitsPerByte)
209 {
210 if (currPrefixLength < c_numBitsPerByte)
211 {
212 const int bitShiftAmt = (c_numBitsPerByte - std::max(currPrefixLength, 0));
213 addressPointer[i] &= (0xFF >> bitShiftAmt) << bitShiftAmt;
214 }
215 }
216
217 return wsl::windows::common::string::SockAddrInetToWstring(*address) + L"/" + std::to_wstring(PrefixLength);
218 }
219 };
220
221 struct InterfaceState
222 {
223 std::wstring Name;
224 std::vector<IpAddress> V4Addresses;
225 std::optional<std::wstring> Gateway;
226 std::vector<IpAddress> V6Addresses;
227 std::optional<std::wstring> V6Gateway;
228
229 bool Up = false;
230 int Mtu = 0;
231 bool Rename = false;
232 };
233
234 struct Route
235 {
236 std::wstring Via;
237 std::wstring Device;
238 std::optional<std::wstring> Prefix;
239 int Metric = 0;
240
241 bool operator==(const Route& other) const
242 {
243 return Via == other.Via && Device == other.Device && Prefix == other.Prefix;
244 }
245 };
246
247 struct RoutingTableState
248 {
249 std::optional<Route> DefaultRoute;
250 std::vector<Route> Routes;
251 };
252
253 enum class FirewallType
254 {
255 Host,
256 HyperV
257 };
258
259 struct FirewallRule
260 {
261 FirewallType Type;
262 std::wstring Name;
263 std::wstring RemotePorts;
264 std::wstring Action;
265 std::wstring VmCreatorId;
266 };
267
268 GUID AdapterId;
269
270 TEST_CLASS_SETUP(TestClassSetup)
271 {
272 VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
273
274 return true;
275 }
276
277 TEST_CLASS_CLEANUP(TestClassCleanup)
278 {
279 if (LxsstuVmMode())
280 {
281 WslShutdown();
282 }
283
284 VERIFY_NO_THROW(LxsstuUninitialize(false));
285
286 return true;
287 }
288
289 TEST_METHOD_SETUP(MethodSetup)
290 {
291 if (!LxsstuVmMode())
292 {
293 return true;
294 }
295
296 AdapterId = NetworkTests::QueryAdapterId();
297 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ln -f -s /init /gns"), (DWORD)0);
298
299 return true;
300 }
301
302 TEST_METHOD(DefaultRouteClassification)
303 {
304 const auto makeRoute = [](ADDRESS_FAMILY family, const wchar_t* destination, uint8_t prefixLength) {
305 MIB_IPFORWARD_ROW2 routeRow{};
306 routeRow.DestinationPrefix.Prefix = wsl::windows::common::string::StringToSockAddrInet(destination);
307 routeRow.DestinationPrefix.PrefixLength = prefixLength;
308 routeRow.NextHop.si_family = family;
309 return wsl::core::networking::EndpointRoute(routeRow);
310 };
311
312 VERIFY_IS_TRUE(makeRoute(AF_INET, L"0.0.0.0", 0).IsDefault());
313 VERIFY_IS_FALSE(makeRoute(AF_INET, L"0.0.0.0", 1).IsDefault());
314 VERIFY_IS_FALSE(makeRoute(AF_INET, L"128.0.0.0", 1).IsDefault());
315 VERIFY_IS_FALSE(makeRoute(AF_INET, L"0.0.0.0", 32).IsDefault());
316
317 VERIFY_IS_TRUE(makeRoute(AF_INET6, L"::", 0).IsDefault());
318 VERIFY_IS_FALSE(makeRoute(AF_INET6, L"::", 1).IsDefault());
319 VERIFY_IS_FALSE(makeRoute(AF_INET6, L"8000::", 1).IsDefault());
320 VERIFY_IS_FALSE(makeRoute(AF_INET6, L"::", 128).IsDefault());
321 }
322
323 WSL2_TEST_METHOD(RemoveAndAddDefaultRoute)
324 {
325 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
326
327 // Verify that the default routes are set
328 auto state = GetIpv4RoutingTableState();
329 VERIFY_IS_TRUE(state.DefaultRoute.has_value());
330 VERIFY_ARE_EQUAL(state.DefaultRoute->Via, L"192.168.0.1");
331
332 auto v6State = GetIpv6RoutingTableState();
333 VERIFY_IS_TRUE(v6State.DefaultRoute.has_value());
334 VERIFY_ARE_EQUAL(v6State.DefaultRoute->Via, L"fc00::1");
335
336 // Now remove them
337 wsl::shared::hns::Route route;
338 route.NextHop = L"192.168.0.1";
339 route.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_PREFIX;
340 route.Family = AF_INET;
341 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Remove, GuestEndpointResourceType::Route);
342
343 wsl::shared::hns::Route v6Route;
344 v6Route.NextHop = L"fc00::1";
345 v6Route.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_V6_PREFIX;
346 v6Route.Family = AF_INET6;
347 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Remove, GuestEndpointResourceType::Route);
348
349 // Verify that the routes are removed
350 state = GetIpv4RoutingTableState();
351 VERIFY_IS_FALSE(state.DefaultRoute.has_value());
352
353 v6State = GetIpv6RoutingTableState();
354 VERIFY_IS_FALSE(v6State.DefaultRoute.has_value());
355
356 // Add them again
357 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Add, GuestEndpointResourceType::Route);
358 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Add, GuestEndpointResourceType::Route);
359
360 // Verify that the routes are restored
361 state = GetIpv4RoutingTableState();
362 VERIFY_IS_TRUE(state.DefaultRoute.has_value());
363 VERIFY_ARE_EQUAL(state.DefaultRoute->Via, L"192.168.0.1");
364 VERIFY_ARE_EQUAL(state.DefaultRoute->Device, L"eth0");
365
366 v6State = GetIpv6RoutingTableState();
367 VERIFY_IS_TRUE(v6State.DefaultRoute.has_value());
368 VERIFY_ARE_EQUAL(v6State.DefaultRoute->Via, L"fc00::1");
369 VERIFY_ARE_EQUAL(v6State.DefaultRoute->Device, L"eth0");
370 }
371
372 WSL2_TEST_METHOD(AddDefaultRouteWithOfflinkGateway)
373 {
374 TestCase({{L"eth0", {{L"100.96.5.160", 32}}, L"100.96.5.161"}});
375 }
376
377 WSL2_TEST_METHOD(AddRemoveDefaultOnlinkRoutes)
378 {
379 wsl::shared::hns::Route defaultRouteV4;
380 defaultRouteV4.NextHop = L"0.0.0.0";
381 defaultRouteV4.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_PREFIX;
382 defaultRouteV4.Family = AF_INET;
383 defaultRouteV4.Metric = 1;
384 SendDeviceSettingsRequest(L"eth0", defaultRouteV4, ModifyRequestType::Add, GuestEndpointResourceType::Route);
385
386 wsl::shared::hns::Route defaultRouteV6;
387 defaultRouteV6.NextHop = L"::";
388 defaultRouteV6.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_V6_PREFIX;
389 defaultRouteV6.Family = AF_INET6;
390 defaultRouteV6.Metric = 1;
391 SendDeviceSettingsRequest(L"eth0", defaultRouteV6, ModifyRequestType::Add, GuestEndpointResourceType::Route);
392
393 const bool defaultV4RouteExists =
394 LxsstuLaunchWsl(L"ip -4 route show | grep \"default dev eth0\" | grep -w \"metric 1\"") == (DWORD)0;
395 const bool defaultV6RouteExists =
396 LxsstuLaunchWsl(L"ip -6 route show | grep \"default dev eth0\" | grep -w \"metric 1\"") == (DWORD)0;
397
398 SendDeviceSettingsRequest(L"eth0", defaultRouteV4, ModifyRequestType::Remove, GuestEndpointResourceType::Route);
399 SendDeviceSettingsRequest(L"eth0", defaultRouteV6, ModifyRequestType::Remove, GuestEndpointResourceType::Route);
400
401 const bool defaultV4RouteRemoved =
402 LxsstuLaunchWsl(L"ip -4 route show | grep \"default dev eth0\" | grep -w \"metric 1\"") != (DWORD)0;
403 const bool defaultV6RouteRemoved =
404 LxsstuLaunchWsl(L"ip -6 route show | grep \"default dev eth0\" | grep -w \"metric 1\"") != (DWORD)0;
405
406 VERIFY_IS_TRUE(defaultV4RouteExists);
407 VERIFY_IS_TRUE(defaultV6RouteExists);
408 VERIFY_IS_TRUE(defaultV4RouteRemoved);
409 VERIFY_IS_TRUE(defaultV6RouteRemoved);
410 }
411
412 WSL2_TEST_METHOD(SetInterfaceDownAndUp)
413 {
414 // Disconnect interface
415 wsl::shared::hns::NetworkInterface link;
416 link.Connected = false;
417 RunGns(link, ModifyRequestType::Update, GuestEndpointResourceType::Interface);
418 VERIFY_IS_FALSE(GetInterfaceState(L"eth0").Up);
419
420 // Connect it again
421 link.Connected = true;
422 RunGns(link, ModifyRequestType::Update, GuestEndpointResourceType::Interface);
423 VERIFY_IS_TRUE(GetInterfaceState(L"eth0").Up);
424 }
425
426 WSL2_TEST_METHOD(SetMtu)
427 {
428 // Set MTU - must be 1280 bytes or above to meet IPv6 minimum MTU requirement
429 wsl::shared::hns::NetworkInterface link;
430 link.Connected = true;
431 link.NlMtu = 1280;
432 RunGns(link, ModifyRequestType::Update, GuestEndpointResourceType::Interface);
433 VERIFY_ARE_EQUAL(GetInterfaceState(L"eth0").Mtu, 1280);
434 }
435
436 WSL2_TEST_METHOD(AddAndRemoveCustomRoute)
437 {
438 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
439
440 // Add custom routes, one per address family
441 wsl::shared::hns::Route route;
442 route.NextHop = L"192.168.0.12";
443 route.DestinationPrefix = L"192.168.2.0/24";
444 route.Family = AF_INET;
445 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
446
447 wsl::shared::hns::Route v6Route;
448 v6Route.NextHop = L"fc00::12";
449 v6Route.DestinationPrefix = L"fc00:abcd::/80";
450 v6Route.Family = AF_INET6;
451 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
452
453 // Check that the routes are there
454 const bool v4CustomRouteExists = RouteExists({L"192.168.0.12", L"eth0", L"192.168.2.0/24"});
455 const bool v6CustomRouteExists = RouteExists({L"fc00::12", L"eth0", L"fc00:abcd::/80"});
456
457 // Now remove them
458 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Remove, GuestEndpointResourceType::Route);
459 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Remove, GuestEndpointResourceType::Route);
460
461 // Check that the routes are gone
462 const bool v4CustomRouteGone = !RouteExists({L"192.168.0.12", L"eth0", L"192.168.2.0/24"});
463 const bool v6CustomRouteGone = !RouteExists({L"fc00::12", L"eth0", L"fc00:abcd::/80"});
464
465 VERIFY_IS_TRUE(v4CustomRouteExists);
466 VERIFY_IS_TRUE(v6CustomRouteExists);
467
468 VERIFY_IS_TRUE(v4CustomRouteGone);
469 VERIFY_IS_TRUE(v6CustomRouteGone);
470 }
471
472 WSL2_TEST_METHOD(AddRouteWithMetrics)
473 {
474 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
475
476 // Add a custom route per address family
477 wsl::shared::hns::Route route;
478 route.NextHop = L"192.168.0.12";
479 route.DestinationPrefix = L"192.168.2.0/24";
480 route.Family = AF_INET;
481 route.Metric = 12;
482 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
483
484 wsl::shared::hns::Route v6Route;
485 v6Route.NextHop = L"fc00::12";
486 v6Route.DestinationPrefix = L"fc00:abcd::/64";
487 v6Route.Family = AF_INET6;
488 v6Route.Metric = 12;
489 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
490
491 // Check that the routes are there
492 const bool v4CustomRouteExists = RouteExists({L"192.168.0.12", L"eth0", L"192.168.2.0/24", 12});
493 const bool v6CustomRouteExists = RouteExists({L"fc00::12", L"eth0", L"fc00:abcd::/64", 12});
494
495 // Now remove them
496 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Remove, GuestEndpointResourceType::Route);
497 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Remove, GuestEndpointResourceType::Route);
498
499 // Check that the routes are gone
500 const bool v4CustomRouteGone = !RouteExists({L"192.168.0.12", L"eth0", L"192.168.2.0/24", 12});
501 const bool v6CustomRouteGone = !RouteExists({L"fc00::12", L"eth0", L"fc00:abcd::/64", 12});
502
503 VERIFY_IS_TRUE(v4CustomRouteExists);
504 VERIFY_IS_TRUE(v6CustomRouteExists);
505
506 VERIFY_IS_TRUE(v4CustomRouteGone);
507 VERIFY_IS_TRUE(v6CustomRouteGone);
508 }
509
510 WSL2_TEST_METHOD(ResetRoutes)
511 {
512 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
513
514 // Add a custom route per address family
515 wsl::shared::hns::Route route;
516 route.NextHop = L"192.168.0.12";
517 route.DestinationPrefix = L"192.168.2.0/24";
518 route.Family = AF_INET;
519 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
520
521 wsl::shared::hns::Route v6Route;
522 v6Route.NextHop = L"fc00::12";
523 v6Route.DestinationPrefix = L"fc00:abcd::/80";
524 v6Route.Family = AF_INET6;
525 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
526
527 // Check that the custom routes are there
528 bool v4RouteExists = RouteExists({L"192.168.0.12", L"eth0", L"192.168.2.0/24"});
529 bool v6RouteExists = RouteExists({L"fc00::12", L"eth0", L"fc00:abcd::/80"});
530
531 // Reset the routing table
532 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Reset, GuestEndpointResourceType::Route);
533 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Reset, GuestEndpointResourceType::Route);
534
535 // Check that both routes are gone, per address family
536 bool v4RouteGoneAfterReset = !RouteExists({L"192.168.0.12", L"eth0", L"192.168.2.0/24"});
537 auto state = GetIpv4RoutingTableState();
538 bool v4GwGoneAfterReset = !state.DefaultRoute.has_value();
539
540 bool v6RouteGoneAfterReset = !RouteExists({L"fc00::12", L"eth0", L"fc00:abcd::/80"});
541 auto v6State = GetIpv6RoutingTableState();
542 bool v6GwGoneAfterReset = !v6State.DefaultRoute.has_value();
543
544 // Add the custom and default routes back
545 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
546 route.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_PREFIX;
547 route.NextHop = L"192.168.0.1";
548 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
549
550 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
551 v6Route.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_V6_PREFIX;
552 v6Route.NextHop = L"fc00::1";
553 SendDeviceSettingsRequest(L"eth0", v6Route, ModifyRequestType::Update, GuestEndpointResourceType::Route);
554
555 // Verify that all the routes are there
556 bool v4RouteRestored = RouteExists({L"192.168.0.12", L"eth0", L"192.168.2.0/24"});
557 state = GetIpv4RoutingTableState();
558 bool v4GwRestored = state.DefaultRoute.has_value();
559 bool v4GwRestoredCorrectly = state.DefaultRoute->Via == L"192.168.0.1";
560
561 bool v6RouteRestored = RouteExists({L"fc00::12", L"eth0", L"fc00:abcd::/80"});
562 v6State = GetIpv6RoutingTableState();
563 bool v6GwRestored = v6State.DefaultRoute.has_value();
564 bool v6GwRestoredCorrectly = v6State.DefaultRoute->Via == L"fc00::1";
565
566 VERIFY_IS_TRUE(v4RouteExists);
567 VERIFY_IS_TRUE(v6RouteExists);
568
569 VERIFY_IS_TRUE(v4RouteGoneAfterReset);
570 VERIFY_IS_TRUE(v4GwGoneAfterReset);
571 VERIFY_IS_TRUE(v6RouteGoneAfterReset);
572 VERIFY_IS_TRUE(v6GwGoneAfterReset);
573
574 VERIFY_IS_TRUE(v4RouteRestored);
575 VERIFY_IS_TRUE(v4GwRestored);
576 VERIFY_IS_TRUE(v4GwRestoredCorrectly);
577 VERIFY_IS_TRUE(v6RouteRestored);
578 VERIFY_IS_TRUE(v6GwRestored);
579 VERIFY_IS_TRUE(v6GwRestoredCorrectly);
580 }
581
582 WSL2_TEST_METHOD(ResetRoutesTwice)
583 {
584 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
585
586 auto state = GetIpv4RoutingTableState();
587 VERIFY_IS_TRUE(state.DefaultRoute.has_value());
588
589 auto v6State = GetIpv6RoutingTableState();
590 VERIFY_IS_TRUE(v6State.DefaultRoute.has_value());
591
592 // Reset the IPv4 table twice
593 wsl::shared::hns::Route route;
594 route.Family = AF_INET;
595 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Reset, GuestEndpointResourceType::Route);
596 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Reset, GuestEndpointResourceType::Route);
597
598 state = GetIpv4RoutingTableState();
599 VERIFY_IS_FALSE(state.DefaultRoute.has_value());
600 VERIFY_IS_TRUE(state.Routes.empty());
601
602 // Then reset the IPv6 table twice
603 route.Family = AF_INET6;
604 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Reset, GuestEndpointResourceType::Route);
605 SendDeviceSettingsRequest(L"eth0", route, ModifyRequestType::Reset, GuestEndpointResourceType::Route);
606
607 state = GetIpv6RoutingTableState();
608 VERIFY_IS_FALSE(state.DefaultRoute.has_value());
609 VERIFY_IS_TRUE(state.Routes.empty());
610 }
611
612 TEST_METHOD(TrackedRouteOrderingPreservesRouteIdentity)
613 {
614 const auto makeRoute = [](const wchar_t* destination, uint8_t prefixLength, const wchar_t* nextHop, ULONG metric = 10) {
615 MIB_IPFORWARD_ROW2 routeRow{};
616 routeRow.DestinationPrefix.Prefix = wsl::windows::common::string::StringToSockAddrInet(destination);
617 routeRow.DestinationPrefix.PrefixLength = prefixLength;
618 routeRow.NextHop = wsl::windows::common::string::StringToSockAddrInet(nextHop);
619 routeRow.Metric = metric;
620 return wsl::core::networking::EndpointRoute(routeRow);
621 };
622
623 std::set<wsl::core::networking::TrackedRoute> routes;
624 routes.emplace(makeRoute(L"10.0.0.0", 8, L"192.168.0.1"));
625 routes.emplace(makeRoute(L"10.0.0.0", 24, L"192.168.0.1"));
626 routes.emplace(makeRoute(L"10.0.0.0", 24, L"192.168.0.2"));
627 routes.emplace(makeRoute(L"10.0.0.0", 24, L"192.168.0.2", 20));
628 routes.emplace(makeRoute(L"10.0.0.0", 24, L"192.168.0.2"));
629
630 VERIFY_ARE_EQUAL(static_cast<size_t>(4), routes.size());
631
632 auto autoGeneratedRoute = makeRoute(L"192.168.0.0", 24, L"0.0.0.0");
633 autoGeneratedRoute.IsAutoGeneratedPrefixRoute = true;
634 const wsl::core::networking::TrackedRoute trackedAutoGeneratedRoute(autoGeneratedRoute);
635 const wsl::core::networking::TrackedRoute trackedOnlinkRoute(makeRoute(L"192.168.1.0", 24, L"0.0.0.0"));
636 const wsl::core::networking::TrackedRoute trackedOfflinkRoute(makeRoute(L"192.168.2.0", 24, L"192.168.0.1"));
637
638 VERIFY_IS_TRUE(trackedAutoGeneratedRoute < trackedOnlinkRoute);
639 VERIFY_IS_TRUE(trackedOnlinkRoute < trackedOfflinkRoute);
640 }
641
642 WSL2_TEST_METHOD(UpdateIpAddress)
643 {
644 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
645
646 // Verify that the IPs are in the preferred state
647 auto interfaceState = GetInterfaceState(L"eth0");
648 VERIFY_ARE_EQUAL(1, interfaceState.V4Addresses.size());
649 VERIFY_ARE_EQUAL(L"192.168.0.2", interfaceState.V4Addresses[0].Address);
650 VERIFY_IS_TRUE(interfaceState.V4Addresses[0].Preferred);
651
652 VERIFY_ARE_EQUAL(1, interfaceState.V6Addresses.size());
653 VERIFY_ARE_EQUAL(L"fc00::2", interfaceState.V6Addresses[0].Address);
654 VERIFY_IS_TRUE(interfaceState.V6Addresses[0].Preferred);
655
656 // Change current ip addresses to be deprecated
657 wsl::shared::hns::IPAddress address;
658 address.Address = L"192.168.0.2";
659 address.OnLinkPrefixLength = 24;
660 address.Family = AF_INET;
661 address.PreferredLifetime = 0;
662 SendDeviceSettingsRequest(L"eth0", address, ModifyRequestType::Update, GuestEndpointResourceType::IPAddress);
663
664 wsl::shared::hns::IPAddress v6Address;
665 v6Address.Address = L"fc00::2";
666 v6Address.OnLinkPrefixLength = 64;
667 v6Address.Family = AF_INET6;
668 address.PreferredLifetime = 0;
669 SendDeviceSettingsRequest(L"eth0", v6Address, ModifyRequestType::Update, GuestEndpointResourceType::IPAddress);
670
671 // Validate that the IPs are no longer preferred
672 interfaceState = GetInterfaceState(L"eth0");
673 VERIFY_ARE_EQUAL(1, interfaceState.V4Addresses.size());
674 VERIFY_ARE_EQUAL(L"192.168.0.2", interfaceState.V4Addresses[0].Address);
675 VERIFY_IS_FALSE(interfaceState.V4Addresses[0].Preferred);
676
677 VERIFY_ARE_EQUAL(1, interfaceState.V6Addresses.size());
678 VERIFY_ARE_EQUAL(L"fc00::2", interfaceState.V6Addresses[0].Address);
679 VERIFY_IS_FALSE(interfaceState.V6Addresses[0].Preferred);
680 }
681
682 enum IpPrefixOrigin
683 {
684 IpPrefixOriginOther = 0,
685 IpPrefixOriginManual,
686 IpPrefixOriginWellKnown,
687 IpPrefixOriginDhcp,
688 IpPrefixOriginRouterAdvertisement,
689 };
690
691 enum IpSuffixOrigin
692 {
693 IpSuffixOriginOther = 0,
694 IpSuffixOriginManual,
695 IpSuffixOriginWellKnown,
696 IpSuffixOriginDhcp,
697 IpSuffixOriginLinkLayerAddress,
698 IpSuffixOriginRandom,
699 };
700
701 WSL2_TEST_METHOD(TemporaryAddress)
702 {
703 TestCase({{L"eth0", {}, {}, {{L"fc00::2", 64}}, L"fc00::1"}});
704
705 // Make the address public
706 wsl::shared::hns::IPAddress v6Address;
707 v6Address.Address = L"fc00::2";
708 v6Address.OnLinkPrefixLength = 64;
709 v6Address.Family = AF_INET6;
710 v6Address.PrefixOrigin = IpPrefixOriginRouterAdvertisement;
711 v6Address.SuffixOrigin = IpSuffixOriginLinkLayerAddress;
712 v6Address.PreferredLifetime = 0xFFFFFFFF;
713 SendDeviceSettingsRequest(L"eth0", v6Address, ModifyRequestType::Update, GuestEndpointResourceType::IPAddress);
714
715 // Add a temporary address
716 v6Address.Address = L"fc00::abcd:1234:5678:9999";
717 v6Address.OnLinkPrefixLength = 64;
718 v6Address.Family = AF_INET6;
719 v6Address.PrefixOrigin = IpPrefixOriginRouterAdvertisement;
720 v6Address.SuffixOrigin = IpSuffixOriginRandom;
721 v6Address.PreferredLifetime = 0xFFFFFFFF;
722 SendDeviceSettingsRequest(L"eth0", v6Address, ModifyRequestType::Add, GuestEndpointResourceType::IPAddress);
723
724 // Wait for DAD to finish to avoid it being a factor in source address selection
725 std::this_thread::sleep_for(std::chrono::milliseconds(2000));
726
727 VERIFY_ARE_EQUAL(2, GetInterfaceState(L"eth0").V6Addresses.size());
728
729 // Ensure that the temporary address is preferred during source address selection
730 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip route get 2001::5");
731 LogInfo("'ip route get 2001::5' - '%ls'", out.c_str());
732
733 auto [out5, _5] = LxsstuLaunchWslAndCaptureOutput(L"ip addr show eth0");
734 LogInfo("[TemporaryAddress] ip addr show output:\r\n%ls", FixLineEndings(out5).c_str());
735
736 std::wsmatch match;
737 std::wregex pattern(L"2001::5 from :: via fc00::1 dev eth0 proto kernel src ([a-f,A-F,0-9,:]+)");
738 VERIFY_IS_TRUE(std::regex_search(out, match, pattern));
739 VERIFY_ARE_EQUAL(2, match.size());
740 VERIFY_ARE_EQUAL(L"fc00::abcd:1234:5678:9999", match.str(1));
741
742 // Make another public address
743 v6Address.Address = L"fc00::3";
744 v6Address.OnLinkPrefixLength = 64;
745 v6Address.Family = AF_INET6;
746 v6Address.PrefixOrigin = IpPrefixOriginRouterAdvertisement;
747 v6Address.SuffixOrigin = IpSuffixOriginLinkLayerAddress;
748 v6Address.PreferredLifetime = 0xFFFFFFFF;
749 SendDeviceSettingsRequest(L"eth0", v6Address, ModifyRequestType::Add, GuestEndpointResourceType::IPAddress);
750
751 // Test source address selection again
752 auto [out2, _2] = LxsstuLaunchWslAndCaptureOutput(L"ip route get 2001::6");
753 LogInfo("'ip route get 2001::6' - '%ls'", out2.c_str());
754
755 std::wregex pattern2(L"2001::6 from :: via fc00::1 dev eth0 proto kernel src ([a-f,A-F,0-9,:]+)");
756 VERIFY_IS_TRUE(std::regex_search(out2, match, pattern2));
757 VERIFY_ARE_EQUAL(2, match.size());
758 VERIFY_ARE_EQUAL(L"fc00::abcd:1234:5678:9999", match.str(1));
759 }
760
761 WSL2_TEST_METHOD(SimpleCase)
762 {
763 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
764 }
765
766 WSL2_TEST_METHOD(AddressChange)
767 {
768 TestCase(
769 {{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"},
770 {L"eth0", {{L"192.168.0.3", 24}}, L"192.168.0.1", {{L"fc00::3", 64}}, L"fc00::1"}});
771 }
772
773 WSL2_TEST_METHOD(GatewayChange)
774 {
775 TestCase(
776 {{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"},
777 {L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.3", {{L"fc00::2", 64}}, L"fc00::3"}});
778 }
779
780 WSL2_TEST_METHOD(NetworkChange)
781 {
782 TestCase(
783 {{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"},
784 {L"eth0", {{L"10.0.0.2", 16}}, L"10.0.0.1", {{L"fc00:abcd::5", 80}}, L"fc00:abcd::1"}});
785 }
786
787 WSL2_TEST_METHOD(NetworkChangeAndBack)
788 {
789 TestCase(
790 {{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"},
791 {L"eth0", {{L"10.0.0.2", 16}}, L"10.0.0.1", {{L"fc00:abcd::5", 80}}, L"fc00:abcd::1"},
792 {L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
793 }
794
795 WSL2_TEST_METHOD(NoChange)
796 {
797 TestCase(
798 {{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"},
799 {L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});
800 }
801
802 WSL2_TEST_METHOD(MultipleIps)
803 {
804 TestCase(
805 {{L"eth0",
806 {{L"192.168.0.2", 24}, {L"192.168.0.3", 24}},
807 L"192.168.0.1",
808 {{L"fc00::2", 64}, {L"fc00::3", 64}},
809 L"fc00::1"}});
810 }
811
812 WSL2_TEST_METHOD(MacAddressChangeAndBack)
813 {
814 const auto originalMac = GetMacAddress();
815
816 wsl::shared::hns::MacAddress macAddress;
817 macAddress.PhysicalAddress = "AA-AA-FF-FF-FF-FF";
818 SendDeviceSettingsRequest(L"eth0", macAddress, ModifyRequestType::Update, GuestEndpointResourceType::MacAddress);
819 VERIFY_ARE_EQUAL(GetMacAddress(), L"aa:aa:ff:ff:ff:ff");
820
821 macAddress.PhysicalAddress = wsl::shared::string::WideToMultiByte(originalMac);
822 std::replace(macAddress.PhysicalAddress.begin(), macAddress.PhysicalAddress.end(), ':', '-');
823 SendDeviceSettingsRequest(L"eth0", macAddress, ModifyRequestType::Update, GuestEndpointResourceType::MacAddress);
824 VERIFY_ARE_EQUAL(GetMacAddress(), originalMac);
825 }
826
827 static void VerifyDigDnsResolution(const std::wstring& digCommandLine)
828 {
829 // dig has exit code 0 when it receives a DNS response
830 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(digCommandLine.data(), 0);
831
832 // Verify dig returned a non-empty output
833 VERIFY_IS_TRUE(!out.empty());
834 }
835
836 static void VerifyDnsResolutionBasic()
837 {
838 // Verify basic DNS resolution using getent
839 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"getent ahosts bing.com", 0);
840 VERIFY_IS_TRUE(!out.empty());
841 }
842
843 static void VerifyDnsResolutionDig()
844 {
845 if (HostHasInternetConnectivity(AF_INET))
846 {
847 // Test A record resolution (IPv4) with both UDP and TCP
848 VerifyDigDnsResolution(L"dig +short +time=10 A bing.com");
849 VerifyDigDnsResolution(L"dig +tcp +short +time=10 A bing.com");
850
851 // Test reverse DNS lookup
852 VerifyDigDnsResolution(L"dig +short +time=10 -x 8.8.8.8");
853 VerifyDigDnsResolution(L"dig +tcp +short +time=10 -x 8.8.8.8");
854 }
855 else
856 {
857 LogInfo("Host does not have IPv4 internet connectivity. Skipping IPv4 DNS tests.");
858 }
859
860 if (HostHasInternetConnectivity(AF_INET6))
861 {
862 // Test AAAA record resolution (IPv6) with both UDP and TCP
863 VerifyDigDnsResolution(L"dig +short +time=10 AAAA bing.com");
864 VerifyDigDnsResolution(L"dig +tcp +short +time=10 AAAA bing.com");
865 }
866 else
867 {
868 LogInfo("Host does not have IPv6 internet connectivity. Skipping IPv6 DNS tests.");
869 }
870 }
871
872 static void VerifyDnsResolutionRecordTypes()
873 {
874 // Test various DNS record types
875 VerifyDigDnsResolution(L"dig +short +time=10 MX bing.com");
876 VerifyDigDnsResolution(L"dig +short +time=10 NS bing.com");
877 VerifyDigDnsResolution(L"dig +short +time=10 TXT bing.com");
878 VerifyDigDnsResolution(L"dig +short +time=10 SOA bing.com");
879 }
880
881 static void VerifyDnsQueries()
882 {
883 // query for A/IPv4 records
884 VerifyDigDnsResolution(L"dig +short +time=10 A bing.com");
885 VerifyDigDnsResolution(L"dig +tcp +short +time=10 A bing.com");
886
887 // query for AAAA/IPv6 records
888 VerifyDigDnsResolution(L"dig +short +time=10 AAAA bing.com");
889 VerifyDigDnsResolution(L"dig +tcp +short +time=10 AAAA bing.com");
890
891 // query for MX records
892 VerifyDigDnsResolution(L"dig +short +time=10 MX bing.com");
893 VerifyDigDnsResolution(L"dig +tcp +short +time=10 MX bing.com");
894
895 // query for NS records
896 VerifyDigDnsResolution(L"dig +short +time=10 NS bing.com");
897 VerifyDigDnsResolution(L"dig +tcp +short +time=10 NS bing.com");
898
899 // reverse DNS lookup
900 VerifyDigDnsResolution(L"dig +short +time=10 -x 8.8.8.8");
901 VerifyDigDnsResolution(L"dig +tcp +short +time=10 -x 8.8.8.8");
902
903 // query for SOA records
904 VerifyDigDnsResolution(L"dig +short +time=10 SOA bing.com");
905 VerifyDigDnsResolution(L"dig +tcp +short +time=10 SOA bing.com");
906
907 // query for TXT records
908 VerifyDigDnsResolution(L"dig +short +time=10 TXT bing.com");
909 VerifyDigDnsResolution(L"dig +tcp +short +time=10 TXT bing.com");
910
911 // query for CNAME records
912 VerifyDigDnsResolution(L"dig +time=10 CNAME bing.com");
913 VerifyDigDnsResolution(L"dig +tcp +time=10 CNAME bing.com");
914
915 // query for SRV records
916 VerifyDigDnsResolution(L"dig +time=10 SRV bing.com");
917 VerifyDigDnsResolution(L"dig +tcp +time=10 SRV bing.com");
918
919 // query for ANY - for this option dig expects a large response so it will query directly over TCP,
920 // instead of trying UDP first and falling back to TCP.
921 VerifyDigDnsResolution(L"dig +short +time=10 ANY bing.com");
922 }
923
924 static void VerifyDnsSuffixes()
925 {
926 bool foundSuffix = false;
927
928 // Verify global DNS suffixes are reflected in Linux
929 auto [outGlobal, errGlobal] = LxsstuLaunchPowershellAndCaptureOutput(
930 L"Get-DnsClientGlobalSetting | Select-Object -Property SuffixSearchList | ForEach-Object {$_.SuffixSearchList}");
931
932 const std::wstring separators = L" \n\t\r";
933
934 for (const auto& suffix : wsl::shared::string::SplitByMultipleSeparators(outGlobal, separators))
935 {
936 if (!suffix.empty())
937 {
938 foundSuffix = true;
939 // use grep -F as suffixes can contain '.'
940 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"cat /etc/resolv.conf | grep search | grep -F " + suffix), static_cast<DWORD>(0));
941 }
942 }
943
944 // Verify per-interface DNS suffixes are reflected in Linux
945 auto [outPerInterface, errPerInterface] =
946 LxsstuLaunchPowershellAndCaptureOutput(L"Get-DnsClient | ForEach-Object {$_.ConnectionSpecificSuffix}");
947
948 for (const auto& suffix : wsl::shared::string::SplitByMultipleSeparators(outPerInterface, separators))
949 {
950 if (!suffix.empty())
951 {
952 foundSuffix = true;
953 // use grep -F as suffixes can contain '.'
954 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"cat /etc/resolv.conf | grep search | grep -F " + suffix), static_cast<DWORD>(0));
955 }
956 }
957
958 // No suffix was found - configure a dummy global suffix, verify it's reflected in Linux, then delete it
959 if (!foundSuffix)
960 {
961 LxsstuLaunchPowershellAndCaptureOutput(L"Set-DnsClientGlobalSetting -SuffixSearchList @('test.com')");
962 auto restoreGlobalSuffixes = wil::scope_exit(
963 [&] { LxsstuLaunchPowershellAndCaptureOutput(L"Set-DnsClientGlobalSetting -SuffixSearchList @()"); });
964
965 std::this_thread::sleep_for(std::chrono::seconds(1));
966
967 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"cat /etc/resolv.conf | grep search | grep -F test.com"), static_cast<DWORD>(0));
968
969 LxsstuLaunchPowershellAndCaptureOutput(L"Set-DnsClientGlobalSetting -SuffixSearchList @()");
970 std::this_thread::sleep_for(std::chrono::seconds(1));
971
972 VERIFY_ARE_NOT_EQUAL(LxsstuLaunchWsl(L"cat /etc/resolv.conf | grep search | grep -F test.com"), static_cast<DWORD>(0));
973 }
974 }
975
976 static void VerifyEtcHosts()
977 {
978 const auto windowsHostsPath = "C:\\Windows\\System32\\drivers\\etc\\hosts";
979
980 // Save existing Windows /etc/hosts
981 std::wifstream windowsHostsRead(windowsHostsPath);
982 const auto oldWindowsHosts = std::wstring{std::istreambuf_iterator<wchar_t>(windowsHostsRead), {}};
983 windowsHostsRead.close();
984
985 auto restoreWindowsHosts = wil::scope_exit([&] {
986 std::wofstream windowsHostsWrite(windowsHostsPath);
987 windowsHostsWrite << oldWindowsHosts;
988 });
989
990 // Add dummy entry matching bing.com to IP 1.2.3.4
991 std::wofstream windowsHostsWrite(windowsHostsPath, std::ios_base::app);
992 windowsHostsWrite << "\n1.2.3.4 bing.com";
993 windowsHostsWrite.close();
994
995 // Verify Linux /etc/hosts does *not* contain 1.2.3.4
996 VERIFY_ARE_NOT_EQUAL(LxsstuLaunchWsl(L"cat /etc/hosts | grep -F 1.2.3.4"), static_cast<DWORD>(0));
997
998 // Verify bing.com gets resolved to 1.2.3.4 by dig
999 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"dig bing.com | grep -F 1.2.3.4"), static_cast<DWORD>(0));
1000 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"dig +tcp bing.com | grep -F 1.2.3.4"), static_cast<DWORD>(0));
1001 }
1002
1003 static void VerifyDnsTunneling(const std::wstring& dnsTunnelingIpAddress)
1004 {
1005 // Verify /etc/resolv.conf is configured with the expected nameserver
1006 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"cat /etc/resolv.conf | grep nameserver | grep -F " + dnsTunnelingIpAddress), static_cast<DWORD>(0));
1007
1008 // Verify that we have a working connection.
1009 GuestClient(L"tcp-connect:bing.com:80");
1010
1011 // Verify multiple types of DNS queries
1012 VerifyDnsQueries();
1013
1014 // Verify resolution via Windows /etc/hosts
1015 VerifyEtcHosts();
1016
1017 // Verify DNS tunneling works with systemd enabled
1018 auto revert = EnableSystemd();
1019
1020 GuestClient(L"tcp-connect:bing.com:80");
1021 VerifyDnsQueries();
1022 }
1023
1024 WSL2_TEST_METHOD(NatDnsTunneling)
1025 {
1026 DNS_TUNNELING_TEST_ONLY();
1027
1028 WslConfigChange config(LxssGenerateTestConfig({.dnsTunneling = true}));
1029
1030 VerifyDnsTunneling(c_dnsTunnelingDefaultIp);
1031 }
1032
1033 WSL2_TEST_METHOD(NatDnsTunnelingWithSpecificIp)
1034 {
1035 DNS_TUNNELING_TEST_ONLY();
1036
1037 WslConfigChange config(LxssGenerateTestConfig({.dnsTunneling = true, .dnsTunnelingIpAddress = L"10.255.255.1"}));
1038
1039 VerifyDnsTunneling(L"10.255.255.1");
1040 }
1041
1042 WSL2_TEST_METHOD(NatDnsTunnelingVerifySuffixes)
1043 {
1044 DNS_TUNNELING_TEST_ONLY();
1045
1046 WslConfigChange config(LxssGenerateTestConfig({.dnsTunneling = true}));
1047
1048 VerifyDnsSuffixes();
1049 }
1050
1051 WSL2_TEST_METHOD(NatWithoutIcsDnsProxy)
1052 {
1053 // Verify WSL has connectivity in NAT mode when the ICS DNS proxy is turned off (in which case the DNS servers
1054 // from Windows are mirrored in Linux)
1055 WslConfigChange config(LxssGenerateTestConfig({.dnsProxy = false}));
1056
1057 GuestClient(L"tcp-connect:bing.com:80");
1058 }
1059
1060 WSL2_TEST_METHOD(DnsChange)
1061 {
1062 wsl::shared::hns::DNS dns;
1063 dns.ServerList = {L"1.1.1.1"};
1064 dns.Options = LX_INIT_RESOLVCONF_FULL_HEADER;
1065 RunGns(dns, ModifyRequestType::Update, GuestEndpointResourceType::DNS);
1066
1067 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
1068 const std::wstring expected = std::wstring(LX_INIT_RESOLVCONF_FULL_HEADER) + L"nameserver 1.1.1.1\n";
1069 VERIFY_ARE_EQUAL(expected, out.c_str());
1070 }
1071
1072 WSL2_TEST_METHOD(DnsChangeMultipleServerAndSearch)
1073 {
1074 wsl::shared::hns::DNS dns;
1075 dns.ServerList = L"1.1.1.1,1.1.1.2";
1076 dns.Search = L"foo.microsoft.com,bar.microsoft.com";
1077 dns.Options = LX_INIT_RESOLVCONF_FULL_HEADER;
1078 RunGns(dns, ModifyRequestType::Update, GuestEndpointResourceType::DNS);
1079
1080 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
1081
1082 const std::wstring expected = std::wstring(LX_INIT_RESOLVCONF_FULL_HEADER) +
1083 L"nameserver 1.1.1.1\n"
1084 L"nameserver 1.1.1.2\n"
1085 L"search foo.microsoft.com bar.microsoft.com\n";
1086 VERIFY_ARE_EQUAL(expected, out.c_str());
1087 }
1088
1089 WSL2_TEST_METHOD(DnsResolutionBasic)
1090 {
1091 NetworkTests::VerifyDnsResolutionBasic();
1092 }
1093
1094 WSL2_TEST_METHOD(DnsResolutionDig)
1095 {
1096 NetworkTests::VerifyDnsResolutionDig();
1097 }
1098
1099 WSL2_TEST_METHOD(DnsResolutionRecordTypes)
1100 {
1101 NetworkTests::VerifyDnsResolutionRecordTypes();
1102 }
1103
1104 static void ClearHttpProxySettings(bool userScope)
1105 {
1106 auto command = L"Set-WinhttpProxy -SettingScope Machine -Proxy \\\"\\\" -AutoconfigUrl \\\"\\\"";
1107 if (userScope)
1108 {
1109 command = L"Set-WinhttpProxy -SettingScope User -Proxy \\\"\\\" -AutoconfigUrl \\\"\\\"";
1110 }
1111 LxsstuLaunchPowershellAndCaptureOutput(command);
1112 }
1113
1114 static void SetHttpProxySettings(const std::wstring& proxyString, const std::wstring& bypasses, const std::wstring& autoconfigUrl, bool userScope)
1115 {
1116 std::wstringstream proxySettings{};
1117 if (userScope)
1118 {
1119 proxySettings << L" -SettingScope User";
1120 }
1121 else
1122 {
1123 proxySettings << L" -SettingScope Machine";
1124 }
1125 if (!proxyString.empty())
1126 {
1127 proxySettings << L" -Proxy " + proxyString;
1128 }
1129 if (!bypasses.empty())
1130 {
1131 proxySettings << L" -ProxyBypass \\\"" + bypasses + L"\\\"";
1132 }
1133 if (!autoconfigUrl.empty())
1134 {
1135 proxySettings << L" -AutoconfigUrl " + autoconfigUrl;
1136 }
1137 LogInfo("SetHttpProxySettings %ls", proxySettings.str().c_str());
1138 auto [out, _] = LxsstuLaunchPowershellAndCaptureOutput(L"Set-WinhttpProxy" + proxySettings.str());
1139 LogInfo("WinhttpProxy %ls", out.c_str());
1140 }
1141
1142 static constexpr auto c_httpProxyLower = L"http_proxy";
1143 static constexpr auto c_httpProxyUpper = L"HTTP_PROXY";
1144 static constexpr auto c_httpsProxyLower = L"https_proxy";
1145 static constexpr auto c_httpsProxyUpper = L"HTTPS_PROXY";
1146 static constexpr auto c_proxyBypassLower = L"no_proxy";
1147 static constexpr auto c_proxyBypassUpper = L"NO_PROXY";
1148 static constexpr auto c_pacProxy = L"WSL_PAC_URL";
1149 static constexpr auto c_httpProxyHostPort = L"test.com:8888";
1150 static inline const std::wstring c_httpProxyString = std::wstring(L"http://") + c_httpProxyHostPort;
1151 static constexpr auto c_httpProxyString2 = L"http://otherServer.com:1234";
1152 static constexpr auto c_httpProxyLocalhost = L"http://localhost:8888";
1153 static constexpr auto c_httpProxyLoopback = L"http://loopback:8888";
1154 static constexpr auto c_httpProxyLocalhostv4 = L"http://127.0.0.1:8888";
1155 static constexpr auto c_httpProxyLocalhostv6 = L"http://[::1]:8888";
1156 static constexpr auto c_httpProxyIpV4 = L"http://198.168.1.128:8888";
1157 static constexpr auto c_httpProxyIpV6 = L"http://[2001::1]:8888";
1158 static constexpr auto c_httpProxyBypassString = L"test";
1159 static constexpr auto c_pacServerPrefix = L"http://127.0.0.1:12399/";
1160 static constexpr auto c_pacUrl = L"http://127.0.0.1:12399/wslproxy.pac";
1161 static inline const std::wstring c_pacScript =
1162 std::wstring(LR"(function FindProxyForURL(url, host) { return \"PROXY )") + c_httpProxyHostPort + LR"(\"; })";
1163
1164 static void VerifyWslEnvVariable(const std::wstring& envVar, const std::wstring& proxyString)
1165 {
1166 auto [output, _] = LxsstuLaunchWslAndCaptureOutput(L"echo -n $" + envVar);
1167 VERIFY_ARE_EQUAL(proxyString, output);
1168 }
1169
1170 static void VerifyHttpProxyBypassesMirrored(const std::wstring& bypassString)
1171 {
1172 VerifyWslEnvVariable(c_proxyBypassLower, bypassString);
1173 VerifyWslEnvVariable(c_proxyBypassUpper, bypassString);
1174 }
1175
1176 static void VerifyHttpProxyPacUrlMirrored(const std::wstring& pacUrl)
1177 {
1178 VerifyWslEnvVariable(c_pacProxy, pacUrl);
1179 }
1180
1181 static void VerifyHttpProxyStringMirrored(const std::wstring& proxyString)
1182 {
1183 VerifyWslEnvVariable(c_httpProxyLower, proxyString);
1184 VerifyWslEnvVariable(c_httpProxyUpper, proxyString);
1185 VerifyWslEnvVariable(c_httpsProxyLower, proxyString);
1186 VerifyWslEnvVariable(c_httpsProxyUpper, proxyString);
1187 }
1188
1189 static void VerifyHttpProxyEnvVariables(const std::wstring& proxyString, const std::wstring& bypassString, const std::wstring& pacUrl)
1190 {
1191 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"printenv");
1192 LogInfo("VerifyHttpProxyEnvVariables:\r\n%ls", FixLineEndings(out).c_str());
1193
1194 VerifyHttpProxyStringMirrored(proxyString);
1195 VerifyHttpProxyBypassesMirrored(bypassString);
1196 VerifyHttpProxyPacUrlMirrored(pacUrl);
1197 }
1198
1199 static void VerifyHttpProxySimple(bool userScope = true)
1200 {
1201 auto restoreProxySettings = wil::scope_exit([&] { ClearHttpProxySettings(userScope); });
1202
1203 SetHttpProxySettings(c_httpProxyString, L"", L"", userScope);
1204 VerifyHttpProxyEnvVariables(c_httpProxyString, L"", L"");
1205 }
1206
1207 static void VerifyNoHttpProxyConfigured(bool userScope = true)
1208 {
1209 ClearHttpProxySettings(userScope);
1210 VerifyHttpProxyEnvVariables(L"", L"", L"");
1211 }
1212
1213 static void VerifyHttpProxyWithBypassesConfigured(bool userScope = true)
1214 {
1215 auto restoreProxySettings = wil::scope_exit([&] { ClearHttpProxySettings(userScope); });
1216
1217 SetHttpProxySettings(c_httpProxyString, c_httpProxyBypassString, L"", userScope);
1218 VerifyHttpProxyEnvVariables(c_httpProxyString, c_httpProxyBypassString, L"");
1219 }
1220
1221 static void VerifyHttpProxyChange(bool userScope = true)
1222 {
1223 auto restoreProxySettings = wil::scope_exit([&] { ClearHttpProxySettings(userScope); });
1224
1225 SetHttpProxySettings(c_httpProxyString, L"", L"", userScope);
1226 VerifyHttpProxyEnvVariables(c_httpProxyString, L"", L"");
1227
1228 SetHttpProxySettings(c_httpProxyString2, L"", L"", userScope);
1229 VerifyHttpProxyEnvVariables(c_httpProxyString2, L"", L"");
1230 }
1231
1232 static void VerifyHttpProxyAndWslEnv(bool userScope = true)
1233 {
1234 auto restoreProxySettings = wil::scope_exit([&] {
1235 ClearHttpProxySettings(userScope);
1236 THROW_LAST_ERROR_IF(!SetEnvironmentVariable(c_httpProxyLower, nullptr));
1237 THROW_LAST_ERROR_IF(!SetEnvironmentVariable(L"WSLENV", nullptr));
1238 });
1239
1240 THROW_LAST_ERROR_IF(!SetEnvironmentVariable(c_httpProxyLower, c_httpProxyString.c_str()));
1241 std::wstring wslEnvVal{c_httpProxyLower};
1242 THROW_LAST_ERROR_IF(!SetEnvironmentVariable(L"WSLENV", wslEnvVal.append(L"/u").c_str()));
1243
1244 VerifyWslEnvVariable(c_httpProxyLower, c_httpProxyString);
1245 SetHttpProxySettings(c_httpProxyString2, L"", L"", true);
1246 // the user set environment variable should have priority over the proxy configured on host
1247 VerifyWslEnvVariable(c_httpProxyLower, c_httpProxyString);
1248 // this variable was not configured by user, so we use host configured proxy
1249 VerifyWslEnvVariable(c_httpProxyUpper, c_httpProxyString2);
1250 }
1251
1252 static void VerifyHttpProxyFilterByNetworkConfiguration(bool isNatMode)
1253 {
1254 auto restoreProxySettings = wil::scope_exit([&] { ClearHttpProxySettings(true); });
1255
1256 SetHttpProxySettings(c_httpProxyLocalhost, L"", L"", true);
1257 if (isNatMode)
1258 {
1259 VerifyHttpProxyEnvVariables(L"", L"", L"");
1260 }
1261 else
1262 {
1263 VerifyHttpProxyEnvVariables(c_httpProxyLocalhost, L"", L"");
1264 }
1265
1266 ClearHttpProxySettings(true);
1267
1268 SetHttpProxySettings(c_httpProxyLoopback, L"", L"", true);
1269 if (isNatMode)
1270 {
1271 VerifyHttpProxyEnvVariables(L"", L"", L"");
1272 }
1273 else
1274 {
1275 VerifyHttpProxyEnvVariables(c_httpProxyLoopback, L"", L"");
1276 }
1277
1278 ClearHttpProxySettings(true);
1279
1280 SetHttpProxySettings(c_httpProxyLocalhostv4, L"", L"", true);
1281 if (isNatMode)
1282 {
1283 VerifyHttpProxyEnvVariables(L"", L"", L"");
1284 }
1285 else
1286 {
1287 VerifyHttpProxyEnvVariables(c_httpProxyLocalhostv4, L"", L"");
1288 }
1289
1290 ClearHttpProxySettings(true);
1291
1292 SetHttpProxySettings(c_httpProxyLocalhostv4, c_httpProxyBypassString, L"", true);
1293 if (isNatMode)
1294 {
1295 VerifyHttpProxyEnvVariables(L"", L"", L"");
1296 }
1297 else
1298 {
1299 VerifyHttpProxyEnvVariables(c_httpProxyLocalhostv4, c_httpProxyBypassString, L"");
1300 }
1301
1302 ClearHttpProxySettings(true);
1303 // validate nonloopback v4 still works
1304 SetHttpProxySettings(c_httpProxyIpV4, L"", L"", true);
1305 VerifyHttpProxyEnvVariables(c_httpProxyIpV4, L"", L"");
1306
1307 ClearHttpProxySettings(true);
1308
1309 SetHttpProxySettings(c_httpProxyIpV6, c_httpProxyBypassString, L"", true);
1310 // v6 addresses is only supported in mirrored mode
1311 if (isNatMode)
1312 {
1313 VerifyHttpProxyEnvVariables(L"", L"", L"");
1314 }
1315 else
1316 {
1317 VerifyHttpProxyEnvVariables(c_httpProxyIpV6, c_httpProxyBypassString, L"");
1318 }
1319
1320 ClearHttpProxySettings(true);
1321 // v6 loopback is unsupported in both network modes
1322 SetHttpProxySettings(c_httpProxyLocalhostv6, L"", L"", true);
1323 VerifyHttpProxyEnvVariables(L"", L"", L"");
1324 }
1325
1326 static void VerifyHttpProxyFilterByNetworkConfigurationNAT()
1327 {
1328 VerifyHttpProxyFilterByNetworkConfiguration(true);
1329 }
1330
1331 static void VerifyHttpProxyFilterByNetworkConfigurationMirrored()
1332 {
1333 VerifyHttpProxyFilterByNetworkConfiguration(false);
1334 }
1335
1336 static void VerifyHttpProxyPac(bool userScope = true)
1337 {
1338 UniqueWebServer pacServer(c_pacServerPrefix, c_pacScript.c_str());
1339
1340 auto restoreProxySettings = wil::scope_exit([&] { ClearHttpProxySettings(userScope); });
1341
1342 SetHttpProxySettings(L"", L"", c_pacUrl, userScope);
1343
1344 // The update race condition is more likely to trigger for PAC as there is an additional http round trip.
1345 wsl::shared::retry::RetryWithTimeout<void>(
1346 [&]() {
1347 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(std::wstring(L"echo -n $") + c_httpProxyLower);
1348 THROW_HR_IF(E_FAIL, out != c_httpProxyString);
1349 },
1350 std::chrono::seconds(1),
1351 std::chrono::minutes(2));
1352
1353 VerifyHttpProxyPacUrlMirrored(c_pacUrl);
1354 VerifyHttpProxyStringMirrored(c_httpProxyString);
1355 }
1356
1357 WSL2_TEST_METHOD(NatHttpProxyVerifyConfigDisabled)
1358 {
1359 WINHTTP_PROXY_TEST_ONLY();
1360 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = false}));
1361
1362 auto restoreProxySettings = wil::scope_exit([&] { ClearHttpProxySettings(true); });
1363 SetHttpProxySettings(c_httpProxyString, L"", L"", true);
1364 VerifyHttpProxyEnvVariables(L"", L"", L"");
1365 }
1366
1367 WSL2_TEST_METHOD(NatHttpProxySimple)
1368 {
1369 WINHTTP_PROXY_TEST_ONLY();
1370 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = true}));
1371
1372 VerifyHttpProxySimple();
1373 }
1374
1375 WSL2_TEST_METHOD(NatHttpProxySimpleMachineScope)
1376 {
1377 WINHTTP_PROXY_TEST_ONLY();
1378 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = true}));
1379
1380 // verify with machine scope
1381 VerifyHttpProxySimple(false);
1382 }
1383
1384 WSL2_TEST_METHOD(NatNoHttpProxyConfigured)
1385 {
1386 WINHTTP_PROXY_TEST_ONLY();
1387 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = true}));
1388
1389 VerifyNoHttpProxyConfigured();
1390 }
1391
1392 WSL2_TEST_METHOD(NatHttpProxyWithBypassesConfigured)
1393 {
1394 WINHTTP_PROXY_TEST_ONLY();
1395 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = true}));
1396 VerifyHttpProxyWithBypassesConfigured();
1397 }
1398
1399 WSL2_TEST_METHOD(NatHttpProxyChange)
1400 {
1401 WINHTTP_PROXY_TEST_ONLY();
1402 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = true}));
1403 VerifyHttpProxyChange();
1404 }
1405
1406 WSL2_TEST_METHOD(NatHttpProxyAndWslEnv)
1407 {
1408 WINHTTP_PROXY_TEST_ONLY();
1409 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = true}));
1410 VerifyHttpProxyAndWslEnv();
1411 }
1412
1413 WSL2_TEST_METHOD(NatHttpProxyFilterByNetworkConfiguration)
1414 {
1415 WINHTTP_PROXY_TEST_ONLY();
1416 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = true}));
1417 VerifyHttpProxyFilterByNetworkConfigurationNAT();
1418 }
1419
1420 WSL2_TEST_METHOD(NatHttpProxyPac)
1421 {
1422 WINHTTP_PROXY_TEST_ONLY();
1423 WslConfigChange config(LxssGenerateTestConfig({.autoProxy = true}));
1424 VerifyHttpProxyPac();
1425 }
1426
1427 WSL2_TEST_METHOD(RenameInterface)
1428 {
1429 // Disconnect "eth0" interface so it can be renamed
1430 wsl::shared::hns::NetworkInterface link;
1431 link.Connected = false;
1432 RunGns(link, ModifyRequestType::Update, GuestEndpointResourceType::Interface);
1433 const bool eth0Disconnected = !GetInterfaceState(L"eth0").Up;
1434
1435 TestCase({{L"myeth", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1", false, 1500, true}});
1436 const bool myethConnected = GetInterfaceState(L"myeth").Up;
1437
1438 // Disconnect "myeth" interface so it can be restored
1439 link.Connected = false;
1440 RunGns(link, ModifyRequestType::Update, GuestEndpointResourceType::Interface);
1441 const bool myethDisconnected = !GetInterfaceState(L"myeth").Up;
1442
1443 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1", false, 1500, true}});
1444 const bool eth0Connected = GetInterfaceState(L"eth0").Up;
1445
1446 VERIFY_IS_TRUE(eth0Disconnected);
1447 VERIFY_IS_TRUE(myethConnected);
1448 VERIFY_IS_TRUE(myethDisconnected);
1449 VERIFY_IS_TRUE(eth0Connected);
1450 }
1451
1452 WSL2_TEST_METHOD(RenameWifiInterface)
1453 {
1454 std::wstring commandLine(L"wsl.exe bash -c \"zcat /proc/config.gz | grep CONFIG_PROXY_WIFI=y\"");
1455 const auto out = std::get<0>(LxsstuLaunchCommandAndCaptureOutputWithResult(commandLine.data()));
1456 if (out.empty())
1457 {
1458 LogSkipped("Kernel does not support PROXY_WIFI. Skipping test...");
1459 return;
1460 }
1461
1462 // Disconnect "eth0" interface so it can be renamed
1463 wsl::shared::hns::NetworkInterface link;
1464 link.Connected = false;
1465 RunGns(link, ModifyRequestType::Update, GuestEndpointResourceType::Interface);
1466 const bool eth0Disconnected = !GetInterfaceState(L"eth0").Up;
1467
1468 TestCase({{L"wlan0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1", false, 1500, true}});
1469 const bool _wlan0Connected = GetInterfaceState(L"_wlan0").Up;
1470
1471 const bool _wlan0Deleted = LxsstuLaunchWsl(L"ip link del wlan0") == (DWORD)0;
1472 TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1", false, 1500, true}});
1473 const bool eth0Connected = GetInterfaceState(L"eth0").Up;
1474
1475 VERIFY_IS_TRUE(eth0Disconnected);
1476 VERIFY_IS_TRUE(_wlan0Connected);
1477 VERIFY_IS_TRUE(_wlan0Deleted);
1478 VERIFY_IS_TRUE(eth0Connected);
1479 }
1480
1481 WSL2_TEST_METHOD(EnableLoopbackRouting)
1482 {
1483 // Enable accept_local and route_localnet settings for eth0
1484 wsl::shared::hns::VmNicCreatedNotification creationNotification{AdapterId};
1485 RunGns(creationNotification, LxGnsMessageVmNicCreatedNotification);
1486
1487 // Verify the settings were enabled
1488 const bool acceptLocalEnabled = LxsstuLaunchWsl(L"sysctl net.ipv4.conf.eth0.accept_local | grep -w 1") == (DWORD)0;
1489 const bool routeLocalnetEnabled = LxsstuLaunchWsl(L"sysctl net.ipv4.conf.eth0.route_localnet | grep -w 1") == (DWORD)0;
1490
1491 VERIFY_IS_TRUE(acceptLocalEnabled);
1492 VERIFY_IS_TRUE(routeLocalnetEnabled);
1493 }
1494
1495 WSL2_TEST_METHOD(InitializeLoopbackConfiguration)
1496 {
1497 // Assume eth0 is the GELNIC
1498 wsl::shared::hns::CreateDeviceRequest createDeviceRequest{wsl::shared::hns::DeviceType::Loopback, L"loopback", AdapterId};
1499 RunGns(createDeviceRequest, LxGnsMessageCreateDeviceRequest);
1500
1501 // Verify the expected ip rules are present
1502 const bool gelnicRuleTcpExists =
1503 LxsstuLaunchWsl(L"ip rule show | grep \"from all iif eth0 ipproto tcp lookup local\" | grep ^0:") == (DWORD)0;
1504 const bool gelnicRuleUdpExists =
1505 LxsstuLaunchWsl(L"ip rule show | grep \"from all iif eth0 ipproto tcp lookup local\" | grep ^0:") == (DWORD)0;
1506
1507 const bool table127RuleTcpExists =
1508 LxsstuLaunchWsl(L"ip rule show | grep \"from all ipproto tcp lookup 127\" | grep ^1:") == (DWORD)0;
1509 const bool table127RuleUdpExists =
1510 LxsstuLaunchWsl(L"ip rule show | grep \"from all ipproto udp lookup 127\" | grep ^1:") == (DWORD)0;
1511 const bool table128RuleTcpExists =
1512 LxsstuLaunchWsl(L"ip rule show | grep \"from all ipproto tcp lookup 128\" | grep ^1:") == (DWORD)0;
1513 const bool table128RuleUdpExists =
1514 LxsstuLaunchWsl(L"ip rule show | grep \"from all ipproto udp lookup 128\" | grep ^1:") == (DWORD)0;
1515
1516 const bool localTableRuleExists = LxsstuLaunchWsl(L"ip rule show | grep \"from all lookup local\" | grep ^2:") == (DWORD)0;
1517
1518 // Verify that the static neighbor entry was added for the gateway
1519 const bool gatewayArpEntryExists =
1520 LxsstuLaunchWsl(L"ip neigh show dev eth0 | grep \"169\\.254\\.73\\.249 lladdr 00:11:22:33:44:55 PERMANENT\"") == (DWORD)0;
1521
1522 // Verify route was added for destination 127.0.0.1, with preferred source 127.0.0.1
1523 const bool routeToLoopbackRangeExists =
1524 LxsstuLaunchWsl(
1525 L"ip route show table 127 | grep \"127\\.0\\.0\\.1 via 169\\.254\\.73\\.249 dev eth0\" | grep "
1526 L"\"src 127\\.0\\.0\\.1\" | grep onlink") == (DWORD)0;
1527
1528 const bool shutdownSuccessful = WslShutdown();
1529
1530 VERIFY_IS_TRUE(gelnicRuleTcpExists);
1531 VERIFY_IS_TRUE(gelnicRuleUdpExists);
1532 VERIFY_IS_TRUE(table127RuleTcpExists);
1533 VERIFY_IS_TRUE(table127RuleUdpExists);
1534 VERIFY_IS_TRUE(table128RuleTcpExists);
1535 VERIFY_IS_TRUE(table128RuleUdpExists);
1536 VERIFY_IS_TRUE(localTableRuleExists);
1537
1538 VERIFY_IS_TRUE(gatewayArpEntryExists);
1539 VERIFY_IS_TRUE(routeToLoopbackRangeExists);
1540
1541 VERIFY_IS_TRUE(shutdownSuccessful);
1542 }
1543
1544 WSL2_TEST_METHOD(AddRemoveLoopbackRoutesv4)
1545 {
1546 const std::wstring interfaceName = L"eth0";
1547 const std::vector<std::wstring> ipAddresses = {L"127.0.0.1", L"127.0.0.2"};
1548
1549 // Add routes on interface eth0 and verify that the routes were added in the custom local routing table (id 128)
1550 for (const auto address : ipAddresses)
1551 {
1552 wsl::shared::hns::LoopbackRoutesRequest addRequest{interfaceName, wsl::shared::hns::OperationType::Create, AF_INET, address};
1553 RunGns(addRequest, LxGnsMessageLoopbackRoutesRequest);
1554 }
1555
1556 const bool firstRouteExists =
1557 LxsstuLaunchWsl(
1558 L"ip route show table 128 | grep \"127\\.0\\.0\\.1 via 169\\.254\\.73\\.249 dev eth0\" | grep \"src "
1559 L"127\\.0\\.0\\.1\" | grep onlink") == (DWORD)0;
1560 const bool secondRouteExists =
1561 LxsstuLaunchWsl(
1562 L"ip route show table 128 | grep \"127\\.0\\.0\\.2 via 169\\.254\\.73\\.249 dev eth0\" | grep \"src "
1563 L"127\\.0\\.0\\.2\" | grep onlink") == (DWORD)0;
1564
1565 // Verify that the static neighbor entry was added for the gateway
1566 const bool gatewayArpEntryExists =
1567 LxsstuLaunchWsl(L"ip neigh show dev eth0 | grep \"169\\.254\\.73\\.249 lladdr 00:11:22:33:44:55 PERMANENT\"") == (DWORD)0;
1568
1569 // Verify that the routes are deleted
1570 for (const auto address : ipAddresses)
1571 {
1572 wsl::shared::hns::LoopbackRoutesRequest removeRequest{interfaceName, wsl::shared::hns::OperationType::Remove, AF_INET, address};
1573 RunGns(removeRequest, LxGnsMessageLoopbackRoutesRequest);
1574 }
1575
1576 const bool firstRouteDeleted = LxsstuLaunchWsl(L"ip route show table 128 | grep 127\\.0\\.0\\.1") == (DWORD)1;
1577 const bool secondRouteDeleted = LxsstuLaunchWsl(L"ip route show table 128 | grep 127\\.0\\.0\\.2") == (DWORD)1;
1578
1579 const bool shutdownSuccessful = WslShutdown();
1580
1581 VERIFY_IS_TRUE(firstRouteExists);
1582 VERIFY_IS_TRUE(secondRouteExists);
1583
1584 VERIFY_IS_TRUE(gatewayArpEntryExists);
1585
1586 VERIFY_IS_TRUE(firstRouteDeleted);
1587 VERIFY_IS_TRUE(secondRouteDeleted);
1588
1589 VERIFY_IS_TRUE(shutdownSuccessful);
1590 }
1591
1592 /*
1593 The test uses the "ip route get" command, which is equivalent to asking the OS what route it will take for a packet. It
1594 functions as a small integration test.
1595 */
1596 WSL2_TEST_METHOD(LoopbackGetRoute)
1597 {
1598 // Verify that before configurations are applied, the route chosen for 127.0.0.1 tcp/udp uses the local routing table
1599 const bool loopbackTcpUsesLocalTable =
1600 LxsstuLaunchWsl(L"ip route get from 127.0.0.1 127.0.0.1 ipproto tcp | grep local") == (DWORD)0;
1601 const bool loopbackUdpUsesLocalTable =
1602 LxsstuLaunchWsl(L"ip route get from 127.0.0.1 127.0.0.1 ipproto udp | grep local") == (DWORD)0;
1603
1604 // Assume eth0 is the GELNIC
1605 wsl::shared::hns::CreateDeviceRequest createDeviceRequest{wsl::shared::hns::DeviceType::Loopback, L"loopback", AdapterId};
1606 RunGns(createDeviceRequest, LxGnsMessageCreateDeviceRequest);
1607
1608 // Verify that after configurations are applied, the route chosen for 127.0.0.1 tcp/udp is the desired one
1609 const bool loopbackTcpUsesCustomTable =
1610 LxsstuLaunchWsl(L"ip route get from 127.0.0.1 127.0.0.1 ipproto tcp | grep \"via 169\\.254\\.73\\.249 dev eth0\"") == (DWORD)0;
1611 const bool loopbackUdpUsesCustomTable =
1612 LxsstuLaunchWsl(L"ip route get from 127.0.0.1 127.0.0.1 ipproto udp | grep \"via 169\\.254\\.73\\.249 dev eth0\"") == (DWORD)0;
1613
1614 const bool shutdownSuccessful = WslShutdown();
1615
1616 VERIFY_IS_TRUE(loopbackTcpUsesLocalTable);
1617 VERIFY_IS_TRUE(loopbackUdpUsesLocalTable);
1618
1619 VERIFY_IS_TRUE(loopbackTcpUsesCustomTable);
1620 VERIFY_IS_TRUE(loopbackUdpUsesCustomTable);
1621
1622 VERIFY_IS_TRUE(shutdownSuccessful);
1623 }
1624
1625 // Validate that adapter has an ip address, default route and DNS configuration in NAT mode
1626 WSL2_TEST_METHOD(NatConfiguration)
1627 {
1628 WslConfigChange config(LxssGenerateTestConfig());
1629
1630 const auto state = GetInterfaceState(L"eth0");
1631 VERIFY_IS_FALSE(state.V4Addresses.empty());
1632 VERIFY_IS_TRUE(state.Gateway.has_value());
1633
1634 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
1635 const std::wregex pattern(L"(.|\n)*nameserver [0-9\\. ]+(.|\n)*", std::regex::extended);
1636
1637 VERIFY_IS_TRUE(std::regex_match(out, pattern));
1638 }
1639
1640 static void WriteNatConfiguration(const std::wstring& network, const std::wstring& gateway, const std::wstring& ipAddress)
1641 {
1642 using namespace wsl::windows::common;
1643 const auto key = registry::OpenLxssMachineKey(KEY_SET_VALUE);
1644
1645 if (gateway == L"delete")
1646 {
1647 registry::DeleteValue(key.get(), L"NatGatewayIpAddress");
1648 }
1649 else if (!gateway.empty())
1650 {
1651 registry::WriteString(key.get(), nullptr, L"NatGatewayIpAddress", gateway.c_str());
1652 }
1653
1654 if (network == L"delete")
1655 {
1656 registry::DeleteValue(key.get(), L"NatNetwork");
1657 }
1658 else if (!network.empty())
1659 {
1660 registry::WriteString(key.get(), nullptr, L"NatNetwork", network.c_str());
1661 }
1662
1663 const auto userKey = registry::OpenLxssUserKey();
1664 if (ipAddress == L"delete")
1665 {
1666 registry::DeleteValue(userKey.get(), L"NatIpAddress");
1667 }
1668 else if (!ipAddress.empty())
1669 {
1670 registry::WriteString(userKey.get(), nullptr, L"NatIpAddress", ipAddress.c_str());
1671 }
1672 }
1673
1674 struct NatNetworkingConfiguration
1675 {
1676 std::wstring networkRange;
1677 std::wstring gatewayIpAddress;
1678 std::wstring ipAddress;
1679 };
1680
1681 static NatNetworkingConfiguration GetNatConfiguration()
1682 {
1683 using namespace wsl::windows::common;
1684 const auto key = registry::OpenLxssMachineKey();
1685
1686 const auto userKey = registry::OpenLxssUserKey();
1687
1688 return {
1689 registry::ReadString(key.get(), nullptr, L"NatNetwork", L""),
1690 registry::ReadString(key.get(), nullptr, L"NatGatewayIpAddress", L""),
1691 registry::ReadString(userKey.get(), nullptr, L"NatIpAddress", L"")};
1692 }
1693
1694 static void ResetWslNetwork()
1695 {
1696 // N.B. This must be kept in sync with the network IDs in NatNetworking.cpp.
1697 GUID natNetworkId;
1698 if (!AreExperimentalNetworkingFeaturesSupported() || !IsHyperVFirewallSupported())
1699 {
1700 natNetworkId = {0xb95d0c5e, 0x57d4, 0x412b, {0xb5, 0x71, 0x18, 0xa8, 0x1a, 0x16, 0xe0, 0x05}};
1701 }
1702 else
1703 {
1704 natNetworkId = {0x790e58b4, 0x7939, 0x4434, {0x93, 0x58, 0x89, 0xae, 0x7d, 0xdb, 0xe8, 0x7e}};
1705 }
1706
1707 wil::unique_cotaskmem_string error;
1708 const auto hr = HcnDeleteNetwork(natNetworkId, &error);
1709 VERIFY_SUCCEEDED(hr, error.get());
1710 }
1711
1712 WSL2_TEST_METHOD(NatInvalidRange)
1713 {
1714 WslConfigChange config(LxssGenerateTestConfig());
1715 WriteNatConfiguration(L"InvalidRange", {}, {L"delete"});
1716 ResetWslNetwork();
1717 RestartWslService();
1718
1719 const auto state = GetInterfaceState(
1720 L"eth0",
1721 L"wsl: Failed to create virtual network with address range: 'InvalidRange', created new network with range: "
1722 L"'*.*.*.*/*', *.*");
1723
1724 VERIFY_IS_FALSE(state.V4Addresses.empty());
1725 VERIFY_IS_TRUE(state.Gateway.has_value());
1726
1727 const auto networkConfiguration = GetNatConfiguration();
1728 VERIFY_IS_FALSE(networkConfiguration.networkRange.empty());
1729 VERIFY_ARE_EQUAL(state.V4Addresses[0].Address, networkConfiguration.ipAddress);
1730 VERIFY_ARE_EQUAL(state.Gateway.value_or(L""), networkConfiguration.gatewayIpAddress);
1731 }
1732
1733 WSL2_TEST_METHOD(NatInvalidGateway)
1734 {
1735 WslConfigChange config(LxssGenerateTestConfig());
1736 WriteNatConfiguration({}, L"InvalidGateway", {});
1737 ResetWslNetwork();
1738 RestartWslService();
1739
1740 const auto state = GetInterfaceState(
1741 L"eth0",
1742 L"wsl: Failed to create virtual network with address range: '*.*.*.*/*', created new network with range: "
1743 L"'*.*.*.*/*', *.*");
1744
1745 VERIFY_IS_FALSE(state.V4Addresses.empty());
1746 VERIFY_IS_TRUE(state.Gateway.has_value());
1747
1748 const auto networkConfiguration = GetNatConfiguration();
1749 VERIFY_IS_FALSE(networkConfiguration.networkRange.empty());
1750 VERIFY_ARE_EQUAL(state.V4Addresses[0].Address, networkConfiguration.ipAddress);
1751 VERIFY_ARE_EQUAL(state.Gateway.value_or(L""), networkConfiguration.gatewayIpAddress);
1752 }
1753
1754 WSL2_TEST_METHOD(NatInvalidAddress)
1755 {
1756 WslConfigChange config(LxssGenerateTestConfig());
1757
1758 const auto previousConfiguration = GetNatConfiguration();
1759 WriteNatConfiguration({}, {}, L"InvalidAddress");
1760 ResetWslNetwork();
1761 RestartWslService();
1762
1763 const auto state = GetInterfaceState(
1764 L"eth0", L"wsl: Failed to create network endpoint with address: 'InvalidAddress', assigned new address: '*.*.*.*'*");
1765 VERIFY_IS_FALSE(state.V4Addresses.empty());
1766 VERIFY_IS_TRUE(state.Gateway.has_value());
1767
1768 const auto networkConfiguration = GetNatConfiguration();
1769 // The network range should be the same
1770 VERIFY_ARE_EQUAL(networkConfiguration.networkRange, previousConfiguration.networkRange);
1771
1772 VERIFY_IS_FALSE(networkConfiguration.networkRange.empty());
1773 VERIFY_ARE_EQUAL(state.V4Addresses[0].Address, networkConfiguration.ipAddress);
1774 VERIFY_ARE_EQUAL(state.Gateway.value_or(L""), networkConfiguration.gatewayIpAddress);
1775 }
1776
1777 struct unique_kill_process
1778 {
1779 unique_kill_process()
1780 {
1781 }
1782 unique_kill_process(wil::unique_handle&& process) : m_process(std::move(process))
1783 {
1784 }
1785
1786 unique_kill_process(unique_kill_process&&) = default;
1787 unique_kill_process& operator=(unique_kill_process&&) = default;
1788
1789 unique_kill_process& operator=(const unique_kill_process&) = delete;
1790 unique_kill_process(const unique_kill_process&) = delete;
1791
1792 ~unique_kill_process()
1793 {
1794 reset();
1795 }
1796
1797 void reset()
1798 {
1799 if (m_process)
1800 {
1801 TerminateProcess(m_process.get(), 0);
1802 m_process.reset();
1803 }
1804 }
1805
1806 wil::unique_handle m_process;
1807 };
1808
1809 static void VerifyLoopbackHostToGuest(const std::wstring& address, int protocol, std::chrono::duration<int> timeout = std::chrono::minutes(5))
1810 {
1811 LogInfo("VerifyLoopbackHostToGuest(address=%ls, protocol=%d)", address.c_str(), protocol);
1812
1813 SOCKADDR_INET addr = wsl::windows::common::string::StringToSockAddrInet(address);
1814 SS_PORT(&addr) = htons(1234);
1815
1816 {
1817 // Create listener in guest
1818 std::optional<GuestListener> listener;
1819
1820 // Note: If a previous test case had the same port bound it can take a bit of time for the port to be released on the host.
1821 auto createListener = [&]() { listener.emplace(addr, protocol); };
1822 try
1823 {
1824 wsl::shared::retry::RetryWithTimeout<void>(
1825 createListener, std::chrono::seconds(1), timeout, []() { return wil::ResultFromCaughtException() == E_FAIL; });
1826 }
1827 catch (...)
1828 {
1829 LogError("Failed to bind %ls in the guest, 0x%x", address.c_str(), wil::ResultFromCaughtException());
1830 VERIFY_FAIL();
1831 }
1832
1833 // If the guest is listening on any address, connect via loopback.
1834 const auto ipAddress = (addr.si_family == AF_INET) ? reinterpret_cast<const void*>(&addr.Ipv4.sin_addr)
1835 : reinterpret_cast<const void*>(&addr.Ipv6.sin6_addr);
1836 if (INET_IS_ADDR_UNSPECIFIED(addr.si_family, ipAddress))
1837 {
1838 INETADDR_SETLOOPBACK(reinterpret_cast<PSOCKADDR>(&addr));
1839 SS_PORT(&addr) = htons(1234);
1840 }
1841
1842 // Connect from a client on the host
1843 const wil::unique_socket clientSocket(socket(addr.si_family, (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM, protocol));
1844 VERIFY_ARE_NOT_EQUAL(clientSocket.get(), INVALID_SOCKET);
1845 // The WSL2 loopback relay may have a one second delay after creation.
1846
1847 auto pred = [&]() {
1848 if (protocol == IPPROTO_UDP)
1849 {
1850 const char buffer = 'A';
1851 THROW_HR_IF(
1852 E_FAIL,
1853 sendto(clientSocket.get(), &buffer, sizeof(buffer), 0, reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) !=
1854 sizeof(buffer));
1855 }
1856 else
1857 {
1858 THROW_HR_IF(E_FAIL, connect(clientSocket.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) == SOCKET_ERROR);
1859 }
1860 };
1861
1862 try
1863 {
1864 wsl::shared::retry::RetryWithTimeout<void>(pred, std::chrono::seconds(1), timeout);
1865 }
1866 catch (...)
1867 {
1868 LogError("Timed out trying to connect to %ls", address.c_str());
1869 VERIFY_FAIL();
1870 }
1871
1872 // Verify the connection was accepted on the listener
1873 listener->AcceptConnection();
1874 }
1875
1876 // Wait until the guest has released its port
1877 VerifyNotBound(addr, addr.si_family, protocol);
1878 }
1879
1880 TEST_METHOD(HostToGuestLoopback)
1881 {
1882 BEGIN_TEST_METHOD_PROPERTIES()
1883 TEST_METHOD_PROPERTY(L"Data:NetConfig", L"{1, 2, 3, 4}")
1884 END_TEST_METHOD_PROPERTIES()
1885
1886 // All networking modes for both WSL1/2 are expected to support TCP/IPv4 host to guest loopback by default.
1887 int networkingModeVal = 0;
1888 WEX::TestExecution::TestData::TryGetValue(L"NetConfig", networkingModeVal);
1889 auto networkingMode = static_cast<wsl::core::NetworkingMode>(networkingModeVal);
1890 switch (networkingMode)
1891 {
1892 case wsl::core::NetworkingMode::Bridged:
1893 WINDOWS_11_TEST_ONLY();
1894 __fallthrough;
1895 case wsl::core::NetworkingMode::Mirrored:
1896 case wsl::core::NetworkingMode::Consomme:
1897 if (!LxsstuVmMode())
1898 {
1899 LogSkipped("This test is only applicable to WSL2");
1900 return;
1901 }
1902 break;
1903 }
1904
1905 LogInfo("HostToGuestLoopback (networkingMode=%hs)", ToString(networkingMode));
1906 WslConfigChange config(LxssGenerateTestConfig({.networkingMode = networkingMode, .vmSwitch = L"Default Switch"}));
1907 VerifyLoopbackHostToGuest(L"127.0.0.1", IPPROTO_TCP);
1908 VerifyLoopbackHostToGuest(L"0.0.0.0", IPPROTO_TCP);
1909 }
1910
1911 static void VerifyLoopbackGuestToHost(const std::wstring& address, int protocol)
1912 {
1913 LogInfo("VerifyLoopbackGuestToHost(address=%ls, protocol=%d)", address.c_str(), protocol);
1914
1915 SOCKADDR_INET addr = wsl::windows::common::string::StringToSockAddrInet(address);
1916 SS_PORT(&addr) = htons(1234);
1917
1918 // Create a listener on the host
1919 const wil::unique_socket listenSocket(socket(addr.si_family, (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM, protocol));
1920 VERIFY_ARE_NOT_EQUAL(listenSocket.get(), INVALID_SOCKET);
1921 VERIFY_ARE_NOT_EQUAL(bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)), SOCKET_ERROR);
1922 if (protocol == IPPROTO_TCP)
1923 {
1924 VERIFY_ARE_NOT_EQUAL(listen(listenSocket.get(), SOMAXCONN), SOCKET_ERROR);
1925 }
1926
1927 // Connect from a client in the guest
1928 GuestClient client(addr, protocol);
1929
1930 // Accept the connection on the listener
1931 SOCKADDR_INET remoteAddr{};
1932 int remoteAddrLen = sizeof(remoteAddr);
1933 if (protocol == IPPROTO_UDP)
1934 {
1935 char buffer[2048];
1936 int Timeout = 3000;
1937 VERIFY_ARE_NOT_EQUAL(setsockopt(listenSocket.get(), SOL_SOCKET, SO_RCVTIMEO, (char*)&Timeout, sizeof(Timeout)), SOCKET_ERROR);
1938 VERIFY_ARE_NOT_EQUAL(
1939 recvfrom(listenSocket.get(), buffer, sizeof(buffer), 0, reinterpret_cast<SOCKADDR*>(&remoteAddr), &remoteAddrLen), SOCKET_ERROR);
1940 }
1941 else
1942 {
1943 // TODO: this accept call needs to timeout to avoid indefinite wait
1944 const wil::unique_socket acceptSocket(accept(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&remoteAddr), &remoteAddrLen));
1945 VERIFY_ARE_NOT_EQUAL(acceptSocket.get(), INVALID_SOCKET);
1946 }
1947 }
1948
1949 static void VerifyLoopbackGuestToGuest(const std::wstring& address, int protocol)
1950 {
1951 LogInfo("VerifyLoopbackGuestToGuest(address=%ls, protocol=%d)", address.c_str(), protocol);
1952
1953 SOCKADDR_INET addr = wsl::windows::common::string::StringToSockAddrInet(address);
1954 SS_PORT(&addr) = htons(1234);
1955
1956 {
1957 std::optional<GuestListener> listener;
1958
1959 auto createListener = [&]() { listener.emplace(addr, protocol); };
1960 try
1961 {
1962 wsl::shared::retry::RetryWithTimeout<void>(createListener, std::chrono::seconds(1), std::chrono::minutes(1), []() {
1963 return wil::ResultFromCaughtException() == E_FAIL;
1964 });
1965 }
1966 catch (...)
1967 {
1968 LogError("Failed to bind %ls", address.c_str());
1969 VERIFY_FAIL();
1970 }
1971
1972 // Create listener in guest
1973
1974 // Connect from a client in the guest
1975 GuestClient client(addr, protocol);
1976
1977 // Verify the connection was accepted on the listener
1978 listener->AcceptConnection();
1979 }
1980
1981 // Wait until the guest has released its port
1982 VerifyNotBound(addr, addr.si_family, protocol);
1983 }
1984
1985 static void VerifyLoopbackConnectivity(const std::wstring& address)
1986 {
1987 // Verify guest to host
1988 VerifyLoopbackGuestToHost(address, IPPROTO_UDP);
1989 VerifyLoopbackGuestToHost(address, IPPROTO_TCP);
1990
1991 // Verify host to guest
1992 VerifyLoopbackHostToGuest(address, IPPROTO_UDP);
1993 VerifyLoopbackHostToGuest(address, IPPROTO_TCP);
1994
1995 // Verify guest to guest
1996 VerifyLoopbackGuestToGuest(address, IPPROTO_UDP);
1997 VerifyLoopbackGuestToGuest(address, IPPROTO_TCP);
1998 }
1999
2000 static wil::unique_socket BindHostPort(uint16_t Port, int Type, int Protocol, bool ExpectSuccess, bool Ipv6 = false, bool Localhost = false)
2001 {
2002 int AddressFamily{};
2003 const SOCKADDR* Address{};
2004 int AddressSize{};
2005 SOCKADDR_IN Address4{};
2006 SOCKADDR_IN6 Address6{};
2007 if (Ipv6)
2008 {
2009 AddressFamily = AF_INET6;
2010 Address6.sin6_family = AF_INET6;
2011 Address6.sin6_port = htons(Port);
2012 if (Localhost)
2013 {
2014 Address6.sin6_addr = IN6ADDR_LOOPBACK_INIT;
2015 }
2016 Address = reinterpret_cast<SOCKADDR*>(&Address6);
2017 AddressSize = sizeof(Address6);
2018 }
2019 else
2020 {
2021 AddressFamily = AF_INET;
2022 Address4.sin_family = AF_INET;
2023 Address4.sin_port = htons(Port);
2024 if (Localhost)
2025 {
2026 Address4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2027 }
2028 Address = reinterpret_cast<SOCKADDR*>(&Address4);
2029 AddressSize = sizeof(Address4);
2030 }
2031
2032 wil::unique_socket listenSocket(socket(AddressFamily, Type, Protocol));
2033 VERIFY_IS_TRUE(!!listenSocket);
2034
2035 VERIFY_ARE_EQUAL(bind(listenSocket.get(), Address, AddressSize) != SOCKET_ERROR, ExpectSuccess);
2036
2037 return listenSocket;
2038 }
2039
2040 static std::tuple<unique_kill_process, bool, wil::unique_handle> BindGuestPortHelper(std::wstring_view BindSpec)
2041 {
2042 auto [stdErrRead, stdErrWrite] = CreateSubprocessPipe(false, true);
2043 auto [stdOutRead, stdOutWrite] = CreateSubprocessPipe(false, true);
2044 const std::wstring wslCmd = L"socat -dd " + std::wstring(BindSpec) + L" STDOUT";
2045 auto cmd = LxssGenerateWslCommandLine(wslCmd.data());
2046
2047 auto process = LxsstuStartProcess(cmd.data(), nullptr, stdOutWrite.get(), stdErrWrite.get());
2048 stdErrWrite.reset();
2049 stdOutWrite.reset();
2050
2051 const std::map<std::string_view, bool> patterns = {
2052 {"listening on", true},
2053 {"Address already in use", false},
2054 };
2055
2056 bool success = false;
2057 bool finished = false;
2058 DWORD writeOffset = 0;
2059 constexpr DWORD readOffset = 0;
2060 std::string output(512, '\0');
2061 while (!finished)
2062 {
2063 DWORD bytesRead = 0;
2064 if (!ReadFile(stdErrRead.get(), output.data() + writeOffset, static_cast<DWORD>(output.size() - writeOffset), &bytesRead, nullptr))
2065 {
2066 break;
2067 }
2068
2069 writeOffset += bytesRead;
2070 LogInfo("output %hs", output.c_str());
2071 std::string_view outputView = output;
2072 for (const auto& pattern : patterns)
2073 {
2074 DWORD patternOffset = readOffset;
2075 auto matchString = pattern.first;
2076 while (!finished && (patternOffset + matchString.length() < writeOffset))
2077 {
2078 if (outputView.substr(patternOffset).starts_with(matchString))
2079 {
2080 finished = true;
2081 success = pattern.second;
2082 }
2083 patternOffset++;
2084 }
2085 }
2086 }
2087
2088 VERIFY_IS_TRUE(finished);
2089
2090 return std::tuple(std::move(process), success, std::move(stdOutRead));
2091 }
2092
2093 static std::tuple<unique_kill_process, wil::unique_handle> BindGuestPort(std::wstring_view BindSpec, bool ExpectSuccess)
2094 {
2095 auto [process, success, read] = BindGuestPortHelper(BindSpec);
2096
2097 VERIFY_ARE_EQUAL(ExpectSuccess, success);
2098
2099 return std::tuple(std::move(process), std::move(read));
2100 }
2101
2102 // Bind port 0 in the guest and return the process handle and the kernel-assigned port.
2103 // Uses socat's -dd output to extract the actual port from the "listening on" line.
2104 static std::tuple<unique_kill_process, uint16_t> BindGuestPortZero(bool Ipv6 = false)
2105 {
2106 auto [stdErrRead, stdErrWrite] = CreateSubprocessPipe(false, true);
2107 const std::wstring protocol = Ipv6 ? L"TCP6-LISTEN:0" : L"TCP4-LISTEN:0";
2108 const std::wstring wslCmd = L"socat -dd " + protocol + L" STDOUT";
2109 auto cmd = LxssGenerateWslCommandLine(wslCmd.data());
2110
2111 auto process = LxsstuStartProcess(cmd.data(), nullptr, nullptr, stdErrWrite.get());
2112 stdErrWrite.reset();
2113
2114 // Parse the assigned port from socat's debug output.
2115 // socat -dd prints a line like: "... listening on AF=2 0.0.0.0:PORT"
2116 std::string output(512, '\0');
2117 DWORD writeOffset = 0;
2118 uint16_t assignedPort = 0;
2119 bool found = false;
2120
2121 while (!found)
2122 {
2123 // Grow the buffer if full to avoid zero-byte reads and infinite loops.
2124 if (writeOffset == output.size())
2125 {
2126 output.resize(output.size() * 2);
2127 }
2128
2129 DWORD bytesRead = 0;
2130 if (!ReadFile(stdErrRead.get(), output.data() + writeOffset, static_cast<DWORD>(output.size() - writeOffset), &bytesRead, nullptr))
2131 {
2132 break;
2133 }
2134
2135 if (bytesRead == 0)
2136 {
2137 break;
2138 }
2139
2140 writeOffset += bytesRead;
2141 LogInfo("output %hs", output.c_str());
2142 std::string_view outputView(output.data(), writeOffset);
2143 auto pos = outputView.find("listening on");
2144 if (pos != std::string_view::npos)
2145 {
2146 // Limit the search to just the "listening on" line to avoid
2147 // matching colons in subsequent debug lines socat may emit.
2148 auto lineEnd = outputView.find('\n', pos);
2149 auto line = outputView.substr(pos, lineEnd != std::string_view::npos ? lineEnd - pos : std::string_view::npos);
2150
2151 // Find the last ':' before the port digits. For IPv6, socat outputs
2152 // "listening on AF=10 :::PORT", so using find() would match the
2153 // first colon in the address instead of the port separator.
2154 auto colonPos = line.rfind(':');
2155 if (colonPos != std::string_view::npos)
2156 {
2157 auto portStr = line.substr(colonPos + 1);
2158 auto end = portStr.find_first_not_of("0123456789");
2159 if (end != std::string_view::npos)
2160 {
2161 portStr = portStr.substr(0, end);
2162 }
2163
2164 if (portStr.empty())
2165 {
2166 continue;
2167 }
2168
2169 assignedPort = static_cast<uint16_t>(std::stoi(std::string(portStr)));
2170 found = true;
2171 }
2172 }
2173 }
2174
2175 VERIFY_IS_TRUE(found);
2176 VERIFY_IS_TRUE(assignedPort > 0);
2177 LogInfo("Port-0 bind resolved to port %u", assignedPort);
2178
2179 return {std::move(process), assignedPort};
2180 }
2181
2182 // Create a TCP listening socket in the guest via listen() WITHOUT ever calling bind() first
2183 // (implicit autobind to an ephemeral port on the wildcard address, e.g. INADDR_ANY:0).
2184 // This exercises the seccomp listen() trap added for the implicit-autobind port tracking fix,
2185 // as opposed to BindGuestPortZero() which exercises the pre-existing explicit bind(0) path.
2186 static std::tuple<unique_kill_process, uint16_t> BindGuestPortViaListenOnly()
2187 {
2188 auto [stdOutRead, stdOutWrite] = CreateSubprocessPipe(false, true);
2189
2190 // Perl one-liner: socket() + listen() with no bind(), print the kernel-assigned
2191 // port via getsockname(), then accept() (blocking) to keep the socket alive.
2192 const std::wstring wslCmd =
2193 L"perl -MSocket -e '"
2194 L"$|=1;"
2195 L"socket(S,AF_INET,SOCK_STREAM,0) or die;"
2196 L"listen(S,5) or die;"
2197 L"my $port=(sockaddr_in(getsockname(S)))[0];"
2198 L"print \"PORT=$port\\n\";"
2199 L"accept(C,S);"
2200 L"'";
2201 auto cmd = LxssGenerateWslCommandLine(wslCmd.data());
2202
2203 auto process = LxsstuStartProcess(cmd.data(), nullptr, stdOutWrite.get(), nullptr);
2204 stdOutWrite.reset();
2205
2206 std::string output(256, '\0');
2207 DWORD writeOffset = 0;
2208 uint16_t assignedPort = 0;
2209 bool found = false;
2210
2211 while (!found)
2212 {
2213 if (writeOffset == output.size())
2214 {
2215 output.resize(output.size() * 2);
2216 }
2217
2218 DWORD bytesRead = 0;
2219 if (!ReadFile(stdOutRead.get(), output.data() + writeOffset, static_cast<DWORD>(output.size() - writeOffset), &bytesRead, nullptr) ||
2220 bytesRead == 0)
2221 {
2222 break;
2223 }
2224
2225 writeOffset += bytesRead;
2226 LogInfo("output %hs", output.c_str());
2227 std::string_view outputView(output.data(), writeOffset);
2228 auto pos = outputView.find("PORT=");
2229 if (pos != std::string_view::npos)
2230 {
2231 auto portStr = outputView.substr(pos + 5);
2232
2233 // Only parse once the line is fully read (terminated by '\n'); otherwise a
2234 // partial read could truncate the port digits (e.g. "123" read as "12").
2235 auto newlinePos = portStr.find('\n');
2236 if (newlinePos != std::string_view::npos)
2237 {
2238 portStr = portStr.substr(0, newlinePos);
2239 if (!portStr.empty())
2240 {
2241 assignedPort = static_cast<uint16_t>(std::stoi(std::string(portStr)));
2242 found = true;
2243 }
2244 }
2245 }
2246 }
2247
2248 VERIFY_IS_TRUE(found);
2249 VERIFY_IS_TRUE(assignedPort > 0);
2250 LogInfo("listen()-only autobind resolved to port %u", assignedPort);
2251
2252 return {std::move(process), assignedPort};
2253 }
2254
2255 // Verifies that a listen() call with no preceding bind() (implicit autobind) is tracked
2256 // by the host port tracker, mirroring VerifyPortZeroBindIsTracked's coverage of the
2257 // pre-existing explicit bind(0) path.
2258 static void VerifyListenWithoutBindIsTracked(bool verifyRelease = true)
2259 {
2260 WslKeepAlive keepAlive;
2261
2262 auto [guestProcess, assignedPort] = BindGuestPortViaListenOnly();
2263
2264 // Port resolution is asynchronous (deferred to a background thread) for the case where
2265 // the socket wasn't already bound. Retry until the host port tracker registers the port,
2266 // blocking the host bind.
2267 VERIFY_NO_THROW(wsl::shared::retry::RetryWithTimeout<void>(
2268 [&assignedPort]() {
2269 wil::unique_socket sock(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2270 THROW_LAST_ERROR_IF(!sock);
2271
2272 SOCKADDR_IN addr{};
2273 addr.sin_family = AF_INET;
2274 addr.sin_port = htons(assignedPort);
2275 THROW_HR_IF(E_FAIL, bind(sock.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) != SOCKET_ERROR);
2276 },
2277 std::chrono::seconds(1),
2278 std::chrono::seconds(30)));
2279
2280 if (!verifyRelease)
2281 {
2282 return;
2283 }
2284
2285 // Kill the guest process so the port tracker releases the port.
2286 guestProcess.reset();
2287
2288 // Retry until the host can bind the port again, confirming it was released.
2289 VERIFY_NO_THROW(wsl::shared::retry::RetryWithTimeout<void>(
2290 [&assignedPort]() {
2291 wil::unique_socket sock(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2292 THROW_LAST_ERROR_IF(!sock);
2293
2294 SOCKADDR_IN addr{};
2295 addr.sin_family = AF_INET;
2296 addr.sin_port = htons(assignedPort);
2297 THROW_HR_IF(E_FAIL, bind(sock.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) == SOCKET_ERROR);
2298 },
2299 std::chrono::seconds(1),
2300 std::chrono::minutes(2)));
2301 }
2302
2303 static void VerifyPortZeroBindIsTracked(bool verifyRelease = true)
2304 {
2305 // Make sure the VM doesn't time out while we wait for async port resolution
2306 WslKeepAlive keepAlive;
2307
2308 // Bind port 0 in the guest - the kernel assigns an ephemeral port.
2309 // The port tracker intercepts the bind() via seccomp and defers lookup
2310 // to a background thread that resolves the actual port via getsockname().
2311 auto [guestProcess, assignedPort] = BindGuestPortZero();
2312
2313 // The port-0 resolution is asynchronous (deferred to a background thread).
2314 // Retry until the host port tracker registers the port, blocking the host bind.
2315 VERIFY_NO_THROW(wsl::shared::retry::RetryWithTimeout<void>(
2316 [&assignedPort]() {
2317 wil::unique_socket sock(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2318 THROW_LAST_ERROR_IF(!sock);
2319
2320 SOCKADDR_IN addr{};
2321 addr.sin_family = AF_INET;
2322 addr.sin_port = htons(assignedPort);
2323 THROW_HR_IF(E_FAIL, bind(sock.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) != SOCKET_ERROR);
2324 },
2325 std::chrono::seconds(1),
2326 std::chrono::seconds(30)));
2327
2328 if (!verifyRelease)
2329 {
2330 return;
2331 }
2332
2333 // Kill the guest process so the port tracker releases the port.
2334 guestProcess.reset();
2335
2336 // Retry until the host can bind the port again, confirming it was released.
2337 VERIFY_NO_THROW(wsl::shared::retry::RetryWithTimeout<void>(
2338 [&assignedPort]() {
2339 wil::unique_socket sock(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2340 THROW_LAST_ERROR_IF(!sock);
2341
2342 SOCKADDR_IN addr{};
2343 addr.sin_family = AF_INET;
2344 addr.sin_port = htons(assignedPort);
2345 THROW_HR_IF(E_FAIL, bind(sock.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) == SOCKET_ERROR);
2346 },
2347 std::chrono::seconds(1),
2348 std::chrono::minutes(2)));
2349 }
2350
2351 static void VerifyPortZeroBindFromThreadIsTracked()
2352 {
2353 auto [stdOutRead, stdOutWrite] = CreateSubprocessPipe(false, true);
2354 // LXT uses one bit per variation; the threaded port-zero server is the sixth server variation.
2355 constexpr unsigned long long c_portZeroThreadVariationMask = 1ull << 5;
2356 const auto commandLine = std::format(L"/data/test/wsl_unit_tests socket -s -v {}", c_portZeroThreadVariationMask);
2357 auto cmd = LxssGenerateWslCommandLine(commandLine.data());
2358 unique_kill_process serverProcess(LxsstuStartProcess(cmd.data(), nullptr, stdOutWrite.get()));
2359 stdOutWrite.reset();
2360
2361 constexpr std::string_view portMarker = "PORT_ZERO_THREAD_LISTENER_PORT=";
2362 std::string output(512, '\0');
2363 DWORD writeOffset = 0;
2364 uint16_t assignedPort = 0;
2365 while (assignedPort == 0)
2366 {
2367 if (writeOffset == output.size())
2368 {
2369 output.resize(output.size() * 2);
2370 }
2371
2372 DWORD bytesRead = 0;
2373 VERIFY_IS_TRUE(ReadFile(
2374 stdOutRead.get(), output.data() + writeOffset, static_cast<DWORD>(output.size() - writeOffset), &bytesRead, nullptr));
2375 VERIFY_ARE_NOT_EQUAL(bytesRead, 0u);
2376 writeOffset += bytesRead;
2377
2378 const std::string_view outputView(output.data(), writeOffset);
2379 const auto markerPosition = outputView.find(portMarker);
2380 if (markerPosition == std::string_view::npos)
2381 {
2382 continue;
2383 }
2384
2385 const auto portBegin = markerPosition + portMarker.size();
2386 const auto portEnd = outputView.find_first_not_of("0123456789", portBegin);
2387 if (portEnd == std::string_view::npos)
2388 {
2389 continue;
2390 }
2391
2392 assignedPort = static_cast<uint16_t>(std::stoi(std::string(outputView.substr(portBegin, portEnd - portBegin))));
2393 }
2394
2395 LogInfo("Threaded guest listener assigned port %u", assignedPort);
2396 const auto connectToGuest = [&]() {
2397 wil::unique_socket clientSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2398 THROW_LAST_ERROR_IF(!clientSocket);
2399
2400 SOCKADDR_IN address{};
2401 address.sin_family = AF_INET;
2402 address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2403 address.sin_port = htons(assignedPort);
2404 THROW_LAST_ERROR_IF(connect(clientSocket.get(), reinterpret_cast<SOCKADDR*>(&address), sizeof(address)) == SOCKET_ERROR);
2405 };
2406
2407 VERIFY_NO_THROW(wsl::shared::retry::RetryWithTimeout<void>(connectToGuest, std::chrono::seconds(1), std::chrono::seconds(30)));
2408 }
2409
2410 static void VerifyPortZeroRebindSucceeds()
2411 {
2412 // Verify that bind(0) -> close -> immediate rebind on the same port succeeds.
2413 // Uses a perl one-liner to perform the entire sequence in a single process,
2414 // matching the semantics of a native C test (no SO_REUSEADDR, same-process rebind).
2415 VERIFY_ARE_EQUAL(
2416 LxsstuLaunchWsl(L"perl -MSocket -e '"
2417 L"socket(S1,AF_INET,SOCK_STREAM,0) or die;"
2418 L"bind(S1,sockaddr_in(0,INADDR_ANY)) or die;"
2419 L"my $port=(sockaddr_in(getsockname(S1)))[0];"
2420 L"close(S1);"
2421 L"socket(S2,AF_INET,SOCK_STREAM,0) or die;"
2422 L"bind(S2,sockaddr_in($port,INADDR_ANY)) or die;"
2423 L"close(S2)"
2424 L"'"),
2425 0L);
2426 }
2427
2428 // Verifies that after a listen socket is closed, the port remains allocated to the guest
2429 // as long as an accepted connection in Linux is still using it.
2430 static void VerifyAcceptedConnectionPortTracking()
2431 {
2432 WslKeepAlive keepAlive;
2433
2434 // Perl server: listen on port 1234, print "listening", accept one connection,
2435 // close the listen socket, print "ready", then wait forever to keep the accepted connection alive.
2436 auto serverCmd = LxssGenerateWslCommandLine(
2437 L"perl -MSocket -e '"
2438 L"$|=1;"
2439 L"socket(S,AF_INET,SOCK_STREAM,0) or die;"
2440 L"bind(S,sockaddr_in(1234,INADDR_ANY)) or die;"
2441 L"listen(S,1) or die;"
2442 L"print \"listening\\n\";"
2443 L"accept(C,S) or die;"
2444 L"close(S);"
2445 L"print \"ready\\n\";"
2446 L"while(1){sleep 1000}"
2447 L"'");
2448
2449 wil::unique_handle serverOutRead;
2450 wil::unique_handle serverOutWrite;
2451 VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&serverOutRead, &serverOutWrite, nullptr, 0));
2452 VERIFY_WIN32_BOOL_SUCCEEDED(SetHandleInformation(serverOutWrite.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
2453
2454 unique_kill_process serverProcess(LxsstuStartProcess(serverCmd.data(), nullptr, serverOutWrite.get()));
2455 serverOutWrite.reset();
2456
2457 // Wait for the server to be listening
2458 std::string output;
2459 VERIFY_IS_TRUE(FindSubstring(serverOutRead, "listening", output));
2460
2461 // Perl client: connect to 127.0.0.1:1234, then wait forever to keep the connection alive.
2462 auto clientCmd = LxssGenerateWslCommandLine(
2463 L"perl -MSocket -e '"
2464 L"socket(S,AF_INET,SOCK_STREAM,0) or die;"
2465 L"connect(S,sockaddr_in(1234,inet_aton(\"127.0.0.1\"))) or die;"
2466 L"while(1){sleep 1000}"
2467 L"'");
2468
2469 unique_kill_process guestClientProcess(LxsstuStartProcess(clientCmd.data()));
2470
2471 // Wait for the server to accept the connection and close the listen socket
2472 VERIFY_IS_TRUE(FindSubstring(serverOutRead, "ready", output));
2473
2474 // We need to wait > 60 seconds so that the port tracker's deallocation logic kicks in for this port.
2475 // See c_bind_timeout_seconds in GnsPortTracker.cpp
2476 std::this_thread::sleep_for(std::chrono::seconds(90));
2477
2478 // Verify the port is still allocated to the guest — host bind should fail
2479 BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
2480
2481 // stop server and client processes
2482 serverProcess.reset();
2483 guestClientProcess.reset();
2484
2485 // Verify the port is eventually released and host is able to bind to it
2486 wsl::shared::retry::RetryWithTimeout<void>(
2487 [&]() {
2488 wil::unique_socket sock(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2489 THROW_LAST_ERROR_IF(!sock);
2490
2491 SOCKADDR_IN addr{};
2492 addr.sin_family = AF_INET;
2493 addr.sin_port = htons(1234);
2494 THROW_HR_IF(E_FAIL, bind(sock.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) == SOCKET_ERROR);
2495 },
2496 std::chrono::seconds(1),
2497 std::chrono::minutes(2));
2498 }
2499
2500 template <typename T>
2501 static void VerifyNotBound(T& Address, int AddressFamily, int Protocol)
2502 {
2503 const wil::unique_socket listenSocket(socket(AddressFamily, (Protocol == IPPROTO_TCP) ? SOCK_STREAM : SOCK_DGRAM, Protocol));
2504 VERIFY_IS_TRUE(!!listenSocket);
2505
2506 const auto timeout = std::chrono::steady_clock::now() + std::chrono::minutes(2);
2507
2508 bool bound = false;
2509 while (!bound && std::chrono::steady_clock::now() < timeout)
2510 {
2511 bound = bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) != SOCKET_ERROR;
2512 std::this_thread::sleep_for(std::chrono::seconds(1));
2513 }
2514
2515 VERIFY_IS_TRUE(bound);
2516 }
2517
2518 static void VerifyNotBoundLoopback(uint16_t port, bool Ipv6)
2519 {
2520 if (Ipv6)
2521 {
2522 SOCKADDR_IN6 Address{};
2523 Address.sin6_family = AF_INET6;
2524 Address.sin6_port = htons(port);
2525 Address.sin6_addr = IN6ADDR_LOOPBACK_INIT;
2526
2527 VerifyNotBound(Address, Address.sin6_family, IPPROTO_TCP);
2528 }
2529 else
2530 {
2531 SOCKADDR_IN Address{};
2532 Address.sin_family = AF_INET;
2533 Address.sin_port = htons(port);
2534 Address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2535
2536 VerifyNotBound(Address, Address.sin_family, IPPROTO_TCP);
2537 }
2538 }
2539
2540 struct
2541 {
2542 wchar_t const* const SocatServer = {};
2543 bool const Ipv6 = false;
2544 bool const expectRelay = true;
2545 } LoopbackBindTests[5] = {
2546 {
2547 .SocatServer = L"TCP4-LISTEN:1234,bind=127.0.0.1",
2548 },
2549 {
2550 .SocatServer = L"TCP4-LISTEN:1234,bind=127.0.0.2",
2551 .expectRelay = false,
2552 },
2553 {
2554 .SocatServer = L"TCP4-LISTEN:1234,bind=0.0.0.0",
2555 },
2556 {
2557 .SocatServer = L"TCP6-LISTEN:1234,bind=::1",
2558 .Ipv6 = true,
2559 },
2560 {
2561 .SocatServer = L"TCP6-LISTEN:1234,bind=::",
2562 .Ipv6 = true,
2563 },
2564 };
2565
2566 void NatGuestPortIsReleased()
2567 {
2568 constexpr uint16_t port = 1234;
2569 for (auto const& test : LoopbackBindTests)
2570 {
2571 {
2572 auto guestProcess = BindGuestPort(test.SocatServer, true);
2573 std::this_thread::sleep_for(std::chrono::seconds(3));
2574 BindHostPort(port, SOCK_STREAM, IPPROTO_TCP, !test.expectRelay, test.Ipv6, true);
2575 }
2576
2577 VerifyNotBoundLoopback(port, test.Ipv6);
2578 }
2579 }
2580
2581 void NatHostPortCantBeBoundByGuest()
2582 {
2583 constexpr uint16_t port = 1234;
2584 for (auto const& test : LoopbackBindTests)
2585 {
2586 {
2587 auto hostPort = BindHostPort(port, SOCK_STREAM, IPPROTO_TCP, true, test.Ipv6, true);
2588 BindGuestPort(test.SocatServer, !test.expectRelay);
2589 }
2590
2591 VerifyNotBoundLoopback(port, test.Ipv6);
2592 }
2593 }
2594
2595 static void NatReusePortOnGuest()
2596 {
2597 constexpr uint16_t port = 1234;
2598 {
2599 auto [guestLocal, write] = BindGuestPort(L"TCP4-LISTEN:1234,bind=127.0.0.1,reuseport", true);
2600 BindHostPort(port, SOCK_STREAM, IPPROTO_TCP, false, false, true);
2601 auto guestWild = BindGuestPort(L"TCP4-LISTEN:1234,bind=0.0.0.0,reuseport", true);
2602 BindHostPort(port, SOCK_STREAM, IPPROTO_TCP, false, false, true);
2603 guestLocal.reset();
2604 BindHostPort(port, SOCK_STREAM, IPPROTO_TCP, false, false, true);
2605 }
2606
2607 VerifyNotBoundLoopback(port, false);
2608 }
2609
2610 static void ValidateLocalhostRelayTraffic(ADDRESS_FAMILY addressFamily)
2611 {
2612 THROW_HR_IF(E_INVALIDARG, addressFamily != AF_INET && addressFamily != AF_INET6);
2613
2614 // Bind a port in the guest.
2615 auto [guestProcess, read] =
2616 BindGuestPort(addressFamily == AF_INET6 ? L"TCP6-LISTEN:1234,bind=::1" : L"TCP4-LISTEN:1234,bind=127.0.0.1", true);
2617
2618 // Connect to the port via the localhost relay
2619 wil::unique_socket hostSocket;
2620 SOCKADDR_INET addr{};
2621 addr.si_family = addressFamily;
2622 INETADDR_SETLOOPBACK((PSOCKADDR)&addr);
2623 SS_PORT(&addr) = htons(1234);
2624
2625 auto pred = [&]() {
2626 hostSocket.reset(socket(addressFamily, SOCK_STREAM, IPPROTO_TCP));
2627 THROW_HR_IF(E_ABORT, !hostSocket);
2628 THROW_HR_IF(E_FAIL, connect(hostSocket.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) == SOCKET_ERROR);
2629 };
2630
2631 try
2632 {
2633 wsl::shared::retry::RetryWithTimeout<void>(pred, std::chrono::seconds(1), std::chrono::minutes(1));
2634 }
2635 catch (...)
2636 {
2637 LogError("Timed out trying to connect to relay, 0x%x", wil::ResultFromCaughtException());
2638 VERIFY_FAIL();
2639 }
2640
2641 // Send data from host to guest.
2642 constexpr auto buffer = "test-relay-buffer";
2643 VERIFY_ARE_EQUAL(send(hostSocket.get(), buffer, static_cast<int>(strlen(buffer)), 0), strlen(buffer));
2644
2645 {
2646 // Validate that the guest received the correct data.
2647 std::string content(strlen(buffer), '\0');
2648
2649 DWORD totalRead{};
2650 while (totalRead < content.size())
2651 {
2652 DWORD bytesRead{};
2653 VERIFY_IS_TRUE(ReadFile(read.get(), content.data() + totalRead, static_cast<DWORD>(content.size()) - totalRead, &bytesRead, nullptr));
2654 LogInfo("Read %lu bytes", bytesRead);
2655
2656 totalRead += bytesRead;
2657 }
2658 VERIFY_ARE_EQUAL(content, buffer);
2659 }
2660 }
2661
2662 WSL2_TEST_METHOD(NatLocalhostRelay)
2663 {
2664 WslKeepAlive keepAlive;
2665
2666 ValidateLocalhostRelayTraffic(AF_INET);
2667 ValidateLocalhostRelayTraffic(AF_INET6);
2668 }
2669
2670 WSL2_TEST_METHOD(NatLocalhostRelayNoIpv6)
2671 {
2672 WslConfigChange config(LxssGenerateTestConfig({.kernelCommandLine = L"ipv6.disable=1"}));
2673 WslKeepAlive keepAlive;
2674
2675 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -f /proc/net/tcp6"), 1L);
2676 ValidateLocalhostRelayTraffic(AF_INET);
2677 }
2678
2679 static void TestNonRootNamespaceEphemeralBind()
2680 {
2681 // Get the forwarding state.
2682 auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
2683 std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
2684
2685 // Ensure the ephemeral port range configured in the non-root networking namespace does not
2686 // overlap with the ephemeral port range in the root networking namespace (use the 300 ports
2687 // preceding the root networking namespace ephemeral port range).
2688 auto [start, _2] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f1", 0);
2689 start.pop_back();
2690 int ephemeralRangeStart = std::stoi(start);
2691
2692 int ephemeralRangeEnd = ephemeralRangeStart - 1;
2693 ephemeralRangeStart = ephemeralRangeEnd - 299;
2694 VERIFY_IS_GREATER_THAN(ephemeralRangeStart, 1024);
2695 VERIFY_IS_LESS_THAN_OR_EQUAL(ephemeralRangeEnd, UINT16_MAX);
2696 const std::wstring ephemeralRangeCommand =
2697 std::format(L"ip netns exec testns sysctl -w net.ipv4.ip_local_port_range=\"{} {}\"", ephemeralRangeStart, ephemeralRangeEnd);
2698
2699 // Clean up the below configurations.
2700 auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
2701 LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
2702 LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
2703 LxsstuLaunchWsl(L"ip link delete veth-test-br");
2704 LxsstuLaunchWsl(L"ip link delete testbridge");
2705 LxsstuLaunchWsl(L"ip netns delete testns");
2706 });
2707
2708 // Set up a networking namespace and provide it external network access via a bridge, veth
2709 // pair, SRCNAT iptables rule and forwarding.
2710 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
2711 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(ephemeralRangeCommand.c_str()), 0);
2712 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
2713 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
2714 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
2715 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
2716 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
2717 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
2718 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
2719 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
2720 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
2721 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
2722 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
2723 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
2724 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
2725 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
2726
2727 // Verify we have connectivity from the networking namespace when using ephemeral port selection.
2728 auto [output, warnings] =
2729 LxsstuLaunchWslAndCaptureOutput(L"ip netns exec testns socat -dd tcp-connect:bing.com:80 create:/tmp/nonexistent", 1);
2730 LogInfo("output %s", output.c_str());
2731 LogInfo("warnings %s", warnings.c_str());
2732 VERIFY_ARE_NOT_EQUAL(warnings.find(L"starting data transfer loop"), std::string::npos);
2733 }
2734
2735 WSL2_TEST_METHOD(NatNonRootNamespaceEphemeralBind)
2736 {
2737 // Because the test creates a new network namespace, the resolv.conf from the root network namespace
2738 // is copied in the resolv.conf of the new network namespace. The DNS tunneling listener running in the root namespace
2739 // needs to be accessible from the new namespace, so it can't use a 127* IP.
2740 WslConfigChange config(LxssGenerateTestConfig({
2741 .guiApplications = true,
2742 .dnsTunneling = true,
2743 .dnsTunnelingIpAddress = L"10.255.255.254",
2744 }));
2745
2746 // Configure the root namespace ephemeral port range so we can guarantee a valid,
2747 // non-overlapping ephemeral port range in the non-root namespace using the very simple port
2748 // range selection logic in TestNonRootNamespaceEphemeralBind.
2749 auto [originalRange, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range", 0);
2750 std::wstring restoreEphemeralPortRangeCommand =
2751 std::format(L"sysctl -w net.ipv4.ip_local_port_range=\"{}\"", originalRange.c_str());
2752 auto revertEphemeralPortRange = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreEphemeralPortRangeCommand] {
2753 LxsstuLaunchWsl(restoreEphemeralPortRangeCommand.c_str());
2754 });
2755
2756 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_local_port_range=\"60400 60700\""), 0);
2757
2758 TestNonRootNamespaceEphemeralBind();
2759 }
2760
2761 enum class FirewallObjects
2762 {
2763 Required,
2764 NotRequired
2765 };
2766
2767 static void ValidateInitialFirewallState(FirewallObjects expectHyperVFirewallObjects)
2768 {
2769 // Verify that we have an initially working connection.
2770 // This also ensures that WSL is started to allow for
2771 // validating the initial Hyper-V port state
2772 GuestClient(L"tcp-connect:bing.com:80");
2773
2774 if (expectHyperVFirewallObjects == FirewallObjects::Required)
2775 {
2776 // Query for Hyper-V objects. At least one Hyper-V port is expected
2777 auto [out, err] = LxsstuLaunchPowershellAndCaptureOutput(L"Get-NetFirewallHyperVPort");
2778 LogInfo("out:[%ls] err:[%ls]", out.c_str(), err.c_str());
2779 VERIFY_IS_TRUE(!out.empty());
2780 }
2781 }
2782
2783 static auto AddFirewallRule(const FirewallRule& rule)
2784 {
2785 try
2786 {
2787 std::wstring cmdPrefix;
2788 if (rule.Type == FirewallType::HyperV)
2789 {
2790 cmdPrefix = L"New-NetFirewallHyperVRule -VmCreatorId " + rule.VmCreatorId + L" -RemotePorts " + rule.RemotePorts;
2791 }
2792 else
2793 {
2794 cmdPrefix = L"New-NetFirewallRule -Protocol TCP -RemotePort " + rule.RemotePorts;
2795 }
2796
2797 auto [out, _] = LxsstuLaunchPowershellAndCaptureOutput(
2798 cmdPrefix + L" -Name " + rule.Name + L" -DisplayName " + rule.Name + L" -Action " + rule.Action +
2799 L" -Direction Outbound");
2800
2801 LogInfo("AddRule output:\r\n%ls", FixLineEndings(out).c_str());
2802
2803 // output what, if any, Hyper-V Firewall rules were created in response to the above
2804 auto [query_output, __] = LxsstuLaunchPowershellAndCaptureOutput(L"Get-NetFirewallHyperVRule -Name " + rule.Name);
2805 LogInfo("Get-NetFirewallHyperVRule output:\r\n%ls", FixLineEndings(query_output).c_str());
2806 }
2807 CATCH_LOG()
2808
2809 return wil::scope_exit([rule]() {
2810 try
2811 {
2812 LogInfo("Removing the test rule %ls\n", rule.Name.c_str());
2813 std::wstring cmdPrefix;
2814 if (rule.Type == FirewallType::HyperV)
2815 {
2816 cmdPrefix = L"Remove-NetFirewallHyperVRule";
2817 }
2818 else
2819 {
2820 cmdPrefix = L"Remove-NetFirewallRule";
2821 }
2822 LxsstuLaunchPowershellAndCaptureOutput(cmdPrefix + L" -Name " + rule.Name);
2823 }
2824 CATCH_LOG()
2825 });
2826 }
2827
2828 enum class FirewallTestConnectivity
2829 {
2830 Allowed,
2831 Blocked
2832 };
2833
2834 static auto AddFirewallRuleAndValidateTraffic(const FirewallRule& rule, FirewallTestConnectivity expectedConnectivityAfterRule)
2835 {
2836 LogInfo(
2837 "Validating ruleType=[%ls] name=[%ls] and expectedConnectivity=[%ls]",
2838 (rule.Type == FirewallType::Host) ? L"Host" : L"HyperV",
2839 rule.Name.c_str(),
2840 expectedConnectivityAfterRule == FirewallTestConnectivity::Allowed ? L"Allowed" : L"Blocked");
2841
2842 // Add rule and verify the connection is allowed/blocked as expected
2843 auto firewallRuleCleanup = AddFirewallRule(rule);
2844
2845 GuestClient(L"tcp-connect:bing.com:80,connect-timeout=5", expectedConnectivityAfterRule);
2846 return firewallRuleCleanup;
2847 }
2848
2849 static auto ConfigureFirewallEnabled(FirewallType firewallType, bool settingValue, std::wstring vmCreatorId = L"")
2850 {
2851 LogInfo(
2852 "Configure FirewallEnabled for Type=[%ls] enabled=[%ls]",
2853 (firewallType == FirewallType::Host) ? L"Host" : L"HyperV",
2854 settingValue ? L"True" : L"False");
2855 try
2856 {
2857 std::wstring prefix;
2858 if (firewallType == FirewallType::HyperV)
2859 {
2860 prefix = L"Set-NetFirewallHyperVProfile -VmCreatorId " + vmCreatorId;
2861 }
2862 else
2863 {
2864 prefix = L"Set-NetFirewallProfile";
2865 }
2866 LxsstuLaunchPowershellAndCaptureOutput(prefix + L" -Profile Public -Enabled " + (settingValue ? L"True" : L"False"));
2867 LxsstuLaunchPowershellAndCaptureOutput(prefix + L" -Profile Private -Enabled " + (settingValue ? L"True" : L"False"));
2868 LxsstuLaunchPowershellAndCaptureOutput(prefix + L" -Profile Domain -Enabled " + (settingValue ? L"True" : L"False"));
2869 }
2870 CATCH_LOG()
2871
2872 return wil::scope_exit([vmCreatorId, firewallType]() {
2873 try
2874 {
2875 std::wstring prefix;
2876 if (firewallType == FirewallType::HyperV)
2877 {
2878 prefix = L"Set-NetFirewallHyperVProfile -VmCreatorId " + vmCreatorId;
2879 }
2880 else
2881 {
2882 prefix = L"Set-NetFirewallProfile";
2883 }
2884
2885 LxsstuLaunchPowershellAndCaptureOutput(prefix + L" -Profile Public -Enabled NotConfigured");
2886 LxsstuLaunchPowershellAndCaptureOutput(prefix + L" -Profile Private -Enabled NotConfigured");
2887 LxsstuLaunchPowershellAndCaptureOutput(prefix + L" -Profile Domain -Enabled NotConfigured");
2888 }
2889 CATCH_LOG()
2890 });
2891 }
2892
2893 static auto ConfigureHyperVFirewallLoopbackEnabled(bool settingValue, std::wstring vmCreatorId)
2894 {
2895 LogInfo("Configuring LoopbackEnabled=[%d]", settingValue);
2896 try
2897 {
2898 LxsstuLaunchPowershellAndCaptureOutput(
2899 L"Set-NetFirewallHyperVVMSetting -VmCreatorId " + vmCreatorId + L" -LoopbackEnabled " + (settingValue ? L"True" : L"False"));
2900 }
2901 CATCH_LOG()
2902
2903 return wil::scope_exit([vmCreatorId]() {
2904 try
2905 {
2906 LxsstuLaunchPowershellAndCaptureOutput(
2907 L"Set-NetFirewallHyperVVMSetting -VmCreatorId " + vmCreatorId + L" -LoopbackEnabled NotConfigured");
2908 }
2909 CATCH_LOG()
2910 });
2911 }
2912
2913 static void FirewallRuleBlockedTests(FirewallTestConnectivity expectedConnectivity)
2914 {
2915 // Adding a block rule should result in traffic being blocked
2916 FirewallRule blockRule = {FirewallType::Host, L"WSLTestBlockRule", c_firewallTrafficTestPort, c_firewallRuleActionBlock};
2917 AddFirewallRuleAndValidateTraffic(blockRule, expectedConnectivity);
2918
2919 // Adding both an allow and block rule should result in traffic being blocked
2920 FirewallRule allowRule = {FirewallType::Host, L"WSLTestAllowRule", c_firewallTrafficTestPort, c_firewallRuleActionAllow};
2921 auto allowRuleCleanup = AddFirewallRuleAndValidateTraffic(allowRule, FirewallTestConnectivity::Allowed);
2922 AddFirewallRuleAndValidateTraffic(blockRule, expectedConnectivity);
2923 allowRuleCleanup.reset();
2924
2925 // Adding a block rule should result in traffic being blocked
2926 FirewallRule hyperVBlockRule = {
2927 FirewallType::HyperV, L"WSLTestBlockRuleHyperV", c_firewallTrafficTestPort, c_firewallRuleActionBlock, c_wslVmCreatorId};
2928 AddFirewallRuleAndValidateTraffic(hyperVBlockRule, expectedConnectivity);
2929
2930 // Adding both an allow and block rule should result in traffic being blocked
2931 FirewallRule hyperVAllowRule = {
2932 FirewallType::HyperV, L"WSLTestAllowRuleHyperV", c_firewallTrafficTestPort, c_firewallRuleActionAllow, c_wslVmCreatorId};
2933 auto hyperVAllowRuleCleanup = AddFirewallRuleAndValidateTraffic(hyperVAllowRule, FirewallTestConnectivity::Allowed);
2934 AddFirewallRuleAndValidateTraffic(hyperVBlockRule, expectedConnectivity);
2935 hyperVAllowRuleCleanup.reset();
2936
2937 // Adding a rule with vm creator 'any' should result in traffic being blocked
2938 FirewallRule anyHyperVBlockRule = {
2939 FirewallType::HyperV, L"WSLTestBlockRuleHyperVAny", c_firewallTrafficTestPort, c_firewallRuleActionBlock, c_wslVmCreatorId};
2940 AddFirewallRuleAndValidateTraffic(hyperVBlockRule, expectedConnectivity);
2941 }
2942
2943 WSL2_TEST_METHOD(NatFirewallRulesExpectedBlock)
2944 {
2945 HYPERV_FIREWALL_TEST_ONLY();
2946 WslConfigChange config(LxssGenerateTestConfig({.firewall = true}));
2947
2948 ValidateInitialFirewallState(FirewallObjects::Required);
2949 FirewallRuleBlockedTests(FirewallTestConnectivity::Blocked);
2950 }
2951
2952 WSL2_TEST_METHOD(NatFirewallRulesExpectedBlockFirewallDisabled)
2953 {
2954 HYPERV_FIREWALL_TEST_ONLY();
2955 SKIP_TEST_UNSTABLE();
2956
2957 WslConfigChange config(LxssGenerateTestConfig({.firewall = false}));
2958
2959 ValidateInitialFirewallState(FirewallObjects::NotRequired);
2960 FirewallRuleBlockedTests(FirewallTestConnectivity::Allowed);
2961 }
2962
2963 WSL2_TEST_METHOD(NatFirewallRulesExpectedBlockFirewallDisabledByPolicy)
2964 {
2965 HYPERV_FIREWALL_TEST_ONLY();
2966
2967 RegistryKeyChange<DWORD> change(
2968 HKEY_LOCAL_MACHINE, wsl::windows::policies::c_registryKey, wsl::windows::policies::c_allowCustomFirewallUserSetting, 0);
2969
2970 // the user tries to disable Hyper-V FW in the config file, but the admin disabled user control
2971 WslConfigChange config(LxssGenerateTestConfig({.firewall = false}));
2972
2973 ValidateInitialFirewallState(FirewallObjects::NotRequired);
2974 FirewallRuleBlockedTests(FirewallTestConnectivity::Blocked);
2975 }
2976
2977 static void FirewallRuleAllowedTests(FirewallTestConnectivity expectedConnectivity)
2978 {
2979 // A host rule with different IP address should not affect traffic
2980 FirewallRule differentIPRule = {FirewallType::Host, L"WSLTestDifferentIPRule", c_firewallTestOtherPort, c_firewallRuleActionBlock};
2981 AddFirewallRuleAndValidateTraffic(differentIPRule, expectedConnectivity);
2982
2983 // A host rule with action allow should not affect traffic
2984 FirewallRule allowRule = {FirewallType::Host, L"WSLTestAllowRule", c_firewallTrafficTestPort, c_firewallRuleActionAllow};
2985 AddFirewallRuleAndValidateTraffic(allowRule, expectedConnectivity);
2986
2987 // A hyperv- rule with a different VM creator ID should not affect this traffic
2988 FirewallRule differentVmCreatorRule = {
2989 FirewallType::HyperV, L"WSLTestDifferentVMCreatorIdRule", c_firewallTrafficTestPort, c_firewallRuleActionBlock, c_wsaVmCreatorId};
2990 AddFirewallRuleAndValidateTraffic(differentVmCreatorRule, expectedConnectivity);
2991
2992 // A hyper-v rule with a different IP address should not affect this traffic
2993 FirewallRule differentIPHyperVRule = {
2994 FirewallType::HyperV, L"WSLTestDifferentIPRuleHyperV", c_firewallTestOtherPort, c_firewallRuleActionBlock, c_wslVmCreatorId};
2995 AddFirewallRuleAndValidateTraffic(differentIPHyperVRule, expectedConnectivity);
2996
2997 // A hyper-v rule with action allow should not affect traffic
2998 FirewallRule allowHyperVRule = {
2999 FirewallType::HyperV, L"WSLTestAllowRuleHyperV", c_firewallTrafficTestPort, c_firewallRuleActionAllow, c_wslVmCreatorId};
3000 AddFirewallRuleAndValidateTraffic(allowHyperVRule, expectedConnectivity);
3001 }
3002
3003 WSL2_TEST_METHOD(NatFirewallRulesExpectedAllow)
3004 {
3005 HYPERV_FIREWALL_TEST_ONLY();
3006 WslConfigChange config(LxssGenerateTestConfig({.firewall = true}));
3007
3008 ValidateInitialFirewallState(FirewallObjects::Required);
3009 FirewallRuleAllowedTests(FirewallTestConnectivity::Allowed);
3010 }
3011
3012 WSL2_TEST_METHOD(NatFirewallRulesExpectedAllowFirewallDisabled)
3013 {
3014 HYPERV_FIREWALL_TEST_ONLY();
3015 SKIP_TEST_UNSTABLE();
3016
3017 WslConfigChange config(LxssGenerateTestConfig({.firewall = false}));
3018
3019 ValidateInitialFirewallState(FirewallObjects::NotRequired);
3020 FirewallRuleAllowedTests(FirewallTestConnectivity::Allowed);
3021 }
3022
3023 static void FirewallSettingEnabledTests(bool isHyperVFirewallEnabled)
3024 {
3025 // Configure Firewall disabled
3026 auto hostDisabledCleanup = ConfigureFirewallEnabled(FirewallType::Host, false);
3027
3028 // Add host block rule, which is expected to be enforced
3029 FirewallRule blockRule = {FirewallType::Host, L"WSLTestBlockRule", c_firewallTrafficTestPort, c_firewallRuleActionBlock, c_wslVmCreatorId};
3030 AddFirewallRuleAndValidateTraffic(blockRule, FirewallTestConnectivity::Allowed);
3031 blockRule.Type = FirewallType::HyperV;
3032 // Add hyper-v block rule, which is expected to be enforced
3033 AddFirewallRuleAndValidateTraffic(blockRule, FirewallTestConnectivity::Allowed);
3034 hostDisabledCleanup.reset();
3035
3036 // Configure Hyper-V firewall disabled
3037 auto hyperVDisabledCleanup = ConfigureFirewallEnabled(FirewallType::HyperV, false, c_wslVmCreatorId);
3038 // Add host block rule, which is expected to be enforced
3039 blockRule.Type = FirewallType::Host;
3040 AddFirewallRuleAndValidateTraffic(blockRule, FirewallTestConnectivity::Allowed);
3041 // Add hyper-v block rule, which is expected to be enforced
3042 blockRule.Type = FirewallType::HyperV;
3043 AddFirewallRuleAndValidateTraffic(blockRule, FirewallTestConnectivity::Allowed);
3044 hyperVDisabledCleanup.reset();
3045
3046 // host rules are propagated only if Hyper-V Firewall is enabled
3047 // Configure conflicting policy for host and hyper-v (hyper-v policy takes precedence)
3048 auto conflictingHostEnabledCleanup = ConfigureFirewallEnabled(FirewallType::Host, true);
3049 // Add host block rule, which is expected to be enforced
3050 blockRule.Type = FirewallType::Host;
3051 AddFirewallRuleAndValidateTraffic(
3052 blockRule, isHyperVFirewallEnabled ? FirewallTestConnectivity::Blocked : FirewallTestConnectivity::Allowed);
3053 // Add hyper-v block rule, which is expected to be enforced
3054 blockRule.Type = FirewallType::HyperV;
3055 AddFirewallRuleAndValidateTraffic(
3056 blockRule, isHyperVFirewallEnabled ? FirewallTestConnectivity::Blocked : FirewallTestConnectivity::Allowed);
3057
3058 // Configure hyper-v disabled
3059 auto conflictingHyperVDisabledCleanup = ConfigureFirewallEnabled(FirewallType::HyperV, false, c_wslVmCreatorId);
3060 // Add host block rule, which is expected to be NOT enforced (firewall is disabled)
3061 blockRule.Type = FirewallType::Host;
3062 AddFirewallRuleAndValidateTraffic(blockRule, FirewallTestConnectivity::Allowed);
3063 // Add hyper-v block rule, which is expected to be NOT enforced (firewall is disabled)
3064 blockRule.Type = FirewallType::HyperV;
3065 AddFirewallRuleAndValidateTraffic(blockRule, FirewallTestConnectivity::Allowed);
3066 conflictingHostEnabledCleanup.reset();
3067 conflictingHyperVDisabledCleanup.reset();
3068
3069 // Configure conflicting policy for host and hyper-v (hyper-v policy takes precedence)
3070 auto conflictingHyperVEnabledCleanup = ConfigureFirewallEnabled(FirewallType::HyperV, true, c_wslVmCreatorId);
3071 // Add host block rule, which is expected to be enforced
3072 blockRule.Type = FirewallType::Host;
3073 AddFirewallRuleAndValidateTraffic(
3074 blockRule, isHyperVFirewallEnabled ? FirewallTestConnectivity::Blocked : FirewallTestConnectivity::Allowed);
3075 // Add hyper-v block rule, which is expected to be enforced
3076 blockRule.Type = FirewallType::HyperV;
3077 AddFirewallRuleAndValidateTraffic(
3078 blockRule, isHyperVFirewallEnabled ? FirewallTestConnectivity::Blocked : FirewallTestConnectivity::Allowed);
3079 // Configure host firewall disabled. Hyper-V firewall is still expected to be enforced, but host firewall rules will not be
3080 auto conflictingHostDisabledCleanup = ConfigureFirewallEnabled(FirewallType::Host, false);
3081 // Add host block rule, which is NOT expected to be enforced (host firewall disabled)
3082 blockRule.Type = FirewallType::Host;
3083 AddFirewallRuleAndValidateTraffic(blockRule, FirewallTestConnectivity::Allowed);
3084 // Add hyper-v block rule, which is expected to be enforced (hyper-v firewall still enabled)
3085 blockRule.Type = FirewallType::HyperV;
3086 AddFirewallRuleAndValidateTraffic(
3087 blockRule, isHyperVFirewallEnabled ? FirewallTestConnectivity::Blocked : FirewallTestConnectivity::Allowed);
3088 }
3089
3090 WSL2_TEST_METHOD(NatFirewallRulesEnabledSetting)
3091 {
3092 HYPERV_FIREWALL_TEST_ONLY();
3093 WslConfigChange config(LxssGenerateTestConfig({.firewall = true}));
3094
3095 ValidateInitialFirewallState(FirewallObjects::Required);
3096 FirewallSettingEnabledTests(true);
3097 }
3098
3099 WSL2_TEST_METHOD(NatFirewallRulesEnabledSettingFirewallDisabled)
3100 {
3101 HYPERV_FIREWALL_TEST_ONLY();
3102 SKIP_TEST_UNSTABLE();
3103 WslConfigChange config(LxssGenerateTestConfig({.firewall = false}));
3104
3105 ValidateInitialFirewallState(FirewallObjects::NotRequired);
3106 FirewallSettingEnabledTests(false);
3107 }
3108
3109 /* Network Tests Helper Methods */
3110
3111 static GUID QueryAdapterId()
3112 {
3113 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(
3114 L"readlink /sys/class/net/eth0 | grep -o -E '[[:xdigit:]]{8}(-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}'", 0);
3115 out.pop_back();
3116
3117 const auto guid = wsl::shared::string::ToGuid(out);
3118 VERIFY_IS_TRUE(guid.has_value());
3119
3120 return guid.value();
3121 }
3122
3123 static void RunGns(const std::string& input, const std::optional<GUID>& adapter = {}, const std::optional<LX_MESSAGE_TYPE>& messageType = {}, int expectedErrorCode = 0)
3124 {
3125 constexpr auto InheritOnReadHandle = true;
3126 constexpr auto DoNotEnableInheritOnWriteHandle = false;
3127 SECURITY_ATTRIBUTES attributes = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
3128 auto [read, write] =
3129 CreateSubprocessPipe(InheritOnReadHandle, DoNotEnableInheritOnWriteHandle, static_cast<DWORD>(input.size()), &attributes);
3130
3131 THROW_IF_WIN32_BOOL_FALSE(WriteFile(write.get(), input.data(), static_cast<DWORD>(input.size()), nullptr, nullptr));
3132 write.reset();
3133
3134 LogInfo("GNS Input: '%S'", input.c_str());
3135 const auto adapterArg =
3136 adapter.has_value() ? L"--adapter " + wsl::shared::string::GuidToString<wchar_t>(adapter.value()) + std::wstring(L" ") : L"";
3137 const auto messageTypeArg =
3138 messageType.has_value() ? L"--msg_type " + std::to_wstring(static_cast<int>(messageType.value())) + std::wstring(L" ") : L"";
3139 LxsstuLaunchWslAndCaptureOutput(L"/gns " + adapterArg + messageTypeArg, expectedErrorCode, read.get());
3140 }
3141
3142 template <typename T>
3143 void RunGns(T& input, ModifyRequestType action, GuestEndpointResourceType type)
3144 {
3145 ModifyGuestEndpointSettingRequest<T> request;
3146 request.RequestType = action;
3147 request.ResourceType = type;
3148 request.Settings = input;
3149
3150 RunGns(wsl::shared::ToJson(request), AdapterId, LxGnsMessageNotification);
3151 }
3152
3153 template <typename T>
3154 void RunGns(T& input, const LX_MESSAGE_TYPE messageType)
3155 {
3156 RunGns(wsl::shared::ToJson(input), AdapterId, messageType);
3157 }
3158
3159 template <typename T>
3160 void SendDeviceSettingsRequest(std::wstring targetDevice, T& input, ModifyRequestType action, GuestEndpointResourceType type)
3161 {
3162 wsl::shared::hns::ModifyGuestEndpointSettingRequest<T> request;
3163 request.targetDeviceName = targetDevice;
3164 request.RequestType = action;
3165 request.ResourceType = type;
3166 request.Settings = input;
3167
3168 RunGns(request, LxGnsMessageDeviceSettingRequest);
3169 }
3170
3171 // Convert Unix line endings (\n) to Windows line endings (\r\n) for proper console display
3172 static std::wstring FixLineEndings(const std::wstring& input)
3173 {
3174 std::wstring output;
3175 for (size_t i = 0; i < input.length(); ++i)
3176 {
3177 if (input[i] == L'\n')
3178 {
3179 output += L"\r\n";
3180 }
3181 else if (input[i] != L'\r')
3182 {
3183 output += input[i];
3184 }
3185 }
3186
3187 return output;
3188 }
3189
3190 static RoutingTableState GetRoutingTableState(std::wstring& out, std::wregex& defaultRoutePattern, std::wregex& routePattern)
3191 {
3192 RoutingTableState state;
3193 std::wsmatch match;
3194
3195 std::wistringstream input(out);
3196 std::wstring line;
3197 while (std::getline(input, line) && !line.empty())
3198 {
3199 if (std::regex_search(line, match, defaultRoutePattern) && match.size() >= 3)
3200 {
3201 if (state.DefaultRoute.has_value())
3202 {
3203 continue;
3204 }
3205
3206 state.DefaultRoute = {{match.str(1), match.str(2), {}, match.size() > 4 && match[4].matched ? std::stoi(match.str(4)) : 0}};
3207 }
3208 else if (std::regex_search(line, match, routePattern) && match.size() >= 4)
3209 {
3210 state.Routes.emplace_back(Route{
3211 match.str(2), match.str(3), {match.str(1)}, match.size() > 5 && match[5].matched ? std::stoi(match.str(5)) : 0});
3212 }
3213 }
3214
3215 return state;
3216 }
3217
3218 static RoutingTableState GetIpv4RoutingTableState()
3219 {
3220 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip route show");
3221 LogInfo("Ip route output:\r\n%ls", FixLineEndings(out).c_str());
3222
3223 std::wregex defaultRoutePattern(L"default via ([0-9,.]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
3224 std::wregex routePattern(L"([0-9,.,/]+) via ([0-9,.]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
3225
3226 return GetRoutingTableState(out, defaultRoutePattern, routePattern);
3227 }
3228
3229 static void WaitForIpv6DefaultRoute()
3230 {
3231 const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(30);
3232 while (std::chrono::steady_clock::now() < timeout)
3233 {
3234 auto state = GetIpv6RoutingTableState();
3235 if (state.DefaultRoute.has_value())
3236 {
3237 return;
3238 }
3239
3240 LogInfo("Waiting for IPv6 default route...");
3241 std::this_thread::sleep_for(std::chrono::seconds(1));
3242 }
3243
3244 VERIFY_FAIL(L"Timed out waiting for IPv6 default route");
3245 }
3246
3247 static RoutingTableState GetIpv6RoutingTableState()
3248 {
3249 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip -6 route show");
3250 LogInfo("Ip -6 route output:\r\n%ls", FixLineEndings(out).c_str());
3251
3252 RoutingTableState state;
3253 std::wregex defaultRoutePattern(L"default via ([a-f,A-F,0-9,:]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
3254 std::wregex routePattern(L"([a-f,A-F,0-9,:,/]+) via ([a-f,A-F,0-9,:]+) dev ([a-zA-Z0-9]*) *(metric ([0-9]+))?");
3255
3256 return GetRoutingTableState(out, defaultRoutePattern, routePattern);
3257 }
3258
3259 static InterfaceState GetInterfaceState(const std::wstring& name, const std::wstring& expectedWarnings = L"")
3260 {
3261 // Sample output from "ip addr show":
3262 // 4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
3263 // link/ether 00:12:34:56:78:9A brd ff:ff:ff:ff:ff:ff
3264 // inet 172.17.123.249/20 brd 172.17.127.255 scope global eth0
3265 // valid_lft forever preferred_lft forever
3266 // inet6 2001::1:2:3:4/64 scope global
3267 // valid_lft forever preferred_lft 0sec
3268 auto [out, warnings] = LxsstuLaunchWslAndCaptureOutput(L"ip addr show " + name);
3269 LogInfo("ip addr show output:\r\n%ls", FixLineEndings(out).c_str());
3270
3271 if (expectedWarnings.empty())
3272 {
3273 VERIFY_IS_TRUE(warnings.empty());
3274 }
3275 else
3276 {
3277 if (!PathMatchSpec(warnings.c_str(), expectedWarnings.c_str()))
3278 {
3279 LogError("Warning '%ls' didn't match pattern '%ls'", warnings.c_str(), expectedWarnings.c_str());
3280 VERIFY_FAIL();
3281 }
3282 }
3283
3284 std::wistringstream input(out);
3285
3286 std::wstring line;
3287
3288 InterfaceState state = {name};
3289
3290 // Drop first two lines
3291 VERIFY_IS_TRUE(std::getline(input, line).good());
3292 VERIFY_IS_TRUE(std::getline(input, line).good());
3293
3294 // Read the address lines
3295 while (std::getline(input, line).good())
3296 {
3297 std::wregex v4Pattern(L"inet ([0-9,.]+)\\/([0-9]+) brd ([0-9,.]+) scope global .*" + name);
3298 std::wregex v6Pattern(L"inet6 ([a-f,A-F,0-9,:]+)\\/([0-9]+) scope global");
3299 std::wregex v4LocalPattern(L"inet 169.254.([0-9,.]+)\\/([0-9]+) brd 169.254.255.255 scope link");
3300 std::wregex v6LocalPattern(L"inet6 ([a-f,A-F,0-9,:]+)\\/([0-9]+) scope link");
3301 std::wregex v4LoopbackPattern(L"inet 127.0.0.1/8 scope host");
3302 std::wregex v6LoopbackPattern(L"inet6 ::1/128 scope host");
3303 std::wregex deprecatedPattern(L"deprecated");
3304
3305 std::wsmatch match, preferredStateMatch;
3306 if (std::regex_search(line, match, v4Pattern) && match.size() == 4)
3307 {
3308 bool preferred = !std::regex_search(line, preferredStateMatch, deprecatedPattern);
3309 state.V4Addresses.emplace_back(IpAddress{match.str(1), (uint8_t)std::stoul(match.str(2)), preferred});
3310 }
3311 else if (std::regex_search(line, match, v6Pattern) && match.size() == 3)
3312 {
3313 bool preferred = !std::regex_search(line, preferredStateMatch, deprecatedPattern);
3314 state.V6Addresses.emplace_back(IpAddress{match.str(1), (uint8_t)std::stoul(match.str(2)), preferred});
3315 }
3316 else if (std::regex_search(line, match, v4LocalPattern) && match.size() == 3)
3317 {
3318 LogInfo("Skipping ipv4 link local address");
3319 }
3320 else if (std::regex_search(line, match, v6LocalPattern) && match.size() == 3)
3321 {
3322 LogInfo("Skipping ipv6 link local address");
3323 }
3324 else if (std::regex_search(line, match, v4LoopbackPattern) && match.size() == 1)
3325 {
3326 LogInfo("Skipping ipv4 loopback");
3327 }
3328 else if (std::regex_search(line, match, v6LoopbackPattern) && match.size() == 1)
3329 {
3330 LogInfo("Skipping ipv6 loopback");
3331 }
3332 else
3333 {
3334 LogInfo("Ip addr output:\r\n%ls", FixLineEndings(out).c_str());
3335 LogInfo("Current line: \"%ls\"", line.c_str());
3336 VERIFY_FAIL(L"Failed to extract interface state");
3337 }
3338
3339 // Skip the lifetimes line
3340 VERIFY_IS_TRUE(std::getline(input, line).good());
3341 }
3342
3343 out = LxsstuLaunchWslAndCaptureOutput(L"cat /sys/class/net/" + name + L"/operstate").first;
3344 state.Up = false;
3345 if (out == L"up\n")
3346 {
3347 state.Up = true;
3348 }
3349 else if ((out != L"down\n") && ((name.substr(0, 4).compare(L"wlan") != 0) && (name != L"lo")))
3350 {
3351 LogInfo("Unexpected operstate: '%s'", out.c_str());
3352 VERIFY_FAIL();
3353 }
3354
3355 out = LxsstuLaunchWslAndCaptureOutput(L"cat /sys/class/net/" + name + L"/mtu").first;
3356 state.Mtu = std::stoi(out);
3357
3358 auto routingTableState = GetIpv4RoutingTableState();
3359 if (routingTableState.DefaultRoute.has_value())
3360 {
3361 state.Gateway = routingTableState.DefaultRoute->Via;
3362 }
3363
3364 auto v6RoutingTableState = GetIpv6RoutingTableState();
3365 if (v6RoutingTableState.DefaultRoute.has_value())
3366 {
3367 state.V6Gateway = v6RoutingTableState.DefaultRoute->Via;
3368 }
3369
3370 return state;
3371 }
3372
3373 static std::vector<InterfaceState> GetAllInterfaceStates()
3374 {
3375 // Result output is a list of interface names with newline as the delimiter
3376 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip -brief link show | awk -F '[@ ]' '{print $1}'");
3377 LogInfo("parsed ip link output:\r\n%ls", FixLineEndings(out).c_str());
3378
3379 std::wistringstream input(out);
3380
3381 std::vector<InterfaceState> interfaceStates;
3382 std::wstring line;
3383
3384 while (std::getline(input, line).good())
3385 {
3386 interfaceStates.push_back(GetInterfaceState(line));
3387 }
3388
3389 return interfaceStates;
3390 }
3391
3392 void TestCase(const std::vector<InterfaceState>& interfaceStates)
3393 {
3394 for (const auto& state : interfaceStates)
3395 {
3396 if (state.Rename)
3397 {
3398 wsl::shared::hns::HNSEndpoint endpoint;
3399 endpoint.ID = AdapterId;
3400 endpoint.PortFriendlyName = state.Name;
3401 RunGns(wsl::shared::ToJson(endpoint));
3402 }
3403
3404 // Remove existing addresses not in goal state
3405 auto currentInterfaceState = GetInterfaceState(state.Name);
3406 for (auto it = currentInterfaceState.V4Addresses.begin(); it != currentInterfaceState.V4Addresses.end(); ++it)
3407 {
3408 if (std::find(state.V4Addresses.begin(), state.V4Addresses.end(), *it) == state.V4Addresses.end())
3409 {
3410 wsl::shared::hns::IPAddress address;
3411 address.Address = it->Address;
3412 address.OnLinkPrefixLength = it->PrefixLength;
3413 address.Family = AF_INET;
3414 SendDeviceSettingsRequest(state.Name, address, ModifyRequestType::Remove, GuestEndpointResourceType::IPAddress);
3415 }
3416 }
3417
3418 for (auto it = currentInterfaceState.V6Addresses.begin(); it != currentInterfaceState.V6Addresses.end(); ++it)
3419 {
3420 if (std::find(state.V4Addresses.begin(), state.V4Addresses.end(), *it) == state.V4Addresses.end())
3421 {
3422 wsl::shared::hns::IPAddress address;
3423 address.Address = it->Address;
3424 address.OnLinkPrefixLength = it->PrefixLength;
3425 address.Family = AF_INET6;
3426 SendDeviceSettingsRequest(state.Name, address, ModifyRequestType::Remove, GuestEndpointResourceType::IPAddress);
3427 }
3428 }
3429
3430 // Add or update addresses
3431 for (auto it = state.V4Addresses.begin(); it != state.V4Addresses.end(); ++it)
3432 {
3433 wsl::shared::hns::IPAddress address;
3434 address.Address = it->Address;
3435 address.OnLinkPrefixLength = it->PrefixLength;
3436 address.Family = AF_INET;
3437 address.PreferredLifetime = 0xFFFFFFFF;
3438 bool updateAddress =
3439 (std::find(currentInterfaceState.V4Addresses.begin(), currentInterfaceState.V4Addresses.end(), *it) !=
3440 currentInterfaceState.V4Addresses.end());
3441 SendDeviceSettingsRequest(
3442 state.Name, address, updateAddress ? ModifyRequestType::Update : ModifyRequestType::Add, GuestEndpointResourceType::IPAddress);
3443
3444 Route prefixRoute{LX_INIT_UNSPECIFIED_ADDRESS, L"eth0", it->GetPrefix()};
3445 if (!RouteExists(prefixRoute))
3446 {
3447 // Add the prefix route for the newly added/updated address
3448 wsl::shared::hns::Route route;
3449 route.NextHop = prefixRoute.Via;
3450 route.DestinationPrefix = prefixRoute.Prefix.value();
3451 route.Family = AF_INET;
3452 SendDeviceSettingsRequest(state.Name, route, ModifyRequestType::Add, GuestEndpointResourceType::Route);
3453 }
3454 }
3455
3456 for (auto it = state.V6Addresses.begin(); it != state.V6Addresses.end(); ++it)
3457 {
3458 wsl::shared::hns::IPAddress address;
3459 address.Address = it->Address;
3460 address.OnLinkPrefixLength = it->PrefixLength;
3461 address.Family = AF_INET6;
3462 address.PreferredLifetime = 0xFFFFFFFF;
3463 bool updateAddress =
3464 (std::find(currentInterfaceState.V6Addresses.begin(), currentInterfaceState.V6Addresses.end(), *it) !=
3465 currentInterfaceState.V6Addresses.end());
3466 SendDeviceSettingsRequest(
3467 state.Name, address, updateAddress ? ModifyRequestType::Update : ModifyRequestType::Add, GuestEndpointResourceType::IPAddress);
3468
3469 Route prefixRoute{LX_INIT_UNSPECIFIED_V6_ADDRESS, L"eth0", it->GetPrefix()};
3470 if (!RouteExists(prefixRoute))
3471 {
3472 // Add the prefix route for the newly added/updated address
3473 wsl::shared::hns::Route route;
3474 route.NextHop = prefixRoute.Via;
3475 route.DestinationPrefix = prefixRoute.Prefix.value();
3476 route.Family = AF_INET6;
3477 SendDeviceSettingsRequest(state.Name, route, ModifyRequestType::Add, GuestEndpointResourceType::Route);
3478 }
3479 }
3480
3481 if (state.Gateway.has_value())
3482 {
3483 wsl::shared::hns::Route route;
3484 route.NextHop = state.Gateway.value();
3485 route.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_PREFIX;
3486 route.Family = AF_INET;
3487 bool updateGw = currentInterfaceState.Gateway.has_value();
3488 SendDeviceSettingsRequest(
3489 state.Name, route, updateGw ? ModifyRequestType::Update : ModifyRequestType::Add, GuestEndpointResourceType::Route);
3490 }
3491
3492 if (state.V6Gateway.has_value())
3493 {
3494 wsl::shared::hns::Route route;
3495 route.NextHop = state.V6Gateway.value();
3496 route.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_V6_PREFIX;
3497 route.Family = AF_INET6;
3498 bool updateGw = currentInterfaceState.V6Gateway.has_value();
3499 SendDeviceSettingsRequest(
3500 state.Name, route, updateGw ? ModifyRequestType::Update : ModifyRequestType::Add, GuestEndpointResourceType::Route);
3501 }
3502 }
3503
3504 // Validate that the addresses and routes are in the final goal state
3505 const auto& expectedInterfaceState = interfaceStates.back();
3506
3507 auto interfaceState = GetInterfaceState(expectedInterfaceState.Name);
3508 for (auto it = expectedInterfaceState.V4Addresses.begin(); it != expectedInterfaceState.V4Addresses.end(); ++it)
3509 {
3510 VERIFY_IS_TRUE(
3511 std::find(interfaceState.V4Addresses.begin(), interfaceState.V4Addresses.end(), *it) != interfaceState.V4Addresses.end());
3512 }
3513
3514 if (expectedInterfaceState.Gateway.has_value())
3515 {
3516 VERIFY_ARE_EQUAL(expectedInterfaceState.Gateway, interfaceState.Gateway);
3517 }
3518
3519 for (auto it = expectedInterfaceState.V6Addresses.begin(); it != expectedInterfaceState.V6Addresses.end(); ++it)
3520 {
3521 VERIFY_IS_TRUE(
3522 std::find(interfaceState.V6Addresses.begin(), interfaceState.V6Addresses.end(), *it) != interfaceState.V6Addresses.end());
3523 }
3524
3525 if (expectedInterfaceState.V6Gateway.has_value())
3526 {
3527 VERIFY_ARE_EQUAL(expectedInterfaceState.V6Gateway, interfaceState.V6Gateway);
3528 }
3529 }
3530
3531 static bool RouteExists(const Route& route)
3532 {
3533 auto v4State = GetIpv4RoutingTableState();
3534 if (std::find(v4State.Routes.begin(), v4State.Routes.end(), route) != v4State.Routes.end())
3535 {
3536 return true;
3537 }
3538
3539 auto v6State = GetIpv6RoutingTableState();
3540 return std::find(v6State.Routes.begin(), v6State.Routes.end(), route) != v6State.Routes.end();
3541 }
3542
3543 // Reads from the file until the substring is found, a timeout is reached, or ReadFile returns an error
3544 // Returns true on success, false otherwise
3545 static bool FindSubstring(wil::unique_handle& file, const std::string& substr, std::string& output)
3546 {
3547 char buffer[256];
3548 DWORD bytesRead;
3549 const wil::unique_handle readFileThread(OpenThread(THREAD_ALL_ACCESS, false, GetCurrentThreadId()));
3550 VERIFY_IS_NOT_NULL(readFileThread.get());
3551 const wil::unique_handle event(CreateEvent(nullptr, FALSE, FALSE, nullptr));
3552 VERIFY_ARE_NOT_EQUAL(event.get(), INVALID_HANDLE_VALUE);
3553
3554 // ReadFile will block, so cancel the syscall if it is taking too long
3555 const auto watchdogThread = std::async(std::launch::async, [&] {
3556 if (WaitForSingleObject(event.get(), 30000) == WAIT_TIMEOUT)
3557 {
3558 LogInfo("Canceling synchronous IO", GetTickCount());
3559 CancelSynchronousIo(readFileThread.get());
3560 }
3561 });
3562
3563 do
3564 {
3565 if (!ReadFile(file.get(), buffer, sizeof(buffer) - 1, &bytesRead, nullptr))
3566 {
3567 LogInfo("ReadFile failed with %d", GetLastError());
3568 break;
3569 }
3570
3571 buffer[bytesRead] = '\0';
3572 output += std::string(buffer);
3573
3574 if (output.find(substr) != std::string::npos)
3575 {
3576 break;
3577 }
3578 } while (true);
3579
3580 SetEvent(event.get());
3581 watchdogThread.wait();
3582
3583 // Convert narrow string output to wide string for logging, and fix line endings
3584 std::wstring wideOutput;
3585 wideOutput.reserve(output.length());
3586 for (char c : output)
3587 {
3588 if (c == '\n')
3589 {
3590 wideOutput += L'\r';
3591 wideOutput += L'\n';
3592 }
3593 else if (c != '\r')
3594 {
3595 wideOutput += static_cast<wchar_t>(static_cast<unsigned char>(c));
3596 }
3597 }
3598 LogInfo("output=\r\n%ls", wideOutput.c_str());
3599
3600 return (output.find(substr) != std::string::npos);
3601 }
3602
3603 static std::wstring CreateSocatString(const SOCKADDR_INET& si, int protocol, bool listen)
3604 {
3605 return std::wstring(((protocol == IPPROTO_TCP) ? L"TCP" : L"UDP")) + std::wstring(((si.si_family == AF_INET) ? L"4" : L"6")) +
3606 std::wstring(L"-") + std::wstring((listen) ? L"LISTEN:" : ((IPPROTO_TCP) ? L"CONNECT:" : L"SENDTO:")) +
3607 std::wstring(
3608 (listen) ? std::to_wstring(ntohs(SS_PORT(&si))) + std::wstring(L",bind=") +
3609 wsl::windows::common::string::SockAddrInetToWstring(si)
3610 : wsl::windows::common::string::SockAddrInetToWstring(si) + std::wstring(L":") +
3611 std::to_wstring(ntohs(SS_PORT(&si))));
3612 }
3613
3614 struct GuestListener
3615 {
3616 GuestListener(const SOCKADDR_INET& addr, int protocol)
3617 {
3618 THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&readPipe, &writePipe, nullptr, 0));
3619 THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(writePipe.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
3620
3621 const auto wslCmd = L"socat -dd " + CreateSocatString(addr, protocol, true) + L" STDOUT";
3622 auto cmd = LxssGenerateWslCommandLine(wslCmd.data());
3623
3624 process = unique_kill_process(LxsstuStartProcess(cmd.data(), nullptr, nullptr, writePipe.get()));
3625 writePipe.reset();
3626
3627 std::string output;
3628 THROW_HR_IF(E_FAIL, !NetworkTests::FindSubstring(readPipe, "listening on", output));
3629 }
3630
3631 // Start a listener in a different network namespace
3632 GuestListener(const SOCKADDR_INET& addr, int protocol, const std::wstring& namespaceName)
3633 {
3634 THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&readPipe, &writePipe, nullptr, 0));
3635 THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(writePipe.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
3636
3637 const auto wslCmd =
3638 L"ip netns exec " + namespaceName + L" socat -dd " + CreateSocatString(addr, protocol, true) + L" STDOUT";
3639 auto cmd = LxssGenerateWslCommandLine(wslCmd.data());
3640
3641 process = unique_kill_process(LxsstuStartProcess(cmd.data(), nullptr, nullptr, writePipe.get()));
3642 writePipe.reset();
3643
3644 std::string output;
3645 THROW_HR_IF(E_FAIL, !NetworkTests::FindSubstring(readPipe, "listening on", output));
3646 }
3647
3648 void AcceptConnection()
3649 {
3650 std::string output;
3651 VERIFY_IS_TRUE(NetworkTests::FindSubstring(readPipe, "starting data transfer loop", output));
3652 }
3653
3654 wil::unique_handle dmesgFile;
3655 unique_kill_process dmesg;
3656 unique_kill_process process;
3657 wil::unique_handle readPipe;
3658 wil::unique_handle writePipe;
3659 };
3660
3661 struct GuestClient
3662 {
3663 GuestClient(const SOCKADDR_INET& addr, int protocol) : GuestClient(CreateSocatString(addr, protocol, false))
3664 {
3665 }
3666
3667 GuestClient(const std::wstring& socatString, FirewallTestConnectivity expectedSuccess = FirewallTestConnectivity::Allowed)
3668 {
3669 const auto expectSuccess = expectedSuccess == FirewallTestConnectivity::Allowed;
3670 const auto wslCmd = L"echo A | socat -dd " + socatString + L" STDIN";
3671 auto cmd = LxssGenerateWslCommandLine(wslCmd.data());
3672 const auto* connectionString = expectSuccess ? "starting data transfer loop" : "Connection timed out";
3673 bool valueFound = false;
3674 for (int i = 0; i < 3; ++i)
3675 {
3676 wil::unique_handle readPipe;
3677 wil::unique_handle writePipe;
3678 THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&readPipe, &writePipe, nullptr, 0));
3679 THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(writePipe.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
3680
3681 unique_kill_process process = unique_kill_process(LxsstuStartProcess(cmd.data(), nullptr, nullptr, writePipe.get()));
3682 writePipe.reset();
3683
3684 std::string output;
3685 valueFound = FindSubstring(readPipe, connectionString, output);
3686
3687 if (expectSuccess && !valueFound && (output.find("Temporary failure") != std::string::npos))
3688 {
3689 LogWarning("Temporary failure - retrying up to 3 times");
3690 continue;
3691 }
3692
3693 break;
3694 }
3695
3696 VERIFY_IS_TRUE(valueFound, (expectSuccess) ? "Verifying connection succeeded" : "Verifying connection failed");
3697 }
3698 };
3699
3700 static std::wstring GetGelNicDeviceName()
3701 {
3702 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip route get from 127.0.0.1 127.0.0.1 | awk 'FNR <= 1 {print $7}'");
3703 out.pop_back();
3704 return out;
3705 }
3706
3707 static bool HostHasInternetConnectivity(ADDRESS_FAMILY family)
3708 {
3709 using ABI::Windows::Foundation::Collections::IVectorView;
3710 using ABI::Windows::Networking::Connectivity::ConnectionProfile;
3711 using ABI::Windows::Networking::Connectivity::INetworkAdapter;
3712 using ABI::Windows::Networking::Connectivity::INetworkInformationStatics;
3713 using ABI::Windows::Networking::Connectivity::NetworkConnectivityLevel;
3714
3715 // Get adapter addresses info.
3716 const auto adapterAddresses = GetAdapterAddresses(family);
3717
3718 // Get connection profile info.
3719 const auto roInit = wil::RoInitialize();
3720 const auto networkInformationStatics =
3721 wil::GetActivationFactory<INetworkInformationStatics>(RuntimeClass_Windows_Networking_Connectivity_NetworkInformation);
3722 THROW_HR_IF_NULL_MSG(E_OUTOFMEMORY, networkInformationStatics.get(), "null INetworkInformationStatics");
3723 wil::com_ptr<IVectorView<ConnectionProfile*>> connectionList;
3724 THROW_IF_FAILED(networkInformationStatics->GetConnectionProfiles(&connectionList));
3725
3726 // If we find a connection profile marked as having internet access and the associated
3727 // adapter has a <family> unicast address and a <family> default gateway, then conclude the
3728 // host has <family> internet connectivity.
3729 for (const auto& connectionProfile : wil::get_range(connectionList.get()))
3730 {
3731 NetworkConnectivityLevel connectivityLevel{};
3732 CONTINUE_IF_FAILED(connectionProfile->GetNetworkConnectivityLevel(&connectivityLevel));
3733 if (connectivityLevel != NetworkConnectivityLevel::NetworkConnectivityLevel_InternetAccess)
3734 {
3735 continue;
3736 }
3737
3738 wil::com_ptr<INetworkAdapter> networkAdapter;
3739 CONTINUE_IF_FAILED(connectionProfile->get_NetworkAdapter(&networkAdapter));
3740
3741 GUID interfaceGuid{};
3742 CONTINUE_IF_FAILED(networkAdapter->get_NetworkAdapterId(&interfaceGuid));
3743
3744 NET_LUID interfaceLuid{};
3745 CONTINUE_IF_FAILED_WIN32(ConvertInterfaceGuidToLuid(&interfaceGuid, &interfaceLuid));
3746
3747 for (auto* adapter = reinterpret_cast<const IP_ADAPTER_ADDRESSES*>(adapterAddresses.data()); adapter != nullptr;
3748 adapter = adapter->Next)
3749 {
3750 if (interfaceLuid.Value == adapter->Luid.Value && adapter->FirstUnicastAddress != nullptr && adapter->FirstGatewayAddress != nullptr)
3751 {
3752 return true;
3753 }
3754 }
3755 }
3756
3757 return false;
3758 }
3759
3760 static bool HostHasIpv6DnsServers()
3761 {
3762 ULONG bufferSize = 0;
3763 constexpr ULONG flags = GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS;
3764 std::vector<BYTE> buffer;
3765 ULONG result = GetAdaptersAddresses(AF_INET6, flags, nullptr, nullptr, &bufferSize);
3766 while (result == ERROR_BUFFER_OVERFLOW)
3767 {
3768 buffer.resize(bufferSize);
3769 result = GetAdaptersAddresses(AF_INET6, flags, nullptr, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data()), &bufferSize);
3770 }
3771
3772 if (result != NO_ERROR)
3773 {
3774 return false;
3775 }
3776
3777 DWORD bestIndex = 0;
3778 SOCKADDR_IN6 dest{};
3779 dest.sin6_family = AF_INET6;
3780 InetPtonW(AF_INET6, L"2001:4860:4860::8888", &dest.sin6_addr);
3781
3782 if (GetBestInterfaceEx(reinterpret_cast<SOCKADDR*>(&dest), &bestIndex) != NO_ERROR)
3783 {
3784 return false;
3785 }
3786
3787 for (auto* adapter = reinterpret_cast<const IP_ADAPTER_ADDRESSES*>(buffer.data()); adapter != nullptr; adapter = adapter->Next)
3788 {
3789 if (adapter->IfIndex != bestIndex)
3790 {
3791 continue;
3792 }
3793
3794 for (auto* dns = adapter->FirstDnsServerAddress; dns != nullptr; dns = dns->Next)
3795 {
3796 if (dns->Address.lpSockaddr->sa_family == AF_INET6)
3797 {
3798 return true;
3799 }
3800 }
3801 }
3802
3803 return false;
3804 }
3805
3806 static std::vector<BYTE> GetAdapterAddresses(ADDRESS_FAMILY family)
3807 {
3808 constexpr ULONG flags =
3809 (GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS);
3810 ULONG bufferSize = 0;
3811 std::vector<BYTE> buffer;
3812 ULONG result = GetAdaptersAddresses(family, flags, nullptr, nullptr, &bufferSize);
3813 while (result == ERROR_BUFFER_OVERFLOW)
3814 {
3815 buffer.resize(bufferSize);
3816 result = GetAdaptersAddresses(family, flags, nullptr, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data()), &bufferSize);
3817 }
3818
3819 VERIFY_WIN32_SUCCEEDED(result);
3820
3821 return buffer;
3822 }
3823
3824 static void WaitForNATStateInLinux()
3825 {
3826 Stopwatch<std::chrono::seconds> Watchdog(std::chrono::seconds(30));
3827
3828 // NAT only supports IPv4 connectivity
3829 // wait for the host to have v4 connectivity
3830 do
3831 {
3832 if (HostHasInternetConnectivity(AF_INET))
3833 {
3834 break;
3835 }
3836
3837 LogInfo("Waiting for Windows network connectivity...");
3838 } while (Sleep(1000), !Watchdog.IsExpired());
3839 VERIFY_IS_FALSE(Watchdog.IsExpired());
3840
3841 // reset the watchdog
3842 Watchdog = Stopwatch{std::chrono::seconds(30)};
3843
3844 do
3845 {
3846 // Count how many interfaces have v4 connectivity, as defined by having a gateway and at least 1 preferred address.
3847 int interfacesWithV4Connectivity = 0;
3848
3849 // Get all interface info from the VM.
3850 for (const auto& i : GetAllInterfaceStates())
3851 {
3852 if (i.Gateway.has_value())
3853 {
3854 for (const auto& j : i.V4Addresses)
3855 {
3856 if (j.Preferred)
3857 {
3858 interfacesWithV4Connectivity++;
3859 break;
3860 }
3861 }
3862 }
3863 }
3864
3865 // Consider mirroring to be complete if we have the same v4 connectivity in the VM as the host.
3866 if (interfacesWithV4Connectivity > 0)
3867 {
3868 break;
3869 }
3870
3871 LogInfo("Waiting for NAT state...");
3872 } while (Sleep(1000), !Watchdog.IsExpired());
3873 VERIFY_IS_FALSE(Watchdog.IsExpired());
3874 }
3875
3876 WSL2_TEST_METHOD(ConnectivityCheckTestNATDefaultSuccess)
3877 {
3878 WslConfigChange config(LxssGenerateTestConfig());
3879 WaitForNATStateInLinux();
3880
3881 const auto coInit = wil::CoInitializeEx();
3882 const wil::com_ptr<INetworkListManager> networkListManager = wil::CoCreateInstance<NetworkListManager, INetworkListManager>();
3883 VERIFY_IS_NOT_NULL(networkListManager.get());
3884 NLM_CONNECTIVITY hostConnectivity{};
3885 VERIFY_SUCCEEDED(networkListManager->GetConnectivity(&hostConnectivity));
3886
3887 // Windows
3888 const wsl::shared::conncheck::ConnCheckResult hostResult =
3889 wsl::shared::conncheck::CheckConnection("www.msftconnecttest.com", "ipv6.msftconnecttest.com", "80");
3890
3891 if (hostConnectivity & NLM_CONNECTIVITY_IPV4_INTERNET)
3892 {
3893 VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::Success, hostResult.Ipv4Status);
3894 }
3895 else
3896 {
3897 // one of the 2 expected runtime failures
3898 VERIFY_IS_TRUE(
3899 hostResult.Ipv4Status == wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo ||
3900 hostResult.Ipv4Status == wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
3901 }
3902 if (hostConnectivity & NLM_CONNECTIVITY_IPV6_INTERNET)
3903 {
3904 VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::Success, hostResult.Ipv4Status);
3905 }
3906 else
3907 {
3908 // one of the 2 expected runtime failures (sometimes v6 name resolution will fail, depending on the configuration)
3909 VERIFY_IS_TRUE(
3910 hostResult.Ipv6Status == wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo ||
3911 hostResult.Ipv6Status == wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
3912 }
3913
3914 // www.msftconnecttest.com will always fail IPv6 name resolution - it doesn't have any AAAA records registered for it
3915 const int expectedErrorCode = static_cast<int>(hostResult.Ipv4Status) |
3916 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) << 16);
3917 LogInfo("RunGns(www.msftconnecttest.com, 0x%x)", expectedErrorCode);
3918 // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
3919 // it returns 1 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::Success)
3920 // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
3921 // if ManualConnectivityValidation is set true, one can confirm from the stdout captured that the correct result was determined and returned by init.
3922 constexpr auto testErrorCode =
3923 ManualConnectivityValidation ? expectedErrorCode : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::Success);
3924 RunGns("www.msftconnecttest.com", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
3925 }
3926
3927 WSL2_TEST_METHOD(ConnectivityCheckTestNATNameResolutionFailure)
3928 {
3929 WslConfigChange config(LxssGenerateTestConfig());
3930 WaitForNATStateInLinux();
3931
3932 // Windows
3933 const wsl::shared::conncheck::ConnCheckResult result =
3934 wsl::shared::conncheck::CheckConnection("asdlkfadsf.bbcxzncvb", nullptr, "80");
3935
3936 VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo, result.Ipv4Status);
3937 VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo, result.Ipv6Status);
3938
3939 constexpr int expectedErrorCode = static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) |
3940 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo) << 16);
3941 LogInfo("RunGns(asdlkfadsf.bbcxzncvb, 0x%x)", expectedErrorCode);
3942 // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
3943 // it returns 2 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo))
3944 // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
3945 // if temporarily change this back to expectedErrorCode, one can confirm from the stdout captured that the correct result was determined and returned by init.
3946 constexpr auto testErrorCode = ManualConnectivityValidation
3947 ? expectedErrorCode
3948 : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo);
3949 RunGns("asdlkfadsf.bbcxzncvb", AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
3950 }
3951
3952 WSL2_TEST_METHOD(ConnectivityCheckTestNATNameResolvesButConnectivityFails)
3953 {
3954 WslConfigChange config(LxssGenerateTestConfig());
3955 WaitForNATStateInLinux();
3956
3957 const auto* ncsiDnsOnlyName = "dns.msftncsi.com";
3958 // v4 and v6 should succeed to resolve the name, but fail to connect,
3959 // as this NCSI name is registered in global DNS, but there's not HTTP endpoint for it
3960
3961 // Windows
3962 const wsl::shared::conncheck::ConnCheckResult result =
3963 wsl::shared::conncheck::CheckConnection(ncsiDnsOnlyName, nullptr, "80");
3964
3965 VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect, result.Ipv4Status);
3966 // v6 name resolution might fail, depending on the configuration
3967 VERIFY_IS_TRUE(
3968 (wsl::shared::conncheck::ConnCheckStatus::FailureGetAddrInfo == result.Ipv6Status) ||
3969 (wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect == result.Ipv6Status));
3970
3971 constexpr int expectedErrorCode = static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect) |
3972 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect) << 16);
3973 LogInfo("RunGns(%hs, 0x%x)", ncsiDnsOnlyName, expectedErrorCode);
3974 // TODO: pass 'expectedErrorCode' instead of 1, once the pipeline is fixed from running Init back to wsl.exe
3975 // it returns 4 (static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect))
3976 // as that's the lowest 16 bit value (unknown where the upper 16 bits are trimmed)
3977 // if ManualConnectivityValidation is set true, one can confirm from the stdout captured that the correct result was determined and returned by init.
3978 constexpr auto testErrorCode = ManualConnectivityValidation
3979 ? expectedErrorCode
3980 : static_cast<int>(wsl::shared::conncheck::ConnCheckStatus::FailureSocketConnect);
3981 RunGns(ncsiDnsOnlyName, AdapterId, LxGnsMessageConnectTestRequest, testErrorCode);
3982 }
3983 };
3984
3985 class GnsCallbackResultTests
3986 {
3987 WSL_TEST_CLASS(GnsCallbackResultTests)
3988
3989 TEST_METHOD(GnsCallbackSuccessfulTransportAndLinuxResultSucceeds)
3990 {
3991 VERIFY_SUCCEEDED(wsl::core::networking::GetGnsCallbackResult(LxGnsMessageDeviceSettingRequest, S_OK, 0));
3992 }
3993
3994 TEST_METHOD(GnsCallbackSuccessfulTransportAndLinuxFailureFails)
3995 {
3996 VERIFY_ARE_EQUAL(E_FAIL, wsl::core::networking::GetGnsCallbackResult(LxGnsMessageDeviceSettingRequest, S_OK, -1));
3997 }
3998
3999 TEST_METHOD(GnsCallbackTransportFailureFails)
4000 {
4001 VERIFY_ARE_EQUAL(E_ABORT, wsl::core::networking::GetGnsCallbackResult(LxGnsMessageDeviceSettingRequest, E_ABORT, 0));
4002 }
4003
4004 TEST_METHOD(GnsCallbackConnectTestBusinessResultSucceeds)
4005 {
4006 VERIFY_SUCCEEDED(wsl::core::networking::GetGnsCallbackResult(LxGnsMessageConnectTestRequest, S_OK, -1));
4007 }
4008
4009 TEST_METHOD(GnsCallbackConnectTestTransportFailureFails)
4010 {
4011 VERIFY_ARE_EQUAL(E_ABORT, wsl::core::networking::GetGnsCallbackResult(LxGnsMessageConnectTestRequest, E_ABORT, -1));
4012 }
4013 };
4014
4015 class MirroredTests
4016 {
4017 WSL_TEST_CLASS(MirroredTests)
4018
4019 std::optional<WslConfigChange> m_config;
4020 GUID AdapterId;
4021
4022 TEST_CLASS_SETUP(TestClassSetup)
4023 {
4024 VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
4025
4026 // Build the Linux unit tests used by the port tracking tests.
4027 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(LXSST_TESTS_INSTALL_COMMAND_LINE), (DWORD)0);
4028
4029 if (LxsstuVmMode())
4030 {
4031 m_config.emplace(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4032
4033 AdapterId = NetworkTests::QueryAdapterId();
4034 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ln -f -s /init /gns"), (DWORD)0);
4035 }
4036
4037 return true;
4038 }
4039
4040 TEST_CLASS_CLEANUP(TestClassCleanup)
4041 {
4042 m_config.reset();
4043
4044 VERIFY_NO_THROW(LxsstuUninitialize(false));
4045
4046 return true;
4047 }
4048
4049 WSL2_TEST_METHOD(DnsTunneling)
4050 {
4051 DNS_TUNNELING_TEST_ONLY();
4052 MIRRORED_NETWORKING_TEST_ONLY();
4053
4054 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true}));
4055 WaitForMirroredStateInLinux();
4056
4057 NetworkTests::VerifyDnsTunneling(c_dnsTunnelingDefaultIp);
4058 }
4059
4060 WSL2_TEST_METHOD(DnsTunnelingWithSpecificIp)
4061 {
4062 DNS_TUNNELING_TEST_ONLY();
4063 MIRRORED_NETWORKING_TEST_ONLY();
4064
4065 m_config->Update(LxssGenerateTestConfig(
4066 {.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true, .dnsTunnelingIpAddress = L"10.255.255.1"}));
4067 WaitForMirroredStateInLinux();
4068
4069 NetworkTests::VerifyDnsTunneling(L"10.255.255.1");
4070 }
4071
4072 WSL2_TEST_METHOD(DnsTunnelingVerifySuffixes)
4073 {
4074 DNS_TUNNELING_TEST_ONLY();
4075 MIRRORED_NETWORKING_TEST_ONLY();
4076
4077 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true}));
4078 WaitForMirroredStateInLinux();
4079
4080 NetworkTests::VerifyDnsSuffixes();
4081 }
4082
4083 WSL2_TEST_METHOD(WithoutTunnelingVerifySuffixes)
4084 {
4085 MIRRORED_NETWORKING_TEST_ONLY();
4086
4087 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = false}));
4088 WaitForMirroredStateInLinux();
4089
4090 NetworkTests::VerifyDnsSuffixes();
4091 }
4092
4093 WSL2_TEST_METHOD(HttpProxyVerifyConfigDisabled)
4094 {
4095 MIRRORED_NETWORKING_TEST_ONLY();
4096 WINHTTP_PROXY_TEST_ONLY();
4097 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = false}));
4098 WaitForMirroredStateInLinux();
4099
4100 auto restoreProxySettings = wil::scope_exit([&] { NetworkTests::ClearHttpProxySettings(true); });
4101 NetworkTests::SetHttpProxySettings(NetworkTests::c_httpProxyString, L"", L"", true);
4102 NetworkTests::VerifyHttpProxyEnvVariables(L"", L"", L"");
4103 }
4104
4105 WSL2_TEST_METHOD(HttpProxySimple)
4106 {
4107 MIRRORED_NETWORKING_TEST_ONLY();
4108 WINHTTP_PROXY_TEST_ONLY();
4109
4110 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
4111 WaitForMirroredStateInLinux();
4112 NetworkTests::VerifyHttpProxySimple();
4113 }
4114
4115 WSL2_TEST_METHOD(HttpProxySimpleMachineScope)
4116 {
4117 MIRRORED_NETWORKING_TEST_ONLY();
4118 WINHTTP_PROXY_TEST_ONLY();
4119
4120 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
4121 WaitForMirroredStateInLinux();
4122
4123 // verify with machine scope
4124 NetworkTests::VerifyHttpProxySimple(false);
4125 }
4126
4127 WSL2_TEST_METHOD(NoHttpProxyConfigured)
4128 {
4129 MIRRORED_NETWORKING_TEST_ONLY();
4130 WINHTTP_PROXY_TEST_ONLY();
4131
4132 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
4133 WaitForMirroredStateInLinux();
4134 NetworkTests::VerifyNoHttpProxyConfigured();
4135 }
4136
4137 WSL2_TEST_METHOD(HttpProxyWithBypassesConfigured)
4138 {
4139 MIRRORED_NETWORKING_TEST_ONLY();
4140 WINHTTP_PROXY_TEST_ONLY();
4141
4142 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
4143 WaitForMirroredStateInLinux();
4144 NetworkTests::VerifyHttpProxyWithBypassesConfigured();
4145 }
4146
4147 WSL2_TEST_METHOD(HttpProxyChange)
4148 {
4149 MIRRORED_NETWORKING_TEST_ONLY();
4150 WINHTTP_PROXY_TEST_ONLY();
4151
4152 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
4153 WaitForMirroredStateInLinux();
4154 NetworkTests::VerifyHttpProxyChange();
4155 }
4156
4157 WSL2_TEST_METHOD(HttpProxyAndWslEnv)
4158 {
4159 MIRRORED_NETWORKING_TEST_ONLY();
4160 WINHTTP_PROXY_TEST_ONLY();
4161 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
4162 WaitForMirroredStateInLinux();
4163 NetworkTests::VerifyHttpProxyAndWslEnv();
4164 }
4165
4166 WSL2_TEST_METHOD(HttpProxyFilterByNetworkConfiguration)
4167 {
4168 MIRRORED_NETWORKING_TEST_ONLY();
4169 WINHTTP_PROXY_TEST_ONLY();
4170 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
4171
4172 NetworkTests::VerifyHttpProxyFilterByNetworkConfigurationMirrored();
4173 }
4174
4175 WSL2_TEST_METHOD(HttpProxyPac)
4176 {
4177 MIRRORED_NETWORKING_TEST_ONLY();
4178 WINHTTP_PROXY_TEST_ONLY();
4179
4180 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .autoProxy = true}));
4181 WaitForMirroredStateInLinux();
4182 NetworkTests::VerifyHttpProxyPac();
4183 }
4184
4185 WSL2_TEST_METHOD(SmokeTest)
4186 {
4187 MIRRORED_NETWORKING_TEST_ONLY();
4188
4189 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4190 WaitForMirroredStateInLinux();
4191
4192 // Verify that we have a working connection
4193 NetworkTests::GuestClient(L"tcp-connect:bing.com:80");
4194 }
4195
4196 WSL2_TEST_METHOD(InternetConnectivityV4)
4197 {
4198 MIRRORED_NETWORKING_TEST_ONLY();
4199
4200 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4201 WaitForMirroredStateInLinux();
4202
4203 if (!NetworkTests::HostHasInternetConnectivity(AF_INET))
4204 {
4205 LogSkipped("Host does not have IPv4 internet connectivity. Skipping...");
4206 return;
4207 }
4208
4209 NetworkTests::GuestClient(L"tcp4-connect:bing.com:80");
4210 }
4211
4212 WSL2_TEST_METHOD(InternetConnectivityV6)
4213 {
4214 MIRRORED_NETWORKING_TEST_ONLY();
4215
4216 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4217 WaitForMirroredStateInLinux();
4218
4219 if (!NetworkTests::HostHasInternetConnectivity(AF_INET6))
4220 {
4221 LogSkipped("Host does not have IPv6 internet connectivity. Skipping...");
4222 return;
4223 }
4224
4225 NetworkTests::GuestClient(L"tcp6-connect:bing.com:80");
4226 }
4227
4228 WSL2_TEST_METHOD(LoopbackLocal)
4229 {
4230 MIRRORED_NETWORKING_TEST_ONLY();
4231
4232 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
4233 WaitForMirroredStateInLinux();
4234
4235 std::vector<NetworkTests::InterfaceState> interfaceStates = NetworkTests::GetAllInterfaceStates();
4236
4237 // Verify loopback connectivity on assigned unicast addresses
4238 for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
4239 {
4240 for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
4241 {
4242 // The IP used for DNS tunneling is not intended for guest<->host communication
4243 if (j->Address != c_dnsTunnelingDefaultIp)
4244 {
4245 NetworkTests::VerifyLoopbackConnectivity(j->Address);
4246 }
4247 }
4248 for (auto j = i->V6Addresses.begin(); j != i->V6Addresses.end(); ++j)
4249 {
4250 // TODO: enable when v6 loopback is supported
4251 // VerifyLoopbackConnectivity(j->Address);
4252 }
4253 }
4254 }
4255
4256 WSL2_TEST_METHOD(LoopbackExplicit)
4257 {
4258 // TODO: re-enable once OS build 29555 loopback regression is resolved.
4259 SKIP_TEST_UNSTABLE();
4260
4261 MIRRORED_NETWORKING_TEST_ONLY();
4262
4263 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4264 WaitForMirroredStateInLinux();
4265
4266 // Verify loopback connectivity on loopback addresses
4267 NetworkTests::VerifyLoopbackConnectivity(L"127.0.0.1");
4268 // TODO: enable when v6 loopback is supported
4269 // VerifyLoopbackConnectivity(L"::1");
4270 }
4271
4272 WSL2_TEST_METHOD(LoopbackSystemd)
4273 {
4274 MIRRORED_NETWORKING_TEST_ONLY();
4275
4276 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4277 WaitForMirroredStateInLinux();
4278
4279 // Write a .conf file to conflict with loopback settings.
4280 #define CONFIG_FILE_PATH L"/etc/sysctl.d/MirroredLoopbackSystemd.conf"
4281 auto revertConfigFile = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [] {
4282 const std::wstring deleteConfigFileCmd(L"-u root -e rm " CONFIG_FILE_PATH);
4283 LxsstuLaunchWsl(deleteConfigFileCmd.data());
4284 });
4285 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"echo \"net.ipv4.conf.*.rp_filter=2\" > " CONFIG_FILE_PATH), static_cast<DWORD>(0));
4286
4287 // Enable systemd which will apply the .conf file.
4288 auto revertSystemd = EnableSystemd();
4289
4290 // Verify the settings configured in the systemd hardening logic.
4291 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl net.ipv4.conf.all.rp_filter | grep -w 0"), 0);
4292 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl net.ipv4.conf." TEXT(LX_INIT_LOOPBACK_DEVICE_NAME) L".rp_filter | grep -w 0"), 0);
4293
4294 // Verify an E2E loopback scenario.
4295 NetworkTests::VerifyLoopbackGuestToHost(L"127.0.0.1", IPPROTO_TCP);
4296 }
4297
4298 WSL2_TEST_METHOD(GuestPortCantBeBoundByHost)
4299 {
4300 MIRRORED_NETWORKING_TEST_ONLY();
4301
4302 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4303 WaitForMirroredStateInLinux();
4304
4305 {
4306 auto guestProcess = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", true);
4307 NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
4308 }
4309
4310 {
4311 auto guestProcess = NetworkTests::BindGuestPort(L"UDP4-LISTEN:1234", true);
4312 NetworkTests::BindHostPort(1234, SOCK_DGRAM, IPPROTO_UDP, false);
4313 }
4314 }
4315
4316 WSL2_TEST_METHOD(GuestPortIsReleased)
4317 {
4318 MIRRORED_NETWORKING_TEST_ONLY();
4319
4320 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4321 WaitForMirroredStateInLinux();
4322
4323 // Make sure the VM doesn't time out
4324 WslKeepAlive keepAlive;
4325
4326 {
4327 auto guestProcess = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", true);
4328 NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
4329 }
4330
4331 const wil::unique_socket listenSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
4332 VERIFY_IS_TRUE(!!listenSocket);
4333
4334 SOCKADDR_IN Address{};
4335 Address.sin_family = AF_INET;
4336 Address.sin_port = htons(1234);
4337
4338 const auto timeout = std::chrono::steady_clock::now() + std::chrono::minutes(2);
4339
4340 bool bound = false;
4341 while (!bound && std::chrono::steady_clock::now() < timeout)
4342 {
4343 bound = bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) != SOCKET_ERROR;
4344 std::this_thread::sleep_for(std::chrono::seconds(1));
4345 }
4346
4347 VERIFY_IS_TRUE(bound);
4348 }
4349
4350 WSL2_TEST_METHOD(HostPortCantBeBoundByGuest)
4351 {
4352 MIRRORED_NETWORKING_TEST_ONLY();
4353
4354 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4355 WaitForMirroredStateInLinux();
4356
4357 {
4358 auto hostPort = NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, true);
4359 NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", false);
4360 }
4361
4362 {
4363 auto hostPort = NetworkTests::BindHostPort(1234, SOCK_DGRAM, IPPROTO_UDP, true);
4364 NetworkTests::BindGuestPort(L"UDP4-LISTEN:1234", false);
4365 }
4366 }
4367
4368 WSL2_TEST_METHOD(UdpBindDoesNotPreventTcpBind)
4369 {
4370 MIRRORED_NETWORKING_TEST_ONLY();
4371
4372 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4373 WaitForMirroredStateInLinux();
4374
4375 auto tcpPort = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234", true);
4376 auto udpPort = NetworkTests::BindGuestPort(L"UDP4-LISTEN:1234", true);
4377 }
4378
4379 WSL2_TEST_METHOD(HostUdpBindDoesNotPreventGuestTcpBind)
4380 {
4381 MIRRORED_NETWORKING_TEST_ONLY();
4382
4383 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4384 WaitForMirroredStateInLinux();
4385
4386 auto udpPort = NetworkTests::BindHostPort(2345, SOCK_DGRAM, IPPROTO_UDP, true);
4387 auto tcpPort = NetworkTests::BindGuestPort(L"TCP4-LISTEN:2345", true);
4388 }
4389
4390 WSL2_TEST_METHOD(MultipleGuestBindOnSameTuple)
4391 {
4392 MIRRORED_NETWORKING_TEST_ONLY();
4393
4394 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4395 WaitForMirroredStateInLinux();
4396
4397 auto bind1 = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234,bind=127.0.0.1", true);
4398 {
4399 auto bind2 = NetworkTests::BindGuestPort(L"TCP6-LISTEN:1234,bind=::1", true);
4400
4401 // Allow time for this second bind to be viewed as "in use" by the init port tracker
4402 // before closing the socket. If the socket is closed before the init port tracker sees
4403 // that the port allocation was in use, then the init port tracker will hold onto the
4404 // allocation for a considerable amount of time (through the duration of this test case)
4405 // before releasing it.
4406 std::this_thread::sleep_for(std::chrono::seconds(3));
4407 }
4408
4409 // Allow time for the init port tracker to detect the second port allocation as no longer in
4410 // use and perform its cleanup of the second port allocation.
4411 const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(3);
4412 while (std::chrono::steady_clock::now() < timeout)
4413 {
4414 // {TCP, 1234} should still be reserved for the guest from the first bind.
4415 auto hostPort = NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
4416 std::this_thread::sleep_for(std::chrono::seconds(1));
4417 }
4418 }
4419
4420 WSL2_TEST_METHOD(EphemeralBind)
4421 {
4422 MIRRORED_NETWORKING_TEST_ONLY();
4423
4424 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4425 WaitForMirroredStateInLinux();
4426
4427 auto tcpPort = NetworkTests::BindGuestPort(L"TCP4-LISTEN:0", true);
4428 auto udpPort = NetworkTests::BindGuestPort(L"UDP4-LISTEN:0", true);
4429 }
4430
4431 WSL2_TEST_METHOD(PortZeroBindIsTracked)
4432 {
4433 MIRRORED_NETWORKING_TEST_ONLY();
4434
4435 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4436 WaitForMirroredStateInLinux();
4437
4438 // Skip port-release verification in mirrored mode. The host reserves a contiguous
4439 // ephemeral port range via HcnReserveGuestNetworkServicePortRange that no Windows
4440 // process can bind for the lifetime of the VM. Port-0 binds resolve to ports within
4441 // this range, so even after the guest releases the port the host still cannot bind
4442 // it — the range-level reservation remains, making release unverifiable.
4443 NetworkTests::VerifyPortZeroBindIsTracked(false);
4444
4445 NetworkTests::VerifyPortZeroBindFromThreadIsTracked();
4446 }
4447
4448 WSL2_TEST_METHOD(ListenWithoutBindIsTracked)
4449 {
4450 MIRRORED_NETWORKING_TEST_ONLY();
4451
4452 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4453 WaitForMirroredStateInLinux();
4454
4455 // See PortZeroBindIsTracked above for why release verification is skipped in mirrored mode.
4456 NetworkTests::VerifyListenWithoutBindIsTracked(false);
4457 }
4458
4459 WSL2_TEST_METHOD(AcceptedConnectionPortTracking)
4460 {
4461 MIRRORED_NETWORKING_TEST_ONLY();
4462
4463 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4464 WaitForMirroredStateInLinux();
4465
4466 NetworkTests::VerifyAcceptedConnectionPortTracking();
4467 }
4468
4469 WSL2_TEST_METHOD(MirroredReusePortOnGuest)
4470 {
4471 MIRRORED_NETWORKING_TEST_ONLY();
4472
4473 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4474 WaitForMirroredStateInLinux();
4475
4476 WslKeepAlive keepAlive;
4477
4478 // Verify that when guest has two binds on the same port (with reuseport) and the first
4479 // bind is released, the port remains allocated to the guest because the second bind is
4480 // still active. This validates the relaxed port+protocol matching in mirrored mode.
4481 {
4482 auto [guestLocal, read1] = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234,bind=127.0.0.1,reuseport", true);
4483
4484 auto guestWild = NetworkTests::BindGuestPort(L"TCP4-LISTEN:1234,bind=0.0.0.0,reuseport", true);
4485
4486 // Release the first bind (127.0.0.1)
4487 guestLocal.reset();
4488 read1.reset();
4489
4490 // Wait > 60 seconds so that the port tracker's deallocation logic kicks in.
4491 // See c_bind_timeout_seconds in GnsPortTracker.cpp
4492 std::this_thread::sleep_for(std::chrono::seconds(90));
4493
4494 // The host tries to bind on 127.0.0.1 (matching the released guest bind). This should
4495 // still fail because the second guest bind (0.0.0.0) is still active and the mirrored
4496 // mode port tracker matches by port+protocol, not the full allocation tuple.
4497 NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false, false, true);
4498 }
4499
4500 // Both binds are now released. Verify the port is eventually released.
4501 wsl::shared::retry::RetryWithTimeout<void>(
4502 [&]() {
4503 wil::unique_socket sock(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
4504 THROW_LAST_ERROR_IF(!sock);
4505
4506 SOCKADDR_IN addr{};
4507 addr.sin_family = AF_INET;
4508 addr.sin_port = htons(1234);
4509 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
4510 THROW_HR_IF(E_FAIL, bind(sock.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)) == SOCKET_ERROR);
4511 },
4512 std::chrono::seconds(1),
4513 std::chrono::minutes(2));
4514 }
4515
4516 WSL2_TEST_METHOD(PortZeroRebindSucceeds)
4517 {
4518 MIRRORED_NETWORKING_TEST_ONLY();
4519
4520 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4521 WaitForMirroredStateInLinux();
4522
4523 NetworkTests::VerifyPortZeroRebindSucceeds();
4524 }
4525
4526 WSL2_TEST_METHOD(ExplicitEphemeralBind)
4527 {
4528 MIRRORED_NETWORKING_TEST_ONLY();
4529
4530 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4531 WaitForMirroredStateInLinux();
4532
4533 // Get ephemeral port range
4534 auto [start, err1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f1", 0);
4535 start.pop_back();
4536 const auto ephemeralRangeStart = std::stoi(start);
4537
4538 auto [end, err2] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f2", 0);
4539 end.pop_back();
4540 const auto ephemeralRangeEnd = std::stoi(end);
4541
4542 // Walk the ephemeral port range and verify we can bind to at least one port (some might be already taken, but the test
4543 // assumes there should be at least one free).
4544 bool canBindTcp = false;
4545 bool canBindUdp = false;
4546
4547 for (int port = ephemeralRangeStart; port <= ephemeralRangeEnd; port++)
4548 {
4549 auto [tcpListener, tcpSuccess, read] = NetworkTests::BindGuestPortHelper(L"TCP4-LISTEN:" + std::to_wstring(port));
4550 if (tcpSuccess)
4551 {
4552 canBindTcp = true;
4553 break;
4554 }
4555 }
4556
4557 for (int port = ephemeralRangeStart; port <= ephemeralRangeEnd; port++)
4558 {
4559 auto [udpListener, udpSuccess, read] = NetworkTests::BindGuestPortHelper(L"UDP4-LISTEN:" + std::to_wstring(port));
4560 if (udpSuccess)
4561 {
4562 canBindUdp = true;
4563 break;
4564 }
4565 }
4566
4567 VERIFY_IS_TRUE(canBindTcp);
4568 VERIFY_IS_TRUE(canBindUdp);
4569 }
4570
4571 static std::pair<int, int> QueryHostEphemeralRange(LPCWSTR ProtocolSettingCmdlet)
4572 {
4573 const auto startQuery =
4574 std::wstring(L"(") + ProtocolSettingCmdlet +
4575 L" | Where-Object { $_.DynamicPortRangeStartPort -gt 0 } | Select-Object -First 1).DynamicPortRangeStartPort";
4576 auto [startStr, _1] = LxsstuLaunchPowershellAndCaptureOutput(startQuery, 0);
4577 const auto start = std::stoi(startStr);
4578
4579 const auto countQuery =
4580 std::wstring(L"(") + ProtocolSettingCmdlet +
4581 L" | Where-Object { $_.DynamicPortRangeNumberOfPorts -gt 0 } | Select-Object -First 1).DynamicPortRangeNumberOfPorts";
4582 auto [countStr, _2] = LxsstuLaunchPowershellAndCaptureOutput(countQuery, 0);
4583 const auto count = std::stoi(countStr);
4584
4585 return {start, count};
4586 }
4587
4588 static void SetHostEphemeralRange(LPCWSTR Protocol, int Start, int NumberOfPorts)
4589 {
4590 // Note: setting the range for v4 also sets the same range for v6, so we only need to set one of them.
4591 auto cmd = std::format(L"netsh int ipv4 set dynamicportrange {} startport={} numberofports={}", Protocol, Start, NumberOfPorts);
4592 VERIFY_ARE_EQUAL(LxsstuRunCommand(cmd.data()), 0L);
4593 }
4594
4595 // Attempt every host-ephemeral port from the guest and verify exactly the service-enforced cap
4596 // (half of the host ephemeral range size) can be reserved.
4597 static void VerifyHostEphemeralRangeCap(int Protocol, int HostEphemeralStart, int HostEphemeralEnd, int Cap)
4598 {
4599 WslKeepAlive keepAlive;
4600
4601 auto [guestStartStr, err1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f1", 0);
4602 guestStartStr.pop_back();
4603 const auto guestStart = std::stoi(guestStartStr);
4604
4605 auto [guestEndStr, err2] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_local_port_range | cut -f2", 0);
4606 guestEndStr.pop_back();
4607 const auto guestEnd = std::stoi(guestEndStr);
4608
4609 const int overlapStart = std::max(HostEphemeralStart, guestStart);
4610 const int overlapEnd = std::min(HostEphemeralEnd, guestEnd);
4611 const int overlap = (overlapStart <= overlapEnd) ? (overlapEnd - overlapStart + 1) : 0;
4612
4613 const int expectedSuccesses = Cap - overlap;
4614 VERIFY_IS_GREATER_THAN(expectedSuccesses, 0);
4615
4616 // Repeat the pattern a couple of times: verify the cap, release every socket, then verify the
4617 // reservations drain and the full capacity becomes available again.
4618 constexpr int c_cycles = 2;
4619
4620 for (int cycle = 0; cycle < c_cycles; cycle++)
4621 {
4622 // Bind every candidate from one guest process so the test does not launch wsl.exe once per port.
4623 std::wstring perlCommand = L"perl -MSocket -MErrno=EADDRINUSE -e '";
4624 perlCommand += L"$|=1;";
4625 perlCommand += L"my @sockets;";
4626 perlCommand += L"my $candidate=0;";
4627 perlCommand +=
4628 L"for my $port (" + std::to_wstring(HostEphemeralStart) + L".." + std::to_wstring(HostEphemeralEnd) + L"){";
4629 perlCommand += L"next if $port>=" + std::to_wstring(guestStart) + L" && $port<=" + std::to_wstring(guestEnd) + L";";
4630 perlCommand += L"my $family=(($candidate++ % 2)==0) ? AF_INET : AF_INET6;";
4631 perlCommand += L"socket(my $socket,$family," + std::wstring(Protocol == IPPROTO_TCP ? L"SOCK_STREAM" : L"SOCK_DGRAM") +
4632 L",0) or die \"socket port=$port: $!\\n\";";
4633 perlCommand +=
4634 L"my $address=$family==AF_INET ? sockaddr_in($port,INADDR_ANY) : "
4635 L"Socket::sockaddr_in6($port,Socket::inet_pton(AF_INET6,\"::\"));";
4636 perlCommand += L"if(bind($socket,$address)){";
4637 perlCommand += L"push @sockets,$socket;";
4638 perlCommand += L"}elsif($!{EADDRINUSE}){}else{die \"bind port=$port: $!\\n\";}";
4639 perlCommand += L"}";
4640 perlCommand += L"print \"successes=\",scalar(@sockets),\"\\nready\\n\";";
4641 perlCommand += L"while(1){sleep 1000}'";
4642
4643 auto cmd = LxssGenerateWslCommandLine(perlCommand.data());
4644 auto [readPipe, writePipe] = CreateSubprocessPipe(false, true);
4645 NetworkTests::unique_kill_process process(LxsstuStartProcess(cmd.data(), nullptr, writePipe.get(), writePipe.get()));
4646 writePipe.reset();
4647
4648 std::string output;
4649 VERIFY_IS_TRUE(NetworkTests::FindSubstring(readPipe, "ready", output));
4650
4651 constexpr std::string_view c_successPrefix = "successes=";
4652 const auto successOffset = output.find(c_successPrefix);
4653 THROW_HR_IF(E_FAIL, successOffset == std::string::npos);
4654 const auto successEnd = output.find('\n', successOffset);
4655 THROW_HR_IF(E_FAIL, successEnd == std::string::npos);
4656 const auto successes =
4657 std::stoi(output.substr(successOffset + c_successPrefix.size(), successEnd - successOffset - c_successPrefix.size()));
4658 VERIFY_ARE_EQUAL(expectedSuccesses, successes, L"Expected exactly the service-enforced number of reservations");
4659
4660 // Release every reservation so usage drops back below the cap.
4661 process.reset();
4662
4663 // The Linux port tracker only releases a reservation c_bind_timeout_seconds (60s) after the
4664 // socket is closed (see GnsPortTracker.cpp), so wait for the reservations to drain before the
4665 // next iteration reserves the same ports again.
4666 if (cycle + 1 < c_cycles)
4667 {
4668 std::this_thread::sleep_for(std::chrono::seconds(90));
4669 }
4670 }
4671 }
4672
4673 WSL2_TEST_METHOD(GuestBindToHostEphemeralRangeCapped)
4674 {
4675 MIRRORED_NETWORKING_TEST_ONLY();
4676
4677 // The service caps the number of host-ephemeral ports the guest can reserve at half the host
4678 // ephemeral range size, so it cannot exhaust the host's ephemeral ports. Shrink the host
4679 // TCP/UDP ephemeral ranges to the smallest allowed size (255 ports) so the cap is a small,
4680 // deterministic number (255 / 2 = 127), then verify the guest is denied once it reaches it.
4681 constexpr int c_ephemeralRangeSize = 255;
4682 constexpr int c_expectedCap = c_ephemeralRangeSize / 2;
4683
4684 // Save the current host ephemeral ranges so they can be restored at the end of the test.
4685 int originalTcpStart = 0, originalTcpCount = 0, originalUdpStart = 0, originalUdpCount = 0;
4686 std::tie(originalTcpStart, originalTcpCount) = QueryHostEphemeralRange(L"Get-NetTCPSetting");
4687 std::tie(originalUdpStart, originalUdpCount) = QueryHostEphemeralRange(L"Get-NetUDPSetting");
4688
4689 auto restoreRanges = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
4690 SetHostEphemeralRange(L"tcp", originalTcpStart, originalTcpCount);
4691 SetHostEphemeralRange(L"udp", originalUdpStart, originalUdpCount);
4692 });
4693
4694 // Use a low start port so the small host ephemeral window is less likely to overlap the
4695 // guest's reserved ephemeral range, which HNS assigns from the high port space.
4696 constexpr int c_hostEphemeralStart = 10000;
4697 constexpr int c_hostEphemeralEnd = c_hostEphemeralStart + c_ephemeralRangeSize - 1;
4698 SetHostEphemeralRange(L"tcp", c_hostEphemeralStart, c_ephemeralRangeSize);
4699 SetHostEphemeralRange(L"udp", c_hostEphemeralStart, c_ephemeralRangeSize);
4700
4701 // Force a restart of WSL so that it queries the new host ephemeral ranges.
4702 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4703 RestartWslService();
4704
4705 WaitForMirroredStateInLinux();
4706
4707 // TCP and UDP are capped independently, but each cap is shared by IPv4 and IPv6.
4708 VerifyHostEphemeralRangeCap(IPPROTO_TCP, c_hostEphemeralStart, c_hostEphemeralEnd, c_expectedCap);
4709 VerifyHostEphemeralRangeCap(IPPROTO_UDP, c_hostEphemeralStart, c_hostEphemeralEnd, c_expectedCap);
4710 }
4711
4712 WSL2_TEST_METHOD(NonRootNamespaceEphemeralBind)
4713 {
4714 MIRRORED_NETWORKING_TEST_ONLY();
4715
4716 // Because the test creates a new network namespace, the resolv.conf from the root network namespace
4717 // is copied in the resolv.conf of the new network namespace. The DNS tunneling listener running in the root namespace
4718 // needs to be accessible from the new namespace, so it can't use a 127* IP
4719 m_config->Update(LxssGenerateTestConfig(
4720 {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .dnsTunneling = true, .dnsTunnelingIpAddress = L"10.255.255.254"}));
4721 WaitForMirroredStateInLinux();
4722
4723 NetworkTests::TestNonRootNamespaceEphemeralBind();
4724 }
4725
4726 // Verifies that in mirrored mode, Windows can connect to a listener running in a Linux network namespace different from
4727 // the Linux root network namespace.
4728 WSL2_TEST_METHOD(PortForwardingToNonRootNamespace)
4729 {
4730 MIRRORED_NETWORKING_TEST_ONLY();
4731
4732 m_config->Update(LxssGenerateTestConfig(
4733 {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
4734 WaitForMirroredStateInLinux();
4735
4736 // We list the IPv4 addresses mirrored in Linux and use the first one we find in the test
4737 std::vector<NetworkTests::InterfaceState> interfaceStates = NetworkTests::GetAllInterfaceStates();
4738 std::wstring ipAddress;
4739
4740 for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
4741 {
4742 for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
4743 {
4744 // The IP used for DNS tunneling is not intended for guest<->host communication
4745 if (j->Address != c_dnsTunnelingDefaultIp)
4746 {
4747 ipAddress = j->Address;
4748 break;
4749 }
4750 }
4751 }
4752
4753 // Get the forwarding state.
4754 auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
4755 std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
4756
4757 // Clean up the below configurations.
4758 auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
4759 LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
4760 LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
4761 LxsstuLaunchWsl(L"--system --user root nft flush chain nat PREROUTING");
4762 LxsstuLaunchWsl(L"ip link delete veth-test-br");
4763 LxsstuLaunchWsl(L"ip link delete testbridge");
4764 LxsstuLaunchWsl(L"ip netns delete testns");
4765 });
4766
4767 // Set up a networking namespace and provide it external network access via a bridge, veth
4768 // pair, SRCNAT iptables rule and forwarding.
4769 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
4770 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
4771 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
4772 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
4773 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
4774 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
4775 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
4776 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
4777 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
4778 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
4779 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
4780 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
4781 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
4782 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
4783 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
4784
4785 // Add rule for port forwarding traffic with destination port 8080 to port 80 in the new namespace
4786 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat PREROUTING { type nat hook prerouting priority dstnat; }\""), 0);
4787 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat PREROUTING tcp dport 8080 dnat to 192.168.15.2:80"), 0);
4788
4789 // Start listeners in root namespace on port 8080 and new namespace on port 80
4790 SOCKADDR_INET rootListenerAddr = wsl::windows::common::string::StringToSockAddrInet(L"0.0.0.0");
4791 SS_PORT(&rootListenerAddr) = htons(8080);
4792 NetworkTests::GuestListener rootListener(rootListenerAddr, IPPROTO_TCP);
4793
4794 SOCKADDR_INET namespaceListenerAddr = wsl::windows::common::string::StringToSockAddrInet(L"0.0.0.0");
4795 SS_PORT(&namespaceListenerAddr) = htons(80);
4796 NetworkTests::GuestListener namespaceListener(namespaceListenerAddr, IPPROTO_TCP, L"testns");
4797
4798 // Verify Windows can connect to port 8080
4799 SOCKADDR_INET serverAddr = wsl::windows::common::string::StringToSockAddrInet(ipAddress);
4800 SS_PORT(&serverAddr) = htons(8080);
4801
4802 wil::unique_socket clientSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
4803 VERIFY_ARE_NOT_EQUAL(clientSocket.get(), INVALID_SOCKET);
4804
4805 VERIFY_ARE_EQUAL(connect(clientSocket.get(), reinterpret_cast<SOCKADDR*>(&serverAddr), sizeof(serverAddr)), 0);
4806 }
4807
4808 WSL2_TEST_METHOD(LinuxNonRootNamespaceConnectToWindowsHost)
4809 {
4810 MIRRORED_NETWORKING_TEST_ONLY();
4811
4812 m_config->Update(LxssGenerateTestConfig(
4813 {.guiApplications = true, .networkingMode = wsl::core::NetworkingMode::Mirrored, .hostAddressLoopback = true}));
4814 WaitForMirroredStateInLinux();
4815
4816 // We list the IPv4 addresses mirrored in Linux and use the first one we find in the test
4817 std::vector<NetworkTests::InterfaceState> interfaceStates = NetworkTests::GetAllInterfaceStates();
4818 std::wstring ipAddress;
4819
4820 for (auto i = interfaceStates.begin(); i != interfaceStates.end(); ++i)
4821 {
4822 for (auto j = i->V4Addresses.begin(); j != i->V4Addresses.end(); ++j)
4823 {
4824 // The IP used for DNS tunneling is not intended for guest<->host communication
4825 if (j->Address != c_dnsTunnelingDefaultIp)
4826 {
4827 ipAddress = j->Address;
4828 break;
4829 }
4830 }
4831 }
4832
4833 // Get the forwarding state.
4834 auto [oldIpForwardState, _1] = LxsstuLaunchWslAndCaptureOutput(L"cat /proc/sys/net/ipv4/ip_forward", 0);
4835 std::wstring restoreIpForwardCommand = std::format(L"sysctl -w net.ipv4.ip_forward={}", oldIpForwardState.c_str());
4836
4837 // Clean up the below configurations.
4838 auto revertConfig = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&restoreIpForwardCommand] {
4839 LxsstuLaunchWsl(restoreIpForwardCommand.c_str());
4840 LxsstuLaunchWsl(L"--system --user root nft flush chain nat POSTROUTING");
4841 LxsstuLaunchWsl(L"ip link delete veth-test-br");
4842 LxsstuLaunchWsl(L"ip link delete testbridge");
4843 LxsstuLaunchWsl(L"ip netns delete testns");
4844 });
4845
4846 // Set up a networking namespace and provide it external network access via a bridge, veth
4847 // pair, SRCNAT iptables rule and forwarding.
4848 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip netns add testns"), 0);
4849 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add testbridge type bridge"), 0);
4850 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link add veth-test type veth peer name veth-test-br"), 0);
4851 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test netns testns"), 0);
4852 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br master testbridge"), 0);
4853 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns link set veth-test up"), 0);
4854 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set veth-test-br up"), 0);
4855 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip link set testbridge up"), 0);
4856 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns addr add 192.168.15.2/24 dev veth-test"), 0);
4857 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip addr add 192.168.15.1/24 dev testbridge"), 0);
4858 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"ip -n testns route add default via 192.168.15.1 dev veth-test"), 0);
4859 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add table nat"), 0);
4860 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft \"add chain nat POSTROUTING { type nat hook postrouting priority srcnat; }\""), 0);
4861 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--system --user root nft add rule nat POSTROUTING ip saddr 192.168.15.0/24 oif != testbridge masquerade"), 0);
4862 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"sysctl -w net.ipv4.ip_forward=1"), 0);
4863
4864 // Create a listener on the Windows host on port 1234
4865 SOCKADDR_INET addr = wsl::windows::common::string::StringToSockAddrInet(ipAddress);
4866 SS_PORT(&addr) = htons(1234);
4867
4868 const wil::unique_socket listenSocket(socket(addr.si_family, SOCK_STREAM, IPPROTO_TCP));
4869 VERIFY_ARE_NOT_EQUAL(listenSocket.get(), INVALID_SOCKET);
4870 VERIFY_ARE_NOT_EQUAL(bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr)), SOCKET_ERROR);
4871 VERIFY_ARE_NOT_EQUAL(listen(listenSocket.get(), SOMAXCONN), SOCKET_ERROR);
4872
4873 // Verify the new network namespace can connect to the Windows host listener
4874 auto [output, warnings] = LxsstuLaunchWslAndCaptureOutput(
4875 L"ip netns exec testns socat -dd tcp-connect:" + ipAddress + L":1234 create:/tmp/nonexistent", 1);
4876 LogInfo("output %s", output.c_str());
4877 LogInfo("warnings %s", warnings.c_str());
4878 VERIFY_ARE_NOT_EQUAL(warnings.find(L"starting data transfer loop"), std::string::npos);
4879 }
4880
4881 WSL2_TEST_METHOD(ResolvConf)
4882 {
4883 MIRRORED_NETWORKING_TEST_ONLY();
4884
4885 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4886 WaitForMirroredStateInLinux();
4887
4888 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
4889 const std::wregex pattern(L"(.|\n)*nameserver [0-9\\. ]+(.|\n)*", std::regex::extended);
4890
4891 VERIFY_IS_TRUE(std::regex_match(out, pattern));
4892 }
4893
4894 WSL2_TEST_METHOD(NetworkSettings)
4895 {
4896 MIRRORED_NETWORKING_TEST_ONLY();
4897
4898 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4899 WaitForMirroredStateInLinux();
4900
4901 struct NetworkSetting
4902 {
4903 const std::wstring Path;
4904 const std::wstring ExpectedValue;
4905 };
4906
4907 std::vector<NetworkSetting> settings{
4908 {L"/proc/sys/net/ipv6/conf/all/accept_ra", L"0\n"},
4909 {L"/proc/sys/net/ipv6/conf/default/accept_ra", L"0\n"},
4910 {L"/proc/sys/net/ipv6/conf/all/dad_transmits", L"0\n"},
4911 {L"/proc/sys/net/ipv6/conf/default/dad_transmits", L"0\n"},
4912 {L"/proc/sys/net/ipv6/conf/all/autoconf", L"0\n"},
4913 {L"/proc/sys/net/ipv6/conf/default/autoconf", L"0\n"},
4914 {L"/proc/sys/net/ipv6/conf/all/addr_gen_mode", L"1\n"},
4915 {L"/proc/sys/net/ipv6/conf/default/addr_gen_mode", L"1\n"},
4916 {L"/proc/sys/net/ipv6/conf/all/use_tempaddr", L"0\n"},
4917 {L"/proc/sys/net/ipv6/conf/default/use_tempaddr", L"0\n"},
4918 {L"/proc/sys/net/ipv4/conf/all/arp_filter", L"1\n"},
4919 {L"/proc/sys/net/ipv4/conf/all/rp_filter", L"0\n"},
4920 };
4921
4922 settings.push_back({L"/proc/sys/net/ipv4/conf/" + NetworkTests::GetGelNicDeviceName() + L"/rp_filter", L"0\n"});
4923
4924 for (const auto& setting : settings)
4925 {
4926 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat " + setting.Path);
4927 LogInfo("%ls", (setting.Path + L" : " + out).c_str());
4928 VERIFY_ARE_EQUAL(setting.ExpectedValue, out);
4929 }
4930 }
4931
4932 WSL2_TEST_METHOD(FirewallRulesExpectedBlock)
4933 {
4934 HYPERV_FIREWALL_TEST_ONLY();
4935 MIRRORED_NETWORKING_TEST_ONLY();
4936
4937 SKIP_TEST_UNSTABLE();
4938
4939 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4940 WaitForMirroredStateInLinux();
4941
4942 NetworkTests::ValidateInitialFirewallState(NetworkTests::FirewallObjects::Required);
4943 NetworkTests::FirewallRuleBlockedTests(NetworkTests::FirewallTestConnectivity::Blocked);
4944 }
4945
4946 WSL2_TEST_METHOD(FirewallRulesExpectedAllow)
4947 {
4948 HYPERV_FIREWALL_TEST_ONLY();
4949 MIRRORED_NETWORKING_TEST_ONLY();
4950
4951 SKIP_TEST_UNSTABLE();
4952
4953 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4954 WaitForMirroredStateInLinux();
4955
4956 NetworkTests::ValidateInitialFirewallState(NetworkTests::FirewallObjects::Required);
4957 NetworkTests::FirewallRuleAllowedTests(NetworkTests::FirewallTestConnectivity::Allowed);
4958 }
4959
4960 WSL2_TEST_METHOD(FirewallRulesEnabledSetting)
4961 {
4962 HYPERV_FIREWALL_TEST_ONLY();
4963 MIRRORED_NETWORKING_TEST_ONLY();
4964
4965 SKIP_TEST_UNSTABLE();
4966
4967 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4968 WaitForMirroredStateInLinux();
4969
4970 NetworkTests::ValidateInitialFirewallState(NetworkTests::FirewallObjects::Required);
4971 NetworkTests::FirewallSettingEnabledTests(true);
4972 }
4973
4974 WSL2_TEST_METHOD(ConnectivityCheckTestDefaultSuccess)
4975 {
4976 MIRRORED_NETWORKING_TEST_ONLY();
4977
4978 SKIP_TEST_UNSTABLE();
4979
4980 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
4981 WaitForMirroredStateInLinux();
4982
4983 const auto coInit = wil::CoInitializeEx();
4984 const wil::com_ptr<INetworkListManager> networkListManager = wil::CoCreateInstance<NetworkListManager, INetworkListManager>();
4985 VERIFY_IS_NOT_NULL(networkListManager.get());
4986 NLM_CONNECTIVITY hostConnectivity{};
4987 VERIFY_SUCCEEDED(networkListManager->GetConnectivity(&hostConnectivity));
4988
4989 // Windows
4990 const wsl::shared::conncheck::ConnCheckResult hostResult =
4991 wsl::shared::conncheck::CheckConnection("www.msftconnecttest.com", "ipv6.msftconnecttest.com", "80");
4992
4993 if (hostConnectivity & NLM_CONNECTIVITY_IPV4_INTERNET)
4994 {
4995 VERIFY_ARE_EQUAL(wsl::shared::conncheck::ConnCheckStatus::Success, hostResult.Ipv4Status);
4996 }
4997 else
4998 {
4999 // one of the 2 expected runtime failures
5000 VERIFY_IS_TRUE(
Showing first 5,000 of 5,726 lines. View raw