mirror of
https://github.com/torproject/torspec.git
synced 2024-11-26 19:30:32 +00:00
530 lines
18 KiB
Plaintext
530 lines
18 KiB
Plaintext
Filename: 156-tracking-blocked-ports.txt
|
|
Title: Tracking blocked ports on the client side
|
|
Author: Robert Hogan
|
|
Created: 14-Oct-2008
|
|
Status: Superseded
|
|
|
|
[Superseded by 156, which recognizes the security issues here.]
|
|
|
|
|
|
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
|
|
|