blob: 363bb4c2b9c0ee3be7d4a2ea3658894da6013a43 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/direct_sockets/direct_sockets_service_impl.h"
#include "build/build_config.h"
#include "content/browser/direct_sockets/direct_udp_socket_impl.h"
#include "content/browser/direct_sockets/resolve_host_and_open_socket.h"
#include "content/browser/process_lock.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/direct_sockets_delegate.h"
#include "content/public/browser/document_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/tcp_socket.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/direct_sockets/direct_sockets.mojom-shared.h"
#include "third_party/blink/public/mojom/direct_sockets/direct_sockets.mojom.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
namespace content {
namespace {
constexpr net::NetworkTrafficAnnotationTag kDirectSocketsTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("direct_sockets", R"(
semantics {
sender: "Direct Sockets API"
description: "Web app request to communicate with network device"
trigger: "User completes network connection dialog"
data: "Any data sent by web app"
destination: OTHER
destination_other: "Address entered by user in connection dialog"
}
policy {
cookies_allowed: NO
setting: "This feature cannot yet be controlled by settings."
policy_exception_justification: "To be implemented"
}
)");
constexpr int32_t kMaxBufferSize = 32 * 1024 * 1024;
network::mojom::NetworkContext*& GetNetworkContextForTesting() {
static network::mojom::NetworkContext* network_context = nullptr;
return network_context;
}
bool IsFrameSufficientlyIsolated(content::RenderFrameHost* frame) {
if (frame->GetWebExposedIsolationLevel() >=
content::RenderFrameHost::WebExposedIsolationLevel::
kMaybeIsolatedApplication) {
return true;
}
if (GetContentClient()->browser()->IsIsolatedContextAllowedForUrl(
frame->GetBrowserContext(),
frame->GetProcess()->GetProcessLock().lock_url())) {
return true;
}
return false;
}
network::mojom::TCPConnectedSocketOptionsPtr CreateTCPConnectedSocketOptions(
blink::mojom::DirectSocketOptionsPtr options) {
network::mojom::TCPConnectedSocketOptionsPtr tcp_connected_socket_options =
network::mojom::TCPConnectedSocketOptions::New();
if (options->send_buffer_size > 0) {
tcp_connected_socket_options->send_buffer_size =
std::min(options->send_buffer_size, kMaxBufferSize);
}
if (options->receive_buffer_size > 0) {
tcp_connected_socket_options->receive_buffer_size =
std::min(options->receive_buffer_size, kMaxBufferSize);
}
tcp_connected_socket_options->no_delay = options->no_delay;
if (options->keep_alive_options) {
// options->keep_alive_options will be invalidated.
tcp_connected_socket_options->keep_alive_options =
std::move(options->keep_alive_options);
}
return tcp_connected_socket_options;
}
network::mojom::UDPSocketOptionsPtr CreateUDPSocketOptions(
blink::mojom::DirectSocketOptionsPtr options) {
network::mojom::UDPSocketOptionsPtr udp_socket_options =
network::mojom::UDPSocketOptions::New();
if (options->send_buffer_size > 0) {
udp_socket_options->send_buffer_size =
std::min(options->send_buffer_size, kMaxBufferSize);
}
if (options->receive_buffer_size > 0) {
udp_socket_options->receive_buffer_size =
std::min(options->receive_buffer_size, kMaxBufferSize);
}
return udp_socket_options;
}
absl::optional<net::IPEndPoint> GetLocalAddress(
const blink::mojom::DirectSocketOptions& options) {
if (net::IPAddress address;
options.local_hostname &&
address.AssignFromIPLiteral(*options.local_hostname)) {
return net::IPEndPoint{std::move(address), options.local_port};
}
return {};
}
} // namespace
DirectSocketsServiceImpl::DirectSocketsServiceImpl(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver)
: DocumentService(*render_frame_host, std::move(receiver)) {}
DirectSocketsServiceImpl::~DirectSocketsServiceImpl() = default;
// static
void DirectSocketsServiceImpl::CreateForFrame(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!render_frame_host->IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kDirectSockets)) {
mojo::ReportBadMessage(
"Permissions policy blocks access to Direct Sockets.");
}
if (!IsFrameSufficientlyIsolated(render_frame_host)) {
mojo::ReportBadMessage(
"Frame is not sufficiently isolated to use Direct Sockets.");
return;
}
new DirectSocketsServiceImpl(render_frame_host, std::move(receiver));
}
content::DirectSocketsDelegate* DirectSocketsServiceImpl::GetDelegate() {
return GetContentClient()->browser()->GetDirectSocketsDelegate();
}
void DirectSocketsServiceImpl::OpenTcpSocket(
blink::mojom::DirectSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> receiver,
mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTcpSocketCallback callback) {
const std::string remote_host = options->remote_hostname;
const uint16_t remote_port = options->remote_port;
if (auto* delegate = GetDelegate();
delegate &&
!delegate->ValidateAddressAndPort(
render_frame_host().GetBrowserContext(),
render_frame_host().GetProcess()->GetProcessLock().lock_url(),
remote_host, remote_port,
blink::mojom::DirectSocketProtocolType::kTcp)) {
std::move(callback).Run(net::ERR_ACCESS_DENIED, absl::nullopt,
absl::nullopt, mojo::ScopedDataPipeConsumerHandle(),
mojo::ScopedDataPipeProducerHandle());
return;
}
ResolveHostAndOpenSocket::Create(
remote_host, remote_port,
base::BindOnce(&DirectSocketsServiceImpl::OnResolveCompleteForTcpSocket,
weak_ptr_factory_.GetWeakPtr(), std::move(options),
std::move(receiver), std::move(observer),
std::move(callback)))
->Start(GetNetworkContext());
}
void DirectSocketsServiceImpl::OpenUdpSocket(
blink::mojom::DirectSocketOptionsPtr options,
mojo::PendingReceiver<blink::mojom::DirectUDPSocket> receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenUdpSocketCallback callback) {
const std::string remote_host = options->remote_hostname;
const uint16_t remote_port = options->remote_port;
if (auto* delegate = GetDelegate();
delegate &&
!delegate->ValidateAddressAndPort(
render_frame_host().GetBrowserContext(),
render_frame_host().GetProcess()->GetProcessLock().lock_url(),
remote_host, remote_port,
blink::mojom::DirectSocketProtocolType::kUdp)) {
std::move(callback).Run(net::ERR_ACCESS_DENIED, absl::nullopt,
absl::nullopt);
return;
}
ResolveHostAndOpenSocket::Create(
remote_host, remote_port,
base::BindOnce(&DirectSocketsServiceImpl::OnResolveCompleteForUdpSocket,
weak_ptr_factory_.GetWeakPtr(), std::move(options),
std::move(receiver), std::move(listener),
std::move(callback)))
->Start(GetNetworkContext());
}
// static
net::NetworkTrafficAnnotationTag DirectSocketsServiceImpl::TrafficAnnotation() {
return kDirectSocketsTrafficAnnotation;
}
// static
void DirectSocketsServiceImpl::SetNetworkContextForTesting(
network::mojom::NetworkContext* network_context) {
GetNetworkContextForTesting() = network_context;
}
network::mojom::NetworkContext* DirectSocketsServiceImpl::GetNetworkContext()
const {
if (auto* network_context = GetNetworkContextForTesting()) {
return network_context;
}
return render_frame_host().GetStoragePartition()->GetNetworkContext();
}
void DirectSocketsServiceImpl::OnResolveCompleteForTcpSocket(
blink::mojom::DirectSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> socket,
mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTcpSocketCallback callback,
int result,
const absl::optional<net::AddressList>& resolved_addresses) {
if (result != net::OK) {
std::move(callback).Run(result, absl::nullopt, absl::nullopt,
mojo::ScopedDataPipeConsumerHandle(),
mojo::ScopedDataPipeProducerHandle());
return;
}
DCHECK(resolved_addresses && !resolved_addresses->empty());
absl::optional<net::IPEndPoint> local_addr = GetLocalAddress(*options);
GetNetworkContext()->CreateTCPConnectedSocket(
std::move(local_addr), *resolved_addresses,
CreateTCPConnectedSocketOptions(std::move(options)),
net::MutableNetworkTrafficAnnotationTag{
DirectSocketsServiceImpl::TrafficAnnotation()},
std::move(socket), std::move(observer), std::move(callback));
}
void DirectSocketsServiceImpl::OnResolveCompleteForUdpSocket(
blink::mojom::DirectSocketOptionsPtr options,
mojo::PendingReceiver<blink::mojom::DirectUDPSocket>
direct_udp_socket_receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenUdpSocketCallback callback,
int result,
const absl::optional<net::AddressList>& resolved_addresses) {
if (result != net::OK) {
std::move(callback).Run(result, absl::nullopt, absl::nullopt);
return;
}
DCHECK(resolved_addresses && !resolved_addresses->empty());
net::IPEndPoint peer_addr = resolved_addresses->front();
auto direct_udp_socket = std::make_unique<DirectUDPSocketImpl>(
GetNetworkContext(), std::move(listener));
direct_udp_socket->Connect(
resolved_addresses->front(), CreateUDPSocketOptions(std::move(options)),
base::BindOnce(
[](OpenUdpSocketCallback callback, net::IPEndPoint peer_addr,
int result, const absl::optional<net::IPEndPoint>& local_addr) {
std::move(callback).Run(result, local_addr, peer_addr);
},
std::move(callback), resolved_addresses->front()));
direct_udp_socket_receivers_.Add(std::move(direct_udp_socket),
std::move(direct_udp_socket_receiver));
}
} // namespace content