blob: 14d0f366b57b9385e28e1b6e40ffd9ea552bd55c [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 <optional>
#include <variant>
#include "base/check_deref.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/worker_host/shared_worker_host.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/isolated_context_util.h"
#include "content/public/browser/render_frame_host.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/pending_receiver.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/bindings/unique_receiver_set.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/network_anonymization_key.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/ip_address_space_util.h"
#include "services/network/public/cpp/simple_host_resolver.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
#include "services/network/public/mojom/restricted_udp_socket.mojom.h"
#include "services/network/public/mojom/tcp_socket.mojom.h"
#include "services/network/public/mojom/udp_socket.mojom.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/mojom/direct_sockets/direct_sockets.mojom.h"
#if BUILDFLAG(IS_WIN)
#include <winsock2.h>
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_POSIX)
#include <sys/socket.h>
#endif // BUILDFLAG(IS_POSIX)
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/components/firewall_hole/firewall_hole.h"
#include "services/network/public/mojom/socket_connection_tracker.mojom.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace content {
namespace {
using Context = DirectSocketsServiceImpl::Context;
#if BUILDFLAG(IS_CHROMEOS)
bool g_always_open_firewall_hole_for_testing = false;
#endif // BUILDFLAG(IS_CHROMEOS)
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"
}
)");
network::mojom::NetworkContext*& GetNetworkContextForTesting() {
static network::mojom::NetworkContext* network_context = nullptr;
return network_context;
}
// Runs the supplied `callback` with `net_error` and default params for other
// args.
template <typename... Args>
void FulfillWithError(base::OnceCallback<void(int32_t, Args...)> callback,
int32_t net_error) {
std::move(callback).Run(net_error, std::remove_cvref_t<Args>()...);
}
bool ValidateRequest(const Context& context,
const std::string& address,
uint16_t port,
DirectSocketsDelegate::ProtocolType protocol) {
auto* delegate = GetContentClient()->browser()->GetDirectSocketsDelegate();
if (!delegate) {
// No additional rules from the embedder.
return true;
}
return std::visit(
absl::Overload{
[&](RenderFrameHost* rfh) {
return delegate->ValidateRequest(*rfh, {address, port, protocol});
},
[&](base::WeakPtr<SharedWorkerHost> shared_worker) {
if (!shared_worker) {
return false;
}
return delegate->ValidateRequestForSharedWorker(
CHECK_DEREF(shared_worker->GetProcessHost())
.GetBrowserContext(),
shared_worker->instance().url(), {address, port, protocol});
},
[&](base::WeakPtr<ServiceWorkerVersion> service_worker) {
if (!service_worker || !service_worker->context()) {
return false;
}
return delegate->ValidateRequestForServiceWorker(
service_worker->context()->wrapper()->browser_context(),
service_worker->key().origin(), {address, port, protocol});
}},
context);
}
bool ValidateRequest(const Context& context,
const net::IPEndPoint& ip_endpoint,
DirectSocketsDelegate::ProtocolType protocol) {
return ValidateRequest(context, ip_endpoint.address().ToString(),
ip_endpoint.port(), protocol);
}
bool ValidateRequest(const Context& context,
const net::HostPortPair& host_port_pair,
DirectSocketsDelegate::ProtocolType protocol) {
return ValidateRequest(context, host_port_pair.host(), host_port_pair.port(),
protocol);
}
#if BUILDFLAG(IS_CHROMEOS)
bool ShouldOpenFirewallHole(const net::IPAddress& address) {
if (g_always_open_firewall_hole_for_testing) {
return true;
}
return !address.IsLoopback();
}
#endif // BUILDFLAG(IS_CHROMEOS)
bool RequiresPrivateNetworkAccess(const net::AddressList& addresses) {
return std::ranges::any_of(
addresses.endpoints(), [](const net::IPEndPoint& ip_endpoint) {
return network::IPAddressToIPAddressSpace(ip_endpoint.address()) ==
network::mojom::IPAddressSpace::kLocal;
});
}
void RequestPrivateNetworkAccess(const Context& context,
base::OnceCallback<void(bool)> callback) {
auto* delegate = GetContentClient()->browser()->GetDirectSocketsDelegate();
if (!delegate) {
std::move(callback).Run(/*access_allowed=*/true);
return;
}
return std::visit(
absl::Overload{
[&](content::RenderFrameHost* rfh) {
if (!rfh->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::
kDirectSocketsPrivate)) {
std::move(callback).Run(/*access_allowed=*/false);
return;
}
delegate->RequestPrivateNetworkAccess(*rfh, std::move(callback));
},
[&](base::WeakPtr<SharedWorkerHost> shared_worker) {
// TODO(crbug.com/393539884): Figure out the appropriate checks wrt
// permissions.
std::move(callback)
.Run(/*access_allowed=*/
shared_worker &&
delegate->IsPrivateNetworkAccessAllowedForSharedWorker(
CHECK_DEREF(shared_worker->GetProcessHost())
.GetBrowserContext(),
shared_worker->instance().url()));
},
[&](base::WeakPtr<ServiceWorkerVersion> service_worker) {
// TODO(crbug.com/392843918): Figure out the appropriate checks
// wrt permissions.
std::move(callback).Run(
/*access_allowed=*/service_worker &&
service_worker->context() &&
delegate->IsPrivateNetworkAccessAllowedForServiceWorker(
service_worker->context()->wrapper()->browser_context(),
service_worker->key().origin()));
}},
context);
}
template <typename FinishCallback>
void CreateSocketIfAllowed(
base::OnceCallback<void(FinishCallback)> create_socket_callback,
FinishCallback finish_callback,
bool access_allowed) {
if (access_allowed) {
std::move(create_socket_callback).Run(std::move(finish_callback));
return;
}
FulfillWithError(std::move(finish_callback),
net::ERR_BLOCKED_BY_PRIVATE_NETWORK_ACCESS_CHECKS);
}
// Queries the embedder whether private network access is allowed, and on
// success invokes `create_socket_callback` with `finish_callback`. Upon failure
// discards `create_socket_callback` and errors `finish_callback` with
// net::ERR_BLOCKED_BY_PRIVATE_NETWORK_ACCESS_CHECKS.
template <typename FinishCallback>
void RequestPrivateNetworkAccessAndCreateSocket(
const Context& context,
base::OnceCallback<void(FinishCallback)> create_socket_callback,
FinishCallback finish_callback) {
RequestPrivateNetworkAccess(
context, base::BindOnce(&CreateSocketIfAllowed<FinishCallback>,
std::move(create_socket_callback),
std::move(finish_callback)));
}
// Deletes the DirectSocketsServiceImpl when the connected document is
// destroyed.
class DocumentHelper
: public DocumentService<blink::mojom::DirectSocketsService> {
public:
DocumentHelper(
std::unique_ptr<DirectSocketsServiceImpl> service,
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver)
: DocumentService(*render_frame_host, std::move(receiver)),
service_(std::move(service)) {}
DocumentHelper(const DocumentHelper&) = delete;
DocumentHelper& operator=(const DocumentHelper&) = delete;
~DocumentHelper() override = default;
// blink::mojom::DirectSocketsService:
void OpenTCPSocket(
blink::mojom::DirectTCPSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> socket,
mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTCPSocketCallback callback) override {
service_->OpenTCPSocket(std::move(options), std::move(socket),
std::move(observer), std::move(callback));
}
void OpenConnectedUDPSocket(
blink::mojom::DirectConnectedUDPSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::RestrictedUDPSocket> receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenConnectedUDPSocketCallback callback) override {
service_->OpenConnectedUDPSocket(std::move(options), std::move(receiver),
std::move(listener), std::move(callback));
}
void OpenBoundUDPSocket(
blink::mojom::DirectBoundUDPSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::RestrictedUDPSocket> receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenBoundUDPSocketCallback callback) override {
service_->OpenBoundUDPSocket(std::move(options), std::move(receiver),
std::move(listener), std::move(callback));
}
void OpenTCPServerSocket(
blink::mojom::DirectTCPServerSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::TCPServerSocket> socket,
OpenTCPServerSocketCallback callback) override {
service_->OpenTCPServerSocket(std::move(options), std::move(socket),
std::move(callback));
}
private:
const std::unique_ptr<DirectSocketsServiceImpl> service_;
};
bool ServiceWorkerRunsInIsolatedContext(ServiceWorkerVersion& service_worker) {
auto* rph =
RenderProcessHost::FromID(service_worker.embedded_worker()->process_id());
return rph ? IsIsolatedContext(rph) : false;
}
} // namespace
#if BUILDFLAG(IS_CHROMEOS)
// This class inherits from SocketConnectionTracker so that all stored firewall
// hole handles reference |this| in the internal ReceiverSet.
class DirectSocketsServiceImpl::FirewallHoleDelegate
: public network::mojom::SocketConnectionTracker {
public:
void OpenTCPFirewallHole(
mojo::PendingReceiver<network::mojom::SocketConnectionTracker>
connection_tracker,
OpenTCPServerSocketCallback callback,
int32_t result,
const std::optional<net::IPEndPoint>& local_addr) {
if (result != net::OK) {
FulfillWithError(std::move(callback), result);
return;
}
if (!ShouldOpenFirewallHole(local_addr->address())) {
std::move(callback).Run(net::OK, *local_addr);
return;
}
auto [callback_a, callback_b] =
base::SplitOnceCallback(std::move(callback));
chromeos::FirewallHole::Open(
chromeos::FirewallHole::PortType::kTcp, local_addr->port(),
"" /*all interfaces*/,
base::BindOnce(
&FirewallHoleDelegate::OnFirewallHoleOpened, GetWeakPtr(),
std::move(connection_tracker),
/*on_success=*/
base::BindOnce(std::move(callback_a), net::OK, *local_addr),
/*on_failure=*/
base::BindOnce(std::move(callback_b),
net::ERR_NETWORK_ACCESS_DENIED, std::nullopt)));
}
void OpenUDPFirewallHole(
mojo::PendingReceiver<network::mojom::SocketConnectionTracker>
connection_tracker,
OpenBoundUDPSocketCallback callback,
int32_t result,
const std::optional<net::IPEndPoint>& local_addr) {
if (result != net::OK) {
FulfillWithError(std::move(callback), result);
return;
}
if (!ShouldOpenFirewallHole(local_addr->address())) {
std::move(callback).Run(net::OK, *local_addr);
return;
}
auto [callback_a, callback_b] =
base::SplitOnceCallback(std::move(callback));
chromeos::FirewallHole::Open(
chromeos::FirewallHole::PortType::kUdp, local_addr->port(),
"" /*all interfaces*/,
base::BindOnce(
&FirewallHoleDelegate::OnFirewallHoleOpened, GetWeakPtr(),
std::move(connection_tracker),
/*on_success=*/
base::BindOnce(std::move(callback_a), net::OK, *local_addr),
/*on_failure=*/
base::BindOnce(std::move(callback_b),
net::ERR_NETWORK_ACCESS_DENIED, std::nullopt)));
}
base::WeakPtr<FirewallHoleDelegate> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
void OnFirewallHoleOpened(
mojo::PendingReceiver<network::mojom::SocketConnectionTracker>
connection_tracker,
base::OnceClosure on_success,
base::OnceClosure on_failure,
std::unique_ptr<chromeos::FirewallHole> firewall_hole) {
if (!firewall_hole) {
std::move(on_failure).Run();
return;
}
receivers_.Add(this, std::move(connection_tracker),
std::move(firewall_hole));
std::move(on_success).Run();
}
mojo::ReceiverSet<network::mojom::SocketConnectionTracker,
std::unique_ptr<chromeos::FirewallHole>>
receivers_;
base::WeakPtrFactory<FirewallHoleDelegate> weak_factory_{this};
};
#endif // BUILDFLAG(IS_CHROMEOS)
DirectSocketsServiceImpl::DirectSocketsServiceImpl(Context context)
: context_(std::move(context)),
resolver_(network::SimpleHostResolver::Create(
/*network_context_factory=*/base::BindRepeating(
&DirectSocketsServiceImpl::GetNetworkContext,
base::Unretained(this)))) {
#if BUILDFLAG(IS_CHROMEOS)
firewall_hole_delegate_ = std::make_unique<FirewallHoleDelegate>();
#endif // BUILDFLAG(IS_CHROMEOS)
}
DirectSocketsServiceImpl::~DirectSocketsServiceImpl() = default;
// static
void DirectSocketsServiceImpl::CreateForFrame(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!base::FeatureList::IsEnabled(blink::features::kDirectSockets)) {
mojo::ReportBadMessage(
"features::kDirectSockets is disabled by command line parameters or a "
"Finch experiment.");
return;
}
if (!render_frame_host->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kDirectSockets)) {
mojo::ReportBadMessage(
"Permissions policy blocks access to Direct Sockets.");
return;
}
if (!HasIsolatedContextCapability(render_frame_host)) {
mojo::ReportBadMessage(
"Frame is not sufficiently isolated to use Direct Sockets.");
return;
}
new DocumentHelper(
base::WrapUnique(new DirectSocketsServiceImpl(render_frame_host)),
render_frame_host, std::move(receiver));
}
// static
void DirectSocketsServiceImpl::CreateForSharedWorker(
SharedWorkerHost& shared_worker,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!base::FeatureList::IsEnabled(blink::features::kDirectSockets)) {
mojo::ReportBadMessage(
"features::kDirectSockets is disabled by command line parameters or a "
"Finch experiment.");
return;
}
if (!base::FeatureList::IsEnabled(
blink::features::kDirectSocketsInSharedWorkers)) {
mojo::ReportBadMessage(
"features::kDirectSocketsInSharedWorkers is disabled by command line "
"parameters or a Finch experiment.");
return;
}
if (!IsIsolatedContext(shared_worker.GetProcessHost())) {
mojo::ReportBadMessage(
"SharedWorker is not sufficiently isolated to use Direct Sockets.");
return;
}
// TODO(crbug.com/393539884): Figure out the appropriate checks wrt
// permissions.
mojo::MakeSelfOwnedReceiver(
base::WrapUnique(new DirectSocketsServiceImpl(shared_worker.AsWeakPtr())),
std::move(receiver));
}
// static
void DirectSocketsServiceImpl::CreateForServiceWorker(
ServiceWorkerVersion& service_worker,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!base::FeatureList::IsEnabled(blink::features::kDirectSockets)) {
mojo::ReportBadMessage(
"features::kDirectSockets is disabled by command line parameters or a "
"Finch experiment.");
return;
}
if (!base::FeatureList::IsEnabled(
blink::features::kDirectSocketsInServiceWorkers)) {
mojo::ReportBadMessage(
"features::kDirectSocketsInServiceWorkers is disabled by command line "
"parameters or a Finch experiment.");
return;
}
if (!ServiceWorkerRunsInIsolatedContext(service_worker)) {
mojo::ReportBadMessage(
"ServiceWorker is not sufficiently isolated to use Direct Sockets.");
return;
}
// TODO(crbug.com/392843918): Figure out the appropriate checks wrt
// permissions.
mojo::MakeSelfOwnedReceiver(base::WrapUnique(new DirectSocketsServiceImpl(
service_worker.GetWeakPtr())),
std::move(receiver));
}
void DirectSocketsServiceImpl::OpenTCPSocket(
blink::mojom::DirectTCPSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> receiver,
mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTCPSocketCallback callback) {
net::HostPortPair remote_addr = options->remote_addr;
if (!ValidateRequest(context_, remote_addr,
DirectSocketsDelegate::ProtocolType::kTcp)) {
FulfillWithError(std::move(callback), net::ERR_ACCESS_DENIED);
return;
}
network::mojom::ResolveHostParametersPtr parameters =
network::mojom::ResolveHostParameters::New();
if (options->dns_query_type.has_value()) {
parameters->dns_query_type = *options->dns_query_type;
}
// Unretained(this) is safe here because the callback will be owned by
// |resolver_| which in turn is owned by |this|.
resolver_->ResolveHost(
network::mojom::HostResolverHost::NewHostPortPair(std::move(remote_addr)),
net::NetworkAnonymizationKey::CreateTransient(), std::move(parameters),
base::BindOnce(&DirectSocketsServiceImpl::OnResolveCompleteForTCPSocket,
base::Unretained(this), std::move(options),
std::move(receiver), std::move(observer),
std::move(callback)));
}
void DirectSocketsServiceImpl::OpenConnectedUDPSocket(
blink::mojom::DirectConnectedUDPSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::RestrictedUDPSocket> receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenConnectedUDPSocketCallback callback) {
net::HostPortPair remote_addr = options->remote_addr;
if (!ValidateRequest(context_, remote_addr,
DirectSocketsDelegate::ProtocolType::kConnectedUdp)) {
FulfillWithError(std::move(callback), net::ERR_ACCESS_DENIED);
return;
}
network::mojom::ResolveHostParametersPtr parameters =
network::mojom::ResolveHostParameters::New();
if (options->dns_query_type.has_value()) {
parameters->dns_query_type = *options->dns_query_type;
}
// Unretained(this) is safe here because the callback will be owned by
// |resolver_| which in turn is owned by |this|.
resolver_->ResolveHost(
network::mojom::HostResolverHost::NewHostPortPair(std::move(remote_addr)),
net::NetworkAnonymizationKey::CreateTransient(), std::move(parameters),
base::BindOnce(&DirectSocketsServiceImpl::OnResolveCompleteForUDPSocket,
base::Unretained(this), std::move(options),
std::move(receiver), std::move(listener),
std::move(callback)));
}
void DirectSocketsServiceImpl::OpenBoundUDPSocket(
blink::mojom::DirectBoundUDPSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::RestrictedUDPSocket> receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenBoundUDPSocketCallback callback) {
if (!ValidateRequest(context_, options->local_addr,
DirectSocketsDelegate::ProtocolType::kBoundUdp)) {
FulfillWithError(std::move(callback), net::ERR_ACCESS_DENIED);
return;
}
auto socket_options = network::mojom::UDPSocketOptions::New();
socket_options->ipv6_only = options->ipv6_only;
if (options->send_buffer_size.has_value()) {
socket_options->send_buffer_size = *options->send_buffer_size;
}
if (options->receive_buffer_size.has_value()) {
socket_options->receive_buffer_size = *options->receive_buffer_size;
}
auto params = network::mojom::RestrictedUDPSocketParams::New();
params->socket_options = std::move(socket_options);
#if BUILDFLAG(IS_CHROMEOS)
mojo::PendingReceiver<network::mojom::SocketConnectionTracker>
connection_tracker;
params->connection_tracker =
connection_tracker.InitWithNewPipeAndPassRemote();
#endif // BUILDFLAG(IS_CHROMEOS)
RequestPrivateNetworkAccessAndCreateSocket(
context_,
/*create_socket_callback=*/
base::BindOnce(&DirectSocketsServiceImpl::CreateRestrictedUDPSocketImpl,
weak_factory_.GetWeakPtr(), options->local_addr,
network::mojom::RestrictedUDPSocketMode::BOUND,
std::move(params), std::move(receiver),
std::move(listener)),
/*finish_callback=*/
#if !BUILDFLAG(IS_CHROMEOS)
std::move(callback)
#else // BUILDFLAG(IS_CHROMEOS)
// On ChromeOS the original callback will be invoked after punching a
// firewall hole.
base::BindOnce(&FirewallHoleDelegate::OpenUDPFirewallHole,
firewall_hole_delegate_->GetWeakPtr(),
std::move(connection_tracker), std::move(callback))
#endif // BUILDFLAG(IS_CHROMEOS)
);
}
void DirectSocketsServiceImpl::OpenTCPServerSocket(
blink::mojom::DirectTCPServerSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::TCPServerSocket> socket,
OpenTCPServerSocketCallback callback) {
if (!ValidateRequest(context_, options->local_addr,
DirectSocketsDelegate::ProtocolType::kTcpServer)) {
FulfillWithError(std::move(callback), net::ERR_ACCESS_DENIED);
return;
}
auto server_options = network::mojom::TCPServerSocketOptions::New();
server_options->ipv6_only = options->ipv6_only;
// Substitute |options->backlog| with SOMAXCONN if not specified.
server_options->backlog =
std::min<uint32_t>(SOMAXCONN, options->backlog.value_or(SOMAXCONN));
#if BUILDFLAG(IS_CHROMEOS)
mojo::PendingReceiver<network::mojom::SocketConnectionTracker>
connection_tracker;
server_options->connection_tracker =
connection_tracker.InitWithNewPipeAndPassRemote();
#endif // BUILDFLAG(IS_CHROMEOS)
auto* network_context = GetNetworkContext();
if (!network_context) {
FulfillWithError(std::move(callback), net::ERR_CONTEXT_SHUT_DOWN);
return;
}
network_context->CreateTCPServerSocket(
options->local_addr, std::move(server_options),
net::MutableNetworkTrafficAnnotationTag(kDirectSocketsTrafficAnnotation),
std::move(socket),
#if !BUILDFLAG(IS_CHROMEOS)
std::move(callback)
#else // BUILDFLAG(IS_CHROMEOS)
// On ChromeOS the original callback will be invoked after punching a
// firewall hole.
base::BindOnce(&FirewallHoleDelegate::OpenTCPFirewallHole,
firewall_hole_delegate_->GetWeakPtr(),
std::move(connection_tracker), std::move(callback))
#endif // BUILDFLAG(IS_CHROMEOS)
);
}
// static
void DirectSocketsServiceImpl::SetNetworkContextForTesting(
network::mojom::NetworkContext* network_context) {
GetNetworkContextForTesting() = network_context;
}
#if BUILDFLAG(IS_CHROMEOS)
// static
void DirectSocketsServiceImpl::SetAlwaysOpenFirewallHoleForTesting() {
g_always_open_firewall_hole_for_testing = true;
}
#endif // BUILDFLAG(IS_CHROMEOS)
network::mojom::NetworkContext* DirectSocketsServiceImpl::GetNetworkContext()
const {
if (auto* network_context = GetNetworkContextForTesting()) {
return network_context;
}
return std::visit(
absl::Overload{
[](RenderFrameHost* rfh) {
return rfh->GetStoragePartition()->GetNetworkContext();
},
[](base::WeakPtr<SharedWorkerHost> shared_worker)
-> network::mojom::NetworkContext* {
return shared_worker ? CHECK_DEREF(shared_worker->GetProcessHost())
.GetStoragePartition()
->GetNetworkContext()
: nullptr;
},
[](base::WeakPtr<ServiceWorkerVersion> service_worker)
-> network::mojom::NetworkContext* {
if (!service_worker || !service_worker->context()) {
return nullptr;
}
return service_worker->context()
->wrapper()
->storage_partition()
->GetNetworkContext();
}},
context_);
}
void DirectSocketsServiceImpl::OnResolveCompleteForTCPSocket(
blink::mojom::DirectTCPSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> socket,
mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTCPSocketCallback callback,
int result,
const net::ResolveErrorInfo&,
const net::AddressList& resolved_addresses,
const net::HostResolverEndpointResults&) {
if (result != net::OK) {
FulfillWithError(std::move(callback), result);
return;
}
DCHECK(!resolved_addresses.empty());
auto socket_options = network::mojom::TCPConnectedSocketOptions::New();
if (options->send_buffer_size.has_value()) {
socket_options->send_buffer_size = *options->send_buffer_size;
}
if (options->receive_buffer_size.has_value()) {
socket_options->receive_buffer_size = *options->receive_buffer_size;
}
socket_options->no_delay = options->no_delay;
if (options->keep_alive_options) {
// options->keep_alive_options will be invalidated.
socket_options->keep_alive_options = std::move(options->keep_alive_options);
}
if (!RequiresPrivateNetworkAccess(resolved_addresses)) {
CreateTCPConnectedSocketImpl(resolved_addresses, std::move(socket_options),
std::move(socket), std::move(observer),
std::move(callback));
return;
}
RequestPrivateNetworkAccessAndCreateSocket(
context_,
/*create_socket_callback=*/
base::BindOnce(&DirectSocketsServiceImpl::CreateTCPConnectedSocketImpl,
weak_factory_.GetWeakPtr(), resolved_addresses,
std::move(socket_options), std::move(socket),
std::move(observer)),
/*finish_callback=*/std::move(callback));
}
void DirectSocketsServiceImpl::CreateTCPConnectedSocketImpl(
const net::AddressList& resolved_addresses,
network::mojom::TCPConnectedSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> socket,
mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTCPSocketCallback callback) {
auto* network_context = GetNetworkContext();
if (!network_context) {
FulfillWithError(std::move(callback), net::ERR_CONTEXT_SHUT_DOWN);
return;
}
network_context->CreateTCPConnectedSocket(
/*local_addr=*/std::nullopt,
/*remote_addr_list=*/resolved_addresses, std::move(options),
net::MutableNetworkTrafficAnnotationTag(kDirectSocketsTrafficAnnotation),
std::move(socket), std::move(observer), std::move(callback));
}
void DirectSocketsServiceImpl::OnResolveCompleteForUDPSocket(
blink::mojom::DirectConnectedUDPSocketOptionsPtr options,
mojo::PendingReceiver<network::mojom::RestrictedUDPSocket>
restricted_udp_socket_receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenConnectedUDPSocketCallback callback,
int result,
const net::ResolveErrorInfo&,
const net::AddressList& resolved_addresses,
const net::HostResolverEndpointResults&) {
if (result != net::OK) {
FulfillWithError(std::move(callback), result);
return;
}
DCHECK(!resolved_addresses.empty());
auto socket_options = network::mojom::UDPSocketOptions::New();
if (options->send_buffer_size.has_value()) {
socket_options->send_buffer_size = *options->send_buffer_size;
}
if (options->receive_buffer_size.has_value()) {
socket_options->receive_buffer_size = *options->receive_buffer_size;
}
auto params = network::mojom::RestrictedUDPSocketParams::New();
params->socket_options = std::move(socket_options);
const auto& peer_addr = resolved_addresses.front();
auto finish_callback = base::BindOnce(
[](OpenConnectedUDPSocketCallback callback, net::IPEndPoint peer_addr,
int result, const std::optional<net::IPEndPoint>& local_addr) {
std::move(callback).Run(result, local_addr, peer_addr);
},
std::move(callback), peer_addr);
if (!RequiresPrivateNetworkAccess(resolved_addresses)) {
CreateRestrictedUDPSocketImpl(
resolved_addresses.front(),
network::mojom::RestrictedUDPSocketMode::CONNECTED, std::move(params),
std::move(restricted_udp_socket_receiver), std::move(listener),
std::move(finish_callback));
return;
}
RequestPrivateNetworkAccessAndCreateSocket(
context_,
/*create_socket_callback=*/
base::BindOnce(
&DirectSocketsServiceImpl::CreateRestrictedUDPSocketImpl,
weak_factory_.GetWeakPtr(), peer_addr,
network::mojom::RestrictedUDPSocketMode::CONNECTED, std::move(params),
std::move(restricted_udp_socket_receiver), std::move(listener)),
/*finish_callback=*/std::move(finish_callback));
}
void DirectSocketsServiceImpl::CreateRestrictedUDPSocketImpl(
const net::IPEndPoint& peer_addr,
network::mojom::RestrictedUDPSocketMode mode,
network::mojom::RestrictedUDPSocketParamsPtr options,
mojo::PendingReceiver<network::mojom::RestrictedUDPSocket> socket,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
base::OnceCallback<void(int32_t, const std::optional<net::IPEndPoint>&)>
callback) {
auto* network_context = GetNetworkContext();
if (!network_context) {
FulfillWithError(std::move(callback), net::ERR_CONTEXT_SHUT_DOWN);
return;
}
network_context->CreateRestrictedUDPSocket(
peer_addr, mode,
/*traffic_annotation=*/
net::MutableNetworkTrafficAnnotationTag(kDirectSocketsTrafficAnnotation),
std::move(options), std::move(socket), std::move(listener),
std::move(callback));
}
} // namespace content