From 22f2f13f81407cffd46d5b17eca4bcead347fe58 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 11:41:37 -0500 Subject: [PATCH 01/80] prop271: make entry_guard_t mostly-private The entry_guard_t structure should really be opaque, so that we can change its contents and have the rest of Tor not care. This commit makes it "mostly opaque" -- circpathbias.c can still see inside it. (I'm making circpathbias.c exempt since it's the only part of Tor outside of entrynodes.c that made serious use of entry_guard_t internals.) --- src/or/circpathbias.c | 3 +++ src/or/circuitbuild.c | 2 +- src/or/entrynodes.c | 9 +++++++++ src/or/entrynodes.h | 18 ++++++++++++------ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 6ee69aac1e..2968607f1b 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -21,6 +21,9 @@ * each guard, and stored persistently in the state file. */ +/* XXXX prop271 I would like to remove this. */ +#define ENTRYNODES_EXPOSE_STRUCT + #include "or.h" #include "channel.h" #include "circpathbias.h" diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 0881f231aa..2998f5c602 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2238,7 +2238,7 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) )) { SMARTLIST_FOREACH(get_entry_guards(), const entry_guard_t *, entry, { - if ((node = node_get_by_id(entry->identity))) { + if ((node = entry_guard_find_node(entry))) { nodelist_add_node_and_family(excluded, node); } }); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index b3fa31df7b..434f2f6222 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -405,6 +405,15 @@ entry_guard_get_by_id_digest_for_guard_selection(guard_selection_t *gs, return NULL; } +/** Return the node_t associated with a single entry_guard_t. May + * return NULL if the guard is not currently in the consensus. */ +const node_t * +entry_guard_find_node(const entry_guard_t *guard) +{ + tor_assert(guard); + return node_get_by_id(guard->identity); +} + /** If digest matches the identity of any node in the * entry_guards list for the default guard selection state, return that node. Else return NULL. */ diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 00f96916b6..7f5a9110c9 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -12,18 +12,18 @@ #ifndef TOR_ENTRYNODES_H #define TOR_ENTRYNODES_H -#if 1 -/* XXXX NM I would prefer that all of this stuff be private to - * entrynodes.c. */ - /* Forward declare for guard_selection_t; entrynodes.c has the real struct */ typedef struct guard_selection_s guard_selection_t; +/* Forward declare for entry_guard_t; the real declaration is private. */ +typedef struct entry_guard_t entry_guard_t; + +#if defined(ENTRYNODES_PRIVATE) || defined(ENTRYNODES_EXPOSE_STRUCT) /** An entry_guard_t represents our information about a chosen long-term * first hop, known as a "helper" node in the literature. We can't just * use a node_t, since we want to remember these even when we * don't have any directory info. */ -typedef struct entry_guard_t { +struct entry_guard_t { char nickname[MAX_NICKNAME_LEN+1]; char identity[DIGEST_LEN]; time_t chosen_on_date; /**< Approximately when was this guard added? @@ -80,8 +80,12 @@ typedef struct entry_guard_t { double use_successes; /**< Number of successfully used circuits using * this guard as first hop. */ /**@}*/ -} entry_guard_t; +}; +#endif +#if 1 +/* XXXX NM I would prefer that all of this stuff be private to + * entrynodes.c. */ entry_guard_t *entry_guard_get_by_id_digest_for_guard_selection( guard_selection_t *gs, const char *digest); entry_guard_t *entry_guard_get_by_id_digest(const char *digest); @@ -98,6 +102,8 @@ int num_live_entry_guards(int for_directory); #endif +const node_t *entry_guard_find_node(const entry_guard_t *guard); + #ifdef ENTRYNODES_PRIVATE STATIC const node_t *add_an_entry_guard(guard_selection_t *gs, const node_t *chosen, From be447bc7700bc91c3b0f2475c06d1e9e64c90804 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 11:51:38 -0500 Subject: [PATCH 02/80] Move path-bias fields into a separate structure (Other than the field movement, the code changes here are just search-and-replace) --- src/or/circpathbias.c | 216 ++++++++++++++++++------------------- src/or/entrynodes.c | 52 ++++----- src/or/entrynodes.h | 69 ++++++------ src/test/test_entrynodes.c | 24 ++--- 4 files changed, 181 insertions(+), 180 deletions(-) diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 2968607f1b..6e2589caa2 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -58,14 +58,14 @@ entry_guard_inc_circ_attempt_count(entry_guard_t *guard) pathbias_measure_close_rate(guard); - if (guard->path_bias_disabled) + if (guard->pb.path_bias_disabled) return -1; pathbias_scale_close_rates(guard); - guard->circ_attempts++; + guard->pb.circ_attempts++; log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, guard->nickname, + guard->pb.circ_successes, guard->pb.circ_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); return 0; } @@ -518,11 +518,11 @@ pathbias_count_build_success(origin_circuit_t *circ) if (guard) { if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) { circ->path_state = PATH_STATE_BUILD_SUCCEEDED; - guard->circ_successes++; + guard->pb.circ_successes++; entry_guards_changed(); log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, + guard->pb.circ_successes, guard->pb.circ_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } else { if ((rate_msg = rate_limit_log(&success_notice_limit, @@ -538,10 +538,10 @@ pathbias_count_build_success(origin_circuit_t *circ) } } - if (guard->circ_attempts < guard->circ_successes) { + if (guard->pb.circ_attempts < guard->pb.circ_successes) { log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) " "for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, + guard->pb.circ_successes, guard->pb.circ_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to @@ -604,13 +604,13 @@ pathbias_count_use_attempt(origin_circuit_t *circ) if (guard) { pathbias_measure_use_rate(guard); pathbias_scale_use_rates(guard); - guard->use_attempts++; + guard->pb.use_attempts++; entry_guards_changed(); log_debug(LD_CIRC, "Marked circuit %d (%f/%f) as used for guard %s ($%s).", circ->global_identifier, - guard->use_successes, guard->use_attempts, + guard->pb.use_successes, guard->pb.use_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } @@ -713,21 +713,21 @@ pathbias_count_use_success(origin_circuit_t *circ) guard = entry_guard_get_by_id_digest( circ->cpath->extend_info->identity_digest); if (guard) { - guard->use_successes++; + guard->pb.use_successes++; entry_guards_changed(); - if (guard->use_attempts < guard->use_successes) { + if (guard->pb.use_attempts < guard->pb.use_successes) { log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) " "for guard %s=%s", - guard->use_successes, guard->use_attempts, + guard->pb.use_successes, guard->pb.use_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } log_debug(LD_CIRC, "Marked circuit %d (%f/%f) as used successfully for guard " "%s ($%s).", - circ->global_identifier, guard->use_successes, - guard->use_attempts, guard->nickname, + circ->global_identifier, guard->pb.use_successes, + guard->pb.use_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } } @@ -1031,7 +1031,7 @@ pathbias_count_successful_close(origin_circuit_t *circ) if (guard) { /* In the long run: circuit_success ~= successful_circuit_close + * circ_failure + stream_failure */ - guard->successful_circuits_closed++; + guard->pb.successful_circuits_closed++; entry_guards_changed(); } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to @@ -1068,7 +1068,7 @@ pathbias_count_collapse(origin_circuit_t *circ) } if (guard) { - guard->collapsed_circuits++; + guard->pb.collapsed_circuits++; entry_guards_changed(); } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to @@ -1101,7 +1101,7 @@ pathbias_count_use_failed(origin_circuit_t *circ) } if (guard) { - guard->unusable_circuits++; + guard->pb.unusable_circuits++; entry_guards_changed(); } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to @@ -1144,7 +1144,7 @@ pathbias_count_timeout(origin_circuit_t *circ) } if (guard) { - guard->timeouts++; + guard->pb.timeouts++; entry_guards_changed(); } } @@ -1200,7 +1200,7 @@ pathbias_count_circs_in_states(entry_guard_t *guard, double pathbias_get_close_success_count(entry_guard_t *guard) { - return guard->successful_circuits_closed + + return guard->pb.successful_circuits_closed + pathbias_count_circs_in_states(guard, PATH_STATE_BUILD_SUCCEEDED, PATH_STATE_USE_SUCCEEDED); @@ -1216,7 +1216,7 @@ pathbias_get_close_success_count(entry_guard_t *guard) double pathbias_get_use_success_count(entry_guard_t *guard) { - return guard->use_successes + + return guard->pb.use_successes + pathbias_count_circs_in_states(guard, PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED); @@ -1235,15 +1235,15 @@ pathbias_measure_use_rate(entry_guard_t *guard) { const or_options_t *options = get_options(); - if (guard->use_attempts > pathbias_get_min_use(options)) { + if (guard->pb.use_attempts > pathbias_get_min_use(options)) { /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_use_success_count(guard)/guard->use_attempts + if (pathbias_get_use_success_count(guard)/guard->pb.use_attempts < pathbias_get_extreme_use_rate(options)) { /* Dropping is currently disabled by default. */ if (pathbias_get_dropguards(options)) { - if (!guard->path_bias_disabled) { + if (!guard->pb.path_bias_disabled) { log_warn(LD_CIRC, "Your Guard %s ($%s) is failing to carry an extremely large " "amount of stream on its circuits. " @@ -1255,21 +1255,21 @@ pathbias_measure_use_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), + tor_lround(guard->pb.use_attempts), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), + tor_lround(guard->pb.circ_attempts), + tor_lround(guard->pb.circ_successes), + tor_lround(guard->pb.unusable_circuits), + tor_lround(guard->pb.collapsed_circuits), + tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); - guard->path_bias_disabled = 1; + guard->pb.path_bias_disabled = 1; guard->bad_since = approx_time(); entry_guards_changed(); return; } - } else if (!guard->path_bias_use_extreme) { - guard->path_bias_use_extreme = 1; + } else if (!guard->pb.path_bias_use_extreme) { + guard->pb.path_bias_use_extreme = 1; log_warn(LD_CIRC, "Your Guard %s ($%s) is failing to carry an extremely large " "amount of streams on its circuits. " @@ -1281,19 +1281,19 @@ pathbias_measure_use_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), + tor_lround(guard->pb.use_attempts), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), + tor_lround(guard->pb.circ_attempts), + tor_lround(guard->pb.circ_successes), + tor_lround(guard->pb.unusable_circuits), + tor_lround(guard->pb.collapsed_circuits), + tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } - } else if (pathbias_get_use_success_count(guard)/guard->use_attempts + } else if (pathbias_get_use_success_count(guard)/guard->pb.use_attempts < pathbias_get_notice_use_rate(options)) { - if (!guard->path_bias_use_noticed) { - guard->path_bias_use_noticed = 1; + if (!guard->pb.path_bias_use_noticed) { + guard->pb.path_bias_use_noticed = 1; log_notice(LD_CIRC, "Your Guard %s ($%s) is failing to carry more streams on its " "circuits than usual. " @@ -1305,13 +1305,13 @@ pathbias_measure_use_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), + tor_lround(guard->pb.use_attempts), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), + tor_lround(guard->pb.circ_attempts), + tor_lround(guard->pb.circ_successes), + tor_lround(guard->pb.unusable_circuits), + tor_lround(guard->pb.collapsed_circuits), + tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } } @@ -1341,15 +1341,15 @@ pathbias_measure_close_rate(entry_guard_t *guard) { const or_options_t *options = get_options(); - if (guard->circ_attempts > pathbias_get_min_circs(options)) { + if (guard->pb.circ_attempts > pathbias_get_min_circs(options)) { /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_close_success_count(guard)/guard->circ_attempts + if (pathbias_get_close_success_count(guard)/guard->pb.circ_attempts < pathbias_get_extreme_rate(options)) { /* Dropping is currently disabled by default. */ if (pathbias_get_dropguards(options)) { - if (!guard->path_bias_disabled) { + if (!guard->pb.path_bias_disabled) { log_warn(LD_CIRC, "Your Guard %s ($%s) is failing an extremely large " "amount of circuits. " @@ -1361,21 +1361,21 @@ pathbias_measure_close_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), + tor_lround(guard->pb.circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), + tor_lround(guard->pb.use_attempts), + tor_lround(guard->pb.circ_successes), + tor_lround(guard->pb.unusable_circuits), + tor_lround(guard->pb.collapsed_circuits), + tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); - guard->path_bias_disabled = 1; + guard->pb.path_bias_disabled = 1; guard->bad_since = approx_time(); entry_guards_changed(); return; } - } else if (!guard->path_bias_extreme) { - guard->path_bias_extreme = 1; + } else if (!guard->pb.path_bias_extreme) { + guard->pb.path_bias_extreme = 1; log_warn(LD_CIRC, "Your Guard %s ($%s) is failing an extremely large " "amount of circuits. " @@ -1387,19 +1387,19 @@ pathbias_measure_close_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), + tor_lround(guard->pb.circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), + tor_lround(guard->pb.use_attempts), + tor_lround(guard->pb.circ_successes), + tor_lround(guard->pb.unusable_circuits), + tor_lround(guard->pb.collapsed_circuits), + tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } - } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts + } else if (pathbias_get_close_success_count(guard)/guard->pb.circ_attempts < pathbias_get_warn_rate(options)) { - if (!guard->path_bias_warned) { - guard->path_bias_warned = 1; + if (!guard->pb.path_bias_warned) { + guard->pb.path_bias_warned = 1; log_warn(LD_CIRC, "Your Guard %s ($%s) is failing a very large " "amount of circuits. " @@ -1412,19 +1412,19 @@ pathbias_measure_close_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), + tor_lround(guard->pb.circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), + tor_lround(guard->pb.use_attempts), + tor_lround(guard->pb.circ_successes), + tor_lround(guard->pb.unusable_circuits), + tor_lround(guard->pb.collapsed_circuits), + tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } - } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts + } else if (pathbias_get_close_success_count(guard)/guard->pb.circ_attempts < pathbias_get_notice_rate(options)) { - if (!guard->path_bias_noticed) { - guard->path_bias_noticed = 1; + if (!guard->pb.path_bias_noticed) { + guard->pb.path_bias_noticed = 1; log_notice(LD_CIRC, "Your Guard %s ($%s) is failing more circuits than " "usual. " @@ -1435,13 +1435,13 @@ pathbias_measure_close_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->circ_attempts), + tor_lround(guard->pb.circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->use_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), + tor_lround(guard->pb.use_attempts), + tor_lround(guard->pb.circ_successes), + tor_lround(guard->pb.unusable_circuits), + tor_lround(guard->pb.collapsed_circuits), + tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } } @@ -1463,7 +1463,7 @@ pathbias_scale_close_rates(entry_guard_t *guard) const or_options_t *options = get_options(); /* If we get a ton of circuits, just scale everything down */ - if (guard->circ_attempts > pathbias_get_scale_threshold(options)) { + if (guard->pb.circ_attempts > pathbias_get_scale_threshold(options)) { double scale_ratio = pathbias_get_scale_ratio(options); int opened_attempts = pathbias_count_circs_in_states(guard, PATH_STATE_BUILD_ATTEMPTED, PATH_STATE_BUILD_ATTEMPTED); @@ -1471,36 +1471,36 @@ pathbias_scale_close_rates(entry_guard_t *guard) PATH_STATE_BUILD_SUCCEEDED, PATH_STATE_USE_FAILED); /* Verify that the counts are sane before and after scaling */ - int counts_are_sane = (guard->circ_attempts >= guard->circ_successes); + int counts_are_sane = (guard->pb.circ_attempts >= guard->pb.circ_successes); - guard->circ_attempts -= (opened_attempts+opened_built); - guard->circ_successes -= opened_built; + guard->pb.circ_attempts -= (opened_attempts+opened_built); + guard->pb.circ_successes -= opened_built; - guard->circ_attempts *= scale_ratio; - guard->circ_successes *= scale_ratio; - guard->timeouts *= scale_ratio; - guard->successful_circuits_closed *= scale_ratio; - guard->collapsed_circuits *= scale_ratio; - guard->unusable_circuits *= scale_ratio; + guard->pb.circ_attempts *= scale_ratio; + guard->pb.circ_successes *= scale_ratio; + guard->pb.timeouts *= scale_ratio; + guard->pb.successful_circuits_closed *= scale_ratio; + guard->pb.collapsed_circuits *= scale_ratio; + guard->pb.unusable_circuits *= scale_ratio; - guard->circ_attempts += (opened_attempts+opened_built); - guard->circ_successes += opened_built; + guard->pb.circ_attempts += (opened_attempts+opened_built); + guard->pb.circ_successes += opened_built; entry_guards_changed(); log_info(LD_CIRC, "Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard " "%s ($%s)", - guard->circ_successes, guard->successful_circuits_closed, - guard->circ_attempts, opened_built, opened_attempts, + guard->pb.circ_successes, guard->pb.successful_circuits_closed, + guard->pb.circ_attempts, opened_built, opened_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); /* Have the counts just become invalid by this scaling attempt? */ - if (counts_are_sane && guard->circ_attempts < guard->circ_successes) { + if (counts_are_sane && guard->pb.circ_attempts < guard->pb.circ_successes) { log_notice(LD_BUG, "Scaling has mangled pathbias counts to %f/%f (%d/%d open) " "for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, opened_built, + guard->pb.circ_successes, guard->pb.circ_attempts, opened_built, opened_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } @@ -1522,31 +1522,31 @@ pathbias_scale_use_rates(entry_guard_t *guard) const or_options_t *options = get_options(); /* If we get a ton of circuits, just scale everything down */ - if (guard->use_attempts > pathbias_get_scale_use_threshold(options)) { + if (guard->pb.use_attempts > pathbias_get_scale_use_threshold(options)) { double scale_ratio = pathbias_get_scale_ratio(options); int opened_attempts = pathbias_count_circs_in_states(guard, PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED); /* Verify that the counts are sane before and after scaling */ - int counts_are_sane = (guard->use_attempts >= guard->use_successes); + int counts_are_sane = (guard->pb.use_attempts >= guard->pb.use_successes); - guard->use_attempts -= opened_attempts; + guard->pb.use_attempts -= opened_attempts; - guard->use_attempts *= scale_ratio; - guard->use_successes *= scale_ratio; + guard->pb.use_attempts *= scale_ratio; + guard->pb.use_successes *= scale_ratio; - guard->use_attempts += opened_attempts; + guard->pb.use_attempts += opened_attempts; log_info(LD_CIRC, "Scaled pathbias use counts to %f/%f (%d open) for guard %s ($%s)", - guard->use_successes, guard->use_attempts, opened_attempts, + guard->pb.use_successes, guard->pb.use_attempts, opened_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); /* Have the counts just become invalid by this scaling attempt? */ - if (counts_are_sane && guard->use_attempts < guard->use_successes) { + if (counts_are_sane && guard->pb.use_attempts < guard->pb.use_successes) { log_notice(LD_BUG, "Scaling has mangled pathbias usage counts to %f/%f " "(%d open) for guard %s ($%s)", - guard->circ_successes, guard->circ_attempts, + guard->pb.circ_successes, guard->pb.circ_attempts, opened_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 434f2f6222..c1940a19e8 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -192,7 +192,7 @@ entry_guard_set_status(entry_guard_t *e, const node_t *node, /* We only care about OR connection connectivity for entry guards. */ else if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) *reason = "unreachable by config"; - else if (e->path_bias_disabled) + else if (e->pb.path_bias_disabled) *reason = "path-biased"; if (*reason && ! e->bad_since) { @@ -297,7 +297,7 @@ entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags, tor_assert(msg); - if (e->path_bias_disabled) { + if (e->pb.path_bias_disabled) { *msg = "path-biased"; return NULL; } @@ -748,7 +748,7 @@ remove_dead_entry_guards(guard_selection_t *gs, time_t now) for (i = 0; i < smartlist_len(gs->chosen_entry_guards); ) { entry_guard_t *entry = smartlist_get(gs->chosen_entry_guards, i); if (entry->bad_since && - ! entry->path_bias_disabled && + ! entry->pb.path_bias_disabled && entry->bad_since + ENTRY_GUARD_REMOVE_AFTER < now) { base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); @@ -1534,22 +1534,22 @@ entry_guards_parse_state_for_guard_selection( success_cnt = use_cnt; } - node->use_attempts = use_cnt; - node->use_successes = success_cnt; + node->pb.use_attempts = use_cnt; + node->pb.use_successes = success_cnt; log_info(LD_GENERAL, "Read %f/%f path use bias for node %s", - node->use_successes, node->use_attempts, node->nickname); + node->pb.use_successes, node->pb.use_attempts, node->nickname); /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_use_success_count(node)/node->use_attempts + if (pathbias_get_use_success_count(node)/node->pb.use_attempts < pathbias_get_extreme_use_rate(options) && pathbias_get_dropguards(options)) { - node->path_bias_disabled = 1; + node->pb.path_bias_disabled = 1; log_info(LD_GENERAL, "Path use bias is too high (%f/%f); disabling node %s", - node->circ_successes, node->circ_attempts, node->nickname); + node->pb.circ_successes, node->pb.circ_attempts, node->nickname); } } else if (!strcasecmp(line->key, "EntryGuardPathBias")) { const or_options_t *options = get_options(); @@ -1599,26 +1599,26 @@ entry_guards_parse_state_for_guard_selection( success_cnt = hop_cnt; } - node->circ_attempts = hop_cnt; - node->circ_successes = success_cnt; + node->pb.circ_attempts = hop_cnt; + node->pb.circ_successes = success_cnt; - node->successful_circuits_closed = successful_closed; - node->timeouts = timeouts; - node->collapsed_circuits = collapsed; - node->unusable_circuits = unusable; + node->pb.successful_circuits_closed = successful_closed; + node->pb.timeouts = timeouts; + node->pb.collapsed_circuits = collapsed; + node->pb.unusable_circuits = unusable; log_info(LD_GENERAL, "Read %f/%f path bias for node %s", - node->circ_successes, node->circ_attempts, node->nickname); + node->pb.circ_successes, node->pb.circ_attempts, node->nickname); /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_close_success_count(node)/node->circ_attempts + if (pathbias_get_close_success_count(node)/node->pb.circ_attempts < pathbias_get_extreme_rate(options) && pathbias_get_dropguards(options)) { - node->path_bias_disabled = 1; + node->pb.path_bias_disabled = 1; log_info(LD_GENERAL, "Path bias is too high (%f/%f); disabling node %s", - node->circ_successes, node->circ_attempts, node->nickname); + node->pb.circ_successes, node->pb.circ_attempts, node->nickname); } } else { @@ -1644,7 +1644,7 @@ entry_guards_parse_state_for_guard_selection( e->chosen_by_version = tor_strdup(state_version); } } - if (e->path_bias_disabled && !e->bad_since) + if (e->pb.path_bias_disabled && !e->bad_since) e->bad_since = time(NULL); } SMARTLIST_FOREACH_END(e); @@ -1788,25 +1788,25 @@ entry_guards_update_state(or_state_t *state) d, e->chosen_by_version, t); next = &(line->next); } - if (e->circ_attempts > 0) { + if (e->pb.circ_attempts > 0) { *next = line = tor_malloc_zero(sizeof(config_line_t)); line->key = tor_strdup("EntryGuardPathBias"); /* In the long run: circuit_success ~= successful_circuit_close + * collapsed_circuits + * unusable_circuits */ tor_asprintf(&line->value, "%f %f %f %f %f %f", - e->circ_attempts, e->circ_successes, + e->pb.circ_attempts, e->pb.circ_successes, pathbias_get_close_success_count(e), - e->collapsed_circuits, - e->unusable_circuits, e->timeouts); + e->pb.collapsed_circuits, + e->pb.unusable_circuits, e->pb.timeouts); next = &(line->next); } - if (e->use_attempts > 0) { + if (e->pb.use_attempts > 0) { *next = line = tor_malloc_zero(sizeof(config_line_t)); line->key = tor_strdup("EntryGuardPathUseBias"); tor_asprintf(&line->value, "%f %f", - e->use_attempts, + e->pb.use_attempts, pathbias_get_use_success_count(e)); next = &(line->next); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 7f5a9110c9..3320c5cf7d 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -18,22 +18,11 @@ typedef struct guard_selection_s guard_selection_t; /* Forward declare for entry_guard_t; the real declaration is private. */ typedef struct entry_guard_t entry_guard_t; -#if defined(ENTRYNODES_PRIVATE) || defined(ENTRYNODES_EXPOSE_STRUCT) -/** An entry_guard_t represents our information about a chosen long-term - * first hop, known as a "helper" node in the literature. We can't just - * use a node_t, since we want to remember these even when we - * don't have any directory info. */ -struct entry_guard_t { - char nickname[MAX_NICKNAME_LEN+1]; - char identity[DIGEST_LEN]; - time_t chosen_on_date; /**< Approximately when was this guard added? - * "0" if we don't know. */ - char *chosen_by_version; /**< What tor version added this guard? NULL - * if we don't know. */ - unsigned int made_contact : 1; /**< 0 if we have never connected to this - * router, 1 if we have. */ - unsigned int can_retry : 1; /**< Should we retry connecting to this entry, - * in spite of having it marked as unreachable?*/ +/* Information about a guard's pathbias status. + * These fields are used in circpathbias.c to try to detect entry + * nodes that are failing circuits at a suspicious frequency. + */ +typedef struct guard_pathbias_t { unsigned int path_bias_noticed : 1; /**< Did we alert the user about path * bias for this node already? */ unsigned int path_bias_warned : 1; /**< Did we alert the user about path bias @@ -46,23 +35,6 @@ struct entry_guard_t { * use bias for this node already? */ unsigned int path_bias_use_extreme : 1; /**< Did we alert the user about path * use bias for this node already? */ - unsigned int is_dir_cache : 1; /**< Is this node a directory cache? */ - time_t bad_since; /**< 0 if this guard is currently usable, or the time at - * which it was observed to become (according to the - * directory or the user configuration) unusable. */ - time_t unreachable_since; /**< 0 if we can connect to this guard, or the - * time at which we first noticed we couldn't - * connect to it. */ - time_t last_attempted; /**< 0 if we can connect to this guard, or the time - * at which we last failed to connect to it. */ - - /** - * @name circpathbias fields - * - * These fields are used in circpathbias.c to try to detect entry - * nodes that are failing circuits at a suspicious frequency. - */ - /**@{*/ double circ_attempts; /**< Number of circuits this guard has "attempted" */ double circ_successes; /**< Number of successfully built circuits using @@ -79,7 +51,36 @@ struct entry_guard_t { double use_attempts; /**< Number of circuits we tried to use with streams */ double use_successes; /**< Number of successfully used circuits using * this guard as first hop. */ - /**@}*/ +} guard_pathbias_t; + +#if defined(ENTRYNODES_PRIVATE) || defined(ENTRYNODES_EXPOSE_STRUCT) +/** An entry_guard_t represents our information about a chosen long-term + * first hop, known as a "helper" node in the literature. We can't just + * use a node_t, since we want to remember these even when we + * don't have any directory info. */ +struct entry_guard_t { + char nickname[MAX_NICKNAME_LEN+1]; + char identity[DIGEST_LEN]; + time_t chosen_on_date; /**< Approximately when was this guard added? + * "0" if we don't know. */ + char *chosen_by_version; /**< What tor version added this guard? NULL + * if we don't know. */ + unsigned int made_contact : 1; /**< 0 if we have never connected to this + * router, 1 if we have. */ + unsigned int can_retry : 1; /**< Should we retry connecting to this entry, + * in spite of having it marked as unreachable?*/ + unsigned int is_dir_cache : 1; /**< Is this node a directory cache? */ + time_t bad_since; /**< 0 if this guard is currently usable, or the time at + * which it was observed to become (according to the + * directory or the user configuration) unusable. */ + time_t unreachable_since; /**< 0 if we can connect to this guard, or the + * time at which we first noticed we couldn't + * connect to it. */ + time_t last_attempted; /**< 0 if we can connect to this guard, or the time + * at which we last failed to connect to it. */ + + /** Path bias information for this guard. */ + guard_pathbias_t pb; }; #endif diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 8e4f4061c6..781fa4d195 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -477,12 +477,12 @@ test_entry_guards_parse_state_simple(void *arg) /* The rest should be unset */ tt_assert(!e->unreachable_since); tt_assert(!e->can_retry); - tt_assert(!e->path_bias_noticed); - tt_assert(!e->path_bias_warned); - tt_assert(!e->path_bias_extreme); - tt_assert(!e->path_bias_disabled); - tt_assert(!e->path_bias_use_noticed); - tt_assert(!e->path_bias_use_extreme); + tt_assert(!e->pb.path_bias_noticed); + tt_assert(!e->pb.path_bias_warned); + tt_assert(!e->pb.path_bias_extreme); + tt_assert(!e->pb.path_bias_disabled); + tt_assert(!e->pb.path_bias_use_noticed); + tt_assert(!e->pb.path_bias_use_extreme); tt_assert(!e->last_attempted); } @@ -563,13 +563,13 @@ test_entry_guards_parse_state_pathbias(void *arg) tt_assert(!e->can_retry); /* XXX tt_double_op doesn't support equality. Cast to int for now. */ - tt_int_op((int)e->circ_attempts, OP_EQ, (int)circ_attempts); - tt_int_op((int)e->circ_successes, OP_EQ, (int)circ_successes); - tt_int_op((int)e->successful_circuits_closed, OP_EQ, + tt_int_op((int)e->pb.circ_attempts, OP_EQ, (int)circ_attempts); + tt_int_op((int)e->pb.circ_successes, OP_EQ, (int)circ_successes); + tt_int_op((int)e->pb.successful_circuits_closed, OP_EQ, (int)successful_closed); - tt_int_op((int)e->timeouts, OP_EQ, (int)timeouts); - tt_int_op((int)e->collapsed_circuits, OP_EQ, (int)collapsed); - tt_int_op((int)e->unusable_circuits, OP_EQ, (int)unusable); + tt_int_op((int)e->pb.timeouts, OP_EQ, (int)timeouts); + tt_int_op((int)e->pb.collapsed_circuits, OP_EQ, (int)collapsed); + tt_int_op((int)e->pb.unusable_circuits, OP_EQ, (int)unusable); } done: From 823357dbe4874e9726749f1d9d16d85fab949ee5 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 12:04:42 -0500 Subject: [PATCH 03/80] Add an entry_guard_describe() function This function helpfully removes all but one remaining use of an entry_guard_t private field in pathbias.c --- src/or/circpathbias.c | 82 ++++++++++++++++++-------------------- src/or/entrynodes.c | 20 ++++++++++ src/or/entrynodes.h | 3 +- src/test/test_entrynodes.c | 17 ++++++++ 4 files changed, 78 insertions(+), 44 deletions(-) diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 6e2589caa2..a2e1641d1d 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -64,9 +64,9 @@ entry_guard_inc_circ_attempt_count(entry_guard_t *guard) pathbias_scale_close_rates(guard); guard->pb.circ_attempts++; - log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", - guard->pb.circ_successes, guard->pb.circ_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); + log_info(LD_CIRC, "Got success count %f/%f for guard %s", + guard->pb.circ_successes, guard->pb.circ_attempts, + entry_guard_describe(guard)); return 0; } @@ -521,9 +521,9 @@ pathbias_count_build_success(origin_circuit_t *circ) guard->pb.circ_successes++; entry_guards_changed(); - log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)", + log_info(LD_CIRC, "Got success count %f/%f for guard %s", guard->pb.circ_successes, guard->pb.circ_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + entry_guard_describe(guard)); } else { if ((rate_msg = rate_limit_log(&success_notice_limit, approx_time()))) { @@ -540,9 +540,9 @@ pathbias_count_build_success(origin_circuit_t *circ) if (guard->pb.circ_attempts < guard->pb.circ_successes) { log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) " - "for guard %s ($%s)", + "for guard %s", guard->pb.circ_successes, guard->pb.circ_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + entry_guard_describe(guard)); } /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. @@ -608,10 +608,10 @@ pathbias_count_use_attempt(origin_circuit_t *circ) entry_guards_changed(); log_debug(LD_CIRC, - "Marked circuit %d (%f/%f) as used for guard %s ($%s).", + "Marked circuit %d (%f/%f) as used for guard %s.", circ->global_identifier, guard->pb.use_successes, guard->pb.use_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + entry_guard_describe(guard)); } circ->path_state = PATH_STATE_USE_ATTEMPTED; @@ -718,17 +718,16 @@ pathbias_count_use_success(origin_circuit_t *circ) if (guard->pb.use_attempts < guard->pb.use_successes) { log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) " - "for guard %s=%s", + "for guard %s", guard->pb.use_successes, guard->pb.use_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + entry_guard_describe(guard)); } log_debug(LD_CIRC, - "Marked circuit %d (%f/%f) as used successfully for guard " - "%s ($%s).", + "Marked circuit %d (%f/%f) as used successfully for guard %s", circ->global_identifier, guard->pb.use_successes, - guard->pb.use_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); + guard->pb.use_attempts, + entry_guard_describe(guard)); } } @@ -1245,7 +1244,7 @@ pathbias_measure_use_rate(entry_guard_t *guard) if (pathbias_get_dropguards(options)) { if (!guard->pb.path_bias_disabled) { log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing to carry an extremely large " + "Your Guard %s is failing to carry an extremely large " "amount of stream on its circuits. " "To avoid potential route manipulation attacks, Tor has " "disabled use of this guard. " @@ -1253,7 +1252,7 @@ pathbias_measure_use_rate(entry_guard_t *guard) "%ld circuits completed, %ld were unusable, %ld collapsed, " "and %ld timed out. " "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), + entry_guard_describe(guard), tor_lround(pathbias_get_use_success_count(guard)), tor_lround(guard->pb.use_attempts), tor_lround(pathbias_get_close_success_count(guard)), @@ -1264,14 +1263,13 @@ pathbias_measure_use_rate(entry_guard_t *guard) tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); guard->pb.path_bias_disabled = 1; - guard->bad_since = approx_time(); - entry_guards_changed(); + entry_guard_mark_bad(guard); return; } } else if (!guard->pb.path_bias_use_extreme) { guard->pb.path_bias_use_extreme = 1; log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing to carry an extremely large " + "Your Guard %s is failing to carry an extremely large " "amount of streams on its circuits. " "This could indicate a route manipulation attack, network " "overload, bad local network connectivity, or a bug. " @@ -1279,7 +1277,7 @@ pathbias_measure_use_rate(entry_guard_t *guard) "%ld circuits completed, %ld were unusable, %ld collapsed, " "and %ld timed out. " "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), + entry_guard_describe(guard), tor_lround(pathbias_get_use_success_count(guard)), tor_lround(guard->pb.use_attempts), tor_lround(pathbias_get_close_success_count(guard)), @@ -1295,7 +1293,7 @@ pathbias_measure_use_rate(entry_guard_t *guard) if (!guard->pb.path_bias_use_noticed) { guard->pb.path_bias_use_noticed = 1; log_notice(LD_CIRC, - "Your Guard %s ($%s) is failing to carry more streams on its " + "Your Guard %s is failing to carry more streams on its " "circuits than usual. " "Most likely this means the Tor network is overloaded " "or your network connection is poor. " @@ -1303,7 +1301,7 @@ pathbias_measure_use_rate(entry_guard_t *guard) "%ld circuits completed, %ld were unusable, %ld collapsed, " "and %ld timed out. " "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), + entry_guard_describe(guard), tor_lround(pathbias_get_use_success_count(guard)), tor_lround(guard->pb.use_attempts), tor_lround(pathbias_get_close_success_count(guard)), @@ -1351,7 +1349,7 @@ pathbias_measure_close_rate(entry_guard_t *guard) if (pathbias_get_dropguards(options)) { if (!guard->pb.path_bias_disabled) { log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing an extremely large " + "Your Guard %s is failing an extremely large " "amount of circuits. " "To avoid potential route manipulation attacks, Tor has " "disabled use of this guard. " @@ -1359,7 +1357,7 @@ pathbias_measure_close_rate(entry_guard_t *guard) "%ld circuits completed, %ld were unusable, %ld collapsed, " "and %ld timed out. " "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), + entry_guard_describe(guard), tor_lround(pathbias_get_close_success_count(guard)), tor_lround(guard->pb.circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), @@ -1370,14 +1368,13 @@ pathbias_measure_close_rate(entry_guard_t *guard) tor_lround(guard->pb.timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); guard->pb.path_bias_disabled = 1; - guard->bad_since = approx_time(); - entry_guards_changed(); + entry_guard_mark_bad(guard); return; } } else if (!guard->pb.path_bias_extreme) { guard->pb.path_bias_extreme = 1; log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing an extremely large " + "Your Guard %s is failing an extremely large " "amount of circuits. " "This could indicate a route manipulation attack, " "extreme network overload, or a bug. " @@ -1385,7 +1382,7 @@ pathbias_measure_close_rate(entry_guard_t *guard) "%ld circuits completed, %ld were unusable, %ld collapsed, " "and %ld timed out. " "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), + entry_guard_describe(guard), tor_lround(pathbias_get_close_success_count(guard)), tor_lround(guard->pb.circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), @@ -1401,7 +1398,7 @@ pathbias_measure_close_rate(entry_guard_t *guard) if (!guard->pb.path_bias_warned) { guard->pb.path_bias_warned = 1; log_warn(LD_CIRC, - "Your Guard %s ($%s) is failing a very large " + "Your Guard %s is failing a very large " "amount of circuits. " "Most likely this means the Tor network is " "overloaded, but it could also mean an attack against " @@ -1410,7 +1407,7 @@ pathbias_measure_close_rate(entry_guard_t *guard) "%ld circuits completed, %ld were unusable, %ld collapsed, " "and %ld timed out. " "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), + entry_guard_describe(guard), tor_lround(pathbias_get_close_success_count(guard)), tor_lround(guard->pb.circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), @@ -1426,14 +1423,14 @@ pathbias_measure_close_rate(entry_guard_t *guard) if (!guard->pb.path_bias_noticed) { guard->pb.path_bias_noticed = 1; log_notice(LD_CIRC, - "Your Guard %s ($%s) is failing more circuits than " + "Your Guard %s is failing more circuits than " "usual. " "Most likely this means the Tor network is overloaded. " "Success counts are %ld/%ld. Use counts are %ld/%ld. " "%ld circuits completed, %ld were unusable, %ld collapsed, " "and %ld timed out. " "For reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), + entry_guard_describe(guard), tor_lround(pathbias_get_close_success_count(guard)), tor_lround(guard->pb.circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), @@ -1490,19 +1487,19 @@ pathbias_scale_close_rates(entry_guard_t *guard) log_info(LD_CIRC, "Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard " - "%s ($%s)", + "%s", guard->pb.circ_successes, guard->pb.successful_circuits_closed, guard->pb.circ_attempts, opened_built, opened_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + entry_guard_describe(guard)); /* Have the counts just become invalid by this scaling attempt? */ if (counts_are_sane && guard->pb.circ_attempts < guard->pb.circ_successes) { log_notice(LD_BUG, "Scaling has mangled pathbias counts to %f/%f (%d/%d open) " - "for guard %s ($%s)", + "for guard %s", guard->pb.circ_successes, guard->pb.circ_attempts, opened_built, - opened_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); + opened_attempts, + entry_guard_describe(guard)); } } } @@ -1537,18 +1534,17 @@ pathbias_scale_use_rates(entry_guard_t *guard) guard->pb.use_attempts += opened_attempts; log_info(LD_CIRC, - "Scaled pathbias use counts to %f/%f (%d open) for guard %s ($%s)", + "Scaled pathbias use counts to %f/%f (%d open) for guard %s", guard->pb.use_successes, guard->pb.use_attempts, opened_attempts, - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + entry_guard_describe(guard)); /* Have the counts just become invalid by this scaling attempt? */ if (counts_are_sane && guard->pb.use_attempts < guard->pb.use_successes) { log_notice(LD_BUG, "Scaling has mangled pathbias usage counts to %f/%f " - "(%d open) for guard %s ($%s)", + "(%d open) for guard %s", guard->pb.circ_successes, guard->pb.circ_attempts, - opened_attempts, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); + opened_attempts, entry_guard_describe(guard)); } entry_guards_changed(); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index c1940a19e8..32d198a8c3 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -157,6 +157,26 @@ get_entry_guards(void) return get_entry_guards_for_guard_selection(get_guard_selection_info()); } +/** Helper: mark an entry guard as not usable. */ +void +entry_guard_mark_bad(entry_guard_t *guard) +{ + guard->bad_since = approx_time(); + entry_guards_changed(); +} + +/** Return a statically allocated human-readable description of guard + */ +const char * +entry_guard_describe(const entry_guard_t *guard) +{ + static char buf[256]; + tor_snprintf(buf, sizeof(buf), + "%s ($%s)", + guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + return buf; +} + /** Check whether the entry guard e is usable, given the directory * authorities' opinion about the router (stored in ri) and the user's * configuration (in options). Set e->bad_since diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 3320c5cf7d..97ae3ac378 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -100,10 +100,11 @@ int num_live_entry_guards_for_guard_selection( guard_selection_t *gs, int for_directory); int num_live_entry_guards(int for_directory); - #endif const node_t *entry_guard_find_node(const entry_guard_t *guard); +void entry_guard_mark_bad(entry_guard_t *guard); +const char *entry_guard_describe(const entry_guard_t *guard); #ifdef ENTRYNODES_PRIVATE STATIC const node_t *add_an_entry_guard(guard_selection_t *gs, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 781fa4d195..aa1b455ff1 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -843,6 +843,22 @@ test_node_preferred_orport(void *arg) UNMOCK(get_options); } +static void +test_entry_guard_describe(void *arg) +{ + (void)arg; + entry_guard_t g; + memset(&g, 0, sizeof(g)); + strlcpy(g.nickname, "okefenokee", sizeof(g.nickname)); + memcpy(g.identity, "theforestprimeval---", DIGEST_LEN); + + tt_str_op(entry_guard_describe(&g), OP_EQ, + "okefenokee ($746865666F726573747072696D6576616C2D2D2D)"); + + done: + ; +} + static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; @@ -876,6 +892,7 @@ struct testcase_t entrynodes_tests[] = { { "node_preferred_orport", test_node_preferred_orport, 0, NULL, NULL }, + { "entry_guard_describe", test_entry_guard_describe, 0, NULL, NULL }, END_OF_TESTCASES }; From 62477906e9b5a378bcdd7b4588253ee422ccbb9f Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 12:48:18 -0500 Subject: [PATCH 04/80] Fix remaining case of circpathbias inspecting entryguard internals --- src/or/circpathbias.c | 2 +- src/or/entrynodes.c | 7 +++++++ src/or/entrynodes.h | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index a2e1641d1d..7a9af82dd4 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -1175,7 +1175,7 @@ pathbias_count_circs_in_states(entry_guard_t *guard, if (ocirc->path_state >= from && ocirc->path_state <= to && pathbias_should_count(ocirc) && - fast_memeq(guard->identity, + fast_memeq(entry_guard_get_rsa_id_digest(guard), ocirc->cpath->extend_info->identity_digest, DIGEST_LEN)) { log_debug(LD_CIRC, "Found opened circuit %d in path_state %s", diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 32d198a8c3..1324e31789 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -177,6 +177,13 @@ entry_guard_describe(const entry_guard_t *guard) return buf; } +/** Return guard's 20-byte RSA identity digest */ +const char * +entry_guard_get_rsa_id_digest(const entry_guard_t *guard) +{ + return guard->identity; +} + /** Check whether the entry guard e is usable, given the directory * authorities' opinion about the router (stored in ri) and the user's * configuration (in options). Set e->bad_since diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 97ae3ac378..ba8cd9f649 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -104,6 +104,7 @@ int num_live_entry_guards(int for_directory); const node_t *entry_guard_find_node(const entry_guard_t *guard); void entry_guard_mark_bad(entry_guard_t *guard); +const char *entry_guard_get_rsa_id_digest(const entry_guard_t *guard); const char *entry_guard_describe(const entry_guard_t *guard); #ifdef ENTRYNODES_PRIVATE From f66f9c82e9b8aaac04dc01ee3bbcf60019864c9b Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 12:57:05 -0500 Subject: [PATCH 05/80] Make entry_guard_t opaque to circpathbias.c This was a relatively mechanical change. First, I added an accessor function for the pathbias-state field of a guard. Then I did a search-and-replace in circpathbias.c to replace "guard->pb." with "pb->". Finally, I made sure that "pb" was declared whenever it was needed. --- src/or/circpathbias.c | 249 +++++++++++++++++++++++------------------- src/or/entrynodes.c | 7 ++ src/or/entrynodes.h | 3 +- 3 files changed, 143 insertions(+), 116 deletions(-) diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 7a9af82dd4..3df68b8744 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -21,9 +21,6 @@ * each guard, and stored persistently in the state file. */ -/* XXXX prop271 I would like to remove this. */ -#define ENTRYNODES_EXPOSE_STRUCT - #include "or.h" #include "channel.h" #include "circpathbias.h" @@ -54,18 +51,20 @@ static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard); static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard) { + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + entry_guards_changed(); pathbias_measure_close_rate(guard); - if (guard->pb.path_bias_disabled) + if (pb->path_bias_disabled) return -1; pathbias_scale_close_rates(guard); - guard->pb.circ_attempts++; + pb->circ_attempts++; log_info(LD_CIRC, "Got success count %f/%f for guard %s", - guard->pb.circ_successes, guard->pb.circ_attempts, + pb->circ_successes, pb->circ_attempts, entry_guard_describe(guard)); return 0; } @@ -516,13 +515,15 @@ pathbias_count_build_success(origin_circuit_t *circ) } if (guard) { + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) { circ->path_state = PATH_STATE_BUILD_SUCCEEDED; - guard->pb.circ_successes++; + pb->circ_successes++; entry_guards_changed(); log_info(LD_CIRC, "Got success count %f/%f for guard %s", - guard->pb.circ_successes, guard->pb.circ_attempts, + pb->circ_successes, pb->circ_attempts, entry_guard_describe(guard)); } else { if ((rate_msg = rate_limit_log(&success_notice_limit, @@ -538,10 +539,10 @@ pathbias_count_build_success(origin_circuit_t *circ) } } - if (guard->pb.circ_attempts < guard->pb.circ_successes) { + if (pb->circ_attempts < pb->circ_successes) { log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) " "for guard %s", - guard->pb.circ_successes, guard->pb.circ_attempts, + pb->circ_successes, pb->circ_attempts, entry_guard_describe(guard)); } /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to @@ -585,8 +586,6 @@ pathbias_count_build_success(origin_circuit_t *circ) void pathbias_count_use_attempt(origin_circuit_t *circ) { - entry_guard_t *guard; - if (!pathbias_should_count(circ)) { return; } @@ -599,18 +598,20 @@ pathbias_count_use_attempt(origin_circuit_t *circ) circuit_purpose_to_string(circ->base_.purpose), circuit_state_to_string(circ->base_.state)); } else if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { - guard = entry_guard_get_by_id_digest( + entry_guard_t *guard = entry_guard_get_by_id_digest( circ->cpath->extend_info->identity_digest); if (guard) { + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + pathbias_measure_use_rate(guard); pathbias_scale_use_rates(guard); - guard->pb.use_attempts++; + pb->use_attempts++; entry_guards_changed(); log_debug(LD_CIRC, "Marked circuit %d (%f/%f) as used for guard %s.", circ->global_identifier, - guard->pb.use_successes, guard->pb.use_attempts, + pb->use_successes, pb->use_attempts, entry_guard_describe(guard)); } @@ -713,20 +714,22 @@ pathbias_count_use_success(origin_circuit_t *circ) guard = entry_guard_get_by_id_digest( circ->cpath->extend_info->identity_digest); if (guard) { - guard->pb.use_successes++; + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + + pb->use_successes++; entry_guards_changed(); - if (guard->pb.use_attempts < guard->pb.use_successes) { + if (pb->use_attempts < pb->use_successes) { log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) " "for guard %s", - guard->pb.use_successes, guard->pb.use_attempts, + pb->use_successes, pb->use_attempts, entry_guard_describe(guard)); } log_debug(LD_CIRC, "Marked circuit %d (%f/%f) as used successfully for guard %s", - circ->global_identifier, guard->pb.use_successes, - guard->pb.use_attempts, + circ->global_identifier, pb->use_successes, + pb->use_attempts, entry_guard_describe(guard)); } } @@ -1028,9 +1031,11 @@ pathbias_count_successful_close(origin_circuit_t *circ) } if (guard) { + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + /* In the long run: circuit_success ~= successful_circuit_close + * circ_failure + stream_failure */ - guard->pb.successful_circuits_closed++; + pb->successful_circuits_closed++; entry_guards_changed(); } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to @@ -1067,7 +1072,9 @@ pathbias_count_collapse(origin_circuit_t *circ) } if (guard) { - guard->pb.collapsed_circuits++; + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + + pb->collapsed_circuits++; entry_guards_changed(); } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to @@ -1100,7 +1107,9 @@ pathbias_count_use_failed(origin_circuit_t *circ) } if (guard) { - guard->pb.unusable_circuits++; + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + + pb->unusable_circuits++; entry_guards_changed(); } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to @@ -1143,7 +1152,9 @@ pathbias_count_timeout(origin_circuit_t *circ) } if (guard) { - guard->pb.timeouts++; + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + + pb->timeouts++; entry_guards_changed(); } } @@ -1199,7 +1210,9 @@ pathbias_count_circs_in_states(entry_guard_t *guard, double pathbias_get_close_success_count(entry_guard_t *guard) { - return guard->pb.successful_circuits_closed + + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + + return pb->successful_circuits_closed + pathbias_count_circs_in_states(guard, PATH_STATE_BUILD_SUCCEEDED, PATH_STATE_USE_SUCCEEDED); @@ -1215,7 +1228,9 @@ pathbias_get_close_success_count(entry_guard_t *guard) double pathbias_get_use_success_count(entry_guard_t *guard) { - return guard->pb.use_successes + + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + + return pb->use_successes + pathbias_count_circs_in_states(guard, PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED); @@ -1233,16 +1248,17 @@ static void pathbias_measure_use_rate(entry_guard_t *guard) { const or_options_t *options = get_options(); + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); - if (guard->pb.use_attempts > pathbias_get_min_use(options)) { + if (pb->use_attempts > pathbias_get_min_use(options)) { /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_use_success_count(guard)/guard->pb.use_attempts + if (pathbias_get_use_success_count(guard)/pb->use_attempts < pathbias_get_extreme_use_rate(options)) { /* Dropping is currently disabled by default. */ if (pathbias_get_dropguards(options)) { - if (!guard->pb.path_bias_disabled) { + if (!pb->path_bias_disabled) { log_warn(LD_CIRC, "Your Guard %s is failing to carry an extremely large " "amount of stream on its circuits. " @@ -1254,20 +1270,20 @@ pathbias_measure_use_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", entry_guard_describe(guard), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->pb.use_attempts), + tor_lround(pb->use_attempts), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->pb.circ_attempts), - tor_lround(guard->pb.circ_successes), - tor_lround(guard->pb.unusable_circuits), - tor_lround(guard->pb.collapsed_circuits), - tor_lround(guard->pb.timeouts), + tor_lround(pb->circ_attempts), + tor_lround(pb->circ_successes), + tor_lround(pb->unusable_circuits), + tor_lround(pb->collapsed_circuits), + tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); - guard->pb.path_bias_disabled = 1; + pb->path_bias_disabled = 1; entry_guard_mark_bad(guard); return; } - } else if (!guard->pb.path_bias_use_extreme) { - guard->pb.path_bias_use_extreme = 1; + } else if (!pb->path_bias_use_extreme) { + pb->path_bias_use_extreme = 1; log_warn(LD_CIRC, "Your Guard %s is failing to carry an extremely large " "amount of streams on its circuits. " @@ -1279,19 +1295,19 @@ pathbias_measure_use_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", entry_guard_describe(guard), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->pb.use_attempts), + tor_lround(pb->use_attempts), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->pb.circ_attempts), - tor_lround(guard->pb.circ_successes), - tor_lround(guard->pb.unusable_circuits), - tor_lround(guard->pb.collapsed_circuits), - tor_lround(guard->pb.timeouts), + tor_lround(pb->circ_attempts), + tor_lround(pb->circ_successes), + tor_lround(pb->unusable_circuits), + tor_lround(pb->collapsed_circuits), + tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } - } else if (pathbias_get_use_success_count(guard)/guard->pb.use_attempts + } else if (pathbias_get_use_success_count(guard)/pb->use_attempts < pathbias_get_notice_use_rate(options)) { - if (!guard->pb.path_bias_use_noticed) { - guard->pb.path_bias_use_noticed = 1; + if (!pb->path_bias_use_noticed) { + pb->path_bias_use_noticed = 1; log_notice(LD_CIRC, "Your Guard %s is failing to carry more streams on its " "circuits than usual. " @@ -1303,13 +1319,13 @@ pathbias_measure_use_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", entry_guard_describe(guard), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->pb.use_attempts), + tor_lround(pb->use_attempts), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->pb.circ_attempts), - tor_lround(guard->pb.circ_successes), - tor_lround(guard->pb.unusable_circuits), - tor_lround(guard->pb.collapsed_circuits), - tor_lround(guard->pb.timeouts), + tor_lround(pb->circ_attempts), + tor_lround(pb->circ_successes), + tor_lround(pb->unusable_circuits), + tor_lround(pb->collapsed_circuits), + tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } } @@ -1338,16 +1354,17 @@ static void pathbias_measure_close_rate(entry_guard_t *guard) { const or_options_t *options = get_options(); - - if (guard->pb.circ_attempts > pathbias_get_min_circs(options)) { + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); + + if (pb->circ_attempts > pathbias_get_min_circs(options)) { /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_close_success_count(guard)/guard->pb.circ_attempts + if (pathbias_get_close_success_count(guard)/pb->circ_attempts < pathbias_get_extreme_rate(options)) { /* Dropping is currently disabled by default. */ if (pathbias_get_dropguards(options)) { - if (!guard->pb.path_bias_disabled) { + if (!pb->path_bias_disabled) { log_warn(LD_CIRC, "Your Guard %s is failing an extremely large " "amount of circuits. " @@ -1359,20 +1376,20 @@ pathbias_measure_close_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", entry_guard_describe(guard), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->pb.circ_attempts), + tor_lround(pb->circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->pb.use_attempts), - tor_lround(guard->pb.circ_successes), - tor_lround(guard->pb.unusable_circuits), - tor_lround(guard->pb.collapsed_circuits), - tor_lround(guard->pb.timeouts), + tor_lround(pb->use_attempts), + tor_lround(pb->circ_successes), + tor_lround(pb->unusable_circuits), + tor_lround(pb->collapsed_circuits), + tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); - guard->pb.path_bias_disabled = 1; + pb->path_bias_disabled = 1; entry_guard_mark_bad(guard); return; } - } else if (!guard->pb.path_bias_extreme) { - guard->pb.path_bias_extreme = 1; + } else if (!pb->path_bias_extreme) { + pb->path_bias_extreme = 1; log_warn(LD_CIRC, "Your Guard %s is failing an extremely large " "amount of circuits. " @@ -1384,19 +1401,19 @@ pathbias_measure_close_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", entry_guard_describe(guard), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->pb.circ_attempts), + tor_lround(pb->circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->pb.use_attempts), - tor_lround(guard->pb.circ_successes), - tor_lround(guard->pb.unusable_circuits), - tor_lround(guard->pb.collapsed_circuits), - tor_lround(guard->pb.timeouts), + tor_lround(pb->use_attempts), + tor_lround(pb->circ_successes), + tor_lround(pb->unusable_circuits), + tor_lround(pb->collapsed_circuits), + tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } - } else if (pathbias_get_close_success_count(guard)/guard->pb.circ_attempts + } else if (pathbias_get_close_success_count(guard)/pb->circ_attempts < pathbias_get_warn_rate(options)) { - if (!guard->pb.path_bias_warned) { - guard->pb.path_bias_warned = 1; + if (!pb->path_bias_warned) { + pb->path_bias_warned = 1; log_warn(LD_CIRC, "Your Guard %s is failing a very large " "amount of circuits. " @@ -1409,19 +1426,19 @@ pathbias_measure_close_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", entry_guard_describe(guard), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->pb.circ_attempts), + tor_lround(pb->circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->pb.use_attempts), - tor_lround(guard->pb.circ_successes), - tor_lround(guard->pb.unusable_circuits), - tor_lround(guard->pb.collapsed_circuits), - tor_lround(guard->pb.timeouts), + tor_lround(pb->use_attempts), + tor_lround(pb->circ_successes), + tor_lround(pb->unusable_circuits), + tor_lround(pb->collapsed_circuits), + tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } - } else if (pathbias_get_close_success_count(guard)/guard->pb.circ_attempts + } else if (pathbias_get_close_success_count(guard)/pb->circ_attempts < pathbias_get_notice_rate(options)) { - if (!guard->pb.path_bias_noticed) { - guard->pb.path_bias_noticed = 1; + if (!pb->path_bias_noticed) { + pb->path_bias_noticed = 1; log_notice(LD_CIRC, "Your Guard %s is failing more circuits than " "usual. " @@ -1432,13 +1449,13 @@ pathbias_measure_close_rate(entry_guard_t *guard) "For reference, your timeout cutoff is %ld seconds.", entry_guard_describe(guard), tor_lround(pathbias_get_close_success_count(guard)), - tor_lround(guard->pb.circ_attempts), + tor_lround(pb->circ_attempts), tor_lround(pathbias_get_use_success_count(guard)), - tor_lround(guard->pb.use_attempts), - tor_lround(guard->pb.circ_successes), - tor_lround(guard->pb.unusable_circuits), - tor_lround(guard->pb.collapsed_circuits), - tor_lround(guard->pb.timeouts), + tor_lround(pb->use_attempts), + tor_lround(pb->circ_successes), + tor_lround(pb->unusable_circuits), + tor_lround(pb->collapsed_circuits), + tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); } } @@ -1458,9 +1475,10 @@ static void pathbias_scale_close_rates(entry_guard_t *guard) { const or_options_t *options = get_options(); + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); /* If we get a ton of circuits, just scale everything down */ - if (guard->pb.circ_attempts > pathbias_get_scale_threshold(options)) { + if (pb->circ_attempts > pathbias_get_scale_threshold(options)) { double scale_ratio = pathbias_get_scale_ratio(options); int opened_attempts = pathbias_count_circs_in_states(guard, PATH_STATE_BUILD_ATTEMPTED, PATH_STATE_BUILD_ATTEMPTED); @@ -1468,36 +1486,36 @@ pathbias_scale_close_rates(entry_guard_t *guard) PATH_STATE_BUILD_SUCCEEDED, PATH_STATE_USE_FAILED); /* Verify that the counts are sane before and after scaling */ - int counts_are_sane = (guard->pb.circ_attempts >= guard->pb.circ_successes); + int counts_are_sane = (pb->circ_attempts >= pb->circ_successes); - guard->pb.circ_attempts -= (opened_attempts+opened_built); - guard->pb.circ_successes -= opened_built; + pb->circ_attempts -= (opened_attempts+opened_built); + pb->circ_successes -= opened_built; - guard->pb.circ_attempts *= scale_ratio; - guard->pb.circ_successes *= scale_ratio; - guard->pb.timeouts *= scale_ratio; - guard->pb.successful_circuits_closed *= scale_ratio; - guard->pb.collapsed_circuits *= scale_ratio; - guard->pb.unusable_circuits *= scale_ratio; + pb->circ_attempts *= scale_ratio; + pb->circ_successes *= scale_ratio; + pb->timeouts *= scale_ratio; + pb->successful_circuits_closed *= scale_ratio; + pb->collapsed_circuits *= scale_ratio; + pb->unusable_circuits *= scale_ratio; - guard->pb.circ_attempts += (opened_attempts+opened_built); - guard->pb.circ_successes += opened_built; + pb->circ_attempts += (opened_attempts+opened_built); + pb->circ_successes += opened_built; entry_guards_changed(); log_info(LD_CIRC, "Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard " "%s", - guard->pb.circ_successes, guard->pb.successful_circuits_closed, - guard->pb.circ_attempts, opened_built, opened_attempts, + pb->circ_successes, pb->successful_circuits_closed, + pb->circ_attempts, opened_built, opened_attempts, entry_guard_describe(guard)); /* Have the counts just become invalid by this scaling attempt? */ - if (counts_are_sane && guard->pb.circ_attempts < guard->pb.circ_successes) { + if (counts_are_sane && pb->circ_attempts < pb->circ_successes) { log_notice(LD_BUG, "Scaling has mangled pathbias counts to %f/%f (%d/%d open) " "for guard %s", - guard->pb.circ_successes, guard->pb.circ_attempts, opened_built, + pb->circ_successes, pb->circ_attempts, opened_built, opened_attempts, entry_guard_describe(guard)); } @@ -1517,33 +1535,34 @@ void pathbias_scale_use_rates(entry_guard_t *guard) { const or_options_t *options = get_options(); + guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); /* If we get a ton of circuits, just scale everything down */ - if (guard->pb.use_attempts > pathbias_get_scale_use_threshold(options)) { + if (pb->use_attempts > pathbias_get_scale_use_threshold(options)) { double scale_ratio = pathbias_get_scale_ratio(options); int opened_attempts = pathbias_count_circs_in_states(guard, PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED); /* Verify that the counts are sane before and after scaling */ - int counts_are_sane = (guard->pb.use_attempts >= guard->pb.use_successes); + int counts_are_sane = (pb->use_attempts >= pb->use_successes); - guard->pb.use_attempts -= opened_attempts; + pb->use_attempts -= opened_attempts; - guard->pb.use_attempts *= scale_ratio; - guard->pb.use_successes *= scale_ratio; + pb->use_attempts *= scale_ratio; + pb->use_successes *= scale_ratio; - guard->pb.use_attempts += opened_attempts; + pb->use_attempts += opened_attempts; log_info(LD_CIRC, "Scaled pathbias use counts to %f/%f (%d open) for guard %s", - guard->pb.use_successes, guard->pb.use_attempts, opened_attempts, + pb->use_successes, pb->use_attempts, opened_attempts, entry_guard_describe(guard)); /* Have the counts just become invalid by this scaling attempt? */ - if (counts_are_sane && guard->pb.use_attempts < guard->pb.use_successes) { + if (counts_are_sane && pb->use_attempts < pb->use_successes) { log_notice(LD_BUG, "Scaling has mangled pathbias usage counts to %f/%f " "(%d open) for guard %s", - guard->pb.circ_successes, guard->pb.circ_attempts, + pb->circ_successes, pb->circ_attempts, opened_attempts, entry_guard_describe(guard)); } diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 1324e31789..90918fa1dc 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -184,6 +184,13 @@ entry_guard_get_rsa_id_digest(const entry_guard_t *guard) return guard->identity; } +/** Return the pathbias state associated with guard. */ +guard_pathbias_t * +entry_guard_get_pathbias_state(entry_guard_t *guard) +{ + return &guard->pb; +} + /** Check whether the entry guard e is usable, given the directory * authorities' opinion about the router (stored in ri) and the user's * configuration (in options). Set e->bad_since diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index ba8cd9f649..2e18a2f393 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -53,7 +53,7 @@ typedef struct guard_pathbias_t { * this guard as first hop. */ } guard_pathbias_t; -#if defined(ENTRYNODES_PRIVATE) || defined(ENTRYNODES_EXPOSE_STRUCT) +#if defined(ENTRYNODES_PRIVATE) /** An entry_guard_t represents our information about a chosen long-term * first hop, known as a "helper" node in the literature. We can't just * use a node_t, since we want to remember these even when we @@ -106,6 +106,7 @@ const node_t *entry_guard_find_node(const entry_guard_t *guard); void entry_guard_mark_bad(entry_guard_t *guard); const char *entry_guard_get_rsa_id_digest(const entry_guard_t *guard); const char *entry_guard_describe(const entry_guard_t *guard); +guard_pathbias_t *entry_guard_get_pathbias_state(entry_guard_t *guard); #ifdef ENTRYNODES_PRIVATE STATIC const node_t *add_an_entry_guard(guard_selection_t *gs, From 043e9b01516c7cdcff939f2724b75155458da1b1 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 13:01:19 -0500 Subject: [PATCH 06/80] Whitespace fixes from previous mechanical search-and-replaces --- src/or/circpathbias.c | 2 +- src/or/entrynodes.c | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 3df68b8744..be1146513e 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -1355,7 +1355,7 @@ pathbias_measure_close_rate(entry_guard_t *guard) { const or_options_t *options = get_options(); guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard); - + if (pb->circ_attempts > pathbias_get_min_circs(options)) { /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 90918fa1dc..7890f83aaa 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1583,7 +1583,8 @@ entry_guards_parse_state_for_guard_selection( node->pb.path_bias_disabled = 1; log_info(LD_GENERAL, "Path use bias is too high (%f/%f); disabling node %s", - node->pb.circ_successes, node->pb.circ_attempts, node->nickname); + node->pb.circ_successes, node->pb.circ_attempts, + node->nickname); } } else if (!strcasecmp(line->key, "EntryGuardPathBias")) { const or_options_t *options = get_options(); @@ -1642,7 +1643,8 @@ entry_guards_parse_state_for_guard_selection( node->pb.unusable_circuits = unusable; log_info(LD_GENERAL, "Read %f/%f path bias for node %s", - node->pb.circ_successes, node->pb.circ_attempts, node->nickname); + node->pb.circ_successes, node->pb.circ_attempts, + node->nickname); /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ @@ -1652,7 +1654,8 @@ entry_guards_parse_state_for_guard_selection( node->pb.path_bias_disabled = 1; log_info(LD_GENERAL, "Path bias is too high (%f/%f); disabling node %s", - node->pb.circ_successes, node->pb.circ_attempts, node->nickname); + node->pb.circ_successes, node->pb.circ_attempts, + node->nickname); } } else { From df8256a931099767d9f70997c9eb1ef934afd392 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 13:27:35 -0500 Subject: [PATCH 07/80] Add the prop271 fields to entry_guard_t. Not used yet. --- src/or/entrynodes.c | 1 + src/or/entrynodes.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 7890f83aaa..bd4d83c4a0 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -683,6 +683,7 @@ entry_guard_free(entry_guard_t *e) if (!e) return; tor_free(e->chosen_by_version); + tor_free(e->sampled_by_version); tor_free(e); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 2e18a2f393..4f39a091f0 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -18,6 +18,10 @@ typedef struct guard_selection_s guard_selection_t; /* Forward declare for entry_guard_t; the real declaration is private. */ typedef struct entry_guard_t entry_guard_t; +#define GUARD_REACHABLE_NO 0 +#define GUARD_REACHABLE_YES 1 +#define GUARD_REACHABLE_MAYBE 2 + /* Information about a guard's pathbias status. * These fields are used in circpathbias.c to try to detect entry * nodes that are failing circuits at a suspicious frequency. @@ -61,6 +65,43 @@ typedef struct guard_pathbias_t { struct entry_guard_t { char nickname[MAX_NICKNAME_LEN+1]; char identity[DIGEST_LEN]; + ed25519_public_key_t ed_id; + + /* XXXX prop271 DOCDOC document all these fields better */ + + /* Persistent fields, present for all sampled guards. */ + time_t sampled_on_date; + time_t unlisted_since_date; + char *sampled_by_version; + unsigned currently_listed : 1; + + /* Persistent fields, for confirmed guards. */ + time_t confirmed_on_date; /* 0 if not confirmed */ + int confirmed_idx; /* -1 if not confirmed; otherwise the order that this + * item should occur in the CONFIRMED_GUARDS ordered + * list */ + + /* ==== Non-persistent fields. */ + /* == These are used by sampled guards */ + time_t last_tried_to_connect; + unsigned is_reachable : 2; /* One of GUARD_REACHABLE_{NO,YES,MAYBE} */ + unsigned is_pending : 1; + time_t failing_since; + + /* These determine presence in filtered guards and usable-filtered-guards + * respectively. */ + unsigned is_filtered_guard : 1; + unsigned is_usable_filtered_guard : 1; + + /** + * @name legacy guard selection algorithm fields + * + * These are used and maintained by the legacy (pre-prop271) entry guard + * algorithm. Most of them we will remove as prop271 gets implemented. + * The rest we'll migrate over, if they are 100% semantically identical to + * their prop271 equivalents. XXXXprop271 + */ + /**@{*/ time_t chosen_on_date; /**< Approximately when was this guard added? * "0" if we don't know. */ char *chosen_by_version; /**< What tor version added this guard? NULL @@ -79,6 +120,8 @@ struct entry_guard_t { time_t last_attempted; /**< 0 if we can connect to this guard, or the time * at which we last failed to connect to it. */ + /**}@*/ + /** Path bias information for this guard. */ guard_pathbias_t pb; }; From 539eba0a4bab1e22a7e2e19132d356e99c32e232 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 14:57:32 -0500 Subject: [PATCH 08/80] Teach parse_iso_time about the spaceless variant. (We previously added support for generating the spaceless 2016-11-14T19:58:12 variant, but not for actually parsing it.) --- src/common/util.c | 32 +++++++++++++++++++++++++------- src/common/util.h | 3 ++- src/or/entrynodes.c | 2 +- src/or/routerparse.c | 3 ++- src/test/test_util.c | 17 +++++++++++++++++ 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/common/util.c b/src/common/util.c index 3421d117b0..ec4ecf2d66 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -1803,17 +1803,26 @@ format_iso_time_nospace_usec(char *buf, const struct timeval *tv) /** Given an ISO-formatted UTC time value (after the epoch) in cp, * parse it and store its value in *t. Return 0 on success, -1 on * failure. Ignore extraneous stuff in cp after the end of the time - * string, unless strict is set. */ + * string, unless strict is set. If nospace is set, + * expect the YYYY-MM-DDTHH:MM:SS format. */ int -parse_iso_time_(const char *cp, time_t *t, int strict) +parse_iso_time_(const char *cp, time_t *t, int strict, int nospace) { struct tm st_tm; unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0; int n_fields; - char extra_char; - n_fields = tor_sscanf(cp, "%u-%2u-%2u %2u:%2u:%2u%c", &year, &month, - &day, &hour, &minute, &second, &extra_char); - if (strict ? (n_fields != 6) : (n_fields < 6)) { + char extra_char, separator_char; + n_fields = tor_sscanf(cp, "%u-%2u-%2u%c%2u:%2u:%2u%c", + &year, &month, &day, + &separator_char, + &hour, &minute, &second, &extra_char); + if (strict ? (n_fields != 7) : (n_fields < 7)) { + char *esc = esc_for_log(cp); + log_warn(LD_GENERAL, "ISO time %s was unparseable", esc); + tor_free(esc); + return -1; + } + if (separator_char != (nospace ? 'T' : ' ')) { char *esc = esc_for_log(cp); log_warn(LD_GENERAL, "ISO time %s was unparseable", esc); tor_free(esc); @@ -1855,7 +1864,16 @@ parse_iso_time_(const char *cp, time_t *t, int strict) int parse_iso_time(const char *cp, time_t *t) { - return parse_iso_time_(cp, t, 1); + return parse_iso_time_(cp, t, 1, 0); +} + +/** + * As parse_iso_time, but parses a time encoded by format_iso_time_nospace(). + */ +int +parse_iso_time_nospace(const char *cp, time_t *t) +{ + return parse_iso_time_(cp, t, 1, 1); } /** Given a date in one of the three formats allowed by HTTP (ugh), diff --git a/src/common/util.h b/src/common/util.h index 37f4bed1cb..2b3e48ea8e 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -255,8 +255,9 @@ void format_local_iso_time(char *buf, time_t t); void format_iso_time(char *buf, time_t t); void format_iso_time_nospace(char *buf, time_t t); void format_iso_time_nospace_usec(char *buf, const struct timeval *tv); -int parse_iso_time_(const char *cp, time_t *t, int strict); +int parse_iso_time_(const char *cp, time_t *t, int strict, int nospace); int parse_iso_time(const char *buf, time_t *t); +int parse_iso_time_nospace(const char *cp, time_t *t); int parse_http_time(const char *buf, struct tm *tm); int format_time_interval(char *out, size_t out_len, long interval); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index bd4d83c4a0..c96ff095da 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1502,7 +1502,7 @@ entry_guards_parse_state_for_guard_selection( "EntryGuardDownSince/UnlistedSince without EntryGuard"); break; } - if (parse_iso_time_(line->value, &when, 0)<0) { + if (parse_iso_time_(line->value, &when, 0, 0)<0) { *msg = tor_strdup("Unable to parse entry nodes: " "Bad time in EntryGuardDownSince/UnlistedSince"); break; diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 2cfd3fc58a..60fdce0b64 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -5150,7 +5150,8 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, * descriptor. */ tok = find_by_keyword(tokens, R_PUBLICATION_TIME); tor_assert(tok->n_args == 1); - if (parse_iso_time_(tok->args[0], &result->timestamp, strict_time_fmt) < 0) { + if (parse_iso_time_(tok->args[0], &result->timestamp, + strict_time_fmt, 0) < 0) { log_warn(LD_REND, "Invalid publication time: '%s'", tok->args[0]); goto err; } diff --git a/src/test/test_util.c b/src/test/test_util.c index b74f658146..187bfdd93c 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -1059,6 +1059,23 @@ test_util_time(void *arg) tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-04 00:48:22.100", &t_res)); tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-04 00:48:22XYZ", &t_res)); + /* but... that _is_ acceptable if we aren't being strict. */ + t_res = 0; + i = parse_iso_time_("2004-08-04 00:48:22XYZ", &t_res, 0, 0); + tt_int_op(0,OP_EQ, i); + tt_int_op(t_res,OP_EQ, (time_t)1091580502UL); + + /* try nospace variant. */ + t_res = 0; + i = parse_iso_time_nospace("2004-08-04T00:48:22", &t_res); + tt_int_op(0,OP_EQ, i); + tt_int_op(t_res,OP_EQ, (time_t)1091580502UL); + + tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-04T00:48:22", &t_res)); + tt_int_op(-1,OP_EQ, parse_iso_time_nospace("2004-08-04 00:48:22", &t_res)); + tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-04x00:48:22", &t_res)); + tt_int_op(-1,OP_EQ, parse_iso_time_nospace("2004-08-04x00:48:22", &t_res)); + /* Test tor_gettimeofday */ end.tv_sec = 4; From dd6def5daf5b0b579a61c9e83cfa905b333f99a1 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Nov 2016 15:46:09 -0500 Subject: [PATCH 09/80] Initial code to parse/encode/sample prop271 guards The encoding code is very straightforward. The decoding code is a bit tricky, but clean-ish. The sampling code is untested and probably needs more work. --- src/or/entrynodes.c | 310 ++++++++++++++++++++++++++++++++++++- src/or/entrynodes.h | 14 +- src/test/test_entrynodes.c | 243 +++++++++++++++++++++++++++++ 3 files changed, 564 insertions(+), 3 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index c96ff095da..9af71404de 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -76,6 +76,11 @@ struct guard_selection_s { */ int dirty; + /** + * A list of the sampled entry guards, as entry_guard_t structures. + * Not in any particular order. */ + smartlist_t *sampled_entry_guards; + /** * A list of our chosen entry guards, as entry_guard_t structures; this * preserves the pre-Prop271 behavior. @@ -87,6 +92,8 @@ struct guard_selection_s { * config's EntryNodes first? This was formerly a global. */ int should_add_entry_nodes; + + int filtered_up_to_date; }; static smartlist_t *guard_contexts = NULL; @@ -118,6 +125,7 @@ guard_selection_new(void) gs = tor_malloc_zero(sizeof(*gs)); gs->chosen_entry_guards = smartlist_new(); + gs->sampled_entry_guards = smartlist_new(); return gs; } @@ -191,6 +199,293 @@ entry_guard_get_pathbias_state(entry_guard_t *guard) return &guard->pb; } +/** Return an interval betweeen 'now' and 'max_backdate' seconds in the past, + * chosen uniformly at random. */ +STATIC time_t +randomize_time(time_t now, time_t max_backdate) +{ + tor_assert(max_backdate > 0); + + time_t earliest = now - max_backdate; + time_t latest = now; + if (earliest <= 0) + earliest = 1; + if (latest <= earliest) + latest = earliest + 1; + + return crypto_rand_time_range(earliest, latest); +} + +/** + * DOCDOC + */ +STATIC void +entry_guard_add_to_sample(guard_selection_t *gs, + node_t *node) +{ + (void) entry_guard_add_to_sample; // XXXX prop271 remove -- unused + const int GUARD_LIFETIME = 90 * 86400; // xxxx prop271 + tor_assert(gs); + tor_assert(node); + + // XXXX prop271 take ed25519 identity here too. + + /* make sure that the guard is not already sampled. */ + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, + entry_guard_t *, sampled) { + if (BUG(tor_memeq(node->identity, sampled->identity, DIGEST_LEN))) { + return; + } + } SMARTLIST_FOREACH_END(sampled); + + entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); + + /* persistent fields */ + memcpy(guard->identity, node->identity, DIGEST_LEN); + strlcpy(guard->nickname, node_get_nickname(node), sizeof(guard->nickname)); + guard->sampled_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10); + tor_free(guard->sampled_by_version); + guard->sampled_by_version = tor_strdup(VERSION); + guard->confirmed_idx = -1; + + /* non-persistent fields */ + guard->is_reachable = GUARD_REACHABLE_MAYBE; + + smartlist_add(gs->sampled_entry_guards, guard); + gs->filtered_up_to_date = 0; + + entry_guards_changed_for_guard_selection(gs); +} + +/** + * Return a newly allocated string for encoding the persistent parts of + * guard to the state file. + */ +STATIC char * +entry_guard_encode_for_state(entry_guard_t *guard) +{ + /* + * The meta-format we use is K=V K=V K=V... where K can be any + * characters excepts space and =, and V can be any characters except + * space. The order of entries is not allowed to matter. + * Unrecognized K=V entries are persisted; recognized but erroneous + * entries are corrected. + */ + + smartlist_t *result = smartlist_new(); + char tbuf[ISO_TIME_LEN+1]; + + tor_assert(guard); + + smartlist_add_asprintf(result, "rsa_id=%s", + hex_str(guard->identity, DIGEST_LEN)); + if (strlen(guard->nickname)) { + smartlist_add_asprintf(result, "nickname=%s", guard->nickname); + } + + format_iso_time_nospace(tbuf, guard->sampled_on_date); + smartlist_add_asprintf(result, "sampled_on=%s", tbuf); + + if (guard->sampled_by_version) { + smartlist_add_asprintf(result, "sampled_by=%s", + guard->sampled_by_version); + } + + if (guard->unlisted_since_date > 0) { + format_iso_time_nospace(tbuf, guard->unlisted_since_date); + smartlist_add_asprintf(result, "unlisted_since=%s", tbuf); + } + + smartlist_add_asprintf(result, "listed=%d", + (int)guard->currently_listed); + + if (guard->confirmed_idx >= 0) { + format_iso_time_nospace(tbuf, guard->confirmed_on_date); + smartlist_add_asprintf(result, "confirmed_on=%s", tbuf); + + smartlist_add_asprintf(result, "confirmed_idx=%d", guard->confirmed_idx); + } + + if (guard->extra_state_fields) + smartlist_add_strdup(result, guard->extra_state_fields); + + char *joined = smartlist_join_strings(result, " ", 0, NULL); + SMARTLIST_FOREACH(result, char *, cp, tor_free(cp)); + smartlist_free(result); + + return joined; +} + +/** + * Given a string generated by entry_guard_encode_for_state(), parse it + * (if possible) and return an entry_guard_t object for it. Return NULL + * on complete failure. + */ +STATIC entry_guard_t * +entry_guard_parse_from_state(const char *s) +{ + /* Unrecognized entries get put in here. */ + smartlist_t *extra = smartlist_new(); + + /* These fields get parsed from the string. */ + char *rsa_id = NULL; + char *nickname = NULL; + char *sampled_on = NULL; + char *sampled_by = NULL; + char *unlisted_since = NULL; + char *listed = NULL; + char *confirmed_on = NULL; + char *confirmed_idx = NULL; + + /* Split up the entries. Put the ones we know about in strings and the + * rest in "extra". */ + { + smartlist_t *entries = smartlist_new(); + + strmap_t *vals = strmap_new(); // Maps keyword to location + strmap_set(vals, "rsa_id", &rsa_id); + strmap_set(vals, "nickname", &nickname); + strmap_set(vals, "sampled_on", &sampled_on); + strmap_set(vals, "sampled_by", &sampled_by); + strmap_set(vals, "unlisted_since", &unlisted_since); + strmap_set(vals, "listed", &listed); + strmap_set(vals, "confirmed_on", &confirmed_on); + strmap_set(vals, "confirmed_idx", &confirmed_idx); + + smartlist_split_string(entries, s, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + + SMARTLIST_FOREACH_BEGIN(entries, char *, entry) { + const char *eq = strchr(entry, '='); + if (!eq) { + smartlist_add(extra, entry); + continue; + } + char *key = tor_strndup(entry, eq-entry); + char **target = strmap_get(vals, key); + if (target == NULL || *target != NULL) { + /* unrecognized or already set */ + smartlist_add(extra, entry); + tor_free(key); + continue; + } + + *target = tor_strdup(eq+1); + tor_free(key); + tor_free(entry); + } SMARTLIST_FOREACH_END(entry); + + smartlist_free(entries); + strmap_free(vals, NULL); + } + + entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); + + if (rsa_id == NULL) { + log_warn(LD_CIRC, "Guard missing RSA ID field"); + goto err; + } + + /* Process the identity and nickname. */ + if (base16_decode(guard->identity, sizeof(guard->identity), + rsa_id, strlen(rsa_id)) != DIGEST_LEN) { + log_warn(LD_CIRC, "Unable to decode guard identity %s", escaped(rsa_id)); + goto err; + } + + if (nickname) { + strlcpy(guard->nickname, nickname, sizeof(guard->nickname)); + } else { + guard->nickname[0]='$'; + base16_encode(guard->nickname+1, sizeof(guard->nickname)-1, + guard->identity, DIGEST_LEN); + } + + /* Process the various time fields. */ + +#define HANDLE_TIME(field) do { \ + if (field) { \ + int r = parse_iso_time_nospace(field, &field ## _time); \ + if (r < 0) { \ + log_warn(LD_CIRC, "Unable to parse %s %s from guard", \ + #field, escaped(field)); \ + field##_time = -1; \ + } \ + } \ + } while (0) + + time_t sampled_on_time = 0; + time_t unlisted_since_time = 0; + time_t confirmed_on_time = 0; + + HANDLE_TIME(sampled_on); + HANDLE_TIME(unlisted_since); + HANDLE_TIME(confirmed_on); + + if (sampled_on_time <= 0) + sampled_on_time = approx_time(); + if (unlisted_since_time < 0) + unlisted_since_time = 0; + if (confirmed_on_time < 0) + confirmed_on_time = 0; + + #undef HANDLE_TIME + + guard->sampled_on_date = sampled_on_time; + guard->unlisted_since_date = unlisted_since_time; + guard->confirmed_on_date = confirmed_on_time; + + /* Take sampled_by_version verbatim. */ + guard->sampled_by_version = sampled_by; + sampled_by = NULL; /* prevent free */ + + /* Listed is a boolean */ + if (listed && strcmp(listed, "0")) + guard->currently_listed = 1; + + /* The index is a nonnegative integer. */ + guard->confirmed_idx = -1; + if (confirmed_idx) { + int ok=1; + long idx = tor_parse_long(confirmed_idx, 10, 0, INT_MAX, &ok, NULL); + if (! ok) { + log_warn(LD_CIRC, "Guard has invalid confirmed_idx %s", + escaped(confirmed_idx)); + } else { + guard->confirmed_idx = (int)idx; + } + } + + /* Anything we didn't recognize gets crammed together */ + if (smartlist_len(extra) > 0) { + guard->extra_state_fields = smartlist_join_strings(extra, " ", 0, NULL); + } + + /* initialize non-persistent fields */ + guard->is_reachable = GUARD_REACHABLE_MAYBE; + + goto done; + + err: + // only consider it an error if the guard state was totally unparseable. + entry_guard_free(guard); + guard = NULL; + + done: + tor_free(rsa_id); + tor_free(nickname); + tor_free(sampled_on); + tor_free(sampled_by); + tor_free(unlisted_since); + tor_free(listed); + tor_free(confirmed_on); + tor_free(confirmed_idx); + SMARTLIST_FOREACH(extra, char *, cp, tor_free(cp)); + smartlist_free(extra); + + return guard; +} + /** Check whether the entry guard e is usable, given the directory * authorities' opinion about the router (stored in ri) and the user's * configuration (in options). Set e->bad_since @@ -677,13 +972,14 @@ pick_entry_guards(guard_selection_t *gs, #define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) /** Release all storage held by e. */ -static void +STATIC void entry_guard_free(entry_guard_t *e) { if (!e) return; tor_free(e->chosen_by_version); tor_free(e->sampled_by_version); + tor_free(e->extra_state_fields); tor_free(e); } @@ -1452,6 +1748,9 @@ entry_guards_parse_state_for_guard_selection( const char *state_version = state->TorVersion; digestmap_t *added_by = digestmap_new(); + if (0) entry_guard_parse_from_state(NULL); // XXXX prop271 remove -- unused + if (0) entry_guard_add_to_sample(NULL, NULL); // XXXX prop271 remove + tor_assert(gs != NULL); *msg = NULL; @@ -1777,6 +2076,8 @@ entry_guards_update_state(or_state_t *state) config_line_t **next, *line; guard_selection_t *gs = get_guard_selection_info(); + if (0) entry_guard_encode_for_state(NULL); // XXXX prop271 remove -- unused + tor_assert(gs != NULL); tor_assert(gs->chosen_entry_guards != NULL); @@ -2837,6 +3138,13 @@ guard_selection_free(guard_selection_t *gs) gs->chosen_entry_guards = NULL; } + if (gs->sampled_entry_guards) { + SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, e, + entry_guard_free(e)); + smartlist_free(gs->sampled_entry_guards); + gs->sampled_entry_guards = NULL; + } + tor_free(gs); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 4f39a091f0..5c0857b426 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -63,7 +63,7 @@ typedef struct guard_pathbias_t { * use a node_t, since we want to remember these even when we * don't have any directory info. */ struct entry_guard_t { - char nickname[MAX_NICKNAME_LEN+1]; + char nickname[MAX_HEX_NICKNAME_LEN+1]; char identity[DIGEST_LEN]; ed25519_public_key_t ed_id; @@ -71,7 +71,7 @@ struct entry_guard_t { /* Persistent fields, present for all sampled guards. */ time_t sampled_on_date; - time_t unlisted_since_date; + time_t unlisted_since_date; // can be zero char *sampled_by_version; unsigned currently_listed : 1; @@ -93,6 +93,9 @@ struct entry_guard_t { unsigned is_filtered_guard : 1; unsigned is_usable_filtered_guard : 1; + /** This string holds any fields that we are maintaining because + * we saw them in the state, even if we don't understand them. */ + char *extra_state_fields; /** * @name legacy guard selection algorithm fields * @@ -152,6 +155,13 @@ const char *entry_guard_describe(const entry_guard_t *guard); guard_pathbias_t *entry_guard_get_pathbias_state(entry_guard_t *guard); #ifdef ENTRYNODES_PRIVATE +STATIC time_t randomize_time(time_t now, time_t max_backdate); +STATIC void entry_guard_add_to_sample(guard_selection_t *gs, + node_t *node); +STATIC char *entry_guard_encode_for_state(entry_guard_t *guard); +STATIC entry_guard_t *entry_guard_parse_from_state(const char *s); +STATIC void entry_guard_free(entry_guard_t *e); + STATIC const node_t *add_an_entry_guard(guard_selection_t *gs, const node_t *chosen, int reset_status, int prepend, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index aa1b455ff1..45b730c69f 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -859,6 +859,236 @@ test_entry_guard_describe(void *arg) ; } +static void +test_entry_guard_randomize_time(void *arg) +{ + const time_t now = 1479153573; + const int delay = 86400; + const int N = 1000; + (void)arg; + + time_t t; + int i; + for (i = 0; i < N; ++i) { + t = randomize_time(now, delay); + tt_int_op(t, OP_LE, now); + tt_int_op(t, OP_GE, now-delay); + } + + /* now try the corner cases */ + for (i = 0; i < N; ++i) { + t = randomize_time(100, delay); + tt_int_op(t, OP_GE, 1); + tt_int_op(t, OP_LE, 100); + + t = randomize_time(0, delay); + tt_int_op(t, OP_EQ, 1); + } + + done: + ; +} + +static void +test_entry_guard_encode_for_state_minimal(void *arg) +{ + (void) arg; + entry_guard_t *eg = tor_malloc_zero(sizeof(entry_guard_t)); + + memcpy(eg->identity, "plurpyflurpyslurpydo", DIGEST_LEN); + eg->sampled_on_date = 1479081600; + eg->confirmed_idx = -1; + + char *s = NULL; + s = entry_guard_encode_for_state(eg); + + tt_str_op(s, OP_EQ, + "rsa_id=706C75727079666C75727079736C75727079646F " + "sampled_on=2016-11-14T00:00:00 " + "listed=0"); + + done: + entry_guard_free(eg); + tor_free(s); +} + +static void +test_entry_guard_encode_for_state_maximal(void *arg) +{ + (void) arg; + entry_guard_t *eg = tor_malloc_zero(sizeof(entry_guard_t)); + + strlcpy(eg->nickname, "Fred", sizeof(eg->nickname)); + memcpy(eg->identity, "plurpyflurpyslurpydo", DIGEST_LEN); + eg->sampled_on_date = 1479081600; + eg->sampled_by_version = tor_strdup("1.2.3"); + eg->unlisted_since_date = 1479081645; + eg->currently_listed = 1; + eg->confirmed_on_date = 1479081690; + eg->confirmed_idx = 333; + eg->extra_state_fields = tor_strdup("and the green grass grew all around"); + + char *s = NULL; + s = entry_guard_encode_for_state(eg); + + tt_str_op(s, OP_EQ, + "rsa_id=706C75727079666C75727079736C75727079646F " + "nickname=Fred " + "sampled_on=2016-11-14T00:00:00 " + "sampled_by=1.2.3 " + "unlisted_since=2016-11-14T00:00:45 " + "listed=1 " + "confirmed_on=2016-11-14T00:01:30 " + "confirmed_idx=333 " + "and the green grass grew all around"); + + done: + entry_guard_free(eg); + tor_free(s); +} + +static void +test_entry_guard_parse_from_state_minimal(void *arg) +{ + (void)arg; + char *mem_op_hex_tmp = NULL; + entry_guard_t *eg = NULL; + time_t t = approx_time(); + + eg = entry_guard_parse_from_state( + "rsa_id=596f75206d6179206e656564206120686f626279"); + tt_assert(eg); + + test_mem_op_hex(eg->identity, OP_EQ, + "596f75206d6179206e656564206120686f626279"); + tt_str_op(eg->nickname, OP_EQ, "$596F75206D6179206E656564206120686F626279"); + tt_i64_op(eg->sampled_on_date, OP_GE, t); + tt_i64_op(eg->sampled_on_date, OP_LE, t+86400); + tt_i64_op(eg->unlisted_since_date, OP_EQ, 0); + tt_ptr_op(eg->sampled_by_version, OP_EQ, NULL); + tt_int_op(eg->currently_listed, OP_EQ, 0); + tt_i64_op(eg->confirmed_on_date, OP_EQ, 0); + tt_int_op(eg->confirmed_idx, OP_EQ, -1); + + tt_int_op(eg->last_tried_to_connect, OP_EQ, 0); + tt_int_op(eg->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + + done: + entry_guard_free(eg); + tor_free(mem_op_hex_tmp); +} + +static void +test_entry_guard_parse_from_state_maximal(void *arg) +{ + (void)arg; + char *mem_op_hex_tmp = NULL; + entry_guard_t *eg = NULL; + + eg = entry_guard_parse_from_state( + "rsa_id=706C75727079666C75727079736C75727079646F " + "nickname=Fred " + "sampled_on=2016-11-14T00:00:00 " + "sampled_by=1.2.3 " + "unlisted_since=2016-11-14T00:00:45 " + "listed=1 " + "confirmed_on=2016-11-14T00:01:30 " + "confirmed_idx=333 " + "and the green grass grew all around " + "rsa_id=all,around"); + tt_assert(eg); + + test_mem_op_hex(eg->identity, OP_EQ, + "706C75727079666C75727079736C75727079646F"); + tt_str_op(eg->nickname, OP_EQ, "Fred"); + tt_i64_op(eg->sampled_on_date, OP_EQ, 1479081600); + tt_i64_op(eg->unlisted_since_date, OP_EQ, 1479081645); + tt_str_op(eg->sampled_by_version, OP_EQ, "1.2.3"); + tt_int_op(eg->currently_listed, OP_EQ, 1); + tt_i64_op(eg->confirmed_on_date, OP_EQ, 1479081690); + tt_int_op(eg->confirmed_idx, OP_EQ, 333); + tt_str_op(eg->extra_state_fields, OP_EQ, + "and the green grass grew all around rsa_id=all,around"); + + tt_int_op(eg->last_tried_to_connect, OP_EQ, 0); + tt_int_op(eg->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + + done: + entry_guard_free(eg); + tor_free(mem_op_hex_tmp); +} + +static void +test_entry_guard_parse_from_state_failure(void *arg) +{ + (void)arg; + entry_guard_t *eg = NULL; + + /* no RSA ID. */ + eg = entry_guard_parse_from_state("nickname=Fred"); + tt_assert(! eg); + + /* Bad RSA ID: bad character. */ + eg = entry_guard_parse_from_state( + "rsa_id=596f75206d6179206e656564206120686f62627q"); + tt_assert(! eg); + + /* Bad RSA ID: too long.*/ + eg = entry_guard_parse_from_state( + "rsa_id=596f75206d6179206e656564206120686f6262703"); + tt_assert(! eg); + + /* Bad RSA ID: too short.*/ + eg = entry_guard_parse_from_state( + "rsa_id=596f75206d6179206e65656420612"); + tt_assert(! eg); + + done: + entry_guard_free(eg); +} + +static void +test_entry_guard_parse_from_state_partial_failure(void *arg) +{ + (void)arg; + char *mem_op_hex_tmp = NULL; + entry_guard_t *eg = NULL; + time_t t = approx_time(); + + eg = entry_guard_parse_from_state( + "rsa_id=706C75727079666C75727079736C75727079646F " + "nickname=FredIsANodeWithAStrangeNicknameThatIsTooLong " + "sampled_on=2016-11-14T00:00:99 " + "sampled_by=1.2.3 stuff in the middle " + "unlisted_since=2016-xx-14T00:00:45 " + "listed=0 " + "confirmed_on=2016-11-14T00:01:30zz " + "confirmed_idx=idx " + "and the green grass grew all around " + "rsa_id=all,around"); + tt_assert(eg); + + test_mem_op_hex(eg->identity, OP_EQ, + "706C75727079666C75727079736C75727079646F"); + tt_str_op(eg->nickname, OP_EQ, "FredIsANodeWithAStrangeNicknameThatIsTooL"); + tt_i64_op(eg->sampled_on_date, OP_EQ, t); + tt_i64_op(eg->unlisted_since_date, OP_EQ, 0); + tt_str_op(eg->sampled_by_version, OP_EQ, "1.2.3"); + tt_int_op(eg->currently_listed, OP_EQ, 0); + tt_i64_op(eg->confirmed_on_date, OP_EQ, 0); + tt_int_op(eg->confirmed_idx, OP_EQ, -1); + tt_str_op(eg->extra_state_fields, OP_EQ, + "stuff in the middle and the green grass grew all around " + "rsa_id=all,around"); + + tt_int_op(eg->last_tried_to_connect, OP_EQ, 0); + tt_int_op(eg->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + + done: + entry_guard_free(eg); + tor_free(mem_op_hex_tmp); +} + static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; @@ -893,6 +1123,19 @@ struct testcase_t entrynodes_tests[] = { test_node_preferred_orport, 0, NULL, NULL }, { "entry_guard_describe", test_entry_guard_describe, 0, NULL, NULL }, + { "randomize_time", test_entry_guard_randomize_time, 0, NULL, NULL }, + { "encode_for_state_minimal", + test_entry_guard_encode_for_state_minimal, 0, NULL, NULL }, + { "encode_for_state_maximal", + test_entry_guard_encode_for_state_maximal, 0, NULL, NULL }, + { "parse_from_state_minimal", + test_entry_guard_parse_from_state_minimal, 0, NULL, NULL }, + { "parse_from_state_maximal", + test_entry_guard_parse_from_state_maximal, 0, NULL, NULL }, + { "parse_from_state_failure", + test_entry_guard_parse_from_state_failure, 0, NULL, NULL }, + { "parse_from_state_partial_failure", + test_entry_guard_parse_from_state_partial_failure, 0, NULL, NULL }, END_OF_TESTCASES }; From 8da24c99bdb90b04a05d5bccf5bcff1218174b75 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 15 Nov 2016 07:49:06 -0500 Subject: [PATCH 10/80] Split bridge functions into a new module. This patch is just: * Code movement * Adding headers here and there as needed * Adding a bridges_free_all() with a call to it. It breaks compilation, since the bridge code needed to make exactly 2 calls into entrynodes.c internals. I'll fix those in the next commit. --- src/or/bridges.c | 809 +++++++++++++++++++++++++++++++++++++ src/or/bridges.h | 54 +++ src/or/circuitbuild.c | 1 + src/or/circuituse.c | 1 + src/or/config.c | 1 + src/or/connection.c | 1 + src/or/connection_or.c | 1 + src/or/control.c | 1 + src/or/directory.c | 1 + src/or/entrynodes.c | 776 +---------------------------------- src/or/entrynodes.h | 34 -- src/or/include.am | 2 + src/or/main.c | 2 + src/or/networkstatus.c | 1 + src/or/routerlist.c | 1 + src/or/transports.c | 2 +- src/test/test_config.c | 1 + src/test/test_controller.c | 1 + 18 files changed, 880 insertions(+), 810 deletions(-) create mode 100644 src/or/bridges.c create mode 100644 src/or/bridges.h diff --git a/src/or/bridges.c b/src/or/bridges.c new file mode 100644 index 0000000000..73510cf10c --- /dev/null +++ b/src/or/bridges.c @@ -0,0 +1,809 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file bridges.c + * \brief Code to manage bridges and bridge selection. + * + * Bridges are fixed entry nodes, used for censorship circumvention. + **/ + +#include "or.h" +#include "bridges.h" +#include "circuitbuild.h" +#include "config.h" +#include "connection.h" +#include "directory.h" +#include "entrynodes.h" +#include "nodelist.h" +#include "policies.h" +#include "router.h" +#include "routerlist.h" +#include "routerset.h" +#include "transports.h" + +/** Information about a configured bridge. Currently this just matches the + * ones in the torrc file, but one day we may be able to learn about new + * bridges on our own, and remember them in the state file. */ +typedef struct { + /** Address of the bridge. */ + tor_addr_t addr; + /** TLS port for the bridge. */ + uint16_t port; + /** Boolean: We are re-parsing our bridge list, and we are going to remove + * this one if we don't find it in the list of configured bridges. */ + unsigned marked_for_removal : 1; + /** Expected identity digest, or all zero bytes if we don't know what the + * digest should be. */ + char identity[DIGEST_LEN]; + + /** Name of pluggable transport protocol taken from its config line. */ + char *transport_name; + + /** When should we next try to fetch a descriptor for this bridge? */ + download_status_t fetch_status; + + /** A smartlist of k=v values to be passed to the SOCKS proxy, if + transports are used for this bridge. */ + smartlist_t *socks_args; +} bridge_info_t; + +static void bridge_free(bridge_info_t *bridge); +static int num_bridges_usable(void); + +/** A list of configured bridges. Whenever we actually get a descriptor + * for one, we add it as an entry guard. Note that the order of bridges + * in this list does not necessarily correspond to the order of bridges + * in the torrc. */ +static smartlist_t *bridge_list = NULL; + +/** Mark every entry of the bridge list to be removed on our next call to + * sweep_bridge_list unless it has first been un-marked. */ +void +mark_bridge_list(void) +{ + if (!bridge_list) + bridge_list = smartlist_new(); + SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, + b->marked_for_removal = 1); +} + +/** Remove every entry of the bridge list that was marked with + * mark_bridge_list if it has not subsequently been un-marked. */ +void +sweep_bridge_list(void) +{ + if (!bridge_list) + bridge_list = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { + if (b->marked_for_removal) { + SMARTLIST_DEL_CURRENT(bridge_list, b); + bridge_free(b); + } + } SMARTLIST_FOREACH_END(b); +} + +/** Initialize the bridge list to empty, creating it if needed. */ +static void +clear_bridge_list(void) +{ + if (!bridge_list) + bridge_list = smartlist_new(); + SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b)); + smartlist_clear(bridge_list); +} + +/** Free the bridge bridge. */ +static void +bridge_free(bridge_info_t *bridge) +{ + if (!bridge) + return; + + tor_free(bridge->transport_name); + if (bridge->socks_args) { + SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s)); + smartlist_free(bridge->socks_args); + } + + tor_free(bridge); +} + +/** If we have a bridge configured whose digest matches digest, or a + * bridge with no known digest whose address matches any of the + * tor_addr_port_t's in orports, return that bridge. Else return + * NULL. */ +static bridge_info_t * +get_configured_bridge_by_orports_digest(const char *digest, + const smartlist_t *orports) +{ + if (!bridge_list) + return NULL; + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) + { + if (tor_digest_is_zero(bridge->identity)) { + SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, ap) + { + if (tor_addr_compare(&bridge->addr, &ap->addr, CMP_EXACT) == 0 && + bridge->port == ap->port) + return bridge; + } + SMARTLIST_FOREACH_END(ap); + } + if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) + return bridge; + } + SMARTLIST_FOREACH_END(bridge); + return NULL; +} + +/** If we have a bridge configured whose digest matches digest, or a + * bridge with no known digest whose address matches addr:port, + * return that bridge. Else return NULL. If digest is NULL, check for + * address/port matches only. */ +static bridge_info_t * +get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, + uint16_t port, + const char *digest) +{ + if (!bridge_list) + return NULL; + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) + { + if ((tor_digest_is_zero(bridge->identity) || digest == NULL) && + !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) && + bridge->port == port) + return bridge; + if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) + return bridge; + } + SMARTLIST_FOREACH_END(bridge); + return NULL; +} + +/** If we have a bridge configured whose digest matches digest, or a + * bridge with no known digest whose address matches addr:port, + * return 1. Else return 0. If digest is NULL, check for + * address/port matches only. */ +int +addr_is_a_configured_bridge(const tor_addr_t *addr, + uint16_t port, + const char *digest) +{ + tor_assert(addr); + return get_configured_bridge_by_addr_port_digest(addr, port, digest) ? 1 : 0; +} + +/** If we have a bridge configured whose digest matches + * ei->identity_digest, or a bridge with no known digest whose address + * matches ei->addr:ei->port, return 1. Else return 0. + * If ei->onion_key is NULL, check for address/port matches only. */ +int +extend_info_is_a_configured_bridge(const extend_info_t *ei) +{ + const char *digest = ei->onion_key ? ei->identity_digest : NULL; + return addr_is_a_configured_bridge(&ei->addr, ei->port, digest); +} + +/** Wrapper around get_configured_bridge_by_addr_port_digest() to look + * it up via router descriptor ri. */ +static bridge_info_t * +get_configured_bridge_by_routerinfo(const routerinfo_t *ri) +{ + bridge_info_t *bi = NULL; + smartlist_t *orports = router_get_all_orports(ri); + bi = get_configured_bridge_by_orports_digest(ri->cache_info.identity_digest, + orports); + SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); + smartlist_free(orports); + return bi; +} + +/** Return 1 if ri is one of our known bridges, else 0. */ +int +routerinfo_is_a_configured_bridge(const routerinfo_t *ri) +{ + return get_configured_bridge_by_routerinfo(ri) ? 1 : 0; +} + +/** Return 1 if node is one of our configured bridges, else 0. */ +int +node_is_a_configured_bridge(const node_t *node) +{ + int retval = 0; + smartlist_t *orports = node_get_all_orports(node); + retval = get_configured_bridge_by_orports_digest(node->identity, + orports) != NULL; + SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); + smartlist_free(orports); + return retval; +} + +/** We made a connection to a router at addr:port + * without knowing its digest. Its digest turned out to be digest. + * If it was a bridge, and we still don't know its digest, record it. + */ +void +learned_router_identity(const tor_addr_t *addr, uint16_t port, + const char *digest) +{ + bridge_info_t *bridge = + get_configured_bridge_by_addr_port_digest(addr, port, digest); + if (bridge && tor_digest_is_zero(bridge->identity)) { + char *transport_info = NULL; + const char *transport_name = + find_transport_name_by_bridge_addrport(addr, port); + if (transport_name) + tor_asprintf(&transport_info, " (with transport '%s')", transport_name); + + memcpy(bridge->identity, digest, DIGEST_LEN); + log_notice(LD_DIR, "Learned fingerprint %s for bridge %s%s.", + hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port), + transport_info ? transport_info : ""); + tor_free(transport_info); + } +} + +/** Return true if bridge has the same identity digest as + * digest. If digest is NULL, it matches + * bridges with unspecified identity digests. */ +static int +bridge_has_digest(const bridge_info_t *bridge, const char *digest) +{ + if (digest) + return tor_memeq(digest, bridge->identity, DIGEST_LEN); + else + return tor_digest_is_zero(bridge->identity); +} + +/** We are about to add a new bridge at addr:port, with optional + * digest and transport_name. Mark for removal any previously + * existing bridge with the same address and port, and warn the user as + * appropriate. + */ +static void +bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port, + const char *digest, const char *transport_name) +{ + /* Iterate the already-registered bridge list: + + If you find a bridge with the same adress and port, mark it for + removal. It doesn't make sense to have two active bridges with + the same IP:PORT. If the bridge in question has a different + digest or transport than digest/transport_name, + it's probably a misconfiguration and we should warn the user. + */ + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { + if (bridge->marked_for_removal) + continue; + + if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) { + + bridge->marked_for_removal = 1; + + if (!bridge_has_digest(bridge, digest) || + strcmp_opt(bridge->transport_name, transport_name)) { + /* warn the user */ + char *bridge_description_new, *bridge_description_old; + tor_asprintf(&bridge_description_new, "%s:%s:%s", + fmt_addrport(addr, port), + digest ? hex_str(digest, DIGEST_LEN) : "", + transport_name ? transport_name : ""); + tor_asprintf(&bridge_description_old, "%s:%s:%s", + fmt_addrport(&bridge->addr, bridge->port), + tor_digest_is_zero(bridge->identity) ? + "" : hex_str(bridge->identity,DIGEST_LEN), + bridge->transport_name ? bridge->transport_name : ""); + + log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict" + " with the already registered bridge '%s'. We will discard" + " the old bridge and keep '%s'. If this is not what you" + " wanted, please change your configuration file accordingly.", + bridge_description_new, bridge_description_old, + bridge_description_new); + + tor_free(bridge_description_new); + tor_free(bridge_description_old); + } + } + } SMARTLIST_FOREACH_END(bridge); +} + +/** Return True if we have a bridge that uses a transport with name + * transport_name. */ +MOCK_IMPL(int, +transport_is_needed, (const char *transport_name)) +{ + if (!bridge_list) + return 0; + + SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { + if (bridge->transport_name && + !strcmp(bridge->transport_name, transport_name)) + return 1; + } SMARTLIST_FOREACH_END(bridge); + + return 0; +} + +/** Register the bridge information in bridge_line to the + * bridge subsystem. Steals reference of bridge_line. */ +void +bridge_add_from_config(bridge_line_t *bridge_line) +{ + bridge_info_t *b; + + { /* Log the bridge we are about to register: */ + log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)", + fmt_addrport(&bridge_line->addr, bridge_line->port), + bridge_line->transport_name ? + bridge_line->transport_name : "no transport", + tor_digest_is_zero(bridge_line->digest) ? + "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN)); + + if (bridge_line->socks_args) { /* print socks arguments */ + int i = 0; + + tor_assert(smartlist_len(bridge_line->socks_args) > 0); + + log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:", + smartlist_len(bridge_line->socks_args)); + SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg, + log_debug(LD_CONFIG, "%d: %s", ++i, arg)); + } + } + + bridge_resolve_conflicts(&bridge_line->addr, + bridge_line->port, + bridge_line->digest, + bridge_line->transport_name); + + b = tor_malloc_zero(sizeof(bridge_info_t)); + tor_addr_copy(&b->addr, &bridge_line->addr); + b->port = bridge_line->port; + memcpy(b->identity, bridge_line->digest, DIGEST_LEN); + if (bridge_line->transport_name) + b->transport_name = bridge_line->transport_name; + b->fetch_status.schedule = DL_SCHED_BRIDGE; + b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL; + b->socks_args = bridge_line->socks_args; + if (!bridge_list) + bridge_list = smartlist_new(); + + tor_free(bridge_line); /* Deallocate bridge_line now. */ + + smartlist_add(bridge_list, b); +} + +/** Return true iff routerset contains the bridge bridge. */ +static int +routerset_contains_bridge(const routerset_t *routerset, + const bridge_info_t *bridge) +{ + int result; + extend_info_t *extinfo; + tor_assert(bridge); + if (!routerset) + return 0; + + extinfo = extend_info_new( + NULL, bridge->identity, NULL, NULL, &bridge->addr, bridge->port); + result = routerset_contains_extendinfo(routerset, extinfo); + extend_info_free(extinfo); + return result; +} + +/** If digest is one of our known bridges, return it. */ +static bridge_info_t * +find_bridge_by_digest(const char *digest) +{ + SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge, + { + if (tor_memeq(bridge->identity, digest, DIGEST_LEN)) + return bridge; + }); + return NULL; +} + +/** Given the addr and port of a bridge, if that bridge + * supports a pluggable transport, return its name. Otherwise, return + * NULL. */ +const char * +find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) +{ + if (!bridge_list) + return NULL; + + SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { + if (tor_addr_eq(&bridge->addr, addr) && + (bridge->port == port)) + return bridge->transport_name; + } SMARTLIST_FOREACH_END(bridge); + + return NULL; +} + +/** If addr and port match the address and port of a + * bridge of ours that uses pluggable transports, place its transport + * in transport. + * + * Return 0 on success (found a transport, or found a bridge with no + * transport, or found no bridge); return -1 if we should be using a + * transport, but the transport could not be found. + */ +int +get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, + const transport_t **transport) +{ + *transport = NULL; + if (!bridge_list) + return 0; + + SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { + if (tor_addr_eq(&bridge->addr, addr) && + (bridge->port == port)) { /* bridge matched */ + if (bridge->transport_name) { /* it also uses pluggable transports */ + *transport = transport_get_by_name(bridge->transport_name); + if (*transport == NULL) { /* it uses pluggable transports, but + the transport could not be found! */ + return -1; + } + return 0; + } else { /* bridge matched, but it doesn't use transports. */ + break; + } + } + } SMARTLIST_FOREACH_END(bridge); + + *transport = NULL; + return 0; +} + +/** Return a smartlist containing all the SOCKS arguments that we + * should pass to the SOCKS proxy. */ +const smartlist_t * +get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) +{ + bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr, + port, + NULL); + return bridge ? bridge->socks_args : NULL; +} + +/** We need to ask bridge for its server descriptor. */ +static void +launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) +{ + const or_options_t *options = get_options(); + + if (connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &bridge->addr, bridge->port, + DIR_PURPOSE_FETCH_SERVERDESC)) + return; /* it's already on the way */ + + if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { + download_status_mark_impossible(&bridge->fetch_status); + log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", + safe_str_client(fmt_and_decorate_addr(&bridge->addr))); + return; + } + + /* Until we get a descriptor for the bridge, we only know one address for + * it. */ + if (!fascist_firewall_allows_address_addr(&bridge->addr, bridge->port, + FIREWALL_OR_CONNECTION, 0, 0)) { + log_notice(LD_CONFIG, "Tried to fetch a descriptor directly from a " + "bridge, but that bridge is not reachable through our " + "firewall."); + return; + } + + directory_initiate_command(&bridge->addr, bridge->port, + NULL, 0, /*no dirport*/ + bridge->identity, + DIR_PURPOSE_FETCH_SERVERDESC, + ROUTER_PURPOSE_BRIDGE, + DIRIND_ONEHOP, "authority.z", NULL, 0, 0); +} + +/** Fetching the bridge descriptor from the bridge authority returned a + * "not found". Fall back to trying a direct fetch. */ +void +retry_bridge_descriptor_fetch_directly(const char *digest) +{ + bridge_info_t *bridge = find_bridge_by_digest(digest); + if (!bridge) + return; /* not found? oh well. */ + + launch_direct_bridge_descriptor_fetch(bridge); +} + +/** For each bridge in our list for which we don't currently have a + * descriptor, fetch a new copy of its descriptor -- either directly + * from the bridge or via a bridge authority. */ +void +fetch_bridge_descriptors(const or_options_t *options, time_t now) +{ + int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO); + int ask_bridge_directly; + int can_use_bridge_authority; + + if (!bridge_list) + return; + + /* If we still have unconfigured managed proxies, don't go and + connect to a bridge. */ + if (pt_proxies_configuration_pending()) + return; + + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) + { + if (!download_status_is_ready(&bridge->fetch_status, now, + IMPOSSIBLE_TO_DOWNLOAD)) + continue; /* don't bother, no need to retry yet */ + if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { + download_status_mark_impossible(&bridge->fetch_status); + log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", + safe_str_client(fmt_and_decorate_addr(&bridge->addr))); + continue; + } + + /* schedule another fetch as if this one will fail, in case it does */ + download_status_failed(&bridge->fetch_status, 0); + + can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) && + num_bridge_auths; + ask_bridge_directly = !can_use_bridge_authority || + !options->UpdateBridgesFromAuthority; + log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)", + ask_bridge_directly, tor_digest_is_zero(bridge->identity), + !options->UpdateBridgesFromAuthority, !num_bridge_auths); + + if (ask_bridge_directly && + !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port, + FIREWALL_OR_CONNECTION, 0, + 0)) { + log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our " + "firewall policy. %s.", + fmt_addrport(&bridge->addr, bridge->port), + can_use_bridge_authority ? + "Asking bridge authority instead" : "Skipping"); + if (can_use_bridge_authority) + ask_bridge_directly = 0; + else + continue; + } + + if (ask_bridge_directly) { + /* we need to ask the bridge itself for its descriptor. */ + launch_direct_bridge_descriptor_fetch(bridge); + } else { + /* We have a digest and we want to ask an authority. We could + * combine all the requests into one, but that may give more + * hints to the bridge authority than we want to give. */ + char resource[10 + HEX_DIGEST_LEN]; + memcpy(resource, "fp/", 3); + base16_encode(resource+3, HEX_DIGEST_LEN+1, + bridge->identity, DIGEST_LEN); + memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3); + log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.", + resource); + directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, + ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY); + } + } + SMARTLIST_FOREACH_END(bridge); +} + +/** If our bridge is configured to be a different address than + * the bridge gives in node, rewrite the routerinfo + * we received to use the address we meant to use. Now we handle + * multihomed bridges better. + */ +static void +rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) +{ + /* XXXX move this function. */ + /* XXXX overridden addresses should really live in the node_t, so that the + * routerinfo_t and the microdesc_t can be immutable. But we can only + * do that safely if we know that no function that connects to an OR + * does so through an address from any source other than node_get_addr(). + */ + tor_addr_t addr; + const or_options_t *options = get_options(); + + if (node->ri) { + routerinfo_t *ri = node->ri; + tor_addr_from_ipv4h(&addr, ri->addr); + + if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && + bridge->port == ri->or_port) || + (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) && + bridge->port == ri->ipv6_orport)) { + /* they match, so no need to do anything */ + } else { + if (tor_addr_family(&bridge->addr) == AF_INET) { + ri->addr = tor_addr_to_ipv4h(&bridge->addr); + ri->or_port = bridge->port; + log_info(LD_DIR, + "Adjusted bridge routerinfo for '%s' to match configured " + "address %s:%d.", + ri->nickname, fmt_addr32(ri->addr), ri->or_port); + } else if (tor_addr_family(&bridge->addr) == AF_INET6) { + tor_addr_copy(&ri->ipv6_addr, &bridge->addr); + ri->ipv6_orport = bridge->port; + log_info(LD_DIR, + "Adjusted bridge routerinfo for '%s' to match configured " + "address %s.", + ri->nickname, fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport)); + } else { + log_err(LD_BUG, "Address family not supported: %d.", + tor_addr_family(&bridge->addr)); + return; + } + } + + if (options->ClientPreferIPv6ORPort == -1) { + /* Mark which address to use based on which bridge_t we got. */ + node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 && + !tor_addr_is_null(&node->ri->ipv6_addr)); + } else { + /* Mark which address to use based on user preference */ + node->ipv6_preferred = (fascist_firewall_prefer_ipv6_orport(options) && + !tor_addr_is_null(&node->ri->ipv6_addr)); + } + + /* XXXipv6 we lack support for falling back to another address for + the same relay, warn the user */ + if (!tor_addr_is_null(&ri->ipv6_addr)) { + tor_addr_port_t ap; + node_get_pref_orport(node, &ap); + log_notice(LD_CONFIG, + "Bridge '%s' has both an IPv4 and an IPv6 address. " + "Will prefer using its %s address (%s) based on %s.", + ri->nickname, + node->ipv6_preferred ? "IPv6" : "IPv4", + fmt_addrport(&ap.addr, ap.port), + options->ClientPreferIPv6ORPort == -1 ? + "the configured Bridge address" : + "ClientPreferIPv6ORPort"); + } + } + if (node->rs) { + routerstatus_t *rs = node->rs; + tor_addr_from_ipv4h(&addr, rs->addr); + + if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && + bridge->port == rs->or_port) { + /* they match, so no need to do anything */ + } else { + rs->addr = tor_addr_to_ipv4h(&bridge->addr); + rs->or_port = bridge->port; + log_info(LD_DIR, + "Adjusted bridge routerstatus for '%s' to match " + "configured address %s.", + rs->nickname, fmt_addrport(&bridge->addr, rs->or_port)); + } + } +} + +/** We just learned a descriptor for a bridge. See if that + * digest is in our entry guard list, and add it if not. */ +void +learned_bridge_descriptor(routerinfo_t *ri, int from_cache) +{ + tor_assert(ri); + tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE); + if (get_options()->UseBridges) { + int first = num_bridges_usable() <= 1; + bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); + time_t now = time(NULL); + router_set_status(ri->cache_info.identity_digest, 1); + + if (bridge) { /* if we actually want to use this one */ + node_t *node; + /* it's here; schedule its re-fetch for a long time from now. */ + if (!from_cache) + download_status_reset(&bridge->fetch_status); + + node = node_get_mutable_by_id(ri->cache_info.identity_digest); + tor_assert(node); + rewrite_node_address_for_bridge(bridge, node); + if (tor_digest_is_zero(bridge->identity)) { + memcpy(bridge->identity,ri->cache_info.identity_digest, DIGEST_LEN); + log_notice(LD_DIR, "Learned identity %s for bridge at %s:%d", + hex_str(bridge->identity, DIGEST_LEN), + fmt_and_decorate_addr(&bridge->addr), + (int) bridge->port); + } + add_an_entry_guard(get_guard_selection_info(), node, 1, 1, 0, 0); + + log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, + from_cache ? "cached" : "fresh", router_describe(ri)); + /* set entry->made_contact so if it goes down we don't drop it from + * our entry node list */ + entry_guard_register_connect_status(ri->cache_info.identity_digest, + 1, 0, now); + if (first) { + routerlist_retry_directory_downloads(now); + } + } + } +} + +/** Return the number of bridges that have descriptors that + * are marked with purpose 'bridge' and are running. + * + * We use this function to decide if we're ready to start building + * circuits through our bridges, or if we need to wait until the + * directory "server/authority" requests finish. */ +int +any_bridge_descriptors_known(void) +{ + tor_assert(get_options()->UseBridges); + return choose_random_entry(NULL) != NULL; +} + +/** Return the number of bridges that have descriptors that are marked with + * purpose 'bridge' and are running. + */ +static int +num_bridges_usable(void) +{ + int n_options = 0; + tor_assert(get_options()->UseBridges); + (void) choose_random_entry_impl(get_guard_selection_info(), + NULL, 0, 0, &n_options); + return n_options; +} + +/** Return a smartlist containing all bridge identity digests */ +MOCK_IMPL(smartlist_t *, +list_bridge_identities, (void)) +{ + smartlist_t *result = NULL; + char *digest_tmp; + + if (get_options()->UseBridges && bridge_list) { + result = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { + digest_tmp = tor_malloc(DIGEST_LEN); + memcpy(digest_tmp, b->identity, DIGEST_LEN); + smartlist_add(result, digest_tmp); + } SMARTLIST_FOREACH_END(b); + } + + return result; +} + +/** Get the download status for a bridge descriptor given its identity */ +MOCK_IMPL(download_status_t *, +get_bridge_dl_status_by_id, (const char *digest)) +{ + download_status_t *dl = NULL; + + if (digest && get_options()->UseBridges && bridge_list) { + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { + if (tor_memeq(digest, b->identity, DIGEST_LEN)) { + dl = &(b->fetch_status); + break; + } + } SMARTLIST_FOREACH_END(b); + } + + return dl; +} + +/** Release all storage held in bridges.c */ +void +bridges_free_all(void) +{ + clear_bridge_list(); + smartlist_free(bridge_list); + bridge_list = NULL; +} + diff --git a/src/or/bridges.h b/src/or/bridges.h new file mode 100644 index 0000000000..738b1a68ee --- /dev/null +++ b/src/or/bridges.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file bridges.h + * \brief Header file for circuitbuild.c. + **/ + +#ifndef TOR_BRIDGES_H +#define TOR_BRIDGES_H + +struct bridge_line_t; + +void mark_bridge_list(void); +void sweep_bridge_list(void); + +int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port, + const char *digest); +int extend_info_is_a_configured_bridge(const extend_info_t *ei); +int routerinfo_is_a_configured_bridge(const routerinfo_t *ri); +int node_is_a_configured_bridge(const node_t *node); +void learned_router_identity(const tor_addr_t *addr, uint16_t port, + const char *digest); + +void bridge_add_from_config(struct bridge_line_t *bridge_line); +void retry_bridge_descriptor_fetch_directly(const char *digest); +void fetch_bridge_descriptors(const or_options_t *options, time_t now); +void learned_bridge_descriptor(routerinfo_t *ri, int from_cache); +int any_bridge_descriptors_known(void); +const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr, + uint16_t port); + +int any_bridges_dont_support_microdescriptors(void); + +const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr, + uint16_t port); +struct transport_t; +int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, + const struct transport_t **transport); + +MOCK_DECL(int, transport_is_needed, (const char *transport_name)); +int validate_pluggable_transports_config(void); + +MOCK_DECL(smartlist_t *, list_bridge_identities, (void)); +MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id, + (const char *digest)); + +void bridges_free_all(void); + +#endif + diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 2998f5c602..b482e7818d 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -28,6 +28,7 @@ #define CIRCUITBUILD_PRIVATE #include "or.h" +#include "bridges.h" #include "channel.h" #include "circpathbias.h" #define CIRCUITBUILD_PRIVATE diff --git a/src/or/circuituse.c b/src/or/circuituse.c index ba7b75ff25..2f972d1a28 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -29,6 +29,7 @@ #include "or.h" #include "addressmap.h" +#include "bridges.h" #include "channel.h" #include "circpathbias.h" #include "circuitbuild.h" diff --git a/src/or/config.c b/src/or/config.c index 9553822ba3..79a5847b81 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -60,6 +60,7 @@ #define CONFIG_PRIVATE #include "or.h" +#include "bridges.h" #include "compat.h" #include "addressmap.h" #include "channel.h" diff --git a/src/or/connection.c b/src/or/connection.c index 2e3df34a5a..2964c30f73 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -56,6 +56,7 @@ #define CONNECTION_PRIVATE #include "or.h" +#include "bridges.h" #include "buffers.h" /* * Define this so we get channel internal functions, since we're implementing diff --git a/src/or/connection_or.c b/src/or/connection_or.c index eb67f0653f..ecc5a4511a 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -21,6 +21,7 @@ * This module also implements the client side of the v3 Tor link handshake, **/ #include "or.h" +#include "bridges.h" #include "buffers.h" /* * Define this so we get channel internal functions, since we're implementing diff --git a/src/or/control.c b/src/or/control.c index f0cb435845..1d7ec1b57b 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -36,6 +36,7 @@ #include "or.h" #include "addressmap.h" +#include "bridges.h" #include "buffers.h" #include "channel.h" #include "channeltls.h" diff --git a/src/or/directory.c b/src/or/directory.c index 65ddd7d583..efa5a3126a 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -7,6 +7,7 @@ #include "or.h" #include "backtrace.h" +#include "bridges.h" #include "buffers.h" #include "circuitbuild.h" #include "config.h" diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 9af71404de..886e6533b7 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -15,6 +15,7 @@ #define ENTRYNODES_PRIVATE #include "or.h" +#include "bridges.h" #include "circpathbias.h" #include "circuitbuild.h" #include "circuitstats.h" @@ -37,32 +38,6 @@ #include "transports.h" #include "statefile.h" -/** Information about a configured bridge. Currently this just matches the - * ones in the torrc file, but one day we may be able to learn about new - * bridges on our own, and remember them in the state file. */ -typedef struct { - /** Address of the bridge. */ - tor_addr_t addr; - /** TLS port for the bridge. */ - uint16_t port; - /** Boolean: We are re-parsing our bridge list, and we are going to remove - * this one if we don't find it in the list of configured bridges. */ - unsigned marked_for_removal : 1; - /** Expected identity digest, or all zero bytes if we don't know what the - * digest should be. */ - char identity[DIGEST_LEN]; - - /** Name of pluggable transport protocol taken from its config line. */ - char *transport_name; - - /** When should we next try to fetch a descriptor for this bridge? */ - download_status_t fetch_status; - - /** A smartlist of k=v values to be passed to the SOCKS proxy, if - transports are used for this bridge. */ - smartlist_t *socks_args; -} bridge_info_t; - /** All the context for guard selection on a particular client */ struct guard_selection_s { @@ -99,14 +74,12 @@ struct guard_selection_s { static smartlist_t *guard_contexts = NULL; static guard_selection_t *curr_guard_context = NULL; -static void bridge_free(bridge_info_t *bridge); static const node_t *choose_random_entry_impl(guard_selection_t *gs, cpath_build_state_t *state, int for_directory, dirinfo_type_t dirtype, int *n_options_out); static guard_selection_t * guard_selection_new(void); -static int num_bridges_usable(void); /* Default number of entry guards in the case where the NumEntryGuards * consensus parameter is not set */ @@ -2282,330 +2255,6 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, guardfraction_bw->non_guard_bw = orig_bandwidth - (int) guard_bw; } -/** A list of configured bridges. Whenever we actually get a descriptor - * for one, we add it as an entry guard. Note that the order of bridges - * in this list does not necessarily correspond to the order of bridges - * in the torrc. */ -static smartlist_t *bridge_list = NULL; - -/** Mark every entry of the bridge list to be removed on our next call to - * sweep_bridge_list unless it has first been un-marked. */ -void -mark_bridge_list(void) -{ - if (!bridge_list) - bridge_list = smartlist_new(); - SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, - b->marked_for_removal = 1); -} - -/** Remove every entry of the bridge list that was marked with - * mark_bridge_list if it has not subsequently been un-marked. */ -void -sweep_bridge_list(void) -{ - if (!bridge_list) - bridge_list = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { - if (b->marked_for_removal) { - SMARTLIST_DEL_CURRENT(bridge_list, b); - bridge_free(b); - } - } SMARTLIST_FOREACH_END(b); -} - -/** Initialize the bridge list to empty, creating it if needed. */ -static void -clear_bridge_list(void) -{ - if (!bridge_list) - bridge_list = smartlist_new(); - SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b)); - smartlist_clear(bridge_list); -} - -/** Free the bridge bridge. */ -static void -bridge_free(bridge_info_t *bridge) -{ - if (!bridge) - return; - - tor_free(bridge->transport_name); - if (bridge->socks_args) { - SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s)); - smartlist_free(bridge->socks_args); - } - - tor_free(bridge); -} - -/** If we have a bridge configured whose digest matches digest, or a - * bridge with no known digest whose address matches any of the - * tor_addr_port_t's in orports, return that bridge. Else return - * NULL. */ -static bridge_info_t * -get_configured_bridge_by_orports_digest(const char *digest, - const smartlist_t *orports) -{ - if (!bridge_list) - return NULL; - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) - { - if (tor_digest_is_zero(bridge->identity)) { - SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, ap) - { - if (tor_addr_compare(&bridge->addr, &ap->addr, CMP_EXACT) == 0 && - bridge->port == ap->port) - return bridge; - } - SMARTLIST_FOREACH_END(ap); - } - if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) - return bridge; - } - SMARTLIST_FOREACH_END(bridge); - return NULL; -} - -/** If we have a bridge configured whose digest matches digest, or a - * bridge with no known digest whose address matches addr:port, - * return that bridge. Else return NULL. If digest is NULL, check for - * address/port matches only. */ -static bridge_info_t * -get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, - uint16_t port, - const char *digest) -{ - if (!bridge_list) - return NULL; - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) - { - if ((tor_digest_is_zero(bridge->identity) || digest == NULL) && - !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) && - bridge->port == port) - return bridge; - if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) - return bridge; - } - SMARTLIST_FOREACH_END(bridge); - return NULL; -} - -/** If we have a bridge configured whose digest matches digest, or a - * bridge with no known digest whose address matches addr:port, - * return 1. Else return 0. If digest is NULL, check for - * address/port matches only. */ -int -addr_is_a_configured_bridge(const tor_addr_t *addr, - uint16_t port, - const char *digest) -{ - tor_assert(addr); - return get_configured_bridge_by_addr_port_digest(addr, port, digest) ? 1 : 0; -} - -/** If we have a bridge configured whose digest matches - * ei->identity_digest, or a bridge with no known digest whose address - * matches ei->addr:ei->port, return 1. Else return 0. - * If ei->onion_key is NULL, check for address/port matches only. */ -int -extend_info_is_a_configured_bridge(const extend_info_t *ei) -{ - const char *digest = ei->onion_key ? ei->identity_digest : NULL; - return addr_is_a_configured_bridge(&ei->addr, ei->port, digest); -} - -/** Wrapper around get_configured_bridge_by_addr_port_digest() to look - * it up via router descriptor ri. */ -static bridge_info_t * -get_configured_bridge_by_routerinfo(const routerinfo_t *ri) -{ - bridge_info_t *bi = NULL; - smartlist_t *orports = router_get_all_orports(ri); - bi = get_configured_bridge_by_orports_digest(ri->cache_info.identity_digest, - orports); - SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); - smartlist_free(orports); - return bi; -} - -/** Return 1 if ri is one of our known bridges, else 0. */ -int -routerinfo_is_a_configured_bridge(const routerinfo_t *ri) -{ - return get_configured_bridge_by_routerinfo(ri) ? 1 : 0; -} - -/** Return 1 if node is one of our configured bridges, else 0. */ -int -node_is_a_configured_bridge(const node_t *node) -{ - int retval = 0; - smartlist_t *orports = node_get_all_orports(node); - retval = get_configured_bridge_by_orports_digest(node->identity, - orports) != NULL; - SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); - smartlist_free(orports); - return retval; -} - -/** We made a connection to a router at addr:port - * without knowing its digest. Its digest turned out to be digest. - * If it was a bridge, and we still don't know its digest, record it. - */ -void -learned_router_identity(const tor_addr_t *addr, uint16_t port, - const char *digest) -{ - bridge_info_t *bridge = - get_configured_bridge_by_addr_port_digest(addr, port, digest); - if (bridge && tor_digest_is_zero(bridge->identity)) { - char *transport_info = NULL; - const char *transport_name = - find_transport_name_by_bridge_addrport(addr, port); - if (transport_name) - tor_asprintf(&transport_info, " (with transport '%s')", transport_name); - - memcpy(bridge->identity, digest, DIGEST_LEN); - log_notice(LD_DIR, "Learned fingerprint %s for bridge %s%s.", - hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port), - transport_info ? transport_info : ""); - tor_free(transport_info); - } -} - -/** Return true if bridge has the same identity digest as - * digest. If digest is NULL, it matches - * bridges with unspecified identity digests. */ -static int -bridge_has_digest(const bridge_info_t *bridge, const char *digest) -{ - if (digest) - return tor_memeq(digest, bridge->identity, DIGEST_LEN); - else - return tor_digest_is_zero(bridge->identity); -} - -/** We are about to add a new bridge at addr:port, with optional - * digest and transport_name. Mark for removal any previously - * existing bridge with the same address and port, and warn the user as - * appropriate. - */ -static void -bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port, - const char *digest, const char *transport_name) -{ - /* Iterate the already-registered bridge list: - - If you find a bridge with the same adress and port, mark it for - removal. It doesn't make sense to have two active bridges with - the same IP:PORT. If the bridge in question has a different - digest or transport than digest/transport_name, - it's probably a misconfiguration and we should warn the user. - */ - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { - if (bridge->marked_for_removal) - continue; - - if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) { - - bridge->marked_for_removal = 1; - - if (!bridge_has_digest(bridge, digest) || - strcmp_opt(bridge->transport_name, transport_name)) { - /* warn the user */ - char *bridge_description_new, *bridge_description_old; - tor_asprintf(&bridge_description_new, "%s:%s:%s", - fmt_addrport(addr, port), - digest ? hex_str(digest, DIGEST_LEN) : "", - transport_name ? transport_name : ""); - tor_asprintf(&bridge_description_old, "%s:%s:%s", - fmt_addrport(&bridge->addr, bridge->port), - tor_digest_is_zero(bridge->identity) ? - "" : hex_str(bridge->identity,DIGEST_LEN), - bridge->transport_name ? bridge->transport_name : ""); - - log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict" - " with the already registered bridge '%s'. We will discard" - " the old bridge and keep '%s'. If this is not what you" - " wanted, please change your configuration file accordingly.", - bridge_description_new, bridge_description_old, - bridge_description_new); - - tor_free(bridge_description_new); - tor_free(bridge_description_old); - } - } - } SMARTLIST_FOREACH_END(bridge); -} - -/** Return True if we have a bridge that uses a transport with name - * transport_name. */ -MOCK_IMPL(int, -transport_is_needed, (const char *transport_name)) -{ - if (!bridge_list) - return 0; - - SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { - if (bridge->transport_name && - !strcmp(bridge->transport_name, transport_name)) - return 1; - } SMARTLIST_FOREACH_END(bridge); - - return 0; -} - -/** Register the bridge information in bridge_line to the - * bridge subsystem. Steals reference of bridge_line. */ -void -bridge_add_from_config(bridge_line_t *bridge_line) -{ - bridge_info_t *b; - - { /* Log the bridge we are about to register: */ - log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)", - fmt_addrport(&bridge_line->addr, bridge_line->port), - bridge_line->transport_name ? - bridge_line->transport_name : "no transport", - tor_digest_is_zero(bridge_line->digest) ? - "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN)); - - if (bridge_line->socks_args) { /* print socks arguments */ - int i = 0; - - tor_assert(smartlist_len(bridge_line->socks_args) > 0); - - log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:", - smartlist_len(bridge_line->socks_args)); - SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg, - log_debug(LD_CONFIG, "%d: %s", ++i, arg)); - } - } - - bridge_resolve_conflicts(&bridge_line->addr, - bridge_line->port, - bridge_line->digest, - bridge_line->transport_name); - - b = tor_malloc_zero(sizeof(bridge_info_t)); - tor_addr_copy(&b->addr, &bridge_line->addr); - b->port = bridge_line->port; - memcpy(b->identity, bridge_line->digest, DIGEST_LEN); - if (bridge_line->transport_name) - b->transport_name = bridge_line->transport_name; - b->fetch_status.schedule = DL_SCHED_BRIDGE; - b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL; - b->socks_args = bridge_line->socks_args; - if (!bridge_list) - bridge_list = smartlist_new(); - - tor_free(bridge_line); /* Deallocate bridge_line now. */ - - smartlist_add(bridge_list, b); -} - /** Returns true iff the node is used as a guard in the specified guard * context */ int @@ -2642,426 +2291,6 @@ is_node_used_as_guard, (const node_t *node)) get_guard_selection_info(), node); } -/** Return true iff routerset contains the bridge bridge. */ -static int -routerset_contains_bridge(const routerset_t *routerset, - const bridge_info_t *bridge) -{ - int result; - extend_info_t *extinfo; - tor_assert(bridge); - if (!routerset) - return 0; - - extinfo = extend_info_new( - NULL, bridge->identity, NULL, NULL, &bridge->addr, bridge->port); - result = routerset_contains_extendinfo(routerset, extinfo); - extend_info_free(extinfo); - return result; -} - -/** If digest is one of our known bridges, return it. */ -static bridge_info_t * -find_bridge_by_digest(const char *digest) -{ - SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge, - { - if (tor_memeq(bridge->identity, digest, DIGEST_LEN)) - return bridge; - }); - return NULL; -} - -/** Given the addr and port of a bridge, if that bridge - * supports a pluggable transport, return its name. Otherwise, return - * NULL. */ -const char * -find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) -{ - if (!bridge_list) - return NULL; - - SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { - if (tor_addr_eq(&bridge->addr, addr) && - (bridge->port == port)) - return bridge->transport_name; - } SMARTLIST_FOREACH_END(bridge); - - return NULL; -} - -/** If addr and port match the address and port of a - * bridge of ours that uses pluggable transports, place its transport - * in transport. - * - * Return 0 on success (found a transport, or found a bridge with no - * transport, or found no bridge); return -1 if we should be using a - * transport, but the transport could not be found. - */ -int -get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, - const transport_t **transport) -{ - *transport = NULL; - if (!bridge_list) - return 0; - - SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { - if (tor_addr_eq(&bridge->addr, addr) && - (bridge->port == port)) { /* bridge matched */ - if (bridge->transport_name) { /* it also uses pluggable transports */ - *transport = transport_get_by_name(bridge->transport_name); - if (*transport == NULL) { /* it uses pluggable transports, but - the transport could not be found! */ - return -1; - } - return 0; - } else { /* bridge matched, but it doesn't use transports. */ - break; - } - } - } SMARTLIST_FOREACH_END(bridge); - - *transport = NULL; - return 0; -} - -/** Return a smartlist containing all the SOCKS arguments that we - * should pass to the SOCKS proxy. */ -const smartlist_t * -get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) -{ - bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr, - port, - NULL); - return bridge ? bridge->socks_args : NULL; -} - -/** We need to ask bridge for its server descriptor. */ -static void -launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) -{ - const or_options_t *options = get_options(); - - if (connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &bridge->addr, bridge->port, - DIR_PURPOSE_FETCH_SERVERDESC)) - return; /* it's already on the way */ - - if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { - download_status_mark_impossible(&bridge->fetch_status); - log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", - safe_str_client(fmt_and_decorate_addr(&bridge->addr))); - return; - } - - /* Until we get a descriptor for the bridge, we only know one address for - * it. */ - if (!fascist_firewall_allows_address_addr(&bridge->addr, bridge->port, - FIREWALL_OR_CONNECTION, 0, 0)) { - log_notice(LD_CONFIG, "Tried to fetch a descriptor directly from a " - "bridge, but that bridge is not reachable through our " - "firewall."); - return; - } - - directory_initiate_command(&bridge->addr, bridge->port, - NULL, 0, /*no dirport*/ - bridge->identity, - DIR_PURPOSE_FETCH_SERVERDESC, - ROUTER_PURPOSE_BRIDGE, - DIRIND_ONEHOP, "authority.z", NULL, 0, 0); -} - -/** Fetching the bridge descriptor from the bridge authority returned a - * "not found". Fall back to trying a direct fetch. */ -void -retry_bridge_descriptor_fetch_directly(const char *digest) -{ - bridge_info_t *bridge = find_bridge_by_digest(digest); - if (!bridge) - return; /* not found? oh well. */ - - launch_direct_bridge_descriptor_fetch(bridge); -} - -/** For each bridge in our list for which we don't currently have a - * descriptor, fetch a new copy of its descriptor -- either directly - * from the bridge or via a bridge authority. */ -void -fetch_bridge_descriptors(const or_options_t *options, time_t now) -{ - int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO); - int ask_bridge_directly; - int can_use_bridge_authority; - - if (!bridge_list) - return; - - /* If we still have unconfigured managed proxies, don't go and - connect to a bridge. */ - if (pt_proxies_configuration_pending()) - return; - - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) - { - if (!download_status_is_ready(&bridge->fetch_status, now, - IMPOSSIBLE_TO_DOWNLOAD)) - continue; /* don't bother, no need to retry yet */ - if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { - download_status_mark_impossible(&bridge->fetch_status); - log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", - safe_str_client(fmt_and_decorate_addr(&bridge->addr))); - continue; - } - - /* schedule another fetch as if this one will fail, in case it does */ - download_status_failed(&bridge->fetch_status, 0); - - can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) && - num_bridge_auths; - ask_bridge_directly = !can_use_bridge_authority || - !options->UpdateBridgesFromAuthority; - log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)", - ask_bridge_directly, tor_digest_is_zero(bridge->identity), - !options->UpdateBridgesFromAuthority, !num_bridge_auths); - - if (ask_bridge_directly && - !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port, - FIREWALL_OR_CONNECTION, 0, - 0)) { - log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our " - "firewall policy. %s.", - fmt_addrport(&bridge->addr, bridge->port), - can_use_bridge_authority ? - "Asking bridge authority instead" : "Skipping"); - if (can_use_bridge_authority) - ask_bridge_directly = 0; - else - continue; - } - - if (ask_bridge_directly) { - /* we need to ask the bridge itself for its descriptor. */ - launch_direct_bridge_descriptor_fetch(bridge); - } else { - /* We have a digest and we want to ask an authority. We could - * combine all the requests into one, but that may give more - * hints to the bridge authority than we want to give. */ - char resource[10 + HEX_DIGEST_LEN]; - memcpy(resource, "fp/", 3); - base16_encode(resource+3, HEX_DIGEST_LEN+1, - bridge->identity, DIGEST_LEN); - memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3); - log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.", - resource); - directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, - ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY); - } - } - SMARTLIST_FOREACH_END(bridge); -} - -/** If our bridge is configured to be a different address than - * the bridge gives in node, rewrite the routerinfo - * we received to use the address we meant to use. Now we handle - * multihomed bridges better. - */ -static void -rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) -{ - /* XXXX move this function. */ - /* XXXX overridden addresses should really live in the node_t, so that the - * routerinfo_t and the microdesc_t can be immutable. But we can only - * do that safely if we know that no function that connects to an OR - * does so through an address from any source other than node_get_addr(). - */ - tor_addr_t addr; - const or_options_t *options = get_options(); - - if (node->ri) { - routerinfo_t *ri = node->ri; - tor_addr_from_ipv4h(&addr, ri->addr); - - if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && - bridge->port == ri->or_port) || - (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) && - bridge->port == ri->ipv6_orport)) { - /* they match, so no need to do anything */ - } else { - if (tor_addr_family(&bridge->addr) == AF_INET) { - ri->addr = tor_addr_to_ipv4h(&bridge->addr); - ri->or_port = bridge->port; - log_info(LD_DIR, - "Adjusted bridge routerinfo for '%s' to match configured " - "address %s:%d.", - ri->nickname, fmt_addr32(ri->addr), ri->or_port); - } else if (tor_addr_family(&bridge->addr) == AF_INET6) { - tor_addr_copy(&ri->ipv6_addr, &bridge->addr); - ri->ipv6_orport = bridge->port; - log_info(LD_DIR, - "Adjusted bridge routerinfo for '%s' to match configured " - "address %s.", - ri->nickname, fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport)); - } else { - log_err(LD_BUG, "Address family not supported: %d.", - tor_addr_family(&bridge->addr)); - return; - } - } - - if (options->ClientPreferIPv6ORPort == -1) { - /* Mark which address to use based on which bridge_t we got. */ - node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 && - !tor_addr_is_null(&node->ri->ipv6_addr)); - } else { - /* Mark which address to use based on user preference */ - node->ipv6_preferred = (fascist_firewall_prefer_ipv6_orport(options) && - !tor_addr_is_null(&node->ri->ipv6_addr)); - } - - /* XXXipv6 we lack support for falling back to another address for - the same relay, warn the user */ - if (!tor_addr_is_null(&ri->ipv6_addr)) { - tor_addr_port_t ap; - node_get_pref_orport(node, &ap); - log_notice(LD_CONFIG, - "Bridge '%s' has both an IPv4 and an IPv6 address. " - "Will prefer using its %s address (%s) based on %s.", - ri->nickname, - node->ipv6_preferred ? "IPv6" : "IPv4", - fmt_addrport(&ap.addr, ap.port), - options->ClientPreferIPv6ORPort == -1 ? - "the configured Bridge address" : - "ClientPreferIPv6ORPort"); - } - } - if (node->rs) { - routerstatus_t *rs = node->rs; - tor_addr_from_ipv4h(&addr, rs->addr); - - if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && - bridge->port == rs->or_port) { - /* they match, so no need to do anything */ - } else { - rs->addr = tor_addr_to_ipv4h(&bridge->addr); - rs->or_port = bridge->port; - log_info(LD_DIR, - "Adjusted bridge routerstatus for '%s' to match " - "configured address %s.", - rs->nickname, fmt_addrport(&bridge->addr, rs->or_port)); - } - } -} - -/** We just learned a descriptor for a bridge. See if that - * digest is in our entry guard list, and add it if not. */ -void -learned_bridge_descriptor(routerinfo_t *ri, int from_cache) -{ - tor_assert(ri); - tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE); - if (get_options()->UseBridges) { - int first = num_bridges_usable() <= 1; - bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); - time_t now = time(NULL); - router_set_status(ri->cache_info.identity_digest, 1); - - if (bridge) { /* if we actually want to use this one */ - node_t *node; - /* it's here; schedule its re-fetch for a long time from now. */ - if (!from_cache) - download_status_reset(&bridge->fetch_status); - - node = node_get_mutable_by_id(ri->cache_info.identity_digest); - tor_assert(node); - rewrite_node_address_for_bridge(bridge, node); - if (tor_digest_is_zero(bridge->identity)) { - memcpy(bridge->identity,ri->cache_info.identity_digest, DIGEST_LEN); - log_notice(LD_DIR, "Learned identity %s for bridge at %s:%d", - hex_str(bridge->identity, DIGEST_LEN), - fmt_and_decorate_addr(&bridge->addr), - (int) bridge->port); - } - add_an_entry_guard(get_guard_selection_info(), node, 1, 1, 0, 0); - - log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, - from_cache ? "cached" : "fresh", router_describe(ri)); - /* set entry->made_contact so if it goes down we don't drop it from - * our entry node list */ - entry_guard_register_connect_status(ri->cache_info.identity_digest, - 1, 0, now); - if (first) { - routerlist_retry_directory_downloads(now); - } - } - } -} - -/** Return the number of bridges that have descriptors that - * are marked with purpose 'bridge' and are running. - * - * We use this function to decide if we're ready to start building - * circuits through our bridges, or if we need to wait until the - * directory "server/authority" requests finish. */ -int -any_bridge_descriptors_known(void) -{ - tor_assert(get_options()->UseBridges); - return choose_random_entry(NULL) != NULL; -} - -/** Return the number of bridges that have descriptors that are marked with - * purpose 'bridge' and are running. - */ -static int -num_bridges_usable(void) -{ - int n_options = 0; - tor_assert(get_options()->UseBridges); - (void) choose_random_entry_impl(get_guard_selection_info(), - NULL, 0, 0, &n_options); - return n_options; -} - -/** Return a smartlist containing all bridge identity digests */ -MOCK_IMPL(smartlist_t *, -list_bridge_identities, (void)) -{ - smartlist_t *result = NULL; - char *digest_tmp; - - if (get_options()->UseBridges && bridge_list) { - result = smartlist_new(); - - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { - digest_tmp = tor_malloc(DIGEST_LEN); - memcpy(digest_tmp, b->identity, DIGEST_LEN); - smartlist_add(result, digest_tmp); - } SMARTLIST_FOREACH_END(b); - } - - return result; -} - -/** Get the download status for a bridge descriptor given its identity */ -MOCK_IMPL(download_status_t *, -get_bridge_dl_status_by_id, (const char *digest)) -{ - download_status_t *dl = NULL; - - if (digest && get_options()->UseBridges && bridge_list) { - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { - if (tor_memeq(digest, b->identity, DIGEST_LEN)) { - dl = &(b->fetch_status); - break; - } - } SMARTLIST_FOREACH_END(b); - } - - return dl; -} - /** Return 1 if we have at least one descriptor for an entry guard * (bridge or member of EntryNodes) and all descriptors we know are * down. Else return 0. If act is 1, then mark the down guards @@ -3163,9 +2392,6 @@ entry_guards_free_all(void) smartlist_free(guard_contexts); guard_contexts = NULL; } - clear_bridge_list(); - smartlist_free(bridge_list); - bridge_list = NULL; circuit_build_times_free_timeouts(get_circuit_build_times_mutable()); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 5c0857b426..8407945e4c 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -221,41 +221,11 @@ int is_node_used_as_guard_for_guard_selection(guard_selection_t *gs, const node_t *node); MOCK_DECL(int, is_node_used_as_guard, (const node_t *node)); -void mark_bridge_list(void); -void sweep_bridge_list(void); - -int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port, - const char *digest); -int extend_info_is_a_configured_bridge(const extend_info_t *ei); -int routerinfo_is_a_configured_bridge(const routerinfo_t *ri); -int node_is_a_configured_bridge(const node_t *node); -void learned_router_identity(const tor_addr_t *addr, uint16_t port, - const char *digest); -struct bridge_line_t; -void bridge_add_from_config(struct bridge_line_t *bridge_line); -void retry_bridge_descriptor_fetch_directly(const char *digest); -void fetch_bridge_descriptors(const or_options_t *options, time_t now); -void learned_bridge_descriptor(routerinfo_t *ri, int from_cache); -int any_bridge_descriptors_known(void); int entries_known_but_down(const or_options_t *options); void entries_retry_all(const or_options_t *options); -const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr, - uint16_t port); - -int any_bridges_dont_support_microdescriptors(void); - void entry_guards_free_all(void); -const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr, - uint16_t port); -struct transport_t; -int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, - const struct transport_t **transport); - -MOCK_DECL(int, transport_is_needed, (const char *transport_name)); -int validate_pluggable_transports_config(void); - double pathbias_get_close_success_count(entry_guard_t *guard); double pathbias_get_use_success_count(entry_guard_t *guard); @@ -275,9 +245,5 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, int orig_bandwidth, uint32_t guardfraction_percentage); -MOCK_DECL(smartlist_t *, list_bridge_identities, (void)); -MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id, - (const char *digest)); - #endif diff --git a/src/or/include.am b/src/or/include.am index 10f8b85bdf..278aa9baad 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -19,6 +19,7 @@ EXTRA_DIST+= src/or/ntmain.c src/or/Makefile.nmake LIBTOR_A_SOURCES = \ src/or/addressmap.c \ + src/or/bridges.c \ src/or/buffers.c \ src/or/channel.c \ src/or/channeltls.c \ @@ -130,6 +131,7 @@ endif ORHEADERS = \ src/or/addressmap.h \ + src/or/bridges.h \ src/or/buffers.h \ src/or/channel.h \ src/or/channeltls.h \ diff --git a/src/or/main.c b/src/or/main.c index c10f62724a..a508003f97 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -50,6 +50,7 @@ #include "or.h" #include "addressmap.h" #include "backtrace.h" +#include "bridges.h" #include "buffers.h" #include "channel.h" #include "channeltls.h" @@ -3114,6 +3115,7 @@ tor_free_all(int postfork) control_free_all(); sandbox_free_getaddrinfo_cache(); protover_free_all(); + bridges_free_all(); if (!postfork) { config_free_all(); or_state_free_all(); diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 316ce48387..ec8f77fa42 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -38,6 +38,7 @@ #define NETWORKSTATUS_PRIVATE #include "or.h" +#include "bridges.h" #include "channel.h" #include "circuitmux.h" #include "circuitmux_ewma.h" diff --git a/src/or/routerlist.c b/src/or/routerlist.c index c99d22ed41..f51b50e8e5 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -93,6 +93,7 @@ #define ROUTERLIST_PRIVATE #include "or.h" #include "backtrace.h" +#include "bridges.h" #include "crypto_ed25519.h" #include "circuitstats.h" #include "config.h" diff --git a/src/or/transports.c b/src/or/transports.c index f755882c16..feeff6e66c 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -91,13 +91,13 @@ #define PT_PRIVATE #include "or.h" +#include "bridges.h" #include "config.h" #include "circuitbuild.h" #include "transports.h" #include "util.h" #include "router.h" #include "statefile.h" -#include "entrynodes.h" #include "connection_or.h" #include "ext_orport.h" #include "control.h" diff --git a/src/test/test_config.c b/src/test/test_config.c index 2fc37b0bb8..cf047f0132 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -11,6 +11,7 @@ #include "or.h" #include "address.h" #include "addressmap.h" +#include "bridges.h" #include "circuitmux_ewma.h" #include "circuitbuild.h" #include "config.h" diff --git a/src/test/test_controller.c b/src/test/test_controller.c index f19c846144..4e65d76662 100644 --- a/src/test/test_controller.c +++ b/src/test/test_controller.c @@ -3,6 +3,7 @@ #define CONTROL_PRIVATE #include "or.h" +#include "bridges.h" #include "control.h" #include "entrynodes.h" #include "networkstatus.h" From c74542c51a2ce75e602e3e237bd41d1fd08a2fb0 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 15 Nov 2016 08:15:29 -0500 Subject: [PATCH 11/80] Add accessors as needed to repair compilation The previous commit, in moving a bunch of functions to bridges.c, broke compilation because bridges.c required two entry points to entrynodes.c it didn't have. --- src/or/bridges.c | 16 +--------------- src/or/entrynodes.c | 23 +++++++++++++++++++++++ src/or/entrynodes.h | 5 +++++ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/or/bridges.c b/src/or/bridges.c index 73510cf10c..508c77fc9e 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -52,7 +52,6 @@ typedef struct { } bridge_info_t; static void bridge_free(bridge_info_t *bridge); -static int num_bridges_usable(void); /** A list of configured bridges. Whenever we actually get a descriptor * for one, we add it as an entry guard. Note that the order of bridges @@ -719,7 +718,7 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) fmt_and_decorate_addr(&bridge->addr), (int) bridge->port); } - add_an_entry_guard(get_guard_selection_info(), node, 1, 1, 0, 0); + add_bridge_as_entry_guard(get_guard_selection_info(), node); log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, from_cache ? "cached" : "fresh", router_describe(ri)); @@ -747,19 +746,6 @@ any_bridge_descriptors_known(void) return choose_random_entry(NULL) != NULL; } -/** Return the number of bridges that have descriptors that are marked with - * purpose 'bridge' and are running. - */ -static int -num_bridges_usable(void) -{ - int n_options = 0; - tor_assert(get_options()->UseBridges); - (void) choose_random_entry_impl(get_guard_selection_info(), - NULL, 0, 0, &n_options); - return n_options; -} - /** Return a smartlist containing all bridge identity digests */ MOCK_IMPL(smartlist_t *, list_bridge_identities, (void)) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 886e6533b7..95b3c5aaaa 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -893,6 +893,16 @@ add_an_entry_guard(guard_selection_t *gs, return node; } +/** Entry point for bridges.c to add a bridge as guard. + * + * XXXX prop271 refactor.*/ +void +add_bridge_as_entry_guard(guard_selection_t *gs, + const node_t *chosen) +{ + add_an_entry_guard(gs, chosen, 1, 1, 0, 0); +} + /** Choose how many entry guards or directory guards we'll use. If * for_directory is true, we return how many directory guards to * use; else we return how many entry guards to use. */ @@ -1490,6 +1500,19 @@ choose_random_dirguard(dirinfo_type_t type) NULL, 1, type, NULL); } +/** Return the number of bridges that have descriptors that are marked with + * purpose 'bridge' and are running. + */ +int +num_bridges_usable(void) +{ + int n_options = 0; + tor_assert(get_options()->UseBridges); + (void) choose_random_entry_impl(get_guard_selection_info(), + NULL, 0, 0, &n_options); + return n_options; +} + /** Filter all_entry_guards for usable entry guards and put them * in live_entry_guards. We filter based on whether the node is * currently alive, and on whether it satisfies the restrictions diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 8407945e4c..f07f843ae7 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -154,6 +154,11 @@ const char *entry_guard_get_rsa_id_digest(const entry_guard_t *guard); const char *entry_guard_describe(const entry_guard_t *guard); guard_pathbias_t *entry_guard_get_pathbias_state(entry_guard_t *guard); +/* Used by bridges.c only. */ +void add_bridge_as_entry_guard(guard_selection_t *gs, + const node_t *chosen); +int num_bridges_usable(void); + #ifdef ENTRYNODES_PRIVATE STATIC time_t randomize_time(time_t now, time_t max_backdate); STATIC void entry_guard_add_to_sample(guard_selection_t *gs, From 3c12133038f5a9213b13beca50d91ddac2f9d7fb Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 15 Nov 2016 08:28:41 -0500 Subject: [PATCH 12/80] Collect old guard algorithm parameters into one place --- src/or/entrynodes.c | 156 ++++++++++++++++++++++------------------- src/or/networkstatus.h | 4 ++ 2 files changed, 87 insertions(+), 73 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 95b3c5aaaa..461d29f8e9 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -81,6 +81,12 @@ static const node_t *choose_random_entry_impl(guard_selection_t *gs, int *n_options_out); static guard_selection_t * guard_selection_new(void); +/** + * @name Constants for old (pre-prop271) guard selection algorithm. + */ + +/**@{*/ + /* Default number of entry guards in the case where the NumEntryGuards * consensus parameter is not set */ #define DEFAULT_N_GUARDS 1 @@ -88,6 +94,62 @@ static guard_selection_t * guard_selection_new(void); * consensus parameter is set). */ #define MIN_N_GUARDS 1 #define MAX_N_GUARDS 10 +/** Largest amount that we'll backdate chosen_on_date */ +#define CHOSEN_ON_DATE_SLOP (30*86400) +/** How long (in seconds) do we allow an entry guard to be nonfunctional, + * unlisted, excluded, or otherwise nonusable before we give up on it? */ +#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) +/**}@*/ + +/** + * @name Networkstatus parameters for old (pre-prop271) guard selection + */ +/**@}*/ +/** Choose how many entry guards or directory guards we'll use. If + * for_directory is true, we return how many directory guards to + * use; else we return how many entry guards to use. */ +STATIC int +decide_num_guards(const or_options_t *options, int for_directory) +{ + if (for_directory) { + int answer; + if (options->NumDirectoryGuards != 0) + return options->NumDirectoryGuards; + answer = networkstatus_get_param(NULL, "NumDirectoryGuards", 0, 0, 10); + if (answer) /* non-zero means use the consensus value */ + return answer; + } + + if (options->NumEntryGuards) + return options->NumEntryGuards; + + /* Use the value from the consensus, or 3 if no guidance. */ + return networkstatus_get_param(NULL, "NumEntryGuards", DEFAULT_N_GUARDS, + MIN_N_GUARDS, MAX_N_GUARDS); +} + +/** Return 0 if we should apply guardfraction information found in the + * consensus. A specific consensus can be specified with the + * ns argument, if NULL the most recent one will be picked.*/ +int +should_apply_guardfraction(const networkstatus_t *ns) +{ + /* We need to check the corresponding torrc option and the consensus + * parameter if we need to. */ + const or_options_t *options = get_options(); + + /* If UseGuardFraction is 'auto' then check the same-named consensus + * parameter. If the consensus parameter is not present, default to + * "off". */ + if (options->UseGuardFraction == -1) { + return networkstatus_get_param(ns, "UseGuardFraction", + 0, /* default to "off" */ + 0, 1); + } + + return options->UseGuardFraction; +} +/**@}*/ /** Allocate a new guard_selection_t */ @@ -795,9 +857,6 @@ control_event_guard_deferred(void) #endif } -/** Largest amount that we'll backdate chosen_on_date */ -#define CHOSEN_ON_DATE_SLOP (30*86400) - /** Add a new (preferably stable and fast) router to our chosen_entry_guards * list for the supplied guard selection. Return a pointer to the router if * we succeed, or NULL if we can't find any more suitable entries. @@ -903,27 +962,30 @@ add_bridge_as_entry_guard(guard_selection_t *gs, add_an_entry_guard(gs, chosen, 1, 1, 0, 0); } -/** Choose how many entry guards or directory guards we'll use. If - * for_directory is true, we return how many directory guards to - * use; else we return how many entry guards to use. */ -STATIC int -decide_num_guards(const or_options_t *options, int for_directory) +/** + * Return the minimum lifetime of working entry guard, in seconds, + * as given in the consensus networkstatus. (Plus CHOSEN_ON_DATE_SLOP, + * so that we can do the chosen_on_date randomization while achieving the + * desired minimum lifetime.) + */ +static int32_t +guards_get_lifetime(void) { - if (for_directory) { - int answer; - if (options->NumDirectoryGuards != 0) - return options->NumDirectoryGuards; - answer = networkstatus_get_param(NULL, "NumDirectoryGuards", 0, 0, 10); - if (answer) /* non-zero means use the consensus value */ - return answer; + const or_options_t *options = get_options(); +#define DFLT_GUARD_LIFETIME (86400 * 60) /* Two months. */ +#define MIN_GUARD_LIFETIME (86400 * 30) /* One months. */ +#define MAX_GUARD_LIFETIME (86400 * 1826) /* Five years. */ + + if (options->GuardLifetime >= 1) { + return CLAMP(MIN_GUARD_LIFETIME, + options->GuardLifetime, + MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; } - if (options->NumEntryGuards) - return options->NumEntryGuards; - - /* Use the value from the consensus, or 3 if no guidance. */ - return networkstatus_get_param(NULL, "NumEntryGuards", DEFAULT_N_GUARDS, - MIN_N_GUARDS, MAX_N_GUARDS); + return networkstatus_get_param(NULL, "GuardLifetime", + DFLT_GUARD_LIFETIME, + MIN_GUARD_LIFETIME, + MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; } /** If the use of entry guards is configured, choose more entry guards @@ -950,10 +1012,6 @@ pick_entry_guards(guard_selection_t *gs, entry_guards_changed_for_guard_selection(gs); } -/** How long (in seconds) do we allow an entry guard to be nonfunctional, - * unlisted, excluded, or otherwise nonusable before we give up on it? */ -#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) - /** Release all storage held by e. */ STATIC void entry_guard_free(entry_guard_t *e) @@ -966,32 +1024,6 @@ entry_guard_free(entry_guard_t *e) tor_free(e); } -/** - * Return the minimum lifetime of working entry guard, in seconds, - * as given in the consensus networkstatus. (Plus CHOSEN_ON_DATE_SLOP, - * so that we can do the chosen_on_date randomization while achieving the - * desired minimum lifetime.) - */ -static int32_t -guards_get_lifetime(void) -{ - const or_options_t *options = get_options(); -#define DFLT_GUARD_LIFETIME (86400 * 60) /* Two months. */ -#define MIN_GUARD_LIFETIME (86400 * 30) /* One months. */ -#define MAX_GUARD_LIFETIME (86400 * 1826) /* Five years. */ - - if (options->GuardLifetime >= 1) { - return CLAMP(MIN_GUARD_LIFETIME, - options->GuardLifetime, - MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; - } - - return networkstatus_get_param(NULL, "GuardLifetime", - DFLT_GUARD_LIFETIME, - MIN_GUARD_LIFETIME, - MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; -} - /** Remove from a guard selection context any entry guard which was selected * by an unknown version of Tor, or which was selected by a version of Tor * that's known to select entry guards badly, or which was selected more 2 @@ -2221,28 +2253,6 @@ getinfo_helper_entry_guards(control_connection_t *conn, return 0; } -/** Return 0 if we should apply guardfraction information found in the - * consensus. A specific consensus can be specified with the - * ns argument, if NULL the most recent one will be picked.*/ -int -should_apply_guardfraction(const networkstatus_t *ns) -{ - /* We need to check the corresponding torrc option and the consensus - * parameter if we need to. */ - const or_options_t *options = get_options(); - - /* If UseGuardFraction is 'auto' then check the same-named consensus - * parameter. If the consensus parameter is not present, default to - * "off". */ - if (options->UseGuardFraction == -1) { - return networkstatus_get_param(ns, "UseGuardFraction", - 0, /* default to "off" */ - 0, 1); - } - - return options->UseGuardFraction; -} - /* Given the original bandwidth of a guard and its guardfraction, * calculate how much bandwidth the guard should have as a guard and * as a non-guard. diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 71f36b69ed..96f8347584 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -107,6 +107,10 @@ void signed_descs_update_status_from_consensus_networkstatus( char *networkstatus_getinfo_helper_single(const routerstatus_t *rs); char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now); void networkstatus_dump_bridge_status_to_file(time_t now); +int32_t networkstatus_get_param(const networkstatus_t *ns, + const char *param_name, + int32_t default_val, int32_t min_val, + int32_t max_val); int32_t networkstatus_get_param(const networkstatus_t *ns, const char *param_name, int32_t default_val, int32_t min_val, From 6a02f9f35a824ced871de7bb80c8266b873a0710 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 15 Nov 2016 08:38:33 -0500 Subject: [PATCH 13/80] Add parameters for new (prop271) guard algorithm. These are taken from the proposal, and defined there. Some of them should turn into consensus parameters. Also, remove some dead code that was there to make compilation work, and use ATTR_UNUSED like a normal person. --- src/or/entrynodes.c | 34 ++++++++++++++++++++++++---------- src/or/networkstatus.h | 4 ---- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 461d29f8e9..c6ed59ddce 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -151,6 +151,26 @@ should_apply_guardfraction(const networkstatus_t *ns) } /**@}*/ +/** + * @name Parameters for new (prop271) entry guard algorithm. + */ +/* XXXX prop271 some of these should be networkstatus parameters */ +#define MIN_SAMPLE_THRESHOLD 15 +#define MAX_SAMPLE_THRESHOLD 50 +#define GUARD_LIFETIME_DAYS 120 +#define REMOVE_UNLISTED_GUARDS_AFTER_DAYS 20 +#define MIN_FILTERED_SAMPLE_SIZE 20 +#define N_PRIMARY_GUARDS 3 +#define PRIMARY_GUARDS_RETRY_SCHEDULE /* XXX prop271 */ +#define OTHER_GUARDS_RETRY_SCHEDULE /* XXX prop271 */ +#define INTERNET_LIKELY_DOWN_INTERVAL (10*60) +#define NONPRIMARY_GUARD_CONNECT_TIMEOUT 15 +#define NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60) +#define MEANINGFUL_RESTRICTION_FRAC 0.2 +#define EXTREME_RESTRICTION_FRAC 0.01 +#define GUARD_CONFIRMED_MIN_LIFETIME_DAYS 60 +/**}@*/ + /** Allocate a new guard_selection_t */ static guard_selection_t * @@ -254,12 +274,11 @@ randomize_time(time_t now, time_t max_backdate) /** * DOCDOC */ -STATIC void +ATTR_UNUSED STATIC void entry_guard_add_to_sample(guard_selection_t *gs, node_t *node) { - (void) entry_guard_add_to_sample; // XXXX prop271 remove -- unused - const int GUARD_LIFETIME = 90 * 86400; // xxxx prop271 + const int GUARD_LIFETIME = GUARD_LIFETIME_DAYS * 86400; tor_assert(gs); tor_assert(node); @@ -296,7 +315,7 @@ entry_guard_add_to_sample(guard_selection_t *gs, * Return a newly allocated string for encoding the persistent parts of * guard to the state file. */ -STATIC char * +ATTR_UNUSED STATIC char * entry_guard_encode_for_state(entry_guard_t *guard) { /* @@ -356,7 +375,7 @@ entry_guard_encode_for_state(entry_guard_t *guard) * (if possible) and return an entry_guard_t object for it. Return NULL * on complete failure. */ -STATIC entry_guard_t * +ATTR_UNUSED STATIC entry_guard_t * entry_guard_parse_from_state(const char *s) { /* Unrecognized entries get put in here. */ @@ -1776,9 +1795,6 @@ entry_guards_parse_state_for_guard_selection( const char *state_version = state->TorVersion; digestmap_t *added_by = digestmap_new(); - if (0) entry_guard_parse_from_state(NULL); // XXXX prop271 remove -- unused - if (0) entry_guard_add_to_sample(NULL, NULL); // XXXX prop271 remove - tor_assert(gs != NULL); *msg = NULL; @@ -2104,8 +2120,6 @@ entry_guards_update_state(or_state_t *state) config_line_t **next, *line; guard_selection_t *gs = get_guard_selection_info(); - if (0) entry_guard_encode_for_state(NULL); // XXXX prop271 remove -- unused - tor_assert(gs != NULL); tor_assert(gs->chosen_entry_guards != NULL); diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 96f8347584..71f36b69ed 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -107,10 +107,6 @@ void signed_descs_update_status_from_consensus_networkstatus( char *networkstatus_getinfo_helper_single(const routerstatus_t *rs); char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now); void networkstatus_dump_bridge_status_to_file(time_t now); -int32_t networkstatus_get_param(const networkstatus_t *ns, - const char *param_name, - int32_t default_val, int32_t min_val, - int32_t max_val); int32_t networkstatus_get_param(const networkstatus_t *ns, const char *param_name, int32_t default_val, int32_t min_val, From bf64564e37c5fc01111bc476d1b93890b15a18bf Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 15 Nov 2016 18:57:17 -0500 Subject: [PATCH 14/80] Add a GUARD log domain, for use with new guards code --- src/common/log.c | 2 +- src/common/torlog.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/log.c b/src/common/log.c index 3b0eb882c3..d031364c63 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -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", "GUARD", NULL }; /** Return a bitmask for the log domain for which domain is the name, diff --git a/src/common/torlog.h b/src/common/torlog.h index 6732a42741..bc957858d9 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -99,8 +99,10 @@ #define LD_CHANNEL (1u<<21) /** Scheduler */ #define LD_SCHED (1u<<22) +/** Guard nodes */ +#define LD_GUARD (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. */ From 21c47c44109a9de373f40c454e653953ba21312e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 14:07:53 -0500 Subject: [PATCH 15/80] Add a smartlist_remove_keeporder() function, with tests. --- src/common/container.c | 18 +++++++++++++++++ src/common/container.h | 1 + src/test/test_containers.c | 41 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/common/container.c b/src/common/container.c index ec59dccf62..1448ab403c 100644 --- a/src/common/container.c +++ b/src/common/container.c @@ -132,6 +132,24 @@ smartlist_remove(smartlist_t *sl, const void *element) } } +/** As smartlist_remove, but do not change the order of + * any elements not removed */ +void +smartlist_remove_keeporder(smartlist_t *sl, const void *element) +{ + int i, j, num_used_orig = sl->num_used; + if (element == NULL) + return; + + for (i=j=0; j < num_used_orig; ++j) { + if (sl->list[j] == element) { + --sl->num_used; + } else { + sl->list[i++] = sl->list[j]; + } + } +} + /** If sl is nonempty, remove and return the final element. Otherwise, * return NULL. */ void * diff --git a/src/common/container.h b/src/common/container.h index 71495b660a..00c3ca81ad 100644 --- a/src/common/container.h +++ b/src/common/container.h @@ -33,6 +33,7 @@ void smartlist_clear(smartlist_t *sl); void smartlist_add(smartlist_t *sl, void *element); void smartlist_add_all(smartlist_t *sl, const smartlist_t *s2); void smartlist_remove(smartlist_t *sl, const void *element); +void smartlist_remove_keeporder(smartlist_t *sl, const void *element); void *smartlist_pop_last(smartlist_t *sl); void smartlist_reverse(smartlist_t *sl); void smartlist_string_remove(smartlist_t *sl, const char *element); diff --git a/src/test/test_containers.c b/src/test/test_containers.c index d7291a2ce2..41f3f873de 100644 --- a/src/test/test_containers.c +++ b/src/test/test_containers.c @@ -882,6 +882,46 @@ test_container_strmap(void *arg) tor_free(v105); } +static void +test_container_smartlist_remove(void *arg) +{ + (void) arg; + int array[5]; + smartlist_t *sl = smartlist_new(); + int i,j; + + for (j=0; j < 2; ++j) + for (i=0; i < 5; ++i) + smartlist_add(sl, &array[i]); + + smartlist_remove(sl, &array[0]); + smartlist_remove(sl, &array[3]); + smartlist_remove(sl, &array[4]); + tt_assert(! smartlist_contains(sl, &array[0])); + tt_assert(smartlist_contains(sl, &array[1])); + tt_assert(smartlist_contains(sl, &array[2])); + tt_assert(! smartlist_contains(sl, &array[3])); + tt_assert(! smartlist_contains(sl, &array[4])); + tt_int_op(smartlist_len(sl), OP_EQ, 4); + + smartlist_clear(sl); + for (j=0; j < 2; ++j) + for (i=0; i < 5; ++i) + smartlist_add(sl, &array[i]); + + smartlist_remove_keeporder(sl, &array[0]); + smartlist_remove_keeporder(sl, &array[3]); + smartlist_remove_keeporder(sl, &array[4]); + tt_int_op(smartlist_len(sl), OP_EQ, 4); + tt_ptr_op(smartlist_get(sl, 0), OP_EQ, &array[1]); + tt_ptr_op(smartlist_get(sl, 1), OP_EQ, &array[2]); + tt_ptr_op(smartlist_get(sl, 2), OP_EQ, &array[1]); + tt_ptr_op(smartlist_get(sl, 3), OP_EQ, &array[2]); + + done: + smartlist_free(sl); +} + /** Run unit tests for getting the median of a list. */ static void test_container_order_functions(void *arg) @@ -1239,6 +1279,7 @@ struct testcase_t container_tests[] = { CONTAINER_LEGACY(smartlist_digests), CONTAINER_LEGACY(smartlist_join), CONTAINER_LEGACY(smartlist_pos), + CONTAINER(smartlist_remove, 0), CONTAINER(smartlist_ints_eq, 0), CONTAINER_LEGACY(bitarray), CONTAINER_LEGACY(digestset), From 7bf946965bad88116582dfd3d20e5837eeddd758 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 16 Nov 2016 08:21:39 -0500 Subject: [PATCH 16/80] Implement most of the prop271 data structure backends. This code handles: * Maintaining the sampled set, the filtered set, and the usable_filtered set. * Maintaining the confirmed and primary guard lists. * Picking guards for circuits, and updating guard state when circuit state changes. Additionally, I've done code structure movement: even more constants and structures from entrynodes.c have become ENTRYNODES_PRIVATE fields of entrynodes.h. I've also included a bunch of documentation and a bunch of unit tests. Coverage on the new code is pretty high. I've noted important things to resolve before this branch is done with the /XXXX.*prop271/ regex. --- src/or/entrynodes.c | 1256 ++++++++++++++++++++++++++++++++---- src/or/entrynodes.h | 272 +++++++- src/test/test_entrynodes.c | 1093 +++++++++++++++++++++++++++++++ 3 files changed, 2488 insertions(+), 133 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index c6ed59ddce..958aba47da 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -10,7 +10,113 @@ * * Entry nodes can be guards (for general use) or bridges (for censorship * circumvention). + * + * XXXX prop271 This module is in flux, since I'm currently in the middle of + * implementation proposal 271. The module documentation here will describe + * the new algorithm and data structures; the old ones should get removed as + * proposal 271 is completed. + * + * In general, we use entry guards to prevent traffic-sampling attacks: + * if we chose every circuit independently, an adversary controlling + * some fraction of paths on the network would observe a sample of every + * user's traffic. Using guards gives users a chance of not being + * profiled. + * + * The current entry guard selection code is designed to try to avoid + * _ever_ trying every guard on the network, to try to stick to guards + * that we've used before, to handle hostile/broken networks, and + * to behave sanely when the network goes up and down. + * + * Our algorithm works as follows: First, we maintain a SAMPLE of guards + * we've seen in the networkstatus consensus. We maintain this sample + * over time, and store it persistently; it is chosen without reference + * to our configuration or firewall rules. Guards remain in the sample + * as they enter and leave the consensus. We expand this sample as + * needed, up to a maximum size. + * + * As a subset of the sample, we maintain a FILTERED SET of the guards + * that we would be willing to use if we could connect to them. The + * filter removes all the guards that we're excluding because they're + * bridges (or not bridges), because we have restrictive firewall rules, + * because of ExcludeNodes, because we of path bias restrictions, + * because they're absent from the network at present, and so on. + * + * As a subset of the filtered set, we keep a REACHABLE FILTERED SET + * (also called a "usable filtered set") of those guards that we call + * "reachable" or "maybe reachable". A guard is reachable if we've + * connected to it more recently than we've failed. A guard is "maybe + * reachable" if we have never tried to connect to it, or if we + * failed to connect to it so long ago that we no longer think our + * failure means it's down. + * + * As a persistent ordered list whose elements are taken from the + * sampled set, we track a CONFIRMED GUARDS LIST. A guard becomes + * confirmed when we successfully build a circuit through it, and decide + * to use that circuit. We order the guards on this list by the order + * in which they became confirmed. + * + * And as a final group, we have an ordered list of PRIMARY GUARDS, + * whose elements are taken from the filtered set. We prefer + * confirmed guards to non-confirmed guards for this list, and place + * other restrictions on it. The primary guards are the ones that we + * connect to "when nothing is wrong" -- circuits through them can be used + * immediately. + * + * To build circuits, we take a primary guard if possible -- or a + * reachable filtered confirmed guard if no primary guard is possible -- + * or a random reachable filtered guard otherwise. If the guard is + * primary, we can use the circuit immediately on success. Otherwise, + * the guard is now "pending" -- we won't use its circuit unless all + * of the circuits we're trying to build through better guards have + * definitely failed. + * + * While we're building circuits, we track a little "guard state" for + * each circuit. We use this to keep track of whether the circuit is + * one that we can use as soon as its done, or whether it's one that + * we should keep around to see if we can do better. In the latter case, + * a periodic call to entry_guards_upgrade_waiting_circuits() will + * eventually upgrade it. **/ +/* DOCDOC -- expand this. + * + * XXXX prop271 -- make sure we check all of these properties everywhere we + * should. + * + * Information invariants: + * + * [x] whenever a guard becomes unreachable, clear its usable_filtered flag. + * + * [x] Whenever a guard becomes reachable or maybe-reachable, if its filtered + * flag is set, set its usable_filtered flag. + * + * [ ] Whenever we get a new consensus, call update_from_consensus(). (LATER.) + * + * [ ] Whenever the configuration changes in a relevant way, update the + * filtered/usable flags. (LATER.) + * + * [x] Whenever we add a guard to the sample, make sure its filtered/usable + * flags are set as possible. + * + * [x] Whenever we remove a guard from the sample, remove it from the primary + * and confirmed lists. + * + * [ ] When we make a guard confirmed, update the primary list. + * + * [ ] When we make a guard filtered or unfiltered, update the primary list. + * + * [ ] When we are about to pick a guard, make sure that the primary list is + * full. + * + * [x] Before calling sample_reachable_filtered_entry_guards(), make sure + * that the filtered, primary, and confirmed flags are up-to-date. + * + * [x] Call entry_guard_consider_retry every time we are about to check + * is_usable_filtered or is_reachable, and every time we set + * is_filtered to 1. + * + * [x] Call entry_guards_changed_for_guard_selection() whenever we update + * a persistent field. + */ #define ENTRYNODES_PRIVATE @@ -38,39 +144,6 @@ #include "transports.h" #include "statefile.h" -/** All the context for guard selection on a particular client */ - -struct guard_selection_s { - /** - * A value of 1 means that guard_selection_t structures have changed - * and those changes need to be flushed to disk. - * - * XXX we don't know how to flush multiple guard contexts to disk yet; - * fix that as soon as any way to change the default exists, or at least - * make sure this gets set on change. - */ - int dirty; - - /** - * A list of the sampled entry guards, as entry_guard_t structures. - * Not in any particular order. */ - smartlist_t *sampled_entry_guards; - - /** - * A list of our chosen entry guards, as entry_guard_t structures; this - * preserves the pre-Prop271 behavior. - */ - smartlist_t *chosen_entry_guards; - - /** - * When we try to choose an entry guard, should we parse and add - * config's EntryNodes first? This was formerly a global. - */ - int should_add_entry_nodes; - - int filtered_up_to_date; -}; - static smartlist_t *guard_contexts = NULL; static guard_selection_t *curr_guard_context = NULL; @@ -79,54 +152,9 @@ static const node_t *choose_random_entry_impl(guard_selection_t *gs, int for_directory, dirinfo_type_t dirtype, int *n_options_out); -static guard_selection_t * guard_selection_new(void); - -/** - * @name Constants for old (pre-prop271) guard selection algorithm. - */ - -/**@{*/ - -/* Default number of entry guards in the case where the NumEntryGuards - * consensus parameter is not set */ -#define DEFAULT_N_GUARDS 1 -/* Minimum and maximum number of entry guards (in case the NumEntryGuards - * consensus parameter is set). */ -#define MIN_N_GUARDS 1 -#define MAX_N_GUARDS 10 -/** Largest amount that we'll backdate chosen_on_date */ -#define CHOSEN_ON_DATE_SLOP (30*86400) -/** How long (in seconds) do we allow an entry guard to be nonfunctional, - * unlisted, excluded, or otherwise nonusable before we give up on it? */ -#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) -/**}@*/ - -/** - * @name Networkstatus parameters for old (pre-prop271) guard selection - */ -/**@}*/ -/** Choose how many entry guards or directory guards we'll use. If - * for_directory is true, we return how many directory guards to - * use; else we return how many entry guards to use. */ -STATIC int -decide_num_guards(const or_options_t *options, int for_directory) -{ - if (for_directory) { - int answer; - if (options->NumDirectoryGuards != 0) - return options->NumDirectoryGuards; - answer = networkstatus_get_param(NULL, "NumDirectoryGuards", 0, 0, 10); - if (answer) /* non-zero means use the consensus value */ - return answer; - } - - if (options->NumEntryGuards) - return options->NumEntryGuards; - - /* Use the value from the consensus, or 3 if no guidance. */ - return networkstatus_get_param(NULL, "NumEntryGuards", DEFAULT_N_GUARDS, - MIN_N_GUARDS, MAX_N_GUARDS); -} +static void entry_guard_set_filtered_flags(const or_options_t *options, + guard_selection_t *gs, + entry_guard_t *guard); /** Return 0 if we should apply guardfraction information found in the * consensus. A specific consensus can be specified with the @@ -149,31 +177,10 @@ should_apply_guardfraction(const networkstatus_t *ns) return options->UseGuardFraction; } -/**@}*/ -/** - * @name Parameters for new (prop271) entry guard algorithm. - */ -/* XXXX prop271 some of these should be networkstatus parameters */ -#define MIN_SAMPLE_THRESHOLD 15 -#define MAX_SAMPLE_THRESHOLD 50 -#define GUARD_LIFETIME_DAYS 120 -#define REMOVE_UNLISTED_GUARDS_AFTER_DAYS 20 -#define MIN_FILTERED_SAMPLE_SIZE 20 -#define N_PRIMARY_GUARDS 3 -#define PRIMARY_GUARDS_RETRY_SCHEDULE /* XXX prop271 */ -#define OTHER_GUARDS_RETRY_SCHEDULE /* XXX prop271 */ -#define INTERNET_LIKELY_DOWN_INTERVAL (10*60) -#define NONPRIMARY_GUARD_CONNECT_TIMEOUT 15 -#define NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60) -#define MEANINGFUL_RESTRICTION_FRAC 0.2 -#define EXTREME_RESTRICTION_FRAC 0.01 -#define GUARD_CONFIRMED_MIN_LIFETIME_DAYS 60 -/**}@*/ +/** Allocate and return a new guard_selection_t */ -/** Allocate a new guard_selection_t */ - -static guard_selection_t * +STATIC guard_selection_t * guard_selection_new(void) { guard_selection_t *gs; @@ -181,6 +188,8 @@ guard_selection_new(void) gs = tor_malloc_zero(sizeof(*gs)); gs->chosen_entry_guards = smartlist_new(); gs->sampled_entry_guards = smartlist_new(); + gs->confirmed_entry_guards = smartlist_new(); + gs->primary_entry_guards = smartlist_new(); return gs; } @@ -255,9 +264,11 @@ entry_guard_get_pathbias_state(entry_guard_t *guard) } /** Return an interval betweeen 'now' and 'max_backdate' seconds in the past, - * chosen uniformly at random. */ -STATIC time_t -randomize_time(time_t now, time_t max_backdate) + * chosen uniformly at random. We use this before recording persistent + * dates, so that we aren't leaking exactly when we recorded it. + */ +MOCK_IMPL(STATIC time_t, +randomize_time,(time_t now, time_t max_backdate)) { tor_assert(max_backdate > 0); @@ -272,25 +283,71 @@ randomize_time(time_t now, time_t max_backdate) } /** - * DOCDOC + * Return true iff node has all the flags needed for us to consider it + * a possible guard when sampling guards. */ -ATTR_UNUSED STATIC void +static int +node_is_possible_guard(guard_selection_t *gs, const node_t *node) +{ + /* The "GUARDS" set is all nodes in the nodelist for which this predicate + * holds. */ + + /* XXXX -- prop271 spec deviation. We require node_is_dir() here. */ + (void)gs; + tor_assert(node); + return (node->is_possible_guard && + node->is_stable && + node->is_fast && + node->is_valid && + node_is_dir(node)); +} + +/** + * Return the sampled guard with the RSA identity digest rsa_id, or + * NULL if we don't have one. */ +STATIC entry_guard_t * +get_sampled_guard_with_id(guard_selection_t *gs, + const uint8_t *rsa_id) +{ + tor_assert(gs); + tor_assert(rsa_id); + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + if (tor_memeq(guard->identity, rsa_id, DIGEST_LEN)) + return guard; + } SMARTLIST_FOREACH_END(guard); + return NULL; +} + +/** + * Return true iff we have a sampled guard with the RSA identity digest + * rsa_id. */ +static inline int +have_sampled_guard_with_id(guard_selection_t *gs, const uint8_t *rsa_id) +{ + return get_sampled_guard_with_id(gs, rsa_id) != NULL; +} + +/** + * Allocate a new entry_guard_t object for node, add it to the + * sampled entry guards in gs, and return it. node must + * not currently be a sampled guard in gs. + */ +STATIC entry_guard_t * entry_guard_add_to_sample(guard_selection_t *gs, - node_t *node) + const node_t *node) { const int GUARD_LIFETIME = GUARD_LIFETIME_DAYS * 86400; tor_assert(gs); tor_assert(node); + log_info(LD_GUARD, "Adding %s as to the entry guard sample set.", + node_describe(node)); + // XXXX prop271 take ed25519 identity here too. /* make sure that the guard is not already sampled. */ - SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, - entry_guard_t *, sampled) { - if (BUG(tor_memeq(node->identity, sampled->identity, DIGEST_LEN))) { - return; - } - } SMARTLIST_FOREACH_END(sampled); + if (BUG(have_sampled_guard_with_id(gs, (uint8_t*)node->identity))) + return NULL; // LCOV_EXCL_LINE entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); @@ -300,22 +357,902 @@ entry_guard_add_to_sample(guard_selection_t *gs, guard->sampled_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10); tor_free(guard->sampled_by_version); guard->sampled_by_version = tor_strdup(VERSION); + guard->currently_listed = 1; guard->confirmed_idx = -1; /* non-persistent fields */ guard->is_reachable = GUARD_REACHABLE_MAYBE; smartlist_add(gs->sampled_entry_guards, guard); - gs->filtered_up_to_date = 0; + entry_guard_set_filtered_flags(get_options(), gs, guard); + entry_guards_changed_for_guard_selection(gs); + return guard; +} + +/** + * Return the number of sampled guards in gs that are "filtered" + * (that is, we're willing to connect to them) and that are "usable" + * (that is, either "reachable" or "maybe reachable"). */ +STATIC int +num_reachable_filtered_guards(guard_selection_t *gs) +{ + int n_reachable_filtered_guards = 0; + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + entry_guard_consider_retry(guard); + if (guard->is_usable_filtered_guard) + ++n_reachable_filtered_guards; + } SMARTLIST_FOREACH_END(guard); + return n_reachable_filtered_guards; +} + +/** + * Add new guards to the sampled guards in gs until there are + * enough usable filtered guards, but never grow the sample beyond its + * maximum size. Return the last guard added, or NULL if none were + * added. + */ +STATIC entry_guard_t * +entry_guards_expand_sample(guard_selection_t *gs) +{ + tor_assert(gs); + int n_sampled = smartlist_len(gs->sampled_entry_guards); + entry_guard_t *added_guard = NULL; + + smartlist_t *nodes = nodelist_get_list(); + /* Construct eligible_guards as GUARDS - SAMPLED_GUARDS */ + smartlist_t *eligible_guards = smartlist_new(); + int n_guards = 0; // total size of "GUARDS" + int n_usable_filtered_guards = num_reachable_filtered_guards(gs); + { + /* Build a bloom filter of our current guards: let's keep this O(N). */ + digestset_t *sampled_guard_ids = digestset_new(n_sampled); + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, const entry_guard_t *, + guard) { + digestset_add(sampled_guard_ids, guard->identity); + } SMARTLIST_FOREACH_END(guard); + + SMARTLIST_FOREACH_BEGIN(nodes, node_t *, node) { + if (! node_is_possible_guard(gs, node)) + continue; + ++n_guards; + if (digestset_contains(sampled_guard_ids, node->identity)) + continue; + smartlist_add(eligible_guards, node); + } SMARTLIST_FOREACH_END(node); + + /* Now we can free that bloom filter. */ + digestset_free(sampled_guard_ids); + } + + /* Is there at least one guard we haven't sampled? */ + if (! smartlist_len(eligible_guards)) + goto done; + + const int max_sample = (int)(n_guards * MAX_SAMPLE_THRESHOLD); + const int min_filtered_sample = MIN_FILTERED_SAMPLE_SIZE; + + log_info(LD_GUARD, "Expanding the sample guard set. We have %d guards " + "in the sample, and %d eligible guards to extend it with.", + n_sampled, smartlist_len(eligible_guards)); + + while (n_usable_filtered_guards < min_filtered_sample) { + /* Has our sample grown too large to expand? */ + if (n_sampled >= max_sample) { + log_info(LD_GUARD, "Not expanding the guard sample any further; " + "just hit the maximum sample threshold of %d", + max_sample); + goto done; + } + + /* Did we run out of guards? */ + if (smartlist_len(eligible_guards) == 0) { + /* LCOV_EXCL_START + As long as MAX_SAMPLE_THRESHOLD makes can't be adjusted to + allow all guards to be sampled, this can't be reached. + */ + log_info(LD_GUARD, "Not expanding the guard sample any further; " + "just ran out of eligible guards"); + goto done; + /* LCOV_EXCL_STOP */ + } + + /* Otherwise we can add at least one new guard. */ + const node_t *node = + node_sl_choose_by_bandwidth(eligible_guards, WEIGHT_FOR_GUARD); + if (BUG(! node)) + goto done; // LCOV_EXCL_LINE -- should be impossible. + + added_guard = entry_guard_add_to_sample(gs, node); + if (!added_guard) + goto done; // LCOV_EXCL_LINE -- only fails on BUG. + + ++n_sampled; + + if (added_guard->is_usable_filtered_guard) + ++n_usable_filtered_guards; + + smartlist_remove(eligible_guards, node); + } + + done: + smartlist_free(eligible_guards); + return added_guard; +} + +/** + * Helper: guard has just been removed from the sampled guards: + * also remove it from primary and confirmed. */ +static void +remove_guard_from_confirmed_and_primary_lists(guard_selection_t *gs, + entry_guard_t *guard) +{ + if (guard->is_primary) { + guard->is_primary = 0; + smartlist_remove_keeporder(gs->primary_entry_guards, guard); + } else { + if (BUG(smartlist_contains(gs->primary_entry_guards, guard))) { + smartlist_remove_keeporder(gs->primary_entry_guards, guard); + } + } + + if (guard->confirmed_idx >= 0) { + entry_guard_t *found_guard = NULL; + if (guard->confirmed_idx < smartlist_len(gs->confirmed_entry_guards)) + found_guard = smartlist_get(gs->confirmed_entry_guards, + guard->confirmed_idx); + if (BUG(guard != found_guard)) { + smartlist_remove_keeporder(gs->confirmed_entry_guards, guard); + } else { + smartlist_del_keeporder(gs->confirmed_entry_guards, + guard->confirmed_idx); + } + guard->confirmed_idx = -1; + guard->confirmed_on_date = 0; + } else { + if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard))) { + smartlist_remove_keeporder(gs->confirmed_entry_guards, guard); + } + } +} + +/** + * Update the status of all sampled guards based on the arrival of a + * new consensus networkstatus document. This will include marking + * some guards as listed or unlisted, and removing expired guards. */ +STATIC void +sampled_guards_update_from_consensus(guard_selection_t *gs) +{ + tor_assert(gs); + const int REMOVE_UNLISTED_GUARDS_AFTER = + (REMOVE_UNLISTED_GUARDS_AFTER_DAYS * 86400); + const int unlisted_since_slop = REMOVE_UNLISTED_GUARDS_AFTER / 5; + + // It's important to use only a live consensus here; we don't want to + // make changes based on anything expired or old. + networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); + + log_info(LD_GUARD, "Updating sampled guard status based on received " + "consensus."); + + if (! ns || ns->valid_until < approx_time()) { + log_info(LD_GUARD, "Hey, that consensus isn't still valid. Ignoring."); + return; + } + + int n_changes = 0; + + /* First: Update listed/unlisted. */ + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + /* XXXX prop271 handle bridges right? */ + /* XXXX prop271 check ed ID too */ + const node_t *node = node_get_by_id(guard->identity); + + const unsigned is_listed = node && node_is_possible_guard(gs, node); + + if (is_listed && ! guard->currently_listed) { + ++n_changes; + guard->currently_listed = 1; + guard->unlisted_since_date = 0; + log_info(LD_GUARD, "Sampled guard %s is now listed again.", + entry_guard_describe(guard)); + } else if (!is_listed && guard->currently_listed) { + ++n_changes; + guard->currently_listed = 0; + guard->unlisted_since_date = randomize_time(approx_time(), + unlisted_since_slop); + log_info(LD_GUARD, "Sampled guard %s is now unlisted.", + entry_guard_describe(guard)); + } else if (is_listed && guard->currently_listed) { + log_debug(LD_GUARD, "Sampled guard %s is still listed.", + entry_guard_describe(guard)); + } else { + tor_assert(! is_listed && ! guard->currently_listed); + log_debug(LD_GUARD, "Sampled guard %s is still unlisted.", + entry_guard_describe(guard)); + } + + /* Clean up unlisted_since_date, just in case. */ + if (guard->currently_listed && guard->unlisted_since_date) { + ++n_changes; + guard->unlisted_since_date = 0; + log_warn(LD_BUG, "Sampled guard %s was listed, but with " + "unlisted_since_date set. Fixing.", + entry_guard_describe(guard)); + } else if (!guard->currently_listed && ! guard->unlisted_since_date) { + ++n_changes; + guard->unlisted_since_date = randomize_time(approx_time(), + unlisted_since_slop); + log_warn(LD_BUG, "Sampled guard %s was unlisted, but with " + "unlisted_since_date unset. Fixing.", + entry_guard_describe(guard)); + } + } SMARTLIST_FOREACH_END(guard); + + const time_t remove_if_unlisted_since = + approx_time() - REMOVE_UNLISTED_GUARDS_AFTER; + const time_t maybe_remove_if_sampled_before = + approx_time() - (GUARD_LIFETIME_DAYS * 86400); + const time_t remove_if_confirmed_before = + approx_time() - (GUARD_CONFIRMED_MIN_LIFETIME_DAYS * 86400); + + /* Then: remove the ones that have been junk for too long */ + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + /* XXXX prop271 handle bridges right? */ + + int remove = 0; + + if (guard->currently_listed == 0 && + guard->unlisted_since_date < remove_if_unlisted_since) { + /* + "We have a live consensus, and {IS_LISTED} is false, and + {FIRST_UNLISTED_AT} is over {REMOVE_UNLISTED_GUARDS_AFTER} + days in the past." + */ + log_info(LD_GUARD, "Removing sampled guard %s: it has been unlisted " + "for over %d days", entry_guard_describe(guard), + REMOVE_UNLISTED_GUARDS_AFTER_DAYS); + remove = 1; + } else if (guard->sampled_on_date < maybe_remove_if_sampled_before) { + /* We have a live consensus, and {ADDED_ON_DATE} is over + {GUARD_LIFETIME} ago, *and* {CONFIRMED_ON_DATE} is either + "never", or over {GUARD_CONFIRMED_MIN_LIFETIME} ago. + */ + if (guard->confirmed_on_date == 0) { + remove = 1; + log_info(LD_GUARD, "Removing sampled guard %s: it was sampled " + "over %d days ago, but never confirmed.", + entry_guard_describe(guard), + GUARD_LIFETIME_DAYS); + } else if (guard->confirmed_on_date < remove_if_confirmed_before) { + remove = 1; + log_info(LD_GUARD, "Removing sampled guard %s: it was sampled " + "over %d days ago, and confirmed over %d days ago.", + entry_guard_describe(guard), + GUARD_LIFETIME_DAYS, GUARD_CONFIRMED_MIN_LIFETIME_DAYS); + } + } + + if (remove) { + ++n_changes; + SMARTLIST_DEL_CURRENT(gs->sampled_entry_guards, guard); + remove_guard_from_confirmed_and_primary_lists(gs, guard); + entry_guard_free(guard); + } + } SMARTLIST_FOREACH_END(guard); + + if (n_changes) { + /* Regnerate other things. XXXXXX prop271 */ + // XXXX prop271 rebuild confirmed list. + entry_guards_update_filtered_sets(gs); + entry_guards_changed_for_guard_selection(gs); + } +} + +/** + * Return true iff node is a Tor relay that we are configured to + * be able to connect to. */ +static int +node_passes_guard_filter(const or_options_t *options, guard_selection_t *gs, + const node_t *node) +{ + (void)gs; + if (routerset_contains_node(options->ExcludeNodes, node)) + return 0; + + /* XXXX -- prop271 spec deviation -- add entrynodes to spec. */ + if (options->EntryNodes && + !options->UseBridges && + !routerset_contains_node(options->EntryNodes, node)) + return 0; + + if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) + return 0; + + if (bool_neq(options->UseBridges, node_is_a_configured_bridge(node))) + return 0; + + return 1; +} + +/** + * Return true iff guard is a Tor relay that we are configured to + * be able to connect to, and we haven't disabled it for omission from + * the consensus or path bias issues. */ +static int +entry_guard_passes_filter(const or_options_t *options, guard_selection_t *gs, + entry_guard_t *guard) +{ + if (guard->currently_listed == 0) + return 0; + if (guard->pb.path_bias_disabled) + return 0; + + const node_t *node = node_get_by_id(guard->identity); + if (BUG(node == NULL)) { + // should be impossible, since currently_listed was true. + return 0; + } + + return node_passes_guard_filter(options, gs, node); +} + +/** + * Update the is_filtered_guard and is_usable_filtered_guard + * flags on guard. */ +void +entry_guard_set_filtered_flags(const or_options_t *options, + guard_selection_t *gs, + entry_guard_t *guard) +{ + guard->is_filtered_guard = 0; + guard->is_usable_filtered_guard = 0; + + if (entry_guard_passes_filter(options, gs, guard)) { + guard->is_filtered_guard = 1; + + if (guard->is_reachable != GUARD_REACHABLE_NO) + guard->is_usable_filtered_guard = 1; + + entry_guard_consider_retry(guard); + } + log_debug(LD_GUARD, "Updated sampled guard %s: filtered=%d; " + "reachable_filtered=%d.", entry_guard_describe(guard), + guard->is_filtered_guard, guard->is_usable_filtered_guard); +} + +/** + * Update the is_filtered_guard and is_usable_filtered_guard + * flag on every guard in gs. */ +STATIC void +entry_guards_update_filtered_sets(guard_selection_t *gs) +{ + const or_options_t *options = get_options(); + + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + entry_guard_set_filtered_flags(options, gs, guard); + } SMARTLIST_FOREACH_END(guard); +} + +/** + * Return a random guard from the reachable filtered sample guards + * in gs, subject to the exclusion rules listed in flags. + * Return NULL if no such guard can be found. + * + * Make sure that the sample is big enough, and that all the filter flags + * are set correctly, before calling this function. + **/ +STATIC entry_guard_t * +sample_reachable_filtered_entry_guards(guard_selection_t *gs, + unsigned flags) +{ + tor_assert(gs); + entry_guard_t *result = NULL; + const unsigned exclude_confirmed = flags & SAMPLE_EXCLUDE_CONFIRMED; + const unsigned exclude_primary = flags & SAMPLE_EXCLUDE_PRIMARY; + const unsigned exclude_pending = flags & SAMPLE_EXCLUDE_PENDING; + + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + entry_guard_consider_retry(guard); + } SMARTLIST_FOREACH_END(guard); + + const int n_reachable_filtered = num_reachable_filtered_guards(gs); + + log_info(LD_GUARD, "Trying to sample a reachable guard: We know of %d " + "in the USABLE_FILTERED set.", n_reachable_filtered); + + if (n_reachable_filtered < MIN_FILTERED_SAMPLE_SIZE) { + log_info(LD_GUARD, " (That isn't enough. Trying to expand the sample.)"); + entry_guards_expand_sample(gs); + } + + /* Build the set of reachable filtered guards. */ + smartlist_t *reachable_filtered_sample = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + entry_guard_consider_retry(guard);// redundant, but cheap. + if (! guard->is_usable_filtered_guard) + continue; + if (exclude_confirmed && guard->confirmed_idx >= 0) + continue; + if (exclude_primary && guard->is_primary) + continue; + if (exclude_pending && guard->is_pending) + continue; + smartlist_add(reachable_filtered_sample, guard); + } SMARTLIST_FOREACH_END(guard); + + log_info(LD_GUARD, " (After filters [%x], we have %d guards to consider.)", + flags, smartlist_len(reachable_filtered_sample)); + + if (smartlist_len(reachable_filtered_sample)) { + result = smartlist_choose(reachable_filtered_sample); + log_info(LD_GUARD, " (Selected %s.)", + result ? entry_guard_describe(result) : ""); + } + smartlist_free(reachable_filtered_sample); + + return result; +} + +/** + * Helper: compare two entry_guard_t by their confirmed_idx values. + * Used to sort the confirmed list. + */ +static int +compare_guards_by_confirmed_idx(const void **a_, const void **b_) +{ + const entry_guard_t *a = *a_, *b = *b_; + if (a->confirmed_idx < b->confirmed_idx) + return -1; + else if (a->confirmed_idx > b->confirmed_idx) + return 1; + else + return 0; +} + +/** + * Find the confirmed guards from among the sampled guards in gs, + * and put them in confirmed_entry_guards in the correct + * order. Recalculate their indices. + */ +STATIC void +entry_guards_update_confirmed(guard_selection_t *gs) +{ + smartlist_clear(gs->confirmed_entry_guards); + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + if (guard->confirmed_idx >= 0) + smartlist_add(gs->confirmed_entry_guards, guard); + } SMARTLIST_FOREACH_END(guard); + + smartlist_sort(gs->confirmed_entry_guards, compare_guards_by_confirmed_idx); + + int any_changed = 0; + SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) { + if (guard->confirmed_idx != guard_sl_idx) { + any_changed = 1; + guard->confirmed_idx = guard_sl_idx; + } + } SMARTLIST_FOREACH_END(guard); + + gs->next_confirmed_idx = smartlist_len(gs->confirmed_entry_guards); + + if (any_changed) { + entry_guards_changed_for_guard_selection(gs); + } +} + +/** + * Mark guard as a confirmed guard -- that is, one that we have + * connected to, and intend to use again. + */ +STATIC void +make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard) +{ + if (BUG(guard->confirmed_on_date && guard->confirmed_idx >= 0)) + return; + + if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard))) + return; + + const int GUARD_LIFETIME = GUARD_LIFETIME_DAYS * 86400; + guard->confirmed_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10); + + log_info(LD_GUARD, "Marking %s as a confirmed guard (index %d)", + entry_guard_describe(guard), + gs->next_confirmed_idx); + + guard->confirmed_idx = gs->next_confirmed_idx++; + smartlist_add(gs->confirmed_entry_guards, guard); entry_guards_changed_for_guard_selection(gs); } +/** + * Recalculate the list of primary guards (the ones we'd prefer to use) from + * the filtered sample and the confirmed list. + * + * XXXXX prop271 are calling this enough ??? + */ +STATIC void +entry_guards_update_primary(guard_selection_t *gs) +{ + tor_assert(gs); + + smartlist_t *new_primary_guards = smartlist_new(); + smartlist_t *old_primary_guards = smartlist_new(); + smartlist_add_all(old_primary_guards, gs->primary_entry_guards); + + /* First, can we fill it up with confirmed guards? */ + SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) { + if (smartlist_len(new_primary_guards) >= N_PRIMARY_GUARDS) + break; + if (! guard->is_filtered_guard) + continue; + guard->is_primary = 1; + smartlist_add(new_primary_guards, guard); + } SMARTLIST_FOREACH_END(guard); + + /* Can we keep any older primary guards? First remove all the ones + * that we already kept. */ + SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) { + if (smartlist_contains(new_primary_guards, guard)) { + SMARTLIST_DEL_CURRENT_KEEPORDER(old_primary_guards, guard); + } + } SMARTLIST_FOREACH_END(guard); + + /* Now add any that are still good. */ + SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) { + if (smartlist_len(new_primary_guards) >= N_PRIMARY_GUARDS) + break; + if (! guard->is_filtered_guard) + continue; + guard->is_primary = 1; + smartlist_add(new_primary_guards, guard); + SMARTLIST_DEL_CURRENT_KEEPORDER(old_primary_guards, guard); + } SMARTLIST_FOREACH_END(guard); + + /* Mark the remaining previous primary guards as non-primary */ + SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) { + guard->is_primary = 0; + } SMARTLIST_FOREACH_END(guard); + + /* Finally, fill out the list with sampled guards. */ + while (smartlist_len(new_primary_guards) < N_PRIMARY_GUARDS) { + entry_guard_t *guard = sample_reachable_filtered_entry_guards(gs, + SAMPLE_EXCLUDE_CONFIRMED| + SAMPLE_EXCLUDE_PRIMARY); + if (!guard) + break; + guard->is_primary = 1; + smartlist_add(new_primary_guards, guard); + } + +#if 1 + /* Debugging. */ + SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, guard, { + tor_assert_nonfatal( + bool_eq(guard->is_primary, + smartlist_contains(new_primary_guards, guard))); + }); +#endif + + int any_change = 0; + if (smartlist_len(gs->primary_entry_guards) != + smartlist_len(new_primary_guards)) { + any_change = 1; + } else { + SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, g) { + if (g != smartlist_get(new_primary_guards, g_sl_idx)) { + any_change = 1; + } + } SMARTLIST_FOREACH_END(g); + } + + if (any_change) { + log_info(LD_GUARD, "Primary entry guards have changed. " + "New primary guard list is: "); + int n = smartlist_len(new_primary_guards); + SMARTLIST_FOREACH_BEGIN(new_primary_guards, entry_guard_t *, g) { + log_info(LD_GUARD, " %d/%d: %s%s%s", + g_sl_idx+1, n, entry_guard_describe(g), + g->confirmed_idx >= 0 ? " (confirmed)" : "", + g->is_filtered_guard ? "" : " (excluded by filter)"); + } SMARTLIST_FOREACH_END(g); + } + + smartlist_free(old_primary_guards); + smartlist_free(gs->primary_entry_guards); + gs->primary_entry_guards = new_primary_guards; +} + +/** + * Return the number of seconds after the last attempt at which we should + * retry a guard that has been failing since failing_since. + */ +static unsigned +get_retry_schedule(time_t failing_since, time_t now, + int is_primary) +{ + const unsigned SIX_HOURS = 6 * 3600; + const unsigned FOUR_DAYS = 4 * 86400; + const unsigned SEVEN_DAYS = 7 * 86400; + + time_t tdiff; + if (now > failing_since) { + tdiff = now - failing_since; + } else { + tdiff = 0; + } + + const struct { + time_t maximum; int primary_delay; int nonprimary_delay; + } delays[] = { + { SIX_HOURS, 30*60, 1*60*60 }, + { FOUR_DAYS, 2*60*60, 4*60*60 }, + { SEVEN_DAYS, 4*60*60, 18*60*60 }, + { TIME_MAX, 9*60*60, 36*60*60 } + }; + + unsigned i; + for (i = 0; i < ARRAY_LENGTH(delays); ++i) { + if (tdiff <= delays[i].maximum) { + return is_primary ? delays[i].primary_delay : delays[i].nonprimary_delay; + } + } + /* LCOV_EXCL_START -- can't reach, since delays ends with TIME_MAX. */ + tor_assert_nonfatal_unreached(); + return 36*60*60; + /* LCOV_EXCL_STOP */ +} + +/** + * If guard is unreachable, consider whether enough time has passed + * to consider it maybe-reachable again. + */ +STATIC void +entry_guard_consider_retry(entry_guard_t *guard) +{ + if (guard->is_reachable != GUARD_REACHABLE_NO) + return; /* No retry needed. */ + + const time_t now = approx_time(); + const unsigned delay = + get_retry_schedule(guard->failing_since, now, guard->is_primary); + const time_t last_attempt = guard->last_tried_to_connect; + + if (BUG(last_attempt == 0) || + now >= last_attempt + delay) { + /* We should mark this retriable. */ + char tbuf[ISO_TIME_LEN+1]; + format_local_iso_time(tbuf, last_attempt); + log_info(LD_GUARD, "Marked %s%sguard %s for possible retry, since we " + "haven't tried to use it since %s.", + guard->is_primary?"primary ":"", + guard->confirmed_idx>=0?"confirmed ":"", + entry_guard_describe(guard), + tbuf); + + guard->is_reachable = GUARD_REACHABLE_MAYBE; + if (guard->is_filtered_guard) + guard->is_usable_filtered_guard = 1; + } +} + +/** + * Get a guard for use with a circuit. Prefer to pick a running primary + * guard; then a non-pending running filtered confirmed guard; then a + * non-pending runnable filtered guard. Update the + * last_tried_to_connect time and the is_pending fields of the + * guard as appropriate. Set state_out to the new guard-state + * of the circuit. + */ +STATIC entry_guard_t * +select_entry_guard_for_circuit(guard_selection_t *gs, unsigned *state_out) +{ + tor_assert(gs); + tor_assert(state_out); + + /* "If any entry in PRIMARY_GUARDS has {is_reachable} status of + or , return the first such guard." */ + SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) { + entry_guard_consider_retry(guard); + if (guard->is_reachable != GUARD_REACHABLE_NO) { + *state_out = GUARD_CIRC_STATE_USABLE_ON_COMPLETION; + guard->last_tried_to_connect = approx_time(); + log_info(LD_GUARD, "Selected primary guard %s for circuit.", + entry_guard_describe(guard)); + return guard; + } + } SMARTLIST_FOREACH_END(guard); + + /* "Otherwise, if the ordered intersection of {CONFIRMED_GUARDS} + and {USABLE_FILTERED_GUARDS} is nonempty, return the first + entry in that intersection that has {is_pending} set to + false." */ + SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) { + if (guard->is_primary) + continue; /* we already considered this one. */ + entry_guard_consider_retry(guard); + if (guard->is_usable_filtered_guard && ! guard->is_pending) { + guard->is_pending = 1; + guard->last_tried_to_connect = approx_time(); + *state_out = GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD; + log_info(LD_GUARD, "No primary guards available. Selected confirmed " + "guard %s for circuit. Will try other guards before using " + "this circuit.", + entry_guard_describe(guard)); + return guard; + } + } SMARTLIST_FOREACH_END(guard); + + /* "Otherwise, if there is no such entry, select a member at + random from {USABLE_FILTERED_GUARDS}." */ + { + entry_guard_t *guard; + guard = sample_reachable_filtered_entry_guards(gs, + SAMPLE_EXCLUDE_CONFIRMED | + SAMPLE_EXCLUDE_PRIMARY | + SAMPLE_EXCLUDE_PENDING); + if (guard == NULL) { + log_info(LD_GUARD, "Absolutely no sampled guards were available."); + return NULL; + } + guard->is_pending = 1; + guard->last_tried_to_connect = approx_time(); + *state_out = GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD; + log_info(LD_GUARD, "No primary or confirmed guards available. Selected " + "random guard %s for circuit. Will try other guards before " + "using this circuit.", + entry_guard_describe(guard)); + return guard; + } +} + +/** + * Note that we failed to connect to or build circuits through guard. + * Use with a guard returned by select_entry_guards_for_circuit(). + */ +STATIC void +entry_guards_note_guard_failure(guard_selection_t *gs, + entry_guard_t *guard) +{ + tor_assert(gs); + + guard->is_reachable = GUARD_REACHABLE_NO; + guard->is_usable_filtered_guard = 0; + + guard->is_pending = 0; + if (guard->failing_since == 0) + guard->failing_since = approx_time(); + + log_info(LD_GUARD, "Recorded failure for %s%sguard %s", + guard->is_primary?"primary ":"", + guard->confirmed_idx>=0?"confirmed ":"", + entry_guard_describe(guard)); +} + +/** + * Called when the network comes up after having seemed to be down for + * a while: Mark the primary guards as maybe-reachable so that we'll + * try them again. + */ +STATIC void +mark_primary_guards_maybe_reachable(guard_selection_t *gs) +{ + SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) { + if (guard->is_reachable != GUARD_REACHABLE_NO) + continue; + + /* Note that we do not clear failing_since: this guard is now only + * _maybe-reachable_. */ + guard->is_reachable = GUARD_REACHABLE_MAYBE; + if (guard->is_filtered_guard) + guard->is_usable_filtered_guard = 1; + + } SMARTLIST_FOREACH_END(guard); +} + +/** + * Note that we successfully connected to, and built a circuit through + * guard. Given the old guard-state of the circuit in old_state, + * return the new guard-state of the circuit. + * + * Be aware: the circuit is only usable when its guard-state becomes + * GUARD_CIRC_STATE_COMPLETE. + **/ +STATIC unsigned +entry_guards_note_guard_success(guard_selection_t *gs, + entry_guard_t *guard, + unsigned old_state) +{ + tor_assert(gs); + + /* Save this, since we're about to overwrite it. */ + const time_t last_time_on_internet = gs->last_time_on_internet; + gs->last_time_on_internet = approx_time(); + + guard->is_reachable = GUARD_REACHABLE_YES; + guard->failing_since = 0; + guard->is_pending = 0; + if (guard->is_filtered_guard) + guard->is_usable_filtered_guard = 1; + + if (guard->confirmed_idx < 0) { + // XXXX prop271 XXXX update primary guards, since we confirmed something? + make_guard_confirmed(gs, guard); + } + + unsigned new_state; + if (old_state == GUARD_CIRC_STATE_USABLE_ON_COMPLETION) { + new_state = GUARD_CIRC_STATE_COMPLETE; + } else { + tor_assert_nonfatal( + old_state == GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + new_state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD; + + if (last_time_on_internet + INTERNET_LIKELY_DOWN_INTERVAL + < approx_time()) { + mark_primary_guards_maybe_reachable(gs); + } else { + // update_waiting_circuits(gs); // XXXX prop271 write this function. + } + } + + // XXXX prop271 XXXX update primary guards, since we confirmed something? + // XXXX prop261 XXXX if so, here or above? + + log_info(LD_GUARD, "Recorded success for %s%sguard %s", + guard->is_primary?"primary ":"", + guard->confirmed_idx>=0?"confirmed ":"", + entry_guard_describe(guard)); + + return new_state; +} + +/** + * Helper: Return true iff a has higher priority than b. + */ +STATIC int +entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b) +{ + tor_assert(a && b); + if (a == b) + return 0; + + /* Confirmed is always better than unconfirmed; lower index better + than higher */ + if (a->confirmed_idx < 0) { + if (b->confirmed_idx >= 0) + return 0; + } else { + if (b->confirmed_idx < 0) + return 1; + + /* Lower confirmed_idx is better than higher. */ + return (a->confirmed_idx < b->confirmed_idx); + } + + /* If we reach this point, both are unconfirmed. If one is pending, it + * has higher priority. */ + if (a->is_pending) { + if (! b->is_pending) + return 1; + + /* Both are pending: earlier last_tried_connect wins. */ + return a->last_tried_to_connect < b->last_tried_to_connect; + } else { + if (b->is_pending) + return 0; + + /* Neither is pending: priorities are equal. */ + return 0; // XXXX prop271 return a tristate instead? + } +} + /** * Return a newly allocated string for encoding the persistent parts of * guard to the state file. */ -ATTR_UNUSED STATIC char * +STATIC char * entry_guard_encode_for_state(entry_guard_t *guard) { /* @@ -375,7 +1312,7 @@ entry_guard_encode_for_state(entry_guard_t *guard) * (if possible) and return an entry_guard_t object for it. Return NULL * on complete failure. */ -ATTR_UNUSED STATIC entry_guard_t * +STATIC entry_guard_t * entry_guard_parse_from_state(const char *s) { /* Unrecognized entries get put in here. */ @@ -492,6 +1429,7 @@ entry_guard_parse_from_state(const char *s) /* Take sampled_by_version verbatim. */ guard->sampled_by_version = sampled_by; sampled_by = NULL; /* prevent free */ + // XXXX -- prop271 spec deviation -- we do not require sampled_by_version /* Listed is a boolean */ if (listed && strcmp(listed, "0")) @@ -518,6 +1456,8 @@ entry_guard_parse_from_state(const char *s) /* initialize non-persistent fields */ guard->is_reachable = GUARD_REACHABLE_MAYBE; + /* XXXX prop271 Update everything on this guard. */ + goto done; err: @@ -540,6 +1480,79 @@ entry_guard_parse_from_state(const char *s) return guard; } +/* XXXXprop271 This is a dummy function added for now so that all of the + * new guard code will be counted as reachable. It should get removed. + */ +__attribute__((noreturn)) void +entry_guards_DUMMY_ENTRY_POINT(void) +{ + // prop271 XXXXX kludge; remove this + sampled_guards_update_from_consensus(NULL); + sample_reachable_filtered_entry_guards(NULL, 0); + entry_guards_update_confirmed(NULL); + entry_guards_update_primary(NULL); + select_entry_guard_for_circuit(NULL, NULL); + entry_guards_note_guard_failure(NULL, NULL); + entry_guards_note_guard_success(NULL, NULL, 0); + entry_guard_has_higher_priority(NULL, NULL); + entry_guard_encode_for_state(NULL); + entry_guard_parse_from_state(NULL); + compare_guards_by_confirmed_idx(NULL, NULL); + entry_guards_update_filtered_sets(NULL); + tor_assert(0); +} + +/* XXXXX ----------------------------------------------- */ +/* XXXXX prop271 ----- end of new-for-prop271 code ----- */ +/* XXXXX ----------------------------------------------- */ + +/** + * @name Constants for old (pre-prop271) guard selection algorithm. + */ + +/**@{*/ + +/* Default number of entry guards in the case where the NumEntryGuards + * consensus parameter is not set */ +#define DEFAULT_N_GUARDS 1 +/* Minimum and maximum number of entry guards (in case the NumEntryGuards + * consensus parameter is set). */ +#define MIN_N_GUARDS 1 +#define MAX_N_GUARDS 10 +/** Largest amount that we'll backdate chosen_on_date */ +#define CHOSEN_ON_DATE_SLOP (30*86400) +/** How long (in seconds) do we allow an entry guard to be nonfunctional, + * unlisted, excluded, or otherwise nonusable before we give up on it? */ +#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) +/**}@*/ + +/** + * @name Networkstatus parameters for old (pre-prop271) guard selection + */ +/**@}*/ +/** Choose how many entry guards or directory guards we'll use. If + * for_directory is true, we return how many directory guards to + * use; else we return how many entry guards to use. */ +STATIC int +decide_num_guards(const or_options_t *options, int for_directory) +{ + if (for_directory) { + int answer; + if (options->NumDirectoryGuards != 0) + return options->NumDirectoryGuards; + answer = networkstatus_get_param(NULL, "NumDirectoryGuards", 0, 0, 10); + if (answer) /* non-zero means use the consensus value */ + return answer; + } + + if (options->NumEntryGuards) + return options->NumEntryGuards; + + /* Use the value from the consensus, or 3 if no guidance. */ + return networkstatus_get_param(NULL, "NumEntryGuards", DEFAULT_N_GUARDS, + MIN_N_GUARDS, MAX_N_GUARDS); +} + /** Check whether the entry guard e is usable, given the directory * authorities' opinion about the router (stored in ri) and the user's * configuration (in options). Set e->bad_since @@ -2402,7 +3415,7 @@ entries_retry_all(const or_options_t *options) } /** Free one guard selection context */ -static void +STATIC void guard_selection_free(guard_selection_t *gs) { if (!gs) return; @@ -2421,6 +3434,9 @@ guard_selection_free(guard_selection_t *gs) gs->sampled_entry_guards = NULL; } + smartlist_free(gs->confirmed_entry_guards); + smartlist_free(gs->primary_entry_guards); + tor_free(gs); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index f07f843ae7..5501e624eb 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -18,10 +18,6 @@ typedef struct guard_selection_s guard_selection_t; /* Forward declare for entry_guard_t; the real declaration is private. */ typedef struct entry_guard_t entry_guard_t; -#define GUARD_REACHABLE_NO 0 -#define GUARD_REACHABLE_YES 1 -#define GUARD_REACHABLE_MAYBE 2 - /* Information about a guard's pathbias status. * These fields are used in circpathbias.c to try to detect entry * nodes that are failing circuits at a suspicious frequency. @@ -58,6 +54,17 @@ typedef struct guard_pathbias_t { } guard_pathbias_t; #if defined(ENTRYNODES_PRIVATE) +/** + * @name values for entry_guard_t.is_reachable. + * + * See entry_guard_t.is_reachable for more information. + */ +/**@{*/ +#define GUARD_REACHABLE_NO 0 +#define GUARD_REACHABLE_YES 1 +#define GUARD_REACHABLE_MAYBE 2 +/**@}*/ + /** An entry_guard_t represents our information about a chosen long-term * first hop, known as a "helper" node in the literature. We can't just * use a node_t, since we want to remember these even when we @@ -67,35 +74,80 @@ struct entry_guard_t { char identity[DIGEST_LEN]; ed25519_public_key_t ed_id; - /* XXXX prop271 DOCDOC document all these fields better */ + /** + * @name new guard selection algorithm fields. + * + * Only the new (prop271) algorithm uses these. For a more full + * description of the algorithm, see the module documentation for + * entrynodes.c + */ + /**@{*/ - /* Persistent fields, present for all sampled guards. */ + /* == Persistent fields, present for all sampled guards. */ + /** When was this guard added to the sample? */ time_t sampled_on_date; + /** Since what date has this guard been "unlisted"? A guard counts as + * unlisted if we have a live consensus that does not include it, or + * if we have a live consensus that does not include it as a usable + * guard. This field is zero when the guard is listed. */ time_t unlisted_since_date; // can be zero + /** What version of Tor added this guard to the sample? */ char *sampled_by_version; + /** Is this guard listed right now? If this is set, then + * unlisted_since_date should be set too. */ unsigned currently_listed : 1; - /* Persistent fields, for confirmed guards. */ + /* == Persistent fields, for confirmed guards only */ + /** When was this guard confirmed? (That is, when did we first use it + * successfully and decide to keep it?) This field is zero if this is not a + * confirmed guard. */ time_t confirmed_on_date; /* 0 if not confirmed */ + /** + * In what order was this guard confirmed? Guards with lower indices + * appear earlier on the confirmed list. If the confirmed list is compacted, + * this field corresponds to the index of this guard on the confirmed list. + * + * This field is set to -1 if this guard is not confirmed. + */ int confirmed_idx; /* -1 if not confirmed; otherwise the order that this * item should occur in the CONFIRMED_GUARDS ordered * list */ /* ==== Non-persistent fields. */ /* == These are used by sampled guards */ + /** When did we last decide to try using this guard for a circuit? 0 for + * "not since we started up." */ time_t last_tried_to_connect; - unsigned is_reachable : 2; /* One of GUARD_REACHABLE_{NO,YES,MAYBE} */ + /** How reachable do we consider this guard to be? One of + * GUARD_REACHABLE_NO, GUARD_REACHABLE_YES, or GUARD_REACHABLE_MAYBE. */ + unsigned is_reachable : 2; + /** Boolean: true iff this guard is pending. A pending guard is one + * that we have an in-progress circuit through, and which we do not plan + * to try again until it either succeeds or fails. Primary guards can + * never be pending. */ unsigned is_pending : 1; + /** When did we get the earliest connection failure for this guard? + * We clear this field on a successful connect. We do _not_ clear it + * when we mark the guard as "MAYBE" reachable. + */ time_t failing_since; - /* These determine presence in filtered guards and usable-filtered-guards - * respectively. */ + /* == Set inclusion flags. */ + /** If true, this guard is in the filtered set. The filtered set includes + * all sampled guards that our configuration allows us to use. */ unsigned is_filtered_guard : 1; + /** If true, this guard is in the usable filtered set. The usable filtered + * set includes all filtered guards that are not believed to be + * unreachable. (That is, those for which is_reachable is not + * GUARD_REACHABLE_NO) */ unsigned is_usable_filtered_guard : 1; + unsigned is_primary:1; /** This string holds any fields that we are maintaining because * we saw them in the state, even if we don't understand them. */ char *extra_state_fields; + /**@}*/ + /** * @name legacy guard selection algorithm fields * @@ -128,6 +180,85 @@ struct entry_guard_t { /** Path bias information for this guard. */ guard_pathbias_t pb; }; + +/** + * All of the the context for guard selection on a particular client. + * + * (XXXX prop271 this paragraph below is not actually implemented yet.) + * We maintain multiple guard selection contexts for a client, depending + * aspects on its current configuration -- whether an extremely + * restrictive EntryNodes is used, whether UseBridges is enabled, and so + * on.) + * + * See the module documentation for entrynodes.c for more information + * about guard selection algorithms. + */ +struct guard_selection_s { + /** + * A value of 1 means that guard_selection_t structures have changed + * and those changes need to be flushed to disk. + * + * XXX prop271 we don't know how to flush multiple guard contexts to + * disk yet; fix that as soon as any way to change the default exists, + * or at least make sure this gets set on change. + */ + int dirty; + + /** + * A list of the sampled entry guards, as entry_guard_t structures. + * Not in any particular order. When we 'sample' a guard, we are + * noting it as a possible guard to pick in the future. The use of + * sampling here prevents us from being forced by an attacker to try + * every guard on the network. This list is persistent. + */ + smartlist_t *sampled_entry_guards; + + /** + * Ordered list (from highest to lowest priority) of guards that we + * have successfully contacted and decided to use. Every member of + * this list is a member of sampled_entry_guards. Every member should + * have confirmed_on_date set, and have confirmed_idx greater than + * any earlier member of the list. + * + * This list is persistent. It is a subset of the elements in + * sampled_entry_guards, and its pointers point to elements of + * sampled_entry_guards. + */ + smartlist_t *confirmed_entry_guards; + + /** + * Ordered list (from highest to lowest priority) of guards that we + * are willing to use the most happily. These guards may or may not + * yet be confirmed yet. If we can use one of these guards, we are + * probably not on a network that is trying to restrict our guard + * choices. + * + * This list is a subset of the elements in + * sampled_entry_guards, and its pointers point to elements of + * sampled_entry_guards. + */ + smartlist_t *primary_entry_guards; + + /** When did we last successfully build a circuit or use a circuit? */ + time_t last_time_on_internet; + + /** What confirmed_idx value should the next-added member of + * confirmed_entry_guards receive? */ + int next_confirmed_idx; + + /** + * A list of our chosen entry guards, as entry_guard_t structures; this + * preserves the pre-Prop271 behavior. + */ + smartlist_t *chosen_entry_guards; + + /** + * When we try to choose an entry guard, should we parse and add + * config's EntryNodes first? This was formerly a global. This + * preserves the pre-Prop271 behavior. + */ + int should_add_entry_nodes; +}; #endif #if 1 @@ -160,12 +291,127 @@ void add_bridge_as_entry_guard(guard_selection_t *gs, int num_bridges_usable(void); #ifdef ENTRYNODES_PRIVATE -STATIC time_t randomize_time(time_t now, time_t max_backdate); -STATIC void entry_guard_add_to_sample(guard_selection_t *gs, - node_t *node); +/** + * @name Parameters for the new (prop271) entry guard algorithm. + */ +/* XXXX prop271 some of these should be networkstatus parameters */ +/**@{*/ +/** + * We never let our sampled guard set grow larger than this fraction + * of the guards on the network. + */ +#define MAX_SAMPLE_THRESHOLD 0.30 +/** + * We always try to make our sample contain at least this many guards. + * + * XXXX prop271 There was a MIN_SAMPLE_THRESHOLD in the proposal, but I + * removed it in favor of MIN_FILTERED_SAMPLE_SIZE. -NM + */ +#define MIN_FILTERED_SAMPLE_SIZE 20 +/** + * If a guard is unlisted for this many days in a row, we remove it. + */ +#define REMOVE_UNLISTED_GUARDS_AFTER_DAYS 20 +/** + * We remove unconfirmed guards from the sample after this many days, + * regardless of whether they are listed or unlisted. + */ +#define GUARD_LIFETIME_DAYS 120 +/** + * We remove confirmed guards from the sample if they were sampled + * GUARD_LIFETIME_DAYS ago and confirmed this many days ago. + */ +#define GUARD_CONFIRMED_MIN_LIFETIME_DAYS 60 +/** + * How many guards do we try to keep on our primary guard list? + */ +#define N_PRIMARY_GUARDS 3 +/** + * If we haven't successfully built or used a circuit in this long, then + * consider that the internet is probably down. + */ +#define INTERNET_LIKELY_DOWN_INTERVAL (10*60) +/** + * DOCDOC. not yet used; see prop271. + */ +#define NONPRIMARY_GUARD_CONNECT_TIMEOUT 15 +/** + * DOCDOC. not yet used; see prop271. + */ +#define NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60) +/** + * DOCDOC. not yet used; see prop271. + */ +#define MEANINGFUL_RESTRICTION_FRAC 0.2 +/** + * DOCDOC. not yet used. see prop271. + */ +#define EXTREME_RESTRICTION_FRAC 0.01 +/**@}*/ + +// ---------- XXXX these functions and definitions are post-prop271. +STATIC guard_selection_t *guard_selection_new(void); +STATIC void guard_selection_free(guard_selection_t *gs); +STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs, + const uint8_t *rsa_id); + +MOCK_DECL(STATIC time_t, randomize_time, (time_t now, time_t max_backdate)); +STATIC entry_guard_t *entry_guard_add_to_sample(guard_selection_t *gs, + const node_t *node); +STATIC entry_guard_t *entry_guards_expand_sample(guard_selection_t *gs); STATIC char *entry_guard_encode_for_state(entry_guard_t *guard); STATIC entry_guard_t *entry_guard_parse_from_state(const char *s); STATIC void entry_guard_free(entry_guard_t *e); +STATIC void entry_guards_update_filtered_sets(guard_selection_t *gs); +/** + * @name Flags for sample_reachable_filtered_entry_guards() + */ +/**@{*/ +#define SAMPLE_EXCLUDE_CONFIRMED (1u<<0) +#define SAMPLE_EXCLUDE_PRIMARY (1u<<1) +#define SAMPLE_EXCLUDE_PENDING (1u<<2) +/**@}*/ +STATIC entry_guard_t *sample_reachable_filtered_entry_guards( + guard_selection_t *gs, + unsigned flags); +STATIC void entry_guard_consider_retry(entry_guard_t *guard); +STATIC void make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard); +STATIC void entry_guards_update_confirmed(guard_selection_t *gs); +STATIC void entry_guards_update_primary(guard_selection_t *gs); +STATIC int num_reachable_filtered_guards(guard_selection_t *gs); +STATIC void sampled_guards_update_from_consensus(guard_selection_t *gs); +/** + * @name Possible guard-states for a circuit. + */ +/**@{*/ +/** State for a circuit that can (so far as the guard subsystem is + * concerned) be used for actual traffic as soon as it is successfully + * opened. */ +#define GUARD_CIRC_STATE_USABLE_ON_COMPLETION 1 +/** State for an non-open circuit that we shouldn't use for actual + * traffic, when it completes, unless other circuits to preferable + * guards fail. */ +#define GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD 2 +/** State for an open circuit that we shouldn't use for actual traffic + * unless other circuits to preferable guards fail. */ +#define GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD 3 +/** State for a circuit that can (so far as the guard subsystem is + * concerned) be used for actual traffic. */ +#define GUARD_CIRC_STATE_COMPLETE 4 +/**@}*/ +STATIC void entry_guards_note_guard_failure(guard_selection_t *gs, + entry_guard_t *guard); +STATIC entry_guard_t *select_entry_guard_for_circuit(guard_selection_t *gs, + unsigned *state_out); +STATIC void mark_primary_guards_maybe_reachable(guard_selection_t *gs); +STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs, + entry_guard_t *guard, + unsigned old_state); +STATIC int entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b); + +void entry_guards_DUMMY_ENTRY_POINT(void); + +// ---------- XXXX this stuff is pre-prop271. STATIC const node_t *add_an_entry_guard(guard_selection_t *gs, const node_t *chosen, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 45b730c69f..8e90b000fc 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -10,9 +10,11 @@ #include "or.h" #include "test.h" +#include "bridges.h" #include "config.h" #include "entrynodes.h" #include "nodelist.h" +#include "networkstatus.h" #include "policies.h" #include "routerlist.h" #include "routerparse.h" @@ -21,6 +23,7 @@ #include "util.h" #include "test_helpers.h" +#include "log_test_helpers.h" /* TODO: * choose_random_entry() test with state set. @@ -72,6 +75,120 @@ fake_network_setup(const struct testcase_t *testcase) return dummy_state; } +static networkstatus_t *dummy_consensus = NULL; + +static smartlist_t *big_fake_net_nodes = NULL; + +static smartlist_t * +bfn_mock_nodelist_get_list(void) +{ + return big_fake_net_nodes; +} + +static networkstatus_t * +bfn_mock_networkstatus_get_live_consensus(time_t now) +{ + (void)now; + return dummy_consensus; +} + +static const node_t * +bfn_mock_node_get_by_id(const char *id) +{ + SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, + if (fast_memeq(n->identity, id, 20)) + return n); + + return NULL; +} + +/* Unittest cleanup function: Cleanup the fake network. */ +static int +big_fake_network_cleanup(const struct testcase_t *testcase, void *ptr) +{ + (void) testcase; + (void) ptr; + + if (big_fake_net_nodes) { + SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, { + tor_free(n->rs); + tor_free(n->md); + tor_free(n); + }); + smartlist_free(big_fake_net_nodes); + } + + UNMOCK(nodelist_get_list); + UNMOCK(node_get_by_id); + UNMOCK(get_or_state); + UNMOCK(networkstatus_get_live_consensus); + or_state_free(dummy_state); + dummy_state = NULL; + tor_free(dummy_consensus); + + return 1; /* NOP */ +} + +/* Unittest setup function: Setup a fake network. */ +static void * +big_fake_network_setup(const struct testcase_t *testcase) +{ + int i; + + /* These are minimal node_t objects that only contain the aspects of node_t + * that we need for entrynodes.c. */ + const int N_NODES = 271; + + big_fake_net_nodes = smartlist_new(); + for (i = 0; i < N_NODES; ++i) { + node_t *n = tor_malloc_zero(sizeof(node_t)); + n->md = tor_malloc_zero(sizeof(microdesc_t)); + + crypto_rand(n->identity, sizeof(n->identity)); + n->rs = tor_malloc_zero(sizeof(routerstatus_t)); + + memcpy(n->rs->identity_digest, n->identity, DIGEST_LEN); + + n->is_running = n->is_valid = n->is_fast = n->is_stable = 1; + + n->rs->addr = 0x04020202; + n->rs->or_port = 1234; + n->rs->is_v2_dir = 1; + n->rs->has_bandwidth = 1; + n->rs->bandwidth_kb = 30; + + /* Call half of the nodes a possible guard. */ + if (i % 2 == 0) { + n->is_possible_guard = 1; + n->rs->guardfraction_percentage = 100; + n->rs->has_guardfraction = 1; + } + + smartlist_add(big_fake_net_nodes, n); + } + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t)); + dummy_consensus->valid_after = approx_time() - 3600; + dummy_consensus->valid_until = approx_time() + 3600; + + MOCK(nodelist_get_list, bfn_mock_nodelist_get_list); + MOCK(node_get_by_id, bfn_mock_node_get_by_id); + MOCK(get_or_state, + get_or_state_replacement); + MOCK(networkstatus_get_live_consensus, + bfn_mock_networkstatus_get_live_consensus); + /* Return anything but NULL (it's interpreted as test fail) */ + return (void*)testcase; +} + +static time_t +mock_randomize_time_no_randomization(time_t a, time_t b) +{ + (void) b; + return a; +} + static or_options_t mocked_options; static const or_options_t * @@ -1089,10 +1206,972 @@ test_entry_guard_parse_from_state_partial_failure(void *arg) tor_free(mem_op_hex_tmp); } +static void +test_entry_guard_add_single_guard(void *arg) +{ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + + /* 1: Add a single guard to the sample. */ + node_t *n1 = smartlist_get(big_fake_net_nodes, 0); + time_t now = approx_time(); + tt_assert(n1->is_possible_guard == 1); + entry_guard_t *g1 = entry_guard_add_to_sample(gs, n1); + tt_assert(g1); + + /* Make sure its fields look right. */ + tt_mem_op(n1->identity, OP_EQ, g1->identity, DIGEST_LEN); + tt_i64_op(g1->sampled_on_date, OP_GE, now - 12*86400); + tt_i64_op(g1->sampled_on_date, OP_LE, now); + tt_str_op(g1->sampled_by_version, OP_EQ, VERSION); + tt_assert(g1->currently_listed == 1); + tt_i64_op(g1->confirmed_on_date, OP_EQ, 0); + tt_int_op(g1->confirmed_idx, OP_EQ, -1); + tt_int_op(g1->last_tried_to_connect, OP_EQ, 0); + tt_uint_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + tt_i64_op(g1->failing_since, OP_EQ, 0); + tt_assert(g1->is_filtered_guard == 1); + tt_assert(g1->is_usable_filtered_guard == 1); + tt_assert(g1->is_primary == 0); + tt_assert(g1->extra_state_fields == NULL); + + /* Make sure it got added. */ + tt_int_op(1, OP_EQ, smartlist_len(gs->sampled_entry_guards)); + tt_ptr_op(g1, OP_EQ, smartlist_get(gs->sampled_entry_guards, 0)); + tt_ptr_op(g1, OP_EQ, get_sampled_guard_with_id(gs, (uint8_t*)n1->identity)); + const uint8_t bad_id[20] = {0}; + tt_ptr_op(NULL, OP_EQ, get_sampled_guard_with_id(gs, bad_id)); + + done: + guard_selection_free(gs); +} + +static void +test_entry_guard_node_filter(void *arg) +{ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + bridge_line_t *bl = NULL; + + /* Initialize a bunch of node objects that are all guards. */ + const int NUM = 7; + node_t *n[NUM]; + entry_guard_t *g[NUM]; + int i; + for (i=0; i < NUM; ++i) { + n[i] = smartlist_get(big_fake_net_nodes, i*2); // even ones are guards. + g[i] = entry_guard_add_to_sample(gs, n[i]); + + // everything starts out filtered-in + tt_assert(g[i]->is_filtered_guard == 1); + tt_assert(g[i]->is_usable_filtered_guard == 1); + } + tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, NUM); + + /* Make sure refiltering doesn't hurt */ + entry_guards_update_filtered_sets(gs); + for (i = 0; i < NUM; ++i) { + tt_assert(g[i]->is_filtered_guard == 1); + tt_assert(g[i]->is_usable_filtered_guard == 1); + } + tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, NUM); + + /* Now start doing things to make the guards get filtered out, 1 by 1. */ + + /* 0: Not listed. */ + g[0]->currently_listed = 0; + + /* 1: path bias says this guard is maybe eeeevil. */ + g[1]->pb.path_bias_disabled = 1; + + /* 2: Unreachable address. */ + n[2]->rs->addr = 0; + + /* 3: ExcludeNodes */ + n[3]->rs->addr = 0x90902020; + routerset_free(get_options_mutable()->ExcludeNodes); + get_options_mutable()->ExcludeNodes = routerset_new(); + routerset_parse(get_options_mutable()->ExcludeNodes, "144.144.0.0/16", ""); + + /* 4: Bridge. */ + sweep_bridge_list(); + bl = tor_malloc_zero(sizeof(bridge_line_t)); + tor_addr_from_ipv4h(&bl->addr, n[4]->rs->addr); + bl->port = n[4]->rs->or_port; + memcpy(bl->digest, n[4]->identity, 20); + bridge_add_from_config(bl); + bl = NULL; // prevent free. + + /* 5: Unreachable. This stays in the filter, but isn't in usable-filtered */ + g[5]->last_tried_to_connect = approx_time(); // prevent retry. + g[5]->is_reachable = GUARD_REACHABLE_NO; + + /* 6: no change. */ + + /* Now refilter and inspect. */ + entry_guards_update_filtered_sets(gs); + for (i = 0; i < NUM; ++i) { + tt_assert(g[i]->is_filtered_guard == (i == 5 || i == 6)); + tt_assert(g[i]->is_usable_filtered_guard == (i == 6)); + } + tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, 1); + + done: + guard_selection_free(gs); + tor_free(bl); +} + +static void +test_entry_guard_expand_sample(void *arg) +{ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + digestmap_t *node_by_id = digestmap_new(); + + entry_guard_t *guard = entry_guards_expand_sample(gs); + tt_assert(guard); // the last guard returned. + + // Every sampled guard here should be filtered and reachable for now. + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, + num_reachable_filtered_guards(gs)); + + /* Make sure we got the right number. */ + tt_int_op(MIN_FILTERED_SAMPLE_SIZE, OP_EQ, + num_reachable_filtered_guards(gs)); + + // Make sure everything we got was from our fake node list, and everything + // was unique. + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, g) { + const node_t *n = bfn_mock_node_get_by_id(g->identity); + tt_assert(n); + tt_ptr_op(NULL, OP_EQ, digestmap_get(node_by_id, g->identity)); + digestmap_set(node_by_id, g->identity, (void*) n); + int idx = smartlist_pos(big_fake_net_nodes, n); + // The even ones are the guards; make sure we got guards. + tt_int_op(idx & 1, OP_EQ, 0); + } SMARTLIST_FOREACH_END(g); + + // Nothing became unusable/unfiltered, so a subsequent expand should + // make no changes. + guard = entry_guards_expand_sample(gs); + tt_assert(! guard); // no guard was added. + tt_int_op(MIN_FILTERED_SAMPLE_SIZE, OP_EQ, + num_reachable_filtered_guards(gs)); + + // Make a few guards unreachable. + guard = smartlist_get(gs->sampled_entry_guards, 0); + guard->is_usable_filtered_guard = 0; + guard = smartlist_get(gs->sampled_entry_guards, 1); + guard->is_usable_filtered_guard = 0; + guard = smartlist_get(gs->sampled_entry_guards, 2); + guard->is_usable_filtered_guard = 0; + tt_int_op(MIN_FILTERED_SAMPLE_SIZE - 3, OP_EQ, + num_reachable_filtered_guards(gs)); + + // This time, expanding the sample will add some more guards. + guard = entry_guards_expand_sample(gs); + tt_assert(guard); // no guard was added. + tt_int_op(MIN_FILTERED_SAMPLE_SIZE, OP_EQ, + num_reachable_filtered_guards(gs)); + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, + num_reachable_filtered_guards(gs)+3); + + // Still idempotent. + guard = entry_guards_expand_sample(gs); + tt_assert(! guard); // no guard was added. + tt_int_op(MIN_FILTERED_SAMPLE_SIZE, OP_EQ, + num_reachable_filtered_guards(gs)); + + // Now, do a nasty trick: tell the filter to exclude 31/32 of the guards. + // This will cause the sample size to get reeeeally huge, while the + // filtered sample size grows only slowly. + routerset_free(get_options_mutable()->ExcludeNodes); + get_options_mutable()->ExcludeNodes = routerset_new(); + routerset_parse(get_options_mutable()->ExcludeNodes, "144.144.0.0/16", ""); + SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, { + if (n_sl_idx % 64 != 0) { + n->rs->addr = 0x90903030; + } + }); + entry_guards_update_filtered_sets(gs); + + // Surely (p ~ 1-2**-60), one of our guards has been excluded. + tt_int_op(num_reachable_filtered_guards(gs), OP_LT, + MIN_FILTERED_SAMPLE_SIZE); + + // Try to regenerate the guards. + guard = entry_guards_expand_sample(gs); + tt_assert(guard); // no guard was added. + + /* this time, it's possible that we didn't add enough sampled guards. */ + tt_int_op(num_reachable_filtered_guards(gs), OP_LE, + MIN_FILTERED_SAMPLE_SIZE); + /* but we definitely didn't exceed the sample maximum. */ + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_LE, + (int)((271 / 2) * .3)); + + done: + guard_selection_free(gs); + digestmap_free(node_by_id, NULL); +} + +static void +test_entry_guard_expand_sample_small_net(void *arg) +{ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + + /* Fun corner case: not enough guards to make up our whole sample size. */ + SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, { + if (n_sl_idx >= 40) { + tor_free(n->rs); + tor_free(n->md); + tor_free(n); + SMARTLIST_DEL_CURRENT(big_fake_net_nodes, n); + } else { + n->rs->addr = 0; // make the filter reject this. + } + }); + + entry_guard_t *guard = entry_guards_expand_sample(gs); + tt_assert(guard); // the last guard returned -- some guard was added. + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_GT, 0); + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_LT, 10); + tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, 0); + done: + guard_selection_free(gs); +} + +static void +test_entry_guard_update_from_consensus_status(void *arg) +{ + /* Here we're going to have some nodes become un-guardy, and say we got a + * new consensus. This should cause those nodes to get detected as + * unreachable. */ + + (void)arg; + int i; + time_t start = approx_time(); + guard_selection_t *gs = guard_selection_new(); + networkstatus_t *ns_tmp = NULL; + + /* Don't randomly backdate stuff; it will make correctness harder to check.*/ + MOCK(randomize_time, mock_randomize_time_no_randomization); + + /* First, sample some guards. */ + entry_guards_expand_sample(gs); + int n_sampled_pre = smartlist_len(gs->sampled_entry_guards); + int n_filtered_pre = num_reachable_filtered_guards(gs); + tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre); + tt_i64_op(n_sampled_pre, OP_GT, 10); + + /* At this point, it should be a no-op to do this: */ + sampled_guards_update_from_consensus(gs); + + /* Now let's make some of our guards become unlisted. The easiest way to + * do that would be to take away their guard flag. */ + for (i = 0; i < 5; ++i) { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); + node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity); + n->is_possible_guard = 0; + } + + update_approx_time(start + 30); + { + /* try this with no live networkstatus. Nothing should happen! */ + ns_tmp = dummy_consensus; + dummy_consensus = NULL; + sampled_guards_update_from_consensus(gs); + tt_i64_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre); + tt_i64_op(num_reachable_filtered_guards(gs), OP_EQ, n_filtered_pre); + /* put the networkstatus back. */ + dummy_consensus = ns_tmp; + ns_tmp = NULL; + } + + /* Now those guards should become unlisted, and drop off the filter, but + * stay in the sample. */ + update_approx_time(start + 60); + sampled_guards_update_from_consensus(gs); + + tt_i64_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre); + tt_i64_op(num_reachable_filtered_guards(gs), OP_EQ, n_filtered_pre - 5); + for (i = 0; i < 5; ++i) { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); + tt_assert(! g->currently_listed); + tt_i64_op(g->unlisted_since_date, OP_EQ, start+60); + } + for (i = 5; i < n_sampled_pre; ++i) { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); + tt_assert(g->currently_listed); + tt_i64_op(g->unlisted_since_date, OP_EQ, 0); + } + + /* Now re-list one, and remove one completely. */ + { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 0); + node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity); + n->is_possible_guard = 1; + } + { + /* try removing the node, to make sure we don't crash on an absent node + */ + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 5); + node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity); + smartlist_remove(big_fake_net_nodes, n); + tor_free(n->rs); + tor_free(n->md); + tor_free(n); + } + update_approx_time(start + 300); + sampled_guards_update_from_consensus(gs); + + /* guards 1..5 are now unlisted; 0,6,7.. are listed. */ + tt_i64_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre); + for (i = 1; i < 6; ++i) { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); + tt_assert(! g->currently_listed); + if (i == 5) + tt_i64_op(g->unlisted_since_date, OP_EQ, start+300); + else + tt_i64_op(g->unlisted_since_date, OP_EQ, start+60); + } + for (i = 0; i < n_sampled_pre; i = (!i) ? 6 : i+1) { /* 0,6,7,8, ... */ + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); + tt_assert(g->currently_listed); + tt_i64_op(g->unlisted_since_date, OP_EQ, 0); + } + + done: + tor_free(ns_tmp); /* in case we couldn't put it back */ + guard_selection_free(gs); + UNMOCK(randomize_time); +} + +static void +test_entry_guard_update_from_consensus_repair(void *arg) +{ + /* Here we'll make sure that our code to repair the unlisted-since + * times is correct. */ + + (void)arg; + int i; + time_t start = approx_time(); + guard_selection_t *gs = guard_selection_new(); + + /* Don't randomly backdate stuff; it will make correctness harder to check.*/ + MOCK(randomize_time, mock_randomize_time_no_randomization); + + /* First, sample some guards. */ + entry_guards_expand_sample(gs); + int n_sampled_pre = smartlist_len(gs->sampled_entry_guards); + int n_filtered_pre = num_reachable_filtered_guards(gs); + tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre); + tt_i64_op(n_sampled_pre, OP_GT, 10); + + /* Now corrupt the list a bit. Call some unlisted-since-never, and some + * listed-and-unlisted-since-a-time. */ + update_approx_time(start + 300); + for (i = 0; i < 3; ++i) { + /* these will get a date. */ + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); + node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity); + n->is_possible_guard = 0; + g->currently_listed = 0; + } + for (i = 3; i < 6; ++i) { + /* these will become listed. */ + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); + g->unlisted_since_date = start+100; + } + setup_full_capture_of_logs(LOG_WARN); + sampled_guards_update_from_consensus(gs); + expect_log_msg_containing( + "was listed, but with unlisted_since_date set"); + expect_log_msg_containing( + "was unlisted, but with unlisted_since_date unset"); + teardown_capture_of_logs(); + + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre); + tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, n_filtered_pre - 3); + for (i = 3; i < n_sampled_pre; ++i) { + /* these will become listed. */ + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); + if (i < 3) { + tt_assert(! g->currently_listed); + tt_i64_op(g->unlisted_since_date, OP_EQ, start+300); + } else { + tt_assert(g->currently_listed); + tt_i64_op(g->unlisted_since_date, OP_EQ, 0); + } + } + + done: + teardown_capture_of_logs(); + guard_selection_free(gs); + UNMOCK(randomize_time); +} + +static void +test_entry_guard_update_from_consensus_remove(void *arg) +{ + /* Now let's check the logic responsible for removing guards from the + * sample entirely. */ + + (void)arg; + //int i; + guard_selection_t *gs = guard_selection_new(); + smartlist_t *keep_ids = smartlist_new(); + smartlist_t *remove_ids = smartlist_new(); + + /* Don't randomly backdate stuff; it will make correctness harder to check.*/ + MOCK(randomize_time, mock_randomize_time_no_randomization); + + /* First, sample some guards. */ + entry_guards_expand_sample(gs); + int n_sampled_pre = smartlist_len(gs->sampled_entry_guards); + int n_filtered_pre = num_reachable_filtered_guards(gs); + tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre); + tt_i64_op(n_sampled_pre, OP_GT, 10); + + const time_t one_day_ago = approx_time() - 1*24*60*60; + const time_t one_year_ago = approx_time() - 365*24*60*60; + const time_t two_years_ago = approx_time() - 2*365*24*60*60; + /* 0: unlisted for a day. (keep this) */ + { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 0); + node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity); + n->is_possible_guard = 0; + g->currently_listed = 0; + g->unlisted_since_date = one_day_ago; + smartlist_add(keep_ids, tor_memdup(g->identity, 20)); + } + /* 1: unlisted for a year. (remove this) */ + { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 1); + node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity); + n->is_possible_guard = 0; + g->currently_listed = 0; + g->unlisted_since_date = one_year_ago; + smartlist_add(remove_ids, tor_memdup(g->identity, 20)); + } + /* 2: added a day ago, never confirmed. (keep this) */ + { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 2); + g->sampled_on_date = one_day_ago; + smartlist_add(keep_ids, tor_memdup(g->identity, 20)); + } + /* 3: added a year ago, never confirmed. (remove this) */ + { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 3); + g->sampled_on_date = one_year_ago; + smartlist_add(remove_ids, tor_memdup(g->identity, 20)); + } + /* 4: added two year ago, confirmed yesterday, primary. (keep this.) */ + { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 4); + g->sampled_on_date = one_year_ago; + g->confirmed_on_date = one_day_ago; + g->confirmed_idx = 0; + g->is_primary = 1; + smartlist_add(gs->confirmed_entry_guards, g); + smartlist_add(gs->primary_entry_guards, g); + smartlist_add(keep_ids, tor_memdup(g->identity, 20)); + } + /* 5: added two years ago, confirmed a year ago, primary. (remove this) */ + { + entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 5); + g->sampled_on_date = two_years_ago; + g->confirmed_on_date = one_year_ago; + g->confirmed_idx = 1; + g->is_primary = 1; + smartlist_add(gs->confirmed_entry_guards, g); + smartlist_add(gs->primary_entry_guards, g); + smartlist_add(remove_ids, tor_memdup(g->identity, 20)); + } + + sampled_guards_update_from_consensus(gs); + + /* Did we remove the right ones? */ + SMARTLIST_FOREACH(keep_ids, uint8_t *, id, { + tt_assert(get_sampled_guard_with_id(gs, id) != NULL); + }); + SMARTLIST_FOREACH(remove_ids, uint8_t *, id, { + tt_want(get_sampled_guard_with_id(gs, id) == NULL); + }); + + /* Did we remove the right number? */ + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre - 3); + + done: + guard_selection_free(gs); + UNMOCK(randomize_time); + SMARTLIST_FOREACH(keep_ids, char *, cp, tor_free(cp)); + SMARTLIST_FOREACH(remove_ids, char *, cp, tor_free(cp)); + smartlist_free(keep_ids); + smartlist_free(remove_ids); +} + +static void +test_entry_guard_confirming_guards(void *arg) +{ + (void)arg; + /* Now let's check the logic responsible for manipulating the list + * of confirmed guards */ + guard_selection_t *gs = guard_selection_new(); + MOCK(randomize_time, mock_randomize_time_no_randomization); + + /* Create the sample. */ + entry_guards_expand_sample(gs); + + /* Confirm a few guards. */ + time_t start = approx_time(); + entry_guard_t *g1 = smartlist_get(gs->sampled_entry_guards, 0); + entry_guard_t *g2 = smartlist_get(gs->sampled_entry_guards, 1); + entry_guard_t *g3 = smartlist_get(gs->sampled_entry_guards, 8); + make_guard_confirmed(gs, g2); + update_approx_time(start + 10); + make_guard_confirmed(gs, g1); + make_guard_confirmed(gs, g3); + + /* Were the correct dates and indices fed in? */ + tt_int_op(g1->confirmed_idx, OP_EQ, 1); + tt_int_op(g2->confirmed_idx, OP_EQ, 0); + tt_int_op(g3->confirmed_idx, OP_EQ, 2); + tt_i64_op(g1->confirmed_on_date, OP_EQ, start+10); + tt_i64_op(g2->confirmed_on_date, OP_EQ, start); + tt_i64_op(g3->confirmed_on_date, OP_EQ, start+10); + tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 0), OP_EQ, g2); + tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 1), OP_EQ, g1); + tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 2), OP_EQ, g3); + + /* Now make sure we can regenerate the confirmed_entry_guards list. */ + smartlist_clear(gs->confirmed_entry_guards); + g2->confirmed_idx = 0; + g1->confirmed_idx = 10; + g3->confirmed_idx = 100; + entry_guards_update_confirmed(gs); + tt_int_op(g1->confirmed_idx, OP_EQ, 1); + tt_int_op(g2->confirmed_idx, OP_EQ, 0); + tt_int_op(g3->confirmed_idx, OP_EQ, 2); + tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 0), OP_EQ, g2); + tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 1), OP_EQ, g1); + tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 2), OP_EQ, g3); + + /* Now make sure we can regenerate the confirmed_entry_guards list if + * the indices are messed up. */ + g1->confirmed_idx = g2->confirmed_idx = g3->confirmed_idx = 999; + smartlist_clear(gs->confirmed_entry_guards); + entry_guards_update_confirmed(gs); + tt_int_op(g1->confirmed_idx, OP_GE, 0); + tt_int_op(g2->confirmed_idx, OP_GE, 0); + tt_int_op(g3->confirmed_idx, OP_GE, 0); + tt_int_op(g1->confirmed_idx, OP_LE, 2); + tt_int_op(g2->confirmed_idx, OP_LE, 2); + tt_int_op(g3->confirmed_idx, OP_LE, 2); + g1 = smartlist_get(gs->confirmed_entry_guards, 0); + g2 = smartlist_get(gs->confirmed_entry_guards, 1); + g3 = smartlist_get(gs->confirmed_entry_guards, 2); + tt_int_op(g1->confirmed_idx, OP_EQ, 0); + tt_int_op(g2->confirmed_idx, OP_EQ, 1); + tt_int_op(g3->confirmed_idx, OP_EQ, 2); + tt_assert(g1 != g2); + tt_assert(g1 != g3); + tt_assert(g2 != g3); + + done: + UNMOCK(randomize_time); + guard_selection_free(gs); +} + +static void +test_entry_guard_sample_reachable_filtered(void *arg) +{ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + entry_guards_expand_sample(gs); + const int N = 10000; + bitarray_t *selected = NULL; + int i, j; + + /* We've got a sampled list now; let's make one non-usable-filtered; some + * confirmed, some primary, some pending. + */ + int n_guards = smartlist_len(gs->sampled_entry_guards); + tt_int_op(n_guards, OP_GT, 10); + entry_guard_t *g; + g = smartlist_get(gs->sampled_entry_guards, 0); + g->is_pending = 1; + g = smartlist_get(gs->sampled_entry_guards, 1); + make_guard_confirmed(gs, g); + g = smartlist_get(gs->sampled_entry_guards, 2); + g->is_primary = 1; + g = smartlist_get(gs->sampled_entry_guards, 3); + g->pb.path_bias_disabled = 1; + + entry_guards_update_filtered_sets(gs); + tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, n_guards - 1); + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_guards); + + // +1 since the one we made disabled will make another one get added. + ++n_guards; + + /* Try a bunch of selections. */ + const struct { + int flag; int idx; + } tests[] = { + { 0, -1 }, + { SAMPLE_EXCLUDE_CONFIRMED, 1 }, + { SAMPLE_EXCLUDE_PRIMARY, 2 }, + { SAMPLE_EXCLUDE_PENDING, 0 }, + { -1, -1}, + }; + + for (j = 0; tests[j].flag >= 0; ++j) { + selected = bitarray_init_zero(n_guards); + const int excluded_flags = tests[j].flag; + const int excluded_idx = tests[j].idx; + for (i = 0; i < N; ++i) { + g = sample_reachable_filtered_entry_guards(gs, excluded_flags); + tor_assert(g); + int pos = smartlist_pos(gs->sampled_entry_guards, g); + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_guards); + tt_int_op(pos, OP_GE, 0); + tt_int_op(pos, OP_LT, n_guards); + bitarray_set(selected, pos); + } + for (i = 0; i < n_guards; ++i) { + const int should_be_set = (i != excluded_idx && + i != 3); // filtered out. + tt_int_op(!!bitarray_is_set(selected, i), OP_EQ, should_be_set); + } + bitarray_free(selected); + selected = NULL; + } + + done: + guard_selection_free(gs); + bitarray_free(selected); +} + +static void +test_entry_guard_sample_reachable_filtered_empty(void *arg) +{ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + /* What if we try to sample from a set of 0? */ + SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, + n->is_possible_guard = 0); + + entry_guard_t *g = sample_reachable_filtered_entry_guards(gs, 0); + tt_ptr_op(g, OP_EQ, NULL); + + done: + guard_selection_free(gs); +} + +static void +test_entry_guard_retry_unreachable(void *arg) +{ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + + entry_guards_expand_sample(gs); + /* Let's say that we have two guards, and they're down. + */ + time_t start = approx_time();; + entry_guard_t *g1 = smartlist_get(gs->sampled_entry_guards, 0); + entry_guard_t *g2 = smartlist_get(gs->sampled_entry_guards, 1); + entry_guard_t *g3 = smartlist_get(gs->sampled_entry_guards, 2); + g1->is_reachable = GUARD_REACHABLE_NO; + g2->is_reachable = GUARD_REACHABLE_NO; + g1->is_primary = 1; + g1->failing_since = g2->failing_since = start; + g1->last_tried_to_connect = g2->last_tried_to_connect = start; + + /* Wait 5 minutes. Nothing will get retried. */ + update_approx_time(start + 5 * 60); + entry_guard_consider_retry(g1); + entry_guard_consider_retry(g2); + entry_guard_consider_retry(g3); // just to make sure this doesn't crash. + tt_int_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_NO); + tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_NO); + tt_int_op(g3->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + + /* After 30 min, the primary one gets retried */ + update_approx_time(start + 35 * 60); + entry_guard_consider_retry(g1); + entry_guard_consider_retry(g2); + tt_int_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_NO); + + g1->is_reachable = GUARD_REACHABLE_NO; + g1->last_tried_to_connect = start + 35*60; + + /* After 1 hour, we'll retry the nonprimary one. */ + update_approx_time(start + 61 * 60); + entry_guard_consider_retry(g1); + entry_guard_consider_retry(g2); + tt_int_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_NO); + tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + + g2->is_reachable = GUARD_REACHABLE_NO; + g2->last_tried_to_connect = start + 61*60; + + /* And then the primary one again. */ + update_approx_time(start + 66 * 60); + entry_guard_consider_retry(g1); + entry_guard_consider_retry(g2); + tt_int_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_NO); + + done: + guard_selection_free(gs); +} + +static void +test_entry_guard_manage_primary(void *arg) +{ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + smartlist_t *prev_guards = smartlist_new(); + + /* If no guards are confirmed, we should pick a few reachable guards and + * call them all primary. But not confirmed.*/ + entry_guards_update_primary(gs); + int n_primary = smartlist_len(gs->primary_entry_guards); + tt_int_op(n_primary, OP_GE, 1); + SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, g, { + tt_assert(g->is_primary); + tt_assert(g->confirmed_idx == -1); + }); + + /* Calling it a second time should leave the guards unchanged. */ + smartlist_add_all(prev_guards, gs->primary_entry_guards); + entry_guards_update_primary(gs); + tt_int_op(smartlist_len(gs->primary_entry_guards), OP_EQ, n_primary); + SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, g, { + tt_ptr_op(g, OP_EQ, smartlist_get(prev_guards, g_sl_idx)); + }); + + /* If we have one confirmed guard, that guards becomes the first primary + * guard, and the other primary guards get kept. */ + + /* find a non-primary guard... */ + entry_guard_t *confirmed = NULL; + SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, g, { + if (! g->is_primary) { + confirmed = g; + break; + } + }); + tt_assert(confirmed); + /* make it confirmed. */ + make_guard_confirmed(gs, confirmed); + /* update the list... */ + smartlist_clear(prev_guards); + smartlist_add_all(prev_guards, gs->primary_entry_guards); + entry_guards_update_primary(gs); + + /* and see what's primary now! */ + tt_int_op(smartlist_len(gs->primary_entry_guards), OP_EQ, n_primary); + tt_ptr_op(smartlist_get(gs->primary_entry_guards, 0), OP_EQ, confirmed); + SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, g, { + tt_assert(g->is_primary); + if (g_sl_idx == 0) + continue; + tt_ptr_op(g, OP_EQ, smartlist_get(prev_guards, g_sl_idx - 1)); + }); + { + entry_guard_t *prev_last_guard = smartlist_get(prev_guards, n_primary-1); + tt_assert(! prev_last_guard->is_primary); + } + + /* Calling it a fourth time should leave the guards unchanged. */ + smartlist_clear(prev_guards); + smartlist_add_all(prev_guards, gs->primary_entry_guards); + entry_guards_update_primary(gs); + tt_int_op(smartlist_len(gs->primary_entry_guards), OP_EQ, n_primary); + SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, g, { + tt_ptr_op(g, OP_EQ, smartlist_get(prev_guards, g_sl_idx)); + }); + + done: + guard_selection_free(gs); + smartlist_free(prev_guards); +} + +static void +test_entry_guard_select_for_circuit_no_confirmed(void *arg) +{ + /* Simpler cases: no gaurds are confirmed yet. */ + (void)arg; + guard_selection_t *gs = guard_selection_new(); + + /* simple starting configuration */ + entry_guards_update_primary(gs); + unsigned state = 9999; + + entry_guard_t *g = select_entry_guard_for_circuit(gs, &state); + + tt_assert(g); + tt_assert(g->is_primary); + tt_int_op(g->confirmed_idx, OP_EQ, -1); + tt_assert(g->is_pending == 0); // primary implies non-pending. + tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + tt_i64_op(g->last_tried_to_connect, OP_EQ, approx_time()); + + // If we do that again, we should get the same guard. + entry_guard_t *g2 = select_entry_guard_for_circuit(gs, &state); + tt_ptr_op(g2, OP_EQ, g); + + // if we mark that guard down, we should get a different primary guard. + // auto-retry it. + g->is_reachable = GUARD_REACHABLE_NO; + g->unreachable_since = approx_time() - 10; + g->last_tried_to_connect = approx_time() - 10; + state = 9999; + g2 = select_entry_guard_for_circuit(gs, &state); + tt_ptr_op(g2, OP_NE, g); + tt_assert(g2); + tt_assert(g2->is_primary); + tt_int_op(g2->confirmed_idx, OP_EQ, -1); + tt_assert(g2->is_pending == 0); // primary implies non-pending. + tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time()); + + // If we say that the first primary guard was last tried a long time ago, we + // should get an automatic retry on it. + g->unreachable_since = approx_time() - 72*60*60; + g->last_tried_to_connect = approx_time() - 72*60*60; + state = 9999; + g2 = select_entry_guard_for_circuit(gs, &state); + tt_ptr_op(g2, OP_EQ, g); + tt_assert(g2); + tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time()); + tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + + // And if we mark ALL the primary guards down, we should get another guard + // at random. + SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, guard, { + guard->is_reachable = GUARD_REACHABLE_NO; + guard->last_tried_to_connect = approx_time() - 5; + guard->unreachable_since = approx_time() - 30; + }); + state = 9999; + g2 = select_entry_guard_for_circuit(gs, &state); + tt_assert(g2); + tt_assert(!g2->is_primary); + tt_int_op(g2->confirmed_idx, OP_EQ, -1); + tt_assert(g2->is_pending == 1); + tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time()); + tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + + // As a bonus, maybe we should be retrying the primary guards. Let's say so. + mark_primary_guards_maybe_reachable(gs); + SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, guard, { + tt_int_op(guard->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + tt_assert(guard->is_usable_filtered_guard == 1); + // no change to these fields. + tt_i64_op(guard->last_tried_to_connect, OP_EQ, approx_time() - 5); + tt_i64_op(guard->unreachable_since, OP_EQ, approx_time() - 30); + }); + + done: + guard_selection_free(gs); +} + +static void +test_entry_guard_select_for_circuit_confirmed(void *arg) +{ + /* Case 2: if all the primary guards are down, and there are more confirmed + guards, we use a confirmed guard. */ + (void)arg; + int i; + guard_selection_t *gs = guard_selection_new(); + const int N_CONFIRMED = 10; + + /* slightly more complicated simple starting configuration */ + entry_guards_update_primary(gs); + for (i = 0; i < N_CONFIRMED; ++i) { + entry_guard_t *guard = smartlist_get(gs->sampled_entry_guards, i); + make_guard_confirmed(gs, guard); + } + entry_guards_update_primary(gs); // rebuild the primary list. + + unsigned state = 9999; + + // As above, this gives us a primary guard. + entry_guard_t *g = select_entry_guard_for_circuit(gs, &state); + tt_assert(g); + tt_assert(g->is_primary); + tt_int_op(g->confirmed_idx, OP_EQ, 0); + tt_assert(g->is_pending == 0); // primary implies non-pending. + tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + tt_i64_op(g->last_tried_to_connect, OP_EQ, approx_time()); + tt_ptr_op(g, OP_EQ, smartlist_get(gs->primary_entry_guards, 0)); + + // But if we mark all the primary guards down... + SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, guard, { + guard->last_tried_to_connect = approx_time(); + entry_guards_note_guard_failure(gs, guard); + }); + + // ... we should get a confirmed guard. + state = 9999; + g = select_entry_guard_for_circuit(gs, &state); + tt_assert(g); + tt_assert(! g->is_primary); + tt_int_op(g->confirmed_idx, OP_EQ, smartlist_len(gs->primary_entry_guards)); + tt_assert(g->is_pending); + tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + tt_i64_op(g->last_tried_to_connect, OP_EQ, approx_time()); + + // And if we try again, we should get a different confirmed guard, since + // that one is pending. + state = 9999; + entry_guard_t *g2 = select_entry_guard_for_circuit(gs, &state); + tt_assert(g2); + tt_assert(! g2->is_primary); + tt_ptr_op(g2, OP_NE, g); + tt_int_op(g2->confirmed_idx, OP_EQ, + smartlist_len(gs->primary_entry_guards)+1); + tt_assert(g2->is_pending); + tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time()); + + // If we make every confirmed guard become pending then we start poking + // other guards. + const int n_remaining_confirmed = + N_CONFIRMED - 2 - smartlist_len(gs->primary_entry_guards); + for (i = 0; i < n_remaining_confirmed; ++i) { + g = select_entry_guard_for_circuit(gs, &state); + tt_int_op(g->confirmed_idx, OP_GE, 0); + tt_assert(g); + } + state = 9999; + g = select_entry_guard_for_circuit(gs, &state); + tt_assert(g); + tt_assert(g->is_pending); + tt_int_op(g->confirmed_idx, OP_EQ, -1); + + done: + guard_selection_free(gs); +} + static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; +static const struct testcase_setup_t big_fake_network = { + big_fake_network_setup, big_fake_network_cleanup +}; + +#define BFN_TEST(name) \ + { #name, test_entry_guard_ ## name, TT_FORK, &big_fake_network, NULL } + struct testcase_t entrynodes_tests[] = { { "entry_is_time_to_retry", test_entry_is_time_to_retry, TT_FORK, NULL, NULL }, @@ -1136,6 +2215,20 @@ struct testcase_t entrynodes_tests[] = { test_entry_guard_parse_from_state_failure, 0, NULL, NULL }, { "parse_from_state_partial_failure", test_entry_guard_parse_from_state_partial_failure, 0, NULL, NULL }, + BFN_TEST(add_single_guard), + BFN_TEST(node_filter), + BFN_TEST(expand_sample), + BFN_TEST(expand_sample_small_net), + BFN_TEST(update_from_consensus_status), + BFN_TEST(update_from_consensus_repair), + BFN_TEST(update_from_consensus_remove), + BFN_TEST(confirming_guards), + BFN_TEST(sample_reachable_filtered), + BFN_TEST(sample_reachable_filtered_empty), + BFN_TEST(retry_unreachable), + BFN_TEST(manage_primary), + BFN_TEST(select_for_circuit_no_confirmed), + BFN_TEST(select_for_circuit_confirmed), END_OF_TESTCASES }; From dd6bdab3f6254e7245f8ee76b2c6b4314f0472fa Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 18 Nov 2016 16:05:09 -0500 Subject: [PATCH 17/80] Write the easy parts of the public entryguard interface. Here we add a little bit of state to origin circuits, and set up the necessary functions for the circuit code to call in order to find guards, use guards, and decide when circuits can be used. There's also an incomplete function for the hard part of the circuit-maintenance code, where we figure out whether any waiting guards are ready to become usable. (This patch finally uses the handle.c code to make safe handles to entry_guard_t objects, so that we are allowed to free an entry_guard_t without checking whether any origin_circuit_t is holding a reference to it.) --- src/or/circuitbuild.c | 7 ++ src/or/circuitbuild.h | 2 + src/or/circuitlist.c | 2 + src/or/entrynodes.c | 200 +++++++++++++++++++++++++++++++++++++++--- src/or/entrynodes.h | 38 ++++++++ src/or/or.h | 5 ++ 6 files changed, 241 insertions(+), 13 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index b482e7818d..a33c2ca654 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -519,6 +519,13 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, int flags) return circ; } +/** Return the guard state associated with circ, which may be NULL. */ +circuit_guard_state_t * +origin_circuit_get_guard_state(origin_circuit_t *circ) +{ + return circ->guard_state; +} + /** Start establishing the first hop of our circuit. Figure out what * OR we should connect to, and if necessary start the connection to * it. If we're already connected, then send the 'create' cell. diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 1244601f71..56f66a19d6 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -21,6 +21,8 @@ origin_circuit_t *origin_circuit_init(uint8_t purpose, int flags); origin_circuit_t *circuit_establish_circuit(uint8_t purpose, extend_info_t *exit, int flags); +struct circuit_guard_state_t *origin_circuit_get_guard_state( + origin_circuit_t *circ); int circuit_handle_first_hop(origin_circuit_t *circ); void circuit_n_chan_done(channel_t *chan, int status, int close_origin_circuits); diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index dee103e36a..f13126d76b 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -63,6 +63,7 @@ #include "connection_edge.h" #include "connection_or.h" #include "control.h" +#include "entrynodes.h" #include "main.h" #include "hs_common.h" #include "networkstatus.h" @@ -834,6 +835,7 @@ circuit_free(circuit_t *circ) cpath_ref_decref(ocirc->build_state->service_pending_final_cpath_ref); } tor_free(ocirc->build_state); + circuit_guard_state_free(ocirc->guard_state); circuit_clear_cpath(ocirc); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 958aba47da..260b3d6e82 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -263,6 +263,8 @@ entry_guard_get_pathbias_state(entry_guard_t *guard) return &guard->pb; } +HANDLE_IMPL(entry_guard, entry_guard_t, ATTR_UNUSED STATIC) + /** Return an interval betweeen 'now' and 'max_backdate' seconds in the past, * chosen uniformly at random. We use this before recording persistent * dates, so that we aren't leaking exactly when we recorded it. @@ -1248,6 +1250,186 @@ entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b) } } +/** + * Release all storage held in state. + */ +void +circuit_guard_state_free(circuit_guard_state_t *state) +{ + /* XXXX prop271 -- do we want to inline this structure? */ + if (!state) + return; + entry_guard_handle_free(state->guard); + tor_free(state); +} + +/** + * Pick a suitable entry guard for a circuit in, and place that guard + * in *chosen_node_out. Set *guard_state_out to an opaque + * state object that will record whether the circuit is ready to be used + * or not. Return 0 on success; on failure, return -1. + */ +int +entry_guard_pick_for_circuit(guard_selection_t *gs, + const node_t **chosen_node_out, + circuit_guard_state_t **guard_state_out) +{ + tor_assert(gs); + tor_assert(chosen_node_out); + tor_assert(guard_state_out); + *chosen_node_out = NULL; + *guard_state_out = NULL; + + unsigned state = 0; + entry_guard_t *guard = select_entry_guard_for_circuit(gs, &state); + if (! guard) + return -1; + if (BUG(state == 0)) + return -1; + const node_t *node = node_get_by_id(guard->identity); + // XXXX prop271 check Ed ID. + if (! node) + return -1; + + *chosen_node_out = node; + *guard_state_out = tor_malloc_zero(sizeof(circuit_guard_state_t)); + (*guard_state_out)->guard = entry_guard_handle_new(guard); + (*guard_state_out)->state = state; + (*guard_state_out)->state_set_at = approx_time(); + + return 0; +} + +/** + * Called by the circuit building module when a circuit has succeeded: + * informs the guards code that the guard in *guard_state_p is + * working, and advances the state of the guard module. On a -1 return + * value, the circuit is broken and should not be used. On a 1 return + * value, the circuit is ready to use. On a 0 return value, the circuit + * should not be used until we find out whether preferred guards will + * work for us. + * + * XXXXX prop271 tristates are ugly; reconsider that interface. + */ +int +entry_guard_succeeded(guard_selection_t *gs, + circuit_guard_state_t **guard_state_p) +{ + if (BUG(*guard_state_p == NULL)) + return -1; + + entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard); + if (! guard) + return -1; + + unsigned newstate = + entry_guards_note_guard_success(gs, guard, (*guard_state_p)->state); + + (*guard_state_p)->state = newstate; + (*guard_state_p)->state_set_at = approx_time(); + + if (newstate == GUARD_CIRC_STATE_COMPLETE) { + return 1; + } else { + return 0; + } +} + +/** + * Called by the circuit building module when a circuit has succeeded: + * informs the guards code that the guard in *guard_state_p is + * not working, and advances the state of the guard module. Return -1 on + * bug or inconsistency; 0 on success. + */ +int +entry_guard_failed(guard_selection_t *gs, + circuit_guard_state_t **guard_state_p) +{ + if (BUG(*guard_state_p == NULL)) + return -1; + + entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard); + if (! guard) + return -1; + + entry_guards_note_guard_failure(gs, guard); + + (*guard_state_p)->state = GUARD_CIRC_STATE_DEAD; + (*guard_state_p)->state_set_at = approx_time(); + + return 0; +} + +/** + * Return true iff every primary guard in gs is believed to + * be unreachable. + */ +static int +entry_guards_all_primary_guards_are_down(guard_selection_t *gs) +{ + /* XXXXX prop271 do we have to call entry_guards_update_primary() ?? */ + tor_assert(gs); + SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) { + entry_guard_consider_retry(guard); + if (guard->is_reachable != GUARD_REACHABLE_NO) + return 0; + } SMARTLIST_FOREACH_END(guard); + return 1; +} + +/** + * Look at all of the origin_circuit_t * objects in all_circuits, + * and see if any of them that were previously not ready to use for + * guard-related reasons are now ready to use. Place those circuits + * in newly_complete_out, and mark them COMPLETE. + * + * Return 1 if we upgraded any circuits, and 0 otherwise. + */ +int +entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, + smartlist_t *all_circuits, + smartlist_t *newly_complete_out) +{ + tor_assert(gs); + tor_assert(all_circuits); + tor_assert(newly_complete_out); + + if (! entry_guards_all_primary_guards_are_down(gs)) { + /* We only upgrade a waiting circuit if the primary guards are all + * down. */ + return 0; + } + + /* XXXX finish implementing. prop271 */ + + /* "If any circuit is , and every currently + {is_pending} circuit whose guard has higher priority has been + in state for at least + {NONPRIMARY_GUARD_CONNECT_TIMEOUT} seconds, and all primary + guards have reachable status of , then call that circuit + ." + */ + + /* "If any circuit is , then do not use any + or circuits + circuits whose guards have lower priority. (Time them out + after a {NONPRIMARY_GUARD_IDLE_TIMEOUT} seconds.)" + */ + return 0; +} + +/** + * Update all derived pieces of the guard selection state in gs. + */ +void +entry_guards_update_all(guard_selection_t *gs) +{ + sampled_guards_update_from_consensus(gs); + entry_guards_update_filtered_sets(gs); + entry_guards_update_confirmed(gs); + entry_guards_update_primary(gs); +} + /** * Return a newly allocated string for encoding the persistent parts of * guard to the state file. @@ -1487,19 +1669,10 @@ __attribute__((noreturn)) void entry_guards_DUMMY_ENTRY_POINT(void) { // prop271 XXXXX kludge; remove this - sampled_guards_update_from_consensus(NULL); - sample_reachable_filtered_entry_guards(NULL, 0); - entry_guards_update_confirmed(NULL); - entry_guards_update_primary(NULL); - select_entry_guard_for_circuit(NULL, NULL); - entry_guards_note_guard_failure(NULL, NULL); - entry_guards_note_guard_success(NULL, NULL, 0); - entry_guard_has_higher_priority(NULL, NULL); - entry_guard_encode_for_state(NULL); - entry_guard_parse_from_state(NULL); - compare_guards_by_confirmed_idx(NULL, NULL); - entry_guards_update_filtered_sets(NULL); - tor_assert(0); + entry_guard_has_higher_priority(NULL, NULL); + entry_guard_encode_for_state(NULL); + entry_guard_parse_from_state(NULL); + tor_assert(0); } /* XXXXX ----------------------------------------------- */ @@ -2050,6 +2223,7 @@ entry_guard_free(entry_guard_t *e) { if (!e) return; + entry_guard_handles_clear(e); tor_free(e->chosen_by_version); tor_free(e->sampled_by_version); tor_free(e->extra_state_fields); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 5501e624eb..5b3aa66a26 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -12,12 +12,18 @@ #ifndef TOR_ENTRYNODES_H #define TOR_ENTRYNODES_H +#include "handles.h" + /* Forward declare for guard_selection_t; entrynodes.c has the real struct */ typedef struct guard_selection_s guard_selection_t; /* Forward declare for entry_guard_t; the real declaration is private. */ typedef struct entry_guard_t entry_guard_t; +/* Forward declaration for circuit_guard_state_t; the real declaration is + private. */ +typedef struct circuit_guard_state_t circuit_guard_state_t; + /* Information about a guard's pathbias status. * These fields are used in circpathbias.c to try to detect entry * nodes that are failing circuits at a suspicious frequency. @@ -70,6 +76,8 @@ typedef struct guard_pathbias_t { * use a node_t, since we want to remember these even when we * don't have any directory info. */ struct entry_guard_t { + HANDLE_ENTRY(entry_guard, entry_guard_t); + char nickname[MAX_HEX_NICKNAME_LEN+1]; char identity[DIGEST_LEN]; ed25519_public_key_t ed_id; @@ -259,6 +267,20 @@ struct guard_selection_s { */ int should_add_entry_nodes; }; + +struct entry_guard_handle_t; + +/** + * Per-circuit state to track whether we'll be able to use the circuit. + */ +struct circuit_guard_state_t { + /** Handle to the entry guard object for this circuit. */ + struct entry_guard_handle_t *guard; + /** The time at which state last changed. */ + time_t state_set_at; + /** One of GUARD_CIRC_STATE_* */ + uint8_t state; +}; #endif #if 1 @@ -285,6 +307,19 @@ const char *entry_guard_get_rsa_id_digest(const entry_guard_t *guard); const char *entry_guard_describe(const entry_guard_t *guard); guard_pathbias_t *entry_guard_get_pathbias_state(entry_guard_t *guard); +void circuit_guard_state_free(circuit_guard_state_t *state); +int entry_guard_pick_for_circuit(guard_selection_t *gs, + const node_t **chosen_node_out, + circuit_guard_state_t **guard_state_out); +int entry_guard_succeeded(guard_selection_t *gs, + circuit_guard_state_t **guard_state_p); +int entry_guard_failed(guard_selection_t *gs, + circuit_guard_state_t **guard_state_p); +void entry_guards_update_all(guard_selection_t *gs); +int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, + smartlist_t *all_circuits, + smartlist_t *newly_complete_out); + /* Used by bridges.c only. */ void add_bridge_as_entry_guard(guard_selection_t *gs, const node_t *chosen); @@ -350,6 +385,7 @@ int num_bridges_usable(void); /**@}*/ // ---------- XXXX these functions and definitions are post-prop271. +HANDLE_DECL(entry_guard, entry_guard_t, STATIC) STATIC guard_selection_t *guard_selection_new(void); STATIC void guard_selection_free(guard_selection_t *gs); STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs, @@ -398,6 +434,8 @@ STATIC void sampled_guards_update_from_consensus(guard_selection_t *gs); /** State for a circuit that can (so far as the guard subsystem is * concerned) be used for actual traffic. */ #define GUARD_CIRC_STATE_COMPLETE 4 +/** State for a circuit that is unusable, and will not become usable. */ +#define GUARD_CIRC_STATE_DEAD 5 /**@}*/ STATIC void entry_guards_note_guard_failure(guard_selection_t *gs, entry_guard_t *guard); diff --git a/src/or/or.h b/src/or/or.h index eb94f63d5e..4bc806619c 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3148,6 +3148,11 @@ typedef struct origin_circuit_t { /** Holds all rendezvous data on either client or service side. */ rend_data_t *rend_data; + /** Holds the data that the entry guard system uses to track the + * status of the guard this circuit is using, and thereby to determine + * whether this circuit can be used. */ + struct circuit_guard_state_t *guard_state; + /** How many more relay_early cells can we send on this circuit, according * to the specification? */ unsigned int remaining_relay_early_cells : 4; From 36e9fbd7522fcee54b9f048bf506bfddc4bffc95 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 21 Nov 2016 09:40:45 -0500 Subject: [PATCH 18/80] Backend for upgrading 'waiting' circuits to 'complete' When a nonprimary guard's circuit is complete, we don't call it actually usable until we are pretty sure that every better guard is indeed not going to give us a working circuit. --- src/or/entrynodes.c | 143 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 130 insertions(+), 13 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 260b3d6e82..0e561478bf 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1377,6 +1377,33 @@ entry_guards_all_primary_guards_are_down(guard_selection_t *gs) return 1; } +/** Wrapper for entry_guard_has_higher_priority that compares the + * guard-priorities of a pair of circuits. */ +static int +circ_state_has_higher_priority(origin_circuit_t *a, + origin_circuit_t *b) +{ + circuit_guard_state_t *state_a = origin_circuit_get_guard_state(a); + circuit_guard_state_t *state_b = origin_circuit_get_guard_state(b); + + tor_assert(state_a); + tor_assert(state_b); + + entry_guard_t *guard_a = entry_guard_handle_get(state_a->guard); + entry_guard_t *guard_b = entry_guard_handle_get(state_b->guard); + + if (! guard_a) { + /* Unknown guard -- never higher priority. */ + return 0; + } else if (! guard_b) { + /* Known guard -- higher priority than any unknown guard. */ + return 1; + } else { + /* Both known -- compare.*/ + return entry_guard_has_higher_priority(guard_a, guard_b); + } +} + /** * Look at all of the origin_circuit_t * objects in all_circuits, * and see if any of them that were previously not ready to use for @@ -1397,25 +1424,116 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, if (! entry_guards_all_primary_guards_are_down(gs)) { /* We only upgrade a waiting circuit if the primary guards are all * down. */ + log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits, " + "but not all primary guards were definitely down."); return 0; } - /* XXXX finish implementing. prop271 */ + int n_waiting = 0; + int n_complete = 0; + origin_circuit_t *best_waiting_circuit = NULL; + origin_circuit_t *best_complete_circuit = NULL; + SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { + circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); + if (state == NULL) + continue; + + if (state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD) { + ++n_waiting; + if (! best_waiting_circuit || + circ_state_has_higher_priority(circ, best_waiting_circuit)) { + best_waiting_circuit = circ; + } + } else if (state->state == GUARD_CIRC_STATE_COMPLETE) { + ++n_complete; + if (! best_complete_circuit || + circ_state_has_higher_priority(circ, best_complete_circuit)) { + best_complete_circuit = circ; + } + } + } SMARTLIST_FOREACH_END(circ); + + if (! best_waiting_circuit) { + log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits, " + "but didn't find any."); + return 0; + } + + if (best_complete_circuit) { + if (circ_state_has_higher_priority(best_complete_circuit, + best_waiting_circuit)) { + /* "If any circuit is , then do not use any + or circuits + circuits whose guards have lower priority." */ + log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits: found " + "%d complete and %d guard-stalled. At least one complete " + "circuit had higher priority, so not upgrading.", + n_complete, n_waiting); + + /* XXXX prop271 implement: "(Time them out after a + {NONPRIMARY_GUARD_IDLE_TIMEOUT} seconds.)" + */ + return 0; + } + } /* "If any circuit is , and every currently - {is_pending} circuit whose guard has higher priority has been - in state for at least - {NONPRIMARY_GUARD_CONNECT_TIMEOUT} seconds, and all primary - guards have reachable status of , then call that circuit - ." - */ + {is_pending} circuit whose guard has higher priority has been in + state for at least + {NONPRIMARY_GUARD_CONNECT_TIMEOUT} seconds, and all primary guards + have reachable status of , then call that circuit ." - /* "If any circuit is , then do not use any - or circuits - circuits whose guards have lower priority. (Time them out - after a {NONPRIMARY_GUARD_IDLE_TIMEOUT} seconds.)" + XXXX --- prop271 deviation. there's no such thing in the spec as + an {is_pending circuit}; fix the spec. */ - return 0; + int n_blockers_found = 0; + const time_t state_set_at_cutoff = + approx_time() - NONPRIMARY_GUARD_CONNECT_TIMEOUT; + SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { + circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); + if (state == NULL) + continue; + if (state->state != GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD) + continue; + if (state->state_set_at > state_set_at_cutoff && + circ_state_has_higher_priority(circ, best_waiting_circuit)) + ++n_blockers_found; + } SMARTLIST_FOREACH_END(circ); + + if (n_blockers_found) { + log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits: found " + "%d guard-stalled, but %d pending circuit(s) had higher " + "guard priority, so not upgrading.", + n_waiting, n_blockers_found); + return 0; + } + + /* Okay. We have a best waiting circuit, and we aren't waiting for + anything better. Add all circuits with that priority to the + list, and call them COMPLETE. */ + int n_succeeded = 0; + SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { + circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); + if (state == NULL) + continue; + if (state->state != GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD) + continue; + if (circ_state_has_higher_priority(best_waiting_circuit, circ)) + continue; + + state->state = GUARD_CIRC_STATE_COMPLETE; + state->state_set_at = approx_time(); + smartlist_add(newly_complete_out, circ); + ++n_succeeded; + } SMARTLIST_FOREACH_END(circ); + + log_info(LD_GUARD, "Considered upgrading guard-stalled circuits: found " + "%d guard-stalled, %d complete. %d of the guard-stalled " + "circuit(s) had high enough priority to upgrade.", + n_waiting, n_complete, n_succeeded); + + tor_assert_nonfatal(n_succeeded >= 1); + return 1; } /** @@ -1669,7 +1787,6 @@ __attribute__((noreturn)) void entry_guards_DUMMY_ENTRY_POINT(void) { // prop271 XXXXX kludge; remove this - entry_guard_has_higher_priority(NULL, NULL); entry_guard_encode_for_state(NULL); entry_guard_parse_from_state(NULL); tor_assert(0); From 238828c92b1cc186577e44490caf4fa3870e724d Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 21 Nov 2016 16:59:00 -0500 Subject: [PATCH 19/80] Add a new GUARD_WAIT state for circuits This state corresponds to the WAITING_FOR_BETTER_GUARD state; it's for circuits that are 100% constructed, but which we won't use until we are sure that we wouldn't use circuits with a better guard. --- src/or/circuitlist.c | 20 ++++++++++++++++---- src/or/control.c | 5 ++++- src/or/or.h | 6 +++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index f13126d76b..0189412df0 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -439,7 +439,13 @@ circuit_set_state(circuit_t *circ, uint8_t state) /* add to waiting-circuit list. */ smartlist_add(circuits_pending_chans, circ); } - if (state == CIRCUIT_STATE_OPEN) + if (circ->state == CIRCUIT_STATE_GUARD_WAIT) { + smartlist_remove(circuits_pending_other_guards, circ); + } + if (state == CIRCUIT_STATE_GUARD_WAIT) { + smartlist_add(circuits_pending_other_guards, circ); + } + if (state == CIRCUIT_STATE_GUARD_WAIT || state == CIRCUIT_STATE_OPEN) tor_assert(!circ->n_chan_create_cell); circ->state = state; } @@ -542,6 +548,8 @@ circuit_state_to_string(int state) case CIRCUIT_STATE_BUILDING: return "doing handshakes"; case CIRCUIT_STATE_ONIONSKIN_PENDING: return "processing the onion"; case CIRCUIT_STATE_CHAN_WAIT: return "connecting to server"; + case CIRCUIT_STATE_GUARD_WAIT: return "waiting to see how other " + "guards perform"; case CIRCUIT_STATE_OPEN: return "open"; default: log_warn(LD_BUG, "Unknown circuit state %d", state); @@ -1868,7 +1876,8 @@ circuit_about_to_free(circuit_t *circ) * module then. If it isn't OPEN, we send it there now to remember which * links worked and which didn't. */ - if (circ->state != CIRCUIT_STATE_OPEN) { + if (circ->state != CIRCUIT_STATE_OPEN && + circ->state != CIRCUIT_STATE_GUARD_WAIT) { if (CIRCUIT_IS_ORIGIN(circ)) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); circuit_build_failed(ocirc); /* take actions if necessary */ @@ -1881,7 +1890,9 @@ circuit_about_to_free(circuit_t *circ) } if (CIRCUIT_IS_ORIGIN(circ)) { control_event_circuit_status(TO_ORIGIN_CIRCUIT(circ), - (circ->state == CIRCUIT_STATE_OPEN)?CIRC_EVENT_CLOSED:CIRC_EVENT_FAILED, + (circ->state == CIRCUIT_STATE_OPEN || + circ->state == CIRCUIT_STATE_GUARD_WAIT) ? + CIRC_EVENT_CLOSED:CIRC_EVENT_FAILED, orig_reason); } @@ -2403,7 +2414,8 @@ assert_circuit_ok(const circuit_t *c) tor_assert(c->deliver_window >= 0); tor_assert(c->package_window >= 0); - if (c->state == CIRCUIT_STATE_OPEN) { + if (c->state == CIRCUIT_STATE_OPEN || + c->state == CIRCUIT_STATE_GUARD_WAIT) { tor_assert(!c->n_chan_create_cell); if (or_circ) { tor_assert(or_circ->n_crypto); diff --git a/src/or/control.c b/src/or/control.c index 1d7ec1b57b..9cc99b6b96 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -2596,6 +2596,8 @@ getinfo_helper_events(control_connection_t *control_conn, if (circ->base_.state == CIRCUIT_STATE_OPEN) state = "BUILT"; + else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) + state = "GUARD_WAIT"; // XXXX prop271 must specify this. else if (circ->cpath) state = "EXTENDED"; else @@ -3379,7 +3381,8 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, goto done; } } else { - if (circ->base_.state == CIRCUIT_STATE_OPEN) { + if (circ->base_.state == CIRCUIT_STATE_OPEN || + circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) { int err_reason = 0; circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) { diff --git a/src/or/or.h b/src/or/or.h index 4bc806619c..eb0025d100 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -438,8 +438,12 @@ typedef enum { /** Circuit state: I'd like to deliver a create, but my n_chan is still * connecting. */ #define CIRCUIT_STATE_CHAN_WAIT 2 +/** Circuit state: the circuit is open but we don't want to actually use it + * until we find out if a better guard will be available. + */ +#define CIRCUIT_STATE_GUARD_WAIT 3 /** Circuit state: onionskin(s) processed, ready to send/receive cells. */ -#define CIRCUIT_STATE_OPEN 3 +#define CIRCUIT_STATE_OPEN 4 #define CIRCUIT_PURPOSE_MIN_ 1 From 8dc6048c02806e8d30740c5a2a0dd11476909ce4 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 21 Nov 2016 17:02:51 -0500 Subject: [PATCH 20/80] Add an (as yet) unused UseDeprecatedGuardAlgorithm_ option. I expect we'll be ripping this out somewhere in 0.3.0, but let's keep it around for a little while in case it turns out to be the only way to avert disaster? --- src/or/config.c | 11 +++++++++++ src/or/entrynodes.c | 19 +++++++++++++++++++ src/or/or.h | 8 ++++++++ src/test/test_entrynodes.c | 11 ++++++++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/or/config.c b/src/or/config.c index 79a5847b81..f77f4d1879 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -307,6 +307,10 @@ static config_var_t option_vars_[] = { V(ExtORPortCookieAuthFileGroupReadable, BOOL, "0"), V(ExtraInfoStatistics, BOOL, "1"), V(FallbackDir, LINELIST, NULL), + /* XXXX prop271 -- this has an ugly name to remind us to remove it. */ + VAR("UseDeprecatedGuardAlgorithm_", BOOL, + UseDeprecatedGuardAlgorithm, "0"), + V(UseDefaultFallbackDirs, BOOL, "1"), OBSOLETE("FallbackNetworkstatusFile"), @@ -4489,6 +4493,13 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (old->UseDeprecatedGuardAlgorithm != + new_val->UseDeprecatedGuardAlgorithm) { + *msg = tor_strdup("While Tor is running, changing " + "UseDeprecatedGuardAlgorithm is not allowed."); + return -1; + } + if (sandbox_is_active()) { #define SB_NOCHANGE_STR(opt) \ do { \ diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 0e561478bf..959b4221c1 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1315,6 +1315,9 @@ int entry_guard_succeeded(guard_selection_t *gs, circuit_guard_state_t **guard_state_p) { + if (get_options()->UseDeprecatedGuardAlgorithm) + return 1; + if (BUG(*guard_state_p == NULL)) return -1; @@ -1345,6 +1348,9 @@ int entry_guard_failed(guard_selection_t *gs, circuit_guard_state_t **guard_state_p) { + if (get_options()->UseDeprecatedGuardAlgorithm) + return; + if (BUG(*guard_state_p == NULL)) return -1; @@ -2490,6 +2496,9 @@ entry_guards_compute_status_for_guard_selection(guard_selection_t *gs, if ((!gs) || !(gs->chosen_entry_guards)) return; + if (!get_options()->UseDeprecatedGuardAlgorithm) + return; + if (options->EntryNodes) /* reshuffle the entry guard list if needed */ entry_nodes_should_be_added(); @@ -2582,6 +2591,10 @@ entry_guard_register_connect_status_for_guard_selection( return 0; } + if (! get_options()->UseDeprecatedGuardAlgorithm) { + return 0; + } + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, e) { tor_assert(e); if (tor_memeq(e->identity, digest, DIGEST_LEN)) { @@ -2842,6 +2855,8 @@ entry_list_is_constrained(const or_options_t *options) const node_t * choose_random_entry(cpath_build_state_t *state) { + tor_assert(get_options()->UseDeprecatedGuardAlgorithm); + return choose_random_entry_impl(get_guard_selection_info(), state, 0, NO_DIRINFO, NULL); } @@ -2851,6 +2866,8 @@ choose_random_entry(cpath_build_state_t *state) const node_t * choose_random_dirguard(dirinfo_type_t type) { + tor_assert(get_options()->UseDeprecatedGuardAlgorithm); + return choose_random_entry_impl(get_guard_selection_info(), NULL, 1, type, NULL); } @@ -2861,6 +2878,8 @@ choose_random_dirguard(dirinfo_type_t type) int num_bridges_usable(void) { + tor_assert(get_options()->UseDeprecatedGuardAlgorithm); + int n_options = 0; tor_assert(get_options()->UseBridges); (void) choose_random_entry_impl(get_guard_selection_info(), diff --git a/src/or/or.h b/src/or/or.h index eb0025d100..8282731eea 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4579,6 +4579,14 @@ typedef struct { /** If 1, we skip all OOS checks. */ int DisableOOSCheck; + + /** If 1, we use the old (pre-prop271) guard selection algorithm. + * + * XXXX prop271 This option is only here as a stopgap while we're + * XXXX tuning and debugging the new (post-prop271) algorithm. Eventually + * we should remove it entirely. + */ + int UseDeprecatedGuardAlgorithm; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 8e90b000fc..65118594ea 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -212,6 +212,7 @@ test_choose_random_entry_no_guards(void *arg) memset(&mocked_options, 0, sizeof(mocked_options)); mocked_options.ClientUseIPv4 = 1; mocked_options.ClientPreferIPv6ORPort = 0; + mocked_options.UseDeprecatedGuardAlgorithm = 1; /* Try to pick an entry even though none of our routers are guards. */ chosen_entry = choose_random_entry(NULL); @@ -236,6 +237,7 @@ test_choose_random_entry_no_guards(void *arg) memset(&mocked_options, 0, sizeof(mocked_options)); mocked_options.ClientUseIPv4 = 0; mocked_options.ClientPreferIPv6ORPort = 0; + mocked_options.UseDeprecatedGuardAlgorithm = 1; chosen_entry = choose_random_entry(NULL); @@ -248,6 +250,7 @@ test_choose_random_entry_no_guards(void *arg) mocked_options.ClientUseIPv4 = 1; mocked_options.ClientUseIPv6 = 1; mocked_options.ClientPreferIPv6ORPort = 1; + mocked_options.UseDeprecatedGuardAlgorithm = 1; chosen_entry = choose_random_entry(NULL); tt_assert(chosen_entry); @@ -257,6 +260,7 @@ test_choose_random_entry_no_guards(void *arg) memset(&mocked_options, 0, sizeof(mocked_options)); mocked_options.ClientUseIPv4 = 1; mocked_options.ClientPreferIPv6ORPort = -1; + mocked_options.UseDeprecatedGuardAlgorithm = 1; chosen_entry = choose_random_entry(NULL); tt_assert(chosen_entry); @@ -295,6 +299,7 @@ test_choose_random_entry_one_possible_guard(void *arg) memset(&mocked_options, 0, sizeof(mocked_options)); mocked_options.ClientUseIPv4 = 1; mocked_options.ClientPreferIPv6ORPort = 0; + mocked_options.UseDeprecatedGuardAlgorithm = 1; /* Pick an entry. Make sure we pick the node we marked as guard. */ chosen_entry = choose_random_entry(NULL); @@ -315,6 +320,7 @@ test_choose_random_entry_one_possible_guard(void *arg) memset(&mocked_options, 0, sizeof(mocked_options)); mocked_options.ClientUseIPv4 = 0; mocked_options.ClientPreferIPv6ORPort = 0; + mocked_options.UseDeprecatedGuardAlgorithm = 1; chosen_entry = choose_random_entry(NULL); @@ -327,6 +333,7 @@ test_choose_random_entry_one_possible_guard(void *arg) mocked_options.ClientUseIPv4 = 1; mocked_options.ClientUseIPv6 = 1; mocked_options.ClientPreferIPv6ORPort = 1; + mocked_options.UseDeprecatedGuardAlgorithm = 1; chosen_entry = choose_random_entry(NULL); @@ -339,6 +346,7 @@ test_choose_random_entry_one_possible_guard(void *arg) memset(&mocked_options, 0, sizeof(mocked_options)); mocked_options.ClientUseIPv4 = 1; mocked_options.ClientPreferIPv6ORPort = -1; + mocked_options.UseDeprecatedGuardAlgorithm = 1; chosen_entry = choose_random_entry(NULL); @@ -701,6 +709,7 @@ static void test_entry_guards_set_from_config(void *arg) { or_options_t *options = get_options_mutable(); + options->UseDeprecatedGuardAlgorithm = 1; guard_selection_t *gs = get_guard_selection_info(); const smartlist_t *all_entry_guards = get_entry_guards_for_guard_selection(gs); @@ -2177,7 +2186,7 @@ struct testcase_t entrynodes_tests[] = { TT_FORK, NULL, NULL }, { "choose_random_entry_no_guards", test_choose_random_entry_no_guards, TT_FORK, &fake_network, NULL }, - { "choose_random_entry_one_possibleguard", + { "choose_random_entry_one_possible_guard", test_choose_random_entry_one_possible_guard, TT_FORK, &fake_network, NULL }, { "populate_live_entry_guards_1guard", From 1fd0a547bb6af73cbc32be06ffbf6233eb9050a6 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 21 Nov 2016 17:14:04 -0500 Subject: [PATCH 21/80] New function to tell the guard module "We're on the net!" (Call it whenever we read a cell.) --- src/or/channeltls.c | 9 +++++++++ src/or/entrynodes.c | 8 ++++++++ src/or/entrynodes.h | 12 +++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/or/channeltls.c b/src/or/channeltls.c index 9fb309d0fd..02b783a8e3 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -49,6 +49,7 @@ #include "connection.h" #include "connection_or.h" #include "control.h" +#include "entrynodes.h" #include "link_handshake.h" #include "relay.h" #include "rephist.h" @@ -1095,6 +1096,10 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn) if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_cell(conn, conn->handshake_state, cell, 1); + /* We note that we're on the internet whenever we read a cell. This is + * a fast operation. */ + entry_guards_note_internet_connectivity(get_guard_selection_info()); + switch (cell->command) { case CELL_PADDING: ++stats_n_padding_cells_processed; @@ -1273,6 +1278,10 @@ channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn) return; } + /* We note that we're on the internet whenever we read a cell. This is + * a fast operation. */ + entry_guards_note_internet_connectivity(get_guard_selection_info()); + /* Now handle the cell */ switch (var_cell->command) { diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 959b4221c1..4a998975d8 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1039,6 +1039,14 @@ entry_guard_consider_retry(entry_guard_t *guard) } } +/** Tell the entry guards subsystem that we have confirmed that as of + * just now, we're on the internet. */ +void +entry_guards_note_internet_connectivity(guard_selection_t *gs) +{ + gs->last_time_on_internet = approx_time(); +} + /** * Get a guard for use with a circuit. Prefer to pick a running primary * guard; then a non-pending running filtered confirmed guard; then a diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 5b3aa66a26..7119d547b1 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -283,6 +283,13 @@ struct circuit_guard_state_t { }; #endif +/* Common entry points for old and new guard code */ +void guards_update_all(void); +const node_t *guards_choose_guard(cpath_build_state_t *state, + circuit_guard_state_t **guard_state_out); +const node_t *guards_choose_dirguard(dirinfo_type_t info, + circuit_guard_state_t **guard_state_out); + #if 1 /* XXXX NM I would prefer that all of this stuff be private to * entrynodes.c. */ @@ -313,12 +320,15 @@ int entry_guard_pick_for_circuit(guard_selection_t *gs, circuit_guard_state_t **guard_state_out); int entry_guard_succeeded(guard_selection_t *gs, circuit_guard_state_t **guard_state_p); -int entry_guard_failed(guard_selection_t *gs, +void entry_guard_failed(guard_selection_t *gs, circuit_guard_state_t **guard_state_p); +void entry_guard_chan_failed(guard_selection_t *gs, + channel_t *chan); void entry_guards_update_all(guard_selection_t *gs); int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, smartlist_t *all_circuits, smartlist_t *newly_complete_out); +void entry_guards_note_internet_connectivity(guard_selection_t *gs); /* Used by bridges.c only. */ void add_bridge_as_entry_guard(guard_selection_t *gs, From af1918d28999c2c38ace984296927d9244c7c7b1 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 21 Nov 2016 17:18:35 -0500 Subject: [PATCH 22/80] New entry_guard_chan_failed function To be called when an entire channel has failed: tell any/all circuits pending for the guard of that channel that they have failed. --- src/or/entrynodes.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 4a998975d8..2b6fd51116 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -124,6 +124,7 @@ #include "bridges.h" #include "circpathbias.h" #include "circuitbuild.h" +#include "circuitlist.h" #include "circuitstats.h" #include "config.h" #include "confparse.h" @@ -1371,7 +1372,30 @@ entry_guard_failed(guard_selection_t *gs, (*guard_state_p)->state = GUARD_CIRC_STATE_DEAD; (*guard_state_p)->state_set_at = approx_time(); - return 0; +/** + * Run the entry_guard_failed() function on every circuit that is + * pending on chan. + */ +void +entry_guard_chan_failed(guard_selection_t *gs, + channel_t *chan) +{ + tor_assert(gs); + if (!chan) + return; + if (get_options()->UseDeprecatedGuardAlgorithm) + return; + + smartlist_t *pending = smartlist_new(); + circuit_get_all_pending_on_channel(pending, chan); + SMARTLIST_FOREACH_BEGIN(pending, circuit_t *, circ) { + if (!CIRCUIT_IS_ORIGIN(circ)) + continue; + + origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); + entry_guard_failed(gs, &origin_circ->guard_state); + } SMARTLIST_FOREACH_END(circ); + smartlist_free(pending); } /** From 4689096ed165e893a541b11520b347fc03e39db0 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 21 Nov 2016 17:19:29 -0500 Subject: [PATCH 23/80] No need to say success/failure when recording failure; remove returnval (We can fail at succeeding, but there's no plausible way to fail at failing) --- src/or/entrynodes.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 2b6fd51116..cda554049d 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1350,10 +1350,9 @@ entry_guard_succeeded(guard_selection_t *gs, /** * Called by the circuit building module when a circuit has succeeded: * informs the guards code that the guard in *guard_state_p is - * not working, and advances the state of the guard module. Return -1 on - * bug or inconsistency; 0 on success. + * not working, and advances the state of the guard module. */ -int +void entry_guard_failed(guard_selection_t *gs, circuit_guard_state_t **guard_state_p) { @@ -1361,16 +1360,17 @@ entry_guard_failed(guard_selection_t *gs, return; if (BUG(*guard_state_p == NULL)) - return -1; + return; entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard); if (! guard) - return -1; + return; entry_guards_note_guard_failure(gs, guard); (*guard_state_p)->state = GUARD_CIRC_STATE_DEAD; (*guard_state_p)->state_set_at = approx_time(); +} /** * Run the entry_guard_failed() function on every circuit that is From 8e43398986313f31bfda53aa798263972bf24c11 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 22 Nov 2016 10:03:18 -0500 Subject: [PATCH 24/80] Function to cancel a guard state. We'll want to use this if we allocate a guard state then decide, "whoops, we don't want to use this." --- src/or/entrynodes.c | 23 +++++++++++++++++++++++ src/or/entrynodes.h | 2 ++ 2 files changed, 25 insertions(+) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index cda554049d..24a3448969 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1347,6 +1347,29 @@ entry_guard_succeeded(guard_selection_t *gs, } } +/** Cancel the selection of *guard_state_p without declaring + * success or failure. It is safe to call this function if success or + * failure _has_ already been declared. */ +void +entry_guard_cancel(guard_selection_t *gs, + circuit_guard_state_t **guard_state_p) +{ + (void) gs; + if (get_options()->UseDeprecatedGuardAlgorithm) + return; + if (BUG(*guard_state_p == NULL)) + return; + entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard); + if (! guard) + return; + + /* XXXX prop271 -- last_tried_to_connect_at will be erroneous here, but this + * function will only get called in "bug" cases anyway. */ + guard->is_pending = 0; + circuit_guard_state_free(*guard_state_p); + *guard_state_p = NULL; +} + /** * Called by the circuit building module when a circuit has succeeded: * informs the guards code that the guard in *guard_state_p is diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 7119d547b1..60191ab8b9 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -322,6 +322,8 @@ int entry_guard_succeeded(guard_selection_t *gs, circuit_guard_state_t **guard_state_p); void entry_guard_failed(guard_selection_t *gs, circuit_guard_state_t **guard_state_p); +void entry_guard_cancel(guard_selection_t *gs, + circuit_guard_state_t **guard_state_p); void entry_guard_chan_failed(guard_selection_t *gs, channel_t *chan); void entry_guards_update_all(guard_selection_t *gs); From de617a471442342fc2abafdde4e250fd31eb45ac Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 22 Nov 2016 09:05:52 -0500 Subject: [PATCH 25/80] Maintain a list of all the origin circuits. We'll want this for upgrading waiting circuits. --- src/or/circuitlist.c | 39 +++++++++++++++++++++++++++++++++++++++ src/or/or.h | 4 ++++ 2 files changed, 43 insertions(+) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 0189412df0..c274534759 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -85,6 +85,10 @@ /** A global list of all circuits at this hop. */ static smartlist_t *global_circuitlist = NULL; +/** A global list of all origin circuits. Every element of this is also + * an element of global_circuitlist. */ +static smartlist_t *global_origin_circuit_list = NULL; + /** A list of all the circuits in CIRCUIT_STATE_CHAN_WAIT. */ static smartlist_t *circuits_pending_chans = NULL; @@ -523,6 +527,19 @@ circuit_close_all_marked(void) } circ->global_circuitlist_idx = -1; + /* Remove it from the origin circuit list, if appropriate. */ + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); + int origin_idx = origin_circ->global_origin_circuit_list_idx; + smartlist_del(global_origin_circuit_list, origin_idx); + if (origin_idx < smartlist_len(global_origin_circuit_list)) { + origin_circuit_t *replacement = + smartlist_get(global_origin_circuit_list, origin_idx); + replacement->global_origin_circuit_list_idx = origin_idx; + } + origin_circ->global_origin_circuit_list_idx = -1; + } + circuit_about_to_free(circ); circuit_free(circ); } SMARTLIST_FOREACH_END(circ); @@ -780,6 +797,13 @@ origin_circuit_new(void) init_circuit_base(TO_CIRCUIT(circ)); + /* Add to origin-list. */ + if (!global_origin_circuit_list) + global_origin_circuit_list = smartlist_new(); + smartlist_add(global_origin_circuit_list, circ); + circ->global_origin_circuit_list_idx = + smartlist_len(global_origin_circuit_list) - 1; + circuit_build_times_update_last_circ(get_circuit_build_times_mutable()); return circ; @@ -837,6 +861,18 @@ circuit_free(circuit_t *circ) mem = ocirc; memlen = sizeof(origin_circuit_t); tor_assert(circ->magic == ORIGIN_CIRCUIT_MAGIC); + + if (ocirc->global_origin_circuit_list_idx != -1) { + int idx = ocirc->global_origin_circuit_list_idx; + origin_circuit_t *c2 = smartlist_get(global_origin_circuit_list, idx); + tor_assert(c2 == ocirc); + smartlist_del(global_origin_circuit_list, idx); + if (idx < smartlist_len(global_origin_circuit_list)) { + c2 = smartlist_get(global_origin_circuit_list, idx); + c2->global_origin_circuit_list_idx = idx; + } + } + if (ocirc->build_state) { extend_info_free(ocirc->build_state->chosen_exit); circuit_free_cpath_node(ocirc->build_state->pending_final_cpath); @@ -977,6 +1013,9 @@ circuit_free_all(void) smartlist_free(lst); global_circuitlist = NULL; + smartlist_free(global_origin_circuit_list); + global_origin_circuit_list = NULL; + smartlist_free(circuits_pending_chans); circuits_pending_chans = NULL; diff --git a/src/or/or.h b/src/or/or.h index 8282731eea..c8f39f90d9 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3157,6 +3157,10 @@ typedef struct origin_circuit_t { * whether this circuit can be used. */ struct circuit_guard_state_t *guard_state; + /** Index into global_origin_circuit_list for this circuit. -1 if not + * present. */ + int global_origin_circuit_list_idx; + /** How many more relay_early cells can we send on this circuit, according * to the specification? */ unsigned int remaining_relay_early_cells : 4; From dbbaa515183e250e20c40fa7b4c00df9487058fa Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 21 Nov 2016 17:23:25 -0500 Subject: [PATCH 26/80] Use the new guard notification/selection APIs throughout Tor This patch doesn't cover every case; omitted cases are marked with "XXXX prop271", as usual. It leaves both the old interface and the new interface for guard status notification, since they don't actually work in the same way: the new API wants to be told when a circuit has failed or succeeded, whereas the old API wants to know when a channel has failed or succeeded. I ran into some trouble with directory guard stuff, since when we pick the directory guard, we don't actually have a circuit to associate it with. I solved that by allowing guard states to be associated with directory connections, not just circuits. --- src/or/bridges.c | 2 ++ src/or/channel.c | 1 + src/or/circuitbuild.c | 75 +++++++++++++++++++++++++++++++++++++++--- src/or/circuitbuild.h | 6 +++- src/or/circuitlist.c | 42 ++++++++++++++++++++++- src/or/circuitlist.h | 2 ++ src/or/circuituse.c | 2 ++ src/or/connection.c | 1 + src/or/connection_or.c | 6 ++++ src/or/directory.c | 74 +++++++++++++++++++++++++++++++++-------- src/or/directory.h | 6 ++-- src/or/entrynodes.c | 60 ++++++++++++++++++++++++++++++--- src/or/entrynodes.h | 2 +- src/or/main.c | 5 ++- src/or/or.h | 4 +++ src/or/rendclient.c | 2 +- src/or/rendservice.c | 2 +- src/or/routerlist.c | 4 +-- src/test/test_dir.c | 8 +++-- 19 files changed, 268 insertions(+), 36 deletions(-) diff --git a/src/or/bridges.c b/src/or/bridges.c index 508c77fc9e..2170cc668a 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -724,6 +724,7 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) from_cache ? "cached" : "fresh", router_describe(ri)); /* set entry->made_contact so if it goes down we don't drop it from * our entry node list */ + // XXXX prop271 use new interface here when we hit bridges? entry_guard_register_connect_status(ri->cache_info.identity_digest, 1, 0, now); if (first) { @@ -743,6 +744,7 @@ int any_bridge_descriptors_known(void) { tor_assert(get_options()->UseBridges); + // XXXX prop271 this needs to get fixed. -- bridges return choose_random_entry(NULL) != NULL; } diff --git a/src/or/channel.c b/src/or/channel.c index af5810788c..1e3e99c51d 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -2538,6 +2538,7 @@ channel_do_open_actions(channel_t *chan) if (started_here) { circuit_build_times_network_is_live(get_circuit_build_times_mutable()); rep_hist_note_connect_succeeded(chan->identity_digest, now); + // XXXX prop271 this call is no longer useful with the new algorithm. if (entry_guard_register_connect_status( chan->identity_digest, 1, 0, now) < 0) { /* Close any circuits pending on this channel. We leave it in state diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index a33c2ca654..2f4ce7a727 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -964,7 +964,35 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) memset(&ec, 0, sizeof(ec)); if (!hop) { /* done building the circuit. whew. */ - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); + int r; + if (! circ->guard_state) { + if (circuit_get_cpath_len(circ) != 1) { + log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " + "guard state", + circuit_get_cpath_len(circ), circ, circ->base_.purpose); + } + r = 1; + } else { + r = entry_guard_succeeded(get_guard_selection_info(), + &circ->guard_state); + } + const int is_usable_for_streams = (r == 1); + if (r == 1) { + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); + } else if (r == 0) { + // XXXX prop271 we might want to probe for whether this + // XXXX one is ready even before the next second rolls over. + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT); + } else { + return - END_CIRC_REASON_INTERNAL; + } + + /* XXXX prop271 -- the rest of this branch needs careful thought! + * Some of the things here need to happen when a circuit becomes + * mechanically open; some need to happen when it is actually usable. + * I think I got them right, but more checking would be wise. -NM + */ + if (circuit_timeout_want_to_count_circ(circ)) { struct timeval end; long timediff; @@ -1006,7 +1034,8 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) pathbias_count_build_success(circ); circuit_rep_hist_note_result(circ); - circuit_has_opened(circ); /* do other actions as necessary */ + if (is_usable_for_streams) + circuit_has_opened(circ); /* do other actions as necessary */ if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { const or_options_t *options = get_options(); @@ -2206,9 +2235,20 @@ choose_good_middle_server(uint8_t purpose, * * If state is NULL, we're choosing a router to serve as an entry * guard, not for any particular circuit. + * + * Set *guard_state_out to information about the guard that + * we're selecting, which we'll use later to remember whether the + * guard worked or not. + * + * XXXX prop271 this function is used in four ways: picking out guards for + * the old (pre-prop271) guard algorithm; picking out guards for circuits; + * picking out guards for testing circuits on non-bridgees; + * picking out entries when entry guards are disabled. These options + * should be disentangled. */ const node_t * -choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) +choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state, + circuit_guard_state_t **guard_state_out) { const node_t *choice; smartlist_t *excluded; @@ -2223,7 +2263,8 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) (purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) { /* This request is for an entry server to use for a regular circuit, * and we use entry guard nodes. Just return one of the guard nodes. */ - return choose_random_entry(state); + tor_assert(guard_state_out); + return guards_choose_guard(state, guard_state_out); } excluded = smartlist_new(); @@ -2306,7 +2347,8 @@ onion_extend_cpath(origin_circuit_t *circ) if (cur_len == state->desired_path_len - 1) { /* Picking last node */ info = extend_info_dup(state->chosen_exit); } else if (cur_len == 0) { /* picking first node */ - const node_t *r = choose_good_entry_server(purpose, state); + const node_t *r = choose_good_entry_server(purpose, state, + &circ->guard_state); if (r) { /* If we're a client, use the preferred address rather than the primary address, for potentially connecting to an IPv6 OR @@ -2574,3 +2616,26 @@ extend_info_has_preferred_onion_key(const extend_info_t* ei) return extend_info_supports_ntor(ei); } +/** Find the circuits that are waiting to find out whether their guards are + * usable, and if any are ready to become usable, mark them open and try + * attaching streams as appropriate. */ +void +circuit_upgrade_circuits_from_guard_wait(void) +{ + smartlist_t *to_upgrade = + circuit_find_circuits_to_upgrade_from_guard_wait(); + + if (to_upgrade == NULL) + return; + + log_info(LD_GUARD, "Upgrading %d circuits from 'waiting for better guard' " + "to 'open'.", smartlist_len(to_upgrade)); + + SMARTLIST_FOREACH_BEGIN(to_upgrade, origin_circuit_t *, circ) { + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); + circuit_has_opened(circ); + } SMARTLIST_FOREACH_END(circ); + + smartlist_free(to_upgrade); +} + diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 56f66a19d6..2c83a16550 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -64,8 +64,12 @@ int extend_info_has_preferred_onion_key(const extend_info_t* ei); const node_t *build_state_get_exit_node(cpath_build_state_t *state); const char *build_state_get_exit_nickname(cpath_build_state_t *state); +struct circuit_guard_state_t; + const node_t *choose_good_entry_server(uint8_t purpose, - cpath_build_state_t *state); + cpath_build_state_t *state, + struct circuit_guard_state_t **guard_state_out); +void circuit_upgrade_circuits_from_guard_wait(void); #ifdef CIRCUITBUILD_PRIVATE STATIC circid_t get_unique_circ_id_by_chan(channel_t *chan); diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index c274534759..2a03f8a0d9 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -92,6 +92,10 @@ static smartlist_t *global_origin_circuit_list = NULL; /** A list of all the circuits in CIRCUIT_STATE_CHAN_WAIT. */ static smartlist_t *circuits_pending_chans = NULL; +/** List of all the (origin) circuits whose state is + * CIRCUIT_STATE_GUARD_WAIT. */ +static smartlist_t *circuits_pending_other_guards = NULL; + /** A list of all the circuits that have been marked with * circuit_mark_for_close and which are waiting for circuit_about_to_free. */ static smartlist_t *circuits_pending_close = NULL; @@ -433,8 +437,10 @@ circuit_set_state(circuit_t *circ, uint8_t state) tor_assert(circ); if (state == circ->state) return; - if (!circuits_pending_chans) + if (PREDICT_UNLIKELY(!circuits_pending_chans)) circuits_pending_chans = smartlist_new(); + if (PREDICT_UNLIKELY(!circuits_pending_other_guards)) + circuits_pending_other_guards = smartlist_new(); if (circ->state == CIRCUIT_STATE_CHAN_WAIT) { /* remove from waiting-circuit list. */ smartlist_remove(circuits_pending_chans, circ); @@ -1022,6 +1028,9 @@ circuit_free_all(void) smartlist_free(circuits_pending_close); circuits_pending_close = NULL; + smartlist_free(circuits_pending_other_guards); + circuits_pending_other_guards = NULL; + { chan_circid_circuit_map_t **elt, **next, *c; for (elt = HT_START(chan_circid_map, &chan_circid_map); @@ -1721,6 +1730,37 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, return best; } +/** + * Check whether any of the origin circuits that are waiting to see if + * their guard is good enough to use can be upgraded to "ready". If so, + * return a new smartlist containing them. Otherwise return NULL. + */ +smartlist_t * +circuit_find_circuits_to_upgrade_from_guard_wait(void) +{ + /* Only if some circuit is actually waiting on an upgrade should we + * run the algorithm. */ + if (! circuits_pending_other_guards || + smartlist_len(circuits_pending_other_guards)==0) + return NULL; + /* Only if we have some origin circuiuts should we run the algorithm. + */ + if (!global_origin_circuit_list) + return NULL; + + /* Okay; we can pass our circuit list to entrynodes.c.*/ + smartlist_t *result = smartlist_new(); + int r = entry_guards_upgrade_waiting_circuits(get_guard_selection_info(), + global_origin_circuit_list, + result); + if (r && smartlist_len(result)) { + return result; + } else { + smartlist_free(result); + return NULL; + } +} + /** Return the number of hops in circuit's path. If circ has no entries, * or is NULL, returns 0. */ int diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 989c02afd5..73039cc06e 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -77,6 +77,8 @@ void channel_note_destroy_pending(channel_t *chan, circid_t id); MOCK_DECL(void, channel_note_destroy_not_pending, (channel_t *chan, circid_t id)); +smartlist_t *circuit_find_circuits_to_upgrade_from_guard_wait(void); + #ifdef CIRCUITLIST_PRIVATE STATIC void circuit_free(circuit_t *circ); STATIC size_t n_cells_in_circ_queues(const circuit_t *c); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 2f972d1a28..d2a7f20aa2 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1633,6 +1633,8 @@ circuit_build_failed(origin_circuit_t *circ) "Our circuit died before the first hop with no connection"); } if (n_chan_id && !already_marked) { + entry_guard_failed(get_guard_selection_info(), &circ->guard_state); + /* XXXX prop271 -- old API */ entry_guard_register_connect_status(n_chan_id, 0, 1, time(NULL)); /* if there are any one-hop streams waiting on this circuit, fail * them now so they can retry elsewhere. */ diff --git a/src/or/connection.c b/src/or/connection.c index 2964c30f73..c2a7a87d41 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -634,6 +634,7 @@ connection_free_(connection_t *conn) cached_dir_decref(dir_conn->cached_dir); rend_data_free(dir_conn->rend_data); + circuit_guard_state_free(dir_conn->guard_state); } if (SOCKET_OK(conn->s)) { diff --git a/src/or/connection_or.c b/src/or/connection_or.c index ecc5a4511a..fefcc86932 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -735,6 +735,9 @@ connection_or_about_to_close(or_connection_t *or_conn) const or_options_t *options = get_options(); connection_or_note_state_when_broken(or_conn); rep_hist_note_connect_failed(or_conn->identity_digest, now); + entry_guard_chan_failed(get_guard_selection_info(), + TLS_CHAN_TO_BASE(or_conn->chan)); + /* XXXX prop271 -- old API */ entry_guard_register_connect_status(or_conn->identity_digest,0, !options->HTTPSProxy, now); if (conn->state >= OR_CONN_STATE_TLS_HANDSHAKING) { @@ -1673,6 +1676,9 @@ connection_or_client_learned_peer_id(or_connection_t *conn, "Tried connecting to router at %s:%d, but identity key was not " "as expected: wanted %s but got %s.%s", conn->base_.address, conn->base_.port, expected, seen, extra_log); + entry_guard_chan_failed(get_guard_selection_info(), + TLS_CHAN_TO_BASE(conn->chan)); + /* XXXX prop271 old API */ entry_guard_register_connect_status(conn->identity_digest, 0, 1, time(NULL)); control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, diff --git a/src/or/directory.c b/src/or/directory.c index efa5a3126a..4164672f10 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -128,7 +128,8 @@ static void directory_initiate_command_rend( const char *payload, size_t payload_len, time_t if_modified_since, - const rend_data_t *rend_query); + const rend_data_t *rend_query, + circuit_guard_state_t *guard_state); static void connection_dir_close_consensus_fetches( dir_connection_t *except_this_one, const char *resource); @@ -422,7 +423,8 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, indirection, - NULL, payload, upload_len, 0); + NULL, payload, upload_len, 0, + NULL); } SMARTLIST_FOREACH_END(ds); if (!found) { char *s = authdir_type_to_string(type); @@ -458,7 +460,8 @@ should_use_directory_guards(const or_options_t *options) * information of type type, and return its routerstatus. */ static const routerstatus_t * directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, - uint8_t dir_purpose) + uint8_t dir_purpose, + circuit_guard_state_t **guard_state_out) { const routerstatus_t *rs = NULL; const or_options_t *options = get_options(); @@ -467,7 +470,7 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, log_warn(LD_BUG, "Called when we have UseBridges set."); if (should_use_directory_guards(options)) { - const node_t *node = choose_random_dirguard(type); + const node_t *node = guards_choose_dirguard(type, guard_state_out); if (node) rs = node->rs; } else { @@ -548,6 +551,7 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( if (!options->FetchServerDescriptors) return; + circuit_guard_state_t *guard_state = NULL; if (!get_via_tor) { if (options->UseBridges && !(type & BRIDGE_DIRINFO)) { /* We want to ask a running bridge for which we have a descriptor. @@ -556,6 +560,7 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( * sort of dir fetch we'll be doing, so it won't return a bridge * that can't answer our question. */ + // XXXX prop271 update this for bridge support. const node_t *node = choose_random_dirguard(type); if (node && node->ri) { /* every bridge has a routerinfo. */ @@ -605,9 +610,9 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( } } if (!rs && !(type & BRIDGE_DIRINFO)) { - /* */ rs = directory_pick_generic_dirserver(type, pds_flags, - dir_purpose); + dir_purpose, + &guard_state); if (!rs) get_via_tor = 1; /* last resort: try routing it via Tor */ } @@ -630,7 +635,8 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( router_purpose, indirection, resource, NULL, 0, - if_modified_since); + if_modified_since, + guard_state); } else { log_notice(LD_DIR, "While fetching directory info, " @@ -664,7 +670,7 @@ directory_get_from_all_authorities(uint8_t dir_purpose, rs = &ds->fake_status; directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, DIRIND_ONEHOP, resource, NULL, - 0, 0); + 0, 0, NULL); } SMARTLIST_FOREACH_END(ds); } @@ -775,7 +781,8 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, const char *payload, size_t payload_len, time_t if_modified_since, - const rend_data_t *rend_query) + const rend_data_t *rend_query, + circuit_guard_state_t *guard_state) { const or_options_t *options = get_options(); const node_t *node; @@ -830,7 +837,8 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, dir_purpose, router_purpose, indirection, resource, payload, payload_len, if_modified_since, - rend_query); + rend_query, + guard_state); } /** Launch a new connection to the directory server status to @@ -855,13 +863,15 @@ MOCK_IMPL(void, directory_initiate_command_routerstatus, const char *resource, const char *payload, size_t payload_len, - time_t if_modified_since)) + time_t if_modified_since, + circuit_guard_state_t *guard_state)) { directory_initiate_command_routerstatus_rend(status, dir_purpose, router_purpose, indirection, resource, payload, payload_len, - if_modified_since, NULL); + if_modified_since, NULL, + guard_state); } /** Return true iff conn is the client side of a directory connection @@ -889,6 +899,11 @@ directory_conn_is_self_reachability_test(dir_connection_t *conn) static void connection_dir_request_failed(dir_connection_t *conn) { + if (conn->guard_state) { + /* We haven't seen a success on this guard state, so consider it to have + * failed. */ + entry_guard_failed(get_guard_selection_info(), &conn->guard_state); + } if (directory_conn_is_self_reachability_test(conn)) { return; /* this was a test fetch. don't retry. */ } @@ -1136,7 +1151,7 @@ directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, digest, dir_purpose, router_purpose, indirection, resource, payload, payload_len, - if_modified_since, NULL); + if_modified_since, NULL, NULL); } /** Same as directory_initiate_command(), but accepts rendezvous data to @@ -1151,7 +1166,8 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, const char *resource, const char *payload, size_t payload_len, time_t if_modified_since, - const rend_data_t *rend_query) + const rend_data_t *rend_query, + circuit_guard_state_t *guard_state) { tor_assert(or_addr_port); tor_assert(dir_addr_port); @@ -1246,12 +1262,18 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, if (!anonymized_connection && !use_begindir) { /* then we want to connect to dirport directly */ + // XXXX prop271 I think that we never use guards in this case. if (options->HTTPProxy) { tor_addr_copy(&addr, &options->HTTPProxyAddr); port = options->HTTPProxyPort; } + // In this case we should not have picked a directory guard. + if (BUG(guard_state)) { + entry_guard_cancel(get_guard_selection_info(), &guard_state); + } + switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr, port, &socket_error)) { case -1: @@ -1288,6 +1310,14 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, else if (anonymized_connection && !use_begindir) rep_hist_note_used_port(time(NULL), conn->base_.port); + // In this case we should not have a directory guard; we'll + // get a regular guard later when we build the circuit. + if (BUG(anonymized_connection && guard_state)) { + entry_guard_cancel(get_guard_selection_info(), &guard_state); + } + + conn->guard_state = guard_state; + /* make an AP connection * populate it and add it at the right state * hook up both sides @@ -2540,6 +2570,22 @@ connection_dir_process_inbuf(dir_connection_t *conn) tor_assert(conn); tor_assert(conn->base_.type == CONN_TYPE_DIR); + if (conn->guard_state) { + /* we count the connection as successful once we can read from it. We do + * not, however, delay use of the circuit here, since it's just for a + * one-hop directory request. */ + /* XXXXprop271 note that this will not do the right thing for other + * waiting circuits that would be triggered by this circuit becoming + * complete/usable. But that's ok, I think. + */ + /* XXXXprop271 should we count this as only a partial success somehow? + */ + entry_guard_succeeded(get_guard_selection_info(), + &conn->guard_state); + circuit_guard_state_free(conn->guard_state); + conn->guard_state = NULL; + } + /* Directory clients write, then read data until they receive EOF; * directory servers read data until they get an HTTP command, then * write their response (when it's finished flushing, they mark for diff --git a/src/or/directory.h b/src/or/directory.h index 589df7b70d..ee0a198c52 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -49,7 +49,8 @@ MOCK_DECL(void, directory_initiate_command_routerstatus, const char *resource, const char *payload, size_t payload_len, - time_t if_modified_since)); + time_t if_modified_since, + struct circuit_guard_state_t *guard_state)); void directory_initiate_command_routerstatus_rend(const routerstatus_t *status, uint8_t dir_purpose, @@ -59,7 +60,8 @@ void directory_initiate_command_routerstatus_rend(const routerstatus_t *status, const char *payload, size_t payload_len, time_t if_modified_since, - const rend_data_t *rend_query); + const rend_data_t *rend_query, + struct circuit_guard_state_t *guard_state); int parse_http_response(const char *headers, int *code, time_t *date, compress_method_t *compression, char **response); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 24a3448969..eca88a947c 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -89,7 +89,7 @@ * [x] Whenever a guard becomes reachable or maybe-reachable, if its filtered * flag is set, set its usable_filtered flag. * - * [ ] Whenever we get a new consensus, call update_from_consensus(). (LATER.) + * [x] Whenever we get a new consensus, call update_from_consensus(). (LATER.) * * [ ] Whenever the configuration changes in a relevant way, update the * filtered/usable flags. (LATER.) @@ -1203,8 +1203,6 @@ entry_guards_note_guard_success(guard_selection_t *gs, if (last_time_on_internet + INTERNET_LIKELY_DOWN_INTERVAL < approx_time()) { mark_primary_guards_maybe_reachable(gs); - } else { - // update_waiting_circuits(gs); // XXXX prop271 write this function. } } @@ -1475,7 +1473,7 @@ circ_state_has_higher_priority(origin_circuit_t *a, */ int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, - smartlist_t *all_circuits, + const smartlist_t *all_circuits, smartlist_t *newly_complete_out) { tor_assert(gs); @@ -2274,7 +2272,7 @@ add_an_entry_guard(guard_selection_t *gs, return NULL; } } else if (!for_directory) { - node = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); + node = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL, NULL); if (!node) return NULL; } else { @@ -3779,6 +3777,58 @@ entries_retry_all(const or_options_t *options) entries_retry_helper(options, 1); } +/** Helper: Update the status of all entry guards, in whatever algorithm + is used. */ +void +guards_update_all(void) +{ + if (get_options()->UseDeprecatedGuardAlgorithm) { + entry_guards_compute_status(get_options(), approx_time()); + } else { + entry_guards_update_all(get_guard_selection_info()); + } +} + +/** Helper: pick a guard for a circuit, with whatever algorithm is + used. */ +const node_t * +guards_choose_guard(cpath_build_state_t *state, + circuit_guard_state_t **guard_state_out) +{ + if (get_options()->UseDeprecatedGuardAlgorithm) { + return choose_random_entry(state); + } else { + // XXXX prop271 we need to look at the chosen exit node if any, and + // not duplicate it. + const node_t *r = NULL; + if (entry_guard_pick_for_circuit(get_guard_selection_info(), + &r, + guard_state_out) < 0) { + tor_assert(r == NULL); + } + return r; + } +} + +/** Helper: pick a directory guard, with whatever algorithm is used. */ +const node_t * +guards_choose_dirguard(dirinfo_type_t info, + circuit_guard_state_t **guard_state_out) +{ + if (get_options()->UseDeprecatedGuardAlgorithm) { + return choose_random_dirguard(info); + } else { + // XXXX prop271 look at info? + const node_t *r = NULL; + if (entry_guard_pick_for_circuit(get_guard_selection_info(), + &r, + guard_state_out) < 0) { + tor_assert(r == NULL); + } + return r; + } +} + /** Free one guard selection context */ STATIC void guard_selection_free(guard_selection_t *gs) diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 60191ab8b9..7dcedd6066 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -328,7 +328,7 @@ void entry_guard_chan_failed(guard_selection_t *gs, channel_t *chan); void entry_guards_update_all(guard_selection_t *gs); int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, - smartlist_t *all_circuits, + const smartlist_t *all_circuits, smartlist_t *newly_complete_out); void entry_guards_note_internet_connectivity(guard_selection_t *gs); diff --git a/src/or/main.c b/src/or/main.c index a508003f97..65d1c1fd79 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -979,7 +979,7 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs) /* if we have enough dir info, then update our guard status with * whatever we just learned. */ - entry_guards_compute_status(options, now); + guards_update_all(); /* Don't even bother trying to get extrainfo until the rest of our * directory info is up-to-date */ if (options->DownloadExtraInfo) @@ -1376,6 +1376,9 @@ run_scheduled_events(time_t now) /* 0c. If we've deferred log messages for the controller, handle them now */ flush_pending_log_callbacks(); + /* Maybe enough time elapsed for us to reconsider a circuit. */ + circuit_upgrade_circuits_from_guard_wait(); + if (options->UseBridges && !options->DisableNetwork) { fetch_bridge_descriptors(options, now); } diff --git a/src/or/or.h b/src/or/or.h index c8f39f90d9..8b9ede3607 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1786,6 +1786,10 @@ typedef struct dir_connection_t { /** What rendezvous service are we querying for? */ rend_data_t *rend_data; + /** If this is a one-hop connection, tracks the state of the directory guard + * for this connection (if any). */ + struct circuit_guard_state_t *guard_state; + char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for * the directory server's signing key. */ diff --git a/src/or/rendclient.c b/src/or/rendclient.c index b0dcf52507..06744ad795 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -762,7 +762,7 @@ directory_get_from_hs_dir(const char *desc_id, how_to_fetch, desc_id_base32, NULL, 0, 0, - rend_query); + rend_query, NULL); log_info(LD_REND, "Sending fetch request for v2 descriptor for " "service '%s' with descriptor ID '%s', auth type %d, " "and descriptor cookie '%s' to hidden service " diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 8ffd0bc319..cf57c7d061 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -3452,7 +3452,7 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, DIRIND_ANONYMOUS, NULL, desc->desc_str, strlen(desc->desc_str), - 0, rend_data); + 0, rend_data, NULL); rend_data_free(rend_data); base32_encode(desc_id_base32, sizeof(desc_id_base32), desc->desc_id, DIGEST_LEN); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index f51b50e8e5..d2f360a63f 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -971,7 +971,7 @@ authority_certs_fetch_resource_impl(const char *resource, directory_initiate_command_routerstatus(rs, DIR_PURPOSE_FETCH_CERTIFICATE, 0, indirection, resource, NULL, - 0, 0); + 0, 0, NULL); return; } @@ -4946,7 +4946,7 @@ MOCK_IMPL(STATIC void, initiate_descriptor_downloads, directory_initiate_command_routerstatus(source, purpose, ROUTER_PURPOSE_GENERAL, DIRIND_ONEHOP, - resource, NULL, 0, 0); + resource, NULL, 0, 0, NULL); } else { directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource, pds_flags, DL_WANT_ANY_DIRSERVER); diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 6f83ceff00..4501d6b547 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -23,6 +23,7 @@ #include "directory.h" #include "dirserv.h" #include "dirvote.h" +#include "entrynodes.h" #include "hibernate.h" #include "memarea.h" #include "networkstatus.h" @@ -4397,7 +4398,8 @@ directory_initiate_command_routerstatus, (const routerstatus_t *status, const char *resource, const char *payload, size_t payload_len, - time_t if_modified_since)); + time_t if_modified_since, + circuit_guard_state_t *guardstate)); static void test_dir_should_not_init_request_to_ourselves(void *data) @@ -4504,7 +4506,8 @@ NS(directory_initiate_command_routerstatus)(const routerstatus_t *status, const char *resource, const char *payload, size_t payload_len, - time_t if_modified_since) + time_t if_modified_since, + circuit_guard_state_t *guardstate) { (void)status; (void)dir_purpose; @@ -4514,6 +4517,7 @@ NS(directory_initiate_command_routerstatus)(const routerstatus_t *status, (void)payload; (void)payload_len; (void)if_modified_since; + (void)guardstate; CALLED(directory_initiate_command_routerstatus)++; } From 858c8f5593e573cdf36c360141cf6e96d91d6474 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 22 Nov 2016 14:22:54 -0500 Subject: [PATCH 27/80] Make new prop271 entry guards persistent To do this, it makes sense to treat legacy guards as a separate guard_selection_t *, and handle them separately. This also means we add support here for having multiple guard selections. Note that we don't persist pathbias information yet; that will take some refactoring. --- src/or/entrynodes.c | 186 +++++++++++++++++++++++++++++++++---- src/or/entrynodes.h | 14 ++- src/or/or.h | 5 +- src/or/statefile.c | 2 + src/test/test_entrynodes.c | 50 +++++++--- 5 files changed, 218 insertions(+), 39 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index eca88a947c..4e32154b95 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -179,14 +179,16 @@ should_apply_guardfraction(const networkstatus_t *ns) return options->UseGuardFraction; } -/** Allocate and return a new guard_selection_t */ - +/** + * Allocate and return a new guard_selection_t, with the name name. + */ STATIC guard_selection_t * -guard_selection_new(void) +guard_selection_new(const char *name) { guard_selection_t *gs; gs = tor_malloc_zero(sizeof(*gs)); + gs->name = tor_strdup(name); gs->chosen_entry_guards = smartlist_new(); gs->sampled_entry_guards = smartlist_new(); gs->confirmed_entry_guards = smartlist_new(); @@ -195,6 +197,37 @@ guard_selection_new(void) return gs; } +/** + * Return the guard selection called name. If there is none, and + * create_if_absent is true, then create and return it. If there + * is none, and create_if_absent is false, then return NULL. + */ +static guard_selection_t * +get_guard_selection_by_name(const char *name, int create_if_absent) +{ + if (!guard_contexts) { + guard_contexts = smartlist_new(); + } + SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) { + if (!strcmp(gs->name, name)) + return gs; + } SMARTLIST_FOREACH_END(gs); + + if (! create_if_absent) + return NULL; + + guard_selection_t *new_selection = guard_selection_new(name); + smartlist_add(guard_contexts, new_selection); + + const char *default_name = get_options()->UseDeprecatedGuardAlgorithm ? + "legacy" : "default"; + + if (!strcmp(name, default_name)) + curr_guard_context = new_selection; + + return new_selection; +} + /** Get current default guard_selection_t, creating it if necessary */ guard_selection_t * get_guard_selection_info(void) @@ -204,7 +237,9 @@ get_guard_selection_info(void) } if (!curr_guard_context) { - curr_guard_context = guard_selection_new(); + const char *name = get_options()->UseDeprecatedGuardAlgorithm ? + "legacy" : "default"; + curr_guard_context = guard_selection_new(name); smartlist_add(guard_contexts, curr_guard_context); } @@ -355,6 +390,7 @@ entry_guard_add_to_sample(guard_selection_t *gs, entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); /* persistent fields */ + guard->selection_name = tor_strdup(gs->name); memcpy(guard->identity, node->identity, DIGEST_LEN); strlcpy(guard->nickname, node_get_nickname(node), sizeof(guard->nickname)); guard->sampled_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10); @@ -691,8 +727,9 @@ entry_guard_passes_filter(const or_options_t *options, guard_selection_t *gs, return 0; const node_t *node = node_get_by_id(guard->identity); - if (BUG(node == NULL)) { - // should be impossible, since currently_listed was true. + if (node == NULL) { + // This can happen when currently_listed is true, and we're not updating + // it because we don't have a live consensus. return 0; } @@ -1627,6 +1664,7 @@ entry_guard_encode_for_state(entry_guard_t *guard) tor_assert(guard); + smartlist_add_asprintf(result, "in=%s", guard->selection_name); smartlist_add_asprintf(result, "rsa_id=%s", hex_str(guard->identity, DIGEST_LEN)); if (strlen(guard->nickname)) { @@ -1678,6 +1716,7 @@ entry_guard_parse_from_state(const char *s) smartlist_t *extra = smartlist_new(); /* These fields get parsed from the string. */ + char *in = NULL; char *rsa_id = NULL; char *nickname = NULL; char *sampled_on = NULL; @@ -1693,6 +1732,7 @@ entry_guard_parse_from_state(const char *s) smartlist_t *entries = smartlist_new(); strmap_t *vals = strmap_new(); // Maps keyword to location + strmap_set(vals, "in", &in); strmap_set(vals, "rsa_id", &rsa_id); strmap_set(vals, "nickname", &nickname); strmap_set(vals, "sampled_on", &sampled_on); @@ -1731,6 +1771,14 @@ entry_guard_parse_from_state(const char *s) entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); + if (in == NULL) { + log_warn(LD_CIRC, "Guard missing 'in' field"); + goto err; + } + + guard->selection_name = in; + in = NULL; + if (rsa_id == NULL) { log_warn(LD_CIRC, "Guard missing RSA ID field"); goto err; @@ -1825,6 +1873,7 @@ entry_guard_parse_from_state(const char *s) guard = NULL; done: + tor_free(in); tor_free(rsa_id); tor_free(nickname); tor_free(sampled_on); @@ -1839,16 +1888,95 @@ entry_guard_parse_from_state(const char *s) return guard; } -/* XXXXprop271 This is a dummy function added for now so that all of the - * new guard code will be counted as reachable. It should get removed. +/** + * Replace the Guards entries in state with a list of all our + * non-legacy sampled guards. */ -__attribute__((noreturn)) void -entry_guards_DUMMY_ENTRY_POINT(void) +static void +entry_guards_update_guards_in_state(or_state_t *state) { - // prop271 XXXXX kludge; remove this - entry_guard_encode_for_state(NULL); - entry_guard_parse_from_state(NULL); - tor_assert(0); + if (!guard_contexts) + return; + config_line_t *lines = NULL; + config_line_t **nextline = &lines; + + SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) { + if (!strcmp(gs->name, "legacy")) + continue; /* This is encoded differently. */ + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + *nextline = tor_malloc_zero(sizeof(config_line_t)); + (*nextline)->key = tor_strdup("Guard"); + (*nextline)->value = entry_guard_encode_for_state(guard); + nextline = &(*nextline)->next; + } SMARTLIST_FOREACH_END(guard); + gs->dirty = 0; + } SMARTLIST_FOREACH_END(gs); + + /* XXXXX prop271 circuitpathbias */ + + config_free_lines(state->Guard); + state->Guard = lines; +} + +/** + * Replace our non-legacy sampled guards from the Guards entries in + * state. Return 0 on success, -1 on failure. (If set is + * true, replace nothing -- only check whether replacing would work.) + */ +static int +entry_guards_load_guards_from_state(or_state_t *state, int set) +{ + /* XXXXX prop271 circuitpathbias */ + const config_line_t *line = state->Guard; + int n_errors = 0; + + if (!guard_contexts) + guard_contexts = smartlist_new(); + + /* Wipe all our existing guard info. (we shouldn't have any, but + * let's be safe.) */ + if (set) { + SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) { + if (!strcmp(gs->name, "legacy")) + continue; + guard_selection_free(gs); + if (curr_guard_context == gs) + curr_guard_context = NULL; + SMARTLIST_DEL_CURRENT(guard_contexts, gs); + } SMARTLIST_FOREACH_END(gs); + } + + for ( ; line != NULL; line = line->next) { + entry_guard_t *guard = entry_guard_parse_from_state(line->value); + if (guard == NULL) { + ++n_errors; + continue; + } + tor_assert(guard->selection_name); + if (!strcmp(guard->selection_name, "legacy")) { + ++n_errors; + entry_guard_free(guard); + continue; + } + + if (set) { + guard_selection_t *gs; + gs = get_guard_selection_by_name(guard->selection_name, 1); + tor_assert(gs); + smartlist_add(gs->sampled_entry_guards, guard); + } else { + entry_guard_free(guard); + } + } + + if (set) { + SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) { + if (!strcmp(gs->name, "legacy")) + continue; + entry_guards_update_all(gs); + } SMARTLIST_FOREACH_END(gs); + } + return n_errors ? -1 : 0; } /* XXXXX ----------------------------------------------- */ @@ -2403,6 +2531,7 @@ entry_guard_free(entry_guard_t *e) tor_free(e->chosen_by_version); tor_free(e->sampled_by_version); tor_free(e->extra_state_fields); + tor_free(e->selection_name); tor_free(e); } @@ -3436,9 +3565,19 @@ entry_guards_parse_state_for_guard_selection( int entry_guards_parse_state(or_state_t *state, int set, char **msg) { - return entry_guards_parse_state_for_guard_selection( - get_guard_selection_info(), + int r1 = entry_guards_load_guards_from_state(state, set); + + int r2 = entry_guards_parse_state_for_guard_selection( + get_guard_selection_by_name("legacy", 1), state, set, msg); + + if (r1 < 0 || r2 < 0) { + if (msg && *msg == NULL) { + *msg = tor_strdup("parsing error"); //xxxx prop271 should we try harder? + } + return -1; + } + return 0; } /** How long will we let a change in our guard nodes stay un-saved @@ -3466,7 +3605,9 @@ entry_guards_changed_for_guard_selection(guard_selection_t *gs) else when = time(NULL) + FAST_GUARD_STATE_FLUSH_TIME; - /* or_state_save() will call entry_guards_update_state(). */ + /* or_state_save() will call entry_guards_update_state() and + entry_guards_update_guards_in_state() + */ or_state_mark_dirty(get_or_state(), when); } @@ -3494,11 +3635,14 @@ void entry_guards_update_state(or_state_t *state) { config_line_t **next, *line; - guard_selection_t *gs = get_guard_selection_info(); - tor_assert(gs != NULL); + // Handles all non-legacy guard info. + entry_guards_update_guards_in_state(state); + + guard_selection_t *gs = get_guard_selection_by_name("legacy", 0); + if (!gs) + return; // nothign to save. tor_assert(gs->chosen_entry_guards != NULL); - if (!gs->dirty) return; @@ -3835,6 +3979,8 @@ guard_selection_free(guard_selection_t *gs) { if (!gs) return; + tor_free(gs->name); + if (gs->chosen_entry_guards) { SMARTLIST_FOREACH(gs->chosen_entry_guards, entry_guard_t *, e, entry_guard_free(e)); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 7dcedd6066..d8468eb287 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -121,6 +121,11 @@ struct entry_guard_t { * item should occur in the CONFIRMED_GUARDS ordered * list */ + /** + * Which selection does this guard belong to? + */ + char *selection_name; + /* ==== Non-persistent fields. */ /* == These are used by sampled guards */ /** When did we last decide to try using this guard for a circuit? 0 for @@ -202,6 +207,11 @@ struct entry_guard_t { * about guard selection algorithms. */ struct guard_selection_s { + /** + * The name for this guard-selection object. (Must not contain spaces). + */ + char *name; + /** * A value of 1 means that guard_selection_t structures have changed * and those changes need to be flushed to disk. @@ -398,7 +408,7 @@ int num_bridges_usable(void); // ---------- XXXX these functions and definitions are post-prop271. HANDLE_DECL(entry_guard, entry_guard_t, STATIC) -STATIC guard_selection_t *guard_selection_new(void); +STATIC guard_selection_t *guard_selection_new(const char *name); STATIC void guard_selection_free(guard_selection_t *gs); STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs, const uint8_t *rsa_id); @@ -459,8 +469,6 @@ STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs, unsigned old_state); STATIC int entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b); -void entry_guards_DUMMY_ENTRY_POINT(void); - // ---------- XXXX this stuff is pre-prop271. STATIC const node_t *add_an_entry_guard(guard_selection_t *gs, diff --git a/src/or/or.h b/src/or/or.h index 8b9ede3607..04ff548a78 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4618,9 +4618,12 @@ typedef struct { uint64_t AccountingBytesAtSoftLimit; uint64_t AccountingExpectedUsage; - /** A list of Entry Guard-related configuration lines. */ + /** A list of Entry Guard-related configuration lines. (pre-prop271) */ config_line_t *EntryGuards; + /** A list of guard-related configuration lines. (post-prop271) */ + config_line_t *Guard; + config_line_t *TransportProxies; /** These fields hold information on the history of bandwidth usage for diff --git a/src/or/statefile.c b/src/or/statefile.c index 8fa4324b25..a95ba8533c 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -102,6 +102,8 @@ static config_var_t state_vars_[] = { V(BWHistoryDirWriteValues, CSV, ""), V(BWHistoryDirWriteMaxima, CSV, ""), + V(Guard, LINELIST, NULL), + V(TorVersion, STRING, NULL), V(LastRotatedOnionKey, ISOTIME, NULL), diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 65118594ea..d8b9ccb7e6 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -528,6 +528,8 @@ state_lines_free(smartlist_t *entry_guard_lines) static void test_entry_guards_parse_state_simple(void *arg) { + or_options_t *options = get_options_mutable(); + options->UseDeprecatedGuardAlgorithm = 1; or_state_t *state = or_state_new(); const smartlist_t *all_entry_guards = get_entry_guards(); smartlist_t *entry_state_lines = smartlist_new(); @@ -622,6 +624,8 @@ test_entry_guards_parse_state_simple(void *arg) static void test_entry_guards_parse_state_pathbias(void *arg) { + or_options_t *options = get_options_mutable(); + options->UseDeprecatedGuardAlgorithm = 1; or_state_t *state = or_state_new(); const smartlist_t *all_entry_guards = get_entry_guards(); char *msg = NULL; @@ -1021,6 +1025,7 @@ test_entry_guard_encode_for_state_minimal(void *arg) (void) arg; entry_guard_t *eg = tor_malloc_zero(sizeof(entry_guard_t)); + eg->selection_name = tor_strdup("wubwub"); memcpy(eg->identity, "plurpyflurpyslurpydo", DIGEST_LEN); eg->sampled_on_date = 1479081600; eg->confirmed_idx = -1; @@ -1029,6 +1034,7 @@ test_entry_guard_encode_for_state_minimal(void *arg) s = entry_guard_encode_for_state(eg); tt_str_op(s, OP_EQ, + "in=wubwub " "rsa_id=706C75727079666C75727079736C75727079646F " "sampled_on=2016-11-14T00:00:00 " "listed=0"); @@ -1045,6 +1051,7 @@ test_entry_guard_encode_for_state_maximal(void *arg) entry_guard_t *eg = tor_malloc_zero(sizeof(entry_guard_t)); strlcpy(eg->nickname, "Fred", sizeof(eg->nickname)); + eg->selection_name = tor_strdup("default"); memcpy(eg->identity, "plurpyflurpyslurpydo", DIGEST_LEN); eg->sampled_on_date = 1479081600; eg->sampled_by_version = tor_strdup("1.2.3"); @@ -1058,6 +1065,7 @@ test_entry_guard_encode_for_state_maximal(void *arg) s = entry_guard_encode_for_state(eg); tt_str_op(s, OP_EQ, + "in=default " "rsa_id=706C75727079666C75727079736C75727079646F " "nickname=Fred " "sampled_on=2016-11-14T00:00:00 " @@ -1082,9 +1090,11 @@ test_entry_guard_parse_from_state_minimal(void *arg) time_t t = approx_time(); eg = entry_guard_parse_from_state( + "in=default_plus " "rsa_id=596f75206d6179206e656564206120686f626279"); tt_assert(eg); + tt_str_op(eg->selection_name, OP_EQ, "default_plus"); test_mem_op_hex(eg->identity, OP_EQ, "596f75206d6179206e656564206120686f626279"); tt_str_op(eg->nickname, OP_EQ, "$596F75206D6179206E656564206120686F626279"); @@ -1112,6 +1122,7 @@ test_entry_guard_parse_from_state_maximal(void *arg) entry_guard_t *eg = NULL; eg = entry_guard_parse_from_state( + "in=fred " "rsa_id=706C75727079666C75727079736C75727079646F " "nickname=Fred " "sampled_on=2016-11-14T00:00:00 " @@ -1150,22 +1161,30 @@ test_entry_guard_parse_from_state_failure(void *arg) (void)arg; entry_guard_t *eg = NULL; + /* no selection */ + eg = entry_guard_parse_from_state( + "rsa_id=596f75206d6179206e656564206120686f626270"); + tt_assert(! eg); + /* no RSA ID. */ - eg = entry_guard_parse_from_state("nickname=Fred"); + eg = entry_guard_parse_from_state("in=default nickname=Fred"); tt_assert(! eg); /* Bad RSA ID: bad character. */ eg = entry_guard_parse_from_state( + "in=default " "rsa_id=596f75206d6179206e656564206120686f62627q"); tt_assert(! eg); /* Bad RSA ID: too long.*/ eg = entry_guard_parse_from_state( + "in=default " "rsa_id=596f75206d6179206e656564206120686f6262703"); tt_assert(! eg); /* Bad RSA ID: too short.*/ eg = entry_guard_parse_from_state( + "in=default " "rsa_id=596f75206d6179206e65656420612"); tt_assert(! eg); @@ -1182,6 +1201,7 @@ test_entry_guard_parse_from_state_partial_failure(void *arg) time_t t = approx_time(); eg = entry_guard_parse_from_state( + "in=default " "rsa_id=706C75727079666C75727079736C75727079646F " "nickname=FredIsANodeWithAStrangeNicknameThatIsTooLong " "sampled_on=2016-11-14T00:00:99 " @@ -1219,7 +1239,7 @@ static void test_entry_guard_add_single_guard(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); /* 1: Add a single guard to the sample. */ node_t *n1 = smartlist_get(big_fake_net_nodes, 0); @@ -1259,7 +1279,7 @@ static void test_entry_guard_node_filter(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); bridge_line_t *bl = NULL; /* Initialize a bunch of node objects that are all guards. */ @@ -1334,7 +1354,7 @@ static void test_entry_guard_expand_sample(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); digestmap_t *node_by_id = digestmap_new(); entry_guard_t *guard = entry_guards_expand_sample(gs); @@ -1428,7 +1448,7 @@ static void test_entry_guard_expand_sample_small_net(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); /* Fun corner case: not enough guards to make up our whole sample size. */ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, { @@ -1461,7 +1481,7 @@ test_entry_guard_update_from_consensus_status(void *arg) (void)arg; int i; time_t start = approx_time(); - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); networkstatus_t *ns_tmp = NULL; /* Don't randomly backdate stuff; it will make correctness harder to check.*/ @@ -1566,7 +1586,7 @@ test_entry_guard_update_from_consensus_repair(void *arg) (void)arg; int i; time_t start = approx_time(); - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); /* Don't randomly backdate stuff; it will make correctness harder to check.*/ MOCK(randomize_time, mock_randomize_time_no_randomization); @@ -1629,7 +1649,7 @@ test_entry_guard_update_from_consensus_remove(void *arg) (void)arg; //int i; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); smartlist_t *keep_ids = smartlist_new(); smartlist_t *remove_ids = smartlist_new(); @@ -1727,7 +1747,7 @@ test_entry_guard_confirming_guards(void *arg) (void)arg; /* Now let's check the logic responsible for manipulating the list * of confirmed guards */ - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); MOCK(randomize_time, mock_randomize_time_no_randomization); /* Create the sample. */ @@ -1797,7 +1817,7 @@ static void test_entry_guard_sample_reachable_filtered(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); entry_guards_expand_sample(gs); const int N = 10000; bitarray_t *selected = NULL; @@ -1867,7 +1887,7 @@ static void test_entry_guard_sample_reachable_filtered_empty(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); /* What if we try to sample from a set of 0? */ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, n->is_possible_guard = 0); @@ -1883,7 +1903,7 @@ static void test_entry_guard_retry_unreachable(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); entry_guards_expand_sample(gs); /* Let's say that we have two guards, and they're down. @@ -1942,7 +1962,7 @@ static void test_entry_guard_manage_primary(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); smartlist_t *prev_guards = smartlist_new(); /* If no guards are confirmed, we should pick a few reachable guards and @@ -2015,7 +2035,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) { /* Simpler cases: no gaurds are confirmed yet. */ (void)arg; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); /* simple starting configuration */ entry_guards_update_primary(gs); @@ -2099,7 +2119,7 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) guards, we use a confirmed guard. */ (void)arg; int i; - guard_selection_t *gs = guard_selection_new(); + guard_selection_t *gs = guard_selection_new("default"); const int N_CONFIRMED = 10; /* slightly more complicated simple starting configuration */ From 783fa2f58637f896d5476d907aa460cae067e51a Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 22 Nov 2016 15:12:31 -0500 Subject: [PATCH 28/80] Make pathbias fields persistent for new guards --- src/or/entrynodes.c | 173 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 133 insertions(+), 40 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 4e32154b95..f1fc055797 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -156,6 +156,8 @@ static const node_t *choose_random_entry_impl(guard_selection_t *gs, static void entry_guard_set_filtered_flags(const or_options_t *options, guard_selection_t *gs, entry_guard_t *guard); +static void pathbias_check_use_success_count(entry_guard_t *guard); +static void pathbias_check_close_success_count(entry_guard_t *guard); /** Return 0 if we should apply guardfraction information found in the * consensus. A specific consensus can be specified with the @@ -1694,6 +1696,30 @@ entry_guard_encode_for_state(entry_guard_t *guard) smartlist_add_asprintf(result, "confirmed_idx=%d", guard->confirmed_idx); } + const double EPSILON = 1.0e-6; + + /* Make a copy of the pathbias object, since we will want to update + some of them */ + guard_pathbias_t *pb = tor_memdup(&guard->pb, sizeof(*pb)); + pb->use_successes = pathbias_get_use_success_count(guard); + pb->successful_circuits_closed = pathbias_get_close_success_count(guard); + + #define PB_FIELD(field) do { \ + if (pb->field >= EPSILON) { \ + smartlist_add_asprintf(result, "pb_" #field "=%f", pb->field); \ + } \ + } while (0) + PB_FIELD(use_attempts); + PB_FIELD(use_successes); + PB_FIELD(circ_attempts); + PB_FIELD(circ_successes); + PB_FIELD(successful_circuits_closed); + PB_FIELD(collapsed_circuits); + PB_FIELD(unusable_circuits); + PB_FIELD(timeouts); + tor_free(pb); +#undef PB_FIELD + if (guard->extra_state_fields) smartlist_add_strdup(result, guard->extra_state_fields); @@ -1726,21 +1752,42 @@ entry_guard_parse_from_state(const char *s) char *confirmed_on = NULL; char *confirmed_idx = NULL; + // pathbias + char *pb_use_attempts = NULL; + char *pb_use_successes = NULL; + char *pb_circ_attempts = NULL; + char *pb_circ_successes = NULL; + char *pb_successful_circuits_closed = NULL; + char *pb_collapsed_circuits = NULL; + char *pb_unusable_circuits = NULL; + char *pb_timeouts = NULL; + /* Split up the entries. Put the ones we know about in strings and the * rest in "extra". */ { smartlist_t *entries = smartlist_new(); strmap_t *vals = strmap_new(); // Maps keyword to location - strmap_set(vals, "in", &in); - strmap_set(vals, "rsa_id", &rsa_id); - strmap_set(vals, "nickname", &nickname); - strmap_set(vals, "sampled_on", &sampled_on); - strmap_set(vals, "sampled_by", &sampled_by); - strmap_set(vals, "unlisted_since", &unlisted_since); - strmap_set(vals, "listed", &listed); - strmap_set(vals, "confirmed_on", &confirmed_on); - strmap_set(vals, "confirmed_idx", &confirmed_idx); +#define FIELD(f) \ + strmap_set(vals, #f, &f); + FIELD(in); + FIELD(rsa_id); + FIELD(nickname); + FIELD(sampled_on); + FIELD(sampled_by); + FIELD(unlisted_since); + FIELD(listed); + FIELD(confirmed_on); + FIELD(confirmed_idx); + FIELD(pb_use_attempts); + FIELD(pb_use_successes); + FIELD(pb_circ_attempts); + FIELD(pb_circ_successes); + FIELD(pb_successful_circuits_closed); + FIELD(pb_collapsed_circuits); + FIELD(pb_unusable_circuits); + FIELD(pb_timeouts); +#undef FIELD smartlist_split_string(entries, s, " ", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); @@ -1848,7 +1895,7 @@ entry_guard_parse_from_state(const char *s) int ok=1; long idx = tor_parse_long(confirmed_idx, 10, 0, INT_MAX, &ok, NULL); if (! ok) { - log_warn(LD_CIRC, "Guard has invalid confirmed_idx %s", + log_warn(LD_GUARD, "Guard has invalid confirmed_idx %s", escaped(confirmed_idx)); } else { guard->confirmed_idx = (int)idx; @@ -1863,7 +1910,34 @@ entry_guard_parse_from_state(const char *s) /* initialize non-persistent fields */ guard->is_reachable = GUARD_REACHABLE_MAYBE; - /* XXXX prop271 Update everything on this guard. */ +#define PB_FIELD(field) \ + do { \ + if (pb_ ## field) { \ + int ok = 1; \ + double r = tor_parse_double(pb_ ## field, 0.0, 1e9, &ok, NULL); \ + if (! ok) { \ + log_warn(LD_CIRC, "Guard has invalid pb_%s %s", \ + #field, pb_ ## field); \ + } else { \ + guard->pb.field = r; \ + } \ + } \ + } while (0) + PB_FIELD(use_attempts); + PB_FIELD(use_successes); + PB_FIELD(circ_attempts); + PB_FIELD(circ_successes); + PB_FIELD(successful_circuits_closed); + PB_FIELD(collapsed_circuits); + PB_FIELD(unusable_circuits); + PB_FIELD(timeouts); +#undef PB_FIELD + + pathbias_check_use_success_count(guard); + pathbias_check_close_success_count(guard); + + /* We update everything on this guard later, after we've parsed + * everything. */ goto done; @@ -1882,6 +1956,15 @@ entry_guard_parse_from_state(const char *s) tor_free(listed); tor_free(confirmed_on); tor_free(confirmed_idx); + tor_free(pb_use_attempts); + tor_free(pb_use_successes); + tor_free(pb_circ_attempts); + tor_free(pb_circ_successes); + tor_free(pb_successful_circuits_closed); + tor_free(pb_collapsed_circuits); + tor_free(pb_unusable_circuits); + tor_free(pb_timeouts); + SMARTLIST_FOREACH(extra, char *, cp, tor_free(cp)); smartlist_free(extra); @@ -1912,8 +1995,6 @@ entry_guards_update_guards_in_state(or_state_t *state) gs->dirty = 0; } SMARTLIST_FOREACH_END(gs); - /* XXXXX prop271 circuitpathbias */ - config_free_lines(state->Guard); state->Guard = lines; } @@ -1926,7 +2007,6 @@ entry_guards_update_guards_in_state(or_state_t *state) static int entry_guards_load_guards_from_state(or_state_t *state, int set) { - /* XXXXX prop271 circuitpathbias */ const config_line_t *line = state->Guard; int n_errors = 0; @@ -3282,6 +3362,42 @@ choose_random_entry_impl(guard_selection_t *gs, return node; } +static void +pathbias_check_use_success_count(entry_guard_t *node) +{ + const or_options_t *options = get_options(); + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_use_success_count(node)/node->pb.use_attempts + < pathbias_get_extreme_use_rate(options) && + pathbias_get_dropguards(options)) { + node->pb.path_bias_disabled = 1; + log_info(LD_GENERAL, + "Path use bias is too high (%f/%f); disabling node %s", + node->pb.circ_successes, node->pb.circ_attempts, + node->nickname); + } +} + +static void +pathbias_check_close_success_count(entry_guard_t *node) +{ + const or_options_t *options = get_options(); + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_close_success_count(node)/node->pb.circ_attempts + < pathbias_get_extreme_rate(options) && + pathbias_get_dropguards(options)) { + node->pb.path_bias_disabled = 1; + log_info(LD_GENERAL, + "Path bias is too high (%f/%f); disabling node %s", + node->pb.circ_successes, node->pb.circ_attempts, + node->nickname); + } +} + /** Parse state and learn about the entry guards it describes. * If set is true, and there are no errors, replace the guard * list in the provided guard selection context with what we find. @@ -3386,7 +3502,6 @@ entry_guards_parse_state_for_guard_selection( } digestmap_set(added_by, d, tor_strdup(line->value+HEX_DIGEST_LEN+1)); } else if (!strcasecmp(line->key, "EntryGuardPathUseBias")) { - const or_options_t *options = get_options(); double use_cnt, success_cnt; if (!node) { @@ -3423,20 +3538,9 @@ entry_guards_parse_state_for_guard_selection( log_info(LD_GENERAL, "Read %f/%f path use bias for node %s", node->pb.use_successes, node->pb.use_attempts, node->nickname); - /* Note: We rely on the < comparison here to allow us to set a 0 - * rate and disable the feature entirely. If refactoring, don't - * change to <= */ - if (pathbias_get_use_success_count(node)/node->pb.use_attempts - < pathbias_get_extreme_use_rate(options) && - pathbias_get_dropguards(options)) { - node->pb.path_bias_disabled = 1; - log_info(LD_GENERAL, - "Path use bias is too high (%f/%f); disabling node %s", - node->pb.circ_successes, node->pb.circ_attempts, - node->nickname); - } + pathbias_check_use_success_count(node); + } else if (!strcasecmp(line->key, "EntryGuardPathBias")) { - const or_options_t *options = get_options(); double hop_cnt, success_cnt, timeouts, collapsed, successful_closed, unusable; @@ -3494,19 +3598,8 @@ entry_guards_parse_state_for_guard_selection( log_info(LD_GENERAL, "Read %f/%f path bias for node %s", node->pb.circ_successes, node->pb.circ_attempts, node->nickname); - /* Note: We rely on the < comparison here to allow us to set a 0 - * rate and disable the feature entirely. If refactoring, don't - * change to <= */ - if (pathbias_get_close_success_count(node)/node->pb.circ_attempts - < pathbias_get_extreme_rate(options) && - pathbias_get_dropguards(options)) { - node->pb.path_bias_disabled = 1; - log_info(LD_GENERAL, - "Path bias is too high (%f/%f); disabling node %s", - node->pb.circ_successes, node->pb.circ_attempts, - node->nickname); - } + pathbias_check_close_success_count(node); } else { log_warn(LD_BUG, "Unexpected key %s", line->key); } From d98b9b6d65946e14ee325327d5beac1a60ace6cc Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 22 Nov 2016 15:30:12 -0500 Subject: [PATCH 29/80] Fix pathbias interactions with entry guards entry_guard_get_by_id_digest() was always returning NULL, which was causing "adventure" and "fun" --- src/or/entrynodes.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index f1fc055797..81751f565c 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -2351,6 +2351,11 @@ entry_guard_get_by_id_digest_for_guard_selection(guard_selection_t *gs, { tor_assert(gs != NULL); + SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, entry, + if (tor_memeq(digest, entry->identity, DIGEST_LEN)) + return entry; + ); + SMARTLIST_FOREACH(gs->chosen_entry_guards, entry_guard_t *, entry, if (tor_memeq(digest, entry->identity, DIGEST_LEN)) return entry; From 8edd3d2b6c43bd5eb64d79177e6e4c44ca4fc618 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 08:02:48 -0500 Subject: [PATCH 30/80] Don't call into the new guard algorithm when the old one is enabled. (I'm surprised that these are the only bugs I ran into when I tested running with the old algorithm again!) --- src/or/circuitbuild.c | 5 ++++- src/or/circuituse.c | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 2f4ce7a727..16b53f6e21 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -965,7 +965,10 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) if (!hop) { /* done building the circuit. whew. */ int r; - if (! circ->guard_state) { + if (get_options()->UseDeprecatedGuardAlgorithm) { + // The circuit is usable; we already marked the guard as okay. + r = 1; + } else if (! circ->guard_state) { if (circuit_get_cpath_len(circ) != 1) { log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " "guard state", diff --git a/src/or/circuituse.c b/src/or/circuituse.c index d2a7f20aa2..b9f94fb3a2 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1633,7 +1633,8 @@ circuit_build_failed(origin_circuit_t *circ) "Our circuit died before the first hop with no connection"); } if (n_chan_id && !already_marked) { - entry_guard_failed(get_guard_selection_info(), &circ->guard_state); + if (circ->guard_state) + entry_guard_failed(get_guard_selection_info(), &circ->guard_state); /* XXXX prop271 -- old API */ entry_guard_register_connect_status(n_chan_id, 0, 1, time(NULL)); /* if there are any one-hop streams waiting on this circuit, fail From 897626953b15ac216d27b3814804524caa9fdd1c Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 09:09:30 -0500 Subject: [PATCH 31/80] Rebuild the guard lists as appropriate on torrc change. (Also, prepare to tie guard changes into the mark-all-old-circuits logic.) --- src/or/config.c | 43 +++++++++++++++++++++++++++++++++++++++++++ src/or/entrynodes.c | 17 ++++++++++++----- src/or/entrynodes.h | 4 ++-- src/or/main.c | 5 ++++- 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index f77f4d1879..b7b5cff35a 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1561,6 +1561,36 @@ options_transition_requires_fresh_tls_context(const or_options_t *old_options, return 0; } +/** + * Return true if changing the configuration from old to new + * affects the guard susbsystem. + */ +static int +options_transition_affects_guards(const or_options_t *old, + const or_options_t *new) +{ + /* NOTE: Make sure this function stays in sync with + * entry_guards_set_filtered_flags */ + + tor_assert(old); + tor_assert(new); + + return + (old->UseEntryGuards != new->UseEntryGuards || + old->UseDeprecatedGuardAlgorithm != new->UseDeprecatedGuardAlgorithm || + old->UseBridges != new->UseBridges || + old->UseEntryGuards != new->UseEntryGuards || + old->ClientUseIPv4 != new->ClientUseIPv4 || + old->ClientUseIPv6 != new->ClientUseIPv6 || + old->FascistFirewall != new->FascistFirewall || + !routerset_equal(old->ExcludeNodes, new->ExcludeNodes) || + !routerset_equal(old->EntryNodes, new->EntryNodes) || + !smartlist_strings_eq(old->FirewallPorts, new->FirewallPorts) || + !config_lines_eq(old->Bridges, new->Bridges) || + !config_lines_eq(old->ReachableORAddresses, new->ReachableORAddresses) || + !config_lines_eq(old->ReachableDirAddresses, new->ReachableDirAddresses)); +} + /** Fetch the active option list, and take actions based on it. All of the * things we do should survive being done repeatedly. If present, * old_options contains the previous value of the options. @@ -1580,6 +1610,8 @@ options_act(const or_options_t *old_options) const int transition_affects_workers = old_options && options_transition_affects_workers(old_options, options); int old_ewma_enabled; + const int transition_affects_guards = + old_options && options_transition_affects_guards(old_options, options); /* disable ptrace and later, other basic debugging techniques */ { @@ -1875,6 +1907,7 @@ options_act(const or_options_t *old_options) if (old_options) { int revise_trackexithosts = 0; int revise_automap_entries = 0; + int abandon_circuits = 0; if ((options->UseEntryGuards && !old_options->UseEntryGuards) || options->UseBridges != old_options->UseBridges || (options->UseBridges && @@ -1891,6 +1924,16 @@ options_act(const or_options_t *old_options) "Changed to using entry guards or bridges, or changed " "preferred or excluded node lists. " "Abandoning previous circuits."); + abandon_circuits = 1; + } + + if (transition_affects_guards) { + if (guards_update_all()) { + abandon_circuits = 1; + } + } + + if (abandon_circuits) { circuit_mark_all_unused_circs(); circuit_mark_all_dirty_circs_as_unusable(); revise_trackexithosts = 1; diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 81751f565c..9a753e6d25 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -91,7 +91,7 @@ * * [x] Whenever we get a new consensus, call update_from_consensus(). (LATER.) * - * [ ] Whenever the configuration changes in a relevant way, update the + * [x] Whenever the configuration changes in a relevant way, update the * filtered/usable flags. (LATER.) * * [x] Whenever we add a guard to the sample, make sure its filtered/usable @@ -696,6 +696,9 @@ static int node_passes_guard_filter(const or_options_t *options, guard_selection_t *gs, const node_t *node) { + /* NOTE: Make sure that this function stays in sync with + * options_transition_affects_entry_guards */ + (void)gs; if (routerset_contains_node(options->ExcludeNodes, node)) return 0; @@ -1636,14 +1639,16 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, /** * Update all derived pieces of the guard selection state in gs. + * Return true iff we should stop using all previously generated circuits. */ -void +int entry_guards_update_all(guard_selection_t *gs) { sampled_guards_update_from_consensus(gs); entry_guards_update_filtered_sets(gs); entry_guards_update_confirmed(gs); entry_guards_update_primary(gs); + return 0; } /** @@ -4020,14 +4025,16 @@ entries_retry_all(const or_options_t *options) } /** Helper: Update the status of all entry guards, in whatever algorithm - is used. */ -void + * is used. Return true if we should stop using all previously generated + * circuits. */ +int guards_update_all(void) { if (get_options()->UseDeprecatedGuardAlgorithm) { entry_guards_compute_status(get_options(), approx_time()); + return 0; } else { - entry_guards_update_all(get_guard_selection_info()); + return entry_guards_update_all(get_guard_selection_info()); } } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index d8468eb287..4cbfbf55bf 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -294,7 +294,7 @@ struct circuit_guard_state_t { #endif /* Common entry points for old and new guard code */ -void guards_update_all(void); +int guards_update_all(void); const node_t *guards_choose_guard(cpath_build_state_t *state, circuit_guard_state_t **guard_state_out); const node_t *guards_choose_dirguard(dirinfo_type_t info, @@ -336,7 +336,7 @@ void entry_guard_cancel(guard_selection_t *gs, circuit_guard_state_t **guard_state_p); void entry_guard_chan_failed(guard_selection_t *gs, channel_t *chan); -void entry_guards_update_all(guard_selection_t *gs); +int entry_guards_update_all(guard_selection_t *gs); int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, const smartlist_t *all_circuits, smartlist_t *newly_complete_out); diff --git a/src/or/main.c b/src/or/main.c index 65d1c1fd79..16106612a9 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -979,7 +979,10 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs) /* if we have enough dir info, then update our guard status with * whatever we just learned. */ - guards_update_all(); + int invalidate_circs = guards_update_all(); + // This shouldn't be able to occur at this point. + tor_assert_nonfatal(! invalidate_circs); + /* Don't even bother trying to get extrainfo until the rest of our * directory info is up-to-date */ if (options->DownloadExtraInfo) From f71be7434074a1b7f8508b96cbf55cee44afb993 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 09:15:51 -0500 Subject: [PATCH 32/80] When freeing a guard state, cancel it if its state is unknown We don't want a guard to stay "pending" forever if the circuit_guard_state_t for it is freed before it succeeds or fails. --- src/or/circuitlist.c | 5 +++++ src/or/connection.c | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 2a03f8a0d9..9d7a5d7f0e 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -885,6 +885,11 @@ circuit_free(circuit_t *circ) cpath_ref_decref(ocirc->build_state->service_pending_final_cpath_ref); } tor_free(ocirc->build_state); + + /* Cancel before freeing, if we haven't already succeeded or failed. */ + if (ocirc->guard_state) { + entry_guard_cancel(get_guard_selection_info(), ô->guard_state); + } circuit_guard_state_free(ocirc->guard_state); circuit_clear_cpath(ocirc); diff --git a/src/or/connection.c b/src/or/connection.c index c2a7a87d41..25c75ff101 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -634,6 +634,10 @@ connection_free_(connection_t *conn) cached_dir_decref(dir_conn->cached_dir); rend_data_free(dir_conn->rend_data); + if (dir_conn->guard_state) { + /* Cancel before freeing, if it's still there. */ + entry_guard_cancel(get_guard_selection_info(), &dir_conn->guard_state); + } circuit_guard_state_free(dir_conn->guard_state); } From ac67819396ac9e96c3dd65a5b5b23715e11eeec5 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 10:04:23 -0500 Subject: [PATCH 33/80] Make sure primary-guards are up-to-date when we inspect them. (Plus some magic to prevent and detect recursive invocation of entry_guards_update_primary(), since that can cause some pretty tricky misbehavior.) --- src/or/entrynodes.c | 58 +++++++++++++++++++++++++++----------- src/or/entrynodes.h | 8 ++++++ src/test/test_entrynodes.c | 3 +- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 9a753e6d25..bd30078540 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -78,9 +78,6 @@ * eventually upgrade it. **/ /* DOCDOC -- expand this. - * - * XXXX prop271 -- make sure we check all of these properties everywhere we - * should. * * Information invariants: * @@ -100,11 +97,11 @@ * [x] Whenever we remove a guard from the sample, remove it from the primary * and confirmed lists. * - * [ ] When we make a guard confirmed, update the primary list. + * [x] When we make a guard confirmed, update the primary list. * - * [ ] When we make a guard filtered or unfiltered, update the primary list. + * [x] When we make a guard filtered or unfiltered, update the primary list. * - * [ ] When we are about to pick a guard, make sure that the primary list is + * [x] When we are about to pick a guard, make sure that the primary list is * full. * * [x] Before calling sample_reachable_filtered_entry_guards(), make sure @@ -682,9 +679,12 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) } SMARTLIST_FOREACH_END(guard); if (n_changes) { - /* Regnerate other things. XXXXXX prop271 */ - // XXXX prop271 rebuild confirmed list. + gs->primary_guards_up_to_date = 0; entry_guards_update_filtered_sets(gs); + /* We don't need to rebuild the confirmed list right here -- we may have + * removed confirmed guards above, but we can't have added any new + * confirmed guards. + */ entry_guards_changed_for_guard_selection(gs); } } @@ -749,6 +749,7 @@ entry_guard_set_filtered_flags(const or_options_t *options, guard_selection_t *gs, entry_guard_t *guard) { + unsigned was_filtered = guard->is_filtered_guard; guard->is_filtered_guard = 0; guard->is_usable_filtered_guard = 0; @@ -763,6 +764,11 @@ entry_guard_set_filtered_flags(const or_options_t *options, log_debug(LD_GUARD, "Updated sampled guard %s: filtered=%d; " "reachable_filtered=%d.", entry_guard_describe(guard), guard->is_filtered_guard, guard->is_usable_filtered_guard); + + if (!bool_eq(was_filtered, guard->is_filtered_guard)) { + /* This guard might now be primary or nonprimary. */ + gs->primary_guards_up_to_date = 0; + } } /** @@ -795,6 +801,7 @@ sample_reachable_filtered_entry_guards(guard_selection_t *gs, const unsigned exclude_confirmed = flags & SAMPLE_EXCLUDE_CONFIRMED; const unsigned exclude_primary = flags & SAMPLE_EXCLUDE_PRIMARY; const unsigned exclude_pending = flags & SAMPLE_EXCLUDE_PENDING; + const unsigned no_update_primary = flags & SAMPLE_NO_UPDATE_PRIMARY; SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { entry_guard_consider_retry(guard); @@ -810,6 +817,9 @@ sample_reachable_filtered_entry_guards(guard_selection_t *gs, entry_guards_expand_sample(gs); } + if (exclude_primary && !gs->primary_guards_up_to_date && !no_update_primary) + entry_guards_update_primary(gs); + /* Build the set of reachable filtered guards. */ smartlist_t *reachable_filtered_sample = smartlist_new(); SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { @@ -908,24 +918,34 @@ make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard) guard->confirmed_idx = gs->next_confirmed_idx++; smartlist_add(gs->confirmed_entry_guards, guard); + // This confirmed guard might kick something else out of the primary + // guards. + gs->primary_guards_up_to_date = 0; + entry_guards_changed_for_guard_selection(gs); } /** * Recalculate the list of primary guards (the ones we'd prefer to use) from * the filtered sample and the confirmed list. - * - * XXXXX prop271 are calling this enough ??? */ STATIC void entry_guards_update_primary(guard_selection_t *gs) { tor_assert(gs); + // prevent recursion. Recursion is potentially very bad here. + static int running = 0; + tor_assert(!running); + running = 1; + smartlist_t *new_primary_guards = smartlist_new(); smartlist_t *old_primary_guards = smartlist_new(); smartlist_add_all(old_primary_guards, gs->primary_entry_guards); + /* Set this flag now, to prevent the calls below from recursing. */ + gs->primary_guards_up_to_date = 1; + /* First, can we fill it up with confirmed guards? */ SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) { if (smartlist_len(new_primary_guards) >= N_PRIMARY_GUARDS) @@ -964,7 +984,8 @@ entry_guards_update_primary(guard_selection_t *gs) while (smartlist_len(new_primary_guards) < N_PRIMARY_GUARDS) { entry_guard_t *guard = sample_reachable_filtered_entry_guards(gs, SAMPLE_EXCLUDE_CONFIRMED| - SAMPLE_EXCLUDE_PRIMARY); + SAMPLE_EXCLUDE_PRIMARY| + SAMPLE_NO_UPDATE_PRIMARY); if (!guard) break; guard->is_primary = 1; @@ -1007,6 +1028,8 @@ entry_guards_update_primary(guard_selection_t *gs) smartlist_free(old_primary_guards); smartlist_free(gs->primary_entry_guards); gs->primary_entry_guards = new_primary_guards; + gs->primary_guards_up_to_date = 1; + running = 0; } /** @@ -1104,6 +1127,9 @@ select_entry_guard_for_circuit(guard_selection_t *gs, unsigned *state_out) tor_assert(gs); tor_assert(state_out); + if (!gs->primary_guards_up_to_date) + entry_guards_update_primary(gs); + /* "If any entry in PRIMARY_GUARDS has {is_reachable} status of or , return the first such guard." */ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) { @@ -1191,6 +1217,9 @@ entry_guards_note_guard_failure(guard_selection_t *gs, STATIC void mark_primary_guards_maybe_reachable(guard_selection_t *gs) { + if (!gs->primary_guards_up_to_date) + entry_guards_update_primary(gs); + SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) { if (guard->is_reachable != GUARD_REACHABLE_NO) continue; @@ -1230,7 +1259,6 @@ entry_guards_note_guard_success(guard_selection_t *gs, guard->is_usable_filtered_guard = 1; if (guard->confirmed_idx < 0) { - // XXXX prop271 XXXX update primary guards, since we confirmed something? make_guard_confirmed(gs, guard); } @@ -1248,9 +1276,6 @@ entry_guards_note_guard_success(guard_selection_t *gs, } } - // XXXX prop271 XXXX update primary guards, since we confirmed something? - // XXXX prop261 XXXX if so, here or above? - log_info(LD_GUARD, "Recorded success for %s%sguard %s", guard->is_primary?"primary ":"", guard->confirmed_idx>=0?"confirmed ":"", @@ -1468,8 +1493,9 @@ entry_guard_chan_failed(guard_selection_t *gs, static int entry_guards_all_primary_guards_are_down(guard_selection_t *gs) { - /* XXXXX prop271 do we have to call entry_guards_update_primary() ?? */ tor_assert(gs); + if (!gs->primary_guards_up_to_date) + entry_guards_update_primary(gs); SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) { entry_guard_consider_retry(guard); if (guard->is_reachable != GUARD_REACHABLE_NO) diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 4cbfbf55bf..a514c13a8e 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -222,6 +222,13 @@ struct guard_selection_s { */ int dirty; + /** + * A value of 1 means that primary_entry_guards is up-to-date; 0 + * means we need to recalculate it before using primary_entry_guards + * or the is_primary flag on any guard. + */ + int primary_guards_up_to_date; + /** * A list of the sampled entry guards, as entry_guard_t structures. * Not in any particular order. When we 'sample' a guard, we are @@ -428,6 +435,7 @@ STATIC void entry_guards_update_filtered_sets(guard_selection_t *gs); #define SAMPLE_EXCLUDE_CONFIRMED (1u<<0) #define SAMPLE_EXCLUDE_PRIMARY (1u<<1) #define SAMPLE_EXCLUDE_PENDING (1u<<2) +#define SAMPLE_NO_UPDATE_PRIMARY (1u<<3) /**@}*/ STATIC entry_guard_t *sample_reachable_filtered_entry_guards( guard_selection_t *gs, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index d8b9ccb7e6..cdf8672f11 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1839,6 +1839,7 @@ test_entry_guard_sample_reachable_filtered(void *arg) g->pb.path_bias_disabled = 1; entry_guards_update_filtered_sets(gs); + gs->primary_guards_up_to_date = 1; tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, n_guards - 1); tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_guards); @@ -1851,7 +1852,7 @@ test_entry_guard_sample_reachable_filtered(void *arg) } tests[] = { { 0, -1 }, { SAMPLE_EXCLUDE_CONFIRMED, 1 }, - { SAMPLE_EXCLUDE_PRIMARY, 2 }, + { SAMPLE_EXCLUDE_PRIMARY|SAMPLE_NO_UPDATE_PRIMARY, 2 }, { SAMPLE_EXCLUDE_PENDING, 0 }, { -1, -1}, }; From 526b0e2ce2c5d31c70eb3e48eda59b34e9eb681d Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 13:05:22 -0500 Subject: [PATCH 34/80] Avoid division-by-zero in pathbias_check_*_success_count --- src/or/entrynodes.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index bd30078540..860be9b56c 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -3402,10 +3402,13 @@ static void pathbias_check_use_success_count(entry_guard_t *node) { const or_options_t *options = get_options(); + const double EPSILON = 1.0e-9; + /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_use_success_count(node)/node->pb.use_attempts + if (node->pb.use_attempts > EPSILON && + pathbias_get_use_success_count(node)/node->pb.use_attempts < pathbias_get_extreme_use_rate(options) && pathbias_get_dropguards(options)) { node->pb.path_bias_disabled = 1; @@ -3420,10 +3423,13 @@ static void pathbias_check_close_success_count(entry_guard_t *node) { const or_options_t *options = get_options(); + const double EPSILON = 1.0e-9; + /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_close_success_count(node)/node->pb.circ_attempts + if (node->pb.circ_attempts > EPSILON && + pathbias_get_close_success_count(node)/node->pb.circ_attempts < pathbias_get_extreme_rate(options) && pathbias_get_dropguards(options)) { node->pb.path_bias_disabled = 1; From a7bc73935b030100b0d7b9f39c5dec5ef6eb0a85 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 15:08:07 -0500 Subject: [PATCH 35/80] Test get_guard_selection_by_name --- src/or/entrynodes.c | 2 +- src/or/entrynodes.h | 2 ++ src/test/test_entrynodes.c | 48 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 860be9b56c..cf35b02ce0 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -201,7 +201,7 @@ guard_selection_new(const char *name) * create_if_absent is true, then create and return it. If there * is none, and create_if_absent is false, then return NULL. */ -static guard_selection_t * +STATIC guard_selection_t * get_guard_selection_by_name(const char *name, int create_if_absent) { if (!guard_contexts) { diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index a514c13a8e..285664da18 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -416,6 +416,8 @@ int num_bridges_usable(void); // ---------- XXXX these functions and definitions are post-prop271. HANDLE_DECL(entry_guard, entry_guard_t, STATIC) STATIC guard_selection_t *guard_selection_new(const char *name); +STATIC guard_selection_t *get_guard_selection_by_name( + const char *name, int create_if_absent); STATIC void guard_selection_free(guard_selection_t *gs); STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs, const uint8_t *rsa_id); diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index cdf8672f11..785503bd26 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1235,6 +1235,52 @@ test_entry_guard_parse_from_state_partial_failure(void *arg) tor_free(mem_op_hex_tmp); } +static void +test_entry_guard_get_guard_selection_by_name(void *arg) +{ + (void)arg; + guard_selection_t *gs1, *gs2, *gs3; + + gs1 = get_guard_selection_by_name("unlikely", 0); + tt_assert(gs1 == NULL); + gs1 = get_guard_selection_by_name("unlikely", 1); + tt_assert(gs1 != NULL); + gs2 = get_guard_selection_by_name("unlikely", 1); + tt_assert(gs2 == gs1); + gs2 = get_guard_selection_by_name("unlikely", 0); + tt_assert(gs2 == gs1); + + gs2 = get_guard_selection_by_name("implausible", 0); + tt_assert(gs2 == NULL); + gs2 = get_guard_selection_by_name("implausible", 1); + tt_assert(gs2 != NULL); + tt_assert(gs2 != gs1); + gs3 = get_guard_selection_by_name("implausible", 0); + tt_assert(gs3 == gs2); + + gs3 = get_guard_selection_by_name("default", 0); + tt_assert(gs3 == NULL); + gs3 = get_guard_selection_by_name("default", 1); + tt_assert(gs3 != NULL); + tt_assert(gs3 != gs2); + tt_assert(gs3 != gs1); + tt_assert(gs3 == get_guard_selection_info()); + +#if 0 + or_options_t *options = get_options_mutable(); + options->UseDeprecatedGuardAlgorithm = 1; + gs4 = get_guard_selection_info(); + tt_assert(gs4 != gs3); + tt_assert(gs4 == get_guard_selection_by_name("legacy", 1)); + + options->UseDeprecatedGuardAlgorithm = 0; + tt_assert(gs3 == get_guard_selection_info()); +#endif + + done: + entry_guards_free_all(); +} + static void test_entry_guard_add_single_guard(void *arg) { @@ -2245,6 +2291,8 @@ struct testcase_t entrynodes_tests[] = { test_entry_guard_parse_from_state_failure, 0, NULL, NULL }, { "parse_from_state_partial_failure", test_entry_guard_parse_from_state_partial_failure, 0, NULL, NULL }, + { "get_guard_selection_by_name", + test_entry_guard_get_guard_selection_by_name, TT_FORK, NULL, NULL }, BFN_TEST(add_single_guard), BFN_TEST(node_filter), BFN_TEST(expand_sample), From 9cad2628dd8c22d41f0e3c47bcd4c926e733f4c3 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 15:32:48 -0500 Subject: [PATCH 36/80] Test no-consensus case for filter. --- src/test/test_entrynodes.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 785503bd26..de36142d9a 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1391,6 +1391,20 @@ test_entry_guard_node_filter(void *arg) } tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, 1); + /* Now make sure we have no live consensus, and no nodes. Nothing should + * pass the filter any more. */ + tor_free(dummy_consensus); + dummy_consensus = NULL; + SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, node, { + memset(node->identity, 0xff, 20); + }); + entry_guards_update_filtered_sets(gs); + for (i = 0; i < NUM; ++i) { + tt_assert(g[i]->is_filtered_guard == 0); + tt_assert(g[i]->is_usable_filtered_guard == 0); + } + tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, 0); + done: guard_selection_free(gs); tor_free(bl); From bce0f79252e12a791c50e9b11ceb5867eeb07559 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 15:33:02 -0500 Subject: [PATCH 37/80] Mark some more BUG lines as unreachable. --- src/or/entrynodes.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index cf35b02ce0..1501bf7f3e 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -539,7 +539,9 @@ remove_guard_from_confirmed_and_primary_lists(guard_selection_t *gs, found_guard = smartlist_get(gs->confirmed_entry_guards, guard->confirmed_idx); if (BUG(guard != found_guard)) { + // LCOV_EXCL_START smartlist_remove_keeporder(gs->confirmed_entry_guards, guard); + // LCOV_EXCL_STOP } else { smartlist_del_keeporder(gs->confirmed_entry_guards, guard->confirmed_idx); @@ -548,7 +550,9 @@ remove_guard_from_confirmed_and_primary_lists(guard_selection_t *gs, guard->confirmed_on_date = 0; } else { if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard))) { + // LCOV_EXCL_START smartlist_remove_keeporder(gs->confirmed_entry_guards, guard); + // LCOV_EXCL_STOP } } } @@ -903,10 +907,10 @@ STATIC void make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard) { if (BUG(guard->confirmed_on_date && guard->confirmed_idx >= 0)) - return; + return; // LCOV_EXCL_LINE if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard))) - return; + return; // LCOV_EXCL_LINE const int GUARD_LIFETIME = GUARD_LIFETIME_DAYS * 86400; guard->confirmed_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10); From e56bc1e5de05d9bc6876d91d364d33f0771eb322 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 23 Nov 2016 15:48:10 -0500 Subject: [PATCH 38/80] Move the 'dirty' flag for the guards to a global again It makes more sense to have a single dirty flag, since we always regenerate the whole state file when we save it. --- src/or/entrynodes.c | 28 ++++++++++++++++------------ src/or/entrynodes.h | 10 ---------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 1501bf7f3e..951ce15f85 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -145,6 +145,10 @@ static smartlist_t *guard_contexts = NULL; static guard_selection_t *curr_guard_context = NULL; +/** A value of 1 means that at least one context has changed, + * and those changes need to be flushed to disk. */ +static int entry_guards_dirty = 0; + static const node_t *choose_random_entry_impl(guard_selection_t *gs, cpath_build_state_t *state, int for_directory, @@ -2027,7 +2031,6 @@ entry_guards_update_guards_in_state(or_state_t *state) (*nextline)->value = entry_guard_encode_for_state(guard); nextline = &(*nextline)->next; } SMARTLIST_FOREACH_END(guard); - gs->dirty = 0; } SMARTLIST_FOREACH_END(gs); config_free_lines(state->Guard); @@ -3685,11 +3688,11 @@ entry_guards_parse_state_for_guard_selection( smartlist_free(gs->chosen_entry_guards); } gs->chosen_entry_guards = new_entry_guards; - gs->dirty = 0; + /* XXX hand new_entry_guards to this func, and move it up a * few lines, so we don't have to re-dirty it */ if (remove_obsolete_entry_guards(gs, now)) - gs->dirty = 1; + entry_guards_dirty = 1; } digestmap_free(added_by, tor_free_); return *msg ? -1 : 0; @@ -3704,12 +3707,16 @@ entry_guards_parse_state_for_guard_selection( int entry_guards_parse_state(or_state_t *state, int set, char **msg) { + entry_guards_dirty = 0; + int r1 = entry_guards_load_guards_from_state(state, set); int r2 = entry_guards_parse_state_for_guard_selection( get_guard_selection_by_name("legacy", 1), state, set, msg); + entry_guards_dirty = 0; + if (r1 < 0 || r2 < 0) { if (msg && *msg == NULL) { *msg = tor_strdup("parsing error"); //xxxx prop271 should we try harder? @@ -3737,7 +3744,7 @@ entry_guards_changed_for_guard_selection(guard_selection_t *gs) tor_assert(gs != NULL); - gs->dirty = 1; + entry_guards_dirty = 1; if (get_options()->AvoidDiskWrites) when = time(NULL) + SLOW_GUARD_STATE_FLUSH_TIME; @@ -3764,26 +3771,23 @@ entry_guards_changed(void) * Otherwise, free the EntryGuards piece of state and create * a new one out of the global entry_guards list, and then mark * state dirty so it will get saved to disk. - * - * XXX this should get totally redesigned around storing multiple - * entry guard contexts. For the initial refactor we'll just - * always use the current default. Fix it as soon as we actually - * have any way that default can change. */ void entry_guards_update_state(or_state_t *state) { config_line_t **next, *line; + entry_guards_dirty = 0; + // Handles all non-legacy guard info. entry_guards_update_guards_in_state(state); + entry_guards_dirty = 0; + guard_selection_t *gs = get_guard_selection_by_name("legacy", 0); if (!gs) return; // nothign to save. tor_assert(gs->chosen_entry_guards != NULL); - if (!gs->dirty) - return; config_free_lines(state->EntryGuards); next = &state->EntryGuards; @@ -3854,7 +3858,7 @@ entry_guards_update_state(or_state_t *state) } SMARTLIST_FOREACH_END(e); if (!get_options()->AvoidDiskWrites) or_state_mark_dirty(get_or_state(), 0); - gs->dirty = 0; + entry_guards_dirty = 0; } /** If question is the string "entry-guards", then dump diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 285664da18..ec24011377 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -212,16 +212,6 @@ struct guard_selection_s { */ char *name; - /** - * A value of 1 means that guard_selection_t structures have changed - * and those changes need to be flushed to disk. - * - * XXX prop271 we don't know how to flush multiple guard contexts to - * disk yet; fix that as soon as any way to change the default exists, - * or at least make sure this gets set on change. - */ - int dirty; - /** * A value of 1 means that primary_entry_guards is up-to-date; 0 * means we need to recalculate it before using primary_entry_guards From 2ea5aa71823f385e36f20e643a20996dcb164464 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 25 Nov 2016 12:53:00 -0500 Subject: [PATCH 39/80] Expire circuits that have been WAITING_FOR_BETTER_GUARD too long (This is required by 3.9 in prop271, but is better done as a separate function IMO) --- src/or/circuitlist.c | 12 +++++++++++- src/or/circuitlist.h | 1 + src/or/circuituse.c | 19 +++++++++++++++++++ src/or/circuituse.h | 1 + src/or/entrynodes.c | 18 +++++++++++++++--- src/or/entrynodes.h | 1 + src/or/main.c | 1 + 7 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 9d7a5d7f0e..0afe2f8059 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -553,7 +553,7 @@ circuit_close_all_marked(void) smartlist_clear(circuits_pending_close); } -/** Return the head of the global linked list of circuits. */ +/** Return a pointer to the global list of circuits. */ MOCK_IMPL(smartlist_t *, circuit_get_global_list,(void)) { @@ -562,6 +562,16 @@ circuit_get_global_list,(void)) return global_circuitlist; } +/** */ +/** Return a pointer to the global list of origin circuits. */ +smartlist_t * +circuit_get_global_origin_circuit_list(void) +{ + if (NULL == global_origin_circuit_list) + global_origin_circuit_list = smartlist_new(); + return global_circuitlist; +} + /** Function to make circ-\>state human-readable */ const char * circuit_state_to_string(int state) diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 73039cc06e..e2102a118b 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -15,6 +15,7 @@ #include "testsupport.h" MOCK_DECL(smartlist_t *, circuit_get_global_list, (void)); +smartlist_t *circuit_get_global_origin_circuit_list(void); const char *circuit_state_to_string(int state); const char *circuit_purpose_to_controller_string(uint8_t purpose); const char *circuit_purpose_to_controller_hs_state_string(uint8_t purpose); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index b9f94fb3a2..b925729e01 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -800,6 +800,25 @@ circuit_expire_building(void) } SMARTLIST_FOREACH_END(victim); } +/** + * Mark for close all circuits that start here, that were built through a + * guard we weren't sure if we wanted to use, and that have been waiting + * around for way too long. + */ +void +circuit_expire_waiting_for_better_guard(void) +{ + SMARTLIST_FOREACH_BEGIN(circuit_get_global_origin_circuit_list(), + origin_circuit_t *, circ) { + if (TO_CIRCUIT(circ)->marked_for_close) + continue; + if (circ->guard_state == NULL) + continue; + if (entry_guard_state_should_expire(circ->guard_state)) + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NONE); + } SMARTLIST_FOREACH_END(circ); +} + /** For debugging #8387: track when we last called * circuit_expire_old_circuits_clientside. */ static time_t last_expired_clientside_circuits = 0; diff --git a/src/or/circuituse.h b/src/or/circuituse.h index 5973978c45..110bdda5b2 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -13,6 +13,7 @@ #define TOR_CIRCUITUSE_H void circuit_expire_building(void); +void circuit_expire_waiting_for_better_guard(void); void circuit_remove_handled_ports(smartlist_t *needed_ports); int circuit_stream_is_being_handled(entry_connection_t *conn, uint16_t port, int min); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 951ce15f85..1c9349ee03 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1605,9 +1605,6 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, "circuit had higher priority, so not upgrading.", n_complete, n_waiting); - /* XXXX prop271 implement: "(Time them out after a - {NONPRIMARY_GUARD_IDLE_TIMEOUT} seconds.)" - */ return 0; } } @@ -1671,6 +1668,21 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, return 1; } +/** + * Return true iff the circuit whose state is guard_state should + * expire. + */ +int +entry_guard_state_should_expire(circuit_guard_state_t *guard_state) +{ + if (guard_state == NULL) + return 0; + const time_t expire_if_waiting_since = + approx_time() - NONPRIMARY_GUARD_IDLE_TIMEOUT; + return (guard_state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD + && guard_state->state_set_at < expire_if_waiting_since); +} + /** * Update all derived pieces of the guard selection state in gs. * Return true iff we should stop using all previously generated circuits. diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index ec24011377..648e599310 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -337,6 +337,7 @@ int entry_guards_update_all(guard_selection_t *gs); int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, const smartlist_t *all_circuits, smartlist_t *newly_complete_out); +int entry_guard_state_should_expire(circuit_guard_state_t *guard_state); void entry_guards_note_internet_connectivity(guard_selection_t *gs); /* Used by bridges.c only. */ diff --git a/src/or/main.c b/src/or/main.c index 16106612a9..96ff442c68 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1402,6 +1402,7 @@ run_scheduled_events(time_t now) /* (If our circuit build timeout can ever become lower than a second (which * it can't, currently), we should do this more often.) */ circuit_expire_building(); + circuit_expire_waiting_for_better_guard(); /* 3b. Also look at pending streams and prune the ones that 'began' * a long time ago but haven't gotten a 'connected' yet. From 039bd01767d42961cb16ff4914481332b52cf8db Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sat, 26 Nov 2016 09:22:04 -0500 Subject: [PATCH 40/80] Add a wrapper for a common networkstatus param pattern We frequently want to check a networkstatus parameter only when it isn't overridden from the torrc file. --- src/or/networkstatus.c | 19 +++++++++++++++++++ src/or/networkstatus.h | 5 +++++ src/test/test_dir.c | 9 +++++++++ 3 files changed, 33 insertions(+) diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index ec8f77fa42..ce23d67979 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -2303,6 +2303,25 @@ networkstatus_get_param(const networkstatus_t *ns, const char *param_name, default_val, min_val, max_val); } +/** + * As networkstatus_get_param(), but check torrc_value before checking the + * consensus. If torrc_value is in-range, then return it instead of the + * value from the consensus. + */ +int32_t +networkstatus_get_overridable_param(const networkstatus_t *ns, + int32_t torrc_value, + const char *param_name, + int32_t default_val, + int32_t min_val, int32_t max_val) +{ + if (torrc_value >= min_val && torrc_value <= max_val) + return torrc_value; + else + return networkstatus_get_param( + ns, param_name, default_val, min_val, max_val); +} + /** * Retrieve the consensus parameter that governs the * fixed-point precision of our network balancing 'bandwidth-weights' diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 71f36b69ed..4b3854db0c 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -111,6 +111,11 @@ int32_t networkstatus_get_param(const networkstatus_t *ns, const char *param_name, int32_t default_val, int32_t min_val, int32_t max_val); +int32_t networkstatus_get_overridable_param(const networkstatus_t *ns, + int32_t torrc_value, + const char *param_name, + int32_t default_val, + int32_t min_val, int32_t max_val); int getinfo_helper_networkstatus(control_connection_t *conn, const char *question, char **answer, const char **errmsg); diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 4501d6b547..4ef421f8e3 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -1494,6 +1494,15 @@ test_dir_param_voting(void *arg) tt_int_op(-8,OP_EQ, networkstatus_get_param(&vote4, "ab", -12, -100, -8)); tt_int_op(0,OP_EQ, networkstatus_get_param(&vote4, "foobar", 0, -100, 8)); + tt_int_op(100,OP_EQ, networkstatus_get_overridable_param( + &vote4, -1, "x-yz", 50, 0, 300)); + tt_int_op(30,OP_EQ, networkstatus_get_overridable_param( + &vote4, 30, "x-yz", 50, 0, 300)); + tt_int_op(0,OP_EQ, networkstatus_get_overridable_param( + &vote4, -101, "foobar", 0, -100, 8)); + tt_int_op(-99,OP_EQ, networkstatus_get_overridable_param( + &vote4, -99, "foobar", 0, -100, 8)); + smartlist_add(votes, &vote1); /* Do the first tests without adding all the other votes, for From d2af9826fd0a75efee8612b96709c39f24196f53 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sat, 26 Nov 2016 10:06:50 -0500 Subject: [PATCH 41/80] Turn #defines for prop271 into networkstatus params Some of these will get torrc options to override them too; this is just the mechanical conversion. Also, add documentation for a couple of undocumented (but now used) parameters. --- src/or/entrynodes.c | 144 +++++++++++++++++++++++++++++++++---- src/or/entrynodes.h | 53 ++++++++------ src/test/test_entrynodes.c | 14 ++-- 3 files changed, 170 insertions(+), 41 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 1c9349ee03..f1fe9f13c1 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -323,6 +323,118 @@ randomize_time,(time_t now, time_t max_backdate)) return crypto_rand_time_range(earliest, latest); } +/** + * @name parameters for networkstatus algorithm + * + * These parameters are taken from the consensus; some are overrideable in + * the torrc. + */ +/**@{*/ +/** + * We never let our sampled guard set grow larger than this fraction + * of the guards on the network. + */ +STATIC double +get_max_sample_threshold(void) +{ + int32_t pct = + networkstatus_get_param(NULL, "guard-max-sample-threshold-percent", + DFLT_MAX_SAMPLE_THRESHOLD_PERCENT, + 1, 100); + return pct / 100.0; +} +/** + * We always try to make our sample contain at least this many guards. + * + * XXXX prop271 There was a MIN_SAMPLE_THRESHOLD in the proposal, but I + * removed it in favor of MIN_FILTERED_SAMPLE_SIZE. -NM + */ +STATIC int +get_min_filtered_sample_size(void) +{ + return networkstatus_get_param(NULL, "guard-min-filtered-sample-size", + DFLT_MIN_FILTERED_SAMPLE_SIZE, + 1, INT32_MAX); +} +/** + * If a guard is unlisted for this many days in a row, we remove it. + */ +STATIC int +get_remove_unlisted_guards_after_days(void) +{ + return networkstatus_get_param(NULL, + "guard-remove-unlisted-guards-after-days", + DFLT_REMOVE_UNLISTED_GUARDS_AFTER_DAYS, + 1, 365*10); +} +/** + * We remove unconfirmed guards from the sample after this many days, + * regardless of whether they are listed or unlisted. + */ +STATIC int +get_guard_lifetime_days(void) +{ + return networkstatus_get_param(NULL, + "guard-lifetime-days", + DFLT_GUARD_LIFETIME_DAYS, 1, 365*10); +} +/** + * We remove confirmed guards from the sample if they were sampled + * GUARD_LIFETIME_DAYS ago and confirmed this many days ago. + */ +STATIC int +get_guard_confirmed_min_lifetime_days(void) +{ + return networkstatus_get_param(NULL, "guard-confirmed-min-lifetime-days", + DFLT_GUARD_CONFIRMED_MIN_LIFETIME_DAYS, + 1, 365*10); +} +/** + * How many guards do we try to keep on our primary guard list? + */ +STATIC int +get_n_primary_guards(void) +{ + return networkstatus_get_param(NULL, "guard-n-primary-guards", + DFLT_N_PRIMARY_GUARDS, 1, INT32_MAX); +} +/** + * If we haven't successfully built or used a circuit in this long, then + * consider that the internet is probably down. + */ +STATIC int +get_internet_likely_down_interval(void) +{ + return networkstatus_get_param(NULL, "guard-internet-likely-down-interval", + DFLT_INTERNET_LIKELY_DOWN_INTERVAL, + 1, INT32_MAX); +} +/** + * If we're trying to connect to a nonprimary guard for at least this + * many seconds, and we haven't gotten the connection to work, we will treat + * lower-priority guards as usable. + */ +STATIC int +get_nonprimary_guard_connect_timeout(void) +{ + return networkstatus_get_param(NULL, + "guard-nonprimary-guard-connect-timeout", + DFLT_NONPRIMARY_GUARD_CONNECT_TIMEOUT, + 1, INT32_MAX); +} +/** + * If a circuit has been sitting around in 'waiting for better guard' state + * for at least this long, we'll expire it. + */ +STATIC int +get_nonprimary_guard_idle_timeout(void) +{ + return networkstatus_get_param(NULL, + "guard-nonprimary-guard-idle-timeout", + (10*60), 1, INT32_MAX); +} +/**@}*/ + /** * Return true iff node has all the flags needed for us to consider it * a possible guard when sampling guards. @@ -377,7 +489,7 @@ STATIC entry_guard_t * entry_guard_add_to_sample(guard_selection_t *gs, const node_t *node) { - const int GUARD_LIFETIME = GUARD_LIFETIME_DAYS * 86400; + const int GUARD_LIFETIME = get_guard_lifetime_days() * 86400; tor_assert(gs); tor_assert(node); @@ -470,8 +582,8 @@ entry_guards_expand_sample(guard_selection_t *gs) if (! smartlist_len(eligible_guards)) goto done; - const int max_sample = (int)(n_guards * MAX_SAMPLE_THRESHOLD); - const int min_filtered_sample = MIN_FILTERED_SAMPLE_SIZE; + const int max_sample = (int)(n_guards * get_max_sample_threshold()); + const int min_filtered_sample = get_min_filtered_sample_size(); log_info(LD_GUARD, "Expanding the sample guard set. We have %d guards " "in the sample, and %d eligible guards to extend it with.", @@ -570,7 +682,7 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) { tor_assert(gs); const int REMOVE_UNLISTED_GUARDS_AFTER = - (REMOVE_UNLISTED_GUARDS_AFTER_DAYS * 86400); + (get_remove_unlisted_guards_after_days() * 86400); const int unlisted_since_slop = REMOVE_UNLISTED_GUARDS_AFTER / 5; // It's important to use only a live consensus here; we don't want to @@ -637,9 +749,9 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) const time_t remove_if_unlisted_since = approx_time() - REMOVE_UNLISTED_GUARDS_AFTER; const time_t maybe_remove_if_sampled_before = - approx_time() - (GUARD_LIFETIME_DAYS * 86400); + approx_time() - (get_guard_lifetime_days() * 86400); const time_t remove_if_confirmed_before = - approx_time() - (GUARD_CONFIRMED_MIN_LIFETIME_DAYS * 86400); + approx_time() - (get_guard_confirmed_min_lifetime_days() * 86400); /* Then: remove the ones that have been junk for too long */ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { @@ -656,7 +768,7 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) */ log_info(LD_GUARD, "Removing sampled guard %s: it has been unlisted " "for over %d days", entry_guard_describe(guard), - REMOVE_UNLISTED_GUARDS_AFTER_DAYS); + get_remove_unlisted_guards_after_days()); remove = 1; } else if (guard->sampled_on_date < maybe_remove_if_sampled_before) { /* We have a live consensus, and {ADDED_ON_DATE} is over @@ -668,13 +780,14 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) log_info(LD_GUARD, "Removing sampled guard %s: it was sampled " "over %d days ago, but never confirmed.", entry_guard_describe(guard), - GUARD_LIFETIME_DAYS); + get_guard_lifetime_days()); } else if (guard->confirmed_on_date < remove_if_confirmed_before) { remove = 1; log_info(LD_GUARD, "Removing sampled guard %s: it was sampled " "over %d days ago, and confirmed over %d days ago.", entry_guard_describe(guard), - GUARD_LIFETIME_DAYS, GUARD_CONFIRMED_MIN_LIFETIME_DAYS); + get_guard_lifetime_days(), + get_guard_confirmed_min_lifetime_days()); } } @@ -820,7 +933,8 @@ sample_reachable_filtered_entry_guards(guard_selection_t *gs, log_info(LD_GUARD, "Trying to sample a reachable guard: We know of %d " "in the USABLE_FILTERED set.", n_reachable_filtered); - if (n_reachable_filtered < MIN_FILTERED_SAMPLE_SIZE) { + const int min_filtered_sample = get_min_filtered_sample_size(); + if (n_reachable_filtered < min_filtered_sample) { log_info(LD_GUARD, " (That isn't enough. Trying to expand the sample.)"); entry_guards_expand_sample(gs); } @@ -916,7 +1030,7 @@ make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard) if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard))) return; // LCOV_EXCL_LINE - const int GUARD_LIFETIME = GUARD_LIFETIME_DAYS * 86400; + const int GUARD_LIFETIME = get_guard_lifetime_days() * 86400; guard->confirmed_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10); log_info(LD_GUARD, "Marking %s as a confirmed guard (index %d)", @@ -947,6 +1061,8 @@ entry_guards_update_primary(guard_selection_t *gs) tor_assert(!running); running = 1; + const int N_PRIMARY_GUARDS = get_n_primary_guards(); + smartlist_t *new_primary_guards = smartlist_new(); smartlist_t *old_primary_guards = smartlist_new(); smartlist_add_all(old_primary_guards, gs->primary_entry_guards); @@ -1278,7 +1394,7 @@ entry_guards_note_guard_success(guard_selection_t *gs, old_state == GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); new_state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD; - if (last_time_on_internet + INTERNET_LIKELY_DOWN_INTERVAL + if (last_time_on_internet + get_internet_likely_down_interval() < approx_time()) { mark_primary_guards_maybe_reachable(gs); } @@ -1620,7 +1736,7 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, */ int n_blockers_found = 0; const time_t state_set_at_cutoff = - approx_time() - NONPRIMARY_GUARD_CONNECT_TIMEOUT; + approx_time() - get_nonprimary_guard_connect_timeout(); SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); if (state == NULL) @@ -1678,7 +1794,7 @@ entry_guard_state_should_expire(circuit_guard_state_t *guard_state) if (guard_state == NULL) return 0; const time_t expire_if_waiting_since = - approx_time() - NONPRIMARY_GUARD_IDLE_TIMEOUT; + approx_time() - get_nonprimary_guard_idle_timeout(); return (guard_state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD && guard_state->state_set_at < expire_if_waiting_since); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 648e599310..0ed94cb418 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -347,63 +347,76 @@ int num_bridges_usable(void); #ifdef ENTRYNODES_PRIVATE /** - * @name Parameters for the new (prop271) entry guard algorithm. + * @name Default values for the parameters for the new (prop271) entry guard + * algorithm. */ -/* XXXX prop271 some of these should be networkstatus parameters */ /**@{*/ /** - * We never let our sampled guard set grow larger than this fraction + * We never let our sampled guard set grow larger than this percentage * of the guards on the network. */ -#define MAX_SAMPLE_THRESHOLD 0.30 +#define DFLT_MAX_SAMPLE_THRESHOLD_PERCENT 30 /** * We always try to make our sample contain at least this many guards. * * XXXX prop271 There was a MIN_SAMPLE_THRESHOLD in the proposal, but I * removed it in favor of MIN_FILTERED_SAMPLE_SIZE. -NM */ -#define MIN_FILTERED_SAMPLE_SIZE 20 +#define DFLT_MIN_FILTERED_SAMPLE_SIZE 20 /** * If a guard is unlisted for this many days in a row, we remove it. */ -#define REMOVE_UNLISTED_GUARDS_AFTER_DAYS 20 +#define DFLT_REMOVE_UNLISTED_GUARDS_AFTER_DAYS 20 /** * We remove unconfirmed guards from the sample after this many days, * regardless of whether they are listed or unlisted. */ -#define GUARD_LIFETIME_DAYS 120 +#define DFLT_GUARD_LIFETIME_DAYS 120 /** * We remove confirmed guards from the sample if they were sampled * GUARD_LIFETIME_DAYS ago and confirmed this many days ago. */ -#define GUARD_CONFIRMED_MIN_LIFETIME_DAYS 60 +#define DFLT_GUARD_CONFIRMED_MIN_LIFETIME_DAYS 60 /** * How many guards do we try to keep on our primary guard list? */ -#define N_PRIMARY_GUARDS 3 +#define DFLT_N_PRIMARY_GUARDS 3 /** * If we haven't successfully built or used a circuit in this long, then * consider that the internet is probably down. */ -#define INTERNET_LIKELY_DOWN_INTERVAL (10*60) +#define DFLT_INTERNET_LIKELY_DOWN_INTERVAL (10*60) +/** + * If we're trying to connect to a nonprimary guard for at least this + * many seconds, and we haven't gotten the connection to work, we will treat + * lower-priority guards as usable. + */ +#define DFLT_NONPRIMARY_GUARD_CONNECT_TIMEOUT 15 +/** + * If a circuit has been sitting around in 'waiting for better guard' state + * for at least this long, we'll expire it. + */ +#define DLFT_NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60) /** * DOCDOC. not yet used; see prop271. */ -#define NONPRIMARY_GUARD_CONNECT_TIMEOUT 15 -/** - * DOCDOC. not yet used; see prop271. - */ -#define NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60) -/** - * DOCDOC. not yet used; see prop271. - */ -#define MEANINGFUL_RESTRICTION_FRAC 0.2 +#define DFLT_MEANINGFUL_RESTRICTION_FRAC 0.2 /** * DOCDOC. not yet used. see prop271. */ -#define EXTREME_RESTRICTION_FRAC 0.01 +#define DFLT_EXTREME_RESTRICTION_FRAC 0.01 /**@}*/ +STATIC double get_max_sample_threshold(void); +STATIC int get_min_filtered_sample_size(void); +STATIC int get_remove_unlisted_guards_after_days(void); +STATIC int get_guard_lifetime_days(void); +STATIC int get_guard_confirmed_min_lifetime_days(void); +STATIC int get_n_primary_guards(void); +STATIC int get_internet_likely_down_interval(void); +STATIC int get_nonprimary_guard_connect_timeout(void); +STATIC int get_nonprimary_guard_idle_timeout(void); + // ---------- XXXX these functions and definitions are post-prop271. HANDLE_DECL(entry_guard, entry_guard_t, STATIC) STATIC guard_selection_t *guard_selection_new(const char *name); diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index de36142d9a..ee0837532c 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1425,7 +1425,7 @@ test_entry_guard_expand_sample(void *arg) num_reachable_filtered_guards(gs)); /* Make sure we got the right number. */ - tt_int_op(MIN_FILTERED_SAMPLE_SIZE, OP_EQ, + tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, num_reachable_filtered_guards(gs)); // Make sure everything we got was from our fake node list, and everything @@ -1444,7 +1444,7 @@ test_entry_guard_expand_sample(void *arg) // make no changes. guard = entry_guards_expand_sample(gs); tt_assert(! guard); // no guard was added. - tt_int_op(MIN_FILTERED_SAMPLE_SIZE, OP_EQ, + tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, num_reachable_filtered_guards(gs)); // Make a few guards unreachable. @@ -1454,13 +1454,13 @@ test_entry_guard_expand_sample(void *arg) guard->is_usable_filtered_guard = 0; guard = smartlist_get(gs->sampled_entry_guards, 2); guard->is_usable_filtered_guard = 0; - tt_int_op(MIN_FILTERED_SAMPLE_SIZE - 3, OP_EQ, + tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE - 3, OP_EQ, num_reachable_filtered_guards(gs)); // This time, expanding the sample will add some more guards. guard = entry_guards_expand_sample(gs); tt_assert(guard); // no guard was added. - tt_int_op(MIN_FILTERED_SAMPLE_SIZE, OP_EQ, + tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, num_reachable_filtered_guards(gs)); tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, num_reachable_filtered_guards(gs)+3); @@ -1468,7 +1468,7 @@ test_entry_guard_expand_sample(void *arg) // Still idempotent. guard = entry_guards_expand_sample(gs); tt_assert(! guard); // no guard was added. - tt_int_op(MIN_FILTERED_SAMPLE_SIZE, OP_EQ, + tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, num_reachable_filtered_guards(gs)); // Now, do a nasty trick: tell the filter to exclude 31/32 of the guards. @@ -1486,7 +1486,7 @@ test_entry_guard_expand_sample(void *arg) // Surely (p ~ 1-2**-60), one of our guards has been excluded. tt_int_op(num_reachable_filtered_guards(gs), OP_LT, - MIN_FILTERED_SAMPLE_SIZE); + DFLT_MIN_FILTERED_SAMPLE_SIZE); // Try to regenerate the guards. guard = entry_guards_expand_sample(gs); @@ -1494,7 +1494,7 @@ test_entry_guard_expand_sample(void *arg) /* this time, it's possible that we didn't add enough sampled guards. */ tt_int_op(num_reachable_filtered_guards(gs), OP_LE, - MIN_FILTERED_SAMPLE_SIZE); + DFLT_MIN_FILTERED_SAMPLE_SIZE); /* but we definitely didn't exceed the sample maximum. */ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_LE, (int)((271 / 2) * .3)); From 9493711077a0de1a704657b9645f0127e77455ed Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 27 Nov 2016 13:19:54 -0500 Subject: [PATCH 42/80] Mark confirmed guards primary as appropriate. If a guard becomes primary as a result of confirming it, consider the circuit through that guard as a primary circuit. Also, note open questions on behavior when confirming nonprimary guards --- src/or/entrynodes.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index f1fe9f13c1..0650cbe963 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1384,6 +1384,8 @@ entry_guards_note_guard_success(guard_selection_t *gs, if (guard->confirmed_idx < 0) { make_guard_confirmed(gs, guard); + if (!gs->primary_guards_up_to_date) + entry_guards_update_primary(gs); } unsigned new_state; @@ -1392,7 +1394,19 @@ entry_guards_note_guard_success(guard_selection_t *gs, } else { tor_assert_nonfatal( old_state == GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); - new_state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD; + + if (guard->is_primary) { + /* XXXX prop271 -- I don't actually like this logic. It seems to make us + * a little more susceptible to evil-ISP attacks. The mitigations I'm + * thinking of, however, aren't local to this point, so I'll leave it + * alone. */ + /* This guard may have become primary by virtue of being confirmed. + If so, the circuit for it is now complete. + */ + new_state = GUARD_CIRC_STATE_COMPLETE; + } else { + new_state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD; + } if (last_time_on_internet + get_internet_likely_down_interval() < approx_time()) { From c6d218c44b723bbc02efc43d89507c7305137e5a Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 27 Nov 2016 13:55:36 -0500 Subject: [PATCH 43/80] Unit tests for entry_guard_{pick_for_circuit,succeeded,failed} --- src/or/entrynodes.c | 2 +- src/or/entrynodes.h | 1 + src/test/test_entrynodes.c | 262 +++++++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 0650cbe963..6f6853e782 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1628,7 +1628,7 @@ entry_guard_chan_failed(guard_selection_t *gs, * Return true iff every primary guard in gs is believed to * be unreachable. */ -static int +STATIC int entry_guards_all_primary_guards_are_down(guard_selection_t *gs) { tor_assert(gs); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 0ed94cb418..a0f4c2e3f1 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -434,6 +434,7 @@ STATIC char *entry_guard_encode_for_state(entry_guard_t *guard); STATIC entry_guard_t *entry_guard_parse_from_state(const char *s); STATIC void entry_guard_free(entry_guard_t *e); STATIC void entry_guards_update_filtered_sets(guard_selection_t *gs); +STATIC int entry_guards_all_primary_guards_are_down(guard_selection_t *gs); /** * @name Flags for sample_reachable_filtered_entry_guards() */ diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index ee0837532c..4678c77b7f 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -3,6 +3,7 @@ #include "orconfig.h" +#define CIRCUITLIST_PRIVATE #define STATEFILE_PRIVATE #define ENTRYNODES_PRIVATE #define ROUTERLIST_PRIVATE @@ -11,6 +12,7 @@ #include "test.h" #include "bridges.h" +#include "circuitlist.h" #include "config.h" #include "entrynodes.h" #include "nodelist.h" @@ -2251,6 +2253,263 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) guard_selection_free(gs); } +static void +test_entry_guard_select_for_circuit_highlevel_primary(void *arg) +{ + /* Play around with selecting primary guards for circuits and markign + * them up and down */ + (void)arg; + guard_selection_t *gs = guard_selection_new("default"); + + time_t start = approx_time(); + + const node_t *node = NULL; + circuit_guard_state_t *guard = NULL; + entry_guard_t *g; + /* + * Make sure that the pick-for-circuit API basically works. We'll get + * a primary guard, so it'll be usable on completion. + */ + int r = entry_guard_pick_for_circuit(gs, &node, &guard); + + tt_assert(r == 0); + tt_assert(node); + tt_assert(guard); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + g = entry_guard_handle_get(guard->guard); + tt_assert(g); + tt_mem_op(g->identity, OP_EQ, node->identity, DIGEST_LEN); + tt_int_op(g->is_primary, OP_EQ, 1); + tt_i64_op(g->last_tried_to_connect, OP_EQ, start); + tt_int_op(g->confirmed_idx, OP_EQ, -1); + + /* Call that circuit successful. */ + update_approx_time(start+15); + r = entry_guard_succeeded(gs, &guard); + tt_int_op(r, OP_EQ, 1); /* We can use it now. */ + tt_assert(guard); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); + g = entry_guard_handle_get(guard->guard); + tt_assert(g); + tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_YES); + tt_int_op(g->confirmed_idx, OP_EQ, 0); + + circuit_guard_state_free(guard); + guard = NULL; + node = NULL; + g = NULL; + + /* Try again. We'll also get a primary guard this time. (The same one, + in fact.) But this time, we'll say the connection has failed. */ + update_approx_time(start+35); + r = entry_guard_pick_for_circuit(gs, &node, &guard); + tt_assert(r == 0); + tt_assert(node); + tt_assert(guard); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + tt_i64_op(guard->state_set_at, OP_EQ, start+35); + g = entry_guard_handle_get(guard->guard); + tt_assert(g); + tt_mem_op(g->identity, OP_EQ, node->identity, DIGEST_LEN); + tt_int_op(g->is_primary, OP_EQ, 1); + tt_i64_op(g->last_tried_to_connect, OP_EQ, start+35); + tt_int_op(g->confirmed_idx, OP_EQ, 0); // same one. + + /* It's failed! What will happen to our poor guard? */ + update_approx_time(start+45); + entry_guard_failed(gs, &guard); + tt_assert(guard); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_DEAD); + tt_i64_op(guard->state_set_at, OP_EQ, start+45); + g = entry_guard_handle_get(guard->guard); + tt_assert(g); + tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_NO); + tt_i64_op(g->failing_since, OP_EQ, start+45); + tt_int_op(g->confirmed_idx, OP_EQ, 0); // still confirmed. + + circuit_guard_state_free(guard); + guard = NULL; + node = NULL; + entry_guard_t *g_prev = g; + g = NULL; + + /* Now try a third time. Since the other one is down, we'll get a different + * (still primary) guard. + */ + update_approx_time(start+60); + r = entry_guard_pick_for_circuit(gs, &node, &guard); + tt_assert(r == 0); + tt_assert(node); + tt_assert(guard); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + g = entry_guard_handle_get(guard->guard); + tt_assert(g); + tt_ptr_op(g, OP_NE, g_prev); + tt_mem_op(g->identity, OP_EQ, node->identity, DIGEST_LEN); + tt_mem_op(g->identity, OP_NE, g_prev->identity, DIGEST_LEN); + tt_int_op(g->is_primary, OP_EQ, 1); + tt_i64_op(g->last_tried_to_connect, OP_EQ, start+60); + tt_int_op(g->confirmed_idx, OP_EQ, -1); // not confirmd now. + + /* Call this one up; watch it get confirmed. */ + update_approx_time(start+90); + r = entry_guard_succeeded(gs, &guard); + tt_int_op(r, OP_EQ, 1); /* We can use it now. */ + tt_assert(guard); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); + g = entry_guard_handle_get(guard->guard); + tt_assert(g); + tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_YES); + tt_int_op(g->confirmed_idx, OP_EQ, 1); + + done: + guard_selection_free(gs); + circuit_guard_state_free(guard); +} + +static void +test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) +{ + (void) arg; + const int N_PRIMARY = DFLT_N_PRIMARY_GUARDS; + + /* At the start, we have no confirmed guards. We'll mark the primary guards + * down, then confirm something else. As soon as we do, it should become + * primary, and we should get it next time. */ + + time_t start = approx_time(); + guard_selection_t *gs = guard_selection_new("default"); + circuit_guard_state_t *guard = NULL; + int i, r; + const node_t *node = NULL; + + /* Declare that we're on the internet. */ + entry_guards_note_internet_connectivity(gs); + + /* Primary guards are down! */ + for (i = 0; i < N_PRIMARY; ++i) { + r = entry_guard_pick_for_circuit(gs, &node, &guard); + tt_assert(node); + tt_assert(guard); + tt_assert(r == 0); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + entry_guard_failed(gs, &guard); + circuit_guard_state_free(guard); + guard = NULL; + node = NULL; + } + + /* Next guard should be non-primary. */ + node = NULL; + r = entry_guard_pick_for_circuit(gs, &node, &guard); + tt_assert(node); + tt_assert(guard); + tt_assert(r == 0); + entry_guard_t *g = entry_guard_handle_get(guard->guard); + tt_assert(g); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + tt_int_op(g->confirmed_idx, OP_EQ, -1); + tt_int_op(g->is_primary, OP_EQ, 0); + tt_int_op(g->is_pending, OP_EQ, 1); + (void)start; + + r = entry_guard_succeeded(gs, &guard); + /* We're on the internet (by fiat), so this guard will get called "confirmed" + * and should immediately become primary. + * XXXX prop271 -- I don't like that behavior, but it's what is specified + */ + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); + tt_assert(r == 1); + tt_int_op(g->confirmed_idx, OP_EQ, 0); + tt_int_op(g->is_primary, OP_EQ, 1); + tt_int_op(g->is_pending, OP_EQ, 0); + + done: + guard_selection_free(gs); + circuit_guard_state_free(guard); +} + +static void +test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) +{ + (void) arg; + const int N_PRIMARY = DFLT_N_PRIMARY_GUARDS; + + /* At the start, we have no confirmed guards. We'll mark the primary guards + * down, then confirm something else. As soon as we do, it should become + * primary, and we should get it next time. */ + + time_t start = approx_time(); + guard_selection_t *gs = guard_selection_new("default"); + circuit_guard_state_t *guard = NULL, *guard2 = NULL; + int i, r; + const node_t *node = NULL; + entry_guard_t *g; + + /* Declare that we're on the internet. */ + entry_guards_note_internet_connectivity(gs); + + /* Make primary guards confirmed (so they won't be superseded by a later + * guard), then mark them down. */ + for (i = 0; i < N_PRIMARY; ++i) { + r = entry_guard_pick_for_circuit(gs, &node, &guard); + tt_assert(node); + tt_assert(guard); + tt_assert(r == 0); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + g = entry_guard_handle_get(guard->guard); + make_guard_confirmed(gs, g); + tt_int_op(g->is_primary, OP_EQ, 1); + entry_guard_failed(gs, &guard); + circuit_guard_state_free(guard); + tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_NO); + guard = NULL; + node = NULL; + } + + /* Get another guard that we might try. */ + r = entry_guard_pick_for_circuit(gs, &node, &guard); + tt_assert(node); + tt_assert(guard); + tt_assert(r == 0); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + g = entry_guard_handle_get(guard->guard); + tt_int_op(g->is_primary, OP_EQ, 0); + + tt_assert(entry_guards_all_primary_guards_are_down(gs)); + + /* And an hour has passed ... */ + update_approx_time(start + 3600); + + /* Say that guard has succeeded! */ + r = entry_guard_succeeded(gs, &guard); + tt_int_op(r, OP_EQ, 0); // can't use it yet. + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); + g = entry_guard_handle_get(guard->guard); + + /* The primary guards should have been marked up! */ + SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, pg, { + tt_int_op(pg->is_primary, OP_EQ, 1); + tt_ptr_op(g, OP_NE, pg); + tt_int_op(pg->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); + }); + + /* Have a circuit to a primary guard succeed. */ + r = entry_guard_pick_for_circuit(gs, &node, &guard2); + tt_assert(r == 0); + tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + r = entry_guard_succeeded(gs, &guard2); + tt_assert(r == 1); + tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); + + tt_assert(! entry_guards_all_primary_guards_are_down(gs)); + + done: + guard_selection_free(gs); + circuit_guard_state_free(guard); + circuit_guard_state_free(guard2); +} + static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; @@ -2321,6 +2580,9 @@ struct testcase_t entrynodes_tests[] = { BFN_TEST(manage_primary), BFN_TEST(select_for_circuit_no_confirmed), BFN_TEST(select_for_circuit_confirmed), + BFN_TEST(select_for_circuit_highlevel_primary), + BFN_TEST(select_for_circuit_highlevel_confirm_other), + BFN_TEST(select_for_circuit_highlevel_primary_retry), END_OF_TESTCASES }; From fcb50f18398791c2a6e6c78465ab12012d3991e7 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 27 Nov 2016 14:48:17 -0500 Subject: [PATCH 44/80] Test for entry_guard_has_higher_priority(). --- src/test/test_entrynodes.c | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 4678c77b7f..eaba3c40fe 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -2093,6 +2093,54 @@ test_entry_guard_manage_primary(void *arg) smartlist_free(prev_guards); } +static void +test_entry_guard_guard_preferred(void *arg) +{ + (void) arg; + entry_guard_t *g1 = tor_malloc_zero(sizeof(entry_guard_t)); + entry_guard_t *g2 = tor_malloc_zero(sizeof(entry_guard_t)); + + g1->confirmed_idx = g2->confirmed_idx = -1; + g1->last_tried_to_connect = approx_time(); + g2->last_tried_to_connect = approx_time(); + + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g1)); + + /* Neither is pending; priorities equal. */ + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g2, g1)); + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g2)); + + /* If one is pending, the pending one has higher priority */ + g1->is_pending = 1; + tt_int_op(1, OP_EQ, entry_guard_has_higher_priority(g1, g2)); + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g2, g1)); + + /* If both are pending, and last_tried_to_connect is equal: + priorities equal */ + g2->is_pending = 1; + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g2, g1)); + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g2)); + + /* One had a connection that startied earlier: it has higher priority. */ + g2->last_tried_to_connect -= 10; + tt_int_op(1, OP_EQ, entry_guard_has_higher_priority(g2, g1)); + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g2)); + + /* Now, say that g1 is confirmed. It will get higher priority. */ + g1->confirmed_idx = 5; + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g2, g1)); + tt_int_op(1, OP_EQ, entry_guard_has_higher_priority(g1, g2)); + + /* But if g2 was confirmed first, it will get priority */ + g2->confirmed_idx = 2; + tt_int_op(1, OP_EQ, entry_guard_has_higher_priority(g2, g1)); + tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g2)); + + done: + tor_free(g1); + tor_free(g2); +} + static void test_entry_guard_select_for_circuit_no_confirmed(void *arg) { @@ -2578,6 +2626,7 @@ struct testcase_t entrynodes_tests[] = { BFN_TEST(sample_reachable_filtered_empty), BFN_TEST(retry_unreachable), BFN_TEST(manage_primary), + { "guard_preferred", test_entry_guard_guard_preferred, TT_FORK, NULL, NULL }, BFN_TEST(select_for_circuit_no_confirmed), BFN_TEST(select_for_circuit_confirmed), BFN_TEST(select_for_circuit_highlevel_primary), From 08d3ca2e5657a759d10064a2acb62b0a47bc15ff Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 27 Nov 2016 18:47:27 -0500 Subject: [PATCH 45/80] More entry guard tests: for cancel, and for upgrade. --- src/test/test_entrynodes.c | 371 +++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index eaba3c40fe..eaaadce152 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -2558,6 +2558,359 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) circuit_guard_state_free(guard2); } +static void +test_entry_guard_select_and_cancel(void *arg) +{ + (void) arg; + const int N_PRIMARY = DFLT_N_PRIMARY_GUARDS; + int i,r; + const node_t *node = NULL; + circuit_guard_state_t *guard; + guard_selection_t *gs = guard_selection_new("default"); + entry_guard_t *g; + + /* Once more, we mark all the primary guards down. */ + entry_guards_note_internet_connectivity(gs); + for (i = 0; i < N_PRIMARY; ++i) { + r = entry_guard_pick_for_circuit(gs, &node, &guard); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); + g = entry_guard_handle_get(guard->guard); + tt_int_op(g->is_primary, OP_EQ, 1); + tt_int_op(g->is_pending, OP_EQ, 0); + make_guard_confirmed(gs, g); + entry_guard_failed(gs, &guard); + circuit_guard_state_free(guard); + guard = NULL; + node = NULL; + } + + tt_assert(entry_guards_all_primary_guards_are_down(gs)); + + /* Now get another guard we could try... */ + r = entry_guard_pick_for_circuit(gs, &node, &guard); + tt_assert(node); + tt_assert(guard); + tt_assert(r == 0); + tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + g = entry_guard_handle_get(guard->guard); + tt_int_op(g->is_primary, OP_EQ, 0); + tt_int_op(g->is_pending, OP_EQ, 1); + + /* Whoops! We should never have asked for this guard. Cancel the request! */ + entry_guard_cancel(gs, &guard); + tt_assert(guard == NULL); + tt_int_op(g->is_primary, OP_EQ, 0); + tt_int_op(g->is_pending, OP_EQ, 0); + + done: + guard_selection_free(gs); + circuit_guard_state_free(guard); +} + +/* Unit test setup function: Create a fake network, and set everything up + * for testing the upgrade-a-waiting-circuit code. */ +typedef struct { + guard_selection_t *gs; + time_t start; + circuit_guard_state_t *guard1_state; + circuit_guard_state_t *guard2_state; + entry_guard_t *guard1; + entry_guard_t *guard2; + origin_circuit_t *circ1; + origin_circuit_t *circ2; + smartlist_t *all_origin_circuits; +} upgrade_circuits_data_t; +static void * +upgrade_circuits_setup(const struct testcase_t *testcase) +{ + upgrade_circuits_data_t *data = tor_malloc_zero(sizeof(*data)); + guard_selection_t *gs = data->gs = guard_selection_new("default"); + circuit_guard_state_t *guard; + const node_t *node; + entry_guard_t *g; + int i; + const int N_PRIMARY = DFLT_N_PRIMARY_GUARDS; + const char *argument = testcase->setup_data; + const int make_circ1_succeed = strstr(argument, "c1-done") != NULL; + const int make_circ2_succeed = strstr(argument, "c2-done") != NULL; + + big_fake_network_setup(testcase); + + /* We're going to set things up in a state where a circuit will be ready to + * be upgraded. Each test can make a single change (or not) that should + * block the upgrade. + */ + + /* First, make all the primary guards confirmed, and down. */ + data->start = approx_time(); + entry_guards_note_internet_connectivity(gs); + for (i = 0; i < N_PRIMARY; ++i) { + entry_guard_pick_for_circuit(gs, &node, &guard); + g = entry_guard_handle_get(guard->guard); + make_guard_confirmed(gs, g); + entry_guard_failed(gs, &guard); + circuit_guard_state_free(guard); + } + + /* Grab another couple of guards */ + data->all_origin_circuits = smartlist_new(); + + update_approx_time(data->start + 27); + entry_guard_pick_for_circuit(gs, &node, &data->guard1_state); + origin_circuit_t *circ; + data->circ1 = circ = origin_circuit_new(); + circ->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL; + circ->guard_state = data->guard1_state; + smartlist_add(data->all_origin_circuits, circ); + + update_approx_time(data->start + 30); + entry_guard_pick_for_circuit(gs, &node, &data->guard2_state); + data->circ2 = circ = origin_circuit_new(); + circ->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL; + circ->guard_state = data->guard2_state; + smartlist_add(data->all_origin_circuits, circ); + + data->guard1 = entry_guard_handle_get(data->guard1_state->guard); + data->guard2 = entry_guard_handle_get(data->guard2_state->guard); + tor_assert(data->guard1 != data->guard2); + tor_assert(data->guard1_state->state == + GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + tor_assert(data->guard2_state->state == + GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); + + int r; + update_approx_time(data->start + 32); + if (make_circ1_succeed) { + r = entry_guard_succeeded(gs, &data->guard1_state); + tor_assert(r == 0); + tor_assert(data->guard1_state->state == + GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); + } + update_approx_time(data->start + 33); + if (make_circ2_succeed) { + r = entry_guard_succeeded(gs, &data->guard2_state); + tor_assert(r == 0); + tor_assert(data->guard2_state->state == + GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); + } + + return data; +} +static int +upgrade_circuits_cleanup(const struct testcase_t *testcase, void *ptr) +{ + upgrade_circuits_data_t *data = ptr; + // circuit_guard_state_free(data->guard1_state); // held in circ1 + // circuit_guard_state_free(data->guard2_state); // held in circ2 + guard_selection_free(data->gs); + smartlist_free(data->all_origin_circuits); + circuit_free(TO_CIRCUIT(data->circ1)); + circuit_free(TO_CIRCUIT(data->circ2)); + tor_free(data); + return big_fake_network_cleanup(testcase, ptr); +} + +static void +test_entry_guard_upgrade_a_circuit(void *arg) +{ + upgrade_circuits_data_t *data = arg; + + /* This is the easy case: we have no COMPLETED circuits, all the + * primary guards are down, we have two WAITING circuits: one will + * get upgraded to COMPLETED! (The one that started first.) + */ + /* XXXX prop271 -- perhaps the one that started first should + * also wind up in confirmed_entry_guards earlier? + */ + + smartlist_t *result = smartlist_new(); + int r; + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 1); + tt_int_op(smartlist_len(result), OP_EQ, 1); + origin_circuit_t *oc = smartlist_get(result, 0); + + /* circ1 was started first, so we'll get told to ugrade it... */ + tt_ptr_op(oc, OP_EQ, data->circ1); + + /* And the guard state should be complete */ + tt_ptr_op(data->guard1_state, OP_NE, NULL); + tt_int_op(data->guard1_state->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); + + done: + smartlist_free(result); +} + +static void +test_entry_guard_upgrade_blocked_by_live_primary_guards(void *arg) +{ + upgrade_circuits_data_t *data = arg; + + /* If any primary guards might be up, we can't upgrade any waiting + * circuits. + */ + mark_primary_guards_maybe_reachable(data->gs); + + smartlist_t *result = smartlist_new(); + int r; + setup_capture_of_logs(LOG_DEBUG); + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 0); + tt_int_op(smartlist_len(result), OP_EQ, 0); + expect_log_msg_containing("not all primary guards were definitely down."); + + done: + teardown_capture_of_logs(); + smartlist_free(result); +} + +static void +test_entry_guard_upgrade_blocked_by_lack_of_waiting_circuits(void *arg) +{ + upgrade_circuits_data_t *data = arg; + + /* If no circuits are waiting, we can't upgrade anything. (The test + * setup in this case was told not to make any of the circuits "waiting".) + */ + smartlist_t *result = smartlist_new(); + int r; + setup_capture_of_logs(LOG_DEBUG); + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 0); + tt_int_op(smartlist_len(result), OP_EQ, 0); + expect_log_msg_containing("Considered upgrading guard-stalled circuits, " + "but didn't find any."); + + done: + teardown_capture_of_logs(); + smartlist_free(result); +} + +static void +test_entry_guard_upgrade_blocked_by_better_circ_complete(void *arg) +{ + upgrade_circuits_data_t *data = arg; + + /* We'll run through the logic of upgrade_a_circuit below... + * and then try again to make sure that circ2 isn't also upgraded. + */ + + smartlist_t *result = smartlist_new(); + int r; + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 1); + tt_int_op(smartlist_len(result), OP_EQ, 1); + origin_circuit_t *oc = smartlist_get(result, 0); + tt_ptr_op(oc, OP_EQ, data->circ1); + tt_ptr_op(data->guard1_state, OP_NE, NULL); + tt_int_op(data->guard1_state->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); + + /* Now, try again. Make sure that circ2 isn't upgraded. */ + smartlist_clear(result); + setup_capture_of_logs(LOG_DEBUG); + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 0); + tt_int_op(smartlist_len(result), OP_EQ, 0); + expect_log_msg_containing("At least one complete circuit had higher " + "priority, so not upgrading."); + + done: + teardown_capture_of_logs(); + smartlist_free(result); +} + +static void +test_entry_guard_upgrade_not_blocked_by_worse_circ_complete(void *arg) +{ + upgrade_circuits_data_t *data = arg; + smartlist_t *result = smartlist_new(); + /* here we manually make circ2 COMPLETE, and make sure that circ1 + * gets made complete anyway, since guard1 has higher priority + */ + update_approx_time(data->start + 300); + data->guard2_state->state = GUARD_CIRC_STATE_COMPLETE; + data->guard2_state->state_set_at = approx_time(); + update_approx_time(data->start + 301); + + /* Now, try again. Make sure that circ1 is approved. */ + int r; + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 1); + tt_int_op(smartlist_len(result), OP_EQ, 1); + origin_circuit_t *oc = smartlist_get(result, 0); + tt_ptr_op(oc, OP_EQ, data->circ1); + + done: + smartlist_free(result); +} + +static void +test_entry_guard_upgrade_blocked_by_better_circ_pending(void *arg) +{ + upgrade_circuits_data_t *data = arg; + + /* circ2 is done, but circ1 is still pending. Since circ1 is better, + * we won't upgrade circ2. */ + + /* XXXX Prop271 -- this is a kludge. I'm making sure circ1 _is_ better, + * by messing with the guards' confirmed_idx */ + make_guard_confirmed(data->gs, data->guard1); + { + int tmp; + tmp = data->guard1->confirmed_idx; + data->guard1->confirmed_idx = data->guard2->confirmed_idx; + data->guard2->confirmed_idx = tmp; + } + + smartlist_t *result = smartlist_new(); + setup_capture_of_logs(LOG_DEBUG); + int r; + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 0); + tt_int_op(smartlist_len(result), OP_EQ, 0); + expect_log_msg_containing("but 1 pending circuit(s) had higher guard " + "priority, so not upgrading."); + + done: + teardown_capture_of_logs(); + smartlist_free(result); +} + +static void +test_entry_guard_upgrade_not_blocked_by_worse_circ_pending(void *arg) +{ + upgrade_circuits_data_t *data = arg; + + /* circ1 is done, but circ2 is still pending. Since circ1 is better, + * we will upgrade it. */ + smartlist_t *result = smartlist_new(); + int r; + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 1); + tt_int_op(smartlist_len(result), OP_EQ, 1); + origin_circuit_t *oc = smartlist_get(result, 0); + tt_ptr_op(oc, OP_EQ, data->circ1); + + done: + smartlist_free(result); +} + static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; @@ -2566,9 +2919,17 @@ static const struct testcase_setup_t big_fake_network = { big_fake_network_setup, big_fake_network_cleanup }; +static const struct testcase_setup_t upgrade_circuits = { + upgrade_circuits_setup, upgrade_circuits_cleanup +}; + #define BFN_TEST(name) \ { #name, test_entry_guard_ ## name, TT_FORK, &big_fake_network, NULL } +#define UPGRADE_TEST(name, arg) \ + { #name, test_entry_guard_ ## name, TT_FORK, &upgrade_circuits, \ + (void*)(arg) } + struct testcase_t entrynodes_tests[] = { { "entry_is_time_to_retry", test_entry_is_time_to_retry, TT_FORK, NULL, NULL }, @@ -2632,6 +2993,16 @@ struct testcase_t entrynodes_tests[] = { BFN_TEST(select_for_circuit_highlevel_primary), BFN_TEST(select_for_circuit_highlevel_confirm_other), BFN_TEST(select_for_circuit_highlevel_primary_retry), + BFN_TEST(select_and_cancel), + + UPGRADE_TEST(upgrade_a_circuit, "c1-done c2-done"), + UPGRADE_TEST(upgrade_blocked_by_live_primary_guards, "c1-done c2-done"), + UPGRADE_TEST(upgrade_blocked_by_lack_of_waiting_circuits, ""), + UPGRADE_TEST(upgrade_blocked_by_better_circ_complete, "c1-done c2-done"), + UPGRADE_TEST(upgrade_not_blocked_by_worse_circ_complete, "c1-done c2-done"), + UPGRADE_TEST(upgrade_blocked_by_better_circ_pending, "c2-done"), + UPGRADE_TEST(upgrade_not_blocked_by_worse_circ_pending, "c1-done"), + END_OF_TESTCASES }; From 404e9e5611eff39866c2e45133a60b40d7492f7e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Nov 2016 07:41:45 -0500 Subject: [PATCH 46/80] Have multiple guard contexts we can switch between. Currently, this code doesn't actually have the contexts behave differently, (except for the legacy context), but it does switch back and forth between them nicely. --- src/or/config.c | 7 - src/or/entrynodes.c | 272 +++++++++++++++++++++++++++++++++---- src/or/entrynodes.h | 50 ++++++- src/or/main.c | 7 +- src/test/test_entrynodes.c | 59 ++++---- 5 files changed, 322 insertions(+), 73 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index b7b5cff35a..22e5dfdaa0 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -4536,13 +4536,6 @@ options_transition_allowed(const or_options_t *old, return -1; } - if (old->UseDeprecatedGuardAlgorithm != - new_val->UseDeprecatedGuardAlgorithm) { - *msg = tor_strdup("While Tor is running, changing " - "UseDeprecatedGuardAlgorithm is not allowed."); - return -1; - } - if (sandbox_is_active()) { #define SB_NOCHANGE_STR(opt) \ do { \ diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 6f6853e782..59205a8bcc 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -159,6 +159,10 @@ static void entry_guard_set_filtered_flags(const or_options_t *options, entry_guard_t *guard); static void pathbias_check_use_success_count(entry_guard_t *guard); static void pathbias_check_close_success_count(entry_guard_t *guard); +static int node_is_possible_guard(guard_selection_t *gs, const node_t *node); +static int node_passes_guard_filter(const or_options_t *options, + guard_selection_t *gs, + const node_t *node); /** Return 0 if we should apply guardfraction information found in the * consensus. A specific consensus can be specified with the @@ -186,12 +190,25 @@ should_apply_guardfraction(const networkstatus_t *ns) * Allocate and return a new guard_selection_t, with the name name. */ STATIC guard_selection_t * -guard_selection_new(const char *name) +guard_selection_new(const char *name, + guard_selection_type_t type) { guard_selection_t *gs; + if (type == GS_TYPE_INFER) { + if (!strcmp(name, "legacy")) + type = GS_TYPE_LEGACY; + else if (!strcmp(name, "bridges")) + type = GS_TYPE_BRIDGE; + else if (!strcmp(name, "restricted")) + type = GS_TYPE_RESTRICTED; + else + type = GS_TYPE_NORMAL; + } + gs = tor_malloc_zero(sizeof(*gs)); gs->name = tor_strdup(name); + gs->type = type; gs->chosen_entry_guards = smartlist_new(); gs->sampled_entry_guards = smartlist_new(); gs->confirmed_entry_guards = smartlist_new(); @@ -206,7 +223,9 @@ guard_selection_new(const char *name) * is none, and create_if_absent is false, then return NULL. */ STATIC guard_selection_t * -get_guard_selection_by_name(const char *name, int create_if_absent) +get_guard_selection_by_name(const char *name, + guard_selection_type_t type, + int create_if_absent) { if (!guard_contexts) { guard_contexts = smartlist_new(); @@ -219,31 +238,42 @@ get_guard_selection_by_name(const char *name, int create_if_absent) if (! create_if_absent) return NULL; - guard_selection_t *new_selection = guard_selection_new(name); + log_debug(LD_GUARD, "Creating a guard selection called %s", name); + guard_selection_t *new_selection = guard_selection_new(name, type); smartlist_add(guard_contexts, new_selection); - const char *default_name = get_options()->UseDeprecatedGuardAlgorithm ? - "legacy" : "default"; - - if (!strcmp(name, default_name)) - curr_guard_context = new_selection; - return new_selection; } +/** + * Allocate the first guard context that we're planning to use, + * and make it the current context. + */ +static void +create_initial_guard_context(void) +{ + tor_assert(! curr_guard_context); + if (!guard_contexts) { + guard_contexts = smartlist_new(); + } + guard_selection_type_t type = GS_TYPE_INFER; + const char *name = choose_guard_selection( + get_options(), + networkstatus_get_live_consensus(approx_time()), + NULL, + &type); + tor_assert(name); // "name" can only be NULL if we had an old name. + tor_assert(type != GS_TYPE_INFER); + log_notice(LD_GUARD, "Starting with guard context \"%s\"", name); + curr_guard_context = get_guard_selection_by_name_and_type(name, type); +} + /** Get current default guard_selection_t, creating it if necessary */ guard_selection_t * get_guard_selection_info(void) { - if (!guard_contexts) { - guard_contexts = smartlist_new(); - } - if (!curr_guard_context) { - const char *name = get_options()->UseDeprecatedGuardAlgorithm ? - "legacy" : "default"; - curr_guard_context = guard_selection_new(name); - smartlist_add(guard_contexts, curr_guard_context); + create_initial_guard_context(); } return curr_guard_context; @@ -431,10 +461,184 @@ get_nonprimary_guard_idle_timeout(void) { return networkstatus_get_param(NULL, "guard-nonprimary-guard-idle-timeout", - (10*60), 1, INT32_MAX); + DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT, + 1, INT32_MAX); +} +/** + * If our configuration retains fewer than this fraction of guards from the + * torrc, we are in a restricted setting. + */ +STATIC double +get_meaningful_restriction_threshold(void) +{ + int32_t pct = networkstatus_get_param(NULL, + "guard-meaningful-restriction-percent", + DFLT_MEANINGFUL_RESTRICTION_PERCENT, + 1, INT32_MAX); + return pct / 100.0; +} +/** + * If our configuration retains fewer than this fraction of guards from the + * torrc, we are in an extremely restricted setting, and should warn. + */ +STATIC double +get_extreme_restriction_threshold(void) +{ + int32_t pct = networkstatus_get_param(NULL, + "guard-extreme-restriction-percent", + DFLT_EXTREME_RESTRICTION_PERCENT, + 1, INT32_MAX); + return pct / 100.0; } /**@}*/ +/** + * Given our options and our list of nodes, return the name of the + * guard selection that we should use. Return NULL for "use the + * same selection you were using before. + */ +STATIC const char * +choose_guard_selection(const or_options_t *options, + const networkstatus_t *live_ns, + const char *old_selection, + guard_selection_type_t *type_out) +{ + tor_assert(options); + tor_assert(type_out); + if (options->UseDeprecatedGuardAlgorithm) { + *type_out = GS_TYPE_LEGACY; + return "legacy"; + } + + if (options->UseBridges) { + *type_out = GS_TYPE_BRIDGE; + return "bridges"; + } + + if (! live_ns) { + /* without a networkstatus, we can't tell any more than that. */ + *type_out = GS_TYPE_NORMAL; + return "default"; + } + + const smartlist_t *nodes = nodelist_get_list(); + int n_guards = 0, n_passing_filter = 0; + SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) { + if (node_is_possible_guard(NULL, node)) { + ++n_guards; + if (node_passes_guard_filter(options, NULL, node)) { + ++n_passing_filter; + } + } + } SMARTLIST_FOREACH_END(node); + + /* XXXX prop271 spec deviation -- separate 'high' and 'low' thresholds + * to prevent flapping */ + const int meaningful_threshold_high = + (int)(n_guards * get_meaningful_restriction_threshold() * 1.05); + const int meaningful_threshold_mid = + (int)(n_guards * get_meaningful_restriction_threshold()); + const int meaningful_threshold_low = + (int)(n_guards * get_meaningful_restriction_threshold() * .95); + const int extreme_threshold = + (int)(n_guards * get_extreme_restriction_threshold()); + + /* + If we have no previous selection, then we're "restricted" iff we are + below the meaningful restriction threshold. That's easy enough. + + But if we _do_ have a previous selection, we make it a little + "sticky": we only move from "restricted" to "default" when we find + that we're above the threshold plus 5%, and we only move from + "default" to "restricted" when we're below the threshold minus 5%. + That should prevent us from flapping back and forth if we happen to + be hovering very close to the default. + + The extreme threshold is for warning only. + */ + + static int have_warned_extreme_threshold = 0; + if (n_passing_filter < extreme_threshold && + ! have_warned_extreme_threshold) { + have_warned_extreme_threshold = 1; + const double exclude_frac = + (n_guards - n_passing_filter) / (double)n_guards; + log_warn(LD_GUARD, "Your configuration excludes %d%% of all possible " + "guards. That's likely to make you stand out from the " + "rest of the world.", (int)(exclude_frac * 100)); + } + + /* Easy case: no previous selection */ + if (old_selection == NULL) { + if (n_passing_filter >= meaningful_threshold_mid) { + *type_out = GS_TYPE_NORMAL; + return "default"; + } else { + *type_out = GS_TYPE_RESTRICTED; + return "restricted"; + } + } + + /* Trickier case: we do have a previous selection */ + if (n_passing_filter >= meaningful_threshold_high) { + *type_out = GS_TYPE_NORMAL; + return "default"; + } else if (n_passing_filter < meaningful_threshold_low) { + *type_out = GS_TYPE_RESTRICTED; + return "restricted"; + } else { + return NULL; + } +} + +/** + * Check whether we should switch from our current guard selection to a + * different one. If so, switch and return 1. Return 0 otherwise. + * + * On a 1 return, the caller should mark all currently live circuits + * unusable for new streams. + */ +int +update_guard_selection_choice(const or_options_t *options) +{ + if (!curr_guard_context) { + create_initial_guard_context(); + return 1; + } + + const char *cur_name = curr_guard_context->name; + guard_selection_type_t type = GS_TYPE_INFER; + const char *new_name = choose_guard_selection( + options, + networkstatus_get_live_consensus(approx_time()), + cur_name, + &type); + tor_assert(new_name); + tor_assert(type != GS_TYPE_INFER); + + if (! strcmp(cur_name, new_name)) { + log_debug(LD_GUARD, + "Staying with guard context \"%s\" (no change)", new_name); + return 0; // No change + } + + log_notice(LD_GUARD, "Switching to guard context \"%s\" (was using \"%s\")", + new_name, cur_name); + guard_selection_t *new_guard_context; + new_guard_context = get_guard_selection_by_name(new_name, type, 1); + tor_assert(new_guard_context); + tor_assert(new_guard_context != curr_guard_context); + curr_guard_context = new_guard_context; + + /* + Be sure to call: + circuit_mark_all_unused_circs(); + circuit_mark_all_dirty_circs_as_unusable(); + */ + + return 1; +} + /** * Return true iff node has all the flags needed for us to consider it * a possible guard when sampling guards. @@ -446,7 +650,7 @@ node_is_possible_guard(guard_selection_t *gs, const node_t *node) * holds. */ /* XXXX -- prop271 spec deviation. We require node_is_dir() here. */ - (void)gs; + (void)gs; /* Remove this argument */ tor_assert(node); return (node->is_possible_guard && node->is_stable && @@ -552,7 +756,7 @@ entry_guards_expand_sample(guard_selection_t *gs) int n_sampled = smartlist_len(gs->sampled_entry_guards); entry_guard_t *added_guard = NULL; - smartlist_t *nodes = nodelist_get_list(); + const smartlist_t *nodes = nodelist_get_list(); /* Construct eligible_guards as GUARDS - SAMPLED_GUARDS */ smartlist_t *eligible_guards = smartlist_new(); int n_guards = 0; // total size of "GUARDS" @@ -565,13 +769,13 @@ entry_guards_expand_sample(guard_selection_t *gs) digestset_add(sampled_guard_ids, guard->identity); } SMARTLIST_FOREACH_END(guard); - SMARTLIST_FOREACH_BEGIN(nodes, node_t *, node) { + SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) { if (! node_is_possible_guard(gs, node)) continue; ++n_guards; if (digestset_contains(sampled_guard_ids, node->identity)) continue; - smartlist_add(eligible_guards, node); + smartlist_add(eligible_guards, (node_t*)node); } SMARTLIST_FOREACH_END(node); /* Now we can free that bloom filter. */ @@ -817,6 +1021,8 @@ static int node_passes_guard_filter(const or_options_t *options, guard_selection_t *gs, const node_t *node) { + /* XXXX prop271 remote the gs option; it is unused, and sometimes NULL. */ + /* NOTE: Make sure that this function stays in sync with * options_transition_affects_entry_guards */ @@ -2221,7 +2427,8 @@ entry_guards_load_guards_from_state(or_state_t *state, int set) if (set) { guard_selection_t *gs; - gs = get_guard_selection_by_name(guard->selection_name, 1); + gs = get_guard_selection_by_name(guard->selection_name, + GS_TYPE_INFER, 1); tor_assert(gs); smartlist_add(gs->sampled_entry_guards, guard); } else { @@ -3854,7 +4061,7 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) int r1 = entry_guards_load_guards_from_state(state, set); int r2 = entry_guards_parse_state_for_guard_selection( - get_guard_selection_by_name("legacy", 1), + get_guard_selection_by_name("legacy", GS_TYPE_LEGACY, 1), state, set, msg); entry_guards_dirty = 0; @@ -3926,7 +4133,8 @@ entry_guards_update_state(or_state_t *state) entry_guards_dirty = 0; - guard_selection_t *gs = get_guard_selection_by_name("legacy", 0); + guard_selection_t *gs; + gs = get_guard_selection_by_name("legacy", GS_TYPE_LEGACY, 0); if (!gs) return; // nothign to save. tor_assert(gs->chosen_entry_guards != NULL); @@ -4212,12 +4420,20 @@ entries_retry_all(const or_options_t *options) int guards_update_all(void) { - if (get_options()->UseDeprecatedGuardAlgorithm) { + int mark_circuits = 0; + if (update_guard_selection_choice(get_options())) + mark_circuits = 1; + + tor_assert(curr_guard_context); + + if (curr_guard_context->type == GS_TYPE_LEGACY) { entry_guards_compute_status(get_options(), approx_time()); - return 0; } else { - return entry_guards_update_all(get_guard_selection_info()); + if (entry_guards_update_all(get_guard_selection_info())) + mark_circuits = 1; } + + return mark_circuits; } /** Helper: pick a guard for a circuit, with whatever algorithm is diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index a0f4c2e3f1..0164667d22 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -194,6 +194,26 @@ struct entry_guard_t { guard_pathbias_t pb; }; +/** + * Possible rules for a guard selection to follow + */ +typedef enum guard_selection_type_t { + /** Infer the type of this selection from its name. */ + GS_TYPE_INFER=0, + /** Use the normal guard selection algorithm, taking our sample from the + * complete list of guards in the consensus. */ + GS_TYPE_NORMAL=1, + /** Use the normal guard selection algorithm, taking our sample from the + * configured bridges, and allowing it to grow as large as all the configured + * bridges */ + GS_TYPE_BRIDGE, + /** Use the normal guard selection algorithm, taking our sample from the + * set of filtered nodes. */ + GS_TYPE_RESTRICTED, + /** Use the legacy (pre-prop271) guard selection algorithm and fields */ + GS_TYPE_LEGACY, +} guard_selection_type_t; + /** * All of the the context for guard selection on a particular client. * @@ -212,6 +232,11 @@ struct guard_selection_s { */ char *name; + /** + * What rules does this guard-selection object follow? + */ + guard_selection_type_t type; + /** * A value of 1 means that primary_entry_guards is up-to-date; 0 * means we need to recalculate it before using primary_entry_guards @@ -340,6 +365,8 @@ int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, int entry_guard_state_should_expire(circuit_guard_state_t *guard_state); void entry_guards_note_internet_connectivity(guard_selection_t *gs); +int update_guard_selection_choice(const or_options_t *options); + /* Used by bridges.c only. */ void add_bridge_as_entry_guard(guard_selection_t *gs, const node_t *chosen); @@ -396,15 +423,17 @@ int num_bridges_usable(void); * If a circuit has been sitting around in 'waiting for better guard' state * for at least this long, we'll expire it. */ -#define DLFT_NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60) +#define DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60) /** - * DOCDOC. not yet used; see prop271. + * If our configuration retains fewer than this fraction of guards from the + * torrc, we are in a restricted setting. */ -#define DFLT_MEANINGFUL_RESTRICTION_FRAC 0.2 +#define DFLT_MEANINGFUL_RESTRICTION_PERCENT 20 /** - * DOCDOC. not yet used. see prop271. + * If our configuration retains fewer than this fraction of guards from the + * torrc, we are in an extremely restricted setting, and should warn. */ -#define DFLT_EXTREME_RESTRICTION_FRAC 0.01 +#define DFLT_EXTREME_RESTRICTION_PERCENT 1 /**@}*/ STATIC double get_max_sample_threshold(void); @@ -416,13 +445,20 @@ STATIC int get_n_primary_guards(void); STATIC int get_internet_likely_down_interval(void); STATIC int get_nonprimary_guard_connect_timeout(void); STATIC int get_nonprimary_guard_idle_timeout(void); +STATIC double get_meaningful_restriction_threshold(void); +STATIC double get_extreme_restriction_threshold(void); // ---------- XXXX these functions and definitions are post-prop271. HANDLE_DECL(entry_guard, entry_guard_t, STATIC) -STATIC guard_selection_t *guard_selection_new(const char *name); +STATIC guard_selection_t *guard_selection_new(const char *name, + guard_selection_type_t type); STATIC guard_selection_t *get_guard_selection_by_name( - const char *name, int create_if_absent); + const char *name, guard_selection_type_t type, int create_if_absent); STATIC void guard_selection_free(guard_selection_t *gs); +STATIC const char *choose_guard_selection(const or_options_t *options, + const networkstatus_t *ns, + const char *old_selection, + guard_selection_type_t *type_out); STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs, const uint8_t *rsa_id); diff --git a/src/or/main.c b/src/or/main.c index 96ff442c68..25f8b1270c 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -980,8 +980,11 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs) /* if we have enough dir info, then update our guard status with * whatever we just learned. */ int invalidate_circs = guards_update_all(); - // This shouldn't be able to occur at this point. - tor_assert_nonfatal(! invalidate_circs); + + if (invalidate_circs) { + circuit_mark_all_unused_circs(); + circuit_mark_all_dirty_circs_as_unusable(); + } /* Don't even bother trying to get extrainfo until the rest of our * directory info is up-to-date */ diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index eaaadce152..4595e5d675 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1243,30 +1243,30 @@ test_entry_guard_get_guard_selection_by_name(void *arg) (void)arg; guard_selection_t *gs1, *gs2, *gs3; - gs1 = get_guard_selection_by_name("unlikely", 0); + gs1 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 0); tt_assert(gs1 == NULL); - gs1 = get_guard_selection_by_name("unlikely", 1); + gs1 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 1); tt_assert(gs1 != NULL); - gs2 = get_guard_selection_by_name("unlikely", 1); + gs2 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 1); tt_assert(gs2 == gs1); - gs2 = get_guard_selection_by_name("unlikely", 0); + gs2 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 0); tt_assert(gs2 == gs1); - gs2 = get_guard_selection_by_name("implausible", 0); + gs2 = get_guard_selection_by_name("implausible", GS_TYPE_NORMAL, 0); tt_assert(gs2 == NULL); - gs2 = get_guard_selection_by_name("implausible", 1); + gs2 = get_guard_selection_by_name("implausible", GS_TYPE_NORMAL, 1); tt_assert(gs2 != NULL); tt_assert(gs2 != gs1); - gs3 = get_guard_selection_by_name("implausible", 0); + gs3 = get_guard_selection_by_name("implausible", GS_TYPE_NORMAL, 0); tt_assert(gs3 == gs2); - gs3 = get_guard_selection_by_name("default", 0); + gs3 = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); tt_assert(gs3 == NULL); - gs3 = get_guard_selection_by_name("default", 1); + gs3 = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 1); tt_assert(gs3 != NULL); tt_assert(gs3 != gs2); tt_assert(gs3 != gs1); - tt_assert(gs3 == get_guard_selection_info()); + // XXXX prop271 re-enable this. tt_assert(gs3 == get_guard_selection_info()); #if 0 or_options_t *options = get_options_mutable(); @@ -1287,7 +1287,7 @@ static void test_entry_guard_add_single_guard(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); /* 1: Add a single guard to the sample. */ node_t *n1 = smartlist_get(big_fake_net_nodes, 0); @@ -1327,7 +1327,7 @@ static void test_entry_guard_node_filter(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); bridge_line_t *bl = NULL; /* Initialize a bunch of node objects that are all guards. */ @@ -1416,7 +1416,7 @@ static void test_entry_guard_expand_sample(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); digestmap_t *node_by_id = digestmap_new(); entry_guard_t *guard = entry_guards_expand_sample(gs); @@ -1510,7 +1510,7 @@ static void test_entry_guard_expand_sample_small_net(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); /* Fun corner case: not enough guards to make up our whole sample size. */ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, { @@ -1543,7 +1543,7 @@ test_entry_guard_update_from_consensus_status(void *arg) (void)arg; int i; time_t start = approx_time(); - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); networkstatus_t *ns_tmp = NULL; /* Don't randomly backdate stuff; it will make correctness harder to check.*/ @@ -1648,7 +1648,7 @@ test_entry_guard_update_from_consensus_repair(void *arg) (void)arg; int i; time_t start = approx_time(); - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); /* Don't randomly backdate stuff; it will make correctness harder to check.*/ MOCK(randomize_time, mock_randomize_time_no_randomization); @@ -1711,7 +1711,7 @@ test_entry_guard_update_from_consensus_remove(void *arg) (void)arg; //int i; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); smartlist_t *keep_ids = smartlist_new(); smartlist_t *remove_ids = smartlist_new(); @@ -1809,7 +1809,7 @@ test_entry_guard_confirming_guards(void *arg) (void)arg; /* Now let's check the logic responsible for manipulating the list * of confirmed guards */ - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); MOCK(randomize_time, mock_randomize_time_no_randomization); /* Create the sample. */ @@ -1879,7 +1879,7 @@ static void test_entry_guard_sample_reachable_filtered(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); entry_guards_expand_sample(gs); const int N = 10000; bitarray_t *selected = NULL; @@ -1950,7 +1950,7 @@ static void test_entry_guard_sample_reachable_filtered_empty(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); /* What if we try to sample from a set of 0? */ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, n->is_possible_guard = 0); @@ -1966,7 +1966,7 @@ static void test_entry_guard_retry_unreachable(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); entry_guards_expand_sample(gs); /* Let's say that we have two guards, and they're down. @@ -2025,7 +2025,7 @@ static void test_entry_guard_manage_primary(void *arg) { (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); smartlist_t *prev_guards = smartlist_new(); /* If no guards are confirmed, we should pick a few reachable guards and @@ -2146,7 +2146,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) { /* Simpler cases: no gaurds are confirmed yet. */ (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); /* simple starting configuration */ entry_guards_update_primary(gs); @@ -2230,7 +2230,7 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) guards, we use a confirmed guard. */ (void)arg; int i; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); const int N_CONFIRMED = 10; /* slightly more complicated simple starting configuration */ @@ -2307,7 +2307,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) /* Play around with selecting primary guards for circuits and markign * them up and down */ (void)arg; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); time_t start = approx_time(); @@ -2426,7 +2426,7 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) * primary, and we should get it next time. */ time_t start = approx_time(); - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); circuit_guard_state_t *guard = NULL; int i, r; const node_t *node = NULL; @@ -2488,7 +2488,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) * primary, and we should get it next time. */ time_t start = approx_time(); - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); circuit_guard_state_t *guard = NULL, *guard2 = NULL; int i, r; const node_t *node = NULL; @@ -2566,7 +2566,7 @@ test_entry_guard_select_and_cancel(void *arg) int i,r; const node_t *node = NULL; circuit_guard_state_t *guard; - guard_selection_t *gs = guard_selection_new("default"); + guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); entry_guard_t *g; /* Once more, we mark all the primary guards down. */ @@ -2624,7 +2624,8 @@ static void * upgrade_circuits_setup(const struct testcase_t *testcase) { upgrade_circuits_data_t *data = tor_malloc_zero(sizeof(*data)); - guard_selection_t *gs = data->gs = guard_selection_new("default"); + guard_selection_t *gs = data->gs = + guard_selection_new("default", GS_TYPE_NORMAL); circuit_guard_state_t *guard; const node_t *node; entry_guard_t *g; From 6dcbc24a4e9da3d46dc9fa1c225982f7088a6e34 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Nov 2016 10:50:36 -0500 Subject: [PATCH 47/80] Add a backpointer from entry_guard_t to guard_selection_t This is safe, because no entry_guard_t ever outlives its guard_selection_t. I want this because now that multiple guard selections can be active during one tor session, we should make sure that any information we register about guards is with respect to the selection that they came from. --- src/or/entrynodes.c | 52 +++++++++++++++++++++++++++++++++++---------- src/or/entrynodes.h | 1 + 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 59205a8bcc..e0626cfbe0 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -722,6 +722,7 @@ entry_guard_add_to_sample(guard_selection_t *gs, guard->is_reachable = GUARD_REACHABLE_MAYBE; smartlist_add(gs->sampled_entry_guards, guard); + guard->in_selection = gs; entry_guard_set_filtered_flags(get_options(), gs, guard); entry_guards_changed_for_guard_selection(gs); return guard; @@ -1743,6 +1744,8 @@ entry_guard_succeeded(guard_selection_t *gs, if (! guard) return -1; + tor_assert(gs == guard->in_selection); // XXXX prop271 remove argument + unsigned newstate = entry_guards_note_guard_success(gs, guard, (*guard_state_p)->state); @@ -1772,6 +1775,8 @@ entry_guard_cancel(guard_selection_t *gs, if (! guard) return; + tor_assert(gs == guard->in_selection); // XXXX prop271 remove argument + /* XXXX prop271 -- last_tried_to_connect_at will be erroneous here, but this * function will only get called in "bug" cases anyway. */ guard->is_pending = 0; @@ -1798,6 +1803,8 @@ entry_guard_failed(guard_selection_t *gs, if (! guard) return; + tor_assert(gs == guard->in_selection); // XXXX prop271 remove argument + entry_guards_note_guard_failure(gs, guard); (*guard_state_p)->state = GUARD_CIRC_STATE_DEAD; @@ -1876,7 +1883,7 @@ circ_state_has_higher_priority(origin_circuit_t *a, } /** - * Look at all of the origin_circuit_t * objects in all_circuits, + * Look at all of the origin_circuit_t * objects in all_circuits_in, * and see if any of them that were previously not ready to use for * guard-related reasons are now ready to use. Place those circuits * in newly_complete_out, and mark them COMPLETE. @@ -1885,11 +1892,11 @@ circ_state_has_higher_priority(origin_circuit_t *a, */ int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, - const smartlist_t *all_circuits, + const smartlist_t *all_circuits_in, smartlist_t *newly_complete_out) { tor_assert(gs); - tor_assert(all_circuits); + tor_assert(all_circuits_in); tor_assert(newly_complete_out); if (! entry_guards_all_primary_guards_are_down(gs)) { @@ -1904,10 +1911,24 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, int n_complete = 0; origin_circuit_t *best_waiting_circuit = NULL; origin_circuit_t *best_complete_circuit = NULL; - SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { + smartlist_t *all_circuits = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(all_circuits_in, origin_circuit_t *, circ) { + // We filter out circuits that aren't ours, or which we can't + // reason about. circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); if (state == NULL) continue; + entry_guard_t *guard = entry_guard_handle_get(state->guard); + if (!guard || guard->in_selection != gs) + continue; + + smartlist_add(all_circuits, circ); + } SMARTLIST_FOREACH_END(circ); + + SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { + circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); + if BUG((state == NULL)) + continue; if (state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD) { ++n_waiting; @@ -1927,7 +1948,7 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, if (! best_waiting_circuit) { log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits, " "but didn't find any."); - return 0; + goto no_change; } if (best_complete_circuit) { @@ -1940,8 +1961,7 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, "%d complete and %d guard-stalled. At least one complete " "circuit had higher priority, so not upgrading.", n_complete, n_waiting); - - return 0; + goto no_change; } } @@ -1959,7 +1979,7 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, approx_time() - get_nonprimary_guard_connect_timeout(); SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); - if (state == NULL) + if (BUG(state == NULL)) continue; if (state->state != GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD) continue; @@ -1973,7 +1993,7 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, "%d guard-stalled, but %d pending circuit(s) had higher " "guard priority, so not upgrading.", n_waiting, n_blockers_found); - return 0; + goto no_change; } /* Okay. We have a best waiting circuit, and we aren't waiting for @@ -1982,7 +2002,7 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, int n_succeeded = 0; SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); - if (state == NULL) + if (BUG(state == NULL)) continue; if (state->state != GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD) continue; @@ -2001,7 +2021,12 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, n_waiting, n_complete, n_succeeded); tor_assert_nonfatal(n_succeeded >= 1); + smartlist_free(all_circuits); return 1; + + no_change: + smartlist_free(all_circuits); + return 0; } /** @@ -2431,6 +2456,7 @@ entry_guards_load_guards_from_state(or_state_t *state, int set) GS_TYPE_INFER, 1); tor_assert(gs); smartlist_add(gs->sampled_entry_guards, guard); + guard->in_selection = gs; } else { entry_guard_free(guard); } @@ -2925,6 +2951,7 @@ add_an_entry_guard(guard_selection_t *gs, smartlist_insert(gs->chosen_entry_guards, 0, entry); else smartlist_add(gs->chosen_entry_guards, entry); + entry->in_selection = gs; control_event_guard(entry->nickname, entry->identity, "NEW"); control_event_guard_deferred(); @@ -3127,6 +3154,7 @@ remove_all_entry_guards_for_guard_selection(guard_selection_t *gs) void remove_all_entry_guards(void) { + // XXXX prop271 this function shouldn't exist, in the new order. remove_all_entry_guards_for_guard_selection(get_guard_selection_info()); } @@ -4037,6 +4065,8 @@ entry_guards_parse_state_for_guard_selection( smartlist_free(gs->chosen_entry_guards); } gs->chosen_entry_guards = new_entry_guards; + SMARTLIST_FOREACH(new_entry_guards, entry_guard_t *, e, + e->in_selection = gs); /* XXX hand new_entry_guards to this func, and move it up a * few lines, so we don't have to re-dirty it */ @@ -4429,7 +4459,7 @@ guards_update_all(void) if (curr_guard_context->type == GS_TYPE_LEGACY) { entry_guards_compute_status(get_options(), approx_time()); } else { - if (entry_guards_update_all(get_guard_selection_info())) + if (entry_guards_update_all(curr_guard_context)) mark_circuits = 1; } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 0164667d22..97cc4d254b 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -125,6 +125,7 @@ struct entry_guard_t { * Which selection does this guard belong to? */ char *selection_name; + guard_selection_t *in_selection; /* ==== Non-persistent fields. */ /* == These are used by sampled guards */ From 89f5f149df984bab00de9868a9305b611c4aa17e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Nov 2016 11:04:28 -0500 Subject: [PATCH 48/80] Remove guard_selection argument from status-reporting functions This prevents us from mixing up multiple guard_selections --- src/or/circuitbuild.c | 3 +-- src/or/circuitlist.c | 2 +- src/or/circuituse.c | 2 +- src/or/connection.c | 2 +- src/or/connection_or.c | 6 ++---- src/or/directory.c | 9 ++++----- src/or/entrynodes.c | 31 ++++++++++--------------------- src/or/entrynodes.h | 12 ++++-------- src/test/test_entrynodes.c | 26 +++++++++++++------------- 9 files changed, 37 insertions(+), 56 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 16b53f6e21..5d0a04fe71 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -976,8 +976,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) } r = 1; } else { - r = entry_guard_succeeded(get_guard_selection_info(), - &circ->guard_state); + r = entry_guard_succeeded(&circ->guard_state); } const int is_usable_for_streams = (r == 1); if (r == 1) { diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 0afe2f8059..b25f817d91 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -898,7 +898,7 @@ circuit_free(circuit_t *circ) /* Cancel before freeing, if we haven't already succeeded or failed. */ if (ocirc->guard_state) { - entry_guard_cancel(get_guard_selection_info(), ô->guard_state); + entry_guard_cancel(ô->guard_state); } circuit_guard_state_free(ocirc->guard_state); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index b925729e01..698b158457 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1653,7 +1653,7 @@ circuit_build_failed(origin_circuit_t *circ) } if (n_chan_id && !already_marked) { if (circ->guard_state) - entry_guard_failed(get_guard_selection_info(), &circ->guard_state); + entry_guard_failed(&circ->guard_state); /* XXXX prop271 -- old API */ entry_guard_register_connect_status(n_chan_id, 0, 1, time(NULL)); /* if there are any one-hop streams waiting on this circuit, fail diff --git a/src/or/connection.c b/src/or/connection.c index 25c75ff101..87f0f91fd2 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -636,7 +636,7 @@ connection_free_(connection_t *conn) rend_data_free(dir_conn->rend_data); if (dir_conn->guard_state) { /* Cancel before freeing, if it's still there. */ - entry_guard_cancel(get_guard_selection_info(), &dir_conn->guard_state); + entry_guard_cancel(&dir_conn->guard_state); } circuit_guard_state_free(dir_conn->guard_state); } diff --git a/src/or/connection_or.c b/src/or/connection_or.c index fefcc86932..14d597960e 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -735,8 +735,7 @@ connection_or_about_to_close(or_connection_t *or_conn) const or_options_t *options = get_options(); connection_or_note_state_when_broken(or_conn); rep_hist_note_connect_failed(or_conn->identity_digest, now); - entry_guard_chan_failed(get_guard_selection_info(), - TLS_CHAN_TO_BASE(or_conn->chan)); + entry_guard_chan_failed(TLS_CHAN_TO_BASE(or_conn->chan)); /* XXXX prop271 -- old API */ entry_guard_register_connect_status(or_conn->identity_digest,0, !options->HTTPSProxy, now); @@ -1676,8 +1675,7 @@ connection_or_client_learned_peer_id(or_connection_t *conn, "Tried connecting to router at %s:%d, but identity key was not " "as expected: wanted %s but got %s.%s", conn->base_.address, conn->base_.port, expected, seen, extra_log); - entry_guard_chan_failed(get_guard_selection_info(), - TLS_CHAN_TO_BASE(conn->chan)); + entry_guard_chan_failed(TLS_CHAN_TO_BASE(conn->chan)); /* XXXX prop271 old API */ entry_guard_register_connect_status(conn->identity_digest, 0, 1, time(NULL)); diff --git a/src/or/directory.c b/src/or/directory.c index 4164672f10..6fc8809448 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -902,7 +902,7 @@ connection_dir_request_failed(dir_connection_t *conn) if (conn->guard_state) { /* We haven't seen a success on this guard state, so consider it to have * failed. */ - entry_guard_failed(get_guard_selection_info(), &conn->guard_state); + entry_guard_failed(&conn->guard_state); } if (directory_conn_is_self_reachability_test(conn)) { return; /* this was a test fetch. don't retry. */ @@ -1271,7 +1271,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, // In this case we should not have picked a directory guard. if (BUG(guard_state)) { - entry_guard_cancel(get_guard_selection_info(), &guard_state); + entry_guard_cancel(&guard_state); } switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr, @@ -1313,7 +1313,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, // In this case we should not have a directory guard; we'll // get a regular guard later when we build the circuit. if (BUG(anonymized_connection && guard_state)) { - entry_guard_cancel(get_guard_selection_info(), &guard_state); + entry_guard_cancel(&guard_state); } conn->guard_state = guard_state; @@ -2580,8 +2580,7 @@ connection_dir_process_inbuf(dir_connection_t *conn) */ /* XXXXprop271 should we count this as only a partial success somehow? */ - entry_guard_succeeded(get_guard_selection_info(), - &conn->guard_state); + entry_guard_succeeded(&conn->guard_state); circuit_guard_state_free(conn->guard_state); conn->guard_state = NULL; } diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index e0626cfbe0..0795d19026 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1731,8 +1731,7 @@ entry_guard_pick_for_circuit(guard_selection_t *gs, * XXXXX prop271 tristates are ugly; reconsider that interface. */ int -entry_guard_succeeded(guard_selection_t *gs, - circuit_guard_state_t **guard_state_p) +entry_guard_succeeded(circuit_guard_state_t **guard_state_p) { if (get_options()->UseDeprecatedGuardAlgorithm) return 1; @@ -1741,13 +1740,12 @@ entry_guard_succeeded(guard_selection_t *gs, return -1; entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard); - if (! guard) + if (! guard || BUG(guard->in_selection == NULL)) return -1; - tor_assert(gs == guard->in_selection); // XXXX prop271 remove argument - unsigned newstate = - entry_guards_note_guard_success(gs, guard, (*guard_state_p)->state); + entry_guards_note_guard_success(guard->in_selection, guard, + (*guard_state_p)->state); (*guard_state_p)->state = newstate; (*guard_state_p)->state_set_at = approx_time(); @@ -1763,10 +1761,8 @@ entry_guard_succeeded(guard_selection_t *gs, * success or failure. It is safe to call this function if success or * failure _has_ already been declared. */ void -entry_guard_cancel(guard_selection_t *gs, - circuit_guard_state_t **guard_state_p) +entry_guard_cancel(circuit_guard_state_t **guard_state_p) { - (void) gs; if (get_options()->UseDeprecatedGuardAlgorithm) return; if (BUG(*guard_state_p == NULL)) @@ -1775,8 +1771,6 @@ entry_guard_cancel(guard_selection_t *gs, if (! guard) return; - tor_assert(gs == guard->in_selection); // XXXX prop271 remove argument - /* XXXX prop271 -- last_tried_to_connect_at will be erroneous here, but this * function will only get called in "bug" cases anyway. */ guard->is_pending = 0; @@ -1790,8 +1784,7 @@ entry_guard_cancel(guard_selection_t *gs, * not working, and advances the state of the guard module. */ void -entry_guard_failed(guard_selection_t *gs, - circuit_guard_state_t **guard_state_p) +entry_guard_failed(circuit_guard_state_t **guard_state_p) { if (get_options()->UseDeprecatedGuardAlgorithm) return; @@ -1800,12 +1793,10 @@ entry_guard_failed(guard_selection_t *gs, return; entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard); - if (! guard) + if (! guard || BUG(guard->in_selection == NULL)) return; - tor_assert(gs == guard->in_selection); // XXXX prop271 remove argument - - entry_guards_note_guard_failure(gs, guard); + entry_guards_note_guard_failure(guard->in_selection, guard); (*guard_state_p)->state = GUARD_CIRC_STATE_DEAD; (*guard_state_p)->state_set_at = approx_time(); @@ -1816,10 +1807,8 @@ entry_guard_failed(guard_selection_t *gs, * pending on chan. */ void -entry_guard_chan_failed(guard_selection_t *gs, - channel_t *chan) +entry_guard_chan_failed(channel_t *chan) { - tor_assert(gs); if (!chan) return; if (get_options()->UseDeprecatedGuardAlgorithm) @@ -1832,7 +1821,7 @@ entry_guard_chan_failed(guard_selection_t *gs, continue; origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); - entry_guard_failed(gs, &origin_circ->guard_state); + entry_guard_failed(&origin_circ->guard_state); } SMARTLIST_FOREACH_END(circ); smartlist_free(pending); } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 97cc4d254b..0abbea8cb6 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -351,14 +351,10 @@ void circuit_guard_state_free(circuit_guard_state_t *state); int entry_guard_pick_for_circuit(guard_selection_t *gs, const node_t **chosen_node_out, circuit_guard_state_t **guard_state_out); -int entry_guard_succeeded(guard_selection_t *gs, - circuit_guard_state_t **guard_state_p); -void entry_guard_failed(guard_selection_t *gs, - circuit_guard_state_t **guard_state_p); -void entry_guard_cancel(guard_selection_t *gs, - circuit_guard_state_t **guard_state_p); -void entry_guard_chan_failed(guard_selection_t *gs, - channel_t *chan); +int entry_guard_succeeded(circuit_guard_state_t **guard_state_p); +void entry_guard_failed(circuit_guard_state_t **guard_state_p); +void entry_guard_cancel(circuit_guard_state_t **guard_state_p); +void entry_guard_chan_failed(channel_t *chan); int entry_guards_update_all(guard_selection_t *gs); int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, const smartlist_t *all_circuits, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 4595e5d675..bc0862a93e 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -2333,7 +2333,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) /* Call that circuit successful. */ update_approx_time(start+15); - r = entry_guard_succeeded(gs, &guard); + r = entry_guard_succeeded(&guard); tt_int_op(r, OP_EQ, 1); /* We can use it now. */ tt_assert(guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); @@ -2365,7 +2365,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) /* It's failed! What will happen to our poor guard? */ update_approx_time(start+45); - entry_guard_failed(gs, &guard); + entry_guard_failed(&guard); tt_assert(guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_DEAD); tt_i64_op(guard->state_set_at, OP_EQ, start+45); @@ -2401,7 +2401,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) /* Call this one up; watch it get confirmed. */ update_approx_time(start+90); - r = entry_guard_succeeded(gs, &guard); + r = entry_guard_succeeded(&guard); tt_int_op(r, OP_EQ, 1); /* We can use it now. */ tt_assert(guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); @@ -2441,7 +2441,7 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) tt_assert(guard); tt_assert(r == 0); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); - entry_guard_failed(gs, &guard); + entry_guard_failed(&guard); circuit_guard_state_free(guard); guard = NULL; node = NULL; @@ -2461,7 +2461,7 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) tt_int_op(g->is_pending, OP_EQ, 1); (void)start; - r = entry_guard_succeeded(gs, &guard); + r = entry_guard_succeeded(&guard); /* We're on the internet (by fiat), so this guard will get called "confirmed" * and should immediately become primary. * XXXX prop271 -- I don't like that behavior, but it's what is specified @@ -2508,7 +2508,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) g = entry_guard_handle_get(guard->guard); make_guard_confirmed(gs, g); tt_int_op(g->is_primary, OP_EQ, 1); - entry_guard_failed(gs, &guard); + entry_guard_failed(&guard); circuit_guard_state_free(guard); tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_NO); guard = NULL; @@ -2530,7 +2530,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) update_approx_time(start + 3600); /* Say that guard has succeeded! */ - r = entry_guard_succeeded(gs, &guard); + r = entry_guard_succeeded(&guard); tt_int_op(r, OP_EQ, 0); // can't use it yet. tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); g = entry_guard_handle_get(guard->guard); @@ -2546,7 +2546,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) r = entry_guard_pick_for_circuit(gs, &node, &guard2); tt_assert(r == 0); tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); - r = entry_guard_succeeded(gs, &guard2); + r = entry_guard_succeeded(&guard2); tt_assert(r == 1); tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); @@ -2578,7 +2578,7 @@ test_entry_guard_select_and_cancel(void *arg) tt_int_op(g->is_primary, OP_EQ, 1); tt_int_op(g->is_pending, OP_EQ, 0); make_guard_confirmed(gs, g); - entry_guard_failed(gs, &guard); + entry_guard_failed(&guard); circuit_guard_state_free(guard); guard = NULL; node = NULL; @@ -2597,7 +2597,7 @@ test_entry_guard_select_and_cancel(void *arg) tt_int_op(g->is_pending, OP_EQ, 1); /* Whoops! We should never have asked for this guard. Cancel the request! */ - entry_guard_cancel(gs, &guard); + entry_guard_cancel(&guard); tt_assert(guard == NULL); tt_int_op(g->is_primary, OP_EQ, 0); tt_int_op(g->is_pending, OP_EQ, 0); @@ -2649,7 +2649,7 @@ upgrade_circuits_setup(const struct testcase_t *testcase) entry_guard_pick_for_circuit(gs, &node, &guard); g = entry_guard_handle_get(guard->guard); make_guard_confirmed(gs, g); - entry_guard_failed(gs, &guard); + entry_guard_failed(&guard); circuit_guard_state_free(guard); } @@ -2682,14 +2682,14 @@ upgrade_circuits_setup(const struct testcase_t *testcase) int r; update_approx_time(data->start + 32); if (make_circ1_succeed) { - r = entry_guard_succeeded(gs, &data->guard1_state); + r = entry_guard_succeeded(&data->guard1_state); tor_assert(r == 0); tor_assert(data->guard1_state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); } update_approx_time(data->start + 33); if (make_circ2_succeed) { - r = entry_guard_succeeded(gs, &data->guard2_state); + r = entry_guard_succeeded(&data->guard2_state); tor_assert(r == 0); tor_assert(data->guard2_state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); From 1d52ac4d3f67a6e3fac3602f87d00c14060068ab Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 08:04:41 -0500 Subject: [PATCH 49/80] Lay down some infrastructure for bridges in the New Guard Order. This includes: * making bridge_info_t exposed but opaque * allowing guards where we don't know an identity * making it possible to learn the identity of a guard * creating a guard that lacks a node_t * remembering a guard's address and port. * Looking up a guard by address and port. * Only enforcing the rule that we need a live consensus to update the "listed" status for guards when we are not using bridges. --- src/common/address.c | 8 ++ src/common/address.h | 2 + src/or/bridges.c | 44 +++++++++- src/or/bridges.h | 6 ++ src/or/entrynodes.c | 161 ++++++++++++++++++++++++++++++++++--- src/or/entrynodes.h | 18 ++++- src/test/test_entrynodes.c | 10 +++ 7 files changed, 233 insertions(+), 16 deletions(-) diff --git a/src/common/address.c b/src/common/address.c index 773e688554..1bb0c077d1 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -2121,3 +2121,11 @@ tor_addr_port_new(const tor_addr_t *addr, uint16_t port) return ap; } +/** Return true iff a and b are the same address and port */ +int +tor_addr_port_eq(const tor_addr_port_t *a, + const tor_addr_port_t *b) +{ + return tor_addr_eq(&a->addr, &b->addr) && a->port == b->port; +} + diff --git a/src/common/address.h b/src/common/address.h index 51db42c315..41daf012e6 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -342,6 +342,8 @@ get_interface_address_list(int severity, int include_internal) } tor_addr_port_t *tor_addr_port_new(const tor_addr_t *addr, uint16_t port); +int tor_addr_port_eq(const tor_addr_port_t *a, + const tor_addr_port_t *b); #ifdef ADDRESS_PRIVATE MOCK_DECL(smartlist_t *,get_interface_addresses_raw,(int severity, diff --git a/src/or/bridges.c b/src/or/bridges.c index 2170cc668a..f16acfa28a 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -28,7 +28,9 @@ /** Information about a configured bridge. Currently this just matches the * ones in the torrc file, but one day we may be able to learn about new * bridges on our own, and remember them in the state file. */ -typedef struct { +struct bridge_info_t { + /** Address and port of the bridge, as configured by the user.*/ + tor_addr_port_t addrport_configured; /** Address of the bridge. */ tor_addr_t addr; /** TLS port for the bridge. */ @@ -49,7 +51,7 @@ typedef struct { /** A smartlist of k=v values to be passed to the SOCKS proxy, if transports are used for this bridge. */ smartlist_t *socks_args; -} bridge_info_t; +}; static void bridge_free(bridge_info_t *bridge); @@ -111,6 +113,40 @@ bridge_free(bridge_info_t *bridge) tor_free(bridge); } +/** Return a list of all the configured bridges, as bridge_info_t pointers. */ +const smartlist_t * +bridge_list_get(void) +{ + if (!bridge_list) + bridge_list = smartlist_new(); + return bridge_list; +} + +/** + * Given a bridge, return a pointer to its RSA identity digest, or + * NULL if we don't know one for it. + */ +const uint8_t * +bridge_get_rsa_id_digest(const bridge_info_t *bridge) +{ + tor_assert(bridge); + if (tor_digest_is_zero(bridge->identity)) + return NULL; + else + return (const uint8_t *) bridge->identity; +} + +/** + * Given a bridge, return a pointer to its configured addr:port + * combination. + */ +const tor_addr_port_t * +bridge_get_addr_port(const bridge_info_t *bridge) +{ + tor_assert(bridge); + return &bridge->addrport_configured; +} + /** If we have a bridge configured whose digest matches digest, or a * bridge with no known digest whose address matches any of the * tor_addr_port_t's in orports, return that bridge. Else return @@ -243,6 +279,7 @@ learned_router_identity(const tor_addr_t *addr, uint16_t port, hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port), transport_info ? transport_info : ""); tor_free(transport_info); + // XXXX prop271 here. we will need to update the guard info too. } } @@ -361,6 +398,8 @@ bridge_add_from_config(bridge_line_t *bridge_line) bridge_line->transport_name); b = tor_malloc_zero(sizeof(bridge_info_t)); + tor_addr_copy(&b->addrport_configured.addr, &bridge_line->addr); + b->addrport_configured.port = bridge_line->port; tor_addr_copy(&b->addr, &bridge_line->addr); b->port = bridge_line->port; memcpy(b->identity, bridge_line->digest, DIGEST_LEN); @@ -718,6 +757,7 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) fmt_and_decorate_addr(&bridge->addr), (int) bridge->port); } + // XXXX prop271 here we will need to update the guard info too. add_bridge_as_entry_guard(get_guard_selection_info(), node); log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, diff --git a/src/or/bridges.h b/src/or/bridges.h index 738b1a68ee..d01794f573 100644 --- a/src/or/bridges.h +++ b/src/or/bridges.h @@ -14,8 +14,14 @@ struct bridge_line_t; +/* Opaque handle to a configured bridge */ +typedef struct bridge_info_t bridge_info_t; + void mark_bridge_list(void); void sweep_bridge_list(void); +const smartlist_t *bridge_list_get(void); +const uint8_t *bridge_get_rsa_id_digest(const bridge_info_t *bridge); +const tor_addr_port_t * bridge_get_addr_port(const bridge_info_t *bridge); int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port, const char *digest); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 0795d19026..e725d4e5ae 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -163,6 +163,10 @@ static int node_is_possible_guard(guard_selection_t *gs, const node_t *node); static int node_passes_guard_filter(const or_options_t *options, guard_selection_t *gs, const node_t *node); +static entry_guard_t *entry_guard_add_to_sample_impl(guard_selection_t *gs, + const uint8_t *rsa_id_digest, + const char *nickname, + const tor_addr_port_t *bridge_addrport); /** Return 0 if we should apply guardfraction information found in the * consensus. A specific consensus can be specified with the @@ -693,25 +697,47 @@ STATIC entry_guard_t * entry_guard_add_to_sample(guard_selection_t *gs, const node_t *node) { - const int GUARD_LIFETIME = get_guard_lifetime_days() * 86400; - tor_assert(gs); - tor_assert(node); - log_info(LD_GUARD, "Adding %s as to the entry guard sample set.", node_describe(node)); + return entry_guard_add_to_sample_impl(gs, + (const uint8_t*)node->identity, + node_get_nickname(node), + NULL); +} + +/** + * Backend: adds a new sampled guard to gs, with given identity, + * nickname, and ORPort. rsa_id_digest and bridge_addrport are + * optional, but we need one of them. nickname is optional. + */ +static entry_guard_t * +entry_guard_add_to_sample_impl(guard_selection_t *gs, + const uint8_t *rsa_id_digest, + const char *nickname, + const tor_addr_port_t *bridge_addrport) +{ + const int GUARD_LIFETIME = get_guard_lifetime_days() * 86400; + tor_assert(gs); + // XXXX prop271 take ed25519 identity here too. /* make sure that the guard is not already sampled. */ - if (BUG(have_sampled_guard_with_id(gs, (uint8_t*)node->identity))) + if (rsa_id_digest && BUG(have_sampled_guard_with_id(gs, rsa_id_digest))) + return NULL; // LCOV_EXCL_LINE + /* Make sure we can actually identify the guard. */ + if (BUG(!rsa_id_digest && !bridge_addrport)) return NULL; // LCOV_EXCL_LINE entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); /* persistent fields */ + guard->is_persistent = (rsa_id_digest != NULL); guard->selection_name = tor_strdup(gs->name); - memcpy(guard->identity, node->identity, DIGEST_LEN); - strlcpy(guard->nickname, node_get_nickname(node), sizeof(guard->nickname)); + if (rsa_id_digest) + memcpy(guard->identity, rsa_id_digest, DIGEST_LEN); + if (nickname) + strlcpy(guard->nickname, nickname, sizeof(guard->nickname)); guard->sampled_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10); tor_free(guard->sampled_by_version); guard->sampled_by_version = tor_strdup(VERSION); @@ -720,6 +746,8 @@ entry_guard_add_to_sample(guard_selection_t *gs, /* non-persistent fields */ guard->is_reachable = GUARD_REACHABLE_MAYBE; + if (bridge_addrport) + guard->bridge_addr = tor_memdup(bridge_addrport, sizeof(*bridge_addrport)); smartlist_add(gs->sampled_entry_guards, guard); guard->in_selection = gs; @@ -728,6 +756,87 @@ entry_guard_add_to_sample(guard_selection_t *gs, return guard; } +/** + * Add an entry guard to the "bridges" guard selection sample, with + * information taken from bridge. Return that entry guard. + */ +entry_guard_t * +entry_guard_add_bridge_to_sample(const bridge_info_t *bridge) +{ + guard_selection_t *gs = get_guard_selection_by_name("bridges", + GS_TYPE_BRIDGE, + 1); + const uint8_t *id_digest = bridge_get_rsa_id_digest(bridge); + const tor_addr_port_t *addrport = bridge_get_addr_port(bridge); + + tor_assert(addrport); + + return entry_guard_add_to_sample_impl(gs, id_digest, NULL, addrport); +} + +/** + * Return the entry_guard_t in gs whose address is addrport, + * or NULL if none exists. +*/ +static entry_guard_t * +entry_guard_get_by_bridge_addr(guard_selection_t *gs, + const tor_addr_port_t *addrport) +{ + if (! gs) + return NULL; + if (BUG(!addrport)) + return NULL; + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, g) { + if (g->bridge_addr && tor_addr_port_eq(addrport, g->bridge_addr)) + return g; + } SMARTLIST_FOREACH_END(g); + return NULL; +} + +/** Update the guard subsystem's knowledge of the identity of the bridge + * at addrport. Idempotent. + */ +void +entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, + const uint8_t *rsa_id_digest) +{ + guard_selection_t *gs = get_guard_selection_by_name("bridges", + GS_TYPE_BRIDGE, + 0); + if (!gs) + return; + + entry_guard_t *g = entry_guard_get_by_bridge_addr(gs, addrport); + if (!g) + return; + + int make_persistent = 0; + + if (tor_digest_is_zero(g->identity)) { + memcpy(g->identity, rsa_id_digest, DIGEST_LEN); + make_persistent = 1; + } else if (tor_memeq(g->identity, rsa_id_digest, DIGEST_LEN)) { + /* Nothing to see here; we learned something we already knew. */ + if (BUG(! g->is_persistent)) + make_persistent = 1; + } else { + char old_id[HEX_DIGEST_LEN+1]; + base16_encode(old_id, sizeof(old_id), g->identity, sizeof(g->identity)); + log_warn(LD_BUG, "We 'learned' an identity %s for a bridge at %s:%d, but " + "we already knew a different one (%s). Ignoring the new info as " + "possibly bogus.", + hex_str((const char *)rsa_id_digest, DIGEST_LEN), + fmt_and_decorate_addr(&addrport->addr), addrport->port, + old_id); + return; // redundant, but let's be clear: we're not making this persistent. + } + + if (make_persistent) { + g->is_persistent = 1; + entry_guards_changed_for_guard_selection(gs); + } +} + /** * Return the number of sampled guards in gs that are "filtered" * (that is, we're willing to connect to them) and that are "usable" @@ -892,14 +1001,16 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) // It's important to use only a live consensus here; we don't want to // make changes based on anything expired or old. - networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); + if (gs->type != GS_TYPE_BRIDGE) { + networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); - log_info(LD_GUARD, "Updating sampled guard status based on received " - "consensus."); + log_info(LD_GUARD, "Updating sampled guard status based on received " + "consensus."); - if (! ns || ns->valid_until < approx_time()) { - log_info(LD_GUARD, "Hey, that consensus isn't still valid. Ignoring."); - return; + if (! ns || ns->valid_until < approx_time()) { + log_info(LD_GUARD, "Hey, there wasn't a valid consensus. Ignoring"); + return; + } } int n_changes = 0; @@ -2070,6 +2181,11 @@ entry_guard_encode_for_state(entry_guard_t *guard) smartlist_add_asprintf(result, "in=%s", guard->selection_name); smartlist_add_asprintf(result, "rsa_id=%s", hex_str(guard->identity, DIGEST_LEN)); + if (guard->bridge_addr) { + smartlist_add_asprintf(result, "bridge_addr=%s:%d", + fmt_and_decorate_addr(&guard->bridge_addr->addr), + guard->bridge_addr->port); + } if (strlen(guard->nickname)) { smartlist_add_asprintf(result, "nickname=%s", guard->nickname); } @@ -2152,6 +2268,7 @@ entry_guard_parse_from_state(const char *s) char *listed = NULL; char *confirmed_on = NULL; char *confirmed_idx = NULL; + char *bridge_addr = NULL; // pathbias char *pb_use_attempts = NULL; @@ -2180,6 +2297,7 @@ entry_guard_parse_from_state(const char *s) FIELD(listed); FIELD(confirmed_on); FIELD(confirmed_idx); + FIELD(bridge_addr); FIELD(pb_use_attempts); FIELD(pb_use_successes); FIELD(pb_circ_attempts); @@ -2218,6 +2336,7 @@ entry_guard_parse_from_state(const char *s) } entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); + guard->is_persistent = 1; if (in == NULL) { log_warn(LD_CIRC, "Guard missing 'in' field"); @@ -2247,6 +2366,16 @@ entry_guard_parse_from_state(const char *s) guard->identity, DIGEST_LEN); } + if (bridge_addr) { + tor_addr_port_t res; + memset(&res, 0, sizeof(res)); + int r = tor_addr_port_parse(LOG_WARN, bridge_addr, + &res.addr, &res.port, -1); + if (r == 0) + guard->bridge_addr = tor_memdup(&res, sizeof(res)); + /* On error, we already warned. */ + } + /* Process the various time fields. */ #define HANDLE_TIME(field) do { \ @@ -2357,6 +2486,7 @@ entry_guard_parse_from_state(const char *s) tor_free(listed); tor_free(confirmed_on); tor_free(confirmed_idx); + tor_free(bridge_addr); tor_free(pb_use_attempts); tor_free(pb_use_successes); tor_free(pb_circ_attempts); @@ -2388,6 +2518,8 @@ entry_guards_update_guards_in_state(or_state_t *state) if (!strcmp(gs->name, "legacy")) continue; /* This is encoded differently. */ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + if (guard->is_persistent == 0) + continue; *nextline = tor_malloc_zero(sizeof(config_line_t)); (*nextline)->key = tor_strdup("Guard"); (*nextline)->value = entry_guard_encode_for_state(guard); @@ -2908,6 +3040,7 @@ add_an_entry_guard(guard_selection_t *gs, return NULL; } entry = tor_malloc_zero(sizeof(entry_guard_t)); + entry->is_persistent = 1; log_info(LD_CIRC, "Chose %s as new entry guard.", node_describe(node)); strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); @@ -3020,6 +3153,7 @@ entry_guard_free(entry_guard_t *e) tor_free(e->sampled_by_version); tor_free(e->extra_state_fields); tor_free(e->selection_name); + tor_free(e->bridge_addr); tor_free(e); } @@ -3840,6 +3974,7 @@ entry_guards_parse_state_for_guard_selection( node = tor_malloc_zero(sizeof(entry_guard_t)); /* all entry guards on disk have been contacted */ node->made_contact = 1; + node->is_persistent = 1; smartlist_add(new_entry_guards, node); smartlist_split_string(args, line->value, " ", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 0abbea8cb6..cbc3f89cb4 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -125,7 +125,9 @@ struct entry_guard_t { * Which selection does this guard belong to? */ char *selection_name; - guard_selection_t *in_selection; + + /** Bridges only: address of the bridge. */ + tor_addr_port_t *bridge_addr; /* ==== Non-persistent fields. */ /* == These are used by sampled guards */ @@ -140,6 +142,9 @@ struct entry_guard_t { * to try again until it either succeeds or fails. Primary guards can * never be pending. */ unsigned is_pending : 1; + /** If true, don't write this guard to disk. (Used for bridges with unknown + * identities) */ + unsigned is_persistent : 1; /** When did we get the earliest connection failure for this guard? * We clear this field on a successful connect. We do _not_ clear it * when we mark the guard as "MAYBE" reachable. @@ -160,6 +165,9 @@ struct entry_guard_t { /** This string holds any fields that we are maintaining because * we saw them in the state, even if we don't understand them. */ char *extra_state_fields; + + /** Backpointer to the guard selection that this guard belongs to. */ + guard_selection_t *in_selection; /**@}*/ /** @@ -554,6 +562,14 @@ STATIC int entry_is_time_to_retry(const entry_guard_t *e, time_t now); void remove_all_entry_guards_for_guard_selection(guard_selection_t *gs); void remove_all_entry_guards(void); +struct bridge_info_t; +// XXXX prop271 should this be a public API? +entry_guard_t *entry_guard_add_bridge_to_sample( + const struct bridge_info_t *bridge); + +void entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, + const uint8_t *rsa_id_digest); + void entry_guards_compute_status_for_guard_selection( guard_selection_t *gs, const or_options_t *options, time_t now); void entry_guards_compute_status(const or_options_t *options, time_t now); diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index bc0862a93e..32af7ffd5d 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1055,6 +1055,9 @@ test_entry_guard_encode_for_state_maximal(void *arg) strlcpy(eg->nickname, "Fred", sizeof(eg->nickname)); eg->selection_name = tor_strdup("default"); memcpy(eg->identity, "plurpyflurpyslurpydo", DIGEST_LEN); + eg->bridge_addr = tor_malloc_zero(sizeof(tor_addr_port_t)); + tor_addr_from_ipv4h(&eg->bridge_addr->addr, 0x08080404); + eg->bridge_addr->port = 9999; eg->sampled_on_date = 1479081600; eg->sampled_by_version = tor_strdup("1.2.3"); eg->unlisted_since_date = 1479081645; @@ -1069,6 +1072,7 @@ test_entry_guard_encode_for_state_maximal(void *arg) tt_str_op(s, OP_EQ, "in=default " "rsa_id=706C75727079666C75727079736C75727079646F " + "bridge_addr=8.8.4.4:9999 " "nickname=Fred " "sampled_on=2016-11-14T00:00:00 " "sampled_by=1.2.3 " @@ -1100,6 +1104,7 @@ test_entry_guard_parse_from_state_minimal(void *arg) test_mem_op_hex(eg->identity, OP_EQ, "596f75206d6179206e656564206120686f626279"); tt_str_op(eg->nickname, OP_EQ, "$596F75206D6179206E656564206120686F626279"); + tt_ptr_op(eg->bridge_addr, OP_EQ, NULL); tt_i64_op(eg->sampled_on_date, OP_GE, t); tt_i64_op(eg->sampled_on_date, OP_LE, t+86400); tt_i64_op(eg->unlisted_since_date, OP_EQ, 0); @@ -1126,6 +1131,7 @@ test_entry_guard_parse_from_state_maximal(void *arg) eg = entry_guard_parse_from_state( "in=fred " "rsa_id=706C75727079666C75727079736C75727079646F " + "bridge_addr=[1::3]:9999 " "nickname=Fred " "sampled_on=2016-11-14T00:00:00 " "sampled_by=1.2.3 " @@ -1139,6 +1145,8 @@ test_entry_guard_parse_from_state_maximal(void *arg) test_mem_op_hex(eg->identity, OP_EQ, "706C75727079666C75727079736C75727079646F"); + tt_str_op(fmt_addr(&eg->bridge_addr->addr), OP_EQ, "1::3"); + tt_int_op(eg->bridge_addr->port, OP_EQ, 9999); tt_str_op(eg->nickname, OP_EQ, "Fred"); tt_i64_op(eg->sampled_on_date, OP_EQ, 1479081600); tt_i64_op(eg->unlisted_since_date, OP_EQ, 1479081645); @@ -1205,6 +1213,7 @@ test_entry_guard_parse_from_state_partial_failure(void *arg) eg = entry_guard_parse_from_state( "in=default " "rsa_id=706C75727079666C75727079736C75727079646F " + "bridge_addr=1.2.3.3.4:5 " "nickname=FredIsANodeWithAStrangeNicknameThatIsTooLong " "sampled_on=2016-11-14T00:00:99 " "sampled_by=1.2.3 stuff in the middle " @@ -1219,6 +1228,7 @@ test_entry_guard_parse_from_state_partial_failure(void *arg) test_mem_op_hex(eg->identity, OP_EQ, "706C75727079666C75727079736C75727079646F"); tt_str_op(eg->nickname, OP_EQ, "FredIsANodeWithAStrangeNicknameThatIsTooL"); + tt_ptr_op(eg->bridge_addr, OP_EQ, NULL); tt_i64_op(eg->sampled_on_date, OP_EQ, t); tt_i64_op(eg->unlisted_since_date, OP_EQ, 0); tt_str_op(eg->sampled_by_version, OP_EQ, "1.2.3"); From 53f248f6c9d71784c271cf14501ec4c28e5e885d Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 10:14:42 -0500 Subject: [PATCH 50/80] Add some needed accessors/inspectors for bridge/guard convergence --- src/or/bridges.c | 24 ++++------------------- src/or/bridges.h | 5 +++++ src/or/entrynodes.c | 48 ++++++++++++++++++++++++++++++++++++++++++--- src/or/entrynodes.h | 4 ---- src/or/routerset.c | 13 ++++++++++++ src/or/routerset.h | 5 ++++- 6 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/or/bridges.c b/src/or/bridges.c index f16acfa28a..8090bae268 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -179,7 +179,7 @@ get_configured_bridge_by_orports_digest(const char *digest, * bridge with no known digest whose address matches addr:port, * return that bridge. Else return NULL. If digest is NULL, check for * address/port matches only. */ -static bridge_info_t * +bridge_info_t * get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, uint16_t port, const char *digest) @@ -416,28 +416,12 @@ bridge_add_from_config(bridge_line_t *bridge_line) smartlist_add(bridge_list, b); } -/** Return true iff routerset contains the bridge bridge. */ -static int -routerset_contains_bridge(const routerset_t *routerset, - const bridge_info_t *bridge) -{ - int result; - extend_info_t *extinfo; - tor_assert(bridge); - if (!routerset) - return 0; - - extinfo = extend_info_new( - NULL, bridge->identity, NULL, NULL, &bridge->addr, bridge->port); - result = routerset_contains_extendinfo(routerset, extinfo); - extend_info_free(extinfo); - return result; -} - /** If digest is one of our known bridges, return it. */ -static bridge_info_t * +bridge_info_t * find_bridge_by_digest(const char *digest) { + if (! bridge_list) + return NULL; SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge, { if (tor_memeq(bridge->identity, digest, DIGEST_LEN)) diff --git a/src/or/bridges.h b/src/or/bridges.h index d01794f573..74c5113231 100644 --- a/src/or/bridges.h +++ b/src/or/bridges.h @@ -20,8 +20,13 @@ typedef struct bridge_info_t bridge_info_t; void mark_bridge_list(void); void sweep_bridge_list(void); const smartlist_t *bridge_list_get(void); +bridge_info_t *find_bridge_by_digest(const char *digest); const uint8_t *bridge_get_rsa_id_digest(const bridge_info_t *bridge); const tor_addr_port_t * bridge_get_addr_port(const bridge_info_t *bridge); +bridge_info_t *get_configured_bridge_by_addr_port_digest( + const tor_addr_t *addr, + uint16_t port, + const char *digest); int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port, const char *digest); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index e725d4e5ae..dcaab352d0 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -167,6 +167,8 @@ static entry_guard_t *entry_guard_add_to_sample_impl(guard_selection_t *gs, const uint8_t *rsa_id_digest, const char *nickname, const tor_addr_port_t *bridge_addrport); +static entry_guard_t *get_sampled_guard_by_bridge_addr(guard_selection_t *gs, + const tor_addr_port_t *addrport); /** Return 0 if we should apply guardfraction information found in the * consensus. A specific consensus can be specified with the @@ -679,6 +681,46 @@ get_sampled_guard_with_id(guard_selection_t *gs, return NULL; } +/** If gs contains a sampled entry guard matching bridge, + * return that guard. Otherwise return NULL. */ +static entry_guard_t * +get_sampled_guard_for_bridge(guard_selection_t *gs, + const bridge_info_t *bridge) +{ + const uint8_t *id = bridge_get_rsa_id_digest(bridge); + const tor_addr_port_t *addrport = bridge_get_addr_port(bridge); + entry_guard_t *guard; + if (id) { + guard = get_sampled_guard_with_id(gs, id); + if (guard) + return guard; + } + if (BUG(!addrport)) + return NULL; // LCOV_EXCL_LINE + guard = get_sampled_guard_by_bridge_addr(gs, addrport); + if (! guard || (id && tor_memneq(id, guard->identity, DIGEST_LEN))) + return NULL; + else + return guard; +} + +/** If we know a bridge_info_t matching guard, return that + * bridge. Otherwise return NULL. */ +static bridge_info_t * +get_bridge_info_for_guard(const entry_guard_t *guard) +{ + if (! tor_digest_is_zero(guard->identity)) { + bridge_info_t *bridge = find_bridge_by_digest(guard->identity); + if (bridge) + return bridge; + } + if (BUG(guard->bridge_addr == NULL)) + return NULL; + return get_configured_bridge_by_addr_port_digest(&guard->bridge_addr->addr, + guard->bridge_addr->port, + NULL); +} + /** * Return true iff we have a sampled guard with the RSA identity digest * rsa_id. */ @@ -779,8 +821,8 @@ entry_guard_add_bridge_to_sample(const bridge_info_t *bridge) * or NULL if none exists. */ static entry_guard_t * -entry_guard_get_by_bridge_addr(guard_selection_t *gs, - const tor_addr_port_t *addrport) +get_sampled_guard_by_bridge_addr(guard_selection_t *gs, + const tor_addr_port_t *addrport) { if (! gs) return NULL; @@ -806,7 +848,7 @@ entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, if (!gs) return; - entry_guard_t *g = entry_guard_get_by_bridge_addr(gs, addrport); + entry_guard_t *g = get_sampled_guard_by_bridge_addr(gs, addrport); if (!g) return; diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index cbc3f89cb4..21dab6ea18 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -563,10 +563,6 @@ void remove_all_entry_guards_for_guard_selection(guard_selection_t *gs); void remove_all_entry_guards(void); struct bridge_info_t; -// XXXX prop271 should this be a public API? -entry_guard_t *entry_guard_add_bridge_to_sample( - const struct bridge_info_t *bridge); - void entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, const uint8_t *rsa_id_digest); diff --git a/src/or/routerset.c b/src/or/routerset.c index 4182dbc5c4..d0df0a74e6 100644 --- a/src/or/routerset.c +++ b/src/or/routerset.c @@ -28,6 +28,7 @@ #define ROUTERSET_PRIVATE #include "or.h" +#include "bridges.h" #include "geoip.h" #include "nodelist.h" #include "policies.h" @@ -334,6 +335,18 @@ routerset_contains_node(const routerset_t *set, const node_t *node) return 0; } +/** Return true iff routerset contains the bridge bridge. */ +int +routerset_contains_bridge(const routerset_t *set, const bridge_info_t *bridge) +{ + const char *id = (const char*)bridge_get_rsa_id_digest(bridge); + const tor_addr_port_t *addrport = bridge_get_addr_port(bridge); + + tor_assert(addrport); + return routerset_contains(set, &addrport->addr, addrport->port, + NULL, id, -1); +} + /** Add every known node_t that is a member of routerset to * out, but never add any that are part of excludeset. * If running_only, only add the running ones. */ diff --git a/src/or/routerset.h b/src/or/routerset.h index c2f7205c3e..2e3b4b0fe0 100644 --- a/src/or/routerset.h +++ b/src/or/routerset.h @@ -26,8 +26,11 @@ int routerset_contains_routerstatus(const routerset_t *set, country_t country); int routerset_contains_extendinfo(const routerset_t *set, const extend_info_t *ei); - +struct bridge_info_t; +int routerset_contains_bridge(const routerset_t *set, + const struct bridge_info_t *bridge); int routerset_contains_node(const routerset_t *set, const node_t *node); + void routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, const routerset_t *excludeset, int running_only); From 82fa71610de1c7d7faed78490a3cb90ce917a3e2 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 10:19:10 -0500 Subject: [PATCH 51/80] Implement bridge backends for sampling, filtering guards. Still missing is functionality for picking bridges when we don't know a descriptor for them yet, and functionality for learning a bridge ID. Everything else remains (basically) the same. Neat! --- src/or/entrynodes.c | 185 +++++++++++++++++++++++++++++++++----------- 1 file changed, 139 insertions(+), 46 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index dcaab352d0..6ac316671d 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -802,12 +802,10 @@ entry_guard_add_to_sample_impl(guard_selection_t *gs, * Add an entry guard to the "bridges" guard selection sample, with * information taken from bridge. Return that entry guard. */ -entry_guard_t * -entry_guard_add_bridge_to_sample(const bridge_info_t *bridge) +static entry_guard_t * +entry_guard_add_bridge_to_sample(guard_selection_t *gs, + const bridge_info_t *bridge) { - guard_selection_t *gs = get_guard_selection_by_name("bridges", - GS_TYPE_BRIDGE, - 1); const uint8_t *id_digest = bridge_get_rsa_id_digest(bridge); const tor_addr_port_t *addrport = bridge_get_addr_port(bridge); @@ -896,24 +894,34 @@ num_reachable_filtered_guards(guard_selection_t *gs) } /** - * Add new guards to the sampled guards in gs until there are - * enough usable filtered guards, but never grow the sample beyond its - * maximum size. Return the last guard added, or NULL if none were - * added. + * Return a smartlist of the all the guards that are not currently + * members of the sample (GUARDS - SAMPLED_GUARDS). The elements of + * this list are node_t pointers in the non-bridge case, and + * bridge_info_t pointers in the bridge case. Set *n_guards_out/b> + * to the number of guards that we found in GUARDS, including those + * that were already sampled. */ -STATIC entry_guard_t * -entry_guards_expand_sample(guard_selection_t *gs) +static smartlist_t * +get_eligible_guards(guard_selection_t *gs, + int *n_guards_out) { - tor_assert(gs); - int n_sampled = smartlist_len(gs->sampled_entry_guards); - entry_guard_t *added_guard = NULL; - - const smartlist_t *nodes = nodelist_get_list(); /* Construct eligible_guards as GUARDS - SAMPLED_GUARDS */ smartlist_t *eligible_guards = smartlist_new(); int n_guards = 0; // total size of "GUARDS" - int n_usable_filtered_guards = num_reachable_filtered_guards(gs); - { + + if (gs->type == GS_TYPE_BRIDGE) { + const smartlist_t *bridges = bridge_list_get(); + SMARTLIST_FOREACH_BEGIN(bridges, bridge_info_t *, bridge) { + ++n_guards; + if (NULL != get_sampled_guard_for_bridge(gs, bridge)) { + continue; + } + smartlist_add(eligible_guards, bridge); + } SMARTLIST_FOREACH_END(bridge); + } else { + const smartlist_t *nodes = nodelist_get_list(); + const int n_sampled = smartlist_len(gs->sampled_entry_guards); + /* Build a bloom filter of our current guards: let's keep this O(N). */ digestset_t *sampled_guard_ids = digestset_new(n_sampled); SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, const entry_guard_t *, @@ -934,11 +942,58 @@ entry_guards_expand_sample(guard_selection_t *gs) digestset_free(sampled_guard_ids); } - /* Is there at least one guard we haven't sampled? */ - if (! smartlist_len(eligible_guards)) - goto done; + *n_guards_out = n_guards; + return eligible_guards; +} - const int max_sample = (int)(n_guards * get_max_sample_threshold()); +/** Helper: given a smartlist of either bridge_info_t (if gs->type is + * GS_TYPE_BRIDGE) or node_t (otherwise), pick one that can be a guard, + * add it as a guard, remove it from the list, and return a new + * entry_guard_t. Return NULL on failure. */ +static entry_guard_t * +select_and_add_guard_item_for_sample(guard_selection_t *gs, + smartlist_t *eligible_guards) +{ + entry_guard_t *added_guard; + if (gs->type == GS_TYPE_BRIDGE) { + const bridge_info_t *bridge = smartlist_choose(eligible_guards); + if (BUG(!bridge)) + return NULL; // LCOV_EXCL_LINE + smartlist_remove(eligible_guards, bridge); + added_guard = entry_guard_add_bridge_to_sample(gs, bridge); + } else { + const node_t *node = + node_sl_choose_by_bandwidth(eligible_guards, WEIGHT_FOR_GUARD); + if (BUG(!node)) + return NULL; // LCOV_EXCL_LINE + smartlist_remove(eligible_guards, node); + added_guard = entry_guard_add_to_sample(gs, node); + } + + return added_guard; +} + +/** + * Add new guards to the sampled guards in gs until there are + * enough usable filtered guards, but never grow the sample beyond its + * maximum size. Return the last guard added, or NULL if none were + * added. + */ +STATIC entry_guard_t * +entry_guards_expand_sample(guard_selection_t *gs) +{ + tor_assert(gs); + int n_sampled = smartlist_len(gs->sampled_entry_guards); + entry_guard_t *added_guard = NULL; + int n_usable_filtered_guards = num_reachable_filtered_guards(gs); + int n_guards = 0; + smartlist_t *eligible_guards = get_eligible_guards(gs, &n_guards); + + const int using_bridges = (gs->type == GS_TYPE_BRIDGE); + + /* XXXX prop271 spec deviation with bridges, max_sample is "all of them" */ + const int max_sample = using_bridges ? n_guards : + (int)(n_guards * get_max_sample_threshold()); const int min_filtered_sample = get_min_filtered_sample_size(); log_info(LD_GUARD, "Expanding the sample guard set. We have %d guards " @@ -967,12 +1022,7 @@ entry_guards_expand_sample(guard_selection_t *gs) } /* Otherwise we can add at least one new guard. */ - const node_t *node = - node_sl_choose_by_bandwidth(eligible_guards, WEIGHT_FOR_GUARD); - if (BUG(! node)) - goto done; // LCOV_EXCL_LINE -- should be impossible. - - added_guard = entry_guard_add_to_sample(gs, node); + added_guard = select_and_add_guard_item_for_sample(gs, eligible_guards); if (!added_guard) goto done; // LCOV_EXCL_LINE -- only fails on BUG. @@ -980,8 +1030,6 @@ entry_guards_expand_sample(guard_selection_t *gs) if (added_guard->is_usable_filtered_guard) ++n_usable_filtered_guards; - - smartlist_remove(eligible_guards, node); } done: @@ -1029,6 +1077,21 @@ remove_guard_from_confirmed_and_primary_lists(guard_selection_t *gs, } } +/** Return true iff guard is currently "listed" -- that is, it + * appears in the consensus, or as a configured bridge (as + * appropriate) */ +static int +entry_guard_is_listed(guard_selection_t *gs, const entry_guard_t *guard) +{ + if (gs->type == GS_TYPE_BRIDGE) { + return NULL != get_bridge_info_for_guard(guard); + } else { + const node_t *node = node_get_by_id(guard->identity); + + return node && node_is_possible_guard(gs, node); + } +} + /** * Update the status of all sampled guards based on the arrival of a * new consensus networkstatus document. This will include marking @@ -1059,11 +1122,8 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) /* First: Update listed/unlisted. */ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { - /* XXXX prop271 handle bridges right? */ /* XXXX prop271 check ed ID too */ - const node_t *node = node_get_by_id(guard->identity); - - const unsigned is_listed = node && node_is_possible_guard(gs, node); + const int is_listed = entry_guard_is_listed(gs, guard); if (is_listed && ! guard->currently_listed) { ++n_changes; @@ -1113,8 +1173,6 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) /* Then: remove the ones that have been junk for too long */ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { - /* XXXX prop271 handle bridges right? */ - int remove = 0; if (guard->currently_listed == 0 && @@ -1180,20 +1238,48 @@ node_passes_guard_filter(const or_options_t *options, guard_selection_t *gs, /* NOTE: Make sure that this function stays in sync with * options_transition_affects_entry_guards */ + tor_assert(! options->UseBridges); + (void)gs; if (routerset_contains_node(options->ExcludeNodes, node)) return 0; /* XXXX -- prop271 spec deviation -- add entrynodes to spec. */ if (options->EntryNodes && - !options->UseBridges && !routerset_contains_node(options->EntryNodes, node)) return 0; if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) return 0; - if (bool_neq(options->UseBridges, node_is_a_configured_bridge(node))) + if (node_is_a_configured_bridge(node)) + return 0; + + return 1; +} + +/** Helper: Return true iff bridge passes our configuration + * filter-- if it is a relay that we are configured to be able to + * connect to. */ +static int +bridge_passes_guard_filter(const or_options_t *options, + const bridge_info_t *bridge) +{ + tor_assert(options->UseBridges); + tor_assert(bridge); + if (!bridge) + return 0; + + if (routerset_contains_bridge(options->ExcludeNodes, bridge)) + return 0; + + /* Ignore entrynodes */ + const tor_addr_port_t *addrport = bridge_get_addr_port(bridge); + + if (!fascist_firewall_allows_address_addr(&addrport->addr, + addrport->port, + FIREWALL_OR_CONNECTION, + 0, 0)) return 0; return 1; @@ -1212,14 +1298,21 @@ entry_guard_passes_filter(const or_options_t *options, guard_selection_t *gs, if (guard->pb.path_bias_disabled) return 0; - const node_t *node = node_get_by_id(guard->identity); - if (node == NULL) { - // This can happen when currently_listed is true, and we're not updating - // it because we don't have a live consensus. - return 0; - } + if (gs->type == GS_TYPE_BRIDGE) { + const bridge_info_t *bridge = get_bridge_info_for_guard(guard); + if (bridge == NULL) + return 0; + return bridge_passes_guard_filter(options, bridge); + } else { + const node_t *node = node_get_by_id(guard->identity); + if (node == NULL) { + // This can happen when currently_listed is true, and we're not updating + // it because we don't have a live consensus. + return 0; + } - return node_passes_guard_filter(options, gs, node); + return node_passes_guard_filter(options, gs, node); + } } /** @@ -3126,7 +3219,7 @@ add_an_entry_guard(guard_selection_t *gs, /** Entry point for bridges.c to add a bridge as guard. * - * XXXX prop271 refactor.*/ + * XXXX prop271 refactor, bridge.*/ void add_bridge_as_entry_guard(guard_selection_t *gs, const node_t *chosen) From 3bcbbea350ccab4bc25b191fcce1dd3fc63775d3 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 11:26:55 -0500 Subject: [PATCH 52/80] More progress on bridge implementation with prop271 guards Here we handle most (all?) of the remaining tasks, and fix some bugs, in the prop271 bridge implementation. * We record bridge identities as we learn them. * We only call deprecated functions from bridges.c when the deprecated guard algorithm is in use. * We update any_bridge_descriptors_known() and num_bridges_usable() to work correctly with the new backend code. (Previously, they called into the guard selection logic. * We update bridge directory fetches to work with the new guard code. * We remove some erroneous assertions where we assumed that we'd never load a guard that wasn't for the current selection. Also, we fix a couple of typos. --- src/or/bridges.c | 34 ++++++++++++++++++++++++++-------- src/or/directory.c | 29 +++++++++++++++++++---------- src/or/entrynodes.c | 38 ++++++++++++++++++++++++++------------ 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/or/bridges.c b/src/or/bridges.c index 8090bae268..c480e3fb7b 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -279,7 +279,8 @@ learned_router_identity(const tor_addr_t *addr, uint16_t port, hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port), transport_info ? transport_info : ""); tor_free(transport_info); - // XXXX prop271 here. we will need to update the guard info too. + entry_guard_learned_bridge_identity(&bridge->addrport_configured, + (const uint8_t *)digest); } } @@ -741,16 +742,21 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) fmt_and_decorate_addr(&bridge->addr), (int) bridge->port); } - // XXXX prop271 here we will need to update the guard info too. - add_bridge_as_entry_guard(get_guard_selection_info(), node); + if (get_options()->UseDeprecatedGuardAlgorithm) { + add_bridge_as_entry_guard(get_guard_selection_info(), node); + } else { + entry_guard_learned_bridge_identity(&bridge->addrport_configured, + (const uint8_t*)ri->cache_info.identity_digest); + } log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, from_cache ? "cached" : "fresh", router_describe(ri)); /* set entry->made_contact so if it goes down we don't drop it from * our entry node list */ - // XXXX prop271 use new interface here when we hit bridges? - entry_guard_register_connect_status(ri->cache_info.identity_digest, - 1, 0, now); + if (get_options()->UseDeprecatedGuardAlgorithm) { + entry_guard_register_connect_status(ri->cache_info.identity_digest, + 1, 0, now); + } if (first) { routerlist_retry_directory_downloads(now); } @@ -768,8 +774,20 @@ int any_bridge_descriptors_known(void) { tor_assert(get_options()->UseBridges); - // XXXX prop271 this needs to get fixed. -- bridges - return choose_random_entry(NULL) != NULL; + + if (!bridge_list) + return 0; + + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { + const node_t *node; + if (!tor_digest_is_zero(bridge->identity) && + (node = node_get_by_id(bridge->identity)) != NULL && + node->ri) { + return 1; + } + } SMARTLIST_FOREACH_END(bridge); + + return 0; } /** Return a smartlist containing all bridge identity digests */ diff --git a/src/or/directory.c b/src/or/directory.c index 6fc8809448..9c039a006f 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -560,26 +560,35 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( * sort of dir fetch we'll be doing, so it won't return a bridge * that can't answer our question. */ - // XXXX prop271 update this for bridge support. - const node_t *node = choose_random_dirguard(type); + const node_t *node = guards_choose_dirguard(type, + &guard_state); if (node && node->ri) { /* every bridge has a routerinfo. */ routerinfo_t *ri = node->ri; /* clients always make OR connections to bridges */ tor_addr_port_t or_ap; + tor_addr_port_t nil_dir_ap; /* we are willing to use a non-preferred address if we need to */ fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0, &or_ap); - directory_initiate_command(&or_ap.addr, or_ap.port, - NULL, 0, /*no dirport*/ - ri->cache_info.identity_digest, - dir_purpose, - router_purpose, - DIRIND_ONEHOP, - resource, NULL, 0, if_modified_since); - } else + tor_addr_make_null(&nil_dir_ap.addr, AF_INET); + nil_dir_ap.port = 0; + directory_initiate_command_rend(&or_ap, + &nil_dir_ap, + ri->cache_info.identity_digest, + dir_purpose, + router_purpose, + DIRIND_ONEHOP, + resource, NULL, 0, if_modified_since, + NULL, guard_state); + } else { + if (guard_state) { + entry_guard_cancel(&guard_state); + } log_notice(LD_DIR, "Ignoring directory request, since no bridge " "nodes are available yet."); + } + return; } else { if (prefer_authority || (type & BRIDGE_DIRINFO)) { diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 6ac316671d..004081e86c 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -271,7 +271,7 @@ create_initial_guard_context(void) tor_assert(name); // "name" can only be NULL if we had an old name. tor_assert(type != GS_TYPE_INFER); log_notice(LD_GUARD, "Starting with guard context \"%s\"", name); - curr_guard_context = get_guard_selection_by_name_and_type(name, type); + curr_guard_context = get_guard_selection_by_name(name, type, 1); } /** Get current default guard_selection_t, creating it if necessary */ @@ -1234,13 +1234,10 @@ node_passes_guard_filter(const or_options_t *options, guard_selection_t *gs, const node_t *node) { /* XXXX prop271 remote the gs option; it is unused, and sometimes NULL. */ + (void)gs; /* NOTE: Make sure that this function stays in sync with * options_transition_affects_entry_guards */ - - tor_assert(! options->UseBridges); - - (void)gs; if (routerset_contains_node(options->ExcludeNodes, node)) return 0; @@ -1265,7 +1262,6 @@ static int bridge_passes_guard_filter(const or_options_t *options, const bridge_info_t *bridge) { - tor_assert(options->UseBridges); tor_assert(bridge); if (!bridge) return 0; @@ -1765,7 +1761,7 @@ select_entry_guard_for_circuit(guard_selection_t *gs, unsigned *state_out) /** * Note that we failed to connect to or build circuits through guard. - * Use with a guard returned by select_entry_guards_for_circuit(). + * Use with a guard returned by select_entry_guard_for_circuit(). */ STATIC void entry_guards_note_guard_failure(guard_selection_t *gs, @@ -3818,12 +3814,30 @@ choose_random_dirguard(dirinfo_type_t type) int num_bridges_usable(void) { - tor_assert(get_options()->UseDeprecatedGuardAlgorithm); - int n_options = 0; - tor_assert(get_options()->UseBridges); - (void) choose_random_entry_impl(get_guard_selection_info(), - NULL, 0, 0, &n_options); + + if (get_options()->UseDeprecatedGuardAlgorithm) { + + tor_assert(get_options()->UseBridges); + (void) choose_random_entry_impl(get_guard_selection_info(), + NULL, 0, 0, &n_options); + } else { + /* XXXX prop271 Is this quite right? */ + tor_assert(get_options()->UseBridges); + guard_selection_t *gs = get_guard_selection_info(); + tor_assert(gs->type == GS_TYPE_BRIDGE); + + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + if (guard->is_reachable == GUARD_REACHABLE_NO) + continue; + if (tor_digest_is_zero(guard->identity)) + continue; + const node_t *node = node_get_by_id(guard->identity); + if (node && node->ri) + ++n_options; + } SMARTLIST_FOREACH_END(guard); + } + return n_options; } From 46619ec9143450b181a8510011d3e3fd92542aa4 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 11:34:37 -0500 Subject: [PATCH 53/80] Note some large functions that could be split. George Kadianakis pointed these out. --- src/or/entrynodes.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 004081e86c..af1869f045 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1099,6 +1099,7 @@ entry_guard_is_listed(guard_selection_t *gs, const entry_guard_t *guard) STATIC void sampled_guards_update_from_consensus(guard_selection_t *gs) { + /*XXXX prop271 consider splitting this function up. */ tor_assert(gs); const int REMOVE_UNLISTED_GUARDS_AFTER = (get_remove_unlisted_guards_after_days() * 86400); @@ -1503,6 +1504,7 @@ make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard) STATIC void entry_guards_update_primary(guard_selection_t *gs) { + /*XXXX prop271 consider splitting this function up. */ tor_assert(gs); // prevent recursion. Recursion is potentially very bad here. @@ -1697,6 +1699,7 @@ entry_guards_note_internet_connectivity(guard_selection_t *gs) STATIC entry_guard_t * select_entry_guard_for_circuit(guard_selection_t *gs, unsigned *state_out) { + /*XXXX prop271 consider splitting this function up. */ tor_assert(gs); tor_assert(state_out); From 84bfa895d725338d92f677a31a4dcf6381845e0c Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 11:47:12 -0500 Subject: [PATCH 54/80] Change return value of entry_guard_succeeded to an enum. George pointed out that (-1,0,1) for (never usable, maybe usable later, usable right now) was a pretty rotten convention that made the code harder to read. --- src/or/circuitbuild.c | 13 +++++++------ src/or/entrynodes.c | 28 +++++++++++++--------------- src/or/entrynodes.h | 7 ++++++- src/test/test_entrynodes.c | 29 ++++++++++++++++------------- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 5d0a04fe71..c7e116e853 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -964,28 +964,29 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) memset(&ec, 0, sizeof(ec)); if (!hop) { /* done building the circuit. whew. */ - int r; + guard_usable_t r; if (get_options()->UseDeprecatedGuardAlgorithm) { // The circuit is usable; we already marked the guard as okay. - r = 1; + r = GUARD_USABLE_NOW; } else if (! circ->guard_state) { if (circuit_get_cpath_len(circ) != 1) { log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " "guard state", circuit_get_cpath_len(circ), circ, circ->base_.purpose); } - r = 1; + r = GUARD_USABLE_NOW; } else { r = entry_guard_succeeded(&circ->guard_state); } - const int is_usable_for_streams = (r == 1); - if (r == 1) { + const int is_usable_for_streams = (r == GUARD_USABLE_NOW); + if (r == GUARD_USABLE_NOW) { circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); - } else if (r == 0) { + } else if (r == GUARD_MAYBE_USABLE_LATER) { // XXXX prop271 we might want to probe for whether this // XXXX one is ready even before the next second rolls over. circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT); } else { + tor_assert_nonfatal(r == GUARD_USABLE_NEVER); return - END_CIRC_REASON_INTERNAL; } diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index af1869f045..aa90566cf7 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1965,28 +1965,26 @@ entry_guard_pick_for_circuit(guard_selection_t *gs, } /** - * Called by the circuit building module when a circuit has succeeded: - * informs the guards code that the guard in *guard_state_p is - * working, and advances the state of the guard module. On a -1 return - * value, the circuit is broken and should not be used. On a 1 return - * value, the circuit is ready to use. On a 0 return value, the circuit - * should not be used until we find out whether preferred guards will - * work for us. - * - * XXXXX prop271 tristates are ugly; reconsider that interface. + * Called by the circuit building module when a circuit has succeeded: informs + * the guards code that the guard in *guard_state_p is working, and + * advances the state of the guard module. On a GUARD_USABLE_NEVER return + * value, the circuit is broken and should not be used. On a GUARD_USABLE_NOW + * return value, the circuit is ready to use. On a GUARD_MAYBE_USABLE_LATER + * return value, the circuit should not be used until we find out whether + * preferred guards will work for us. */ -int +guard_usable_t entry_guard_succeeded(circuit_guard_state_t **guard_state_p) { if (get_options()->UseDeprecatedGuardAlgorithm) - return 1; + return GUARD_USABLE_NOW; if (BUG(*guard_state_p == NULL)) - return -1; + return GUARD_USABLE_NEVER; entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard); if (! guard || BUG(guard->in_selection == NULL)) - return -1; + return GUARD_USABLE_NEVER; unsigned newstate = entry_guards_note_guard_success(guard->in_selection, guard, @@ -1996,9 +1994,9 @@ entry_guard_succeeded(circuit_guard_state_t **guard_state_p) (*guard_state_p)->state_set_at = approx_time(); if (newstate == GUARD_CIRC_STATE_COMPLETE) { - return 1; + return GUARD_USABLE_NOW; } else { - return 0; + return GUARD_MAYBE_USABLE_LATER; } } diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 21dab6ea18..ceccd0ff10 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -359,7 +359,12 @@ void circuit_guard_state_free(circuit_guard_state_t *state); int entry_guard_pick_for_circuit(guard_selection_t *gs, const node_t **chosen_node_out, circuit_guard_state_t **guard_state_out); -int entry_guard_succeeded(circuit_guard_state_t **guard_state_p); +typedef enum { + GUARD_USABLE_NEVER = -1, + GUARD_MAYBE_USABLE_LATER = 0, + GUARD_USABLE_NOW = 1, +} guard_usable_t; +guard_usable_t entry_guard_succeeded(circuit_guard_state_t **guard_state_p); void entry_guard_failed(circuit_guard_state_t **guard_state_p); void entry_guard_cancel(circuit_guard_state_t **guard_state_p); void entry_guard_chan_failed(channel_t *chan); diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 32af7ffd5d..6a3048b338 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -2324,6 +2324,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) const node_t *node = NULL; circuit_guard_state_t *guard = NULL; entry_guard_t *g; + guard_usable_t u; /* * Make sure that the pick-for-circuit API basically works. We'll get * a primary guard, so it'll be usable on completion. @@ -2343,8 +2344,8 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) /* Call that circuit successful. */ update_approx_time(start+15); - r = entry_guard_succeeded(&guard); - tt_int_op(r, OP_EQ, 1); /* We can use it now. */ + u = entry_guard_succeeded(&guard); + tt_int_op(u, OP_EQ, GUARD_USABLE_NOW); /* We can use it now. */ tt_assert(guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); g = entry_guard_handle_get(guard->guard); @@ -2411,8 +2412,8 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) /* Call this one up; watch it get confirmed. */ update_approx_time(start+90); - r = entry_guard_succeeded(&guard); - tt_int_op(r, OP_EQ, 1); /* We can use it now. */ + u = entry_guard_succeeded(&guard); + tt_int_op(u, OP_EQ, GUARD_USABLE_NOW); tt_assert(guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); g = entry_guard_handle_get(guard->guard); @@ -2440,6 +2441,7 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) circuit_guard_state_t *guard = NULL; int i, r; const node_t *node = NULL; + guard_usable_t u; /* Declare that we're on the internet. */ entry_guards_note_internet_connectivity(gs); @@ -2471,13 +2473,13 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) tt_int_op(g->is_pending, OP_EQ, 1); (void)start; - r = entry_guard_succeeded(&guard); + u = entry_guard_succeeded(&guard); /* We're on the internet (by fiat), so this guard will get called "confirmed" * and should immediately become primary. * XXXX prop271 -- I don't like that behavior, but it's what is specified */ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); - tt_assert(r == 1); + tt_assert(u == GUARD_USABLE_NOW); tt_int_op(g->confirmed_idx, OP_EQ, 0); tt_int_op(g->is_primary, OP_EQ, 1); tt_int_op(g->is_pending, OP_EQ, 0); @@ -2503,6 +2505,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) int i, r; const node_t *node = NULL; entry_guard_t *g; + guard_usable_t u; /* Declare that we're on the internet. */ entry_guards_note_internet_connectivity(gs); @@ -2540,8 +2543,8 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) update_approx_time(start + 3600); /* Say that guard has succeeded! */ - r = entry_guard_succeeded(&guard); - tt_int_op(r, OP_EQ, 0); // can't use it yet. + u = entry_guard_succeeded(&guard); + tt_int_op(u, OP_EQ, GUARD_MAYBE_USABLE_LATER); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); g = entry_guard_handle_get(guard->guard); @@ -2556,8 +2559,8 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) r = entry_guard_pick_for_circuit(gs, &node, &guard2); tt_assert(r == 0); tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); - r = entry_guard_succeeded(&guard2); - tt_assert(r == 1); + u = entry_guard_succeeded(&guard2); + tt_assert(u == GUARD_USABLE_NOW); tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); tt_assert(! entry_guards_all_primary_guards_are_down(gs)); @@ -2689,18 +2692,18 @@ upgrade_circuits_setup(const struct testcase_t *testcase) tor_assert(data->guard2_state->state == GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); - int r; + guard_usable_t r; update_approx_time(data->start + 32); if (make_circ1_succeed) { r = entry_guard_succeeded(&data->guard1_state); - tor_assert(r == 0); + tor_assert(r == GUARD_MAYBE_USABLE_LATER); tor_assert(data->guard1_state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); } update_approx_time(data->start + 33); if (make_circ2_succeed) { r = entry_guard_succeeded(&data->guard2_state); - tor_assert(r == 0); + tor_assert(r == GUARD_MAYBE_USABLE_LATER); tor_assert(data->guard2_state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD); } From eac8b3f758545f02fd7db58c458de19a6442044b Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 11:59:48 -0500 Subject: [PATCH 55/80] Remove a few unused arguments. --- src/or/entrynodes.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index aa90566cf7..3ba01794da 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -159,9 +159,8 @@ static void entry_guard_set_filtered_flags(const or_options_t *options, entry_guard_t *guard); static void pathbias_check_use_success_count(entry_guard_t *guard); static void pathbias_check_close_success_count(entry_guard_t *guard); -static int node_is_possible_guard(guard_selection_t *gs, const node_t *node); +static int node_is_possible_guard(const node_t *node); static int node_passes_guard_filter(const or_options_t *options, - guard_selection_t *gs, const node_t *node); static entry_guard_t *entry_guard_add_to_sample_impl(guard_selection_t *gs, const uint8_t *rsa_id_digest, @@ -530,9 +529,9 @@ choose_guard_selection(const or_options_t *options, const smartlist_t *nodes = nodelist_get_list(); int n_guards = 0, n_passing_filter = 0; SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) { - if (node_is_possible_guard(NULL, node)) { + if (node_is_possible_guard(node)) { ++n_guards; - if (node_passes_guard_filter(options, NULL, node)) { + if (node_passes_guard_filter(options, node)) { ++n_passing_filter; } } @@ -650,13 +649,12 @@ update_guard_selection_choice(const or_options_t *options) * a possible guard when sampling guards. */ static int -node_is_possible_guard(guard_selection_t *gs, const node_t *node) +node_is_possible_guard(const node_t *node) { /* The "GUARDS" set is all nodes in the nodelist for which this predicate * holds. */ /* XXXX -- prop271 spec deviation. We require node_is_dir() here. */ - (void)gs; /* Remove this argument */ tor_assert(node); return (node->is_possible_guard && node->is_stable && @@ -930,7 +928,7 @@ get_eligible_guards(guard_selection_t *gs, } SMARTLIST_FOREACH_END(guard); SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) { - if (! node_is_possible_guard(gs, node)) + if (! node_is_possible_guard(node)) continue; ++n_guards; if (digestset_contains(sampled_guard_ids, node->identity)) @@ -1088,7 +1086,7 @@ entry_guard_is_listed(guard_selection_t *gs, const entry_guard_t *guard) } else { const node_t *node = node_get_by_id(guard->identity); - return node && node_is_possible_guard(gs, node); + return node && node_is_possible_guard(node); } } @@ -1231,12 +1229,9 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) * Return true iff node is a Tor relay that we are configured to * be able to connect to. */ static int -node_passes_guard_filter(const or_options_t *options, guard_selection_t *gs, +node_passes_guard_filter(const or_options_t *options, const node_t *node) { - /* XXXX prop271 remote the gs option; it is unused, and sometimes NULL. */ - (void)gs; - /* NOTE: Make sure that this function stays in sync with * options_transition_affects_entry_guards */ if (routerset_contains_node(options->ExcludeNodes, node)) @@ -1308,7 +1303,7 @@ entry_guard_passes_filter(const or_options_t *options, guard_selection_t *gs, return 0; } - return node_passes_guard_filter(options, gs, node); + return node_passes_guard_filter(options, node); } } From 80fa404625b757cbde07be76abf848efadab7c46 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 12:48:32 -0500 Subject: [PATCH 56/80] Fix for small test networks: don't refuse to have any sampled guards. Don't restrict the sample size if the network size is less than 20 guards. Maybe we'll think of a better rule later on? --- src/or/entrynodes.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 3ba01794da..8380dbf3d8 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -891,6 +891,23 @@ num_reachable_filtered_guards(guard_selection_t *gs) return n_reachable_filtered_guards; } +/** Return the actual maximum size for the sample in gs, + * given that we know about n_guards total. */ +static int +get_max_sample_size(guard_selection_t *gs, + int n_guards) +{ + const int using_bridges = (gs->type == GS_TYPE_BRIDGE); + + /* XXXX prop271 spec deviation with bridges, max_sample is "all of them" */ + if (using_bridges) + return n_guards; + else if (n_guards < 20) // XXXX prop271 spec deviation + return n_guards; + else + return (int)(n_guards * get_max_sample_threshold()); +} + /** * Return a smartlist of the all the guards that are not currently * members of the sample (GUARDS - SAMPLED_GUARDS). The elements of @@ -987,11 +1004,7 @@ entry_guards_expand_sample(guard_selection_t *gs) int n_guards = 0; smartlist_t *eligible_guards = get_eligible_guards(gs, &n_guards); - const int using_bridges = (gs->type == GS_TYPE_BRIDGE); - - /* XXXX prop271 spec deviation with bridges, max_sample is "all of them" */ - const int max_sample = using_bridges ? n_guards : - (int)(n_guards * get_max_sample_threshold()); + const int max_sample = get_max_sample_size(gs, n_guards); const int min_filtered_sample = get_min_filtered_sample_size(); log_info(LD_GUARD, "Expanding the sample guard set. We have %d guards " From 6c3f555a8c4d33b8f9dcdc55c03bee8170feb65f Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 14:28:43 -0500 Subject: [PATCH 57/80] Re-enable some disabled tests about switching guard_selections --- src/test/test_entrynodes.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 6a3048b338..e3a9d18daa 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1276,18 +1276,18 @@ test_entry_guard_get_guard_selection_by_name(void *arg) tt_assert(gs3 != NULL); tt_assert(gs3 != gs2); tt_assert(gs3 != gs1); - // XXXX prop271 re-enable this. tt_assert(gs3 == get_guard_selection_info()); + tt_assert(gs3 == get_guard_selection_info()); -#if 0 or_options_t *options = get_options_mutable(); options->UseDeprecatedGuardAlgorithm = 1; - gs4 = get_guard_selection_info(); + update_guard_selection_choice(options); + guard_selection_t *gs4 = get_guard_selection_info(); tt_assert(gs4 != gs3); - tt_assert(gs4 == get_guard_selection_by_name("legacy", 1)); + tt_assert(gs4 == get_guard_selection_by_name("legacy", GS_TYPE_LEGACY, 1)); options->UseDeprecatedGuardAlgorithm = 0; + update_guard_selection_choice(options); tt_assert(gs3 == get_guard_selection_info()); -#endif done: entry_guards_free_all(); From f4e64c04f49a3cd8c9b2289bd28641db85441acc Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 14:31:24 -0500 Subject: [PATCH 58/80] Remove some resolved "XXXX prop271" comments. --- src/or/circuituse.c | 3 ++- src/or/connection_or.c | 6 ++++-- src/or/entrynodes.c | 1 - src/or/entrynodes.h | 1 - 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 698b158457..787c490387 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1652,9 +1652,10 @@ circuit_build_failed(origin_circuit_t *circ) "Our circuit died before the first hop with no connection"); } if (n_chan_id && !already_marked) { + /* New guard API: we failed. */ if (circ->guard_state) entry_guard_failed(&circ->guard_state); - /* XXXX prop271 -- old API */ + /* Old guard API: we failed. */ entry_guard_register_connect_status(n_chan_id, 0, 1, time(NULL)); /* if there are any one-hop streams waiting on this circuit, fail * them now so they can retry elsewhere. */ diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 14d597960e..3b6f82c904 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -735,8 +735,9 @@ connection_or_about_to_close(or_connection_t *or_conn) const or_options_t *options = get_options(); connection_or_note_state_when_broken(or_conn); rep_hist_note_connect_failed(or_conn->identity_digest, now); + /* Tell the new guard API about the channel failure */ entry_guard_chan_failed(TLS_CHAN_TO_BASE(or_conn->chan)); - /* XXXX prop271 -- old API */ + /* Tell the old guard API about the channel failure */ entry_guard_register_connect_status(or_conn->identity_digest,0, !options->HTTPSProxy, now); if (conn->state >= OR_CONN_STATE_TLS_HANDSHAKING) { @@ -1675,8 +1676,9 @@ connection_or_client_learned_peer_id(or_connection_t *conn, "Tried connecting to router at %s:%d, but identity key was not " "as expected: wanted %s but got %s.%s", conn->base_.address, conn->base_.port, expected, seen, extra_log); + /* Tell the new guard API about the channel failure */ entry_guard_chan_failed(TLS_CHAN_TO_BASE(conn->chan)); - /* XXXX prop271 old API */ + /* Tell the old guard API about the channel failure */ entry_guard_register_connect_status(conn->identity_digest, 0, 1, time(NULL)); control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 8380dbf3d8..bb8cd4c813 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1928,7 +1928,6 @@ entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b) void circuit_guard_state_free(circuit_guard_state_t *state) { - /* XXXX prop271 -- do we want to inline this structure? */ if (!state) return; entry_guard_handle_free(state->guard); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index ceccd0ff10..4ea60e88fb 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -226,7 +226,6 @@ typedef enum guard_selection_type_t { /** * All of the the context for guard selection on a particular client. * - * (XXXX prop271 this paragraph below is not actually implemented yet.) * We maintain multiple guard selection contexts for a client, depending * aspects on its current configuration -- whether an extremely * restrictive EntryNodes is used, whether UseBridges is enabled, and so From 1e9cd5d2bbbf54818da6b6585bb60298712e6f06 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 14:32:32 -0500 Subject: [PATCH 59/80] Note a couple of XXX-prop271s as spec deviations. --- src/or/control.c | 2 +- src/or/entrynodes.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/control.c b/src/or/control.c index 9cc99b6b96..03742e8731 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -2597,7 +2597,7 @@ getinfo_helper_events(control_connection_t *control_conn, if (circ->base_.state == CIRCUIT_STATE_OPEN) state = "BUILT"; else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) - state = "GUARD_WAIT"; // XXXX prop271 must specify this. + state = "GUARD_WAIT"; // XXXX prop271 spec deviation-- specify this. else if (circ->cpath) state = "EXTENDED"; else diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index bb8cd4c813..c624c64b3f 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -381,8 +381,8 @@ get_max_sample_threshold(void) /** * We always try to make our sample contain at least this many guards. * - * XXXX prop271 There was a MIN_SAMPLE_THRESHOLD in the proposal, but I - * removed it in favor of MIN_FILTERED_SAMPLE_SIZE. -NM + * XXXX prop271 spec deviation There was a MIN_SAMPLE_THRESHOLD in the + * proposal, but I removed it in favor of MIN_FILTERED_SAMPLE_SIZE. -NM */ STATIC int get_min_filtered_sample_size(void) From 9d065ecc3d9e1a34c35be2d3531696798f6ecd3e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2016 14:47:39 -0500 Subject: [PATCH 60/80] Fix a magic number in get_max_sample_size --- src/or/entrynodes.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index c624c64b3f..9630f170f1 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -898,14 +898,17 @@ get_max_sample_size(guard_selection_t *gs, int n_guards) { const int using_bridges = (gs->type == GS_TYPE_BRIDGE); + const int min_sample = get_min_filtered_sample_size(); /* XXXX prop271 spec deviation with bridges, max_sample is "all of them" */ if (using_bridges) return n_guards; - else if (n_guards < 20) // XXXX prop271 spec deviation - return n_guards; + + const int max_sample = (int)(n_guards * get_max_sample_threshold()); + if (max_sample < min_sample) // XXXX prop271 spec deviation + return min_sample; else - return (int)(n_guards * get_max_sample_threshold()); + return max_sample; } /** From 17c3faa2e393c59e9ee4aeca6986b0905d17f3b5 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 07:51:40 -0500 Subject: [PATCH 61/80] guards_choose_dirguard(): replace one XXXX with another. I had been asking myself, "hey, doesn't the new code need to look at this "info" parameter? The old code did!" But it turns out that the old code hasn't, since 05f7336624d6a47b3. So instead of "support this!" the comment now says "we can remove this!" --- src/or/entrynodes.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 9630f170f1..dd3a890a2b 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -4779,7 +4779,13 @@ guards_choose_dirguard(dirinfo_type_t info, if (get_options()->UseDeprecatedGuardAlgorithm) { return choose_random_dirguard(info); } else { - // XXXX prop271 look at info? + /* XXXX prop271 We don't need to look at the dirinfo_type_t here, + * apparently. If you look at the old implementation, and you follow info + * downwards through choose_random_dirguard(), into + * choose_random_entry_impl(), into populate_live_entry_guards()... you + * find out that it isn't even used, and hasn't been since 0.2.7.1-alpha, + * when we realized that every Tor on the network would support + * microdescriptors. -NM */ const node_t *r = NULL; if (entry_guard_pick_for_circuit(get_guard_selection_info(), &r, From 87f9b42179bd23418c3e698938bdeead56da1c43 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 08:49:39 -0500 Subject: [PATCH 62/80] Implement support for per-circuit guard restrictions. This is an important thing I hadn't considered when writing prop271: sometimes you have to restrict what guard you use for a particular circuit. Most frequently, that would be because you plan to use a certain node as your exit, and so you can't choose that for your guard. This change means that the upgrade-waiting-circuits algorithm needs a slight tweak too: circuit A cannot block circuit B from upgrading if circuit B needs to follow a restriction that circuit A does not follow. --- src/or/circuitbuild.c | 15 ++++- src/or/circuitbuild.h | 1 + src/or/entrynodes.c | 122 +++++++++++++++++++++++++++++++------ src/or/entrynodes.h | 38 +++++++++++- src/test/test_entrynodes.c | 92 ++++++++++++++-------------- 5 files changed, 200 insertions(+), 68 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index c7e116e853..07903092b5 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2515,8 +2515,8 @@ extend_info_dup(extend_info_t *info) return newinfo; } -/** Return the routerinfo_t for the chosen exit router in state. - * If there is no chosen exit, or if we don't know the routerinfo_t for +/** Return the node_t for the chosen exit router in state. + * If there is no chosen exit, or if we don't know the node_t for * the chosen exit, return NULL. */ const node_t * @@ -2527,6 +2527,17 @@ build_state_get_exit_node(cpath_build_state_t *state) return node_get_by_id(state->chosen_exit->identity_digest); } +/** Return the RSA ID digest for the chosen exit router in state. + * If there is no chosen exit, return NULL. + */ +const uint8_t * +build_state_get_exit_rsa_id(cpath_build_state_t *state) +{ + if (!state || !state->chosen_exit) + return NULL; + return (const uint8_t *) state->chosen_exit->identity_digest; +} + /** Return the nickname for the chosen exit router in state. If * there is no chosen exit, or if we don't know the routerinfo_t for the * chosen exit, return NULL. diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 2c83a16550..b85dbecf7e 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -61,6 +61,7 @@ int extend_info_supports_ntor(const extend_info_t* ei); int circuit_can_use_tap(const origin_circuit_t *circ); int circuit_has_usable_onion_key(const origin_circuit_t *circ); int extend_info_has_preferred_onion_key(const extend_info_t* ei); +const uint8_t *build_state_get_exit_rsa_id(cpath_build_state_t *state); const node_t *build_state_get_exit_node(cpath_build_state_t *state); const char *build_state_get_exit_nickname(cpath_build_state_t *state); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index dd3a890a2b..9b38641b1e 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -168,6 +168,8 @@ static entry_guard_t *entry_guard_add_to_sample_impl(guard_selection_t *gs, const tor_addr_port_t *bridge_addrport); static entry_guard_t *get_sampled_guard_by_bridge_addr(guard_selection_t *gs, const tor_addr_port_t *addrport); +static int entry_guard_obeys_restriction(const entry_guard_t *guard, + const entry_guard_restriction_t *rst); /** Return 0 if we should apply guardfraction information found in the * consensus. A specific consensus can be specified with the @@ -878,13 +880,20 @@ entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, /** * Return the number of sampled guards in gs that are "filtered" * (that is, we're willing to connect to them) and that are "usable" - * (that is, either "reachable" or "maybe reachable"). */ + * (that is, either "reachable" or "maybe reachable"). + * + * If a restriction is provided in rst, do not count any guards that + * violate it. + */ STATIC int -num_reachable_filtered_guards(guard_selection_t *gs) +num_reachable_filtered_guards(guard_selection_t *gs, + const entry_guard_restriction_t *rst) { int n_reachable_filtered_guards = 0; SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { entry_guard_consider_retry(guard); + if (! entry_guard_obeys_restriction(guard, rst)) + continue; if (guard->is_usable_filtered_guard) ++n_reachable_filtered_guards; } SMARTLIST_FOREACH_END(guard); @@ -1003,7 +1012,7 @@ entry_guards_expand_sample(guard_selection_t *gs) tor_assert(gs); int n_sampled = smartlist_len(gs->sampled_entry_guards); entry_guard_t *added_guard = NULL; - int n_usable_filtered_guards = num_reachable_filtered_guards(gs); + int n_usable_filtered_guards = num_reachable_filtered_guards(gs, NULL); int n_guards = 0; smartlist_t *eligible_guards = get_eligible_guards(gs, &n_guards); @@ -1323,6 +1332,22 @@ entry_guard_passes_filter(const or_options_t *options, guard_selection_t *gs, } } +/** + * Return true iff guard obeys the restrictions defined in rst. + * (If rst is NULL, there are no restrictions.) + */ +static int +entry_guard_obeys_restriction(const entry_guard_t *guard, + const entry_guard_restriction_t *rst) +{ + tor_assert(guard); + if (! rst) + return 1; // No restriction? No problem. + + // Only one kind of restriction exists right now + return tor_memneq(guard->identity, rst->exclude_id, DIGEST_LEN); +} + /** * Update the is_filtered_guard and is_usable_filtered_guard * flags on guard. */ @@ -1373,9 +1398,13 @@ entry_guards_update_filtered_sets(guard_selection_t *gs) * * Make sure that the sample is big enough, and that all the filter flags * are set correctly, before calling this function. + * + * If a restriction is provided in rst, do not return any guards that + * violate it. **/ STATIC entry_guard_t * sample_reachable_filtered_entry_guards(guard_selection_t *gs, + const entry_guard_restriction_t *rst, unsigned flags) { tor_assert(gs); @@ -1389,7 +1418,7 @@ sample_reachable_filtered_entry_guards(guard_selection_t *gs, entry_guard_consider_retry(guard); } SMARTLIST_FOREACH_END(guard); - const int n_reachable_filtered = num_reachable_filtered_guards(gs); + const int n_reachable_filtered = num_reachable_filtered_guards(gs, rst); log_info(LD_GUARD, "Trying to sample a reachable guard: We know of %d " "in the USABLE_FILTERED set.", n_reachable_filtered); @@ -1407,6 +1436,8 @@ sample_reachable_filtered_entry_guards(guard_selection_t *gs, smartlist_t *reachable_filtered_sample = smartlist_new(); SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { entry_guard_consider_retry(guard);// redundant, but cheap. + if (! entry_guard_obeys_restriction(guard, rst)) + continue; if (! guard->is_usable_filtered_guard) continue; if (exclude_confirmed && guard->confirmed_idx >= 0) @@ -1568,7 +1599,7 @@ entry_guards_update_primary(guard_selection_t *gs) /* Finally, fill out the list with sampled guards. */ while (smartlist_len(new_primary_guards) < N_PRIMARY_GUARDS) { - entry_guard_t *guard = sample_reachable_filtered_entry_guards(gs, + entry_guard_t *guard = sample_reachable_filtered_entry_guards(gs, NULL, SAMPLE_EXCLUDE_CONFIRMED| SAMPLE_EXCLUDE_PRIMARY| SAMPLE_NO_UPDATE_PRIMARY); @@ -1708,7 +1739,9 @@ entry_guards_note_internet_connectivity(guard_selection_t *gs) * of the circuit. */ STATIC entry_guard_t * -select_entry_guard_for_circuit(guard_selection_t *gs, unsigned *state_out) +select_entry_guard_for_circuit(guard_selection_t *gs, + const entry_guard_restriction_t *rst, + unsigned *state_out) { /*XXXX prop271 consider splitting this function up. */ tor_assert(gs); @@ -1721,6 +1754,8 @@ select_entry_guard_for_circuit(guard_selection_t *gs, unsigned *state_out) or , return the first such guard." */ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) { entry_guard_consider_retry(guard); + if (! entry_guard_obeys_restriction(guard, rst)) + continue; if (guard->is_reachable != GUARD_REACHABLE_NO) { *state_out = GUARD_CIRC_STATE_USABLE_ON_COMPLETION; guard->last_tried_to_connect = approx_time(); @@ -1737,6 +1772,8 @@ select_entry_guard_for_circuit(guard_selection_t *gs, unsigned *state_out) SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) { if (guard->is_primary) continue; /* we already considered this one. */ + if (! entry_guard_obeys_restriction(guard, rst)) + continue; entry_guard_consider_retry(guard); if (guard->is_usable_filtered_guard && ! guard->is_pending) { guard->is_pending = 1; @@ -1755,6 +1792,7 @@ select_entry_guard_for_circuit(guard_selection_t *gs, unsigned *state_out) { entry_guard_t *guard; guard = sample_reachable_filtered_entry_guards(gs, + rst, SAMPLE_EXCLUDE_CONFIRMED | SAMPLE_EXCLUDE_PRIMARY | SAMPLE_EXCLUDE_PENDING); @@ -1925,6 +1963,13 @@ entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b) } } +/** Release all storage held in restriction */ +static void +entry_guard_restriction_free(entry_guard_restriction_t *rst) +{ + tor_free(rst); +} + /** * Release all storage held in state. */ @@ -1933,6 +1978,7 @@ circuit_guard_state_free(circuit_guard_state_t *state) { if (!state) return; + entry_guard_restriction_free(state->restrictions); entry_guard_handle_free(state->guard); tor_free(state); } @@ -1942,9 +1988,14 @@ circuit_guard_state_free(circuit_guard_state_t *state) * in *chosen_node_out. Set *guard_state_out to an opaque * state object that will record whether the circuit is ready to be used * or not. Return 0 on success; on failure, return -1. + * + * If a restriction is provided in rst, do not return any guards that + * violate it, and remember that restriction in guard_state_out for + * later use. (Takes ownership of the rst object.) */ int entry_guard_pick_for_circuit(guard_selection_t *gs, + entry_guard_restriction_t *rst, const node_t **chosen_node_out, circuit_guard_state_t **guard_state_out) { @@ -1955,23 +2006,27 @@ entry_guard_pick_for_circuit(guard_selection_t *gs, *guard_state_out = NULL; unsigned state = 0; - entry_guard_t *guard = select_entry_guard_for_circuit(gs, &state); + entry_guard_t *guard = select_entry_guard_for_circuit(gs, rst, &state); if (! guard) - return -1; + goto fail; if (BUG(state == 0)) - return -1; + goto fail; const node_t *node = node_get_by_id(guard->identity); // XXXX prop271 check Ed ID. if (! node) - return -1; + goto fail; *chosen_node_out = node; *guard_state_out = tor_malloc_zero(sizeof(circuit_guard_state_t)); (*guard_state_out)->guard = entry_guard_handle_new(guard); (*guard_state_out)->state = state; (*guard_state_out)->state_set_at = approx_time(); + (*guard_state_out)->restrictions = rst; return 0; + fail: + entry_guard_restriction_free(rst); + return -1; } /** @@ -2098,9 +2153,14 @@ entry_guards_all_primary_guards_are_down(guard_selection_t *gs) } /** Wrapper for entry_guard_has_higher_priority that compares the - * guard-priorities of a pair of circuits. */ + * guard-priorities of a pair of circuits. + * + * If a restriction is provided in rst, then do not consider + * a to have higher priority if it violates the restriction. + */ static int circ_state_has_higher_priority(origin_circuit_t *a, + const entry_guard_restriction_t *rst, origin_circuit_t *b) { circuit_guard_state_t *state_a = origin_circuit_get_guard_state(a); @@ -2118,6 +2178,9 @@ circ_state_has_higher_priority(origin_circuit_t *a, } else if (! guard_b) { /* Known guard -- higher priority than any unknown guard. */ return 1; + } else if (! entry_guard_obeys_restriction(guard_a, rst)) { + /* Restriction violated; guard_a cannot have higher priority. */ + return 0; } else { /* Both known -- compare.*/ return entry_guard_has_higher_priority(guard_a, guard_b); @@ -2175,13 +2238,13 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, if (state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD) { ++n_waiting; if (! best_waiting_circuit || - circ_state_has_higher_priority(circ, best_waiting_circuit)) { + circ_state_has_higher_priority(circ, NULL, best_waiting_circuit)) { best_waiting_circuit = circ; } } else if (state->state == GUARD_CIRC_STATE_COMPLETE) { ++n_complete; if (! best_complete_circuit || - circ_state_has_higher_priority(circ, best_complete_circuit)) { + circ_state_has_higher_priority(circ, NULL, best_complete_circuit)) { best_complete_circuit = circ; } } @@ -2193,8 +2256,15 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, goto no_change; } + /* We'll need to keep track of what restrictions were used when picking this + * circuit, so that we don't allow any circuit without those restrictions to + * block it. */ + const entry_guard_restriction_t *rst_on_best_waiting = + origin_circuit_get_guard_state(best_waiting_circuit)->restrictions; + if (best_complete_circuit) { if (circ_state_has_higher_priority(best_complete_circuit, + rst_on_best_waiting, best_waiting_circuit)) { /* "If any circuit is , then do not use any or circuits @@ -2225,8 +2295,10 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, continue; if (state->state != GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD) continue; - if (state->state_set_at > state_set_at_cutoff && - circ_state_has_higher_priority(circ, best_waiting_circuit)) + if (state->state_set_at <= state_set_at_cutoff) + continue; + if (circ_state_has_higher_priority(circ, rst_on_best_waiting, + best_waiting_circuit)) ++n_blockers_found; } SMARTLIST_FOREACH_END(circ); @@ -2246,9 +2318,14 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); if (BUG(state == NULL)) continue; + if (circ != best_waiting_circuit && rst_on_best_waiting) { + /* Can't upgrade other circ with same priority as best; might + be blocked. */ + continue; + } if (state->state != GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD) continue; - if (circ_state_has_higher_priority(best_waiting_circuit, circ)) + if (circ_state_has_higher_priority(best_waiting_circuit, NULL, circ)) continue; state->state = GUARD_CIRC_STATE_COMPLETE; @@ -4759,10 +4836,18 @@ guards_choose_guard(cpath_build_state_t *state, if (get_options()->UseDeprecatedGuardAlgorithm) { return choose_random_entry(state); } else { - // XXXX prop271 we need to look at the chosen exit node if any, and - // not duplicate it. const node_t *r = NULL; + const uint8_t *exit_id = NULL; + entry_guard_restriction_t *rst = NULL; + // XXXX prop271 spec deviation -- use of restriction here. + if (state && (exit_id = build_state_get_exit_rsa_id(state))) { + /* We're building to a targeted exit node, so that node can't be + * chosen as our guard for this circuit. */ + rst = tor_malloc_zero(sizeof(entry_guard_restriction_t)); + memcpy(rst->exclude_id, exit_id, DIGEST_LEN); + } if (entry_guard_pick_for_circuit(get_guard_selection_info(), + rst, &r, guard_state_out) < 0) { tor_assert(r == NULL); @@ -4788,6 +4873,7 @@ guards_choose_dirguard(dirinfo_type_t info, * microdescriptors. -NM */ const node_t *r = NULL; if (entry_guard_pick_for_circuit(get_guard_selection_info(), + NULL, &r, guard_state_out) < 0) { tor_assert(r == NULL); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 4ea60e88fb..753d6f7f8a 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -24,6 +24,10 @@ typedef struct entry_guard_t entry_guard_t; private. */ typedef struct circuit_guard_state_t circuit_guard_state_t; +/* Forward declaration for entry_guard_restriction_t; the real declaration is + private. */ +typedef struct entry_guard_restriction_t entry_guard_restriction_t; + /* Information about a guard's pathbias status. * These fields are used in circpathbias.c to try to detect entry * nodes that are failing circuits at a suspicious frequency. @@ -310,6 +314,24 @@ struct guard_selection_s { struct entry_guard_handle_t; +/** + * A restriction to remember which entry guards are off-limits for a given + * circuit. + * + * Right now, we only use restrictions to block a single guard from being + * selected; this mechanism is designed to be more extensible in the future, + * however. + * + * Note: This mechanism is NOT for recording which guards are never to be + * used: only which guards cannot be used on one particular circuit. + */ +struct entry_guard_restriction_t { + /** + * The guard's RSA identity digest must not equal this. + */ + uint8_t exclude_id[DIGEST_LEN]; +}; + /** * Per-circuit state to track whether we'll be able to use the circuit. */ @@ -320,6 +342,14 @@ struct circuit_guard_state_t { time_t state_set_at; /** One of GUARD_CIRC_STATE_* */ uint8_t state; + + /** + * A set of restrictions that were placed on this guard when we selected it + * for this particular circuit. We need to remember the restrictions here, + * since any guard that breaks these restrictions will not block this + * circuit from becoming COMPLETE. + */ + entry_guard_restriction_t *restrictions; }; #endif @@ -356,6 +386,7 @@ guard_pathbias_t *entry_guard_get_pathbias_state(entry_guard_t *guard); void circuit_guard_state_free(circuit_guard_state_t *state); int entry_guard_pick_for_circuit(guard_selection_t *gs, + entry_guard_restriction_t *rst, const node_t **chosen_node_out, circuit_guard_state_t **guard_state_out); typedef enum { @@ -491,12 +522,14 @@ STATIC int entry_guards_all_primary_guards_are_down(guard_selection_t *gs); /**@}*/ STATIC entry_guard_t *sample_reachable_filtered_entry_guards( guard_selection_t *gs, + const entry_guard_restriction_t *rst, unsigned flags); STATIC void entry_guard_consider_retry(entry_guard_t *guard); STATIC void make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard); STATIC void entry_guards_update_confirmed(guard_selection_t *gs); STATIC void entry_guards_update_primary(guard_selection_t *gs); -STATIC int num_reachable_filtered_guards(guard_selection_t *gs); +STATIC int num_reachable_filtered_guards(guard_selection_t *gs, + const entry_guard_restriction_t *rst); STATIC void sampled_guards_update_from_consensus(guard_selection_t *gs); /** * @name Possible guard-states for a circuit. @@ -522,7 +555,8 @@ STATIC void sampled_guards_update_from_consensus(guard_selection_t *gs); STATIC void entry_guards_note_guard_failure(guard_selection_t *gs, entry_guard_t *guard); STATIC entry_guard_t *select_entry_guard_for_circuit(guard_selection_t *gs, - unsigned *state_out); + const entry_guard_restriction_t *rst, + unsigned *state_out); STATIC void mark_primary_guards_maybe_reachable(guard_selection_t *gs); STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs, entry_guard_t *guard, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index e3a9d18daa..0921e2011e 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1353,7 +1353,7 @@ test_entry_guard_node_filter(void *arg) tt_assert(g[i]->is_filtered_guard == 1); tt_assert(g[i]->is_usable_filtered_guard == 1); } - tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, NUM); + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, NUM); /* Make sure refiltering doesn't hurt */ entry_guards_update_filtered_sets(gs); @@ -1361,7 +1361,7 @@ test_entry_guard_node_filter(void *arg) tt_assert(g[i]->is_filtered_guard == 1); tt_assert(g[i]->is_usable_filtered_guard == 1); } - tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, NUM); + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, NUM); /* Now start doing things to make the guards get filtered out, 1 by 1. */ @@ -1401,7 +1401,7 @@ test_entry_guard_node_filter(void *arg) tt_assert(g[i]->is_filtered_guard == (i == 5 || i == 6)); tt_assert(g[i]->is_usable_filtered_guard == (i == 6)); } - tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, 1); + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, 1); /* Now make sure we have no live consensus, and no nodes. Nothing should * pass the filter any more. */ @@ -1415,7 +1415,7 @@ test_entry_guard_node_filter(void *arg) tt_assert(g[i]->is_filtered_guard == 0); tt_assert(g[i]->is_usable_filtered_guard == 0); } - tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, 0); + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, 0); done: guard_selection_free(gs); @@ -1434,11 +1434,11 @@ test_entry_guard_expand_sample(void *arg) // Every sampled guard here should be filtered and reachable for now. tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, - num_reachable_filtered_guards(gs)); + num_reachable_filtered_guards(gs, NULL)); /* Make sure we got the right number. */ tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, - num_reachable_filtered_guards(gs)); + num_reachable_filtered_guards(gs, NULL)); // Make sure everything we got was from our fake node list, and everything // was unique. @@ -1457,7 +1457,7 @@ test_entry_guard_expand_sample(void *arg) guard = entry_guards_expand_sample(gs); tt_assert(! guard); // no guard was added. tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, - num_reachable_filtered_guards(gs)); + num_reachable_filtered_guards(gs, NULL)); // Make a few guards unreachable. guard = smartlist_get(gs->sampled_entry_guards, 0); @@ -1467,21 +1467,21 @@ test_entry_guard_expand_sample(void *arg) guard = smartlist_get(gs->sampled_entry_guards, 2); guard->is_usable_filtered_guard = 0; tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE - 3, OP_EQ, - num_reachable_filtered_guards(gs)); + num_reachable_filtered_guards(gs, NULL)); // This time, expanding the sample will add some more guards. guard = entry_guards_expand_sample(gs); tt_assert(guard); // no guard was added. tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, - num_reachable_filtered_guards(gs)); + num_reachable_filtered_guards(gs, NULL)); tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, - num_reachable_filtered_guards(gs)+3); + num_reachable_filtered_guards(gs, NULL)+3); // Still idempotent. guard = entry_guards_expand_sample(gs); tt_assert(! guard); // no guard was added. tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, - num_reachable_filtered_guards(gs)); + num_reachable_filtered_guards(gs, NULL)); // Now, do a nasty trick: tell the filter to exclude 31/32 of the guards. // This will cause the sample size to get reeeeally huge, while the @@ -1497,7 +1497,7 @@ test_entry_guard_expand_sample(void *arg) entry_guards_update_filtered_sets(gs); // Surely (p ~ 1-2**-60), one of our guards has been excluded. - tt_int_op(num_reachable_filtered_guards(gs), OP_LT, + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_LT, DFLT_MIN_FILTERED_SAMPLE_SIZE); // Try to regenerate the guards. @@ -1505,7 +1505,7 @@ test_entry_guard_expand_sample(void *arg) tt_assert(guard); // no guard was added. /* this time, it's possible that we didn't add enough sampled guards. */ - tt_int_op(num_reachable_filtered_guards(gs), OP_LE, + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_LE, DFLT_MIN_FILTERED_SAMPLE_SIZE); /* but we definitely didn't exceed the sample maximum. */ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_LE, @@ -1538,7 +1538,7 @@ test_entry_guard_expand_sample_small_net(void *arg) tt_assert(guard); // the last guard returned -- some guard was added. tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_GT, 0); tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_LT, 10); - tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, 0); + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, 0); done: guard_selection_free(gs); } @@ -1562,7 +1562,7 @@ test_entry_guard_update_from_consensus_status(void *arg) /* First, sample some guards. */ entry_guards_expand_sample(gs); int n_sampled_pre = smartlist_len(gs->sampled_entry_guards); - int n_filtered_pre = num_reachable_filtered_guards(gs); + int n_filtered_pre = num_reachable_filtered_guards(gs, NULL); tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre); tt_i64_op(n_sampled_pre, OP_GT, 10); @@ -1584,7 +1584,7 @@ test_entry_guard_update_from_consensus_status(void *arg) dummy_consensus = NULL; sampled_guards_update_from_consensus(gs); tt_i64_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre); - tt_i64_op(num_reachable_filtered_guards(gs), OP_EQ, n_filtered_pre); + tt_i64_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, n_filtered_pre); /* put the networkstatus back. */ dummy_consensus = ns_tmp; ns_tmp = NULL; @@ -1596,7 +1596,7 @@ test_entry_guard_update_from_consensus_status(void *arg) sampled_guards_update_from_consensus(gs); tt_i64_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre); - tt_i64_op(num_reachable_filtered_guards(gs), OP_EQ, n_filtered_pre - 5); + tt_i64_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, n_filtered_pre-5); for (i = 0; i < 5; ++i) { entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); tt_assert(! g->currently_listed); @@ -1666,7 +1666,7 @@ test_entry_guard_update_from_consensus_repair(void *arg) /* First, sample some guards. */ entry_guards_expand_sample(gs); int n_sampled_pre = smartlist_len(gs->sampled_entry_guards); - int n_filtered_pre = num_reachable_filtered_guards(gs); + int n_filtered_pre = num_reachable_filtered_guards(gs, NULL); tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre); tt_i64_op(n_sampled_pre, OP_GT, 10); @@ -1694,7 +1694,7 @@ test_entry_guard_update_from_consensus_repair(void *arg) teardown_capture_of_logs(); tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre); - tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, n_filtered_pre - 3); + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, n_filtered_pre-3); for (i = 3; i < n_sampled_pre; ++i) { /* these will become listed. */ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i); @@ -1731,7 +1731,7 @@ test_entry_guard_update_from_consensus_remove(void *arg) /* First, sample some guards. */ entry_guards_expand_sample(gs); int n_sampled_pre = smartlist_len(gs->sampled_entry_guards); - int n_filtered_pre = num_reachable_filtered_guards(gs); + int n_filtered_pre = num_reachable_filtered_guards(gs, NULL); tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre); tt_i64_op(n_sampled_pre, OP_GT, 10); @@ -1912,7 +1912,7 @@ test_entry_guard_sample_reachable_filtered(void *arg) entry_guards_update_filtered_sets(gs); gs->primary_guards_up_to_date = 1; - tt_int_op(num_reachable_filtered_guards(gs), OP_EQ, n_guards - 1); + tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, n_guards - 1); tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_guards); // +1 since the one we made disabled will make another one get added. @@ -1934,7 +1934,7 @@ test_entry_guard_sample_reachable_filtered(void *arg) const int excluded_flags = tests[j].flag; const int excluded_idx = tests[j].idx; for (i = 0; i < N; ++i) { - g = sample_reachable_filtered_entry_guards(gs, excluded_flags); + g = sample_reachable_filtered_entry_guards(gs, NULL, excluded_flags); tor_assert(g); int pos = smartlist_pos(gs->sampled_entry_guards, g); tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_guards); @@ -1965,7 +1965,7 @@ test_entry_guard_sample_reachable_filtered_empty(void *arg) SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, n->is_possible_guard = 0); - entry_guard_t *g = sample_reachable_filtered_entry_guards(gs, 0); + entry_guard_t *g = sample_reachable_filtered_entry_guards(gs, NULL, 0); tt_ptr_op(g, OP_EQ, NULL); done: @@ -2162,7 +2162,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) entry_guards_update_primary(gs); unsigned state = 9999; - entry_guard_t *g = select_entry_guard_for_circuit(gs, &state); + entry_guard_t *g = select_entry_guard_for_circuit(gs, NULL, &state); tt_assert(g); tt_assert(g->is_primary); @@ -2172,7 +2172,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) tt_i64_op(g->last_tried_to_connect, OP_EQ, approx_time()); // If we do that again, we should get the same guard. - entry_guard_t *g2 = select_entry_guard_for_circuit(gs, &state); + entry_guard_t *g2 = select_entry_guard_for_circuit(gs, NULL, &state); tt_ptr_op(g2, OP_EQ, g); // if we mark that guard down, we should get a different primary guard. @@ -2181,7 +2181,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) g->unreachable_since = approx_time() - 10; g->last_tried_to_connect = approx_time() - 10; state = 9999; - g2 = select_entry_guard_for_circuit(gs, &state); + g2 = select_entry_guard_for_circuit(gs, NULL, &state); tt_ptr_op(g2, OP_NE, g); tt_assert(g2); tt_assert(g2->is_primary); @@ -2195,7 +2195,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) g->unreachable_since = approx_time() - 72*60*60; g->last_tried_to_connect = approx_time() - 72*60*60; state = 9999; - g2 = select_entry_guard_for_circuit(gs, &state); + g2 = select_entry_guard_for_circuit(gs, NULL, &state); tt_ptr_op(g2, OP_EQ, g); tt_assert(g2); tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); @@ -2210,7 +2210,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) guard->unreachable_since = approx_time() - 30; }); state = 9999; - g2 = select_entry_guard_for_circuit(gs, &state); + g2 = select_entry_guard_for_circuit(gs, NULL, &state); tt_assert(g2); tt_assert(!g2->is_primary); tt_int_op(g2->confirmed_idx, OP_EQ, -1); @@ -2254,7 +2254,7 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) unsigned state = 9999; // As above, this gives us a primary guard. - entry_guard_t *g = select_entry_guard_for_circuit(gs, &state); + entry_guard_t *g = select_entry_guard_for_circuit(gs, NULL, &state); tt_assert(g); tt_assert(g->is_primary); tt_int_op(g->confirmed_idx, OP_EQ, 0); @@ -2271,7 +2271,7 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) // ... we should get a confirmed guard. state = 9999; - g = select_entry_guard_for_circuit(gs, &state); + g = select_entry_guard_for_circuit(gs, NULL, &state); tt_assert(g); tt_assert(! g->is_primary); tt_int_op(g->confirmed_idx, OP_EQ, smartlist_len(gs->primary_entry_guards)); @@ -2282,7 +2282,7 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) // And if we try again, we should get a different confirmed guard, since // that one is pending. state = 9999; - entry_guard_t *g2 = select_entry_guard_for_circuit(gs, &state); + entry_guard_t *g2 = select_entry_guard_for_circuit(gs, NULL, &state); tt_assert(g2); tt_assert(! g2->is_primary); tt_ptr_op(g2, OP_NE, g); @@ -2297,12 +2297,12 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) const int n_remaining_confirmed = N_CONFIRMED - 2 - smartlist_len(gs->primary_entry_guards); for (i = 0; i < n_remaining_confirmed; ++i) { - g = select_entry_guard_for_circuit(gs, &state); + g = select_entry_guard_for_circuit(gs, NULL, &state); tt_int_op(g->confirmed_idx, OP_GE, 0); tt_assert(g); } state = 9999; - g = select_entry_guard_for_circuit(gs, &state); + g = select_entry_guard_for_circuit(gs, NULL, &state); tt_assert(g); tt_assert(g->is_pending); tt_int_op(g->confirmed_idx, OP_EQ, -1); @@ -2329,7 +2329,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) * Make sure that the pick-for-circuit API basically works. We'll get * a primary guard, so it'll be usable on completion. */ - int r = entry_guard_pick_for_circuit(gs, &node, &guard); + int r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_assert(r == 0); tt_assert(node); @@ -2361,7 +2361,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) /* Try again. We'll also get a primary guard this time. (The same one, in fact.) But this time, we'll say the connection has failed. */ update_approx_time(start+35); - r = entry_guard_pick_for_circuit(gs, &node, &guard); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_assert(r == 0); tt_assert(node); tt_assert(guard); @@ -2396,7 +2396,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) * (still primary) guard. */ update_approx_time(start+60); - r = entry_guard_pick_for_circuit(gs, &node, &guard); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_assert(r == 0); tt_assert(node); tt_assert(guard); @@ -2448,7 +2448,7 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) /* Primary guards are down! */ for (i = 0; i < N_PRIMARY; ++i) { - r = entry_guard_pick_for_circuit(gs, &node, &guard); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_assert(node); tt_assert(guard); tt_assert(r == 0); @@ -2461,7 +2461,7 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) /* Next guard should be non-primary. */ node = NULL; - r = entry_guard_pick_for_circuit(gs, &node, &guard); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_assert(node); tt_assert(guard); tt_assert(r == 0); @@ -2513,7 +2513,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) /* Make primary guards confirmed (so they won't be superseded by a later * guard), then mark them down. */ for (i = 0; i < N_PRIMARY; ++i) { - r = entry_guard_pick_for_circuit(gs, &node, &guard); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_assert(node); tt_assert(guard); tt_assert(r == 0); @@ -2529,7 +2529,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) } /* Get another guard that we might try. */ - r = entry_guard_pick_for_circuit(gs, &node, &guard); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_assert(node); tt_assert(guard); tt_assert(r == 0); @@ -2556,7 +2556,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) }); /* Have a circuit to a primary guard succeed. */ - r = entry_guard_pick_for_circuit(gs, &node, &guard2); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard2); tt_assert(r == 0); tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); u = entry_guard_succeeded(&guard2); @@ -2585,7 +2585,7 @@ test_entry_guard_select_and_cancel(void *arg) /* Once more, we mark all the primary guards down. */ entry_guards_note_internet_connectivity(gs); for (i = 0; i < N_PRIMARY; ++i) { - r = entry_guard_pick_for_circuit(gs, &node, &guard); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); g = entry_guard_handle_get(guard->guard); tt_int_op(g->is_primary, OP_EQ, 1); @@ -2600,7 +2600,7 @@ test_entry_guard_select_and_cancel(void *arg) tt_assert(entry_guards_all_primary_guards_are_down(gs)); /* Now get another guard we could try... */ - r = entry_guard_pick_for_circuit(gs, &node, &guard); + r = entry_guard_pick_for_circuit(gs, NULL, &node, &guard); tt_assert(node); tt_assert(guard); tt_assert(r == 0); @@ -2659,7 +2659,7 @@ upgrade_circuits_setup(const struct testcase_t *testcase) data->start = approx_time(); entry_guards_note_internet_connectivity(gs); for (i = 0; i < N_PRIMARY; ++i) { - entry_guard_pick_for_circuit(gs, &node, &guard); + entry_guard_pick_for_circuit(gs, NULL, &node, &guard); g = entry_guard_handle_get(guard->guard); make_guard_confirmed(gs, g); entry_guard_failed(&guard); @@ -2670,7 +2670,7 @@ upgrade_circuits_setup(const struct testcase_t *testcase) data->all_origin_circuits = smartlist_new(); update_approx_time(data->start + 27); - entry_guard_pick_for_circuit(gs, &node, &data->guard1_state); + entry_guard_pick_for_circuit(gs, NULL, &node, &data->guard1_state); origin_circuit_t *circ; data->circ1 = circ = origin_circuit_new(); circ->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL; @@ -2678,7 +2678,7 @@ upgrade_circuits_setup(const struct testcase_t *testcase) smartlist_add(data->all_origin_circuits, circ); update_approx_time(data->start + 30); - entry_guard_pick_for_circuit(gs, &node, &data->guard2_state); + entry_guard_pick_for_circuit(gs, NULL, &node, &data->guard2_state); data->circ2 = circ = origin_circuit_new(); circ->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL; circ->guard_state = data->guard2_state; From 2c8c58ab2fe7d452d06abdb7328be7eae658bcc0 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 09:04:34 -0500 Subject: [PATCH 63/80] Another tweak for guard restrictions: don't let complete circs block If a complete circuit C2 doesn't obey the restrictions of C1, then C2 cannot block C1. The patch here is a little big-ish, since we can no longer look through all the complete circuits and all the waiting circuits on a single pass: we have to find the best waiting circuit first. --- src/or/entrynodes.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 9b38641b1e..02ed9247c9 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -2214,8 +2214,8 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, int n_waiting = 0; int n_complete = 0; + int n_complete_blocking = 0; origin_circuit_t *best_waiting_circuit = NULL; - origin_circuit_t *best_complete_circuit = NULL; smartlist_t *all_circuits = smartlist_new(); SMARTLIST_FOREACH_BEGIN(all_circuits_in, origin_circuit_t *, circ) { // We filter out circuits that aren't ours, or which we can't @@ -2241,12 +2241,6 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, circ_state_has_higher_priority(circ, NULL, best_waiting_circuit)) { best_waiting_circuit = circ; } - } else if (state->state == GUARD_CIRC_STATE_COMPLETE) { - ++n_complete; - if (! best_complete_circuit || - circ_state_has_higher_priority(circ, NULL, best_complete_circuit)) { - best_complete_circuit = circ; - } } } SMARTLIST_FOREACH_END(circ); @@ -2262,19 +2256,28 @@ entry_guards_upgrade_waiting_circuits(guard_selection_t *gs, const entry_guard_restriction_t *rst_on_best_waiting = origin_circuit_get_guard_state(best_waiting_circuit)->restrictions; - if (best_complete_circuit) { - if (circ_state_has_higher_priority(best_complete_circuit, - rst_on_best_waiting, - best_waiting_circuit)) { - /* "If any circuit is , then do not use any - or circuits - circuits whose guards have lower priority." */ - log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits: found " - "%d complete and %d guard-stalled. At least one complete " - "circuit had higher priority, so not upgrading.", - n_complete, n_waiting); - goto no_change; - } + /* First look at the complete circuits: Do any block this circuit? */ + SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) { + circuit_guard_state_t *state = origin_circuit_get_guard_state(circ); + if BUG((state == NULL)) + continue; + if (state->state != GUARD_CIRC_STATE_COMPLETE) + continue; + ++n_complete; + if (circ_state_has_higher_priority(circ, rst_on_best_waiting, + best_waiting_circuit)) + ++n_complete_blocking; + } SMARTLIST_FOREACH_END(circ); + + if (n_complete_blocking) { + /* "If any circuit is , then do not use any + or circuits + circuits whose guards have lower priority." */ + log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits: found " + "%d complete and %d guard-stalled. At least one complete " + "circuit had higher priority, so not upgrading.", + n_complete, n_waiting); + goto no_change; } /* "If any circuit is , and every currently From 13315812e857f37828475101cc8d5acb63403c0e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 09:19:10 -0500 Subject: [PATCH 64/80] Repair unit test for tiny-network case. The test assumed that the old rules about handling small max_sample were in effect, and didn't actually handle that case very well anyway. --- src/test/test_entrynodes.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 0921e2011e..5fff1d6d22 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1524,7 +1524,7 @@ test_entry_guard_expand_sample_small_net(void *arg) /* Fun corner case: not enough guards to make up our whole sample size. */ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, { - if (n_sl_idx >= 40) { + if (n_sl_idx >= 15) { tor_free(n->rs); tor_free(n->md); tor_free(n); @@ -1536,8 +1536,9 @@ test_entry_guard_expand_sample_small_net(void *arg) entry_guard_t *guard = entry_guards_expand_sample(gs); tt_assert(guard); // the last guard returned -- some guard was added. - tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_GT, 0); - tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_LT, 10); + // half the nodes are guards, so we have 8 guards left. The set + // is small, so we sampled everything. + tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, 8); tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, 0); done: guard_selection_free(gs); From 217590ad05943968683f02c3f556b987e99158b1 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 10:16:24 -0500 Subject: [PATCH 65/80] Extract guard_selection_infer_type into its own function. --- src/or/entrynodes.c | 24 ++++++++++++++++++------ src/or/entrynodes.h | 3 +++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 02ed9247c9..9441be4c78 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -194,14 +194,13 @@ should_apply_guardfraction(const networkstatus_t *ns) } /** - * Allocate and return a new guard_selection_t, with the name name. + * Try to determine the correct type for a selection named "name", + * if type is GS_TYPE_INFER. */ -STATIC guard_selection_t * -guard_selection_new(const char *name, - guard_selection_type_t type) +STATIC guard_selection_type_t +guard_selection_infer_type(guard_selection_type_t type, + const char *name) { - guard_selection_t *gs; - if (type == GS_TYPE_INFER) { if (!strcmp(name, "legacy")) type = GS_TYPE_LEGACY; @@ -212,6 +211,19 @@ guard_selection_new(const char *name, else type = GS_TYPE_NORMAL; } + return type; +} + +/** + * Allocate and return a new guard_selection_t, with the name name. + */ +STATIC guard_selection_t * +guard_selection_new(const char *name, + guard_selection_type_t type) +{ + guard_selection_t *gs; + + type = guard_selection_infer_type(type, name); gs = tor_malloc_zero(sizeof(*gs)); gs->name = tor_strdup(name); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 753d6f7f8a..b676172567 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -490,6 +490,9 @@ STATIC double get_extreme_restriction_threshold(void); // ---------- XXXX these functions and definitions are post-prop271. HANDLE_DECL(entry_guard, entry_guard_t, STATIC) +STATIC guard_selection_type_t guard_selection_infer_type( + guard_selection_type_t type_in, + const char *name); STATIC guard_selection_t *guard_selection_new(const char *name, guard_selection_type_t type); STATIC guard_selection_t *get_guard_selection_by_name( From 171981f8a0eebf3f00feabe36dc66e031d51c5bd Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 11:28:18 -0500 Subject: [PATCH 66/80] Add a test for entry_guard_state_should_expire() --- src/test/test_entrynodes.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 5fff1d6d22..5360b0e440 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -2926,6 +2926,37 @@ test_entry_guard_upgrade_not_blocked_by_worse_circ_pending(void *arg) smartlist_free(result); } +static void +test_enty_guard_should_expire_waiting(void *arg) +{ + (void)arg; + circuit_guard_state_t *fake_state = tor_malloc_zero(sizeof(*fake_state)); + /* We'll leave "guard" unset -- it won't matter here. */ + + /* No state? Can't expire. */ + tt_assert(! entry_guard_state_should_expire(NULL)); + + /* Let's try one that expires. */ + fake_state->state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD; + fake_state->state_set_at = + approx_time() - DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT - 1; + + tt_assert(entry_guard_state_should_expire(fake_state)); + + /* But it wouldn't expire if we changed the state. */ + fake_state->state = GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD; + tt_assert(! entry_guard_state_should_expire(fake_state)); + + /* And it wouldn't have expired a few seconds ago. */ + fake_state->state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD; + fake_state->state_set_at = + approx_time() - DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT + 5; + tt_assert(! entry_guard_state_should_expire(fake_state)); + + done: + tor_free(fake_state); +} + static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; @@ -3017,6 +3048,8 @@ struct testcase_t entrynodes_tests[] = { UPGRADE_TEST(upgrade_not_blocked_by_worse_circ_complete, "c1-done c2-done"), UPGRADE_TEST(upgrade_blocked_by_better_circ_pending, "c2-done"), UPGRADE_TEST(upgrade_not_blocked_by_worse_circ_pending, "c1-done"), + { "should_expire_waiting", test_enty_guard_should_expire_waiting, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; From 79d3e94f8b1769ee8d1957cb1d6dd35bd02a7271 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 12:35:16 -0500 Subject: [PATCH 67/80] prop271: Tests for the highlevel or_state_t encode/decode functions --- src/or/entrynodes.c | 4 +- src/or/entrynodes.h | 2 + src/test/test_entrynodes.c | 238 +++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 2 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 9441be4c78..1f6d562fd8 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1115,8 +1115,8 @@ remove_guard_from_confirmed_and_primary_lists(guard_selection_t *gs, /** Return true iff guard is currently "listed" -- that is, it * appears in the consensus, or as a configured bridge (as * appropriate) */ -static int -entry_guard_is_listed(guard_selection_t *gs, const entry_guard_t *guard) +MOCK_IMPL(STATIC int, +entry_guard_is_listed,(guard_selection_t *gs, const entry_guard_t *guard)) { if (gs->type == GS_TYPE_BRIDGE) { return NULL != get_bridge_info_for_guard(guard); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index b676172567..c05a3e3a2c 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -498,6 +498,8 @@ STATIC guard_selection_t *guard_selection_new(const char *name, STATIC guard_selection_t *get_guard_selection_by_name( const char *name, guard_selection_type_t type, int create_if_absent); STATIC void guard_selection_free(guard_selection_t *gs); +MOCK_DECL(STATIC int, entry_guard_is_listed, + (guard_selection_t *gs, const entry_guard_t *guard)); STATIC const char *choose_guard_selection(const or_options_t *options, const networkstatus_t *ns, const char *old_selection, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 5360b0e440..1fbb8f8888 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -14,6 +14,7 @@ #include "bridges.h" #include "circuitlist.h" #include "config.h" +#include "confparse.h" #include "entrynodes.h" #include "nodelist.h" #include "networkstatus.h" @@ -1247,6 +1248,239 @@ test_entry_guard_parse_from_state_partial_failure(void *arg) tor_free(mem_op_hex_tmp); } +static int +mock_entry_guard_is_listed(guard_selection_t *gs, const entry_guard_t *guard) +{ + (void)gs; + (void)guard; + return 1; +} + +static void +test_entry_guard_parse_from_state_full(void *arg) +{ + (void)arg; + /* Here's a state I made while testing. The identities and locations for + * the bridges are redacted. */ + const char STATE[] = + "Guard in=default rsa_id=214F44BD5B638E8C817D47FF7C97397790BF0345 " + "nickname=TotallyNinja sampled_on=2016-11-12T19:32:49 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1\n" + "Guard in=default rsa_id=052900AB0EA3ED54BAB84AE8A99E74E8693CE2B2 " + "nickname=5OfNovember sampled_on=2016-11-20T04:32:05 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1 confirmed_on=2016-11-22T08:13:28 confirmed_idx=0 " + "pb_circ_attempts=4.000000 pb_circ_successes=2.000000 " + "pb_successful_circuits_closed=2.000000\n" + "Guard in=default rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A " + "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1 confirmed_on=2016-11-24T08:45:30 confirmed_idx=4 " + "pb_circ_attempts=5.000000 pb_circ_successes=5.000000 " + "pb_successful_circuits_closed=5.000000\n" + "Guard in=wobblesome rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A " + "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1\n" + "Guard in=default rsa_id=E9025AD60D86875D5F11548D536CC6AF60F0EF5E " + "nickname=maibrunn sampled_on=2016-11-25T22:36:38 " + "sampled_by=0.3.0.0-alpha-dev listed=1\n" + "Guard in=default rsa_id=DCD30B90BA3A792DA75DC54A327EF353FB84C38E " + "nickname=Unnamed sampled_on=2016-11-25T14:34:00 " + "sampled_by=0.3.0.0-alpha-dev listed=1\n" + "Guard in=bridges rsa_id=8FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E " + "bridge_addr=24.1.1.1:443 sampled_on=2016-11-25T06:44:14 " + "sampled_by=0.3.0.0-alpha-dev listed=1 " + "confirmed_on=2016-11-29T10:36:06 confirmed_idx=0 " + "pb_circ_attempts=8.000000 pb_circ_successes=8.000000 " + "pb_successful_circuits_closed=13.000000\n" + "Guard in=bridges rsa_id=5800000000000000000000000000000000000000 " + "bridge_addr=37.218.246.143:28366 " + "sampled_on=2016-11-18T15:07:34 sampled_by=0.3.0.0-alpha-dev listed=1\n"; + + config_line_t *lines = NULL; + or_state_t *state = tor_malloc_zero(sizeof(or_state_t)); + int r = config_get_lines(STATE, &lines, 0); + char *msg = NULL; + smartlist_t *text = smartlist_new(); + char *joined = NULL; + + MOCK(entry_guard_is_listed, mock_entry_guard_is_listed); + + dummy_state = state; + MOCK(get_or_state, + get_or_state_replacement); + + tt_assert(r == 0); + tt_assert(lines); + + state->Guard = lines; + + /* Try it first without setting the result. */ + r = entry_guards_parse_state(state, 0, &msg); + tt_assert(r == 0); + guard_selection_t *gs_br = + get_guard_selection_by_name("bridges", GS_TYPE_BRIDGE, 0); + tt_assert(!gs_br); + + r = entry_guards_parse_state(state, 1, &msg); + tt_assert(r == 0); + gs_br = get_guard_selection_by_name("bridges", GS_TYPE_BRIDGE, 0); + guard_selection_t *gs_df = + get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); + guard_selection_t *gs_wb = + get_guard_selection_by_name("wobblesome", GS_TYPE_NORMAL, 0); + + tt_assert(gs_br); + tt_assert(gs_df); + tt_assert(gs_wb); + + tt_int_op(smartlist_len(gs_df->sampled_entry_guards), OP_EQ, 5); + tt_int_op(smartlist_len(gs_br->sampled_entry_guards), OP_EQ, 2); + tt_int_op(smartlist_len(gs_wb->sampled_entry_guards), OP_EQ, 1); + + /* Try again; make sure it doesn't double-add the guards. */ + r = entry_guards_parse_state(state, 1, &msg); + tt_assert(r == 0); + gs_br = get_guard_selection_by_name("bridges", GS_TYPE_BRIDGE, 0); + gs_df = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); + tt_assert(gs_br); + tt_assert(gs_df); + tt_int_op(smartlist_len(gs_df->sampled_entry_guards), OP_EQ, 5); + tt_int_op(smartlist_len(gs_br->sampled_entry_guards), OP_EQ, 2); + + /* Re-encode; it should be the same... almost. */ + { + /* (Make a guard nonpersistent first) */ + entry_guard_t *g = smartlist_get(gs_df->sampled_entry_guards, 0); + g->is_persistent = 0; + } + config_free_lines(lines); + lines = state->Guard = NULL; // to prevent double-free. + entry_guards_update_state(state); + tt_assert(state->Guard); + lines = state->Guard; + + config_line_t *ln; + for (ln = lines; ln; ln = ln->next) { + smartlist_add_asprintf(text, "%s %s\n",ln->key, ln->value); + } + joined = smartlist_join_strings(text, "", 0, NULL); + tt_str_op(joined, OP_EQ, + "Guard in=default rsa_id=052900AB0EA3ED54BAB84AE8A99E74E8693CE2B2 " + "nickname=5OfNovember sampled_on=2016-11-20T04:32:05 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1 confirmed_on=2016-11-22T08:13:28 confirmed_idx=0 " + "pb_circ_attempts=4.000000 pb_circ_successes=2.000000 " + "pb_successful_circuits_closed=2.000000\n" + "Guard in=default rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A " + "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1 confirmed_on=2016-11-24T08:45:30 confirmed_idx=1 " + "pb_circ_attempts=5.000000 pb_circ_successes=5.000000 " + "pb_successful_circuits_closed=5.000000\n" + "Guard in=default rsa_id=E9025AD60D86875D5F11548D536CC6AF60F0EF5E " + "nickname=maibrunn sampled_on=2016-11-25T22:36:38 " + "sampled_by=0.3.0.0-alpha-dev listed=1\n" + "Guard in=default rsa_id=DCD30B90BA3A792DA75DC54A327EF353FB84C38E " + "nickname=Unnamed sampled_on=2016-11-25T14:34:00 " + "sampled_by=0.3.0.0-alpha-dev listed=1\n" + "Guard in=wobblesome rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A " + "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1\n" + "Guard in=bridges rsa_id=8FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E " + "bridge_addr=24.1.1.1:443 sampled_on=2016-11-25T06:44:14 " + "sampled_by=0.3.0.0-alpha-dev listed=1 " + "confirmed_on=2016-11-29T10:36:06 confirmed_idx=0 " + "pb_circ_attempts=8.000000 pb_circ_successes=8.000000 " + "pb_successful_circuits_closed=13.000000\n" + "Guard in=bridges rsa_id=5800000000000000000000000000000000000000 " + "bridge_addr=37.218.246.143:28366 " + "sampled_on=2016-11-18T15:07:34 sampled_by=0.3.0.0-alpha-dev listed=1\n"); + + done: + config_free_lines(lines); + tor_free(state); + tor_free(msg); + UNMOCK(get_or_state); + UNMOCK(entry_guard_is_listed); + SMARTLIST_FOREACH(text, char *, cp, tor_free(cp)); + smartlist_free(text); + tor_free(joined); +} + +static void +test_entry_guard_parse_from_state_broken(void *arg) +{ + (void)arg; + /* Here's a variation on the previous state. Every line but the first is + * busted somehow. */ + const char STATE[] = + /* Okay. */ + "Guard in=default rsa_id=214F44BD5B638E8C817D47FF7C97397790BF0345 " + "nickname=TotallyNinja sampled_on=2016-11-12T19:32:49 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1\n" + /* No selection listed. */ + "Guard rsa_id=052900AB0EA3ED54BAB84AE8A99E74E8693CE2B2 " + "nickname=5OfNovember sampled_on=2016-11-20T04:32:05 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1 confirmed_on=2016-11-22T08:13:28 confirmed_idx=0 " + "pb_circ_attempts=4.000000 pb_circ_successes=2.000000 " + "pb_successful_circuits_closed=2.000000\n" + /* Selection is "legacy"!! */ + "Guard in=legacy rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A " + "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 " + "sampled_by=0.3.0.0-alpha-dev " + "listed=1 confirmed_on=2016-11-24T08:45:30 confirmed_idx=4 " + "pb_circ_attempts=5.000000 pb_circ_successes=5.000000 " + "pb_successful_circuits_closed=5.000000\n"; + + config_line_t *lines = NULL; + or_state_t *state = tor_malloc_zero(sizeof(or_state_t)); + int r = config_get_lines(STATE, &lines, 0); + char *msg = NULL; + + dummy_state = state; + MOCK(get_or_state, + get_or_state_replacement); + + tt_assert(r == 0); + tt_assert(lines); + + state->Guard = lines; + + /* First, no-set case. we should get an error. */ + r = entry_guards_parse_state(state, 0, &msg); + tt_int_op(r, OP_LT, 0); + tt_ptr_op(msg, OP_NE, NULL); + /* And we shouldn't have made anything. */ + guard_selection_t *gs_df = + get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); + tt_assert(gs_df == NULL); + tor_free(msg); + + /* Now see about the set case (which shouldn't happen IRL) */ + r = entry_guards_parse_state(state, 1, &msg); + tt_int_op(r, OP_LT, 0); + tt_ptr_op(msg, OP_NE, NULL); + gs_df = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); + tt_assert(gs_df != NULL); + tt_int_op(smartlist_len(gs_df->sampled_entry_guards), OP_EQ, 1); + guard_selection_t *gs_legacy = + get_guard_selection_by_name("legacy", GS_TYPE_LEGACY, 0); + tt_assert(gs_legacy != NULL); + tt_int_op(smartlist_len(gs_legacy->chosen_entry_guards), OP_EQ, 0); + + done: + config_free_lines(lines); + tor_free(state); + tor_free(msg); + UNMOCK(get_or_state); +} + static void test_entry_guard_get_guard_selection_by_name(void *arg) { @@ -3019,6 +3253,10 @@ struct testcase_t entrynodes_tests[] = { test_entry_guard_parse_from_state_failure, 0, NULL, NULL }, { "parse_from_state_partial_failure", test_entry_guard_parse_from_state_partial_failure, 0, NULL, NULL }, + { "parse_from_state_full", + test_entry_guard_parse_from_state_full, TT_FORK, NULL, NULL }, + { "parse_from_state_broken", + test_entry_guard_parse_from_state_broken, TT_FORK, NULL, NULL }, { "get_guard_selection_by_name", test_entry_guard_get_guard_selection_by_name, TT_FORK, NULL, NULL }, BFN_TEST(add_single_guard), From 52e196bab56d97f31e3fd24f187f842ac08cf7b7 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 13:10:35 -0500 Subject: [PATCH 68/80] Don't make $hexid nicknames persistent. (That's asking for trouble, and also totally completely redundant.) --- src/or/entrynodes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 1f6d562fd8..78257ca84d 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -2420,7 +2420,7 @@ entry_guard_encode_for_state(entry_guard_t *guard) fmt_and_decorate_addr(&guard->bridge_addr->addr), guard->bridge_addr->port); } - if (strlen(guard->nickname)) { + if (strlen(guard->nickname) && is_legal_nickname(guard->nickname)) { smartlist_add_asprintf(result, "nickname=%s", guard->nickname); } From 7361e1b499f3b2dc4a24192eed47d0adb668c25a Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 13:28:44 -0500 Subject: [PATCH 69/80] Tests for restricted-circuit cases of upgrade_waiting_circuits() --- src/test/test_entrynodes.c | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 1fbb8f8888..84fdf0745b 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -3078,6 +3078,45 @@ test_entry_guard_upgrade_blocked_by_better_circ_complete(void *arg) smartlist_free(result); } +static void +test_entry_guard_upgrade_not_blocked_by_restricted_circ_complete(void *arg) +{ + upgrade_circuits_data_t *data = arg; + + /* Once more, let circ1 become complete. But this time, we'll claim + * that circ2 was restricted to not use the same guard as circ1. */ + data->guard2_state->restrictions = + tor_malloc_zero(sizeof(entry_guard_restriction_t)); + memcpy(data->guard2_state->restrictions->exclude_id, + data->guard1->identity, DIGEST_LEN); + + smartlist_t *result = smartlist_new(); + int r; + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 1); + tt_int_op(smartlist_len(result), OP_EQ, 1); + origin_circuit_t *oc = smartlist_get(result, 0); + tt_ptr_op(oc, OP_EQ, data->circ1); + tt_ptr_op(data->guard1_state, OP_NE, NULL); + tt_int_op(data->guard1_state->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE); + + /* Now, we try again. Since circ2 has a restriction that circ1 doesn't obey, + * circ2 _is_ eligible for upgrade. */ + smartlist_clear(result); + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 1); + tt_int_op(smartlist_len(result), OP_EQ, 1); + origin_circuit_t *oc2 = smartlist_get(result, 0); + tt_ptr_op(oc2, OP_EQ, data->circ2); + + done: + smartlist_free(result); +} + static void test_entry_guard_upgrade_not_blocked_by_worse_circ_complete(void *arg) { @@ -3139,6 +3178,43 @@ test_entry_guard_upgrade_blocked_by_better_circ_pending(void *arg) smartlist_free(result); } +static void +test_entry_guard_upgrade_not_blocked_by_restricted_circ_pending(void *arg) +{ + upgrade_circuits_data_t *data = arg; + /* circ2 is done, but circ1 is still pending. But when there is a + restriction on circ2 that circ1 can't satisfy, circ1 can't block + circ2. */ + + /* XXXX Prop271 -- this is a kludge. I'm making sure circ1 _is_ better, + * by messing with the guards' confirmed_idx */ + make_guard_confirmed(data->gs, data->guard1); + { + int tmp; + tmp = data->guard1->confirmed_idx; + data->guard1->confirmed_idx = data->guard2->confirmed_idx; + data->guard2->confirmed_idx = tmp; + } + + data->guard2_state->restrictions = + tor_malloc_zero(sizeof(entry_guard_restriction_t)); + memcpy(data->guard2_state->restrictions->exclude_id, + data->guard1->identity, DIGEST_LEN); + + smartlist_t *result = smartlist_new(); + int r; + r = entry_guards_upgrade_waiting_circuits(data->gs, + data->all_origin_circuits, + result); + tt_int_op(r, OP_EQ, 1); + tt_int_op(smartlist_len(result), OP_EQ, 1); + origin_circuit_t *oc = smartlist_get(result, 0); + tt_ptr_op(oc, OP_EQ, data->circ2); + + done: + smartlist_free(result); +} + static void test_entry_guard_upgrade_not_blocked_by_worse_circ_pending(void *arg) { @@ -3283,8 +3359,12 @@ struct testcase_t entrynodes_tests[] = { UPGRADE_TEST(upgrade_blocked_by_live_primary_guards, "c1-done c2-done"), UPGRADE_TEST(upgrade_blocked_by_lack_of_waiting_circuits, ""), UPGRADE_TEST(upgrade_blocked_by_better_circ_complete, "c1-done c2-done"), + UPGRADE_TEST(upgrade_not_blocked_by_restricted_circ_complete, + "c1-done c2-done"), UPGRADE_TEST(upgrade_not_blocked_by_worse_circ_complete, "c1-done c2-done"), UPGRADE_TEST(upgrade_blocked_by_better_circ_pending, "c2-done"), + UPGRADE_TEST(upgrade_not_blocked_by_restricted_circ_pending, + "c2-done"), UPGRADE_TEST(upgrade_not_blocked_by_worse_circ_pending, "c1-done"), { "should_expire_waiting", test_enty_guard_should_expire_waiting, TT_FORK, NULL, NULL }, From d9f010db8448fa2aa4de80f0c26c41fafb25a694 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 13:37:37 -0500 Subject: [PATCH 70/80] Update node-selection tests to consider restrictions --- src/test/test_entrynodes.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 84fdf0745b..e443210196 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -2464,6 +2464,19 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) tt_i64_op(guard->unreachable_since, OP_EQ, approx_time() - 30); }); + /* Let's try again and we should get the first primary guard again */ + g = select_entry_guard_for_circuit(gs, NULL, &state); + tt_ptr_op(g, OP_EQ, smartlist_get(gs->primary_entry_guards, 0)); + g2 = select_entry_guard_for_circuit(gs, NULL, &state); + tt_ptr_op(g2, OP_EQ, g); + + /* But if we impose a restriction, we don't get the same guard */ + entry_guard_restriction_t rst; + memset(&rst, 0, sizeof(rst)); + memcpy(rst.exclude_id, g->identity, DIGEST_LEN); + g2 = select_entry_guard_for_circuit(gs, &rst, &state); + tt_ptr_op(g2, OP_NE, g); + done: guard_selection_free(gs); } @@ -2527,10 +2540,22 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time()); + // If we say that the next confirmed guard in order is excluded, we get + // The one AFTER that. + g = smartlist_get(gs->confirmed_entry_guards, + smartlist_len(gs->primary_entry_guards)+2); + entry_guard_restriction_t rst; + memset(&rst, 0, sizeof(rst)); + memcpy(rst.exclude_id, g->identity, DIGEST_LEN); + g2 = select_entry_guard_for_circuit(gs, &rst, &state); + tt_ptr_op(g2, OP_NE, g); + tt_int_op(g2->confirmed_idx, OP_EQ, + smartlist_len(gs->primary_entry_guards)+3); + // If we make every confirmed guard become pending then we start poking // other guards. const int n_remaining_confirmed = - N_CONFIRMED - 2 - smartlist_len(gs->primary_entry_guards); + N_CONFIRMED - 3 - smartlist_len(gs->primary_entry_guards); for (i = 0; i < n_remaining_confirmed; ++i) { g = select_entry_guard_for_circuit(gs, NULL, &state); tt_int_op(g->confirmed_idx, OP_GE, 0); From 72dc2ae319f66d3b4dec59709c28605912c6bc56 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 14:11:36 -0500 Subject: [PATCH 71/80] Tests for choosing which guard_selection to use --- src/test/test_entrynodes.c | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index e443210196..fbb3b13448 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1527,6 +1527,48 @@ test_entry_guard_get_guard_selection_by_name(void *arg) entry_guards_free_all(); } +static void +test_entry_guard_choose_selection_initial(void *arg) +{ + /* Tests for picking our initial guard selection (based on having had + * no previous selection */ + (void)arg; + guard_selection_type_t type = GS_TYPE_INFER; + const char *name = choose_guard_selection(get_options(), + dummy_consensus, NULL, &type); + tt_str_op(name, OP_EQ, "default"); + tt_int_op(type, OP_EQ, GS_TYPE_NORMAL); + + /* If we're using bridges, we get the bridge selection. */ + get_options_mutable()->UseBridges = 1; + name = choose_guard_selection(get_options(), + dummy_consensus, NULL, &type); + tt_str_op(name, OP_EQ, "bridges"); + tt_int_op(type, OP_EQ, GS_TYPE_BRIDGE); + get_options_mutable()->UseBridges = 0; + + /* If we're using legacy guards, we get the legacy selection */ + get_options_mutable()->UseDeprecatedGuardAlgorithm = 1; + name = choose_guard_selection(get_options(), + dummy_consensus, NULL, &type); + tt_str_op(name, OP_EQ, "legacy"); + tt_int_op(type, OP_EQ, GS_TYPE_LEGACY); + get_options_mutable()->UseDeprecatedGuardAlgorithm = 0; + + /* If we discard >99% of our guards, though, we should be in the restricted + * set. */ + tt_assert(get_options_mutable()->EntryNodes == NULL); + get_options_mutable()->EntryNodes = routerset_new(); + routerset_parse(get_options_mutable()->EntryNodes, "1.0.0.0/8", "foo"); + name = choose_guard_selection(get_options(), + dummy_consensus, NULL, &type); + tt_str_op(name, OP_EQ, "restricted"); + tt_int_op(type, OP_EQ, GS_TYPE_RESTRICTED); + + done: + ; +} + static void test_entry_guard_add_single_guard(void *arg) { @@ -3360,6 +3402,7 @@ struct testcase_t entrynodes_tests[] = { test_entry_guard_parse_from_state_broken, TT_FORK, NULL, NULL }, { "get_guard_selection_by_name", test_entry_guard_get_guard_selection_by_name, TT_FORK, NULL, NULL }, + BFN_TEST(choose_selection_initial), BFN_TEST(add_single_guard), BFN_TEST(node_filter), BFN_TEST(expand_sample), From 2b4bfe62ee74b927d65923f5d07fe04f51f8779a Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Nov 2016 14:25:16 -0500 Subject: [PATCH 72/80] Fix a signed/unsigned warning on 32-bit --- src/or/entrynodes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 78257ca84d..ac621552ed 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1665,7 +1665,7 @@ entry_guards_update_primary(guard_selection_t *gs) * Return the number of seconds after the last attempt at which we should * retry a guard that has been failing since failing_since. */ -static unsigned +static int get_retry_schedule(time_t failing_since, time_t now, int is_primary) { @@ -1712,7 +1712,7 @@ entry_guard_consider_retry(entry_guard_t *guard) return; /* No retry needed. */ const time_t now = approx_time(); - const unsigned delay = + const int delay = get_retry_schedule(guard->failing_since, now, guard->is_primary); const time_t last_attempt = guard->last_tried_to_connect; From 68679504323b0a676a446b8fb34b976c9dc66b4f Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 7 Dec 2016 12:36:13 -0500 Subject: [PATCH 73/80] Wrap all of the legacy guard code, and its users, in #ifdefs This will make it easier to see what we remove down the line. --- src/or/bridges.c | 8 ++++ src/or/channel.c | 2 + src/or/circpathbias.c | 6 +++ src/or/circuitbuild.c | 2 + src/or/circuituse.c | 16 +++---- src/or/config.c | 9 ++++ src/or/connection_or.c | 4 ++ src/or/control.c | 5 +++ src/or/entrynodes.c | 91 ++++++++++++++++++++++++++++++++++++-- src/or/entrynodes.h | 26 +++++++++++ src/or/routerlist.c | 6 +++ src/test/test_entrynodes.c | 20 +++++++-- src/test/test_routerlist.c | 10 +++++ 13 files changed, 188 insertions(+), 17 deletions(-) diff --git a/src/or/bridges.c b/src/or/bridges.c index c480e3fb7b..4058979bfb 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -743,7 +743,11 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) (int) bridge->port); } if (get_options()->UseDeprecatedGuardAlgorithm) { +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM add_bridge_as_entry_guard(get_guard_selection_info(), node); +#else + tor_assert_nonfatal_unreached(); +#endif } else { entry_guard_learned_bridge_identity(&bridge->addrport_configured, (const uint8_t*)ri->cache_info.identity_digest); @@ -754,8 +758,12 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) /* set entry->made_contact so if it goes down we don't drop it from * our entry node list */ if (get_options()->UseDeprecatedGuardAlgorithm) { +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM entry_guard_register_connect_status(ri->cache_info.identity_digest, 1, 0, now); +#else + tor_assert_nonfatal_unreached(); +#endif } if (first) { routerlist_retry_directory_downloads(now); diff --git a/src/or/channel.c b/src/or/channel.c index 1e3e99c51d..9898148dde 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -2538,6 +2538,7 @@ channel_do_open_actions(channel_t *chan) if (started_here) { circuit_build_times_network_is_live(get_circuit_build_times_mutable()); rep_hist_note_connect_succeeded(chan->identity_digest, now); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM // XXXX prop271 this call is no longer useful with the new algorithm. if (entry_guard_register_connect_status( chan->identity_digest, 1, 0, now) < 0) { @@ -2549,6 +2550,7 @@ channel_do_open_actions(channel_t *chan) "connection so we can retry the earlier entry guards."); close_origin_circuits = 1; } +#endif router_set_status(chan->identity_digest, 1); } else { /* only report it to the geoip module if it's not a known router */ diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index be1146513e..d86d70f1ff 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -1279,7 +1279,10 @@ pathbias_measure_use_rate(entry_guard_t *guard) tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); pb->path_bias_disabled = 1; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM + // XXXX entry_guard_mark_bad(guard); +#endif return; } } else if (!pb->path_bias_use_extreme) { @@ -1385,7 +1388,10 @@ pathbias_measure_close_rate(entry_guard_t *guard) tor_lround(pb->timeouts), tor_lround(get_circuit_build_close_time_ms()/1000)); pb->path_bias_disabled = 1; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM + // XXXX entry_guard_mark_bad(guard); +#endif return; } } else if (!pb->path_bias_extreme) { diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 07903092b5..bf52b90730 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2277,6 +2277,7 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state, * family. */ nodelist_add_node_and_family(excluded, node); } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /* and exclude current entry guards and their families, * unless we're in a test network, and excluding guards * would exclude all nodes (i.e. we're in an incredibly small tor network, @@ -2295,6 +2296,7 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state, } }); } +#endif if (state) { if (state->need_uptime) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 787c490387..8e0fbd1d39 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -550,16 +550,14 @@ circuit_expire_building(void) == CPATH_STATE_OPEN; log_info(LD_CIRC, "No circuits are opened. Relaxing timeout for circuit %d " - "(a %s %d-hop circuit in state %s with channel state %s). " - "%d guards are live.", + "(a %s %d-hop circuit in state %s with channel state %s).", TO_ORIGIN_CIRCUIT(victim)->global_identifier, circuit_purpose_to_string(victim->purpose), TO_ORIGIN_CIRCUIT(victim)->build_state ? TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len : -1, circuit_state_to_string(victim->state), - channel_state_to_string(victim->n_chan->state), - num_live_entry_guards(0)); + channel_state_to_string(victim->n_chan->state)); /* We count the timeout here for CBT, because technically this * was a timeout, and the timeout value needs to reset if we @@ -577,7 +575,7 @@ circuit_expire_building(void) "No circuits are opened. Relaxed timeout for circuit %d " "(a %s %d-hop circuit in state %s with channel state %s) to " "%ldms. However, it appears the circuit has timed out " - "anyway. %d guards are live.", + "anyway.", TO_ORIGIN_CIRCUIT(victim)->global_identifier, circuit_purpose_to_string(victim->purpose), TO_ORIGIN_CIRCUIT(victim)->build_state ? @@ -585,8 +583,7 @@ circuit_expire_building(void) -1, circuit_state_to_string(victim->state), channel_state_to_string(victim->n_chan->state), - (long)build_close_ms, - num_live_entry_guards(0)); + (long)build_close_ms); } } @@ -1655,8 +1652,10 @@ circuit_build_failed(origin_circuit_t *circ) /* New guard API: we failed. */ if (circ->guard_state) entry_guard_failed(&circ->guard_state); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /* Old guard API: we failed. */ entry_guard_register_connect_status(n_chan_id, 0, 1, time(NULL)); +#endif /* if there are any one-hop streams waiting on this circuit, fail * them now so they can retry elsewhere. */ connection_ap_fail_onehop(n_chan_id, circ->build_state); @@ -1966,7 +1965,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, int severity = LOG_NOTICE; /* Retry some stuff that might help the connection work. */ if (entry_list_is_constrained(options) && - entries_known_but_down(options)) { + guards_retry_optimistic(options)) { log_fn(severity, LD_APP|LD_DIR, "Application request when we haven't %s. " "Optimistically trying known %s again.", @@ -1974,7 +1973,6 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, "used client functionality lately" : "received a consensus with exits", options->UseBridges ? "bridges" : "entrynodes"); - entries_retry_all(options); } else if (!options->UseBridges || any_bridge_descriptors_known()) { log_fn(severity, LD_APP|LD_DIR, "Application request when we haven't %s. " diff --git a/src/or/config.c b/src/or/config.c index 22e5dfdaa0..2ec96d39e9 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -2116,11 +2116,13 @@ options_act(const or_options_t *old_options) rep_hist_desc_stats_term(); /* Check if we need to parse and add the EntryNodes config option. */ +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM if (options->EntryNodes && (!old_options || !routerset_equal(old_options->EntryNodes,options->EntryNodes) || !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes))) entry_nodes_should_be_added(); +#endif /* Since our options changed, we might need to regenerate and upload our * server descriptor. @@ -3040,6 +3042,13 @@ options_validate(or_options_t *old_options, or_options_t *options, warn_about_relative_paths(options); +#ifndef ENABLE_LEGACY_GUARD_ALGORITHM + if (options->UseDeprecatedGuardAlgorithm) { + log_warn(LD_CONFIG, "DeprecatedGuardAlgorithm not supported."); + return -1; + } +#endif + if (server_mode(options) && (!strcmpstart(uname, "Windows 95") || !strcmpstart(uname, "Windows 98") || diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 3b6f82c904..6233fb7669 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -737,9 +737,11 @@ connection_or_about_to_close(or_connection_t *or_conn) rep_hist_note_connect_failed(or_conn->identity_digest, now); /* Tell the new guard API about the channel failure */ entry_guard_chan_failed(TLS_CHAN_TO_BASE(or_conn->chan)); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /* Tell the old guard API about the channel failure */ entry_guard_register_connect_status(or_conn->identity_digest,0, !options->HTTPSProxy, now); +#endif if (conn->state >= OR_CONN_STATE_TLS_HANDSHAKING) { int reason = tls_error_to_orconn_end_reason(or_conn->tls_error); control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED, @@ -1678,9 +1680,11 @@ connection_or_client_learned_peer_id(or_connection_t *conn, conn->base_.address, conn->base_.port, expected, seen, extra_log); /* Tell the new guard API about the channel failure */ entry_guard_chan_failed(TLS_CHAN_TO_BASE(conn->chan)); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /* Tell the old guard API about the channel failure */ entry_guard_register_connect_status(conn->identity_digest, 0, 1, time(NULL)); +#endif control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, END_OR_CONN_REASON_OR_IDENTITY); if (!authdir_mode_tests_reachability(options)) diff --git a/src/or/control.c b/src/or/control.c index 03742e8731..9a8845a0d3 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -4041,12 +4041,17 @@ handle_control_dropguards(control_connection_t *conn, smartlist_split_string(args, body, " ", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM if (smartlist_len(args)) { connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n"); } else { remove_all_entry_guards(); send_control_done(conn); } +#else + // XXXX + connection_printf_to_buf(conn, "512 not supported\r\n"); +#endif SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); smartlist_free(args); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index ac621552ed..76070a3b7d 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -149,11 +149,13 @@ static guard_selection_t *curr_guard_context = NULL; * and those changes need to be flushed to disk. */ static int entry_guards_dirty = 0; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM static const node_t *choose_random_entry_impl(guard_selection_t *gs, cpath_build_state_t *state, int for_directory, dirinfo_type_t dirtype, int *n_options_out); +#endif static void entry_guard_set_filtered_flags(const or_options_t *options, guard_selection_t *gs, entry_guard_t *guard); @@ -228,7 +230,9 @@ guard_selection_new(const char *name, gs = tor_malloc_zero(sizeof(*gs)); gs->name = tor_strdup(name); gs->type = type; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM gs->chosen_entry_guards = smartlist_new(); +#endif gs->sampled_entry_guards = smartlist_new(); gs->confirmed_entry_guards = smartlist_new(); gs->primary_entry_guards = smartlist_new(); @@ -298,6 +302,7 @@ get_guard_selection_info(void) return curr_guard_context; } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** Return the list of entry guards for a guard_selection_t, creating it * if necessary. */ const smartlist_t * @@ -324,6 +329,7 @@ entry_guard_mark_bad(entry_guard_t *guard) guard->bad_since = approx_time(); entry_guards_changed(); } +#endif /** Return a statically allocated human-readable description of guard */ @@ -2831,6 +2837,7 @@ entry_guards_load_guards_from_state(or_state_t *state, int set) /* XXXXX prop271 ----- end of new-for-prop271 code ----- */ /* XXXXX ----------------------------------------------- */ +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** * @name Constants for old (pre-prop271) guard selection algorithm. */ @@ -3109,6 +3116,7 @@ num_live_entry_guards(int for_directory) return num_live_entry_guards_for_guard_selection( get_guard_selection_info(), for_directory); } +#endif /** If digest matches the identity of any node in the * entry_guards list for the provided guard selection state, @@ -3123,11 +3131,12 @@ entry_guard_get_by_id_digest_for_guard_selection(guard_selection_t *gs, if (tor_memeq(digest, entry->identity, DIGEST_LEN)) return entry; ); - +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM SMARTLIST_FOREACH(gs->chosen_entry_guards, entry_guard_t *, entry, if (tor_memeq(digest, entry->identity, DIGEST_LEN)) return entry; ); +#endif return NULL; } @@ -3150,6 +3159,7 @@ entry_guard_get_by_id_digest(const char *digest) get_guard_selection_info(), digest); } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** Dump a description of our list of entry guards in the given guard * selection context to the log at level severity. */ static void @@ -3375,6 +3385,7 @@ pick_entry_guards(guard_selection_t *gs, if (changed) entry_guards_changed_for_guard_selection(gs); } +#endif /** Release all storage held by e. */ STATIC void @@ -3383,7 +3394,9 @@ entry_guard_free(entry_guard_t *e) if (!e) return; entry_guard_handles_clear(e); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM tor_free(e->chosen_by_version); +#endif tor_free(e->sampled_by_version); tor_free(e->extra_state_fields); tor_free(e->selection_name); @@ -3391,6 +3404,7 @@ entry_guard_free(entry_guard_t *e) tor_free(e); } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** Remove from a guard selection context any entry guard which was selected * by an unknown version of Tor, or which was selected by a version of Tor * that's known to select entry guards badly, or which was selected more 2 @@ -3869,6 +3883,7 @@ entry_guards_set_from_config(guard_selection_t *gs, smartlist_free(old_entry_guards_not_on_list); entry_guards_changed_for_guard_selection(gs); } +#endif /** Return 0 if we're fine adding arbitrary routers out of the * directory to our entry guard list, or return 1 if we have a @@ -3877,6 +3892,7 @@ entry_guards_set_from_config(guard_selection_t *gs, int entry_list_is_constrained(const or_options_t *options) { + // XXXX prop271 look at the current selection. if (options->EntryNodes) return 1; if (options->UseBridges) @@ -3884,6 +3900,7 @@ entry_list_is_constrained(const or_options_t *options) return 0; } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** Pick a live (up and listed) entry guard from entry_guards. If * state is non-NULL, this is for a specific circuit -- * make sure not to pick this circuit's exit or any node in the @@ -3910,6 +3927,7 @@ choose_random_dirguard(dirinfo_type_t type) return choose_random_entry_impl(get_guard_selection_info(), NULL, 1, type, NULL); } +#endif /** Return the number of bridges that have descriptors that are marked with * purpose 'bridge' and are running. @@ -3920,10 +3938,13 @@ num_bridges_usable(void) int n_options = 0; if (get_options()->UseDeprecatedGuardAlgorithm) { - +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM tor_assert(get_options()->UseBridges); (void) choose_random_entry_impl(get_guard_selection_info(), NULL, 0, 0, &n_options); +#else + tor_assert_nonfatal_unreached(); +#endif } else { /* XXXX prop271 Is this quite right? */ tor_assert(get_options()->UseBridges); @@ -3944,6 +3965,7 @@ num_bridges_usable(void) return n_options; } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** Filter all_entry_guards for usable entry guards and put them * in live_entry_guards. We filter based on whether the node is * currently alive, and on whether it satisfies the restrictions @@ -4156,6 +4178,7 @@ choose_random_entry_impl(guard_selection_t *gs, smartlist_free(live_entry_guards); return node; } +#endif static void pathbias_check_use_success_count(entry_guard_t *node) @@ -4199,6 +4222,7 @@ pathbias_check_close_success_count(entry_guard_t *node) } } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** Parse state and learn about the entry guards it describes. * If set is true, and there are no errors, replace the guard * list in the provided guard selection context with what we find. @@ -4452,6 +4476,7 @@ entry_guards_parse_state_for_guard_selection( digestmap_free(added_by, tor_free_); return *msg ? -1 : 0; } +#endif /** Parse state and learn about the entry guards it describes. * If set is true, and there are no errors, replace the guard @@ -4466,9 +4491,13 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) int r1 = entry_guards_load_guards_from_state(state, set); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM int r2 = entry_guards_parse_state_for_guard_selection( get_guard_selection_by_name("legacy", GS_TYPE_LEGACY, 1), state, set, msg); +#else + int r2 = 0; +#endif entry_guards_dirty = 0; @@ -4530,8 +4559,6 @@ entry_guards_changed(void) void entry_guards_update_state(or_state_t *state) { - config_line_t **next, *line; - entry_guards_dirty = 0; // Handles all non-legacy guard info. @@ -4539,6 +4566,9 @@ entry_guards_update_state(or_state_t *state) entry_guards_dirty = 0; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM + config_line_t **next, *line; + guard_selection_t *gs; gs = get_guard_selection_by_name("legacy", GS_TYPE_LEGACY, 0); if (!gs) @@ -4612,6 +4642,7 @@ entry_guards_update_state(or_state_t *state) } } SMARTLIST_FOREACH_END(e); +#endif if (!get_options()->AvoidDiskWrites) or_state_mark_dirty(get_or_state(), 0); entry_guards_dirty = 0; @@ -4634,11 +4665,18 @@ getinfo_helper_entry_guards(control_connection_t *conn, guard_selection_t *gs = get_guard_selection_info(); tor_assert(gs != NULL); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM tor_assert(gs->chosen_entry_guards != NULL); +#else + // XXXX + (void)question; + (void)answer; +#endif (void) conn; (void) errmsg; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM if (!strcmp(question,"entry-guards") || !strcmp(question,"helper-nodes")) { smartlist_t *sl = smartlist_new(); @@ -4683,6 +4721,7 @@ getinfo_helper_entry_guards(control_connection_t *conn, SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); smartlist_free(sl); } +#endif return 0; } @@ -4721,6 +4760,7 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, guardfraction_bw->non_guard_bw = orig_bandwidth - (int) guard_bw; } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** Returns true iff the node is used as a guard in the specified guard * context */ int @@ -4819,6 +4859,7 @@ entries_retry_all(const or_options_t *options) tor_assert(entry_list_is_constrained(options)); entries_retry_helper(options, 1); } +#endif /** Helper: Update the status of all entry guards, in whatever algorithm * is used. Return true if we should stop using all previously generated @@ -4833,7 +4874,11 @@ guards_update_all(void) tor_assert(curr_guard_context); if (curr_guard_context->type == GS_TYPE_LEGACY) { +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM entry_guards_compute_status(get_options(), approx_time()); +#else + tor_assert_nonfatal_unreached(); +#endif } else { if (entry_guards_update_all(curr_guard_context)) mark_circuits = 1; @@ -4849,7 +4894,12 @@ guards_choose_guard(cpath_build_state_t *state, circuit_guard_state_t **guard_state_out) { if (get_options()->UseDeprecatedGuardAlgorithm) { +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM return choose_random_entry(state); +#else + tor_assert_nonfatal_unreached(); + return NULL; +#endif } else { const node_t *r = NULL; const uint8_t *exit_id = NULL; @@ -4877,7 +4927,13 @@ guards_choose_dirguard(dirinfo_type_t info, circuit_guard_state_t **guard_state_out) { if (get_options()->UseDeprecatedGuardAlgorithm) { +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM return choose_random_dirguard(info); +#else + (void)info; + tor_assert_nonfatal_unreached(); + return NULL; +#endif } else { /* XXXX prop271 We don't need to look at the dirinfo_type_t here, * apparently. If you look at the old implementation, and you follow info @@ -4897,6 +4953,31 @@ guards_choose_dirguard(dirinfo_type_t info, } } +/** + * If we're running with a constrained guard set, then maybe mark our guards + * usable. Return 1 if we do; 0 if we don't. + */ +int +guards_retry_optimistic(const or_options_t *options) +{ + if (! entry_list_is_constrained(options)) + return 0; + +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM + if (options->UseDeprecatedGuardAlgorithm) { + if (entries_known_but_down(options)) { + entries_retry_all(options); + return 1; + } + } +#endif + + // XXXX prop271 -- is this correct? + mark_primary_guards_maybe_reachable(get_guard_selection_info()); + + return 1; +} + /** Free one guard selection context */ STATIC void guard_selection_free(guard_selection_t *gs) @@ -4905,12 +4986,14 @@ guard_selection_free(guard_selection_t *gs) tor_free(gs->name); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM if (gs->chosen_entry_guards) { SMARTLIST_FOREACH(gs->chosen_entry_guards, entry_guard_t *, e, entry_guard_free(e)); smartlist_free(gs->chosen_entry_guards); gs->chosen_entry_guards = NULL; } +#endif if (gs->sampled_entry_guards) { SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, e, diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index c05a3e3a2c..3250be1222 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -28,6 +28,11 @@ typedef struct circuit_guard_state_t circuit_guard_state_t; private. */ typedef struct entry_guard_restriction_t entry_guard_restriction_t; +/* + XXXX Prop271 undefine this in order to disable all legacy guard functions. +*/ +#define ENABLE_LEGACY_GUARD_ALGORITHM + /* Information about a guard's pathbias status. * These fields are used in circpathbias.c to try to detect entry * nodes that are failing circuits at a suspicious frequency. @@ -174,6 +179,7 @@ struct entry_guard_t { guard_selection_t *in_selection; /**@}*/ +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** * @name legacy guard selection algorithm fields * @@ -202,6 +208,7 @@ struct entry_guard_t { * at which we last failed to connect to it. */ /**}@*/ +#endif /** Path bias information for this guard. */ guard_pathbias_t pb; @@ -298,6 +305,7 @@ struct guard_selection_s { * confirmed_entry_guards receive? */ int next_confirmed_idx; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** * A list of our chosen entry guards, as entry_guard_t structures; this * preserves the pre-Prop271 behavior. @@ -310,6 +318,7 @@ struct guard_selection_s { * preserves the pre-Prop271 behavior. */ int should_add_entry_nodes; +#endif }; struct entry_guard_handle_t; @@ -369,9 +378,11 @@ entry_guard_t *entry_guard_get_by_id_digest(const char *digest); void entry_guards_changed_for_guard_selection(guard_selection_t *gs); void entry_guards_changed(void); guard_selection_t * get_guard_selection_info(void); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM const smartlist_t *get_entry_guards_for_guard_selection( guard_selection_t *gs); const smartlist_t *get_entry_guards(void); +#endif int num_live_entry_guards_for_guard_selection( guard_selection_t *gs, int for_directory); @@ -379,7 +390,9 @@ int num_live_entry_guards(int for_directory); #endif const node_t *entry_guard_find_node(const entry_guard_t *guard); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM void entry_guard_mark_bad(entry_guard_t *guard); +#endif const char *entry_guard_get_rsa_id_digest(const entry_guard_t *guard); const char *entry_guard_describe(const entry_guard_t *guard); guard_pathbias_t *entry_guard_get_pathbias_state(entry_guard_t *guard); @@ -408,8 +421,10 @@ void entry_guards_note_internet_connectivity(guard_selection_t *gs); int update_guard_selection_choice(const or_options_t *options); /* Used by bridges.c only. */ +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM void add_bridge_as_entry_guard(guard_selection_t *gs, const node_t *chosen); +#endif int num_bridges_usable(void); #ifdef ENTRYNODES_PRIVATE @@ -568,6 +583,7 @@ STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs, unsigned old_state); STATIC int entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM // ---------- XXXX this stuff is pre-prop271. STATIC const node_t *add_an_entry_guard(guard_selection_t *gs, @@ -599,16 +615,20 @@ STATIC const node_t *entry_is_live(const entry_guard_t *e, const char **msg); STATIC int entry_is_time_to_retry(const entry_guard_t *e, time_t now); +#endif #endif +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM void remove_all_entry_guards_for_guard_selection(guard_selection_t *gs); void remove_all_entry_guards(void); +#endif struct bridge_info_t; void entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, const uint8_t *rsa_id_digest); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM void entry_guards_compute_status_for_guard_selection( guard_selection_t *gs, const or_options_t *options, time_t now); void entry_guards_compute_status(const or_options_t *options, time_t now); @@ -619,9 +639,13 @@ int entry_guard_register_connect_status(const char *digest, int succeeded, int mark_relay_status, time_t now); void entry_nodes_should_be_added_for_guard_selection(guard_selection_t *gs); void entry_nodes_should_be_added(void); +#endif int entry_list_is_constrained(const or_options_t *options); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM const node_t *choose_random_entry(cpath_build_state_t *state); const node_t *choose_random_dirguard(dirinfo_type_t t); +#endif +int guards_retry_optimistic(const or_options_t *options); int entry_guards_parse_state_for_guard_selection( guard_selection_t *gs, or_state_t *state, int set, char **msg); int entry_guards_parse_state(or_state_t *state, int set, char **msg); @@ -629,9 +653,11 @@ void entry_guards_update_state(or_state_t *state); int getinfo_helper_entry_guards(control_connection_t *conn, const char *question, char **answer, const char **errmsg); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM int is_node_used_as_guard_for_guard_selection(guard_selection_t *gs, const node_t *node); MOCK_DECL(int, is_node_used_as_guard, (const node_t *node)); +#endif int entries_known_but_down(const or_options_t *options); void entries_retry_all(const or_options_t *options); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index d2f360a63f..cfa570c4a0 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -2003,6 +2003,10 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, int try_excluding = 1, n_excluded = 0, n_busy = 0; int try_ip_pref = 1; +#ifndef ENABLE_LEGACY_GUARD_ALGORITHM + tor_assert_nonfatal(! for_guard); +#endif + if (!consensus) return NULL; @@ -2038,10 +2042,12 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, if ((type & EXTRAINFO_DIRINFO) && !router_supports_extrainfo(node->identity, is_trusted_extrainfo)) continue; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /* Don't make the same node a guard twice */ if (for_guard && is_node_used_as_guard(node)) { continue; } +#endif /* Ensure that a directory guard is actually a guard node. */ if (for_guard && !node->is_possible_guard) { continue; diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index fbb3b13448..594708a12b 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -45,6 +45,7 @@ get_or_state_replacement(void) return dummy_state; } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /* Unittest cleanup function: Cleanup the fake network. */ static int fake_network_cleanup(const struct testcase_t *testcase, void *ptr) @@ -77,6 +78,7 @@ fake_network_setup(const struct testcase_t *testcase) /* Return anything but NULL (it's interpreted as test fail) */ return dummy_state; } +#endif static networkstatus_t *dummy_consensus = NULL; @@ -200,6 +202,7 @@ mock_get_options(void) return &mocked_options; } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM /** Test choose_random_entry() with none of our routers being guard nodes. */ static void test_choose_random_entry_no_guards(void *arg) @@ -888,6 +891,7 @@ test_entry_is_live(void *arg) done: ; /* XXX */ } +#endif #define TEST_IPV4_ADDR "123.45.67.89" #define TEST_IPV6_ADDR "[1234:5678:90ab:cdef::]" @@ -1471,8 +1475,12 @@ test_entry_guard_parse_from_state_broken(void *arg) tt_int_op(smartlist_len(gs_df->sampled_entry_guards), OP_EQ, 1); guard_selection_t *gs_legacy = get_guard_selection_by_name("legacy", GS_TYPE_LEGACY, 0); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM tt_assert(gs_legacy != NULL); tt_int_op(smartlist_len(gs_legacy->chosen_entry_guards), OP_EQ, 0); +#else + tt_assert(gs_legacy == NULL); +#endif done: config_free_lines(lines); @@ -2455,7 +2463,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) // if we mark that guard down, we should get a different primary guard. // auto-retry it. g->is_reachable = GUARD_REACHABLE_NO; - g->unreachable_since = approx_time() - 10; + g->failing_since = approx_time() - 10; g->last_tried_to_connect = approx_time() - 10; state = 9999; g2 = select_entry_guard_for_circuit(gs, NULL, &state); @@ -2469,7 +2477,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) // If we say that the first primary guard was last tried a long time ago, we // should get an automatic retry on it. - g->unreachable_since = approx_time() - 72*60*60; + g->failing_since = approx_time() - 72*60*60; g->last_tried_to_connect = approx_time() - 72*60*60; state = 9999; g2 = select_entry_guard_for_circuit(gs, NULL, &state); @@ -2484,7 +2492,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, guard, { guard->is_reachable = GUARD_REACHABLE_NO; guard->last_tried_to_connect = approx_time() - 5; - guard->unreachable_since = approx_time() - 30; + guard->failing_since = approx_time() - 30; }); state = 9999; g2 = select_entry_guard_for_circuit(gs, NULL, &state); @@ -2503,7 +2511,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) tt_assert(guard->is_usable_filtered_guard == 1); // no change to these fields. tt_i64_op(guard->last_tried_to_connect, OP_EQ, approx_time() - 5); - tt_i64_op(guard->unreachable_since, OP_EQ, approx_time() - 30); + tt_i64_op(guard->failing_since, OP_EQ, approx_time() - 30); }); /* Let's try again and we should get the first primary guard again */ @@ -3334,9 +3342,11 @@ test_enty_guard_should_expire_waiting(void *arg) tor_free(fake_state); } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; +#endif static const struct testcase_setup_t big_fake_network = { big_fake_network_setup, big_fake_network_cleanup @@ -3354,6 +3364,7 @@ static const struct testcase_setup_t upgrade_circuits = { (void*)(arg) } struct testcase_t entrynodes_tests[] = { +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM { "entry_is_time_to_retry", test_entry_is_time_to_retry, TT_FORK, NULL, NULL }, { "choose_random_entry_no_guards", test_choose_random_entry_no_guards, @@ -3379,6 +3390,7 @@ struct testcase_t entrynodes_tests[] = { { "entry_is_live", test_entry_is_live, TT_FORK, &fake_network, NULL }, +#endif { "node_preferred_orport", test_node_preferred_orport, 0, NULL, NULL }, diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index af5c121ce2..73e8d1047c 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -204,6 +204,7 @@ mock_usable_consensus_flavor(void) return mock_usable_consensus_flavor_value; } +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM static smartlist_t *mock_is_guard_list = NULL; static int @@ -250,6 +251,7 @@ clear_mock_guard_list(void) mock_is_guard_list = NULL; } } +#endif static void test_router_pick_directory_server_impl(void *arg) @@ -271,7 +273,9 @@ test_router_pick_directory_server_impl(void *arg) (void)arg; MOCK(usable_consensus_flavor, mock_usable_consensus_flavor); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM MOCK(is_node_used_as_guard, mock_is_node_used_as_guard); +#endif /* With no consensus, we must be bootstrapping, regardless of time or flavor */ @@ -384,6 +388,7 @@ test_router_pick_directory_server_impl(void *arg) node_router1->is_valid = 1; node_router3->is_valid = 1; +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM flags |= PDS_FOR_GUARD; mark_node_used_as_guard(node_router1); mark_node_used_as_guard(node_router2); @@ -397,8 +402,10 @@ test_router_pick_directory_server_impl(void *arg) rs = NULL; mark_node_unused_as_guard(node_router2); mark_node_unused_as_guard(node_router3); +#endif /* One not valid, one guard. This should leave one remaining */ +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM node_router1->is_valid = 0; mark_node_used_as_guard(node_router2); rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); @@ -407,6 +414,7 @@ test_router_pick_directory_server_impl(void *arg) rs = NULL; node_router1->is_valid = 1; mark_node_unused_as_guard(node_router2); +#endif /* Manipulate overloaded */ @@ -469,8 +477,10 @@ test_router_pick_directory_server_impl(void *arg) done: UNMOCK(usable_consensus_flavor); +#ifdef ENABLE_LEGACY_GUARD_ALGORITHM UNMOCK(is_node_used_as_guard); clear_mock_guard_list(); +#endif if (router1_id) tor_free(router1_id); From 7ab2678074e5d49628d948fadb80c5904950236c Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 6 Dec 2016 14:34:48 -0500 Subject: [PATCH 74/80] Trivial documentation improvements. --- src/or/circuitlist.c | 13 ++++++------- src/or/entrynodes.c | 17 +++++++++++++---- src/or/entrynodes.h | 9 ++++++--- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index b25f817d91..ab38b54985 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -562,7 +562,6 @@ circuit_get_global_list,(void)) return global_circuitlist; } -/** */ /** Return a pointer to the global list of origin circuits. */ smartlist_t * circuit_get_global_origin_circuit_list(void) @@ -1758,17 +1757,17 @@ circuit_find_circuits_to_upgrade_from_guard_wait(void) if (! circuits_pending_other_guards || smartlist_len(circuits_pending_other_guards)==0) return NULL; - /* Only if we have some origin circuiuts should we run the algorithm. - */ + /* Only if we have some origin circuits should we run the algorithm. */ if (!global_origin_circuit_list) return NULL; /* Okay; we can pass our circuit list to entrynodes.c.*/ smartlist_t *result = smartlist_new(); - int r = entry_guards_upgrade_waiting_circuits(get_guard_selection_info(), - global_origin_circuit_list, - result); - if (r && smartlist_len(result)) { + int circuits_upgraded = entry_guards_upgrade_waiting_circuits( + get_guard_selection_info(), + global_origin_circuit_list, + result); + if (circuits_upgraded && smartlist_len(result)) { return result; } else { smartlist_free(result); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 76070a3b7d..a28603d98d 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -142,7 +142,9 @@ #include "transports.h" #include "statefile.h" +/** A list of existing guard selection contexts. */ static smartlist_t *guard_contexts = NULL; +/** The currently enabled guard selection context. */ static guard_selection_t *curr_guard_context = NULL; /** A value of 1 means that at least one context has changed, @@ -593,7 +595,8 @@ choose_guard_selection(const or_options_t *options, "rest of the world.", (int)(exclude_frac * 100)); } - /* Easy case: no previous selection */ + /* Easy case: no previous selection. Just check if we are in restricted or + normal guard selection. */ if (old_selection == NULL) { if (n_passing_filter >= meaningful_threshold_mid) { *type_out = GS_TYPE_NORMAL; @@ -768,8 +771,9 @@ entry_guard_add_to_sample(guard_selection_t *gs, /** * Backend: adds a new sampled guard to gs, with given identity, - * nickname, and ORPort. rsa_id_digest and bridge_addrport are - * optional, but we need one of them. nickname is optional. + * nickname, and ORPort. rsa_id_digest and bridge_addrport are optional, but + * we need one of them. nickname is optional. The caller is responsible for + * maintaining the size limit of the SAMPLED_GUARDS set. */ static entry_guard_t * entry_guard_add_to_sample_impl(guard_selection_t *gs, @@ -2171,7 +2175,8 @@ entry_guards_all_primary_guards_are_down(guard_selection_t *gs) } /** Wrapper for entry_guard_has_higher_priority that compares the - * guard-priorities of a pair of circuits. + * guard-priorities of a pair of circuits. Return 1 if a has higher + * priority than b. * * If a restriction is provided in rst, then do not consider * a to have higher priority if it violates the restriction. @@ -4180,6 +4185,8 @@ choose_random_entry_impl(guard_selection_t *gs, } #endif +/** Check the pathbias use success count of node and disable it if it + * goes over our thresholds. */ static void pathbias_check_use_success_count(entry_guard_t *node) { @@ -4201,6 +4208,8 @@ pathbias_check_use_success_count(entry_guard_t *node) } } +/** Check the pathbias close count of node and disable it if it goes + * over our thresholds. */ static void pathbias_check_close_success_count(entry_guard_t *node) { diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 3250be1222..116e5abb8c 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -402,11 +402,14 @@ int entry_guard_pick_for_circuit(guard_selection_t *gs, entry_guard_restriction_t *rst, const node_t **chosen_node_out, circuit_guard_state_t **guard_state_out); + +/* We just connected to an entry guard. What should we do with the circuit? */ typedef enum { - GUARD_USABLE_NEVER = -1, - GUARD_MAYBE_USABLE_LATER = 0, - GUARD_USABLE_NOW = 1, + GUARD_USABLE_NEVER = -1, /* Never use the circuit */ + GUARD_MAYBE_USABLE_LATER = 0, /* Keep it. We might use it in the future */ + GUARD_USABLE_NOW = 1, /* Use it right now */ } guard_usable_t; + guard_usable_t entry_guard_succeeded(circuit_guard_state_t **guard_state_p); void entry_guard_failed(circuit_guard_state_t **guard_state_p); void entry_guard_cancel(circuit_guard_state_t **guard_state_p); From 50783d0123c38c649851421f33c616e0bf75d827 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 6 Dec 2016 14:35:31 -0500 Subject: [PATCH 75/80] Easy code fixes. - Correctly maintain the previous guard selection in choose_guard_selection(). - Print bridge identifier instead of nothing in entry_guard_describe()._ --- src/or/entrynodes.c | 19 +++++++++++++------ src/or/entrynodes.h | 6 +++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index a28603d98d..bcf4182640 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -341,7 +341,8 @@ entry_guard_describe(const entry_guard_t *guard) static char buf[256]; tor_snprintf(buf, sizeof(buf), "%s ($%s)", - guard->nickname, hex_str(guard->identity, DIGEST_LEN)); + guard->nickname ? guard->nickname : "[bridge]", + hex_str(guard->identity, DIGEST_LEN)); return buf; } @@ -527,7 +528,7 @@ get_extreme_restriction_threshold(void) STATIC const char * choose_guard_selection(const or_options_t *options, const networkstatus_t *live_ns, - const char *old_selection, + const guard_selection_t *old_selection, guard_selection_type_t *type_out) { tor_assert(options); @@ -607,7 +608,11 @@ choose_guard_selection(const or_options_t *options, } } - /* Trickier case: we do have a previous selection */ + /* Trickier case: we do have a previous guard selection context. */ + tor_assert(old_selection); + + /* Use high and low thresholds to decide guard selection, and if we fall in + the middle then keep the current guard selection context. */ if (n_passing_filter >= meaningful_threshold_high) { *type_out = GS_TYPE_NORMAL; return "default"; @@ -615,7 +620,9 @@ choose_guard_selection(const or_options_t *options, *type_out = GS_TYPE_RESTRICTED; return "restricted"; } else { - return NULL; + /* we are in the middle: maintain previous guard selection */ + *type_out = old_selection->type; + return old_selection->name; } } @@ -634,16 +641,16 @@ update_guard_selection_choice(const or_options_t *options) return 1; } - const char *cur_name = curr_guard_context->name; guard_selection_type_t type = GS_TYPE_INFER; const char *new_name = choose_guard_selection( options, networkstatus_get_live_consensus(approx_time()), - cur_name, + curr_guard_context, &type); tor_assert(new_name); tor_assert(type != GS_TYPE_INFER); + const char *cur_name = curr_guard_context->name; if (! strcmp(cur_name, new_name)) { log_debug(LD_GUARD, "Staying with guard context \"%s\" (no change)", new_name); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 116e5abb8c..11335255e2 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -519,9 +519,9 @@ STATIC void guard_selection_free(guard_selection_t *gs); MOCK_DECL(STATIC int, entry_guard_is_listed, (guard_selection_t *gs, const entry_guard_t *guard)); STATIC const char *choose_guard_selection(const or_options_t *options, - const networkstatus_t *ns, - const char *old_selection, - guard_selection_type_t *type_out); + const networkstatus_t *ns, + const guard_selection_t *old_selection, + guard_selection_type_t *type_out); STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs, const uint8_t *rsa_id); From b7088e5b5add8bf3b6f783ec37ce7cd231476b35 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 7 Dec 2016 13:57:04 -0500 Subject: [PATCH 76/80] Move a TODO comment into doxygen comments. --- src/or/entrynodes.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index bcf4182640..ad9242e45f 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -630,8 +630,9 @@ choose_guard_selection(const or_options_t *options, * Check whether we should switch from our current guard selection to a * different one. If so, switch and return 1. Return 0 otherwise. * - * On a 1 return, the caller should mark all currently live circuits - * unusable for new streams. + * On a 1 return, the caller should mark all currently live circuits unusable + * for new streams, by calling circuit_mark_all_unused_circs() and + * circuit_mark_all_dirty_circs_as_unusable(). */ int update_guard_selection_choice(const or_options_t *options) @@ -665,12 +666,6 @@ update_guard_selection_choice(const or_options_t *options) tor_assert(new_guard_context != curr_guard_context); curr_guard_context = new_guard_context; - /* - Be sure to call: - circuit_mark_all_unused_circs(); - circuit_mark_all_dirty_circs_as_unusable(); - */ - return 1; } @@ -4879,7 +4874,9 @@ entries_retry_all(const or_options_t *options) /** Helper: Update the status of all entry guards, in whatever algorithm * is used. Return true if we should stop using all previously generated - * circuits. */ + * circuits, by calling circuit_mark_all_unused_circs() and + * circuit_mark_all_dirty_circs_as_unusable(). + */ int guards_update_all(void) { From e50d85b90cb3fbc562517c11ded12940682ffec0 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 7 Dec 2016 14:15:38 -0500 Subject: [PATCH 77/80] Clean check for live consensus when updating the guard sample. The valid_until check was redundant. --- src/or/entrynodes.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index ad9242e45f..f41464a4c9 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1157,12 +1157,13 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) if (gs->type != GS_TYPE_BRIDGE) { networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); - log_info(LD_GUARD, "Updating sampled guard status based on received " - "consensus."); - - if (! ns || ns->valid_until < approx_time()) { - log_info(LD_GUARD, "Hey, there wasn't a valid consensus. Ignoring"); + if (! ns) { + log_info(LD_GUARD, "No live consensus; can't update " + "sampled entry guards."); return; + } else { + log_info(LD_GUARD, "Updating sampled guard status based on received " + "consensus."); } } From 2e2f3a4d99885c0d348024dc85ed6ef064a62ace Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 8 Dec 2016 10:02:19 -0500 Subject: [PATCH 78/80] Add a separate, non-fractional, limit to the sampled guard set size. Letting the maximum sample size grow proportionally to the number of guards defeats its purpose to a certain extent. Noted by asn during code review. Fixes bug 20920; bug not in any released (or merged) version of Tor. --- src/or/entrynodes.c | 14 +++++++++++++- src/or/entrynodes.h | 8 +++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index f41464a4c9..3249ce2947 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -401,6 +401,16 @@ get_max_sample_threshold(void) 1, 100); return pct / 100.0; } +/** + * We never let our sampled guard set grow larger than this number. + */ +STATIC int +get_max_sample_size_absolute(void) +{ + return (int) networkstatus_get_param(NULL, "guard-max-sample-size", + DFLT_MAX_SAMPLE_SIZE, + 1, INT32_MAX); +} /** * We always try to make our sample contain at least this many guards. * @@ -937,7 +947,9 @@ get_max_sample_size(guard_selection_t *gs, if (using_bridges) return n_guards; - const int max_sample = (int)(n_guards * get_max_sample_threshold()); + const int max_sample_by_pct = (int)(n_guards * get_max_sample_threshold()); + const int max_sample_absolute = get_max_sample_size_absolute(); + const int max_sample = MIN(max_sample_by_pct, max_sample_absolute); if (max_sample < min_sample) // XXXX prop271 spec deviation return min_sample; else diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 11335255e2..d7dc01424a 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -440,7 +440,12 @@ int num_bridges_usable(void); * We never let our sampled guard set grow larger than this percentage * of the guards on the network. */ -#define DFLT_MAX_SAMPLE_THRESHOLD_PERCENT 30 +#define DFLT_MAX_SAMPLE_THRESHOLD_PERCENT 20 +/** + * We never let our sampled guard set grow larger than this number of + * guards. + */ +#define DFLT_MAX_SAMPLE_SIZE 60 /** * We always try to make our sample contain at least this many guards. * @@ -495,6 +500,7 @@ int num_bridges_usable(void); /**@}*/ STATIC double get_max_sample_threshold(void); +STATIC int get_max_sample_size_absolute(void); STATIC int get_min_filtered_sample_size(void); STATIC int get_remove_unlisted_guards_after_days(void); STATIC int get_guard_lifetime_days(void); From fc7751a989681fbf0f94387c070cced261a83c9c Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 8 Dec 2016 10:22:23 -0500 Subject: [PATCH 79/80] Rewrite state transition logic in entry_guards_note_success() asn found while testing that this function can be reached with GUARD_STATE_COMPLETE circuits; I believe this happens when cannibalization occurs. The added complexity of handling one more state made it reasonable to turn the main logic here into a switch statement. --- src/or/entrynodes.c | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 3249ce2947..cf85dad309 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1927,25 +1927,31 @@ entry_guards_note_guard_success(guard_selection_t *gs, } unsigned new_state; - if (old_state == GUARD_CIRC_STATE_USABLE_ON_COMPLETION) { - new_state = GUARD_CIRC_STATE_COMPLETE; - } else { - tor_assert_nonfatal( - old_state == GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); - - if (guard->is_primary) { - /* XXXX prop271 -- I don't actually like this logic. It seems to make us - * a little more susceptible to evil-ISP attacks. The mitigations I'm - * thinking of, however, aren't local to this point, so I'll leave it - * alone. */ - /* This guard may have become primary by virtue of being confirmed. - If so, the circuit for it is now complete. - */ + switch (old_state) { + case GUARD_CIRC_STATE_COMPLETE: + case GUARD_CIRC_STATE_USABLE_ON_COMPLETION: new_state = GUARD_CIRC_STATE_COMPLETE; - } else { - new_state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD; - } + break; + default: + tor_assert_nonfatal_unreached(); + /* Fall through. */ + case GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD: + if (guard->is_primary) { + /* XXXX prop271 -- I don't actually like this logic. It seems to make + * us a little more susceptible to evil-ISP attacks. The mitigations + * I'm thinking of, however, aren't local to this point, so I'll leave + * it alone. */ + /* This guard may have become primary by virtue of being confirmed. + * If so, the circuit for it is now complete. + */ + new_state = GUARD_CIRC_STATE_COMPLETE; + } else { + new_state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD; + } + break; + } + if (! guard->is_primary) { if (last_time_on_internet + get_internet_likely_down_interval() < approx_time()) { mark_primary_guards_maybe_reachable(gs); From 20292ec4974b777d430e7962cc38349c5f82b220 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 8 Dec 2016 13:10:22 -0500 Subject: [PATCH 80/80] Per suggestion, increase the retry frequency for primary guards. --- src/or/entrynodes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index cf85dad309..ac5398f5f8 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1708,8 +1708,8 @@ get_retry_schedule(time_t failing_since, time_t now, const struct { time_t maximum; int primary_delay; int nonprimary_delay; } delays[] = { - { SIX_HOURS, 30*60, 1*60*60 }, - { FOUR_DAYS, 2*60*60, 4*60*60 }, + { SIX_HOURS, 10*60, 1*60*60 }, + { FOUR_DAYS, 90*60, 4*60*60 }, { SEVEN_DAYS, 4*60*60, 18*60*60 }, { TIME_MAX, 9*60*60, 36*60*60 } };