commit 0c4210fb65d1fe45cbd39ea078be1b09c0f52fc9 Author: Nick Mathewson nickm@torproject.org Date: Tue Dec 11 12:44:18 2012 -0500
Directory guard implementation.
Implements proposal 207; ticket 6526. --- changes/dirguards | 8 ++++ src/or/circuituse.c | 4 +- src/or/directory.c | 42 +++++++++++++++++++- src/or/entrynodes.c | 104 ++++++++++++++++++++++++++++++++++++++------------ src/or/entrynodes.h | 5 +- 5 files changed, 131 insertions(+), 32 deletions(-)
diff --git a/changes/dirguards b/changes/dirguards new file mode 100644 index 0000000..942ae6c --- /dev/null +++ b/changes/dirguards @@ -0,0 +1,8 @@ + o Major features: + - Preliminary support for directory guards: when possible, + clients now use guards for non-anonymous directory requests. + This can help prevent client enumeration. Note that this + behavior only works when we have a usable consensus directory: + and when options about what to download are more or less + standard. Implements proposal 207; closes ticket 6526. + diff --git a/src/or/circuituse.c b/src/or/circuituse.c index e14f9d0..298f31a 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -467,7 +467,7 @@ circuit_expire_building(void) "No circuits are opened. Relaxing timeout for " "a circuit with channel state %s. %d guards are live.", channel_state_to_string(victim->n_chan->state), - num_live_entry_guards()); + num_live_entry_guards(0));
/* We count the timeout here for CBT, because technically this * was a timeout, and the timeout value needs to reset if we @@ -484,7 +484,7 @@ circuit_expire_building(void) "However, it appears the circuit has timed out anyway. " "%d guards are live. ", channel_state_to_string(victim->n_chan->state), - (long)circ_times.close_ms, num_live_entry_guards()); + (long)circ_times.close_ms, num_live_entry_guards(0)); } }
diff --git a/src/or/directory.c b/src/or/directory.c index 769e3d1..fc1b76a 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -334,15 +334,51 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, } }
-/**DOCDOC*/ +/** Return true iff, according to the values in <b>options</b>, we should be + * using directory guards for direct downloads of directory information. */ +static int +should_use_directory_guards(const or_options_t *options) +{ + /* Public (non-bridge) servers never use directory guards. */ + if (public_server_mode(options)) + return 0; + /* If guards are disabled, or directory guards are disabled, we can't + * use directory guards. + */ + if (!options->UseEntryGuards) + return 0; + /* If we're configured to fetch directory info aggressively or of a + * nonstandard type, don't use directory guards. */ + if (options->DownloadExtraInfo || options->FetchDirInfoEarly || + options->FetchDirInfoExtraEarly || options->FetchUselessDescriptors || + options->FetchV2Networkstatus) + return 0; + if (! options->PreferTunneledDirConns) + return 0; + return 1; +} + +/** Pick an unconsetrained directory server from among our guards, the latest + * networkstatus, or the fallback dirservers, for use in downloading + * information of type <b>type</b>, and return its routerstatus. */ static const routerstatus_t * directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, uint8_t dir_purpose) { const routerstatus_t *rs; + const or_options_t *options = get_options();
- /* anybody with a non-zero dirport will do */ - rs = router_pick_directory_server(type, pds_flags); + if (options->UseBridges) + log_warn(LD_BUG, "Called when we have UseBridges set."); + + if (should_use_directory_guards(options)) { + const node_t *node = choose_random_dirguard(type); + if (node) + rs = node->rs; + } else { + /* anybody with a non-zero dirport will do */ + rs = router_pick_directory_server(type, pds_flags); + } if (!rs) { log_info(LD_DIR, "No router found for %s; falling back to " "dirserver list.", dir_conn_purpose_to_string(dir_purpose)); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index f013481..a872091 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -61,6 +61,9 @@ static smartlist_t *entry_guards = NULL; static int entry_guards_dirty = 0;
static void bridge_free(bridge_info_t *bridge); +static const node_t *choose_random_entry_impl(cpath_build_state_t *state, + int for_directory, + dirinfo_type_t dirtype);
/** Return the list of entry guards, creating it if necessary. */ const smartlist_t * @@ -127,7 +130,8 @@ entry_guard_set_status(entry_guard_t *e, const node_t *node, }
if (node) { - int is_dir = node_is_dir(node) != 0; + int is_dir = node_is_dir(node) && node->rs && + node->rs->version_supports_microdesc_cache; if (e->is_dir_cache != is_dir) { e->is_dir_cache = is_dir; changed = 1; @@ -169,10 +173,13 @@ entry_is_time_to_retry(entry_guard_t *e, time_t now) * is true). * * If the answer is no, set *<b>msg</b> to an explanation of why. + * + * If need_descriptor is true, only return the node if we currently have + * a descriptor (routerinfo or microdesc) for it. */ static INLINE const node_t * entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, - int assume_reachable, const char **msg) + int assume_reachable, int need_descriptor, const char **msg) { const node_t *node; const or_options_t *options = get_options(); @@ -193,7 +200,11 @@ entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, return NULL; } node = node_get_by_id(e->identity); - if (!node || !node_has_descriptor(node)) { + if (!node) { + *msg = "no node info"; + return NULL; + } + if (need_descriptor && !node_has_descriptor(node)) { *msg = "no descriptor"; return NULL; } @@ -229,17 +240,18 @@ entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity,
/** Return the number of entry guards that we think are usable. */ int -num_live_entry_guards(void) +num_live_entry_guards(int for_directory) { int n = 0; const char *msg; if (! entry_guards) return 0; - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, - { - if (entry_is_live(entry, 0, 1, 0, &msg)) + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { + if (for_directory && !entry->is_dir_cache) + continue; + if (entry_is_live(entry, 0, 1, 0, !for_directory, &msg)) ++n; - }); + } SMARTLIST_FOREACH_END(entry); return n; }
@@ -266,7 +278,7 @@ log_entry_guards(int severity) SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { const char *msg = NULL; - if (entry_is_live(e, 0, 1, 0, &msg)) + if (entry_is_live(e, 0, 1, 0, 0, &msg)) smartlist_add_asprintf(elements, "%s [%s] (up %s)", e->nickname, hex_str(e->identity, DIGEST_LEN), @@ -325,7 +337,8 @@ control_event_guard_deferred(void) * already in our entry_guards list, put it at the *beginning*. * Else, put the one we pick at the end of the list. */ static const node_t * -add_an_entry_guard(const node_t *chosen, int reset_status, int prepend) +add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, + int for_directory) { const node_t *node; entry_guard_t *entry; @@ -338,19 +351,32 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend) entry->bad_since = 0; entry->can_retry = 1; } + entry->is_dir_cache = node->rs && + node->rs->version_supports_microdesc_cache; return NULL; } - } else { + } else if (!for_directory) { node = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); if (!node) return NULL; + } else { + const routerstatus_t *rs; + rs = router_pick_directory_server(MICRODESC_DIRINFO|V3_DIRINFO, + PDS_PREFER_TUNNELED_DIR_CONNS_); + if (!rs) + return NULL; + node = node_get_by_id(rs->identity_digest); + if (!node) + return NULL; } entry = tor_malloc_zero(sizeof(entry_guard_t)); log_info(LD_CIRC, "Chose %s as new entry guard.", node_describe(node)); strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); memcpy(entry->identity, node->identity, DIGEST_LEN); - entry->is_dir_cache = node_is_dir(node) != 0; + entry->is_dir_cache = node_is_dir(node) && + node->rs && node->rs->version_supports_microdesc_cache; + /* Choose expiry time smudged over the past month. The goal here * is to a) spread out when Tor clients rotate their guards, so they * don't all select them on the same day, and b) avoid leaving a @@ -371,14 +397,14 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend) /** If the use of entry guards is configured, choose more entry guards * until we have enough in the list. */ static void -pick_entry_guards(const or_options_t *options) +pick_entry_guards(const or_options_t *options, int for_directory) { int changed = 0;
tor_assert(entry_guards);
- while (num_live_entry_guards() < options->NumEntryGuards) { - if (!add_an_entry_guard(NULL, 0, 0)) + while (num_live_entry_guards(for_directory) < options->NumEntryGuards) { + if (!add_an_entry_guard(NULL, 0, 0, for_directory)) break; changed = 1; } @@ -541,7 +567,7 @@ entry_guards_compute_status(const or_options_t *options, time_t now) SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { const char *reason = digestmap_get(reasons, entry->identity); const char *live_msg = ""; - const node_t *r = entry_is_live(entry, 0, 1, 0, &live_msg); + const node_t *r = entry_is_live(entry, 0, 1, 0, 0, &live_msg); log_info(LD_CIRC, "Summary: Entry %s [%s] is %s, %s%s%s, and %s%s.", entry->nickname, hex_str(entry->identity, DIGEST_LEN), @@ -553,7 +579,7 @@ entry_guards_compute_status(const or_options_t *options, time_t now) r ? "" : live_msg); } SMARTLIST_FOREACH_END(entry); log_info(LD_CIRC, " (%d/%d entry guards are usable/new)", - num_live_entry_guards(), smartlist_len(entry_guards)); + num_live_entry_guards(0), smartlist_len(entry_guards)); log_entry_guards(LOG_INFO); entry_guards_changed(); } @@ -620,7 +646,7 @@ entry_guard_register_connect_status(const char *digest, int succeeded, "Connection to never-contacted entry guard '%s' (%s) failed. " "Removing from the list. %d/%d entry guards usable/new.", entry->nickname, buf, - num_live_entry_guards()-1, smartlist_len(entry_guards)-1); + num_live_entry_guards(0)-1, smartlist_len(entry_guards)-1); control_event_guard(entry->nickname, entry->identity, "DROPPED"); entry_guard_free(entry); smartlist_del_keeporder(entry_guards, idx); @@ -659,7 +685,7 @@ entry_guard_register_connect_status(const char *digest, int succeeded, break; if (e->made_contact) { const char *msg; - const node_t *r = entry_is_live(e, 0, 1, 1, &msg); + const node_t *r = entry_is_live(e, 0, 1, 1, 0, &msg); if (r && e->unreachable_since) { refuse_conn = 1; e->can_retry = 1; @@ -671,7 +697,7 @@ entry_guard_register_connect_status(const char *digest, int succeeded, "Connected to new entry guard '%s' (%s). Marking earlier " "entry guards up. %d/%d entry guards usable/new.", entry->nickname, buf, - num_live_entry_guards(), smartlist_len(entry_guards)); + num_live_entry_guards(0), smartlist_len(entry_guards)); log_entry_guards(LOG_INFO); changed = 1; } @@ -769,7 +795,7 @@ entry_guards_set_from_config(const or_options_t *options)
/* Next, the rest of EntryNodes */ SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) { - add_an_entry_guard(node, 0, 0); + add_an_entry_guard(node, 0, 0, 0); if (smartlist_len(entry_guards) > options->NumEntryGuards * 10) break; } SMARTLIST_FOREACH_END(node); @@ -809,6 +835,22 @@ entry_list_is_constrained(const or_options_t *options) const node_t * choose_random_entry(cpath_build_state_t *state) { + return choose_random_entry_impl(state, 0, 0); +} + +/** Pick a live (up and listed) directory guard from entry_guards for + * downloading information of type <b>type</b>. */ +const node_t * +choose_random_dirguard(dirinfo_type_t type) +{ + return choose_random_entry_impl(NULL, 1, type); +} + +/** Helper for choose_random{entry,dirguard}. */ +static const node_t * +choose_random_entry_impl(cpath_build_state_t *state, int for_directory, + dirinfo_type_t dirinfo_type) +{ const or_options_t *options = get_options(); smartlist_t *live_entry_guards = smartlist_new(); smartlist_t *exit_family = smartlist_new(); @@ -818,6 +860,13 @@ choose_random_entry(cpath_build_state_t *state) int need_uptime = state ? state->need_uptime : 0; int need_capacity = state ? state->need_capacity : 0; int preferred_min, consider_exit_family = 0; + int need_descriptor = !for_directory; + + /* Checking dirinfo_type isn't required yet, since we only choose directory + guards that can support microdescs, routerinfos, and networkstatuses, AND + we don't use directory guards if we're configured to do direct downloads + of anything else. */ + (void) dirinfo_type;
if (chosen_exit) { nodelist_add_node_and_family(exit_family, chosen_exit); @@ -832,15 +881,20 @@ choose_random_entry(cpath_build_state_t *state)
if (!entry_list_is_constrained(options) && smartlist_len(entry_guards) < options->NumEntryGuards) - pick_entry_guards(options); + pick_entry_guards(options, for_directory);
retry: smartlist_clear(live_entry_guards); SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { const char *msg; - node = entry_is_live(entry, need_uptime, need_capacity, 0, &msg); + node = entry_is_live(entry, need_uptime, need_capacity, 0, + need_descriptor, &msg); if (!node) continue; /* down, no point */ + if (for_directory) { + if (!entry->is_dir_cache) + continue; /* We need a directory and didn't get one. */ + } if (node == chosen_exit) continue; /* don't pick the same node for entry and exit */ if (consider_exit_family && smartlist_isin(exit_family, node)) @@ -891,7 +945,7 @@ choose_random_entry(cpath_build_state_t *state) /* XXX if guard doesn't imply fast and stable, then we need * to tell add_an_entry_guard below what we want, or it might * be a long time til we get it. -RD */ - node = add_an_entry_guard(NULL, 0, 0); + node = add_an_entry_guard(NULL, 0, 0, for_directory); if (node) { entry_guards_changed(); /* XXX we start over here in case the new node we added shares @@ -1817,7 +1871,7 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) node = node_get_mutable_by_id(ri->cache_info.identity_digest); tor_assert(node); rewrite_node_address_for_bridge(bridge, node); - add_an_entry_guard(node, 1, 1); + add_an_entry_guard(node, 1, 1, 0);
log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, from_cache ? "cached" : "fresh", router_describe(ri)); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 24f2eef..ae5d230 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -35,7 +35,7 @@ typedef struct entry_guard_t { * for this node already? */ unsigned int path_bias_disabled : 1; /**< Have we disabled this node because * of path bias issues? */ - unsigned int is_dir_cache : 1; /**< DOCDOC */ + 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. */ @@ -53,7 +53,7 @@ typedef struct entry_guard_t { entry_guard_t *entry_guard_get_by_id_digest(const char *digest); void entry_guards_changed(void); const smartlist_t *get_entry_guards(void); -int num_live_entry_guards(void); +int num_live_entry_guards(int for_directory);
#endif
@@ -63,6 +63,7 @@ int entry_guard_register_connect_status(const char *digest, int succeeded, void entry_nodes_should_be_added(void); int entry_list_is_constrained(const or_options_t *options); const node_t *choose_random_entry(cpath_build_state_t *state); +const node_t *choose_random_dirguard(dirinfo_type_t t); int entry_guards_parse_state(or_state_t *state, int set, char **msg); void entry_guards_update_state(or_state_t *state); int getinfo_helper_entry_guards(control_connection_t *conn,
tor-commits@lists.torproject.org