2729
{
2730
if (std::regex_search(line, match, defaultRoutePattern) && match.size() >= 3)
2731
{
2732
- VERIFY_IS_FALSE(state.DefaultRoute.has_value());
2732
+ if (state.DefaultRoute.has_value())
2733
+ {
2734
+ continue;
2735
+ }
2736
2737
state.DefaultRoute = {{match.str(1), match.str(2), {}, match.size() > 4 && match[4].matched ? std::stoi(match.str(4)) : 0}};
2738
}
2757
return GetRoutingTableState(out, defaultRoutePattern, routePattern);
2758
}
2759
2760
+ static void WaitForIpv6DefaultRoute()
2761
+ {
2762
+ const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(30);
2763
+ while (std::chrono::steady_clock::now() < timeout)
2764
+ {
2765
+ auto state = GetIpv6RoutingTableState();
2766
+ if (state.DefaultRoute.has_value())
2767
+ {
2768
+ return;
2769
+ }
2770
+
2771
+ LogInfo("Waiting for IPv6 default route...");
2772
+ std::this_thread::sleep_for(std::chrono::seconds(1));
2773
+ }
2774
+
2775
+ VERIFY_FAIL(L"Timed out waiting for IPv6 default route");
2776
+ }
2777
+
2778
static RoutingTableState GetIpv6RoutingTableState()
2779
{
2780
auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"ip -6 route show");
3276
NET_LUID interfaceLuid{};
3277
CONTINUE_IF_FAILED_WIN32(ConvertInterfaceGuidToLuid(&interfaceGuid, &interfaceLuid));
3278
3258
- for (const IP_ADAPTER_ADDRESSES* adapter = adapterAddresses.get(); adapter != nullptr; adapter = adapter->Next)
3279
+ for (auto* adapter = reinterpret_cast<const IP_ADAPTER_ADDRESSES*>(adapterAddresses.data()); adapter != nullptr;
3280
+ adapter = adapter->Next)
3281
{
3282
if (interfaceLuid.Value == adapter->Luid.Value && adapter->FirstUnicastAddress != nullptr && adapter->FirstGatewayAddress != nullptr)
3283
{
3289
return false;
3290
}
3291
3270
- static std::unique_ptr<IP_ADAPTER_ADDRESSES> GetAdapterAddresses(ADDRESS_FAMILY family)
3292
+ static bool HostHasIpv6DnsServers()
3293
+ {
3294
+ ULONG bufferSize = 0;
3295
+ constexpr ULONG flags = GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS;
3296
+ std::vector<BYTE> buffer;
3297
+ ULONG result = GetAdaptersAddresses(AF_INET6, flags, nullptr, nullptr, &bufferSize);
3298
+ while (result == ERROR_BUFFER_OVERFLOW)
3299
+ {
3300
+ buffer.resize(bufferSize);
3301
+ result = GetAdaptersAddresses(AF_INET6, flags, nullptr, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data()), &bufferSize);
3302
+ }
3303
+
3304
+ if (result != NO_ERROR)
3305
+ {
3306
+ return false;
3307
+ }
3308
+
3309
+ DWORD bestIndex = 0;
3310
+ SOCKADDR_IN6 dest{};
3311
+ dest.sin6_family = AF_INET6;
3312
+ InetPtonW(AF_INET6, L"2001:4860:4860::8888", &dest.sin6_addr);
3313
+
3314
+ if (GetBestInterfaceEx(reinterpret_cast<SOCKADDR*>(&dest), &bestIndex) != NO_ERROR)
3315
+ {
3316
+ return false;
3317
+ }
3318
+
3319
+ for (auto* adapter = reinterpret_cast<const IP_ADAPTER_ADDRESSES*>(buffer.data()); adapter != nullptr; adapter = adapter->Next)
3320
+ {
3321
+ if (adapter->IfIndex != bestIndex)
3322
+ {
3323
+ continue;
3324
+ }
3325
+
3326
+ for (auto* dns = adapter->FirstDnsServerAddress; dns != nullptr; dns = dns->Next)
3327
+ {
3328
+ if (dns->Address.lpSockaddr->sa_family == AF_INET6)
3329
+ {
3330
+ return true;
3331
+ }
3332
+ }
3333
+ }
3334
+
3335
+ return false;
3336
+ }
3337
+
3338
+ static std::vector<BYTE> GetAdapterAddresses(ADDRESS_FAMILY family)
3339
{
3272
- ULONG result;
3340
constexpr ULONG flags =
3341
(GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS);
3342
ULONG bufferSize = 0;
3276
- std::unique_ptr<IP_ADAPTER_ADDRESSES> buffer;
3277
-
3278
- while ((result = GetAdaptersAddresses(family, flags, nullptr, buffer.get(), &bufferSize)) == ERROR_BUFFER_OVERFLOW)
3343
+ std::vector<BYTE> buffer;
3344
+ ULONG result = GetAdaptersAddresses(family, flags, nullptr, nullptr, &bufferSize);
3345
+ while (result == ERROR_BUFFER_OVERFLOW)
3346
{
3280
- buffer.reset(static_cast<IP_ADAPTER_ADDRESSES*>(malloc(bufferSize)));
3281
- VERIFY_IS_NOT_NULL(buffer.get());
3347
+ buffer.resize(bufferSize);
3348
+ result = GetAdaptersAddresses(family, flags, nullptr, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data()), &bufferSize);
3349
}
3350
3351
VERIFY_WIN32_SUCCEEDED(result);
4633
return;
4634
}
4635
4636
+ NetworkTests::WaitForIpv6DefaultRoute();
4637
+
4638
NetworkTests::GuestClient(L"tcp6-connect:bing.com:80");
4639
}
4640
4653
4654
VERIFY_IS_TRUE(std::regex_match(out, pattern));
4655
4587
- // Verify that /etc/resolv.conf contains a 'search' line with DNS suffixes
4588
- VERIFY_IS_TRUE(out.find(L"search ") != std::wstring::npos);
4656
+ // Verify that /etc/resolv.conf contains a 'search' line if the host has DNS suffixes
4657
+ auto [suffixOut, suffixErr] = LxsstuLaunchPowershellAndCaptureOutput(
4658
+ L"(@((Get-DnsClientGlobalSetting).SuffixSearchList) + @((Get-DnsClient).ConnectionSpecificSuffix) | Where-Object "
4659
+ L"{$_}).Count");
4660
+ if (_wtoi(suffixOut.c_str()) > 0)
4661
+ {
4662
+ VERIFY_IS_TRUE(out.find(L"search ") != std::wstring::npos);
4663
+ }
4664
}
4665
4666
TEST_METHOD(GuestPortIsReleased)
4677
NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false);
4678
}
4679
4605
- const wil::unique_socket listenSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
4606
- VERIFY_IS_TRUE(!!listenSocket);
4680
+ wsl::shared::retry::RetryWithTimeout<void>(
4681
+ [&]() {
4682
+ const wil::unique_socket listenSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
4683
+ THROW_HR_IF(E_ABORT, !listenSocket);
4684
4608
- SOCKADDR_IN Address{};
4609
- Address.sin_family = AF_INET;
4610
- Address.sin_port = htons(1234);
4611
-
4612
- const auto timeout = std::chrono::steady_clock::now() + std::chrono::minutes(2);
4613
-
4614
- bool bound = false;
4615
- while (!bound && std::chrono::steady_clock::now() < timeout)
4616
- {
4617
- bound = bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) != SOCKET_ERROR;
4618
- std::this_thread::sleep_for(std::chrono::seconds(1));
4619
- }
4620
-
4621
- VERIFY_IS_TRUE(bound);
4685
+ SOCKADDR_IN Address{};
4686
+ Address.sin_family = AF_INET;
4687
+ Address.sin_port = htons(1234);
4688
+ THROW_HR_IF(E_FAIL, bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) == SOCKET_ERROR);
4689
+ },
4690
+ std::chrono::seconds(1),
4691
+ std::chrono::minutes(2));
4692
}
4693
4694
TEST_METHOD(LoopbackGuestToHost)
4760
m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy, .autoProxy = true}));
4761
NetworkTests::VerifyHttpProxySimple();
4762
}
4763
+
4764
+ TEST_METHOD(ConfigurationV6)
4765
+ {
4766
+ VIRTIOPROXY_TEST_ONLY();
4767
+
4768
+ m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy}));
4769
+
4770
+ if (!NetworkTests::HostHasInternetConnectivity(AF_INET6))
4771
+ {
4772
+ LogSkipped("Host does not have IPv6 internet connectivity. Skipping...");
4773
+ return;
4774
+ }
4775
+
4776
+ // Wait for the device host to send an IPv6 Router Advertisement
4777
+ // before querying the interface state.
4778
+ NetworkTests::WaitForIpv6DefaultRoute();
4779
+
4780
+ const auto state = NetworkTests::GetInterfaceState(L"eth0");
4781
+
4782
+ // Verify that the guest has a global IPv6 address assigned
4783
+ VERIFY_IS_FALSE(state.V6Addresses.empty());
4784
+
4785
+ // Verify that the guest has an IPv6 default gateway
4786
+ VERIFY_IS_TRUE(state.V6Gateway.has_value());
4787
+
4788
+ // Verify the guest IPv6 address matches the host global IPv6 address
4789
+ const auto adapterAddresses = NetworkTests::GetAdapterAddresses(AF_INET6);
4790
+ DWORD bestIndex = 0;
4791
+ SOCKADDR_IN6 dest{};
4792
+ dest.sin6_family = AF_INET6;
4793
+ InetPtonW(AF_INET6, L"2001:4860:4860::8888", &dest.sin6_addr);
4794
+ VERIFY_ARE_EQUAL(NO_ERROR, GetBestInterfaceEx(reinterpret_cast<SOCKADDR*>(&dest), &bestIndex));
4795
+
4796
+ for (auto* adapter = reinterpret_cast<const IP_ADAPTER_ADDRESSES*>(adapterAddresses.data()); adapter != nullptr;
4797
+ adapter = adapter->Next)
4798
+ {
4799
+ if (adapter->IfIndex != bestIndex)
4800
+ {
4801
+ continue;
4802
+ }
4803
+
4804
+ for (auto* unicast = adapter->FirstUnicastAddress; unicast != nullptr; unicast = unicast->Next)
4805
+ {
4806
+ if (unicast->Address.lpSockaddr->sa_family != AF_INET6)
4807
+ {
4808
+ continue;
4809
+ }
4810
+
4811
+ const auto& sin6 = *reinterpret_cast<SOCKADDR_IN6*>(unicast->Address.lpSockaddr);
4812
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr) || IN6_IS_ADDR_LOOPBACK(&sin6.sin6_addr))
4813
+ {
4814
+ continue;
4815
+ }
4816
+
4817
+ SOCKADDR_INET hostAddr{};
4818
+ hostAddr.Ipv6 = sin6;
4819
+ const auto hostAddrString = wsl::windows::common::string::SockAddrInetToWstring(hostAddr);
4820
+
4821
+ // The host address may not be at index 0 due to SLAAC addresses from RA
4822
+ bool addressFound = false;
4823
+ for (const auto& v6Addr : state.V6Addresses)
4824
+ {
4825
+ if (v6Addr.Address == hostAddrString)
4826
+ {
4827
+ addressFound = true;
4828
+ break;
4829
+ }
4830
+ }
4831
+ VERIFY_IS_TRUE(addressFound);
4832
+ break;
4833
+ }
4834
+
4835
+ break;
4836
+ }
4837
+ }
4838
+
4839
+ TEST_METHOD(DnsResolutionDigV6)
4840
+ {
4841
+ VIRTIOPROXY_TEST_ONLY();
4842
+
4843
+ m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy}));
4844
+
4845
+ if (!NetworkTests::HostHasInternetConnectivity(AF_INET6))
4846
+ {
4847
+ LogSkipped("Host does not have IPv6 internet connectivity. Skipping...");
4848
+ return;
4849
+ }
4850
+
4851
+ // Test AAAA record resolution (IPv6) with both UDP and TCP
4852
+ NetworkTests::VerifyDigDnsResolution(L"dig +short +time=5 AAAA bing.com");
4853
+ NetworkTests::VerifyDigDnsResolution(L"dig +tcp +short +time=5 AAAA bing.com");
4854
+ }
4855
+
4856
+ TEST_METHOD(GuestPortIsReleasedV6)
4857
+ {
4858
+ VIRTIOPROXY_TEST_ONLY();
4859
+
4860
+ m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy}));
4861
+
4862
+ // Make sure the VM doesn't time out
4863
+ WslKeepAlive keepAlive;
4864
+
4865
+ {
4866
+ auto guestProcess = NetworkTests::BindGuestPort(L"TCP6-LISTEN:1234,bind=::", true);
4867
+ NetworkTests::BindHostPort(1234, SOCK_STREAM, IPPROTO_TCP, false, true);
4868
+ }
4869
+
4870
+ wsl::shared::retry::RetryWithTimeout<void>(
4871
+ [&]() {
4872
+ const wil::unique_socket listenSocket(socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP));
4873
+ THROW_HR_IF(E_ABORT, !listenSocket);
4874
+
4875
+ SOCKADDR_IN6 Address{};
4876
+ Address.sin6_family = AF_INET6;
4877
+ Address.sin6_port = htons(1234);
4878
+ THROW_HR_IF(E_FAIL, bind(listenSocket.get(), reinterpret_cast<SOCKADDR*>(&Address), sizeof(Address)) == SOCKET_ERROR);
4879
+ },
4880
+ std::chrono::seconds(1),
4881
+ std::chrono::minutes(2));
4882
+ }
4883
+
4884
+ TEST_METHOD(ConfigurationV6DnsServers)
4885
+ {
4886
+ VIRTIOPROXY_TEST_ONLY();
4887
+
4888
+ m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::VirtioProxy}));
4889
+
4890
+ if (!NetworkTests::HostHasInternetConnectivity(AF_INET6))
4891
+ {
4892
+ LogSkipped("Host does not have IPv6 internet connectivity. Skipping...");
4893
+ return;
4894
+ }
4895
+
4896
+ if (!NetworkTests::HostHasIpv6DnsServers())
4897
+ {
4898
+ LogSkipped("Host does not have IPv6 DNS servers configured. Skipping...");
4899
+ return;
4900
+ }
4901
+
4902
+ auto [out, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /etc/resolv.conf", 0);
4903
+
4904
+ // Verify that /etc/resolv.conf contains at least one IPv6 nameserver
4905
+ const std::wregex v6Pattern(L"(.|\\n)*nameserver [0-9a-fA-F:]+\\n(.|\\n)*");
4906
+ VERIFY_IS_TRUE(std::regex_match(out, v6Pattern));
4907
+ }
4908
};
4909
} // namespace NetworkTests