From b0e92634d85a3bf7612a6ce0339b96e4aad1e0bb Mon Sep 17 00:00:00 2001 From: Mike Perry Date: Tue, 6 Sep 2016 11:35:53 -0700 Subject: Netflow record collapsing defense. This defense will cause Cisco, Juniper, Fortinet, and other routers operating in the default configuration to collapse netflow records that would normally be split due to the 15 second flow idle timeout. Collapsing these records should greatly reduce the utility of default netflow data for correlation attacks, since all client-side records should become 30 minute chunks of total bytes sent/received, rather than creating multiple separate records for every webpage load/ssh command interaction/XMPP chat/whatever else happens to be inactive for more than 15 seconds. The defense adds consensus parameters to govern the range of timeout values for sending padding packets, as well as for keeping connections open. The defense only sends padding when connections are otherwise inactive, and it does not pad connections used solely for directory traffic at all. By default it also doesn't pad inter-relay connections. Statistics on the total padding in the last 24 hours are exported to the extra-info descriptors. --- src/or/networkstatus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/or/networkstatus.c') diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 508cf6c5b6..76b968a8c8 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -72,11 +72,11 @@ static strmap_t *unnamed_server_map = NULL; /** Most recently received and validated v3 "ns"-flavored consensus network * status. */ -static networkstatus_t *current_ns_consensus = NULL; +STATIC networkstatus_t *current_ns_consensus = NULL; /** Most recently received and validated v3 "microdec"-flavored consensus * network status. */ -static networkstatus_t *current_md_consensus = NULL; +STATIC networkstatus_t *current_md_consensus = NULL; /** A v3 consensus networkstatus that we've received, but which we don't * have enough certificates to be happy about. */ -- cgit v1.2.3-54-g00ecf From 687a85950afc25010c80cd14539728b3a7ae5675 Mon Sep 17 00:00:00 2001 From: Mike Perry Date: Thu, 22 Sep 2016 17:52:25 -0400 Subject: Cache netflow-related consensus parameters. Checking all of these parameter lists for every single connection every second seems like it could be an expensive waste. Updating globally cached versions when there is a new consensus will still allow us to apply consensus parameter updates to all existing connections immediately. --- src/or/channelpadding.c | 148 +++++++++++++++++++++++++++-------------- src/or/channelpadding.h | 1 + src/or/main.c | 4 ++ src/or/networkstatus.c | 2 + src/test/test_channelpadding.c | 11 +++ src/test/testing_common.c | 3 + 6 files changed, 118 insertions(+), 51 deletions(-) (limited to 'src/or/networkstatus.c') diff --git a/src/or/channelpadding.c b/src/or/channelpadding.c index 3156051c3b..a84994afc8 100644 --- a/src/or/channelpadding.c +++ b/src/or/channelpadding.c @@ -29,6 +29,87 @@ STATIC int64_t channelpadding_compute_time_until_pad_for_netflow(channel_t *); /** The total number of pending channelpadding timers */ static uint64_t total_timers_pending; +/** These are cached consensus parameters for netflow */ +/** The timeout lower bound that is allowed before sending padding */ +static int consensus_nf_ito_low; +/** The timeout upper bound that is allowed before sending padding */ +static int consensus_nf_ito_high; +/** The timeout lower bound that is allowed before sending reduced padding */ +static int consensus_nf_ito_low_reduced; +/** The timeout upper bound that is allowed before sending reduced padding */ +static int consensus_nf_ito_high_reduced; +/** The connection timeout between relays */ +static int consensus_nf_conntimeout_relays; +/** The connection timeout for client connections */ +static int consensus_nf_conntimeout_clients; +/** Should we pad before circuits are actually used for client data? */ +static int consensus_nf_pad_before_usage; +/** Should we pad relay-to-relay connections? */ +static int consensus_nf_pad_relays; + +/** + * This function is called to update cached consensus parameters every time + * there is a consensus update. This allows us to move the consensus param + * search off of the critical path, so it does not need to be evaluated + * for every single connection, every second. + */ +void +channelpadding_new_consensus_params(networkstatus_t *ns) +{ +#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW 1500 +#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH 9500 +#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN 0 +#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX 60000 + consensus_nf_ito_low = networkstatus_get_param(ns, "nf_ito_low", + DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW, + DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN, + DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX); + consensus_nf_ito_high = networkstatus_get_param(NULL, "nf_ito_high", + DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH, + consensus_nf_ito_low, + DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX); + +#define DFLT_NETFLOW_REDUCED_KEEPALIVE_LOW 9000 +#define DFLT_NETFLOW_REDUCED_KEEPALIVE_HIGH 14000 +#define DFLT_NETFLOW_REDUCED_KEEPALIVE_MIN 0 +#define DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX 60000 + consensus_nf_ito_low_reduced = + networkstatus_get_param(NULL, "nf_ito_low_reduced", + DFLT_NETFLOW_REDUCED_KEEPALIVE_LOW, + DFLT_NETFLOW_REDUCED_KEEPALIVE_MIN, + DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX); + + consensus_nf_ito_high_reduced = + networkstatus_get_param(NULL, "nf_ito_high_reduced", + DFLT_NETFLOW_REDUCED_KEEPALIVE_HIGH, + consensus_nf_ito_low_reduced, + DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX); + +#define CONNTIMEOUT_RELAYS_DFLT (60*60) // 1 hour +#define CONNTIMEOUT_RELAYS_MIN 60 +#define CONNTIMEOUT_RELAYS_MAX (7*24*60*60) // 1 week + consensus_nf_conntimeout_relays = + networkstatus_get_param(NULL, "nf_conntimeout_relays", + CONNTIMEOUT_RELAYS_DFLT, + CONNTIMEOUT_RELAYS_MIN, + CONNTIMEOUT_RELAYS_MAX); + +#define CIRCTIMEOUT_CLIENTS_DFLT (30*60) // 30 minutes +#define CIRCTIMEOUT_CLIENTS_MIN 60 +#define CIRCTIMEOUT_CLIENTS_MAX (24*60*60) // 24 hours + consensus_nf_conntimeout_clients = + networkstatus_get_param(NULL, "nf_conntimeout_clients", + CIRCTIMEOUT_CLIENTS_DFLT, + CIRCTIMEOUT_CLIENTS_MIN, + CIRCTIMEOUT_CLIENTS_MAX); + + consensus_nf_pad_before_usage = + networkstatus_get_param(NULL, "nf_pad_before_usage", 1, 0, 1); + + consensus_nf_pad_relays = + networkstatus_get_param(NULL, "nf_pad_relays", 0, 0, 1); +} + /** * Get a random netflow inactive timeout keepalive period in milliseconds, * the range for which is determined by consensus parameters, negotiation, @@ -47,21 +128,11 @@ static uint64_t total_timers_pending; * Returns the next timeout period (in milliseconds) after which we should * send a padding packet, or 0 if padding is disabled. */ -#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW 1500 -#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH 9500 -#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN 0 -#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX 60000 STATIC int channelpadding_get_netflow_inactive_timeout_ms(const channel_t *chan) { - int low_timeout = networkstatus_get_param(NULL, "nf_ito_low", - DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW, - DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN, - DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX); - int high_timeout = networkstatus_get_param(NULL, "nf_ito_high", - DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH, - low_timeout, - DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX); + int low_timeout = consensus_nf_ito_low; + int high_timeout = consensus_nf_ito_high; int X1, X2; if (low_timeout == 0 && low_timeout == high_timeout) @@ -162,12 +233,8 @@ channelpadding_update_padding_for_channel(channel_t *chan, /* Min must not be lower than the current consensus parameter nf_ito_low. */ - chan->padding_timeout_low_ms = MAX(networkstatus_get_param(NULL, - "nf_ito_low", - DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW, - DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN, - DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX), - pad_vars->ito_low_ms); + chan->padding_timeout_low_ms = MAX(consensus_nf_ito_low, + pad_vars->ito_low_ms); /* Max must not be lower than ito_low_ms */ chan->padding_timeout_high_ms = MAX(chan->padding_timeout_low_ms, @@ -399,6 +466,12 @@ channelpadding_compute_time_until_pad_for_netflow(channel_t *chan) uint64_t long_now = monotime_coarse_absolute_msec(); if (!chan->next_padding_time_ms) { + /* If the below line or crypto_rand_int() shows up on a profile, + * we can avoid getting a timeout until we're at least nt_ito_lo + * from a timeout window. That will prevent us from setting timers + * on connections that were active up to 1.5 seconds ago. + * Idle connections should only call this once every 5.5s on average + * though, so that might be a micro-optimization for little gain. */ int64_t padding_timeout = channelpadding_get_netflow_inactive_timeout_ms(chan); @@ -475,14 +548,7 @@ channelpadding_get_channel_idle_timeout(const channel_t *chan, + crypto_rand_int(CONNTIMEOUT_CLIENTS_BASE/2); } else { // Canonical relay-to-relay channels // 45..75min or consensus +/- 25% -#define CONNTIMEOUT_RELAYS_DFLT (60*60) // 1 hour -#define CONNTIMEOUT_RELAYS_MIN 60 -#define CONNTIMEOUT_RELAYS_MAX (7*24*60*60) // 1 week - timeout = networkstatus_get_param(NULL, "nf_conntimeout_relays", - CONNTIMEOUT_RELAYS_DFLT, - CONNTIMEOUT_RELAYS_MIN, - CONNTIMEOUT_RELAYS_MAX); - + timeout = consensus_nf_conntimeout_relays; timeout = 3*timeout/4 + crypto_rand_int(timeout/2); } @@ -522,13 +588,7 @@ channelpadding_get_circuits_available_timeout(void) int timeout = options->CircuitsAvailableTimeout; if (!timeout) { -#define CIRCTIMEOUT_CLIENTS_DFLT (30*60) // 30 minutes -#define CIRCTIMEOUT_CLIENTS_MIN 60 -#define CIRCTIMEOUT_CLIENTS_MAX (24*60*60) // 24 hours - timeout = networkstatus_get_param(NULL, "nf_conntimeout_clients", - CIRCTIMEOUT_CLIENTS_DFLT, - CIRCTIMEOUT_CLIENTS_MIN, - CIRCTIMEOUT_CLIENTS_MAX); + timeout = consensus_nf_conntimeout_clients; /* If ReducedConnectionPadding is set, we want to halve the duration of * the channel idle timeout, since reducing the additional time that @@ -579,21 +639,8 @@ channelpadding_reduce_padding_on_channel(channel_t *chan) channelpadding_send_disable_command(chan); } -#define DFLT_NETFLOW_REDUCED_KEEPALIVE_LOW 9000 -#define DFLT_NETFLOW_REDUCED_KEEPALIVE_HIGH 14000 -#define DFLT_NETFLOW_REDUCED_KEEPALIVE_MIN 0 -#define DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX 60000 - chan->padding_timeout_low_ms = - networkstatus_get_param(NULL, "nf_ito_low_reduced", - DFLT_NETFLOW_REDUCED_KEEPALIVE_LOW, - DFLT_NETFLOW_REDUCED_KEEPALIVE_MIN, - DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX); - - chan->padding_timeout_high_ms = - networkstatus_get_param(NULL, "nf_ito_high_reduced", - DFLT_NETFLOW_REDUCED_KEEPALIVE_HIGH, - chan->padding_timeout_low_ms, - DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX); + chan->padding_timeout_low_ms = consensus_nf_ito_low_reduced; + chan->padding_timeout_high_ms = consensus_nf_ito_high_reduced; log_fn(LOG_INFO,LD_OR, "Reduced padding on channel "U64_FORMAT": lo=%d, hi=%d", @@ -621,7 +668,7 @@ channelpadding_decide_to_pad_channel(channel_t *chan) return CHANNELPADDING_WONTPAD; if (chan->channel_usage == CHANNEL_USED_FOR_FULL_CIRCS) { - if (!networkstatus_get_param(NULL, "nf_pad_before_usage", 1, 0, 1)) + if (!consensus_nf_pad_before_usage) return CHANNELPADDING_WONTPAD; } else if (chan->channel_usage != CHANNEL_USED_FOR_USER_TRAFFIC) { return CHANNELPADDING_WONTPAD; @@ -649,8 +696,7 @@ channelpadding_decide_to_pad_channel(channel_t *chan) /* If nf_pad_relays=1 is set in the consensus, we pad * on *all* idle connections, relay-relay or relay-client. * Otherwise pad only for client+bridge cons */ - if (is_client_channel || - networkstatus_get_param(NULL, "nf_pad_relays", 0, 0, 1)) { + if (is_client_channel || consensus_nf_pad_relays) { int64_t pad_time_ms = channelpadding_compute_time_until_pad_for_netflow(chan); diff --git a/src/or/channelpadding.h b/src/or/channelpadding.h index 07af7a6b46..e08f7a2045 100644 --- a/src/or/channelpadding.h +++ b/src/or/channelpadding.h @@ -33,6 +33,7 @@ int channelpadding_send_enable_command(channel_t *chan, uint16_t low_timeout, int channelpadding_get_circuits_available_timeout(void); unsigned int channelpadding_get_channel_idle_timeout(const channel_t *, int); +void channelpadding_new_consensus_params(networkstatus_t *ns); #endif diff --git a/src/or/main.c b/src/or/main.c index 5bc132a0e3..b729e0b378 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -3033,6 +3033,10 @@ tor_init(int argc, char *argv[]) /* The options are now initialised */ const or_options_t *options = get_options(); + /* Initialize channelpadding parameters to defaults until we get + * a consensus */ + channelpadding_new_consensus_params(NULL); + /* Initialize predicted ports list after loading options */ predicted_ports_init(); diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 76b968a8c8..4107542f56 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -63,6 +63,7 @@ #include "shared_random.h" #include "transports.h" #include "torcert.h" +#include "channelpadding.h" /** Map from lowercase nickname to identity digest of named server, if any. */ static strmap_t *named_server_map = NULL; @@ -1966,6 +1967,7 @@ networkstatus_set_current_consensus(const char *consensus, circuit_build_times_new_consensus_params( get_circuit_build_times_mutable(), c); + channelpadding_new_consensus_params(c); } /* Reset the failure count only if this consensus is actually valid. */ diff --git a/src/test/test_channelpadding.c b/src/test/test_channelpadding.c index de88bb21bd..9b04f55cf0 100644 --- a/src/test/test_channelpadding.c +++ b/src/test/test_channelpadding.c @@ -174,6 +174,7 @@ setup_mock_network(void) = tor_malloc_zero(sizeof(networkstatus_t)); current_md_consensus->net_params = smartlist_new(); current_md_consensus->routerstatus_list = smartlist_new(); + channelpadding_new_consensus_params(current_md_consensus); relay1_relay2 = (channel_t*)new_fake_channeltls(2); relay1_relay2->write_cell = mock_channel_write_cell_relay1; @@ -259,6 +260,7 @@ test_channelpadding_timers(void *arg) monotime_init(); timers_initialize(); + channelpadding_new_consensus_params(NULL); for (int i = 0; i < CHANNELS_TO_TEST; i++) { chans[i] = (channel_t*)new_fake_channeltls(0); @@ -374,6 +376,7 @@ test_channelpadding_consensus(void *arg) = tor_malloc_zero(sizeof(networkstatus_t)); current_md_consensus->net_params = smartlist_new(); current_md_consensus->routerstatus_list = smartlist_new(); + channelpadding_new_consensus_params(current_md_consensus); get_options_mutable()->ORPort_set = 1; @@ -398,6 +401,7 @@ test_channelpadding_consensus(void *arg) smartlist_add(current_md_consensus->net_params, (void*)"nf_ito_high=0"); get_options_mutable()->ConnectionPadding = 1; + channelpadding_new_consensus_params(current_md_consensus); decision = channelpadding_decide_to_pad_channel(chan); tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); @@ -427,6 +431,7 @@ test_channelpadding_consensus(void *arg) (void*)"nf_ito_low=100"); smartlist_add(current_md_consensus->net_params, (void*)"nf_ito_high=200"); + channelpadding_new_consensus_params(current_md_consensus); tried_to_write_cell = 0; decision = channelpadding_decide_to_pad_channel(chan); @@ -449,6 +454,7 @@ test_channelpadding_consensus(void *arg) (void*)"nf_ito_low=1500"); smartlist_add(current_md_consensus->net_params, (void*)"nf_ito_high=4500"); + channelpadding_new_consensus_params(current_md_consensus); channelpadding_send_enable_command(chan, 100, 200); tried_to_write_cell = 0; @@ -474,6 +480,7 @@ test_channelpadding_consensus(void *arg) smartlist_add(current_md_consensus->net_params, (void*)"nf_pad_relays=1"); + channelpadding_new_consensus_params(current_md_consensus); decision = channelpadding_decide_to_pad_channel(chan); tt_int_op(decision, OP_EQ, CHANNELPADDING_PADLATER); @@ -487,6 +494,7 @@ test_channelpadding_consensus(void *arg) /* Test 5: If we disable padding before channel usage, does that work? */ smartlist_add(current_md_consensus->net_params, (void*)"nf_pad_before_usage=0"); + channelpadding_new_consensus_params(current_md_consensus); tried_to_write_cell = 0; decision = channelpadding_decide_to_pad_channel(chan); tt_int_op(decision, OP_EQ, CHANNELPADDING_WONTPAD); @@ -508,6 +516,7 @@ test_channelpadding_consensus(void *arg) options->ORPort_set = 1; smartlist_add(current_md_consensus->net_params, (void*)"nf_conntimeout_relays=600"); + channelpadding_new_consensus_params(current_md_consensus); val = channelpadding_get_channel_idle_timeout(chan, 1); tt_int_op(val, OP_GE, 450); tt_int_op(val, OP_LE, 750); @@ -519,6 +528,7 @@ test_channelpadding_consensus(void *arg) options->ReducedConnectionPadding = 1; smartlist_add(current_md_consensus->net_params, (void*)"nf_conntimeout_clients=600"); + channelpadding_new_consensus_params(current_md_consensus); val = channelpadding_get_circuits_available_timeout(); tt_int_op(val, OP_GE, 600/2); tt_int_op(val, OP_LE, 600*2/2); @@ -714,6 +724,7 @@ test_channelpadding_decide_to_pad_channel(void *arg) monotime_init(); timers_initialize(); + channelpadding_new_consensus_params(NULL); chan = (channel_t*)new_fake_channeltls(0); channel_timestamp_active(chan); diff --git a/src/test/testing_common.c b/src/test/testing_common.c index 43cf0aa508..07fd9e0afd 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -21,6 +21,7 @@ const char tor_git_revision[] = ""; #include "rephist.h" #include "backtrace.h" #include "test.h" +#include "channelpadding.h" #include #ifdef HAVE_FCNTL_H @@ -309,6 +310,8 @@ main(int c, const char **v) init_pregenerated_keys(); + channelpadding_new_consensus_params(NULL); + predicted_ports_init(); atexit(remove_directory); -- cgit v1.2.3-54-g00ecf