diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index b9b07acd2a..577993a740 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -48,10 +48,7 @@ static int onion_extend_cpath(uint8_t purpose, crypt_path_t **head_ptr, cpath_build_state_t *state); static int count_acceptable_routers(smartlist_t *routers); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); -static void pick_helper_nodes(void); -static routerinfo_t *choose_random_helper(routerinfo_t *chosen_exit); -static void clear_helper_nodes(void); -static void remove_dead_helpers(void); +static routerinfo_t *choose_random_helper(cpath_build_state_t *state); static void helper_nodes_changed(void); /** Iterate over values of circ_id, starting from conn-\>next_circ_id, @@ -1508,7 +1505,7 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) if (state && options->UseHelperNodes && purpose != CIRCUIT_PURPOSE_TESTING) { - return choose_random_helper(build_state_get_exit_router(state)); + return choose_random_helper(state); } if (state && (r = build_state_get_exit_router(state))) { @@ -1698,6 +1695,31 @@ build_state_get_exit_nickname(cpath_build_state_t *state) return state->chosen_exit->nickname; } +/** Return the router corresponding to h, if h is + * working well enough that we are willing to use it as a helper + * right now. (Else return NULL.) In particular, it must be + * - Listed as either up or never yet contacted; + * - Present in the routerlist; + * - Listed as 'fast' by the current dirserver concensus; and + * - Allowed by our current ReachableAddresses config option. + */ +static INLINE routerinfo_t * +helper_is_live(helper_node_t *h, int need_uptime, int need_capacity) +{ + routerinfo_t *r; + if (h->down_since && h->made_contact) + return NULL; + r = router_get_by_digest(h->identity); + if (!r) + return NULL; + if (router_is_unreliable(r, need_uptime, need_capacity)) + return NULL; + if (firewall_is_fascist() && + !fascist_firewall_allows_address(r->addr,r->or_port)) + return NULL; + return r; +} + /** Return the number of helper nodes that we think are usable. */ static int num_live_helpers(void) @@ -1706,11 +1728,55 @@ num_live_helpers(void) if (! helper_nodes) return 0; SMARTLIST_FOREACH(helper_nodes, helper_node_t *, helper, - if (! helper->down_since && ! helper->unlisted_since) - ++n;); + { + if (helper_is_live(helper, 0, 1)) + ++n; + }); return n; } +/** Return 1 if digest matches the identity of any entry + * in the helper_nodes list. Else return 0. */ +static INLINE int +is_a_helper(char *digest) +{ + SMARTLIST_FOREACH(helper_nodes, helper_node_t *, helper, + if(!memcmp(digest, helper->identity, DIGEST_LEN)) + return 1; + ); + return 0; +} + +#define NUM_HELPER_PICK_TRIES 100 + +/** Add a new (preferably stable and fast) helper to the end of our + * helper_nodes list. Return a pointer to the router if we succeed, + * or NULL if we can't find any more suitable helpers. If + * tries_left is <= 1, that means you should fail. */ +static routerinfo_t * +add_a_helper(int tries_left) +{ + routerinfo_t *entry; + helper_node_t *helper; + if (--tries_left <= 0) { + warn(LD_CIRC, "Tried finding a new helper, but failed. Bad news. XXX."); + return NULL; + } + entry = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); + if (!entry) + return NULL; + /* make sure it's not already a helper */ + if (is_a_helper(entry->cache_info.identity_digest)) + return add_a_helper(tries_left); /* recurse */ + helper = tor_malloc_zero(sizeof(helper_node_t)); + /* XXXX Downgrade this to info before release. NM */ + notice(LD_CIRC, "Chose '%s' as new helper node.", entry->nickname); + strlcpy(helper->nickname, entry->nickname, sizeof(helper->nickname)); + memcpy(helper->identity, entry->cache_info.identity_digest, DIGEST_LEN); + smartlist_add(helper_nodes, helper); + return entry; +} + /** If the use of helper nodes is configured, choose more helper nodes * until we have enough in the list. */ static void @@ -1719,43 +1785,22 @@ pick_helper_nodes(void) or_options_t *options = get_options(); int changed = 0; - if (! options->UseHelperNodes) - return; - if (!helper_nodes) helper_nodes = smartlist_create(); - while (smartlist_len(helper_nodes) < options->NumHelperNodes) { - routerinfo_t *entry = - choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); - /* XXXX deal with duplicate entries. NM */ - helper_node_t *helper = tor_malloc_zero(sizeof(helper_node_t)); - /* XXXX Downgrade this to info before release. NM */ - notice(LD_CIRC, "Chose '%s' as helper node.", entry->nickname); - strlcpy(helper->nickname, entry->nickname, sizeof(helper->nickname)); - memcpy(helper->identity, entry->cache_info.identity_digest, DIGEST_LEN); - smartlist_add(helper_nodes, helper); + while (num_live_helpers() < options->NumHelperNodes) { + if (!add_a_helper(NUM_HELPER_PICK_TRIES)) + break; changed = 1; } if (changed) helper_nodes_changed(); } -/** Remove all elements from the list of helper nodes. */ -static void -clear_helper_nodes(void) -{ - SMARTLIST_FOREACH(helper_nodes, helper_node_t *, h, tor_free(h)); - smartlist_clear(helper_nodes); - helper_nodes_changed(); -} - /** Release all storage held by the list of helper nodes. */ void helper_nodes_free_all(void) { - /* Don't call clear_helper_nodes(); that will flush our state change to - * disk. */ if (helper_nodes) { SMARTLIST_FOREACH(helper_nodes, helper_node_t *, h, tor_free(h)); smartlist_free(helper_nodes); @@ -1763,16 +1808,19 @@ helper_nodes_free_all(void) } } +/* XXX These are 12 hours for now, but I'd like to make them 30 days */ + /** How long (in seconds) do we allow a helper node to be nonfunctional * before we give up on it? */ -#define HELPER_ALLOW_DOWNTIME 48*60*60 +#define HELPER_ALLOW_DOWNTIME (1*12*60*60) /** How long (in seconds) do we allow a helper node to be unlisted in the * directory before we give up on it? */ -#define HELPER_ALLOW_UNLISTED 48*60*60 +#define HELPER_ALLOW_UNLISTED (1*12*60*60) /** Remove all helper nodes that have been down or unlisted for so - * long that we don't think they'll come up again. */ -static void + * long that we don't think they'll come up again. Return 1 if we + * removed any, or 0 if we did nothing. */ +static int remove_dead_helpers(void) { char dbuf[HEX_DIGEST_LEN+1]; @@ -1800,13 +1848,12 @@ remove_dead_helpers(void) warn(LD_CIRC, "Helper node '%s' (%s) has been %s since %s; removing.", helper->nickname, dbuf, why, tbuf); tor_free(helper); - smartlist_del(helper_nodes, i); + smartlist_del_keeporder(helper_nodes, i); changed = 1; } else ++i; } - if (changed) - helper_nodes_changed(); + return changed ? 1 : 0; } /** A new directory or router-status has arrived; update the down/listed @@ -1870,14 +1917,14 @@ helper_nodes_set_status_from_directory(void) } }); + if (remove_dead_helpers()) + changed = 1; + if (changed) { log_fn(severity, LD_CIRC, " (%d/%d helpers are usable)", num_live_helpers(), smartlist_len(helper_nodes)); helper_nodes_changed(); } - - remove_dead_helpers(); - pick_helper_nodes(); } /** Called when a connection to an OR with the identity digest digest @@ -1911,13 +1958,20 @@ helper_node_set_status(const char *digest, int succeeded) } } else { if (!helper->made_contact) { /* dump him */ + notice(LD_CIRC, + "Connection to never-contacted helper node '%s' failed. " + "Removing from the list. %d/%d helpers usable.", + helper->nickname, + num_live_helpers(), smartlist_len(helper_nodes)); + tor_free(helper); + smartlist_del_keeporder(helper_nodes, helper_sl_idx); changed = 1; } else if (!helper->down_since) { helper->down_since = time(NULL); warn(LD_CIRC, "Connection to helper node '%s' failed." " %d/%d helpers usable.", - helper->nickname, num_live_helpers(), - smartlist_len(helper_nodes)); + helper->nickname, + num_live_helpers(), smartlist_len(helper_nodes)); changed = 1; } } @@ -1928,32 +1982,55 @@ helper_node_set_status(const char *digest, int succeeded) helper_nodes_changed(); } -/** Pick a live (up and listed) helper node from the list of helpers, but - * don't pick exit. If no helpers are available, pick a new list. */ +/** Pick a live (up and listed) helper node from the list of helpers, and + * make sure not to pick this circuit's exit. */ static routerinfo_t * -choose_random_helper(routerinfo_t *chosen_exit) +choose_random_helper(cpath_build_state_t *state) { smartlist_t *live_helpers = smartlist_create(); + routerinfo_t *chosen_exit = build_state_get_exit_router(state); routerinfo_t *r; + int need_uptime = state->need_uptime; + int need_capacity = state->need_capacity; - if (! helper_nodes) + if (! helper_nodes || + smartlist_len(helper_nodes) < get_options()->NumHelperNodes) pick_helper_nodes(); retry: + smartlist_clear(live_helpers); SMARTLIST_FOREACH(helper_nodes, helper_node_t *, helper, - if (! helper->down_since && ! helper->unlisted_since) { - if ((r = router_get_by_digest(helper->identity))) { - if (r != chosen_exit) - smartlist_add(live_helpers, r); - } - }); + { + r = helper_is_live(helper, need_uptime, need_capacity); + if (r && r != chosen_exit) { + smartlist_add(live_helpers, r); + if (smartlist_len(live_helpers) >= get_options()->NumHelperNodes) + break; /* we have enough */ + } + }); - if (! smartlist_len(live_helpers)) { - /* XXXX Is this right? What if network is down? */ - warn(LD_CIRC, "No functional helper nodes found; picking a new set."); - clear_helper_nodes(); - pick_helper_nodes(); - goto retry; + /* Try to have at least 2 choices available. This way we don't + * get stuck with a single live-but-crummy helper and just keep + * using him. + * (We might get 2 live-but-crummy helpers, but so be it.) */ + if (smartlist_len(live_helpers) < 2) { + if (need_uptime) { + need_uptime = 0; /* try without that requirement */ + goto retry; + } + /* still no? try adding a new helper then */ + r = add_a_helper(NUM_HELPER_PICK_TRIES); + if (r) { + smartlist_add(live_helpers, r); + helper_nodes_changed(); + } else { + if (need_capacity) { + /* still no? last attempt, try without requiring capacity */ + need_capacity = 0; + goto retry; + } + /* live_helpers will be empty below. Oh well, we tried. */ + } } r = smartlist_choose(live_helpers); @@ -1971,7 +2048,7 @@ int helper_nodes_parse_state(or_state_t *state, int set, const char **err) { helper_node_t *node = NULL; - smartlist_t *helpers = smartlist_create(); + smartlist_t *new_helpers = smartlist_create(); config_line_t *line; *err = NULL; @@ -1980,7 +2057,7 @@ helper_nodes_parse_state(or_state_t *state, int set, const char **err) smartlist_t *args = smartlist_create(); node = tor_malloc_zero(sizeof(helper_node_t)); node->made_contact = 1; /* all helpers on disk have been contacted */ - smartlist_add(helpers, node); + smartlist_add(new_helpers, node); smartlist_split_string(args, line->value, " ", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); if (smartlist_len(args)<2) { @@ -2016,16 +2093,14 @@ helper_nodes_parse_state(or_state_t *state, int set, const char **err) } if (*err || !set) { - SMARTLIST_FOREACH(helpers, helper_node_t *, h, tor_free(h)); - smartlist_free(helpers); - helpers = NULL; - } - if (!*err && set) { + SMARTLIST_FOREACH(new_helpers, helper_node_t *, h, tor_free(h)); + smartlist_free(new_helpers); + } else { /* !*err && set */ if (helper_nodes) { SMARTLIST_FOREACH(helper_nodes, helper_node_t *, h, tor_free(h)); smartlist_free(helper_nodes); } - helper_nodes = helpers; + helper_nodes = new_helpers; helper_nodes_dirty = 0; } return *err ? -1 : 0;