Skip to content

Commit ea155d2

Browse files
committed
proxy.config.http.per_client.connection.allow_list.filename
This implements proxy.config.http.per_client.connection.allow_list.filename, a configuration for the user to be able to provide a set of IP addresses that are not counted against proxy.config.net.per_client.max_connections_in.
1 parent 9b571e4 commit ea155d2

File tree

11 files changed

+310
-55
lines changed

11 files changed

+310
-55
lines changed

doc/admin-guide/files/records.yaml.en.rst

+34-1
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,39 @@ Network
524524
below this limit. A value of 0 disables the per client concurrent connection
525525
limit.
526526

527+
See :ts:cv:`proxy.config.http.per_client.connection.allow_list.filename` for a way to
528+
allow (not count) certain client IP addresses when applying this limit.
529+
530+
.. ts:cv:: CONFIG proxy.config.http.per_client.connection.allow_list.filename STRING NULL
531+
532+
A path to a YAML formatted file containing a sequence of IP addresses to ignore
533+
when counting incoming client connections. Incoming addresses in this
534+
specified set will not count against
535+
:ts:cv:`proxy.config.net.per_client.max_connections_in` and thus will not be
536+
blocked for that configuration. This may be useful, for example, to allow any
537+
number of incoming connections from within an organization's network without
538+
blocking them due to the per client connection max feature.
539+
540+
This configuration takes a YAML sequence of IP addresses, CIDR networks, or
541+
ranges separated by a dash.
542+
543+
======================= ===========================================================
544+
Example Effect
545+
======================= ===========================================================
546+
``10.0.2.123`` Ignore a single IP Address.
547+
``10.0.3.1-10.0.3.254`` Ignore a range of IP address.
548+
``10.0.4.0/24`` Ignore a range of IP address specified by CIDR notation.
549+
======================= ===========================================================
550+
551+
The root node should be ``allow_list``. Here is an example of the contents
552+
of such a file::
553+
554+
allow_list:
555+
- 10.0.2.123
556+
- 172.16.0.0/20
557+
- 192.168.1.0/24
558+
559+
527560
.. ts:cv:: CONFIG proxy.config.http.per_client.connection.alert_delay INT 60
528561
:reloadable:
529562
:units: seconds
@@ -2023,7 +2056,7 @@ Proxy User Variables
20232056
by a dash or by using CIDR notation.
20242057

20252058
======================= ===========================================================
2026-
Example Effect
2059+
Example Effect
20272060
======================= ===========================================================
20282061
``10.0.2.123`` A single IP Address.
20292062
``10.0.3.1-10.0.3.254`` A range of IP address.

include/iocore/net/ConnectionTracker.h

+24-2
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,28 @@ class ConnectionTracker
8181

8282
/** Static configuration values. */
8383
struct GlobalConfig {
84+
GlobalConfig() = default;
85+
GlobalConfig(GlobalConfig const &);
86+
GlobalConfig &operator=(GlobalConfig const &);
87+
8488
std::chrono::seconds client_alert_delay{60}; ///< Alert delay in seconds.
8589
std::chrono::seconds server_alert_delay{60}; ///< Alert delay in seconds.
90+
swoc::IPRangeSet client_allow_list; ///< The set of IP addresses to not block due client connection counting.
8691
};
8792

8893
// The names of the configuration values.
8994
// Unfortunately these are not used in RecordsConfig.cc so that must be made consistent by hand.
9095
// Note: These need to be @c constexpr or there are static initialization ordering risks.
9196
static constexpr std::string_view CONFIG_CLIENT_VAR_ALERT_DELAY{"proxy.config.http.per_client.connection.alert_delay"};
97+
static constexpr std::string_view CONFIG_CLIENT_VAR_ALLOW_LIST_FILENAME{
98+
"proxy.config.http.per_client.connection.allow_list.filename"};
9299
static constexpr std::string_view CONFIG_SERVER_VAR_MAX{"proxy.config.http.per_server.connection.max"};
93100
static constexpr std::string_view CONFIG_SERVER_VAR_MIN{"proxy.config.http.per_server.connection.min"};
94101
static constexpr std::string_view CONFIG_SERVER_VAR_MATCH{"proxy.config.http.per_server.connection.match"};
95102
static constexpr std::string_view CONFIG_SERVER_VAR_ALERT_DELAY{"proxy.config.http.per_server.connection.alert_delay"};
96103

104+
static constexpr std::string_view ALLOW_LIST_ROOT_NODE{"allow_list"};
105+
97106
/// A record for the outbound connection count.
98107
/// These are stored per outbound session equivalence class, as determined by the session matching.
99108
struct Group {
@@ -161,11 +170,18 @@ class ConnectionTracker
161170
std::shared_ptr<Group> _g; ///< Active group for this transaction.
162171
bool _reserved_p{false}; ///< Set if a connection slot has been reserved.
163172
bool _queued_p{false}; ///< Set if the connection is delayed / queued.
173+
bool _allowed_p{false}; ///< Set if the peer is in the connection allow list.
164174

165175
/// Check if tracking is active.
166-
bool is_active();
176+
bool is_active() const;
177+
178+
/// Whether this group is in the connection max allow list.
179+
/// @return @c true if this group should not be blocked due to
180+
/// proxy.config.net.per_client.max_connections_in.
181+
bool is_allowed() const;
167182

168183
/// Reserve a connection.
184+
/// @return the number of tracked connections.
169185
int reserve();
170186
/// Release a connection reservation.
171187
void release();
@@ -323,11 +339,17 @@ ConnectionTracker::Group::hash(const Key &key)
323339
}
324340

325341
inline bool
326-
ConnectionTracker::TxnState::is_active()
342+
ConnectionTracker::TxnState::is_active() const
327343
{
328344
return nullptr != _g;
329345
}
330346

347+
inline bool
348+
ConnectionTracker::TxnState::is_allowed() const
349+
{
350+
return _allowed_p;
351+
}
352+
331353
inline int
332354
ConnectionTracker::TxnState::reserve()
333355
{

src/iocore/net/ConnectionTracker.cc

+99-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
#include "P_Net.h" // For Metrics.
2525
#include "iocore/net/ConnectionTracker.h"
2626
#include "records/RecCore.h"
27+
#include "swoc/IPAddr.h"
28+
#include "swoc/swoc_file.h"
29+
#include <yaml-cpp/yaml.h>
2730

2831
using namespace std::literals;
2932

@@ -149,7 +152,93 @@ Config_Update_Conntrack_Client_Alert_Delay(const char *name, RecDataT dtype, Rec
149152
return Config_Update_Conntrack_Server_Alert_Delay_Helper(name, dtype, data, cookie, config->client_alert_delay);
150153
}
151154

152-
} // namespace
155+
bool
156+
Update_Client_Allow_List_From_File(swoc::IPRangeSet &range_set, std::string_view filename)
157+
{
158+
char const *config_name = ConnectionTracker::CONFIG_CLIENT_VAR_ALLOW_LIST_FILENAME.data();
159+
Note("%s: loading %.*s", config_name, static_cast<int>(filename.size()), filename.data());
160+
std::error_code ec;
161+
std::string content{swoc::file::load(filename, ec)};
162+
if (ec.value() != 0) {
163+
Warning("%s: Failed to load %.*s - %s", config_name, static_cast<int>(filename.size()), filename.data(), ec.message().c_str());
164+
return false;
165+
}
166+
167+
YAML::Node root_node{YAML::Load(content)};
168+
YAML::Node ranges_node{root_node[ConnectionTracker::ALLOW_LIST_ROOT_NODE.data()]};
169+
if (!ranges_node) {
170+
Warning("%s: no %s root node found in %.*s", config_name, ConnectionTracker::ALLOW_LIST_ROOT_NODE.data(),
171+
static_cast<int>(filename.size()), filename.data());
172+
return false;
173+
}
174+
175+
// The allow list should contain a Sequence of allowed addresses.
176+
if (!ranges_node.IsSequence()) {
177+
Warning("%s: %s root node must be a sequence of ip address ranges in %.*s", config_name,
178+
ConnectionTracker::ALLOW_LIST_ROOT_NODE.data(), static_cast<int>(filename.size()), filename.data());
179+
return false;
180+
}
181+
182+
for (auto const &range_node : ranges_node) {
183+
if (!range_node.IsScalar()) {
184+
Warning("%s: %.*s root node must be a sequence of ip address ranges in %.*s:%d", config_name,
185+
static_cast<int>(ConnectionTracker::ALLOW_LIST_ROOT_NODE.size()), ConnectionTracker::ALLOW_LIST_ROOT_NODE.data(),
186+
static_cast<int>(filename.size()), filename.data(), range_node.Mark().line);
187+
return false;
188+
}
189+
std::string_view range_sv{range_node.Scalar()};
190+
swoc::IPRange range;
191+
if (!range.load(range_sv)) {
192+
Warning("%s: %.*s is not a valid IP range in %.*s:%d", config_name, static_cast<int>(range_sv.size()), range_sv.data(),
193+
static_cast<int>(filename.size()), filename.data(), range_node.Mark().line);
194+
return false;
195+
}
196+
range_set.mark(range);
197+
}
198+
return true;
199+
}
200+
201+
bool
202+
Config_Update_Conntrack_Client_Allow_List_Filename(const char * /* name ATS_UNUSED */, RecDataT dtype, RecData data, void *cookie)
203+
{
204+
if (RECD_STRING != dtype) {
205+
Warning("Invalid type for '%s' - must be 'STRING'", ConnectionTracker::CONFIG_CLIENT_VAR_ALLOW_LIST_FILENAME.data());
206+
return false;
207+
}
208+
auto *config = static_cast<ConnectionTracker::GlobalConfig *>(cookie);
209+
if (data.rec_string == nullptr) {
210+
// There is no allow list configured. Ensure that our allow list is empty.
211+
config->client_allow_list.clear();
212+
return true;
213+
}
214+
std::string_view allow_list_filename{data.rec_string};
215+
ink_release_assert(config != nullptr);
216+
return Update_Client_Allow_List_From_File(config->client_allow_list, allow_list_filename);
217+
}
218+
219+
} // anonymous namespace
220+
221+
ConnectionTracker::GlobalConfig::GlobalConfig(GlobalConfig const &other)
222+
{
223+
this->client_alert_delay = other.client_alert_delay;
224+
this->server_alert_delay = other.server_alert_delay;
225+
this->client_allow_list.clear();
226+
for (auto const &ip_range : other.client_allow_list) {
227+
this->client_allow_list.mark(ip_range);
228+
}
229+
}
230+
231+
ConnectionTracker::GlobalConfig &
232+
ConnectionTracker::GlobalConfig::operator=(GlobalConfig const &other)
233+
{
234+
this->client_alert_delay = other.client_alert_delay;
235+
this->server_alert_delay = other.server_alert_delay;
236+
this->client_allow_list.clear();
237+
for (auto const &ip_range : other.client_allow_list) {
238+
this->client_allow_list.mark(ip_range);
239+
}
240+
return *this;
241+
}
153242

154243
void
155244
ConnectionTracker::config_init(GlobalConfig *global, TxnConfig *txn, RecConfigUpdateCb const &config_cb)
@@ -158,6 +247,7 @@ ConnectionTracker::config_init(GlobalConfig *global, TxnConfig *txn, RecConfigUp
158247
// Per transaction lookup must be done at call time because it changes.
159248

160249
Enable_Config_Var(CONFIG_CLIENT_VAR_ALERT_DELAY, &Config_Update_Conntrack_Client_Alert_Delay, config_cb, global);
250+
Enable_Config_Var(CONFIG_CLIENT_VAR_ALLOW_LIST_FILENAME, &Config_Update_Conntrack_Client_Allow_List_Filename, config_cb, global);
161251
Enable_Config_Var(CONFIG_SERVER_VAR_MIN, &Config_Update_Conntrack_Min, config_cb, txn);
162252
Enable_Config_Var(CONFIG_SERVER_VAR_MAX, &Config_Update_Conntrack_Max, config_cb, txn);
163253
Enable_Config_Var(CONFIG_SERVER_VAR_MATCH, &Config_Update_Conntrack_Match, config_cb, txn);
@@ -167,7 +257,14 @@ ConnectionTracker::config_init(GlobalConfig *global, TxnConfig *txn, RecConfigUp
167257
ConnectionTracker::TxnState
168258
ConnectionTracker::obtain_inbound(IpEndpoint const &addr)
169259
{
170-
TxnState zret;
260+
TxnState zret;
261+
if (_global_config->client_allow_list.contains(swoc::IPAddr{addr})) {
262+
// This short-circuits all our connection logic. Save time by just setting
263+
// the flag for the caller to see that connections are not tracked for this
264+
// address.
265+
zret._allowed_p = true;
266+
return zret;
267+
}
171268
CryptoHash hash;
172269
Group::Key key{addr, hash, MatchType::MATCH_IP};
173270
std::lock_guard<std::mutex> lock(_inbound_table._mutex); // Table lock

src/iocore/net/Net.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ register_net_stats()
9090
net_rsb.connections_throttled_in = Metrics::Counter::createPtr("proxy.process.net.connections_throttled_in");
9191
net_rsb.per_client_connections_throttled_in =
9292
Metrics::Counter::createPtr("proxy.process.net.per_client.connections_throttled_in");
93-
net_rsb.connections_throttled_out = Metrics::Counter::createPtr("proxy.process.net.connections_throttled_out");
93+
net_rsb.per_client_connections_allowed_in = Metrics::Counter::createPtr("proxy.process.net.per_client.connections_allowed_in");
94+
net_rsb.connections_throttled_out = Metrics::Counter::createPtr("proxy.process.net.connections_throttled_out");
9495
net_rsb.tunnel_total_client_connections_blind_tcp =
9596
Metrics::Counter::createPtr("proxy.process.tunnel.total_client_connections_blind_tcp");
9697
net_rsb.tunnel_current_client_connections_blind_tcp =

src/iocore/net/P_Net.h

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct NetStatsBlock {
4545
Metrics::Gauge::AtomicType *connections_currently_open;
4646
Metrics::Counter::AtomicType *connections_throttled_in;
4747
Metrics::Counter::AtomicType *per_client_connections_throttled_in;
48+
Metrics::Counter::AtomicType *per_client_connections_allowed_in;
4849
Metrics::Counter::AtomicType *connections_throttled_out;
4950
Metrics::Counter::AtomicType *default_inactivity_timeout_applied;
5051
Metrics::Counter::AtomicType *default_inactivity_timeout_count;

src/iocore/net/UnixNetAccept.cc

+9-2
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,22 @@ handle_max_client_connections(IpEndpoint const &addr, std::shared_ptr<Connection
5454
{
5555
int const client_max = NetHandler::get_per_client_max_connections_in();
5656
if (client_max > 0) {
57-
auto inbound_tracker = ConnectionTracker::obtain_inbound(addr);
58-
auto const tracked_count = inbound_tracker.reserve();
57+
auto inbound_tracker = ConnectionTracker::obtain_inbound(addr);
58+
if (inbound_tracker.is_allowed()) {
59+
// The user configured connections like this to not be tracked. Simply allow it.
60+
Metrics::Counter::increment(net_rsb.per_client_connections_allowed_in);
61+
Dbg(dbg_ctl_iocore_net_accepts, "Ignoring client connection counting for an incoming address in the allow list.");
62+
return true;
63+
}
64+
auto const tracked_count = inbound_tracker.reserve();
5965
if (tracked_count > client_max) {
6066
// close the connection as we are in per client connection throttle state
6167
inbound_tracker.release();
6268
inbound_tracker.blocked();
6369
inbound_tracker.Warn_Blocked(client_max, 0, tracked_count - 1, addr,
6470
dbg_ctl_iocore_net_accept.on() ? &dbg_ctl_iocore_net_accept : nullptr);
6571
Metrics::Counter::increment(net_rsb.per_client_connections_throttled_in);
72+
Dbg(dbg_ctl_iocore_net_accepts, "Blocking a client connection due to per client connection limit.");
6673
return false;
6774
}
6875
conn_track_group = inbound_tracker.drop();

src/records/RecordsConfig.cc

+2
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ static const RecordElement RecordsConfig[] =
381381
,
382382
{RECT_CONFIG, "proxy.config.net.per_client.max_connections_in", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
383383
,
384+
{RECT_CONFIG, "proxy.config.http.per_client.connection.allow_list.filename", RECD_STRING, nullptr, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
385+
,
384386
{RECT_CONFIG, "proxy.config.http.per_client.connection.alert_delay", RECD_INT, "60", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
385387
,
386388
{RECT_CONFIG, "proxy.config.net.max_requests_in", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
allow_list:
18+
- 0/0
19+
- ::/0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
allow_list:
18+
- 127.0.0.1
19+
- ::1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
allow_list:
18+
- 1.2.3.4
19+
- 5.6.0.0/16

0 commit comments

Comments
 (0)