|
@@ -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 <b>name</b>.
|
|
|
*/
|
|
|
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 <b>create_if_absent</b> 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 <b>node</b> 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
|