Patch: Adding country codes to *Nodes config options

Robert Hogan robert at roberthogan.net
Mon Sep 22 18:20:02 UTC 2008


This patch allows users to add country codes to the following configuration 
options: EntryNodes, ExitNodes, ExcludeNodes, ExcludeExitNodes.

As a side effect, it adds the ability to specify IP address patterns for 
EntryNodes and ExitNodes.

Country codes are specified as: {cc} and are not case sensitive. So for example:

EntryNodes {gb},{DE},$AAAAAAAAAAAAAAAAA, Unnamed, 233.233.0.0./8

How the patch works:

- EntryNodes and ExitNodes become routerset_t's.
- The element 'int country' is added to routerinfo_t.
- The pattern {cc}, where cc is a valid country code, is added to routerset_t.
- Invalid country codes will cause the entire config option to be rejected.
- When a {cc} is specified in a *Nodes option, routerlist is iterated and the 
identity digest of all routers with a matching country is added to the 
routerset_t digest list. If the particular digest already exists, it is added 
anyway.
- Every time Tor parses a new router it checks to see if the router's country is 
specified in any *Nodes option. If so, it adds the router to the option's 
routerset. (router_parse_entry_from_string is the only place where a router's 
routerinfo_t is created/updated right?)
- I think _ExcludeExitNodesUnion was leaking because it was not part of 
option_vars. I've added it option_vars for now - but it looks as though 
config_var_t should be updated to allow internal options that can be memory 
managed in config_var_t but not set/unset/updated by the user. I can't see an 
unintrusive way of doing that, so have left it alone for now.
- routerset_equal is an unfortunate duplication of opt_streq. Maybe there's a 
better way?


Index: src/or/config.c
===================================================================
--- src/or/config.c	(revision 16912)
+++ src/or/config.c	(working copy)
@@ -194,11 +194,12 @@
   V(DNSListenAddress,            LINELIST, NULL),
   V(DownloadExtraInfo,           BOOL,     "0"),
   V(EnforceDistinctSubnets,      BOOL,     "1"),
-  V(EntryNodes,                  STRING,   NULL),
+  V(EntryNodes,                  ROUTERSET,   NULL),
   V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "10 minutes"),
   V(ExcludeNodes,                ROUTERSET, NULL),
   V(ExcludeExitNodes,            ROUTERSET, NULL),
-  V(ExitNodes,                   STRING, NULL),
+  V(_ExcludeExitNodesUnion,      ROUTERSET, NULL),
+  V(ExitNodes,                   ROUTERSET, NULL),
   V(ExitPolicy,                  LINELIST, NULL),
   V(ExitPolicyRejectPrivate,     BOOL,     "1"),
   V(FallbackNetworkstatusFile,   FILENAME,
@@ -1340,7 +1341,7 @@
   /* Check if we need to parse and add the EntryNodes config option. */
   if (options->EntryNodes &&
       (!old_options ||
-       !opt_streq(old_options->EntryNodes, options->EntryNodes)))
+      (!routerset_equal(old_options->ExitNodes,options->ExitNodes))))
     entry_nodes_should_be_added();
 
   /* Since our options changed, we might need to regenerate and upload our
@@ -1701,7 +1702,6 @@
   case CONFIG_TYPE_LINELIST_S:
     config_line_append((config_line_t**)lvalue, c->key, c->value);
     break;
-
   case CONFIG_TYPE_OBSOLETE:
     log_warn(LD_CONFIG, "Skipping obsolete configuration option '%s'", c->key);
     break;
@@ -2964,17 +2964,17 @@
   }
 
   if (options->StrictExitNodes &&
-      (!options->ExitNodes || !strlen(options->ExitNodes)) &&
+      (!options->ExitNodes) &&
       (!old_options ||
        (old_options->StrictExitNodes != options->StrictExitNodes) ||
-       (!opt_streq(old_options->ExitNodes, options->ExitNodes))))
+       (!routerset_equal(old_options->ExitNodes,options->ExitNodes))))
     COMPLAIN("StrictExitNodes set, but no ExitNodes listed.");
 
   if (options->StrictEntryNodes &&
-      (!options->EntryNodes || !strlen(options->EntryNodes)) &&
+      (!options->EntryNodes) &&
       (!old_options ||
        (old_options->StrictEntryNodes != options->StrictEntryNodes) ||
-       (!opt_streq(old_options->EntryNodes, options->EntryNodes))))
+       (!routerset_equal(old_options->EntryNodes,options->EntryNodes))))
     COMPLAIN("StrictEntryNodes set, but no EntryNodes listed.");
 
   if (options->AuthoritativeDir) {
@@ -3334,10 +3334,6 @@
   if (options->UseEntryGuards && ! options->NumEntryGuards)
     REJECT("Cannot enable UseEntryGuards with NumEntryGuards set to 0");
 
-  if (check_nickname_list(options->ExitNodes, "ExitNodes", msg))
-    return -1;
-  if (check_nickname_list(options->EntryNodes, "EntryNodes", msg))
-    return -1;
   if (check_nickname_list(options->MyFamily, "MyFamily", msg))
     return -1;
   for (cl = options->NodeFamilies; cl; cl = cl->next) {
Index: src/or/routerlist.c
===================================================================
--- src/or/routerlist.c	(revision 16912)
+++ src/or/routerlist.c	(working copy)
@@ -4721,6 +4721,54 @@
   return result;
 }
 
+/** Helper.  Add <b>router</b> to any routersets which contain its country. */
+static void
+routerset_add_routerdigest(routerset_t *target, const routerinfo_t *router,
+                           const char *description)
+{
+  log_debug(LD_CONFIG, "Adding identity for router %s to %s",
+            router->nickname, description);
+  digestmap_set(target->digests, router->cache_info.identity_digest,
+                        (void*)1);
+}
+
+/** Add all routers in country <b>c</b> to <b>routerstoadd</b>. Return 0
+ * if <b>c</b> is not a valid country.*/
+static int
+routerset_add_routers_by_country(smartlist_t *routerstoadd, const char *c,
+                                 int running_only)
+{
+  char *country;
+
+  if (!strchr(c,'{') && !strchr(c,'}'))
+    return 0;
+
+  country=tor_strdup(c);
+  tor_strstrip(country,"}");
+  tor_strstrip(country,"{");
+  tor_strlower(country);
+
+  if (!geoip_is_valid_country(country)) {
+    log(LOG_WARN, LD_CONFIG, "Country Code '%s' is not valid, ignoring.",
+          country);
+    tor_free(country);
+    return 0;
+  }
+
+  SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router,
+    {
+    if (!strcmp(country,geoip_get_country_name(router->country))) {
+        log_debug(LD_CONFIG, "Adding identity for router %s from %s",
+                  router->nickname, country);
+        if (!running_only || router->is_running) {
+          smartlist_add(routerstoadd, router);
+        }
+    }
+    });
+  tor_free(country);
+  return 1;
+}
+
 /** Parse the string <b>s</b> to create a set of routerset entries, and add
  * them to <b>target</b>.  In log messages, refer to the string as
  * <b>description</b>.  Return 0 on success, -1 on failure.
@@ -4733,6 +4781,7 @@
 routerset_parse(routerset_t *target, const char *s, const char *description)
 {
   int r = 0;
+  smartlist_t *cc_routers=smartlist_create();
   smartlist_t *list = smartlist_create();
   smartlist_split_string(list, s, ",",
                          SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 0);
@@ -4748,6 +4797,14 @@
       } else if (is_legal_nickname(nick)) {
         log_debug(LD_CONFIG, "Adding nickname %s to %s", nick, description);
         strmap_set_lc(target->names, nick, (void*)1);
+      } else if (routerset_add_routers_by_country(cc_routers, nick, 1)) {
+        log_debug(LD_CONFIG, "Adding identities in %s to %s", nick,
+                  description);
+        SMARTLIST_FOREACH(cc_routers, routerinfo_t *, router,
+        {
+          routerset_add_routerdigest(target, router, description);
+        });
+        smartlist_clear(cc_routers);
       } else if ((strchr(nick,'.') || strchr(nick, '*')) &&
                  (p = router_parse_addr_policy_item_from_string(
                                      nick, ADDR_POLICY_REJECT))) {
@@ -4763,6 +4820,7 @@
     });
   smartlist_add_all(target->list, list);
   smartlist_free(list);
+  smartlist_free(cc_routers);
   return r;
 }
 
@@ -4779,6 +4837,19 @@
   tor_free(s);
 }
 
+/** Helper.  Return true iff <b>routers</b> contains <b>country</b>. */
+static int
+routerset_contains_country(routerset_t *routers, const char *country)
+{
+  char *rs;
+  int r=0;
+  rs=routerset_to_string(routers);
+  if (strstr(rs,country))
+    r=1;
+  tor_free(rs);
+  return r;
+}
+
 /** Helper.  Return true iff <b>set</b> contains a router based on the other
  * provided fields. */
 static int
@@ -4827,9 +4898,11 @@
 routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset,
                           int running_only)
 {
+  smartlist_t *cc_routers=NULL;
   tor_assert(out);
   if (!routerset || !routerset->list)
     return;
+  cc_routers=smartlist_create();
   if (!warned_nicknames)
     warned_nicknames = smartlist_create();
   SMARTLIST_FOREACH(routerset->list, const char *, name, {
@@ -4837,6 +4910,10 @@
     if (router) {
       if (!running_only || router->is_running)
         smartlist_add(out, router);
+    }else if (routerset_add_routers_by_country(cc_routers, name,
+                                               running_only)) {
+      smartlist_add_all(out, cc_routers);
+      smartlist_clear(cc_routers);
     }
   });
   if (smartlist_len(routerset->policies)) {
@@ -4848,6 +4925,7 @@
           smartlist_add(out, router);
       });
   }
+  smartlist_free(cc_routers);
 }
 
 /** Remove every routerinfo_t from <b>lst</b> that is in <b>routerset</b>. */
@@ -4875,6 +4953,72 @@
   return smartlist_join_strings(set->list, ",", 0, NULL);
 }
 
+/** Return 1 if <b>old</b> and <b>new</b> match, otherwise return 0. */
+int
+routerset_equal(const routerset_t *old, const routerset_t *new)
+{
+  char *s1;
+  char *s2;
+  int r=0;
+
+  s1=routerset_to_string(old);
+  s2=routerset_to_string(new);
+  if (!s1 && !s2)
+    r=1;
+  else if (s1 && s2 && !strcmp(s1,s2))
+    r=1;
+  else
+    r=0;
+
+  tor_free(s1);
+  tor_free(s2);
+  return r;
+}
+
+/** Helper.  Add <b>router</b> to any routersets which contain its country. */
+void
+routerset_update_router_country(const routerinfo_t *router)
+{
+  or_options_t *options=get_options();
+  char country[4];
+  int update_excludes=0;
+
+  if (!options->ExitNodes && !options->EntryNodes &&
+      !options->ExcludeNodes && !options->ExcludeExitNodes)
+    return;
+
+  tor_snprintf(country,sizeof(country),"{%s}",
+               geoip_get_country_name(router->country));
+
+  if (options->ExitNodes &&
+     routerset_contains_country(options->ExitNodes,country))
+    routerset_add_routerdigest(options->ExitNodes, router, "Exit Nodes");
+
+  if (options->EntryNodes &&
+     routerset_contains_country(options->EntryNodes,country))
+    routerset_add_routerdigest(options->EntryNodes, router, "Entry Nodes");
+
+  if (options->ExcludeNodes &&
+     routerset_contains_country(options->ExcludeNodes,country)) {
+    update_excludes=1;
+    routerset_add_routerdigest(options->ExcludeNodes, router, "Exclude Nodes");
+  }
+
+  if (options->ExcludeExitNodes &&
+     routerset_contains_country(options->ExcludeExitNodes,country)) {
+    update_excludes=1;
+    routerset_add_routerdigest(options->ExcludeExitNodes, router,
+                         "ExcludeExit Nodes");
+  }
+
+  if (update_excludes) {
+    routerset_free(options->_ExcludeExitNodesUnion);
+    options->_ExcludeExitNodesUnion = routerset_new();
+    routerset_union(options->_ExcludeExitNodesUnion,options->ExcludeExitNodes);
+    routerset_union(options->_ExcludeExitNodesUnion,options->ExcludeNodes);
+  }
+}
+
 /** Free all storage held in <b>routerset</b>. */
 void
 routerset_free(routerset_t *routerset)
Index: src/or/or.h
===================================================================
--- src/or/or.h	(revision 16912)
+++ src/or/or.h	(working copy)
@@ -1348,7 +1348,8 @@
   time_t last_reachable;
   /** When did we start testing reachability for this OR? */
   time_t testing_since;
-
+  /** According to the geoip db what country is this router in? */
+  int country;
 } routerinfo_t;
 
 /** Information needed to keep and cache a signed extra-info document. */
@@ -2065,17 +2066,18 @@
   char *Address; /**< OR only: configured address for this onion router. */
   char *PidFile; /**< Where to store PID of Tor process. */
 
-  char *ExitNodes; /**< Comma-separated list of nicknames of ORs to consider
-                    * as exits. */
-  char *EntryNodes; /**< Comma-separated list of nicknames of ORs to consider
-                     * as entry points. */
+  struct routerset_t *ExitNodes; /**< Comma-separated list of nicknames of ORs
+                                  * to consider as exits. */
+  struct routerset_t *EntryNodes; /**< Comma-separated list of nicknames of ORs
+                                   * to consider as entry points. */
   int StrictExitNodes; /**< Boolean: When none of our ExitNodes are up, do we
                         * stop building circuits? */
   int StrictEntryNodes; /**< Boolean: When none of our EntryNodes are up, do we
                          * stop building circuits? */
   struct routerset_t *ExcludeNodes; /**< Comma-separated list of nicknames of
-                       * ORs not to use in circuits. */
-  struct routerset_t *ExcludeExitNodes; /**<DODOC */
+                                     * ORs not to use in circuits. */
+  struct routerset_t *ExcludeExitNodes; /**< Comma-separated list of ORs
+                                         * not to consider as exits. */
 
   /** Union of ExcludeNodes and ExcludeExitNodes */
   struct routerset_t *_ExcludeExitNodesUnion;
@@ -3432,6 +3434,7 @@
 int geoip_get_n_countries(void);
 const char *geoip_get_country_name(int num);
 int geoip_is_loaded(void);
+int geoip_is_valid_country(const char *country);
 /** Indicates an action that we might be noting geoip statistics on.
  * Note that if we're noticing CONNECT, we're a bridge, and if we're noticing
  * the others, we're not.
@@ -4262,6 +4265,8 @@
 void routerset_subtract_routers(smartlist_t *out,
                                 const routerset_t *routerset);
 char *routerset_to_string(const routerset_t *routerset);
+void routerset_update_router_country(const routerinfo_t *router);
+int routerset_equal(const routerset_t *old, const routerset_t *new);
 void routerset_free(routerset_t *routerset);
 
 int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs,
Index: src/or/geoip.c
===================================================================
--- src/or/geoip.c	(revision 16912)
+++ src/or/geoip.c	(working copy)
@@ -42,6 +42,20 @@
 /** A list of all known geoip_entry_t, sorted by ip_low. */
 static smartlist_t *geoip_entries = NULL;
 
+/** Return 1 if the <b>country</b> is a valid 2-letter country code,
+ * otherwise return 0.
+ */
+int
+geoip_is_valid_country(const char *country)
+{
+  void *_idxplus1;
+
+  _idxplus1 = strmap_get_lc(country_idxplus1_by_lc_code, country);
+  if (!_idxplus1)
+    return 0;
+  return 1;
+}
+
 /** Add an entry to the GeoIP table, mapping all IPs between <b>low</b> and
  * <b>high</b>, inclusive, to the 2-letter country code <b>country</b>.
  */
Index: src/or/routerparse.c
===================================================================
--- src/or/routerparse.c	(revision 16912)
+++ src/or/routerparse.c	(working copy)
@@ -1397,6 +1397,9 @@
     router->platform = tor_strdup("<unknown>");
   }
 
+  router->country = geoip_get_country_by_ip(router->addr);
+  routerset_update_router_country(router);
+
   goto done;
 
  err:
Index: src/or/circuitbuild.c
===================================================================
--- src/or/circuitbuild.c	(revision 16912)
+++ src/or/circuitbuild.c	(working copy)
@@ -1282,7 +1282,7 @@
            n_pending_connections);
 
   preferredexits = smartlist_create();
-  add_nickname_list_to_smartlist(preferredexits,options->ExitNodes,1);
+  routerset_get_all_routers(preferredexits,options->ExitNodes,1);
 
   sl = smartlist_create();
 
@@ -1403,6 +1403,24 @@
   return NULL;
 }
 
+/** Log a warning if the user specified an exit for the circuit that
+ * has been excluded from use by ExcludeNodes or ExcludeExitNodes. */
+static void
+warn_if_router_excluded(extend_info_t *exit)
+{
+  or_options_t *options = get_options();
+  routerinfo_t *ri = router_get_by_digest(exit->identity_digest);
+
+  if (!ri || !options->_ExcludeExitNodesUnion)
+    return;
+
+  if (routerset_contains_router(options->_ExcludeExitNodesUnion, ri))
+    log_warn(LD_CIRC,"Requested exit node '%s' is in ExcludeNodes, "
+             "or ExcludeExitNodes, using anyway.",exit->nickname);
+
+  return;
+}
+
 /** Decide a suitable length for circ's cpath, and pick an exit
  * router (or use <b>exit</b> if provided). Store these in the
  * cpath. Return 0 if ok, -1 if circuit should be closed. */
@@ -1423,6 +1441,7 @@
   }
 
   if (exit) { /* the circuit-builder pre-requested one */
+    warn_if_router_excluded(exit);
     log_info(LD_CIRC,"Using requested exit node '%s'", exit->nickname);
     exit = extend_info_dup(exit);
   } else { /* we have to decide one */
@@ -1836,7 +1855,7 @@
   else if (options->UseBridges && ri->purpose != ROUTER_PURPOSE_BRIDGE)
     *reason = "not a bridge";
   else if (!options->UseBridges && !ri->is_possible_guard &&
-           !router_nickname_is_in_list(ri, options->EntryNodes))
+           !routerset_contains_router(options->EntryNodes,ri))
     *reason = "not recommended as a guard";
   else if (routerset_contains_router(options->ExcludeNodes, ri))
     *reason = "excluded";
@@ -1860,7 +1879,6 @@
     control_event_guard(e->nickname, e->identity, "GOOD");
     changed = 1;
   }
-
   return changed;
 }
 
@@ -2350,8 +2368,9 @@
     return;
   }
 
-  log_info(LD_CIRC,"Adding configured EntryNodes '%s'.",
-           options->EntryNodes);
+  if (options->EntryNodes)
+    log_info(LD_CIRC,"Adding configured EntryNodes '%s'.",
+             routerset_to_string(options->EntryNodes));
 
   entry_routers = smartlist_create();
   entry_fps = smartlist_create();
@@ -2359,7 +2378,7 @@
   old_entry_guards_not_on_list = smartlist_create();
 
   /* Split entry guards into those on the list and those not. */
-  add_nickname_list_to_smartlist(entry_routers, options->EntryNodes, 0);
+  routerset_get_all_routers(entry_routers, options->EntryNodes, 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, {
Index: contrib/checkSpace.pl
===================================================================
--- contrib/checkSpace.pl	(revision 16912)
+++ contrib/checkSpace.pl	(working copy)
@@ -73,7 +73,7 @@
                 s!//.*!!;
             }
             ## Warn about braces preceded by non-space.
-            if (/([^\s])\{/) {
+            if (/([^\s'])\{/) {
                 print "       $1\{:$fn:$.\n";
             }
             ## Warn about multiple internal spaces.
Index: doc/tor.1.in
===================================================================
--- doc/tor.1.in	(revision 16912)
+++ doc/tor.1.in	(working copy)
@@ -422,28 +422,28 @@
 .LP
 .TP
 \fBExcludeNodes \fR\fInode\fR,\fInode\fR,\fI...\fP
-A list of identity fingerprints, nicknames, and address patterns of
-nodes to never use when building a circuit.  (Example: ExcludeNodes
-SlowServer, $ABCDEFFFFFFFFFFFFFFF, 255.254.0.0/8)
+A list of identity fingerprints, nicknames, country codes and address patterns
+of nodes to never use when building a circuit.  (Example: ExcludeNodes
+SlowServer, $ABCDEFFFFFFFFFFFFFFF, {cc}, 255.254.0.0/8)
 .LP
 .TP
 \fBExcludeExitNodes \fR\fInode\fR,\fInode\fR,\fI...\fP
-A list of identity fingerprints, nicknames, and address patterns of
-nodes to never use when picking an exit node.  Note that any 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 node
 listed in ExcludeNodes is automatically considered to be part of this
 list.
 .LP
 .TP
 \fBEntryNodes \fR\fInode\fR,\fInode\fR,\fI...\fP
-A list of identity fingerprints or nicknames of preferred nodes to use for the
-first hop in the circuit.
+A list of identity fingerprints, nicknames, country codes and address patterns
+of nodes to use for the first hop in the circuit.
 These are treated only as preferences unless StrictEntryNodes (see
 below) is also set.
 .LP
 .TP
 \fBExitNodes \fR\fInode\fR,\fInode\fR,\fI...\fP
-A list of identity fingerprints or nicknames of preferred nodes to use for the
-last hop in the circuit.
+A list of identity fingerprints, nicknames, country codes and address patterns
+of nodes to use for the last hop in the circuit.
 These are treated only as preferences unless StrictExitNodes (see
 below) is also set.
 .LP


	



More information about the tor-dev mailing list