diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index bd0a81d974..4811e5c112 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -17,6 +17,15 @@ const char circuitbuild_c_id[] = "$Id$"; /** A global list of all circuits at this hop. */ extern circuit_t *global_circuitlist; +typedef struct { + char nickname[MAX_NICKNAME_LEN+1]; + char identity[DIGEST_LEN]; + time_t down_since; + time_t unlisted_since; +} helper_node_t; + +static smartlist_t *helper_nodes; + /********* END VARIABLES ************/ static int circuit_deliver_create_cell(circuit_t *circ, @@ -27,6 +36,10 @@ 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(void); +static void clear_helper_nodes(void); +static void remove_dead_helpers(void); /** Iterate over values of circ_id, starting from conn-\>next_circ_id, * and with the high bit specified by circ_id_type (see @@ -1368,7 +1381,10 @@ choose_good_middle_server(uint8_t purpose, return choice; } -/** DOCDOC */ +/** DOCDOC + * + * state == null means 'pick a helper.' + */ static routerinfo_t * choose_good_entry_server(cpath_build_state_t *state) { @@ -1376,7 +1392,11 @@ choose_good_entry_server(cpath_build_state_t *state) smartlist_t *excluded = smartlist_create(); or_options_t *options = get_options(); - if ((r = build_state_get_exit_router(state))) { + if (state && options->UseHelperNodes) { + return choose_random_helper(); + } + + if (state && (r = build_state_get_exit_router(state))) { smartlist_add(excluded, r); routerlist_add_family(excluded, r); } @@ -1400,7 +1420,8 @@ choose_good_entry_server(cpath_build_state_t *state) } } choice = router_choose_random_node(options->EntryNodes, options->ExcludeNodes, - excluded, state->need_uptime, state->need_capacity, + excluded, state ? state->need_uptime : 1, + state ? state->need_capacity : 0, options->_AllowUnverified & ALLOW_UNVERIFIED_ENTRY, options->StrictEntryNodes); smartlist_free(excluded); @@ -1569,3 +1590,203 @@ build_state_get_exit_nickname(cpath_build_state_t *state) return state->chosen_exit->nickname; } +/** DOCDOC */ +static int +n_live_helpers(void) +{ + int n = 0; + SMARTLIST_FOREACH(helper_nodes, helper_node_t *, helper, + if (! helper->down_since && ! helper->unlisted_since) + ++n;); + return n; +} + +/** DOCDOC */ +static void +pick_helper_nodes(void) +{ + or_options_t *options = get_options(); + + if (! options->UseHelperNodes) + return; + + if (helper_nodes == NULL) + helper_nodes = smartlist_create(); + + while (smartlist_len(helper_nodes) < options->NumHelperNodes) { + routerinfo_t *entry = choose_good_entry_server(NULL); + /* XXXX deal with duplicate entries. */ + helper_node_t *helper = tor_malloc_zero(sizeof(helper_node_t)); + /* XXXX Downgrade this to info before release. */ + log_fn(LOG_NOTICE, "Chose '%s' as helper node.", entry->nickname); + strlcpy(helper->nickname, entry->nickname, sizeof(helper->nickname)); + memcpy(helper->identity, entry->identity_digest, DIGEST_LEN); + smartlist_add(helper_nodes, helper); + } +} + +/** DOCDOC */ +static void +clear_helper_nodes(void) +{ + SMARTLIST_FOREACH(helper_nodes, helper_node_t *, h, tor_free(h)); + smartlist_clear(helper_nodes); +} + +#define HELPER_ALLOW_DOWNTIME 48*60*60 +#define HELPER_ALLOW_UNLISTED 48*60*60 + +/** DOCDOC */ +static void +remove_dead_helpers(void) +{ + char dbuf[HEX_DIGEST_LEN+1]; + char tbuf[ISO_TIME_LEN+1]; + time_t now = time(NULL); + int i; + + for (i = 0; i < smartlist_len(helper_nodes); ) { + helper_node_t *helper = smartlist_get(helper_nodes, i); + char *why = NULL; + time_t since = 0; + if (helper->unlisted_since + HELPER_ALLOW_UNLISTED > now) { + why = "unlisted"; + since = helper->unlisted_since; + } else if (helper->down_since + HELPER_ALLOW_DOWNTIME > now) { + why = "down"; + since = helper->unlisted_since; + } + if (why) { + base16_encode(dbuf, sizeof(dbuf), helper->identity, DIGEST_LEN); + format_local_iso_time(tbuf, since); + log_fn(LOG_WARN, "Helper node '%s' (%s) has been %s since %s; removing.", + helper->nickname, dbuf, why, tbuf); + tor_free(helper); + smartlist_del(helper_nodes, i); + } else + ++i; + } +} + +/** DOCDOC */ +void +helper_nodes_set_status_from_directory(void) +{ + /* Don't call this on startup; only on a fresh download. Otherwise we'll + * think that things are unlisted. */ + routerlist_t *routers; + time_t now; + int changed = 0; + if (! helper_nodes) + return; + + router_get_routerlist(&routers); + if (! routers) + return; + + now = time(NULL); + + /*XXXX Most of these warns should be non-warns. */ + + SMARTLIST_FOREACH(helper_nodes, helper_node_t *, helper, + { + routerinfo_t *r = router_get_by_digest(helper->identity); + if (! r) { + if (! helper->unlisted_since) { + /* Watch out for skew here. XXXX */ + helper->unlisted_since = routers->published_on; + ++changed; + log_fn(LOG_WARN,"Helper node '%s' is not published in latest directory", + helper->nickname); + } + } else { + if (helper->unlisted_since) { + log_fn(LOG_WARN,"Helper node '%s' is listed again in latest directory", + helper->nickname); + ++changed; + } + helper->unlisted_since = 0; + if (! r->is_running) { + if (! helper->down_since) { + helper->down_since = now; + log_fn(LOG_WARN, "Helper node '%s' is now down.", helper->nickname); + ++changed; + } + } else { + if (helper->down_since) { + log_fn(LOG_WARN,"Helper node '%s' is up in latest directory", + helper->nickname); + ++changed; + } + helper->down_since = 0; + } + } + }); + + if (changed) + log_fn(LOG_WARN, " (%d/%d helpers are usable)", + n_live_helpers(), smartlist_len(helper_nodes)); + + remove_dead_helpers(); + pick_helper_nodes(); +} + +/** DOCDOC */ +void +helper_node_set_status(const char *digest, int succeeded) +{ + if (! helper_nodes) + return; + + SMARTLIST_FOREACH(helper_nodes, helper_node_t *, helper, + { + if (!memcmp(helper->identity, digest, DIGEST_LEN)) { + if (succeeded) { + if (helper->down_since) { + /*XXXX shouldn't warn. */ + log_fn(LOG_WARN, + "Connection to formerly down helper node '%s' succeeeded. " + "%d/%d helpers usable.", helper->nickname, + n_live_helpers(), smartlist_len(helper_nodes)); + } + helper->down_since = 0; + } else if (!helper->down_since) { + helper->down_since = time(NULL); + log_fn(LOG_WARN, + "Connection to helper node '%s' failed. %d/%d helpers usable.", + helper->nickname, n_live_helpers(), smartlist_len(helper_nodes)); + } + } + }); +} + +/** DOCDOC */ +static routerinfo_t * +choose_random_helper(void) +{ + smartlist_t *live_helpers = smartlist_create(); + routerinfo_t *r; + + if (! helper_nodes) + pick_helper_nodes(); + + retry: + SMARTLIST_FOREACH(helper_nodes, helper_node_t *, helper, + if (! helper->down_since && ! helper->unlisted_since) { + if ((r = router_get_by_digest(helper->identity))) + smartlist_add(live_helpers, r); + }); + + if (! smartlist_len(live_helpers)) { + /* XXXX Is this right? What if network is down? */ + log_fn(LOG_WARN, "No functional helper nodes found; picking a new set."); + clear_helper_nodes(); + pick_helper_nodes(); + goto retry; + } + + r = smartlist_choose(live_helpers); + smartlist_free(live_helpers); + return r; +} + diff --git a/src/or/config.c b/src/or/config.c index cd5f3a9d97..f7eb57495b 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -307,6 +307,9 @@ options_act(void) return -1; } + if (options->EntryNodes && strlen(options->EntryNodes)) + options->UseHelperNodes = 0; + /* Setuid/setgid as appropriate */ if (options->User || options->Group) { if (switch_id(options->User, options->Group) != 0) { diff --git a/src/or/connection.c b/src/or/connection.c index fe72c36111..4fd918e66e 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -309,6 +309,7 @@ connection_about_to_close_connection(connection_t *conn) if (conn->state != OR_CONN_STATE_OPEN) { if (connection_or_nonopen_was_started_here(conn)) { rep_hist_note_connect_failed(conn->identity_digest, time(NULL)); + helper_node_set_status(conn->identity_digest, 0); control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED); } } else if (conn->hold_open_until_flushed) { diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 008e401fe4..713c7b8c6c 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -358,6 +358,7 @@ connection_or_connect(uint32_t addr, uint16_t port, const char *id_digest) case -1: if (!options->HttpsProxy) router_mark_as_down(conn->identity_digest); + helper_node_set_status(conn->identity_digest, 0); control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED); connection_free(conn); return NULL; @@ -527,6 +528,7 @@ connection_or_check_valid_handshake(connection_t *conn, char *digest_rcvd) log_fn(severity, "Identity key not as expected for router at %s:%d: wanted %s but got %s", conn->address, conn->port, conn->nickname+1, d); + helper_node_set_status(conn->identity_digest, 0); control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED); as_advertised = 0; } @@ -535,6 +537,7 @@ connection_or_check_valid_handshake(connection_t *conn, char *digest_rcvd) log_fn(severity, "Other side (%s:%d) is '%s', but we tried to connect to '%s'", conn->address, conn->port, nickname, conn->nickname); + helper_node_set_status(conn->identity_digest, 0); control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED); as_advertised = 0; } @@ -592,6 +595,7 @@ connection_tls_finish_handshake(connection_t *conn) connection_watch_events(conn, EV_READ); circuit_n_conn_done(conn, 1); /* send the pending creates, if any. */ rep_hist_note_connect_succeeded(conn->identity_digest, time(NULL)); + helper_node_set_status(conn->identity_digest, 1); control_event_or_conn_status(conn, OR_CONN_EVENT_CONNECTED); return 0; } diff --git a/src/or/directory.c b/src/or/directory.c index 2ebb6907e5..7ca9753da0 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -758,6 +758,7 @@ connection_dir_client_reached_eof(connection_t *conn) log_fn(LOG_INFO,"updated routers."); } /* do things we've been waiting to do */ + helper_nodes_set_status_from_directory(); directory_has_arrived(time(NULL), conn->identity_digest); } @@ -780,6 +781,7 @@ connection_dir_client_reached_eof(connection_t *conn) router_get_routerlist(&rl); if (rl) { routerlist_set_runningrouters(rl,rrs); + helper_nodes_set_status_from_directory(); } else { running_routers_free(rrs); } diff --git a/src/or/or.h b/src/or/or.h index 71c2ea0abc..3ccfa8f1ad 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1252,6 +1252,9 @@ void extend_info_free(extend_info_t *info); routerinfo_t *build_state_get_exit_router(cpath_build_state_t *state); const char *build_state_get_exit_nickname(cpath_build_state_t *state); +void helper_node_set_status(const char *digest, int succeeded); +void helper_nodes_set_status_from_directory(void); + /********************************* circuitlist.c ***********************/ circuit_t * _circuit_get_global_list(void);