summaryrefslogtreecommitdiffstats
path: root/chromeos
diff options
context:
space:
mode:
authorreillyg <reillyg@chromium.org>2015-03-13 12:41:44 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-13 19:43:02 +0000
commitbed8fdc7a0995427e0af24e63693ad790920b612 (patch)
treed5371fd6a4aa2f7cf1a33bc18a6e1eaea34bda41 /chromeos
parentfee295ea8f4d8bac532eae149029362dccfb4800 (diff)
downloadchromium_src-bed8fdc7a0995427e0af24e63693ad790920b612.zip
chromium_src-bed8fdc7a0995427e0af24e63693ad790920b612.tar.gz
chromium_src-bed8fdc7a0995427e0af24e63693ad790920b612.tar.bz2
Open a firewall hole when a TCP server or UDP socket is bound.
This patch implements firewall hole punching when a Chrome App uses the chrome.socket, chrome.sockets.tcpServer or chrome.sockets.udp APIs to open a socket to receive packets from the network. Sockets that only listen on the local loopback device do not open a port. A primitive FirewallHole class is added that could be reused by the Pepper sockets API as well. BUG=435404 Review URL: https://codereview.chromium.org/965613002 Cr-Commit-Position: refs/heads/master@{#320552}
Diffstat (limited to 'chromeos')
-rw-r--r--chromeos/chromeos.gyp3
-rw-r--r--chromeos/chromeos_switches.cc3
-rw-r--r--chromeos/chromeos_switches.h1
-rw-r--r--chromeos/dbus/fake_permission_broker_client.cc12
-rw-r--r--chromeos/network/firewall_hole.cc164
-rw-r--r--chromeos/network/firewall_hole.h80
-rw-r--r--chromeos/network/firewall_hole_unittest.cc135
7 files changed, 394 insertions, 4 deletions
diff --git a/chromeos/chromeos.gyp b/chromeos/chromeos.gyp
index 0a0c259..9deb35b 100644
--- a/chromeos/chromeos.gyp
+++ b/chromeos/chromeos.gyp
@@ -330,6 +330,8 @@
'network/managed_network_configuration_handler_impl.h',
'network/managed_state.cc',
'network/managed_state.h',
+ 'network/firewall_hole.cc',
+ 'network/firewall_hole.h',
'network/network_activation_handler.cc',
'network/network_activation_handler.h',
'network/network_cert_migrator.cc',
@@ -471,6 +473,7 @@
'login/login_state_unittest.cc',
'network/auto_connect_handler_unittest.cc',
'network/client_cert_resolver_unittest.cc',
+ 'network/firewall_hole_unittest.cc',
'network/geolocation_handler_unittest.cc',
'network/host_resolver_impl_chromeos_unittest.cc',
'network/managed_network_configuration_handler_unittest.cc',
diff --git a/chromeos/chromeos_switches.cc b/chromeos/chromeos_switches.cc
index 93d1292..31b01df 100644
--- a/chromeos/chromeos_switches.cc
+++ b/chromeos/chromeos_switches.cc
@@ -332,6 +332,9 @@ const char kDisableTimeZoneTrackingOption[] =
// Disable new GAIA sign-in flow.
const char kDisableWebviewSigninFlow[] = "disable-webview-signin-flow";
+// Enable Chrome OS firewall hole-punching for Chrome Apps.
+const char kEnableFirewallHolePunching[] = "enable-firewall-hole-punching";
+
bool WakeOnWifiEnabled() {
return !base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableWakeOnWifi);
}
diff --git a/chromeos/chromeos_switches.h b/chromeos/chromeos_switches.h
index cf1021d..e32c706 100644
--- a/chromeos/chromeos_switches.h
+++ b/chromeos/chromeos_switches.h
@@ -59,6 +59,7 @@ CHROMEOS_EXPORT extern const char kEnableCarrierSwitching[];
CHROMEOS_EXPORT extern const char kEnableConsumerManagement[];
CHROMEOS_EXPORT extern const char kEnableEmbeddedSignin[];
CHROMEOS_EXPORT extern const char kEnableExtensionAssetsSharing[];
+CHROMEOS_EXPORT extern const char kEnableFirewallHolePunching[];
CHROMEOS_EXPORT extern const char kEnableFirstRunUITransitions[];
CHROMEOS_EXPORT extern const char kEnableKioskMode[];
CHROMEOS_EXPORT extern const char kEnableNetworkPortalNotification[];
diff --git a/chromeos/dbus/fake_permission_broker_client.cc b/chromeos/dbus/fake_permission_broker_client.cc
index 17d84ac..c4b265c 100644
--- a/chromeos/dbus/fake_permission_broker_client.cc
+++ b/chromeos/dbus/fake_permission_broker_client.cc
@@ -5,6 +5,8 @@
#include "chromeos/dbus/fake_permission_broker_client.h"
#include "base/callback.h"
+#include "base/logging.h"
+#include "dbus/file_descriptor.h"
namespace chromeos {
@@ -26,7 +28,8 @@ void FakePermissionBrokerClient::RequestTcpPortAccess(
const std::string& interface,
const dbus::FileDescriptor& lifeline_fd,
const ResultCallback& callback) {
- callback.Run(false);
+ DCHECK(lifeline_fd.is_valid());
+ callback.Run(true);
}
void FakePermissionBrokerClient::RequestUdpPortAccess(
@@ -34,21 +37,22 @@ void FakePermissionBrokerClient::RequestUdpPortAccess(
const std::string& interface,
const dbus::FileDescriptor& lifeline_fd,
const ResultCallback& callback) {
- callback.Run(false);
+ DCHECK(lifeline_fd.is_valid());
+ callback.Run(true);
}
void FakePermissionBrokerClient::ReleaseTcpPort(
uint16 port,
const std::string& interface,
const ResultCallback& callback) {
- callback.Run(false);
+ callback.Run(true);
}
void FakePermissionBrokerClient::ReleaseUdpPort(
uint16 port,
const std::string& interface,
const ResultCallback& callback) {
- callback.Run(false);
+ callback.Run(true);
}
} // namespace chromeos
diff --git a/chromeos/network/firewall_hole.cc b/chromeos/network/firewall_hole.cc
new file mode 100644
index 0000000..425e423
--- /dev/null
+++ b/chromeos/network/firewall_hole.cc
@@ -0,0 +1,164 @@
+// Copyright 2015 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 "chromeos/network/firewall_hole.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/threading/worker_pool.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/permission_broker_client.h"
+#include "dbus/file_descriptor.h"
+
+namespace chromeos {
+
+namespace {
+
+// Creates a pair of file descriptors that form a "lifeline" between Chrome and
+// firewalld. If this pipe is closed unexpectedly (i.e. Chrome crashes) then
+// firewalld will notice and close the hole in the firewall.
+void CreateValidLifeline(dbus::FileDescriptor* lifeline_local,
+ dbus::FileDescriptor* lifeline_remote) {
+ int lifeline[2] = {-1, -1};
+ if (pipe2(lifeline, O_CLOEXEC) < 0) {
+ PLOG(ERROR) << "Failed to create a lifeline pipe";
+ return;
+ }
+
+ lifeline_local->PutValue(lifeline[0]);
+ lifeline_local->CheckValidity();
+
+ lifeline_remote->PutValue(lifeline[1]);
+ lifeline_remote->CheckValidity();
+}
+
+const char* PortTypeToString(FirewallHole::PortType type) {
+ switch (type) {
+ case FirewallHole::PortType::TCP:
+ return "TCP";
+ case FirewallHole::PortType::UDP:
+ return "UDP";
+ }
+ NOTREACHED();
+ return nullptr;
+}
+
+void PortReleased(FirewallHole::PortType type,
+ uint16_t port,
+ const std::string& interface,
+ FirewallHole::ScopedFileDescriptor lifeline_fd,
+ bool success) {
+ if (!success) {
+ LOG(WARNING) << "Failed to release firewall hole for "
+ << PortTypeToString(type) << " port " << port << " on "
+ << interface << ".";
+ }
+}
+
+} // namespace
+
+void CHROMEOS_EXPORT FirewallHole::FileDescriptorDeleter::operator()(
+ dbus::FileDescriptor* fd) {
+ base::WorkerPool::PostTask(
+ FROM_HERE, base::Bind(&base::DeletePointer<dbus::FileDescriptor>, fd),
+ false);
+}
+
+// static
+void FirewallHole::Open(PortType type,
+ uint16_t port,
+ const std::string& interface,
+ const OpenCallback& callback) {
+ ScopedFileDescriptor lifeline_local(new dbus::FileDescriptor());
+ ScopedFileDescriptor lifeline_remote(new dbus::FileDescriptor());
+
+ // This closure shares pointers with the one below. PostTaskAndReply
+ // guarantees that it will always be deleted first.
+ base::Closure create_lifeline_closure = base::Bind(
+ &CreateValidLifeline, lifeline_local.get(), lifeline_remote.get());
+
+ base::WorkerPool::PostTaskAndReply(
+ FROM_HERE, create_lifeline_closure,
+ base::Bind(&FirewallHole::RequestPortAccess, type, port, interface,
+ base::Passed(&lifeline_local), base::Passed(&lifeline_remote),
+ callback),
+ false);
+}
+
+FirewallHole::~FirewallHole() {
+ base::Callback<void(bool)> port_released_closure = base::Bind(
+ &PortReleased, type_, port_, interface_, base::Passed(&lifeline_fd_));
+
+ PermissionBrokerClient* client =
+ DBusThreadManager::Get()->GetPermissionBrokerClient();
+ DCHECK(client) << "Could not get permission broker client.";
+ switch (type_) {
+ case PortType::TCP:
+ client->ReleaseTcpPort(port_, interface_, port_released_closure);
+ return;
+ case PortType::UDP:
+ client->ReleaseUdpPort(port_, interface_, port_released_closure);
+ return;
+ }
+}
+
+void FirewallHole::RequestPortAccess(PortType type,
+ uint16_t port,
+ const std::string& interface,
+ ScopedFileDescriptor lifeline_local,
+ ScopedFileDescriptor lifeline_remote,
+ const OpenCallback& callback) {
+ if (!lifeline_local->is_valid() || !lifeline_remote->is_valid()) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ base::Callback<void(bool)> access_granted_closure =
+ base::Bind(&FirewallHole::PortAccessGranted, type, port, interface,
+ base::Passed(&lifeline_local), callback);
+
+ PermissionBrokerClient* client =
+ DBusThreadManager::Get()->GetPermissionBrokerClient();
+ DCHECK(client) << "Could not get permission broker client.";
+
+ switch (type) {
+ case PortType::TCP:
+ client->RequestTcpPortAccess(port, interface, *lifeline_remote,
+ access_granted_closure);
+ return;
+ case PortType::UDP:
+ client->RequestUdpPortAccess(port, interface, *lifeline_remote,
+ access_granted_closure);
+ return;
+ }
+}
+
+void FirewallHole::PortAccessGranted(PortType type,
+ uint16_t port,
+ const std::string& interface,
+ ScopedFileDescriptor lifeline_fd,
+ const FirewallHole::OpenCallback& callback,
+ bool success) {
+ if (success) {
+ callback.Run(make_scoped_ptr(
+ new FirewallHole(type, port, interface, lifeline_fd.Pass())));
+ } else {
+ callback.Run(nullptr);
+ }
+}
+
+FirewallHole::FirewallHole(PortType type,
+ uint16_t port,
+ const std::string& interface,
+ ScopedFileDescriptor lifeline_fd)
+ : type_(type),
+ port_(port),
+ interface_(interface),
+ lifeline_fd_(lifeline_fd.Pass()) {
+}
+
+} // namespace chromeos
diff --git a/chromeos/network/firewall_hole.h b/chromeos/network/firewall_hole.h
new file mode 100644
index 0000000..2bd6788
--- /dev/null
+++ b/chromeos/network/firewall_hole.h
@@ -0,0 +1,80 @@
+// Copyright 2015 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 CHROMEOS_NETWORK_FIREWALL_HOLE_H_
+#define CHROMEOS_NETWORK_FIREWALL_HOLE_H_
+
+#include <stdint.h>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/chromeos_export.h"
+
+namespace dbus {
+class FileDescriptor;
+}
+
+namespace chromeos {
+
+// This class works with the Chrome OS permission broker to open a port in the
+// system firewall. It is closed on destruction.
+class CHROMEOS_EXPORT FirewallHole {
+ public:
+ enum class PortType {
+ UDP,
+ TCP,
+ };
+
+ typedef base::Callback<void(scoped_ptr<FirewallHole>)> OpenCallback;
+
+ // This provides a simple way to pass around file descriptors since they must
+ // be closed on a thread that is allowed to perform I/O.
+ struct FileDescriptorDeleter {
+ void CHROMEOS_EXPORT operator()(dbus::FileDescriptor* fd);
+ };
+ typedef scoped_ptr<dbus::FileDescriptor, FileDescriptorDeleter>
+ ScopedFileDescriptor;
+
+ // Opens a port on the system firewall for the given network interface (or all
+ // interfaces if |interface| is ""). The hole will be closed when the object
+ // provided to the callback is destroyed.
+ static void Open(PortType type,
+ uint16_t port,
+ const std::string& interface,
+ const OpenCallback& callback);
+
+ ~FirewallHole();
+
+ private:
+ static void RequestPortAccess(PortType type,
+ uint16_t port,
+ const std::string& interface,
+ ScopedFileDescriptor lifeline_local,
+ ScopedFileDescriptor lifeline_remote,
+ const OpenCallback& callback);
+
+ static void PortAccessGranted(PortType type,
+ uint16_t port,
+ const std::string& interface,
+ ScopedFileDescriptor lifeline_fd,
+ const FirewallHole::OpenCallback& callback,
+ bool success);
+
+ FirewallHole(PortType type,
+ uint16_t port,
+ const std::string& interface,
+ ScopedFileDescriptor lifeline_fd);
+
+ const PortType type_;
+ const uint16_t port_;
+ const std::string interface_;
+
+ // A file descriptor used by firewalld to track the lifetime of this process.
+ ScopedFileDescriptor lifeline_fd_;
+};
+
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_FIREWALL_HOLE_H_
diff --git a/chromeos/network/firewall_hole_unittest.cc b/chromeos/network/firewall_hole_unittest.cc
new file mode 100644
index 0000000..ea5f5ec
--- /dev/null
+++ b/chromeos/network/firewall_hole_unittest.cc
@@ -0,0 +1,135 @@
+// Copyright 2015 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/bind.h"
+#include "base/run_loop.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_permission_broker_client.h"
+#include "chromeos/network/firewall_hole.h"
+#include "dbus/file_descriptor.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using chromeos::DBusThreadManager;
+using chromeos::FirewallHole;
+using testing::_;
+
+namespace {
+
+ACTION_TEMPLATE(InvokeCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(p1)) {
+ ::std::tr1::get<k>(args).Run(p1);
+}
+
+class MockPermissionsBrokerClient : public chromeos::PermissionBrokerClient {
+ public:
+ MockPermissionsBrokerClient() {}
+ ~MockPermissionsBrokerClient() override {}
+
+ MOCK_METHOD1(Init, void(dbus::Bus* bus));
+ MOCK_METHOD3(RequestPathAccess,
+ void(const std::string& path,
+ int interface_id,
+ const ResultCallback& callback));
+ MOCK_METHOD4(RequestTcpPortAccess,
+ void(uint16 port,
+ const std::string& interface,
+ const dbus::FileDescriptor& lifeline_fd,
+ const ResultCallback& callback));
+ MOCK_METHOD4(RequestUdpPortAccess,
+ void(uint16 port,
+ const std::string& interface,
+ const dbus::FileDescriptor& lifeline_fd,
+ const ResultCallback& callback));
+ MOCK_METHOD3(ReleaseTcpPort,
+ void(uint16 port,
+ const std::string& interface,
+ const ResultCallback& callback));
+ MOCK_METHOD3(ReleaseUdpPort,
+ void(uint16 port,
+ const std::string& interface,
+ const ResultCallback& callback));
+};
+
+} // namespace
+
+class FirewallHoleTest : public testing::Test {
+ public:
+ FirewallHoleTest() {}
+ ~FirewallHoleTest() override {}
+
+ void SetUp() override {
+ mock_permissions_broker_client_ = new MockPermissionsBrokerClient();
+ DBusThreadManager::GetSetterForTesting()->SetPermissionBrokerClient(
+ make_scoped_ptr(mock_permissions_broker_client_));
+ }
+
+ void TearDown() override { DBusThreadManager::Shutdown(); }
+
+ void AssertOpenSuccess(scoped_ptr<FirewallHole> hole) {
+ EXPECT_TRUE(hole.get() != nullptr);
+ run_loop_.Quit();
+ }
+
+ void AssertOpenFailure(scoped_ptr<FirewallHole> hole) {
+ EXPECT_TRUE(hole.get() == nullptr);
+ run_loop_.Quit();
+ }
+
+ private:
+ base::MessageLoopForUI message_loop_;
+
+ protected:
+ base::RunLoop run_loop_;
+ MockPermissionsBrokerClient* mock_permissions_broker_client_ = nullptr;
+};
+
+TEST_F(FirewallHoleTest, GrantTcpPortAccess) {
+ EXPECT_CALL(*mock_permissions_broker_client_,
+ RequestTcpPortAccess(1234, "foo0", _, _))
+ .WillOnce(InvokeCallback<3>(true));
+ EXPECT_CALL(*mock_permissions_broker_client_, ReleaseTcpPort(1234, "foo0", _))
+ .WillOnce(InvokeCallback<2>(true));
+
+ FirewallHole::Open(
+ FirewallHole::PortType::TCP, 1234, "foo0",
+ base::Bind(&FirewallHoleTest::AssertOpenSuccess, base::Unretained(this)));
+ run_loop_.Run();
+}
+
+TEST_F(FirewallHoleTest, DenyTcpPortAccess) {
+ EXPECT_CALL(*mock_permissions_broker_client_,
+ RequestTcpPortAccess(1234, "foo0", _, _))
+ .WillOnce(InvokeCallback<3>(false));
+
+ FirewallHole::Open(
+ FirewallHole::PortType::TCP, 1234, "foo0",
+ base::Bind(&FirewallHoleTest::AssertOpenFailure, base::Unretained(this)));
+ run_loop_.Run();
+}
+
+TEST_F(FirewallHoleTest, GrantUdpPortAccess) {
+ EXPECT_CALL(*mock_permissions_broker_client_,
+ RequestUdpPortAccess(1234, "foo0", _, _))
+ .WillOnce(InvokeCallback<3>(true));
+ EXPECT_CALL(*mock_permissions_broker_client_, ReleaseUdpPort(1234, "foo0", _))
+ .WillOnce(InvokeCallback<2>(true));
+
+ FirewallHole::Open(
+ FirewallHole::PortType::UDP, 1234, "foo0",
+ base::Bind(&FirewallHoleTest::AssertOpenSuccess, base::Unretained(this)));
+ run_loop_.Run();
+}
+
+TEST_F(FirewallHoleTest, DenyUdpPortAccess) {
+ EXPECT_CALL(*mock_permissions_broker_client_,
+ RequestUdpPortAccess(1234, "foo0", _, _))
+ .WillOnce(InvokeCallback<3>(false));
+
+ FirewallHole::Open(
+ FirewallHole::PortType::UDP, 1234, "foo0",
+ base::Bind(&FirewallHoleTest::AssertOpenFailure, base::Unretained(this)));
+ run_loop_.Run();
+}