Browse Source

Merge maint-0.2.2 for the bug1090-part1-squashed branch

Resolved conflicts in:
	doc/tor.1.txt
	src/or/circuitbuild.c
	src/or/circuituse.c
	src/or/connection_edge.c
	src/or/connection_edge.h
	src/or/directory.c
	src/or/rendclient.c
	src/or/routerlist.c
	src/or/routerlist.h

These were mostly releated to the routerinfo_t->node_t conversion.
Nick Mathewson 13 years ago
parent
commit
8b686d98c4

+ 73 - 0
changes/bug1090-general

@@ -0,0 +1,73 @@
+  o Major features and bugfixes (node selection)
+
+    - Revise and unify the meaning of the ExitNodes, EntryNodes,
+      ExcludeEntryNodes, ExcludeExitNodes, ExcludeNodes, and
+      StrictNodes options.  Previously, we had been ambiguous in
+      describing what counted as an "exit" node, and what operations
+      exactly "StrictNodes 0" would permit.  This created confusion
+      when people saw nodes built through unexpected circuits, and
+      made it hard to tell real bugs from surprises.  We now stipulate
+      that the intended behavior is:
+
+	. "Exit", in the context of ExitNodes and ExcludeExitNodes,
+	   means a node that delivers user traffic outside the Tor
+	   network.
+	. "Entry", in the context of EntryNodes and ExcludeEntryNodes,
+	  means a node used as the first hop of a multihop circuit:
+	  it doesn't include direct connections to directory servers.
+	. "ExcludeNodes" applies to all nodes.
+	. "StrictNodes" changes the behavior of ExcludeNodes only.
+	  When StrictNodes is set, Tor should avoid all nodes listed
+	  in ExcludeNodes, even when it will make user requests
+	  fail.  When StrictNodes is *not* set, then Tor should
+	  follow ExcludeNodes whenever it can, except when it must
+	  use an excluded node to perform self-tests, connect to a
+	  hidden service, provide a hidden service, fulfill a .exit
+	  request, upload directory information, or fetch directory
+	  information.
+
+      Collectively, the changes to implement the behavior are a fix for
+      bug 1090.
+
+    - ExcludeNodes now takes precedence over EntryNodes and ExitNodes:
+      if a node is listed in both, it's treated as excluded.
+
+    - ExcludeNodes now applies to directory nodes: as a preference if
+      StrictNodes is 0, or an absolute requirement if StrictNodes is 1.
+      (Don't exclude all the directory authorities and set StrictNodes
+      to 1 unless you really want your Tor to break.)
+
+    - ExcludeNodes and ExcludeExitNodes now override exit enclaving.
+
+    - ExcludeExitNodes now overrides .exit requests.
+
+    - We don't use bridges from ExcludeNodes.
+
+    - When StrictNodes is 1:
+       . We now apply ExcludeNodes to hidden service introduction points
+         and to rendezvous points selected by hidden service users.
+         This can make your hidden service less reliable: use it with
+         caution!
+       . If we have used ExcludeNodes on ourself, do not try self-tests.
+       . If we have excluded all the directory authorities, we will
+         not even try to upload our descriptor if we're a server.
+       . Do not honor .exit requests to an excluded node.
+
+    - Remove a misfeature that caused us to ignore the Fast/Stable flags
+      if ExitNodes was set.  Bugfix on 0.2.2.7-alpha.
+
+    - When the set of permitted nodes changes, we now remove any
+      mappings introduced via TrackExitHosts to now-excluded nodes.
+      Bugfix on 0.1.0.1-rc.
+
+    - We never cannibalize a circuit that had excluded nodes on it,
+      even if StrictNodes is 0.  Bugfix on 0.1.0.1-rc.
+
+    - Improve log messages related to excluded nodes.
+
+    - Revert a change where we would be laxer about attaching streams to
+      circuits than when building the circuits.  This was meant to
+      prevent a set of bugs where streams were never attachable, but our
+      improved code here should make this unnecessary.  Bugfix on
+      0.2.2.7-alpha.
+

+ 5 - 0
changes/bug1090-launch-warning

@@ -0,0 +1,5 @@
+  o Minor features:
+    - Keep track of how many times we launch a new circuit to handle
+      a given stream. Too many launches could indicate an inconsistency
+      between our "launch a circuit to handle this stream" logic and our
+      "attach our stream to one of the available circuits" logic.

+ 7 - 0
changes/exitnodes_reliable

@@ -0,0 +1,7 @@
+  o Minor features:
+    - If ExitNodes is set, still pay attention to the Fast/Stable
+      status of exits when picking exit nodes.  (We used to ignore
+      these flags when ExitNodes was set, on the grounds that people
+      who set exitnodes wanted all of those nodes to get used, but
+      with the ability to pick exits by country and IP range, this
+      doesn't necessarily make sense any more.)

+ 58 - 17
doc/tor.1.txt

@@ -507,32 +507,73 @@ The following options are useful only for clients (that is, if
 
 **ExcludeNodes** __node__,__node__,__...__::
     A list of identity fingerprints, nicknames, country codes and address
-    patterns of nodes to never use when building a circuit. (Example:
-    ExcludeNodes SlowServer, $    EFFFFFFFFFFFFFFF, \{cc}, 255.254.0.0/8)
+    patterns of nodes to avoid when building a circuit.
+    (Example:
+    ExcludeNodes SlowServer, $    EFFFFFFFFFFFFFFF, \{cc}, 255.254.0.0/8) +
++
+    By default, this option is treated as a preference that Tor is allowed
+    to override in order to keep working.
+    For example, if you try to connect to a hidden service,
+    but you have excluded all of the hidden service's introduction points,
+    Tor will connect to one of them anyway.  If you do not want this
+    behavior, set the StrictNodes option (documented below).  +
++
+    Note also that if you are a relay, this (and the other node selection
+    options below) only affects your own circuits that Tor builds for you.
+    Clients can still build circuits through you to any node.  Controllers
+    can tell Tor to build circuits through any node.
+
 
 **ExcludeExitNodes** __node__,__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
+    patterns of nodes to never use when picking an exit node---that is, a
+    node that delivers traffic for you outside the Tor network.   Note that any
     node listed in ExcludeNodes is automatically considered to be part of this
-    list.
-
-**EntryNodes** __node__,__node__,__...__::
-    A list of identity fingerprints, nicknames, country codes and address
-    patterns of nodes to use for the first hop in normal circuits. These are
-    treated only as preferences unless StrictNodes (see below) is also set.
+    list too.  See also the caveats on the "ExitNodes" option below
 
 **ExitNodes** __node__,__node__,__...__::
     A list of identity fingerprints, nicknames, country codes and address
-    patterns of nodes to use for the last hop in normal exit circuits. These
-    are treated only as preferences unless StrictNodes (see below) is also set.
+    patterns of nodes to use as exit node---that is, a
+    node that delivers traffic for you outside the Tor network. +
++
+    Note that if you list too few nodes here, or if you exclude too many exit
+    nodes with ExcludeExitNodes, you can degrade functionality.  For example,
+    if none of the exits you list allows traffic on port 80 or 443, you won't
+    be able to browse the web. +
++
+    Note also that not every circuit is used to deliver traffic outside of
+    the Tor network.  It is normal to see non-exit circuits (such as those
+    used to connect to hidden services, those that do directory fetches,
+    those used for self-tests, and so on) that end at a non-exit node.  To
+    keep a node from being used entirely, see ExcludeNodes and StrictNodes. +
++
+    The ExcludeNodes option overrides this option: any node listed in both
+    ExitNodes and ExcludeNodes is treated as excluded. +
++
+    The .exit address notation, if enabled, overrides this option.
+
+**EntryNodes** __node__,__node__,__...__::
+    A list of identity fingerprints, nicknames, and country codes of nodes
+    to use for the first hop in your normal circuits.
+    This includes all
+    circuits except for direct connections to directory servers.  The Bridge
+    option overrides this option; if you have configured bridges and
+    UseBridges is 1, the Bridges are used as your entry nodes. +
++
+    The ExcludeNodes option overrides this option: any node listed in both
+    EntryNodes and ExcludeNodes is treated as excluded.
 
 **StrictNodes** **0**|**1**::
-    If 1 and EntryNodes config option is set, Tor will never use any nodes
-    besides those listed in EntryNodes for the first hop of a normal circuit.
-    If 1 and ExitNodes config option is set, Tor will never use any nodes
-    besides those listed in ExitNodes for the last hop of a normal exit
-    circuit. Note that Tor might still use these nodes for non-exit circuits
-    such as one-hop directory fetches or hidden service support circuits.
+    If StrictNodes is set to 1, Tor will treat the ExcludeNodes option as a
+    requirement to follow for all the circuits you generate, even if doing so
+    will break functionality for you.  If StrictNodes is set to 0, Tor will
+    still try to avoid nodes in the ExcludeNodes list, but it will err on the
+    side of avoiding unexpected errors.  Specifically, StrictNodes 0 tells
+    Tor that it is okay to use an excluded node when it is *necessary* to
+    perform self-tests, connect to
+    a hidden service, provide a hidden service to a client, fulfill a .exit
+    request, upload directory information, or download directory information.
+    (Default: 0)
 
 **FascistFirewall** **0**|**1**::
     If 1, Tor will only create outgoing connections to ORs running on ports

+ 106 - 81
src/or/circuitbuild.c

@@ -2046,8 +2046,9 @@ circuit_send_next_onion_skin(origin_circuit_t *circ)
          */
         if (timediff < 0 || timediff > 2*circ_times.close_ms+1000) {
           log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. "
-                              "Assuming clock jump. Purpose %d", timediff,
-                              circ->_base.purpose);
+                              "Assuming clock jump. Purpose %d (%s)", timediff,
+                     circ->_base.purpose,
+                     circuit_purpose_to_string(circ->_base.purpose));
         } else if (!circuit_build_times_disabled()) {
           /* Only count circuit times if the network is live */
           if (circuit_build_times_network_check_live(&circ_times)) {
@@ -2673,16 +2674,23 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
       n_supported[i] = -1;
       continue; /* skip routers that are known to be down or bad exits */
     }
-    if (node_is_unreliable(node, need_uptime, need_capacity, 0) &&
-        (!options->ExitNodes ||
-         !routerset_contains_node(options->ExitNodes, node))) {
-      /* FFFF Someday, differentiate between a routerset that names
-       * routers, and a routerset that names countries, and only do this
-       * check if they've asked for specific exit relays. Or if the country
-       * they ask for is rare. Or something. */
+    if (options->_ExcludeExitNodesUnion &&
+        routerset_contains_node(options->_ExcludeExitNodesUnion, node)) {
       n_supported[i] = -1;
-      continue; /* skip routers that are not suitable, unless we have
-                 * ExitNodes set, in which case we asked for it */
+      continue; /* user asked us not to use it, no matter what */
+    }
+    if (options->ExitNodes &&
+        !routerset_contains_node(options->ExitNodes, node)) {
+      n_supported[i] = -1;
+      continue; /* not one of our chosen exit nodes */
+    }
+
+    if (node_is_unreliable(node, need_uptime, need_capacity, 0)) {
+      n_supported[i] = -1;
+      continue; /* skip routers that are not suitable.  Don't worry if
+                 * this makes us reject all the possible routers: if so,
+                 * we'll retry later in this function with need_update and
+                 * need_capacity set to 0. */
     }
     if (!(node->is_valid || options->_AllowInvalid & ALLOW_INVALID_EXIT)) {
       /* if it's invalid and we don't want it */
@@ -2707,7 +2715,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
     SMARTLIST_FOREACH_BEGIN(connections, connection_t *, conn) {
       if (!ap_stream_wants_exit_attention(conn))
         continue; /* Skip everything but APs in CIRCUIT_WAIT */
-      if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), node, 1)) {
+      if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), node)) {
         ++n_supported[i];
 //        log_fn(LOG_DEBUG,"%s is supported. n_supported[%d] now %d.",
 //               router->nickname, i, n_supported[i]);
@@ -2741,22 +2749,14 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
   /* If any routers definitely support any pending connections, choose one
    * at random. */
   if (best_support > 0) {
-    smartlist_t *supporting = smartlist_create(), *use = smartlist_create();
+    smartlist_t *supporting = smartlist_create();
 
     SMARTLIST_FOREACH(the_nodes, const node_t *, node, {
       if (n_supported[node_sl_idx] == best_support)
         smartlist_add(supporting, (void*)node);
     });
 
-    routersets_get_node_disjunction(use, supporting, options->ExitNodes,
-                               options->_ExcludeExitNodesUnion, 1);
-    if (smartlist_len(use) == 0 && options->ExitNodes &&
-        !options->StrictNodes) { /* give up on exitnodes and try again */
-      routersets_get_node_disjunction(use, supporting, NULL,
-                                 options->_ExcludeExitNodesUnion, 1);
-    }
-    node = node_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT);
-    smartlist_free(use);
+    node = node_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT);
     smartlist_free(supporting);
   } else {
     /* Either there are no pending connections, or no routers even seem to
@@ -2764,7 +2764,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
      * at least one predicted exit port. */
 
     int attempt;
-    smartlist_t *needed_ports, *supporting, *use;
+    smartlist_t *needed_ports, *supporting;
 
     if (best_support == -1) {
       if (need_uptime || need_capacity) {
@@ -2781,7 +2781,6 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
                  options->_ExcludeExitNodesUnion ? " or are Excluded" : "");
     }
     supporting = smartlist_create();
-    use = smartlist_create();
     needed_ports = circuit_get_unhandled_ports(time(NULL));
     for (attempt = 0; attempt < 2; attempt++) {
       /* try once to pick only from routers that satisfy a needed port,
@@ -2797,25 +2796,13 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
         }
       } SMARTLIST_FOREACH_END(node);
 
-      routersets_get_node_disjunction(use, supporting, options->ExitNodes,
-                                 options->_ExcludeExitNodesUnion, 1);
-      if (smartlist_len(use) == 0 && options->ExitNodes &&
-          !options->StrictNodes) { /* give up on exitnodes and try again */
-        routersets_get_node_disjunction(use, supporting, NULL,
-                                   options->_ExcludeExitNodesUnion, 1);
-      }
-      /* FFF sometimes the above results in null, when the requested
-       * exit node is considered down by the consensus. we should pick
-       * it anyway, since the user asked for it. */
-      node = node_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT);
+      node = node_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT);
       if (node)
         break;
       smartlist_clear(supporting);
-      smartlist_clear(use);
     }
     SMARTLIST_FOREACH(needed_ports, uint16_t *, cp, tor_free(cp));
     smartlist_free(needed_ports);
-    smartlist_free(use);
     smartlist_free(supporting);
   }
 
@@ -2824,10 +2811,11 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
     log_info(LD_CIRC, "Chose exit server '%s'", node_get_nickname(node));
     return node;
   }
-  if (options->ExitNodes && options->StrictNodes) {
+  if (options->ExitNodes) {
     log_warn(LD_CIRC,
-             "No specified exit routers seem to be running, and "
-             "StrictNodes is set: can't choose an exit.");
+             "No specified %sexit routers seem to be running: "
+             "can't choose an exit.",
+             options->_ExcludeExitNodesUnion ? "non-excluded " : "");
   }
   return NULL;
 }
@@ -2879,7 +2867,6 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit)
   or_options_t *options = get_options();
   routerset_t *rs = options->ExcludeNodes;
   const char *description;
-  int domain = LD_CIRC;
   uint8_t purpose = circ->_base.purpose;
 
   if (circ->build_state->onehop_tunnel)
@@ -2892,13 +2879,14 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit)
     case CIRCUIT_PURPOSE_INTRO_POINT:
     case CIRCUIT_PURPOSE_REND_POINT_WAITING:
     case CIRCUIT_PURPOSE_REND_ESTABLISHED:
-      log_warn(LD_BUG, "Called on non-origin circuit (purpose %d)",
-               (int)purpose);
+      log_warn(LD_BUG, "Called on non-origin circuit (purpose %d, %s)",
+               (int)purpose,
+               circuit_purpose_to_string(purpose));
       return;
     case CIRCUIT_PURPOSE_C_GENERAL:
       if (circ->build_state->is_internal)
         return;
-      description = "Requested exit node";
+      description = "requested exit node";
       rs = options->_ExcludeExitNodesUnion;
       break;
     case CIRCUIT_PURPOSE_C_INTRODUCING:
@@ -2913,22 +2901,34 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit)
     case CIRCUIT_PURPOSE_C_REND_READY:
     case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED:
     case CIRCUIT_PURPOSE_C_REND_JOINED:
-      description = "Chosen rendezvous point";
-      domain = LD_BUG;
+      description = "chosen rendezvous point";
       break;
     case CIRCUIT_PURPOSE_CONTROLLER:
       rs = options->_ExcludeExitNodesUnion;
-      description = "Controller-selected circuit target";
+      description = "controller-selected circuit target";
       break;
     }
 
   if (routerset_contains_extendinfo(rs, exit)) {
-    log_fn(LOG_WARN, domain, "%s '%s' is in ExcludeNodes%s. Using anyway "
-           "(circuit purpose %d).",
-           description,exit->nickname,
-           rs==options->ExcludeNodes?"":" or ExcludeExitNodes",
-           (int)purpose);
-    circuit_log_path(LOG_WARN, domain, circ);
+    /* We should never get here if StrictNodes is set to 1. */
+    if (options->StrictNodes) {
+      log_warn(LD_BUG, "Using %s '%s' which is listed in ExcludeNodes%s, "
+               "even though StrictNodes is set. Please report. "
+               "(Circuit purpose: %s)",
+               description, exit->nickname,
+               rs==options->ExcludeNodes?"":" or ExcludeExitNodes",
+               circuit_purpose_to_string(purpose));
+    } else {
+      log_warn(LD_CIRC, "Using %s '%s' which is listed in "
+               "ExcludeNodes%s, because no better options were available. To "
+               "prevent this (and possibly break your Tor functionality), "
+               "set the StrictNodes configuration option. "
+               "(Circuit purpose: %s)",
+               description, exit->nickname,
+               rs==options->ExcludeNodes?"":" or ExcludeExitNodes",
+               circuit_purpose_to_string(purpose));
+    }
+    circuit_log_path(LOG_WARN, LD_CIRC, circ);
   }
 
   return;
@@ -3990,12 +3990,14 @@ entry_guards_prepend_from_config(or_options_t *options)
 
   /* Split entry guards into those on the list and those not. */
 
-  /* Now that we allow countries and IP ranges in EntryNodes, this is
-   * potentially an enormous list. It's not so bad though because we
-   * only call this function when a) we're making a new circuit, and b)
-   * we've called directory_info_has_arrived() or changed our EntryNodes
-   * since the last time we made a circuit. */
-  routerset_get_all_nodes(entry_nodes, options->EntryNodes, 0);
+  /* XXXX023 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.
+   *  Perhaps we should do this calculation once whenever the list of routers
+   *  changes or the entrynodes setting changes.
+   */
+  routerset_get_all_nodes(entry_nodes, options->EntryNodes,
+                          options->ExcludeNodes, 0);
   SMARTLIST_FOREACH(entry_nodes, const node_t *,node,
                     smartlist_add(entry_fps, (void*)node->identity));
 
@@ -4021,14 +4023,10 @@ entry_guards_prepend_from_config(or_options_t *options)
   SMARTLIST_FOREACH(entry_nodes, const node_t *, node, {
     add_an_entry_guard(node, 0);
   });
-  /* Finally, the remaining previously configured guards that are not in
-   * EntryNodes, unless we're strict in which case we drop them */
-  if (options->StrictNodes) {
-    SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e,
-                      entry_guard_free(e));
-  } else {
-    smartlist_add_all(entry_guards, old_entry_guards_not_on_list);
-  }
+  /* Finally, free the remaining previously configured guards that are not in
+   * EntryNodes. */
+  SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e,
+                    entry_guard_free(e));
 
   smartlist_free(entry_nodes);
   smartlist_free(entry_fps);
@@ -4039,7 +4037,7 @@ entry_guards_prepend_from_config(or_options_t *options)
 
 /** Return 0 if we're fine adding arbitrary routers out of the
  * directory to our entry guard list, or return 1 if we have a
- * list already and we'd prefer to stick to it.
+ * list already and we must stick to it.
  */
 int
 entry_list_is_constrained(or_options_t *options)
@@ -4051,18 +4049,6 @@ entry_list_is_constrained(or_options_t *options)
   return 0;
 }
 
-/* Are we dead set against changing our entry guard list, or would we
- * change it if it means keeping Tor usable? */
-static int
-entry_list_is_totally_static(or_options_t *options)
-{
-  if (options->EntryNodes && options->StrictNodes)
-    return 1;
-  if (options->UseBridges)
-    return 1;
-  return 0;
-}
-
 /** Pick a live (up and listed) entry guard from entry_guards. If
  * <b>state</b> is non-NULL, this is for a specific circuit --
  * make sure not to pick this circuit's exit or any node in the
@@ -4107,6 +4093,7 @@ choose_random_entry(cpath_build_state_t *state)
         continue; /* don't pick the same node for entry and exit */
       if (consider_exit_family && smartlist_isin(exit_family, node))
         continue; /* avoid relays that are family members of our exit */
+#if 0 /* since EntryNodes is always strict now, this clause is moot */
       if (options->EntryNodes &&
           !routerset_contains_node(options->EntryNodes, node)) {
         /* We've come to the end of our preferred entry nodes. */
@@ -4121,6 +4108,7 @@ choose_random_entry(cpath_build_state_t *state)
                    "No relays from EntryNodes available. Using others.");
         }
       }
+#endif
       smartlist_add(live_entry_guards, (void*)node);
       if (!entry->made_contact) {
         /* Always start with the first not-yet-contacted entry
@@ -4146,7 +4134,7 @@ choose_random_entry(cpath_build_state_t *state)
   }
 
   if (smartlist_len(live_entry_guards) < preferred_min) {
-    if (!entry_list_is_totally_static(options)) {
+    if (!entry_list_is_constrained(options)) {
       /* still no? try adding a new entry then */
       /* XXX if guard doesn't imply fast and stable, then we need
        * to tell add_an_entry_guard below what we want, or it might
@@ -4171,6 +4159,10 @@ choose_random_entry(cpath_build_state_t *state)
       need_capacity = 0;
       goto retry;
     }
+#if 0
+    /* Removing this retry logic: if we only allow one exit, and it is in the
+       same family as all our entries, then we are just plain not going to win
+       here. */
     if (!node && entry_list_is_constrained(options) && consider_exit_family) {
       /* still no? if we're using bridges or have strictentrynodes
        * set, and our chosen exit is in the same family as all our
@@ -4178,6 +4170,7 @@ choose_random_entry(cpath_build_state_t *state)
       consider_exit_family = 0;
       goto retry;
     }
+#endif
     /* live_entry_guards may be empty below. Oh well, we tried. */
   }
 
@@ -4578,6 +4571,24 @@ bridge_add_from_config(const tor_addr_t *addr, uint16_t port, char *digest)
   smartlist_add(bridge_list, b);
 }
 
+/** Return true iff <b>routerset</b> contains the bridge <b>bridge</b>. */
+static int
+routerset_contains_bridge(const routerset_t *routerset,
+                          const bridge_info_t *bridge)
+{
+  int result;
+  extend_info_t *extinfo;
+  tor_assert(bridge);
+  if (!routerset)
+    return 0;
+
+  extinfo = extend_info_alloc(
+         NULL, bridge->identity, NULL, &bridge->addr, bridge->port);
+  result = routerset_contains_extendinfo(routerset, extinfo);
+  extend_info_free(extinfo);
+  return result;
+}
+
 /** If <b>digest</b> is one of our known bridges, return it. */
 static bridge_info_t *
 find_bridge_by_digest(const char *digest)
@@ -4596,6 +4607,7 @@ static void
 launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge)
 {
   char *address;
+  or_options_t *options = get_options();
 
   if (connection_get_by_type_addr_port_purpose(
       CONN_TYPE_DIR, &bridge->addr, bridge->port,
@@ -4603,6 +4615,13 @@ launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge)
     return; /* it's already on the way */
 
   address = tor_dup_addr(&bridge->addr);
+  if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
+    download_status_mark_impossible(&bridge->fetch_status);
+    log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
+             safe_str_client(fmt_addr(&bridge->addr)));
+    return;
+  }
+
   directory_initiate_command(address, &bridge->addr,
                              bridge->port, 0,
                              0, /* does not matter */
@@ -4643,6 +4662,12 @@ fetch_bridge_descriptors(or_options_t *options, time_t now)
       if (!download_status_is_ready(&bridge->fetch_status, now,
                                     IMPOSSIBLE_TO_DOWNLOAD))
         continue; /* don't bother, no need to retry yet */
+      if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
+        download_status_mark_impossible(&bridge->fetch_status);
+        log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
+                 safe_str_client(fmt_addr(&bridge->addr)));
+        continue;
+      }
 
       /* schedule another fetch as if this one will fail, in case it does */
       download_status_failed(&bridge->fetch_status, 0);

+ 70 - 0
src/or/circuitlist.c

@@ -376,6 +376,62 @@ circuit_purpose_to_controller_string(uint8_t purpose)
   }
 }
 
+/** Return a human-readable string for the circuit purpose <b>purpose</b>. */
+const char *
+circuit_purpose_to_string(uint8_t purpose)
+{
+  static char buf[32];
+
+  switch (purpose)
+    {
+    case CIRCUIT_PURPOSE_OR:
+      return "Circuit at relay";
+    case CIRCUIT_PURPOSE_INTRO_POINT:
+      return "Acting as intro point";
+    case CIRCUIT_PURPOSE_REND_POINT_WAITING:
+      return "Acting as rendevous (pending)";
+    case CIRCUIT_PURPOSE_REND_ESTABLISHED:
+      return "Acting as rendevous (established)";
+    case CIRCUIT_PURPOSE_C_GENERAL:
+      return "General-purpose client";
+    case CIRCUIT_PURPOSE_C_INTRODUCING:
+      return "Hidden service client: Connecting to intro point";
+    case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT:
+      return "Hidden service client: Waiting for ack from intro point";
+    case CIRCUIT_PURPOSE_C_INTRODUCE_ACKED:
+      return "Hidden service client: Received ack from intro point";
+    case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+      return "Hidden service client: Establishing rendezvous point";
+    case CIRCUIT_PURPOSE_C_REND_READY:
+      return "Hidden service client: Pending rendezvous point";
+    case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED:
+      return "Hidden service client: Pending rendezvous point (ack received)";
+    case CIRCUIT_PURPOSE_C_REND_JOINED:
+      return "Hidden service client: Active rendezvous point";
+    case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT:
+      return "Measuring circuit timeout";
+
+    case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+      return "Hidden service: Establishing introduction point";
+    case CIRCUIT_PURPOSE_S_INTRO:
+      return "Hidden service: Introduction point";
+    case CIRCUIT_PURPOSE_S_CONNECT_REND:
+      return "Hidden service: Connecting to rendezvous point";
+    case CIRCUIT_PURPOSE_S_REND_JOINED:
+      return "Hidden service: Active rendezvous point";
+
+    case CIRCUIT_PURPOSE_TESTING:
+      return "Testing circuit";
+
+    case CIRCUIT_PURPOSE_CONTROLLER:
+      return "Circuit made by controller";
+
+    default:
+      tor_snprintf(buf, sizeof(buf), "UNKNOWN_%d", (int)purpose);
+      return buf;
+  }
+}
+
 /** Pick a reasonable package_window to start out for our circuits.
  * Originally this was hard-coded at 1000, but now the consensus votes
  * on the answer. See proposal 168. */
@@ -921,6 +977,7 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info,
   int need_uptime = (flags & CIRCLAUNCH_NEED_UPTIME) != 0;
   int need_capacity = (flags & CIRCLAUNCH_NEED_CAPACITY) != 0;
   int internal = (flags & CIRCLAUNCH_IS_INTERNAL) != 0;
+  or_options_t *options = get_options();
 
   /* Make sure we're not trying to create a onehop circ by
    * cannibalization. */
@@ -959,6 +1016,19 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info,
             hop=hop->next;
           } while (hop!=circ->cpath);
         }
+        if (options->ExcludeNodes) {
+          /* Make sure no existing nodes in the circuit are excluded for
+           * general use.  (This may be possible if StrictNodes is 0, and we
+           * thought we needed to use an otherwise excluded node for, say, a
+           * directory operation.) */
+          crypt_path_t *hop = circ->cpath;
+          do {
+            if (routerset_contains_extendinfo(options->ExcludeNodes,
+                                              hop->extend_info))
+              goto next;
+            hop = hop->next;
+          } while (hop != circ->cpath);
+        }
         if (!best || (best->build_state->need_uptime && !need_uptime))
           best = circ;
       next: ;

+ 1 - 0
src/or/circuitlist.h

@@ -15,6 +15,7 @@
 circuit_t * _circuit_get_global_list(void);
 const char *circuit_state_to_string(int state);
 const char *circuit_purpose_to_controller_string(uint8_t purpose);
+const char *circuit_purpose_to_string(uint8_t purpose);
 void circuit_dump_by_conn(connection_t *conn, int severity);
 void circuit_set_p_circid_orconn(or_circuit_t *circ, circid_t id,
                                  or_connection_t *conn);

+ 48 - 28
src/or/circuituse.c

@@ -128,7 +128,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn,
         return 0;
       }
     }
-    if (exitnode && !connection_ap_can_use_exit(conn, exitnode, 0)) {
+    if (exitnode && !connection_ap_can_use_exit(conn, exitnode)) {
       /* can't exit from this router */
       return 0;
     }
@@ -167,6 +167,10 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose)
           return 1;
         if (CIRCUIT_IS_ORIGIN(b) &&
             TO_ORIGIN_CIRCUIT(b)->build_state->is_internal)
+          /* XXX023 what the heck is this internal thing doing here. I
+           * think we can get rid of it. circuit_is_acceptable() already
+           * makes sure that is_internal is exactly what we need it to
+           * be. -RD */
           return 1;
       }
       break;
@@ -243,33 +247,34 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose,
   return best ? TO_ORIGIN_CIRCUIT(best) : NULL;
 }
 
+#if 0
 /** Check whether, according to the policies in <b>options</b>, the
  * circuit <b>circ</b> makes sense. */
-/* XXXX currently only checks Exclude{Exit}Nodes. It should check more. */
+/* XXXX currently only checks Exclude{Exit}Nodes; it should check more.
+ * Also, it doesn't have the right definition of an exit circuit. Also,
+ * it's never called. */
 int
 circuit_conforms_to_options(const origin_circuit_t *circ,
                             const or_options_t *options)
 {
   const crypt_path_t *cpath, *cpath_next = NULL;
 
-  for (cpath = circ->cpath; cpath && cpath_next != circ->cpath;
-       cpath = cpath_next) {
+  /* first check if it includes any excluded nodes */
+  for (cpath = circ->cpath; cpath_next != circ->cpath; cpath = cpath_next) {
     cpath_next = cpath->next;
-
     if (routerset_contains_extendinfo(options->ExcludeNodes,
                                       cpath->extend_info))
       return 0;
+  }
 
-    if (cpath->next == circ->cpath) {
-      /* This is apparently the exit node. */
+  /* then consider the final hop */
+  if (routerset_contains_extendinfo(options->ExcludeExitNodes,
+                                    circ->cpath->prev->extend_info))
+    return 0;
 
-      if (routerset_contains_extendinfo(options->ExcludeExitNodes,
-                                        cpath->extend_info))
-        return 0;
-    }
-  }
   return 1;
 }
+#endif
 
 /** Close all circuits that start at us, aren't open, and were born
  * at least CircuitBuildTimeout seconds ago.
@@ -392,10 +397,11 @@ circuit_expire_building(void)
             TO_ORIGIN_CIRCUIT(victim)->cpath->state == CPATH_STATE_OPEN;
 
       if (TO_ORIGIN_CIRCUIT(victim)->p_streams != NULL) {
-        log_warn(LD_BUG, "Circuit %d (purpose %d) has timed out, "
+        log_warn(LD_BUG, "Circuit %d (purpose %d, %s) has timed out, "
                  "yet has attached streams!",
                  TO_ORIGIN_CIRCUIT(victim)->global_identifier,
-                 victim->purpose);
+                 victim->purpose,
+                 circuit_purpose_to_string(victim->purpose));
         tor_fragile_assert();
         continue;
       }
@@ -426,9 +432,10 @@ circuit_expire_building(void)
         if (timercmp(&victim->timestamp_created, &extremely_old_cutoff, <)) {
           log_notice(LD_CIRC,
                      "Extremely large value for circuit build timeout: %lds. "
-                     "Assuming clock jump. Purpose %d",
+                     "Assuming clock jump. Purpose %d (%s)",
                      (long)(now.tv_sec - victim->timestamp_created.tv_sec),
-                      victim->purpose);
+                     victim->purpose, 
+                     circuit_purpose_to_string(victim->purpose));
         } else if (circuit_build_times_count_close(&circ_times,
                                             first_hop_succeeded,
                                             victim->timestamp_created.tv_sec)) {
@@ -509,7 +516,7 @@ circuit_stream_is_being_handled(edge_connection_t *conn,
       if (exitnode && (!need_uptime || build_state->need_uptime)) {
         int ok;
         if (conn) {
-          ok = connection_ap_can_use_exit(conn, exitnode, 0);
+          ok = connection_ap_can_use_exit(conn, exitnode);
         } else {
           addr_policy_result_t r;
           r = compare_addr_to_node_policy(0, port, exitnode);
@@ -794,12 +801,11 @@ circuit_expire_old_circuits_clientside(void)
               circ->purpose != CIRCUIT_PURPOSE_S_INTRO) {
             log_notice(LD_CIRC,
                        "Ancient non-dirty circuit %d is still around after "
-                       "%ld milliseconds. Purpose: %d",
+                       "%ld milliseconds. Purpose: %d (%s)",
                        TO_ORIGIN_CIRCUIT(circ)->global_identifier,
                        tv_mdiff(&circ->timestamp_created, &now),
-                       circ->purpose);
-            /* FFFF implement a new circuit_purpose_to_string() so we don't
-             * just print out a number for circ->purpose */
+                       circ->purpose,
+                       circuit_purpose_to_string(circ->purpose));
             TO_ORIGIN_CIRCUIT(circ)->is_ancient = 1;
           }
         }
@@ -1128,8 +1134,9 @@ circuit_launch_by_extend_info(uint8_t purpose,
      * internal circs rather than exit circs? -RD */
     circ = circuit_find_to_cannibalize(purpose, extend_info, flags);
     if (circ) {
-      log_info(LD_CIRC,"Cannibalizing circ '%s' for purpose %d",
-               build_state_get_exit_nickname(circ->build_state), purpose);
+      log_info(LD_CIRC,"Cannibalizing circ '%s' for purpose %d (%s)",
+               build_state_get_exit_nickname(circ->build_state), purpose,
+               circuit_purpose_to_string(purpose));
       circ->_base.purpose = purpose;
       /* reset the birth date of this circ, else expire_building
        * will see it and think it's been trying to build since it
@@ -1281,9 +1288,10 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn,
        * refactor into a single function? */
       const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1);
       int opt = conn->chosen_exit_optional;
-      if (node && !connection_ap_can_use_exit(conn, node, 0)) {
+      if (node && !connection_ap_can_use_exit(conn, node)) {
         log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
-               "Requested exit point '%s' would refuse request. %s.",
+               "Requested exit point '%s' is excluded or "
+               "would refuse request. %s.",
                conn->chosen_exit_name, opt ? "Trying others" : "Closing");
         if (opt) {
           conn->chosen_exit_optional = 0;
@@ -1394,7 +1402,18 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn,
 
     extend_info_free(extend_info);
 
-    if (desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL) {
+    if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL) {
+      /* We just caused a circuit to get built because of this stream.
+       * If this stream has caused a _lot_ of circuits to be built, that's
+       * a bad sign: we should tell the user. */
+      if (conn->num_circuits_launched < NUM_CIRCUITS_LAUNCHED_THRESHOLD &&
+          ++conn->num_circuits_launched == NUM_CIRCUITS_LAUNCHED_THRESHOLD)
+        log_warn(LD_BUG, "The application request to %s:%d has launched "
+                 "%d circuits without finding one it likes.",
+                 escaped_safe_str_client(conn->socks_request->address),
+                 conn->socks_request->port,
+                 conn->num_circuits_launched);
+    } else {
       /* help predict this next time */
       rep_hist_note_used_internal(time(NULL), need_uptime, 1);
       if (circ) {
@@ -1601,9 +1620,10 @@ connection_ap_handshake_attach_circuit(edge_connection_t *conn)
         }
         return -1;
       }
-      if (node && !connection_ap_can_use_exit(conn, node, 0)) {
+      if (node && !connection_ap_can_use_exit(conn, node)) {
         log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
-               "Requested exit point '%s' would refuse request. %s.",
+               "Requested exit point '%s' is excluded or "
+               "would refuse request. %s.",
                conn->chosen_exit_name, opt ? "Trying others" : "Closing");
         if (opt) {
           conn->chosen_exit_optional = 0;

+ 2 - 0
src/or/circuituse.h

@@ -16,8 +16,10 @@ void circuit_expire_building(void);
 void circuit_remove_handled_ports(smartlist_t *needed_ports);
 int circuit_stream_is_being_handled(edge_connection_t *conn, uint16_t port,
                                     int min);
+#if 0
 int circuit_conforms_to_options(const origin_circuit_t *circ,
                                 const or_options_t *options);
+#endif
 void circuit_build_needed_circs(time_t now);
 void circuit_detach_stream(circuit_t *circ, edge_connection_t *conn);
 

+ 14 - 10
src/or/config.c

@@ -1289,21 +1289,18 @@ options_act(or_options_t *old_options)
   /* Check for transitions that need action. */
   if (old_options) {
     if ((options->UseEntryGuards && !old_options->UseEntryGuards) ||
-        (options->ExcludeNodes &&
-         !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes)) ||
-        (options->ExcludeExitNodes &&
-         !routerset_equal(old_options->ExcludeExitNodes,
-                          options->ExcludeExitNodes)) ||
-        (options->EntryNodes &&
-         !routerset_equal(old_options->EntryNodes, options->EntryNodes)) ||
-        (options->ExitNodes &&
-         !routerset_equal(old_options->ExitNodes, options->ExitNodes)) ||
+        !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes) ||
+        !routerset_equal(old_options->ExcludeExitNodes,
+                         options->ExcludeExitNodes) ||
+        !routerset_equal(old_options->EntryNodes, options->EntryNodes) ||
+        !routerset_equal(old_options->ExitNodes, options->ExitNodes) ||
         options->StrictNodes != old_options->StrictNodes) {
       log_info(LD_CIRC,
                "Changed to using entry guards, or changed preferred or "
                "excluded node lists. Abandoning previous circuits.");
       circuit_mark_all_unused_circs();
       circuit_expire_all_dirty_circs();
+      addressmap_clear_excluded_trackexithosts(options);
     }
 
 /* How long should we delay counting bridge stats after becoming a bridge?
@@ -1454,7 +1451,8 @@ options_act(or_options_t *old_options)
   /* Check if we need to parse and add the EntryNodes config option. */
   if (options->EntryNodes &&
       (!old_options ||
-      (!routerset_equal(old_options->EntryNodes,options->EntryNodes))))
+       !routerset_equal(old_options->EntryNodes,options->EntryNodes) ||
+       !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes)))
     entry_nodes_should_be_added();
 
   /* Since our options changed, we might need to regenerate and upload our
@@ -3253,6 +3251,12 @@ options_validate(or_options_t *old_options, or_options_t *options,
     REJECT("Servers must be able to freely connect to the rest "
            "of the Internet, so they must not set UseBridges.");
 
+  /* If both of these are set, we'll end up with funny behavior where we
+   * demand enough entrynodes be up and running else we won't build
+   * circuits, yet we never actually use them. */
+  if (options->UseBridges && options->EntryNodes)
+    REJECT("You cannot set both UseBridges and EntryNodes.");
+
   options->_AllowInvalid = 0;
   if (options->AllowInvalidNodes) {
     SMARTLIST_FOREACH(options->AllowInvalidNodes, const char *, cp, {

+ 91 - 26
src/or/connection_edge.c

@@ -799,6 +799,7 @@ clear_trackexithost_mappings(const char *exitname)
   tor_strlower(suffix);
 
   STRMAP_FOREACH_MODIFY(addressmap, address, addressmap_entry_t *, ent) {
+    /* XXXX022 HEY!  Shouldn't this look at ent->new_address? */
     if (ent->source == ADDRMAPSRC_TRACKEXIT && !strcmpend(address, suffix)) {
       addressmap_ent_remove(address, ent);
       MAP_DEL_CURRENT(address);
@@ -808,6 +809,56 @@ clear_trackexithost_mappings(const char *exitname)
   tor_free(suffix);
 }
 
+/** Remove all TRACKEXIT mappings from the addressmap for which the target
+ * host is unknown or no longer allowed. */
+void
+addressmap_clear_excluded_trackexithosts(or_options_t *options)
+{
+  const routerset_t *allow_nodes = options->ExitNodes;
+  const routerset_t *exclude_nodes = options->_ExcludeExitNodesUnion;
+
+  if (!addressmap)
+    return;
+  if (routerset_is_empty(allow_nodes))
+    allow_nodes = NULL;
+  if (allow_nodes == NULL && routerset_is_empty(exclude_nodes))
+    return;
+
+  STRMAP_FOREACH_MODIFY(addressmap, address, addressmap_entry_t *, ent) {
+    size_t len;
+    const char *target = ent->new_address, *dot;
+    char *nodename;
+    const node_t *node;
+
+    if (strcmpend(target, ".exit")) {
+      /* Not a .exit mapping */
+      continue;
+    } else if (ent->source != ADDRMAPSRC_TRACKEXIT) {
+      /* Not a trackexit mapping. */
+      continue;
+    }
+    len = strlen(target);
+    if (len < 6)
+      continue; /* malformed. */
+    dot = target + len - 6; /* dot now points to just before .exit */
+    dot = strrchr(dot, '.'); /* dot now points to the . before .exit or NULL */
+    if (!dot) {
+      nodename = tor_strndup(target, len-5);
+    } else {
+      nodename = tor_strndup(dot+1, strlen(dot+1)-5);
+    }
+    node = node_get_by_nickname(nodename, 0);
+    tor_free(nodename);
+    if (!node ||
+        (allow_nodes && !routerset_contains_node(allow_nodes, node)) ||
+        routerset_contains_node(exclude_nodes, node)) {
+      /* We don't know this one, or we want to be rid of it. */
+      addressmap_ent_remove(address, ent);
+      MAP_DEL_CURRENT(address);
+    }
+  } STRMAP_FOREACH_END;
+}
+
 /** Remove all entries from the addressmap that were set via the
  * configuration file or the command line. */
 void
@@ -1494,9 +1545,13 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
   hostname_type_t addresstype;
   or_options_t *options = get_options();
   struct in_addr addr_tmp;
+  /* We set this to true if this is an address we should automatically
+   * remap to a local address in VirtualAddrNetwork */
   int automap = 0;
   char orig_address[MAX_SOCKS_ADDR_LEN];
   time_t map_expires = TIME_MAX;
+  /* This will be set to true iff the address starts out as a non-.exit
+     address, and we remap it to one because of an entry in the addressmap. */
   int remapped_to_exit = 0;
   time_t now = time(NULL);
 
@@ -1607,14 +1662,23 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
     /* foo.exit -- modify conn->chosen_exit_node to specify the exit
      * node, and conn->address to hold only the address portion. */
     char *s = strrchr(socks->address,'.');
+
+    /* If StrictNodes is not set, then .exit overrides ExcludeNodes. */
+    routerset_t *excludeset = options->StrictNodes ?
+      options->_ExcludeExitNodesUnion : options->ExcludeExitNodes;
+    const node_t *node;
+
     tor_assert(!automap);
     if (s) {
+      /* The address was of the form "(stuff).(name).exit */
       if (s[1] != '\0') {
         conn->chosen_exit_name = tor_strdup(s+1);
+        node = node_get_by_nickname(conn->chosen_exit_name, 1);
         if (remapped_to_exit) /* 5 tries before it expires the addressmap */
           conn->chosen_exit_retries = TRACKHOSTEXITS_RETRIES;
         *s = 0;
       } else {
+        /* Oops, the address was (stuff)..exit.  That's not okay. */
         log_warn(LD_APP,"Malformed exit address '%s.exit'. Refusing.",
                  safe_str_client(socks->address));
         control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
@@ -1623,20 +1687,34 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
         return -1;
       }
     } else {
-      const node_t *r;
+      /* It looks like they just asked for "foo.exit". */
+
       conn->chosen_exit_name = tor_strdup(socks->address);
-      r = node_get_by_nickname(conn->chosen_exit_name, 1);
-      *socks->address = 0;
-      if (r) {
-        node_get_address_string(r, socks->address, sizeof(socks->address));
-      } else {
-        log_warn(LD_APP,
-                 "Unrecognized server in exit address '%s.exit'. Refusing.",
-                 safe_str_client(socks->address));
-        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
-        return -1;
+      node = node_get_by_nickname(conn->chosen_exit_name, 1);
+      if (node) {
+        *socks->address = 0;
+        node_get_address_string(node, socks->address, sizeof(socks->address));
       }
     }
+    /* Now make sure that the chosen exit exists... */
+    if (!node) {
+      log_warn(LD_APP,
+               "Unrecognized relay in exit address '%s.exit'. Refusing.",
+               safe_str_client(socks->address));
+      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
+      return -1;
+    }
+    /* ...and make sure that it isn't excluded. */
+    if (routerset_contains_node(excludeset, node)) {
+      log_warn(LD_APP,
+               "Excluded relay in exit address '%s.exit'. Refusing.",
+               safe_str_client(socks->address));
+      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
+      return -1;
+    }
+    /* XXXX022-1090 Should we also allow foo.bar.exit if ExitNodes is set and
+       Bar is not listed in it?  I say yes, but our revised manpage branch
+       implies no. */
   }
 
   if (addresstype != ONION_HOSTNAME) {
@@ -2977,13 +3055,9 @@ connection_edge_is_rendezvous_stream(edge_connection_t *conn)
  * to exit from it, or 0 if it probably will not allow it.
  * (We might be uncertain if conn's destination address has not yet been
  * resolved.)
- *
- * If <b>excluded_means_no</b> is 1 and Exclude*Nodes is set and excludes
- * this relay, return 0.
  */
 int
-connection_ap_can_use_exit(edge_connection_t *conn, const node_t *exit,
-                           int excluded_means_no)
+connection_ap_can_use_exit(edge_connection_t *conn, const node_t *exit)
 {
   or_options_t *options = get_options();
 
@@ -3027,17 +3101,8 @@ connection_ap_can_use_exit(edge_connection_t *conn, const node_t *exit,
       return 0;
   }
   if (options->_ExcludeExitNodesUnion &&
-      (options->StrictNodes || excluded_means_no) &&
       routerset_contains_node(options->_ExcludeExitNodesUnion, exit)) {
-    /* If we are trying to avoid this node as exit, and we have StrictNodes
-     * set, then this is not a suitable exit. Refuse it.
-     *
-     * If we don't have StrictNodes set, then this function gets called in
-     * two contexts. First, we've got a circuit open and we want to know
-     * whether we can use it. In that case, we somehow built this circuit
-     * despite having the last hop in ExcludeExitNodes, so we should be
-     * willing to use it. Second, we are evaluating whether this is an
-     * acceptable exit for a new circuit. In that case, skip it. */
+    /* Not a suitable exit. Refuse it. */
     return 0;
   }
 

+ 2 - 2
src/or/connection_edge.h

@@ -49,8 +49,7 @@ int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ);
 void connection_exit_connect(edge_connection_t *conn);
 int connection_edge_is_rendezvous_stream(edge_connection_t *conn);
 int connection_ap_can_use_exit(edge_connection_t *conn,
-                               const node_t *exit,
-                               int excluded_means_no);
+                               const node_t *exit);
 void connection_ap_expire_beginning(void);
 void connection_ap_attach_pending(void);
 void connection_ap_fail_onehop(const char *failed_digest,
@@ -64,6 +63,7 @@ int connection_ap_process_transparent(edge_connection_t *conn);
 int address_is_invalid_destination(const char *address, int client);
 
 void addressmap_init(void);
+void addressmap_clear_excluded_trackexithosts(or_options_t *options);
 void addressmap_clean(time_t now);
 void addressmap_clear_configured(void);
 void addressmap_clear_transient(void);

+ 29 - 2
src/or/directory.c

@@ -262,10 +262,13 @@ directories_have_accepted_server_descriptor(void)
 }
 
 /** Start a connection to every suitable directory authority, using
- * connection purpose 'purpose' and uploading the payload 'payload'
- * (length 'payload_len').  dir_purpose should be one of
+ * connection purpose <b>dir_purpose</b> and uploading <b>payload</b>
+ * (of length <b>payload_len</b>). The dir_purpose should be one of
  * 'DIR_PURPOSE_UPLOAD_DIR' or 'DIR_PURPOSE_UPLOAD_RENDDESC'.
  *
+ * <b>router_purpose</b> describes the type of descriptor we're
+ * publishing, if we're publishing a descriptor -- e.g. general or bridge.
+ *
  * <b>type</b> specifies what sort of dir authorities (V1, V2,
  * HIDSERV, BRIDGE) we should upload to.
  *
@@ -281,6 +284,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
                              const char *payload,
                              size_t payload_len, size_t extrainfo_len)
 {
+  or_options_t *options = get_options();
   int post_via_tor;
   smartlist_t *dirservers = router_get_trusted_dir_servers();
   int found = 0;
@@ -296,6 +300,16 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
       if ((type & ds->type) == 0)
         continue;
 
+      if (options->ExcludeNodes && options->StrictNodes &&
+          routerset_contains_routerstatus(options->ExcludeNodes, rs, -1)) {
+        log_warn(LD_DIR, "Wanted to contact authority '%s' for %s, but "
+                 "it's in our ExcludedNodes list and StrictNodes is set. "
+                 "Skipping.",
+                 ds->nickname,
+                 dir_conn_purpose_to_string(dir_purpose));
+        continue;
+      }
+
       found = 1; /* at least one authority of this type was listed */
       if (dir_purpose == DIR_PURPOSE_UPLOAD_DIR)
         ds->has_accepted_serverdesc = 0;
@@ -527,12 +541,14 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
                                              time_t if_modified_since,
                                              const rend_data_t *rend_query)
 {
+  or_options_t *options = get_options();
   const node_t *node;
   char address_buf[INET_NTOA_BUF_LEN+1];
   struct in_addr in;
   const char *address;
   tor_addr_t addr;
   node = node_get_by_id(status->identity_digest);
+
   if (!node && anonymized_connection) {
     log_info(LD_DIR, "Not sending anonymized request to directory '%s'; we "
                      "don't have its router descriptor.", status->nickname);
@@ -546,6 +562,17 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
     address = address_buf;
   }
   tor_addr_from_ipv4h(&addr, status->addr);
+
+  if (options->ExcludeNodes && options->StrictNodes &&
+      routerset_contains_routerstatus(options->ExcludeNodes, status, -1)) {
+    log_warn(LD_DIR, "Wanted to contact directory mirror '%s' for %s, but "
+             "it's in our ExcludedNodes list and StrictNodes is set. "
+             "Skipping. This choice might make your Tor not work.",
+             status->nickname,
+             dir_conn_purpose_to_string(dir_purpose));
+    return;
+  }
+
   directory_initiate_command_rend(address, &addr,
                              status->or_port, status->dir_port,
                              status->version_supports_conditional_consensus,

+ 9 - 2
src/or/or.h

@@ -1175,6 +1175,13 @@ typedef struct edge_connection_t {
    * already retried several times. */
   uint8_t num_socks_retries;
 
+#define NUM_CIRCUITS_LAUNCHED_THRESHOLD 10
+  /** Number of times we've launched a circuit to handle this stream. If
+    * it gets too high, that could indicate an inconsistency between our
+    * "launch a circuit to handle this stream" logic and our "attach our
+    * stream to one of the available circuits" logic. */
+  unsigned int num_circuits_launched:4;
+
   /** True iff this connection is for a DNS request only. */
   unsigned int is_dns_request:1;
 
@@ -2544,7 +2551,7 @@ typedef struct {
                                  * ORs not to consider as exits. */
 
   /** Union of ExcludeNodes and ExcludeExitNodes */
-  struct routerset_t *_ExcludeExitNodesUnion;
+  routerset_t *_ExcludeExitNodesUnion;
 
   int DisableAllSwap; /**< Boolean: Attempt to call mlockall() on our
                        * process for all current and future memory. */
@@ -3661,7 +3668,7 @@ typedef struct trusted_dir_server_t {
 
 #define ROUTER_MAX_DECLARED_BANDWIDTH INT32_MAX
 
-/* Flags for pick_directory_server and pick_trusteddirserver. */
+/* Flags for pick_directory_server() and pick_trusteddirserver(). */
 /** Flag to indicate that we should not automatically be willing to use
  * ourself to answer a directory request.
  * Passed to router_pick_directory_server (et al).*/

+ 73 - 12
src/or/rendclient.c

@@ -23,6 +23,10 @@
 #include "rephist.h"
 #include "routerlist.h"
 
+static extend_info_t *rend_client_get_random_intro_impl(
+                          const rend_cache_entry_t *rend_query,
+                          const int strict, const int warnings);
+
 /** Called when we've established a circuit to an introduction point:
  * send the introduction request. */
 void
@@ -562,7 +566,7 @@ rend_client_remove_intro_point(extend_info_t *failed_intro,
     }
   }
 
-  if (smartlist_len(ent->parsed->intro_nodes) == 0) {
+  if (! rend_client_any_intro_points_usable(ent)) {
     log_info(LD_REND,
              "No more intro points remain for %s. Re-fetching descriptor.",
              escaped_safe_str_client(rend_query->onion_address));
@@ -708,7 +712,7 @@ rend_client_desc_trynow(const char *query)
     assert_connection_ok(TO_CONN(conn), now);
     if (rend_cache_lookup_entry(conn->rend_data->onion_address, -1,
                                 &entry) == 1 &&
-        smartlist_len(entry->parsed->intro_nodes) > 0) {
+        rend_client_any_intro_points_usable(entry)) {
       /* either this fetch worked, or it failed but there was a
        * valid entry from before which we should reuse */
       log_info(LD_REND,"Rend desc is usable. Launching circuits.");
@@ -742,23 +746,62 @@ rend_client_desc_trynow(const char *query)
 extend_info_t *
 rend_client_get_random_intro(const rend_data_t *rend_query)
 {
-  int i;
+  extend_info_t *result;
   rend_cache_entry_t *entry;
-  rend_intro_point_t *intro;
 
   if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) {
-    log_warn(LD_REND,
-             "Query '%s' didn't have valid rend desc in cache. Failing.",
-             safe_str_client(rend_query->onion_address));
+      log_warn(LD_REND,
+               "Query '%s' didn't have valid rend desc in cache. Failing.",
+               safe_str_client(rend_query->onion_address));
     return NULL;
   }
 
+  /* See if we can get a node that complies with ExcludeNodes */
+  if ((result = rend_client_get_random_intro_impl(entry, 1, 1)))
+    return result;
+  /* If not, and StrictNodes is not set, see if we can return any old node
+   */
+  if (!get_options()->StrictNodes)
+    return rend_client_get_random_intro_impl(entry, 0, 1);
+  return NULL;
+}
+
+/** As rend_client_get_random_intro, except assume that StrictNodes is set
+ * iff <b>strict</b> is true. If <b>warnings</b> is false, don't complain
+ * to the user when we're out of nodes, even if StrictNodes is true.
+ */
+static extend_info_t *
+rend_client_get_random_intro_impl(const rend_cache_entry_t *entry,
+                                  const int strict,
+                                  const int warnings)
+{
+  int i;
+
+  rend_intro_point_t *intro;
+  or_options_t *options = get_options();
+  smartlist_t *usable_nodes;
+  int n_excluded = 0;
+
+  /* We'll keep a separate list of the usable nodes.  If this becomes empty,
+   * no nodes are usable.  */
+  usable_nodes = smartlist_create();
+  smartlist_add_all(usable_nodes, entry->parsed->intro_nodes);
+
  again:
-  if (smartlist_len(entry->parsed->intro_nodes) == 0)
+  if (smartlist_len(usable_nodes) == 0) {
+    if (n_excluded && get_options()->StrictNodes && warnings) {
+      /* We only want to warn if StrictNodes is really set. Otherwise
+       * we're just about to retry anyways.
+       */
+      log_warn(LD_REND, "All introduction points for hidden service are "
+               "at excluded relays, and StrictNodes is set. Skipping.");
+    }
+    smartlist_free(usable_nodes);
     return NULL;
+  }
 
-  i = crypto_rand_int(smartlist_len(entry->parsed->intro_nodes));
-  intro = smartlist_get(entry->parsed->intro_nodes, i);
+  i = crypto_rand_int(smartlist_len(usable_nodes));
+  intro = smartlist_get(usable_nodes, i);
   /* Do we need to look up the router or is the extend info complete? */
   if (!intro->extend_info->onion_key) {
     const node_t *node;
@@ -769,16 +812,34 @@ rend_client_get_random_intro(const rend_data_t *rend_query)
     if (!node) {
       log_info(LD_REND, "Unknown router with nickname '%s'; trying another.",
                intro->extend_info->nickname);
-      rend_intro_point_free(intro);
-      smartlist_del(entry->parsed->intro_nodes, i);
+      smartlist_del(usable_nodes, i);
       goto again;
     }
     extend_info_free(intro->extend_info);
     intro->extend_info = extend_info_from_node(node);
   }
+  /* Check if we should refuse to talk to this router. */
+  if (options->ExcludeNodes && strict &&
+      routerset_contains_extendinfo(options->ExcludeNodes,
+                                    intro->extend_info)) {
+    n_excluded++;
+    smartlist_del(usable_nodes, i);
+    goto again;
+  }
+
+  smartlist_free(usable_nodes);
   return extend_info_dup(intro->extend_info);
 }
 
+/** Return true iff any introduction points still listed in <b>entry</b> are
+ * usable. */
+int
+rend_client_any_intro_points_usable(const rend_cache_entry_t *entry)
+{
+  return rend_client_get_random_intro_impl(
+          entry, get_options()->StrictNodes, 0) != NULL;
+}
+
 /** Client-side authorizations for hidden services; map of onion address to
  * rend_service_authorization_t*. */
 static strmap_t *auth_hid_servs = NULL;

+ 1 - 0
src/or/rendclient.h

@@ -29,6 +29,7 @@ int rend_client_receive_rendezvous(origin_circuit_t *circ,
 void rend_client_desc_trynow(const char *query);
 
 extend_info_t *rend_client_get_random_intro(const rend_data_t *rend_query);
+int rend_client_any_intro_points_usable(const rend_cache_entry_t *entry);
 
 int rend_client_send_introduction(origin_circuit_t *introcirc,
                                   origin_circuit_t *rendcirc);

+ 1 - 1
src/or/rendcommon.c

@@ -933,7 +933,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e)
   tor_assert((*e)->parsed && (*e)->parsed->intro_nodes);
   /* XXX023 hack for now, to return "not found" if there are no intro
    * points remaining. See bug 997. */
-  if (smartlist_len((*e)->parsed->intro_nodes) == 0)
+  if (! rend_client_any_intro_points_usable(*e))
     return 0;
   return 1;
 }

+ 32 - 8
src/or/rendservice.c

@@ -849,6 +849,7 @@ clean_accepted_intros(rend_service_t *service, time_t now)
 /** Respond to an INTRODUCE2 cell by launching a circuit to the chosen
  * rendezvous point.
  */
+ /* XXX022 this function sure could use some organizing. -RD */
 int
 rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
                        size_t request_len)
@@ -876,6 +877,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
   time_t now = time(NULL);
   char diffie_hellman_hash[DIGEST_LEN];
   time_t *access_time;
+  or_options_t *options = get_options();
+
   tor_assert(circuit->rend_data);
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
@@ -1048,6 +1051,15 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
     goto err;
   }
 
+  /* Check if we'd refuse to talk to this router */
+  if (options->ExcludeNodes && options->StrictNodes &&
+      routerset_contains_extendinfo(options->ExcludeNodes, extend_info)) {
+    log_warn(LD_REND, "Client asked to rendezvous at a relay that we "
+             "exclude, and StrictNodes is set. Refusing service.");
+    reason = END_CIRC_REASON_INTERNAL; /* XXX might leak why we refused */
+    goto err;
+  }
+
   r_cookie = ptr;
   base16_encode(hexcookie,9,r_cookie,4);
 
@@ -1336,14 +1348,26 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
   }
 
   /* If we already have enough introduction circuits for this service,
-   * redefine this one as a general circuit. */
+   * redefine this one as a general circuit or close it, depending. */
   if (count_established_intro_points(serviceid) > NUM_INTRO_POINTS) {
-    log_info(LD_CIRC|LD_REND, "We have just finished an introduction "
-             "circuit, but we already have enough. Redefining purpose to "
-             "general.");
-    TO_CIRCUIT(circuit)->purpose = CIRCUIT_PURPOSE_C_GENERAL;
-    circuit_has_opened(circuit);
-    return;
+    or_options_t *options = get_options();
+    if (options->ExcludeNodes) {
+      /* XXXX in some future version, we can test whether the transition is
+         allowed or not given the actual nodes in the circuit.  But for now,
+         this case, we might as well close the thing. */
+      log_info(LD_CIRC|LD_REND, "We have just finished an introduction "
+               "circuit, but we already have enough.  Closing it.");
+      circuit_mark_for_close(TO_CIRCUIT(circuit), END_CIRC_REASON_NONE);
+      return;
+    } else {
+      tor_assert(circuit->build_state->is_internal);
+      log_info(LD_CIRC|LD_REND, "We have just finished an introduction "
+               "circuit, but we already have enough. Redefining purpose to "
+               "general; leaving as internal.");
+      TO_CIRCUIT(circuit)->purpose = CIRCUIT_PURPOSE_C_GENERAL;
+      circuit_has_opened(circuit);
+      return;
+    }
   }
 
   log_info(LD_REND,
@@ -1395,7 +1419,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
 
 /** Called when we get an INTRO_ESTABLISHED cell; mark the circuit as a
  * live introduction point, and note that the service descriptor is
- * now out-of-date.*/
+ * now out-of-date. */
 int
 rend_service_intro_established(origin_circuit_t *circuit,
                                const uint8_t *request,

+ 20 - 0
src/or/router.c

@@ -852,9 +852,29 @@ consider_testing_reachability(int test_or, int test_dir)
   const routerinfo_t *me = router_get_my_routerinfo();
   int orport_reachable = check_whether_orport_reachable();
   tor_addr_t addr;
+  or_options_t *options = get_options();
   if (!me)
     return;
 
+  if (routerset_contains_router(options->ExcludeNodes, me, -1) &&
+      options->StrictNodes) {
+    /* If we've excluded ourself, and StrictNodes is set, we can't test
+     * ourself. */
+    if (test_or || test_dir) {
+#define SELF_EXCLUDED_WARN_INTERVAL 3600
+      static ratelim_t warning_limit=RATELIM_INIT(SELF_EXCLUDED_WARN_INTERVAL);
+      char *msg;
+      if ((msg = rate_limit_log(&warning_limit, approx_time()))) {
+        log_warn(LD_CIRC, "Can't peform self-tests for this relay: we have "
+                 "listed ourself in ExcludeNodes, and StrictNodes is set. "
+                 "We cannot learn whether we are usable, and will not "
+                 "be able to advertise ourself.%s", msg);
+        tor_free(msg);
+      }
+    }
+    return;
+  }
+
   if (test_or && (!orport_reachable || !circuit_enough_testing_circs())) {
     extend_info_t *ei;
     log_info(LD_CIRC, "Testing %s of my ORPort: %s:%d.",

+ 63 - 10
src/or/routerlist.c

@@ -1075,6 +1075,7 @@ router_pick_trusteddirserver(authority_type_t type, int flags)
 static const routerstatus_t *
 router_pick_directory_server_impl(authority_type_t type, int flags)
 {
+  or_options_t *options = get_options();
   const node_t *result;
   smartlist_t *direct, *tunnel;
   smartlist_t *trusted_direct, *trusted_tunnel;
@@ -1084,10 +1085,13 @@ router_pick_directory_server_impl(authority_type_t type, int flags)
   int requireother = ! (flags & PDS_ALLOW_SELF);
   int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL);
   int prefer_tunnel = (flags & _PDS_PREFER_TUNNELED_DIR_CONNS);
+  int try_excluding = 1, n_excluded = 0;
 
   if (!consensus)
     return NULL;
 
+ retry_without_exclude:
+
   direct = smartlist_create();
   tunnel = smartlist_create();
   trusted_direct = smartlist_create();
@@ -1101,6 +1105,7 @@ router_pick_directory_server_impl(authority_type_t type, int flags)
     int is_overloaded;
     tor_addr_t addr;
     const routerstatus_t *status = node->rs;
+    const country_t country = node->country;
     if (!status)
       continue;
 
@@ -1122,6 +1127,12 @@ router_pick_directory_server_impl(authority_type_t type, int flags)
     if ((type & EXTRAINFO_CACHE) &&
         !router_supports_extrainfo(node->identity, 0))
       continue;
+    if (try_excluding && options->ExcludeNodes &&
+        routerset_contains_routerstatus(options->ExcludeNodes, status,
+                                        country)) {
+      ++n_excluded;
+      continue;
+    }
 
     /* XXXX IP6 proposal 118 */
     tor_addr_from_ipv4h(&addr, node->rs->addr);
@@ -1165,6 +1176,15 @@ router_pick_directory_server_impl(authority_type_t type, int flags)
   smartlist_free(trusted_tunnel);
   smartlist_free(overloaded_direct);
   smartlist_free(overloaded_tunnel);
+
+  if (result == NULL && try_excluding && !options->StrictNodes && n_excluded) {
+    /* If we got no result, and we are excluding nodes, and StrictNodes is
+     * not set, try again without excluding nodes. */
+    try_excluding = 0;
+    n_excluded = 0;
+    goto retry_without_exclude;
+  }
+
   return result ? result->rs : NULL;
 }
 
@@ -1175,6 +1195,7 @@ static const routerstatus_t *
 router_pick_trusteddirserver_impl(authority_type_t type, int flags,
                                   int *n_busy_out)
 {
+  or_options_t *options = get_options();
   smartlist_t *direct, *tunnel;
   smartlist_t *overloaded_direct, *overloaded_tunnel;
   const routerinfo_t *me = router_get_my_routerinfo();
@@ -1186,10 +1207,13 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags,
   const int no_serverdesc_fetching =(flags & PDS_NO_EXISTING_SERVERDESC_FETCH);
   const int no_microdesc_fetching =(flags & PDS_NO_EXISTING_MICRODESC_FETCH);
   int n_busy = 0;
+  int try_excluding = 1, n_excluded = 0;
 
   if (!trusted_dir_servers)
     return NULL;
 
+ retry_without_exclude:
+
   direct = smartlist_create();
   tunnel = smartlist_create();
   overloaded_direct = smartlist_create();
@@ -1208,6 +1232,12 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags,
         continue;
       if (requireother && me && router_digest_is_me(d->digest))
           continue;
+      if (try_excluding && options->ExcludeNodes &&
+          routerset_contains_routerstatus(options->ExcludeNodes,
+                                          &d->fake_status, -1)) {
+        ++n_excluded;
+        continue;
+      }
 
       /* XXXX IP6 proposal 118 */
       tor_addr_from_ipv4h(&addr, d->addr);
@@ -1261,6 +1291,15 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags,
   smartlist_free(tunnel);
   smartlist_free(overloaded_direct);
   smartlist_free(overloaded_tunnel);
+
+  if (result == NULL && try_excluding && !options->StrictNodes && n_excluded) {
+    /* If we got no result, and we are excluding nodes, and StrictNodes is
+     * not set, try again without excluding nodes. */
+    try_excluding = 0;
+    n_excluded = 0;
+    goto retry_without_exclude;
+  }
+
   return result;
 }
 
@@ -1367,7 +1406,7 @@ nodelist_add_node_family(smartlist_t *sl, const node_t *node)
   if (options->NodeFamilySets) {
     SMARTLIST_FOREACH(options->NodeFamilySets, const routerset_t *, rs, {
       if (routerset_contains_node(rs, node)) {
-        routerset_get_all_nodes(sl, rs, 0);
+        routerset_get_all_nodes(sl, rs, NULL, 0);
       }
     });
   }
@@ -1512,6 +1551,8 @@ routerlist_find_my_routerinfo(void)
 /** Find a router that's up, that has this IP address, and
  * that allows exit to this address:port, or return NULL if there
  * isn't a good one.
+ * Don't exit enclave to excluded relays -- it wouldn't actually
+ * hurt anything, but this way there are fewer confused users.
  */
 const node_t *
 router_find_exact_exit_enclave(const char *address, uint16_t port)
@@ -1519,6 +1560,7 @@ router_find_exact_exit_enclave(const char *address, uint16_t port)
   uint32_t addr;
   struct in_addr in;
   tor_addr_t a;
+  or_options_t *options = get_options();
 
   if (!tor_inet_aton(address, &in))
     return NULL; /* it's not an IP already */
@@ -1530,7 +1572,8 @@ router_find_exact_exit_enclave(const char *address, uint16_t port)
     if (node_get_addr_ipv4h(node) == addr &&
         node->is_running &&
         compare_tor_addr_to_node_policy(&a, port, node) ==
-          ADDR_POLICY_ACCEPTED)
+          ADDR_POLICY_ACCEPTED &&
+        !routerset_contains_node(options->_ExcludeExitNodesUnion, node))
       return node;
   });
   return NULL;
@@ -5484,7 +5527,7 @@ routerset_needs_geoip(const routerset_t *set)
 }
 
 /** Return true iff there are no entries in <b>set</b>. */
-static int
+int
 routerset_is_empty(const routerset_t *set)
 {
   return !set || smartlist_len(set->list) == 0;
@@ -5580,10 +5623,11 @@ routerset_contains_node(const routerset_t *set, const node_t *node)
 }
 
 /** Add every known node_t that is a member of <b>routerset</b> to
- * <b>out</b>.  If <b>running_only</b>, only add the running ones. */
+ * <b>out</b>, but never add any that are part of <b>excludeset</b>.
+ * If <b>running_only</b>, only add the running ones. */
 void
 routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset,
-                        int running_only)
+                        const routerset_t *excludeset, int running_only)
 { /* XXXX MOVE */
   tor_assert(out);
   if (!routerset || !routerset->list)
@@ -5591,12 +5635,13 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset,
 
   if (routerset_is_list(routerset)) {
     /* No routers are specified by type; all are given by name or digest.
-     * we can do a lookup in O(len(list)). */
+     * we can do a lookup in O(len(routerset)). */
     SMARTLIST_FOREACH(routerset->list, const char *, name, {
         const node_t *node = node_get_by_nickname(name, 1);
         if (node) {
           if (!running_only || node->is_running)
-            smartlist_add(out, (void*)node);
+            if (!routerset_contains_node(excludeset, node))
+              smartlist_add(out, (void*)node);
         }
     });
   } else {
@@ -5606,12 +5651,14 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset,
     SMARTLIST_FOREACH(nodes, const node_t *, node, {
         if (running_only && !node->is_running)
           continue;
-        if (routerset_contains_node(routerset, node))
+        if (routerset_contains_node(routerset, node) &&
+            !routerset_contains_node(excludeset, node))
           smartlist_add(out, (void*)node);
     });
   }
 }
 
+#if 0
 /** Add to <b>target</b> every node_t from <b>source</b> except:
  *
  * 1) Don't add it if <b>include</b> is non-empty and the relay isn't in
@@ -5642,6 +5689,7 @@ routersets_get_node_disjunction(smartlist_t *target,
     }
   });
 }
+#endif
 
 /** Remove every node_t from <b>lst</b> that is in <b>routerset</b>. */
 void
@@ -5673,10 +5721,15 @@ routerset_to_string(const routerset_t *set)
 int
 routerset_equal(const routerset_t *old, const routerset_t *new)
 {
-  if (old == NULL && new == NULL)
+  if (routerset_is_empty(old) && routerset_is_empty(new)) {
+    /* Two empty sets are equal */
     return 1;
-  else if (old == NULL || new == NULL)
+  } else if (routerset_is_empty(old) || routerset_is_empty(new)) {
+    /* An empty set is equal to nothing else. */
     return 0;
+  }
+  tor_assert(old != NULL);
+  tor_assert(new != NULL);
 
   if (smartlist_len(old->list) != smartlist_len(new->list))
     return 0;

+ 7 - 1
src/or/routerlist.h

@@ -169,6 +169,7 @@ int routerset_parse(routerset_t *target, const char *s,
 void routerset_union(routerset_t *target, const routerset_t *source);
 int routerset_is_list(const routerset_t *set);
 int routerset_needs_geoip(const routerset_t *set);
+int routerset_is_empty(const routerset_t *set);
 int routerset_contains_router(const routerset_t *set, const routerinfo_t *ri,
                               country_t country);
 int routerset_contains_routerstatus(const routerset_t *set,
@@ -176,15 +177,20 @@ int routerset_contains_routerstatus(const routerset_t *set,
                                     country_t country);
 int routerset_contains_extendinfo(const routerset_t *set,
                                   const extend_info_t *ei);
+
 int routerset_contains_node(const routerset_t *set, const node_t *node);
 void routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset,
+                             const routerset_t *excludeset,
                              int running_only);
+#if 0
 void routersets_get_node_disjunction(smartlist_t *target,
                                 const smartlist_t *source,
                                 const routerset_t *include,
                                 const routerset_t *exclude, int running_only);
+#endif
 void routerset_subtract_nodes(smartlist_t *out,
-                              const routerset_t *routerset);
+                                const routerset_t *routerset);
+
 char *routerset_to_string(const routerset_t *routerset);
 int routerset_equal(const routerset_t *old, const routerset_t *new);
 void routerset_free(routerset_t *routerset);