Skip to content

Commit

Permalink
Add a per-process limit on open UDP sockets.
Browse files Browse the repository at this point in the history
This adds a default limit of 6000 on the open UDP sockets throughout the entire process, configurable with the "LimitOpenUDPSockets" feature.

An "open UDP socket" specifically means a net::UDPSocket which successfully called Open(), and has not yet called Close().

Once the limit has been reached, opening UDP socket will fail with ERR_INSUFFICIENT_RESOURCES.

In Chrome Browser, UDP sockets are brokered through a single process (that hosting the Network Service), so this is functionally a browser-wide limit too.

Bug: 1083278

Change-Id: Ib95ab14b7ccf5e15410b9df9537c66c858de2d7d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2350395
Reviewed-by: David Schinazi <dschinazi@chromium.org>
Commit-Queue: Eric Roman <eroman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797523}
  • Loading branch information
Eric Roman authored and Commit Bot committed Aug 13, 2020
1 parent 9b80c3e commit 5a84192
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 0 deletions.
2 changes: 2 additions & 0 deletions net/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,8 @@ component("net") {
"socket/udp_server_socket.cc",
"socket/udp_server_socket.h",
"socket/udp_socket.h",
"socket/udp_socket_global_limits.cc",
"socket/udp_socket_global_limits.h",
"socket/websocket_endpoint_lock_manager.cc",
"socket/websocket_endpoint_lock_manager.h",
"socket/websocket_transport_client_socket_pool.cc",
Expand Down
8 changes: 8 additions & 0 deletions net/base/features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,13 @@ const base::Feature kReportPoorConnectivity{"ReportPoorConnectivity",
const base::Feature kPreemptiveMobileNetworkActivation{
"PreemptiveMobileNetworkActivation", base::FEATURE_DISABLED_BY_DEFAULT};

const base::Feature kLimitOpenUDPSockets{"LimitOpenUDPSockets",
base::FEATURE_ENABLED_BY_DEFAULT};

extern const base::FeatureParam<int> kLimitOpenUDPSocketsMax(
&kLimitOpenUDPSockets,
"LimitOpenUDPSocketsMax",
6000);

} // namespace features
} // namespace net
10 changes: 10 additions & 0 deletions net/base/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,16 @@ NET_EXPORT extern const base::Feature kReportPoorConnectivity;
// the Wi-Fi connection.
NET_EXPORT extern const base::Feature kPreemptiveMobileNetworkActivation;

// Enables a process-wide limit on "open" UDP sockets. See
// udp_socket_global_limits.h for details on what constitutes an "open" socket.
NET_EXPORT extern const base::Feature kLimitOpenUDPSockets;

// FeatureParams associated with kLimitOpenUDPSockets.

// Sets the maximum allowed open UDP sockets. Provisioning more sockets than
// this will result in a failure (ERR_INSUFFICIENT_RESOURCES).
NET_EXPORT extern const base::FeatureParam<int> kLimitOpenUDPSocketsMax;

} // namespace features
} // namespace net

Expand Down
91 changes: 91 additions & 0 deletions net/socket/udp_socket_global_limits.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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 <limits>

#include "base/atomic_ref_count.h"
#include "base/no_destructor.h"
#include "net/base/features.h"
#include "net/socket/udp_socket_global_limits.h"

namespace net {

namespace {

// Threadsafe singleton for tracking the process-wide count of UDP sockets.
class GlobalUDPSocketCounts {
public:
GlobalUDPSocketCounts() : count_(0) {}

~GlobalUDPSocketCounts() = delete;

static GlobalUDPSocketCounts& Get() {
static base::NoDestructor<GlobalUDPSocketCounts> singleton;
return *singleton;
}

bool TryAcquireSocket() WARN_UNUSED_RESULT {
int previous = count_.Increment(1);
if (previous >= GetMax()) {
count_.Increment(-1);
return false;
}

return true;
}

int GetMax() {
if (base::FeatureList::IsEnabled(features::kLimitOpenUDPSockets))
return features::kLimitOpenUDPSocketsMax.Get();

return std::numeric_limits<int>::max();
}

void ReleaseSocket() { count_.Increment(-1); }

int GetCountForTesting() { return count_.SubtleRefCountForDebug(); }

private:
base::AtomicRefCount count_;
};

} // namespace

OwnedUDPSocketCount::OwnedUDPSocketCount() : OwnedUDPSocketCount(true) {}

OwnedUDPSocketCount::OwnedUDPSocketCount(OwnedUDPSocketCount&& other) {
*this = std::move(other);
}

OwnedUDPSocketCount& OwnedUDPSocketCount::operator=(
OwnedUDPSocketCount&& other) {
Reset();
empty_ = other.empty_;
other.empty_ = true;
return *this;
}

OwnedUDPSocketCount::~OwnedUDPSocketCount() {
Reset();
}

void OwnedUDPSocketCount::Reset() {
if (!empty_) {
GlobalUDPSocketCounts::Get().ReleaseSocket();
empty_ = true;
}
}

OwnedUDPSocketCount::OwnedUDPSocketCount(bool empty) : empty_(empty) {}

OwnedUDPSocketCount TryAcquireGlobalUDPSocketCount() {
bool success = GlobalUDPSocketCounts::Get().TryAcquireSocket();
return OwnedUDPSocketCount(!success);
}

int GetGlobalUDPSocketCountForTesting() {
return GlobalUDPSocketCounts::Get().GetCountForTesting();
}

} // namespace net
75 changes: 75 additions & 0 deletions net/socket/udp_socket_global_limits.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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 NET_SOCKET_UDP_SOCKET_GLOBAL_LIMITS_H_
#define NET_SOCKET_UDP_SOCKET_GLOBAL_LIMITS_H_

#include "base/compiler_specific.h"
#include "net/base/net_errors.h"
#include "net/base/net_export.h"

namespace net {

// Helper class for RAII-style management of the global count of "open UDP
// sockets" [1] in the process.
//
// Keeping OwnedUDPSocketCount alive increases the global socket counter by 1.
// When it goes out of scope - or is explicitly Reset() - the reference is
// returned to the global counter.
class NET_EXPORT OwnedUDPSocketCount {
public:
// The default constructor builds an empty OwnedUDPSocketCount (does not own a
// count).
OwnedUDPSocketCount();

// Any count held by OwnedUDPSocketCount is transferred when moving.
OwnedUDPSocketCount(OwnedUDPSocketCount&&);
OwnedUDPSocketCount& operator=(OwnedUDPSocketCount&&);

// This is a move-only type.
OwnedUDPSocketCount(const OwnedUDPSocketCount&) = delete;
OwnedUDPSocketCount& operator=(const OwnedUDPSocketCount&) = delete;

~OwnedUDPSocketCount();

// Returns false if this instance "owns" a socket count. In
// other words, when |empty()|, destruction of |this| will
// not change the global socket count.
bool empty() const { return empty_; }

// Resets |this| to an empty state (|empty()| becomes true after
// calling this). If |this| was previously |!empty()|, the global
// socket count will be decremented.
void Reset();

private:
// Only TryAcquireGlobalUDPSocketCount() is allowed to construct a non-empty
// OwnedUDPSocketCount.
friend NET_EXPORT OwnedUDPSocketCount TryAcquireGlobalUDPSocketCount();
explicit OwnedUDPSocketCount(bool empty);

bool empty_;
};

// Attempts to increase the global "open UDP socket" [1] count.
//
// * On failure returns an OwnedUDPSocketCount that is |empty()|. This happens
// if the global socket limit has been reached.
// * On success returns an OwnedUDPSocketCount that is |!empty()|. This
// OwnedUDPSocketCount should be kept alive until the socket resource is
// released.
//
// [1] For simplicity, an "open UDP socket" is defined as a net::UDPSocket that
// successfully called Open(), and has not yet called Close(). This is
// analogous to the number of open platform socket handles, and in practice
// should also be a good proxy for the number of consumed UDP ports.
NET_EXPORT OwnedUDPSocketCount TryAcquireGlobalUDPSocketCount()
WARN_UNUSED_RESULT;

// Returns the current count of open UDP sockets (for testing only).
NET_EXPORT int GetGlobalUDPSocketCountForTesting();

} // namespace net

#endif // NET_SOCKET_UDP_SOCKET_GLOBAL_LIMITS_H_
8 changes: 8 additions & 0 deletions net/socket/udp_socket_posix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ int UDPSocketPosix::Open(AddressFamily address_family) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_EQ(socket_, kInvalidSocket);

auto owned_socket_count = TryAcquireGlobalUDPSocketCount();
if (owned_socket_count.empty())
return ERR_INSUFFICIENT_RESOURCES;

addr_family_ = ConvertAddressFamily(address_family);
socket_ = CreatePlatformSocket(addr_family_, SOCK_DGRAM, 0);
if (socket_ == kInvalidSocket)
Expand All @@ -231,6 +235,8 @@ int UDPSocketPosix::Open(AddressFamily address_family) {
}
if (tag_ != SocketTag())
tag_.Apply(socket_);

owned_socket_count_ = std::move(owned_socket_count);
return OK;
}

Expand Down Expand Up @@ -292,6 +298,8 @@ void UDPSocketPosix::ReceivedActivityMonitor::NetworkActivityMonitorIncrement(
void UDPSocketPosix::Close() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

owned_socket_count_.Reset();

if (socket_ == kInvalidSocket)
return;

Expand Down
5 changes: 5 additions & 0 deletions net/socket/udp_socket_posix.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "net/socket/diff_serv_code_point.h"
#include "net/socket/socket_descriptor.h"
#include "net/socket/socket_tag.h"
#include "net/socket/udp_socket_global_limits.h"
#include "net/traffic_annotation/network_traffic_annotation.h"

#if defined(__ANDROID__) && defined(__aarch64__)
Expand Down Expand Up @@ -630,6 +631,10 @@ class NET_EXPORT UDPSocketPosix {
// enable_experimental_recv_optimization() method.
bool experimental_recv_optimization_enabled_;

// Manages decrementing the global open UDP socket counter when this
// UDPSocket is destroyed.
OwnedUDPSocketCount owned_socket_count_;

THREAD_CHECKER(thread_checker_);

// Used for alternate writes that are posted for concurrent execution.
Expand Down
Loading

0 comments on commit 5a84192

Please sign in to comment.