| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/network/resolve_host_request.h" |
| |
| #include <algorithm> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/containers/to_vector.h" |
| #include "base/functional/bind.h" |
| #include "base/strings/string_util.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/url_util.h" |
| #include "net/log/net_log.h" |
| #include "net/log/net_log_with_source.h" |
| #include "url/url_canon.h" |
| |
| namespace network { |
| |
| namespace { |
| |
| // Attempts URL canonicalization, but if unable, leaves `host_port_pair` alone. |
| void TryToCanonicalizeHost(net::HostPortPair& host_port_pair) { |
| url::CanonHostInfo info; |
| std::string canonicalized = |
| net::CanonicalizeHost(host_port_pair.host(), &info); |
| if (info.family != url::CanonHostInfo::BROKEN) { |
| host_port_pair.set_host(canonicalized); |
| } |
| } |
| |
| } // namespace |
| |
| ResolveHostRequest::ResolveHostRequest( |
| net::HostResolver* resolver, |
| mojom::HostResolverHostPtr host, |
| const net::NetworkAnonymizationKey& network_anonymization_key, |
| const std::optional<net::HostResolver::ResolveHostParameters>& |
| optional_parameters, |
| net::NetLog* net_log) { |
| DCHECK(resolver); |
| DCHECK(net_log); |
| |
| if (host->is_host_port_pair()) { |
| // net::HostResolver expects canonicalized hostnames. |
| net::HostPortPair& host_port_pair = host->get_host_port_pair(); |
| TryToCanonicalizeHost(host_port_pair); |
| internal_request_ = resolver->CreateRequest( |
| host_port_pair, network_anonymization_key, |
| net::NetLogWithSource::Make( |
| net_log, net::NetLogSourceType::NETWORK_SERVICE_HOST_RESOLVER), |
| optional_parameters); |
| } else { |
| internal_request_ = resolver->CreateRequest( |
| host->get_scheme_host_port(), network_anonymization_key, |
| net::NetLogWithSource::Make( |
| net_log, net::NetLogSourceType::NETWORK_SERVICE_HOST_RESOLVER), |
| optional_parameters); |
| } |
| } |
| |
| ResolveHostRequest::~ResolveHostRequest() { |
| control_handle_receiver_.reset(); |
| |
| if (response_client_.is_bound()) { |
| response_client_->OnComplete(net::ERR_NAME_NOT_RESOLVED, |
| net::ResolveErrorInfo(net::ERR_FAILED), |
| /*resolved_addresses=*/{}, |
| /*alternative_endpoints=*/{}); |
| response_client_.reset(); |
| } |
| } |
| |
| int ResolveHostRequest::Start( |
| mojo::PendingReceiver<mojom::ResolveHostHandle> control_handle_receiver, |
| mojo::PendingRemote<mojom::ResolveHostClient> pending_response_client, |
| net::CompletionOnceCallback callback) { |
| DCHECK(internal_request_); |
| DCHECK(!control_handle_receiver_.is_bound()); |
| DCHECK(!response_client_.is_bound()); |
| |
| // Unretained |this| reference is safe because if |internal_request_| goes out |
| // of scope, it will cancel the request and ResolveHost() will not call the |
| // callback. |
| int rv = internal_request_->Start( |
| base::BindOnce(&ResolveHostRequest::OnComplete, base::Unretained(this))); |
| |
| mojo::Remote<mojom::ResolveHostClient> response_client( |
| std::move(pending_response_client)); |
| if (rv != net::ERR_IO_PENDING) { |
| response_client->OnComplete(rv, GetResolveErrorInfo(), GetAddressResults(), |
| GetAlternativeEndpoints()); |
| return rv; |
| } |
| |
| if (control_handle_receiver) |
| control_handle_receiver_.Bind(std::move(control_handle_receiver)); |
| |
| response_client_ = std::move(response_client); |
| // Unretained |this| reference is safe because connection error cannot occur |
| // if |response_client_| goes out of scope. |
| response_client_.set_disconnect_handler(base::BindOnce( |
| &ResolveHostRequest::Cancel, base::Unretained(this), net::ERR_FAILED)); |
| |
| callback_ = std::move(callback); |
| |
| return net::ERR_IO_PENDING; |
| } |
| |
| void ResolveHostRequest::Cancel(int error) { |
| DCHECK_NE(net::OK, error); |
| |
| if (cancelled_) |
| return; |
| |
| internal_request_ = nullptr; |
| cancelled_ = true; |
| resolve_error_info_ = net::ResolveErrorInfo(error); |
| OnComplete(error); |
| } |
| |
| void ResolveHostRequest::OnComplete(int error) { |
| DCHECK(response_client_.is_bound()); |
| DCHECK(callback_); |
| |
| control_handle_receiver_.reset(); |
| SignalNonAddressResults(); |
| response_client_->OnComplete(error, GetResolveErrorInfo(), |
| GetAddressResults(), GetAlternativeEndpoints()); |
| |
| response_client_.reset(); |
| // Invoke completion callback last as it may delete |this|. |
| std::move(callback_).Run(GetResolveErrorInfo().error); |
| } |
| |
| net::ResolveErrorInfo ResolveHostRequest::GetResolveErrorInfo() const { |
| if (cancelled_) { |
| return resolve_error_info_; |
| } |
| |
| DCHECK(internal_request_); |
| return internal_request_->GetResolveErrorInfo(); |
| } |
| |
| net::AddressList ResolveHostRequest::GetAddressResults() const { |
| if (cancelled_) { |
| return net::AddressList(); |
| } |
| |
| DCHECK(internal_request_); |
| return internal_request_->GetAddressResults(); |
| } |
| |
| net::HostResolverEndpointResults ResolveHostRequest::GetAlternativeEndpoints() |
| const { |
| if (cancelled_) { |
| return {}; |
| } |
| |
| DCHECK(internal_request_); |
| auto endpoints = internal_request_->GetEndpointResults(); |
| |
| // `endpoints` contains both alternative endpoints (from HTTPS/SVCB) and |
| // authority endpoints (from A/AAAA directly). The authority endpoints are |
| // redundant with `AddressList`, so return only the alternative endpoints. |
| // |
| // TODO(crbug.com/40203587): This is the opposite of the design taken |
| // everywhere else in the DNS logic, where we aimed to migrate `AddressList` |
| // to `HostResolverEndpointResult`. |
| net::HostResolverEndpointResults alternative_endpoints; |
| std::ranges::copy_if( |
| endpoints, std::back_inserter(alternative_endpoints), |
| [](const auto& endpoint) { return endpoint.metadata.IsAlternative(); }); |
| return alternative_endpoints; |
| } |
| |
| void ResolveHostRequest::SignalNonAddressResults() { |
| if (cancelled_) { |
| return; |
| } |
| DCHECK(internal_request_); |
| |
| if (!internal_request_->GetTextResults().empty()) { |
| response_client_->OnTextResults( |
| base::ToVector(internal_request_->GetTextResults())); |
| } |
| |
| if (!internal_request_->GetHostnameResults().empty()) { |
| response_client_->OnHostnameResults( |
| base::ToVector(internal_request_->GetHostnameResults())); |
| } |
| } |
| |
| } // namespace network |