Patch: Adding country codes to *Nodes config options
Robert Hogan
robert at roberthogan.net
Wed Sep 24 18:37:51 UTC 2008
On Monday 22 September 2008 21:02:41 Nick Mathewson wrote:
> {Please take pity on us old-school purists and wrap email to under 75
> colums, so that it doesn't overflow our terminals when we reply}
>
> Hi, Robert! This patch is a good start. I can clean it up if you
> would like, or you can do another version; let me know what you'd
> prefer.
>
OK, I think I've got my wrapping and most of the patch sorted. I've added XXXX
comments wherever I'm unsure of something. Hopefully won't require too much
cleaning up!
Index: src/or/config.c
===================================================================
--- src/or/config.c (revision 16958)
+++ src/or/config.c (working copy)
@@ -194,11 +194,11 @@
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(ExitNodes, ROUTERSET, NULL),
V(ExitPolicy, LINELIST, NULL),
V(ExitPolicyRejectPrivate, BOOL, "1"),
V(FallbackNetworkstatusFile, FILENAME,
@@ -817,13 +817,22 @@
return _version;
}
+/** Release additional memory allocated in options
+ */
+static void
+or_options_free(void)
+{
+ routerset_free(global_options->_ExcludeExitNodesUnion);
+ config_free(&options_format, global_options);
+}
+
/** Release all memory and resources held by global configuration structures.
*/
void
config_free_all(void)
{
if (global_options) {
- config_free(&options_format, global_options);
+ or_options_free();
global_options = NULL;
}
if (global_state) {
@@ -1336,11 +1345,21 @@
#endif
geoip_load_file(actual_fname, options);
tor_free(actual_fname);
+
+ /* XXXX Would iterating through all option_var's routersets be better? */
+ if (options->EntryNodes)
+ routerset_refresh_countries(options->EntryNodes);
+ if (options->ExitNodes)
+ routerset_refresh_countries(options->ExitNodes);
+ if (options->ExcludeNodes)
+ routerset_refresh_countries(options->ExcludeNodes);
+ if (options->ExcludeExitNodes)
+ routerset_refresh_countries(options->ExcludeExitNodes);
}
/* 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->EntryNodes,options->EntryNodes))))
entry_nodes_should_be_added();
/* Since our options changed, we might need to regenerate and upload our
@@ -1701,7 +1720,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 +2982,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 +3352,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 16958)
+++ src/or/routerlist.c (working copy)
@@ -4707,6 +4707,7 @@
/** An address policy for routers in the set. For implementation reasons,
* a router belongs to the set if it is _rejected_ by this policy. */
smartlist_t *policies;
+ bitarray_t *countries;
};
/** Return a new empty routerset. */
@@ -4718,9 +4719,62 @@
result->names = strmap_new();
result->digests = digestmap_new();
result->policies = smartlist_create();
+ result->countries = bitarray_init_zero(geoip_get_n_countries());
return result;
}
+/** Add the GeoIP database's integer index (+1) of a valid two-character
+ * country code to the routerset's <b>countries</b> bitarray. Return the
+ * integer index if the country code is valid, zero otherwise.*/
+static int
+routerset_add_country(const char *c)
+{
+ char *country;
+ int cc;
+
+ /* XXXX: Country codes must be of the form \{[a-z\?]{2}\} but this accepts
+ \{[.]{2}\}. Do we need to be strict? */
+ if (!(*c=='{') || !(*(c+3)=='}'))
+ return 0;
+
+ if (!geoip_is_loaded()) {
+ log(LOG_WARN, LD_CONFIG, "GeoIP Database Not Loaded: Cannot add country"
+ "entry %s, ignoring.", c);
+ return 0;
+ }
+
+ country=tor_strdup(c);
+ tor_strstrip(country,"}");
+ tor_strstrip(country,"{");
+ tor_strlower(country);
+
+ if ((cc=geoip_is_valid_country(country))==-1) {
+ log(LOG_WARN, LD_CONFIG, "Country Code '%s' is not valid, ignoring.",
+ country);
+ }
+ tor_free(country);
+ return cc;
+}
+
+/** Update the routerset's <b>countries</b> bitarray_t. Called whenever
+ * the GeoIP database is reloaded.
+ */
+void
+routerset_refresh_countries(routerset_t *target)
+{
+ int cc;
+ /* XXX: Is this the proper check for a bitarray_t that is empty? */
+ if (!target->countries)
+ return;
+ bitarray_free(target->countries);
+ target->countries=bitarray_init_zero(geoip_get_n_countries());
+ SMARTLIST_FOREACH(target->list, char *, nick, {
+ if ((cc=routerset_add_country(nick))!=-1) {
+ bitarray_set(target->countries,cc);
+ }
+ });
+}
+
/** 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.
@@ -4732,7 +4786,7 @@
int
routerset_parse(routerset_t *target, const char *s, const char *description)
{
- int r = 0;
+ int r = 0,cc=0;
smartlist_t *list = smartlist_create();
smartlist_split_string(list, s, ",",
SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 0);
@@ -4748,6 +4802,10 @@
} 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 ((cc=routerset_add_country(nick))!=-1) {
+ log_debug(LD_CONFIG, "Adding country %s to %s", nick,
+ description);
+ bitarray_set(target->countries,cc);
} else if ((strchr(nick,'.') || strchr(nick, '*')) &&
(p = router_parse_addr_policy_item_from_string(
nick, ADDR_POLICY_REJECT))) {
@@ -4795,6 +4853,10 @@
if (addr && compare_tor_addr_to_addr_policy(addr, orport, set->policies)
== ADDR_POLICY_REJECTED)
return 1;
+ if (addr &&
+ bitarray_is_set(set->countries,
+ geoip_get_country_by_ip(tor_addr_to_ipv4h(addr))))
+ return 1;
return 0;
}
@@ -4856,14 +4918,16 @@
smartlist_add(out, router);
}
});
- if (smartlist_len(routerset->policies)) {
+ if (smartlist_len(routerset->policies) || (routerset->countries)) {
routerlist_t *rl = router_get_routerlist();
SMARTLIST_FOREACH(rl->routers, routerinfo_t *, router,
- if (compare_addr_to_addr_policy(router->addr, router->or_port,
- routerset->policies) == ADDR_POLICY_REJECT) {
+ if ((compare_addr_to_addr_policy(router->addr, router->or_port,
+ routerset->policies) == ADDR_POLICY_REJECT) ||
+ (bitarray_is_set(routerset->countries,
+ geoip_get_country_by_ip(router->addr)))) {
if (!running_only || router->is_running)
smartlist_add(out, router);
- });
+ });
}
}
@@ -4892,6 +4956,29 @@
return smartlist_join_strings(set->list, ",", 0, NULL);
}
+/** Helper: return true iff old and new are both NULL, or both non-NULL
+ * equal routersets. */
+int
+routerset_equal(const routerset_t *old, const routerset_t *new)
+{
+ /* XXXX: This won't work if the names/digests are identical but in a
+ different order. Checking for exact equality would be heavy going,
+ is it worth it? */
+ if (sizeof(old->names) != sizeof(new->names))
+ return 0;
+ if (memcmp(old->names,new->names,sizeof(new->names)))
+ return 0;
+ if (sizeof(old->digests) != sizeof(new->digests))
+ return 0;
+ if (memcmp(old->digests,new->digests,sizeof(new->digests)))
+ return 0;
+ if (sizeof(old->countries) != sizeof(new->countries))
+ return 0;
+ if (memcmp(old->countries,new->countries,sizeof(new->countries)))
+ return 0;
+ return 1;
+}
+
/** Free all storage held in <b>routerset</b>. */
void
routerset_free(routerset_t *routerset)
@@ -4904,7 +4991,7 @@
strmap_free(routerset->names, NULL);
digestmap_free(routerset->digests, NULL);
-
+ bitarray_free(routerset->countries);
tor_free(routerset);
}
Index: src/or/or.h
===================================================================
--- src/or/or.h (revision 16958)
+++ src/or/or.h (working copy)
@@ -1394,7 +1394,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. */
@@ -2090,17 +2091,25 @@
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; /**< Structure containing nicknames,
+ * digests, country codes and IP address
+ * patterns of ORs to consider as exits. */
+ struct routerset_t *EntryNodes;/**< Structure containing nicknames,
+ * digests, country codes and IP address
+ * patterns 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 */
+ struct routerset_t *ExcludeNodes;/**< Structure containing nicknames,
+ * digests, country codes and IP address
+ * patterns of ORs not to use in
+ * circuits. */
+ struct routerset_t *ExcludeExitNodes;/**< Structure containing nicknames,
+ * digests, country codes and IP address
+ * patterns of ORs not to consider as exits.
+ */
/** Union of ExcludeNodes and ExcludeExitNodes */
struct routerset_t *_ExcludeExitNodesUnion;
@@ -3468,6 +3477,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.
@@ -4292,6 +4302,8 @@
void routerset_subtract_routers(smartlist_t *out,
const routerset_t *routerset);
char *routerset_to_string(const routerset_t *routerset);
+void routerset_refresh_countries(routerset_t *target);
+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 16958)
+++ src/or/geoip.c (working copy)
@@ -42,6 +42,23 @@
/** A list of all known geoip_entry_t, sorted by ip_low. */
static smartlist_t *geoip_entries = NULL;
+/** Return the index of the <b>country</b>'s entry in the GeoIP DB
+ * if it is a valid 2-letter country code, otherwise return zero.
+ */
+int
+geoip_is_valid_country(const char *country)
+{
+ void *_idxplus1;
+ intptr_t idx;
+
+ _idxplus1 = strmap_get_lc(country_idxplus1_by_lc_code, country);
+ if (!_idxplus1)
+ return -1;
+
+ idx = ((uintptr_t)_idxplus1)-1;
+ return (int)idx;
+}
+
/** 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/circuitbuild.c
===================================================================
--- src/or/circuitbuild.c (revision 16958)
+++ src/or/circuitbuild.c (working copy)
@@ -1278,7 +1278,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();
@@ -1399,6 +1399,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(const 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. */
@@ -1419,6 +1437,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 */
@@ -1832,7 +1851,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";
@@ -1856,7 +1875,6 @@
control_event_guard(e->nickname, e->identity, "GOOD");
changed = 1;
}
-
return changed;
}
@@ -2346,8 +2364,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();
@@ -2355,7 +2374,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 16958)
+++ 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 16958)
+++ 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