From 82178a81f6748c9b26bdc8a5da36dd34b689281b Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Sat, 17 Oct 2009 18:52:18 -0400 Subject: [PATCH 01/35] refuse excluded hidserv nodes if strictnodes Make hidden services more flaky for people who set both ExcludeNodes and StrictNodes. Not recommended, especially for hidden service operators. --- src/or/rendclient.c | 56 ++++++++++++++++++++++++++++++++++++++++---- src/or/rendservice.c | 14 ++++++++++- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 8ac909fc80..90304c33c3 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -22,6 +22,9 @@ #include "rephist.h" #include "routerlist.h" +static extend_info_t *rend_client_get_random_intro_impl( + const rend_data_t *rend_query, const int strict); + /** Called when we've established a circuit to an introduction point: * send the introduction request. */ void @@ -738,11 +741,32 @@ rend_client_desc_trynow(const char *query) */ extend_info_t * rend_client_get_random_intro(const rend_data_t *rend_query) +{ + extend_info_t *result; + /* See if we can get a node that complies with ExcludeNodes */ + if ((result = rend_client_get_random_intro_impl(rend_query, 1))) + return result; + /* If not, and StrictNodes is not set, see if we can return any old node + */ + if (!get_options()->StrictNodes) + return rend_client_get_random_intro_impl(rend_query, 0); + return NULL; +} + +/** As rend_client_get_random_intro, except assume that StrictNodes is set + * iff strict is true. + */ +static extend_info_t * +rend_client_get_random_intro_impl(const rend_data_t *rend_query, + const int strict) { int i; rend_cache_entry_t *entry; rend_intro_point_t *intro; routerinfo_t *router; + or_options_t *options = get_options(); + smartlist_t *usable_nodes; + int n_excluded = 0; if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) { log_warn(LD_REND, @@ -750,13 +774,26 @@ rend_client_get_random_intro(const rend_data_t *rend_query) safe_str_client(rend_query->onion_address)); return NULL; } + /* We'll keep a separate list of the usable nodes. If this becomes empty, + * no nodes are usable. */ + usable_nodes = smartlist_create(); + smartlist_add_all(usable_nodes, entry->parsed->intro_nodes); again: - if (smartlist_len(entry->parsed->intro_nodes) == 0) + if (smartlist_len(usable_nodes) == 0) { + if (n_excluded && get_options()->StrictNodes) { + /* We only want to warn if StrictNodes is really set. Otherwise + * we're just about to retry anyways. + */ + log_warn(LD_REND, "All introduction points for hidden service are " + "at excluded relays, and StrictNodes is set. Skipping."); + } + smartlist_free(usable_nodes); return NULL; + } - i = crypto_rand_int(smartlist_len(entry->parsed->intro_nodes)); - intro = smartlist_get(entry->parsed->intro_nodes, i); + i = crypto_rand_int(smartlist_len(usable_nodes)); + intro = smartlist_get(usable_nodes, i); /* Do we need to look up the router or is the extend info complete? */ if (!intro->extend_info->onion_key) { if (tor_digest_is_zero(intro->extend_info->identity_digest)) @@ -766,13 +803,22 @@ rend_client_get_random_intro(const rend_data_t *rend_query) if (!router) { log_info(LD_REND, "Unknown router with nickname '%s'; trying another.", intro->extend_info->nickname); - rend_intro_point_free(intro); - smartlist_del(entry->parsed->intro_nodes, i); + smartlist_del(usable_nodes, i); goto again; } extend_info_free(intro->extend_info); intro->extend_info = extend_info_from_router(router); } + /* Check if we should refuse to talk to this router. */ + if (options->ExcludeNodes && strict && + routerset_contains_extendinfo(options->ExcludeNodes, + intro->extend_info)) { + n_excluded++; + smartlist_del(usable_nodes, i); + goto again; + } + + smartlist_free(usable_nodes); return extend_info_dup(intro->extend_info); } diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 45039822f8..88f1ba3ddd 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -848,6 +848,7 @@ clean_accepted_intros(rend_service_t *service, time_t now) /** Respond to an INTRODUCE2 cell by launching a circuit to the chosen * rendezvous point. */ + /* XXX022 this function sure could use some organizing. -RD */ int rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, size_t request_len) @@ -875,6 +876,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, time_t now = time(NULL); char diffie_hellman_hash[DIGEST_LEN]; time_t *access_time; + or_options_t *options = get_options(); + tor_assert(circuit->rend_data); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, @@ -1047,6 +1050,15 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, goto err; } + /* Check if we'd refuse to talk to this router */ + if (options->ExcludeNodes && options->StrictNodes && + routerset_contains_extendinfo(options->ExcludeNodes, extend_info)) { + log_warn(LD_REND, "Client asked to rendezvous at a relay that we " + "exclude, and StrictNodes is set. Refusing service."); + reason = END_CIRC_REASON_INTERNAL; /* XXX might leak why we refused */ + goto err; + } + r_cookie = ptr; base16_encode(hexcookie,9,r_cookie,4); @@ -1394,7 +1406,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) /** Called when we get an INTRO_ESTABLISHED cell; mark the circuit as a * live introduction point, and note that the service descriptor is - * now out-of-date.*/ + * now out-of-date. */ int rend_service_intro_established(origin_circuit_t *circuit, const uint8_t *request, From ad3da535366aeb9b7441f4881899758bc7475168 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Sat, 17 Oct 2009 18:54:20 -0400 Subject: [PATCH 02/35] If EntryNodes and ExcludeNodes overlap, obey ExcludeNodes. --- src/or/circuitbuild.c | 6 ++++-- src/or/config.c | 3 ++- src/or/or.h | 4 ++-- src/or/routerlist.c | 13 ++++++++----- src/or/routerlist.h | 1 + 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 2d4d5c032a..ebbda211db 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2938,6 +2938,7 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) description,exit->nickname, rs==options->ExcludeNodes?"":" or ExcludeExitNodes", (int)purpose); + /* XXX022-1090 "using anyway" is freaking people out -RD */ circuit_log_path(LOG_WARN, domain, circ); } @@ -3979,7 +3980,8 @@ entry_guards_prepend_from_config(or_options_t *options) * Perhaps we should do this calculation once whenever the list of routers * changes or the entrynodes setting changes. */ - routerset_get_all_routers(entry_routers, options->EntryNodes, 0); + routerset_get_all_routers(entry_routers, options->EntryNodes, + options->ExcludeNodes, 0); SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, smartlist_add(entry_fps,ri->cache_info.identity_digest)); SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, { @@ -4155,7 +4157,7 @@ choose_random_entry(cpath_build_state_t *state) goto retry; } if (!r && entry_list_is_constrained(options) && consider_exit_family) { - /* still no? if we're using bridges or have strictentrynodes + /* still no? if we're using bridges or have StrictNodes * set, and our chosen exit is in the same family as all our * bridges/entry guards, then be flexible about families. */ consider_exit_family = 0; diff --git a/src/or/config.c b/src/or/config.c index 9675c73c99..bd904dcf0b 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1412,7 +1412,8 @@ options_act(or_options_t *old_options) /* Check if we need to parse and add the EntryNodes config option. */ if (options->EntryNodes && (!old_options || - (!routerset_equal(old_options->EntryNodes,options->EntryNodes)))) + !routerset_equal(old_options->EntryNodes,options->EntryNodes) || + !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes))) entry_nodes_should_be_added(); /* Since our options changed, we might need to regenerate and upload our diff --git a/src/or/or.h b/src/or/or.h index 06e6d7fc8f..50a1223f3c 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2387,7 +2387,7 @@ typedef struct { * ORs not to consider as exits. */ /** Union of ExcludeNodes and ExcludeExitNodes */ - struct routerset_t *_ExcludeExitNodesUnion; + routerset_t *_ExcludeExitNodesUnion; int DisableAllSwap; /**< Boolean: Attempt to call mlockall() on our * process for all current and future memory. */ @@ -3487,7 +3487,7 @@ typedef struct trusted_dir_server_t { #define ROUTER_MAX_DECLARED_BANDWIDTH INT32_MAX -/* Flags for pick_directory_server and pick_trusteddirserver. */ +/* Flags for pick_directory_server() and pick_trusteddirserver(). */ /** Flag to indicate that we should not automatically be willing to use * ourself to answer a directory request. * Passed to router_pick_directory_server (et al).*/ diff --git a/src/or/routerlist.c b/src/or/routerlist.c index c02654feef..5d9ab8cbac 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -5516,10 +5516,11 @@ routerset_contains_routerstatus(const routerset_t *set, routerstatus_t *rs) } /** Add every known routerinfo_t that is a member of routerset to - * out. If running_only, only add the running ones. */ + * out, but never add any that are part of excludeset. + * If running_only, only add the running ones. */ void routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, - int running_only) + const routerset_t *excludeset, int running_only) { tor_assert(out); if (!routerset || !routerset->list) @@ -5529,12 +5530,13 @@ routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, if (routerset_is_list(routerset)) { /* No routers are specified by type; all are given by name or digest. - * we can do a lookup in O(len(list)). */ + * we can do a lookup in O(len(routerset)). */ SMARTLIST_FOREACH(routerset->list, const char *, name, { routerinfo_t *router = router_get_by_nickname(name, 1); if (router) { if (!running_only || router->is_running) - smartlist_add(out, router); + if (!routerset_contains_router(excludeset, router)) + smartlist_add(out, router); } }); } else { @@ -5544,7 +5546,8 @@ routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, SMARTLIST_FOREACH(rl->routers, routerinfo_t *, router, { if (running_only && !router->is_running) continue; - if (routerset_contains_router(routerset, router)) + if (routerset_contains_router(routerset, router) && + !routerset_contains_router(excludeset, router)) smartlist_add(out, router); }); } diff --git a/src/or/routerlist.h b/src/or/routerlist.h index ca428114ed..cd0eb956b5 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -173,6 +173,7 @@ int routerset_contains_routerstatus(const routerset_t *set, int routerset_contains_extendinfo(const routerset_t *set, const extend_info_t *ei); void routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, + const routerset_t *excludeset, int running_only); void routersets_get_disjunction(smartlist_t *target, const smartlist_t *source, const routerset_t *include, From 4906188b622872899f76cf01167cfef3e09cbffd Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 03:09:24 -0500 Subject: [PATCH 03/35] handle excludenodes for dir fetch/post If we're picking a random directory node, never pick an excluded one. But if we've chosen a specific one (or all), allow it unless strictnodes is set (in which case warn so the user knows it's their fault). When warning that we won't connect to a strictly excluded node, log what it was we were trying to do at that node. When ExcludeNodes is set but StrictNodes is not set, we only use non-excluded nodes if we can, but fall back to using excluded nodes if none of those nodes is usable. --- src/or/directory.c | 31 +++++++++++++++++++++++++++++-- src/or/routerlist.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index 8f33a608d4..0c095fe871 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -253,10 +253,13 @@ directories_have_accepted_server_descriptor(void) } /** Start a connection to every suitable directory authority, using - * connection purpose 'purpose' and uploading the payload 'payload' - * (length 'payload_len'). dir_purpose should be one of + * connection purpose dir_purpose and uploading payload + * (of length payload_len). The dir_purpose should be one of * 'DIR_PURPOSE_UPLOAD_DIR' or 'DIR_PURPOSE_UPLOAD_RENDDESC'. * + * router_purpose describes the type of descriptor we're + * publishing, if we're publishing a descriptor -- e.g. general or bridge. + * * type specifies what sort of dir authorities (V1, V2, * HIDSERV, BRIDGE) we should upload to. * @@ -272,6 +275,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, const char *payload, size_t payload_len, size_t extrainfo_len) { + or_options_t *options = get_options(); int post_via_tor; smartlist_t *dirservers = router_get_trusted_dir_servers(); int found = 0; @@ -287,6 +291,16 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, if ((type & ds->type) == 0) continue; + if (options->ExcludeNodes && options->StrictNodes && + routerset_contains_routerstatus(options->ExcludeNodes, rs)) { + log_warn(LD_DIR, "Wanted to contact authority '%s' for %s, but " + "it's in our ExcludedNodes list and StrictNodes is set. " + "Skipping.", + ds->nickname, + dir_conn_purpose_to_string(dir_purpose)); + continue; + } + found = 1; /* at least one authority of this type was listed */ if (dir_purpose == DIR_PURPOSE_UPLOAD_DIR) ds->has_accepted_serverdesc = 0; @@ -496,12 +510,14 @@ directory_initiate_command_routerstatus_rend(routerstatus_t *status, time_t if_modified_since, const rend_data_t *rend_query) { + or_options_t *options = get_options(); routerinfo_t *router; char address_buf[INET_NTOA_BUF_LEN+1]; struct in_addr in; const char *address; tor_addr_t addr; router = router_get_by_digest(status->identity_digest); + if (!router && anonymized_connection) { log_info(LD_DIR, "Not sending anonymized request to directory '%s'; we " "don't have its router descriptor.", status->nickname); @@ -514,6 +530,17 @@ directory_initiate_command_routerstatus_rend(routerstatus_t *status, address = address_buf; } tor_addr_from_ipv4h(&addr, status->addr); + + if (options->ExcludeNodes && options->StrictNodes && + routerset_contains_routerstatus(options->ExcludeNodes, status)) { + log_warn(LD_DIR, "Wanted to contact directory mirror '%s' for %s, but " + "it's in our ExcludedNodes list and StrictNodes is set. " + "Skipping. This choice might make your Tor not work.", + status->nickname, + dir_conn_purpose_to_string(dir_purpose)); + return; + } + directory_initiate_command_rend(address, &addr, status->or_port, status->dir_port, status->version_supports_conditional_consensus, diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 5d9ab8cbac..29e2e96360 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1070,6 +1070,7 @@ router_pick_trusteddirserver(authority_type_t type, int flags) static routerstatus_t * router_pick_directory_server_impl(authority_type_t type, int flags) { + or_options_t *options = get_options(); routerstatus_t *result; smartlist_t *direct, *tunnel; smartlist_t *trusted_direct, *trusted_tunnel; @@ -1079,10 +1080,13 @@ router_pick_directory_server_impl(authority_type_t type, int flags) int requireother = ! (flags & PDS_ALLOW_SELF); int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL); int prefer_tunnel = (flags & _PDS_PREFER_TUNNELED_DIR_CONNS); + int try_excluding = 1, n_excluded = 0; if (!consensus) return NULL; + retry_without_exclude: + direct = smartlist_create(); tunnel = smartlist_create(); trusted_direct = smartlist_create(); @@ -1114,6 +1118,11 @@ router_pick_directory_server_impl(authority_type_t type, int flags) if ((type & EXTRAINFO_CACHE) && !router_supports_extrainfo(status->identity_digest, 0)) continue; + if (try_excluding && options->ExcludeNodes && + routerset_contains_routerstatus(options->ExcludeNodes, status)) { + ++n_excluded; + continue; + } /* XXXX IP6 proposal 118 */ tor_addr_from_ipv4h(&addr, status->addr); @@ -1155,6 +1164,15 @@ router_pick_directory_server_impl(authority_type_t type, int flags) smartlist_free(trusted_tunnel); smartlist_free(overloaded_direct); smartlist_free(overloaded_tunnel); + + if (result == NULL && try_excluding && !options->StrictNodes && n_excluded) { + /* If we got no result, and we are excluding nodes, and StrictNodes is + * not set, try again without excluding nodes. */ + try_excluding = 0; + n_excluded = 0; + goto retry_without_exclude; + } + return result; } @@ -1165,6 +1183,7 @@ static routerstatus_t * router_pick_trusteddirserver_impl(authority_type_t type, int flags, int *n_busy_out) { + or_options_t *options = get_options(); smartlist_t *direct, *tunnel; smartlist_t *overloaded_direct, *overloaded_tunnel; routerinfo_t *me = router_get_my_routerinfo(); @@ -1175,10 +1194,13 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags, const int prefer_tunnel = (flags & _PDS_PREFER_TUNNELED_DIR_CONNS); const int no_serverdesc_fetching =(flags & PDS_NO_EXISTING_SERVERDESC_FETCH); int n_busy = 0; + int try_excluding = 1, n_excluded = 0; if (!trusted_dir_servers) return NULL; + retry_without_exclude: + direct = smartlist_create(); tunnel = smartlist_create(); overloaded_direct = smartlist_create(); @@ -1197,6 +1219,12 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags, continue; if (requireother && me && router_digest_is_me(d->digest)) continue; + if (try_excluding && options->ExcludeNodes && + routerset_contains_routerstatus(options->ExcludeNodes, + &d->fake_status)) { + ++n_excluded; + continue; + } /* XXXX IP6 proposal 118 */ tor_addr_from_ipv4h(&addr, d->addr); @@ -1243,6 +1271,15 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags, smartlist_free(tunnel); smartlist_free(overloaded_direct); smartlist_free(overloaded_tunnel); + + if (result == NULL && try_excluding && !options->StrictNodes && n_excluded) { + /* If we got no result, and we are excluding nodes, and StrictNodes is + * not set, try again without excluding nodes. */ + try_excluding = 0; + n_excluded = 0; + goto retry_without_exclude; + } + return result; } From 7e2e8074d52f9ace76d98a62d67a4bf781f06c3b Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 03:22:53 -0500 Subject: [PATCH 04/35] slight tweak on circuit_conforms_to_options this function really needs to get a total rewrite (or die) For now, use #if 0 to disable it. --- src/or/circuituse.c | 25 +++++++++++++------------ src/or/circuituse.h | 2 ++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index cdf49e3983..7b20f7f173 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -242,33 +242,34 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose, return best ? TO_ORIGIN_CIRCUIT(best) : NULL; } +#if 0 /** Check whether, according to the policies in options, the * circuit circ makes sense. */ -/* XXXX currently only checks Exclude{Exit}Nodes. It should check more. */ +/* XXXX currently only checks Exclude{Exit}Nodes; it should check more. + * Also, it doesn't have the right definition of an exit circuit. Also, + * it's never called. */ int circuit_conforms_to_options(const origin_circuit_t *circ, const or_options_t *options) { const crypt_path_t *cpath, *cpath_next = NULL; - for (cpath = circ->cpath; cpath && cpath_next != circ->cpath; - cpath = cpath_next) { + /* first check if it includes any excluded nodes */ + for (cpath = circ->cpath; cpath_next != circ->cpath; cpath = cpath_next) { cpath_next = cpath->next; - if (routerset_contains_extendinfo(options->ExcludeNodes, cpath->extend_info)) return 0; - - if (cpath->next == circ->cpath) { - /* This is apparently the exit node. */ - - if (routerset_contains_extendinfo(options->ExcludeExitNodes, - cpath->extend_info)) - return 0; - } } + + /* then consider the final hop */ + if (routerset_contains_extendinfo(options->ExcludeExitNodes, + circ->cpath->prev->extend_info)) + return 0; + return 1; } +#endif /** Close all circuits that start at us, aren't open, and were born * at least CircuitBuildTimeout seconds ago. diff --git a/src/or/circuituse.h b/src/or/circuituse.h index a121099aca..9f393ab378 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -16,8 +16,10 @@ void circuit_expire_building(void); void circuit_remove_handled_ports(smartlist_t *needed_ports); int circuit_stream_is_being_handled(edge_connection_t *conn, uint16_t port, int min); +#if 0 int circuit_conforms_to_options(const origin_circuit_t *circ, const or_options_t *options); +#endif void circuit_build_needed_circs(time_t now); void circuit_detach_stream(circuit_t *circ, edge_connection_t *conn); From 719b5b87dee4910bb3e2d29db6e2012416f60d3d Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 03:57:01 -0500 Subject: [PATCH 05/35] don't exit enclave to excluded relays --- src/or/routerlist.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 29e2e96360..523596ddda 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1538,6 +1538,8 @@ routerlist_find_my_routerinfo(void) /** Find a router that's up, that has this IP address, and * that allows exit to this address:port, or return NULL if there * isn't a good one. + * Don't exit enclave to excluded relays -- it wouldn't actually + * hurt anything, but this way there are fewer confused users. */ routerinfo_t * router_find_exact_exit_enclave(const char *address, uint16_t port) @@ -1545,6 +1547,7 @@ router_find_exact_exit_enclave(const char *address, uint16_t port) uint32_t addr; struct in_addr in; tor_addr_t a; + or_options_t *options = get_options(); if (!tor_inet_aton(address, &in)) return NULL; /* it's not an IP already */ @@ -1557,7 +1560,8 @@ router_find_exact_exit_enclave(const char *address, uint16_t port) if (router->addr == addr && router->is_running && compare_tor_addr_to_addr_policy(&a, port, router->exit_policy) == - ADDR_POLICY_ACCEPTED) + ADDR_POLICY_ACCEPTED && + !routerset_contains_router(options->_ExcludeExitNodesUnion, router)) return router; }); return NULL; From 5d12495d982d076b74ca2c0c88b78b00d2810e71 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 04:21:25 -0500 Subject: [PATCH 06/35] the new entrynodes behavior is always strict --- src/or/circuitbuild.c | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index ebbda211db..86406cbea9 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -4006,14 +4006,10 @@ entry_guards_prepend_from_config(or_options_t *options) SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, { add_an_entry_guard(ri, 0); }); - /* Finally, the remaining previously configured guards that are not in - * EntryNodes, unless we're strict in which case we drop them */ - if (options->StrictNodes) { - SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e, - entry_guard_free(e)); - } else { - smartlist_add_all(entry_guards, old_entry_guards_not_on_list); - } + /* Finally, free the remaining previously configured guards that are not in + * EntryNodes. */ + SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e, + entry_guard_free(e)); smartlist_free(entry_routers); smartlist_free(entry_fps); @@ -4024,7 +4020,7 @@ entry_guards_prepend_from_config(or_options_t *options) /** Return 0 if we're fine adding arbitrary routers out of the * directory to our entry guard list, or return 1 if we have a - * list already and we'd prefer to stick to it. + * list already and we must stick to it. */ int entry_list_is_constrained(or_options_t *options) @@ -4036,18 +4032,6 @@ entry_list_is_constrained(or_options_t *options) return 0; } -/* Are we dead set against changing our entry guard list, or would we - * change it if it means keeping Tor usable? */ -static int -entry_list_is_totally_static(or_options_t *options) -{ - if (options->EntryNodes && options->StrictNodes) - return 1; - if (options->UseBridges) - return 1; - return 0; -} - /** Pick a live (up and listed) entry guard from entry_guards. If * state is non-NULL, this is for a specific circuit -- * make sure not to pick this circuit's exit or any node in the @@ -4092,6 +4076,7 @@ choose_random_entry(cpath_build_state_t *state) continue; /* don't pick the same node for entry and exit */ if (consider_exit_family && smartlist_isin(exit_family, r)) continue; /* avoid relays that are family members of our exit */ +#if 0 /* since EntryNodes is always strict now, this clause is moot */ if (options->EntryNodes && !routerset_contains_router(options->EntryNodes, r)) { /* We've come to the end of our preferred entry nodes. */ @@ -4106,6 +4091,7 @@ choose_random_entry(cpath_build_state_t *state) "No relays from EntryNodes available. Using others."); } } +#endif smartlist_add(live_entry_guards, r); if (!entry->made_contact) { /* Always start with the first not-yet-contacted entry @@ -4131,7 +4117,7 @@ choose_random_entry(cpath_build_state_t *state) } if (smartlist_len(live_entry_guards) < preferred_min) { - if (!entry_list_is_totally_static(options)) { + if (!entry_list_is_constrained(options)) { /* still no? try adding a new entry then */ /* 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 @@ -4157,9 +4143,9 @@ choose_random_entry(cpath_build_state_t *state) goto retry; } if (!r && entry_list_is_constrained(options) && consider_exit_family) { - /* still no? if we're using bridges or have StrictNodes - * set, and our chosen exit is in the same family as all our - * bridges/entry guards, then be flexible about families. */ + /* still no? if we're using bridges, + * and our chosen exit is in the same family as all our + * bridges, then be flexible about families. */ consider_exit_family = 0; goto retry; } From 0ad3836f73cbb6f0aa8b643c43e799d69d378aea Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 04:35:08 -0500 Subject: [PATCH 07/35] If ExitNodes and Exclude{Exit}Nodes overlap, obey Exclude{Exit}Nodes. Also, ExitNodes are always strict. --- src/or/circuitbuild.c | 56 ++++++++++++++++++++----------------------- src/or/routerlist.c | 4 ++++ src/or/routerlist.h | 2 ++ 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 86406cbea9..b8a82e886e 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2685,13 +2685,29 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, n_supported[i] = -1; continue; /* skip routers that are known to be down or bad exits */ } + + if (options->_ExcludeExitNodesUnion && + routerset_contains_router(options->_ExcludeExitNodesUnion, router)) { + n_supported[i] = -1; + continue; /* user asked us not to use it, no matter what */ + } + if (options->ExitNodes && + !routerset_contains_router(options->ExitNodes, router)) { + n_supported[i] = -1; + continue; /* not one of our chosen exit nodes */ + } + if (router_is_unreliable(router, need_uptime, need_capacity, 0) && - (!options->ExitNodes || - !routerset_contains_router(options->ExitNodes, router))) { + !options->ExitNodes) { /* FFFF Someday, differentiate between a routerset that names * routers, and a routerset that names countries, and only do this * check if they've asked for specific exit relays. Or if the country * they ask for is rare. Or something. */ + /* XXX022-1090 We need to pick a tradeoff here: if we throw it out because + * it's unreliable, users might end up with no exit options even + * though some options are up. If we don't throw it out, users who + * set ExitNodes will have partitioning problems because they'll be + * the only folks willing to use this node. */ n_supported[i] = -1; continue; /* skip routers that are not suitable, unless we have * ExitNodes set, in which case we asked for it */ @@ -2753,21 +2769,13 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, /* If any routers definitely support any pending connections, choose one * at random. */ if (best_support > 0) { - smartlist_t *supporting = smartlist_create(), *use = smartlist_create(); + smartlist_t *supporting = smartlist_create(); for (i = 0; i < smartlist_len(dir->routers); i++) if (n_supported[i] == best_support) smartlist_add(supporting, smartlist_get(dir->routers, i)); - routersets_get_disjunction(use, supporting, options->ExitNodes, - options->_ExcludeExitNodesUnion, 1); - if (smartlist_len(use) == 0 && options->ExitNodes && - !options->StrictNodes) { /* give up on exitnodes and try again */ - routersets_get_disjunction(use, supporting, NULL, - options->_ExcludeExitNodesUnion, 1); - } - router = routerlist_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT); - smartlist_free(use); + router = routerlist_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT); smartlist_free(supporting); } else { /* Either there are no pending connections, or no routers even seem to @@ -2775,7 +2783,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, * at least one predicted exit port. */ int attempt; - smartlist_t *needed_ports, *supporting, *use; + smartlist_t *needed_ports, *supporting; if (best_support == -1) { if (need_uptime || need_capacity) { @@ -2792,7 +2800,6 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, options->_ExcludeExitNodesUnion ? " or are Excluded" : ""); } supporting = smartlist_create(); - use = smartlist_create(); needed_ports = circuit_get_unhandled_ports(time(NULL)); for (attempt = 0; attempt < 2; attempt++) { /* try once to pick only from routers that satisfy a needed port, @@ -2807,25 +2814,13 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, } } - routersets_get_disjunction(use, supporting, options->ExitNodes, - options->_ExcludeExitNodesUnion, 1); - if (smartlist_len(use) == 0 && options->ExitNodes && - !options->StrictNodes) { /* give up on exitnodes and try again */ - routersets_get_disjunction(use, supporting, NULL, - options->_ExcludeExitNodesUnion, 1); - } - /* FFF sometimes the above results in null, when the requested - * exit node is considered down by the consensus. we should pick - * it anyway, since the user asked for it. */ - router = routerlist_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT); + router = routerlist_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT); if (router) break; smartlist_clear(supporting); - smartlist_clear(use); } SMARTLIST_FOREACH(needed_ports, uint16_t *, cp, tor_free(cp)); smartlist_free(needed_ports); - smartlist_free(use); smartlist_free(supporting); } @@ -2834,10 +2829,11 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, log_info(LD_CIRC, "Chose exit server '%s'", router->nickname); return router; } - if (options->ExitNodes && options->StrictNodes) { + if (options->ExitNodes) { log_warn(LD_CIRC, - "No specified exit routers seem to be running, and " - "StrictNodes is set: can't choose an exit."); + "No specified %sexit routers seem to be running: " + "can't choose an exit.", + options->_ExcludeExitNodesUnion ? "non-excluded " : ""); } return NULL; } diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 523596ddda..a9a216b2a1 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -5473,12 +5473,14 @@ routerset_needs_geoip(const routerset_t *set) return set && smartlist_len(set->country_names); } +#if 0 /** Return true iff there are no entries in set. */ static int routerset_is_empty(const routerset_t *set) { return !set || smartlist_len(set->list) == 0; } +#endif /** Helper. Return true iff set contains a router based on the other * provided fields. Return higher values for more specific subentries: a @@ -5594,6 +5596,7 @@ routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, } } +#if 0 /** Add to target every routerinfo_t from source except: * * 1) Don't add it if include is non-empty and the relay isn't in @@ -5624,6 +5627,7 @@ routersets_get_disjunction(smartlist_t *target, } }); } +#endif /** Remove every routerinfo_t from lst that is in routerset. */ void diff --git a/src/or/routerlist.h b/src/or/routerlist.h index cd0eb956b5..3bbdc42eb0 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -175,9 +175,11 @@ int routerset_contains_extendinfo(const routerset_t *set, void routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, const routerset_t *excludeset, int running_only); +#if 0 void routersets_get_disjunction(smartlist_t *target, const smartlist_t *source, const routerset_t *include, const routerset_t *exclude, int running_only); +#endif void routerset_subtract_routers(smartlist_t *out, const routerset_t *routerset); char *routerset_to_string(const routerset_t *routerset); From bcea155ce0999d19b596a24edd359d1fc0dbe380 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 04:36:24 -0500 Subject: [PATCH 08/35] note another case where strictnodes is considered for exits --- src/or/connection_edge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 72e2c8a409..fff42be909 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -2969,6 +2969,8 @@ connection_edge_is_rendezvous_stream(edge_connection_t *conn) * * If excluded_means_no is 1 and Exclude*Nodes is set and excludes * this relay, return 0. + * XXX022-1090 This StrictNodes business needs more work, a la bug 1090. See + * also git commit ef81649d. */ int connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, From 2b5c39211c2259404ab9bc23a1788b6d529e838f Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 05:29:28 -0500 Subject: [PATCH 09/35] refuse moria1.exit if moria1 is excluded add a note reminding us to do this for foo.moria1.exit if we decide to. --- src/or/connection_edge.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index fff42be909..4c7442671f 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1610,6 +1610,10 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, tor_assert(!automap); if (s) { if (s[1] != '\0') { + /* XXX022-1090 we should look this up as a relay and see if it's + * in our excluded set, and refuse it here if so. But first, + * figure out what's up with this 'remapped_to_exit' business + * and whether that needs careful treatment. -RD */ conn->chosen_exit_name = tor_strdup(s+1); if (remapped_to_exit) /* 5 tries before it expires the addressmap */ conn->chosen_exit_retries = TRACKHOSTEXITS_RETRIES; @@ -1627,11 +1631,14 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, conn->chosen_exit_name = tor_strdup(socks->address); r = router_get_by_nickname(conn->chosen_exit_name, 1); *socks->address = 0; - if (r) { + if (r && (!options->_ExcludeExitNodesUnion || + !routerset_contains_router(options->_ExcludeExitNodesUnion, + r))) { strlcpy(socks->address, r->address, sizeof(socks->address)); } else { log_warn(LD_APP, - "Unrecognized server in exit address '%s.exit'. Refusing.", + "%s relay in exit address '%s.exit'. Refusing.", + r ? "Excluded" : "Unrecognized", safe_str_client(socks->address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); return -1; From 9f47cfc21a8fa91b98a48291c316121135fa3f17 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 05:39:26 -0500 Subject: [PATCH 10/35] make formal a constraint that's been true a while now --- src/or/config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/or/config.c b/src/or/config.c index bd904dcf0b..404e648dba 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -3189,6 +3189,12 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Servers must be able to freely connect to the rest " "of the Internet, so they must not set UseBridges."); + /* If both of these are set, we'll end up with funny behavior where we + * demand enough entrynodes be up and running else we won't build + * circuits, yet we never actually use them. */ + if (options->UseBridges && options->EntryNodes) + REJECT("You cannot set both UseBridges and EntryNodes."); + options->_AllowInvalid = 0; if (options->AllowInvalidNodes) { SMARTLIST_FOREACH(options->AllowInvalidNodes, const char *, cp, { From 5710ea64757cf0b36ab6d97409bbb9213116f949 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 11 Mar 2011 06:19:15 -0500 Subject: [PATCH 11/35] three more cases where maybe we want to exclude --- src/or/circuitbuild.c | 1 + src/or/router.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index b8a82e886e..4fa87c018f 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -4570,6 +4570,7 @@ launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) return; /* it's already on the way */ address = tor_dup_addr(&bridge->addr); + /* XXX022-1090 if we ExcludeNodes this bridge, should this step fail? -RD */ directory_initiate_command(address, &bridge->addr, bridge->port, 0, 0, /* does not matter */ diff --git a/src/or/router.c b/src/or/router.c index c15b9b236e..6993e1eb0a 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -857,6 +857,7 @@ consider_testing_reachability(int test_or, int test_dir) log_info(LD_CIRC, "Testing %s of my ORPort: %s:%d.", !orport_reachable ? "reachability" : "bandwidth", me->address, me->or_port); + /* XXX022-1090 If we ExcludeNodes ourself, should this fail? -RD */ circuit_launch_by_router(CIRCUIT_PURPOSE_TESTING, me, CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); } @@ -867,6 +868,7 @@ consider_testing_reachability(int test_or, int test_dir) CONN_TYPE_DIR, &addr, me->dir_port, DIR_PURPOSE_FETCH_SERVERDESC)) { /* ask myself, via tor, for my server descriptor. */ + /* XXX022-1090 If we ExcludeNodes ourself, should this fail? -RD */ directory_initiate_command(me->address, &addr, me->or_port, me->dir_port, 0, /* does not matter */ From affdec8d044513fb87a6dd8931be49974326e8e7 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 25 Mar 2011 15:02:37 -0400 Subject: [PATCH 12/35] Add an XXX022-1090 to note consider_exit_fmily b0rkenness --- src/or/circuitbuild.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 4fa87c018f..b6627a0f8b 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -4142,6 +4142,8 @@ choose_random_entry(cpath_build_state_t *state) /* still no? if we're using bridges, * and our chosen exit is in the same family as all our * bridges, then be flexible about families. */ + /* XXXX022-1090 This is probably not what people want. Better to choose + * a new exit. */ consider_exit_family = 0; goto retry; } From ca74badbe95be77bd990c9c4f9c1b26052d4159e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Mar 2011 14:14:45 -0400 Subject: [PATCH 13/35] If we're excluded, and StrictNodes is set, do not do self-tests. --- src/or/router.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/or/router.c b/src/or/router.c index 6993e1eb0a..64cb079bd0 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -850,14 +850,33 @@ consider_testing_reachability(int test_or, int test_dir) routerinfo_t *me = router_get_my_routerinfo(); int orport_reachable = check_whether_orport_reachable(); tor_addr_t addr; + or_options_t *options = get_options(); if (!me) return; + if (routerset_contains_router(options->ExcludeNodes, me) && + options->StrictNodes) { + /* If we've excluded ourself, and StrictNodes is set, we can't test + * ourself. */ + if (test_or || test_dir) { +#define SELF_EXCLUDED_WARN_INTERVAL 3600 + static ratelim_t warning_limit=RATELIM_INIT(SELF_EXCLUDED_WARN_INTERVAL); + char *msg; + if ((msg = rate_limit_log(&warning_limit, approx_time()))) { + log_warn(LD_CIRC, "Can't peform self-tests for this relay: we have " + "listed ourself in ExcludeNodes, and StrictNodes is set. " + "We will cannot learn whether we are usable, and will not " + "be able to advertise ourself.%s", msg); + tor_free(msg); + } + } + return; + } + if (test_or && (!orport_reachable || !circuit_enough_testing_circs())) { log_info(LD_CIRC, "Testing %s of my ORPort: %s:%d.", !orport_reachable ? "reachability" : "bandwidth", me->address, me->or_port); - /* XXX022-1090 If we ExcludeNodes ourself, should this fail? -RD */ circuit_launch_by_router(CIRCUIT_PURPOSE_TESTING, me, CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); } @@ -868,7 +887,6 @@ consider_testing_reachability(int test_or, int test_dir) CONN_TYPE_DIR, &addr, me->dir_port, DIR_PURPOSE_FETCH_SERVERDESC)) { /* ask myself, via tor, for my server descriptor. */ - /* XXX022-1090 If we ExcludeNodes ourself, should this fail? -RD */ directory_initiate_command(me->address, &addr, me->or_port, me->dir_port, 0, /* does not matter */ From db2fd28308fb019e59280219cf95f09f5208092f Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Mar 2011 16:44:40 -0400 Subject: [PATCH 14/35] Note that circuit purpose changing can violate ExcludeNodes --- src/or/rendservice.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 88f1ba3ddd..5b85394ddc 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -1352,6 +1352,9 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) log_info(LD_CIRC|LD_REND, "We have just finished an introduction " "circuit, but we already have enough. Redefining purpose to " "general."); + /* XXX022-1090: This can wind up violating ExcludeNodes/ + * ExitNodes/ExcludeExitNodes restrictions. + */ TO_CIRCUIT(circuit)->purpose = CIRCUIT_PURPOSE_C_GENERAL; circuit_has_opened(circuit); return; From e4689d840266088739eee39e9bef84e13c988ce9 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Mar 2011 16:51:00 -0400 Subject: [PATCH 15/35] Note a slightly less likely way to violate ExcludeNodes --- src/or/circuitlist.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index d11b457944..42073fb96e 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -933,6 +933,11 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, "capacity %d, internal %d", purpose, need_uptime, need_capacity, internal); + /* XXX022-1090 We should make sure that when we cannibalize a circuit, it + * contains no excluded nodes. (This is possible if StrictNodes is 0, and + * we thought we needed to use an excluded exit node for, say, a directory + * operation.) -NM */ + for (_circ=global_circuitlist; _circ; _circ = _circ->next) { if (CIRCUIT_IS_ORIGIN(_circ) && _circ->state == CIRCUIT_STATE_OPEN && From 4851de554d5fc473cc9418b15bfb752e45b7d81d Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Mar 2011 17:29:59 -0400 Subject: [PATCH 16/35] Do not automatically ignore Fast/Stable for exits when ExitNodes is set This once maybe made sense when ExitNodes meant "Here are 3 exits; use them all", but now it more typically means "Here are 3 countries; exit from there." Using non-Fast/Stable exits created a potential partitioning opportunity and an annoying stability problem. (Don't worry about the case where all of our ExitNodes are non-Fast or non-Stable: we handle that later in the function by retrying with need_capacity and need_uptime set to 0.) --- changes/exitnodes_reliable | 7 +++++++ src/or/circuitbuild.c | 18 +++++------------- 2 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 changes/exitnodes_reliable diff --git a/changes/exitnodes_reliable b/changes/exitnodes_reliable new file mode 100644 index 0000000000..62ef03a0ce --- /dev/null +++ b/changes/exitnodes_reliable @@ -0,0 +1,7 @@ + o Minor features: + - If ExitNodes is set, still pay attention to the Fast/Stable + status of exits when picking exit nodes. (We used to ignore + these flags when ExitNodes was set, on the grounds that people + who set exitnodes wanted all of those nodes to get used, but + with the ability to pick exits by country and IP range, this + doesn't necessarily make sense any more.) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index b6627a0f8b..714d6365c6 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2697,20 +2697,12 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, continue; /* not one of our chosen exit nodes */ } - if (router_is_unreliable(router, need_uptime, need_capacity, 0) && - !options->ExitNodes) { - /* FFFF Someday, differentiate between a routerset that names - * routers, and a routerset that names countries, and only do this - * check if they've asked for specific exit relays. Or if the country - * they ask for is rare. Or something. */ - /* XXX022-1090 We need to pick a tradeoff here: if we throw it out because - * it's unreliable, users might end up with no exit options even - * though some options are up. If we don't throw it out, users who - * set ExitNodes will have partitioning problems because they'll be - * the only folks willing to use this node. */ + if (router_is_unreliable(router, need_uptime, need_capacity, 0)) { n_supported[i] = -1; - continue; /* skip routers that are not suitable, unless we have - * ExitNodes set, in which case we asked for it */ + continue; /* skip routers that are not suitable. Don't worry if + * this makes us reject all the possible routers: if so, + * we'll retry later in this function with need_update and + * need_capacity set to 0. */ } if (!(router->is_valid || options->_AllowInvalid & ALLOW_INVALID_EXIT)) { /* if it's invalid and we don't want it */ From ed7c267743f2471a5a16c5ec437efda665f4c6af Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 3 Apr 2011 17:08:29 -0400 Subject: [PATCH 17/35] Note another place that we need to fix a 1090 issue. --- src/or/connection_edge.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 4c7442671f..1f5eb703ae 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -789,6 +789,9 @@ addressmap_ent_remove(const char *address, addressmap_entry_t *ent) static void clear_trackexithost_mappings(const char *exitname) { + /* XXXX022-1090 We need a variant of this that clears all mappings no longer + permitted because of changes to the ExcludeNodes, ExitNodes, or + ExcludeExitNodes settings. */ char *suffix; size_t suffix_len; if (!addressmap || !exitname) From ad78bafb71fbd66e83b29aba612d17f7d03e575b Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 3 Apr 2011 17:08:59 -0400 Subject: [PATCH 18/35] Correct the behavior of .exit with ExcludeNodes, StrictNodes, etc. ExcludeExitNodes foo now means that foo.exit doesn't work. If StrictNodes is set, then ExcludeNodes foo also overrides foo.exit. foo.exit , however, still works even if foo is not listed in ExitNodes. --- src/or/connection_edge.c | 56 +++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 1f5eb703ae..f435976a45 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1497,9 +1497,13 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, hostname_type_t addresstype; or_options_t *options = get_options(); struct in_addr addr_tmp; + /* We set this to true if this is an address we should automatically + * remap to a local address in VirtualAddrNetwork */ int automap = 0; char orig_address[MAX_SOCKS_ADDR_LEN]; time_t map_expires = TIME_MAX; + /* This will be set to true iff the address starts out as a non-.exit + address, and we remap it to one because of an entry in the addressmap. */ int remapped_to_exit = 0; time_t now = time(NULL); @@ -1610,18 +1614,24 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, /* foo.exit -- modify conn->chosen_exit_node to specify the exit * node, and conn->address to hold only the address portion. */ char *s = strrchr(socks->address,'.'); + + /* If StrictNodes is not set, then .exit overrides ExcludeNodes. */ + routerset_t *excludeset = options->StrictNodes ? + options->_ExcludeExitNodesUnion : options->ExcludeExitNodes; + /*XXX023 make this a node_t. */ + routerinfo_t *router; + tor_assert(!automap); if (s) { + /* The address was of the form "(stuff).(name).exit */ if (s[1] != '\0') { - /* XXX022-1090 we should look this up as a relay and see if it's - * in our excluded set, and refuse it here if so. But first, - * figure out what's up with this 'remapped_to_exit' business - * and whether that needs careful treatment. -RD */ conn->chosen_exit_name = tor_strdup(s+1); + router = router_get_by_nickname(conn->chosen_exit_name, 1); if (remapped_to_exit) /* 5 tries before it expires the addressmap */ conn->chosen_exit_retries = TRACKHOSTEXITS_RETRIES; *s = 0; } else { + /* Oops, the address was (stuff)..exit. That's not okay. */ log_warn(LD_APP,"Malformed exit address '%s.exit'. Refusing.", safe_str_client(socks->address)); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", @@ -1630,23 +1640,33 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, return -1; } } else { - routerinfo_t *r; + /* It looks like they just asked for "foo.exit". */ conn->chosen_exit_name = tor_strdup(socks->address); - r = router_get_by_nickname(conn->chosen_exit_name, 1); - *socks->address = 0; - if (r && (!options->_ExcludeExitNodesUnion || - !routerset_contains_router(options->_ExcludeExitNodesUnion, - r))) { - strlcpy(socks->address, r->address, sizeof(socks->address)); - } else { - log_warn(LD_APP, - "%s relay in exit address '%s.exit'. Refusing.", - r ? "Excluded" : "Unrecognized", - safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; + router = router_get_by_nickname(conn->chosen_exit_name, 1); + if (router) { + *socks->address = 0; + strlcpy(socks->address, router->address, sizeof(socks->address)); } } + /* Now make sure that the chosen exit exists... */ + if (!router) { + log_warn(LD_APP, + "Unrecognized relay in exit address '%s.exit'. Refusing.", + safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + } + /* ...and make sure that it isn't excluded. */ + if (routerset_contains_router(excludeset, router)) { + log_warn(LD_APP, + "Excluded relay in exit address '%s.exit'. Refusing.", + safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + } + /* XXXX022-1090 Should we also allow foo.bar.exit if ExitNodes is set and + Bar is not listed in it? I say yes, but our revised manpage branch + implies no. */ } if (addresstype != ONION_HOSTNAME) { From b59a289365169cca228566826f19838403320cb2 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 3 Apr 2011 18:12:26 -0400 Subject: [PATCH 19/35] Do not try to download descriptors for bridges in ExcludeNodes. --- src/or/circuitbuild.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 714d6365c6..4832cae69a 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -4539,6 +4539,24 @@ bridge_add_from_config(const tor_addr_t *addr, uint16_t port, char *digest) smartlist_add(bridge_list, b); } +/** Return true iff routerset contains the bridge bridge. */ +static int +routerset_contains_bridge(const routerset_t *routerset, + const bridge_info_t *bridge) +{ + int result; + extend_info_t *extinfo; + tor_assert(bridge); + if (!routerset) + return 0; + + extinfo = extend_info_alloc( + NULL, bridge->identity, NULL, &bridge->addr, bridge->port); + result = routerset_contains_extendinfo(routerset, extinfo); + extend_info_free(extinfo); + return result; +} + /** If digest is one of our known bridges, return it. */ static bridge_info_t * find_bridge_by_digest(const char *digest) @@ -4557,6 +4575,7 @@ static void launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) { char *address; + or_options_t *options = get_options(); if (connection_get_by_type_addr_port_purpose( CONN_TYPE_DIR, &bridge->addr, bridge->port, @@ -4564,7 +4583,13 @@ launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) return; /* it's already on the way */ address = tor_dup_addr(&bridge->addr); - /* XXX022-1090 if we ExcludeNodes this bridge, should this step fail? -RD */ + if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { + download_status_mark_impossible(&bridge->fetch_status); + log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", + safe_str_client(fmt_addr(&bridge->addr))); + return; + } + directory_initiate_command(address, &bridge->addr, bridge->port, 0, 0, /* does not matter */ @@ -4605,6 +4630,12 @@ fetch_bridge_descriptors(or_options_t *options, time_t now) if (!download_status_is_ready(&bridge->fetch_status, now, IMPOSSIBLE_TO_DOWNLOAD)) continue; /* don't bother, no need to retry yet */ + if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { + download_status_mark_impossible(&bridge->fetch_status); + log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", + safe_str_client(fmt_addr(&bridge->addr))); + continue; + } /* schedule another fetch as if this one will fail, in case it does */ download_status_failed(&bridge->fetch_status, 0); From 84f0e87c6a6629d047a39f658b7f7c96767219ca Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 3 Apr 2011 18:20:19 -0400 Subject: [PATCH 20/35] If we have chosen an exit that shares a family with all bridges, fail the circuit We could probably do something smarter here, but the situation is unusual enough that it's okay to just fail the circuit. --- src/or/circuitbuild.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 4832cae69a..c58509f909 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -4130,15 +4130,18 @@ choose_random_entry(cpath_build_state_t *state) need_capacity = 0; goto retry; } +#if 0 + /* Removing this retry logic: if we only allow one exit, and it is in the + same family as all our entries, then we are just plain not going to win + here. */ if (!r && entry_list_is_constrained(options) && consider_exit_family) { /* still no? if we're using bridges, * and our chosen exit is in the same family as all our * bridges, then be flexible about families. */ - /* XXXX022-1090 This is probably not what people want. Better to choose - * a new exit. */ consider_exit_family = 0; goto retry; } +#endif /* live_entry_guards may be empty below. Oh well, we tried. */ } From 128582cc1f9fd363f3fb2a96b61fde1701a56970 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 3 Apr 2011 19:13:36 -0400 Subject: [PATCH 21/35] Simplify calls to routerset_equal The routerset_equal function explicitly handles NULL inputs, so there's no need to check inputs for NULL before calling it. Also fix a bug in routerset_equal where a non-NULL routerset with no entries didn't get counted as equal to a NULL routerset. This was untriggerable, I think, but potentially annoying down the road. --- src/or/config.c | 14 +++++--------- src/or/routerlist.c | 11 +++++++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index 404e648dba..44cde85dc8 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1260,15 +1260,11 @@ options_act(or_options_t *old_options) /* Check for transitions that need action. */ if (old_options) { if ((options->UseEntryGuards && !old_options->UseEntryGuards) || - (options->ExcludeNodes && - !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes)) || - (options->ExcludeExitNodes && - !routerset_equal(old_options->ExcludeExitNodes, - options->ExcludeExitNodes)) || - (options->EntryNodes && - !routerset_equal(old_options->EntryNodes, options->EntryNodes)) || - (options->ExitNodes && - !routerset_equal(old_options->ExitNodes, options->ExitNodes)) || + !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes) || + !routerset_equal(old_options->ExcludeExitNodes, + options->ExcludeExitNodes) || + !routerset_equal(old_options->EntryNodes, options->EntryNodes) || + !routerset_equal(old_options->ExitNodes, options->ExitNodes) || options->StrictNodes != old_options->StrictNodes) { log_info(LD_CIRC, "Changed to using entry guards, or changed preferred or " diff --git a/src/or/routerlist.c b/src/or/routerlist.c index a9a216b2a1..d5e8a6b051 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -5473,14 +5473,12 @@ routerset_needs_geoip(const routerset_t *set) return set && smartlist_len(set->country_names); } -#if 0 /** Return true iff there are no entries in set. */ static int routerset_is_empty(const routerset_t *set) { return !set || smartlist_len(set->list) == 0; } -#endif /** Helper. Return true iff set contains a router based on the other * provided fields. Return higher values for more specific subentries: a @@ -5659,10 +5657,15 @@ routerset_to_string(const routerset_t *set) int routerset_equal(const routerset_t *old, const routerset_t *new) { - if (old == NULL && new == NULL) + if (routerset_is_empty(old) && routerset_is_empty(new)) { + /* Two empty sets are equal */ return 1; - else if (old == NULL || new == NULL) + } else if (routerset_is_empty(old) || routerset_is_empty(new)) { + /* An empty set is equal to nothing else. */ return 0; + } + tor_assert(old != NULL); + tor_assert(new != NULL); if (smartlist_len(old->list) != smartlist_len(new->list)) return 0; From 80adb3de507db4cd67208396e1ea301f131c1228 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 3 Apr 2011 19:43:47 -0400 Subject: [PATCH 22/35] When there is a transition in permitted nodes, apply it to trackexithosts map IOW, if we were using TrackExitHosts, and we added an excluded node or removed a node from exitnodes, we wouldn't actually remove the mapping that points us at the new node. Also, note with an XXX022 comment a place that I think we are looking at the wrong string. --- src/or/config.c | 1 + src/or/connection_edge.c | 54 +++++++++++++++++++++++++++++++++++++--- src/or/connection_edge.h | 1 + src/or/routerlist.c | 2 +- src/or/routerlist.h | 1 + 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index 44cde85dc8..f003e4d296 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1271,6 +1271,7 @@ options_act(or_options_t *old_options) "excluded node lists. Abandoning previous circuits."); circuit_mark_all_unused_circs(); circuit_expire_all_dirty_circs(); + addressmap_clear_excluded_trackexithosts(options); } /* How long should we delay counting bridge stats after becoming a bridge? diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index f435976a45..5f322cae20 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -789,9 +789,6 @@ addressmap_ent_remove(const char *address, addressmap_entry_t *ent) static void clear_trackexithost_mappings(const char *exitname) { - /* XXXX022-1090 We need a variant of this that clears all mappings no longer - permitted because of changes to the ExcludeNodes, ExitNodes, or - ExcludeExitNodes settings. */ char *suffix; size_t suffix_len; if (!addressmap || !exitname) @@ -802,6 +799,7 @@ clear_trackexithost_mappings(const char *exitname) tor_strlower(suffix); STRMAP_FOREACH_MODIFY(addressmap, address, addressmap_entry_t *, ent) { + /* XXXX022 HEY! Shouldn't this look at ent->new_address? */ if (ent->source == ADDRMAPSRC_TRACKEXIT && !strcmpend(address, suffix)) { addressmap_ent_remove(address, ent); MAP_DEL_CURRENT(address); @@ -811,6 +809,56 @@ clear_trackexithost_mappings(const char *exitname) tor_free(suffix); } +/** Remove all TRACKEXIT mappings from the addressmap for which the target + * host is unknown or no longer allowed. */ +void +addressmap_clear_excluded_trackexithosts(or_options_t *options) +{ + const routerset_t *allow_nodes = options->ExitNodes; + const routerset_t *exclude_nodes = options->_ExcludeExitNodesUnion; + + if (!addressmap) + return; + if (routerset_is_empty(allow_nodes)) + allow_nodes = NULL; + if (allow_nodes == NULL && routerset_is_empty(exclude_nodes)) + return; + + STRMAP_FOREACH_MODIFY(addressmap, address, addressmap_entry_t *, ent) { + size_t len; + const char *target = ent->new_address, *dot; + char *nodename; + routerinfo_t *ri; /* XXX023 Use node_t. */ + + if (strcmpend(target, ".exit")) { + /* Not a .exit mapping */ + continue; + } else if (ent->source != ADDRMAPSRC_TRACKEXIT) { + /* Not a trackexit mapping. */ + continue; + } + len = strlen(target); + if (len < 6) + continue; /* malformed. */ + dot = target + len - 6; /* dot now points to just before .exit */ + dot = strrchr(dot, '.'); /* dot now points to the . before .exit, or NULL */ + if (!dot) { + nodename = tor_strndup(target, len-5); + } else { + nodename = tor_strndup(dot+1, strlen(dot+1)-5); + } + ri = router_get_by_nickname(nodename, 0); + tor_free(nodename); + if (!ri || + (allow_nodes && !routerset_contains_router(allow_nodes, ri)) || + routerset_contains_router(exclude_nodes, ri)) { + /* We don't know this one, or we want to be rid of it. */ + addressmap_ent_remove(address, ent); + MAP_DEL_CURRENT(address); + } + } STRMAP_FOREACH_END; +} + /** Remove all entries from the addressmap that were set via the * configuration file or the command line. */ void diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 0b08dd07ca..c4ae46751d 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -62,6 +62,7 @@ int connection_ap_process_transparent(edge_connection_t *conn); int address_is_invalid_destination(const char *address, int client); void addressmap_init(void); +void addressmap_clear_excluded_trackexithosts(or_options_t *options); void addressmap_clean(time_t now); void addressmap_clear_configured(void); void addressmap_clear_transient(void); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index d5e8a6b051..d9f099b4f8 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -5474,7 +5474,7 @@ routerset_needs_geoip(const routerset_t *set) } /** Return true iff there are no entries in set. */ -static int +int routerset_is_empty(const routerset_t *set) { return !set || smartlist_len(set->list) == 0; diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 3bbdc42eb0..fec18705b3 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -167,6 +167,7 @@ int routerset_parse(routerset_t *target, const char *s, void routerset_union(routerset_t *target, const routerset_t *source); int routerset_is_list(const routerset_t *set); int routerset_needs_geoip(const routerset_t *set); +int routerset_is_empty(const routerset_t *set); int routerset_contains_router(const routerset_t *set, routerinfo_t *ri); int routerset_contains_routerstatus(const routerset_t *set, routerstatus_t *rs); From 6afad6b691d577fba2fe88f2fe9ed76a2f80002d Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 3 Apr 2011 19:58:28 -0400 Subject: [PATCH 23/35] When cannibalizing a circuit, make sure it has no ExcludeNodes on it This could happen if StrictNodes was 0 and we were forced to pick an excluded node as the last hop of the circuit. --- src/or/circuitlist.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 42073fb96e..ce324cad48 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -923,6 +923,7 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int need_uptime = (flags & CIRCLAUNCH_NEED_UPTIME) != 0; int need_capacity = (flags & CIRCLAUNCH_NEED_CAPACITY) != 0; int internal = (flags & CIRCLAUNCH_IS_INTERNAL) != 0; + or_options_t *options = get_options(); /* Make sure we're not trying to create a onehop circ by * cannibalization. */ @@ -933,11 +934,6 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, "capacity %d, internal %d", purpose, need_uptime, need_capacity, internal); - /* XXX022-1090 We should make sure that when we cannibalize a circuit, it - * contains no excluded nodes. (This is possible if StrictNodes is 0, and - * we thought we needed to use an excluded exit node for, say, a directory - * operation.) -NM */ - for (_circ=global_circuitlist; _circ; _circ = _circ->next) { if (CIRCUIT_IS_ORIGIN(_circ) && _circ->state == CIRCUIT_STATE_OPEN && @@ -966,6 +962,19 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, hop=hop->next; } while (hop!=circ->cpath); } + if (options->ExcludeNodes) { + /* Make sure no existing nodes in the circuit are excluded for + * general use. (This may be possible if StrictNodes is 0, and we + * thought we needed to use an otherwise excluded node for, say, a + * directory operation.) */ + crypt_path_t *hop = circ->cpath; + do { + if (routerset_contains_extendinfo(options->ExcludeNodes, + hop->extend_info)) + goto next; + hop = hop->next; + } while (hop != circ->cpath); + } if (!best || (best->build_state->need_uptime && !need_uptime)) best = circ; next: ; From 79a3b3cd3719b3b87b0edbab62b256e42c7b42de Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 3 Apr 2011 20:06:31 -0400 Subject: [PATCH 24/35] Check transition of circuit purpose from INTRO->GENERAL if nodes are constrained This looked at first like another fun way around our node selection logic: if we had introduction circuits, and we wound up building too many, we would turn extras into general-purpose circuits. But when we did so, we wouldn't necessarily check whether the general-purpose circuits conformed to our node constraints. For example, the last node could totally be in ExcludedExitNodes and we wouldn't have cared... ...except that the circuit should already be internal, so it won't get user streams attached to it, so the transition should generally be allowed. Add an assert to make sure we're right about this, and have it not check whether ExitNodes is set, since that's irrelevant to internal circuits. --- src/or/rendservice.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 5b85394ddc..cd8f9eabeb 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -1347,17 +1347,26 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) } /* If we already have enough introduction circuits for this service, - * redefine this one as a general circuit. */ + * redefine this one as a general circuit or close it, depending. */ if (count_established_intro_points(serviceid) > NUM_INTRO_POINTS) { - log_info(LD_CIRC|LD_REND, "We have just finished an introduction " - "circuit, but we already have enough. Redefining purpose to " - "general."); - /* XXX022-1090: This can wind up violating ExcludeNodes/ - * ExitNodes/ExcludeExitNodes restrictions. - */ - TO_CIRCUIT(circuit)->purpose = CIRCUIT_PURPOSE_C_GENERAL; - circuit_has_opened(circuit); - return; + or_options_t *options = get_options(); + if (options->ExcludeNodes) { + /* XXXX in some future version, we can test whether the transition is + allowed or not given the actual nodes in the circuit. But for now, + this case, we might as well close the thing. */ + log_info(LD_CIRC|LD_REND, "We have just finished an introduction " + "circuit, but we already have enough. Closing it."); + circuit_mark_for_close(TO_CIRCUIT(circuit), END_CIRC_REASON_NONE); + return; + } else { + tor_assert(circuit->build_state->is_internal); + log_info(LD_CIRC|LD_REND, "We have just finished an introduction " + "circuit, but we already have enough. Redefining purpose to " + "general; leaving as internal."); + TO_CIRCUIT(circuit)->purpose = CIRCUIT_PURPOSE_C_GENERAL; + circuit_has_opened(circuit); + return; + } } log_info(LD_REND, From 8e2904e269e039713c2b2e7be10b82e11f239cd0 Mon Sep 17 00:00:00 2001 From: Sebastian Hahn Date: Fri, 8 Apr 2011 04:03:50 +0200 Subject: [PATCH 25/35] Fix a log msg --- src/or/router.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/or/router.c b/src/or/router.c index 64cb079bd0..0ef4728a02 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -865,7 +865,7 @@ consider_testing_reachability(int test_or, int test_dir) if ((msg = rate_limit_log(&warning_limit, approx_time()))) { log_warn(LD_CIRC, "Can't peform self-tests for this relay: we have " "listed ourself in ExcludeNodes, and StrictNodes is set. " - "We will cannot learn whether we are usable, and will not " + "We cannot learn whether we are usable, and will not " "be able to advertise ourself.%s", msg); tor_free(msg); } From 92ec36a061de17dd4e378d5ce34d82f91eee9f86 Mon Sep 17 00:00:00 2001 From: Sebastian Hahn Date: Fri, 8 Apr 2011 04:01:23 +0200 Subject: [PATCH 26/35] Explain the "using anyway" log message better Also add a request to report any cases where we are not honoring StrictNodes to the developers: These should now all be bugs. --- src/or/circuitbuild.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index c58509f909..681f52402d 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2896,7 +2896,7 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) case CIRCUIT_PURPOSE_C_GENERAL: if (circ->build_state->is_internal) return; - description = "Requested exit node"; + description = "requested exit node"; rs = options->_ExcludeExitNodesUnion; break; case CIRCUIT_PURPOSE_C_INTRODUCING: @@ -2911,22 +2911,28 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) case CIRCUIT_PURPOSE_C_REND_READY: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_REND_JOINED: - description = "Chosen rendezvous point"; + description = "chosen rendezvous point"; domain = LD_BUG; break; case CIRCUIT_PURPOSE_CONTROLLER: rs = options->_ExcludeExitNodesUnion; - description = "Controller-selected circuit target"; + description = "controller-selected circuit target"; break; } if (routerset_contains_extendinfo(rs, exit)) { - log_fn(LOG_WARN, domain, "%s '%s' is in ExcludeNodes%s. Using anyway " - "(circuit purpose %d).", + /* We should never get here if StrictNodes is set to 1. */ + if (options->StrictNodes) + log_warn(LD_BUG, "Using an excluded node with StrictNodes set. " + "Please report the following log message to the " + "developers."); + log_fn(LOG_WARN, domain, "Using %s '%s' which is listed in " + "ExcludeNodes%s, because no other options were available. To " + "prevent this, set the StrictNodes configuration option." + "(Circuit purpose is %d)", description,exit->nickname, rs==options->ExcludeNodes?"":" or ExcludeExitNodes", (int)purpose); - /* XXX022-1090 "using anyway" is freaking people out -RD */ circuit_log_path(LOG_WARN, domain, circ); } From e03e90bf591f649f7883807f99354634e837ee48 Mon Sep 17 00:00:00 2001 From: Sebastian Hahn Date: Fri, 8 Apr 2011 04:16:26 +0200 Subject: [PATCH 27/35] Fix a check-spaces complaint --- src/or/connection_edge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 5f322cae20..1ee57abe4d 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -841,7 +841,7 @@ addressmap_clear_excluded_trackexithosts(or_options_t *options) if (len < 6) continue; /* malformed. */ dot = target + len - 6; /* dot now points to just before .exit */ - dot = strrchr(dot, '.'); /* dot now points to the . before .exit, or NULL */ + dot = strrchr(dot, '.'); /* dot now points to the . before .exit or NULL */ if (!dot) { nodename = tor_strndup(target, len-5); } else { From 8ee92f28e056fd32f1faef62ae1523ad4d553a64 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 8 Apr 2011 13:27:25 -0400 Subject: [PATCH 28/35] Add a circuit_purpose_to_string() function, and use it We had a circuit_purpose_to_controller_string() function, but it was pretty coarse-grained and didn't try to be human-readable. --- src/or/circuitbuild.c | 10 ++++---- src/or/circuitlist.c | 56 +++++++++++++++++++++++++++++++++++++++++++ src/or/circuitlist.h | 1 + src/or/circuituse.c | 22 +++++++++-------- 4 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 681f52402d..b963f1a777 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2051,8 +2051,9 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) */ if (timediff < 0 || timediff > 2*circ_times.close_ms+1000) { log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. " - "Assuming clock jump. Purpose %d", timediff, - circ->_base.purpose); + "Assuming clock jump. Purpose %d (%s)", timediff, + circ->_base.purpose, + circuit_purpose_to_string(circ->_base.purpose)); } else if (!circuit_build_times_disabled()) { /* Only count circuit times if the network is live */ if (circuit_build_times_network_check_live(&circ_times)) { @@ -2890,8 +2891,9 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) case CIRCUIT_PURPOSE_INTRO_POINT: case CIRCUIT_PURPOSE_REND_POINT_WAITING: case CIRCUIT_PURPOSE_REND_ESTABLISHED: - log_warn(LD_BUG, "Called on non-origin circuit (purpose %d)", - (int)purpose); + log_warn(LD_BUG, "Called on non-origin circuit (purpose %d, %s)", + (int)purpose, + circuit_purpose_to_string(purpose)); return; case CIRCUIT_PURPOSE_C_GENERAL: if (circ->build_state->is_internal) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index ce324cad48..33dc8f09ad 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -378,6 +378,62 @@ circuit_purpose_to_controller_string(uint8_t purpose) } } +/** Return a human-readable string for the circuit purpose purpose. */ +const char * +circuit_purpose_to_string(uint8_t purpose) +{ + static char buf[32]; + + switch (purpose) + { + case CIRCUIT_PURPOSE_OR: + return "Circuit at relay"; + case CIRCUIT_PURPOSE_INTRO_POINT: + return "Acting as intro point"; + case CIRCUIT_PURPOSE_REND_POINT_WAITING: + return "Acting as rendevous (pending)"; + case CIRCUIT_PURPOSE_REND_ESTABLISHED: + return "Acting as rendevous (established)"; + case CIRCUIT_PURPOSE_C_GENERAL: + return "General-purpose client"; + case CIRCUIT_PURPOSE_C_INTRODUCING: + return "Hidden service client: Connecting to intro point"; + case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: + return "Hidden service client: Waiting for ack from intro point"; + case CIRCUIT_PURPOSE_C_INTRODUCE_ACKED: + return "Hidden service client: Received ack from intro point"; + case CIRCUIT_PURPOSE_C_ESTABLISH_REND: + return "Hidden service client: Establishing rendezvous point"; + case CIRCUIT_PURPOSE_C_REND_READY: + return "Hidden service client: Pending rendezvous point"; + case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: + return "Hidden service client: Pending rendezvous point (ack received)"; + case CIRCUIT_PURPOSE_C_REND_JOINED: + return "Hidden service client: Active rendezvous point"; + case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT: + return "Measuring circuit timeout"; + + case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: + return "Hidden service: Establishing introduction point"; + case CIRCUIT_PURPOSE_S_INTRO: + return "Hidden service: Introduction point"; + case CIRCUIT_PURPOSE_S_CONNECT_REND: + return "Hidden service: Connecting to rendezvous point"; + case CIRCUIT_PURPOSE_S_REND_JOINED: + return "Hidden service: Active rendezvous point"; + + case CIRCUIT_PURPOSE_TESTING: + return "Testing circuit"; + + case CIRCUIT_PURPOSE_CONTROLLER: + return "Circuit made by controller"; + + default: + tor_snprintf(buf, sizeof(buf), "UNKNOWN_%d", (int)purpose); + return buf; + } +} + /** Pick a reasonable package_window to start out for our circuits. * Originally this was hard-coded at 1000, but now the consensus votes * on the answer. See proposal 168. */ diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index ef6fc3a3d9..7b01ca3ae2 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -15,6 +15,7 @@ circuit_t * _circuit_get_global_list(void); const char *circuit_state_to_string(int state); const char *circuit_purpose_to_controller_string(uint8_t purpose); +const char *circuit_purpose_to_string(uint8_t purpose); void circuit_dump_by_conn(connection_t *conn, int severity); void circuit_set_p_circid_orconn(or_circuit_t *circ, circid_t id, or_connection_t *conn); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 7b20f7f173..530941cd76 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -392,10 +392,11 @@ circuit_expire_building(void) TO_ORIGIN_CIRCUIT(victim)->cpath->state == CPATH_STATE_OPEN; if (TO_ORIGIN_CIRCUIT(victim)->p_streams != NULL) { - log_warn(LD_BUG, "Circuit %d (purpose %d) has timed out, " + log_warn(LD_BUG, "Circuit %d (purpose %d, %s) has timed out, " "yet has attached streams!", TO_ORIGIN_CIRCUIT(victim)->global_identifier, - victim->purpose); + victim->purpose, + circuit_purpose_to_string(victim->purpose)); tor_fragile_assert(); continue; } @@ -426,9 +427,10 @@ circuit_expire_building(void) if (timercmp(&victim->timestamp_created, &extremely_old_cutoff, <)) { log_notice(LD_CIRC, "Extremely large value for circuit build timeout: %lds. " - "Assuming clock jump. Purpose %d", + "Assuming clock jump. Purpose %d (%s)", (long)(now.tv_sec - victim->timestamp_created.tv_sec), - victim->purpose); + victim->purpose, + circuit_purpose_to_string(victim->purpose)); } else if (circuit_build_times_count_close(&circ_times, first_hop_succeeded, victim->timestamp_created.tv_sec)) { @@ -794,12 +796,11 @@ circuit_expire_old_circuits_clientside(void) circ->purpose != CIRCUIT_PURPOSE_S_INTRO) { log_notice(LD_CIRC, "Ancient non-dirty circuit %d is still around after " - "%ld milliseconds. Purpose: %d", + "%ld milliseconds. Purpose: %d (%s)", TO_ORIGIN_CIRCUIT(circ)->global_identifier, tv_mdiff(&circ->timestamp_created, &now), - circ->purpose); - /* FFFF implement a new circuit_purpose_to_string() so we don't - * just print out a number for circ->purpose */ + circ->purpose, + circuit_purpose_to_string(circ->purpose)); TO_ORIGIN_CIRCUIT(circ)->is_ancient = 1; } } @@ -1136,8 +1137,9 @@ circuit_launch_by_extend_info(uint8_t purpose, * internal circs rather than exit circs? -RD */ circ = circuit_find_to_cannibalize(purpose, extend_info, flags); if (circ) { - log_info(LD_CIRC,"Cannibalizing circ '%s' for purpose %d", - build_state_get_exit_nickname(circ->build_state), purpose); + log_info(LD_CIRC,"Cannibalizing circ '%s' for purpose %d (%s)", + build_state_get_exit_nickname(circ->build_state), purpose, + circuit_purpose_to_string(purpose)); circ->_base.purpose = purpose; /* reset the birth date of this circ, else expire_building * will see it and think it's been trying to build since it From f962dda8c188ebe17be1ad7d6548f303b6425943 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Tue, 26 Apr 2011 19:55:34 -0400 Subject: [PATCH 29/35] revert most of ef81649d2fc Now we believe it to be the case that we never build a circuit for our stream that has an unsuitable exit, so we'll never need to use such a circuit. The risk is that we have some code that builds the circuit, but now we refuse to use it, meaning we just build a bazillion circuits and ignore them all. --- src/or/circuitbuild.c | 2 +- src/or/circuituse.c | 18 ++++++++++++------ src/or/connection_edge.c | 19 ++----------------- src/or/connection_edge.h | 3 +-- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index b963f1a777..6401e71dae 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2728,7 +2728,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, { if (!ap_stream_wants_exit_attention(conn)) continue; /* Skip everything but APs in CIRCUIT_WAIT */ - if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), router, 1)) { + if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), router)) { ++n_supported[i]; // log_fn(LOG_DEBUG,"%s is supported. n_supported[%d] now %d.", // router->nickname, i, n_supported[i]); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 530941cd76..cc4a739a90 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -127,7 +127,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, return 0; } } - if (exitrouter && !connection_ap_can_use_exit(conn, exitrouter, 0)) { + if (exitrouter && !connection_ap_can_use_exit(conn, exitrouter)) { /* can't exit from this router */ return 0; } @@ -166,6 +166,10 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose) return 1; if (CIRCUIT_IS_ORIGIN(b) && TO_ORIGIN_CIRCUIT(b)->build_state->is_internal) + /* XXX023 what the heck is this internal thing doing here. I + * think we can get rid of it. circuit_is_acceptable() already + * makes sure that is_internal is exactly what we need it to + * be. -RD */ return 1; } break; @@ -511,7 +515,7 @@ circuit_stream_is_being_handled(edge_connection_t *conn, if (exitrouter && (!need_uptime || build_state->need_uptime)) { int ok; if (conn) { - ok = connection_ap_can_use_exit(conn, exitrouter, 0); + ok = connection_ap_can_use_exit(conn, exitrouter); } else { addr_policy_result_t r = compare_addr_to_addr_policy( 0, port, exitrouter->exit_policy); @@ -1291,9 +1295,10 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, * refactor into a single function? */ routerinfo_t *router = router_get_by_nickname(conn->chosen_exit_name, 1); int opt = conn->chosen_exit_optional; - if (router && !connection_ap_can_use_exit(conn, router, 0)) { + if (router && !connection_ap_can_use_exit(conn, router)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, - "Requested exit point '%s' would refuse request. %s.", + "Requested exit point '%s' is excluded or " + "would refuse request. %s.", conn->chosen_exit_name, opt ? "Trying others" : "Closing"); if (opt) { conn->chosen_exit_optional = 0; @@ -1611,9 +1616,10 @@ connection_ap_handshake_attach_circuit(edge_connection_t *conn) } return -1; } - if (router && !connection_ap_can_use_exit(conn, router, 0)) { + if (router && !connection_ap_can_use_exit(conn, router)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, - "Requested exit point '%s' would refuse request. %s.", + "Requested exit point '%s' is excluded or " + "would refuse request. %s.", conn->chosen_exit_name, opt ? "Trying others" : "Closing"); if (opt) { conn->chosen_exit_optional = 0; diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 1ee57abe4d..082cd5f1d7 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3044,15 +3044,9 @@ connection_edge_is_rendezvous_stream(edge_connection_t *conn) * to exit from it, or 0 if it probably will not allow it. * (We might be uncertain if conn's destination address has not yet been * resolved.) - * - * If excluded_means_no is 1 and Exclude*Nodes is set and excludes - * this relay, return 0. - * XXX022-1090 This StrictNodes business needs more work, a la bug 1090. See - * also git commit ef81649d. */ int -connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, - int excluded_means_no) +connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit) { or_options_t *options = get_options(); @@ -3102,17 +3096,8 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, return 0; } if (options->_ExcludeExitNodesUnion && - (options->StrictNodes || excluded_means_no) && routerset_contains_router(options->_ExcludeExitNodesUnion, exit)) { - /* If we are trying to avoid this node as exit, and we have StrictNodes - * set, then this is not a suitable exit. Refuse it. - * - * If we don't have StrictNodes set, then this function gets called in - * two contexts. First, we've got a circuit open and we want to know - * whether we can use it. In that case, we somehow built this circuit - * despite having the last hop in ExcludeExitNodes, so we should be - * willing to use it. Second, we are evaluating whether this is an - * acceptable exit for a new circuit. In that case, skip it. */ + /* Not a suitable exit. Refuse it. */ return 0; } diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index c4ae46751d..70d0dd2713 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -47,8 +47,7 @@ int connection_exit_begin_conn(cell_t *cell, circuit_t *circ); int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ); void connection_exit_connect(edge_connection_t *conn); int connection_edge_is_rendezvous_stream(edge_connection_t *conn); -int connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit, - int excluded_means_no); +int connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit); void connection_ap_expire_beginning(void); void connection_ap_attach_pending(void); void connection_ap_fail_onehop(const char *failed_digest, From f7a5bc16d689e8b919285c66cd0f07a6694bcc69 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Tue, 26 Apr 2011 22:18:01 -0400 Subject: [PATCH 30/35] warn if we launch too many circuits for a given stream --- changes/bug1090-launch-warning | 5 +++++ src/or/circuituse.c | 13 ++++++++++++- src/or/or.h | 7 +++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 changes/bug1090-launch-warning diff --git a/changes/bug1090-launch-warning b/changes/bug1090-launch-warning new file mode 100644 index 0000000000..3f3fbcb4d8 --- /dev/null +++ b/changes/bug1090-launch-warning @@ -0,0 +1,5 @@ + o Minor features: + - Keep track of how many times we launch a new circuit to handle + a given stream. Too many launches could indicate an inconsistency + between our "launch a circuit to handle this stream" logic and our + "attach our stream to one of the available circuits" logic. diff --git a/src/or/circuituse.c b/src/or/circuituse.c index cc4a739a90..fd1cf6b9b7 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1409,7 +1409,18 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, extend_info_free(extend_info); - if (desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL) { + if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL) { + /* We just caused a circuit to get built because of this stream. + * If this stream has caused a _lot_ of circuits to be built, that's + * a bad sign: we should tell the user. */ + if (conn->num_circuits_launched < NUM_CIRCUITS_LAUNCHED_THRESHOLD && + ++conn->num_circuits_launched == NUM_CIRCUITS_LAUNCHED_THRESHOLD) + log_warn(LD_BUG, "The application request to %s:%d has launched " + "%d circuits without finding one it likes.", + escaped_safe_str_client(conn->socks_request->address), + conn->socks_request->port, + conn->num_circuits_launched); + } else { /* help predict this next time */ rep_hist_note_used_internal(time(NULL), need_uptime, 1); if (circ) { diff --git a/src/or/or.h b/src/or/or.h index 50a1223f3c..7d354c8fe1 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1151,6 +1151,13 @@ typedef struct edge_connection_t { * already retried several times. */ uint8_t num_socks_retries; +#define NUM_CIRCUITS_LAUNCHED_THRESHOLD 10 + /** Number of times we've launched a circuit to handle this stream. If + * it gets too high, that could indicate an inconsistency between our + * "launch a circuit to handle this stream" logic and our "attach our + * stream to one of the available circuits" logic. */ + unsigned int num_circuits_launched:4; + /** True iff this connection is for a DNS request only. */ unsigned int is_dns_request:1; From b8b557dcb2bf719976858589345f08fffbab13e3 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Tue, 26 Apr 2011 22:48:00 -0400 Subject: [PATCH 31/35] better user-facing warnings for unexpected last hops these still aren't perfect, but we won't know how to correct them until we start experiencing surprised users. --- src/or/circuitbuild.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 6401e71dae..90572d57c8 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2878,7 +2878,6 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) or_options_t *options = get_options(); routerset_t *rs = options->ExcludeNodes; const char *description; - int domain = LD_CIRC; uint8_t purpose = circ->_base.purpose; if (circ->build_state->onehop_tunnel) @@ -2914,7 +2913,6 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_REND_JOINED: description = "chosen rendezvous point"; - domain = LD_BUG; break; case CIRCUIT_PURPOSE_CONTROLLER: rs = options->_ExcludeExitNodesUnion; @@ -2924,18 +2922,24 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) if (routerset_contains_extendinfo(rs, exit)) { /* We should never get here if StrictNodes is set to 1. */ - if (options->StrictNodes) - log_warn(LD_BUG, "Using an excluded node with StrictNodes set. " - "Please report the following log message to the " - "developers."); - log_fn(LOG_WARN, domain, "Using %s '%s' which is listed in " - "ExcludeNodes%s, because no other options were available. To " - "prevent this, set the StrictNodes configuration option." - "(Circuit purpose is %d)", - description,exit->nickname, - rs==options->ExcludeNodes?"":" or ExcludeExitNodes", - (int)purpose); - circuit_log_path(LOG_WARN, domain, circ); + if (options->StrictNodes) { + log_warn(LD_BUG, "Using %s '%s' which is listed in ExcludeNodes%s, " + "even though StrictNodes is set. Please report. " + "(Circuit purpose: %s)", + description, exit->nickname, + rs==options->ExcludeNodes?"":" or ExcludeExitNodes", + circuit_purpose_to_string(purpose)); + } else { + log_warn(LD_CIRC, "Using %s '%s' which is listed in " + "ExcludeNodes%s, because no better options were available. To " + "prevent this (and possibly break your Tor functionality), " + "set the StrictNodes configuration option. " + "(Circuit purpose: %s)", + description, exit->nickname, + rs==options->ExcludeNodes?"":" or ExcludeExitNodes", + circuit_purpose_to_string(purpose)); + } + circuit_log_path(LOG_WARN, LD_CIRC, circ); } return; From 748350ace11fa55758fb8bafe5e5556867dd9c23 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 20 Apr 2011 16:49:41 -0400 Subject: [PATCH 32/35] Instead of checking whether we have unremoved intro points, check for usable ones --- src/or/rendclient.c | 44 +++++++++++++++++++++++++++++--------------- src/or/rendclient.h | 1 + src/or/rendcommon.c | 2 +- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 90304c33c3..d444611019 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -23,7 +23,8 @@ #include "routerlist.h" static extend_info_t *rend_client_get_random_intro_impl( - const rend_data_t *rend_query, const int strict); + const rend_cache_entry_t *rend_query, + const int strict, const int warnings); /** Called when we've established a circuit to an introduction point: * send the introduction request. */ @@ -562,7 +563,7 @@ rend_client_remove_intro_point(extend_info_t *failed_intro, } } - if (smartlist_len(ent->parsed->intro_nodes) == 0) { + if (! rend_client_any_intro_points_usable(ent)) { log_info(LD_REND, "No more intro points remain for %s. Re-fetching descriptor.", escaped_safe_str_client(rend_query->onion_address)); @@ -708,7 +709,7 @@ rend_client_desc_trynow(const char *query) assert_connection_ok(TO_CONN(conn), now); if (rend_cache_lookup_entry(conn->rend_data->onion_address, -1, &entry) == 1 && - smartlist_len(entry->parsed->intro_nodes) > 0) { + rend_client_any_intro_points_usable(entry)) { /* either this fetch worked, or it failed but there was a * valid entry from before which we should reuse */ log_info(LD_REND,"Rend desc is usable. Launching circuits."); @@ -743,13 +744,22 @@ extend_info_t * rend_client_get_random_intro(const rend_data_t *rend_query) { extend_info_t *result; + rend_cache_entry_t *entry; + + if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) { + log_warn(LD_REND, + "Query '%s' didn't have valid rend desc in cache. Failing.", + safe_str_client(rend_query->onion_address)); + return NULL; + } + /* See if we can get a node that complies with ExcludeNodes */ - if ((result = rend_client_get_random_intro_impl(rend_query, 1))) + if ((result = rend_client_get_random_intro_impl(entry, 1, 1))) return result; /* If not, and StrictNodes is not set, see if we can return any old node */ if (!get_options()->StrictNodes) - return rend_client_get_random_intro_impl(rend_query, 0); + return rend_client_get_random_intro_impl(entry, 0, 1); return NULL; } @@ -757,23 +767,18 @@ rend_client_get_random_intro(const rend_data_t *rend_query) * iff strict is true. */ static extend_info_t * -rend_client_get_random_intro_impl(const rend_data_t *rend_query, - const int strict) +rend_client_get_random_intro_impl(const rend_cache_entry_t *entry, + const int strict, + const int warnings) { int i; - rend_cache_entry_t *entry; + rend_intro_point_t *intro; routerinfo_t *router; or_options_t *options = get_options(); smartlist_t *usable_nodes; int n_excluded = 0; - if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) { - log_warn(LD_REND, - "Query '%s' didn't have valid rend desc in cache. Failing.", - safe_str_client(rend_query->onion_address)); - return NULL; - } /* We'll keep a separate list of the usable nodes. If this becomes empty, * no nodes are usable. */ usable_nodes = smartlist_create(); @@ -781,7 +786,7 @@ rend_client_get_random_intro_impl(const rend_data_t *rend_query, again: if (smartlist_len(usable_nodes) == 0) { - if (n_excluded && get_options()->StrictNodes) { + if (n_excluded && get_options()->StrictNodes && warnings) { /* We only want to warn if StrictNodes is really set. Otherwise * we're just about to retry anyways. */ @@ -822,6 +827,15 @@ rend_client_get_random_intro_impl(const rend_data_t *rend_query, return extend_info_dup(intro->extend_info); } +/** Return true iff any introduction points still listed in entry are + * usable. */ +int +rend_client_any_intro_points_usable(const rend_cache_entry_t *entry) +{ + return rend_client_get_random_intro_impl( + entry, get_options()->StrictNodes, 0) != NULL; +} + /** Client-side authorizations for hidden services; map of onion address to * rend_service_authorization_t*. */ static strmap_t *auth_hid_servs = NULL; diff --git a/src/or/rendclient.h b/src/or/rendclient.h index 56ccde1464..3f2e58e30b 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -29,6 +29,7 @@ int rend_client_receive_rendezvous(origin_circuit_t *circ, void rend_client_desc_trynow(const char *query); extend_info_t *rend_client_get_random_intro(const rend_data_t *rend_query); +int rend_client_any_intro_points_usable(const rend_cache_entry_t *entry); int rend_client_send_introduction(origin_circuit_t *introcirc, origin_circuit_t *rendcirc); diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index f4c8888c04..9d6a89ef1f 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -934,7 +934,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) tor_assert((*e)->parsed && (*e)->parsed->intro_nodes); /* XXX023 hack for now, to return "not found" if there are no intro * points remaining. See bug 997. */ - if (smartlist_len((*e)->parsed->intro_nodes) == 0) + if (! rend_client_any_intro_points_usable(*e)) return 0; return 1; } From c49f660c1a9f196c8cbb4863bb377bdb6e642539 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 27 Apr 2011 13:33:38 -0400 Subject: [PATCH 33/35] Add a big changelog entry for bug 1090 fixes --- changes/bug1090-general | 73 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 changes/bug1090-general diff --git a/changes/bug1090-general b/changes/bug1090-general new file mode 100644 index 0000000000..465631592c --- /dev/null +++ b/changes/bug1090-general @@ -0,0 +1,73 @@ + o Major features and bugfixes (node selection) + + - Revise and unify the meaning of the ExitNodes, EntryNodes, + ExcludeEntryNodes, ExcludeExitNodes, ExcludeNodes, and + StrictNodes options. Previously, we had been ambiguous in + describing what counted as an "exit" node, and what operations + exactly "StrictNodes 0" would permit. This created confusion + when people saw nodes built through unexpected circuits, and + made it hard to tell real bugs from surprises. We now stipulate + that the intended behavior is: + + . "Exit", in the context of ExitNodes and ExcludeExitNodes, + means a node that delivers user traffic outside the Tor + network. + . "Entry", in the context of EntryNodes and ExcludeEntryNodes, + means a node used as the first hop of a multihop circuit: + it doesn't include direct connections to directory servers. + . "ExcludeNodes" applies to all nodes. + . "StrictNodes" changes the behavior of ExcludeNodes only. + When StrictNodes is set, Tor should avoid all nodes listed + in ExcludeNodes, even when it will make user requests + fail. When StrictNodes is *not* set, then Tor should + follow ExcludeNodes whenever it can, except when it must + use an excluded node to perform self-tests, connect to a + hidden service, provide a hidden service, fulfill a .exit + request, upload directory information, or fetch directory + information. + + Collectively, the changes to implement the behavior are a fix for + bug 1090. + + - ExcludeNodes now takes precedence over EntryNodes and ExitNodes: + if a node is listed in both, it's treated as excluded. + + - ExcludeNodes now applies to directory nodes: as a preference if + StrictNodes is 0, or an absolute requirement if StrictNodes is 1. + (Don't exclude all the directory authorities and set StrictNodes + to 1 unless you really want your Tor to break.) + + - ExcludeNodes and ExcludeExitNodes now override exit enclaving. + + - ExcludeExitNodes now overrides .exit requests. + + - We don't use bridges from ExcludeNodes. + + - When StrictNodes is 1: + . We now apply ExcludeNodes to hidden service introduction points + and to rendezvous points selected by hidden service users. + This can make your hidden service less reliable: use it with + caution! + . If we have used ExcludeNodes on ourself, do not try self-tests. + . If we have excluded all the directory authorities, we will + not even try to upload our descriptor if we're a server. + . Do not honor .exit requests to an excluded node. + + - Remove a misfeature that caused us to ignore the Fast/Stable flags + if ExitNodes was set. Bugfix on 0.2.2.7-alpha. + + - When the set of permitted nodes changes, we now remove any + mappings introduced via TrackExitHosts to now-excluded nodes. + Bugfix on 0.1.0.1-rc. + + - We never cannibalize a circuit that had excluded nodes on it, + even if StrictNodes is 0. Bugfix on 0.1.0.1-rc. + + - Improve log messages related to excluded nodes. + + - Revert a change where we would be laxer about attaching streams to + circuits than when building the circuits. This was meant to + prevent a set of bugs where streams were never attachable, but our + improved code here should make this unnecessary. Bugfix on + 0.2.2.7-alpha. + From 2ac768e89f16230d9af1ea5dd84856568d4ceeef Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 10 Mar 2011 18:25:51 -0500 Subject: [PATCH 34/35] Revise the manpage to contain the actual intended *Nodes behavior This is a squashed version of my former desired_nodes_behavior branch that we used to specify the intended results wrt bug 1090. --- doc/tor.1.txt | 74 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index f1734d2016..866a702e51 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -489,32 +489,74 @@ The following options are useful only for clients (that is, if **ExcludeNodes** __node__,__node__,__...__:: A list of identity fingerprints, nicknames, country codes and address - patterns of nodes to never use when building a circuit. (Example: - ExcludeNodes SlowServer, $ EFFFFFFFFFFFFFFF, \{cc}, 255.254.0.0/8) + patterns of nodes to avoid when building a circuit. + (Example: + ExcludeNodes SlowServer, $ EFFFFFFFFFFFFFFF, \{cc}, 255.254.0.0/8) + ++ + By default, this option is treated as a preference that Tor is allowed + to override in order to keep working. + For example, if you try to connect to a hidden service, + but you have excluded all of the hidden service's introduction points, + Tor will connect to one of them anyway. If you do not want this + behavior, set the StrictNodes option (documented below). + ++ + Note also that if you are a relay, this (and the other node selection + options below) only affects your own circuits that Tor builds for you. + Clients can still build circuits through you to any node. Controllers + can tell Tor to build circuits through any node. + **ExcludeExitNodes** __node__,__node__,__...__:: A list of identity fingerprints, nicknames, country codes and address - patterns of nodes to never use when picking an exit node. Note that any + patterns of nodes to never use when picking an exit node---that is, a + node that delivers traffic for you outside the Tor network. Note that any node listed in ExcludeNodes is automatically considered to be part of this - list. + list too. See also the caveats on the "ExitNodes" option below -**EntryNodes** __node__,__node__,__...__:: - A list of identity fingerprints, nicknames and address - patterns of nodes to use for the first hop in normal circuits. These are - treated only as preferences unless StrictNodes (see below) is also set. **ExitNodes** __node__,__node__,__...__:: A list of identity fingerprints, nicknames, country codes and address - patterns of nodes to use for the last hop in normal exit circuits. These - are treated only as preferences unless StrictNodes (see below) is also set. + patterns of nodes to use as exit node---that is, a + node that delivers traffic for you outside the Tor network. + ++ + Note that if you list too few nodes here, or if you exclude too many exit + nodes with ExcludeExitNodes, you can degrade functionality. For example, + if none of the exits you list allows traffic on port 80 or 443, you won't + be able to browse the web. + ++ + Note also that not every circuit is used to deliver traffic outside of + the Tor network. It is normal to see non-exit circuits (such as those + used to connect to hidden services, those that do directory fetches, + those used for self-tests, and so on) that end at a non-exit node. To + keep a node from being used entirely, see ExcludeNodes and StrictNodes. + ++ + The ExcludeNodes option overrides this option: any node listed in both + ExitNodes and ExcludeNodes is treated as excluded. + ++ + The .exit address notation, if enabled, overrides this option. + +**EntryNodes** __node__,__node__,__...__:: + A list of identity fingerprints and nicknames of nodes + to use for the first hop in your normal circuits. (Country codes and + address patterns are not yet supported.) This includes all + circuits except for direct connections to directory servers. The Bridge + option overrides this option; if you have configured bridges and + UseBridges is 1, the Bridges are used as your entry nodes. + ++ + The ExcludeNodes option overrides this option: any node listed in both + EntryNodes and ExcludeNodes is treated as excluded. **StrictNodes** **0**|**1**:: - If 1 and EntryNodes config option is set, Tor will never use any nodes - besides those listed in EntryNodes for the first hop of a normal circuit. - If 1 and ExitNodes config option is set, Tor will never use any nodes - besides those listed in ExitNodes for the last hop of a normal exit - circuit. Note that Tor might still use these nodes for non-exit circuits - such as one-hop directory fetches or hidden service support circuits. + If StrictNodes is set to 1, Tor will treat the ExcludeNodes option as a + requirement to follow for all the circuits you generate, even if doing so + will break functionality for you. If StrictNodes is set to 0, Tor will + still try to avoid nodes in the ExcludeNodes list, but it will err on the + side of avoiding unexpected errors. Specifically, StrictNodes 0 tells + Tor that it is okay to use an excluded node when it is *necessary* to + perform self-tests, connect to + a hidden service, provide a hidden service to a client, fulfill a .exit + request, upload directory information, or download directory information. + (Default: 0) **FascistFirewall** **0**|**1**:: If 1, Tor will only create outgoing connections to ORs running on ports From 0c40dda3ad824d8a782dafae4c4233a949837df3 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Wed, 27 Apr 2011 13:43:11 -0400 Subject: [PATCH 35/35] explain an argument in a function comment --- src/or/rendclient.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/or/rendclient.c b/src/or/rendclient.c index d444611019..65e632f259 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -764,7 +764,8 @@ rend_client_get_random_intro(const rend_data_t *rend_query) } /** As rend_client_get_random_intro, except assume that StrictNodes is set - * iff strict is true. + * iff strict is true. If warnings is false, don't complain + * to the user when we're out of nodes, even if StrictNodes is true. */ static extend_info_t * rend_client_get_random_intro_impl(const rend_cache_entry_t *entry,