Skip to content

Commit

Permalink
[DirectSockets] Support firewall holes for TCPServerSocket on ChromeOS
Browse files Browse the repository at this point in the history
This change adds some ChromeOS-specific code that attempts to open a
firewall hole after the server socket has been successfully created.

Change-Id: I2fcbf90a19f46e449d70d40075331f8f9a75cab2
Bug: 1420342
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4296540
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
Reviewed-by: Camille Lamy <clamy@chromium.org>
Commit-Queue: Andrew Rayskiy <greengrape@google.com>
Cr-Commit-Position: refs/heads/main@{#1117547}
  • Loading branch information
Andrew Rayskiy authored and Chromium LUCI CQ committed Mar 15, 2023
1 parent 1867ff1 commit a6f31c2
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 14 deletions.
26 changes: 26 additions & 0 deletions chromeos/dbus/permission_broker/fake_permission_broker_client.cc
Expand Up @@ -223,6 +223,10 @@ void FakePermissionBrokerClient::RequestTcpPortAccess(
const std::string& interface,
int lifeline_fd,
ResultCallback callback) {
if (tcp_deny_all_) {
std::move(callback).Run(false);
return;
}
std::move(callback).Run(
RequestPortImpl(port, interface, tcp_deny_rule_set_, &tcp_hole_set_));
}
Expand All @@ -232,6 +236,10 @@ void FakePermissionBrokerClient::RequestUdpPortAccess(
const std::string& interface,
int lifeline_fd,
ResultCallback callback) {
if (udp_deny_all_) {
std::move(callback).Run(false);
return;
}
std::move(callback).Run(
RequestPortImpl(port, interface, udp_deny_rule_set_, &udp_hole_set_));
}
Expand All @@ -240,24 +248,38 @@ void FakePermissionBrokerClient::ReleaseTcpPort(uint16_t port,
const std::string& interface,
ResultCallback callback) {
std::move(callback).Run(tcp_hole_set_.erase(std::make_pair(port, interface)));
if (delegate_) {
delegate_->OnTcpPortReleased(port, interface);
}
}

void FakePermissionBrokerClient::ReleaseUdpPort(uint16_t port,
const std::string& interface,
ResultCallback callback) {
std::move(callback).Run(udp_hole_set_.erase(std::make_pair(port, interface)));
if (delegate_) {
delegate_->OnUdpPortReleased(port, interface);
}
}

void FakePermissionBrokerClient::AddTcpDenyRule(uint16_t port,
const std::string& interface) {
tcp_deny_rule_set_.insert(std::make_pair(port, interface));
}

void FakePermissionBrokerClient::SetTcpDenyAll() {
tcp_deny_all_ = true;
}

void FakePermissionBrokerClient::AddUdpDenyRule(uint16_t port,
const std::string& interface) {
udp_deny_rule_set_.insert(std::make_pair(port, interface));
}

void FakePermissionBrokerClient::SetUdpDenyAll() {
udp_deny_all_ = true;
}

bool FakePermissionBrokerClient::HasTcpHole(uint16_t port,
const std::string& interface) {
auto rule = std::make_pair(port, interface);
Expand Down Expand Up @@ -327,6 +349,10 @@ void FakePermissionBrokerClient::ReleaseUdpPortForward(
std::move(callback).Run(true);
}

void FakePermissionBrokerClient::AttachDelegate(Delegate* delegate) {
delegate_ = delegate;
}

bool FakePermissionBrokerClient::RequestPortImpl(uint16_t port,
const std::string& interface,
const RuleSet& deny_rule_set,
Expand Down
25 changes: 24 additions & 1 deletion chromeos/dbus/permission_broker/fake_permission_broker_client.h
Expand Up @@ -22,6 +22,16 @@ namespace chromeos {
class COMPONENT_EXPORT(PERMISSION_BROKER) FakePermissionBrokerClient
: public PermissionBrokerClient {
public:
class Delegate {
public:
virtual ~Delegate() = default;

virtual void OnTcpPortReleased(uint16_t port,
const std::string& interface) {}
virtual void OnUdpPortReleased(uint16_t port,
const std::string& interface) {}
};

FakePermissionBrokerClient();

FakePermissionBrokerClient(const FakePermissionBrokerClient&) = delete;
Expand Down Expand Up @@ -90,9 +100,15 @@ class COMPONENT_EXPORT(PERMISSION_BROKER) FakePermissionBrokerClient
// Add a rule to have RequestTcpPortAccess fail.
void AddTcpDenyRule(uint16_t port, const std::string& interface);

// Add a rule to have RequestTcpPortAccess fail.
// Unconditionally fail all RequestTcpPortAccess calls.
void SetTcpDenyAll();

// Add a rule to have RequestUdpPortAccess fail.
void AddUdpDenyRule(uint16_t port, const std::string& interface);

// Unconditionally fail all RequestUdpPortAccess calls.
void SetUdpDenyAll();

// Returns true if TCP port has a hole.
bool HasTcpHole(uint16_t port, const std::string& interface);

Expand All @@ -105,6 +121,8 @@ class COMPONENT_EXPORT(PERMISSION_BROKER) FakePermissionBrokerClient
// Returns true if UDP port is being forwarded.
bool HasUdpPortForward(uint16_t port, const std::string& interface);

void AttachDelegate(Delegate* delegate);

private:
using RuleSet =
std::set<std::pair<uint16_t /* port */, std::string /* interface */>>;
Expand Down Expand Up @@ -147,8 +165,13 @@ class COMPONENT_EXPORT(PERMISSION_BROKER) FakePermissionBrokerClient
RuleSet tcp_deny_rule_set_;
RuleSet udp_deny_rule_set_;

bool tcp_deny_all_ = false;
bool udp_deny_all_ = false;

std::map<std::string, UsbInterfaces> clients_;

raw_ptr<Delegate> delegate_ = nullptr;

base::WeakPtrFactory<FakePermissionBrokerClient> weak_factory_{this};
};

Expand Down
106 changes: 104 additions & 2 deletions content/browser/direct_sockets/direct_sockets_service_impl.cc
Expand Up @@ -4,6 +4,8 @@

#include "content/browser/direct_sockets/direct_sockets_service_impl.h"

#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/isolated_context_util.h"
Expand All @@ -15,6 +17,7 @@
#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/unique_receiver_set.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
Expand All @@ -37,10 +40,19 @@
#include <sys/socket.h>
#endif // BUILDFLAG(IS_POSIX)

#if BUILDFLAG(IS_CHROMEOS)
#include "content/public/browser/firewall_hole_proxy.h"
#include "services/network/public/mojom/socket_connection_tracker.mojom.h"
#endif // BUILDFLAG(IS_CHROMEOS)

namespace content {

namespace {

#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 {
Expand Down Expand Up @@ -124,11 +136,77 @@ bool ValidateAddressAndPort(RenderFrameHost& rfh,

} // 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 absl::optional<net::IPEndPoint>& local_addr) {
if (result != net::OK) {
std::move(callback).Run(result, /*local_addr=*/absl::nullopt);
return;
}
if (local_addr->address().IsLoopback() &&
!g_always_open_firewall_hole_for_testing) {
std::move(callback).Run(net::OK, *local_addr);
return;
}
auto [callback_a, callback_b] =
base::SplitOnceCallback(std::move(callback));
content::OpenTCPFirewallHole(
"" /*all interfaces*/, local_addr->port(),
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, absl::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<content::FirewallHoleProxy> firewall_hole_proxy) {
if (!firewall_hole_proxy) {
std::move(on_failure).Run();
return;
}
receivers_.Add(this, std::move(connection_tracker),
std::move(firewall_hole_proxy));
std::move(on_success).Run();
}

mojo::ReceiverSet<network::mojom::SocketConnectionTracker,
std::unique_ptr<content::FirewallHoleProxy>>
receivers_;
base::WeakPtrFactory<FirewallHoleDelegate> weak_factory_{this};
};
#endif // BUILDFLAG(IS_CHROMEOS)

DirectSocketsServiceImpl::DirectSocketsServiceImpl(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::DirectSocketsService> receiver)
: DocumentService(*render_frame_host, std::move(receiver)),
resolver_(network::SimpleHostResolver::Create(GetNetworkContext())) {}
resolver_(network::SimpleHostResolver::Create(GetNetworkContext())) {
#if BUILDFLAG(IS_CHROMEOS)
firewall_hole_delegate_ = std::make_unique<FirewallHoleDelegate>();
#endif // BUILDFLAG(IS_CHROMEOS)
}

DirectSocketsServiceImpl::~DirectSocketsServiceImpl() = default;

Expand Down Expand Up @@ -269,10 +347,27 @@ void DirectSocketsServiceImpl::OpenTCPServerSocket(
server_options->backlog = std::min<uint32_t>(options->backlog, SOMAXCONN);
}

#if BUILDFLAG(IS_CHROMEOS)
mojo::PendingReceiver<network::mojom::SocketConnectionTracker>
connection_tracker;
server_options->connection_tracker =
connection_tracker.InitWithNewPipeAndPassRemote();
#endif // BUILDFLAG(IS_CHROMEOS)

GetNetworkContext()->CreateTCPServerSocket(
options->local_addr, std::move(server_options),
net::MutableNetworkTrafficAnnotationTag(kDirectSocketsTrafficAnnotation),
std::move(socket), std::move(callback));
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
Expand All @@ -281,6 +376,13 @@ void DirectSocketsServiceImpl::SetNetworkContextForTesting(
GetNetworkContextForTesting() = network_context;
}

#if BUILDFLAG(IS_CHROMEOS)
// static
void DirectSocketsServiceImpl::SetAlwaysOpenFirewallHoleForTesting(bool value) {
g_always_open_firewall_hole_for_testing = value;
}
#endif // BUILDFLAG(IS_CHROMEOS)

network::mojom::NetworkContext* DirectSocketsServiceImpl::GetNetworkContext()
const {
if (auto* network_context = GetNetworkContextForTesting()) {
Expand Down
9 changes: 9 additions & 0 deletions content/browser/direct_sockets/direct_sockets_service_impl.h
Expand Up @@ -59,6 +59,10 @@ class CONTENT_EXPORT DirectSocketsServiceImpl
// Testing:
static void SetNetworkContextForTesting(network::mojom::NetworkContext*);

#if BUILDFLAG(IS_CHROMEOS)
static void SetAlwaysOpenFirewallHoleForTesting(bool value);
#endif // BUILDFLAG(IS_CHROMEOS)

private:
DirectSocketsServiceImpl(
RenderFrameHost*,
Expand Down Expand Up @@ -87,6 +91,11 @@ class CONTENT_EXPORT DirectSocketsServiceImpl
const absl::optional<net::HostResolverEndpointResults>&);

std::unique_ptr<network::SimpleHostResolver> resolver_;

#if BUILDFLAG(IS_CHROMEOS)
class FirewallHoleDelegate;
std::unique_ptr<FirewallHoleDelegate> firewall_hole_delegate_;
#endif // BUILDFLAG(IS_CHROMEOS)
};

} // namespace content
Expand Down

0 comments on commit a6f31c2

Please sign in to comment.