blob: 92c70cee3dfbef7f6075379a1dbfb8e66b95ba96 [file] [log] [blame]
// 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 "content/browser/direct_sockets/direct_sockets_service_impl.h"
#include <memory>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/optional.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/address_list.h"
#include "services/network/public/mojom/network_context.mojom.h"
namespace content {
namespace {
constexpr int32_t kMaxBufferSize = 32 * 1024 * 1024;
DirectSocketsServiceImpl::PermissionCallback&
GetPermissionCallbackForTesting() {
static base::NoDestructor<DirectSocketsServiceImpl::PermissionCallback>
callback;
return *callback;
}
network::mojom::NetworkContext*& GetNetworkContextForTesting() {
static network::mojom::NetworkContext* network_context = nullptr;
return network_context;
}
} // namespace
DirectSocketsServiceImpl::DirectSocketsServiceImpl(RenderFrameHost& frame_host)
: WebContentsObserver(WebContents::FromRenderFrameHost(&frame_host)),
frame_host_(&frame_host) {}
// static
void DirectSocketsServiceImpl::CreateForFrame(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
mojo::MakeSelfOwnedReceiver(
std::make_unique<DirectSocketsServiceImpl>(*render_frame_host),
std::move(receiver));
}
void DirectSocketsServiceImpl::OpenTcpSocket(
blink::mojom::DirectSocketOptionsPtr options,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> receiver,
mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTcpSocketCallback callback) {
if (!options) {
mojo::ReportBadMessage("Invalid request to open socket");
return;
}
net::IPAddress remote_address;
const net::Error result = ValidateOptions(*options, remote_address);
// TODO(crbug.com/1119681): Collect metrics for usage and permission checks
if (result != net::OK) {
std::move(callback).Run(result, base::nullopt, base::nullopt,
mojo::ScopedDataPipeConsumerHandle(),
mojo::ScopedDataPipeProducerHandle());
return;
}
base::Optional<net::IPEndPoint> local_addr = base::nullopt;
PopulateLocalAddr(*options, local_addr);
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;
GetNetworkContext()->CreateTCPConnectedSocket(
local_addr,
net::AddressList::CreateFromIPAddress(remote_address,
options->remote_port),
std::move(tcp_connected_socket_options), traffic_annotation,
std::move(receiver), std::move(observer), std::move(callback));
}
void DirectSocketsServiceImpl::OpenUdpSocket(
blink::mojom::DirectSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::UDPSocket> receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenUdpSocketCallback callback) {
if (!options) {
mojo::ReportBadMessage("Invalid request to open socket");
return;
}
net::IPAddress remote_address;
const net::Error result = ValidateOptions(*options, remote_address);
// TODO(crbug.com/1119681): Collect metrics for usage and permission checks
if (result == net::OK) {
// TODO(crbug.com/1119620): GetNetworkContext()->CreateUDPSocket
// TODO(crbug.com/1119620): Connect(remote_addr, udp_socket_options)
GetNetworkContext();
NOTIMPLEMENTED();
}
std::move(callback).Run(result, base::nullopt, base::nullopt);
}
// static
void DirectSocketsServiceImpl::SetPermissionCallbackForTesting(
PermissionCallback callback) {
GetPermissionCallbackForTesting() = std::move(callback);
}
// static
void DirectSocketsServiceImpl::SetNetworkContextForTesting(
network::mojom::NetworkContext* network_context) {
GetNetworkContextForTesting() = network_context;
}
void DirectSocketsServiceImpl::RenderFrameDeleted(
RenderFrameHost* render_frame_host) {
if (render_frame_host == frame_host_)
frame_host_ = nullptr;
}
void DirectSocketsServiceImpl::WebContentsDestroyed() {
frame_host_ = nullptr;
}
net::Error DirectSocketsServiceImpl::ValidateOptions(
const blink::mojom::DirectSocketOptions& options,
net::IPAddress& remote_address) {
DCHECK(base::FeatureList::IsEnabled(features::kDirectSockets));
if (!frame_host_)
return net::ERR_CONTEXT_SHUT_DOWN;
if (GetPermissionCallbackForTesting())
return GetPermissionCallbackForTesting().Run(options, remote_address);
if (options.send_buffer_size < 0 || options.receive_buffer_size < 0)
return net::ERR_INVALID_ARGUMENT;
// TODO(crbug.com/1119662): Check for enterprise software policies.
// TODO(crbug.com/1119659): Check permissions policy.
// TODO(crbug.com/1119600): Implement rate limiting.
if (options.remote_port == 443) {
// TODO(crbug.com/1119601): Issue a CORS preflight request.
return net::ERR_UNSAFE_PORT;
}
// ValidateOptions() will need to become asynchronous:
// TODO(crbug.com/1119597): Show connection dialog.
// TODO(crbug.com/1119597): Use the hostname provided by the user.
// TODO(crbug.com/1119661): Reject hostnames that resolve to non-public
// addresses.
if (!options.remote_hostname)
return net::ERR_NAME_NOT_RESOLVED;
// TODO(crbug.com/905818): Support resolved hostnames.
// TODO(crbug.com/1124255): Support mDNS.
if (!remote_address.AssignFromIPLiteral(*options.remote_hostname))
return net::ERR_NAME_NOT_RESOLVED;
return net::ERR_NOT_IMPLEMENTED;
}
void DirectSocketsServiceImpl::PopulateLocalAddr(
const blink::mojom::DirectSocketOptions& options,
base::Optional<net::IPEndPoint>& local_addr) {
DCHECK(!local_addr);
if (!options.local_hostname)
return;
net::IPAddress local_address;
bool success = local_address.AssignFromIPLiteral(*options.local_hostname);
if (success)
local_addr = net::IPEndPoint(local_address, options.local_port);
}
network::mojom::NetworkContext* DirectSocketsServiceImpl::GetNetworkContext() {
if (network::mojom::NetworkContext* network_context =
GetNetworkContextForTesting()) {
return network_context;
}
return frame_host_->GetStoragePartition()->GetNetworkContext();
}
} // namespace content