2018-01-25 15:54:58 -05:00
|
|
|
/* 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 "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_tenths;
|
|
|
|
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;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* General interface of the denial of service mitigation subsystem.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* 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_tenths(const networkstatus_t *ns)
|
|
|
|
{
|
|
|
|
/* This is in seconds. */
|
|
|
|
if (get_options()->DoSCircuitCreationRateTenths) {
|
|
|
|
return get_options()->DoSCircuitCreationRateTenths;
|
|
|
|
}
|
|
|
|
return networkstatus_get_param(ns, "DoSCircuitCreationRateTenths",
|
|
|
|
DOS_CC_CIRCUIT_RATE_TENTHS_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_tenths = get_param_cc_circuit_rate_tenths(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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
|
|
|
/* Concurrent connection detection public API. */
|
|
|
|
|
|
|
|
/* General API */
|
|
|
|
|
2018-01-25 16:05:59 -05:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2018-01-25 15:54:58 -05:00
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|