Skip to content

Commit

Permalink
Direct Sockets: stub implementations for openTCPSocket openUDPSocket
Browse files Browse the repository at this point in the history
The rename is tentative, blocked on naming decision
WICG/direct-sockets#10


Explainer: https://github.com/WICG/raw-sockets/blob/master/docs/explainer.md

Intent to Prototype:
https://groups.google.com/a/chromium.org/g/blink-dev/c/ARtkaw4e9T4/m/npjeMssPCAAJ

Design doc:
https://docs.google.com/document/d/1Xa5nFkIWxkL3hZHvDYWPhT8sZvNeFpCUKNuqIwZHxnE/edit?usp=sharing


We introduce IDL methods for opening TCP and UDP sockets.

We introduce a mojom service that will perform permission checks
before forwarding socket opening requests to the Network Service.

Not yet implemented:
Permissions checks, calls to Network Service

Bug: 909927
Change-Id: Ifec9860d4e0b8211af7b142b856efc29cd6f9b35
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2366395
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Glen Robertson <glenrob@chromium.org>
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Auto-Submit: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#802210}
  • Loading branch information
ericwilligers authored and Commit Bot committed Aug 27, 2020
1 parent 41fd12b commit 852cbbc
Show file tree
Hide file tree
Showing 39 changed files with 892 additions and 3 deletions.
2 changes: 2 additions & 0 deletions content/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,8 @@ source_set("browser") {
"devtools/shared_worker_devtools_manager.h",
"devtools/worker_devtools_agent_host.cc",
"devtools/worker_devtools_agent_host.h",
"direct_sockets/direct_sockets_service_impl.cc",
"direct_sockets/direct_sockets_service_impl.h",
"display_cutout/display_cutout_constants.h",
"display_cutout/display_cutout_host_impl.cc",
"display_cutout/display_cutout_host_impl.h",
Expand Down
5 changes: 5 additions & 0 deletions content/browser/browser_interface_binders.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
#include "third_party/blink/public/public_buildflags.h"

#if !defined(OS_ANDROID)
#include "content/browser/direct_sockets/direct_sockets_service_impl.h"
#include "content/browser/installedapp/installed_app_provider_impl.h"
#include "content/public/common/content_switches.h"
#include "media/mojo/mojom/speech_recognition_service.mojom.h"
Expand Down Expand Up @@ -739,6 +740,10 @@ void PopulateBinderMapWithContext(
map->Add<blink::mojom::CredentialManager>(base::BindRepeating(
&EmptyBinderForFrame<blink::mojom::CredentialManager>));
#if !defined(OS_ANDROID)
if (base::FeatureList::IsEnabled(features::kDirectSockets)) {
map->Add<blink::mojom::DirectSocketsService>(
base::BindRepeating(&DirectSocketsServiceImpl::CreateForFrame));
}
map->Add<media::mojom::SpeechRecognitionContext>(base::BindRepeating(
&EmptyBinderForFrame<media::mojom::SpeechRecognitionContext>));
#endif
Expand Down
1 change: 1 addition & 0 deletions content/browser/direct_sockets/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
file://third_party/blink/renderer/modules/direct_sockets/OWNERS
13 changes: 13 additions & 0 deletions content/browser/direct_sockets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This directory implements permission and user consent checks for the
browser side of socket opening for the [Direct Sockets API](
https://github.com/WICG/raw-sockets/blob/master/docs/explainer.md).

Examples of the checks include
- user dialog, allowing user to enter destination address
- permissions policy
- rate limiting
- checking hostnames resolve to public addresses
- content security policy

When requests to establish TCP or UDP communication have passed the
various checks, they are forwarded to the network service.
94 changes: 94 additions & 0 deletions content/browser/direct_sockets/direct_sockets_browsertest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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 "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/direct_sockets/direct_sockets_service_impl.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "net/base/net_errors.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "url/gurl.h"

namespace content {

class DirectSocketsBrowserTest : public ContentBrowserTest {
public:
DirectSocketsBrowserTest() {
feature_list_.InitAndEnableFeature(features::kDirectSockets);
}
~DirectSocketsBrowserTest() override = default;

GURL GetTestPageURL() {
return embedded_test_server()->GetURL("/direct_sockets/index.html");
}

protected:
void SetUp() override {
embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(embedded_test_server()->Start());

ContentBrowserTest::SetUp();
}

private:
base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest, OpenTcp_Success) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestPageURL()));

DirectSocketsServiceImpl::SetPermissionCallbackForTesting(
base::BindLambdaForTesting(
[&](const blink::mojom::DirectSocketOptions&) { return net::OK; }));

// TODO(crbug.com/905818): Use port from a listening net::TCPServerSocket.
const std::string script = base::StringPrintf(
"openTcp({remoteAddress: '127.0.0.1', remotePort: %d})", 0);

EXPECT_EQ("openTcp succeeded", EvalJs(shell(), script));
}

IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest, OpenTcp_NotAllowedError) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestPageURL()));

// TODO(crbug.com/905818): Use port from a listening net::TCPServerSocket.
const std::string script = base::StringPrintf(
"openTcp({remoteAddress: '127.0.0.1', remotePort: %d})", 0);

EXPECT_EQ("openTcp failed: NotAllowedError: Permission denied",
EvalJs(shell(), script));
}

IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest, OpenUdp_Success) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestPageURL()));

DirectSocketsServiceImpl::SetPermissionCallbackForTesting(
base::BindLambdaForTesting(
[&](const blink::mojom::DirectSocketOptions&) { return net::OK; }));

// TODO(crbug.com/1119620): Use port from a listening net::UDPServerSocket.
const std::string script = base::StringPrintf(
"openUdp({remoteAddress: '127.0.0.1', remotePort: %d})", 0);

EXPECT_EQ("openUdp succeeded", EvalJs(shell(), script));
}

IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest, OpenUdp_NotAllowedError) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestPageURL()));

// TODO(crbug.com/1119620): Use port from a listening net::UDPServerSocket.
const std::string script = base::StringPrintf(
"openUdp({remoteAddress: '127.0.0.1', remotePort: %d})", 0);

EXPECT_EQ("openUdp failed: NotAllowedError: Permission denied",
EvalJs(shell(), script));
}

} // namespace content
117 changes: 117 additions & 0 deletions content/browser/direct_sockets/direct_sockets_service_impl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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/common/content_features.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"

namespace content {

namespace {

DirectSocketsServiceImpl::PermissionCallback&
GetPermissionCallbackForTesting() {
static base::NoDestructor<DirectSocketsServiceImpl::PermissionCallback>
callback;
return *callback;
}

} // namespace

DirectSocketsServiceImpl::DirectSocketsServiceImpl(RenderFrameHost& 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,
OpenTcpSocketCallback callback) {
if (!options) {
mojo::ReportBadMessage("Invalid request to open socket");
return;
}
net::Error result = EnsurePermission(*options);

// TODO(crbug.com/1119681): Collect metrics for usage and permission checks

if (result == net::OK) {
// TODO(crbug.com/905818): GetNetworkContext()->CreateTCPConnectedSocket
GetNetworkContext();
NOTIMPLEMENTED();
}

std::move(callback).Run(result);
}

void DirectSocketsServiceImpl::OpenUdpSocket(
blink::mojom::DirectSocketOptionsPtr options,
OpenUdpSocketCallback callback) {
if (!options) {
mojo::ReportBadMessage("Invalid request to open socket");
return;
}
net::Error result = EnsurePermission(*options);

// TODO(crbug.com/1119681): Collect metrics for usage and permission checks

if (result == net::OK) {
// TODO(crbug.com/1119620): GetNetworkContext()->CreateUDPSocket
GetNetworkContext();
NOTIMPLEMENTED();
}

std::move(callback).Run(result);
}

// static
void DirectSocketsServiceImpl::SetPermissionCallbackForTesting(
PermissionCallback callback) {
GetPermissionCallbackForTesting() = std::move(callback);
}

net::Error DirectSocketsServiceImpl::EnsurePermission(
const blink::mojom::DirectSocketOptions& options) {
DCHECK(base::FeatureList::IsEnabled(features::kDirectSockets));

if (GetPermissionCallbackForTesting())
return GetPermissionCallbackForTesting().Run(options);

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): Check for transient activation.
// TODO(crbug.com/1119600): Implement rate limiting.
// TODO(crbug.com/1119601): Check CORS iff requested port is HTTPS.

// EnsurePermission() will need to become asynchronous:
// TODO(crbug.com/1119597): Show consent dialog
// TODO(crbug.com/1119661): Reject hostnames that resolve to non-public
// addresses.

return net::ERR_NOT_IMPLEMENTED;
}

network::mojom::NetworkContext* DirectSocketsServiceImpl::GetNetworkContext() {
return frame_host_->GetStoragePartition()->GetNetworkContext();
}

} // namespace content
60 changes: 60 additions & 0 deletions content/browser/direct_sockets/direct_sockets_service_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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.

#ifndef CONTENT_BROWSER_DIRECT_SOCKETS_DIRECT_SOCKETS_SERVICE_IMPL_H_
#define CONTENT_BROWSER_DIRECT_SOCKETS_DIRECT_SOCKETS_SERVICE_IMPL_H_

#include <string>

#include "base/callback.h"
#include "content/common/content_export.h"
#include "content/public/browser/render_frame_host.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "net/base/net_errors.h"
#include "third_party/blink/public/mojom/direct_sockets/direct_sockets.mojom.h"

namespace network {
namespace mojom {
class NetworkContext;
}
} // namespace network

namespace content {

// Implementation of the DirectSocketsService Mojo service.
class CONTENT_EXPORT DirectSocketsServiceImpl
: public blink::mojom::DirectSocketsService {
public:
using PermissionCallback = base::RepeatingCallback<net::Error(
const blink::mojom::DirectSocketOptions&)>;

explicit DirectSocketsServiceImpl(RenderFrameHost& frame_host);
~DirectSocketsServiceImpl() override = default;

DirectSocketsServiceImpl(const DirectSocketsServiceImpl&) = delete;
DirectSocketsServiceImpl& operator=(const DirectSocketsServiceImpl&) = delete;

static void CreateForFrame(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver);

// blink::mojom::DirectSocketsService override:
void OpenTcpSocket(blink::mojom::DirectSocketOptionsPtr options,
OpenTcpSocketCallback callback) override;
void OpenUdpSocket(blink::mojom::DirectSocketOptionsPtr options,
OpenUdpSocketCallback callback) override;

static void SetPermissionCallbackForTesting(PermissionCallback callback);

private:
net::Error EnsurePermission(const blink::mojom::DirectSocketOptions& options);

network::mojom::NetworkContext* GetNetworkContext();

RenderFrameHost* const frame_host_;
};

} // namespace content

#endif // CONTENT_BROWSER_DIRECT_SOCKETS_DIRECT_SOCKETS_SERVICE_IMPL_H_
3 changes: 3 additions & 0 deletions content/child/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@ void SetCustomizedRuntimeFeaturesFromCombinedArgs(
WebRuntimeFeatures::EnableBackForwardCache(
content::IsBackForwardCacheEnabled());

if (base::FeatureList::IsEnabled(features::kDirectSockets))
WebRuntimeFeatures::EnableDirectSockets(true);

if (base::FeatureList::IsEnabled(
blink::features::kAppCacheRequireOriginTrial)) {
// The kAppCacheRequireOriginTrial is a flag that controls whether or not
Expand Down
4 changes: 4 additions & 0 deletions content/public/common/content_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,10 @@ const base::Feature kProactivelySwapBrowsingInstance{
const base::Feature kPushSubscriptionChangeEvent{
"PushSubscriptionChangeEvent", base::FEATURE_DISABLED_BY_DEFAULT};

// Enables the Direct Sockets API.
const base::Feature kDirectSockets{"DirectSockets",
base::FEATURE_DISABLED_BY_DEFAULT};

// Controls whether FileURLLoaderFactory can fetch additional files based on the
// isolated world's origin. This feature is disabled by default because we want
// content scripts to have the same permissions as the page they are injected
Expand Down
1 change: 1 addition & 0 deletions content/public/common/content_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ CONTENT_EXPORT extern const base::Feature
CONTENT_EXPORT extern const base::Feature
kProcessSharingWithStrictSiteInstances;
CONTENT_EXPORT extern const base::Feature kPushSubscriptionChangeEvent;
CONTENT_EXPORT extern const base::Feature kDirectSockets;
CONTENT_EXPORT extern const base::Feature
kRelaxIsolatedWorldCorsInFileUrlLoaderFactory;
CONTENT_EXPORT extern const base::Feature kReloadHiddenTabsWithCrashedSubframes;
Expand Down
1 change: 1 addition & 0 deletions content/test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,7 @@ test("content_browsertests") {
} else {
# Non-Android.
sources += [
"../browser/direct_sockets/direct_sockets_browsertest.cc",
"../browser/host_zoom_map_impl_browsertest.cc",
"../browser/serial/serial_browsertest.cc",
"../browser/speech/speech_recognition_browsertest.cc",
Expand Down
31 changes: 31 additions & 0 deletions content/test/data/direct_sockets/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<title>Direct Sockets Test</title>
<script>
'use strict';

async function openTcp(options) {
try {
let tcpSocket = await navigator.openTCPSocket(options);
return 'openTcp succeeded';
} catch(error) {
return ('openTcp failed: ' + error);
}
}

async function openUdp(options) {
try {
let udpSocket = await navigator.openUDPSocket(options);
return 'openUdp succeeded';
} catch(error) {
return ('openUdp failed: ' + error);
}
}

</script>
</head>
<body>
<h1>Direct Sockets Test</h1>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ const char* FeatureToString(WebSchedulerTrackedFeature feature) {
return "KeyboardLock";
case WebSchedulerTrackedFeature::kSmsService:
return "SMSService";
case WebSchedulerTrackedFeature::kOutstandingNetworkRequestDirectSocket:
return "outstanding network request (direct socket)";
}
}

Expand Down
Loading

0 comments on commit 852cbbc

Please sign in to comment.