Add country-code support to configured node lists to implement the ever-popular "no exits in Monaco" feature (ExcludeExitNodes {MC}). Also allow country codes and IP ranges in ExitNodes. (EntryNodes needs more work.) Based on code by Robert Hogan. Needs more testing.

svn:r16966
This commit is contained in:
Nick Mathewson 2008-09-25 20:21:35 +00:00
parent b2c7090da6
commit 8bbbbaf87b
10 changed files with 406 additions and 92 deletions

View File

@ -11,6 +11,11 @@ Changes in version 0.2.1.6-alpha - 2008-09-xx
(i.e. new default value for HidServDirectoryV2 is 1). This is the (i.e. new default value for HidServDirectoryV2 is 1). This is the
last step in proposal 114, which aims to make hidden service last step in proposal 114, which aims to make hidden service
connections more reliable. connections more reliable.
- Allow node restrictions to work include country codes. The syntax
to exclude nodes an a country with country code XX is "ExcludeNodes
{XX}". Patch from Robert Hogan.
- Allow ExitNodes list to include IP ranges and country codes, just like
the Exclude*Nodes lists. Patch from Robert Hogan.
o Major bugfixes: o Major bugfixes:
- Fix a bug when parsing ports in tor_addr_port_parse() that caused - Fix a bug when parsing ports in tor_addr_port_parse() that caused

View File

@ -72,8 +72,8 @@ for $fn (@ARGV) {
# print " //:$fn:$.\n"; # print " //:$fn:$.\n";
s!//.*!!; s!//.*!!;
} }
## Warn about braces preceded by non-space. ## Warn about unquoted braces preceded by non-space.
if (/([^\s])\{/) { if (/([^\s'])\{/) {
print " $1\{:$fn:$.\n"; print " $1\{:$fn:$.\n";
} }
## Warn about multiple internal spaces. ## Warn about multiple internal spaces.

View File

@ -422,28 +422,28 @@ you are reliable and high-bandwidth enough to be a useful server.)
.LP .LP
.TP .TP
\fBExcludeNodes \fR\fInode\fR,\fInode\fR,\fI...\fP \fBExcludeNodes \fR\fInode\fR,\fInode\fR,\fI...\fP
A list of identity fingerprints, nicknames, and address patterns of A list of identity fingerprints, nicknames, country codes and address patterns
nodes to never use when building a circuit. (Example: ExcludeNodes of nodes to never use when building a circuit. (Example: ExcludeNodes
SlowServer, $ABCDEFFFFFFFFFFFFFFF, 255.254.0.0/8) SlowServer, $ABCDEFFFFFFFFFFFFFFF, {cc}, 255.254.0.0/8)
.LP .LP
.TP .TP
\fBExcludeExitNodes \fR\fInode\fR,\fInode\fR,\fI...\fP \fBExcludeExitNodes \fR\fInode\fR,\fInode\fR,\fI...\fP
A list of identity fingerprints, nicknames, and address patterns of A list of identity fingerprints, nicknames, country codes and address patterns
nodes to never use when picking an exit node. Note that any node 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 listed in ExcludeNodes is automatically considered to be part of this
list. list.
.LP .LP
.TP .TP
\fBEntryNodes \fR\fInode\fR,\fInode\fR,\fI...\fP \fBEntryNodes \fR\fInode\fR,\fInode\fR,\fI...\fP
A list of identity fingerprints or nicknames of preferred nodes to use for the A list of identity fingerprints, nicknames, country codes and address patterns
first hop in the circuit. of nodes to use for the first hop in the circuit.
These are treated only as preferences unless StrictEntryNodes (see These are treated only as preferences unless StrictEntryNodes (see
below) is also set. below) is also set.
.LP .LP
.TP .TP
\fBExitNodes \fR\fInode\fR,\fInode\fR,\fI...\fP \fBExitNodes \fR\fInode\fR,\fInode\fR,\fI...\fP
A list of identity fingerprints or nicknames of preferred nodes to use for the A list of identity fingerprints, nicknames, country codes and address patterns
last hop in the circuit. of nodes to use for the last hop in the circuit.
These are treated only as preferences unless StrictExitNodes (see These are treated only as preferences unless StrictExitNodes (see
below) is also set. below) is also set.
.LP .LP

View File

@ -1192,7 +1192,6 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime,
smartlist_t *connections; smartlist_t *connections;
int best_support = -1; int best_support = -1;
int n_best_support=0; int n_best_support=0;
smartlist_t *sl, *preferredexits;
routerinfo_t *router; routerinfo_t *router;
or_options_t *options = get_options(); or_options_t *options = get_options();
@ -1277,22 +1276,24 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime,
n_best_support, best_support >= 0 ? best_support : 0, n_best_support, best_support >= 0 ? best_support : 0,
n_pending_connections); n_pending_connections);
preferredexits = smartlist_create();
add_nickname_list_to_smartlist(preferredexits,options->ExitNodes,1);
sl = smartlist_create();
/* If any routers definitely support any pending connections, choose one /* If any routers definitely support any pending connections, choose one
* at random. */ * at random. */
if (best_support > 0) { if (best_support > 0) {
smartlist_t *supporting = smartlist_create(), *use = smartlist_create();
for (i = 0; i < smartlist_len(dir->routers); i++) for (i = 0; i < smartlist_len(dir->routers); i++)
if (n_supported[i] == best_support) if (n_supported[i] == best_support)
smartlist_add(sl, smartlist_get(dir->routers, i)); smartlist_add(supporting, smartlist_get(dir->routers, i));
routerset_subtract_routers(sl,options->_ExcludeExitNodesUnion); routersets_get_disjunction(use, supporting, options->ExitNodes,
if (options->StrictExitNodes || smartlist_overlap(sl,preferredexits)) options->_ExcludeExitNodesUnion, 1);
smartlist_intersect(sl,preferredexits); if (smartlist_len(use) == 0 && !options->StrictExitNodes) {
router = routerlist_sl_choose_by_bandwidth(sl, WEIGHT_FOR_EXIT); routersets_get_disjunction(use, supporting, NULL,
options->_ExcludeExitNodesUnion, 1);
}
router = routerlist_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT);
smartlist_free(use);
smartlist_free(supporting);
} else { } else {
/* Either there are no pending connections, or no routers even seem to /* Either there are no pending connections, or no routers even seem to
* possibly support any of them. Choose a router at random that satisfies * possibly support any of them. Choose a router at random that satisfies
@ -1300,6 +1301,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime,
int try; int try;
smartlist_t *needed_ports; smartlist_t *needed_ports;
smartlist_t *supporting = smartlist_create(), *use = smartlist_create();
if (best_support == -1) { if (best_support == -1) {
if (need_uptime || need_capacity) { if (need_uptime || need_capacity) {
@ -1308,8 +1310,6 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime,
"to list of all routers.", "to list of all routers.",
need_capacity?", fast":"", need_capacity?", fast":"",
need_uptime?", stable":""); need_uptime?", stable":"");
smartlist_free(preferredexits);
smartlist_free(sl);
tor_free(n_supported); tor_free(n_supported);
return choose_good_exit_server_general(dir, 0, 0); return choose_good_exit_server_general(dir, 0, 0);
} }
@ -1326,25 +1326,30 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime,
(try || router_handles_some_port(router, needed_ports))) { (try || router_handles_some_port(router, needed_ports))) {
// log_fn(LOG_DEBUG,"Try %d: '%s' is a possibility.", // log_fn(LOG_DEBUG,"Try %d: '%s' is a possibility.",
// try, router->nickname); // try, router->nickname);
smartlist_add(sl, router); smartlist_add(supporting, router);
} }
} }
routerset_subtract_routers(sl,options->_ExcludeExitNodesUnion); routersets_get_disjunction(use, supporting, options->ExitNodes,
if (options->StrictExitNodes || smartlist_overlap(sl,preferredexits)) options->_ExcludeExitNodesUnion, 1);
smartlist_intersect(sl,preferredexits); if (smartlist_len(use) == 0 && !options->StrictExitNodes) {
/* XXX sometimes the above results in null, when the requested routersets_get_disjunction(use, supporting, NULL,
* exit node is down. we should pick it anyway. */ options->_ExcludeExitNodesUnion, 1);
router = routerlist_sl_choose_by_bandwidth(sl, WEIGHT_FOR_EXIT); }
/* XXX sometimes the above results in null, when the requested
* exit node is down. we should pick it anyway. */
router = routerlist_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT);
if (router) if (router)
break; break;
smartlist_clear(supporting);
smartlist_clear(use);
} }
SMARTLIST_FOREACH(needed_ports, uint16_t *, cp, tor_free(cp)); SMARTLIST_FOREACH(needed_ports, uint16_t *, cp, tor_free(cp));
smartlist_free(needed_ports); smartlist_free(needed_ports);
smartlist_free(use);
smartlist_free(supporting);
} }
smartlist_free(preferredexits);
smartlist_free(sl);
tor_free(n_supported); tor_free(n_supported);
if (router) { if (router) {
log_info(LD_CIRC, "Chose exit server '%s'", router->nickname); log_info(LD_CIRC, "Chose exit server '%s'", router->nickname);
@ -1399,6 +1404,24 @@ choose_good_exit_server(uint8_t purpose, routerlist_t *dir,
return NULL; 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 /** Decide a suitable length for circ's cpath, and pick an exit
* router (or use <b>exit</b> if provided). Store these in the * router (or use <b>exit</b> if provided). Store these in the
* cpath. Return 0 if ok, -1 if circuit should be closed. */ * cpath. Return 0 if ok, -1 if circuit should be closed. */
@ -1419,6 +1442,7 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit)
} }
if (exit) { /* the circuit-builder pre-requested one */ if (exit) { /* the circuit-builder pre-requested one */
warn_if_router_excluded(exit);
log_info(LD_CIRC,"Using requested exit node '%s'", exit->nickname); log_info(LD_CIRC,"Using requested exit node '%s'", exit->nickname);
exit = extend_info_dup(exit); exit = extend_info_dup(exit);
} else { /* we have to decide one */ } else { /* we have to decide one */
@ -1832,7 +1856,7 @@ entry_guard_set_status(entry_guard_t *e, routerinfo_t *ri,
else if (options->UseBridges && ri->purpose != ROUTER_PURPOSE_BRIDGE) else if (options->UseBridges && ri->purpose != ROUTER_PURPOSE_BRIDGE)
*reason = "not a bridge"; *reason = "not a bridge";
else if (!options->UseBridges && !ri->is_possible_guard && 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"; *reason = "not recommended as a guard";
else if (routerset_contains_router(options->ExcludeNodes, ri)) else if (routerset_contains_router(options->ExcludeNodes, ri))
*reason = "excluded"; *reason = "excluded";
@ -1856,7 +1880,6 @@ entry_guard_set_status(entry_guard_t *e, routerinfo_t *ri,
control_event_guard(e->nickname, e->identity, "GOOD"); control_event_guard(e->nickname, e->identity, "GOOD");
changed = 1; changed = 1;
} }
return changed; return changed;
} }
@ -2346,8 +2369,9 @@ entry_guards_prepend_from_config(void)
return; return;
} }
log_info(LD_CIRC,"Adding configured EntryNodes '%s'.", if (options->EntryNodes)
options->EntryNodes); log_info(LD_CIRC,"Adding configured EntryNodes '%s'.",
routerset_to_string(options->EntryNodes));
entry_routers = smartlist_create(); entry_routers = smartlist_create();
entry_fps = smartlist_create(); entry_fps = smartlist_create();
@ -2355,7 +2379,11 @@ entry_guards_prepend_from_config(void)
old_entry_guards_not_on_list = smartlist_create(); old_entry_guards_not_on_list = smartlist_create();
/* Split entry guards into those on the list and those not. */ /* Split entry guards into those on the list and those not. */
add_nickname_list_to_smartlist(entry_routers, options->EntryNodes, 0); /* XXXX021 Now that we allow countries and IP ranges in EntryNodes, this is
* potentially an enormous list. For now, we disable such values for
* EntryNodes in options_validate(); really, this wants a better solution.
*/
routerset_get_all_routers(entry_routers, options->EntryNodes, 0);
SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri,
smartlist_add(entry_fps,ri->cache_info.identity_digest)); smartlist_add(entry_fps,ri->cache_info.identity_digest));
SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, { SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, {

View File

@ -194,11 +194,11 @@ static config_var_t _option_vars[] = {
V(DNSListenAddress, LINELIST, NULL), V(DNSListenAddress, LINELIST, NULL),
V(DownloadExtraInfo, BOOL, "0"), V(DownloadExtraInfo, BOOL, "0"),
V(EnforceDistinctSubnets, BOOL, "1"), V(EnforceDistinctSubnets, BOOL, "1"),
V(EntryNodes, STRING, NULL), V(EntryNodes, ROUTERSET, NULL),
V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "10 minutes"), V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "10 minutes"),
V(ExcludeNodes, ROUTERSET, NULL), V(ExcludeNodes, ROUTERSET, NULL),
V(ExcludeExitNodes, ROUTERSET, NULL), V(ExcludeExitNodes, ROUTERSET, NULL),
V(ExitNodes, STRING, NULL), V(ExitNodes, ROUTERSET, NULL),
V(ExitPolicy, LINELIST, NULL), V(ExitPolicy, LINELIST, NULL),
V(ExitPolicyRejectPrivate, BOOL, "1"), V(ExitPolicyRejectPrivate, BOOL, "1"),
V(FallbackNetworkstatusFile, FILENAME, V(FallbackNetworkstatusFile, FILENAME,
@ -817,13 +817,23 @@ get_version(void)
return _version; return _version;
} }
/** Release additional memory allocated in options
*/
static void
or_options_free(or_options_t *options)
{
if (options->_ExcludeExitNodesUnion)
routerset_free(options->_ExcludeExitNodesUnion);
config_free(&options_format, options);
}
/** Release all memory and resources held by global configuration structures. /** Release all memory and resources held by global configuration structures.
*/ */
void void
config_free_all(void) config_free_all(void)
{ {
if (global_options) { if (global_options) {
config_free(&options_format, global_options); or_options_free(global_options);
global_options = NULL; global_options = NULL;
} }
if (global_state) { if (global_state) {
@ -1322,8 +1332,9 @@ options_act(or_options_t *old_options)
if (options->GeoIPFile && if (options->GeoIPFile &&
((!old_options || !opt_streq(old_options->GeoIPFile, options->GeoIPFile)) ((!old_options || !opt_streq(old_options->GeoIPFile, options->GeoIPFile))
|| !geoip_is_loaded())) { || !geoip_is_loaded())) {
/* XXXX021 Don't use this "<default>" junk; make our filename options /** XXXX021 Don't use this "<default>" junk; make our filename options
* understand prefixes somehow. -NM */ * understand prefixes somehow. -NM */
/** XXXX021 Reload GeoIPFile on SIGHUP. -NM */
char *actual_fname = tor_strdup(options->GeoIPFile); char *actual_fname = tor_strdup(options->GeoIPFile);
#ifdef WIN32 #ifdef WIN32
if (!strcmp(actual_fname, "<default>")) { if (!strcmp(actual_fname, "<default>")) {
@ -1336,11 +1347,22 @@ options_act(or_options_t *old_options)
#endif #endif
geoip_load_file(actual_fname, options); geoip_load_file(actual_fname, options);
tor_free(actual_fname); 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);
routerlist_refresh_countries();
} }
/* Check if we need to parse and add the EntryNodes config option. */ /* Check if we need to parse and add the EntryNodes config option. */
if (options->EntryNodes && if (options->EntryNodes &&
(!old_options || (!old_options ||
!opt_streq(old_options->EntryNodes, options->EntryNodes))) (!routerset_equal(old_options->EntryNodes,options->EntryNodes))))
entry_nodes_should_be_added(); entry_nodes_should_be_added();
/* Since our options changed, we might need to regenerate and upload our /* Since our options changed, we might need to regenerate and upload our
@ -1701,7 +1723,6 @@ config_assign_value(config_format_t *fmt, or_options_t *options,
case CONFIG_TYPE_LINELIST_S: case CONFIG_TYPE_LINELIST_S:
config_line_append((config_line_t**)lvalue, c->key, c->value); config_line_append((config_line_t**)lvalue, c->key, c->value);
break; break;
case CONFIG_TYPE_OBSOLETE: case CONFIG_TYPE_OBSOLETE:
log_warn(LD_CONFIG, "Skipping obsolete configuration option '%s'", c->key); log_warn(LD_CONFIG, "Skipping obsolete configuration option '%s'", c->key);
break; break;
@ -2964,19 +2985,24 @@ options_validate(or_options_t *old_options, or_options_t *options,
} }
if (options->StrictExitNodes && if (options->StrictExitNodes &&
(!options->ExitNodes || !strlen(options->ExitNodes)) && (!options->ExitNodes) &&
(!old_options || (!old_options ||
(old_options->StrictExitNodes != options->StrictExitNodes) || (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."); COMPLAIN("StrictExitNodes set, but no ExitNodes listed.");
if (options->StrictEntryNodes && if (options->StrictEntryNodes &&
(!options->EntryNodes || !strlen(options->EntryNodes)) && (!options->EntryNodes) &&
(!old_options || (!old_options ||
(old_options->StrictEntryNodes != options->StrictEntryNodes) || (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."); COMPLAIN("StrictEntryNodes set, but no EntryNodes listed.");
if (options->EntryNodes && !routerset_is_list(options->EntryNodes)) {
/** XXXX021 fix this; see entry_guards_prepend_from_config(). */
REJECT("IPs or countries are not yet supported in EntryNodes.");
}
if (options->AuthoritativeDir) { if (options->AuthoritativeDir) {
if (!options->ContactInfo) if (!options->ContactInfo)
REJECT("Authoritative directory servers must set ContactInfo"); REJECT("Authoritative directory servers must set ContactInfo");
@ -3334,10 +3360,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
if (options->UseEntryGuards && ! options->NumEntryGuards) if (options->UseEntryGuards && ! options->NumEntryGuards)
REJECT("Cannot enable UseEntryGuards with NumEntryGuards set to 0"); 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)) if (check_nickname_list(options->MyFamily, "MyFamily", msg))
return -1; return -1;
for (cl = options->NodeFamilies; cl; cl = cl->next) { for (cl = options->NodeFamilies; cl; cl = cl->next) {

View File

@ -42,6 +42,23 @@ static strmap_t *country_idxplus1_by_lc_code = NULL;
/** A list of all known geoip_entry_t, sorted by ip_low. */ /** A list of all known geoip_entry_t, sorted by ip_low. */
static smartlist_t *geoip_entries = NULL; 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.
*/
country_t
geoip_get_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 (country_t)idx;
}
/** Add an entry to the GeoIP table, mapping all IPs between <b>low</b> and /** 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>. * <b>high</b>, inclusive, to the 2-letter country code <b>country</b>.
*/ */
@ -167,9 +184,15 @@ geoip_load_file(const char *filename, or_options_t *options)
log_fn(severity, LD_GENERAL, "Failed to open GEOIP file %s.", filename); log_fn(severity, LD_GENERAL, "Failed to open GEOIP file %s.", filename);
return -1; return -1;
} }
geoip_countries = smartlist_create(); if (!geoip_countries) {
geoip_countries = smartlist_create();
country_idxplus1_by_lc_code = strmap_new();
}
if (geoip_entries) {
SMARTLIST_FOREACH(geoip_entries, geoip_entry_t *, e, tor_free(e));
smartlist_free(geoip_entries);
}
geoip_entries = smartlist_create(); geoip_entries = smartlist_create();
country_idxplus1_by_lc_code = strmap_new();
log_info(LD_GENERAL, "Parsing GEOIP file."); log_info(LD_GENERAL, "Parsing GEOIP file.");
while (!feof(f)) { while (!feof(f)) {
char buf[512]; char buf[512];
@ -210,7 +233,7 @@ geoip_get_n_countries(void)
/** Return the two-letter country code associated with the number <b>num</b>, /** Return the two-letter country code associated with the number <b>num</b>,
* or "??" for an unknown value. */ * or "??" for an unknown value. */
const char * const char *
geoip_get_country_name(int num) geoip_get_country_name(country_t num)
{ {
if (geoip_countries && num >= 0 && num < smartlist_len(geoip_countries)) { if (geoip_countries && num >= 0 && num < smartlist_len(geoip_countries)) {
geoip_country_t *c = smartlist_get(geoip_countries, num); geoip_country_t *c = smartlist_get(geoip_countries, num);

View File

@ -1321,6 +1321,9 @@ typedef struct signed_descriptor_t {
unsigned int send_unencrypted : 1; unsigned int send_unencrypted : 1;
} signed_descriptor_t; } signed_descriptor_t;
/** A signed integer representing a country code. */
typedef int16_t country_t;
/** Information about another onion router in the network. */ /** Information about another onion router in the network. */
typedef struct { typedef struct {
signed_descriptor_t cache_info; signed_descriptor_t cache_info;
@ -1394,7 +1397,8 @@ typedef struct {
time_t last_reachable; time_t last_reachable;
/** When did we start testing reachability for this OR? */ /** When did we start testing reachability for this OR? */
time_t testing_since; time_t testing_since;
/** According to the geoip db what country is this router in? */
country_t country;
} routerinfo_t; } routerinfo_t;
/** Information needed to keep and cache a signed extra-info document. */ /** Information needed to keep and cache a signed extra-info document. */
@ -2070,6 +2074,8 @@ typedef struct config_line_t {
struct config_line_t *next; struct config_line_t *next;
} config_line_t; } config_line_t;
typedef struct routerset_t routerset_t;
/** Configuration options for a Tor process. */ /** Configuration options for a Tor process. */
typedef struct { typedef struct {
uint32_t _magic; uint32_t _magic;
@ -2090,17 +2096,22 @@ typedef struct {
char *Address; /**< OR only: configured address for this onion router. */ char *Address; /**< OR only: configured address for this onion router. */
char *PidFile; /**< Where to store PID of Tor process. */ char *PidFile; /**< Where to store PID of Tor process. */
char *ExitNodes; /**< Comma-separated list of nicknames of ORs to consider routerset_t *ExitNodes; /**< Structure containing nicknames, digests,
* as exits. */ * country codes and IP address patterns of ORs to
char *EntryNodes; /**< Comma-separated list of nicknames of ORs to consider * consider as exits. */
* as entry points. */ 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 int StrictExitNodes; /**< Boolean: When none of our ExitNodes are up, do we
* stop building circuits? */ * stop building circuits? */
int StrictEntryNodes; /**< Boolean: When none of our EntryNodes are up, do we int StrictEntryNodes; /**< Boolean: When none of our EntryNodes are up, do we
* stop building circuits? */ * stop building circuits? */
struct routerset_t *ExcludeNodes; /**< Comma-separated list of nicknames of routerset_t *ExcludeNodes;/**< Structure containing nicknames, digests,
* ORs not to use in circuits. */ * country codes and IP address patterns of ORs
struct routerset_t *ExcludeExitNodes; /**<DODOC */ * not to use in circuits. */
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 */ /** Union of ExcludeNodes and ExcludeExitNodes */
struct routerset_t *_ExcludeExitNodesUnion; struct routerset_t *_ExcludeExitNodesUnion;
@ -3466,8 +3477,9 @@ int should_record_bridge_info(or_options_t *options);
int geoip_load_file(const char *filename, or_options_t *options); int geoip_load_file(const char *filename, or_options_t *options);
int geoip_get_country_by_ip(uint32_t ipaddr); int geoip_get_country_by_ip(uint32_t ipaddr);
int geoip_get_n_countries(void); int geoip_get_n_countries(void);
const char *geoip_get_country_name(int num); const char *geoip_get_country_name(country_t num);
int geoip_is_loaded(void); int geoip_is_loaded(void);
country_t geoip_get_country(const char *countrycode);
/** Indicates an action that we might be noting geoip statistics on. /** 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 * Note that if we're noticing CONNECT, we're a bridge, and if we're noticing
* the others, we're not. * the others, we're not.
@ -4277,22 +4289,28 @@ void routerlist_assert_ok(routerlist_t *rl);
const char *esc_router_info(routerinfo_t *router); const char *esc_router_info(routerinfo_t *router);
void routers_sort_by_identity(smartlist_t *routers); void routers_sort_by_identity(smartlist_t *routers);
typedef struct routerset_t routerset_t;
routerset_t *routerset_new(void); routerset_t *routerset_new(void);
int routerset_parse(routerset_t *target, const char *s, int routerset_parse(routerset_t *target, const char *s,
const char *description); const char *description);
void routerset_union(routerset_t *target, const routerset_t *source); void routerset_union(routerset_t *target, const routerset_t *source);
int routerset_is_list(const routerset_t *set);
int routerset_contains_router(const routerset_t *set, routerinfo_t *ri); int routerset_contains_router(const routerset_t *set, routerinfo_t *ri);
int routerset_contains_routerstatus(const routerset_t *set, int routerset_contains_routerstatus(const routerset_t *set,
routerstatus_t *rs); routerstatus_t *rs);
int routerset_contains_extendinfo(const routerset_t *set, extend_info_t *ei); int routerset_contains_extendinfo(const routerset_t *set, extend_info_t *ei);
void routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, void routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset,
int running_only); int running_only);
void routersets_get_disjunction(smartlist_t *target, const smartlist_t *source,
const routerset_t *include,
const routerset_t *exclude, int running_only);
void routerset_subtract_routers(smartlist_t *out, void routerset_subtract_routers(smartlist_t *out,
const routerset_t *routerset); const routerset_t *routerset);
char *routerset_to_string(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); void routerset_free(routerset_t *routerset);
void routerinfo_set_country(routerinfo_t *ri);
void routerlist_refresh_countries(void);
int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs,
const char *id); const char *id);

View File

@ -1403,6 +1403,8 @@ router_rebuild_descriptor(int force)
router_get_router_hash(ri->cache_info.signed_descriptor_body, router_get_router_hash(ri->cache_info.signed_descriptor_body,
ri->cache_info.signed_descriptor_digest); ri->cache_info.signed_descriptor_digest);
routerinfo_set_country(ri);
tor_assert(! routerinfo_incompatible_with_extrainfo(ri, ei, NULL, NULL)); tor_assert(! routerinfo_incompatible_with_extrainfo(ri, ei, NULL, NULL));
if (desc_routerinfo) if (desc_routerinfo)

View File

@ -4707,6 +4707,14 @@ struct routerset_t {
/** An address policy for routers in the set. For implementation reasons, /** An address policy for routers in the set. For implementation reasons,
* a router belongs to the set if it is _rejected_ by this policy. */ * a router belongs to the set if it is _rejected_ by this policy. */
smartlist_t *policies; smartlist_t *policies;
/** DOCDOC */
char *description;
/** DOCDOC */
smartlist_t *country_names;
int n_countries;
bitarray_t *countries;
}; };
/** Return a new empty routerset. */ /** Return a new empty routerset. */
@ -4718,9 +4726,85 @@ routerset_new(void)
result->names = strmap_new(); result->names = strmap_new();
result->digests = digestmap_new(); result->digests = digestmap_new();
result->policies = smartlist_create(); result->policies = smartlist_create();
result->country_names = smartlist_create();
return result; return result;
} }
/** DOCDOC */
static char *
routerset_get_countryname(const char *c)
{
char *country;
if (strlen(c) < 4 || c[0] !='{' || c[3] !='}')
return NULL;
country = tor_strndup(c+1, 2);
tor_strlower(country);
return country;
}
#if 0
/** 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, -1 otherwise.*/
static int
routerset_add_country(const char *c)
{
char country[3];
country_t cc;
/* XXXX: Country codes must be of the form \{[a-z\?]{2}\} but this accepts
\{[.]{2}\}. Do we need to be strict? -RH */
/* Nope; if the country code is bad, we'll get 0 when we look it up. */
if (!geoip_is_loaded()) {
log(LOG_WARN, LD_CONFIG, "GeoIP database not loaded: Cannot add country"
"entry %s, ignoring.", c);
return -1;
}
memcpy(country, c+1, 2);
country[2] = '\0';
tor_strlower(country);
if ((cc=geoip_get_country(country))==-1) {
log(LOG_WARN, LD_CONFIG, "Country code '%s' is not valid, ignoring.",
country);
}
return cc;
}
#endif
/** 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;
if (target->countries) {
bitarray_free(target->countries);
}
if (!geoip_is_loaded()) {
target->countries = NULL;
target->n_countries = 0;
return;
}
target->n_countries = geoip_get_n_countries();
target->countries = bitarray_init_zero(target->n_countries);
SMARTLIST_FOREACH_BEGIN(target->country_names, const char *, country) {
cc = geoip_get_country(country);
if (cc >= 0) {
tor_assert(cc < target->n_countries);
bitarray_set(target->countries, cc);
} else {
log(LOG_WARN, LD_CONFIG, "Country code '%s' is not recognized.",
country);
}
} SMARTLIST_FOREACH_END(country);
}
/** Parse the string <b>s</b> to create a set of routerset entries, and add /** 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 * them to <b>target</b>. In log messages, refer to the string as
* <b>description</b>. Return 0 on success, -1 on failure. * <b>description</b>. Return 0 on success, -1 on failure.
@ -4733,10 +4817,12 @@ int
routerset_parse(routerset_t *target, const char *s, const char *description) routerset_parse(routerset_t *target, const char *s, const char *description)
{ {
int r = 0; int r = 0;
int added_countries = 0;
char *countryname;
smartlist_t *list = smartlist_create(); smartlist_t *list = smartlist_create();
smartlist_split_string(list, s, ",", smartlist_split_string(list, s, ",",
SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 0); SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 0);
SMARTLIST_FOREACH(list, char *, nick, { SMARTLIST_FOREACH_BEGIN(list, char *, nick) {
addr_policy_t *p; addr_policy_t *p;
if (is_legal_hexdigest(nick)) { if (is_legal_hexdigest(nick)) {
char d[DIGEST_LEN]; char d[DIGEST_LEN];
@ -4748,21 +4834,28 @@ routerset_parse(routerset_t *target, const char *s, const char *description)
} else if (is_legal_nickname(nick)) { } else if (is_legal_nickname(nick)) {
log_debug(LD_CONFIG, "Adding nickname %s to %s", nick, description); log_debug(LD_CONFIG, "Adding nickname %s to %s", nick, description);
strmap_set_lc(target->names, nick, (void*)1); strmap_set_lc(target->names, nick, (void*)1);
} else if ((countryname = routerset_get_countryname(nick)) != NULL) {
log_debug(LD_CONFIG, "Adding country %s to %s", nick,
description);
smartlist_add(target->country_names, countryname);
added_countries = 1;
} else if ((strchr(nick,'.') || strchr(nick, '*')) && } else if ((strchr(nick,'.') || strchr(nick, '*')) &&
(p = router_parse_addr_policy_item_from_string( (p = router_parse_addr_policy_item_from_string(
nick, ADDR_POLICY_REJECT))) { nick, ADDR_POLICY_REJECT))) {
log_debug(LD_CONFIG, "Adding address %s to %s", nick, description); log_debug(LD_CONFIG, "Adding address %s to %s", nick, description);
smartlist_add(target->policies, p); smartlist_add(target->policies, p);
} else { } else {
log_warn(LD_CONFIG, "Nickname '%s' in %s is misformed.", nick, log_warn(LD_CONFIG, "Entry '%s' in %s is misformed.", nick,
description); description);
r = -1; r = -1;
tor_free(nick); tor_free(nick);
SMARTLIST_DEL_CURRENT(list, nick); SMARTLIST_DEL_CURRENT(list, nick);
} }
}); } SMARTLIST_FOREACH_END(nick);
smartlist_add_all(target->list, list); smartlist_add_all(target->list, list);
smartlist_free(list); smartlist_free(list);
if (added_countries)
routerset_refresh_countries(target);
return r; return r;
} }
@ -4779,22 +4872,48 @@ routerset_union(routerset_t *target, const routerset_t *source)
tor_free(s); tor_free(s);
} }
/** Return true iff <b>set</b> lists only nicknames and digests, and includes
* no IP ranges or countries. */
int
routerset_is_list(const routerset_t *set)
{
return smartlist_len(set->country_names) == 0 &&
smartlist_len(set->policies) == 0;
}
/** DOCDOC */
static int
routerset_is_empty(const routerset_t *set)
{
return !set || smartlist_len(set->list) == 0;
}
/** Helper. Return true iff <b>set</b> contains a router based on the other /** Helper. Return true iff <b>set</b> contains a router based on the other
* provided fields. */ * provided fields. Return higher values for more specific subentries.
(If country is -1, then we take the country from addr.) */
static int static int
routerset_contains(const routerset_t *set, const tor_addr_t *addr, routerset_contains(const routerset_t *set, const tor_addr_t *addr,
uint16_t orport, uint16_t orport,
const char *nickname, const char *id_digest, int is_named) const char *nickname, const char *id_digest, int is_named,
country_t country)
{ {
if (!set || !set->list) return 0; if (!set || !set->list) return 0;
(void) is_named; /* not supported */ (void) is_named; /* not supported */
if (nickname && strmap_get_lc(set->names, nickname)) if (nickname && strmap_get_lc(set->names, nickname))
return 1; return 4;
if (id_digest && digestmap_get(set->digests, id_digest)) if (id_digest && digestmap_get(set->digests, id_digest))
return 1; return 4;
if (addr && compare_tor_addr_to_addr_policy(addr, orport, set->policies) if (addr && compare_tor_addr_to_addr_policy(addr, orport, set->policies)
== ADDR_POLICY_REJECTED) == ADDR_POLICY_REJECTED)
return 1; return 3;
if (set->countries) {
if (country < 0 && addr)
country = geoip_get_country_by_ip(tor_addr_to_ipv4h(addr));
if (country >= 0 && country < set->n_countries &&
bitarray_is_set(set->countries, country))
return 2;
}
return 0; return 0;
} }
@ -4807,7 +4926,8 @@ routerset_contains_extendinfo(const routerset_t *set, extend_info_t *ei)
ei->port, ei->port,
ei->nickname, ei->nickname,
ei->identity_digest, ei->identity_digest,
-1); -1, /*is_named*/
-1 /*country*/);
} }
/** Return true iff <b>ri</b> is in <b>set</b>. */ /** Return true iff <b>ri</b> is in <b>set</b>. */
@ -4821,7 +4941,8 @@ routerset_contains_router(const routerset_t *set, routerinfo_t *ri)
ri->or_port, ri->or_port,
ri->nickname, ri->nickname,
ri->cache_info.identity_digest, ri->cache_info.identity_digest,
ri->is_named); ri->is_named,
ri->country);
} }
/** Return true iff <b>rs</b> is in <b>set</b>. */ /** Return true iff <b>rs</b> is in <b>set</b>. */
@ -4835,7 +4956,8 @@ routerset_contains_routerstatus(const routerset_t *set, routerstatus_t *rs)
rs->or_port, rs->or_port,
rs->nickname, rs->nickname,
rs->identity_digest, rs->identity_digest,
rs->is_named); rs->is_named,
-1);
} }
/** Add every known routerinfo_t that is a member of <b>routerset</b> to /** Add every known routerinfo_t that is a member of <b>routerset</b> to
@ -4849,22 +4971,54 @@ routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset,
return; return;
if (!warned_nicknames) if (!warned_nicknames)
warned_nicknames = smartlist_create(); warned_nicknames = smartlist_create();
SMARTLIST_FOREACH(routerset->list, const char *, name, { if (routerset_is_list(routerset)) {
routerinfo_t *router = router_get_by_nickname(name, 1);
if (router) { /* No routers are specified by type; all are given by name or digest.
if (!running_only || router->is_running) * we can do a lookup in O(len(list)). */
smartlist_add(out, router); SMARTLIST_FOREACH(routerset->list, const char *, name, {
routerinfo_t *router = router_get_by_nickname(name, 1);
if (router) {
if (!running_only || router->is_running)
smartlist_add(out, router);
}
});
} else {
/* We need to iterate over the routerlist to get all the ones of the
* right kind. */
routerlist_t *rl = router_get_routerlist();
SMARTLIST_FOREACH(rl->routers, routerinfo_t *, router, {
if (running_only && !router->is_running)
continue;
if (routerset_contains_router(routerset, router))
smartlist_add(out, router);
});
}
}
/** Add to <b>target</b> every node from <b>source</b> that is in
* <b>include</b> not excluded in a more specific fashion by
* <b>exclude</b>. DOCDOC */
void
routersets_get_disjunction(smartlist_t *target,
const smartlist_t *source,
const routerset_t *include,
const routerset_t *exclude, int running_only)
{
SMARTLIST_FOREACH(source, routerinfo_t *, router, {
int include_result;
if (running_only && !router->is_running)
continue;
if (!routerset_is_empty(include))
include_result = routerset_contains_router(include, router);
else
include_result = 1;
if (include_result) {
int exclude_result = routerset_contains_router(exclude, router);
if (include_result >= exclude_result)
smartlist_add(target, router);
} }
}); });
if (smartlist_len(routerset->policies)) {
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 (!running_only || router->is_running)
smartlist_add(out, router);
});
}
} }
/** Remove every routerinfo_t from <b>lst</b> that is in <b>routerset</b>. */ /** Remove every routerinfo_t from <b>lst</b> that is in <b>routerset</b>. */
@ -4892,6 +5046,46 @@ routerset_to_string(const routerset_t *set)
return smartlist_join_strings(set->list, ",", 0, NULL); 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)
{
if (smartlist_len(old->list) != smartlist_len(new->list))
return 0;
SMARTLIST_FOREACH(old->list, const char *, cp1, {
const char *cp2 = smartlist_get(new->list, cp1_sl_idx);
if (strcmp(cp1, cp2))
return 0;
});
return 1;
#if 0
/* 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? -RH*/
/* This code is totally bogus; sizeof doesn't work even remotely like this
* code seems to think. Let's revert to a string-based comparison for
* now. -NM*/
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;
#endif
}
/** Free all storage held in <b>routerset</b>. */ /** Free all storage held in <b>routerset</b>. */
void void
routerset_free(routerset_t *routerset) routerset_free(routerset_t *routerset)
@ -4901,13 +5095,32 @@ routerset_free(routerset_t *routerset)
SMARTLIST_FOREACH(routerset->policies, addr_policy_t *, p, SMARTLIST_FOREACH(routerset->policies, addr_policy_t *, p,
addr_policy_free(p)); addr_policy_free(p));
smartlist_free(routerset->policies); smartlist_free(routerset->policies);
SMARTLIST_FOREACH(routerset->country_names, char *, cp, tor_free(cp));
smartlist_free(routerset->country_names);
strmap_free(routerset->names, NULL); strmap_free(routerset->names, NULL);
digestmap_free(routerset->digests, NULL); digestmap_free(routerset->digests, NULL);
if (routerset->countries)
bitarray_free(routerset->countries);
tor_free(routerset); tor_free(routerset);
} }
/** DOCDOC */
void
routerinfo_set_country(routerinfo_t *ri)
{
ri->country = geoip_get_country_by_ip(ri->addr);
}
/** DOCDOC */
void
routerlist_refresh_countries(void)
{
routerlist_t *rl = router_get_routerlist();
SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri,
routerinfo_set_country(ri));
}
/** Determine the routers that are responsible for <b>id</b> (binary) and /** Determine the routers that are responsible for <b>id</b> (binary) and
* add pointers to those routers' routerstatus_t to <b>responsible_dirs</b>. * add pointers to those routers' routerstatus_t to <b>responsible_dirs</b>.
* Return -1 if we're returning an empty smartlist, else return 0. * Return -1 if we're returning an empty smartlist, else return 0.

View File

@ -1184,6 +1184,7 @@ router_parse_entry_from_string(const char *s, const char *end,
tor_assert(tok->n_args >= 5); tor_assert(tok->n_args >= 5);
router = tor_malloc_zero(sizeof(routerinfo_t)); router = tor_malloc_zero(sizeof(routerinfo_t));
router->country = -1;
router->cache_info.routerlist_index = -1; router->cache_info.routerlist_index = -1;
router->cache_info.annotations_len = s-start_of_annotations + prepend_len; router->cache_info.annotations_len = s-start_of_annotations + prepend_len;
router->cache_info.signed_descriptor_len = end-s; router->cache_info.signed_descriptor_len = end-s;
@ -1388,6 +1389,8 @@ router_parse_entry_from_string(const char *s, const char *end,
"router descriptor") < 0) "router descriptor") < 0)
goto err; goto err;
routerinfo_set_country(router);
if (!router->or_port) { if (!router->or_port) {
log_warn(LD_DIR,"or_port unreadable or 0. Failing."); log_warn(LD_DIR,"or_port unreadable or 0. Failing.");
goto err; goto err;