| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/net/network_diagnostics/udp_prober.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/callback.h" |
| #include "base/containers/span.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "chrome/browser/chromeos/net/network_diagnostics/host_resolver.h" |
| #include "chrome/browser/chromeos/net/network_diagnostics/udp_prober.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/base/address_list.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/net_errors.h" |
| #include "net/dns/public/resolve_error_info.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/udp_socket.mojom.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| |
| namespace chromeos { |
| namespace network_diagnostics { |
| |
| // Implements the UdpProber class. |
| class UdpProberImpl final : public network::mojom::UDPSocketListener, |
| public UdpProber { |
| public: |
| using ConnectCallback = base::OnceCallback< |
| void(int result, const absl::optional<net::IPEndPoint>& local_addr_out)>; |
| using SendCallback = base::OnceCallback<void(int result)>; |
| |
| // Establishes a UDP connection and sends |data| to |host_port_pair|. The |
| // traffic sent by the prober is described by |tag|. Since there is no |
| // guarantee the host specified by |host_port_pair| will respond to a UDP |
| // request, the prober will timeout with a failure after |
| // |timeout_after_host_resolution|. As such, the prober will run no longer |
| // than the time it takes to perform host resolution + |
| // |timeout_after_host_resolution|. Note that the constructor will not invoke |
| // |callback|, which is passed into UdpProberImpl during construction. This |
| // ensures the UdpProberImpl instance is constructed before |callback| is |
| // invoked. The UdpProberImpl must be created on the UI thread and will |
| // invoke |callback| on the UI thread. |network_context_getter| will be |
| // invoked on the UI thread. |
| UdpProberImpl(NetworkContextGetter network_context_getter, |
| net::HostPortPair host_port_pair, |
| base::span<const uint8_t> data, |
| net::NetworkTrafficAnnotationTag tag, |
| base::TimeDelta timeout_after_host_resolution, |
| UdpProbeCompleteCallback callback); |
| UdpProberImpl(const UdpProberImpl&) = delete; |
| UdpProberImpl& operator=(const UdpProberImpl&) = delete; |
| ~UdpProberImpl() override; |
| |
| private: |
| // Processes the results of the DNS resolution done by |host_resolver_|. |
| void OnHostResolutionComplete( |
| HostResolver::ResolutionResult& resolution_result); |
| |
| // On success, the UDP socket is connected to the destination and is ready to |
| // send data. On failure, the UdpProberImpl exits with a failure. |
| void OnConnectComplete(int result, |
| const absl::optional<net::IPEndPoint>& local_addr_out); |
| |
| // On success, the UDP socket is ready to receive data. So long as the |
| // received data is not empty, it is considered valid. The content itself is |
| // not verified. |
| void OnSendComplete(int result); |
| |
| // network::mojom::UDPSocketListener: |
| void OnReceived(int32_t result, |
| const absl::optional<net::IPEndPoint>& src_ip, |
| absl::optional<base::span<const uint8_t>> data) override; |
| |
| // Signals the end of the probe. Manages the clean up and returns a response |
| // to the caller. |
| void OnDone(int result, ProbeExitEnum probe_exit_enum); |
| |
| // Handles disconnects on the UDPSocket remote and UDPSocketListener receiver. |
| void OnDisconnect(); |
| |
| // Gets the active profile-specific network context. |
| NetworkContextGetter network_context_getter_; |
| // Contains the hostname and port. |
| net::HostPortPair host_port_pair_; |
| // Data to be sent to the destination. |
| base::span<const uint8_t> data_; |
| // Network annotation tag describing the socket traffic. |
| net::NetworkTrafficAnnotationTag tag_; |
| // Represents the time after host resolution. |
| base::TimeDelta timeout_after_host_resolution_; |
| // Times the prober. |
| base::OneShotTimer timer_; |
| // Host resolver used for DNS lookup. |
| std::unique_ptr<HostResolver> host_resolver_; |
| // Stores the callback invoked once probe is complete or interrupted. |
| UdpProbeCompleteCallback callback_; |
| // Holds the UDPSocket remote. |
| mojo::Remote<network::mojom::UDPSocket> udp_socket_remote_; |
| // Listens to the response from hostname specified by |url_|. |
| mojo::Receiver<network::mojom::UDPSocketListener> |
| udp_socket_listener_receiver_{this}; |
| |
| // Must be the last member so that any callbacks taking a weak pointer to this |
| // instance are invalidated first during object destruction. |
| base::WeakPtrFactory<UdpProberImpl> weak_factory_{this}; |
| }; |
| |
| UdpProberImpl::UdpProberImpl(NetworkContextGetter network_context_getter, |
| net::HostPortPair host_port_pair, |
| base::span<const uint8_t> data, |
| net::NetworkTrafficAnnotationTag tag, |
| base::TimeDelta timeout_after_host_resolution, |
| UdpProbeCompleteCallback callback) |
| : network_context_getter_(std::move(network_context_getter)), |
| host_port_pair_(host_port_pair), |
| data_(std::move(data)), |
| tag_(tag), |
| timeout_after_host_resolution_(timeout_after_host_resolution), |
| callback_(std::move(callback)) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(!data_.empty()); |
| DCHECK(callback_); |
| DCHECK(!host_port_pair_.IsEmpty()); |
| |
| network::mojom::NetworkContext* network_context = |
| network_context_getter_.Run(); |
| DCHECK(network_context); |
| |
| host_resolver_ = std::make_unique<HostResolver>( |
| host_port_pair_, network_context, |
| base::BindOnce(&UdpProberImpl::OnHostResolutionComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| UdpProberImpl::~UdpProberImpl() = default; |
| |
| void UdpProberImpl::OnHostResolutionComplete( |
| HostResolver::ResolutionResult& resolution_result) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| bool success = resolution_result.result == net::OK && |
| !resolution_result.resolved_addresses->empty() && |
| resolution_result.resolved_addresses.has_value(); |
| if (!success) { |
| OnDone(resolution_result.result, ProbeExitEnum::kDnsFailure); |
| return; |
| } |
| |
| network::mojom::NetworkContext* network_context = |
| network_context_getter_.Run(); |
| DCHECK(network_context); |
| |
| auto pending_receiver = udp_socket_remote_.BindNewPipeAndPassReceiver(); |
| udp_socket_remote_.set_disconnect_handler( |
| base::BindOnce(&UdpProberImpl::OnDisconnect, weak_factory_.GetWeakPtr())); |
| DCHECK(udp_socket_remote_.is_bound()); |
| |
| auto pending_remote = |
| udp_socket_listener_receiver_.BindNewPipeAndPassRemote(); |
| udp_socket_listener_receiver_.set_disconnect_handler( |
| base::BindOnce(&UdpProberImpl::OnDisconnect, weak_factory_.GetWeakPtr())); |
| |
| network_context->CreateUDPSocket(std::move(pending_receiver), |
| std::move(pending_remote)); |
| timer_.Start( |
| FROM_HERE, timeout_after_host_resolution_, |
| base::BindOnce(&UdpProberImpl::OnDone, weak_factory_.GetWeakPtr(), |
| net::ERR_TIMED_OUT, ProbeExitEnum::kTimeout)); |
| udp_socket_remote_->Connect( |
| resolution_result.resolved_addresses.value().front(), nullptr, |
| base::BindOnce(&UdpProberImpl::OnConnectComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void UdpProberImpl::OnConnectComplete( |
| int result, |
| const absl::optional<net::IPEndPoint>& local_addr_out) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (result != net::OK) { |
| OnDone(result, ProbeExitEnum::kConnectFailure); |
| return; |
| } |
| udp_socket_remote_->Send(std::move(data_), |
| net::MutableNetworkTrafficAnnotationTag(tag_), |
| base::BindOnce(&UdpProberImpl::OnSendComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void UdpProberImpl::OnSendComplete(int result) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (result != net::OK) { |
| OnDone(result, ProbeExitEnum::kSendFailure); |
| return; |
| } |
| udp_socket_remote_->ReceiveMore(/*num_additional_datagrams=*/1); |
| } |
| |
| void UdpProberImpl::OnReceived(int32_t result, |
| const absl::optional<net::IPEndPoint>& src_ip, |
| absl::optional<base::span<const uint8_t>> data) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (result != net::OK) { |
| OnDone(result, ProbeExitEnum::kNetworkErrorOnReceiveFailure); |
| return; |
| } |
| |
| // The UdpProberImpl instance is only interested in validating whether |
| // data can be received from the destination host. |
| if (!data.has_value() || data.value().empty()) { |
| // Note that net::ERR_FAILED is reported even if |result| is net::OK |
| // when no data is received. |
| OnDone(net::ERR_FAILED, ProbeExitEnum::kNoDataReceivedFailure); |
| return; |
| } |
| OnDone(net::OK, ProbeExitEnum::kSuccess); |
| } |
| |
| void UdpProberImpl::OnDone(int result, ProbeExitEnum probe_exit_enum) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Invalidate pending callbacks. |
| weak_factory_.InvalidateWeakPtrs(); |
| // Destroy the socket connection. |
| udp_socket_listener_receiver_.reset(); |
| udp_socket_remote_.reset(); |
| // Reset the host resolver. |
| host_resolver_.reset(); |
| |
| std::move(callback_).Run(result, probe_exit_enum); |
| } |
| |
| void UdpProberImpl::OnDisconnect() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| OnDone(net::ERR_FAILED, ProbeExitEnum::kMojoDisconnectFailure); |
| } |
| |
| // static |
| std::unique_ptr<UdpProber> UdpProber::Start( |
| NetworkContextGetter network_context_getter, |
| net::HostPortPair host_port_pair, |
| base::span<const uint8_t> data, |
| net::NetworkTrafficAnnotationTag tag, |
| base::TimeDelta timeout_after_host_resolution, |
| UdpProbeCompleteCallback callback) { |
| return std::make_unique<UdpProberImpl>( |
| std::move(network_context_getter), host_port_pair, std::move(data), tag, |
| timeout_after_host_resolution, std::move(callback)); |
| } |
| |
| } // namespace network_diagnostics |
| } // namespace chromeos |