| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include "base/functional/callback.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "net/base/address_family.h" |
| #include "net/base/features.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/network_anonymization_key.h" |
| #include "net/dns/address_sorter.h" |
| #include "net/dns/dns_task_results_manager.h" |
| #include "net/dns/dns_test_util.h" |
| #include "net/dns/host_resolver.h" |
| #include "net/dns/host_resolver_manager_service_endpoint_request_impl.h" |
| #include "net/dns/host_resolver_manager_unittest.h" |
| #include "net/dns/host_resolver_results_test_util.h" |
| #include "net/dns/public/host_resolver_results.h" |
| #include "net/dns/public/host_resolver_source.h" |
| #include "net/dns/resolve_context.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/test/gtest_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/scheme_host_port.h" |
| |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| using ::testing::UnorderedElementsAre; |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| |
| namespace net { |
| |
| using ServiceEndpointRequest = HostResolver::ServiceEndpointRequest; |
| using ResolveHostRequest = HostResolver::ResolveHostRequest; |
| using ResolveHostParameters = HostResolver::ResolveHostParameters; |
| |
| namespace { |
| |
| IPEndPoint MakeIPEndPoint(std::string_view ip_literal, uint16_t port = 0) { |
| std::optional<IPAddress> ip = IPAddress::FromIPLiteral(std::move(ip_literal)); |
| return IPEndPoint(*ip, port); |
| } |
| |
| // Sorts endpoints using IPAddress's comparator. |
| class FakeAddressSorter : public AddressSorter { |
| public: |
| void Sort(const std::vector<IPEndPoint>& endpoints, |
| CallbackType callback) const override { |
| std::vector<IPEndPoint> sorted = endpoints; |
| std::sort(sorted.begin(), sorted.end(), |
| [](const IPEndPoint& a, const IPEndPoint& b) { |
| return a.address() < b.address(); |
| }); |
| std::move(callback).Run(true, sorted); |
| } |
| }; |
| |
| class Requester : public ServiceEndpointRequest::Delegate { |
| public: |
| explicit Requester(std::unique_ptr<ServiceEndpointRequest> request) |
| : request_(std::move(request)) {} |
| |
| ~Requester() override = default; |
| |
| // ServiceEndpointRequest::Delegate overrides: |
| |
| void OnServiceEndpointsUpdated() override { |
| if (on_updated_callback_) { |
| std::move(on_updated_callback_).Run(); |
| } |
| } |
| |
| void OnServiceEndpointRequestFinished(int rv) override { |
| SetFinishedResult(rv); |
| |
| if (on_finished_callback_) { |
| std::move(on_finished_callback_).Run(); |
| } |
| |
| if (wait_for_finished_callback_) { |
| std::move(wait_for_finished_callback_).Run(); |
| } |
| } |
| |
| int Start() { |
| int rv = request_->Start(this); |
| if (rv != ERR_IO_PENDING) { |
| SetFinishedResult(rv); |
| } |
| return rv; |
| } |
| |
| void CancelRequest() { request_.reset(); } |
| |
| void CancelRequestOnUpdated() { |
| SetOnUpdatedCallback(base::BindLambdaForTesting([&] { CancelRequest(); })); |
| } |
| |
| void CancelRequestOnFinished() { |
| SetOnFinishedCallback(base::BindLambdaForTesting([&] { CancelRequest(); })); |
| } |
| |
| void SetOnUpdatedCallback(base::OnceClosure callback) { |
| CHECK(!finished_result_); |
| CHECK(!on_updated_callback_); |
| on_updated_callback_ = std::move(callback); |
| } |
| |
| void SetOnFinishedCallback(base::OnceClosure callback) { |
| CHECK(!finished_result_); |
| CHECK(!on_finished_callback_); |
| on_finished_callback_ = std::move(callback); |
| } |
| |
| void WaitForFinished() { |
| CHECK(!finished_result_); |
| CHECK(!wait_for_finished_callback_); |
| base::RunLoop run_loop; |
| wait_for_finished_callback_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| ServiceEndpointRequest* request() const { return request_.get(); } |
| |
| std::optional<int> finished_result() const { return finished_result_; } |
| |
| const std::vector<ServiceEndpoint>& finished_endpoints() const { |
| CHECK(finished_result_.has_value()); |
| return finished_endpoints_; |
| } |
| |
| private: |
| void SetFinishedResult(int rv) { |
| CHECK(!finished_result_); |
| finished_result_ = rv; |
| |
| if (request_) { |
| finished_endpoints_ = request_->GetEndpointResults(); |
| } |
| } |
| |
| std::unique_ptr<ServiceEndpointRequest> request_; |
| |
| std::optional<int> finished_result_; |
| std::vector<ServiceEndpoint> finished_endpoints_; |
| |
| base::OnceClosure wait_for_finished_callback_; |
| base::OnceClosure on_updated_callback_; |
| base::OnceClosure on_finished_callback_; |
| }; |
| |
| class LegacyRequester { |
| public: |
| explicit LegacyRequester(std::unique_ptr<ResolveHostRequest> request) |
| : request_(std::move(request)) {} |
| |
| ~LegacyRequester() = default; |
| |
| int Start() { |
| return request_->Start( |
| base::BindOnce(&LegacyRequester::OnComplete, base::Unretained(this))); |
| } |
| |
| void CancelRequest() { request_.reset(); } |
| |
| std::optional<int> complete_result() const { return complete_result_; } |
| |
| private: |
| void OnComplete(int rv) { complete_result_ = rv; } |
| |
| std::unique_ptr<ResolveHostRequest> request_; |
| std::optional<int> complete_result_; |
| }; |
| |
| } // namespace |
| |
| class HostResolverServiceEndpointRequestTest |
| : public HostResolverManagerDnsTest { |
| public: |
| HostResolverServiceEndpointRequestTest() { |
| feature_list_.InitAndEnableFeature(features::kUseServiceEndpointRequest); |
| } |
| |
| ~HostResolverServiceEndpointRequestTest() override = default; |
| |
| protected: |
| void SetUp() override { |
| HostResolverManagerDnsTest::SetUp(); |
| |
| // MockHostResolverProc resolves all requests to "127.0.0.1" when there is |
| // no rule. Add a rule to prevent the default behavior. |
| proc_->AddRule(std::string(), ADDRESS_FAMILY_UNSPECIFIED, "192.0.2.1"); |
| } |
| |
| void SetDnsRules(MockDnsClientRuleList rules) { |
| CreateResolver(); |
| UseMockDnsClient(CreateValidDnsConfig(), std::move(rules)); |
| } |
| |
| void UseNoDomanDnsRules(const std::string& host) { |
| MockDnsClientRuleList rules; |
| AddDnsRule(&rules, host, dns_protocol::kTypeA, |
| MockDnsClientRule::ResultType::kNoDomain, /*delay=*/false); |
| AddDnsRule(&rules, host, dns_protocol::kTypeAAAA, |
| MockDnsClientRule::ResultType::kNoDomain, /*delay=*/false); |
| SetDnsRules(std::move(rules)); |
| } |
| |
| void UseNonDelayedDnsRules(const std::string& host) { |
| MockDnsClientRuleList rules; |
| AddDnsRule(&rules, host, dns_protocol::kTypeA, |
| MockDnsClientRule::ResultType::kOk, /*delay=*/false); |
| AddDnsRule(&rules, host, dns_protocol::kTypeAAAA, |
| MockDnsClientRule::ResultType::kOk, /*delay=*/false); |
| SetDnsRules(std::move(rules)); |
| } |
| |
| void UseIpv4DelayedDnsRules(const std::string& host) { |
| MockDnsClientRuleList rules; |
| AddDnsRule(&rules, host, dns_protocol::kTypeA, |
| MockDnsClientRule::ResultType::kOk, /*delay=*/true); |
| AddDnsRule(&rules, host, dns_protocol::kTypeAAAA, |
| MockDnsClientRule::ResultType::kOk, /*delay=*/false); |
| SetDnsRules(std::move(rules)); |
| } |
| |
| void UseIpv6DelayedDnsRules(const std::string& host) { |
| MockDnsClientRuleList rules; |
| AddDnsRule(&rules, host, dns_protocol::kTypeA, |
| MockDnsClientRule::ResultType::kOk, /*delay=*/false); |
| AddDnsRule(&rules, host, dns_protocol::kTypeAAAA, |
| MockDnsClientRule::ResultType::kOk, /*delay=*/true); |
| SetDnsRules(std::move(rules)); |
| } |
| |
| void UseHttpsDelayedDnsRules(const std::string& host) { |
| MockDnsClientRuleList rules; |
| AddDnsRule(&rules, host, dns_protocol::kTypeA, |
| MockDnsClientRule::ResultType::kOk, /*delay=*/false); |
| AddDnsRule(&rules, host, dns_protocol::kTypeAAAA, |
| MockDnsClientRule::ResultType::kOk, /*delay=*/false); |
| |
| std::vector<DnsResourceRecord> records = { |
| BuildTestHttpsServiceRecord(host, /*priority=*/1, /*service_name=*/".", |
| /*params=*/{})}; |
| rules.emplace_back(host, dns_protocol::kTypeHttps, |
| /*secure=*/false, |
| MockDnsClientRule::Result(BuildTestDnsResponse( |
| host, dns_protocol::kTypeHttps, records)), |
| /*delay=*/true); |
| SetDnsRules(std::move(rules)); |
| } |
| |
| std::unique_ptr<ServiceEndpointRequest> CreateRequest( |
| std::string_view host, |
| ResolveHostParameters parameters = ResolveHostParameters()) { |
| return resolver_->CreateServiceEndpointRequest( |
| url::SchemeHostPort(GURL(host)), NetworkAnonymizationKey(), |
| NetLogWithSource(), std::move(parameters), resolve_context_.get()); |
| } |
| |
| Requester CreateRequester( |
| std::string_view host, |
| ResolveHostParameters parameters = ResolveHostParameters()) { |
| return Requester(CreateRequest(host, std::move(parameters))); |
| } |
| |
| LegacyRequester CreateLegacyRequester(std::string_view host) { |
| return LegacyRequester(resolver_->CreateRequest( |
| url::SchemeHostPort(GURL(host)), NetworkAnonymizationKey(), |
| NetLogWithSource(), ResolveHostParameters(), resolve_context_.get())); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, NameNotResolved) { |
| UseNoDomanDnsRules("nodomain"); |
| |
| proc_->SignalMultiple(1u); |
| Requester requester = CreateRequester("https://nodomain"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| requester.WaitForFinished(); |
| EXPECT_THAT(*requester.finished_result(), IsError(ERR_NAME_NOT_RESOLVED)); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, Ok) { |
| UseNonDelayedDnsRules("ok"); |
| |
| Requester requester = CreateRequester("https://ok"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| requester.WaitForFinished(); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| EXPECT_THAT(requester.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, ResolveLocally) { |
| UseNonDelayedDnsRules("ok"); |
| |
| // The first local only request should complete synchronously with a cache |
| // miss. |
| { |
| ResolveHostParameters parameters; |
| parameters.source = HostResolverSource::LOCAL_ONLY; |
| Requester requester = CreateRequester("https://ok", std::move(parameters)); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_DNS_CACHE_MISS)); |
| } |
| |
| // Populate the cache. |
| { |
| Requester requester = CreateRequester("https://ok"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| requester.WaitForFinished(); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| EXPECT_THAT(requester.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| // The second local only request should complete synchronously with a cache |
| // hit. |
| { |
| ResolveHostParameters parameters; |
| parameters.source = HostResolverSource::LOCAL_ONLY; |
| Requester requester = CreateRequester("https://ok", std::move(parameters)); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsOk()); |
| EXPECT_THAT(requester.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, EndpointsAreSorted) { |
| MockDnsClientRuleList rules; |
| constexpr const char* kHost = "multiple"; |
| |
| DnsResponse a_response = BuildTestDnsResponse( |
| kHost, dns_protocol::kTypeA, |
| {BuildTestAddressRecord(kHost, *IPAddress::FromIPLiteral("192.0.2.2")), |
| BuildTestAddressRecord(kHost, *IPAddress::FromIPLiteral("192.0.2.1"))}); |
| DnsResponse aaaa_response = BuildTestDnsResponse( |
| kHost, dns_protocol::kTypeAAAA, |
| {BuildTestAddressRecord(kHost, *IPAddress::FromIPLiteral("2001:db8::2")), |
| BuildTestAddressRecord(kHost, |
| *IPAddress::FromIPLiteral("2001:db8::1"))}); |
| AddDnsRule(&rules, kHost, dns_protocol::kTypeA, std::move(a_response), |
| /*delay=*/false); |
| AddDnsRule(&rules, kHost, dns_protocol::kTypeAAAA, std::move(aaaa_response), |
| /*delay=*/false); |
| |
| CreateResolver(); |
| UseMockDnsClient(CreateValidDnsConfig(), std::move(rules)); |
| mock_dns_client_->SetAddressSorterForTesting( |
| std::make_unique<FakeAddressSorter>()); |
| |
| Requester requester = CreateRequester("https://multiple"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| requester.WaitForFinished(); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| EXPECT_THAT(requester.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("192.0.2.1", 443), |
| MakeIPEndPoint("192.0.2.2", 443)), |
| ElementsAre(MakeIPEndPoint("2001:db8::1", 443), |
| MakeIPEndPoint("2001:db8::2", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, CancelRequestOnUpdated) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| Requester requester = CreateRequester("https://4slow_ok"); |
| requester.CancelRequestOnUpdated(); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| RunUntilIdle(); |
| // The finished callback should not be called because the request was |
| // already cancelled. |
| ASSERT_FALSE(requester.finished_result().has_value()); |
| ASSERT_FALSE(requester.request()); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, CancelRequestOnFinished) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| Requester requester = CreateRequester("https://4slow_ok"); |
| requester.CancelRequestOnFinished(); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| mock_dns_client_->CompleteDelayedTransactions(); |
| requester.WaitForFinished(); |
| // The result should be OK because we cancel the request after completing the |
| // associated Job. |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, Ipv4Slow) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| Requester requester = CreateRequester("https://4slow_ok"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // AAAA and HTTPS should complete. |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| ASSERT_FALSE(requester.finished_result().has_value()); |
| ASSERT_TRUE(requester.request()->EndpointsCryptoReady()); |
| EXPECT_THAT(requester.request()->GetEndpointResults(), |
| ElementsAre(ExpectServiceEndpoint( |
| IsEmpty(), ElementsAre(MakeIPEndPoint("::1", 443))))); |
| EXPECT_THAT(requester.request()->GetDnsAliasResults(), |
| UnorderedElementsAre("4slow_ok")); |
| |
| // Complete A request, which finishes the request synchronously. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| ASSERT_TRUE(requester.request()->EndpointsCryptoReady()); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| EXPECT_THAT(requester.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| EXPECT_THAT(requester.request()->GetDnsAliasResults(), |
| UnorderedElementsAre("4slow_ok")); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, Ipv6Slow) { |
| UseIpv6DelayedDnsRules("6slow_ok"); |
| |
| Requester requester = CreateRequester("https://6slow_ok"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // A and HTTPS should complete, but no endpoints should be available since |
| // waiting for AAAA response. |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| ASSERT_FALSE(requester.finished_result().has_value()); |
| ASSERT_TRUE(requester.request()->EndpointsCryptoReady()); |
| EXPECT_THAT(requester.request()->GetEndpointResults(), IsEmpty()); |
| |
| // Complete AAAA request, which finishes the request synchronously. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| EXPECT_THAT(requester.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, Ipv6SlowResolutionDelayPassed) { |
| UseIpv6DelayedDnsRules("6slow_ok"); |
| |
| Requester requester = CreateRequester("https://6slow_ok"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // A and HTTPS should complete, but no endpoints should be available since |
| // waiting for AAAA response. |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| ASSERT_FALSE(requester.finished_result().has_value()); |
| ASSERT_TRUE(requester.request()->EndpointsCryptoReady()); |
| EXPECT_THAT(requester.request()->GetEndpointResults(), IsEmpty()); |
| |
| // The resolution delay timer fired, IPv4 endpoints should be available. |
| FastForwardBy(DnsTaskResultsManager::kResolutionDelay + |
| base::Milliseconds(1)); |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| ASSERT_FALSE(requester.finished_result().has_value()); |
| EXPECT_THAT(requester.request()->GetEndpointResults(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), IsEmpty()))); |
| |
| // Complete AAAA request, which finishes the request synchronously. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| EXPECT_THAT(requester.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, HttpsSlow) { |
| UseHttpsDelayedDnsRules("https_slow_ok"); |
| |
| Requester requester = CreateRequester("https://https_slow_ok"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // A and AAAA should complete. |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| ASSERT_FALSE(requester.finished_result().has_value()); |
| ASSERT_FALSE(requester.request()->EndpointsCryptoReady()); |
| EXPECT_THAT(requester.request()->GetEndpointResults(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| |
| // Complete HTTPS request, which finishes the request synchronously. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| EXPECT_THAT( |
| requester.finished_endpoints(), |
| ElementsAre( |
| ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443)), |
| ConnectionEndpointMetadata( |
| /*supported_protocol_alpns=*/{"http/1.1"}, |
| /*ech_config_list=*/{}, std::string("https_slow_ok"))), |
| // Non-SVCB endpoints. |
| ExpectServiceEndpoint(ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, DestroyResolverWhileUpdating) { |
| // Using 4slow_ok not to complete transactions at once. |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| Requester requester = CreateRequester("https://4slow_ok"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| |
| requester.SetOnUpdatedCallback( |
| base::BindLambdaForTesting([&]() { DestroyResolver(); })); |
| |
| RunUntilIdle(); |
| EXPECT_THAT(*requester.finished_result(), IsError(ERR_DNS_REQUEST_CANCELLED)); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, DestroyResolverWhileFinishing) { |
| UseNonDelayedDnsRules("ok"); |
| |
| Requester requester = CreateRequester("https://ok"); |
| int rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| |
| requester.SetOnFinishedCallback( |
| base::BindLambdaForTesting([&]() { DestroyResolver(); })); |
| |
| RunUntilIdle(); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, MultipleRequestsOk) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| constexpr std::string_view kHost = "https://4slow_ok"; |
| Requester requester1 = CreateRequester(kHost); |
| EXPECT_THAT(requester1.Start(), IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| Requester requester2 = CreateRequester(kHost); |
| EXPECT_THAT(requester2.Start(), IsError(ERR_IO_PENDING)); |
| // The second request should share the same job with the first request. |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // Complete the delayed transaction, which finishes requests synchronously. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_THAT(*requester1.finished_result(), IsOk()); |
| EXPECT_THAT(requester1.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| |
| EXPECT_THAT(*requester2.finished_result(), IsOk()); |
| EXPECT_THAT(requester2.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| MultipleRequestsAddRequestInTheMiddleOfResolution) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| constexpr std::string_view kHost = "https://4slow_ok"; |
| Requester requester1 = CreateRequester(kHost); |
| EXPECT_THAT(requester1.Start(), IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // Partially complete transactions. Only IPv6 endpoints should be available. |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| ASSERT_FALSE(requester1.finished_result().has_value()); |
| EXPECT_THAT(requester1.request()->GetEndpointResults(), |
| ElementsAre(ExpectServiceEndpoint( |
| IsEmpty(), ElementsAre(MakeIPEndPoint("::1", 443))))); |
| |
| // Add a new request in the middle of resolution. The request should be |
| // attached to the ongoing job. |
| Requester requester2 = CreateRequester(kHost); |
| requester2.CancelRequestOnFinished(); |
| EXPECT_THAT(requester2.Start(), IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // The second request should have the same intermediate results as the first |
| // request. |
| ASSERT_FALSE(requester2.finished_result().has_value()); |
| EXPECT_THAT(requester2.request()->GetEndpointResults(), |
| ElementsAre(ExpectServiceEndpoint( |
| IsEmpty(), ElementsAre(MakeIPEndPoint("::1", 443))))); |
| |
| // Complete all transactions. Both requests should finish and have the same |
| // results. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_EQ(0u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| EXPECT_THAT(*requester1.finished_result(), IsOk()); |
| EXPECT_THAT(requester1.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| |
| EXPECT_THAT(*requester2.finished_result(), IsOk()); |
| EXPECT_THAT(requester2.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| MultipleRequestsAddAndCancelRequestInUpdatedCallback) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| constexpr std::string_view kHost = "https://4slow_ok"; |
| Requester requester1 = CreateRequester(kHost); |
| Requester requester2 = CreateRequester(kHost); |
| |
| requester1.SetOnUpdatedCallback(base::BindLambdaForTesting([&] { |
| EXPECT_THAT(requester2.Start(), IsError(ERR_IO_PENDING)); |
| requester1.CancelRequest(); |
| })); |
| |
| EXPECT_THAT(requester1.Start(), IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // Partially complete transactions. The update callback of the first request |
| // should start the second request and then cancel the first request. |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| ASSERT_FALSE(requester1.finished_result().has_value()); |
| ASSERT_FALSE(requester1.request()); |
| |
| ASSERT_FALSE(requester2.finished_result().has_value()); |
| EXPECT_THAT(requester2.request()->GetEndpointResults(), |
| ElementsAre(ExpectServiceEndpoint( |
| IsEmpty(), ElementsAre(MakeIPEndPoint("::1", 443))))); |
| |
| // Complete all transactions. The second request should finish successfully. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_EQ(0u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| ASSERT_FALSE(requester1.finished_result().has_value()); |
| ASSERT_FALSE(requester1.request()); |
| |
| EXPECT_THAT(*requester2.finished_result(), IsOk()); |
| EXPECT_THAT(requester2.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| MultipleRequestsAddRequestInFinishedCallback) { |
| UseNonDelayedDnsRules("ok"); |
| |
| constexpr std::string_view kHost = "https://ok"; |
| Requester requester1 = CreateRequester(kHost); |
| Requester requester2 = CreateRequester(kHost); |
| |
| requester1.SetOnFinishedCallback(base::BindLambdaForTesting([&] { |
| // The second request should finish synchronously because it should |
| // share the same job as the first one and the job has finished already. |
| EXPECT_THAT(requester2.Start(), IsOk()); |
| })); |
| |
| EXPECT_THAT(requester1.Start(), IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(0u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| EXPECT_THAT(*requester1.finished_result(), IsOk()); |
| EXPECT_THAT(requester1.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| |
| EXPECT_THAT(*requester2.finished_result(), IsOk()); |
| EXPECT_THAT(requester2.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| MultipleRequestsCancelOneRequestOnUpdated) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| constexpr std::string_view kHost = "https://4slow_ok"; |
| Requester requester1 = CreateRequester(kHost); |
| EXPECT_THAT(requester1.Start(), IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| Requester requester2 = CreateRequester(kHost); |
| // The second request destroys self when notified an update. |
| requester2.CancelRequestOnUpdated(); |
| EXPECT_THAT(requester2.Start(), IsError(ERR_IO_PENDING)); |
| // The second request should share the same job with the first request. |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // Complete the delayed transaction, which finishes the first request |
| // synchronously. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_THAT(*requester1.finished_result(), IsOk()); |
| EXPECT_THAT(requester1.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| // The second request was destroyed so it didn't get notified. |
| ASSERT_FALSE(requester2.finished_result().has_value()); |
| ASSERT_FALSE(requester2.request()); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| MultipleRequestsCancelAllRequestOnUpdated) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| constexpr std::string_view kHost = "https://4slow_ok"; |
| Requester requester1 = CreateRequester(kHost); |
| requester1.CancelRequestOnUpdated(); |
| EXPECT_THAT(requester1.Start(), IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| Requester requester2 = CreateRequester(kHost); |
| requester2.CancelRequestOnUpdated(); |
| EXPECT_THAT(requester2.Start(), IsError(ERR_IO_PENDING)); |
| // The second request should share the same job with the first request. |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // Complete non-delayed transactions and invoke update callbacks, which |
| // destroy all requests. |
| RunUntilIdle(); |
| EXPECT_EQ(0u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| ASSERT_FALSE(requester1.finished_result().has_value()); |
| ASSERT_FALSE(requester1.request()); |
| ASSERT_FALSE(requester2.finished_result().has_value()); |
| ASSERT_FALSE(requester2.request()); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| MultipleRequestsCancelAllRequestOnFinished) { |
| UseNonDelayedDnsRules("ok"); |
| |
| constexpr std::string_view kHost = "https://ok"; |
| Requester requester1 = CreateRequester(kHost); |
| requester1.CancelRequestOnFinished(); |
| EXPECT_THAT(requester1.Start(), IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| Requester requester2 = CreateRequester(kHost); |
| requester2.CancelRequestOnFinished(); |
| EXPECT_THAT(requester2.Start(), IsError(ERR_IO_PENDING)); |
| // The second request should share the same job with the first request. |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(0u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| EXPECT_THAT(*requester1.finished_result(), IsOk()); |
| EXPECT_THAT(*requester2.finished_result(), IsOk()); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, WithLegacyRequestOk) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| constexpr std::string_view kHost = "https://4slow_ok"; |
| LegacyRequester legacy_requester = CreateLegacyRequester(kHost); |
| int rv = legacy_requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| Requester requester = CreateRequester(kHost); |
| rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| // The request should share the same job with the legacy request. |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // Partially complete transactions. Requests should not complete but |
| // the non-legacy request should provide intermediate endpoints. |
| RunUntilIdle(); |
| EXPECT_EQ(1u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| ASSERT_FALSE(legacy_requester.complete_result().has_value()); |
| ASSERT_FALSE(requester.finished_result().has_value()); |
| ASSERT_TRUE(requester.request()->EndpointsCryptoReady()); |
| EXPECT_THAT(requester.request()->GetEndpointResults(), |
| ElementsAre(ExpectServiceEndpoint( |
| IsEmpty(), ElementsAre(MakeIPEndPoint("::1", 443))))); |
| |
| // Complete delayed transactions, which finish requests synchronously. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_THAT(*legacy_requester.complete_result(), IsOk()); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| EXPECT_THAT(requester.finished_endpoints(), |
| ElementsAre(ExpectServiceEndpoint( |
| ElementsAre(MakeIPEndPoint("127.0.0.1", 443)), |
| ElementsAre(MakeIPEndPoint("::1", 443))))); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| WithLegacyRequestDestroyResolverOnUpdated) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| constexpr std::string_view kHost = "https://4slow_ok"; |
| LegacyRequester legacy_requester = CreateLegacyRequester(kHost); |
| int rv = legacy_requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| Requester requester = CreateRequester(kHost); |
| rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| // The request should share the same job with the legacy request. |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| requester.SetOnUpdatedCallback( |
| base::BindLambdaForTesting([&]() { DestroyResolver(); })); |
| |
| RunUntilIdle(); |
| // DestroyResolver() removed the corresponding job and the legacy reqquest |
| // didn't get notified, but the non-legacy request got notified via the |
| // update callback. |
| ASSERT_FALSE(legacy_requester.complete_result().has_value()); |
| EXPECT_THAT(*requester.finished_result(), IsError(ERR_DNS_REQUEST_CANCELLED)); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| WithLegacyRequestCancelRequestOnUpdated) { |
| UseIpv4DelayedDnsRules("4slow_ok"); |
| |
| constexpr std::string_view kHost = "https://4slow_ok"; |
| LegacyRequester legacy_requester = CreateLegacyRequester(kHost); |
| int rv = legacy_requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| Requester requester = CreateRequester(kHost); |
| requester.CancelRequestOnUpdated(); |
| rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| // The request should share the same job with the legacy request. |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // Partially complete transactions to trigger the update callback on |
| // non-legacy request, which cancels the request itself. |
| RunUntilIdle(); |
| ASSERT_FALSE(legacy_requester.complete_result().has_value()); |
| ASSERT_FALSE(requester.request()); |
| |
| // Complete delayed transactions, which finish the legacy request |
| // synchronously. Non-legacy request was already destroyed. |
| mock_dns_client_->CompleteDelayedTransactions(); |
| EXPECT_THAT(*legacy_requester.complete_result(), IsOk()); |
| } |
| |
| TEST_F(HostResolverServiceEndpointRequestTest, |
| WithLegacyRequestCancelLegacyRequest) { |
| UseNonDelayedDnsRules("ok"); |
| |
| constexpr std::string_view kHost = "https://ok"; |
| |
| LegacyRequester legacy_requester = CreateLegacyRequester(kHost); |
| int rv = legacy_requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| Requester requester = CreateRequester(kHost); |
| requester.CancelRequestOnUpdated(); |
| rv = requester.Start(); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| // The request should share the same job with the legacy request. |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| // Cancelling legacy request should not cancel non-legacy request. |
| legacy_requester.CancelRequest(); |
| ASSERT_FALSE(requester.finished_result().has_value()); |
| EXPECT_EQ(3u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| |
| requester.WaitForFinished(); |
| EXPECT_EQ(0u, resolver_->num_running_dispatcher_jobs_for_tests()); |
| EXPECT_THAT(*requester.finished_result(), IsOk()); |
| } |
| |
| } // namespace net |