[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Reusing Exit Nodes?



So I've noticed my patch to reuse exit nodes that match certain
domains hasn't showed up in CVS. Was there anything wrong with it?

I've been using it for a while now, and have tested to make sure that
it doesn't interfere with the ExitNodes option. I've also noticed that
sometimes exit servers go down overnight and such, and have since
added code to expire associations between hosts and exit nodes after
30 minutes of not connecting to the host (tunable through
TrackHostExitsExpire, which is also documented in the manpage in the
patch). 

Attached are patches against both 0.0.9.3 and CVS. Please do let me
know if there is something wrong with the patch, I don't mind fixing
it. It seems to work well for me, and I find it useful.

-- 
Mike Perry
Mad Computer Scientist
fscked.org evil labs
diff -ur tor/doc/tor.1.in tor-MP/doc/tor.1.in
--- tor/doc/tor.1.in	2005-01-18 19:46:22.000000000 -0800
+++ tor-MP/doc/tor.1.in	2005-01-27 00:26:18.781516863 -0800
@@ -151,6 +151,23 @@
 If 1, Tor will never use any nodes besides those listed in "entrynodes" for
 the first hop of a circuit.
 .TP
+\fBTrackHostExits \fR\fIhost1\fR,\fI.domain1\fR|\fI.\fR\fP
+For each value in the comma separated list, Tor will track recent connections 
+to hosts that match this value and attempt to
+reuse the same exit node for each. If the value is prepended with a '.', it is
+treated as matching an entire domain. If one of the values is just a '.', it
+means match everything. This option is useful if you frequently connect to
+sites that will expire all your authentication cookies (ie log you out) if
+your IP address changes. Note that this option does have the disadvantage of
+making it more clear that a given history is
+associated with a single user. However, most people who would wish to observe
+this will observe it through cookies or other protocol-specific means anyhow.
+.TP
+\fBTrackHostExitsExpire \fR\fINUM\fP
+Since exit servers go up and down, it is desirable to expire the association
+between host and exit server after so many minutes of inactivity. The default
+is 30.
+.TP
 \fBFascistFirewall \fR\fB0\fR|\fB1\fR\fP
 If 1, Tor will only create outgoing connections to ORs running on ports that
 your firewall allows (defaults to 80 and 443; see \fBFirewallPorts\fR).  This will
diff -ur tor/src/or/circuituse.c tor-MP/src/or/circuituse.c
--- tor/src/or/circuituse.c	2005-01-21 22:33:07.000000000 -0800
+++ tor-MP/src/or/circuituse.c	2005-01-27 00:26:18.783516572 -0800
@@ -17,6 +17,10 @@
 
 /********* START VARIABLES **********/
 
+
+exit_history_pair_t exit_history[EXIT_HISTORY_LENGTH]; /* 1.5k */
+static int exit_history_current;
+
 extern circuit_t *global_circuitlist; /* from circuitlist.c */
 extern int has_fetched_directory; /* from main.c */
 
@@ -955,8 +959,78 @@
 
     link_apconn_to_circ(conn, circ);
     tor_assert(conn->socks_request);
-    if (conn->socks_request->command == SOCKS_COMMAND_CONNECT)
+    if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) {
+
+      or_options_t *options = get_options();
+      /* If an exit wasn't specifically chosen, save the history for future
+       * use */ 
+      /* FIXME: if the exit was chosen by something else, maybe we still
+       * want to cache it.. But how do we avoid an O(n) traversal every time 
+       */
+      if(!conn->chosen_exit_name && options->TrackHostExits) {
+        int found_needle = 0;
+        SMARTLIST_FOREACH(options->TrackHostExits, const char *, cp, {
+            if(cp[0] == '.') { /* match end */
+              char *str;
+              if((str = strstr(conn->socks_request->address, &cp[1]))) {
+                if(str == conn->socks_request->address 
+                  || strcmp(str, &cp[1]) == 0) {
+                  found_needle = 1; 
+                }
+              }
+            } else if(strcmp(cp, conn->socks_request->address) == 0) { 
+              found_needle = 1; 
+            }
+        });
+
+        if(found_needle) {
+          time_t now = time(NULL);
+
+          /* Add connection to our most recently received */
+          if(exit_history[exit_history_current].exit_nickname != NULL) {
+            /* loop through to find one to expire */
+            int i = exit_history_current;
+            or_options_t *options = get_options();
+
+            do {
+              if(!exit_history[i].bobs_hostname ||
+                  (now - exit_history[i].last_used) >
+                  options->TrackHostExitsExpire*60) {
+                exit_history_current = i;
+                break;
+              }
+              i = (i+1)%EXIT_HISTORY_LENGTH;
+            } while(i!=exit_history_current);
+
+            tor_assert(exit_history[exit_history_current].bobs_hostname);
+            log_fn(LOG_INFO, 
+                "Expiring cached old exit: %s to %s (history entry %d), aged %ldmin",
+                exit_history[i].bobs_hostname,
+                exit_history[i].exit_nickname, i, 
+                (now - exit_history[i].last_used)/60);
+
+            free(exit_history[exit_history_current].exit_nickname);
+            free(exit_history[exit_history_current].bobs_hostname);
+          }
+
+          exit_history[exit_history_current].exit_nickname 
+            = strdup(circ->build_state->chosen_exit_name);
+          exit_history[exit_history_current].bobs_hostname 
+            = strdup(conn->socks_request->address);
+          exit_history[exit_history_current].last_used = now;
+
+          log_fn(LOG_INFO, 
+              "Tracking host %s to always connect through exit node %s (history entry %d)",
+              exit_history[exit_history_current].bobs_hostname,
+              exit_history[exit_history_current].exit_nickname,
+              exit_history_current);
+
+          exit_history_current = (exit_history_current+1) % EXIT_HISTORY_LENGTH;
+        }
+      }
+
       connection_ap_handshake_send_begin(conn, circ);
+    }
     else
       connection_ap_handshake_send_resolve(conn, circ);
 
diff -ur tor/src/or/config.c tor-MP/src/or/config.c
--- tor/src/or/config.c	2005-01-21 22:33:07.000000000 -0800
+++ tor-MP/src/or/config.c	2005-01-27 00:26:18.785516281 -0800
@@ -120,6 +120,8 @@
   VAR("StrictEntryNodes",    BOOL,     StrictEntryNodes,     "0"),
   VAR("ExitPolicy",          LINELIST, ExitPolicy,           NULL),
   VAR("ExcludeNodes",        STRING,   ExcludeNodes,         NULL),
+  VAR("TrackHostExits",      CSV,      TrackHostExits,       NULL),
+  VAR("TrackHostExitsExpire",UINT,     TrackHostExitsExpire, "30"),
   VAR("FascistFirewall",     BOOL,     FascistFirewall,      "0"),
   VAR("FirewallPorts",       CSV,      FirewallPorts,        "80,443"),
   VAR("MyFamily",            STRING,   MyFamily,             NULL),
diff -ur tor/src/or/connection_edge.c tor-MP/src/or/connection_edge.c
--- tor/src/or/connection_edge.c	2005-01-18 19:46:23.000000000 -0800
+++ tor-MP/src/or/connection_edge.c	2005-01-27 00:29:38.552474254 -0800
@@ -19,6 +19,8 @@
 
 static int connection_ap_handshake_process_socks(connection_t *conn);
 
+extern exit_history_pair_t exit_history[EXIT_HISTORY_LENGTH];
+
 /** There was an EOF. Send an end and mark the connection for close.
  */
 int connection_edge_reached_eof(connection_t *conn) {
@@ -378,10 +380,15 @@
     conn->chosen_exit_name = tor_strdup(s+1);
     *s = 0;
   }
+  
 
   if (addresstype != ONION_HOSTNAME) {
-    /* not a hidden-service request (i.e. normal or .exit) */
+    int i = 0;
+    int retval;
+    time_t now = time(NULL);
+    or_options_t *options = get_options();
 
+    /* not a hidden-service request (i.e. normal or .exit) */
     if (socks->command == SOCKS_COMMAND_RESOLVE) {
       uint32_t answer = 0;
       struct in_addr in;
@@ -409,9 +416,46 @@
         log_fn(LOG_NOTICE,"Application asked to connect to port 0. Refusing.");
         return -1;
       }
-      rep_hist_note_used_port(socks->port, time(NULL)); /* help predict this next time */
+      rep_hist_note_used_port(socks->port, now); /* help predict this next time */
+    }
+
+    /* Check exit history for a chosen_exit_name */
+    if(options->TrackHostExits) {
+      for(i = 0; i < EXIT_HISTORY_LENGTH; i++) {
+        if(exit_history[i].bobs_hostname && 
+            (now - exit_history[i].last_used) >
+            options->TrackHostExitsExpire*60) {
+          log_fn(LOG_INFO, 
+              "Expiring cached old exit: %s to %s (history entry %d), aged %ldmin",
+              exit_history[i].bobs_hostname,
+              exit_history[i].exit_nickname, i, 
+              (now - exit_history[i].last_used)/60);
+          free(exit_history[i].bobs_hostname);
+          free(exit_history[i].exit_nickname);
+          exit_history[i].bobs_hostname = exit_history[i].exit_nickname = NULL;
+        }
+
+        if(!exit_history[i].bobs_hostname) /* Possible hole from expiration */
+          continue;
+
+        /* If we've connected to this IP before, try to prefer the same exit */
+        if(strcmp(exit_history[i].bobs_hostname, conn->socks_request->address)
+            == 0) {
+          if(conn->chosen_exit_name) free(conn->chosen_exit_name);
+          conn->chosen_exit_name = strdup(exit_history[i].exit_nickname);
+          exit_history[i].last_used = now;
+
+          log_fn(LOG_INFO, 
+              "Reusing cached exit: %s going through %s (history entry %d)",
+              exit_history[i].bobs_hostname,
+              exit_history[i].exit_nickname, i);
+          break;
+        }
+      }
     }
+
     conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
+
     return connection_ap_handshake_attach_circuit(conn);
   } else {
     /* it's a hidden-service request */
diff -ur tor/src/or/or.h tor-MP/src/or/or.h
--- tor/src/or/or.h	2005-01-21 22:33:07.000000000 -0800
+++ tor-MP/src/or/or.h	2005-01-27 00:26:18.791515409 -0800
@@ -482,6 +482,17 @@
 typedef struct buf_t buf_t;
 typedef struct socks_request_t socks_request_t;
 
+#define EXIT_HISTORY_LENGTH                128
+
+typedef struct 
+{
+  char *bobs_hostname;
+  char *exit_nickname;
+  time_t last_used;
+} exit_history_pair_t;
+
+
+
 #define CONNECTION_MAGIC 0x7C3C304Eu
 /** Description of a connection to another host or process, and associated
  * data. */
@@ -941,6 +952,8 @@
   int IgnoreVersion; /**< If true, run no matter what versions of Tor the
                       * directory recommends. */
   int RunAsDaemon; /**< If true, run in the background. (Unix only) */
+  smartlist_t *TrackHostExits; /**< Should we try to reuse the same exit node for a given host */
+  uint32_t TrackHostExitsExpire; /**< Number of minutes until we expire a connection */
   int FascistFirewall; /**< Whether to prefer ORs reachable on open ports. */
   smartlist_t *FirewallPorts; /**< Which ports our firewall allows (strings). */
   /** Application ports that require all nodes in circ to have sufficient uptime. */
diff -ur tor-0.0.9.3/doc/tor.1.in tor-0.0.9.3-MP/doc/tor.1.in
--- tor-0.0.9.3/doc/tor.1.in	2004-12-11 08:13:15.000000000 -0800
+++ tor-0.0.9.3-MP/doc/tor.1.in	2005-01-27 00:20:01.462369557 -0800
@@ -151,6 +151,23 @@
 If 1, Tor will never use any nodes besides those listed in "entrynodes" for
 the first hop of a circuit.
 .TP
+\fBTrackHostExits \fR\fIhost1\fR,\fI.domain1\fR|\fI.\fR\fP
+For each value in the comma separated list, Tor will track recent connections 
+to hosts that match this value and attempt to
+reuse the same exit node for each. If the value is prepended with a '.', it is
+treated as matching an entire domain. If one of the values is just a '.', it
+means match everything. This option is useful if you frequently connect to
+sites that will expire all your authentication cookies (ie log you out) if
+your IP address changes. Note that this option does have the disadvantage of
+making it more clear that a given history is
+associated with a single user. However, most people who would wish to observe
+this will observe it through cookies or other protocol-specific means anyhow.
+.TP
+\fBTrackHostExitsExpire \fR\fINUM\fP
+Since exit servers go up and down, it is desirable to expire the association
+between host and exit server after so many minutes of inactivity. The default
+is 30.
+.TP
 \fBFascistFirewall \fR\fB0\fR|\fB1\fR\fP
 If 1, Tor will only create outgoing connections to ORs running on ports that
 your firewall allows (defaults to 80 and 443; see \fBFirewallPorts\fR).  This will
diff -ur tor-0.0.9.3/src/or/circuituse.c tor-0.0.9.3-MP/src/or/circuituse.c
--- tor-0.0.9.3/src/or/circuituse.c	2005-01-17 10:50:45.000000000 -0800
+++ tor-0.0.9.3-MP/src/or/circuituse.c	2005-01-27 00:21:19.651003100 -0800
@@ -17,6 +17,10 @@
 
 /********* START VARIABLES **********/
 
+
+exit_history_pair_t exit_history[EXIT_HISTORY_LENGTH]; /* 1.5k */
+static int exit_history_current;
+
 extern circuit_t *global_circuitlist; /* from circuitlist.c */
 extern int has_fetched_directory; /* from main.c */
 
@@ -876,8 +880,78 @@
 
     link_apconn_to_circ(conn, circ);
     tor_assert(conn->socks_request);
-    if (conn->socks_request->command == SOCKS_COMMAND_CONNECT)
+    if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) {
+
+      or_options_t *options = get_options();
+      /* If an exit wasn't specifically chosen, save the history for future
+       * use */ 
+      /* FIXME: if the exit was chosen by something else, maybe we still
+       * want to cache it.. But how do we avoid an O(n) traversal every time 
+       */
+      if(!conn->chosen_exit_name && options->TrackHostExits) {
+        int found_needle = 0;
+        SMARTLIST_FOREACH(options->TrackHostExits, const char *, cp, {
+            if(cp[0] == '.') { /* match end */
+              char *str;
+              if((str = strstr(conn->socks_request->address, &cp[1]))) {
+                if(str == conn->socks_request->address 
+                  || strcmp(str, &cp[1]) == 0) {
+                  found_needle = 1; 
+                }
+              }
+            } else if(strcmp(cp, conn->socks_request->address) == 0) { 
+              found_needle = 1; 
+            }
+        });
+
+        if(found_needle) {
+          time_t now = time(NULL);
+
+          /* Add connection to our most recently received */
+          if(exit_history[exit_history_current].exit_nickname != NULL) {
+            /* loop through to find one to expire */
+            int i = exit_history_current;
+            or_options_t *options = get_options();
+
+            do {
+              if(!exit_history[i].bobs_hostname ||
+                  (now - exit_history[i].last_used) >
+                  options->TrackHostExitsExpire*60) {
+                exit_history_current = i;
+                break;
+              }
+              i = (i+1)%EXIT_HISTORY_LENGTH;
+            } while(i!=exit_history_current);
+
+            tor_assert(exit_history[exit_history_current].bobs_hostname);
+            log_fn(LOG_INFO, 
+                "Expiring cached old exit: %s to %s (history entry %d), aged %ldmin",
+                exit_history[i].bobs_hostname,
+                exit_history[i].exit_nickname, i, 
+                (now - exit_history[i].last_used)/60);
+
+            free(exit_history[exit_history_current].exit_nickname);
+            free(exit_history[exit_history_current].bobs_hostname);
+          }
+
+          exit_history[exit_history_current].exit_nickname 
+            = strdup(circ->build_state->chosen_exit_name);
+          exit_history[exit_history_current].bobs_hostname 
+            = strdup(conn->socks_request->address);
+          exit_history[exit_history_current].last_used = now;
+
+          log_fn(LOG_INFO, 
+              "Tracking host %s to always connect through exit node %s (history entry %d)",
+              exit_history[exit_history_current].bobs_hostname,
+              exit_history[exit_history_current].exit_nickname,
+              exit_history_current);
+
+          exit_history_current = (exit_history_current+1) % EXIT_HISTORY_LENGTH;
+        }
+      }
+
       connection_ap_handshake_send_begin(conn, circ);
+    }
     else
       connection_ap_handshake_send_resolve(conn, circ);
 
diff -ur tor-0.0.9.3/src/or/config.c tor-0.0.9.3-MP/src/or/config.c
--- tor-0.0.9.3/src/or/config.c	2005-01-05 23:24:42.000000000 -0800
+++ tor-0.0.9.3-MP/src/or/config.c	2005-01-26 02:22:13.000000000 -0800
@@ -118,6 +118,8 @@
   VAR("StrictEntryNodes",    BOOL,     StrictEntryNodes,     "0"),
   VAR("ExitPolicy",          LINELIST, ExitPolicy,           NULL),
   VAR("ExcludeNodes",        STRING,   ExcludeNodes,         NULL),
+  VAR("TrackHostExits",      CSV,      TrackHostExits,       NULL),
+  VAR("TrackHostExitsExpire",UINT,     TrackHostExitsExpire, "30"),
   VAR("FascistFirewall",     BOOL,     FascistFirewall,      "0"),
   VAR("FirewallPorts",       CSV,      FirewallPorts,        "80,443"),
   VAR("MyFamily",            STRING,   MyFamily,             NULL),
diff -ur tor-0.0.9.3/src/or/connection_edge.c tor-0.0.9.3-MP/src/or/connection_edge.c
--- tor-0.0.9.3/src/or/connection_edge.c	2004-12-24 01:44:45.000000000 -0800
+++ tor-0.0.9.3-MP/src/or/connection_edge.c	2005-01-26 14:47:01.977904762 -0800
@@ -19,6 +19,8 @@
 
 static int connection_ap_handshake_process_socks(connection_t *conn);
 
+extern exit_history_pair_t exit_history[EXIT_HISTORY_LENGTH];
+
 /** There was an EOF. Send an end and mark the connection for close.
  */
 int connection_edge_reached_eof(connection_t *conn) {
@@ -378,10 +380,15 @@
     conn->chosen_exit_name = tor_strdup(s+1);
     *s = 0;
   }
+  
 
   if (addresstype != ONION_HOSTNAME) {
-    /* not a hidden-service request (i.e. normal or .exit) */
+    int i = 0;
+    int retval;
+    time_t now = time(NULL);
+    or_options_t *options = get_options();
 
+    /* not a hidden-service request (i.e. normal or .exit) */
     if (socks->command == SOCKS_COMMAND_RESOLVE) {
       uint32_t answer = 0;
       struct in_addr in;
@@ -409,8 +416,44 @@
       log_fn(LOG_NOTICE,"Application asked to connect to port 0. Refusing.");
       return -1;
     }
+
+    /* Check exit history for a chosen_exit_name */
+    if(options->TrackHostExits) {
+      for(i = 0; i < EXIT_HISTORY_LENGTH; i++) {
+        if(exit_history[i].bobs_hostname && 
+            (now - exit_history[i].last_used) >
+            options->TrackHostExitsExpire*60) {
+          log_fn(LOG_INFO, 
+              "Expiring cached old exit: %s to %s (history entry %d), aged %ldmin",
+              exit_history[i].bobs_hostname,
+              exit_history[i].exit_nickname, i, 
+              (now - exit_history[i].last_used)/60);
+          free(exit_history[i].bobs_hostname);
+          free(exit_history[i].exit_nickname);
+          exit_history[i].bobs_hostname = exit_history[i].exit_nickname = NULL;
+        }
+
+        if(!exit_history[i].bobs_hostname) /* Possible hole from expiration */
+          continue;
+
+        /* If we've connected to this IP before, try to prefer the same exit */
+        if(strcmp(exit_history[i].bobs_hostname, conn->socks_request->address)
+            == 0) {
+          if(conn->chosen_exit_name) free(conn->chosen_exit_name);
+          conn->chosen_exit_name = strdup(exit_history[i].exit_nickname);
+          exit_history[i].last_used = now;
+
+          log_fn(LOG_INFO, 
+              "Reusing cached exit: %s going through %s (history entry %d)",
+              exit_history[i].bobs_hostname,
+              exit_history[i].exit_nickname, i);
+          break;
+        }
+      }
+    }
+
     conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
-    rep_hist_note_used_port(socks->port, time(NULL)); /* help predict this next time */
+    rep_hist_note_used_port(socks->port, now); /* help predict this next time */
     return connection_ap_handshake_attach_circuit(conn);
   } else {
     /* it's a hidden-service request */
diff -ur tor-0.0.9.3/src/or/or.h tor-0.0.9.3-MP/src/or/or.h
--- tor-0.0.9.3/src/or/or.h	2005-01-09 20:36:47.000000000 -0800
+++ tor-0.0.9.3-MP/src/or/or.h	2005-01-27 00:20:45.646946360 -0800
@@ -478,6 +478,17 @@
 typedef struct buf_t buf_t;
 typedef struct socks_request_t socks_request_t;
 
+#define EXIT_HISTORY_LENGTH                128
+
+typedef struct 
+{
+  char *bobs_hostname;
+  char *exit_nickname;
+  time_t last_used;
+} exit_history_pair_t;
+
+
+
 #define CONNECTION_MAGIC 0x7C3C304Eu
 /** Description of a connection to another host or process, and associated
  * data. */
@@ -928,6 +939,8 @@
   int IgnoreVersion; /**< If true, run no matter what versions of Tor the
                       * directory recommends. */
   int RunAsDaemon; /**< If true, run in the background. (Unix only) */
+  smartlist_t *TrackHostExits; /**< Should we try to reuse the same exit node for a given host */
+  uint32_t TrackHostExitsExpire; /**< Number of minutes until we expire a connection */
   int FascistFirewall; /**< Whether to prefer ORs reachable on open ports. */
   smartlist_t *FirewallPorts; /** Which ports our firewall allows. */
   int DirFetchPeriod; /**< How often do we fetch new directories? */