mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-20 02:09:24 +01:00
Merge remote-tracking branch 'dgoulet/ticket24902_029_05' into maint-0.2.9
This commit is contained in:
commit
cb92d47dec
5
changes/bug24952
Normal file
5
changes/bug24952
Normal file
@ -0,0 +1,5 @@
|
||||
o Minor bugfix (channel connection):
|
||||
- The accurate address of a connection is real_addr, not the addr member.
|
||||
TLS Channel remote address is now real_addr content instead of addr
|
||||
member. Fixes bug 24952; bugfix on 707c1e2e26 in 0.2.4.11-alpha.
|
||||
Patch by "ffmancera".
|
4
changes/bug25223
Normal file
4
changes/bug25223
Normal file
@ -0,0 +1,4 @@
|
||||
o Minor bugfixes (DoS mitigation):
|
||||
- Make sure we don't modify consensus parameters if we aren't a public
|
||||
relay when a new consensus arrives. Fixes bug 25223; bugfix on
|
||||
0.3.3.2-alpha.
|
13
changes/ticket24902
Normal file
13
changes/ticket24902
Normal 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.
|
||||
|
4
changes/ticket25122
Normal file
4
changes/ticket25122
Normal file
@ -0,0 +1,4 @@
|
||||
o Minor feature (geoip cache):
|
||||
- Make our OOM handler aware of the geoip client history cache so it
|
||||
doesn't fill up the memory which is especially important for IPv6 and
|
||||
our DoS mitigation subsystem. Closes ticket 25122.
|
4
changes/ticket25202
Normal file
4
changes/ticket25202
Normal file
@ -0,0 +1,4 @@
|
||||
o Minor bugfixes (DoS mitigation):
|
||||
- Add extra safety checks when refilling the circuit creation bucket to
|
||||
ensure we never set a value that is above the allowed burst. Fixes
|
||||
bug 25202; bugfix on 0.3.3.2-alpha.
|
@ -2441,6 +2441,101 @@ The following options are used to configure a hidden service.
|
||||
including setting SOCKSPort to "0".
|
||||
(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. If not defined in the consensus, the value is 0.
|
||||
(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. If not defined in the consensus, the value is 3.
|
||||
(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. If not
|
||||
defined in the consensus, the value is 3.
|
||||
(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. If not defined in the
|
||||
consensus, the value is 90.
|
||||
(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. If not defined in the consensus,
|
||||
the value is 2.
|
||||
(Default: 0)
|
||||
|
||||
[[DoSCircuitCreationDefenseTimePeriod]] **DoSCircuitCreationDefenseTimePeriod** __N__ **seconds**|**minutes**|**hours**::
|
||||
|
||||
The base time period in seconds that the DoS defense is activated for. The
|
||||
actual value is selected randomly for each activation from N+1 to 3/2 * N.
|
||||
"0" means use the consensus parameter. If not defined in the consensus,
|
||||
the value is 3600 seconds (1 hour). (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. If not
|
||||
defined in the consensus, the value is 0.
|
||||
(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. If not defined in the
|
||||
consensus, the value is 100.
|
||||
(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. If not defined in the consensus,
|
||||
the value is 2.
|
||||
(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. If not defined in the consensus, the value is 0.
|
||||
(Default: auto)
|
||||
|
||||
TESTING NETWORK OPTIONS
|
||||
-----------------------
|
||||
|
||||
|
@ -1181,6 +1181,9 @@ tor_addr_compare_masked(const tor_addr_t *addr1, const tor_addr_t *addr2,
|
||||
}
|
||||
}
|
||||
|
||||
/** Input for siphash, to produce some output for an unspec value. */
|
||||
static const uint32_t unspec_hash_input[] = { 0x4e4df09f, 0x92985342 };
|
||||
|
||||
/** Return a hash code based on the address addr. DOCDOC extra */
|
||||
uint64_t
|
||||
tor_addr_hash(const tor_addr_t *addr)
|
||||
@ -1189,7 +1192,7 @@ tor_addr_hash(const tor_addr_t *addr)
|
||||
case AF_INET:
|
||||
return siphash24g(&addr->addr.in_addr.s_addr, 4);
|
||||
case AF_UNSPEC:
|
||||
return 0x4e4d5342;
|
||||
return siphash24g(unspec_hash_input, sizeof(unspec_hash_input));
|
||||
case AF_INET6:
|
||||
return siphash24g(&addr->addr.in6_addr.s6_addr, 16);
|
||||
default:
|
||||
@ -1200,6 +1203,28 @@ tor_addr_hash(const tor_addr_t *addr)
|
||||
}
|
||||
}
|
||||
|
||||
/** As tor_addr_hash, but use a particular siphash key. */
|
||||
uint64_t
|
||||
tor_addr_keyed_hash(const struct sipkey *key, const tor_addr_t *addr)
|
||||
{
|
||||
/* This is duplicate code with tor_addr_hash, since this function needs to
|
||||
* be backportable all the way to 0.2.9. */
|
||||
|
||||
switch (tor_addr_family(addr)) {
|
||||
case AF_INET:
|
||||
return siphash24(&addr->addr.in_addr.s_addr, 4, key);
|
||||
case AF_UNSPEC:
|
||||
return siphash24(unspec_hash_input, sizeof(unspec_hash_input), key);
|
||||
case AF_INET6:
|
||||
return siphash24(&addr->addr.in6_addr.s6_addr, 16, key);
|
||||
default:
|
||||
/* LCOV_EXCL_START */
|
||||
tor_fragile_assert();
|
||||
return 0;
|
||||
/* LCOV_EXCL_END */
|
||||
}
|
||||
}
|
||||
|
||||
/** Return a newly allocated string with a representation of <b>addr</b>. */
|
||||
char *
|
||||
tor_addr_to_str_dup(const tor_addr_t *addr)
|
||||
|
@ -228,6 +228,8 @@ int tor_addr_compare_masked(const tor_addr_t *addr1, const tor_addr_t *addr2,
|
||||
#define tor_addr_eq(a,b) (0==tor_addr_compare((a),(b),CMP_EXACT))
|
||||
|
||||
uint64_t tor_addr_hash(const tor_addr_t *addr);
|
||||
struct sipkey;
|
||||
uint64_t tor_addr_keyed_hash(const struct sipkey *key, const tor_addr_t *addr);
|
||||
int tor_addr_is_v4(const tor_addr_t *addr);
|
||||
int tor_addr_is_internal_(const tor_addr_t *ip, int for_listening,
|
||||
const char *filename, int lineno);
|
||||
|
129
src/common/address_set.c
Normal file
129
src/common/address_set.c
Normal file
@ -0,0 +1,129 @@
|
||||
/* Copyright (c) 2018, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file address_set.c
|
||||
* \brief Implementation for a set of addresses.
|
||||
*
|
||||
* This module was first written on a semi-emergency basis to improve the
|
||||
* robustness of the anti-DoS module. As such, it's written in a pretty
|
||||
* conservative way, and should be susceptible to improvement later on.
|
||||
**/
|
||||
|
||||
#include "orconfig.h"
|
||||
#include "address_set.h"
|
||||
#include "address.h"
|
||||
#include "compat.h"
|
||||
#include "container.h"
|
||||
#include "crypto.h"
|
||||
#include "util.h"
|
||||
#include "siphash.h"
|
||||
|
||||
/** How many 64-bit siphash values to extract per address */
|
||||
#define N_HASHES 2
|
||||
/** How many bloom-filter bits we set per address. This is twice the N_HASHES
|
||||
* value, since we split the siphash output into two 32-bit values. */
|
||||
#define N_BITS_PER_ITEM (N_HASHES * 2)
|
||||
|
||||
/* XXXX This code is largely duplicated with digestset_t. We should merge
|
||||
* them together into a common bloom-filter implementation. I'm keeping
|
||||
* them separate for now, though, since this module needs to be backported
|
||||
* all the way to 0.2.9.
|
||||
*
|
||||
* The main difference between digestset_t and this code is that we use
|
||||
* independent siphashes rather than messing around with bit-shifts. The
|
||||
* approach here is probably more sound, and we should prefer it if&when we
|
||||
* unify the implementations.
|
||||
**/
|
||||
|
||||
struct address_set_t {
|
||||
/** siphash keys to make N_HASHES independent hashes for each address. */
|
||||
struct sipkey key[N_HASHES];
|
||||
int mask; /**< One less than the number of bits in <b>ba</b>; always one less
|
||||
* than a power of two. */
|
||||
bitarray_t *ba; /**< A bit array to implement the Bloom filter. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocate and return an address_set, suitable for holding up to
|
||||
* <b>max_address_guess</b> distinct values.
|
||||
*/
|
||||
address_set_t *
|
||||
address_set_new(int max_addresses_guess)
|
||||
{
|
||||
/* See digestset_new() for rationale on this equation. */
|
||||
int n_bits = 1u << (tor_log2(max_addresses_guess)+5);
|
||||
|
||||
address_set_t *set = tor_malloc_zero(sizeof(address_set_t));
|
||||
set->mask = n_bits - 1;
|
||||
set->ba = bitarray_init_zero(n_bits);
|
||||
crypto_rand((char*) set->key, sizeof(set->key));
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all storage associated with <b>set</b>
|
||||
*/
|
||||
void
|
||||
address_set_free(address_set_t *set)
|
||||
{
|
||||
if (! set)
|
||||
return;
|
||||
|
||||
bitarray_free(set->ba);
|
||||
tor_free(set);
|
||||
}
|
||||
|
||||
/** Yield the bit index corresponding to 'val' for set. */
|
||||
#define BIT(set, val) ((val) & (set)->mask)
|
||||
|
||||
/**
|
||||
* Add <b>addr</b> to <b>set</b>.
|
||||
*
|
||||
* All future queries for <b>addr</b> in set will return true. Removing
|
||||
* items is not possible.
|
||||
*/
|
||||
void
|
||||
address_set_add(address_set_t *set, const struct tor_addr_t *addr)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < N_HASHES; ++i) {
|
||||
uint64_t h = tor_addr_keyed_hash(&set->key[i], addr);
|
||||
uint32_t high_bits = (uint32_t)(h >> 32);
|
||||
uint32_t low_bits = (uint32_t)(h);
|
||||
bitarray_set(set->ba, BIT(set, high_bits));
|
||||
bitarray_set(set->ba, BIT(set, low_bits));
|
||||
}
|
||||
}
|
||||
|
||||
/** As address_set_add(), but take an ipv4 address in host order. */
|
||||
void
|
||||
address_set_add_ipv4h(address_set_t *set, uint32_t addr)
|
||||
{
|
||||
tor_addr_t a;
|
||||
tor_addr_from_ipv4h(&a, addr);
|
||||
address_set_add(set, &a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if <b>addr</b> if a member of <b>set</b>. (And probably,
|
||||
* return false if <b>addr</b> is not a member of set.)
|
||||
*/
|
||||
int
|
||||
address_set_probably_contains(address_set_t *set,
|
||||
const struct tor_addr_t *addr)
|
||||
{
|
||||
int i, matches = 0;
|
||||
for (i = 0; i < N_HASHES; ++i) {
|
||||
uint64_t h = tor_addr_keyed_hash(&set->key[i], addr);
|
||||
uint32_t high_bits = (uint32_t)(h >> 32);
|
||||
uint32_t low_bits = (uint32_t)(h);
|
||||
// Note that !! is necessary here, since bitarray_is_set does not
|
||||
// necessarily return 1 on true.
|
||||
matches += !! bitarray_is_set(set->ba, BIT(set, high_bits));
|
||||
matches += !! bitarray_is_set(set->ba, BIT(set, low_bits));
|
||||
}
|
||||
return matches == N_BITS_PER_ITEM;
|
||||
}
|
||||
|
35
src/common/address_set.h
Normal file
35
src/common/address_set.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* Copyright (c) 2018, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file addressset.h
|
||||
* \brief Types to handle sets of addresses.
|
||||
*
|
||||
* This module was first written on a semi-emergency basis to improve the
|
||||
* robustness of the anti-DoS module. As such, it's written in a pretty
|
||||
* conservative way, and should be susceptible to improvement later on.
|
||||
**/
|
||||
|
||||
#ifndef TOR_ADDRESS_SET_H
|
||||
#define TOR_ADDRESS_SET_H
|
||||
|
||||
#include "orconfig.h"
|
||||
#include "torint.h"
|
||||
|
||||
/**
|
||||
* An address_set_t represents a set of tor_addr_t values. The implementation
|
||||
* is probabilistic: false negatives cannot occur but false positives are
|
||||
* possible.
|
||||
*/
|
||||
typedef struct address_set_t address_set_t;
|
||||
struct tor_addr_t;
|
||||
|
||||
address_set_t *address_set_new(int max_addresses_guess);
|
||||
void address_set_free(address_set_t *set);
|
||||
void address_set_add(address_set_t *set, const struct tor_addr_t *addr);
|
||||
void address_set_add_ipv4h(address_set_t *set, uint32_t addr);
|
||||
int address_set_probably_contains(address_set_t *set,
|
||||
const struct tor_addr_t *addr);
|
||||
|
||||
#endif
|
||||
|
@ -80,6 +80,7 @@ src_common_libor_ctime_testing_a_CFLAGS = @CFLAGS_CONSTTIME@ $(TEST_CFLAGS)
|
||||
|
||||
LIBOR_A_SRC = \
|
||||
src/common/address.c \
|
||||
src/common/address_set.c \
|
||||
src/common/backtrace.c \
|
||||
src/common/compat.c \
|
||||
src/common/compat_threads.c \
|
||||
@ -135,6 +136,7 @@ src_common_libor_event_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
|
||||
|
||||
COMMONHEADERS = \
|
||||
src/common/address.h \
|
||||
src/common/address_set.h \
|
||||
src/common/backtrace.h \
|
||||
src/common/aes.h \
|
||||
src/common/ciphers.inc \
|
||||
|
@ -1177,7 +1177,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", NULL
|
||||
"SCHED", "DOS", NULL
|
||||
};
|
||||
|
||||
/** Return a bitmask for the log domain for which <b>domain</b> is the name,
|
||||
|
@ -99,8 +99,10 @@
|
||||
#define LD_CHANNEL (1u<<21)
|
||||
/** Scheduler */
|
||||
#define LD_SCHED (1u<<22)
|
||||
/** Denial of Service mitigation. */
|
||||
#define LD_DOS (1u<<23)
|
||||
/** Number of logging domains in the code. */
|
||||
#define N_LOGGING_DOMAINS 23
|
||||
#define N_LOGGING_DOMAINS 24
|
||||
|
||||
/** This log message is not safe to send to a callback-based logger
|
||||
* immediately. Used as a flag, not a log domain. */
|
||||
|
@ -2583,6 +2583,7 @@ channel_do_open_actions(channel_t *chan)
|
||||
if (!router_get_by_id_digest(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;
|
||||
|
||||
@ -2590,6 +2591,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 */
|
||||
}
|
||||
@ -3840,8 +3845,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);
|
||||
|
@ -550,7 +550,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);
|
||||
|
@ -514,7 +514,7 @@ channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out)
|
||||
tor_assert(addr_out);
|
||||
|
||||
if (tlschan->conn) {
|
||||
tor_addr_copy(addr_out, &(TO_CONN(tlschan->conn)->addr));
|
||||
tor_addr_copy(addr_out, &(tlschan->conn->real_addr));
|
||||
rv = 1;
|
||||
} else tor_addr_make_unspec(addr_out);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "dirserv.h"
|
||||
#include "dirvote.h"
|
||||
#include "dns.h"
|
||||
#include "dos.h"
|
||||
#include "entrynodes.h"
|
||||
#include "geoip.h"
|
||||
#include "hibernate.h"
|
||||
@ -241,6 +242,19 @@ static config_var_t option_vars_[] = {
|
||||
OBSOLETE("DynamicDHGroups"),
|
||||
VPORT(DNSPort, LINELIST, NULL),
|
||||
V(DNSListenAddress, LINELIST, NULL),
|
||||
/* 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"),
|
||||
@ -2039,6 +2053,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);
|
||||
|
@ -78,6 +78,7 @@
|
||||
#include "dirserv.h"
|
||||
#include "dns.h"
|
||||
#include "dnsserv.h"
|
||||
#include "dos.h"
|
||||
#include "entrynodes.h"
|
||||
#include "ext_orport.h"
|
||||
#include "geoip.h"
|
||||
@ -687,6 +688,13 @@ connection_free,(connection_t *conn))
|
||||
"connection_free");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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_(conn);
|
||||
}
|
||||
@ -1592,6 +1600,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;
|
||||
|
794
src/or/dos.c
Normal file
794
src/or/dos.c
Normal file
@ -0,0 +1,794 @@
|
||||
/* 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 "nodelist.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 uint64_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;
|
||||
uint64_t num_token, elapsed_time_last_refill = 0, circuit_rate = 0;
|
||||
time_t now;
|
||||
int64_t last_refill_ts;
|
||||
|
||||
tor_assert(stats);
|
||||
tor_assert(addr);
|
||||
|
||||
now = approx_time();
|
||||
last_refill_ts = (int64_t)stats->last_circ_bucket_refill_ts;
|
||||
|
||||
/* If less than a second has elapsed, don't add any tokens.
|
||||
* Note: If a relay's clock is ever 0, any new clients won't get a refill
|
||||
* until the next second. But a relay that thinks it is 1970 will never
|
||||
* validate the public consensus. */
|
||||
if ((int64_t)now == last_refill_ts) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* At this point, we know we might need to add token to the bucket. We'll
|
||||
* first get the circuit rate that is how many circuit are we allowed to do
|
||||
* per second. */
|
||||
circuit_rate = get_circuit_rate_per_second();
|
||||
|
||||
/* We've never filled the bucket so fill it with the maximum being the burst
|
||||
* and we are done.
|
||||
* Note: If a relay's clock is ever 0, all clients that were last refilled
|
||||
* in that zero second will get a full refill here. */
|
||||
if (last_refill_ts == 0) {
|
||||
num_token = dos_cc_circuit_burst;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Our clock jumped backward so 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 ((int64_t)now < last_refill_ts) {
|
||||
/* Use the maximum allowed value of token. */
|
||||
num_token = dos_cc_circuit_burst;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* How many seconds have elapsed between now and the last refill?
|
||||
* This subtraction can't underflow, because now >= last_refill_ts.
|
||||
* And it can't overflow, because INT64_MAX - (-INT64_MIN) == UINT64_MAX. */
|
||||
elapsed_time_last_refill = (uint64_t)now - last_refill_ts;
|
||||
|
||||
/* If the elapsed time is very large, it means our clock jumped forward.
|
||||
* If the multiplication would overflow, use the maximum allowed value. */
|
||||
if (elapsed_time_last_refill > UINT32_MAX) {
|
||||
num_token = dos_cc_circuit_burst;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Compute how many circuits we are allowed in that time frame which we'll
|
||||
* add to the bucket. This can't overflow, because both multiplicands
|
||||
* are less than or equal to UINT32_MAX, and num_token is uint64_t. */
|
||||
num_token = elapsed_time_last_refill * circuit_rate;
|
||||
|
||||
end:
|
||||
/* If the sum would overflow, use the maximum allowed value. */
|
||||
if (num_token > UINT32_MAX - stats->circuit_bucket) {
|
||||
new_circuit_bucket_count = dos_cc_circuit_burst;
|
||||
} else {
|
||||
/* We cap the bucket to the burst value else this could overflow uint32_t
|
||||
* over time. */
|
||||
new_circuit_bucket_count = MIN(stats->circuit_bucket + (uint32_t)num_token,
|
||||
dos_cc_circuit_burst);
|
||||
}
|
||||
|
||||
/* This function is not allowed to make the bucket count larger than the
|
||||
* burst value */
|
||||
tor_assert_nonfatal(new_circuit_bucket_count <= dos_cc_circuit_burst);
|
||||
/* This function is not allowed to make the bucket count smaller, unless it
|
||||
* is decreasing it to a newly configured, lower burst value. We allow the
|
||||
* bucket to stay the same size, in case the circuit rate is zero. */
|
||||
tor_assert_nonfatal(new_circuit_bucket_count >= stats->circuit_bucket ||
|
||||
new_circuit_bucket_count == dos_cc_circuit_burst);
|
||||
|
||||
log_debug(LD_DOS, "DoS address %s has its circuit bucket value: %" PRIu32
|
||||
". Filling it to %" PRIu32 ". Circuit rate is %" PRIu64
|
||||
". Elapsed time is %" PRIi64,
|
||||
fmt_addr(addr), stats->circuit_bucket, new_circuit_bucket_count,
|
||||
circuit_rate, (int64_t)elapsed_time_last_refill);
|
||||
|
||||
stats->circuit_bucket = new_circuit_bucket_count;
|
||||
stats->last_circ_bucket_refill_ts = now;
|
||||
|
||||
done:
|
||||
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 ignore any known address meaning an address of a known relay. The
|
||||
* reason to do so is because network reentry is possible where a client
|
||||
* connection comes from an Exit node. Even when we'll fix reentry, this is
|
||||
* a robust defense to keep in place. */
|
||||
if (nodelist_probably_contains_address(&or_conn->real_addr)) {
|
||||
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)
|
||||
{
|
||||
/* There are two ways to configure this subsystem, one at startup through
|
||||
* dos_init() which is called when the options are parsed. And this one
|
||||
* through the consensus. We don't want to enable any DoS mitigation if we
|
||||
* aren't a public relay. */
|
||||
if (!public_server_mode(get_options())) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
140
src/or/dos.h
Normal 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 uint64_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 */
|
||||
|
205
src/or/geoip.c
205
src/or/geoip.c
@ -33,6 +33,7 @@
|
||||
#include "config.h"
|
||||
#include "control.h"
|
||||
#include "dnsserv.h"
|
||||
#include "dos.h"
|
||||
#include "geoip.h"
|
||||
#include "routerlist.h"
|
||||
|
||||
@ -72,6 +73,38 @@ static smartlist_t *geoip_ipv4_entries = NULL, *geoip_ipv6_entries = NULL;
|
||||
static char geoip_digest[DIGEST_LEN];
|
||||
static char geoip6_digest[DIGEST_LEN];
|
||||
|
||||
/* Total size in bytes of the geoip client history cache. Used by the OOM
|
||||
* handler. */
|
||||
static size_t geoip_client_history_cache_size;
|
||||
|
||||
/* Increment the geoip client history cache size counter with the given bytes.
|
||||
* This prevents an overflow and set it to its maximum in that case. */
|
||||
static inline void
|
||||
geoip_increment_client_history_cache_size(size_t bytes)
|
||||
{
|
||||
/* This is shockingly high, lets log it so it can be reported. */
|
||||
IF_BUG_ONCE(geoip_client_history_cache_size > (SIZE_MAX - bytes)) {
|
||||
geoip_client_history_cache_size = SIZE_MAX;
|
||||
return;
|
||||
}
|
||||
geoip_client_history_cache_size += bytes;
|
||||
}
|
||||
|
||||
/* Decrement the geoip client history cache size counter with the given bytes.
|
||||
* This prevents an underflow and set it to 0 in that case. */
|
||||
static inline void
|
||||
geoip_decrement_client_history_cache_size(size_t bytes)
|
||||
{
|
||||
/* Going below 0 means that we either allocated an entry without
|
||||
* incrementing the counter or we have different sizes when allocating and
|
||||
* freeing. It shouldn't happened so log it. */
|
||||
IF_BUG_ONCE(geoip_client_history_cache_size < bytes) {
|
||||
geoip_client_history_cache_size = 0;
|
||||
return;
|
||||
}
|
||||
geoip_client_history_cache_size -= bytes;
|
||||
}
|
||||
|
||||
/** Return the index of the <b>country</b>'s entry in the GeoIP
|
||||
* country list if it is a valid 2-letter country code, otherwise
|
||||
* return -1. */
|
||||
@ -472,24 +505,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.
|
||||
*/
|
||||
@ -526,6 +541,15 @@ HT_PROTOTYPE(clientmap, clientmap_entry_t, node, clientmap_entry_hash,
|
||||
HT_GENERATE2(clientmap, clientmap_entry_t, node, clientmap_entry_hash,
|
||||
clientmap_entries_eq, 0.6, tor_reallocarray_, tor_free_)
|
||||
|
||||
/** Return the size of a client map entry. */
|
||||
static inline size_t
|
||||
clientmap_entry_size(const clientmap_entry_t *ent)
|
||||
{
|
||||
tor_assert(ent);
|
||||
return (sizeof(clientmap_entry_t) +
|
||||
(ent->transport_name ? strlen(ent->transport_name) : 0));
|
||||
}
|
||||
|
||||
/** Free all storage held by <b>ent</b>. */
|
||||
static void
|
||||
clientmap_entry_free(clientmap_entry_t *ent)
|
||||
@ -533,10 +557,40 @@ 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);
|
||||
geoip_decrement_client_history_cache_size(clientmap_entry_size(ent));
|
||||
|
||||
tor_free(ent->transport_name);
|
||||
tor_free(ent);
|
||||
}
|
||||
|
||||
/* Return a newly allocated clientmap entry with the given action and address
|
||||
* that are mandatory. The transport_name can be optional. This can't fail. */
|
||||
static clientmap_entry_t *
|
||||
clientmap_entry_new(geoip_client_action_t action, const tor_addr_t *addr,
|
||||
const char *transport_name)
|
||||
{
|
||||
clientmap_entry_t *entry;
|
||||
|
||||
tor_assert(action == GEOIP_CLIENT_CONNECT ||
|
||||
action == GEOIP_CLIENT_NETWORKSTATUS);
|
||||
tor_assert(addr);
|
||||
|
||||
entry = tor_malloc_zero(sizeof(clientmap_entry_t));
|
||||
entry->action = action;
|
||||
tor_addr_copy(&entry->addr, addr);
|
||||
if (transport_name) {
|
||||
entry->transport_name = tor_strdup(transport_name);
|
||||
}
|
||||
|
||||
/* Allocated and initialized, note down its size for the OOM handler. */
|
||||
geoip_increment_client_history_cache_size(clientmap_entry_size(entry));
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/** Clear history of connecting clients used by entry and bridge stats. */
|
||||
static void
|
||||
client_history_clear(void)
|
||||
@ -564,14 +618,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. */
|
||||
@ -583,17 +640,9 @@ 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);
|
||||
if (transport_name)
|
||||
ent->transport_name = tor_strdup(transport_name);
|
||||
ent->action = (int)action;
|
||||
ent = clientmap_entry_new(action, addr, transport_name);
|
||||
HT_INSERT(clientmap, &client_history, ent);
|
||||
}
|
||||
if (now / 60 <= (int)MAX_LAST_SEEN_IN_MINUTES && now >= 0)
|
||||
@ -635,6 +684,94 @@ 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);
|
||||
}
|
||||
|
||||
/* Cleanup client entries older than the cutoff. Used for the OOM. Return the
|
||||
* number of bytes freed. If 0 is returned, nothing was freed. */
|
||||
static size_t
|
||||
oom_clean_client_entries(time_t cutoff)
|
||||
{
|
||||
size_t bytes = 0;
|
||||
clientmap_entry_t **ent, **ent_next;
|
||||
|
||||
for (ent = HT_START(clientmap, &client_history); ent; ent = ent_next) {
|
||||
clientmap_entry_t *entry = *ent;
|
||||
if (entry->last_seen_in_minutes < (cutoff / 60)) {
|
||||
ent_next = HT_NEXT_RMV(clientmap, &client_history, ent);
|
||||
bytes += clientmap_entry_size(entry);
|
||||
clientmap_entry_free(entry);
|
||||
} else {
|
||||
ent_next = HT_NEXT(clientmap, &client_history, ent);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* Below this minimum lifetime, the OOM won't cleanup any entries. */
|
||||
#define GEOIP_CLIENT_CACHE_OOM_MIN_CUTOFF (4 * 60 * 60)
|
||||
/* The OOM moves the cutoff by that much every run. */
|
||||
#define GEOIP_CLIENT_CACHE_OOM_STEP (15 * 50)
|
||||
|
||||
/* Cleanup the geoip client history cache called from the OOM handler. Return
|
||||
* the amount of bytes removed. This can return a value below or above
|
||||
* min_remove_bytes but will stop as oon as the min_remove_bytes has been
|
||||
* reached. */
|
||||
size_t
|
||||
geoip_client_cache_handle_oom(time_t now, size_t min_remove_bytes)
|
||||
{
|
||||
time_t k;
|
||||
size_t bytes_removed = 0;
|
||||
|
||||
/* Our OOM handler called with 0 bytes to remove is a code flow error. */
|
||||
tor_assert(min_remove_bytes != 0);
|
||||
|
||||
/* Set k to the initial cutoff of an entry. We then going to move it by step
|
||||
* to try to remove as much as we can. */
|
||||
k = WRITE_STATS_INTERVAL;
|
||||
|
||||
do {
|
||||
time_t cutoff;
|
||||
|
||||
/* If k has reached the minimum lifetime, we have to stop else we might
|
||||
* remove every single entries which would be pretty bad for the DoS
|
||||
* mitigation subsystem if by just filling the geoip cache, it was enough
|
||||
* to trigger the OOM and clean every single entries. */
|
||||
if (k <= GEOIP_CLIENT_CACHE_OOM_MIN_CUTOFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
cutoff = now - k;
|
||||
bytes_removed += oom_clean_client_entries(cutoff);
|
||||
k -= GEOIP_CLIENT_CACHE_OOM_STEP;
|
||||
} while (bytes_removed < min_remove_bytes);
|
||||
|
||||
return bytes_removed;
|
||||
}
|
||||
|
||||
/* Return the total size in bytes of the client history cache. */
|
||||
size_t
|
||||
geoip_client_cache_total_allocation(void)
|
||||
{
|
||||
return geoip_client_history_cache_size;
|
||||
}
|
||||
|
||||
/** How many responses are we giving to clients requesting v3 network
|
||||
* statuses? */
|
||||
static uint32_t ns_v3_responses[GEOIP_NS_RESPONSE_NUM];
|
||||
|
@ -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
|
||||
|
||||
/** 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,11 @@ 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);
|
||||
size_t geoip_client_cache_total_allocation(void);
|
||||
size_t geoip_client_cache_handle_oom(time_t now, size_t min_remove_bytes);
|
||||
|
||||
void geoip_note_ns_response(geoip_ns_response_t response);
|
||||
char *geoip_get_transport_history(void);
|
||||
|
@ -43,6 +43,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 \
|
||||
@ -151,6 +152,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 \
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "dirvote.h"
|
||||
#include "dns.h"
|
||||
#include "dnsserv.h"
|
||||
#include "dos.h"
|
||||
#include "entrynodes.h"
|
||||
#include "geoip.h"
|
||||
#include "hibernate.h"
|
||||
@ -2989,6 +2990,7 @@ tor_free_all(int postfork)
|
||||
control_free_all();
|
||||
sandbox_free_getaddrinfo_cache();
|
||||
protover_free_all();
|
||||
dos_free_all();
|
||||
if (!postfork) {
|
||||
config_free_all();
|
||||
or_state_free_all();
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "directory.h"
|
||||
#include "dirserv.h"
|
||||
#include "dirvote.h"
|
||||
#include "dos.h"
|
||||
#include "entrynodes.h"
|
||||
#include "main.h"
|
||||
#include "microdesc.h"
|
||||
@ -1502,6 +1503,15 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c,
|
||||
smartlist_free(changed);
|
||||
}
|
||||
|
||||
/* Called when the consensus has changed from old_c to new_c. */
|
||||
static void
|
||||
notify_networkstatus_changed(const networkstatus_t *old_c,
|
||||
const networkstatus_t *new_c)
|
||||
{
|
||||
notify_control_networkstatus_changed(old_c, new_c);
|
||||
dos_consensus_has_changed(new_c);
|
||||
}
|
||||
|
||||
/** Copy all the ancillary information (like router download status and so on)
|
||||
* from <b>old_c</b> to <b>new_c</b>. */
|
||||
static void
|
||||
@ -1826,8 +1836,7 @@ networkstatus_set_current_consensus(const char *consensus,
|
||||
const int is_usable_flavor = flav == usable_consensus_flavor();
|
||||
|
||||
if (is_usable_flavor) {
|
||||
notify_control_networkstatus_changed(
|
||||
networkstatus_get_latest_consensus(), c);
|
||||
notify_networkstatus_changed(networkstatus_get_latest_consensus(), c);
|
||||
}
|
||||
if (flav == FLAV_NS) {
|
||||
if (current_ns_consensus) {
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "or.h"
|
||||
#include "address.h"
|
||||
#include "address_set.h"
|
||||
#include "config.h"
|
||||
#include "control.h"
|
||||
#include "dirserv.h"
|
||||
@ -52,6 +53,7 @@ static void count_usable_descriptors(int *num_present,
|
||||
static void update_router_have_minimum_dir_info(void);
|
||||
static double get_frac_paths_needed_for_circs(const or_options_t *options,
|
||||
const networkstatus_t *ns);
|
||||
static void node_add_to_address_set(const node_t *node);
|
||||
|
||||
/** A nodelist_t holds a node_t object for every router we're "willing to use
|
||||
* for something". Specifically, it should hold a node_t for every node that
|
||||
@ -63,6 +65,8 @@ typedef struct nodelist_t {
|
||||
/* Hash table to map from node ID digest to node. */
|
||||
HT_HEAD(nodelist_map, node_t) nodes_by_id;
|
||||
|
||||
/* Set of addresses that belong to nodes we believe in. */
|
||||
address_set_t *node_addrs;
|
||||
} nodelist_t;
|
||||
|
||||
static inline unsigned int
|
||||
@ -150,6 +154,50 @@ node_addrs_changed(node_t *node)
|
||||
node->country = -1;
|
||||
}
|
||||
|
||||
/** Add all address information about <b>node</b> to the current address
|
||||
* set (if there is one).
|
||||
*/
|
||||
static void
|
||||
node_add_to_address_set(const node_t *node)
|
||||
{
|
||||
if (!the_nodelist || !the_nodelist->node_addrs)
|
||||
return;
|
||||
|
||||
/* These various address sources can be redundant, but it's likely faster
|
||||
* to add them all than to compare them all for equality. */
|
||||
|
||||
if (node->rs) {
|
||||
if (node->rs->addr)
|
||||
address_set_add_ipv4h(the_nodelist->node_addrs, node->rs->addr);
|
||||
if (!tor_addr_is_null(&node->rs->ipv6_addr))
|
||||
address_set_add(the_nodelist->node_addrs, &node->rs->ipv6_addr);
|
||||
}
|
||||
if (node->ri) {
|
||||
if (node->ri->addr)
|
||||
address_set_add_ipv4h(the_nodelist->node_addrs, node->ri->addr);
|
||||
if (!tor_addr_is_null(&node->ri->ipv6_addr))
|
||||
address_set_add(the_nodelist->node_addrs, &node->ri->ipv6_addr);
|
||||
}
|
||||
if (node->md) {
|
||||
if (!tor_addr_is_null(&node->md->ipv6_addr))
|
||||
address_set_add(the_nodelist->node_addrs, &node->md->ipv6_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return true if <b>addr</b> is the address of some node in the nodelist.
|
||||
* If not, probably return false. */
|
||||
int
|
||||
nodelist_probably_contains_address(const tor_addr_t *addr)
|
||||
{
|
||||
if (BUG(!addr))
|
||||
return 0;
|
||||
|
||||
if (!the_nodelist || !the_nodelist->node_addrs)
|
||||
return 0;
|
||||
|
||||
return address_set_probably_contains(the_nodelist->node_addrs, addr);
|
||||
}
|
||||
|
||||
/** Add <b>ri</b> to an appropriate node in the nodelist. If we replace an
|
||||
* old routerinfo, and <b>ri_old_out</b> is not NULL, set *<b>ri_old_out</b>
|
||||
* to the previous routerinfo.
|
||||
@ -188,6 +236,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out)
|
||||
dirserv_set_node_flags_from_authoritative_status(node, status);
|
||||
}
|
||||
|
||||
node_add_to_address_set(node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@ -219,9 +269,23 @@ nodelist_add_microdesc(microdesc_t *md)
|
||||
node->md = md;
|
||||
md->held_by_nodes++;
|
||||
}
|
||||
|
||||
node_add_to_address_set(node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/* Default value. */
|
||||
#define ESTIMATED_ADDRESS_PER_NODE 2
|
||||
|
||||
/* Return the estimated number of address per node_t. This is used for the
|
||||
* size of the bloom filter in the nodelist (node_addrs). */
|
||||
MOCK_IMPL(int,
|
||||
get_estimated_address_per_node, (void))
|
||||
{
|
||||
return ESTIMATED_ADDRESS_PER_NODE;
|
||||
}
|
||||
|
||||
/** Tell the nodelist that the current usable consensus is <b>ns</b>.
|
||||
* This makes the nodelist change all of the routerstatus entries for
|
||||
* the nodes, drop nodes that no longer have enough info to get used,
|
||||
@ -240,6 +304,12 @@ nodelist_set_consensus(networkstatus_t *ns)
|
||||
SMARTLIST_FOREACH(the_nodelist->nodes, node_t *, node,
|
||||
node->rs = NULL);
|
||||
|
||||
/* Conservatively estimate that every node will have 2 addresses. */
|
||||
const int estimated_addresses = smartlist_len(ns->routerstatus_list) *
|
||||
get_estimated_address_per_node();
|
||||
address_set_free(the_nodelist->node_addrs);
|
||||
the_nodelist->node_addrs = address_set_new(estimated_addresses);
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) {
|
||||
node_t *node = node_get_or_create(rs->identity_digest);
|
||||
node->rs = rs;
|
||||
@ -278,6 +348,11 @@ nodelist_set_consensus(networkstatus_t *ns)
|
||||
|
||||
nodelist_purge();
|
||||
|
||||
/* Now add all the nodes we have to the address set. */
|
||||
SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) {
|
||||
node_add_to_address_set(node);
|
||||
} SMARTLIST_FOREACH_END(node);
|
||||
|
||||
if (! authdir) {
|
||||
SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) {
|
||||
/* We have no routerstatus for this router. Clear flags so we can skip
|
||||
@ -430,6 +505,9 @@ nodelist_free_all(void)
|
||||
|
||||
smartlist_free(the_nodelist->nodes);
|
||||
|
||||
address_set_free(the_nodelist->node_addrs);
|
||||
the_nodelist->node_addrs = NULL;
|
||||
|
||||
tor_free(the_nodelist);
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ const node_t *node_get_by_hex_id(const char *identity_digest);
|
||||
node_t *nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out);
|
||||
node_t *nodelist_add_microdesc(microdesc_t *md);
|
||||
void nodelist_set_consensus(networkstatus_t *ns);
|
||||
int nodelist_probably_contains_address(const tor_addr_t *addr);
|
||||
|
||||
void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md);
|
||||
void nodelist_remove_routerinfo(routerinfo_t *ri);
|
||||
@ -124,5 +125,7 @@ void router_dir_info_changed(void);
|
||||
const char *get_dir_info_status_string(void);
|
||||
int count_loading_descriptors_progress(void);
|
||||
|
||||
MOCK_DECL(int, get_estimated_address_per_node, (void));
|
||||
|
||||
#endif
|
||||
|
||||
|
33
src/or/or.h
33
src/or/or.h
@ -1500,6 +1500,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." */
|
||||
@ -4510,6 +4514,35 @@ typedef struct {
|
||||
|
||||
/** If 1, we skip all OOS checks. */
|
||||
int DisableOOSCheck;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Persistent state for an onion router, as saved to disk. */
|
||||
|
@ -2469,24 +2469,34 @@ static time_t last_time_under_memory_pressure = 0;
|
||||
STATIC int
|
||||
cell_queues_check_size(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
size_t alloc = cell_queues_get_total_allocation();
|
||||
alloc += buf_get_total_allocation();
|
||||
alloc += tor_zlib_get_total_allocation();
|
||||
const size_t rend_cache_total = rend_cache_get_total_allocation();
|
||||
alloc += rend_cache_total;
|
||||
const size_t geoip_client_cache_total =
|
||||
geoip_client_cache_total_allocation();
|
||||
alloc += geoip_client_cache_total;
|
||||
if (alloc >= get_options()->MaxMemInQueues_low_threshold) {
|
||||
last_time_under_memory_pressure = approx_time();
|
||||
if (alloc >= get_options()->MaxMemInQueues) {
|
||||
/* If we're spending over 20% of the memory limit on hidden service
|
||||
* descriptors, free them until we're down to 10%.
|
||||
*/
|
||||
* descriptors, free them until we're down to 10%. Do the same for geoip
|
||||
* client cache. */
|
||||
if (rend_cache_total > get_options()->MaxMemInQueues / 5) {
|
||||
const size_t bytes_to_remove =
|
||||
rend_cache_total - (size_t)(get_options()->MaxMemInQueues / 10);
|
||||
rend_cache_clean_v2_descs_as_dir(time(NULL), bytes_to_remove);
|
||||
rend_cache_clean_v2_descs_as_dir(now, bytes_to_remove);
|
||||
alloc -= rend_cache_total;
|
||||
alloc += rend_cache_get_total_allocation();
|
||||
}
|
||||
if (geoip_client_cache_total > get_options()->MaxMemInQueues / 5) {
|
||||
const size_t bytes_to_remove =
|
||||
geoip_client_cache_total -
|
||||
(size_t)(get_options()->MaxMemInQueues / 10);
|
||||
alloc -= geoip_client_cache_handle_oom(now, bytes_to_remove);
|
||||
}
|
||||
circuits_handle_oom(alloc);
|
||||
return 1;
|
||||
}
|
||||
|
@ -8,9 +8,11 @@
|
||||
**/
|
||||
|
||||
#include "or.h"
|
||||
#include "channel.h"
|
||||
#include "circuitlist.h"
|
||||
#include "circuituse.h"
|
||||
#include "config.h"
|
||||
#include "dos.h"
|
||||
#include "relay.h"
|
||||
#include "rendmid.h"
|
||||
#include "rephist.h"
|
||||
@ -246,6 +248,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");
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "hibernate.h"
|
||||
#include "rephist.h"
|
||||
#include "statefile.h"
|
||||
#include "dos.h"
|
||||
|
||||
static void log_accounting(const time_t now, const or_options_t *options);
|
||||
#include "geoip.h"
|
||||
@ -145,6 +146,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);
|
||||
|
@ -72,6 +72,7 @@ src_test_test_SOURCES = \
|
||||
src/test/test_accounting.c \
|
||||
src/test/test_addr.c \
|
||||
src/test/test_address.c \
|
||||
src/test/test_address_set.c \
|
||||
src/test/test_buffers.c \
|
||||
src/test/test_cell_formats.c \
|
||||
src/test/test_cell_queue.c \
|
||||
@ -87,6 +88,7 @@ src_test_test_SOURCES = \
|
||||
src/test/test_controller.c \
|
||||
src/test/test_controller_events.c \
|
||||
src/test/test_crypto.c \
|
||||
src/test/test_dos.c \
|
||||
src/test/test_data.c \
|
||||
src/test/test_dir.c \
|
||||
src/test/test_dir_common.c \
|
||||
|
@ -912,6 +912,24 @@ test_geoip(void *arg)
|
||||
tt_str_op(entry_stats_2,OP_EQ, s);
|
||||
tor_free(s);
|
||||
|
||||
/* Test the OOM handler. Add a client, run the OOM. */
|
||||
geoip_entry_stats_init(now);
|
||||
SET_TEST_ADDRESS(100);
|
||||
geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL,
|
||||
now - (12 * 60 * 60));
|
||||
/* We've seen this 12 hours ago. Run the OOM, it should clean the entry
|
||||
* because it is above the minimum cutoff of 4 hours. */
|
||||
size_t bytes_removed = geoip_client_cache_handle_oom(now, 1000);
|
||||
tt_size_op(bytes_removed, OP_GT, 0);
|
||||
|
||||
/* Do it again but this time with an entry with a lower cutoff. */
|
||||
geoip_entry_stats_init(now);
|
||||
SET_TEST_ADDRESS(100);
|
||||
geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL,
|
||||
now - (3 * 60 * 60));
|
||||
bytes_removed = geoip_client_cache_handle_oom(now, 1000);
|
||||
tt_size_op(bytes_removed, OP_EQ, 0);
|
||||
|
||||
/* Stop collecting entry statistics. */
|
||||
geoip_entry_stats_term();
|
||||
get_options_mutable()->EntryStatistics = 0;
|
||||
@ -1182,6 +1200,7 @@ struct testgroup_t testgroups[] = {
|
||||
{ "accounting/", accounting_tests },
|
||||
{ "addr/", addr_tests },
|
||||
{ "address/", address_tests },
|
||||
{ "address_set/", address_set_tests },
|
||||
{ "buffer/", buffer_tests },
|
||||
{ "cellfmt/", cell_format_tests },
|
||||
{ "cellqueue/", cell_queue_tests },
|
||||
@ -1197,6 +1216,7 @@ struct testgroup_t testgroups[] = {
|
||||
{ "control/", controller_tests },
|
||||
{ "control/event/", controller_event_tests },
|
||||
{ "crypto/", crypto_tests },
|
||||
{ "dos/", dos_tests },
|
||||
{ "dir/", dir_tests },
|
||||
{ "dir_handle_get/", dir_handle_get_tests },
|
||||
{ "dir/md/", microdesc_tests },
|
||||
|
@ -175,6 +175,7 @@ extern const struct testcase_setup_t ed25519_test_setup;
|
||||
extern struct testcase_t accounting_tests[];
|
||||
extern struct testcase_t addr_tests[];
|
||||
extern struct testcase_t address_tests[];
|
||||
extern struct testcase_t address_set_tests[];
|
||||
extern struct testcase_t buffer_tests[];
|
||||
extern struct testcase_t cell_format_tests[];
|
||||
extern struct testcase_t cell_queue_tests[];
|
||||
@ -190,6 +191,7 @@ extern struct testcase_t container_tests[];
|
||||
extern struct testcase_t controller_tests[];
|
||||
extern struct testcase_t controller_event_tests[];
|
||||
extern struct testcase_t crypto_tests[];
|
||||
extern struct testcase_t dos_tests[];
|
||||
extern struct testcase_t dir_tests[];
|
||||
extern struct testcase_t dir_handle_get_tests[];
|
||||
extern struct testcase_t entryconn_tests[];
|
||||
|
174
src/test/test_address_set.c
Normal file
174
src/test/test_address_set.c
Normal file
@ -0,0 +1,174 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
#include "or.h"
|
||||
#include "address_set.h"
|
||||
#include "microdesc.h"
|
||||
#include "networkstatus.h"
|
||||
#include "nodelist.h"
|
||||
#include "routerlist.h"
|
||||
#include "torcert.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
static networkstatus_t *dummy_ns = NULL;
|
||||
static networkstatus_t *
|
||||
mock_networkstatus_get_latest_consensus(void)
|
||||
{
|
||||
return dummy_ns;
|
||||
}
|
||||
|
||||
static networkstatus_t *
|
||||
mock_networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f)
|
||||
{
|
||||
tor_assert(f == FLAV_MICRODESC);
|
||||
return dummy_ns;
|
||||
}
|
||||
|
||||
/* Number of address a single node_t can have. Default to the production
|
||||
* value. This is to control the size of the bloom filter. */
|
||||
static int addr_per_node = 2;
|
||||
static int
|
||||
mock_get_estimated_address_per_node(void)
|
||||
{
|
||||
return addr_per_node;
|
||||
}
|
||||
|
||||
static void
|
||||
test_contains(void *arg)
|
||||
{
|
||||
int ret;
|
||||
address_set_t *set = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Setup an IPv4 and IPv6 addresses. */
|
||||
tor_addr_t addr_v6;
|
||||
tor_addr_parse(&addr_v6, "1:2:3:4::");
|
||||
tor_addr_t addr_v4;
|
||||
tor_addr_parse(&addr_v4, "42.42.42.42");
|
||||
uint32_t ipv4h = tor_addr_to_ipv4h(&addr_v4);
|
||||
|
||||
/* Make it very big so the chance of failing the contain test will be
|
||||
* extremely rare. */
|
||||
set = address_set_new(1024);
|
||||
tt_assert(set);
|
||||
|
||||
/* Add and lookup IPv6. */
|
||||
address_set_add(set, &addr_v6);
|
||||
ret = address_set_probably_contains(set, &addr_v6);
|
||||
tt_int_op(ret, OP_EQ, 1);
|
||||
|
||||
/* Add and lookup IPv4. */
|
||||
address_set_add_ipv4h(set, ipv4h);
|
||||
ret = address_set_probably_contains(set, &addr_v4);
|
||||
tt_int_op(ret, OP_EQ, 1);
|
||||
|
||||
/* Try a lookup of rubbish. */
|
||||
tor_addr_t dummy_addr;
|
||||
memset(&dummy_addr, 'A', sizeof(dummy_addr));
|
||||
dummy_addr.family = AF_INET;
|
||||
ret = address_set_probably_contains(set, &dummy_addr);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
dummy_addr.family = AF_INET6;
|
||||
ret = address_set_probably_contains(set, &dummy_addr);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
done:
|
||||
address_set_free(set);
|
||||
}
|
||||
|
||||
static void
|
||||
test_nodelist(void *arg)
|
||||
{
|
||||
int ret;
|
||||
routerstatus_t *rs = NULL; microdesc_t *md = NULL; routerinfo_t *ri = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
MOCK(networkstatus_get_latest_consensus,
|
||||
mock_networkstatus_get_latest_consensus);
|
||||
MOCK(networkstatus_get_latest_consensus_by_flavor,
|
||||
mock_networkstatus_get_latest_consensus_by_flavor);
|
||||
MOCK(get_estimated_address_per_node,
|
||||
mock_get_estimated_address_per_node);
|
||||
|
||||
dummy_ns = tor_malloc_zero(sizeof(*dummy_ns));
|
||||
dummy_ns->flavor = FLAV_MICRODESC;
|
||||
dummy_ns->routerstatus_list = smartlist_new();
|
||||
|
||||
tor_addr_t addr_v4, addr_v6, dummy_addr;
|
||||
tor_addr_parse(&addr_v4, "42.42.42.42");
|
||||
uint32_t ipv4h = tor_addr_to_ipv4h(&addr_v4);
|
||||
tor_addr_parse(&addr_v6, "1:2:3:4::");
|
||||
memset(&dummy_addr, 'A', sizeof(dummy_addr));
|
||||
|
||||
/* This will make the nodelist bloom filter very large
|
||||
* (the_nodelist->node_addrs) so we will fail the contain test rarely. */
|
||||
addr_per_node = 1024;
|
||||
|
||||
/* No node no nothing. The lookups should be empty. */
|
||||
nodelist_set_consensus(dummy_ns);
|
||||
|
||||
/* The address set should be empty. */
|
||||
ret = nodelist_probably_contains_address(&addr_v4);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
ret = nodelist_probably_contains_address(&addr_v6);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
dummy_addr.family = AF_INET;
|
||||
ret = nodelist_probably_contains_address(&dummy_addr);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
dummy_addr.family = AF_INET6;
|
||||
ret = nodelist_probably_contains_address(&dummy_addr);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
md = tor_malloc_zero(sizeof(*md));
|
||||
ri = tor_malloc_zero(sizeof(*ri));
|
||||
rs = tor_malloc_zero(sizeof(*rs));
|
||||
crypto_rand(rs->identity_digest, sizeof(rs->identity_digest));
|
||||
crypto_rand(md->digest, sizeof(md->digest));
|
||||
memcpy(rs->descriptor_digest, md->digest, DIGEST256_LEN);
|
||||
|
||||
/* Setup the rs, ri and md addresses. */
|
||||
rs->addr = ipv4h;
|
||||
tor_addr_parse(&rs->ipv6_addr, "1:2:3:4::");
|
||||
ri->addr = ipv4h;
|
||||
tor_addr_parse(&ri->ipv6_addr, "1:2:3:4::");
|
||||
tor_addr_parse(&md->ipv6_addr, "1:2:3:4::");
|
||||
|
||||
/* Add the rs to the consensus becoming a node_t. */
|
||||
smartlist_add(dummy_ns->routerstatus_list, rs);
|
||||
nodelist_set_consensus(dummy_ns);
|
||||
|
||||
/* At this point, the address set should be initialized in the nodelist and
|
||||
* we should be able to lookup. */
|
||||
ret = nodelist_probably_contains_address(&addr_v4);
|
||||
tt_int_op(ret, OP_EQ, 1);
|
||||
ret = nodelist_probably_contains_address(&addr_v6);
|
||||
tt_int_op(ret, OP_EQ, 1);
|
||||
/* Lookup unknown address. */
|
||||
dummy_addr.family = AF_INET;
|
||||
ret = nodelist_probably_contains_address(&dummy_addr);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
dummy_addr.family = AF_INET6;
|
||||
ret = nodelist_probably_contains_address(&dummy_addr);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
done:
|
||||
routerstatus_free(rs); routerinfo_free(ri); microdesc_free(md);
|
||||
smartlist_clear(dummy_ns->routerstatus_list);
|
||||
networkstatus_vote_free(dummy_ns);
|
||||
UNMOCK(networkstatus_get_latest_consensus);
|
||||
UNMOCK(networkstatus_get_latest_consensus_by_flavor);
|
||||
UNMOCK(get_estimated_address_per_node);
|
||||
}
|
||||
|
||||
struct testcase_t address_set_tests[] = {
|
||||
{ "contains", test_contains, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "nodelist", test_nodelist, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
497
src/test/test_dos.c
Normal file
497
src/test/test_dos.c
Normal file
@ -0,0 +1,497 @@
|
||||
/* 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 "microdesc.h"
|
||||
#include "networkstatus.h"
|
||||
#include "nodelist.h"
|
||||
#include "routerlist.h"
|
||||
#include "test.h"
|
||||
#include "log_test_helpers.h"
|
||||
|
||||
static networkstatus_t *dummy_ns = NULL;
|
||||
static networkstatus_t *
|
||||
mock_networkstatus_get_latest_consensus(void)
|
||||
{
|
||||
return dummy_ns;
|
||||
}
|
||||
|
||||
static networkstatus_t *
|
||||
mock_networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f)
|
||||
{
|
||||
tor_assert(f == FLAV_MICRODESC);
|
||||
return dummy_ns;
|
||||
}
|
||||
|
||||
/* Number of address a single node_t can have. Default to the production
|
||||
* value. This is to control the size of the bloom filter. */
|
||||
static int addr_per_node = 2;
|
||||
static int
|
||||
mock_get_estimated_address_per_node(void)
|
||||
{
|
||||
return addr_per_node;
|
||||
}
|
||||
|
||||
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);
|
||||
uint64_t circ_rate = get_circuit_rate_per_second();
|
||||
/* Check that the circuit rate is a positive number and smaller than the max
|
||||
* circuit count */
|
||||
tt_u64_op(circ_rate, OP_GT, 1);
|
||||
tt_u64_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);
|
||||
|
||||
/* 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 use a very large time, and check that the token bucket does not have
|
||||
* more than max_circs allowance, even tho we let it simmer for so long. */
|
||||
now = INT32_MAX; /* 2038? */
|
||||
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);
|
||||
|
||||
/* 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 use a very small time, and check that the token bucket has exactly
|
||||
* the max_circs allowance, because backward clock jumps are rare. */
|
||||
now = INT32_MIN; /* 19?? */
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Progress time forward one sec again, 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 use a very large time (again), and check that the token bucket does
|
||||
* not have more than max_circs allowance, even tho we let it simmer for so
|
||||
* long. */
|
||||
now = INT32_MAX; /* 2038? */
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* This code resets the time to zero with 32-bit time_t, which triggers the
|
||||
* code that initialises the bucket. */
|
||||
#if SIZEOF_TIME_T == 8
|
||||
/* Now use a very very small time, and check that the token bucket has
|
||||
* exactly the max_circs allowance, because backward clock jumps are rare.
|
||||
*/
|
||||
now = (time_t)INT64_MIN; /* ???? */
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Progress time forward one sec again, 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 use a very very small time, and check that the token bucket has
|
||||
* exactly the max_circs allowance, because backward clock jumps are rare.
|
||||
*/
|
||||
now = (time_t)INT64_MIN; /* ???? */
|
||||
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);
|
||||
|
||||
/* 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 use a very very large time, and check that the token bucket does not
|
||||
* have more than max_circs allowance, even tho we let it simmer for so
|
||||
* long. */
|
||||
now = (time_t)INT64_MAX; /* ???? */
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
#endif
|
||||
|
||||
done:
|
||||
tor_free(chan);
|
||||
dos_free_all();
|
||||
}
|
||||
|
||||
/* Test if we avoid counting a known relay. */
|
||||
static void
|
||||
test_known_relay(void *arg)
|
||||
{
|
||||
clientmap_entry_t *entry = NULL;
|
||||
routerstatus_t *rs = NULL; microdesc_t *md = NULL; routerinfo_t *ri = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
MOCK(networkstatus_get_latest_consensus,
|
||||
mock_networkstatus_get_latest_consensus);
|
||||
MOCK(networkstatus_get_latest_consensus_by_flavor,
|
||||
mock_networkstatus_get_latest_consensus_by_flavor);
|
||||
MOCK(get_estimated_address_per_node,
|
||||
mock_get_estimated_address_per_node);
|
||||
MOCK(get_param_cc_enabled, mock_enable_dos_protection);
|
||||
|
||||
dos_init();
|
||||
|
||||
dummy_ns = tor_malloc_zero(sizeof(*dummy_ns));
|
||||
dummy_ns->flavor = FLAV_MICRODESC;
|
||||
dummy_ns->routerstatus_list = smartlist_new();
|
||||
|
||||
/* Setup an OR conn so we can pass it to the DoS subsystem. */
|
||||
or_connection_t or_conn;
|
||||
tor_addr_parse(&or_conn.real_addr, "42.42.42.42");
|
||||
|
||||
rs = tor_malloc_zero(sizeof(*rs));
|
||||
rs->addr = tor_addr_to_ipv4h(&or_conn.real_addr);
|
||||
crypto_rand(rs->identity_digest, sizeof(rs->identity_digest));
|
||||
smartlist_add(dummy_ns->routerstatus_list, rs);
|
||||
|
||||
/* This will make the nodelist bloom filter very large
|
||||
* (the_nodelist->node_addrs) so we will fail the contain test rarely. */
|
||||
addr_per_node = 1024;
|
||||
nodelist_set_consensus(dummy_ns);
|
||||
|
||||
/* We have now a node in our list so we'll make sure we don't count it as a
|
||||
* client connection. */
|
||||
geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &or_conn.real_addr, NULL, 0);
|
||||
/* Suppose we have 5 connections in rapid succession, the counter should
|
||||
* always be 0 because we should ignore this. */
|
||||
dos_new_client_conn(&or_conn);
|
||||
dos_new_client_conn(&or_conn);
|
||||
dos_new_client_conn(&or_conn);
|
||||
dos_new_client_conn(&or_conn);
|
||||
dos_new_client_conn(&or_conn);
|
||||
entry = geoip_lookup_client(&or_conn.real_addr, NULL, GEOIP_CLIENT_CONNECT);
|
||||
tt_assert(entry);
|
||||
/* We should have a count of 0. */
|
||||
tt_uint_op(entry->dos_stats.concurrent_count, OP_EQ, 0);
|
||||
|
||||
/* To make sure that his is working properly, make a unknown client
|
||||
* connection and see if we do get it. */
|
||||
tor_addr_parse(&or_conn.real_addr, "42.42.42.43");
|
||||
geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &or_conn.real_addr, NULL, 0);
|
||||
dos_new_client_conn(&or_conn);
|
||||
dos_new_client_conn(&or_conn);
|
||||
entry = geoip_lookup_client(&or_conn.real_addr, NULL, GEOIP_CLIENT_CONNECT);
|
||||
tt_assert(entry);
|
||||
/* We should have a count of 2. */
|
||||
tt_uint_op(entry->dos_stats.concurrent_count, OP_EQ, 2);
|
||||
|
||||
done:
|
||||
routerstatus_free(rs); routerinfo_free(ri); microdesc_free(md);
|
||||
smartlist_clear(dummy_ns->routerstatus_list);
|
||||
networkstatus_vote_free(dummy_ns);
|
||||
dos_free_all();
|
||||
UNMOCK(networkstatus_get_latest_consensus);
|
||||
UNMOCK(networkstatus_get_latest_consensus_by_flavor);
|
||||
UNMOCK(get_estimated_address_per_node);
|
||||
UNMOCK(get_param_cc_enabled);
|
||||
}
|
||||
|
||||
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 },
|
||||
{ "known_relay" , test_known_relay, TT_FORK,
|
||||
NULL, NULL },
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user