diff options
Diffstat (limited to 'doc/spec/proposals/156-tracking-blocked-ports.txt')
-rw-r--r-- | doc/spec/proposals/156-tracking-blocked-ports.txt | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/doc/spec/proposals/156-tracking-blocked-ports.txt b/doc/spec/proposals/156-tracking-blocked-ports.txt new file mode 100644 index 000000000..1e7b0d963 --- /dev/null +++ b/doc/spec/proposals/156-tracking-blocked-ports.txt @@ -0,0 +1,529 @@ +Filename: 156-tracking-blocked-ports.txt +Title: Tracking blocked ports on the client side +Version: $Revision$ +Last-Modified: $Date$ +Author: Robert Hogan +Created: 14-Oct-2008 +Status: Open +Target: 0.2.? + +Motivation: +Tor clients that are behind extremely restrictive firewalls can end up +waiting a while for their first successful OR connection to a node on the +network. Worse, the more restrictive their firewall the more susceptible +they are to an attacker guessing their entry nodes. Tor routers that +are behind extremely restrictive firewalls can only offer a limited, +'partitioned' service to other routers and clients on the network. Exit +nodes behind extremely restrictive firewalls may advertise ports that they +are actually not able to connect to, wasting network resources in circuit +constructions that are doomed to fail at the last hop on first use. + +Proposal: + +When a client attempts to connect to an entry guard it should avoid +further attempts on ports that fail once until it has connected to at +least one entry guard successfully. (Maybe it should wait for more than +one failure to reduce the skew on the first node selection.) Thereafter +it should select entry guards regardless of port and warn the user if +it observes that connections to a given port have failed every multiple +of 5 times without success or since the last success. + +Tor should warn the operators of exit, middleman and entry nodes if it +observes that connections to a given port have failed a multiple of 5 +times without success or since the last success. If attempts on a port +fail 20 or more times without or since success, Tor should add the port +to a 'blocked-ports' entry in its descriptor's extra-info. Some thought +needs to be given to what the authorities might do with this information. + +Related TODO item: + "- Automatically determine what ports are reachable and start using + those, if circuits aren't working and it's a pattern we + recognize ("port 443 worked once and port 9001 keeps not + working")." + + +I've had a go at implementing all of this in the attached. + +Addendum: +Just a note on the patch, storing the digest of each router that uses the port +is a bit of a memory hog, and its only real purpose is to provide a count of +routers using that port when warning the user. That could be achieved when +warning the user by iterating through the routerlist instead. + +Index: src/or/connection_or.c +=================================================================== +--- src/or/connection_or.c (revision 17104) ++++ src/or/connection_or.c (working copy) +@@ -502,6 +502,9 @@ + connection_or_connect_failed(or_connection_t *conn, + int reason, const char *msg) + { ++ if ((reason == END_OR_CONN_REASON_NO_ROUTE) || ++ (reason == END_OR_CONN_REASON_REFUSED)) ++ or_port_hist_failure(conn->identity_digest,TO_CONN(conn)->port); + control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, reason); + if (!authdir_mode_tests_reachability(get_options())) + control_event_bootstrap_problem(msg, reason); +@@ -580,6 +583,7 @@ + /* already marked for close */ + return NULL; + } ++ + return conn; + } + +@@ -909,6 +913,7 @@ + control_event_or_conn_status(conn, OR_CONN_EVENT_CONNECTED, 0); + + if (started_here) { ++ or_port_hist_success(TO_CONN(conn)->port); + rep_hist_note_connect_succeeded(conn->identity_digest, now); + if (entry_guard_register_connect_status(conn->identity_digest, + 1, now) < 0) { +Index: src/or/rephist.c +=================================================================== +--- src/or/rephist.c (revision 17104) ++++ src/or/rephist.c (working copy) +@@ -18,6 +18,7 @@ + static void bw_arrays_init(void); + static void predicted_ports_init(void); + static void hs_usage_init(void); ++static void or_port_hist_init(void); + + /** Total number of bytes currently allocated in fields used by rephist.c. */ + uint64_t rephist_total_alloc=0; +@@ -89,6 +90,25 @@ + digestmap_t *link_history_map; + } or_history_t; + ++/** or_port_hist_t contains our router/client's knowledge of ++ all OR ports offered on the network, and how many servers with each port we ++ have succeeded or failed to connect to. */ ++typedef struct { ++ /** The port this entry is tracking. */ ++ uint16_t or_port; ++ /** Have we ever connected to this port on another OR?. */ ++ unsigned int success:1; ++ /** The ORs using this port. */ ++ digestmap_t *ids; ++ /** The ORs using this port we have failed to connect to. */ ++ digestmap_t *failure_ids; ++ /** Are we excluding ORs with this port during entry selection?*/ ++ unsigned int excluded; ++} or_port_hist_t; ++ ++static unsigned int still_searching = 0; ++static smartlist_t *or_port_hists; ++ + /** When did we last multiply all routers' weighted_run_length and + * total_run_weights by STABILITY_ALPHA? */ + static time_t stability_last_downrated = 0; +@@ -164,6 +184,16 @@ + tor_free(hist); + } + ++/** Helper: free storage held by a single OR port history entry. */ ++static void ++or_port_hist_free(or_port_hist_t *p) ++{ ++ tor_assert(p); ++ digestmap_free(p->ids,NULL); ++ digestmap_free(p->failure_ids,NULL); ++ tor_free(p); ++} ++ + /** Update an or_history_t object <b>hist</b> so that its uptime/downtime + * count is up-to-date as of <b>when</b>. + */ +@@ -1639,7 +1669,7 @@ + tmp_time = smartlist_get(predicted_ports_times, i); + if (*tmp_time + PREDICTED_CIRCS_RELEVANCE_TIME < now) { + tmp_port = smartlist_get(predicted_ports_list, i); +- log_debug(LD_CIRC, "Expiring predicted port %d", *tmp_port); ++ log_debug(LD_HIST, "Expiring predicted port %d", *tmp_port); + smartlist_del(predicted_ports_list, i); + smartlist_del(predicted_ports_times, i); + rephist_total_alloc -= sizeof(uint16_t)+sizeof(time_t); +@@ -1821,6 +1851,12 @@ + tor_free(last_stability_doc); + built_last_stability_doc_at = 0; + predicted_ports_free(); ++ if (or_port_hists) { ++ SMARTLIST_FOREACH(or_port_hists, or_port_hist_t *, p, ++ or_port_hist_free(p)); ++ smartlist_free(or_port_hists); ++ or_port_hists = NULL; ++ } + } + + /****************** hidden service usage statistics ******************/ +@@ -2356,3 +2392,225 @@ + tor_free(fname); + } + ++/** Create a new entry in the port tracking cache for the or_port in ++ * <b>ri</b>. */ ++void ++or_port_hist_new(const routerinfo_t *ri) ++{ ++ or_port_hist_t *result; ++ const char *id=ri->cache_info.identity_digest; ++ ++ if (!or_port_hists) ++ or_port_hist_init(); ++ ++ SMARTLIST_FOREACH(or_port_hists, or_port_hist_t *, tp, ++ { ++ /* Cope with routers that change their advertised OR port or are ++ dropped from the networkstatus. We don't discard the failures of ++ dropped routers because they are still valid when counting ++ consecutive failures on a port.*/ ++ if (digestmap_get(tp->ids, id) && (tp->or_port != ri->or_port)) { ++ digestmap_remove(tp->ids, id); ++ } ++ if (tp->or_port == ri->or_port) { ++ if (!(digestmap_get(tp->ids, id))) ++ digestmap_set(tp->ids, id, (void*)1); ++ return; ++ } ++ }); ++ ++ result = tor_malloc_zero(sizeof(or_port_hist_t)); ++ result->or_port=ri->or_port; ++ result->success=0; ++ result->ids=digestmap_new(); ++ digestmap_set(result->ids, id, (void*)1); ++ result->failure_ids=digestmap_new(); ++ result->excluded=0; ++ smartlist_add(or_port_hists, result); ++} ++ ++/** Create the port tracking cache. */ ++/*XXX: need to call this when we rebuild/update our network status */ ++static void ++or_port_hist_init(void) ++{ ++ routerlist_t *rl = router_get_routerlist(); ++ ++ if (!or_port_hists) ++ or_port_hists=smartlist_create(); ++ ++ if (rl && rl->routers) { ++ SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, ++ { ++ or_port_hist_new(ri); ++ }); ++ } ++} ++ ++#define NOT_BLOCKED 0 ++#define FAILURES_OBSERVED 1 ++#define POSSIBLY_BLOCKED 5 ++#define PROBABLY_BLOCKED 10 ++/** Return the list of blocked ports for our router's extra-info.*/ ++char * ++or_port_hist_get_blocked_ports(void) ++{ ++ char blocked_ports[2048]; ++ char *bp; ++ ++ tor_snprintf(blocked_ports,sizeof(blocked_ports),"blocked-ports"); ++ SMARTLIST_FOREACH(or_port_hists, or_port_hist_t *, tp, ++ { ++ if (digestmap_size(tp->failure_ids) >= PROBABLY_BLOCKED) ++ tor_snprintf(blocked_ports+strlen(blocked_ports), ++ sizeof(blocked_ports)," %u,",tp->or_port); ++ }); ++ if (strlen(blocked_ports) == 13) ++ return NULL; ++ bp=tor_strdup(blocked_ports); ++ bp[strlen(bp)-1]='\n'; ++ bp[strlen(bp)]='\0'; ++ return bp; ++} ++ ++/** Revert to client-only mode if we have seen to many failures on a port or ++ * range of ports.*/ ++static void ++or_port_hist_report_block(unsigned int min_severity) ++{ ++ or_options_t *options=get_options(); ++ char failures_observed[2048],possibly_blocked[2048],probably_blocked[2048]; ++ char port[1024]; ++ ++ memset(failures_observed,0,sizeof(failures_observed)); ++ memset(possibly_blocked,0,sizeof(possibly_blocked)); ++ memset(probably_blocked,0,sizeof(probably_blocked)); ++ ++ SMARTLIST_FOREACH(or_port_hists, or_port_hist_t *, tp, ++ { ++ unsigned int failures = digestmap_size(tp->failure_ids); ++ if (failures >= min_severity) { ++ tor_snprintf(port, sizeof(port), " %u (%u failures %s out of %u on the" ++ " network)",tp->or_port,failures, ++ (!tp->success)?"and no successes": "since last success", ++ digestmap_size(tp->ids)); ++ if (failures >= PROBABLY_BLOCKED) { ++ strlcat(probably_blocked, port, sizeof(probably_blocked)); ++ } else if (failures >= POSSIBLY_BLOCKED) ++ strlcat(possibly_blocked, port, sizeof(possibly_blocked)); ++ else if (failures >= FAILURES_OBSERVED) ++ strlcat(failures_observed, port, sizeof(failures_observed)); ++ } ++ }); ++ ++ log_warn(LD_HIST,"%s%s%s%s%s%s%s%s", ++ server_mode(options) && ++ ((min_severity==FAILURES_OBSERVED) || strlen(probably_blocked))? ++ "You should consider disabling your Tor server.":"", ++ (min_severity==FAILURES_OBSERVED)? ++ "Tor appears to be blocked from connecting to a range of ports " ++ "with the result that it cannot connect to one tenth of the Tor " ++ "network. ":"", ++ strlen(failures_observed)? ++ "Tor has observed failures on the following ports: ":"", ++ failures_observed, ++ strlen(possibly_blocked)? ++ "Tor is possibly blocked on the following ports: ":"", ++ possibly_blocked, ++ strlen(probably_blocked)? ++ "Tor is almost certainly blocked on the following ports: ":"", ++ probably_blocked); ++ ++} ++ ++/** Record the success of our connection to <b>digest</b>'s ++ * OR port. */ ++void ++or_port_hist_success(uint16_t or_port) ++{ ++ SMARTLIST_FOREACH(or_port_hists, or_port_hist_t *, tp, ++ { ++ if (tp->or_port != or_port) ++ continue; ++ /*Reset our failure stats so we can notice if this port ever gets ++ blocked again.*/ ++ tp->success=1; ++ if (digestmap_size(tp->failure_ids)) { ++ digestmap_free(tp->failure_ids,NULL); ++ tp->failure_ids=digestmap_new(); ++ } ++ if (still_searching) { ++ still_searching=0; ++ SMARTLIST_FOREACH(or_port_hists,or_port_hist_t *,t,t->excluded=0;); ++ } ++ return; ++ }); ++} ++/** Record the failure of our connection to <b>digest</b>'s ++ * OR port. Warn, exclude the port from future entry guard selection, or ++ * add port to blocked-ports in our server's extra-info as appropriate. */ ++void ++or_port_hist_failure(const char *digest, uint16_t or_port) ++{ ++ int total_failures=0, ports_excluded=0, report_block=0; ++ int total_routers=smartlist_len(router_get_routerlist()->routers); ++ ++ SMARTLIST_FOREACH(or_port_hists, or_port_hist_t *, tp, ++ { ++ ports_excluded += tp->excluded; ++ total_failures+=digestmap_size(tp->failure_ids); ++ if (tp->or_port != or_port) ++ continue; ++ /* We're only interested in unique failures */ ++ if (digestmap_get(tp->failure_ids, digest)) ++ return; ++ ++ total_failures++; ++ digestmap_set(tp->failure_ids, digest, (void*)1); ++ if (still_searching && !tp->success) { ++ tp->excluded=1; ++ ports_excluded++; ++ } ++ if ((digestmap_size(tp->ids) >= POSSIBLY_BLOCKED) && ++ !(digestmap_size(tp->failure_ids) % POSSIBLY_BLOCKED)) ++ report_block=POSSIBLY_BLOCKED; ++ }); ++ ++ if (total_failures >= (int)(total_routers/10)) ++ or_port_hist_report_block(FAILURES_OBSERVED); ++ else if (report_block) ++ or_port_hist_report_block(report_block); ++ ++ if (ports_excluded >= smartlist_len(or_port_hists)) { ++ log_warn(LD_HIST,"During entry node selection Tor tried every port " ++ "offered on the network on at least one server " ++ "and didn't manage a single " ++ "successful connection. This suggests you are behind an " ++ "extremely restrictive firewall. Tor will keep trying to find " ++ "a reachable entry node."); ++ SMARTLIST_FOREACH(or_port_hists, or_port_hist_t *, tp, tp->excluded=0;); ++ } ++} ++ ++/** Add any ports marked as excluded in or_port_hist_t to <b>rt</b> */ ++void ++or_port_hist_exclude(routerset_t *rt) ++{ ++ SMARTLIST_FOREACH(or_port_hists, or_port_hist_t *, tp, ++ { ++ char portpolicy[9]; ++ if (tp->excluded) { ++ tor_snprintf(portpolicy,sizeof(portpolicy),"*:%u", tp->or_port); ++ log_warn(LD_HIST,"Port %u may be blocked, excluding it temporarily " ++ "from entry guard selection.", tp->or_port); ++ routerset_parse(rt, portpolicy, "Ports"); ++ } ++ }); ++} ++ ++/** Allow the exclusion of ports during our search for an entry node. */ ++void ++or_port_hist_search_again(void) ++{ ++ still_searching=1; ++} +Index: src/or/or.h +=================================================================== +--- src/or/or.h (revision 17104) ++++ src/or/or.h (working copy) +@@ -3864,6 +3864,13 @@ + int any_predicted_circuits(time_t now); + int rep_hist_circbuilding_dormant(time_t now); + ++void or_port_hist_failure(const char *digest, uint16_t or_port); ++void or_port_hist_success(uint16_t or_port); ++void or_port_hist_new(const routerinfo_t *ri); ++void or_port_hist_exclude(routerset_t *rt); ++void or_port_hist_search_again(void); ++char *or_port_hist_get_blocked_ports(void); ++ + /** Possible public/private key operations in Tor: used to keep track of where + * we're spending our time. */ + typedef enum { +Index: src/or/routerparse.c +=================================================================== +--- src/or/routerparse.c (revision 17104) ++++ src/or/routerparse.c (working copy) +@@ -1401,6 +1401,8 @@ + goto err; + } + ++ or_port_hist_new(router); ++ + if (!router->platform) { + router->platform = tor_strdup("<unknown>"); + } +Index: src/or/router.c +=================================================================== +--- src/or/router.c (revision 17104) ++++ src/or/router.c (working copy) +@@ -1818,6 +1818,7 @@ + char published[ISO_TIME_LEN+1]; + char digest[DIGEST_LEN]; + char *bandwidth_usage; ++ char *blocked_ports; + int result; + size_t len; + +@@ -1825,7 +1826,6 @@ + extrainfo->cache_info.identity_digest, DIGEST_LEN); + format_iso_time(published, extrainfo->cache_info.published_on); + bandwidth_usage = rep_hist_get_bandwidth_lines(1); +- + result = tor_snprintf(s, maxlen, + "extra-info %s %s\n" + "published %s\n%s", +@@ -1835,6 +1835,16 @@ + if (result<0) + return -1; + ++ blocked_ports = or_port_hist_get_blocked_ports(); ++ if (blocked_ports) { ++ result = tor_snprintf(s+strlen(s), maxlen-strlen(s), ++ "%s", ++ blocked_ports); ++ tor_free(blocked_ports); ++ if (result<0) ++ return -1; ++ } ++ + if (should_record_bridge_info(options)) { + static time_t last_purged_at = 0; + char *geoip_summary; +Index: src/or/circuitbuild.c +=================================================================== +--- src/or/circuitbuild.c (revision 17104) ++++ src/or/circuitbuild.c (working copy) +@@ -62,6 +62,7 @@ + + static void entry_guards_changed(void); + static time_t start_of_month(time_t when); ++static int num_live_entry_guards(void); + + /** Iterate over values of circ_id, starting from conn-\>next_circ_id, + * and with the high bit specified by conn-\>circ_id_type, until we get +@@ -1627,12 +1628,14 @@ + smartlist_t *excluded; + or_options_t *options = get_options(); + router_crn_flags_t flags = 0; ++ routerset_t *_ExcludeNodes; + + if (state && options->UseEntryGuards && + (purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) { + return choose_random_entry(state); + } + ++ _ExcludeNodes = routerset_new(); + excluded = smartlist_create(); + + if (state && (r = build_state_get_exit_router(state))) { +@@ -1670,12 +1673,18 @@ + if (options->_AllowInvalid & ALLOW_INVALID_ENTRY) + flags |= CRN_ALLOW_INVALID; + ++ if (options->ExcludeNodes) ++ routerset_union(_ExcludeNodes,options->ExcludeNodes); ++ ++ or_port_hist_exclude(_ExcludeNodes); ++ + choice = router_choose_random_node( + NULL, + excluded, +- options->ExcludeNodes, ++ _ExcludeNodes, + flags); + smartlist_free(excluded); ++ routerset_free(_ExcludeNodes); + return choice; + } + +@@ -2727,6 +2736,7 @@ + entry_guards_update_state(or_state_t *state) + { + config_line_t **next, *line; ++ unsigned int have_reachable_entry=0; + if (! entry_guards_dirty) + return; + +@@ -2740,6 +2750,7 @@ + char dbuf[HEX_DIGEST_LEN+1]; + if (!e->made_contact) + continue; /* don't write this one to disk */ ++ have_reachable_entry=1; + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("EntryGuard"); + line->value = tor_malloc(HEX_DIGEST_LEN+MAX_NICKNAME_LEN+2); +@@ -2785,6 +2796,11 @@ + if (!get_options()->AvoidDiskWrites) + or_state_mark_dirty(get_or_state(), 0); + entry_guards_dirty = 0; ++ ++ /* XXX: Is this the place to decide that we no longer have any reachable ++ guards? */ ++ if (!have_reachable_entry) ++ or_port_hist_search_again(); + } + + /** If <b>question</b> is the string "entry-guards", then dump + |