From 404e9e5611eff39866c2e45133a60b40d7492f7e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Nov 2016 07:41:45 -0500 Subject: 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/entrynodes.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 243 insertions(+), 27 deletions(-) (limited to 'src/or/entrynodes.c') 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; } -/** Get current default guard_selection_t, creating it if necessary */ -guard_selection_t * -get_guard_selection_info(void) +/** + * 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 (!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 -- cgit v1.2.3-54-g00ecf