Merge remote-tracking branch 'tor-github/pr/1033'

This commit is contained in:
Nick Mathewson 2019-05-17 08:18:20 -04:00
commit b2b779228d
9 changed files with 861 additions and 95 deletions

10
changes/ticket28634 Normal file
View file

@ -0,0 +1,10 @@
o Major features (Circuit padding):
- Onion service clients will now add padding cells to the initial portions
of their INTRODUCE and RENDEZVOUS circuits, to make those circuits'
traffic patterns look more like general purpose Exit traffic. The
overhead for this is 2 extra cells in each direction for RENDEZVOUS
circuits, and 1 extra upstream cell and 10 downstream cells for INTRODUCE
circuits. This will only be enabled if the circuit's middle node supports
this feature, too. (Clients may specify fixed middle nodes with the MiddleNodes
torrc directive, and may force-disable this feature with the CircuitPadding
torrc directive). Closes ticket 28634.

View file

@ -290,3 +290,5 @@ problem function-size /src/tools/tor-resolve.c:build_socks5_resolve_request() 10
problem function-size /src/tools/tor-resolve.c:do_resolve() 175
problem function-size /src/tools/tor-resolve.c:main() 112
problem function-size /src/core/or/circuitpadding.c:circpad_machine_schedule_padding() 107
problem function-size /src/core/or/circuitpadding_machines.c:circpad_machine_relay_hide_intro_circuits() 104
problem function-size /src/core/or/circuitpadding_machines.c:circpad_machine_client_hide_rend_circuits() 112

View file

@ -139,6 +139,7 @@ tor_free_all(int postfork)
dos_free_all();
circuitmux_ewma_free_all();
accounting_free_all();
circpad_free_all();
if (!postfork) {
config_free_all();

View file

@ -37,6 +37,7 @@ LIBTOR_APP_A_SOURCES = \
src/core/or/circuitmux.c \
src/core/or/circuitmux_ewma.c \
src/core/or/circuitpadding.c \
src/core/or/circuitpadding_machines.c \
src/core/or/circuitstats.c \
src/core/or/circuituse.c \
src/core/or/crypt_path.c \
@ -247,6 +248,7 @@ noinst_HEADERS += \
src/core/or/circuitmux_ewma.h \
src/core/or/circuitstats.h \
src/core/or/circuitpadding.h \
src/core/or/circuitpadding_machines.h \
src/core/or/circuituse.h \
src/core/or/command.h \
src/core/or/connection_edge.h \

View file

@ -37,6 +37,13 @@
* When a padding machine reaches the END state, it gets wiped from the circuit
* so that other padding machines can take over if needed (see
* circpad_machine_spec_transitioned_to_end()).
*
****************************
* General notes:
*
* All used machines should be heap allocated and placed into
* origin_padding_machines/relay_padding_machines so that they get correctly
* cleaned up by the circpad_free_all() function.
**/
#define CIRCUITPADDING_PRIVATE
@ -46,6 +53,7 @@
#include "lib/math/prob_distr.h"
#include "core/or/or.h"
#include "core/or/circuitpadding.h"
#include "core/or/circuitpadding_machines.h"
#include "core/or/circuitlist.h"
#include "core/or/circuituse.h"
#include "core/mainloop/netstatus.h"
@ -72,8 +80,6 @@
#include "app/config/config.h"
static inline circpad_purpose_mask_t circpad_circ_purpose_to_mask(uint8_t
circ_purpose);
static inline circpad_circuit_state_t circpad_circuit_state(
origin_circuit_t *circ);
static void circpad_setup_machine_on_circ(circuit_t *on_circ,
@ -125,34 +131,6 @@ STATIC smartlist_t *relay_padding_machines = NULL;
continue;
#define FOR_EACH_ACTIVE_CIRCUIT_MACHINE_END } STMT_END ;
/**
* Return a human-readable description for a circuit padding state.
*/
static const char *
circpad_state_to_string(circpad_statenum_t state)
{
const char *descr;
switch (state) {
case CIRCPAD_STATE_START:
descr = "START";
break;
case CIRCPAD_STATE_BURST:
descr = "BURST";
break;
case CIRCPAD_STATE_GAP:
descr = "GAP";
break;
case CIRCPAD_STATE_END:
descr = "END";
break;
default:
descr = "CUSTOM"; // XXX: Just return # in static char buf?
}
return descr;
}
/**
* Free the machineinfo at an index
*/
@ -485,7 +463,10 @@ circpad_machine_setup_tokens(circpad_machine_runtime_t *mi)
const circpad_state_t *state = circpad_machine_current_state(mi);
/* If this state doesn't exist, or doesn't have token removal,
* free any previous state's histogram, and bail */
* free any previous state's runtime histogram, and bail.
*
* If we don't have a token removal strategy, we also don't need a runtime
* histogram and we rely on the immutable one in machine_spec_t. */
if (!state || state->token_removal == CIRCPAD_TOKEN_REMOVAL_NONE) {
if (mi->histogram) {
tor_free(mi->histogram);
@ -525,9 +506,14 @@ circpad_choose_state_length(circpad_machine_runtime_t *mi)
length = circpad_distribution_sample(state->length_dist);
length = MAX(0, length);
length += state->start_length;
length = MIN(length, state->max_length);
if (state->max_length) {
length = MIN(length, state->max_length);
}
mi->state_length = clamp_double_to_int64(length);
log_info(LD_CIRC, "State length sampled to %"PRIu64".", mi->state_length);
}
/**
@ -595,6 +581,11 @@ circpad_machine_sample_delay(circpad_machine_runtime_t *mi)
histogram_total_tokens = state->histogram_total_tokens;
}
/* If we are out of tokens, don't schedule padding. */
if (!histogram_total_tokens) {
return CIRCPAD_DELAY_INFINITE;
}
bin_choice = crypto_fast_rng_get_uint64(get_thread_fast_rng(),
histogram_total_tokens);
@ -897,7 +888,7 @@ circpad_machine_remove_closest_token(circpad_machine_runtime_t *mi,
bin_to_remove = lower;
}
mi->histogram[bin_to_remove]--;
log_debug(LD_GENERAL, "Removing token from bin %d", bin_to_remove);
log_debug(LD_CIRC, "Removing token from bin %d", bin_to_remove);
return;
} else {
if (current - lower > higher - current) {
@ -1207,14 +1198,16 @@ circpad_send_padding_cell_for_callback(circpad_machine_runtime_t *mi)
circpad_send_command_to_hop(TO_ORIGIN_CIRCUIT(mi->on_circ),
CIRCPAD_GET_MACHINE(mi)->target_hopnum,
RELAY_COMMAND_DROP, NULL, 0);
log_fn(LOG_INFO,LD_CIRC, "Callback: Sending padding to origin circuit %u.",
TO_ORIGIN_CIRCUIT(mi->on_circ)->global_identifier);
log_info(LD_CIRC, "Callback: Sending padding to origin circuit %u"
" (%d) [length: %"PRIu64"]",
TO_ORIGIN_CIRCUIT(mi->on_circ)->global_identifier,
mi->on_circ->purpose, mi->state_length);
} else {
// If we're a non-origin circ, we can just send from here as if we're the
// edge.
if (TO_OR_CIRCUIT(circ)->p_chan_cells.n <= circpad_max_circ_queued_cells) {
log_fn(LOG_INFO,LD_CIRC,
"Callback: Sending padding to non-origin circuit.");
log_info(LD_CIRC, "Callback: Sending padding to circuit (%d)"
" [length: %"PRIu64"]", mi->on_circ->purpose, mi->state_length);
relay_send_command_from_edge(0, mi->on_circ, RELAY_COMMAND_DROP, NULL,
0, NULL);
rep_hist_padding_count_write(PADDING_TYPE_DROP);
@ -1582,9 +1575,8 @@ circpad_machine_spec_transition,(circpad_machine_runtime_t *mi,
* a transition to itself. All non-specified events are ignored.
*/
log_fn(LOG_INFO, LD_CIRC,
"Circpad machine %d transitioning from %s to %s",
mi->machine_index, circpad_state_to_string(mi->current_state),
circpad_state_to_string(s));
"Circpad machine %d transitioning from %u to %u",
mi->machine_index, mi->current_state, s);
/* If this is not the same state, switch and init tokens,
* otherwise just reschedule padding. */
@ -1734,13 +1726,12 @@ circpad_estimate_circ_rtt_on_send(circuit_t *circ,
* to back. Stop estimating RTT in this case. Note that we only
* stop RTT update if the circuit is opened, to allow for RTT estimates
* of var cells during circ setup. */
mi->stop_rtt_update = 1;
if (!mi->rtt_estimate_usec) {
if (!mi->rtt_estimate_usec && !mi->stop_rtt_update) {
static ratelim_t rtt_lim = RATELIM_INIT(600);
log_fn_ratelim(&rtt_lim,LOG_NOTICE,LD_BUG,
"Circuit sent two cells back to back before estimating RTT.");
}
mi->stop_rtt_update = 1;
}
}
@ -1976,7 +1967,6 @@ circpad_circuit_state(origin_circuit_t *circ)
* Convert a normal circuit purpose into a bitmask that we can
* use for determining matching circuits.
*/
static inline
circpad_purpose_mask_t
circpad_circ_purpose_to_mask(uint8_t circ_purpose)
{
@ -2019,7 +2009,8 @@ circpad_shutdown_old_machines(origin_circuit_t *on_circ)
}
/**
* Negotiate new machines that would apply to this circuit.
* Negotiate new machines that would apply to this circuit, given the machines
* inside <b>machines_sl</b>.
*
* This function checks to see if we have any free machine indexes,
* and for each free machine index, it initializes the most recently
@ -2027,14 +2018,15 @@ circpad_shutdown_old_machines(origin_circuit_t *on_circ)
* index and circuit conditions, and negotiates it with the appropriate
* middle relay.
*/
static void
circpad_add_matching_machines(origin_circuit_t *on_circ)
STATIC void
circpad_add_matching_machines(origin_circuit_t *on_circ,
smartlist_t *machines_sl)
{
circuit_t *circ = TO_CIRCUIT(on_circ);
#ifdef TOR_UNIT_TESTS
/* Tests don't have to init our padding machines */
if (!origin_padding_machines)
if (!machines_sl)
return;
#endif
@ -2051,7 +2043,7 @@ circpad_add_matching_machines(origin_circuit_t *on_circ)
/* We have a free machine index. Check the origin padding
* machines in reverse order, so that more recently added
* machines take priority over older ones. */
SMARTLIST_FOREACH_REVERSE_BEGIN(origin_padding_machines,
SMARTLIST_FOREACH_REVERSE_BEGIN(machines_sl,
circpad_machine_spec_t *,
machine) {
/* Machine definitions have a specific target machine index.
@ -2079,6 +2071,7 @@ circpad_add_matching_machines(origin_circuit_t *on_circ)
if (circpad_negotiate_padding(on_circ, machine->machine_num,
machine->target_hopnum,
CIRCPAD_COMMAND_START) < 0) {
log_info(LD_CIRC, "Padding not negotiated. Cleaning machine");
circpad_circuit_machineinfo_free_idx(circ, i);
circ->padding_machine[i] = NULL;
on_circ->padding_negotiation_failed = 1;
@ -2102,7 +2095,7 @@ circpad_machine_event_circ_added_hop(origin_circuit_t *on_circ)
{
/* Since our padding conditions do not specify a max_hops,
* all we can do is add machines here */
circpad_add_matching_machines(on_circ);
circpad_add_matching_machines(on_circ, origin_padding_machines);
}
/**
@ -2115,7 +2108,7 @@ void
circpad_machine_event_circ_built(origin_circuit_t *circ)
{
circpad_shutdown_old_machines(circ);
circpad_add_matching_machines(circ);
circpad_add_matching_machines(circ, origin_padding_machines);
}
/**
@ -2128,7 +2121,7 @@ void
circpad_machine_event_circ_purpose_changed(origin_circuit_t *circ)
{
circpad_shutdown_old_machines(circ);
circpad_add_matching_machines(circ);
circpad_add_matching_machines(circ, origin_padding_machines);
}
/**
@ -2142,7 +2135,7 @@ void
circpad_machine_event_circ_has_no_relay_early(origin_circuit_t *circ)
{
circpad_shutdown_old_machines(circ);
circpad_add_matching_machines(circ);
circpad_add_matching_machines(circ, origin_padding_machines);
}
/**
@ -2157,7 +2150,7 @@ void
circpad_machine_event_circ_has_streams(origin_circuit_t *circ)
{
circpad_shutdown_old_machines(circ);
circpad_add_matching_machines(circ);
circpad_add_matching_machines(circ, origin_padding_machines);
}
/**
@ -2172,7 +2165,7 @@ void
circpad_machine_event_circ_has_no_streams(origin_circuit_t *circ)
{
circpad_shutdown_old_machines(circ);
circpad_add_matching_machines(circ);
circpad_add_matching_machines(circ, origin_padding_machines);
}
/**
@ -2253,13 +2246,6 @@ circpad_deliver_recognized_relay_cell_events(circuit_t *circ,
uint8_t relay_command,
crypt_path_t *layer_hint)
{
/* Padding negotiate cells are ignored by the state machines
* for simplicity. */
if (relay_command == RELAY_COMMAND_PADDING_NEGOTIATE ||
relay_command == RELAY_COMMAND_PADDING_NEGOTIATED) {
return;
}
if (relay_command == RELAY_COMMAND_DROP) {
rep_hist_padding_count_read(PADDING_TYPE_DROP);
@ -2296,16 +2282,12 @@ void
circpad_deliver_sent_relay_cell_events(circuit_t *circ,
uint8_t relay_command)
{
/* Padding negotiate cells are ignored by the state machines
* for simplicity. */
if (relay_command == RELAY_COMMAND_PADDING_NEGOTIATE ||
relay_command == RELAY_COMMAND_PADDING_NEGOTIATED) {
return;
}
/* RELAY_COMMAND_DROP is the multi-hop (aka circuit-level) padding cell in
* tor. (CELL_PADDING is a channel-level padding cell, which is not relayed
* or processed here) */
* or processed here).
*
* We do generate events for PADDING_NEGOTIATE and PADDING_NEGOTIATED cells.
*/
if (relay_command == RELAY_COMMAND_DROP) {
/* Optimization: The event for RELAY_COMMAND_DROP is sent directly
* from circpad_send_padding_cell_for_callback(). This is to avoid
@ -2363,12 +2345,21 @@ circpad_setup_machine_on_circ(circuit_t *on_circ,
== NULL);
tor_assert_nonfatal(on_circ->padding_info[machine->machine_index] == NULL);
/* Log message */
if (CIRCUIT_IS_ORIGIN(on_circ)) {
log_info(LD_CIRC, "Registering machine %s to origin circ %u (%d)",
machine->name,
TO_ORIGIN_CIRCUIT(on_circ)->global_identifier, on_circ->purpose);
} else {
log_info(LD_CIRC, "Registering machine %s to non-origin circ (%d)",
machine->name, on_circ->purpose);
}
on_circ->padding_info[machine->machine_index] =
circpad_circuit_machineinfo_new(on_circ, machine->machine_index);
on_circ->padding_machine[machine->machine_index] = machine;
}
#ifdef TOR_UNIT_TESTS
/** Validate a single state of a padding machine */
static bool
padding_machine_state_is_valid(const circpad_state_t *state)
@ -2384,7 +2375,7 @@ padding_machine_state_is_valid(const circpad_state_t *state)
/* We need at least two bins in a histogram */
if (state->histogram_len < 2) {
log_warn(LD_GENERAL, "You can't have a histogram with less than 2 bins");
log_warn(LD_CIRC, "You can't have a histogram with less than 2 bins");
return false;
}
@ -2394,7 +2385,7 @@ padding_machine_state_is_valid(const circpad_state_t *state)
/* Check that histogram edges are strictly increasing. Ignore the first
* edge since it can be zero. */
if (prev_bin_edge >= state->histogram_edges[b] && b > 0) {
log_warn(LD_GENERAL, "Histogram edges are not increasing [%u/%u]",
log_warn(LD_CIRC, "Histogram edges are not increasing [%u/%u]",
prev_bin_edge, state->histogram_edges[b]);
return false;
}
@ -2406,7 +2397,7 @@ padding_machine_state_is_valid(const circpad_state_t *state)
}
/* Verify that the total number of tokens is correct */
if (tokens_count != state->histogram_total_tokens) {
log_warn(LD_GENERAL, "Histogram token count is wrong [%u/%u]",
log_warn(LD_CIRC, "Histogram token count is wrong [%u/%u]",
tokens_count, state->histogram_total_tokens);
return false;
}
@ -2432,12 +2423,12 @@ padding_machine_is_valid(const circpad_machine_spec_t *machine)
/* Validate and register <b>machine</b> into <b>machine_list</b>. If
* <b>machine_list</b> is NULL, then just validate. */
STATIC void
register_padding_machine(circpad_machine_spec_t *machine,
smartlist_t *machine_list)
void
circpad_register_padding_machine(circpad_machine_spec_t *machine,
smartlist_t *machine_list)
{
if (!padding_machine_is_valid(machine)) {
log_warn(LD_GENERAL, "Machine #%u is invalid. Ignoring.",
log_warn(LD_CIRC, "Machine #%u is invalid. Ignoring.",
machine->machine_num);
return;
}
@ -2447,6 +2438,7 @@ register_padding_machine(circpad_machine_spec_t *machine,
}
}
#ifdef TOR_UNIT_TESTS
/* These padding machines are only used for tests pending #28634. */
static void
circpad_circ_client_machine_init(void)
@ -2499,7 +2491,8 @@ circpad_circ_client_machine_init(void)
circ_client_machine->states[CIRCPAD_STATE_BURST].histogram_total_tokens = 5;
circ_client_machine->machine_num = smartlist_len(origin_padding_machines);
register_padding_machine(circ_client_machine, origin_padding_machines);
circpad_register_padding_machine(circ_client_machine,
origin_padding_machines);
}
static void
@ -2599,7 +2592,8 @@ circpad_circ_responder_machine_init(void)
CIRCPAD_TOKEN_REMOVAL_CLOSEST_USEC;
circ_responder_machine->machine_num = smartlist_len(relay_padding_machines);
register_padding_machine(circ_responder_machine, relay_padding_machines);
circpad_register_padding_machine(circ_responder_machine,
relay_padding_machines);
}
#endif
@ -2618,6 +2612,14 @@ circpad_machines_init(void)
origin_padding_machines = smartlist_new();
relay_padding_machines = smartlist_new();
/* Register machines for hiding client-side intro circuits */
circpad_machine_client_hide_intro_circuits(origin_padding_machines);
circpad_machine_relay_hide_intro_circuits(relay_padding_machines);
/* Register machines for hiding client-side rendezvous circuits */
circpad_machine_client_hide_rend_circuits(origin_padding_machines);
circpad_machine_relay_hide_rend_circuits(relay_padding_machines);
// TODO: Parse machines from consensus and torrc
#ifdef TOR_UNIT_TESTS
circpad_circ_client_machine_init();
@ -2668,8 +2670,8 @@ circpad_node_supports_padding(const node_t *node)
* Returns node_t from the consensus for that hop, if it is opened.
* Otherwise returns NULL.
*/
static const node_t *
circuit_get_nth_node(origin_circuit_t *circ, int hop)
MOCK_IMPL(STATIC const node_t *,
circuit_get_nth_node,(origin_circuit_t *circ, int hop))
{
crypt_path_t *iter = circuit_get_cpath_hop(circ, hop);
@ -2732,8 +2734,8 @@ circpad_negotiate_padding(origin_circuit_t *circ,
&type)) < 0)
return -1;
log_fn(LOG_INFO,LD_CIRC, "Negotiating padding on circuit %u",
circ->global_identifier);
log_fn(LOG_INFO,LD_CIRC, "Negotiating padding on circuit %u (%d)",
circ->global_identifier, TO_CIRCUIT(circ)->purpose);
return circpad_send_command_to_hop(circ, target_hopnum,
RELAY_COMMAND_PADDING_NEGOTIATE,
@ -2818,6 +2820,7 @@ circpad_handle_padding_negotiate(circuit_t *circ, cell_t *cell)
const circpad_machine_spec_t *, m) {
if (m->machine_num == negotiate->machine_type) {
circpad_setup_machine_on_circ(circ, m);
circpad_cell_event_nonpadding_received(circ);
goto done;
}
} SMARTLIST_FOREACH_END(m);
@ -2871,6 +2874,7 @@ circpad_handle_padding_negotiated(circuit_t *circ, cell_t *cell,
}
if (negotiated->command == CIRCPAD_COMMAND_STOP) {
log_info(LD_CIRC, "Received STOP command on PADDING_NEGOTIATED");
/* There may not be a padding_info here if we shut down the
* machine in circpad_shutdown_old_machines(). Or, if
* circpad_add_matching_matchines() added a new machine,
@ -2891,6 +2895,36 @@ circpad_handle_padding_negotiated(circuit_t *circ, cell_t *cell,
return 0;
}
/** Free memory allocated by this machine spec. */
STATIC void
machine_spec_free_(circpad_machine_spec_t *m)
{
if (!m) return;
tor_free(m->states);
tor_free(m);
}
/** Free all memory allocated by the circuitpadding subsystem. */
void
circpad_free_all(void)
{
if (origin_padding_machines) {
SMARTLIST_FOREACH_BEGIN(origin_padding_machines,
circpad_machine_spec_t *, m) {
machine_spec_free(m);
} SMARTLIST_FOREACH_END(m);
smartlist_free(origin_padding_machines);
}
if (relay_padding_machines) {
SMARTLIST_FOREACH_BEGIN(relay_padding_machines,
circpad_machine_spec_t *, m) {
machine_spec_free(m);
} SMARTLIST_FOREACH_END(m);
smartlist_free(relay_padding_machines);
}
}
/* Serialization */
// TODO: Should we use keyword=value here? Are there helpers for that?
#if 0

View file

@ -603,6 +603,9 @@ typedef uint8_t circpad_machine_num_t;
/** Global state machine structure from the consensus */
typedef struct circpad_machine_spec_t {
/* Just a user-friendly machine name for logs */
const char *name;
/** Global machine number */
circpad_machine_num_t machine_num;
@ -698,6 +701,8 @@ circpad_machine_event_circ_has_no_relay_early(struct origin_circuit_t *circ);
void circpad_machines_init(void);
void circpad_machines_free(void);
void circpad_register_padding_machine(circpad_machine_spec_t *machine,
smartlist_t *machine_list);
void circpad_machine_states_init(circpad_machine_spec_t *machine,
circpad_statenum_t num_states);
@ -726,6 +731,8 @@ bool circpad_padding_negotiated(struct circuit_t *circ,
uint8_t command,
uint8_t response);
circpad_purpose_mask_t circpad_circ_purpose_to_mask(uint8_t circ_purpose);
MOCK_DECL(circpad_decision_t,
circpad_machine_schedule_padding,(circpad_machine_runtime_t *));
@ -736,7 +743,13 @@ circpad_machine_spec_transition, (circpad_machine_runtime_t *mi,
circpad_decision_t circpad_send_padding_cell_for_callback(
circpad_machine_runtime_t *mi);
void circpad_free_all(void);
#ifdef CIRCUITPADDING_PRIVATE
STATIC void machine_spec_free_(circpad_machine_spec_t *m);
#define machine_spec_free(chan) \
FREE_AND_NULL(circpad_machine_spec_t,machine_spec_free_, (m))
STATIC circpad_delay_t
circpad_machine_sample_delay(circpad_machine_runtime_t *mi);
@ -773,17 +786,21 @@ circpad_send_command_to_hop,(struct origin_circuit_t *circ, uint8_t hopnum,
uint8_t relay_command, const uint8_t *payload,
ssize_t payload_len));
MOCK_DECL(STATIC const node_t *,
circuit_get_nth_node,(origin_circuit_t *circ, int hop));
STATIC circpad_delay_t
histogram_get_bin_upper_bound(const circpad_machine_runtime_t *mi,
circpad_hist_index_t bin);
STATIC void
circpad_add_matching_machines(origin_circuit_t *on_circ,
smartlist_t *machines_sl);
#ifdef TOR_UNIT_TESTS
extern smartlist_t *origin_padding_machines;
extern smartlist_t *relay_padding_machines;
STATIC void
register_padding_machine(circpad_machine_spec_t *machine,
smartlist_t *machine_list);
#endif
#endif

View file

@ -0,0 +1,458 @@
/* Copyright (c) 2019 The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file circuitpadding_machines.c
* \brief Circuit padding state machines
*
* \detail
*
* Introduce circuit padding machines that will be used by Tor circuits, as
* specified by proposal 302 "Hiding onion service clients using padding".
*
* Right now this file introduces two machines that aim to hide the client-side
* of onion service circuits against naive classifiers like the ones from the
* "Circuit Fingerprinting Attacks: Passive Deanonymization of Tor Hidden
* Services" paper from USENIX. By naive classifiers we mean classifiers that
* use basic features like "circuit construction circuits" and "incoming and
* outgoing cell counts" and "duration of activity".
*
* In particular, these machines aim to be lightweight and protect against
* these basic classifiers. They don't aim to protect against more advanced
* attacks that use deep learning or even correlate various circuit
* construction events together. Machines that fool such advanced classifiers
* are also possible, but they can't be so lightweight and might require more
* WTF-PAD features. So for now we opt for the following two machines:
*
* Client-side introduction circuit hiding machine:
*
* This machine hides client-side introduction circuits by making their
* circuit consruction sequence look like normal general circuits that
* download directory information. Furthermore, the circuits are kept open
* until all the padding has been sent, since intro circuits are usually
* very short lived and this act as a distinguisher. For more info see
* circpad_machine_client_hide_intro_circuits() and the sec.
*
* Client-side rendezvous circuit hiding machine:
*
* This machine hides client-side rendezvous circuits by making their
* circuit construction sequence look like normal general circuits. For more
* details see circpad_machine_client_hide_rend_circuits() and the spec.
*
* TODO: These are simple machines that carefully manipulate the cells of the
* initial circuit setup procedure to make them look like general
* circuits. In the future, more states can be baked into their state machine
* to do more advanced obfuscation.
**/
#define CIRCUITPADDING_MACHINES_PRIVATE
#include "core/or/or.h"
#include "feature/nodelist/networkstatus.h"
#include "lib/crypt_ops/crypto_rand.h"
#include "core/or/circuitlist.h"
#include "core/or/circuitpadding_machines.h"
#include "core/or/circuitpadding.h"
/** Create a client-side padding machine that aims to hide IP circuits. In
* particular, it keeps intro circuits alive until a bunch of fake traffic has
* been pushed through.
*/
void
circpad_machine_client_hide_intro_circuits(smartlist_t *machines_sl)
{
circpad_machine_spec_t *client_machine
= tor_malloc_zero(sizeof(circpad_machine_spec_t));
client_machine->name = "client_ip_circ";
client_machine->conditions.state_mask = CIRCPAD_CIRC_OPENED;
client_machine->target_hopnum = 2;
/* This is a client machine */
client_machine->is_origin_side = 1;
/* We only want to pad introduction circuits, and we want to start padding
* only after the INTRODUCE1 cell has been sent, so set the purposes
* appropriately.
*
* In particular we want introduction circuits to blend as much as possible
* with general circuits. Most general circuits have the following initial
* relay cell sequence (outgoing cells marked in [brackets]):
*
* [EXTEND2] -> EXTENDED2 -> [EXTEND2] -> EXTENDED2 -> [BEGIN] -> CONNECTED
* -> [DATA] -> [DATA] -> DATA -> DATA...(inbound data cells continue)
*
* Whereas normal introduction circuits usually look like:
*
* [EXTEND2] -> EXTENDED2 -> [EXTEND2] -> EXTENDED2 -> [EXTEND2] -> EXTENDED2
* -> [INTRO1] -> INTRODUCE_ACK
*
* This means that up to the sixth cell (first line of each sequence above),
* both general and intro circuits have identical cell sequences. After that
* we want to mimic the second line sequence of
* -> [DATA] -> [DATA] -> DATA -> DATA...(inbound data cells continue)
*
* We achieve this by starting padding INTRODUCE1 has been sent. With padding
* negotiation cells, in the common case of the second line looks like:
* -> [INTRO1] -> [PADDING_NEGOTIATE] -> PADDING_NEGOTIATED -> INTRO_ACK
*
* Then, the middle node will send between INTRO_MACHINE_MINIMUM_PADDING and
* INTRO_MACHINE_MAXIMUM_PADDING cells, to match the "...(inbound data cells
* continue)" portion of the trace (aka the rest of an HTTPS response body).
*/
client_machine->conditions.purpose_mask =
circpad_circ_purpose_to_mask(CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT)|
circpad_circ_purpose_to_mask(CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)|
circpad_circ_purpose_to_mask(CIRCUIT_PURPOSE_C_CIRCUIT_PADDING);
/* Keep the circuit alive even after the introduction has been finished,
* otherwise the short-term lifetime of the circuit will blow our cover */
client_machine->manage_circ_lifetime = 1;
/* Set padding machine limits to help guard against excessive padding */
client_machine->allowed_padding_count = INTRO_MACHINE_MAXIMUM_PADDING;
client_machine->max_padding_percent = 1;
/* Two states: START, OBFUSCATE_CIRC_SETUP (and END) */
circpad_machine_states_init(client_machine, 2);
/* For the origin-side machine, we transition to OBFUSCATE_CIRC_SETUP after
* sending PADDING_NEGOTIATE, and we stay there (without sending any padding)
* until we receive a STOP from the other side. */
client_machine->states[CIRCPAD_STATE_START].
next_state[CIRCPAD_EVENT_NONPADDING_SENT] =
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP;
/* origin-side machine has no event reactions while in
* CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP, so no more state transitions here. */
/* The client side should never send padding, so it does not need
* to specify token removal, or a histogram definition or state lengths.
* That is all controlled by the middle node. */
/* Register the machine */
client_machine->machine_num = smartlist_len(machines_sl);
circpad_register_padding_machine(client_machine, machines_sl);
log_info(LD_CIRC,
"Registered client intro point hiding padding machine (%u)",
client_machine->machine_num);
}
/** Create a relay-side padding machine that aims to hide IP circuits. See
* comments on the function above for more details on the workings of the
* machine. */
void
circpad_machine_relay_hide_intro_circuits(smartlist_t *machines_sl)
{
circpad_machine_spec_t *relay_machine
= tor_malloc_zero(sizeof(circpad_machine_spec_t));
relay_machine->name = "relay_ip_circ";
relay_machine->conditions.state_mask = CIRCPAD_CIRC_OPENED;
relay_machine->target_hopnum = 2;
/* This is a relay-side machine */
relay_machine->is_origin_side = 0;
/* We want to negotiate END from this side after all our padding is done, so
* that the origin-side machine goes into END state, and eventually closes
* the circuit. */
relay_machine->should_negotiate_end = 1;
/* Set padding machine limits to help guard against excessive padding */
relay_machine->allowed_padding_count = INTRO_MACHINE_MAXIMUM_PADDING;
relay_machine->max_padding_percent = 1;
/* Two states: START, OBFUSCATE_CIRC_SETUP (and END) */
circpad_machine_states_init(relay_machine, 2);
/* For the relay-side machine, we want to transition
* START -> OBFUSCATE_CIRC_SETUP upon first non-padding
* cell sent (PADDING_NEGOTIATED in this case). */
relay_machine->states[CIRCPAD_STATE_START].
next_state[CIRCPAD_EVENT_NONPADDING_SENT] =
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP;
/* For the relay-side, we want to transition from OBFUSCATE_CIRC_SETUP to END
* state when the length finishes. */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
next_state[CIRCPAD_EVENT_LENGTH_COUNT] = CIRCPAD_STATE_END;
/* Now let's define the OBF -> OBF transitions that maintain our padding
* flow:
*
* For the relay-side machine, we want to keep on sending padding bytes even
* when nothing else happens on this circuit. */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
next_state[CIRCPAD_EVENT_PADDING_SENT] =
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP;
/* For the relay-side machine, we need this transition so that we re-enter
the state, after PADDING_NEGOTIATED is sent. Otherwise, the remove token
function will disable the timer, and nothing will restart it since there
is no other motion on an intro circuit. */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
next_state[CIRCPAD_EVENT_NONPADDING_SENT] =
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP;
/* Token removal strategy for OBFUSCATE_CIRC_SETUP state: Don't
* remove any tokens.
*
* We rely on the state length sampling and not token removal, to avoid
* the mallocs required to copy the histograms for token removal,
* and to avoid monotime calls needed to determine histogram
* bins for token removal. */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
token_removal = CIRCPAD_TOKEN_REMOVAL_NONE;
/* Figure out the length of the OBFUSCATE_CIRC_SETUP state so that it's
* randomized. The relay side will send between INTRO_MACHINE_MINIMUM_PADDING
* and INTRO_MACHINE_MAXIMUM_PADDING padding cells towards the client. */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.type = CIRCPAD_DIST_UNIFORM;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.param1 = INTRO_MACHINE_MINIMUM_PADDING;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.param2 = INTRO_MACHINE_MAXIMUM_PADDING;
/* Configure histogram */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_len = 2;
/* For the relay-side machine we want to batch padding instantly to pretend
* its an incoming directory download. So set the histogram edges tight:
* (1, 10ms, infinity). */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_edges[0] = 1000;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_edges[1] = 10000;
/* We put all our tokens in bin 0, which means we want 100% probability
* for choosing a inter-packet delay of between 1000 and 10000 microseconds
* (1 to 10ms). Since we only have 1 bin, it doesn't matter how many tokens
* there are, 1000 out of 1000 is 100% */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram[0] = 1000;
/* just one bin, so setup the total tokens */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_total_tokens =
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].histogram[0];
/* Register the machine */
relay_machine->machine_num = smartlist_len(machines_sl);
circpad_register_padding_machine(relay_machine, machines_sl);
log_info(LD_CIRC,
"Registered relay intro circuit hiding padding machine (%u)",
relay_machine->machine_num);
}
/************************** Rendezvous-circuit machine ***********************/
/** Create a client-side padding machine that aims to hide rendezvous
* circuits.*/
void
circpad_machine_client_hide_rend_circuits(smartlist_t *machines_sl)
{
circpad_machine_spec_t *client_machine
= tor_malloc_zero(sizeof(circpad_machine_spec_t));
client_machine->name = "client_rp_circ";
/* Only pad after the circuit has been built and pad to the middle */
client_machine->conditions.state_mask = CIRCPAD_CIRC_OPENED;
client_machine->target_hopnum = 2;
/* This is a client machine */
client_machine->is_origin_side = 1;
/* We only want to pad rendezvous circuits, and we want to start padding only
* after the rendezvous circuit has been established.
*
* Following a similar argument as for intro circuits, we are aiming for
* padded rendezvous circuits to blend in with the initial cell sequence of
* general circuits which usually look like this:
*
* [EXTEND2] -> EXTENDED2 -> [EXTEND2] -> EXTENDED2 -> [BEGIN] -> CONNECTED
* -> [DATA] -> [DATA] -> DATA -> DATA...(incoming cells continue)
*
* Whereas normal rendezvous circuits usually look like:
*
* [EXTEND2] -> EXTENDED2 -> [EXTEND2] -> EXTENDED2 -> [EST_REND] -> REND_EST
* -> REND2 -> [BEGIN]
*
* This means that up to the sixth cell (in the first line), both general and
* rend circuits have identical cell sequences.
*
* After that we want to mimic a [DATA] -> [DATA] -> DATA -> DATA sequence.
*
* With padding negotiation right after the REND_ESTABLISHED, the sequence
* becomes:
*
* [EXTEND2] -> EXTENDED2 -> [EXTEND2] -> EXTENDED2 -> [EST_REND] -> REND_EST
* -> [PADDING_NEGOTIATE] -> [DROP] -> PADDING_NEGOTIATED -> DROP...
*
* After which normal application DATA cells continue on the circuit.
*
* Hence this way we make rendezvous circuits look like general circuits up
* till the end of the circuit setup. */
client_machine->conditions.purpose_mask =
circpad_circ_purpose_to_mask(CIRCUIT_PURPOSE_C_REND_JOINED)|
circpad_circ_purpose_to_mask(CIRCUIT_PURPOSE_C_REND_READY)|
circpad_circ_purpose_to_mask(CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
/* Set padding machine limits to help guard against excessive padding */
client_machine->allowed_padding_count = 1;
client_machine->max_padding_percent = 1;
/* Two states: START, OBFUSCATE_CIRC_SETUP (and END) */
circpad_machine_states_init(client_machine, 2);
/* START -> OBFUSCATE_CIRC_SETUP transition upon sending the first
* non-padding cell (which is PADDING_NEGOTIATE) */
client_machine->states[CIRCPAD_STATE_START].
next_state[CIRCPAD_EVENT_NONPADDING_SENT] =
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP;
/* OBFUSCATE_CIRC_SETUP -> END transition when we send our first
* padding packet and/or hit the state length (the state length is 1). */
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
next_state[CIRCPAD_EVENT_PADDING_RECV] = CIRCPAD_STATE_END;
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
next_state[CIRCPAD_EVENT_LENGTH_COUNT] = CIRCPAD_STATE_END;
/* Don't use a token removal strategy since we don't want to use monotime
* functions and we want to avoid mallocing histogram copies. We want
* this machine to be light. */
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
token_removal = CIRCPAD_TOKEN_REMOVAL_NONE;
/* Instead, to control the volume of padding (we just want to send a single
* padding cell) we will use a static state length. We just want one token,
* since we want to make the following pattern:
* [PADDING_NEGOTIATE] -> [DROP] -> PADDING_NEGOTIATED -> DROP */
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.type = CIRCPAD_DIST_UNIFORM;
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.param1 = 1;
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.param2 = 2; // rand(1,2) is always 1
/* Histogram is: (0 msecs, 1 msec, infinity). We want this to be fast so
* that we send our outgoing [DROP] before the PADDING_NEGOTIATED comes
* back from the relay side. */
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_len = 2;
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_edges[0] = 0;
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_edges[1] = 1000;
/* We want a 100% probability of choosing an inter-packet delay of
* between 0 and 1ms. Since we don't use token removal,
* the number of tokens does not matter. (And also, state_length
* governs how many packets we send). */
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram[0] = 1;
client_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_total_tokens = 1;
/* Register the machine */
client_machine->machine_num = smartlist_len(machines_sl);
circpad_register_padding_machine(client_machine, machines_sl);
log_info(LD_CIRC,
"Registered client rendezvous circuit hiding padding machine (%u)",
client_machine->machine_num);
}
/** Create a relay-side padding machine that aims to hide IP circuits.
*
* This is meant to follow the client-side machine.
*/
void
circpad_machine_relay_hide_rend_circuits(smartlist_t *machines_sl)
{
circpad_machine_spec_t *relay_machine
= tor_malloc_zero(sizeof(circpad_machine_spec_t));
relay_machine->name = "relay_rp_circ";
/* Only pad after the circuit has been built and pad to the middle */
relay_machine->conditions.min_hops = 2;
relay_machine->conditions.state_mask = CIRCPAD_CIRC_OPENED;
relay_machine->target_hopnum = 2;
/* This is a relay-side machine */
relay_machine->is_origin_side = 0;
/* Set padding machine limits to help guard against excessive padding */
relay_machine->allowed_padding_count = 1;
relay_machine->max_padding_percent = 1;
/* Two states: START, OBFUSCATE_CIRC_SETUP (and END) */
circpad_machine_states_init(relay_machine, 2);
/* START -> OBFUSCATE_CIRC_SETUP transition upon sending the first
* non-padding cell (which is PADDING_NEGOTIATED) */
relay_machine->states[CIRCPAD_STATE_START].
next_state[CIRCPAD_EVENT_NONPADDING_SENT] =
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP;
/* OBFUSCATE_CIRC_SETUP -> END transition when we send our first
* padding packet and/or hit the state length (the state length is 1). */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
next_state[CIRCPAD_EVENT_PADDING_RECV] = CIRCPAD_STATE_END;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
next_state[CIRCPAD_EVENT_LENGTH_COUNT] = CIRCPAD_STATE_END;
/* Don't use a token removal strategy since we don't want to use monotime
* functions and we want to avoid mallocing histogram copies. We want
* this machine to be light. */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
token_removal = CIRCPAD_TOKEN_REMOVAL_NONE;
/* Instead, to control the volume of padding (we just want to send a single
* padding cell) we will use a static state length. We just want one token,
* since we want to make the following pattern:
* [PADDING_NEGOTIATE] -> [DROP] -> PADDING_NEGOTIATED -> DROP */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.type = CIRCPAD_DIST_UNIFORM;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.param1 = 1;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
length_dist.param2 = 2; // rand(1,2) is always 1
/* Histogram is: (0 msecs, 1 msec, infinity). We want this to be fast so
* that the outgoing DROP cell is sent immediately after the
* PADDING_NEGOTIATED. */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_len = 2;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_edges[0] = 0;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_edges[1] = 1000;
/* We want a 100% probability of choosing an inter-packet delay of
* between 0 and 1ms. Since we don't use token removal,
* the number of tokens does not matter. (And also, state_length
* governs how many packets we send). */
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram[0] = 1;
relay_machine->states[CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP].
histogram_total_tokens = 1;
/* Register the machine */
relay_machine->machine_num = smartlist_len(machines_sl);
circpad_register_padding_machine(relay_machine, machines_sl);
log_info(LD_CIRC,
"Registered relay rendezvous circuit hiding padding machine (%u)",
relay_machine->machine_num);
}

View file

@ -0,0 +1,35 @@
/* Copyright (c) 2018 The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file circuitpadding_machines.h
* \brief Header file for circuitpadding_machines.c.
**/
#ifndef TOR_CIRCUITPADDING_MACHINES_H
#define TOR_CIRCUITPADDING_MACHINES_H
void circpad_machine_relay_hide_intro_circuits(smartlist_t *machines_sl);
void circpad_machine_client_hide_intro_circuits(smartlist_t *machines_sl);
void circpad_machine_relay_hide_rend_circuits(smartlist_t *machines_sl);
void circpad_machine_client_hide_rend_circuits(smartlist_t *machines_sl);
#ifdef CIRCUITPADDING_MACHINES_PRIVATE
/** State of the padding machines that actually sends padding */
#define CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP CIRCPAD_STATE_BURST
/** Constants defining the amount of padding that a machine will send to hide
* HS circuits. The actual value is sampled uniformly random between the
* min/max values.
*/
/** Minimum number of relay-side padding cells to be sent by this machine */
#define INTRO_MACHINE_MINIMUM_PADDING 7
/** Maximum number of relay-side padding cells to be sent by this machine.
* The actual value will be sampled between the min and max.*/
#define INTRO_MACHINE_MAXIMUM_PADDING 10
#endif
#endif

View file

@ -1,6 +1,7 @@
#define TOR_CHANNEL_INTERNAL_
#define TOR_TIMERS_PRIVATE
#define CIRCUITPADDING_PRIVATE
#define CIRCUITPADDING_MACHINES_PRIVATE
#define NETWORKSTATUS_PRIVATE
#define CRYPT_PATH_PRIVATE
@ -19,6 +20,7 @@
#include "core/or/circuitlist.h"
#include "core/or/circuitbuild.h"
#include "core/or/circuitpadding.h"
#include "core/or/circuitpadding_machines.h"
#include "core/mainloop/netstatus.h"
#include "core/crypto/relay_crypto.h"
#include "core/or/protover.h"
@ -112,6 +114,15 @@ node_get_by_id_mock(const char *identity_digest)
return NULL;
}
static const node_t *
circuit_get_nth_node_mock(origin_circuit_t *circ, int hop)
{
(void) circ;
(void) hop;
return &padding_node;
}
static or_circuit_t *
new_fake_orcirc(channel_t *nchan, channel_t *pchan)
{
@ -415,6 +426,8 @@ helper_create_basic_machine(void)
/* Start, burst */
circpad_machine_states_init(&circ_client_machine, 2);
circ_client_machine.name = "basic";
circ_client_machine.states[CIRCPAD_STATE_START].
next_state[CIRCPAD_EVENT_NONPADDING_RECV] = CIRCPAD_STATE_BURST;
circ_client_machine.states[CIRCPAD_STATE_START].use_rtt_estimate = 1;
@ -1759,7 +1772,7 @@ helper_create_conditional_machines(void)
add->conditions.state_mask = CIRCPAD_CIRC_BUILDING|
CIRCPAD_CIRC_NO_STREAMS|CIRCPAD_CIRC_HAS_RELAY_EARLY;
add->conditions.purpose_mask = CIRCPAD_PURPOSE_ALL;
register_padding_machine(add, origin_padding_machines);
circpad_register_padding_machine(add, origin_padding_machines);
add = helper_create_conditional_machine();
add->machine_num = 3;
@ -1778,15 +1791,15 @@ helper_create_conditional_machines(void)
add->conditions.state_mask = CIRCPAD_CIRC_OPENED|
CIRCPAD_CIRC_STREAMS|CIRCPAD_CIRC_HAS_NO_RELAY_EARLY;
add->conditions.purpose_mask = CIRCPAD_PURPOSE_ALL;
register_padding_machine(add, origin_padding_machines);
circpad_register_padding_machine(add, origin_padding_machines);
add = helper_create_conditional_machine();
add->machine_num = 2;
register_padding_machine(add, relay_padding_machines);
circpad_register_padding_machine(add, relay_padding_machines);
add = helper_create_conditional_machine();
add->machine_num = 3;
register_padding_machine(add, relay_padding_machines);
circpad_register_padding_machine(add, relay_padding_machines);
}
void
@ -2650,8 +2663,8 @@ test_circuitpadding_reduce_disable(void *arg)
simulate_single_hop_extend(client_side, relay_side, 1);
/* Verify that machine #0 is added */
tt_int_op(client_side->padding_machine[0]->machine_num, OP_EQ, 0);
tt_int_op(relay_side->padding_machine[0]->machine_num, OP_EQ, 0);
tt_int_op(client_side->padding_machine[0]->machine_num, OP_EQ, 2);
tt_int_op(relay_side->padding_machine[0]->machine_num, OP_EQ, 2);
tt_int_op(
circpad_machine_reached_padding_limit(client_side->padding_info[0]),
@ -2696,8 +2709,8 @@ test_circuitpadding_reduce_disable(void *arg)
simulate_single_hop_extend(client_side, relay_side, 1);
/* Verify that machine #0 is added */
tt_int_op(client_side->padding_machine[0]->machine_num, OP_EQ, 0);
tt_int_op(relay_side->padding_machine[0]->machine_num, OP_EQ, 0);
tt_int_op(client_side->padding_machine[0]->machine_num, OP_EQ, 2);
tt_int_op(relay_side->padding_machine[0]->machine_num, OP_EQ, 2);
tt_int_op(
circpad_machine_reached_padding_limit(client_side->padding_info[0]),
@ -2917,6 +2930,199 @@ test_circuitpadding_manage_circuit_lifetime(void *arg)
UNMOCK(tor_gettimeofday);
}
/** Helper for the test_circuitpadding_hs_machines test:
*
* - Create a client and relay circuit.
* - Setup right circuit purpose and attach a machine to the client circuit.
* - Verify that state transitions work as intended and state length gets
* enforced.
*
* This function is able to do this test both for intro and rend circuits
* depending on the value of <b>test_intro_circs</b>.
*/
static void
helper_test_hs_machines(bool test_intro_circs)
{
/* Setup the circuits */
origin_circuit_t *origin_client_side = origin_circuit_new();
client_side = TO_CIRCUIT(origin_client_side);
client_side->purpose = CIRCUIT_PURPOSE_C_GENERAL;
dummy_channel.cmux = circuitmux_alloc();
relay_side = TO_CIRCUIT(new_fake_orcirc(&dummy_channel, &dummy_channel));
relay_side->purpose = CIRCUIT_PURPOSE_OR;
/* extend the client circ to two hops */
simulate_single_hop_extend(client_side, relay_side, 1);
simulate_single_hop_extend(client_side, relay_side, 1);
/* machines only apply on opened circuits */
origin_client_side->has_opened = 1;
/************************************/
/* Attaching the client machine now won't work here because of a wrong
* purpose */
tt_assert(!client_side->padding_machine[0]);
circpad_add_matching_machines(origin_client_side, origin_padding_machines);
tt_assert(!client_side->padding_machine[0]);
/* Change the purpose, see the machine getting attached */
client_side->purpose = test_intro_circs ?
CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT : CIRCUIT_PURPOSE_C_REND_JOINED;
circpad_add_matching_machines(origin_client_side, origin_padding_machines);
tt_ptr_op(client_side->padding_info[0], OP_NE, NULL);
tt_ptr_op(client_side->padding_machine[0], OP_NE, NULL);
tt_ptr_op(relay_side->padding_info[0], OP_NE, NULL);
tt_ptr_op(relay_side->padding_machine[0], OP_NE, NULL);
/* Verify that the right machine is attached */
tt_str_op(client_side->padding_machine[0]->name, OP_EQ,
test_intro_circs ? "client_ip_circ" : "client_rp_circ");
tt_str_op(relay_side->padding_machine[0]->name, OP_EQ,
test_intro_circs ? "relay_ip_circ": "relay_rp_circ");
/***********************************/
/* Intro machines are at START state, but rend machines have already skipped
* to OBFUSCATE_CIRC_SETUP because of the sent PADDING_NEGOTIATE. */
tt_int_op(client_side->padding_info[0]->current_state, OP_EQ,
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP);
tt_int_op(relay_side->padding_info[0]->current_state, OP_EQ,
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP);
/*Send non-padding to move the machines from START to OBFUSCATE_CIRC_SETUP */
circpad_cell_event_nonpadding_received(client_side);
circpad_cell_event_nonpadding_received(relay_side);
tt_int_op(client_side->padding_info[0]->current_state, OP_EQ,
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP);
tt_int_op(relay_side->padding_info[0]->current_state, OP_EQ,
CIRCPAD_STATE_OBFUSCATE_CIRC_SETUP);
/* Check that the state lengths have been sampled and are within range */
circpad_machine_runtime_t *client_machine_runtime =
client_side->padding_info[0];
circpad_machine_runtime_t *relay_machine_runtime =
relay_side->padding_info[0];
if (test_intro_circs) {
/* on the client side, we don't send any padding so
* state length is not set */
tt_int_op(client_machine_runtime->state_length, OP_EQ, -1);
/* relay side has state limits. check them */
tt_int_op(relay_machine_runtime->state_length, OP_GE,
INTRO_MACHINE_MINIMUM_PADDING);
tt_int_op(relay_machine_runtime->state_length, OP_LT,
INTRO_MACHINE_MAXIMUM_PADDING);
} else {
tt_int_op(client_machine_runtime->state_length, OP_EQ, 1);
tt_int_op(relay_machine_runtime->state_length, OP_EQ, 1);
}
if (test_intro_circs) {
int i;
/* Send state_length worth of padding from the relay and see that the
* client state goes to END */
for (i = (int) relay_machine_runtime->state_length ; i > 0 ; i--) {
circpad_send_padding_cell_for_callback(relay_machine_runtime);
}
/* See that the machine has been teared down after all the length has been
* exhausted (the padding info should now be null on both sides) */
tt_ptr_op(relay_side->padding_info[0], OP_EQ, NULL);
tt_ptr_op(client_side->padding_info[0], OP_EQ, NULL);
} else {
int i;
/* Send state_length worth of padding and see that the state goes to END */
for (i = (int) client_machine_runtime->state_length ; i > 0 ; i--) {
circpad_send_padding_cell_for_callback(client_machine_runtime);
}
/* See that the machine has been teared down after all the length has been
* exhausted. */
tt_int_op(client_side->padding_info[0]->current_state, OP_EQ,
CIRCPAD_STATE_END);
}
done:
free_fake_orcirc(relay_side);
circuitmux_detach_all_circuits(dummy_channel.cmux, NULL);
circuitmux_free(dummy_channel.cmux);
free_fake_origin_circuit(TO_ORIGIN_CIRCUIT(client_side));
}
/** Test that the HS circuit padding machines work as intended. */
static void
test_circuitpadding_hs_machines(void *arg)
{
(void)arg;
/* Test logic:
*
* 1) Register the HS machines, which aim to hide the presense of
* onion service traffic on the client-side
*
* 2) Call helper_test_hs_machines() to perform tests for the intro circuit
* machines and for the rend circuit machines.
*/
MOCK(circuitmux_attach_circuit, circuitmux_attach_circuit_mock);
MOCK(circuit_package_relay_cell, circuit_package_relay_cell_mock);
MOCK(circuit_get_nth_node, circuit_get_nth_node_mock);
MOCK(circpad_machine_schedule_padding,circpad_machine_schedule_padding_mock);
origin_padding_machines = smartlist_new();
relay_padding_machines = smartlist_new();
nodes_init();
monotime_init();
monotime_enable_test_mocking();
monotime_set_mock_time_nsec(1*TOR_NSEC_PER_USEC);
monotime_coarse_set_mock_time_nsec(1*TOR_NSEC_PER_USEC);
curr_mocked_time = 1*TOR_NSEC_PER_USEC;
timers_initialize();
/* This is needed so that we are not considered to be dormant */
note_user_activity(20);
/************************************/
/* Register the HS machines */
circpad_machine_client_hide_intro_circuits(origin_padding_machines);
circpad_machine_client_hide_rend_circuits(origin_padding_machines);
circpad_machine_relay_hide_intro_circuits(relay_padding_machines);
circpad_machine_relay_hide_rend_circuits(relay_padding_machines);
/***********************************/
/* Do the tests for the intro circuit machines */
helper_test_hs_machines(true);
/* Do the tests for the rend circuit machines */
helper_test_hs_machines(false);
timers_shutdown();
monotime_disable_test_mocking();
SMARTLIST_FOREACH_BEGIN(origin_padding_machines,
circpad_machine_spec_t *, m) {
machine_spec_free(m);
} SMARTLIST_FOREACH_END(m);
SMARTLIST_FOREACH_BEGIN(relay_padding_machines,
circpad_machine_spec_t *, m) {
machine_spec_free(m);
} SMARTLIST_FOREACH_END(m);
smartlist_free(origin_padding_machines);
smartlist_free(relay_padding_machines);
UNMOCK(circuitmux_attach_circuit);
UNMOCK(circuit_package_relay_cell);
UNMOCK(circuit_get_nth_node);
UNMOCK(circpad_machine_schedule_padding);
}
#define TEST_CIRCUITPADDING(name, flags) \
{ #name, test_##name, (flags), NULL, NULL }
@ -2939,5 +3145,6 @@ struct testcase_t circuitpadding_tests[] = {
TEST_CIRCUITPADDING(circuitpadding_closest_token_removal_usec, TT_FORK),
TEST_CIRCUITPADDING(circuitpadding_token_removal_exact, TT_FORK),
TEST_CIRCUITPADDING(circuitpadding_manage_circuit_lifetime, TT_FORK),
TEST_CIRCUITPADDING(circuitpadding_hs_machines, TT_FORK),
END_OF_TESTCASES
};