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