aboutsummaryrefslogtreecommitdiff
path: root/src/core/or/dos.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/or/dos.c')
-rw-r--r--src/core/or/dos.c288
1 files changed, 214 insertions, 74 deletions
diff --git a/src/core/or/dos.c b/src/core/or/dos.c
index 41bf303ffe..e8652c901e 100644
--- a/src/core/or/dos.c
+++ b/src/core/or/dos.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2018-2020, The Tor Project, Inc. */
+/* Copyright (c) 2018-2021, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/*
@@ -23,7 +23,9 @@
#include "lib/crypt_ops/crypto_rand.h"
#include "core/or/dos.h"
+#include "core/or/dos_sys.h"
+#include "core/or/dos_options_st.h"
#include "core/or/or_connection_st.h"
/*
@@ -61,9 +63,14 @@ static unsigned int dos_conn_enabled = 0;
* They are initialized with the hardcoded default values. */
static uint32_t dos_conn_max_concurrent_count;
static dos_conn_defense_type_t dos_conn_defense_type;
+static uint32_t dos_conn_connect_rate = DOS_CONN_CONNECT_RATE_DEFAULT;
+static uint32_t dos_conn_connect_burst = DOS_CONN_CONNECT_BURST_DEFAULT;
+static int32_t dos_conn_connect_defense_time_period =
+ DOS_CONN_CONNECT_DEFENSE_TIME_PERIOD_DEFAULT;
/* Keep some stats for the heartbeat so we can report out. */
static uint64_t conn_num_addr_rejected;
+static uint64_t conn_num_addr_connect_rejected;
/*
* General interface of the denial of service mitigation subsystem.
@@ -77,8 +84,8 @@ static uint64_t num_single_hop_client_refused;
MOCK_IMPL(STATIC unsigned int,
get_param_cc_enabled, (const networkstatus_t *ns))
{
- if (get_options()->DoSCircuitCreationEnabled != -1) {
- return get_options()->DoSCircuitCreationEnabled;
+ if (dos_get_options()->DoSCircuitCreationEnabled != -1) {
+ return dos_get_options()->DoSCircuitCreationEnabled;
}
return !!networkstatus_get_param(ns, "DoSCircuitCreationEnabled",
@@ -90,8 +97,8 @@ get_param_cc_enabled, (const networkstatus_t *ns))
STATIC uint32_t
get_param_cc_min_concurrent_connection(const networkstatus_t *ns)
{
- if (get_options()->DoSCircuitCreationMinConnections) {
- return get_options()->DoSCircuitCreationMinConnections;
+ if (dos_get_options()->DoSCircuitCreationMinConnections) {
+ return dos_get_options()->DoSCircuitCreationMinConnections;
}
return networkstatus_get_param(ns, "DoSCircuitCreationMinConnections",
DOS_CC_MIN_CONCURRENT_CONN_DEFAULT,
@@ -104,8 +111,8 @@ static uint32_t
get_param_cc_circuit_rate(const networkstatus_t *ns)
{
/* This is in seconds. */
- if (get_options()->DoSCircuitCreationRate) {
- return get_options()->DoSCircuitCreationRate;
+ if (dos_get_options()->DoSCircuitCreationRate) {
+ return dos_get_options()->DoSCircuitCreationRate;
}
return networkstatus_get_param(ns, "DoSCircuitCreationRate",
DOS_CC_CIRCUIT_RATE_DEFAULT,
@@ -117,8 +124,8 @@ get_param_cc_circuit_rate(const networkstatus_t *ns)
STATIC uint32_t
get_param_cc_circuit_burst(const networkstatus_t *ns)
{
- if (get_options()->DoSCircuitCreationBurst) {
- return get_options()->DoSCircuitCreationBurst;
+ if (dos_get_options()->DoSCircuitCreationBurst) {
+ return dos_get_options()->DoSCircuitCreationBurst;
}
return networkstatus_get_param(ns, "DoSCircuitCreationBurst",
DOS_CC_CIRCUIT_BURST_DEFAULT,
@@ -129,8 +136,8 @@ get_param_cc_circuit_burst(const networkstatus_t *ns)
static uint32_t
get_param_cc_defense_type(const networkstatus_t *ns)
{
- if (get_options()->DoSCircuitCreationDefenseType) {
- return get_options()->DoSCircuitCreationDefenseType;
+ if (dos_get_options()->DoSCircuitCreationDefenseType) {
+ return dos_get_options()->DoSCircuitCreationDefenseType;
}
return networkstatus_get_param(ns, "DoSCircuitCreationDefenseType",
DOS_CC_DEFENSE_TYPE_DEFAULT,
@@ -143,8 +150,8 @@ static int32_t
get_param_cc_defense_time_period(const networkstatus_t *ns)
{
/* Time in seconds. */
- if (get_options()->DoSCircuitCreationDefenseTimePeriod) {
- return get_options()->DoSCircuitCreationDefenseTimePeriod;
+ if (dos_get_options()->DoSCircuitCreationDefenseTimePeriod) {
+ return dos_get_options()->DoSCircuitCreationDefenseTimePeriod;
}
return networkstatus_get_param(ns, "DoSCircuitCreationDefenseTimePeriod",
DOS_CC_DEFENSE_TIME_PERIOD_DEFAULT,
@@ -156,8 +163,8 @@ get_param_cc_defense_time_period(const networkstatus_t *ns)
MOCK_IMPL(STATIC unsigned int,
get_param_conn_enabled, (const networkstatus_t *ns))
{
- if (get_options()->DoSConnectionEnabled != -1) {
- return get_options()->DoSConnectionEnabled;
+ if (dos_get_options()->DoSConnectionEnabled != -1) {
+ return dos_get_options()->DoSConnectionEnabled;
}
return !!networkstatus_get_param(ns, "DoSConnectionEnabled",
DOS_CONN_ENABLED_DEFAULT, 0, 1);
@@ -168,8 +175,8 @@ get_param_conn_enabled, (const networkstatus_t *ns))
STATIC uint32_t
get_param_conn_max_concurrent_count(const networkstatus_t *ns)
{
- if (get_options()->DoSConnectionMaxConcurrentCount) {
- return get_options()->DoSConnectionMaxConcurrentCount;
+ if (dos_get_options()->DoSConnectionMaxConcurrentCount) {
+ return dos_get_options()->DoSConnectionMaxConcurrentCount;
}
return networkstatus_get_param(ns, "DoSConnectionMaxConcurrentCount",
DOS_CONN_MAX_CONCURRENT_COUNT_DEFAULT,
@@ -180,14 +187,55 @@ get_param_conn_max_concurrent_count(const networkstatus_t *ns)
static uint32_t
get_param_conn_defense_type(const networkstatus_t *ns)
{
- if (get_options()->DoSConnectionDefenseType) {
- return get_options()->DoSConnectionDefenseType;
+ if (dos_get_options()->DoSConnectionDefenseType) {
+ return dos_get_options()->DoSConnectionDefenseType;
}
return networkstatus_get_param(ns, "DoSConnectionDefenseType",
DOS_CONN_DEFENSE_TYPE_DEFAULT,
DOS_CONN_DEFENSE_NONE, DOS_CONN_DEFENSE_MAX);
}
+/* Return the connection connect rate parameters either from the configuration
+ * file or, if not found, consensus parameter. */
+static uint32_t
+get_param_conn_connect_rate(const networkstatus_t *ns)
+{
+ if (dos_get_options()->DoSConnectionConnectRate) {
+ return dos_get_options()->DoSConnectionConnectRate;
+ }
+ return networkstatus_get_param(ns, "DoSConnectionConnectRate",
+ DOS_CONN_CONNECT_RATE_DEFAULT,
+ 1, INT32_MAX);
+}
+
+/* Return the connection connect burst parameters either from the
+ * configuration file or, if not found, consensus parameter. */
+STATIC uint32_t
+get_param_conn_connect_burst(const networkstatus_t *ns)
+{
+ if (dos_get_options()->DoSConnectionConnectBurst) {
+ return dos_get_options()->DoSConnectionConnectBurst;
+ }
+ return networkstatus_get_param(ns, "DoSConnectionConnectBurst",
+ DOS_CONN_CONNECT_BURST_DEFAULT,
+ 1, INT32_MAX);
+}
+
+/* Return the connection connect defense time period from the configuration
+ * file or, if not found, the consensus parameter. */
+static int32_t
+get_param_conn_connect_defense_time_period(const networkstatus_t *ns)
+{
+ /* Time in seconds. */
+ if (dos_get_options()->DoSConnectionConnectDefenseTimePeriod) {
+ return dos_get_options()->DoSConnectionConnectDefenseTimePeriod;
+ }
+ return networkstatus_get_param(ns, "DoSConnectionConnectDefenseTimePeriod",
+ DOS_CONN_CONNECT_DEFENSE_TIME_PERIOD_DEFAULT,
+ DOS_CONN_CONNECT_DEFENSE_TIME_PERIOD_MIN,
+ INT32_MAX);
+}
+
/* Set circuit creation parameters located in the consensus or their default
* if none are present. Called at initialization or when the consensus
* changes. */
@@ -206,6 +254,10 @@ set_dos_parameters(const networkstatus_t *ns)
dos_conn_enabled = get_param_conn_enabled(ns);
dos_conn_max_concurrent_count = get_param_conn_max_concurrent_count(ns);
dos_conn_defense_type = get_param_conn_defense_type(ns);
+ dos_conn_connect_rate = get_param_conn_connect_rate(ns);
+ dos_conn_connect_burst = get_param_conn_connect_burst(ns);
+ dos_conn_connect_defense_time_period =
+ get_param_conn_connect_defense_time_period(ns);
}
/* Free everything for the circuit creation DoS mitigation subsystem. */
@@ -347,7 +399,7 @@ cc_has_exhausted_circuits(const dos_client_stats_t *stats)
{
tor_assert(stats);
return stats->cc_stats.circuit_bucket == 0 &&
- stats->concurrent_count >= dos_cc_min_concurrent_conn;
+ stats->conn_stats.concurrent_count >= dos_cc_min_concurrent_conn;
}
/* Mark client address by setting a timestamp in the stats object which tells
@@ -403,6 +455,20 @@ cc_channel_addr_is_marked(channel_t *chan)
/* Concurrent connection private API. */
+/* Mark client connection stats by setting a timestamp which tells us until
+ * when it is marked as positively detected. */
+static void
+conn_mark_client(conn_client_stats_t *stats)
+{
+ tor_assert(stats);
+
+ /* We add a random offset of a maximum of half the defense time so it is
+ * less predictable and thus more difficult to game. */
+ stats->marked_until_ts =
+ approx_time() + dos_conn_connect_defense_time_period +
+ crypto_rand_int_range(1, dos_conn_connect_defense_time_period / 2);
+}
+
/* Free everything for the connection DoS mitigation subsystem. */
static void
conn_free_all(void)
@@ -422,6 +488,63 @@ conn_consensus_has_changed(const networkstatus_t *ns)
}
}
+/** Called when a new client connection has arrived. The following will update
+ * the client connection statistics.
+ *
+ * The addr is used for logging purposes only.
+ *
+ * If the connect counter reaches its limit, it is marked. */
+static void
+conn_update_on_connect(conn_client_stats_t *stats, const tor_addr_t *addr)
+{
+ tor_assert(stats);
+ tor_assert(addr);
+
+ /* Update concurrent count for this new connect. */
+ stats->concurrent_count++;
+
+ /* Refill connect connection count. */
+ token_bucket_ctr_refill(&stats->connect_count, (uint32_t) approx_time());
+
+ /* Decrement counter for this new connection. */
+ if (token_bucket_ctr_get(&stats->connect_count) > 0) {
+ token_bucket_ctr_dec(&stats->connect_count, 1);
+ }
+
+ /* Assess connect counter. Mark it if counter is down to 0 and we haven't
+ * marked it before or it was reset. This is to avoid to re-mark it over and
+ * over again extending continously the blocked time. */
+ if (token_bucket_ctr_get(&stats->connect_count) == 0 &&
+ stats->marked_until_ts == 0) {
+ conn_mark_client(stats);
+ }
+
+ log_debug(LD_DOS, "Client address %s has now %u concurrent connections. "
+ "Remaining %" TOR_PRIuSZ "/sec connections are allowed.",
+ fmt_addr(addr), stats->concurrent_count,
+ token_bucket_ctr_get(&stats->connect_count));
+}
+
+/** Called when a client connection is closed. The following will update
+ * the client connection statistics.
+ *
+ * The addr is used for logging purposes only. */
+static void
+conn_update_on_close(conn_client_stats_t *stats, const tor_addr_t *addr)
+{
+ /* Extra super duper safety. Going below 0 means an underflow which could
+ * lead to most likely a false positive. In theory, this should never happen
+ * but lets be extra safe. */
+ if (BUG(stats->concurrent_count == 0)) {
+ return;
+ }
+
+ stats->concurrent_count--;
+ log_debug(LD_DOS, "Client address %s has lost a connection. Concurrent "
+ "connections are now at %u",
+ fmt_addr(addr), stats->concurrent_count);
+}
+
/* General private API */
/* Return true iff we have at least one DoS detection enabled. This is used to
@@ -547,9 +670,20 @@ dos_conn_addr_get_defense_type(const tor_addr_t *addr)
goto end;
}
+ /* Is this address marked as making too many client connections? */
+ if (entry->dos_stats.conn_stats.marked_until_ts >= approx_time()) {
+ conn_num_addr_connect_rejected++;
+ return dos_conn_defense_type;
+ }
+ /* Reset it to 0 here so that if the marked timestamp has expired that is
+ * we've gone beyond it, we have to reset it so the detection can mark it
+ * again in the future. */
+ entry->dos_stats.conn_stats.marked_until_ts = 0;
+
/* Need to be above the maximum concurrent connection count to trigger a
* defense. */
- if (entry->dos_stats.concurrent_count > dos_conn_max_concurrent_count) {
+ if (entry->dos_stats.conn_stats.concurrent_count >
+ dos_conn_max_concurrent_count) {
conn_num_addr_rejected++;
return dos_conn_defense_type;
}
@@ -574,7 +708,7 @@ dos_geoip_entry_about_to_free(const clientmap_entry_t *geoip_ent)
/* The count is down to 0 meaning no connections right now, we can safely
* clear the geoip entry from the cache. */
- if (geoip_ent->dos_stats.concurrent_count == 0) {
+ if (geoip_ent->dos_stats.conn_stats.concurrent_count == 0) {
goto end;
}
@@ -595,6 +729,22 @@ dos_geoip_entry_about_to_free(const clientmap_entry_t *geoip_ent)
return;
}
+/** A new geoip client entry has been allocated, initialize its DoS object. */
+void
+dos_geoip_entry_init(clientmap_entry_t *geoip_ent)
+{
+ tor_assert(geoip_ent);
+
+ /* Initialize the connection count counter with the rate and burst
+ * parameters taken either from configuration or consensus.
+ *
+ * We do this even if the DoS connection detection is not enabled because it
+ * can be enabled at runtime and these counters need to be valid. */
+ token_bucket_ctr_init(&geoip_ent->dos_stats.conn_stats.connect_count,
+ dos_conn_connect_rate, dos_conn_connect_burst,
+ (uint32_t) approx_time());
+}
+
/* Note down that we've just refused a single hop client. This increments a
* counter later used for the heartbeat. */
void
@@ -613,8 +763,8 @@ dos_should_refuse_single_hop_client(void)
return 0;
}
- if (get_options()->DoSRefuseSingleHopClientRendezvous != -1) {
- return get_options()->DoSRefuseSingleHopClientRendezvous;
+ if (dos_get_options()->DoSRefuseSingleHopClientRendezvous != -1) {
+ return dos_get_options()->DoSRefuseSingleHopClientRendezvous;
}
return (int) networkstatus_get_param(NULL,
@@ -626,55 +776,55 @@ dos_should_refuse_single_hop_client(void)
void
dos_log_heartbeat(void)
{
- char *conn_msg = NULL;
- char *cc_msg = NULL;
- char *single_hop_client_msg = NULL;
- char *circ_stats_msg = NULL;
- char *hs_dos_intro2_msg = NULL;
+ smartlist_t *elems = smartlist_new();
/* Stats number coming from relay.c append_cell_to_circuit_queue(). */
- tor_asprintf(&circ_stats_msg,
- " %" PRIu64 " circuits killed with too many cells.",
- stats_n_circ_max_cell_reached);
+ smartlist_add_asprintf(elems,
+ "%" PRIu64 " circuits killed with too many cells",
+ stats_n_circ_max_cell_reached);
if (dos_cc_enabled) {
- tor_asprintf(&cc_msg,
- " %" PRIu64 " circuits rejected,"
- " %" PRIu32 " marked addresses.",
- cc_num_rejected_cells, cc_num_marked_addrs);
+ smartlist_add_asprintf(elems,
+ "%" PRIu64 " circuits rejected, "
+ "%" PRIu32 " marked addresses",
+ cc_num_rejected_cells, cc_num_marked_addrs);
+ } else {
+ smartlist_add_asprintf(elems, "[DoSCircuitCreationEnabled disabled]");
}
if (dos_conn_enabled) {
- tor_asprintf(&conn_msg,
- " %" PRIu64 " connections closed.",
- conn_num_addr_rejected);
+ smartlist_add_asprintf(elems,
+ "%" PRIu64 " same address concurrent "
+ "connections rejected", conn_num_addr_rejected);
+ smartlist_add_asprintf(elems,
+ "%" PRIu64 " connections rejected",
+ conn_num_addr_connect_rejected);
+ } else {
+ smartlist_add_asprintf(elems, "[DoSConnectionEnabled disabled]");
}
if (dos_should_refuse_single_hop_client()) {
- tor_asprintf(&single_hop_client_msg,
- " %" PRIu64 " single hop clients refused.",
- num_single_hop_client_refused);
+ smartlist_add_asprintf(elems,
+ "%" PRIu64 " single hop clients refused",
+ num_single_hop_client_refused);
+ } else {
+ smartlist_add_asprintf(elems,
+ "[DoSRefuseSingleHopClientRendezvous disabled]");
}
/* HS DoS stats. */
- tor_asprintf(&hs_dos_intro2_msg,
- " %" PRIu64 " INTRODUCE2 rejected.",
- hs_dos_get_intro2_rejected_count());
+ smartlist_add_asprintf(elems,
+ "%" PRIu64 " INTRODUCE2 rejected",
+ hs_dos_get_intro2_rejected_count());
+
+ char *msg = smartlist_join_strings(elems, ", ", 0, NULL);
log_notice(LD_HEARTBEAT,
- "DoS mitigation since startup:%s%s%s%s%s",
- circ_stats_msg,
- (cc_msg != NULL) ? cc_msg : " [cc not enabled]",
- (conn_msg != NULL) ? conn_msg : " [conn not enabled]",
- (single_hop_client_msg != NULL) ? single_hop_client_msg : "",
- (hs_dos_intro2_msg != NULL) ? hs_dos_intro2_msg : "");
-
- tor_free(conn_msg);
- tor_free(cc_msg);
- tor_free(single_hop_client_msg);
- tor_free(circ_stats_msg);
- tor_free(hs_dos_intro2_msg);
- return;
+ "Heartbeat: DoS mitigation since startup: %s.", msg);
+
+ tor_free(msg);
+ SMARTLIST_FOREACH(elems, char *, e, tor_free(e));
+ smartlist_free(elems);
}
/* Called when a new client connection has been established on the given
@@ -709,11 +859,11 @@ dos_new_client_conn(or_connection_t *or_conn, const char *transport_name)
goto end;
}
- entry->dos_stats.concurrent_count++;
+ /* Update stats from this new connect. */
+ conn_update_on_connect(&entry->dos_stats.conn_stats,
+ &TO_CONN(or_conn)->addr);
+
or_conn->tracked_for_dos_mitigation = 1;
- log_debug(LD_DOS, "Client address %s has now %u concurrent connections.",
- fmt_addr(&TO_CONN(or_conn)->addr),
- entry->dos_stats.concurrent_count);
end:
return;
@@ -743,18 +893,8 @@ dos_close_client_conn(const or_connection_t *or_conn)
goto end;
}
- /* Extra super duper safety. Going below 0 means an underflow which could
- * lead to most likely a false positive. In theory, this should never happen
- * but lets be extra safe. */
- if (BUG(entry->dos_stats.concurrent_count == 0)) {
- goto end;
- }
-
- entry->dos_stats.concurrent_count--;
- log_debug(LD_DOS, "Client address %s has lost a connection. Concurrent "
- "connections are now at %u",
- fmt_addr(&TO_CONN(or_conn)->addr),
- entry->dos_stats.concurrent_count);
+ /* Update stats from this new close. */
+ conn_update_on_close(&entry->dos_stats.conn_stats, &TO_CONN(or_conn)->addr);
end:
return;