aboutsummaryrefslogtreecommitdiff
path: root/doc/spec/proposals/156-tracking-blocked-ports.txt
diff options
context:
space:
mode:
Diffstat (limited to 'doc/spec/proposals/156-tracking-blocked-ports.txt')
-rw-r--r--doc/spec/proposals/156-tracking-blocked-ports.txt529
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
+