|
@@ -24,6 +24,23 @@
|
|
|
|
|
|
static void nodelist_drop_node(node_t *node, int remove_from_ht);
|
|
|
static void node_free(node_t *node);
|
|
|
+
|
|
|
+/** count_usable_descriptors counts descriptors with these flag(s)
|
|
|
+ */
|
|
|
+typedef enum {
|
|
|
+ /* All descriptors regardless of flags */
|
|
|
+ USABLE_DESCRIPTOR_ALL = 0,
|
|
|
+ /* Only descriptors with the Exit flag */
|
|
|
+ USABLE_DESCRIPTOR_EXIT_ONLY = 1
|
|
|
+} usable_descriptor_t;
|
|
|
+static void count_usable_descriptors(int *num_present,
|
|
|
+ int *num_usable,
|
|
|
+ smartlist_t *descs_out,
|
|
|
+ const networkstatus_t *consensus,
|
|
|
+ const or_options_t *options,
|
|
|
+ time_t now,
|
|
|
+ routerset_t *in_set,
|
|
|
+ usable_descriptor_t exit_only);
|
|
|
static void update_router_have_minimum_dir_info(void);
|
|
|
static double get_frac_paths_needed_for_circs(const or_options_t *options,
|
|
|
const networkstatus_t *ns);
|
|
@@ -1256,20 +1273,28 @@ router_set_status(const char *digest, int up)
|
|
|
}
|
|
|
|
|
|
/** True iff, the last time we checked whether we had enough directory info
|
|
|
- * to build circuits, the answer was "yes". */
|
|
|
+ * to build circuits, the answer was "yes". If there are no exits in the
|
|
|
+ * consensus, we act as if we have 100% of the exit directory info. */
|
|
|
static int have_min_dir_info = 0;
|
|
|
+
|
|
|
+/** Does the consensus contain nodes that can exit? */
|
|
|
+static consensus_path_type_t have_consensus_path = CONSENSUS_PATH_UNKNOWN;
|
|
|
+
|
|
|
/** True iff enough has changed since the last time we checked whether we had
|
|
|
* enough directory info to build circuits that our old answer can no longer
|
|
|
* be trusted. */
|
|
|
static int need_to_update_have_min_dir_info = 1;
|
|
|
/** String describing what we're missing before we have enough directory
|
|
|
* info. */
|
|
|
-static char dir_info_status[256] = "";
|
|
|
-
|
|
|
-/** Return true iff we have enough networkstatus and router information to
|
|
|
- * start building circuits. Right now, this means "more than half the
|
|
|
- * networkstatus documents, and at least 1/4 of expected routers." */
|
|
|
-//XXX should consider whether we have enough exiting nodes here.
|
|
|
+static char dir_info_status[512] = "";
|
|
|
+
|
|
|
+/** Return true iff we have enough consensus information to
|
|
|
+ * start building circuits. Right now, this means "a consensus that's
|
|
|
+ * less than a day old, and at least 60% of router descriptors (configurable),
|
|
|
+ * weighted by bandwidth. Treat the exit fraction as 100% if there are
|
|
|
+ * no exits in the consensus."
|
|
|
+ * To obtain the final weighted bandwidth, we multiply the
|
|
|
+ * weighted bandwidth fraction for each position (guard, middle, exit). */
|
|
|
int
|
|
|
router_have_minimum_dir_info(void)
|
|
|
{
|
|
@@ -1291,6 +1316,24 @@ router_have_minimum_dir_info(void)
|
|
|
return have_min_dir_info;
|
|
|
}
|
|
|
|
|
|
+/** Set to CONSENSUS_PATH_EXIT if there is at least one exit node
|
|
|
+ * in the consensus. We update this flag in compute_frac_paths_available if
|
|
|
+ * there is at least one relay that has an Exit flag in the consensus.
|
|
|
+ * Used to avoid building exit circuits when they will almost certainly fail.
|
|
|
+ * Set to CONSENSUS_PATH_INTERNAL if there are no exits in the consensus.
|
|
|
+ * (This situation typically occurs during bootstrap of a test network.)
|
|
|
+ * Set to CONSENSUS_PATH_UNKNOWN if we have never checked, or have
|
|
|
+ * reason to believe our last known value was invalid or has expired.
|
|
|
+ * If we're in a network with TestingDirAuthVoteExit set,
|
|
|
+ * this can cause router_have_consensus_path() to be set to
|
|
|
+ * CONSENSUS_PATH_EXIT, even if there are no nodes with accept exit policies.
|
|
|
+ */
|
|
|
+consensus_path_type_t
|
|
|
+router_have_consensus_path(void)
|
|
|
+{
|
|
|
+ return have_consensus_path;
|
|
|
+}
|
|
|
+
|
|
|
/** Called when our internal view of the directory has changed. This can be
|
|
|
* when the authorities change, networkstatuses change, the list of routerdescs
|
|
|
* changes, or number of running routers changes.
|
|
@@ -1313,20 +1356,23 @@ get_dir_info_status_string(void)
|
|
|
/** Iterate over the servers listed in <b>consensus</b>, and count how many of
|
|
|
* them seem like ones we'd use, and how many of <em>those</em> we have
|
|
|
* descriptors for. Store the former in *<b>num_usable</b> and the latter in
|
|
|
- * *<b>num_present</b>. If <b>in_set</b> is non-NULL, only consider those
|
|
|
- * routers in <b>in_set</b>. If <b>exit_only</b> is true, only consider nodes
|
|
|
- * with the Exit flag. If *descs_out is present, add a node_t for each
|
|
|
- * usable descriptor to it.
|
|
|
+ * *<b>num_present</b>.
|
|
|
+ * If <b>in_set</b> is non-NULL, only consider those routers in <b>in_set</b>.
|
|
|
+ * If <b>exit_only</b> is USABLE_DESCRIPTOR_EXIT_ONLY, only consider nodes
|
|
|
+ * with the Exit flag.
|
|
|
+ * If *<b>descs_out</b> is present, add a node_t for each usable descriptor
|
|
|
+ * to it.
|
|
|
*/
|
|
|
static void
|
|
|
count_usable_descriptors(int *num_present, int *num_usable,
|
|
|
smartlist_t *descs_out,
|
|
|
const networkstatus_t *consensus,
|
|
|
const or_options_t *options, time_t now,
|
|
|
- routerset_t *in_set, int exit_only)
|
|
|
+ routerset_t *in_set,
|
|
|
+ usable_descriptor_t exit_only)
|
|
|
{
|
|
|
const int md = (consensus->flavor == FLAV_MICRODESC);
|
|
|
- *num_present = 0, *num_usable=0;
|
|
|
+ *num_present = 0, *num_usable = 0;
|
|
|
|
|
|
SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, routerstatus_t *, rs)
|
|
|
{
|
|
@@ -1334,7 +1380,7 @@ count_usable_descriptors(int *num_present, int *num_usable,
|
|
|
if (!node)
|
|
|
continue; /* This would be a bug: every entry in the consensus is
|
|
|
* supposed to have a node. */
|
|
|
- if (exit_only && ! rs->is_exit)
|
|
|
+ if (exit_only == USABLE_DESCRIPTOR_EXIT_ONLY && ! rs->is_exit)
|
|
|
continue;
|
|
|
if (in_set && ! routerset_contains_routerstatus(in_set, rs, -1))
|
|
|
continue;
|
|
@@ -1358,11 +1404,21 @@ count_usable_descriptors(int *num_present, int *num_usable,
|
|
|
|
|
|
log_debug(LD_DIR, "%d usable, %d present (%s%s).",
|
|
|
*num_usable, *num_present,
|
|
|
- md ? "microdesc" : "desc", exit_only ? " exits" : "s");
|
|
|
+ md ? "microdesc" : "desc",
|
|
|
+ exit_only == USABLE_DESCRIPTOR_EXIT_ONLY ? " exits" : "s");
|
|
|
}
|
|
|
|
|
|
/** Return an estimate of which fraction of usable paths through the Tor
|
|
|
- * network we have available for use. */
|
|
|
+ * network we have available for use.
|
|
|
+ * Count how many routers seem like ones we'd use, and how many of
|
|
|
+ * <em>those</em> we have descriptors for. Store the former in
|
|
|
+ * *<b>num_usable_out</b> and the latter in *<b>num_present_out</b>.
|
|
|
+ * If **<b>status_out</b> is present, allocate a new string and print the
|
|
|
+ * available percentages of guard, middle, and exit nodes to it, noting
|
|
|
+ * whether there are exits in the consensus.
|
|
|
+ * If there are no guards in the consensus,
|
|
|
+ * we treat the exit fraction as 100%.
|
|
|
+ */
|
|
|
static double
|
|
|
compute_frac_paths_available(const networkstatus_t *consensus,
|
|
|
const or_options_t *options, time_t now,
|
|
@@ -1375,14 +1431,19 @@ compute_frac_paths_available(const networkstatus_t *consensus,
|
|
|
smartlist_t *myexits= smartlist_new();
|
|
|
smartlist_t *myexits_unflagged = smartlist_new();
|
|
|
double f_guard, f_mid, f_exit, f_myexit, f_myexit_unflagged;
|
|
|
- int np, nu; /* Ignored */
|
|
|
+ double f_path = 0.0;
|
|
|
+ /* Used to determine whether there are any exits in the consensus */
|
|
|
+ int np = 0;
|
|
|
+ /* Used to determine whether there are any exits with descriptors */
|
|
|
+ int nu = 0;
|
|
|
const int authdir = authdir_mode_v3(options);
|
|
|
|
|
|
count_usable_descriptors(num_present_out, num_usable_out,
|
|
|
- mid, consensus, options, now, NULL, 0);
|
|
|
+ mid, consensus, options, now, NULL,
|
|
|
+ USABLE_DESCRIPTOR_ALL);
|
|
|
if (options->EntryNodes) {
|
|
|
count_usable_descriptors(&np, &nu, guards, consensus, options, now,
|
|
|
- options->EntryNodes, 0);
|
|
|
+ options->EntryNodes, USABLE_DESCRIPTOR_ALL);
|
|
|
} else {
|
|
|
SMARTLIST_FOREACH(mid, const node_t *, node, {
|
|
|
if (authdir) {
|
|
@@ -1395,22 +1456,78 @@ compute_frac_paths_available(const networkstatus_t *consensus,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- /* All nodes with exit flag */
|
|
|
+ /* All nodes with exit flag
|
|
|
+ * If we're in a network with TestingDirAuthVoteExit set,
|
|
|
+ * this can cause false positives on have_consensus_path,
|
|
|
+ * incorrectly setting it to CONSENSUS_PATH_EXIT. This is
|
|
|
+ * an unavoidable feature of forcing authorities to declare
|
|
|
+ * certain nodes as exits.
|
|
|
+ */
|
|
|
count_usable_descriptors(&np, &nu, exits, consensus, options, now,
|
|
|
- NULL, 1);
|
|
|
+ NULL, USABLE_DESCRIPTOR_EXIT_ONLY);
|
|
|
+ log_debug(LD_NET,
|
|
|
+ "%s: %d present, %d usable",
|
|
|
+ "exits",
|
|
|
+ np,
|
|
|
+ nu);
|
|
|
+
|
|
|
+ /* We need at least 1 exit present in the consensus to consider
|
|
|
+ * building exit paths */
|
|
|
+ /* Update our understanding of whether the consensus has exits */
|
|
|
+ consensus_path_type_t old_have_consensus_path = have_consensus_path;
|
|
|
+ have_consensus_path = ((np > 0) ?
|
|
|
+ CONSENSUS_PATH_EXIT :
|
|
|
+ CONSENSUS_PATH_INTERNAL);
|
|
|
+
|
|
|
+ if (have_consensus_path == CONSENSUS_PATH_INTERNAL
|
|
|
+ && old_have_consensus_path != have_consensus_path) {
|
|
|
+ log_notice(LD_NET,
|
|
|
+ "The current consensus has no exit nodes. "
|
|
|
+ "Tor can only build internal paths, "
|
|
|
+ "such as paths to hidden services.");
|
|
|
+
|
|
|
+ /* However, exit nodes can reachability self-test using this consensus,
|
|
|
+ * join the network, and appear in a later consensus. This will allow
|
|
|
+ * the network to build exit paths, such as paths for world wide web
|
|
|
+ * browsing (as distinct from hidden service web browsing). */
|
|
|
+ }
|
|
|
+
|
|
|
/* All nodes with exit flag in ExitNodes option */
|
|
|
count_usable_descriptors(&np, &nu, myexits, consensus, options, now,
|
|
|
- options->ExitNodes, 1);
|
|
|
+ options->ExitNodes, USABLE_DESCRIPTOR_EXIT_ONLY);
|
|
|
+ log_debug(LD_NET,
|
|
|
+ "%s: %d present, %d usable",
|
|
|
+ "myexits",
|
|
|
+ np,
|
|
|
+ nu);
|
|
|
+
|
|
|
/* Now compute the nodes in the ExitNodes option where which we don't know
|
|
|
* what their exit policy is, or we know it permits something. */
|
|
|
count_usable_descriptors(&np, &nu, myexits_unflagged,
|
|
|
consensus, options, now,
|
|
|
- options->ExitNodes, 0);
|
|
|
+ options->ExitNodes, USABLE_DESCRIPTOR_ALL);
|
|
|
+ log_debug(LD_NET,
|
|
|
+ "%s: %d present, %d usable",
|
|
|
+ "myexits_unflagged (initial)",
|
|
|
+ np,
|
|
|
+ nu);
|
|
|
+
|
|
|
SMARTLIST_FOREACH_BEGIN(myexits_unflagged, const node_t *, node) {
|
|
|
- if (node_has_descriptor(node) && node_exit_policy_rejects_all(node))
|
|
|
+ if (node_has_descriptor(node) && node_exit_policy_rejects_all(node)) {
|
|
|
SMARTLIST_DEL_CURRENT(myexits_unflagged, node);
|
|
|
+ /* this node is not actually an exit */
|
|
|
+ np--;
|
|
|
+ /* this node is unusable as an exit */
|
|
|
+ nu--;
|
|
|
+ }
|
|
|
} SMARTLIST_FOREACH_END(node);
|
|
|
|
|
|
+ log_debug(LD_NET,
|
|
|
+ "%s: %d present, %d usable",
|
|
|
+ "myexits_unflagged (final)",
|
|
|
+ np,
|
|
|
+ nu);
|
|
|
+
|
|
|
f_guard = frac_nodes_with_descriptors(guards, WEIGHT_FOR_GUARD);
|
|
|
f_mid = frac_nodes_with_descriptors(mid, WEIGHT_FOR_MID);
|
|
|
f_exit = frac_nodes_with_descriptors(exits, WEIGHT_FOR_EXIT);
|
|
@@ -1418,6 +1535,12 @@ compute_frac_paths_available(const networkstatus_t *consensus,
|
|
|
f_myexit_unflagged=
|
|
|
frac_nodes_with_descriptors(myexits_unflagged,WEIGHT_FOR_EXIT);
|
|
|
|
|
|
+ log_debug(LD_NET,
|
|
|
+ "f_exit: %.2f, f_myexit: %.2f, f_myexit_unflagged: %.2f",
|
|
|
+ f_exit,
|
|
|
+ f_myexit,
|
|
|
+ f_myexit_unflagged);
|
|
|
+
|
|
|
/* If our ExitNodes list has eliminated every possible Exit node, and there
|
|
|
* were some possible Exit nodes, then instead consider nodes that permit
|
|
|
* exiting to some ports. */
|
|
@@ -1439,16 +1562,28 @@ compute_frac_paths_available(const networkstatus_t *consensus,
|
|
|
if (f_myexit < f_exit)
|
|
|
f_exit = f_myexit;
|
|
|
|
|
|
+ /* if the consensus has no exits, treat the exit fraction as 100% */
|
|
|
+ if (router_have_consensus_path() != CONSENSUS_PATH_EXIT) {
|
|
|
+ f_exit = 1.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ f_path = f_guard * f_mid * f_exit;
|
|
|
+
|
|
|
if (status_out)
|
|
|
tor_asprintf(status_out,
|
|
|
"%d%% of guards bw, "
|
|
|
"%d%% of midpoint bw, and "
|
|
|
- "%d%% of exit bw",
|
|
|
+ "%d%% of exit bw%s = "
|
|
|
+ "%d%% of path bw",
|
|
|
(int)(f_guard*100),
|
|
|
(int)(f_mid*100),
|
|
|
- (int)(f_exit*100));
|
|
|
+ (int)(f_exit*100),
|
|
|
+ (router_have_consensus_path() == CONSENSUS_PATH_EXIT ?
|
|
|
+ "" :
|
|
|
+ " (no exits in consensus)"),
|
|
|
+ (int)(f_path*100));
|
|
|
|
|
|
- return f_guard * f_mid * f_exit;
|
|
|
+ return f_path;
|
|
|
}
|
|
|
|
|
|
/** We just fetched a new set of descriptors. Compute how far through
|
|
@@ -1521,6 +1656,9 @@ update_router_have_minimum_dir_info(void)
|
|
|
|
|
|
using_md = consensus->flavor == FLAV_MICRODESC;
|
|
|
|
|
|
+#define NOTICE_DIR_INFO_STATUS_INTERVAL (60)
|
|
|
+
|
|
|
+ /* Check fraction of available paths */
|
|
|
{
|
|
|
char *status = NULL;
|
|
|
int num_present=0, num_usable=0;
|
|
@@ -1529,16 +1667,37 @@ update_router_have_minimum_dir_info(void)
|
|
|
&status);
|
|
|
|
|
|
if (paths < get_frac_paths_needed_for_circs(options,consensus)) {
|
|
|
- tor_snprintf(dir_info_status, sizeof(dir_info_status),
|
|
|
- "We need more %sdescriptors: we have %d/%d, and "
|
|
|
- "can only build %d%% of likely paths. (We have %s.)",
|
|
|
- using_md?"micro":"", num_present, num_usable,
|
|
|
- (int)(paths*100), status);
|
|
|
- /* log_notice(LD_NET, "%s", dir_info_status); */
|
|
|
+ /* these messages can be excessive in testing networks */
|
|
|
+ static ratelim_t last_warned =
|
|
|
+ RATELIM_INIT(NOTICE_DIR_INFO_STATUS_INTERVAL);
|
|
|
+ char *suppression_msg = NULL;
|
|
|
+ if ((suppression_msg = rate_limit_log(&last_warned, time(NULL)))) {
|
|
|
+ tor_snprintf(dir_info_status, sizeof(dir_info_status),
|
|
|
+ "We need more %sdescriptors: we have %d/%d, and "
|
|
|
+ "can only build %d%% of likely paths. (We have %s.)",
|
|
|
+ using_md?"micro":"", num_present, num_usable,
|
|
|
+ (int)(paths*100), status);
|
|
|
+ log_warn(LD_NET, "%s%s", dir_info_status, suppression_msg);
|
|
|
+ tor_free(suppression_msg);
|
|
|
+ }
|
|
|
tor_free(status);
|
|
|
res = 0;
|
|
|
control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0);
|
|
|
goto done;
|
|
|
+ } else {
|
|
|
+ /* these messages can be excessive in testing networks */
|
|
|
+ static ratelim_t last_warned =
|
|
|
+ RATELIM_INIT(NOTICE_DIR_INFO_STATUS_INTERVAL);
|
|
|
+ char *suppression_msg = NULL;
|
|
|
+ if ((suppression_msg = rate_limit_log(&last_warned, time(NULL)))) {
|
|
|
+ tor_snprintf(dir_info_status, sizeof(dir_info_status),
|
|
|
+ "We have enough %sdescriptors: we have %d/%d, and "
|
|
|
+ "can build %d%% of likely paths. (We have %s.)",
|
|
|
+ using_md?"micro":"", num_present, num_usable,
|
|
|
+ (int)(paths*100), status);
|
|
|
+ log_info(LD_NET, "%s%s", dir_info_status, suppression_msg);
|
|
|
+ tor_free(suppression_msg);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
tor_free(status);
|
|
@@ -1546,12 +1705,16 @@ update_router_have_minimum_dir_info(void)
|
|
|
}
|
|
|
|
|
|
done:
|
|
|
+
|
|
|
+ /* If paths have just become available in this update. */
|
|
|
if (res && !have_min_dir_info) {
|
|
|
log_notice(LD_DIR,
|
|
|
"We now have enough directory information to build circuits.");
|
|
|
control_event_client_status(LOG_NOTICE, "ENOUGH_DIR_INFO");
|
|
|
control_event_bootstrap(BOOTSTRAP_STATUS_CONN_OR, 0);
|
|
|
}
|
|
|
+
|
|
|
+ /* If paths have just become unavailable in this update. */
|
|
|
if (!res && have_min_dir_info) {
|
|
|
int quiet = directory_too_idle_to_fetch_descriptors(options, now);
|
|
|
tor_log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR,
|
|
@@ -1563,7 +1726,7 @@ update_router_have_minimum_dir_info(void)
|
|
|
* should only do while circuits are working, like reachability tests
|
|
|
* and fetching bridge descriptors only over circuits. */
|
|
|
note_that_we_maybe_cant_complete_circuits();
|
|
|
-
|
|
|
+ have_consensus_path = CONSENSUS_PATH_UNKNOWN;
|
|
|
control_event_client_status(LOG_NOTICE, "NOT_ENOUGH_DIR_INFO");
|
|
|
}
|
|
|
have_min_dir_info = res;
|