Merge branch 'ticket24902_029_05' into ticket24902_033_02

This commit is contained in:
David Goulet 2018-01-30 09:33:12 -05:00
commit cd81403cc0
23 changed files with 1410 additions and 34 deletions

13
changes/ticket24902 Normal file
View File

@ -0,0 +1,13 @@
o Major features (denial of service mitigation):
- Give relays some defenses against the recent network overload. We start
with three defenses (default parameters in parentheses). First: if a
single client address makes too many concurrent connections (>100), hang
up on further connections. Second: if a single client address makes
circuits too quickly (more than 3 per second, with an allowed burst of
90) while also having too many connections open (3), refuse new create
cells for the next while (1-2 hours). Third: if a client asks to
establish a rendezvous point to you directly, ignore the request. These
defenses can be manually controlled by new torrc options, but relays
will also take guidance from consensus parameters, so there's no need to
configure anything manually. Implements ticket 24902.

View File

@ -2752,6 +2752,94 @@ The following options are used to configure a hidden service.
including setting SOCKSPort to "0". Can not be changed while tor is
running. (Default: 0)
DENIAL OF SERVICE MITIGATION OPTIONS
------------------------------------
The following options are useful only for a public relay. They control the
Denial of Service mitigation subsystem.
[[DoSCircuitCreationEnabled]] **DoSCircuitCreationEnabled** **0**|**1**|**auto**::
Enable circuit creation DoS mitigation. If enabled, tor will cache client
IPs along with statistics in order to detect circuit DoS attacks. If an
address is positively identified, tor will activate defenses against the
address. See the DoSCircuitCreationDefenseType option for more details.
This is a client to relay detection only. "auto" means use the consensus
parameter.
(Default: auto)
[[DoSCircuitCreationMinConnections]] **DoSCircuitCreationMinConnections** __NUM__::
Minimum threshold of concurrent connections before a client address can be
flagged as executing a circuit creation DoS. In other words, once a client
address reaches the circuit rate and has a minimum of NUM concurrent
connections, a detection is positive. "0" means use the consensus
parameter.
(Default: 0)
[[DoSCircuitCreationRate]] **DoSCircuitCreationRate** __NUM__::
The allowed circuit creation rate per second applied per client IP
address. If this option is 0, it obeys a consensus parameter. (Default: 0)
[[DoSCircuitCreationBurst]] **DoSCircuitCreationBurst** __NUM__::
The allowed circuit creation burst per client IP address. If the circuit
rate and the burst are reached, a client is marked as executing a circuit
creation DoS. "0" means use the consensus parameter.
(Default: 0)
[[DoSCircuitCreationDefenseType]] **DoSCircuitCreationDefenseType** __NUM__::
This is the type of defense applied to a detected client address. The
possible values are:
1: No defense.
2: Refuse circuit creation for the DoSCircuitCreationDefenseTimePeriod period of time.
+
"0" means use the consensus parameter.
(Default: 0)
[[DoSCircuitCreationDefenseTimePeriod]] **DoSCircuitCreationDefenseTimePeriod** __NUM__::
The base time period that the DoS defense is activated for. The actual
value is selected randomly for each activation from NUM+1 to 3/2 * NUM.
"0" means use the consensus parameter.
(Default: 0)
[[DoSConnectionEnabled]] **DoSConnectionEnabled** **0**|**1**|**auto**::
Enable the connection DoS mitigation. For client address only, this allows
tor to mitigate against large number of concurrent connections made by a
single IP address. "auto" means use the consensus parameter.
(Default: auto)
[[DoSConnectionMaxConcurrentCount]] **DoSConnectionMaxConcurrentCount** __NUM__::
The maximum threshold of concurrent connection from a client IP address.
Above this limit, a defense selected by DoSConnectionDefenseType is
applied. "0" means use the consensus parameter.
(Default: 0)
[[DoSConnectionDefenseType]] **DoSConnectionDefenseType** __NUM__::
This is the type of defense applied to a detected client address for the
connection mitigation. The possible values are:
1: No defense.
2: Immediately close new connections.
+
"0" means use the consensus parameter.
(Default: 0)
[[DoSRefuseSingleHopClientRendezvous]] **DoSRefuseSingleHopClientRendezvous** **0**|**1**|**auto**::
Refuse establishment of rendezvous points for single hop clients. In other
words, if a client directly connects to the relay and sends an
ESTABLISH_RENDEZVOUS cell, it is silently dropped. "auto" means use the
consensus parameter.
(Default: auto)
TESTING NETWORK OPTIONS
-----------------------

View File

@ -1263,7 +1263,7 @@ static const char *domain_list[] = {
"GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM",
"HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV",
"OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", "CHANNEL",
"SCHED", "GUARD", "CONSDIFF", NULL
"SCHED", "GUARD", "CONSDIFF", "DOS", NULL
};
/** Return a bitmask for the log domain for which <b>domain</b> is the name,

View File

@ -103,8 +103,10 @@
#define LD_GUARD (1u<<23)
/** Generation and application of consensus diffs. */
#define LD_CONSDIFF (1u<<24)
/** Denial of Service mitigation. */
#define LD_DOS (1u<<25)
/** Number of logging domains in the code. */
#define N_LOGGING_DOMAINS 25
#define N_LOGGING_DOMAINS 26
/** This log message is not safe to send to a callback-based logger
* immediately. Used as a flag, not a log domain. */

View File

@ -1888,6 +1888,7 @@ channel_do_open_actions(channel_t *chan)
if (!connection_or_digest_is_known_relay(chan->identity_digest)) {
if (channel_get_addr_if_possible(chan, &remote_addr)) {
char *transport_name = NULL;
channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan);
if (chan->get_transport_name(chan, &transport_name) < 0)
transport_name = NULL;
@ -1895,6 +1896,10 @@ channel_do_open_actions(channel_t *chan)
&remote_addr, transport_name,
now);
tor_free(transport_name);
/* Notify the DoS subsystem of a new client. */
if (tlschan && tlschan->conn) {
dos_new_client_conn(tlschan->conn);
}
}
/* Otherwise the underlying transport can't tell us this, so skip it */
}
@ -2914,8 +2919,8 @@ channel_get_canonical_remote_descr(channel_t *chan)
* supports this operation, and return 1. Return 0 if the underlying transport
* doesn't let us do this.
*/
int
channel_get_addr_if_possible(channel_t *chan, tor_addr_t *addr_out)
MOCK_IMPL(int,
channel_get_addr_if_possible,(channel_t *chan, tor_addr_t *addr_out))
{
tor_assert(chan);
tor_assert(addr_out);

View File

@ -590,7 +590,8 @@ MOCK_DECL(void, channel_dump_statistics, (channel_t *chan, int severity));
void channel_dump_transport_statistics(channel_t *chan, int severity);
const char * channel_get_actual_remote_descr(channel_t *chan);
const char * channel_get_actual_remote_address(channel_t *chan);
int channel_get_addr_if_possible(channel_t *chan, tor_addr_t *addr_out);
MOCK_DECL(int, channel_get_addr_if_possible, (channel_t *chan,
tor_addr_t *addr_out));
const char * channel_get_canonical_remote_descr(channel_t *chan);
int channel_has_queued_writes(channel_t *chan);
int channel_is_bad_for_new_circs(channel_t *chan);

View File

@ -46,6 +46,7 @@
#include "config.h"
#include "control.h"
#include "cpuworker.h"
#include "dos.h"
#include "hibernate.h"
#include "nodelist.h"
#include "onion.h"
@ -247,6 +248,11 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
(unsigned)cell->circ_id,
U64_PRINTF_ARG(chan->global_identifier), chan);
/* First thing we do, even though the cell might be invalid, is inform the
* DoS mitigation subsystem layer of this event. Validation is done by this
* function. */
dos_cc_new_create_cell(chan);
/* We check for the conditions that would make us drop the cell before
* we check for the conditions that would make us send a DESTROY back,
* since those conditions would make a DESTROY nonsensical. */
@ -284,6 +290,13 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
return;
}
/* Check if we should apply a defense for this channel. */
if (dos_cc_get_defense_type(chan) == DOS_CC_DEFENSE_REFUSE_CELL) {
channel_send_destroy(cell->circ_id, chan,
END_CIRC_REASON_RESOURCELIMIT);
return;
}
if (!server_mode(options) ||
(!public_server_mode(options) && channel_is_outgoing(chan))) {
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,

View File

@ -81,6 +81,7 @@
#include "dirserv.h"
#include "dirvote.h"
#include "dns.h"
#include "dos.h"
#include "entrynodes.h"
#include "git_revision.h"
#include "geoip.h"
@ -316,6 +317,19 @@ static config_var_t option_vars_[] = {
OBSOLETE("DynamicDHGroups"),
VPORT(DNSPort),
OBSOLETE("DNSListenAddress"),
/* DoS circuit creation options. */
V(DoSCircuitCreationEnabled, AUTOBOOL, "auto"),
V(DoSCircuitCreationMinConnections, UINT, "0"),
V(DoSCircuitCreationRate, UINT, "0"),
V(DoSCircuitCreationBurst, UINT, "0"),
V(DoSCircuitCreationDefenseType, INT, "0"),
V(DoSCircuitCreationDefenseTimePeriod, INTERVAL, "0"),
/* DoS connection options. */
V(DoSConnectionEnabled, AUTOBOOL, "auto"),
V(DoSConnectionMaxConcurrentCount, UINT, "0"),
V(DoSConnectionDefenseType, INT, "0"),
/* DoS single hop client options. */
V(DoSRefuseSingleHopClientRendezvous, AUTOBOOL, "auto"),
V(DownloadExtraInfo, BOOL, "0"),
V(TestingEnableConnBwEvent, BOOL, "0"),
V(TestingEnableCellStatsEvent, BOOL, "0"),
@ -2323,6 +2337,17 @@ options_act(const or_options_t *old_options)
}
}
/* DoS mitigation subsystem only applies to public relay. */
if (public_server_mode(options)) {
/* If we are configured as a relay, initialize the subsystem. Even on HUP,
* this is safe to call as it will load data from the current options
* or/and the consensus. */
dos_init();
} else if (old_options && public_server_mode(old_options)) {
/* Going from relay to non relay, clean it up. */
dos_free_all();
}
/* Load the webpage we're going to serve every time someone asks for '/' on
our DirPort. */
tor_free(global_dirfrontpagecontents);

View File

@ -80,6 +80,7 @@
#include "dirserv.h"
#include "dns.h"
#include "dnsserv.h"
#include "dos.h"
#include "entrynodes.h"
#include "ext_orport.h"
#include "geoip.h"
@ -703,6 +704,13 @@ connection_free_,(connection_t *conn))
"connection_free");
}
#endif /* 1 */
/* Notify the circuit creation DoS mitigation subsystem that an OR client
* connection has been closed. And only do that if we track it. */
if (conn->type == CONN_TYPE_OR) {
dos_close_client_conn(TO_OR_CONN(conn));
}
connection_unregister_events(conn);
connection_free_minimal(conn);
}
@ -1608,6 +1616,14 @@ connection_handle_listener_read(connection_t *conn, int new_type)
return 0;
}
}
if (new_type == CONN_TYPE_OR) {
/* Assess with the connection DoS mitigation subsystem if this address
* can open a new connection. */
if (dos_conn_addr_get_defense_type(&addr) == DOS_CONN_DEFENSE_CLOSE) {
tor_close_socket(news);
return 0;
}
}
newconn = connection_new(new_type, conn->socket_family);
newconn->s = news;

737
src/or/dos.c Normal file
View File

@ -0,0 +1,737 @@
/* Copyright (c) 2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/*
* \file dos.c
* \brief Implement Denial of Service mitigation subsystem.
*/
#define DOS_PRIVATE
#include "or.h"
#include "channel.h"
#include "config.h"
#include "geoip.h"
#include "main.h"
#include "networkstatus.h"
#include "router.h"
#include "dos.h"
/*
* Circuit creation denial of service mitigation.
*
* Namespace used for this mitigation framework is "dos_cc_" where "cc" is for
* Circuit Creation.
*/
/* Is the circuit creation DoS mitigation enabled? */
static unsigned int dos_cc_enabled = 0;
/* Consensus parameters. They can be changed when a new consensus arrives.
* They are initialized with the hardcoded default values. */
static uint32_t dos_cc_min_concurrent_conn;
static uint32_t dos_cc_circuit_rate;
static uint32_t dos_cc_circuit_burst;
static dos_cc_defense_type_t dos_cc_defense_type;
static int32_t dos_cc_defense_time_period;
/* Keep some stats for the heartbeat so we can report out. */
static uint64_t cc_num_rejected_cells;
static uint32_t cc_num_marked_addrs;
/*
* Concurrent connection denial of service mitigation.
*
* Namespace used for this mitigation framework is "dos_conn_".
*/
/* Is the connection DoS mitigation enabled? */
static unsigned int dos_conn_enabled = 0;
/* Consensus parameters. They can be changed when a new consensus arrives.
* 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;
/* Keep some stats for the heartbeat so we can report out. */
static uint64_t conn_num_addr_rejected;
/*
* General interface of the denial of service mitigation subsystem.
*/
/* Keep stats for the heartbeat. */
static uint64_t num_single_hop_client_refused;
/* Return true iff the circuit creation mitigation is enabled. We look at the
* consensus for this else a default value is returned. */
MOCK_IMPL(STATIC unsigned int,
get_param_cc_enabled, (const networkstatus_t *ns))
{
if (get_options()->DoSCircuitCreationEnabled != -1) {
return get_options()->DoSCircuitCreationEnabled;
}
return !!networkstatus_get_param(ns, "DoSCircuitCreationEnabled",
DOS_CC_ENABLED_DEFAULT, 0, 1);
}
/* Return the parameter for the minimum concurrent connection at which we'll
* start counting circuit for a specific client address. */
STATIC uint32_t
get_param_cc_min_concurrent_connection(const networkstatus_t *ns)
{
if (get_options()->DoSCircuitCreationMinConnections) {
return get_options()->DoSCircuitCreationMinConnections;
}
return networkstatus_get_param(ns, "DoSCircuitCreationMinConnections",
DOS_CC_MIN_CONCURRENT_CONN_DEFAULT,
1, INT32_MAX);
}
/* Return the parameter for the time rate that is how many circuits over this
* time span. */
static uint32_t
get_param_cc_circuit_rate(const networkstatus_t *ns)
{
/* This is in seconds. */
if (get_options()->DoSCircuitCreationRate) {
return get_options()->DoSCircuitCreationRate;
}
return networkstatus_get_param(ns, "DoSCircuitCreationRate",
DOS_CC_CIRCUIT_RATE_DEFAULT,
1, INT32_MAX);
}
/* Return the parameter for the maximum circuit count for the circuit time
* rate. */
STATIC uint32_t
get_param_cc_circuit_burst(const networkstatus_t *ns)
{
if (get_options()->DoSCircuitCreationBurst) {
return get_options()->DoSCircuitCreationBurst;
}
return networkstatus_get_param(ns, "DoSCircuitCreationBurst",
DOS_CC_CIRCUIT_BURST_DEFAULT,
1, INT32_MAX);
}
/* Return the consensus parameter of the circuit creation defense type. */
static uint32_t
get_param_cc_defense_type(const networkstatus_t *ns)
{
if (get_options()->DoSCircuitCreationDefenseType) {
return get_options()->DoSCircuitCreationDefenseType;
}
return networkstatus_get_param(ns, "DoSCircuitCreationDefenseType",
DOS_CC_DEFENSE_TYPE_DEFAULT,
DOS_CC_DEFENSE_NONE, DOS_CC_DEFENSE_MAX);
}
/* Return the consensus parameter of the defense time period which is how much
* time should we defend against a malicious client address. */
static int32_t
get_param_cc_defense_time_period(const networkstatus_t *ns)
{
/* Time in seconds. */
if (get_options()->DoSCircuitCreationDefenseTimePeriod) {
return get_options()->DoSCircuitCreationDefenseTimePeriod;
}
return networkstatus_get_param(ns, "DoSCircuitCreationDefenseTimePeriod",
DOS_CC_DEFENSE_TIME_PERIOD_DEFAULT,
0, INT32_MAX);
}
/* Return true iff connection mitigation is enabled. We look at the consensus
* for this else a default value is returned. */
MOCK_IMPL(STATIC unsigned int,
get_param_conn_enabled, (const networkstatus_t *ns))
{
if (get_options()->DoSConnectionEnabled != -1) {
return get_options()->DoSConnectionEnabled;
}
return !!networkstatus_get_param(ns, "DoSConnectionEnabled",
DOS_CONN_ENABLED_DEFAULT, 0, 1);
}
/* Return the consensus parameter for the maximum concurrent connection
* allowed. */
STATIC uint32_t
get_param_conn_max_concurrent_count(const networkstatus_t *ns)
{
if (get_options()->DoSConnectionMaxConcurrentCount) {
return get_options()->DoSConnectionMaxConcurrentCount;
}
return networkstatus_get_param(ns, "DoSConnectionMaxConcurrentCount",
DOS_CONN_MAX_CONCURRENT_COUNT_DEFAULT,
1, INT32_MAX);
}
/* Return the consensus parameter of the connection defense type. */
static uint32_t
get_param_conn_defense_type(const networkstatus_t *ns)
{
if (get_options()->DoSConnectionDefenseType) {
return get_options()->DoSConnectionDefenseType;
}
return networkstatus_get_param(ns, "DoSConnectionDefenseType",
DOS_CONN_DEFENSE_TYPE_DEFAULT,
DOS_CONN_DEFENSE_NONE, DOS_CONN_DEFENSE_MAX);
}
/* Set circuit creation parameters located in the consensus or their default
* if none are present. Called at initialization or when the consensus
* changes. */
static void
set_dos_parameters(const networkstatus_t *ns)
{
/* Get the default consensus param values. */
dos_cc_enabled = get_param_cc_enabled(ns);
dos_cc_min_concurrent_conn = get_param_cc_min_concurrent_connection(ns);
dos_cc_circuit_rate = get_param_cc_circuit_rate(ns);
dos_cc_circuit_burst = get_param_cc_circuit_burst(ns);
dos_cc_defense_time_period = get_param_cc_defense_time_period(ns);
dos_cc_defense_type = get_param_cc_defense_type(ns);
/* Connection detection. */
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);
}
/* Free everything for the circuit creation DoS mitigation subsystem. */
static void
cc_free_all(void)
{
/* If everything is freed, the circuit creation subsystem is not enabled. */
dos_cc_enabled = 0;
}
/* Called when the consensus has changed. Do appropriate actions for the
* circuit creation subsystem. */
static void
cc_consensus_has_changed(const networkstatus_t *ns)
{
/* Looking at the consensus, is the circuit creation subsystem enabled? If
* not and it was enabled before, clean it up. */
if (dos_cc_enabled && !get_param_cc_enabled(ns)) {
cc_free_all();
}
}
/** Return the number of circuits we allow per second under the current
* configuration. */
STATIC uint32_t
get_circuit_rate_per_second(void)
{
return dos_cc_circuit_rate;
}
/* Given the circuit creation client statistics object, refill the circuit
* bucket if needed. This also works if the bucket was never filled in the
* first place. The addr is only used for logging purposes. */
STATIC void
cc_stats_refill_bucket(cc_client_stats_t *stats, const tor_addr_t *addr)
{
uint32_t new_circuit_bucket_count, circuit_rate = 0, num_token;
time_t now, elapsed_time_last_refill;
tor_assert(stats);
tor_assert(addr);
now = approx_time();
/* We've never filled the bucket so fill it with the maximum being the burst
* and we are done. */
if (stats->last_circ_bucket_refill_ts == 0) {
num_token = dos_cc_circuit_burst;
goto end;
}
/* At this point, we know we might need to add token to the bucket. We'll
* first compute the circuit rate that is how many circuit are we allowed to
* do per second. */
circuit_rate = get_circuit_rate_per_second();
/* How many seconds have elapsed between now and the last refill? */
elapsed_time_last_refill = now - stats->last_circ_bucket_refill_ts;
/* If the elapsed time is below 0 it means our clock jumped backward so in
* that case, lets be safe and fill it up to the maximum. Not filling it
* could trigger a detection for a valid client. Also, if the clock jumped
* negative but we didn't notice until the elapsed time became positive
* again, then we potentially spent many seconds not refilling the bucket
* when we should have been refilling it. But the fact that we didn't notice
* until now means that no circuit creation requests came in during that
* time, so the client doesn't end up punished that much from this hopefully
* rare situation.*/
if (elapsed_time_last_refill < 0) {
/* Dividing the burst by the circuit rate gives us the time span that will
* give us the maximum allowed value of token. */
elapsed_time_last_refill = (dos_cc_circuit_burst / circuit_rate);
}
/* Compute how many circuits we are allowed in that time frame which we'll
* add to the bucket. This can be big but it is cap to a maximum after. */
num_token = elapsed_time_last_refill * circuit_rate;
end:
/* We cap the bucket to the burst value else this could grow to infinity
* over time. */
new_circuit_bucket_count = MIN(stats->circuit_bucket + num_token,
dos_cc_circuit_burst);
log_debug(LD_DOS, "DoS address %s has its circuit bucket value: %" PRIu32
". Filling it to %" PRIu32 ". Circuit rate is %" PRIu32,
fmt_addr(addr), stats->circuit_bucket, new_circuit_bucket_count,
circuit_rate);
stats->circuit_bucket = new_circuit_bucket_count;
stats->last_circ_bucket_refill_ts = now;
return;
}
/* Return true iff the circuit bucket is down to 0 and the number of
* concurrent connections is greater or equal the minimum threshold set the
* consensus parameter. */
static int
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;
}
/* Mark client address by setting a timestamp in the stats object which tells
* us until when it is marked as positively detected. */
static void
cc_mark_client(cc_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. */
stats->marked_until_ts =
approx_time() + dos_cc_defense_time_period +
crypto_rand_int_range(1, dos_cc_defense_time_period / 2);
}
/* Return true iff the given channel address is marked as malicious. This is
* called a lot and part of the fast path of handling cells. It has to remain
* as fast as we can. */
static int
cc_channel_addr_is_marked(channel_t *chan)
{
time_t now;
tor_addr_t addr;
clientmap_entry_t *entry;
cc_client_stats_t *stats = NULL;
if (chan == NULL) {
goto end;
}
/* Must be a client connection else we ignore. */
if (!channel_is_client(chan)) {
goto end;
}
/* Without an IP address, nothing can work. */
if (!channel_get_addr_if_possible(chan, &addr)) {
goto end;
}
/* We are only interested in client connection from the geoip cache. */
entry = geoip_lookup_client(&addr, NULL, GEOIP_CLIENT_CONNECT);
if (entry == NULL) {
/* We can have a connection creating circuits but not tracked by the geoip
* cache. Once this DoS subsystem is enabled, we can end up here with no
* entry for the channel. */
goto end;
}
now = approx_time();
stats = &entry->dos_stats.cc_stats;
end:
return stats && stats->marked_until_ts >= now;
}
/* Concurrent connection private API. */
/* Free everything for the connection DoS mitigation subsystem. */
static void
conn_free_all(void)
{
dos_conn_enabled = 0;
}
/* Called when the consensus has changed. Do appropriate actions for the
* connection mitigation subsystem. */
static void
conn_consensus_has_changed(const networkstatus_t *ns)
{
/* Looking at the consensus, is the connection mitigation subsystem enabled?
* If not and it was enabled before, clean it up. */
if (dos_conn_enabled && !get_param_conn_enabled(ns)) {
conn_free_all();
}
}
/* General private API */
/* Return true iff we have at least one DoS detection enabled. This is used to
* decide if we need to allocate any kind of high level DoS object. */
static inline int
dos_is_enabled(void)
{
return (dos_cc_enabled || dos_conn_enabled);
}
/* Circuit creation public API. */
/* Called when a CREATE cell is received from the given channel. */
void
dos_cc_new_create_cell(channel_t *chan)
{
tor_addr_t addr;
clientmap_entry_t *entry;
tor_assert(chan);
/* Skip everything if not enabled. */
if (!dos_cc_enabled) {
goto end;
}
/* Must be a client connection else we ignore. */
if (!channel_is_client(chan)) {
goto end;
}
/* Without an IP address, nothing can work. */
if (!channel_get_addr_if_possible(chan, &addr)) {
goto end;
}
/* We are only interested in client connection from the geoip cache. */
entry = geoip_lookup_client(&addr, NULL, GEOIP_CLIENT_CONNECT);
if (entry == NULL) {
/* We can have a connection creating circuits but not tracked by the geoip
* cache. Once this DoS subsystem is enabled, we can end up here with no
* entry for the channel. */
goto end;
}
/* General comment. Even though the client can already be marked as
* malicious, we continue to track statistics. If it keeps going above
* threshold while marked, the defense period time will grow longer. There
* is really no point at unmarking a client that keeps DoSing us. */
/* First of all, we'll try to refill the circuit bucket opportunistically
* before we assess. */
cc_stats_refill_bucket(&entry->dos_stats.cc_stats, &addr);
/* Take a token out of the circuit bucket if we are above 0 so we don't
* underflow the bucket. */
if (entry->dos_stats.cc_stats.circuit_bucket > 0) {
entry->dos_stats.cc_stats.circuit_bucket--;
}
/* This is the detection. Assess at every CREATE cell if the client should
* get marked as malicious. This should be kept as fast as possible. */
if (cc_has_exhausted_circuits(&entry->dos_stats)) {
/* If this is the first time we mark this entry, log it a info level.
* Under heavy DDoS, logging each time we mark would results in lots and
* lots of logs. */
if (entry->dos_stats.cc_stats.marked_until_ts == 0) {
log_debug(LD_DOS, "Detected circuit creation DoS by address: %s",
fmt_addr(&addr));
cc_num_marked_addrs++;
}
cc_mark_client(&entry->dos_stats.cc_stats);
}
end:
return;
}
/* Return the defense type that should be used for this circuit.
*
* This is part of the fast path and called a lot. */
dos_cc_defense_type_t
dos_cc_get_defense_type(channel_t *chan)
{
tor_assert(chan);
/* Skip everything if not enabled. */
if (!dos_cc_enabled) {
goto end;
}
/* On an OR circuit, we'll check if the previous channel is a marked client
* connection detected by our DoS circuit creation mitigation subsystem. */
if (cc_channel_addr_is_marked(chan)) {
/* We've just assess that this circuit should trigger a defense for the
* cell it just seen. Note it down. */
cc_num_rejected_cells++;
return dos_cc_defense_type;
}
end:
return DOS_CC_DEFENSE_NONE;
}
/* Concurrent connection detection public API. */
/* Return true iff the given address is permitted to open another connection.
* A defense value is returned for the caller to take appropriate actions. */
dos_conn_defense_type_t
dos_conn_addr_get_defense_type(const tor_addr_t *addr)
{
clientmap_entry_t *entry;
tor_assert(addr);
/* Skip everything if not enabled. */
if (!dos_conn_enabled) {
goto end;
}
/* We are only interested in client connection from the geoip cache. */
entry = geoip_lookup_client(addr, NULL, GEOIP_CLIENT_CONNECT);
if (entry == NULL) {
goto end;
}
/* Need to be above the maximum concurrent connection count to trigger a
* defense. */
if (entry->dos_stats.concurrent_count > dos_conn_max_concurrent_count) {
conn_num_addr_rejected++;
return dos_conn_defense_type;
}
end:
return DOS_CONN_DEFENSE_NONE;
}
/* General API */
/* Take any appropriate actions for the given geoip entry that is about to get
* freed. This is called for every entry that is being freed.
*
* This function will clear out the connection tracked flag if the concurrent
* count of the entry is above 0 so if those connections end up being seen by
* this subsystem, we won't try to decrement the counter for a new geoip entry
* that might have been added after this call for the same address. */
void
dos_geoip_entry_about_to_free(const clientmap_entry_t *geoip_ent)
{
tor_assert(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) {
goto end;
}
/* For each connection matching the geoip entry address, we'll clear the
* tracked flag because the entry is about to get removed from the geoip
* cache. We do not try to decrement if the flag is not set. */
SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) {
if (conn->type == CONN_TYPE_OR) {
or_connection_t *or_conn = TO_OR_CONN(conn);
if (!tor_addr_compare(&geoip_ent->addr, &or_conn->real_addr,
CMP_EXACT)) {
or_conn->tracked_for_dos_mitigation = 0;
}
}
} SMARTLIST_FOREACH_END(conn);
end:
return;
}
/* Note down that we've just refused a single hop client. This increments a
* counter later used for the heartbeat. */
void
dos_note_refuse_single_hop_client(void)
{
num_single_hop_client_refused++;
}
/* Return true iff single hop client connection (ESTABLISH_RENDEZVOUS) should
* be refused. */
int
dos_should_refuse_single_hop_client(void)
{
/* If we aren't a public relay, this shouldn't apply to anything. */
if (!public_server_mode(get_options())) {
return 0;
}
if (get_options()->DoSRefuseSingleHopClientRendezvous != -1) {
return get_options()->DoSRefuseSingleHopClientRendezvous;
}
return (int) networkstatus_get_param(NULL,
"DoSRefuseSingleHopClientRendezvous",
0 /* default */, 0, 1);
}
/* Log a heartbeat message with some statistics. */
void
dos_log_heartbeat(void)
{
char *conn_msg = NULL;
char *cc_msg = NULL;
char *single_hop_client_msg = NULL;
if (!dos_is_enabled()) {
goto end;
}
if (dos_cc_enabled) {
tor_asprintf(&cc_msg,
" %" PRIu64 " circuits rejected,"
" %" PRIu32 " marked addresses.",
cc_num_rejected_cells, cc_num_marked_addrs);
}
if (dos_conn_enabled) {
tor_asprintf(&conn_msg,
" %" PRIu64 " connections closed.",
conn_num_addr_rejected);
}
if (dos_should_refuse_single_hop_client()) {
tor_asprintf(&single_hop_client_msg,
" %" PRIu64 " single hop clients refused.",
num_single_hop_client_refused);
}
log_notice(LD_HEARTBEAT,
"DoS mitigation since startup:%s%s%s",
(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 : "");
tor_free(conn_msg);
tor_free(cc_msg);
tor_free(single_hop_client_msg);
end:
return;
}
/* Called when a new client connection has been established on the given
* address. */
void
dos_new_client_conn(or_connection_t *or_conn)
{
clientmap_entry_t *entry;
tor_assert(or_conn);
/* Past that point, we know we have at least one DoS detection subsystem
* enabled so we'll start allocating stuff. */
if (!dos_is_enabled()) {
goto end;
}
/* We are only interested in client connection from the geoip cache. */
entry = geoip_lookup_client(&or_conn->real_addr, NULL,
GEOIP_CLIENT_CONNECT);
if (BUG(entry == NULL)) {
/* Should never happen because we note down the address in the geoip
* cache before this is called. */
goto end;
}
entry->dos_stats.concurrent_count++;
or_conn->tracked_for_dos_mitigation = 1;
log_debug(LD_DOS, "Client address %s has now %u concurrent connections.",
fmt_addr(&or_conn->real_addr),
entry->dos_stats.concurrent_count);
end:
return;
}
/* Called when a client connection for the given IP address has been closed. */
void
dos_close_client_conn(const or_connection_t *or_conn)
{
clientmap_entry_t *entry;
tor_assert(or_conn);
/* We have to decrement the count on tracked connection only even if the
* subsystem has been disabled at runtime because it might be re-enabled
* after and we need to keep a synchronized counter at all time. */
if (!or_conn->tracked_for_dos_mitigation) {
goto end;
}
/* We are only interested in client connection from the geoip cache. */
entry = geoip_lookup_client(&or_conn->real_addr, NULL,
GEOIP_CLIENT_CONNECT);
if (entry == NULL) {
/* This can happen because we can close a connection before the channel
* got to be noted down in the geoip cache. */
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(&or_conn->real_addr),
entry->dos_stats.concurrent_count);
end:
return;
}
/* Called when the consensus has changed. We might have new consensus
* parameters to look at. */
void
dos_consensus_has_changed(const networkstatus_t *ns)
{
cc_consensus_has_changed(ns);
conn_consensus_has_changed(ns);
/* We were already enabled or we just became enabled but either way, set the
* consensus parameters for all subsystems. */
set_dos_parameters(ns);
}
/* Return true iff the DoS mitigation subsystem is enabled. */
int
dos_enabled(void)
{
return dos_is_enabled();
}
/* Free everything from the Denial of Service subsystem. */
void
dos_free_all(void)
{
/* Free the circuit creation mitigation subsystem. It is safe to do this
* even if it wasn't initialized. */
cc_free_all();
/* Free the connection mitigation subsystem. It is safe to do this even if
* it wasn't initialized. */
conn_free_all();
}
/* Initialize the Denial of Service subsystem. */
void
dos_init(void)
{
/* To initialize, we only need to get the parameters. */
set_dos_parameters(NULL);
}

140
src/or/dos.h Normal file
View File

@ -0,0 +1,140 @@
/* Copyright (c) 2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/*
* \file dos.h
* \brief Header file for dos.c
*/
#ifndef TOR_DOS_H
#define TOR_DOS_H
/* Structure that keeps stats of client connection per-IP. */
typedef struct cc_client_stats_t {
/* Number of allocated circuits remaining for this address. It is
* decremented every time a new circuit is seen for this client address and
* if the count goes to 0, we have a positive detection. */
uint32_t circuit_bucket;
/* When was the last time we've refilled the circuit bucket? This is used to
* know if we need to refill the bucket when a new circuit is seen. It is
* synchronized using approx_time(). */
time_t last_circ_bucket_refill_ts;
/* This client address was detected to be above the circuit creation rate
* and this timestamp indicates until when it should remain marked as
* detected so we can apply a defense for the address. It is synchronized
* using the approx_time(). */
time_t marked_until_ts;
} cc_client_stats_t;
/* This object is a top level object that contains everything related to the
* per-IP client DoS mitigation. Because it is per-IP, it is used in the geoip
* clientmap_entry_t object. */
typedef struct dos_client_stats_t {
/* Concurrent connection count from the specific address. 2^32 is most
* likely way too big for the amount of allowed file descriptors. */
uint32_t concurrent_count;
/* Circuit creation statistics. This is only used if the circuit creation
* subsystem has been enabled (dos_cc_enabled). */
cc_client_stats_t cc_stats;
} dos_client_stats_t;
/* General API. */
/* Stub. */
struct clientmap_entry_t;
void dos_init(void);
void dos_free_all(void);
void dos_consensus_has_changed(const networkstatus_t *ns);
int dos_enabled(void);
void dos_log_heartbeat(void);
void dos_geoip_entry_about_to_free(const struct clientmap_entry_t *geoip_ent);
void dos_new_client_conn(or_connection_t *or_conn);
void dos_close_client_conn(const or_connection_t *or_conn);
int dos_should_refuse_single_hop_client(void);
void dos_note_refuse_single_hop_client(void);
/*
* Circuit creation DoS mitigation subsystemn interface.
*/
/* DoSCircuitCreationEnabled default. Disabled by default. */
#define DOS_CC_ENABLED_DEFAULT 0
/* DoSCircuitCreationDefenseType maps to the dos_cc_defense_type_t enum. */
#define DOS_CC_DEFENSE_TYPE_DEFAULT DOS_CC_DEFENSE_REFUSE_CELL
/* DoSCircuitCreationMinConnections default */
#define DOS_CC_MIN_CONCURRENT_CONN_DEFAULT 3
/* DoSCircuitCreationRateTenths is 3 per seconds. */
#define DOS_CC_CIRCUIT_RATE_DEFAULT 3
/* DoSCircuitCreationBurst default. */
#define DOS_CC_CIRCUIT_BURST_DEFAULT 90
/* DoSCircuitCreationDefenseTimePeriod in seconds. */
#define DOS_CC_DEFENSE_TIME_PERIOD_DEFAULT (60 * 60)
/* Type of defense that we can use for the circuit creation DoS mitigation. */
typedef enum dos_cc_defense_type_t {
/* No defense used. */
DOS_CC_DEFENSE_NONE = 1,
/* Refuse any cells which means a DESTROY cell will be sent back. */
DOS_CC_DEFENSE_REFUSE_CELL = 2,
/* Maximum value that can be used. Useful for the boundaries of the
* consensus parameter. */
DOS_CC_DEFENSE_MAX = 2,
} dos_cc_defense_type_t;
void dos_cc_new_create_cell(channel_t *channel);
dos_cc_defense_type_t dos_cc_get_defense_type(channel_t *chan);
/*
* Concurrent connection DoS mitigation interface.
*/
/* DoSConnectionEnabled default. Disabled by default. */
#define DOS_CONN_ENABLED_DEFAULT 0
/* DoSConnectionMaxConcurrentCount default. */
#define DOS_CONN_MAX_CONCURRENT_COUNT_DEFAULT 100
/* DoSConnectionDefenseType maps to the dos_conn_defense_type_t enum. */
#define DOS_CONN_DEFENSE_TYPE_DEFAULT DOS_CONN_DEFENSE_CLOSE
/* Type of defense that we can use for the concurrent connection DoS
* mitigation. */
typedef enum dos_conn_defense_type_t {
/* No defense used. */
DOS_CONN_DEFENSE_NONE = 1,
/* Close immediately the connection meaning refuse it. */
DOS_CONN_DEFENSE_CLOSE = 2,
/* Maximum value that can be used. Useful for the boundaries of the
* consensus parameter. */
DOS_CONN_DEFENSE_MAX = 2,
} dos_conn_defense_type_t;
dos_conn_defense_type_t dos_conn_addr_get_defense_type(const tor_addr_t *addr);
#ifdef DOS_PRIVATE
STATIC uint32_t get_param_conn_max_concurrent_count(
const networkstatus_t *ns);
STATIC uint32_t get_param_cc_circuit_burst(const networkstatus_t *ns);
STATIC uint32_t get_param_cc_min_concurrent_connection(
const networkstatus_t *ns);
STATIC uint32_t get_circuit_rate_per_second(void);
STATIC void cc_stats_refill_bucket(cc_client_stats_t *stats,
const tor_addr_t *addr);
MOCK_DECL(STATIC unsigned int, get_param_cc_enabled,
(const networkstatus_t *ns));
MOCK_DECL(STATIC unsigned int, get_param_conn_enabled,
(const networkstatus_t *ns));
#endif /* TOR_DOS_PRIVATE */
#endif /* TOR_DOS_H */

View File

@ -34,6 +34,7 @@
#include "config.h"
#include "control.h"
#include "dnsserv.h"
#include "dos.h"
#include "geoip.h"
#include "routerlist.h"
@ -473,24 +474,6 @@ geoip_db_digest(sa_family_t family)
return hex_str(geoip6_digest, DIGEST_LEN);
}
/** Entry in a map from IP address to the last time we've seen an incoming
* connection from that IP address. Used by bridges only, to track which
* countries have them blocked. */
typedef struct clientmap_entry_t {
HT_ENTRY(clientmap_entry_t) node;
tor_addr_t addr;
/* Name of pluggable transport used by this client. NULL if no
pluggable transport was used. */
char *transport_name;
/** Time when we last saw this IP address, in MINUTES since the epoch.
*
* (This will run out of space around 4011 CE. If Tor is still in use around
* 4000 CE, please remember to add more bits to last_seen_in_minutes.) */
unsigned int last_seen_in_minutes:30;
unsigned int action:2;
} clientmap_entry_t;
/** Largest allowable value for last_seen_in_minutes. (It's a 30-bit field,
* so it can hold up to (1u<<30)-1, or 0x3fffffffu.
*/
@ -537,6 +520,10 @@ clientmap_entry_free_(clientmap_entry_t *ent)
if (!ent)
return;
/* This entry is about to be freed so pass it to the DoS subsystem to see if
* any actions can be taken about it. */
dos_geoip_entry_about_to_free(ent);
tor_free(ent->transport_name);
tor_free(ent);
}
@ -568,14 +555,17 @@ geoip_note_client_seen(geoip_client_action_t action,
time_t now)
{
const or_options_t *options = get_options();
clientmap_entry_t lookup, *ent;
memset(&lookup, 0, sizeof(clientmap_entry_t));
clientmap_entry_t *ent;
if (action == GEOIP_CLIENT_CONNECT) {
/* Only remember statistics as entry guard or as bridge. */
if (!options->EntryStatistics &&
(!(options->BridgeRelay && options->BridgeRecordUsageByCountry)))
return;
/* Only remember statistics if the DoS mitigation subsystem is enabled. If
* not, only if as entry guard or as bridge. */
if (!dos_enabled()) {
if (!options->EntryStatistics &&
(!(options->BridgeRelay && options->BridgeRecordUsageByCountry))) {
return;
}
}
} else {
/* Only gather directory-request statistics if configured, and
* forcibly disable them on bridge authorities. */
@ -587,11 +577,7 @@ geoip_note_client_seen(geoip_client_action_t action,
safe_str_client(fmt_addr((addr))),
transport_name ? transport_name : "<no transport>");
tor_addr_copy(&lookup.addr, addr);
lookup.action = (int)action;
lookup.transport_name = (char*) transport_name;
ent = HT_FIND(clientmap, &client_history, &lookup);
ent = geoip_lookup_client(addr, transport_name, action);
if (! ent) {
ent = tor_malloc_zero(sizeof(clientmap_entry_t));
tor_addr_copy(&ent->addr, addr);
@ -639,6 +625,25 @@ geoip_remove_old_clients(time_t cutoff)
&cutoff);
}
/* Return a client entry object matching the given address, transport name and
* geoip action from the clientmap. NULL if not found. The transport_name can
* be NULL. */
clientmap_entry_t *
geoip_lookup_client(const tor_addr_t *addr, const char *transport_name,
geoip_client_action_t action)
{
clientmap_entry_t lookup;
tor_assert(addr);
/* We always look for a client connection with no transport. */
tor_addr_copy(&lookup.addr, addr);
lookup.action = action;
lookup.transport_name = (char *) transport_name;
return HT_FIND(clientmap, &client_history, &lookup);
}
/** How many responses are we giving to clients requesting v3 network
* statuses? */
static uint32_t ns_v3_responses[GEOIP_NS_RESPONSE_NUM];

View File

@ -13,6 +13,7 @@
#define TOR_GEOIP_H
#include "testsupport.h"
#include "dos.h"
#ifdef GEOIP_PRIVATE
STATIC int geoip_parse_entry(const char *line, sa_family_t family);
@ -20,6 +21,29 @@ STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr);
STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr);
STATIC void clear_geoip_db(void);
#endif /* defined(GEOIP_PRIVATE) */
/** Entry in a map from IP address to the last time we've seen an incoming
* connection from that IP address. Used by bridges only to track which
* countries have them blocked, or the DoS mitigation subsystem if enabled. */
typedef struct clientmap_entry_t {
HT_ENTRY(clientmap_entry_t) node;
tor_addr_t addr;
/* Name of pluggable transport used by this client. NULL if no
pluggable transport was used. */
char *transport_name;
/** Time when we last saw this IP address, in MINUTES since the epoch.
*
* (This will run out of space around 4011 CE. If Tor is still in use around
* 4000 CE, please remember to add more bits to last_seen_in_minutes.) */
unsigned int last_seen_in_minutes:30;
unsigned int action:2;
/* This object is used to keep some statistics per client address for the
* DoS mitigation subsystem. */
dos_client_stats_t dos_stats;
} clientmap_entry_t;
int should_record_bridge_info(const or_options_t *options);
int geoip_load_file(sa_family_t family, const char *filename);
MOCK_DECL(int, geoip_get_country_by_addr, (const tor_addr_t *addr));
@ -33,6 +57,9 @@ void geoip_note_client_seen(geoip_client_action_t action,
const tor_addr_t *addr, const char *transport_name,
time_t now);
void geoip_remove_old_clients(time_t cutoff);
clientmap_entry_t *geoip_lookup_client(const tor_addr_t *addr,
const char *transport_name,
geoip_client_action_t action);
void geoip_note_ns_response(geoip_ns_response_t response);
char *geoip_get_transport_history(void);

View File

@ -47,6 +47,7 @@ LIBTOR_A_SOURCES = \
src/or/dirvote.c \
src/or/dns.c \
src/or/dnsserv.c \
src/or/dos.c \
src/or/fp_pair.c \
src/or/geoip.c \
src/or/entrynodes.c \
@ -189,6 +190,7 @@ ORHEADERS = \
src/or/dns.h \
src/or/dns_structs.h \
src/or/dnsserv.h \
src/or/dos.h \
src/or/ext_orport.h \
src/or/fallback_dirs.inc \
src/or/fp_pair.h \

View File

@ -75,6 +75,7 @@
#include "dirvote.h"
#include "dns.h"
#include "dnsserv.h"
#include "dos.h"
#include "entrynodes.h"
#include "geoip.h"
#include "hibernate.h"
@ -3486,6 +3487,7 @@ tor_free_all(int postfork)
bridges_free_all();
consdiffmgr_free_all();
hs_free_all();
dos_free_all();
if (!postfork) {
config_free_all();
or_state_free_all();

View File

@ -51,6 +51,7 @@
#include "directory.h"
#include "dirserv.h"
#include "dirvote.h"
#include "dos.h"
#include "entrynodes.h"
#include "hibernate.h"
#include "main.h"
@ -1606,6 +1607,7 @@ notify_networkstatus_changed(const networkstatus_t *old_c,
{
notify_control_networkstatus_changed(old_c, new_c);
scheduler_notify_networkstatus_changed(old_c, new_c);
dos_consensus_has_changed(new_c);
}
/** Copy all the ancillary information (like router download status and so on)

View File

@ -1636,6 +1636,10 @@ typedef struct or_connection_t {
/** True iff this connection has had its bootstrap failure logged with
* control_event_bootstrap_problem. */
unsigned int have_noted_bootstrap_problem:1;
/** True iff this is a client connection and its address has been put in the
* geoip cache and handled by the DoS mitigation subsystem. We use this to
* insure we have a coherent count of concurrent connection. */
unsigned int tracked_for_dos_mitigation : 1;
uint16_t link_proto; /**< What protocol version are we using? 0 for
* "none negotiated yet." */
@ -4701,6 +4705,35 @@ typedef struct {
* running embedded inside another process.
*/
int DisableSignalHandlers;
/** Autobool: Is the circuit creation DoS mitigation subsystem enabled? */
int DoSCircuitCreationEnabled;
/** Minimum concurrent connection needed from one single address before any
* defense is used. */
int DoSCircuitCreationMinConnections;
/** Circuit rate used to refill the token bucket. */
int DoSCircuitCreationRate;
/** Maximum allowed burst of circuits. Reaching that value, the address is
* detected as malicious and a defense might be used. */
int DoSCircuitCreationBurst;
/** When an address is marked as malicous, what defense should be used
* against it. See the dos_cc_defense_type_t enum. */
int DoSCircuitCreationDefenseType;
/** For how much time (in seconds) the defense is applicable for a malicious
* address. A random time delta is added to the defense time of an address
* which will be between 1 second and half of this value. */
int DoSCircuitCreationDefenseTimePeriod;
/** Autobool: Is the DoS connection mitigation subsystem enabled? */
int DoSConnectionEnabled;
/** Maximum concurrent connection allowed per address. */
int DoSConnectionMaxConcurrentCount;
/** When an address is reaches the maximum count, what defense should be
* used against it. See the dos_conn_defense_type_t enum. */
int DoSConnectionDefenseType;
/** Autobool: Do we refuse single hop client rendezvous? */
int DoSRefuseSingleHopClientRendezvous;
} or_options_t;
#define LOG_PROTOCOL_WARN (get_protocol_warning_severity_level())

View File

@ -8,10 +8,12 @@
**/
#include "or.h"
#include "channel.h"
#include "circuitlist.h"
#include "circuituse.h"
#include "config.h"
#include "crypto.h"
#include "dos.h"
#include "relay.h"
#include "rendmid.h"
#include "rephist.h"
@ -231,6 +233,16 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
goto err;
}
/* Check if we are configured to accept established rendezvous cells from
* client or in other words tor2web clients. */
if (channel_is_client(circ->p_chan) &&
dos_should_refuse_single_hop_client()) {
/* Note it down for the heartbeat log purposes. */
dos_note_refuse_single_hop_client();
/* Silent drop so the client has to time out before moving on. */
return 0;
}
if (circ->base_.n_chan) {
log_warn(LD_PROTOCOL,
"Tried to establish rendezvous on non-edge circuit");

View File

@ -29,6 +29,7 @@
#include "statefile.h"
#include "hs_stats.h"
#include "hs_service.h"
#include "dos.h"
static void log_accounting(const time_t now, const or_options_t *options);
#include "geoip.h"
@ -167,6 +168,7 @@ log_heartbeat(time_t now)
if (public_server_mode(options)) {
rep_hist_log_circuit_handshake_stats(now);
rep_hist_log_link_protocol_counts();
dos_log_heartbeat();
}
circuit_log_ancient_one_hop_circuits(1800);

View File

@ -114,6 +114,7 @@ src_test_test_SOURCES = \
src/test/test_dir.c \
src/test/test_dir_common.c \
src/test/test_dir_handle_get.c \
src/test/test_dos.c \
src/test/test_entryconn.c \
src/test/test_entrynodes.c \
src/test/test_guardfraction.c \

View File

@ -1193,6 +1193,7 @@ struct testgroup_t testgroups[] = {
{ "dir/", dir_tests },
{ "dir_handle_get/", dir_handle_get_tests },
{ "dir/md/", microdesc_tests },
{ "dos/", dos_tests },
{ "entryconn/", entryconn_tests },
{ "entrynodes/", entrynodes_tests },
{ "guardfraction/", guardfraction_tests },

View File

@ -202,6 +202,7 @@ extern struct testcase_t crypto_tests[];
extern struct testcase_t crypto_openssl_tests[];
extern struct testcase_t dir_tests[];
extern struct testcase_t dir_handle_get_tests[];
extern struct testcase_t dos_tests[];
extern struct testcase_t entryconn_tests[];
extern struct testcase_t entrynodes_tests[];
extern struct testcase_t guardfraction_tests[];

248
src/test/test_dos.c Normal file
View File

@ -0,0 +1,248 @@
/* Copyright (c) 2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#define DOS_PRIVATE
#define TOR_CHANNEL_INTERNAL_
#define CIRCUITLIST_PRIVATE
#include "or.h"
#include "dos.h"
#include "circuitlist.h"
#include "geoip.h"
#include "channel.h"
#include "test.h"
#include "log_test_helpers.h"
static unsigned int
mock_enable_dos_protection(const networkstatus_t *ns)
{
(void) ns;
return 1;
}
/** Test that the connection tracker of the DoS subsystem will block clients
* who try to establish too many connections */
static void
test_dos_conn_creation(void *arg)
{
(void) arg;
MOCK(get_param_cc_enabled, mock_enable_dos_protection);
MOCK(get_param_conn_enabled, mock_enable_dos_protection);
/* Initialize test data */
or_connection_t or_conn;
time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */
tt_int_op(AF_INET,OP_EQ, tor_addr_parse(&or_conn.real_addr,
"18.0.0.1"));
tor_addr_t *addr = &or_conn.real_addr;
/* Get DoS subsystem limits */
dos_init();
uint32_t max_concurrent_conns = get_param_conn_max_concurrent_count(NULL);
/* Introduce new client */
geoip_note_client_seen(GEOIP_CLIENT_CONNECT, addr, NULL, now);
{ /* Register many conns from this client but not enough to get it blocked */
unsigned int i;
for (i = 0; i < max_concurrent_conns; i++) {
dos_new_client_conn(&or_conn);
}
}
/* Check that new conns are still permitted */
tt_int_op(DOS_CONN_DEFENSE_NONE, OP_EQ,
dos_conn_addr_get_defense_type(addr));
/* Register another conn and check that new conns are not allowed anymore */
dos_new_client_conn(&or_conn);
tt_int_op(DOS_CONN_DEFENSE_CLOSE, OP_EQ,
dos_conn_addr_get_defense_type(addr));
/* Close a client conn and see that a new conn will be permitted again */
dos_close_client_conn(&or_conn);
tt_int_op(DOS_CONN_DEFENSE_NONE, OP_EQ,
dos_conn_addr_get_defense_type(addr));
/* Register another conn and see that defense measures get reactivated */
dos_new_client_conn(&or_conn);
tt_int_op(DOS_CONN_DEFENSE_CLOSE, OP_EQ,
dos_conn_addr_get_defense_type(addr));
done:
dos_free_all();
}
/** Helper mock: Place a fake IP addr for this channel in <b>addr_out</b> */
static int
mock_channel_get_addr_if_possible(channel_t *chan, tor_addr_t *addr_out)
{
(void)chan;
tt_int_op(AF_INET,OP_EQ, tor_addr_parse(addr_out, "18.0.0.1"));;
return 1;
done:
return 0;
}
/** Test that the circuit tracker of the DoS subsystem will block clients who
* try to establish too many circuits. */
static void
test_dos_circuit_creation(void *arg)
{
(void) arg;
unsigned int i;
MOCK(get_param_cc_enabled, mock_enable_dos_protection);
MOCK(get_param_conn_enabled, mock_enable_dos_protection);
MOCK(channel_get_addr_if_possible,
mock_channel_get_addr_if_possible);
/* Initialize channels/conns/circs that will be used */
channel_t *chan = tor_malloc_zero(sizeof(channel_t));
channel_init(chan);
chan->is_client = 1;
/* Initialize test data */
or_connection_t or_conn;
time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */
tt_int_op(AF_INET,OP_EQ, tor_addr_parse(&or_conn.real_addr,
"18.0.0.1"));
tor_addr_t *addr = &or_conn.real_addr;
/* Get DoS subsystem limits */
dos_init();
uint32_t max_circuit_count = get_param_cc_circuit_burst(NULL);
uint32_t min_conc_conns_for_cc =
get_param_cc_min_concurrent_connection(NULL);
/* Introduce new client and establish enough connections to activate the
* circuit counting subsystem */
geoip_note_client_seen(GEOIP_CLIENT_CONNECT, addr, NULL, now);
for (i = 0; i < min_conc_conns_for_cc ; i++) {
dos_new_client_conn(&or_conn);
}
/* Register new circuits for this client and conn, but not enough to get
* detected as dos */
for (i=0; i < max_circuit_count-1; i++) {
dos_cc_new_create_cell(chan);
}
/* see that we didn't get detected for dosing */
tt_int_op(DOS_CC_DEFENSE_NONE, OP_EQ, dos_cc_get_defense_type(chan));
/* Register another CREATE cell that will push us over the limit. Check that
* the cell gets refused. */
dos_cc_new_create_cell(chan);
tt_int_op(DOS_CC_DEFENSE_REFUSE_CELL, OP_EQ, dos_cc_get_defense_type(chan));
/* TODO: Wait a few seconds before sending the cell, and check that the
buckets got refilled properly. */
/* TODO: Actually send a Tor cell (instead of calling the DoS function) and
* check that it will get refused */
done:
tor_free(chan);
dos_free_all();
}
/** Test that the DoS subsystem properly refills the circuit token buckets. */
static void
test_dos_bucket_refill(void *arg)
{
(void) arg;
int i;
/* For this test, this variable is set to the current circ count of the token
* bucket. */
uint32_t current_circ_count;
MOCK(get_param_cc_enabled, mock_enable_dos_protection);
MOCK(get_param_conn_enabled, mock_enable_dos_protection);
MOCK(channel_get_addr_if_possible,
mock_channel_get_addr_if_possible);
time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */
update_approx_time(now);
/* Initialize channels/conns/circs that will be used */
channel_t *chan = tor_malloc_zero(sizeof(channel_t));
channel_init(chan);
chan->is_client = 1;
or_connection_t or_conn;
tt_int_op(AF_INET,OP_EQ, tor_addr_parse(&or_conn.real_addr,
"18.0.0.1"));
tor_addr_t *addr = &or_conn.real_addr;
/* Initialize DoS subsystem and get relevant limits */
dos_init();
uint32_t max_circuit_count = get_param_cc_circuit_burst(NULL);
int circ_rate = tor_lround(get_circuit_rate_per_second());
/* Check that the circuit rate is a positive number and smaller than the max
* circuit count */
tt_int_op(circ_rate, OP_GT, 1);
tt_int_op(circ_rate, OP_LT, max_circuit_count);
/* Register this client */
geoip_note_client_seen(GEOIP_CLIENT_CONNECT, addr, NULL, now);
dos_new_client_conn(&or_conn);
/* Fetch this client from the geoip cache and get its DoS structs */
clientmap_entry_t *entry = geoip_lookup_client(addr, NULL,
GEOIP_CLIENT_CONNECT);
tt_assert(entry);
dos_client_stats_t* dos_stats = &entry->dos_stats;
/* Check that the circuit bucket is still uninitialized */
tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, 0);
/* Send a create cell: then check that the circ token bucket got initialized
* and one circ was subtracted. */
dos_cc_new_create_cell(chan);
current_circ_count = max_circuit_count - 1;
tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count);
/* Now send 29 more CREATEs and ensure that the bucket is missing 30
* tokens */
for (i=0; i < 29; i++) {
dos_cc_new_create_cell(chan);
current_circ_count--;
}
tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count);
/* OK! Progress time forward one sec, refill the bucket and check that the
* refill happened correctly. */
now += 1;
update_approx_time(now);
cc_stats_refill_bucket(&dos_stats->cc_stats, addr);
/* check refill */
current_circ_count += circ_rate;
tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count);
/* Now send as many CREATE cells as needed to deplete our token bucket
* completely */
for (; current_circ_count != 0; current_circ_count--) {
dos_cc_new_create_cell(chan);
}
tt_uint_op(current_circ_count, OP_EQ, 0);
tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count);
/* Now progress time a week forward, and check that the token bucket does not
* have more than max_circs allowance, even tho we let it simmer for so
* long. */
now += 604800; /* a week */
update_approx_time(now);
cc_stats_refill_bucket(&dos_stats->cc_stats, addr);
current_circ_count += max_circuit_count;
tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count);
done:
tor_free(chan);
dos_free_all();
}
struct testcase_t dos_tests[] = {
{ "conn_creation", test_dos_conn_creation, TT_FORK, NULL, NULL },
{ "circuit_creation", test_dos_circuit_creation, TT_FORK, NULL, NULL },
{ "bucket_refill", test_dos_bucket_refill, TT_FORK, NULL, NULL },
END_OF_TESTCASES
};