Browse Source

Merge remote-tracking branch 'public/bug12538_merged'

Nick Mathewson 8 years ago
parent
commit
62f97545e4

+ 6 - 0
changes/feature12538

@@ -0,0 +1,6 @@
+  o Minor features (directory system):
+    Previously only relays who explicitly opened a directory port (DirPort)
+    accepted directory requests from clients.  Now all relays, with and without
+    a DirPort, who do not disable the DirCache option accept and serve
+    directory requests sent (tunnelled) through their ORPort.
+    Closes ticket 12538.

+ 6 - 0
doc/tor.1.txt

@@ -1987,6 +1987,12 @@ if DirPort is non-zero):
     except that port specifiers are ignored. Any address not matched by
     except that port specifiers are ignored. Any address not matched by
     some entry in the policy is accepted.
     some entry in the policy is accepted.
 
 
+[[DirCache]] **DirCache** **0**|**1**::
+    When this option is set, Tor caches all current directory documents and
+    accepts client requests for them. Setting DirPort is not required for this,
+    because clients connect via the ORPort by default. Setting either DirPort
+    or BridgeRelay and setting DirCache to 0 is not supported.  (Default: 1)
+
 
 
 DIRECTORY AUTHORITY SERVER OPTIONS
 DIRECTORY AUTHORITY SERVER OPTIONS
 ----------------------------------
 ----------------------------------

+ 63 - 1
src/or/config.c

@@ -222,6 +222,7 @@ static config_var_t option_vars_[] = {
   V(DirPortFrontPage,            FILENAME, NULL),
   V(DirPortFrontPage,            FILENAME, NULL),
   VAR("DirReqStatistics",        BOOL,     DirReqStatistics_option, "1"),
   VAR("DirReqStatistics",        BOOL,     DirReqStatistics_option, "1"),
   VAR("DirAuthority",            LINELIST, DirAuthorities, NULL),
   VAR("DirAuthority",            LINELIST, DirAuthorities, NULL),
+  V(DirCache,                    BOOL,     "1"),
   V(DirAuthorityFallbackRate,    DOUBLE,   "1.0"),
   V(DirAuthorityFallbackRate,    DOUBLE,   "1.0"),
   V(DisableAllSwap,              BOOL,     "0"),
   V(DisableAllSwap,              BOOL,     "0"),
   V(DisableDebuggerAttachment,   BOOL,     "1"),
   V(DisableDebuggerAttachment,   BOOL,     "1"),
@@ -3457,6 +3458,24 @@ options_validate(or_options_t *old_options, or_options_t *options,
       REJECT("AccountingRule must be 'sum' or 'max'");
       REJECT("AccountingRule must be 'sum' or 'max'");
   }
   }
 
 
+  if (options->DirPort_set && !options->DirCache) {
+    REJECT("DirPort configured but DirCache disabled. DirPort requires "
+           "DirCache.");
+  }
+
+  if (options->BridgeRelay && !options->DirCache) {
+    REJECT("We're a bridge but DirCache is disabled. BridgeRelay requires "
+           "DirCache.");
+  }
+
+  if (server_mode(options)) {
+    char *msg = NULL;
+    if (have_enough_mem_for_dircache(options, 0, &msg)) {
+      log_warn(LD_CONFIG, "%s", msg);
+      tor_free(msg);
+    }
+  }
+
   if (options->HTTPProxy) { /* parse it now */
   if (options->HTTPProxy) { /* parse it now */
     if (tor_addr_port_lookup(options->HTTPProxy,
     if (tor_addr_port_lookup(options->HTTPProxy,
                         &options->HTTPProxyAddr, &options->HTTPProxyPort) < 0)
                         &options->HTTPProxyAddr, &options->HTTPProxyPort) < 0)
@@ -4065,6 +4084,48 @@ compute_real_max_mem_in_queues(const uint64_t val, int log_guess)
   }
   }
 }
 }
 
 
+/* If we have less than 300 MB suggest disabling dircache */
+#define DIRCACHE_MIN_MB_BANDWIDTH 300
+#define DIRCACHE_MIN_BANDWIDTH (DIRCACHE_MIN_MB_BANDWIDTH*ONE_MEGABYTE)
+#define STRINGIFY(val) #val
+
+/** Create a warning message for emitting if we are a dircache but may not have
+ * enough system memory, or if we are not a dircache but probably should be.
+ * Return -1 when a message is returned in *msg*, else return 0. */
+STATIC int
+have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem,
+                             char **msg)
+{
+  *msg = NULL;
+  if (total_mem == 0) {
+    if (get_total_system_memory(&total_mem) < 0)
+      total_mem = options->MaxMemInQueues;
+  }
+  if (options->DirCache) {
+    if (total_mem < DIRCACHE_MIN_BANDWIDTH) {
+      if (options->BridgeRelay) {
+        *msg = strdup("Running a Bridge with less than "
+                      STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is "
+                      "not recommended.");
+      } else {
+        *msg = strdup("Being a directory cache (default) with less than "
+                      STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is "
+                      "not recommended and may consume most of the available "
+                      "resources, consider disabling this functionality by "
+                      "setting the DirCache option to 0.");
+      }
+    }
+  } else {
+    if (total_mem >= DIRCACHE_MIN_BANDWIDTH) {
+      *msg = strdup("DirCache is disabled and we are configured as a "
+               "relay. This may disqualify us from becoming a guard in the "
+               "future.");
+    }
+  }
+  return *msg == NULL ? 0 : -1;
+}
+#undef STRINGIFY
+
 /** Helper: return true iff s1 and s2 are both NULL, or both non-NULL
 /** Helper: return true iff s1 and s2 are both NULL, or both non-NULL
  * equal strings. */
  * equal strings. */
 static int
 static int
@@ -4253,7 +4314,8 @@ options_transition_affects_descriptor(const or_options_t *old_options,
       !opt_streq(old_options->MyFamily, new_options->MyFamily) ||
       !opt_streq(old_options->MyFamily, new_options->MyFamily) ||
       !opt_streq(old_options->AccountingStart, new_options->AccountingStart) ||
       !opt_streq(old_options->AccountingStart, new_options->AccountingStart) ||
       old_options->AccountingMax != new_options->AccountingMax ||
       old_options->AccountingMax != new_options->AccountingMax ||
-      public_server_mode(old_options) != public_server_mode(new_options))
+      public_server_mode(old_options) != public_server_mode(new_options) ||
+      old_options->DirCache != new_options->DirCache)
     return 1;
     return 1;
 
 
   return 0;
   return 0;

+ 2 - 0
src/or/config.h

@@ -158,6 +158,8 @@ STATIC int parse_dir_authority_line(const char *line,
                                     dirinfo_type_t required_type,
                                     dirinfo_type_t required_type,
                                     int validate_only);
                                     int validate_only);
 STATIC int parse_dir_fallback_line(const char *line, int validate_only);
 STATIC int parse_dir_fallback_line(const char *line, int validate_only);
+STATIC int have_enough_mem_for_dircache(const or_options_t *options,
+                                        size_t total_mem, char **msg);
 #endif
 #endif
 
 
 #endif
 #endif

+ 10 - 2
src/or/directory.c

@@ -943,6 +943,15 @@ directory_initiate_command_rend(const tor_addr_t *_addr,
   log_debug(LD_DIR, "anonymized %d, use_begindir %d.",
   log_debug(LD_DIR, "anonymized %d, use_begindir %d.",
             anonymized_connection, use_begindir);
             anonymized_connection, use_begindir);
 
 
+  if (!dir_port && !use_begindir) {
+    char ipaddr[TOR_ADDR_BUF_LEN];
+    tor_addr_to_str(ipaddr, _addr, TOR_ADDR_BUF_LEN, 0);
+    log_warn(LD_BUG, "Cannot use directory server without dirport or "
+                     "begindir! Address: %s, ORPort: %d, DirPort: %d",
+                     escaped_safe_str_client(ipaddr), or_port, dir_port);
+    return;
+  }
+
   log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose));
   log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose));
 
 
 #ifndef NON_ANONYMOUS_MODE_ENABLED
 #ifndef NON_ANONYMOUS_MODE_ENABLED
@@ -3664,8 +3673,7 @@ connection_dir_finished_connecting(dir_connection_t *conn)
 static const smartlist_t *
 static const smartlist_t *
 find_dl_schedule(download_status_t *dls, const or_options_t *options)
 find_dl_schedule(download_status_t *dls, const or_options_t *options)
 {
 {
-  /* XX/teor Replace with dir_server_mode from #12538 */
-  const int dir_server = options->DirPort_set;
+  const int dir_server = dir_server_mode(options);
   const int multi_d = networkstatus_consensus_can_use_multiple_directories(
   const int multi_d = networkstatus_consensus_can_use_multiple_directories(
                                                                     options);
                                                                     options);
   const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping(
   const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping(

+ 2 - 1
src/or/directory.h

@@ -101,7 +101,8 @@ time_t download_status_increment_attempt(download_status_t *dls,
  * the optional status code <b>sc</b>. */
  * the optional status code <b>sc</b>. */
 #define download_status_failed(dls, sc)                                 \
 #define download_status_failed(dls, sc)                                 \
   download_status_increment_failure((dls), (sc), NULL,                  \
   download_status_increment_failure((dls), (sc), NULL,                  \
-                                    get_options()->DirPort_set, time(NULL))
+                                    dir_server_mode(get_options()), \
+                                    time(NULL))
 
 
 void download_status_reset(download_status_t *dls);
 void download_status_reset(download_status_t *dls);
 static int download_status_is_ready(download_status_t *dls, time_t now,
 static int download_status_is_ready(download_status_t *dls, time_t now,

+ 12 - 9
src/or/dirserv.c

@@ -1091,13 +1091,13 @@ directory_fetches_from_authorities(const or_options_t *options)
     return 1; /* we don't know our IP address; ask an authority. */
     return 1; /* we don't know our IP address; ask an authority. */
   refuseunknown = ! router_my_exit_policy_is_reject_star() &&
   refuseunknown = ! router_my_exit_policy_is_reject_star() &&
     should_refuse_unknown_exits(options);
     should_refuse_unknown_exits(options);
-  if (!options->DirPort_set && !refuseunknown)
+  if (!dir_server_mode(options) && !refuseunknown)
     return 0;
     return 0;
   if (!server_mode(options) || !advertised_server_mode())
   if (!server_mode(options) || !advertised_server_mode())
     return 0;
     return 0;
   me = router_get_my_routerinfo();
   me = router_get_my_routerinfo();
-  if (!me || (!me->dir_port && !refuseunknown))
-    return 0; /* if dirport not advertised, return 0 too */
+  if (!me || (!me->supports_tunnelled_dir_requests && !refuseunknown))
+    return 0; /* if we don't service directory requests, return 0 too */
   return 1;
   return 1;
 }
 }
 
 
@@ -1128,7 +1128,7 @@ directory_fetches_dir_info_later(const or_options_t *options)
 int
 int
 directory_caches_unknown_auth_certs(const or_options_t *options)
 directory_caches_unknown_auth_certs(const or_options_t *options)
 {
 {
-  return options->DirPort_set || options->BridgeRelay;
+  return dir_server_mode(options) || options->BridgeRelay;
 }
 }
 
 
 /** Return 1 if we want to keep descriptors, networkstatuses, etc around
 /** Return 1 if we want to keep descriptors, networkstatuses, etc around
@@ -1137,7 +1137,7 @@ directory_caches_unknown_auth_certs(const or_options_t *options)
 int
 int
 directory_caches_dir_info(const or_options_t *options)
 directory_caches_dir_info(const or_options_t *options)
 {
 {
-  if (options->BridgeRelay || options->DirPort_set)
+  if (options->BridgeRelay || dir_server_mode(options))
     return 1;
     return 1;
   if (!server_mode(options) || !advertised_server_mode())
   if (!server_mode(options) || !advertised_server_mode())
     return 0;
     return 0;
@@ -1153,7 +1153,7 @@ directory_caches_dir_info(const or_options_t *options)
 int
 int
 directory_permits_begindir_requests(const or_options_t *options)
 directory_permits_begindir_requests(const or_options_t *options)
 {
 {
-  return options->BridgeRelay != 0 || options->DirPort_set;
+  return options->BridgeRelay != 0 || dir_server_mode(options);
 }
 }
 
 
 /** Return 1 if we have no need to fetch new descriptors. This generally
 /** Return 1 if we have no need to fetch new descriptors. This generally
@@ -1350,8 +1350,9 @@ dirserv_thinks_router_is_unreliable(time_t now,
 }
 }
 
 
 /** Return true iff <b>router</b> should be assigned the "HSDir" flag.
 /** Return true iff <b>router</b> should be assigned the "HSDir" flag.
+ *
  * Right now this means it advertises support for it, it has a high uptime,
  * Right now this means it advertises support for it, it has a high uptime,
- * it has a DirPort open, it has the Stable and Fast flag and it's currently
+ * it's a directory cache, it has the Stable and Fast flags, and it's currently
  * considered Running.
  * considered Running.
  *
  *
  * This function needs to be called after router-\>is_running has
  * This function needs to be called after router-\>is_running has
@@ -1378,7 +1379,8 @@ dirserv_thinks_router_is_hs_dir(const routerinfo_t *router,
   else
   else
     uptime = real_uptime(router, now);
     uptime = real_uptime(router, now);
 
 
-  return (router->wants_to_be_hs_dir && router->dir_port &&
+  return (router->wants_to_be_hs_dir &&
+          router->supports_tunnelled_dir_requests &&
           node->is_stable && node->is_fast &&
           node->is_stable && node->is_fast &&
           uptime >= get_options()->MinUptimeHidServDirectoryV2 &&
           uptime >= get_options()->MinUptimeHidServDirectoryV2 &&
           router_is_active(router, node, now));
           router_is_active(router, node, now));
@@ -1921,7 +1923,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
                    rs->is_hs_dir?" HSDir":"",
                    rs->is_hs_dir?" HSDir":"",
                    rs->is_flagged_running?" Running":"",
                    rs->is_flagged_running?" Running":"",
                    rs->is_stable?" Stable":"",
                    rs->is_stable?" Stable":"",
-                   (rs->dir_port!=0)?" V2Dir":"",
+                   rs->is_v2_dir?" V2Dir":"",
                    rs->is_valid?" Valid":"");
                    rs->is_valid?" Valid":"");
 
 
   /* length of "opt v \n" */
   /* length of "opt v \n" */
@@ -2185,6 +2187,7 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs,
   strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname));
   strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname));
   rs->or_port = ri->or_port;
   rs->or_port = ri->or_port;
   rs->dir_port = ri->dir_port;
   rs->dir_port = ri->dir_port;
+  rs->is_v2_dir = ri->supports_tunnelled_dir_requests;
   if (options->AuthDirHasIPv6Connectivity == 1 &&
   if (options->AuthDirHasIPv6Connectivity == 1 &&
       !tor_addr_is_null(&ri->ipv6_addr) &&
       !tor_addr_is_null(&ri->ipv6_addr) &&
       node->last_reachable6 >= now - REACHABLE_TIMEOUT) {
       node->last_reachable6 >= now - REACHABLE_TIMEOUT) {

+ 1 - 2
src/or/dirvote.c

@@ -54,7 +54,6 @@ static int dirvote_perform_vote(void);
 static void dirvote_clear_votes(int all_votes);
 static void dirvote_clear_votes(int all_votes);
 static int dirvote_compute_consensuses(void);
 static int dirvote_compute_consensuses(void);
 static int dirvote_publish_consensus(void);
 static int dirvote_publish_consensus(void);
-static char *make_consensus_method_list(int low, int high, const char *sep);
 
 
 /* =====
 /* =====
  * Voting
  * Voting
@@ -564,7 +563,7 @@ consensus_method_is_supported(int method)
 
 
 /** Return a newly allocated string holding the numbers between low and high
 /** Return a newly allocated string holding the numbers between low and high
  * (inclusive) that are supported consensus methods. */
  * (inclusive) that are supported consensus methods. */
-static char *
+STATIC char *
 make_consensus_method_list(int low, int high, const char *separator)
 make_consensus_method_list(int low, int high, const char *separator)
 {
 {
   char *list;
   char *list;

+ 1 - 0
src/or/dirvote.h

@@ -177,6 +177,7 @@ STATIC char *format_networkstatus_vote(crypto_pk_t *private_key,
 STATIC char *dirvote_compute_params(smartlist_t *votes, int method,
 STATIC char *dirvote_compute_params(smartlist_t *votes, int method,
                              int total_authorities);
                              int total_authorities);
 STATIC char *compute_consensus_package_lines(smartlist_t *votes);
 STATIC char *compute_consensus_package_lines(smartlist_t *votes);
+STATIC char *make_consensus_method_list(int low, int high, const char *sep);
 #endif
 #endif
 
 
 #endif
 #endif

+ 32 - 0
src/or/networkstatus.c

@@ -1461,6 +1461,38 @@ networkstatus_copy_old_consensus_info(networkstatus_t *new_c,
   } SMARTLIST_FOREACH_JOIN_END(rs_old, rs_new);
   } SMARTLIST_FOREACH_JOIN_END(rs_old, rs_new);
 }
 }
 
 
+#ifdef TOR_UNIT_TESTS
+/**Accept a <b>flavor</b> consensus <b>c</b> without any additional
+ * validation. This is exclusively for unit tests.
+ * We copy any ancillary information from a pre-existing consensus
+ * and then free the current one and replace it with the newly
+ * provided instance. Returns -1 on unrecognized flavor, 0 otherwise.
+ */
+int
+networkstatus_set_current_consensus_from_ns(networkstatus_t *c,
+                                            const char *flavor)
+{
+  int flav = networkstatus_parse_flavor_name(flavor);
+  switch (flav) {
+    case FLAV_NS:
+      if (current_ns_consensus) {
+        networkstatus_copy_old_consensus_info(c, current_ns_consensus);
+        networkstatus_vote_free(current_ns_consensus);
+      }
+      current_ns_consensus = c;
+      break;
+    case FLAV_MICRODESC:
+      if (current_md_consensus) {
+        networkstatus_copy_old_consensus_info(c, current_md_consensus);
+        networkstatus_vote_free(current_md_consensus);
+      }
+      current_md_consensus = c;
+      break;
+  }
+  return current_md_consensus ? 0 : -1;
+}
+#endif //TOR_UNIT_TESTS
+
 /** Try to replace the current cached v3 networkstatus with the one in
 /** Try to replace the current cached v3 networkstatus with the one in
  * <b>consensus</b>.  If we don't have enough certificates to validate it,
  * <b>consensus</b>.  If we don't have enough certificates to validate it,
  * store it in consensus_waiting_for_certs and launch a certificate fetch.
  * store it in consensus_waiting_for_certs and launch a certificate fetch.

+ 4 - 0
src/or/networkstatus.h

@@ -114,6 +114,10 @@ int networkstatus_get_weight_scale_param(networkstatus_t *ns);
 
 
 #ifdef NETWORKSTATUS_PRIVATE
 #ifdef NETWORKSTATUS_PRIVATE
 STATIC void vote_routerstatus_free(vote_routerstatus_t *rs);
 STATIC void vote_routerstatus_free(vote_routerstatus_t *rs);
+#ifdef TOR_UNIT_TESTS
+STATIC int networkstatus_set_current_consensus_from_ns(networkstatus_t *c,
+                                                const char *flavor);
+#endif // TOR_UNIT_TESTS
 #endif
 #endif
 
 
 #endif
 #endif

+ 12 - 5
src/or/nodelist.c

@@ -644,12 +644,19 @@ node_is_named(const node_t *node)
 int
 int
 node_is_dir(const node_t *node)
 node_is_dir(const node_t *node)
 {
 {
-  if (node->rs)
-    return node->rs->dir_port != 0;
-  else if (node->ri)
-    return node->ri->dir_port != 0;
-  else
+  if (node->rs) {
+    routerstatus_t * rs = node->rs;
+    /* This is true if supports_tunnelled_dir_requests is true which
+     * indicates that we support directory request tunnelled or through the
+     * DirPort. */
+    return rs->is_v2_dir;
+  } else if (node->ri) {
+    routerinfo_t * ri = node->ri;
+    /* Both tunnelled request is supported or DirPort is set. */
+    return ri->supports_tunnelled_dir_requests;
+  } else {
     return 0;
     return 0;
+  }
 }
 }
 
 
 /** Return true iff <b>node</b> has either kind of usable descriptor -- that
 /** Return true iff <b>node</b> has either kind of usable descriptor -- that

+ 12 - 0
src/or/or.h

@@ -2147,6 +2147,11 @@ typedef struct {
    * tests for it. */
    * tests for it. */
   unsigned int needs_retest_if_added:1;
   unsigned int needs_retest_if_added:1;
 
 
+  /** True iff this router included "tunnelled-dir-server" in its descriptor,
+   * implying it accepts tunnelled directory requests, or it advertised
+   * dir_port > 0. */
+  unsigned int supports_tunnelled_dir_requests:1;
+
 /** Tor can use this router for general positions in circuits; we got it
 /** Tor can use this router for general positions in circuits; we got it
  * from a directory server as usual, or we're an authority and a server
  * from a directory server as usual, or we're an authority and a server
  * uploaded it. */
  * uploaded it. */
@@ -2224,6 +2229,9 @@ typedef struct routerstatus_t {
                                * an exit node. */
                                * an exit node. */
   unsigned int is_hs_dir:1; /**< True iff this router is a v2-or-later hidden
   unsigned int is_hs_dir:1; /**< True iff this router is a v2-or-later hidden
                              * service directory. */
                              * service directory. */
+  unsigned int is_v2_dir:1; /** True iff this router publishes an open DirPort
+                             * or it claims to accept tunnelled dir requests.
+                             */
   /** True iff we know version info for this router. (i.e., a "v" entry was
   /** True iff we know version info for this router. (i.e., a "v" entry was
    * included.)  We'll replace all these with a big tor_version_t or a char[]
    * included.)  We'll replace all these with a big tor_version_t or a char[]
    * if the number of traits we care about ever becomes incredibly big. */
    * if the number of traits we care about ever becomes incredibly big. */
@@ -3961,6 +3969,10 @@ typedef struct {
   /** Should we fetch our dir info at the start of the consensus period? */
   /** Should we fetch our dir info at the start of the consensus period? */
   int FetchDirInfoExtraEarly;
   int FetchDirInfoExtraEarly;
 
 
+  int DirCache; /**< Cache all directory documents and accept requests via
+                 * tunnelled dir conns from clients. If 1, enabled (default);
+                 * If 0, disabled. */
+
   char *VirtualAddrNetworkIPv4; /**< Address and mask to hand out for virtual
   char *VirtualAddrNetworkIPv4; /**< Address and mask to hand out for virtual
                                  * MAPADDRESS requests for IPv4 addresses */
                                  * MAPADDRESS requests for IPv4 addresses */
   char *VirtualAddrNetworkIPv6; /**< Address and mask to hand out for virtual
   char *VirtualAddrNetworkIPv6; /**< Address and mask to hand out for virtual

+ 85 - 33
src/or/router.c

@@ -1099,39 +1099,40 @@ check_whether_dirport_reachable(void)
          can_reach_dir_port;
          can_reach_dir_port;
 }
 }
 
 
-/** Look at a variety of factors, and return 0 if we don't want to
- * advertise the fact that we have a DirPort open. Else return the
- * DirPort we want to advertise.
- *
- * Log a helpful message if we change our mind about whether to publish
- * a DirPort.
+/** The lower threshold of remaining bandwidth required to advertise (or
+ * automatically provide) directory services */
+/* XXX Should this be increased? */
+#define MIN_BW_TO_ADVERTISE_DIRSERVER 51200
+
+/** Return true iff we have enough configured bandwidth to cache directory
+ * information. */
+static int
+router_has_bandwidth_to_be_dirserver(const or_options_t *options)
+{
+  if (options->BandwidthRate < MIN_BW_TO_ADVERTISE_DIRSERVER) {
+    return 0;
+  }
+  if (options->RelayBandwidthRate > 0 &&
+      options->RelayBandwidthRate < MIN_BW_TO_ADVERTISE_DIRSERVER) {
+    return 0;
+  }
+  return 1;
+}
+
+/** Helper: Return 1 if we have sufficient resources for serving directory
+ * requests, return 0 otherwise.
+ * dir_port is either 0 or the configured DirPort number.
+ * If AccountingMax is set less than our advertised bandwidth, then don't
+ * serve requests. Likewise, if our advertised bandwidth is less than
+ * MIN_BW_TO_ADVERTISE_DIRSERVER, don't bother trying to serve requests.
  */
  */
 static int
 static int
-decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
+router_should_be_directory_server(const or_options_t *options, int dir_port)
 {
 {
   static int advertising=1; /* start out assuming we will advertise */
   static int advertising=1; /* start out assuming we will advertise */
   int new_choice=1;
   int new_choice=1;
   const char *reason = NULL;
   const char *reason = NULL;
 
 
-  /* Section one: reasons to publish or not publish that aren't
-   * worth mentioning to the user, either because they're obvious
-   * or because they're normal behavior. */
-
-  if (!dir_port) /* short circuit the rest of the function */
-    return 0;
-  if (authdir_mode(options)) /* always publish */
-    return dir_port;
-  if (net_is_disabled())
-    return 0;
-  if (!check_whether_dirport_reachable())
-    return 0;
-  if (!router_get_advertised_dir_port(options, dir_port))
-    return 0;
-
-  /* Section two: reasons to publish or not publish that the user
-   * might find surprising. These are generally config options that
-   * make us choose not to publish. */
-
   if (accounting_is_enabled(options)) {
   if (accounting_is_enabled(options)) {
     /* Don't spend bytes for directory traffic if we could end up hibernating,
     /* Don't spend bytes for directory traffic if we could end up hibernating,
      * but allow DirPort otherwise. Some people set AccountingMax because
      * but allow DirPort otherwise. Some people set AccountingMax because
@@ -1158,10 +1159,7 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
       new_choice = 0;
       new_choice = 0;
       reason = "AccountingMax enabled";
       reason = "AccountingMax enabled";
     }
     }
-#define MIN_BW_TO_ADVERTISE_DIRPORT 51200
-  } else if (options->BandwidthRate < MIN_BW_TO_ADVERTISE_DIRPORT ||
-             (options->RelayBandwidthRate > 0 &&
-              options->RelayBandwidthRate < MIN_BW_TO_ADVERTISE_DIRPORT)) {
+  } else if (! router_has_bandwidth_to_be_dirserver(options)) {
     /* if we're advertising a small amount */
     /* if we're advertising a small amount */
     new_choice = 0;
     new_choice = 0;
     reason = "BandwidthRate under 50KB";
     reason = "BandwidthRate under 50KB";
@@ -1169,15 +1167,63 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
 
 
   if (advertising != new_choice) {
   if (advertising != new_choice) {
     if (new_choice == 1) {
     if (new_choice == 1) {
-      log_notice(LD_DIR, "Advertising DirPort as %d", dir_port);
+      if (dir_port > 0)
+        log_notice(LD_DIR, "Advertising DirPort as %d", dir_port);
+      else
+        log_notice(LD_DIR, "Advertising directory service support");
     } else {
     } else {
       tor_assert(reason);
       tor_assert(reason);
-      log_notice(LD_DIR, "Not advertising DirPort (Reason: %s)", reason);
+      log_notice(LD_DIR, "Not advertising Dir%s (Reason: %s)",
+                 dir_port ? "Port" : "ectory Service support", reason);
     }
     }
     advertising = new_choice;
     advertising = new_choice;
   }
   }
 
 
-  return advertising ? dir_port : 0;
+  return advertising;
+}
+
+/** Return 1 if we are configured to accept either relay or directory requests
+ * from clients and we aren't at risk of exceeding our bandwidth limits, thus
+ * we should be a directory server. If not, return 0.
+ */
+int
+dir_server_mode(const or_options_t *options)
+{
+  if (!options->DirCache)
+    return 0;
+  return options->DirPort_set ||
+    (server_mode(options) && router_has_bandwidth_to_be_dirserver(options));
+}
+
+/** Look at a variety of factors, and return 0 if we don't want to
+ * advertise the fact that we have a DirPort open, else return the
+ * DirPort we want to advertise.
+ *
+ * Log a helpful message if we change our mind about whether to publish
+ * a DirPort.
+ */
+static int
+decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
+{
+  /* Part one: reasons to publish or not publish that aren't
+   * worth mentioning to the user, either because they're obvious
+   * or because they're normal behavior. */
+
+  if (!dir_port) /* short circuit the rest of the function */
+    return 0;
+  if (authdir_mode(options)) /* always publish */
+    return dir_port;
+  if (net_is_disabled())
+    return 0;
+  if (!check_whether_dirport_reachable())
+    return 0;
+  if (!router_get_advertised_dir_port(options, dir_port))
+    return 0;
+
+  /* Part two: reasons to publish or not publish that the user
+   * might find surprising. router_should_be_directory_server()
+   * considers config options that make us choose not to publish. */
+  return router_should_be_directory_server(options, dir_port) ? dir_port : 0;
 }
 }
 
 
 /** Allocate and return a new extend_info_t that can be used to build
 /** Allocate and return a new extend_info_t that can be used to build
@@ -1866,6 +1912,8 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
   ri->addr = addr;
   ri->addr = addr;
   ri->or_port = router_get_advertised_or_port(options);
   ri->or_port = router_get_advertised_or_port(options);
   ri->dir_port = router_get_advertised_dir_port(options, 0);
   ri->dir_port = router_get_advertised_dir_port(options, 0);
+  ri->supports_tunnelled_dir_requests = dir_server_mode(options) &&
+    router_should_be_directory_server(options, ri->dir_port);
   ri->cache_info.published_on = time(NULL);
   ri->cache_info.published_on = time(NULL);
   ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from
   ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from
                                                         * main thread */
                                                         * main thread */
@@ -2642,6 +2690,10 @@ router_dump_router_to_string(routerinfo_t *router,
     tor_free(p6);
     tor_free(p6);
   }
   }
 
 
+  if (router->supports_tunnelled_dir_requests) {
+    smartlist_add(chunks, tor_strdup("tunnelled-dir-server\n"));
+  }
+
   /* Sign the descriptor with Ed25519 */
   /* Sign the descriptor with Ed25519 */
   if (emit_ed_sigs)  {
   if (emit_ed_sigs)  {
     smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));
     smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));

+ 1 - 0
src/or/router.h

@@ -41,6 +41,7 @@ int init_keys_client(void);
 
 
 int check_whether_orport_reachable(void);
 int check_whether_orport_reachable(void);
 int check_whether_dirport_reachable(void);
 int check_whether_dirport_reachable(void);
+int dir_server_mode(const or_options_t *options);
 void consider_testing_reachability(int test_or, int test_dir);
 void consider_testing_reachability(int test_or, int test_dir);
 void router_orport_found_reachable(void);
 void router_orport_found_reachable(void);
 void router_dirport_found_reachable(void);
 void router_dirport_found_reachable(void);

+ 10 - 6
src/or/routerlist.c

@@ -67,8 +67,6 @@ typedef struct cert_list_t cert_list_t;
 static int compute_weighted_bandwidths(const smartlist_t *sl,
 static int compute_weighted_bandwidths(const smartlist_t *sl,
                                        bandwidth_weight_rule_t rule,
                                        bandwidth_weight_rule_t rule,
                                        u64_dbl_t **bandwidths_out);
                                        u64_dbl_t **bandwidths_out);
-static const routerstatus_t *router_pick_directory_server_impl(
-                              dirinfo_type_t auth, int flags, int *n_busy_out);
 static const routerstatus_t *router_pick_trusteddirserver_impl(
 static const routerstatus_t *router_pick_trusteddirserver_impl(
                 const smartlist_t *sourcelist, dirinfo_type_t auth,
                 const smartlist_t *sourcelist, dirinfo_type_t auth,
                 int flags, int *n_busy_out);
                 int flags, int *n_busy_out);
@@ -1472,7 +1470,7 @@ router_pick_dirserver_generic(smartlist_t *sourcelist,
  * directories that we excluded for no other reason than
  * directories that we excluded for no other reason than
  * PDS_NO_EXISTING_SERVERDESC_FETCH or PDS_NO_EXISTING_MICRODESC_FETCH.
  * PDS_NO_EXISTING_SERVERDESC_FETCH or PDS_NO_EXISTING_MICRODESC_FETCH.
  */
  */
-static const routerstatus_t *
+STATIC const routerstatus_t *
 router_pick_directory_server_impl(dirinfo_type_t type, int flags,
 router_pick_directory_server_impl(dirinfo_type_t type, int flags,
                                   int *n_busy_out)
                                   int *n_busy_out)
 {
 {
@@ -1512,7 +1510,7 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
     if (!status)
     if (!status)
       continue;
       continue;
 
 
-    if (!node->is_running || !status->dir_port || !node->is_valid)
+    if (!node->is_running || !node_is_dir(node) || !node->is_valid)
       continue;
       continue;
     if (requireother && router_digest_is_me(node->identity))
     if (requireother && router_digest_is_me(node->identity))
       continue;
       continue;
@@ -3238,7 +3236,11 @@ routerlist_reparse_old(routerlist_t *rl, signed_descriptor_t *sd)
   return ri;
   return ri;
 }
 }
 
 
-/** Free all memory held by the routerlist module. */
+/** Free all memory held by the routerlist module.
+ * Note: Calling routerlist_free_all() should always be paired with
+ * a call to nodelist_free_all(). These should only be called during
+ * cleanup.
+ */
 void
 void
 routerlist_free_all(void)
 routerlist_free_all(void)
 {
 {
@@ -4902,7 +4904,9 @@ router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2)
       (r1->contact_info && r2->contact_info &&
       (r1->contact_info && r2->contact_info &&
        strcasecmp(r1->contact_info, r2->contact_info)) ||
        strcasecmp(r1->contact_info, r2->contact_info)) ||
       r1->is_hibernating != r2->is_hibernating ||
       r1->is_hibernating != r2->is_hibernating ||
-      cmp_addr_policies(r1->exit_policy, r2->exit_policy))
+      cmp_addr_policies(r1->exit_policy, r2->exit_policy) ||
+      (r1->supports_tunnelled_dir_requests !=
+       r2->supports_tunnelled_dir_requests))
     return 0;
     return 0;
   if ((r1->declared_family == NULL) != (r2->declared_family == NULL))
   if ((r1->declared_family == NULL) != (r2->declared_family == NULL))
     return 0;
     return 0;

+ 3 - 0
src/or/routerlist.h

@@ -233,6 +233,9 @@ STATIC int choose_array_element_by_weight(const u64_dbl_t *entries,
                                           int n_entries);
                                           int n_entries);
 STATIC void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries,
 STATIC void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries,
                                         uint64_t *total_out);
                                         uint64_t *total_out);
+STATIC const routerstatus_t *router_pick_directory_server_impl(
+                                           dirinfo_type_t auth, int flags,
+                                           int *n_busy_out);
 
 
 MOCK_DECL(int, router_descriptor_is_older_than, (const routerinfo_t *router,
 MOCK_DECL(int, router_descriptor_is_older_than, (const routerinfo_t *router,
                                                  int seconds));
                                                  int seconds));

+ 13 - 2
src/or/routerparse.c

@@ -35,8 +35,9 @@
 /****************************************************************************/
 /****************************************************************************/
 
 
 /** Enumeration of possible token types.  The ones starting with K_ correspond
 /** Enumeration of possible token types.  The ones starting with K_ correspond
- * to directory 'keywords'. ERR_ is an error in the tokenizing process, EOF_
- * is an end-of-file marker, and NIL_ is used to encode not-a-token.
+ * to directory 'keywords'. A_ is for an annotation, R or C is related to
+ * hidden services, ERR_ is an error in the tokenizing process, EOF_ is an
+ * end-of-file marker, and NIL_ is used to encode not-a-token.
  */
  */
 typedef enum {
 typedef enum {
   K_ACCEPT = 0,
   K_ACCEPT = 0,
@@ -125,6 +126,7 @@ typedef enum {
   K_DIR_KEY_CERTIFICATION,
   K_DIR_KEY_CERTIFICATION,
   K_DIR_KEY_CROSSCERT,
   K_DIR_KEY_CROSSCERT,
   K_DIR_ADDRESS,
   K_DIR_ADDRESS,
+  K_DIR_TUNNELLED,
 
 
   K_VOTE_STATUS,
   K_VOTE_STATUS,
   K_VALID_AFTER,
   K_VALID_AFTER,
@@ -318,6 +320,7 @@ static token_rule_t routerdesc_token_table[] = {
   T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
   T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
   T1( "bandwidth",           K_BANDWIDTH,           GE(3),   NO_OBJ ),
   T1( "bandwidth",           K_BANDWIDTH,           GE(3),   NO_OBJ ),
   A01("@purpose",            A_PURPOSE,             GE(1),   NO_OBJ ),
   A01("@purpose",            A_PURPOSE,             GE(1),   NO_OBJ ),
+  T01("tunnelled-dir-server",K_DIR_TUNNELLED,       NO_ARGS, NO_OBJ ),
 
 
   END_OF_TABLE
   END_OF_TABLE
 };
 };
@@ -1609,6 +1612,12 @@ router_parse_entry_from_string(const char *s, const char *end,
     router->wants_to_be_hs_dir = 1;
     router->wants_to_be_hs_dir = 1;
   }
   }
 
 
+  /* This router accepts tunnelled directory requests via begindir if it has
+   * an open dirport or it included "tunnelled-dir-server". */
+  if (find_opt_by_keyword(tokens, K_DIR_TUNNELLED) || router->dir_port > 0) {
+    router->supports_tunnelled_dir_requests = 1;
+  }
+
   tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE);
   tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE);
   note_crypto_pk_op(VERIFY_RTR);
   note_crypto_pk_op(VERIFY_RTR);
 #ifdef COUNT_DISTINCT_DIGESTS
 #ifdef COUNT_DISTINCT_DIGESTS
@@ -2294,6 +2303,8 @@ routerstatus_parse_entry_from_string(memarea_t *area,
         rs->is_unnamed = 1;
         rs->is_unnamed = 1;
       } else if (!strcmp(tok->args[i], "HSDir")) {
       } else if (!strcmp(tok->args[i], "HSDir")) {
         rs->is_hs_dir = 1;
         rs->is_hs_dir = 1;
+      } else if (!strcmp(tok->args[i], "V2Dir")) {
+        rs->is_v2_dir = 1;
       }
       }
     }
     }
   }
   }

+ 1 - 0
src/test/include.am

@@ -79,6 +79,7 @@ src_test_test_SOURCES = \
 	src/test/test_crypto.c \
 	src/test/test_crypto.c \
 	src/test/test_data.c \
 	src/test/test_data.c \
 	src/test/test_dir.c \
 	src/test/test_dir.c \
+	src/test/test_dir_common.c \
 	src/test/test_dir_handle_get.c \
 	src/test/test_dir_handle_get.c \
 	src/test/test_entryconn.c \
 	src/test/test_entryconn.c \
 	src/test/test_entrynodes.c \
 	src/test/test_entrynodes.c \

+ 5 - 0
src/test/test_config.c

@@ -3575,6 +3575,8 @@ test_config_directory_fetch(void *arg)
   mock_advertised_server_mode_result = 1;
   mock_advertised_server_mode_result = 1;
   mock_router_get_my_routerinfo_result = &routerinfo;
   mock_router_get_my_routerinfo_result = &routerinfo;
 
 
+  routerinfo.supports_tunnelled_dir_requests = 1;
+
   options->RefuseUnknownExits = 1;
   options->RefuseUnknownExits = 1;
   tt_assert(server_mode(options) == 1);
   tt_assert(server_mode(options) == 1);
   tt_assert(public_server_mode(options) == 1);
   tt_assert(public_server_mode(options) == 1);
@@ -3598,6 +3600,7 @@ test_config_directory_fetch(void *arg)
   memset(options, 0, sizeof(or_options_t));
   memset(options, 0, sizeof(or_options_t));
   options->DirPort_set = 1;
   options->DirPort_set = 1;
   options->ORPort_set = 1;
   options->ORPort_set = 1;
+  options->DirCache = 1;
   mock_router_pick_published_address_result = 0;
   mock_router_pick_published_address_result = 0;
   mock_router_my_exit_policy_is_reject_star_result = 1;
   mock_router_my_exit_policy_is_reject_star_result = 1;
 
 
@@ -3629,6 +3632,7 @@ test_config_directory_fetch(void *arg)
 
 
   mock_advertised_server_mode_result = 1;
   mock_advertised_server_mode_result = 1;
   routerinfo.dir_port = 0;
   routerinfo.dir_port = 0;
+  routerinfo.supports_tunnelled_dir_requests = 0;
   mock_router_get_my_routerinfo_result = &routerinfo;
   mock_router_get_my_routerinfo_result = &routerinfo;
   tt_assert(server_mode(options) == 1);
   tt_assert(server_mode(options) == 1);
   tt_assert(public_server_mode(options) == 1);
   tt_assert(public_server_mode(options) == 1);
@@ -3638,6 +3642,7 @@ test_config_directory_fetch(void *arg)
 
 
   mock_advertised_server_mode_result = 1;
   mock_advertised_server_mode_result = 1;
   routerinfo.dir_port = 1;
   routerinfo.dir_port = 1;
+  routerinfo.supports_tunnelled_dir_requests = 1;
   mock_router_get_my_routerinfo_result = &routerinfo;
   mock_router_get_my_routerinfo_result = &routerinfo;
   tt_assert(server_mode(options) == 1);
   tt_assert(server_mode(options) == 1);
   tt_assert(public_server_mode(options) == 1);
   tt_assert(public_server_mode(options) == 1);

+ 45 - 276
src/test/test_dir.c

@@ -26,6 +26,7 @@
 #include "routerparse.h"
 #include "routerparse.h"
 #include "routerset.h"
 #include "routerset.h"
 #include "test.h"
 #include "test.h"
+#include "test_dir_common.h"
 #include "torcert.h"
 #include "torcert.h"
 
 
 static void
 static void
@@ -110,6 +111,7 @@ test_dir_formats(void *arg)
   r1->cache_info.published_on = 0;
   r1->cache_info.published_on = 0;
   r1->or_port = 9000;
   r1->or_port = 9000;
   r1->dir_port = 9003;
   r1->dir_port = 9003;
+  r1->supports_tunnelled_dir_requests = 1;
   tor_addr_parse(&r1->ipv6_addr, "1:2:3:4::");
   tor_addr_parse(&r1->ipv6_addr, "1:2:3:4::");
   r1->ipv6_orport = 9999;
   r1->ipv6_orport = 9999;
   r1->onion_pkey = crypto_pk_dup_key(pk1);
   r1->onion_pkey = crypto_pk_dup_key(pk1);
@@ -154,6 +156,7 @@ test_dir_formats(void *arg)
   r2->cache_info.published_on = 5;
   r2->cache_info.published_on = 5;
   r2->or_port = 9005;
   r2->or_port = 9005;
   r2->dir_port = 0;
   r2->dir_port = 0;
+  r2->supports_tunnelled_dir_requests = 1;
   r2->onion_pkey = crypto_pk_dup_key(pk2);
   r2->onion_pkey = crypto_pk_dup_key(pk2);
   curve25519_keypair_t r2_onion_keypair;
   curve25519_keypair_t r2_onion_keypair;
   curve25519_keypair_generate(&r2_onion_keypair, 0);
   curve25519_keypair_generate(&r2_onion_keypair, 0);
@@ -174,7 +177,9 @@ test_dir_formats(void *arg)
   /* XXXX025 router_dump_to_string should really take this from ri.*/
   /* XXXX025 router_dump_to_string should really take this from ri.*/
   options->ContactInfo = tor_strdup("Magri White "
   options->ContactInfo = tor_strdup("Magri White "
                                     "<magri@elsewhere.example.com>");
                                     "<magri@elsewhere.example.com>");
+
   buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL);
   buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL);
+
   tor_free(options->ContactInfo);
   tor_free(options->ContactInfo);
   tt_assert(buf);
   tt_assert(buf);
 
 
@@ -200,7 +205,8 @@ test_dir_formats(void *arg)
   strlcat(buf2, "hidden-service-dir\n", sizeof(buf2));
   strlcat(buf2, "hidden-service-dir\n", sizeof(buf2));
   strlcat(buf2, "contact Magri White <magri@elsewhere.example.com>\n",
   strlcat(buf2, "contact Magri White <magri@elsewhere.example.com>\n",
           sizeof(buf2));
           sizeof(buf2));
-  strlcat(buf2, "reject *:*\nrouter-signature\n", sizeof(buf2));
+  strlcat(buf2, "reject *:*\n", sizeof(buf2));
+  strlcat(buf2, "tunnelled-dir-server\nrouter-signature\n", sizeof(buf2));
   buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same
   buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same
                              * twice */
                              * twice */
 
 
@@ -214,12 +220,13 @@ test_dir_formats(void *arg)
   tt_assert(rp1);
   tt_assert(rp1);
   tt_int_op(rp1->addr,OP_EQ, r1->addr);
   tt_int_op(rp1->addr,OP_EQ, r1->addr);
   tt_int_op(rp1->or_port,OP_EQ, r1->or_port);
   tt_int_op(rp1->or_port,OP_EQ, r1->or_port);
-  //test_eq(rp1->dir_port, r1->dir_port);
+  tt_int_op(rp1->dir_port,OP_EQ, r1->dir_port);
   tt_int_op(rp1->bandwidthrate,OP_EQ, r1->bandwidthrate);
   tt_int_op(rp1->bandwidthrate,OP_EQ, r1->bandwidthrate);
   tt_int_op(rp1->bandwidthburst,OP_EQ, r1->bandwidthburst);
   tt_int_op(rp1->bandwidthburst,OP_EQ, r1->bandwidthburst);
   tt_int_op(rp1->bandwidthcapacity,OP_EQ, r1->bandwidthcapacity);
   tt_int_op(rp1->bandwidthcapacity,OP_EQ, r1->bandwidthcapacity);
   tt_assert(crypto_pk_cmp_keys(rp1->onion_pkey, pk1) == 0);
   tt_assert(crypto_pk_cmp_keys(rp1->onion_pkey, pk1) == 0);
   tt_assert(crypto_pk_cmp_keys(rp1->identity_pkey, pk2) == 0);
   tt_assert(crypto_pk_cmp_keys(rp1->identity_pkey, pk2) == 0);
+  tt_assert(rp1->supports_tunnelled_dir_requests);
   //tt_assert(rp1->exit_policy == NULL);
   //tt_assert(rp1->exit_policy == NULL);
   tor_free(buf);
   tor_free(buf);
 
 
@@ -290,6 +297,7 @@ test_dir_formats(void *arg)
                 BASE64_ENCODE_MULTILINE);
                 BASE64_ENCODE_MULTILINE);
   strlcat(buf2, cert_buf, sizeof(buf2));
   strlcat(buf2, cert_buf, sizeof(buf2));
   strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2));
   strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2));
+  strlcat(buf2, "tunnelled-dir-server\n", sizeof(buf2));
   strlcat(buf2, "router-sig-ed25519 ", sizeof(buf2));
   strlcat(buf2, "router-sig-ed25519 ", sizeof(buf2));
 
 
   buf = router_dump_router_to_string(r2, pk1, pk2, &r2_onion_keypair, &kp2);
   buf = router_dump_router_to_string(r2, pk1, pk2, &r2_onion_keypair, &kp2);
@@ -301,6 +309,8 @@ test_dir_formats(void *arg)
   tor_free(buf);
   tor_free(buf);
 
 
   buf = router_dump_router_to_string(r2, pk1, NULL, NULL, NULL);
   buf = router_dump_router_to_string(r2, pk1, NULL, NULL, NULL);
+
+  /* Reset for later */
   cp = buf;
   cp = buf;
   rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL);
   rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL);
   tt_assert(rp2);
   tt_assert(rp2);
@@ -315,6 +325,7 @@ test_dir_formats(void *arg)
              CURVE25519_PUBKEY_LEN);
              CURVE25519_PUBKEY_LEN);
   tt_assert(crypto_pk_cmp_keys(rp2->onion_pkey, pk2) == 0);
   tt_assert(crypto_pk_cmp_keys(rp2->onion_pkey, pk2) == 0);
   tt_assert(crypto_pk_cmp_keys(rp2->identity_pkey, pk1) == 0);
   tt_assert(crypto_pk_cmp_keys(rp2->identity_pkey, pk1) == 0);
+  tt_assert(rp2->supports_tunnelled_dir_requests);
 
 
   tt_int_op(smartlist_len(rp2->exit_policy),OP_EQ, 2);
   tt_int_op(smartlist_len(rp2->exit_policy),OP_EQ, 2);
 
 
@@ -1477,13 +1488,6 @@ test_dir_param_voting(void *arg)
   return;
   return;
 }
 }
 
 
-extern const char AUTHORITY_CERT_1[];
-extern const char AUTHORITY_SIGNKEY_1[];
-extern const char AUTHORITY_CERT_2[];
-extern const char AUTHORITY_SIGNKEY_2[];
-extern const char AUTHORITY_CERT_3[];
-extern const char AUTHORITY_SIGNKEY_3[];
-
 /** Helper: Test that two networkstatus_voter_info_t do in fact represent the
 /** Helper: Test that two networkstatus_voter_info_t do in fact represent the
  * same voting authority, and that they do in fact have all the same
  * same voting authority, and that they do in fact have all the same
  * information. */
  * information. */
@@ -1503,42 +1507,6 @@ test_same_voter(networkstatus_voter_info_t *v1,
   ;
   ;
 }
 }
 
 
-/** Helper: Make a new routerinfo containing the right information for a
- * given vote_routerstatus_t. */
-static routerinfo_t *
-generate_ri_from_rs(const vote_routerstatus_t *vrs)
-{
-  routerinfo_t *r;
-  const routerstatus_t *rs = &vrs->status;
-  static time_t published = 0;
-
-  r = tor_malloc_zero(sizeof(routerinfo_t));
-  r->cert_expiration_time = TIME_MAX;
-  memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN);
-  memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest,
-         DIGEST_LEN);
-  r->cache_info.do_not_cache = 1;
-  r->cache_info.routerlist_index = -1;
-  r->cache_info.signed_descriptor_body =
-    tor_strdup("123456789012345678901234567890123");
-  r->cache_info.signed_descriptor_len =
-    strlen(r->cache_info.signed_descriptor_body);
-  r->exit_policy = smartlist_new();
-  r->cache_info.published_on = ++published + time(NULL);
-  if (rs->has_bandwidth) {
-    /*
-     * Multiply by 1000 because the routerinfo_t and the routerstatus_t
-     * seem to use different units (*sigh*) and because we seem stuck on
-     * icky and perverse decimal kilobytes (*double sigh*) - see
-     * router_get_advertised_bandwidth_capped() of routerlist.c and
-     * routerstatus_format_entry() of dirserv.c.
-     */
-    r->bandwidthrate = rs->bandwidth_kb * 1000;
-    r->bandwidthcapacity = rs->bandwidth_kb * 1000;
-  }
-  return r;
-}
-
 /** Helper: get a detached signatures document for one or two
 /** Helper: get a detached signatures document for one or two
  * consensuses. */
  * consensuses. */
 static char *
 static char *
@@ -1556,100 +1524,6 @@ get_detached_sigs(networkstatus_t *ns, networkstatus_t *ns2)
   return r;
   return r;
 }
 }
 
 
-/**
- * Generate a routerstatus for v3_networkstatus test
- */
-static vote_routerstatus_t *
-gen_routerstatus_for_v3ns(int idx, time_t now)
-{
-  vote_routerstatus_t *vrs=NULL;
-  routerstatus_t *rs;
-  tor_addr_t addr_ipv6;
-
-  switch (idx) {
-    case 0:
-      /* Generate the first routerstatus. */
-      vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
-      rs = &vrs->status;
-      vrs->version = tor_strdup("0.1.2.14");
-      rs->published_on = now-1500;
-      strlcpy(rs->nickname, "router2", sizeof(rs->nickname));
-      memset(rs->identity_digest, 3, DIGEST_LEN);
-      memset(rs->descriptor_digest, 78, DIGEST_LEN);
-      rs->addr = 0x99008801;
-      rs->or_port = 443;
-      rs->dir_port = 8000;
-      /* all flags but running cleared */
-      rs->is_flagged_running = 1;
-      break;
-    case 1:
-      /* Generate the second routerstatus. */
-      vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
-      rs = &vrs->status;
-      vrs->version = tor_strdup("0.2.0.5");
-      rs->published_on = now-1000;
-      strlcpy(rs->nickname, "router1", sizeof(rs->nickname));
-      memset(rs->identity_digest, 5, DIGEST_LEN);
-      memset(rs->descriptor_digest, 77, DIGEST_LEN);
-      rs->addr = 0x99009901;
-      rs->or_port = 443;
-      rs->dir_port = 0;
-      tor_addr_parse(&addr_ipv6, "[1:2:3::4]");
-      tor_addr_copy(&rs->ipv6_addr, &addr_ipv6);
-      rs->ipv6_orport = 4711;
-      rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running =
-        rs->is_valid = rs->is_possible_guard = 1;
-      break;
-    case 2:
-      /* Generate the third routerstatus. */
-      vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
-      rs = &vrs->status;
-      vrs->version = tor_strdup("0.1.0.3");
-      rs->published_on = now-1000;
-      strlcpy(rs->nickname, "router3", sizeof(rs->nickname));
-      memset(rs->identity_digest, 33, DIGEST_LEN);
-      memset(rs->descriptor_digest, 79, DIGEST_LEN);
-      rs->addr = 0xAA009901;
-      rs->or_port = 400;
-      rs->dir_port = 9999;
-      rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast =
-        rs->is_flagged_running = rs->is_valid =
-        rs->is_possible_guard = 1;
-      break;
-    case 3:
-      /* Generate a fourth routerstatus that is not running. */
-      vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
-      rs = &vrs->status;
-      vrs->version = tor_strdup("0.1.6.3");
-      rs->published_on = now-1000;
-      strlcpy(rs->nickname, "router4", sizeof(rs->nickname));
-      memset(rs->identity_digest, 34, DIGEST_LEN);
-      memset(rs->descriptor_digest, 47, DIGEST_LEN);
-      rs->addr = 0xC0000203;
-      rs->or_port = 500;
-      rs->dir_port = 1999;
-      /* Running flag (and others) cleared */
-      break;
-    case 4:
-      /* No more for this test; return NULL */
-      vrs = NULL;
-      break;
-    default:
-      /* Shouldn't happen */
-      tt_assert(0);
-  }
-  if (vrs) {
-    vrs->microdesc = tor_malloc_zero(sizeof(vote_microdesc_hash_t));
-    tor_asprintf(&vrs->microdesc->microdesc_hash_line,
-                 "m 9,10,11,12,13,14,15,16,17 "
-                 "sha256=xyzajkldsdsajdadlsdjaslsdksdjlsdjsdaskdaaa%d\n",
-                 idx);
-  }
-
- done:
-  return vrs;
-}
-
 /** Apply tweaks to the vote list for each voter */
 /** Apply tweaks to the vote list for each voter */
 static int
 static int
 vote_tweaks_for_v3ns(networkstatus_t *v, int voter, time_t now)
 vote_tweaks_for_v3ns(networkstatus_t *v, int voter, time_t now)
@@ -1681,7 +1555,7 @@ vote_tweaks_for_v3ns(networkstatus_t *v, int voter, time_t now)
       vrs = smartlist_get(v->routerstatus_list, 0);
       vrs = smartlist_get(v->routerstatus_list, 0);
       memset(vrs->status.descriptor_digest, (int)'Z', DIGEST_LEN);
       memset(vrs->status.descriptor_digest, (int)'Z', DIGEST_LEN);
       tt_assert(router_add_to_routerlist(
       tt_assert(router_add_to_routerlist(
-                  generate_ri_from_rs(vrs), &msg,0,0) >= 0);
+                  dir_common_generate_ri_from_rs(vrs), &msg,0,0) >= 0);
     }
     }
   }
   }
 
 
@@ -1746,11 +1620,11 @@ test_vrs_for_v3ns(vote_routerstatus_t *vrs, int voter, time_t now)
     tt_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6));
     tt_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6));
     tt_int_op(rs->ipv6_orport,OP_EQ, 4711);
     tt_int_op(rs->ipv6_orport,OP_EQ, 4711);
     if (voter == 1) {
     if (voter == 1) {
-      /* all except "authority" (1) and "v2dir" (64) */
-      tt_u64_op(vrs->flags, OP_EQ, U64_LITERAL(190));
+      /* all except "authority" (1) */
+      tt_u64_op(vrs->flags, OP_EQ, U64_LITERAL(254));
     } else {
     } else {
-      /* 1023 - authority(1) - madeofcheese(16) - madeoftin(32) - v2dir(256) */
-      tt_u64_op(vrs->flags, OP_EQ, U64_LITERAL(718));
+      /* 1023 - authority(1) - madeofcheese(16) - madeoftin(32) */
+      tt_u64_op(vrs->flags, OP_EQ, U64_LITERAL(974));
     }
     }
   } else if (tor_memeq(rs->identity_digest,
   } else if (tor_memeq(rs->identity_digest,
                        "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33"
                        "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33"
@@ -1820,6 +1694,7 @@ test_routerstatus_for_v3ns(routerstatus_t *rs, time_t now)
     tt_assert(rs->is_flagged_running);
     tt_assert(rs->is_flagged_running);
     tt_assert(!rs->is_valid);
     tt_assert(!rs->is_valid);
     tt_assert(!rs->is_named);
     tt_assert(!rs->is_named);
+    tt_assert(rs->is_v2_dir);
     /* XXXX check version */
     /* XXXX check version */
   } else if (tor_memeq(rs->identity_digest,
   } else if (tor_memeq(rs->identity_digest,
                        "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5"
                        "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5"
@@ -1845,6 +1720,7 @@ test_routerstatus_for_v3ns(routerstatus_t *rs, time_t now)
     tt_assert(rs->is_stable);
     tt_assert(rs->is_stable);
     tt_assert(rs->is_flagged_running);
     tt_assert(rs->is_flagged_running);
     tt_assert(rs->is_valid);
     tt_assert(rs->is_valid);
+    tt_assert(rs->is_v2_dir);
     tt_assert(!rs->is_named);
     tt_assert(!rs->is_named);
     /* XXXX check version */
     /* XXXX check version */
   } else {
   } else {
@@ -1869,7 +1745,6 @@ test_a_networkstatus(
   authority_cert_t *cert1=NULL, *cert2=NULL, *cert3=NULL;
   authority_cert_t *cert1=NULL, *cert2=NULL, *cert3=NULL;
   crypto_pk_t *sign_skey_1=NULL, *sign_skey_2=NULL, *sign_skey_3=NULL;
   crypto_pk_t *sign_skey_1=NULL, *sign_skey_2=NULL, *sign_skey_3=NULL;
   crypto_pk_t *sign_skey_leg1=NULL;
   crypto_pk_t *sign_skey_leg1=NULL;
-  const char *msg=NULL;
   /*
   /*
    * Sum the non-zero returns from vote_tweaks() we've seen; if vote_tweaks()
    * Sum the non-zero returns from vote_tweaks() we've seen; if vote_tweaks()
    * returns non-zero, it changed net_params and we should skip the tests for
    * returns non-zero, it changed net_params and we should skip the tests for
@@ -1885,8 +1760,7 @@ test_a_networkstatus(
   vote_routerstatus_t *vrs;
   vote_routerstatus_t *vrs;
   routerstatus_t *rs;
   routerstatus_t *rs;
   int idx, n_rs, n_vrs;
   int idx, n_rs, n_vrs;
-  char *v1_text=NULL, *v2_text=NULL, *v3_text=NULL, *consensus_text=NULL,
-    *cp=NULL;
+  char *consensus_text=NULL, *cp=NULL;
   smartlist_t *votes = smartlist_new();
   smartlist_t *votes = smartlist_new();
 
 
   /* For generating the two other consensuses. */
   /* For generating the two other consensuses. */
@@ -1901,79 +1775,13 @@ test_a_networkstatus(
   tt_assert(rs_test);
   tt_assert(rs_test);
   tt_assert(vrs_test);
   tt_assert(vrs_test);
 
 
-  /* Parse certificates and keys. */
-  cert1 = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL);
-  tt_assert(cert1);
-  cert2 = authority_cert_parse_from_string(AUTHORITY_CERT_2, NULL);
-  tt_assert(cert2);
-  cert3 = authority_cert_parse_from_string(AUTHORITY_CERT_3, NULL);
-  tt_assert(cert3);
-  sign_skey_1 = crypto_pk_new();
-  sign_skey_2 = crypto_pk_new();
-  sign_skey_3 = crypto_pk_new();
+  tt_assert(!dir_common_authority_pk_init(&cert1, &cert2, &cert3,
+                                          &sign_skey_1, &sign_skey_2,
+                                          &sign_skey_3));
   sign_skey_leg1 = pk_generate(4);
   sign_skey_leg1 = pk_generate(4);
 
 
-  tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_1,
-                                                   AUTHORITY_SIGNKEY_1, -1));
-  tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_2,
-                                                   AUTHORITY_SIGNKEY_2, -1));
-  tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_3,
-                                                   AUTHORITY_SIGNKEY_3, -1));
-
-  tt_assert(!crypto_pk_cmp_keys(sign_skey_1, cert1->signing_key));
-  tt_assert(!crypto_pk_cmp_keys(sign_skey_2, cert2->signing_key));
-
-  /*
-   * Set up a vote; generate it; try to parse it.
-   */
-  vote = tor_malloc_zero(sizeof(networkstatus_t));
-  vote->type = NS_TYPE_VOTE;
-  vote->published = now;
-  vote->valid_after = now+1000;
-  vote->fresh_until = now+2000;
-  vote->valid_until = now+3000;
-  vote->vote_seconds = 100;
-  vote->dist_seconds = 200;
-  vote->supported_methods = smartlist_new();
-  smartlist_split_string(vote->supported_methods, "1 2 3", NULL, 0, -1);
-  vote->client_versions = tor_strdup("0.1.2.14,0.1.2.15");
-  vote->server_versions = tor_strdup("0.1.2.14,0.1.2.15,0.1.2.16");
-  vote->known_flags = smartlist_new();
-  smartlist_split_string(vote->known_flags,
-                     "Authority Exit Fast Guard Running Stable V2Dir Valid",
-                     0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
-  vote->voters = smartlist_new();
-  voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
-  voter->nickname = tor_strdup("Voter1");
-  voter->address = tor_strdup("1.2.3.4");
-  voter->addr = 0x01020304;
-  voter->dir_port = 80;
-  voter->or_port = 9000;
-  voter->contact = tor_strdup("voter@example.com");
-  crypto_pk_get_digest(cert1->identity_key, voter->identity_digest);
-  smartlist_add(vote->voters, voter);
-  vote->cert = authority_cert_dup(cert1);
-  vote->net_params = smartlist_new();
-  smartlist_split_string(vote->net_params, "circuitwindow=101 foo=990",
-                         NULL, 0, 0);
-  vote->routerstatus_list = smartlist_new();
-  /* add routerstatuses */
-  idx = 0;
-  do {
-    vrs = vrs_gen(idx, now);
-    if (vrs) {
-      smartlist_add(vote->routerstatus_list, vrs);
-      tt_assert(router_add_to_routerlist(generate_ri_from_rs(vrs),
-                                           &msg,0,0)>=0);
-      ++idx;
-    }
-  } while (vrs);
-  n_vrs = idx;
-
-  /* dump the vote and try to parse it. */
-  v1_text = format_networkstatus_vote(sign_skey_1, vote);
-  tt_assert(v1_text);
-  v1 = networkstatus_parse_vote_from_string(v1_text, NULL, NS_TYPE_VOTE);
+  tt_assert(!dir_common_construct_vote_1(&vote, cert1, sign_skey_1, vrs_gen,
+                                         &v1, &n_vrs, now, 1));
   tt_assert(v1);
   tt_assert(v1);
 
 
   /* Make sure the parsed thing was right. */
   /* Make sure the parsed thing was right. */
@@ -2000,6 +1808,7 @@ test_a_networkstatus(
   tt_str_op(cp,OP_EQ, "Authority:Exit:Fast:Guard:Running:Stable:V2Dir:Valid");
   tt_str_op(cp,OP_EQ, "Authority:Exit:Fast:Guard:Running:Stable:V2Dir:Valid");
   tor_free(cp);
   tor_free(cp);
   tt_int_op(smartlist_len(v1->routerstatus_list),OP_EQ, n_vrs);
   tt_int_op(smartlist_len(v1->routerstatus_list),OP_EQ, n_vrs);
+  tor_free(vote);
 
 
   if (vote_tweaks) params_tweaked += vote_tweaks(v1, 1, now);
   if (vote_tweaks) params_tweaked += vote_tweaks(v1, 1, now);
 
 
@@ -2011,33 +1820,10 @@ test_a_networkstatus(
   }
   }
 
 
   /* Generate second vote. It disagrees on some of the times,
   /* Generate second vote. It disagrees on some of the times,
-   * and doesn't list versions, and knows some crazy flags */
-  vote->published = now+1;
-  vote->fresh_until = now+3005;
-  vote->dist_seconds = 300;
-  authority_cert_free(vote->cert);
-  vote->cert = authority_cert_dup(cert2);
-  SMARTLIST_FOREACH(vote->net_params, char *, c, tor_free(c));
-  smartlist_clear(vote->net_params);
-  smartlist_split_string(vote->net_params, "bar=2000000000 circuitwindow=20",
-                         NULL, 0, 0);
-  tor_free(vote->client_versions);
-  tor_free(vote->server_versions);
-  voter = smartlist_get(vote->voters, 0);
-  tor_free(voter->nickname);
-  tor_free(voter->address);
-  voter->nickname = tor_strdup("Voter2");
-  voter->address = tor_strdup("2.3.4.5");
-  voter->addr = 0x02030405;
-  crypto_pk_get_digest(cert2->identity_key, voter->identity_digest);
-  smartlist_add(vote->known_flags, tor_strdup("MadeOfCheese"));
-  smartlist_add(vote->known_flags, tor_strdup("MadeOfTin"));
-  smartlist_sort_strings(vote->known_flags);
-
-  /* generate and parse v2. */
-  v2_text = format_networkstatus_vote(sign_skey_2, vote);
-  tt_assert(v2_text);
-  v2 = networkstatus_parse_vote_from_string(v2_text, NULL, NS_TYPE_VOTE);
+   * and doesn't list versions, and knows some crazy flags.
+   * Generate and parse v2. */
+  tt_assert(!dir_common_construct_vote_2(&vote, cert2, sign_skey_2, vrs_gen,
+                                         &v2, &n_vrs, now, 1));
   tt_assert(v2);
   tt_assert(v2);
 
 
   if (vote_tweaks) params_tweaked += vote_tweaks(v2, 2, now);
   if (vote_tweaks) params_tweaked += vote_tweaks(v2, 2, now);
@@ -2055,34 +1841,11 @@ test_a_networkstatus(
     tt_assert(vrs);
     tt_assert(vrs);
     vrs_test(vrs, 2, now);
     vrs_test(vrs, 2, now);
   }
   }
+  tor_free(vote);
 
 
-  /* Generate the third vote. */
-  vote->published = now;
-  vote->fresh_until = now+2003;
-  vote->dist_seconds = 250;
-  authority_cert_free(vote->cert);
-  vote->cert = authority_cert_dup(cert3);
-  SMARTLIST_FOREACH(vote->net_params, char *, c, tor_free(c));
-  smartlist_clear(vote->net_params);
-  smartlist_split_string(vote->net_params, "circuitwindow=80 foo=660",
-                         NULL, 0, 0);
-  smartlist_add(vote->supported_methods, tor_strdup("4"));
-  vote->client_versions = tor_strdup("0.1.2.14,0.1.2.17");
-  vote->server_versions = tor_strdup("0.1.2.10,0.1.2.15,0.1.2.16");
-  voter = smartlist_get(vote->voters, 0);
-  tor_free(voter->nickname);
-  tor_free(voter->address);
-  voter->nickname = tor_strdup("Voter3");
-  voter->address = tor_strdup("3.4.5.6");
-  voter->addr = 0x03040506;
-  crypto_pk_get_digest(cert3->identity_key, voter->identity_digest);
-  /* This one has a legacy id. */
-  memset(voter->legacy_id_digest, (int)'A', DIGEST_LEN);
-
-  v3_text = format_networkstatus_vote(sign_skey_3, vote);
-  tt_assert(v3_text);
-
-  v3 = networkstatus_parse_vote_from_string(v3_text, NULL, NS_TYPE_VOTE);
+  /* Generate the third vote with a legacy id. */
+  tt_assert(!dir_common_construct_vote_3(&vote, cert3, sign_skey_3, vrs_gen,
+                                         &v3, &n_vrs, now, 1));
   tt_assert(v3);
   tt_assert(v3);
 
 
   if (vote_tweaks) params_tweaked += vote_tweaks(v3, 3, now);
   if (vote_tweaks) params_tweaked += vote_tweaks(v3, 3, now);
@@ -2153,12 +1916,20 @@ test_a_networkstatus(
 
 
   /* Check the routerstatuses. */
   /* Check the routerstatuses. */
   n_rs = smartlist_len(con->routerstatus_list);
   n_rs = smartlist_len(con->routerstatus_list);
+  tt_assert(n_rs);
   for (idx = 0; idx < n_rs; ++idx) {
   for (idx = 0; idx < n_rs; ++idx) {
     rs = smartlist_get(con->routerstatus_list, idx);
     rs = smartlist_get(con->routerstatus_list, idx);
     tt_assert(rs);
     tt_assert(rs);
     rs_test(rs, now);
     rs_test(rs, now);
   }
   }
 
 
+  n_rs = smartlist_len(con_md->routerstatus_list);
+  tt_assert(n_rs);
+  for (idx = 0; idx < n_rs; ++idx) {
+    rs = smartlist_get(con_md->routerstatus_list, idx);
+    tt_assert(rs);
+  }
+
   /* Check signatures.  the first voter is a pseudo-entry with a legacy key.
   /* Check signatures.  the first voter is a pseudo-entry with a legacy key.
    * The second one hasn't signed.  The fourth one has signed: validate it. */
    * The second one hasn't signed.  The fourth one has signed: validate it. */
   voter = smartlist_get(con->voters, 1);
   voter = smartlist_get(con->voters, 1);
@@ -2309,9 +2080,6 @@ test_a_networkstatus(
  done:
  done:
   tor_free(cp);
   tor_free(cp);
   smartlist_free(votes);
   smartlist_free(votes);
-  tor_free(v1_text);
-  tor_free(v2_text);
-  tor_free(v3_text);
   tor_free(consensus_text);
   tor_free(consensus_text);
   tor_free(consensus_text_md);
   tor_free(consensus_text_md);
 
 
@@ -2368,7 +2136,7 @@ static void
 test_dir_v3_networkstatus(void *arg)
 test_dir_v3_networkstatus(void *arg)
 {
 {
   (void)arg;
   (void)arg;
-  test_a_networkstatus(gen_routerstatus_for_v3ns,
+  test_a_networkstatus(dir_common_gen_routerstatus_for_v3ns,
                        vote_tweaks_for_v3ns,
                        vote_tweaks_for_v3ns,
                        test_vrs_for_v3ns,
                        test_vrs_for_v3ns,
                        test_consensus_for_v3ns,
                        test_consensus_for_v3ns,
@@ -2965,6 +2733,7 @@ test_dir_fmt_control_ns(void *arg)
   rs.is_fast = 1;
   rs.is_fast = 1;
   rs.is_flagged_running = 1;
   rs.is_flagged_running = 1;
   rs.has_bandwidth = 1;
   rs.has_bandwidth = 1;
+  rs.is_v2_dir = 1;
   rs.bandwidth_kb = 1000;
   rs.bandwidth_kb = 1000;
 
 
   s = networkstatus_getinfo_helper_single(&rs);
   s = networkstatus_getinfo_helper_single(&rs);

+ 424 - 0
src/test/test_dir_common.c

@@ -0,0 +1,424 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#define DIRVOTE_PRIVATE
+#include "crypto.h"
+#include "test.h"
+#include "container.h"
+#include "or.h"
+#include "dirvote.h"
+#include "nodelist.h"
+#include "routerlist.h"
+#include "test_dir_common.h"
+
+void dir_common_setup_vote(networkstatus_t **vote, time_t now);
+networkstatus_t * dir_common_add_rs_and_parse(networkstatus_t *vote,
+                            networkstatus_t **vote_out,
+               vote_routerstatus_t * (*vrs_gen)(int idx, time_t now),
+                            crypto_pk_t *sign_skey, int *n_vrs,
+                            time_t now, int clear_rl);
+
+extern const char AUTHORITY_CERT_1[];
+extern const char AUTHORITY_SIGNKEY_1[];
+extern const char AUTHORITY_CERT_2[];
+extern const char AUTHORITY_SIGNKEY_2[];
+extern const char AUTHORITY_CERT_3[];
+extern const char AUTHORITY_SIGNKEY_3[];
+
+/** Initialize and set auth certs and keys
+ * Returns 0 on success, -1 on failure. Clean up handled by caller.
+ */
+int
+dir_common_authority_pk_init(authority_cert_t **cert1,
+                             authority_cert_t **cert2,
+                             authority_cert_t **cert3,
+                             crypto_pk_t **sign_skey_1,
+                             crypto_pk_t **sign_skey_2,
+                             crypto_pk_t **sign_skey_3)
+{
+  /* Parse certificates and keys. */
+  authority_cert_t *cert;
+  cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL);
+  tt_assert(cert);
+  tt_assert(cert->identity_key);
+  *cert1 = cert;
+  tt_assert(*cert1);
+  *cert2 = authority_cert_parse_from_string(AUTHORITY_CERT_2, NULL);
+  tt_assert(*cert2);
+  *cert3 = authority_cert_parse_from_string(AUTHORITY_CERT_3, NULL);
+  tt_assert(*cert3);
+  *sign_skey_1 = crypto_pk_new();
+  *sign_skey_2 = crypto_pk_new();
+  *sign_skey_3 = crypto_pk_new();
+
+  tt_assert(!crypto_pk_read_private_key_from_string(*sign_skey_1,
+                                                   AUTHORITY_SIGNKEY_1, -1));
+  tt_assert(!crypto_pk_read_private_key_from_string(*sign_skey_2,
+                                                   AUTHORITY_SIGNKEY_2, -1));
+  tt_assert(!crypto_pk_read_private_key_from_string(*sign_skey_3,
+                                                   AUTHORITY_SIGNKEY_3, -1));
+
+  tt_assert(!crypto_pk_cmp_keys(*sign_skey_1, (*cert1)->signing_key));
+  tt_assert(!crypto_pk_cmp_keys(*sign_skey_2, (*cert2)->signing_key));
+
+  return 0;
+ done:
+  return -1;
+}
+
+/**
+ * Generate a routerstatus for v3_networkstatus test.
+ */
+vote_routerstatus_t *
+dir_common_gen_routerstatus_for_v3ns(int idx, time_t now)
+{
+  vote_routerstatus_t *vrs=NULL;
+  routerstatus_t *rs = NULL;
+  tor_addr_t addr_ipv6;
+  char *method_list = NULL;
+
+  switch (idx) {
+    case 0:
+      /* Generate the first routerstatus. */
+      vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
+      rs = &vrs->status;
+      vrs->version = tor_strdup("0.1.2.14");
+      rs->published_on = now-1500;
+      strlcpy(rs->nickname, "router2", sizeof(rs->nickname));
+      memset(rs->identity_digest, TEST_DIR_ROUTER_ID_1, DIGEST_LEN);
+      memset(rs->descriptor_digest, TEST_DIR_ROUTER_DD_1, DIGEST_LEN);
+      rs->addr = 0x99008801;
+      rs->or_port = 443;
+      rs->dir_port = 8000;
+      /* all flags but running and v2dir cleared */
+      rs->is_flagged_running = 1;
+      rs->is_v2_dir = 1;
+      break;
+    case 1:
+      /* Generate the second routerstatus. */
+      vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
+      rs = &vrs->status;
+      vrs->version = tor_strdup("0.2.0.5");
+      rs->published_on = now-1000;
+      strlcpy(rs->nickname, "router1", sizeof(rs->nickname));
+      memset(rs->identity_digest, TEST_DIR_ROUTER_ID_2, DIGEST_LEN);
+      memset(rs->descriptor_digest, TEST_DIR_ROUTER_DD_2, DIGEST_LEN);
+      rs->addr = 0x99009901;
+      rs->or_port = 443;
+      rs->dir_port = 0;
+      tor_addr_parse(&addr_ipv6, "[1:2:3::4]");
+      tor_addr_copy(&rs->ipv6_addr, &addr_ipv6);
+      rs->ipv6_orport = 4711;
+      rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running =
+        rs->is_valid = rs->is_possible_guard = rs->is_v2_dir = 1;
+      break;
+    case 2:
+      /* Generate the third routerstatus. */
+      vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
+      rs = &vrs->status;
+      vrs->version = tor_strdup("0.1.0.3");
+      rs->published_on = now-1000;
+      strlcpy(rs->nickname, "router3", sizeof(rs->nickname));
+      memset(rs->identity_digest, TEST_DIR_ROUTER_ID_3, DIGEST_LEN);
+      memset(rs->descriptor_digest, TEST_DIR_ROUTER_DD_3, DIGEST_LEN);
+      rs->addr = 0xAA009901;
+      rs->or_port = 400;
+      rs->dir_port = 9999;
+      rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast =
+        rs->is_flagged_running = rs->is_valid = rs->is_v2_dir =
+        rs->is_possible_guard = 1;
+      break;
+    case 3:
+      /* Generate a fourth routerstatus that is not running. */
+      vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
+      rs = &vrs->status;
+      vrs->version = tor_strdup("0.1.6.3");
+      rs->published_on = now-1000;
+      strlcpy(rs->nickname, "router4", sizeof(rs->nickname));
+      memset(rs->identity_digest, TEST_DIR_ROUTER_ID_4, DIGEST_LEN);
+      memset(rs->descriptor_digest, TEST_DIR_ROUTER_DD_4, DIGEST_LEN);
+      rs->addr = 0xC0000203;
+      rs->or_port = 500;
+      rs->dir_port = 1999;
+      rs->is_v2_dir = 1;
+      /* Running flag (and others) cleared */
+      break;
+    case 4:
+      /* No more for this test; return NULL */
+      vrs = NULL;
+      break;
+    default:
+      /* Shouldn't happen */
+      tt_assert(0);
+  }
+  if (vrs) {
+    vrs->microdesc = tor_malloc_zero(sizeof(vote_microdesc_hash_t));
+    method_list = make_consensus_method_list(MIN_SUPPORTED_CONSENSUS_METHOD,
+                                             MAX_SUPPORTED_CONSENSUS_METHOD,
+                                             ",");
+    tor_asprintf(&vrs->microdesc->microdesc_hash_line,
+                 "m %s "
+                 "sha256=xyzajkldsdsajdadlsdjaslsdksdjlsdjsdaskdaaa%d\n",
+                 method_list, idx);
+  }
+
+ done:
+  tor_free(method_list);
+  return vrs;
+}
+
+/** Initialize networkstatus vote object attributes. */
+void
+dir_common_setup_vote(networkstatus_t **vote, time_t now)
+{
+  *vote = tor_malloc_zero(sizeof(networkstatus_t));
+  (*vote)->type = NS_TYPE_VOTE;
+  (*vote)->published = now;
+  (*vote)->supported_methods = smartlist_new();
+  (*vote)->known_flags = smartlist_new();
+  (*vote)->net_params = smartlist_new();
+  (*vote)->routerstatus_list = smartlist_new();
+  (*vote)->voters = smartlist_new();
+}
+
+/** Helper: Make a new routerinfo containing the right information for a
+ * given vote_routerstatus_t. */
+routerinfo_t *
+dir_common_generate_ri_from_rs(const vote_routerstatus_t *vrs)
+{
+  routerinfo_t *r;
+  const routerstatus_t *rs = &vrs->status;
+  static time_t published = 0;
+
+  r = tor_malloc_zero(sizeof(routerinfo_t));
+  r->cert_expiration_time = TIME_MAX;
+  memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN);
+  memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest,
+         DIGEST_LEN);
+  r->cache_info.do_not_cache = 1;
+  r->cache_info.routerlist_index = -1;
+  r->cache_info.signed_descriptor_body =
+    tor_strdup("123456789012345678901234567890123");
+  r->cache_info.signed_descriptor_len =
+    strlen(r->cache_info.signed_descriptor_body);
+  r->exit_policy = smartlist_new();
+  r->cache_info.published_on = ++published + time(NULL);
+  if (rs->has_bandwidth) {
+    /*
+     * Multiply by 1000 because the routerinfo_t and the routerstatus_t
+     * seem to use different units (*sigh*) and because we seem stuck on
+     * icky and perverse decimal kilobytes (*double sigh*) - see
+     * router_get_advertised_bandwidth_capped() of routerlist.c and
+     * routerstatus_format_entry() of dirserv.c.
+     */
+    r->bandwidthrate = rs->bandwidth_kb * 1000;
+    r->bandwidthcapacity = rs->bandwidth_kb * 1000;
+  }
+  return r;
+}
+
+/** Create routerstatuses and signed vote.
+ * Create routerstatuses using *vrs_gen* and add them to global routerlist.
+ * Next, create signed vote using *sign_skey* and *vote*, which should have
+ * predefined header fields.
+ * Setting *clear_rl* clears the global routerlist before adding the new
+ * routers.
+ * Return the signed vote, same as *vote_out*. Save the number of routers added
+ * in *n_vrs*.
+ */
+networkstatus_t *
+dir_common_add_rs_and_parse(networkstatus_t *vote, networkstatus_t **vote_out,
+                       vote_routerstatus_t * (*vrs_gen)(int idx, time_t now),
+                       crypto_pk_t *sign_skey, int *n_vrs, time_t now,
+                       int clear_rl)
+{
+  vote_routerstatus_t *vrs;
+  char *v_text=NULL;
+  const char *msg=NULL;
+  int idx;
+  was_router_added_t router_added = -1;
+  *vote_out = NULL;
+
+  if (clear_rl) {
+    nodelist_free_all();
+    routerlist_free_all();
+  }
+
+  idx = 0;
+  do {
+    vrs = vrs_gen(idx, now);
+    if (vrs) {
+      smartlist_add(vote->routerstatus_list, vrs);
+      router_added =
+        router_add_to_routerlist(dir_common_generate_ri_from_rs(vrs),
+                                 &msg,0,0);
+      tt_assert(router_added >= 0);
+      ++idx;
+    }
+  } while (vrs);
+  *n_vrs = idx;
+
+  /* dump the vote and try to parse it. */
+  v_text = format_networkstatus_vote(sign_skey, vote);
+  tt_assert(v_text);
+  *vote_out = networkstatus_parse_vote_from_string(v_text, NULL, NS_TYPE_VOTE);
+
+ done:
+  if (v_text)
+    tor_free(v_text);
+
+  return *vote_out;
+}
+
+/** Create a fake *vote* where *cert* describes the signer, *sign_skey*
+ * is the signing key, and *vrs_gen* is the function we'll use to create the
+ * routers on which we're voting.
+ * We pass *vote_out*, *n_vrs*, and *clear_rl* directly to vrs_gen().
+ * Return 0 on success, return -1 on failure.
+ */
+int
+dir_common_construct_vote_1(networkstatus_t **vote, authority_cert_t *cert,
+                        crypto_pk_t *sign_skey,
+                        vote_routerstatus_t * (*vrs_gen)(int idx, time_t now),
+                        networkstatus_t **vote_out, int *n_vrs,
+                        time_t now, int clear_rl)
+{
+  networkstatus_voter_info_t *voter;
+
+  dir_common_setup_vote(vote, now);
+  (*vote)->valid_after = now+1000;
+  (*vote)->fresh_until = now+2000;
+  (*vote)->valid_until = now+3000;
+  (*vote)->vote_seconds = 100;
+  (*vote)->dist_seconds = 200;
+  smartlist_split_string((*vote)->supported_methods, "1 2 3", NULL, 0, -1);
+  (*vote)->client_versions = tor_strdup("0.1.2.14,0.1.2.15");
+  (*vote)->server_versions = tor_strdup("0.1.2.14,0.1.2.15,0.1.2.16");
+  smartlist_split_string((*vote)->known_flags,
+                     "Authority Exit Fast Guard Running Stable V2Dir Valid",
+                     0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+  voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
+  voter->nickname = tor_strdup("Voter1");
+  voter->address = tor_strdup("1.2.3.4");
+  voter->addr = 0x01020304;
+  voter->dir_port = 80;
+  voter->or_port = 9000;
+  voter->contact = tor_strdup("voter@example.com");
+  crypto_pk_get_digest(cert->identity_key, voter->identity_digest);
+  /*
+   * Set up a vote; generate it; try to parse it.
+   */
+  smartlist_add((*vote)->voters, voter);
+  (*vote)->cert = authority_cert_dup(cert);
+  smartlist_split_string((*vote)->net_params, "circuitwindow=101 foo=990",
+                         NULL, 0, 0);
+  *n_vrs = 0;
+  /* add routerstatuses */
+  if (!dir_common_add_rs_and_parse(*vote, vote_out, vrs_gen, sign_skey,
+                                  n_vrs, now, clear_rl))
+    return -1;
+
+  return 0;
+}
+
+/** See dir_common_construct_vote_1.
+ * Produces a vote with slightly different values.
+ */
+int
+dir_common_construct_vote_2(networkstatus_t **vote, authority_cert_t *cert,
+                        crypto_pk_t *sign_skey,
+                        vote_routerstatus_t * (*vrs_gen)(int idx, time_t now),
+                        networkstatus_t **vote_out, int *n_vrs,
+                        time_t now, int clear_rl)
+{
+  networkstatus_voter_info_t *voter;
+
+  dir_common_setup_vote(vote, now);
+  (*vote)->type = NS_TYPE_VOTE;
+  (*vote)->published += 1;
+  (*vote)->valid_after = now+1000;
+  (*vote)->fresh_until = now+3005;
+  (*vote)->valid_until = now+3000;
+  (*vote)->vote_seconds = 100;
+  (*vote)->dist_seconds = 300;
+  smartlist_split_string((*vote)->supported_methods, "1 2 3", NULL, 0, -1);
+  smartlist_split_string((*vote)->known_flags,
+                         "Authority Exit Fast Guard MadeOfCheese MadeOfTin "
+                         "Running Stable V2Dir Valid", 0,
+                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+  voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
+  voter->nickname = tor_strdup("Voter2");
+  voter->address = tor_strdup("2.3.4.5");
+  voter->addr = 0x02030405;
+  voter->dir_port = 80;
+  voter->or_port = 9000;
+  voter->contact = tor_strdup("voter@example.com");
+  crypto_pk_get_digest(cert->identity_key, voter->identity_digest);
+  /*
+   * Set up a vote; generate it; try to parse it.
+   */
+  smartlist_add((*vote)->voters, voter);
+  (*vote)->cert = authority_cert_dup(cert);
+  (*vote)->net_params = smartlist_new();
+  smartlist_split_string((*vote)->net_params,
+                         "bar=2000000000 circuitwindow=20",
+                         NULL, 0, 0);
+  /* add routerstatuses */
+  /* dump the vote and try to parse it. */
+  dir_common_add_rs_and_parse(*vote, vote_out, vrs_gen, sign_skey,
+                              n_vrs, now, clear_rl);
+
+  return 0;
+}
+
+/** See dir_common_construct_vote_1.
+ * Produces a vote with slightly different values. Adds a legacy key.
+ */
+int
+dir_common_construct_vote_3(networkstatus_t **vote, authority_cert_t *cert,
+                        crypto_pk_t *sign_skey,
+                        vote_routerstatus_t * (*vrs_gen)(int idx, time_t now),
+                        networkstatus_t **vote_out, int *n_vrs,
+                        time_t now, int clear_rl)
+{
+  networkstatus_voter_info_t *voter;
+
+  dir_common_setup_vote(vote, now);
+  (*vote)->valid_after = now+1000;
+  (*vote)->fresh_until = now+2003;
+  (*vote)->valid_until = now+3000;
+  (*vote)->vote_seconds = 100;
+  (*vote)->dist_seconds = 250;
+  smartlist_split_string((*vote)->supported_methods, "1 2 3 4", NULL, 0, -1);
+  (*vote)->client_versions = tor_strdup("0.1.2.14,0.1.2.17");
+  (*vote)->server_versions = tor_strdup("0.1.2.10,0.1.2.15,0.1.2.16");
+  smartlist_split_string((*vote)->known_flags,
+                     "Authority Exit Fast Guard Running Stable V2Dir Valid",
+                     0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+  voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
+  voter->nickname = tor_strdup("Voter2");
+  voter->address = tor_strdup("3.4.5.6");
+  voter->addr = 0x03040506;
+  voter->dir_port = 80;
+  voter->or_port = 9000;
+  voter->contact = tor_strdup("voter@example.com");
+  crypto_pk_get_digest(cert->identity_key, voter->identity_digest);
+  memset(voter->legacy_id_digest, (int)'A', DIGEST_LEN);
+  /*
+   * Set up a vote; generate it; try to parse it.
+   */
+  smartlist_add((*vote)->voters, voter);
+  (*vote)->cert = authority_cert_dup(cert);
+  smartlist_split_string((*vote)->net_params, "circuitwindow=80 foo=660",
+                         NULL, 0, 0);
+  /* add routerstatuses */
+  /* dump the vote and try to parse it. */
+  dir_common_add_rs_and_parse(*vote, vote_out, vrs_gen, sign_skey,
+                              n_vrs, now, clear_rl);
+
+  return 0;
+}
+

+ 52 - 0
src/test/test_dir_common.h

@@ -0,0 +1,52 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "or.h"
+#include "networkstatus.h"
+#include "routerparse.h"
+
+#define TEST_DIR_ROUTER_ID_1 3
+#define TEST_DIR_ROUTER_ID_2 5
+#define TEST_DIR_ROUTER_ID_3 33
+#define TEST_DIR_ROUTER_ID_4 34
+
+#define TEST_DIR_ROUTER_DD_1 78
+#define TEST_DIR_ROUTER_DD_2 77
+#define TEST_DIR_ROUTER_DD_3 79
+#define TEST_DIR_ROUTER_DD_4 44
+
+int dir_common_authority_pk_init(authority_cert_t **cert1,
+                       authority_cert_t **cert2,
+                       authority_cert_t **cert3,
+                       crypto_pk_t **sign_skey_1,
+                       crypto_pk_t **sign_skey_2,
+                       crypto_pk_t **sign_skey_3);
+
+routerinfo_t * dir_common_generate_ri_from_rs(const vote_routerstatus_t *vrs);
+
+vote_routerstatus_t * dir_common_gen_routerstatus_for_v3ns(int idx,
+                                                           time_t now);
+
+int dir_common_construct_vote_1(networkstatus_t **vote,
+                        authority_cert_t *cert1,
+                        crypto_pk_t *sign_skey,
+                        vote_routerstatus_t * (*vrs_gen)(int idx, time_t now),
+                        networkstatus_t **vote_out, int *n_vrs, time_t now,
+                        int clear_rl);
+
+int dir_common_construct_vote_2(networkstatus_t **vote,
+                        authority_cert_t *cert2,
+                        crypto_pk_t *sign_skey,
+                        vote_routerstatus_t * (*vrs_gen)(int idx, time_t now),
+                        networkstatus_t **vote_out, int *n_vrs, time_t now,
+                        int clear_rl);
+
+int dir_common_construct_vote_3(networkstatus_t **vote,
+                        authority_cert_t *cert3,
+                        crypto_pk_t *sign_skey,
+                        vote_routerstatus_t * (*vrs_gen)(int idx, time_t now),
+                        networkstatus_t **vote_out, int *n_vrs, time_t now,
+                        int clear_rl);
+

+ 41 - 0
src/test/test_nodelist.c

@@ -60,12 +60,53 @@ test_nodelist_node_get_verbose_nickname_not_named(void *arg)
   return;
   return;
 }
 }
 
 
+/** A node should be considered a directory server if it has an open dirport
+ * of it accepts tunnelled directory requests.
+ */
+static void
+test_nodelist_node_is_dir(void *arg)
+{
+  (void)arg;
+
+  routerstatus_t rs;
+  routerinfo_t ri;
+  node_t node;
+  memset(&node, 0, sizeof(node_t));
+  memset(&rs, 0, sizeof(routerstatus_t));
+  memset(&ri, 0, sizeof(routerinfo_t));
+
+  tt_assert(!node_is_dir(&node));
+
+  node.rs = &rs;
+  tt_assert(!node_is_dir(&node));
+
+  rs.is_v2_dir = 1;
+  tt_assert(node_is_dir(&node));
+
+  rs.is_v2_dir = 0;
+  rs.dir_port = 1;
+  tt_assert(! node_is_dir(&node));
+
+  node.rs = NULL;
+  tt_assert(!node_is_dir(&node));
+  node.ri = &ri;
+  ri.supports_tunnelled_dir_requests = 1;
+  tt_assert(node_is_dir(&node));
+  ri.supports_tunnelled_dir_requests = 0;
+  ri.dir_port = 1;
+  tt_assert(! node_is_dir(&node));
+
+ done:
+  return;
+}
+
 #define NODE(name, flags) \
 #define NODE(name, flags) \
   { #name, test_nodelist_##name, (flags), NULL, NULL }
   { #name, test_nodelist_##name, (flags), NULL, NULL }
 
 
 struct testcase_t nodelist_tests[] = {
 struct testcase_t nodelist_tests[] = {
   NODE(node_get_verbose_nickname_by_id_null_node, TT_FORK),
   NODE(node_get_verbose_nickname_by_id_null_node, TT_FORK),
   NODE(node_get_verbose_nickname_not_named, TT_FORK),
   NODE(node_get_verbose_nickname_not_named, TT_FORK),
+  NODE(node_is_dir, TT_FORK),
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };
 
 

+ 110 - 5
src/test/test_options.c

@@ -69,22 +69,29 @@ clear_log_messages(void)
   messages = NULL;
   messages = NULL;
 }
 }
 
 
+#define setup_options(opt,dflt)              \
+  do {                                       \
+    opt = options_new();                     \
+    opt->command = CMD_RUN_TOR;              \
+    options_init(opt);                       \
+                                             \
+    dflt = config_dup(&options_format, opt); \
+    clear_log_messages();                    \
+  } while (0)
+
 static void
 static void
 test_options_validate_impl(const char *configuration,
 test_options_validate_impl(const char *configuration,
                            const char *expect_errmsg,
                            const char *expect_errmsg,
                            int expect_log_severity,
                            int expect_log_severity,
                            const char *expect_log)
                            const char *expect_log)
 {
 {
-  or_options_t *opt = options_new();
+  or_options_t *opt=NULL;
   or_options_t *dflt;
   or_options_t *dflt;
   config_line_t *cl=NULL;
   config_line_t *cl=NULL;
   char *msg=NULL;
   char *msg=NULL;
   int r;
   int r;
-  opt->command = CMD_RUN_TOR;
-  options_init(opt);
 
 
-  dflt = config_dup(&options_format, opt);
-  clear_log_messages();
+  setup_options(opt, dflt);
 
 
   r = config_get_lines(configuration, &cl, 1);
   r = config_get_lines(configuration, &cl, 1);
   tt_int_op(r, OP_EQ, 0);
   tt_int_op(r, OP_EQ, 0);
@@ -159,12 +166,110 @@ test_options_validate(void *arg)
                "ServerTransportOptions did not parse",
                "ServerTransportOptions did not parse",
                LOG_WARN, "\"slingsnappy\" is not a k=v");
                LOG_WARN, "\"slingsnappy\" is not a k=v");
 
 
+  WANT_ERR("DirPort 8080\nDirCache 0",
+           "DirPort configured but DirCache disabled.");
+  WANT_ERR("BridgeRelay 1\nDirCache 0",
+           "We're a bridge but DirCache is disabled.");
+
   clear_log_messages();
   clear_log_messages();
   return;
   return;
 }
 }
 
 
+#define MEGABYTEIFY(mb) (U64_LITERAL(mb) << 20)
+static void
+test_have_enough_mem_for_dircache(void *arg)
+{
+  (void)arg;
+  or_options_t *opt=NULL;
+  or_options_t *dflt;
+  config_line_t *cl=NULL;
+  char *msg=NULL;;
+  int r;
+  const char *configuration = "ORPort 8080\nDirCache 1", *expect_errmsg;
+
+  setup_options(opt, dflt);
+  setup_log_callback();
+  (void)dflt;
+
+  r = config_get_lines(configuration, &cl, 1);
+  tt_int_op(r, OP_EQ, 0);
+
+  r = config_assign(&options_format, opt, cl, 0, 0, &msg);
+  tt_int_op(r, OP_EQ, 0);
+
+  /* 300 MB RAM available, DirCache enabled */
+  r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg);
+  tt_int_op(r, OP_EQ, 0);
+  tt_assert(!msg);
+
+  /* 200 MB RAM available, DirCache enabled */
+  r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg);
+  tt_int_op(r, OP_EQ, -1);
+  expect_errmsg = "Being a directory cache (default) with less than ";
+  if (!strstr(msg, expect_errmsg)) {
+    TT_DIE(("Expected error message <%s> from <%s>, but got <%s>.",
+            expect_errmsg, configuration, msg));
+  }
+  tor_free(msg);
+
+  configuration = "ORPort 8080\nDirCache 1\nBridgeRelay 1";
+  r = config_get_lines(configuration, &cl, 1);
+  tt_int_op(r, OP_EQ, 0);
+
+  r = config_assign(&options_format, opt, cl, 0, 0, &msg);
+  tt_int_op(r, OP_EQ, 0);
+
+  /* 300 MB RAM available, DirCache enabled, Bridge */
+  r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg);
+  tt_int_op(r, OP_EQ, 0);
+  tt_assert(!msg);
+
+  /* 200 MB RAM available, DirCache enabled, Bridge */
+  r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg);
+  tt_int_op(r, OP_EQ, -1);
+  expect_errmsg = "Running a Bridge with less than ";
+  if (!strstr(msg, expect_errmsg)) {
+    TT_DIE(("Expected error message <%s> from <%s>, but got <%s>.",
+            expect_errmsg, configuration, msg));
+  }
+  tor_free(msg);
+
+  configuration = "ORPort 8080\nDirCache 0";
+  r = config_get_lines(configuration, &cl, 1);
+  tt_int_op(r, OP_EQ, 0);
+
+  r = config_assign(&options_format, opt, cl, 0, 0, &msg);
+  tt_int_op(r, OP_EQ, 0);
+
+  /* 200 MB RAM available, DirCache disabled */
+  r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg);
+  tt_int_op(r, OP_EQ, 0);
+  tt_assert(!msg);
+
+  /* 300 MB RAM available, DirCache disabled */
+  r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg);
+  tt_int_op(r, OP_EQ, -1);
+  expect_errmsg = "DirCache is disabled and we are configured as a ";
+  if (!strstr(msg, expect_errmsg)) {
+    TT_DIE(("Expected error message <%s> from <%s>, but got <%s>.",
+            expect_errmsg, configuration, msg));
+  }
+  tor_free(msg);
+
+  clear_log_messages();
+
+ done:
+  if (msg)
+    tor_free(msg);
+  tor_free(dflt);
+  tor_free(opt);
+  tor_free(cl);
+  return;
+}
+
 struct testcase_t options_tests[] = {
 struct testcase_t options_tests[] = {
   { "validate", test_options_validate, TT_FORK, NULL, NULL },
   { "validate", test_options_validate, TT_FORK, NULL, NULL },
+  { "mem_dircache", test_have_enough_mem_for_dircache, TT_FORK, NULL, NULL },
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };
 
 

+ 281 - 1
src/test/test_routerlist.c

@@ -1,11 +1,35 @@
 /* Copyright (c) 2014, The Tor Project, Inc. */
 /* Copyright (c) 2014, The Tor Project, Inc. */
 /* See LICENSE for licensing information */
 /* See LICENSE for licensing information */
 
 
+#include "orconfig.h"
+#include <math.h>
+#include <time.h>
+
+#define DIRVOTE_PRIVATE
+#define NETWORKSTATUS_PRIVATE
 #define ROUTERLIST_PRIVATE
 #define ROUTERLIST_PRIVATE
+#define TOR_UNIT_TESTING
 #include "or.h"
 #include "or.h"
-#include "routerlist.h"
+#include "config.h"
+#include "container.h"
 #include "directory.h"
 #include "directory.h"
+#include "dirvote.h"
+#include "networkstatus.h"
+#include "nodelist.h"
+#include "policies.h"
+#include "routerlist.h"
+#include "routerparse.h"
 #include "test.h"
 #include "test.h"
+#include "test_dir_common.h"
+
+extern const char AUTHORITY_CERT_1[];
+extern const char AUTHORITY_SIGNKEY_1[];
+extern const char AUTHORITY_CERT_2[];
+extern const char AUTHORITY_SIGNKEY_2[];
+extern const char AUTHORITY_CERT_3[];
+extern const char AUTHORITY_SIGNKEY_3[];
+
+void construct_consensus(const char **consensus_text_md);
 
 
 /* 4 digests + 3 sep + pre + post + NULL */
 /* 4 digests + 3 sep + pre + post + NULL */
 static char output[4*BASE64_DIGEST256_LEN+3+2+2+1];
 static char output[4*BASE64_DIGEST256_LEN+3+2+2+1];
@@ -94,12 +118,268 @@ test_routerlist_launch_descriptor_downloads(void *arg)
   smartlist_free(downloadable);
   smartlist_free(downloadable);
 }
 }
 
 
+void
+construct_consensus(const char **consensus_text_md)
+{
+  networkstatus_t *vote = NULL;
+  networkstatus_t *v1 = NULL, *v2 = NULL, *v3 = NULL;
+  networkstatus_voter_info_t *voter = NULL;
+  authority_cert_t *cert1=NULL, *cert2=NULL, *cert3=NULL;
+  crypto_pk_t *sign_skey_1=NULL, *sign_skey_2=NULL, *sign_skey_3=NULL;
+  crypto_pk_t *sign_skey_leg=NULL;
+  time_t now = time(NULL);
+  smartlist_t *votes = NULL;
+  addr_policy_t *pol1 = NULL, *pol2 = NULL, *pol3 = NULL;
+  int n_vrs;
+
+  tt_assert(!dir_common_authority_pk_init(&cert1, &cert2, &cert3,
+                                          &sign_skey_1, &sign_skey_2,
+                                          &sign_skey_3));
+  sign_skey_leg = pk_generate(4);
+
+  dir_common_construct_vote_1(&vote, cert1, sign_skey_1,
+                              &dir_common_gen_routerstatus_for_v3ns,
+                              &v1, &n_vrs, now, 1);
+
+  tt_assert(v1);
+  tt_int_op(n_vrs, ==, 4);
+  tt_int_op(smartlist_len(v1->routerstatus_list), ==, 4);
+
+  dir_common_construct_vote_2(&vote, cert2, sign_skey_2,
+                              &dir_common_gen_routerstatus_for_v3ns,
+                              &v2, &n_vrs, now, 1);
+
+  tt_assert(v2);
+  tt_int_op(n_vrs, ==, 4);
+  tt_int_op(smartlist_len(v2->routerstatus_list), ==, 4);
+
+  dir_common_construct_vote_3(&vote, cert3, sign_skey_3,
+                              &dir_common_gen_routerstatus_for_v3ns,
+                              &v3, &n_vrs, now, 1);
+
+  tt_assert(v3);
+  tt_int_op(n_vrs, ==, 4);
+  tt_int_op(smartlist_len(v3->routerstatus_list), ==, 4);
+
+  votes = smartlist_new();
+  smartlist_add(votes, v1);
+  smartlist_add(votes, v2);
+  smartlist_add(votes, v3);
+
+  *consensus_text_md = networkstatus_compute_consensus(votes, 3,
+                                                   cert1->identity_key,
+                                                   sign_skey_1,
+                                                   "AAAAAAAAAAAAAAAAAAAA",
+                                                   sign_skey_leg,
+                                                   FLAV_MICRODESC);
+
+  tt_assert(*consensus_text_md);
+
+ done:
+  if (vote)
+    tor_free(vote);
+  if (voter)
+    tor_free(voter);
+  if (pol1)
+    tor_free(pol1);
+  if (pol2)
+    tor_free(pol2);
+  if (pol3)
+    tor_free(pol3);
+}
+
+static void
+test_router_pick_directory_server_impl(void *arg)
+{
+  (void)arg;
+
+  networkstatus_t *con_md = NULL;
+  const char *consensus_text_md = NULL;
+  int flags = PDS_IGNORE_FASCISTFIREWALL|PDS_RETRY_IF_NO_SERVERS;
+  or_options_t *options = get_options_mutable();
+  const routerstatus_t *rs = NULL;
+  options->UseMicrodescriptors = 1;
+  char *router1_id = NULL, *router2_id = NULL, *router3_id = NULL;
+  node_t *node_router1 = NULL, *node_router2 = NULL, *node_router3 = NULL;
+  config_line_t *policy_line = NULL;
+  time_t now = time(NULL);
+  int tmp_dirport1, tmp_dirport3;
+
+  (void)arg;
+
+  /* No consensus available, fail early */
+  rs = router_pick_directory_server_impl(V3_DIRINFO, (const int) 0, NULL);
+  tt_assert(rs == NULL);
+
+  construct_consensus(&consensus_text_md);
+  tt_assert(consensus_text_md);
+  con_md = networkstatus_parse_vote_from_string(consensus_text_md, NULL,
+                                                NS_TYPE_CONSENSUS);
+  tt_assert(con_md);
+  tt_int_op(con_md->flavor,==, FLAV_MICRODESC);
+  tt_assert(con_md->routerstatus_list);
+  tt_int_op(smartlist_len(con_md->routerstatus_list), ==, 3);
+  tt_assert(!networkstatus_set_current_consensus_from_ns(con_md,
+                                                 "microdesc"));
+  nodelist_set_consensus(con_md);
+  nodelist_assert_ok();
+
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  /* We should not fail now we have a consensus and routerstatus_list
+   * and nodelist are populated. */
+  tt_assert(rs != NULL);
+
+  /* Manipulate the nodes so we get the dir server we expect */
+  router1_id = tor_malloc(DIGEST_LEN);
+  memset(router1_id, TEST_DIR_ROUTER_ID_1, DIGEST_LEN);
+  router2_id = tor_malloc(DIGEST_LEN);
+  memset(router2_id, TEST_DIR_ROUTER_ID_2, DIGEST_LEN);
+  router3_id = tor_malloc(DIGEST_LEN);
+  memset(router3_id, TEST_DIR_ROUTER_ID_3, DIGEST_LEN);
+
+  node_router1 = node_get_mutable_by_id(router1_id);
+  node_router2 = node_get_mutable_by_id(router2_id);
+  node_router3 = node_get_mutable_by_id(router3_id);
+
+  node_router1->is_possible_guard = 1;
+
+  node_router1->is_running = 0;
+  node_router3->is_running = 0;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN));
+  rs = NULL;
+  node_router1->is_running = 1;
+  node_router3->is_running = 1;
+
+  node_router1->rs->is_v2_dir = 0;
+  node_router3->rs->is_v2_dir = 0;
+  tmp_dirport1 = node_router1->rs->dir_port;
+  tmp_dirport3 = node_router3->rs->dir_port;
+  node_router1->rs->dir_port = 0;
+  node_router3->rs->dir_port = 0;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN));
+  rs = NULL;
+  node_router1->rs->is_v2_dir = 1;
+  node_router3->rs->is_v2_dir = 1;
+  node_router1->rs->dir_port = tmp_dirport1;
+  node_router3->rs->dir_port = tmp_dirport3;
+
+  node_router1->is_valid = 0;
+  node_router3->is_valid = 0;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN));
+  rs = NULL;
+  node_router1->is_valid = 1;
+  node_router3->is_valid = 1;
+
+  flags |= PDS_FOR_GUARD;
+  node_router1->using_as_guard = 1;
+  node_router2->using_as_guard = 1;
+  node_router3->using_as_guard = 1;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs == NULL);
+  node_router1->using_as_guard = 0;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN));
+  rs = NULL;
+  node_router2->using_as_guard = 0;
+  node_router3->using_as_guard = 0;
+
+  /* One not valid, one guard. This should leave one remaining */
+  node_router1->is_valid = 0;
+  node_router2->using_as_guard = 1;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router3_id, DIGEST_LEN));
+  rs = NULL;
+  node_router1->is_valid = 1;
+  node_router2->using_as_guard = 0;
+
+  /* Manipulate overloaded */
+
+  node_router2->rs->last_dir_503_at = now;
+  node_router3->rs->last_dir_503_at = now;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN));
+  node_router2->rs->last_dir_503_at = 0;
+  node_router3->rs->last_dir_503_at = 0;
+
+  /* Set a Fascist firewall */
+  flags &= ! PDS_IGNORE_FASCISTFIREWALL;
+  policy_line = tor_malloc_zero(sizeof(config_line_t));
+  policy_line->key = tor_strdup("ReachableORAddresses");
+  policy_line->value = tor_strdup("accept *:442, reject *:*");
+  options->ReachableORAddresses = policy_line;
+  policies_parse_from_options(options);
+
+  node_router1->rs->or_port = 444;
+  node_router2->rs->or_port = 443;
+  node_router3->rs->or_port = 442;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router3_id, DIGEST_LEN));
+  node_router1->rs->or_port = 442;
+  node_router2->rs->or_port = 443;
+  node_router3->rs->or_port = 444;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN));
+
+  /* Fascist firewall and overloaded */
+  node_router1->rs->or_port = 442;
+  node_router2->rs->or_port = 443;
+  node_router3->rs->or_port = 442;
+  node_router3->rs->last_dir_503_at = now;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN));
+  node_router3->rs->last_dir_503_at = 0;
+
+  /* Fascists against OR and Dir */
+  policy_line = tor_malloc_zero(sizeof(config_line_t));
+  policy_line->key = tor_strdup("ReachableAddresses");
+  policy_line->value = tor_strdup("accept *:80, reject *:*");
+  options->ReachableDirAddresses = policy_line;
+  policies_parse_from_options(options);
+  node_router1->rs->or_port = 442;
+  node_router2->rs->or_port = 441;
+  node_router3->rs->or_port = 443;
+  node_router1->rs->dir_port = 80;
+  node_router2->rs->dir_port = 80;
+  node_router3->rs->dir_port = 81;
+  node_router1->rs->last_dir_503_at = now;
+  rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
+  tt_assert(rs != NULL);
+  tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN));
+  node_router1->rs->last_dir_503_at = 0;
+
+ done:
+  if (router1_id)
+    tor_free(router1_id);
+  if (router2_id)
+    tor_free(router2_id);
+  if (router3_id)
+    tor_free(router3_id);
+  if (options->ReachableORAddresses ||
+      options->ReachableDirAddresses)
+    policies_free_all();
+}
+
 #define NODE(name, flags) \
 #define NODE(name, flags) \
   { #name, test_routerlist_##name, (flags), NULL, NULL }
   { #name, test_routerlist_##name, (flags), NULL, NULL }
+#define ROUTER(name,flags) \
+  { #name, test_router_##name, (flags), NULL, NULL }
 
 
 struct testcase_t routerlist_tests[] = {
 struct testcase_t routerlist_tests[] = {
   NODE(initiate_descriptor_downloads, 0),
   NODE(initiate_descriptor_downloads, 0),
   NODE(launch_descriptor_downloads, 0),
   NODE(launch_descriptor_downloads, 0),
+  ROUTER(pick_directory_server_impl, TT_FORK),
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };