[tor-commits] [tor/master] Split most of dirserv.c into several new modules

nickm at torproject.org nickm at torproject.org
Thu Sep 20 15:07:57 UTC 2018


commit b54a5e704ff4d3585bdd8459b83d4c522d070fba
Author: Nick Mathewson <nickm at torproject.org>
Date:   Thu Sep 20 08:09:35 2018 -0400

    Split most of dirserv.c into several new modules
    
    In dirauth:
      * bwauth.c reads and uses bandwidth files
      * guardfraction.c reads and uses the guardfraction file
      * reachability.c tests relay reachability
      * recommend_pkg.c handles the recommended-packages lines.
      * recv_descs.c handles fingerprint files and processing incoming
        routerinfos that relays upload to us
      * voteflag.c computes flag thresholds and sets those thresholds on
        routerstatuses when computing votes
    
    In control:
      * fmt_serverstatus.c generates the ancient "v1 server status"
        format that controllers expect.
    
    In nodelist:
      * routerstatus_fmt.c formats routerstatus entries for a consensus,
        a vote, or for the controller.
---
 src/app/config/config.c                 |    3 +
 src/core/include.am                     |   20 +
 src/core/mainloop/main.c                |    5 +
 src/core/or/connection_or.c             |    2 +-
 src/core/or/policies.c                  |    4 +-
 src/feature/control/control.c           |    1 +
 src/feature/control/fmt_serverstatus.c  |  103 ++
 src/feature/control/fmt_serverstatus.h  |   18 +
 src/feature/dirauth/bwauth.c            |  453 +++++
 src/feature/dirauth/bwauth.h            |   58 +
 src/feature/dirauth/dirvote.c           |    5 +
 src/feature/dirauth/dirvote.h           |    8 +-
 src/feature/dirauth/guardfraction.c     |  333 ++++
 src/feature/dirauth/guardfraction.h     |   24 +
 src/feature/dirauth/process_descs.c     |  835 +++++++++
 src/feature/dirauth/process_descs.h     |   38 +
 src/feature/dirauth/reachability.c      |  205 +++
 src/feature/dirauth/reachability.h      |   36 +
 src/feature/dirauth/recommend_pkg.c     |   90 +
 src/feature/dirauth/recommend_pkg.h     |   17 +
 src/feature/dirauth/voteflags.c         |  644 +++++++
 src/feature/dirauth/voteflags.h         |   31 +
 src/feature/dircache/directory.c        |    1 +
 src/feature/dircache/dirserv.c          | 3002 ++-----------------------------
 src/feature/dircache/dirserv.h          |  130 --
 src/feature/nodelist/fmt_routerstatus.c |  253 +++
 src/feature/nodelist/fmt_routerstatus.h |   41 +
 src/feature/nodelist/networkstatus.c    |    3 +
 src/feature/nodelist/nodelist.c         |    1 +
 src/feature/nodelist/routerlist.c       |    2 +
 src/feature/nodelist/routerparse.c      |    1 -
 src/feature/relay/router.c              |    1 +
 src/test/test_dir.c                     |   12 +-
 src/test/test_guardfraction.c           |    5 +-
 34 files changed, 3384 insertions(+), 3001 deletions(-)

diff --git a/src/app/config/config.c b/src/app/config/config.c
index a3e775f48..6cef1a9f1 100644
--- a/src/app/config/config.c
+++ b/src/app/config/config.c
@@ -87,7 +87,9 @@
 #else
 #include "lib/crypt_ops/crypto_openssl_mgt.h"
 #endif
+#include "feature/dirauth/bwauth.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/guardfraction.h"
 #include "feature/relay/dns.h"
 #include "core/or/dos.h"
 #include "feature/client/entrynodes.h"
@@ -141,6 +143,7 @@
 #include "lib/evloop/procmon.h"
 
 #include "feature/dirauth/dirvote.h"
+#include "feature/dirauth/recommend_pkg.h"
 #include "feature/dirauth/mode.h"
 
 #include "core/or/connection_st.h"
diff --git a/src/core/include.am b/src/core/include.am
index 7adcc0d9d..04e27b00d 100644
--- a/src/core/include.am
+++ b/src/core/include.am
@@ -56,6 +56,7 @@ LIBTOR_APP_A_SOURCES = 				\
 	src/feature/client/entrynodes.c		\
 	src/feature/client/transports.c		\
 	src/feature/control/control.c		\
+	src/feature/control/fmt_serverstatus.c  \
 	src/feature/dirauth/keypin.c		\
 	src/feature/dircache/conscache.c	\
 	src/feature/dircache/consdiffmgr.c	\
@@ -90,6 +91,7 @@ LIBTOR_APP_A_SOURCES = 				\
 	src/feature/nodelist/routerlist.c	\
 	src/feature/nodelist/routerparse.c	\
 	src/feature/nodelist/routerset.c	\
+	src/feature/nodelist/fmt_routerstatus.c	\
 	src/feature/nodelist/torcert.c		\
 	src/feature/relay/dns.c			\
 	src/feature/relay/ext_orport.c		\
@@ -103,6 +105,16 @@ LIBTOR_APP_A_SOURCES = 				\
 	src/feature/stats/geoip.c		\
 	src/feature/stats/rephist.c
 
+# These should eventually move into module_dirauth_sources, but for now
+# the separation is only in the code location.
+LIBTOR_APP_A_SOURCES += 			\
+	src/feature/dirauth/bwauth.c		\
+	src/feature/dirauth/guardfraction.c	\
+	src/feature/dirauth/reachability.c	\
+	src/feature/dirauth/recommend_pkg.c	\
+	src/feature/dirauth/process_descs.c	\
+	src/feature/dirauth/voteflags.c
+
 if BUILD_NT_SERVICES
 LIBTOR_APP_A_SOURCES += src/app/main/ntmain.c
 endif
@@ -222,14 +234,21 @@ noinst_HEADERS +=					\
 	src/feature/client/transports.h			\
 	src/feature/control/control.h			\
 	src/feature/control/control_connection_st.h	\
+	src/feature/control/fmt_serverstatus.h		\
+	src/feature/dirauth/bwauth.h			\
 	src/feature/dirauth/dircollate.h		\
 	src/feature/dirauth/dirvote.h			\
+	src/feature/dirauth/guardfraction.h		\
 	src/feature/dirauth/keypin.h			\
 	src/feature/dirauth/mode.h			\
 	src/feature/dirauth/ns_detached_signatures_st.h	\
+	src/feature/dirauth/reachability.h		\
+	src/feature/dirauth/recommend_pkg.h		\
+	src/feature/dirauth/process_descs.h		\
 	src/feature/dirauth/shared_random.h		\
 	src/feature/dirauth/shared_random_state.h	\
 	src/feature/dirauth/vote_microdesc_hash_st.h	\
+	src/feature/dirauth/voteflags.h			\
 	src/feature/dircache/cached_dir_st.h		\
 	src/feature/dircache/conscache.h		\
 	src/feature/dircache/consdiffmgr.h		\
@@ -280,6 +299,7 @@ noinst_HEADERS +=					\
 	src/feature/nodelist/routerlist_st.h		\
 	src/feature/nodelist/routerparse.h		\
 	src/feature/nodelist/routerset.h		\
+	src/feature/nodelist/fmt_routerstatus.h		\
 	src/feature/nodelist/routerstatus_st.h		\
 	src/feature/nodelist/signed_descriptor_st.h	\
 	src/feature/nodelist/torcert.h			\
diff --git a/src/core/mainloop/main.c b/src/core/mainloop/main.c
index 6884021bb..2a90192c9 100644
--- a/src/core/mainloop/main.c
+++ b/src/core/mainloop/main.c
@@ -74,6 +74,9 @@
 #include "lib/crypt_ops/crypto_rand.h"
 #include "feature/dircache/directory.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/bwauth.h"
+#include "feature/dirauth/reachability.h"
+#include "feature/dirauth/process_descs.h"
 #include "feature/relay/dns.h"
 #include "feature/client/dnsserv.h"
 #include "core/or/dos.h"
@@ -3663,7 +3666,9 @@ tor_free_all(int postfork)
   routerlist_free_all();
   networkstatus_free_all();
   addressmap_free_all();
+  dirserv_free_fingerprint_list();
   dirserv_free_all();
+  dirserv_clear_measured_bw_cache();
   rend_cache_free_all();
   rend_service_authorization_free_all();
   rep_hist_free_all();
diff --git a/src/core/or/connection_or.c b/src/core/or/connection_or.c
index de6066020..0f233a53a 100644
--- a/src/core/or/connection_or.c
+++ b/src/core/or/connection_or.c
@@ -41,7 +41,7 @@
 #include "feature/control/control.h"
 #include "lib/crypt_ops/crypto_rand.h"
 #include "lib/crypt_ops/crypto_util.h"
-#include "feature/dircache/dirserv.h"
+#include "feature/dirauth/reachability.h"
 #include "feature/client/entrynodes.h"
 #include "feature/stats/geoip.h"
 #include "core/mainloop/main.h"
diff --git a/src/core/or/policies.c b/src/core/or/policies.c
index 3cb5e5359..3443a1710 100644
--- a/src/core/or/policies.c
+++ b/src/core/or/policies.c
@@ -20,7 +20,6 @@
 #include "core/or/or.h"
 #include "feature/client/bridges.h"
 #include "app/config/config.h"
-#include "feature/dircache/dirserv.h"
 #include "feature/nodelist/microdesc.h"
 #include "feature/nodelist/networkstatus.h"
 #include "feature/nodelist/nodelist.h"
@@ -39,6 +38,9 @@
 #include "feature/nodelist/routerinfo_st.h"
 #include "feature/nodelist/routerstatus_st.h"
 
+/** Maximum length of an exit policy summary. */
+#define MAX_EXITPOLICY_SUMMARY_LEN 1000
+
 /** Policy that addresses for incoming SOCKS connections must match. */
 static smartlist_t *socks_policy = NULL;
 /** Policy that addresses for incoming directory connections must match. */
diff --git a/src/feature/control/control.c b/src/feature/control/control.c
index cff3c414c..5e49b48e7 100644
--- a/src/feature/control/control.c
+++ b/src/feature/control/control.c
@@ -53,6 +53,7 @@
 #include "core/or/connection_edge.h"
 #include "core/or/connection_or.h"
 #include "feature/control/control.h"
+#include "feature/control/fmt_serverstatus.h"
 #include "lib/crypt_ops/crypto_rand.h"
 #include "lib/crypt_ops/crypto_util.h"
 #include "feature/dircache/directory.h"
diff --git a/src/feature/control/fmt_serverstatus.c b/src/feature/control/fmt_serverstatus.c
new file mode 100644
index 000000000..f150832ce
--- /dev/null
+++ b/src/feature/control/fmt_serverstatus.c
@@ -0,0 +1,103 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "core/or/or.h"
+#include "feature/control/fmt_serverstatus.h"
+
+#include "app/config/config.h"
+#include "feature/dirauth/voteflags.h"// XXXX remove
+#include "feature/nodelist/nodelist.h"
+#include "feature/relay/router.h"
+
+#include "feature/nodelist/node_st.h"
+#include "feature/nodelist/routerinfo_st.h"
+
+/**
+ * Allocate and return a description of the status of the server <b>desc</b>,
+ * for use in a v1-style router-status line.  The server is listed
+ * as running iff <b>is_live</b> is true.
+ *
+ * This is deprecated: it's only used for controllers that want outputs in
+ * the old format.
+ */
+static char *
+list_single_server_status(const routerinfo_t *desc, int is_live)
+{
+  char buf[MAX_NICKNAME_LEN+HEX_DIGEST_LEN+4]; /* !nickname=$hexdigest\0 */
+  char *cp;
+  const node_t *node;
+
+  tor_assert(desc);
+
+  cp = buf;
+  if (!is_live) {
+    *cp++ = '!';
+  }
+  node = node_get_by_id(desc->cache_info.identity_digest);
+  if (node && node->is_valid) {
+    strlcpy(cp, desc->nickname, sizeof(buf)-(cp-buf));
+    cp += strlen(cp);
+    *cp++ = '=';
+  }
+  *cp++ = '$';
+  base16_encode(cp, HEX_DIGEST_LEN+1, desc->cache_info.identity_digest,
+                DIGEST_LEN);
+  return tor_strdup(buf);
+}
+
+/** Based on the routerinfo_ts in <b>routers</b>, allocate the
+ * contents of a v1-style router-status line, and store it in
+ * *<b>router_status_out</b>.  Return 0 on success, -1 on failure.
+ *
+ * If for_controller is true, include the routers with very old descriptors.
+ *
+ * This is deprecated: it's only used for controllers that want outputs in
+ * the old format.
+ */
+int
+list_server_status_v1(smartlist_t *routers, char **router_status_out,
+                      int for_controller)
+{
+  /* List of entries in a router-status style: An optional !, then an optional
+   * equals-suffixed nickname, then a dollar-prefixed hexdigest. */
+  smartlist_t *rs_entries;
+  time_t now = time(NULL);
+  time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
+  const or_options_t *options = get_options();
+  /* We include v2 dir auths here too, because they need to answer
+   * controllers. Eventually we'll deprecate this whole function;
+   * see also networkstatus_getinfo_by_purpose(). */
+  int authdir = authdir_mode_publishes_statuses(options);
+  tor_assert(router_status_out);
+
+  rs_entries = smartlist_new();
+
+  SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
+    const node_t *node = node_get_by_id(ri->cache_info.identity_digest);
+    tor_assert(node);
+    if (authdir) {
+      /* Update router status in routerinfo_t. */
+      dirserv_set_router_is_running(ri, now);
+    }
+    if (for_controller) {
+      char name_buf[MAX_VERBOSE_NICKNAME_LEN+2];
+      char *cp = name_buf;
+      if (!node->is_running)
+        *cp++ = '!';
+      router_get_verbose_nickname(cp, ri);
+      smartlist_add_strdup(rs_entries, name_buf);
+    } else if (ri->cache_info.published_on >= cutoff) {
+      smartlist_add(rs_entries, list_single_server_status(ri,
+                                                          node->is_running));
+    }
+  } SMARTLIST_FOREACH_END(ri);
+
+  *router_status_out = smartlist_join_strings(rs_entries, " ", 0, NULL);
+
+  SMARTLIST_FOREACH(rs_entries, char *, cp, tor_free(cp));
+  smartlist_free(rs_entries);
+
+  return 0;
+}
diff --git a/src/feature/control/fmt_serverstatus.h b/src/feature/control/fmt_serverstatus.h
new file mode 100644
index 000000000..2ae9c1778
--- /dev/null
+++ b/src/feature/control/fmt_serverstatus.h
@@ -0,0 +1,18 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file fmt_serverstatus.h
+ * \brief Header file for fmt_serverstatus.c.
+ **/
+
+#ifndef TOR_FMT_SERVERSTATUS_H
+#define TOR_FMT_SERVERSTATUS_H
+
+int list_server_status_v1(smartlist_t *routers, char **router_status_out,
+                          int for_controller);
+
+#endif
diff --git a/src/feature/dirauth/bwauth.c b/src/feature/dirauth/bwauth.c
new file mode 100644
index 000000000..90497a3b7
--- /dev/null
+++ b/src/feature/dirauth/bwauth.c
@@ -0,0 +1,453 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file bwauth.c
+ * \brief Code to read and apply bandwidth authority data.
+ **/
+
+#define BWAUTH_PRIVATE
+#include "core/or/or.h"
+#include "feature/dirauth/bwauth.h"
+
+#include "app/config/config.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/routerlist.h"
+#include "feature/nodelist/routerparse.h"
+
+#include "feature/nodelist/routerinfo_st.h"
+#include "feature/nodelist/vote_routerstatus_st.h"
+
+#include "lib/encoding/keyval.h"
+
+/** Total number of routers with measured bandwidth; this is set by
+ * dirserv_count_measured_bs() before the loop in
+ * dirserv_generate_networkstatus_vote_obj() and checked by
+ * dirserv_get_credible_bandwidth() and
+ * dirserv_compute_performance_thresholds() */
+static int routers_with_measured_bw = 0;
+
+/** Look through the routerlist, and using the measured bandwidth cache count
+ * how many measured bandwidths we know.  This is used to decide whether we
+ * ever trust advertised bandwidths for purposes of assigning flags. */
+void
+dirserv_count_measured_bws(const smartlist_t *routers)
+{
+  /* Initialize this first */
+  routers_with_measured_bw = 0;
+
+  /* Iterate over the routerlist and count measured bandwidths */
+  SMARTLIST_FOREACH_BEGIN(routers, const routerinfo_t *, ri) {
+    /* Check if we know a measured bandwidth for this one */
+    if (dirserv_has_measured_bw(ri->cache_info.identity_digest)) {
+      ++routers_with_measured_bw;
+    }
+  } SMARTLIST_FOREACH_END(ri);
+}
+
+/** Return the last-computed result from dirserv_count_mesured_bws(). */
+int
+dirserv_get_last_n_measured_bws(void)
+{
+  return routers_with_measured_bw;
+}
+
+/** Measured bandwidth cache entry */
+typedef struct mbw_cache_entry_s {
+  long mbw_kb;
+  time_t as_of;
+} mbw_cache_entry_t;
+
+/** Measured bandwidth cache - keys are identity_digests, values are
+ * mbw_cache_entry_t *. */
+static digestmap_t *mbw_cache = NULL;
+
+/** Store a measured bandwidth cache entry when reading the measured
+ * bandwidths file. */
+STATIC void
+dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line,
+                          time_t as_of)
+{
+  mbw_cache_entry_t *e = NULL;
+
+  tor_assert(parsed_line);
+
+  /* Allocate a cache if we need */
+  if (!mbw_cache) mbw_cache = digestmap_new();
+
+  /* Check if we have an existing entry */
+  e = digestmap_get(mbw_cache, parsed_line->node_id);
+  /* If we do, we can re-use it */
+  if (e) {
+    /* Check that we really are newer, and update */
+    if (as_of > e->as_of) {
+      e->mbw_kb = parsed_line->bw_kb;
+      e->as_of = as_of;
+    }
+  } else {
+    /* We'll have to insert a new entry */
+    e = tor_malloc(sizeof(*e));
+    e->mbw_kb = parsed_line->bw_kb;
+    e->as_of = as_of;
+    digestmap_set(mbw_cache, parsed_line->node_id, e);
+  }
+}
+
+/** Clear and free the measured bandwidth cache */
+void
+dirserv_clear_measured_bw_cache(void)
+{
+  if (mbw_cache) {
+    /* Free the map and all entries */
+    digestmap_free(mbw_cache, tor_free_);
+    mbw_cache = NULL;
+  }
+}
+
+/** Scan the measured bandwidth cache and remove expired entries */
+STATIC void
+dirserv_expire_measured_bw_cache(time_t now)
+{
+
+  if (mbw_cache) {
+    /* Iterate through the cache and check each entry */
+    DIGESTMAP_FOREACH_MODIFY(mbw_cache, k, mbw_cache_entry_t *, e) {
+      if (now > e->as_of + MAX_MEASUREMENT_AGE) {
+        tor_free(e);
+        MAP_DEL_CURRENT(k);
+      }
+    } DIGESTMAP_FOREACH_END;
+
+    /* Check if we cleared the whole thing and free if so */
+    if (digestmap_size(mbw_cache) == 0) {
+      digestmap_free(mbw_cache, tor_free_);
+      mbw_cache = 0;
+    }
+  }
+}
+
+/** Query the cache by identity digest, return value indicates whether
+ * we found it. The bw_out and as_of_out pointers receive the cached
+ * bandwidth value and the time it was cached if not NULL. */
+int
+dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out,
+                                   time_t *as_of_out)
+{
+  mbw_cache_entry_t *v = NULL;
+  int rv = 0;
+
+  if (mbw_cache && node_id) {
+    v = digestmap_get(mbw_cache, node_id);
+    if (v) {
+      /* Found something */
+      rv = 1;
+      if (bw_kb_out) *bw_kb_out = v->mbw_kb;
+      if (as_of_out) *as_of_out = v->as_of;
+    }
+  }
+
+  return rv;
+}
+
+/** Predicate wrapper for dirserv_query_measured_bw_cache() */
+int
+dirserv_has_measured_bw(const char *node_id)
+{
+  return dirserv_query_measured_bw_cache_kb(node_id, NULL, NULL);
+}
+
+/** Get the current size of the measured bandwidth cache */
+int
+dirserv_get_measured_bw_cache_size(void)
+{
+  if (mbw_cache) return digestmap_size(mbw_cache);
+  else return 0;
+}
+
+/** Return the bandwidth we believe for assigning flags; prefer measured
+ * over advertised, and if we have above a threshold quantity of measured
+ * bandwidths, we don't want to ever give flags to unmeasured routers, so
+ * return 0. */
+uint32_t
+dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri)
+{
+  int threshold;
+  uint32_t bw_kb = 0;
+  long mbw_kb;
+
+  tor_assert(ri);
+  /* Check if we have a measured bandwidth, and check the threshold if not */
+  if (!(dirserv_query_measured_bw_cache_kb(ri->cache_info.identity_digest,
+                                       &mbw_kb, NULL))) {
+    threshold = get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised;
+    if (routers_with_measured_bw > threshold) {
+      /* Return zero for unmeasured bandwidth if we are above threshold */
+      bw_kb = 0;
+    } else {
+      /* Return an advertised bandwidth otherwise */
+      bw_kb = router_get_advertised_bandwidth_capped(ri) / 1000;
+    }
+  } else {
+    /* We have the measured bandwidth in mbw */
+    bw_kb = (uint32_t)mbw_kb;
+  }
+
+  return bw_kb;
+}
+
+/**
+ * Read the measured bandwidth list file, apply it to the list of
+ * vote_routerstatus_t and store all the headers in <b>bw_file_headers</b>.
+ * Returns -1 on error, 0 otherwise.
+ */
+int
+dirserv_read_measured_bandwidths(const char *from_file,
+                                 smartlist_t *routerstatuses,
+                                 smartlist_t *bw_file_headers)
+{
+  FILE *fp = tor_fopen_cloexec(from_file, "r");
+  int applied_lines = 0;
+  time_t file_time, now;
+  int ok;
+   /* This flag will be 1 only when the first successful bw measurement line
+   * has been encountered, so that measured_bw_line_parse don't give warnings
+   * if there are additional header lines, as introduced in Bandwidth List spec
+   * version 1.1.0 */
+  int line_is_after_headers = 0;
+  int rv = -1;
+  char *line = NULL;
+  size_t n = 0;
+
+  /* Initialise line, so that we can't possibly run off the end. */
+
+  if (fp == NULL) {
+    log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s",
+             from_file);
+    goto err;
+  }
+
+  /* If fgets fails, line is either unmodified, or indeterminate. */
+  if (tor_getline(&line,&n,fp) <= 0) {
+    log_warn(LD_DIRSERV, "Empty bandwidth file");
+    goto err;
+  }
+
+  if (!strlen(line) || line[strlen(line)-1] != '\n') {
+    log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s",
+             escaped(line));
+    goto err;
+  }
+
+  line[strlen(line)-1] = '\0';
+  file_time = (time_t)tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL);
+  if (!ok) {
+    log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s",
+             escaped(line));
+    goto err;
+  }
+
+  now = time(NULL);
+  if ((now - file_time) > MAX_MEASUREMENT_AGE) {
+    log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u",
+             (unsigned)(time(NULL) - file_time));
+    goto err;
+  }
+
+  /* If timestamp was correct and bw_file_headers is not NULL,
+   * add timestamp to bw_file_headers */
+  if (bw_file_headers)
+    smartlist_add_asprintf(bw_file_headers, "timestamp=%lu",
+                           (unsigned long)file_time);
+
+  if (routerstatuses)
+    smartlist_sort(routerstatuses, compare_vote_routerstatus_entries);
+
+  while (!feof(fp)) {
+    measured_bw_line_t parsed_line;
+    if (tor_getline(&line, &n, fp) >= 0) {
+      if (measured_bw_line_parse(&parsed_line, line,
+                                 line_is_after_headers) != -1) {
+        /* This condition will be true when the first complete valid bw line
+         * has been encountered, which means the end of the header lines. */
+        line_is_after_headers = 1;
+        /* Also cache the line for dirserv_get_bandwidth_for_router() */
+        dirserv_cache_measured_bw(&parsed_line, file_time);
+        if (measured_bw_line_apply(&parsed_line, routerstatuses) > 0)
+          applied_lines++;
+      /* if the terminator is found, it is the end of header lines, set the
+       * flag but do not store anything */
+      } else if (strcmp(line, BW_FILE_HEADERS_TERMINATOR) == 0) {
+        line_is_after_headers = 1;
+      /* if the line was not a correct relay line nor the terminator and
+       * the end of the header lines has not been detected yet
+       * and it is key_value and bw_file_headers did not reach the maximum
+       * number of headers,
+       * then assume this line is a header and add it to bw_file_headers */
+      } else if (bw_file_headers &&
+              (line_is_after_headers == 0) &&
+              string_is_key_value(LOG_DEBUG, line) &&
+              !strchr(line, ' ') &&
+              (smartlist_len(bw_file_headers)
+               < MAX_BW_FILE_HEADER_COUNT_IN_VOTE)) {
+        line[strlen(line)-1] = '\0';
+        smartlist_add_strdup(bw_file_headers, line);
+      };
+    }
+  }
+
+  /* Now would be a nice time to clean the cache, too */
+  dirserv_expire_measured_bw_cache(now);
+
+  log_info(LD_DIRSERV,
+           "Bandwidth measurement file successfully read. "
+           "Applied %d measurements.", applied_lines);
+  rv = 0;
+
+ err:
+  if (line) {
+    // we need to raw_free this buffer because we got it from tor_getdelim()
+    raw_free(line);
+  }
+  if (fp)
+    fclose(fp);
+  return rv;
+}
+
+/**
+ * Helper function to parse out a line in the measured bandwidth file
+ * into a measured_bw_line_t output structure.
+ *
+ * If <b>line_is_after_headers</b> is true, then if we encounter an incomplete
+ * bw line, return -1 and warn, since we are after the headers and we should
+ * only parse bw lines. Return 0 otherwise.
+ *
+ * If <b>line_is_after_headers</b> is false then it means that we are not past
+ * the header block yet. If we encounter an incomplete bw line, return -1 but
+ * don't warn since there could be additional header lines coming. If we
+ * encounter a proper bw line, return 0 (and we got past the headers).
+ */
+STATIC int
+measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line,
+                       int line_is_after_headers)
+{
+  char *line = tor_strdup(orig_line);
+  char *cp = line;
+  int got_bw = 0;
+  int got_node_id = 0;
+  char *strtok_state; /* lame sauce d'jour */
+
+  if (strlen(line) == 0) {
+    log_warn(LD_DIRSERV, "Empty line in bandwidth file");
+    tor_free(line);
+    return -1;
+  }
+
+  /* Remove end of line character, so that is not part of the token */
+  if (line[strlen(line) - 1] == '\n') {
+    line[strlen(line) - 1] = '\0';
+  }
+
+  cp = tor_strtok_r(cp, " \t", &strtok_state);
+
+  if (!cp) {
+    log_warn(LD_DIRSERV, "Invalid line in bandwidth file: %s",
+             escaped(orig_line));
+    tor_free(line);
+    return -1;
+  }
+
+  if (orig_line[strlen(orig_line)-1] != '\n') {
+    log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
+             escaped(orig_line));
+    tor_free(line);
+    return -1;
+  }
+
+  do {
+    if (strcmpstart(cp, "bw=") == 0) {
+      int parse_ok = 0;
+      char *endptr;
+      if (got_bw) {
+        log_warn(LD_DIRSERV, "Double bw= in bandwidth file line: %s",
+                 escaped(orig_line));
+        tor_free(line);
+        return -1;
+      }
+      cp+=strlen("bw=");
+
+      out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr);
+      if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) {
+        log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s",
+                 escaped(orig_line));
+        tor_free(line);
+        return -1;
+      }
+      got_bw=1;
+    } else if (strcmpstart(cp, "node_id=$") == 0) {
+      if (got_node_id) {
+        log_warn(LD_DIRSERV, "Double node_id= in bandwidth file line: %s",
+                 escaped(orig_line));
+        tor_free(line);
+        return -1;
+      }
+      cp+=strlen("node_id=$");
+
+      if (strlen(cp) != HEX_DIGEST_LEN ||
+          base16_decode(out->node_id, DIGEST_LEN,
+                        cp, HEX_DIGEST_LEN) != DIGEST_LEN) {
+        log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s",
+                 escaped(orig_line));
+        tor_free(line);
+        return -1;
+      }
+      strlcpy(out->node_hex, cp, sizeof(out->node_hex));
+      got_node_id=1;
+    }
+  } while ((cp = tor_strtok_r(NULL, " \t", &strtok_state)));
+
+  if (got_bw && got_node_id) {
+    tor_free(line);
+    return 0;
+  } else if (line_is_after_headers == 0) {
+    /* There could be additional header lines, therefore do not give warnings
+     * but returns -1 since it's not a complete bw line. */
+    log_debug(LD_DIRSERV, "Missing bw or node_id in bandwidth file line: %s",
+             escaped(orig_line));
+    tor_free(line);
+    return -1;
+  } else {
+    log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
+             escaped(orig_line));
+    tor_free(line);
+    return -1;
+  }
+}
+
+/**
+ * Helper function to apply a parsed measurement line to a list
+ * of bandwidth statuses. Returns true if a line is found,
+ * false otherwise.
+ */
+STATIC int
+measured_bw_line_apply(measured_bw_line_t *parsed_line,
+                       smartlist_t *routerstatuses)
+{
+  vote_routerstatus_t *rs = NULL;
+  if (!routerstatuses)
+    return 0;
+
+  rs = smartlist_bsearch(routerstatuses, parsed_line->node_id,
+                         compare_digest_to_vote_routerstatus_entry);
+
+  if (rs) {
+    rs->has_measured_bw = 1;
+    rs->measured_bw_kb = (uint32_t)parsed_line->bw_kb;
+  } else {
+    log_info(LD_DIRSERV, "Node ID %s not found in routerstatus list",
+             parsed_line->node_hex);
+  }
+
+  return rs != NULL;
+}
diff --git a/src/feature/dirauth/bwauth.h b/src/feature/dirauth/bwauth.h
new file mode 100644
index 000000000..f10f8227a
--- /dev/null
+++ b/src/feature/dirauth/bwauth.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file bwauth.h
+ * \brief Header file for bwauth.c
+ **/
+
+#ifndef TOR_BWAUTH_H
+#define TOR_BWAUTH_H
+
+/** Maximum allowable length of bandwidth headers in a bandwidth file */
+#define MAX_BW_FILE_HEADER_COUNT_IN_VOTE 50
+
+/** Terminatore that separates bandwidth file headers from bandwidth file
+ * relay lines */
+#define BW_FILE_HEADERS_TERMINATOR "=====\n"
+
+int dirserv_read_measured_bandwidths(const char *from_file,
+                                     smartlist_t *routerstatuses,
+                                     smartlist_t *bw_file_headers);
+
+int dirserv_query_measured_bw_cache_kb(const char *node_id,
+                                       long *bw_out,
+                                       time_t *as_of_out);
+void dirserv_clear_measured_bw_cache(void);
+int dirserv_has_measured_bw(const char *node_id);
+int dirserv_get_measured_bw_cache_size(void);
+void dirserv_count_measured_bws(const smartlist_t *routers);
+int dirserv_get_last_n_measured_bws(void);
+
+uint32_t dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri);
+
+#ifdef BWAUTH_PRIVATE
+typedef struct measured_bw_line_t {
+  char node_id[DIGEST_LEN];
+  char node_hex[MAX_HEX_NICKNAME_LEN+1];
+  long int bw_kb;
+} measured_bw_line_t;
+
+/* Put the MAX_MEASUREMENT_AGE #define here so unit tests can see it */
+#define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */
+
+STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *line,
+                                  int line_is_after_headers);
+
+STATIC int measured_bw_line_apply(measured_bw_line_t *parsed_line,
+                           smartlist_t *routerstatuses);
+
+STATIC void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line,
+                               time_t as_of);
+STATIC void dirserv_expire_measured_bw_cache(time_t now);
+#endif /* defined(BWAUTH_PRIVATE) */
+
+#endif
diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c
index 76877490b..5ca9ed4a4 100644
--- a/src/feature/dirauth/dirvote.c
+++ b/src/feature/dirauth/dirvote.c
@@ -7,8 +7,12 @@
 #include "core/or/or.h"
 #include "app/config/config.h"
 #include "feature/dirauth/dircollate.h"
+#include "feature/dirauth/recommend_pkg.h"
+#include "feature/dirauth/voteflags.h"
 #include "feature/dircache/directory.h"
+#include "feature/dirauth/bwauth.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/guardfraction.h"
 #include "feature/nodelist/microdesc.h"
 #include "feature/nodelist/networkstatus.h"
 #include "feature/nodelist/nodelist.h"
@@ -23,6 +27,7 @@
 #include "feature/nodelist/dirlist.h"
 #include "feature/nodelist/routerlist.h"
 #include "feature/nodelist/routerparse.h"
+#include "feature/nodelist/fmt_routerstatus.h"
 #include "feature/client/entrynodes.h" /* needed for guardfraction methods */
 #include "feature/nodelist/torcert.h"
 #include "feature/dircommon/voting_schedule.h"
diff --git a/src/feature/dirauth/dirvote.h b/src/feature/dirauth/dirvote.h
index 979a2be8a..a21e9f345 100644
--- a/src/feature/dirauth/dirvote.h
+++ b/src/feature/dirauth/dirvote.h
@@ -115,6 +115,10 @@ int dirvote_add_signatures(const char *detached_signatures_body,
                            const char *source,
                            const char **msg_out);
 
+struct config_line_t;
+char *format_recommended_version_list(const struct config_line_t *line,
+                                      int warn);
+
 #else /* HAVE_MODULE_DIRAUTH */
 
 static inline time_t
@@ -192,10 +196,6 @@ const cached_dir_t *dirvote_get_vote(const char *fp, int flags);
  * API used _only_ by the dirauth subsystem.
  */
 
-void set_routerstatus_from_routerinfo(routerstatus_t *rs,
-                                      node_t *node,
-                                      routerinfo_t *ri, time_t now,
-                                      int listbadexits);
 networkstatus_t *
 dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
                                         authority_cert_t *cert);
diff --git a/src/feature/dirauth/guardfraction.c b/src/feature/dirauth/guardfraction.c
new file mode 100644
index 000000000..60ba316cf
--- /dev/null
+++ b/src/feature/dirauth/guardfraction.c
@@ -0,0 +1,333 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file bwauth.c
+ * \brief Code to read and apply guard fraction data.
+ **/
+
+#define GUARDFRACTION_PRIVATE
+#include "core/or/or.h"
+#include "feature/dirauth/guardfraction.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/routerparse.h"
+
+#include "feature/nodelist/vote_routerstatus_st.h"
+
+#include "lib/encoding/confline.h"
+
+/** The guardfraction of the guard with identity fingerprint <b>guard_id</b>
+ *  is <b>guardfraction_percentage</b>. See if we have a vote routerstatus for
+ *  this guard in <b>vote_routerstatuses</b>, and if we do, register the
+ *  information to it.
+ *
+ *  Return 1 if we applied the information and 0 if we couldn't find a
+ *  matching guard.
+ *
+ * Requires that <b>vote_routerstatuses</b> be sorted.
+ */
+static int
+guardfraction_line_apply(const char *guard_id,
+                      uint32_t guardfraction_percentage,
+                      smartlist_t *vote_routerstatuses)
+{
+  vote_routerstatus_t *vrs = NULL;
+
+  tor_assert(vote_routerstatuses);
+
+  vrs = smartlist_bsearch(vote_routerstatuses, guard_id,
+                         compare_digest_to_vote_routerstatus_entry);
+
+  if (!vrs) {
+    return 0;
+  }
+
+  vrs->status.has_guardfraction = 1;
+  vrs->status.guardfraction_percentage = guardfraction_percentage;
+
+  return 1;
+}
+
+/* Given a guard line from a guardfraction file, parse it and register
+ * its information to <b>vote_routerstatuses</b>.
+ *
+ * Return:
+ * * 1 if the line was proper and its information got registered.
+ * * 0 if the line was proper but no currently active guard was found
+ *     to register the guardfraction information to.
+ * * -1 if the line could not be parsed and set <b>err_msg</b> to a
+      newly allocated string containing the error message.
+ */
+static int
+guardfraction_file_parse_guard_line(const char *guard_line,
+                                    smartlist_t *vote_routerstatuses,
+                                    char **err_msg)
+{
+  char guard_id[DIGEST_LEN];
+  uint32_t guardfraction;
+  char *inputs_tmp = NULL;
+  int num_ok = 1;
+
+  smartlist_t *sl = smartlist_new();
+  int retval = -1;
+
+  tor_assert(err_msg);
+
+  /* guard_line should contain something like this:
+     <hex digest> <guardfraction> <appearances> */
+  smartlist_split_string(sl, guard_line, " ",
+                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3);
+  if (smartlist_len(sl) < 3) {
+    tor_asprintf(err_msg, "bad line '%s'", guard_line);
+    goto done;
+  }
+
+  inputs_tmp = smartlist_get(sl, 0);
+  if (strlen(inputs_tmp) != HEX_DIGEST_LEN ||
+      base16_decode(guard_id, DIGEST_LEN,
+                    inputs_tmp, HEX_DIGEST_LEN) != DIGEST_LEN) {
+    tor_asprintf(err_msg, "bad digest '%s'", inputs_tmp);
+    goto done;
+  }
+
+  inputs_tmp = smartlist_get(sl, 1);
+  /* Guardfraction is an integer in [0, 100]. */
+  guardfraction =
+    (uint32_t) tor_parse_long(inputs_tmp, 10, 0, 100, &num_ok, NULL);
+  if (!num_ok) {
+    tor_asprintf(err_msg, "wrong percentage '%s'", inputs_tmp);
+    goto done;
+  }
+
+  /* If routerstatuses were provided, apply this info to actual routers. */
+  if (vote_routerstatuses) {
+    retval = guardfraction_line_apply(guard_id, guardfraction,
+                                      vote_routerstatuses);
+  } else {
+    retval = 0; /* If we got this far, line was correctly formatted. */
+  }
+
+ done:
+
+  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
+  smartlist_free(sl);
+
+  return retval;
+}
+
+/** Given an inputs line from a guardfraction file, parse it and
+ *  register its information to <b>total_consensuses</b> and
+ *  <b>total_days</b>.
+ *
+ *  Return 0 if it parsed well. Return -1 if there was an error, and
+ *  set <b>err_msg</b> to a newly allocated string containing the
+ *  error message.
+ */
+static int
+guardfraction_file_parse_inputs_line(const char *inputs_line,
+                                     int *total_consensuses,
+                                     int *total_days,
+                                     char **err_msg)
+{
+  int retval = -1;
+  char *inputs_tmp = NULL;
+  int num_ok = 1;
+  smartlist_t *sl = smartlist_new();
+
+  tor_assert(err_msg);
+
+  /* Second line is inputs information:
+   *   n-inputs <total_consensuses> <total_days>. */
+  smartlist_split_string(sl, inputs_line, " ",
+                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3);
+  if (smartlist_len(sl) < 2) {
+    tor_asprintf(err_msg, "incomplete line '%s'", inputs_line);
+    goto done;
+  }
+
+  inputs_tmp = smartlist_get(sl, 0);
+  *total_consensuses =
+    (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL);
+  if (!num_ok) {
+    tor_asprintf(err_msg, "unparseable consensus '%s'", inputs_tmp);
+    goto done;
+  }
+
+  inputs_tmp = smartlist_get(sl, 1);
+  *total_days =
+    (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL);
+  if (!num_ok) {
+    tor_asprintf(err_msg, "unparseable days '%s'", inputs_tmp);
+    goto done;
+  }
+
+  retval = 0;
+
+ done:
+  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
+  smartlist_free(sl);
+
+  return retval;
+}
+
+/* Maximum age of a guardfraction file that we are willing to accept. */
+#define MAX_GUARDFRACTION_FILE_AGE (7*24*60*60) /* approx a week */
+
+/** Static strings of guardfraction files. */
+#define GUARDFRACTION_DATE_STR "written-at"
+#define GUARDFRACTION_INPUTS "n-inputs"
+#define GUARDFRACTION_GUARD "guard-seen"
+#define GUARDFRACTION_VERSION "guardfraction-file-version"
+
+/** Given a guardfraction file in a string, parse it and register the
+ *  guardfraction information to the provided vote routerstatuses.
+ *
+ *  This is the rough format of the guardfraction file:
+ *
+ *      guardfraction-file-version 1
+ *      written-at <date and time>
+ *      n-inputs <number of consesuses parsed> <number of days considered>
+ *
+ *      guard-seen <fpr 1> <guardfraction percentage> <consensus appearances>
+ *      guard-seen <fpr 2> <guardfraction percentage> <consensus appearances>
+ *      guard-seen <fpr 3> <guardfraction percentage> <consensus appearances>
+ *      guard-seen <fpr 4> <guardfraction percentage> <consensus appearances>
+ *      guard-seen <fpr 5> <guardfraction percentage> <consensus appearances>
+ *      ...
+ *
+ *  Return -1 if the parsing failed and 0 if it went smoothly. Parsing
+ *  should tolerate errors in all lines but the written-at header.
+ */
+STATIC int
+dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
+                                      smartlist_t *vote_routerstatuses)
+{
+  config_line_t *front=NULL, *line;
+  int ret_tmp;
+  int retval = -1;
+  int current_line_n = 0; /* line counter for better log messages */
+
+  /* Guardfraction info to be parsed */
+  int total_consensuses = 0;
+  int total_days = 0;
+
+  /* Stats */
+  int guards_read_n = 0;
+  int guards_applied_n = 0;
+
+  /* Parse file and split it in lines */
+  ret_tmp = config_get_lines(guardfraction_file_str, &front, 0);
+  if (ret_tmp < 0) {
+    log_warn(LD_CONFIG, "Error reading from guardfraction file");
+    goto done;
+  }
+
+  /* Sort routerstatuses (needed later when applying guardfraction info) */
+  if (vote_routerstatuses)
+    smartlist_sort(vote_routerstatuses, compare_vote_routerstatus_entries);
+
+  for (line = front; line; line=line->next) {
+    current_line_n++;
+
+    if (!strcmp(line->key, GUARDFRACTION_VERSION)) {
+      int num_ok = 1;
+      unsigned int version;
+
+      version =
+        (unsigned int) tor_parse_long(line->value,
+                                      10, 0, INT_MAX, &num_ok, NULL);
+
+      if (!num_ok || version != 1) {
+        log_warn(LD_GENERAL, "Got unknown guardfraction version %d.", version);
+        goto done;
+      }
+    } else if (!strcmp(line->key, GUARDFRACTION_DATE_STR)) {
+      time_t file_written_at;
+      time_t now = time(NULL);
+
+      /* First line is 'written-at <date>' */
+      if (parse_iso_time(line->value, &file_written_at) < 0) {
+        log_warn(LD_CONFIG, "Guardfraction:%d: Bad date '%s'. Ignoring",
+                 current_line_n, line->value);
+        goto done; /* don't tolerate failure here. */
+      }
+      if (file_written_at < now - MAX_GUARDFRACTION_FILE_AGE) {
+        log_warn(LD_CONFIG, "Guardfraction:%d: was written very long ago '%s'",
+                 current_line_n, line->value);
+        goto done; /* don't tolerate failure here. */
+      }
+    } else if (!strcmp(line->key, GUARDFRACTION_INPUTS)) {
+      char *err_msg = NULL;
+
+      if (guardfraction_file_parse_inputs_line(line->value,
+                                               &total_consensuses,
+                                               &total_days,
+                                               &err_msg) < 0) {
+        log_warn(LD_CONFIG, "Guardfraction:%d: %s",
+                 current_line_n, err_msg);
+        tor_free(err_msg);
+        continue;
+      }
+
+    } else if (!strcmp(line->key, GUARDFRACTION_GUARD)) {
+      char *err_msg = NULL;
+
+      ret_tmp = guardfraction_file_parse_guard_line(line->value,
+                                                    vote_routerstatuses,
+                                                    &err_msg);
+      if (ret_tmp < 0) { /* failed while parsing the guard line */
+        log_warn(LD_CONFIG, "Guardfraction:%d: %s",
+                 current_line_n, err_msg);
+        tor_free(err_msg);
+        continue;
+      }
+
+      /* Successfully parsed guard line. Check if it was applied properly. */
+      guards_read_n++;
+      if (ret_tmp > 0) {
+        guards_applied_n++;
+      }
+    } else {
+      log_warn(LD_CONFIG, "Unknown guardfraction line %d (%s %s)",
+               current_line_n, line->key, line->value);
+    }
+  }
+
+  retval = 0;
+
+  log_info(LD_CONFIG,
+           "Successfully parsed guardfraction file with %d consensuses over "
+           "%d days. Parsed %d nodes and applied %d of them%s.",
+           total_consensuses, total_days, guards_read_n, guards_applied_n,
+           vote_routerstatuses ? "" : " (no routerstatus provided)" );
+
+ done:
+  config_free_lines(front);
+
+  if (retval < 0) {
+    return retval;
+  } else {
+    return guards_read_n;
+  }
+}
+
+/** Read a guardfraction file at <b>fname</b> and load all its
+ *  information to <b>vote_routerstatuses</b>. */
+int
+dirserv_read_guardfraction_file(const char *fname,
+                             smartlist_t *vote_routerstatuses)
+{
+  char *guardfraction_file_str;
+
+  /* Read file to a string */
+  guardfraction_file_str = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL);
+  if (!guardfraction_file_str) {
+      log_warn(LD_FS, "Cannot open guardfraction file '%s'. Failing.", fname);
+      return -1;
+  }
+
+  return dirserv_read_guardfraction_file_from_str(guardfraction_file_str,
+                                               vote_routerstatuses);
+}
diff --git a/src/feature/dirauth/guardfraction.h b/src/feature/dirauth/guardfraction.h
new file mode 100644
index 000000000..38a0781db
--- /dev/null
+++ b/src/feature/dirauth/guardfraction.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file guardfraction.h
+ * \brief Header file for guardfraction.c
+ **/
+
+#ifndef TOR_GUARDFRACTION_H
+#define TOR_GUARDFRACTION_H
+
+#ifdef GUARDFRACTION_PRIVATE
+STATIC int
+dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
+                                      smartlist_t *vote_routerstatuses);
+#endif /* defined(DIRSERV_PRIVATE) */
+
+int dirserv_read_guardfraction_file(const char *fname,
+                                 smartlist_t *vote_routerstatuses);
+
+#endif
diff --git a/src/feature/dirauth/process_descs.c b/src/feature/dirauth/process_descs.c
new file mode 100644
index 000000000..2c2fefca7
--- /dev/null
+++ b/src/feature/dirauth/process_descs.c
@@ -0,0 +1,835 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file process_descs.c
+ * \brief Make decisions about uploaded descriptors
+ *
+ * Authorities use the code in this module to decide what to do with just-
+ * uploaded descriptors, and to manage the fingerprint file that helps
+ * them make those decisions.
+ **/
+
+#include "core/or/or.h"
+#include "feature/dirauth/process_descs.h"
+
+#include "app/config/config.h"
+#include "core/or/policies.h"
+#include "feature/dirauth/keypin.h"
+#include "feature/dirauth/reachability.h"
+#include "feature/dircache/directory.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/nodelist.h"
+#include "feature/nodelist/routerlist.h"
+#include "feature/nodelist/routerparse.h"
+#include "feature/nodelist/torcert.h"
+#include "feature/relay/router.h"
+
+#include "core/or/tor_version_st.h"
+#include "feature/nodelist/extrainfo_st.h"
+#include "feature/nodelist/node_st.h"
+#include "feature/nodelist/routerinfo_st.h"
+#include "feature/nodelist/routerstatus_st.h"
+
+#include "lib/encoding/confline.h"
+
+/** How far in the future do we allow a router to get? (seconds) */
+#define ROUTER_ALLOW_SKEW (60*60*12)
+
+static void directory_remove_invalid(void);
+struct authdir_config_t;
+static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei,
+                                                const char **msg);
+static uint32_t
+dirserv_get_status_impl(const char *fp, const char *nickname,
+                        uint32_t addr, uint16_t or_port,
+                        const char *platform, const char **msg,
+                        int severity);
+
+/*                 1  Historically used to indicate Named */
+#define FP_INVALID 2  /**< Believed invalid. */
+#define FP_REJECT  4  /**< We will not publish this router. */
+/*                 8  Historically used to avoid using this as a dir. */
+#define FP_BADEXIT 16 /**< We'll tell clients not to use this as an exit. */
+/*                 32 Historically used to indicade Unnamed */
+
+/** Target of status_by_digest map. */
+typedef uint32_t router_status_t;
+
+static void add_fingerprint_to_dir(const char *fp,
+                                   struct authdir_config_t *list,
+                                   router_status_t add_status);
+
+/** List of nickname-\>identity fingerprint mappings for all the routers
+ * that we name.  Used to prevent router impersonation. */
+typedef struct authdir_config_t {
+  strmap_t *fp_by_name; /**< Map from lc nickname to fingerprint. */
+  digestmap_t *status_by_digest; /**< Map from digest to router_status_t. */
+} authdir_config_t;
+
+/** Should be static; exposed for testing. */
+static authdir_config_t *fingerprint_list = NULL;
+
+/** Allocate and return a new, empty, authdir_config_t. */
+static authdir_config_t *
+authdir_config_new(void)
+{
+  authdir_config_t *list = tor_malloc_zero(sizeof(authdir_config_t));
+  list->fp_by_name = strmap_new();
+  list->status_by_digest = digestmap_new();
+  return list;
+}
+
+/** Add the fingerprint <b>fp</b> to the smartlist of fingerprint_entry_t's
+ * <b>list</b>, or-ing the currently set status flags with
+ * <b>add_status</b>.
+ */
+/* static */ void
+add_fingerprint_to_dir(const char *fp, authdir_config_t *list,
+                       router_status_t add_status)
+{
+  char *fingerprint;
+  char d[DIGEST_LEN];
+  router_status_t *status;
+  tor_assert(fp);
+  tor_assert(list);
+
+  fingerprint = tor_strdup(fp);
+  tor_strstrip(fingerprint, " ");
+  if (base16_decode(d, DIGEST_LEN,
+                    fingerprint, strlen(fingerprint)) != DIGEST_LEN) {
+    log_warn(LD_DIRSERV, "Couldn't decode fingerprint \"%s\"",
+             escaped(fp));
+    tor_free(fingerprint);
+    return;
+  }
+
+  status = digestmap_get(list->status_by_digest, d);
+  if (!status) {
+    status = tor_malloc_zero(sizeof(router_status_t));
+    digestmap_set(list->status_by_digest, d, status);
+  }
+
+  tor_free(fingerprint);
+  *status |= add_status;
+  return;
+}
+
+/** Add the fingerprint for this OR to the global list of recognized
+ * identity key fingerprints. */
+int
+dirserv_add_own_fingerprint(crypto_pk_t *pk)
+{
+  char fp[FINGERPRINT_LEN+1];
+  if (crypto_pk_get_fingerprint(pk, fp, 0)<0) {
+    log_err(LD_BUG, "Error computing fingerprint");
+    return -1;
+  }
+  if (!fingerprint_list)
+    fingerprint_list = authdir_config_new();
+  add_fingerprint_to_dir(fp, fingerprint_list, 0);
+  return 0;
+}
+
+/** Load the nickname-\>fingerprint mappings stored in the approved-routers
+ * file.  The file format is line-based, with each non-blank holding one
+ * nickname, some space, and a fingerprint for that nickname.  On success,
+ * replace the current fingerprint list with the new list and return 0.  On
+ * failure, leave the current fingerprint list untouched, and return -1. */
+int
+dirserv_load_fingerprint_file(void)
+{
+  char *fname;
+  char *cf;
+  char *nickname, *fingerprint;
+  authdir_config_t *fingerprint_list_new;
+  int result;
+  config_line_t *front=NULL, *list;
+
+  fname = get_datadir_fname("approved-routers");
+  log_info(LD_GENERAL,
+           "Reloading approved fingerprints from \"%s\"...", fname);
+
+  cf = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL);
+  if (!cf) {
+    log_warn(LD_FS, "Cannot open fingerprint file '%s'. That's ok.", fname);
+    tor_free(fname);
+    return 0;
+  }
+  tor_free(fname);
+
+  result = config_get_lines(cf, &front, 0);
+  tor_free(cf);
+  if (result < 0) {
+    log_warn(LD_CONFIG, "Error reading from fingerprint file");
+    return -1;
+  }
+
+  fingerprint_list_new = authdir_config_new();
+
+  for (list=front; list; list=list->next) {
+    char digest_tmp[DIGEST_LEN];
+    router_status_t add_status = 0;
+    nickname = list->key; fingerprint = list->value;
+    tor_strstrip(fingerprint, " "); /* remove spaces */
+    if (strlen(fingerprint) != HEX_DIGEST_LEN ||
+        base16_decode(digest_tmp, sizeof(digest_tmp),
+                      fingerprint, HEX_DIGEST_LEN) != sizeof(digest_tmp)) {
+      log_notice(LD_CONFIG,
+                 "Invalid fingerprint (nickname '%s', "
+                 "fingerprint %s). Skipping.",
+                 nickname, fingerprint);
+      continue;
+    }
+    if (!strcasecmp(nickname, "!reject")) {
+        add_status = FP_REJECT;
+    } else if (!strcasecmp(nickname, "!badexit")) {
+        add_status = FP_BADEXIT;
+    } else if (!strcasecmp(nickname, "!invalid")) {
+        add_status = FP_INVALID;
+    }
+    add_fingerprint_to_dir(fingerprint, fingerprint_list_new, add_status);
+  }
+
+  config_free_lines(front);
+  dirserv_free_fingerprint_list();
+  fingerprint_list = fingerprint_list_new;
+  /* Delete any routers whose fingerprints we no longer recognize */
+  directory_remove_invalid();
+  return 0;
+}
+
+/* If this is set, then we don't allow routers that have advertised an Ed25519
+ * identity to stop doing so.  This is going to be essential for good identity
+ * security: otherwise anybody who can attack RSA-1024 but not Ed25519 could
+ * just sign fake descriptors missing the Ed25519 key.  But we won't actually
+ * be able to prevent that kind of thing until we're confident that there isn't
+ * actually a legit reason to downgrade to 0.2.5.  Now we are not recommending
+ * 0.2.5 anymore so there is no reason to keep the #undef.
+ */
+
+#define DISABLE_DISABLING_ED25519
+
+/** Check whether <b>router</b> has a nickname/identity key combination that
+ * we recognize from the fingerprint list, or an IP we automatically act on
+ * according to our configuration.  Return the appropriate router status.
+ *
+ * If the status is 'FP_REJECT' and <b>msg</b> is provided, set
+ * *<b>msg</b> to an explanation of why. */
+uint32_t
+dirserv_router_get_status(const routerinfo_t *router, const char **msg,
+                          int severity)
+{
+  char d[DIGEST_LEN];
+  const int key_pinning = get_options()->AuthDirPinKeys;
+
+  if (crypto_pk_get_digest(router->identity_pkey, d)) {
+    log_warn(LD_BUG,"Error computing fingerprint");
+    if (msg)
+      *msg = "Bug: Error computing fingerprint";
+    return FP_REJECT;
+  }
+
+  /* Check for the more usual versions to reject a router first. */
+  const uint32_t r = dirserv_get_status_impl(d, router->nickname,
+                                             router->addr, router->or_port,
+                                             router->platform, msg, severity);
+  if (r)
+    return r;
+
+  /* dirserv_get_status_impl already rejects versions older than 0.2.4.18-rc,
+   * and onion_curve25519_pkey was introduced in 0.2.4.8-alpha.
+   * But just in case a relay doesn't provide or lies about its version, or
+   * doesn't include an ntor key in its descriptor, check that it exists,
+   * and is non-zero (clients check that it's non-zero before using it). */
+  if (!routerinfo_has_curve25519_onion_key(router)) {
+    log_fn(severity, LD_DIR,
+           "Descriptor from router %s is missing an ntor curve25519 onion "
+           "key.", router_describe(router));
+    if (msg)
+      *msg = "Missing ntor curve25519 onion key. Please upgrade!";
+    return FP_REJECT;
+  }
+
+  if (router->cache_info.signing_key_cert) {
+    /* This has an ed25519 identity key. */
+    if (KEYPIN_MISMATCH ==
+        keypin_check((const uint8_t*)router->cache_info.identity_digest,
+                   router->cache_info.signing_key_cert->signing_key.pubkey)) {
+      log_fn(severity, LD_DIR,
+             "Descriptor from router %s has an Ed25519 key, "
+               "but the <rsa,ed25519> keys don't match what they were before.",
+               router_describe(router));
+      if (key_pinning) {
+        if (msg) {
+          *msg = "Ed25519 identity key or RSA identity key has changed.";
+        }
+        return FP_REJECT;
+      }
+    }
+  } else {
+    /* No ed25519 key */
+    if (KEYPIN_MISMATCH == keypin_check_lone_rsa(
+                        (const uint8_t*)router->cache_info.identity_digest)) {
+      log_fn(severity, LD_DIR,
+               "Descriptor from router %s has no Ed25519 key, "
+               "when we previously knew an Ed25519 for it. Ignoring for now, "
+               "since Ed25519 keys are fairly new.",
+               router_describe(router));
+#ifdef DISABLE_DISABLING_ED25519
+      if (key_pinning) {
+        if (msg) {
+          *msg = "Ed25519 identity key has disappeared.";
+        }
+        return FP_REJECT;
+      }
+#endif /* defined(DISABLE_DISABLING_ED25519) */
+    }
+  }
+
+  return 0;
+}
+
+/** Return true if there is no point in downloading the router described by
+ * <b>rs</b> because this directory would reject it. */
+int
+dirserv_would_reject_router(const routerstatus_t *rs)
+{
+  uint32_t res;
+
+  res = dirserv_get_status_impl(rs->identity_digest, rs->nickname,
+                                rs->addr, rs->or_port,
+                                NULL, NULL, LOG_DEBUG);
+
+  return (res & FP_REJECT) != 0;
+}
+
+/** Helper: As dirserv_router_get_status, but takes the router fingerprint
+ * (hex, no spaces), nickname, address (used for logging only), IP address, OR
+ * port and platform (logging only) as arguments.
+ *
+ * Log messages at 'severity'. (There's not much point in
+ * logging that we're rejecting servers we'll not download.)
+ */
+static uint32_t
+dirserv_get_status_impl(const char *id_digest, const char *nickname,
+                        uint32_t addr, uint16_t or_port,
+                        const char *platform, const char **msg, int severity)
+{
+  uint32_t result = 0;
+  router_status_t *status_by_digest;
+
+  if (!fingerprint_list)
+    fingerprint_list = authdir_config_new();
+
+  log_debug(LD_DIRSERV, "%d fingerprints, %d digests known.",
+            strmap_size(fingerprint_list->fp_by_name),
+            digestmap_size(fingerprint_list->status_by_digest));
+
+  if (platform) {
+    tor_version_t ver_tmp;
+    if (tor_version_parse_platform(platform, &ver_tmp, 1) < 0) {
+      if (msg) {
+        *msg = "Malformed platform string.";
+      }
+      return FP_REJECT;
+    }
+  }
+
+  /* Versions before Tor 0.2.4.18-rc are too old to support, and are
+   * missing some important security fixes too. Disable them. */
+  if (platform && !tor_version_as_new_as(platform,"0.2.4.18-rc")) {
+    if (msg)
+      *msg = "Tor version is insecure or unsupported. Please upgrade!";
+    return FP_REJECT;
+  }
+
+  /* Tor 0.2.9.x where x<5 suffers from bug #20499, where relays don't
+   * keep their consensus up to date so they make bad guards.
+   * The simple fix is to just drop them from the network. */
+  if (platform &&
+      tor_version_as_new_as(platform,"0.2.9.0-alpha") &&
+      !tor_version_as_new_as(platform,"0.2.9.5-alpha")) {
+    if (msg)
+      *msg = "Tor version contains bug 20499. Please upgrade!";
+    return FP_REJECT;
+  }
+
+  status_by_digest = digestmap_get(fingerprint_list->status_by_digest,
+                                   id_digest);
+  if (status_by_digest)
+    result |= *status_by_digest;
+
+  if (result & FP_REJECT) {
+    if (msg)
+      *msg = "Fingerprint is marked rejected -- if you think this is a "
+             "mistake please set a valid email address in ContactInfo and "
+             "send an email to bad-relays at lists.torproject.org mentioning "
+             "your fingerprint(s)?";
+    return FP_REJECT;
+  } else if (result & FP_INVALID) {
+    if (msg)
+      *msg = "Fingerprint is marked invalid";
+  }
+
+  if (authdir_policy_badexit_address(addr, or_port)) {
+    log_fn(severity, LD_DIRSERV,
+           "Marking '%s' as bad exit because of address '%s'",
+               nickname, fmt_addr32(addr));
+    result |= FP_BADEXIT;
+  }
+
+  if (!authdir_policy_permits_address(addr, or_port)) {
+    log_fn(severity, LD_DIRSERV, "Rejecting '%s' because of address '%s'",
+               nickname, fmt_addr32(addr));
+    if (msg)
+      *msg = "Suspicious relay address range -- if you think this is a "
+             "mistake please set a valid email address in ContactInfo and "
+             "send an email to bad-relays at lists.torproject.org mentioning "
+             "your address(es) and fingerprint(s)?";
+    return FP_REJECT;
+  }
+  if (!authdir_policy_valid_address(addr, or_port)) {
+    log_fn(severity, LD_DIRSERV,
+           "Not marking '%s' valid because of address '%s'",
+               nickname, fmt_addr32(addr));
+    result |= FP_INVALID;
+  }
+
+  return result;
+}
+
+/** Clear the current fingerprint list. */
+void
+dirserv_free_fingerprint_list(void)
+{
+  if (!fingerprint_list)
+    return;
+
+  strmap_free(fingerprint_list->fp_by_name, tor_free_);
+  digestmap_free(fingerprint_list->status_by_digest, tor_free_);
+  tor_free(fingerprint_list);
+}
+
+/*
+ *    Descriptor list
+ */
+
+/** Return -1 if <b>ri</b> has a private or otherwise bad address,
+ * unless we're configured to not care. Return 0 if all ok. */
+static int
+dirserv_router_has_valid_address(routerinfo_t *ri)
+{
+  tor_addr_t addr;
+  if (get_options()->DirAllowPrivateAddresses)
+    return 0; /* whatever it is, we're fine with it */
+  tor_addr_from_ipv4h(&addr, ri->addr);
+
+  if (tor_addr_is_internal(&addr, 0)) {
+    log_info(LD_DIRSERV,
+             "Router %s published internal IP address. Refusing.",
+             router_describe(ri));
+    return -1; /* it's a private IP, we should reject it */
+  }
+  return 0;
+}
+
+/** Check whether we, as a directory server, want to accept <b>ri</b>.  If so,
+ * set its is_valid,running fields and return 0.  Otherwise, return -1.
+ *
+ * If the router is rejected, set *<b>msg</b> to an explanation of why.
+ *
+ * If <b>complain</b> then explain at log-level 'notice' why we refused
+ * a descriptor; else explain at log-level 'info'.
+ */
+int
+authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg,
+                               int complain, int *valid_out)
+{
+  /* Okay.  Now check whether the fingerprint is recognized. */
+  time_t now;
+  int severity = (complain && ri->contact_info) ? LOG_NOTICE : LOG_INFO;
+  uint32_t status = dirserv_router_get_status(ri, msg, severity);
+  tor_assert(msg);
+  if (status & FP_REJECT)
+    return -1; /* msg is already set. */
+
+  /* Is there too much clock skew? */
+  now = time(NULL);
+  if (ri->cache_info.published_on > now+ROUTER_ALLOW_SKEW) {
+    log_fn(severity, LD_DIRSERV, "Publication time for %s is too "
+           "far (%d minutes) in the future; possible clock skew. Not adding "
+           "(%s)",
+           router_describe(ri),
+           (int)((ri->cache_info.published_on-now)/60),
+           esc_router_info(ri));
+    *msg = "Rejected: Your clock is set too far in the future, or your "
+      "timezone is not correct.";
+    return -1;
+  }
+  if (ri->cache_info.published_on < now-ROUTER_MAX_AGE_TO_PUBLISH) {
+    log_fn(severity, LD_DIRSERV,
+           "Publication time for %s is too far "
+           "(%d minutes) in the past. Not adding (%s)",
+           router_describe(ri),
+           (int)((now-ri->cache_info.published_on)/60),
+           esc_router_info(ri));
+    *msg = "Rejected: Server is expired, or your clock is too far in the past,"
+      " or your timezone is not correct.";
+    return -1;
+  }
+  if (dirserv_router_has_valid_address(ri) < 0) {
+    log_fn(severity, LD_DIRSERV,
+           "Router %s has invalid address. Not adding (%s).",
+           router_describe(ri),
+           esc_router_info(ri));
+    *msg = "Rejected: Address is a private address.";
+    return -1;
+  }
+
+  *valid_out = ! (status & FP_INVALID);
+
+  return 0;
+}
+
+/** Update the relevant flags of <b>node</b> based on our opinion as a
+ * directory authority in <b>authstatus</b>, as returned by
+ * dirserv_router_get_status or equivalent.  */
+void
+dirserv_set_node_flags_from_authoritative_status(node_t *node,
+                                                 uint32_t authstatus)
+{
+  node->is_valid = (authstatus & FP_INVALID) ? 0 : 1;
+  node->is_bad_exit = (authstatus & FP_BADEXIT) ? 1 : 0;
+}
+
+/** True iff <b>a</b> is more severe than <b>b</b>. */
+static int
+WRA_MORE_SEVERE(was_router_added_t a, was_router_added_t b)
+{
+  return a < b;
+}
+
+/** As for dirserv_add_descriptor(), but accepts multiple documents, and
+ * returns the most severe error that occurred for any one of them. */
+was_router_added_t
+dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose,
+                                 const char *source,
+                                 const char **msg)
+{
+  was_router_added_t r, r_tmp;
+  const char *msg_out;
+  smartlist_t *list;
+  const char *s;
+  int n_parsed = 0;
+  time_t now = time(NULL);
+  char annotation_buf[ROUTER_ANNOTATION_BUF_LEN];
+  char time_buf[ISO_TIME_LEN+1];
+  int general = purpose == ROUTER_PURPOSE_GENERAL;
+  tor_assert(msg);
+
+  r=ROUTER_ADDED_SUCCESSFULLY; /*Least severe return value. */
+
+  format_iso_time(time_buf, now);
+  if (tor_snprintf(annotation_buf, sizeof(annotation_buf),
+                   "@uploaded-at %s\n"
+                   "@source %s\n"
+                   "%s%s%s", time_buf, escaped(source),
+                   !general ? "@purpose " : "",
+                   !general ? router_purpose_to_string(purpose) : "",
+                   !general ? "\n" : "")<0) {
+    *msg = "Couldn't format annotations";
+    /* XXX Not cool: we return -1 below, but (was_router_added_t)-1 is
+     * ROUTER_BAD_EI, which isn't what's gone wrong here. :( */
+    return -1;
+  }
+
+  s = desc;
+  list = smartlist_new();
+  if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 0, 0,
+                                     annotation_buf, NULL)) {
+    SMARTLIST_FOREACH(list, routerinfo_t *, ri, {
+        msg_out = NULL;
+        tor_assert(ri->purpose == purpose);
+        r_tmp = dirserv_add_descriptor(ri, &msg_out, source);
+        if (WRA_MORE_SEVERE(r_tmp, r)) {
+          r = r_tmp;
+          *msg = msg_out;
+        }
+      });
+  }
+  n_parsed += smartlist_len(list);
+  smartlist_clear(list);
+
+  s = desc;
+  if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 1, 0,
+                                     NULL, NULL)) {
+    SMARTLIST_FOREACH(list, extrainfo_t *, ei, {
+        msg_out = NULL;
+
+        r_tmp = dirserv_add_extrainfo(ei, &msg_out);
+        if (WRA_MORE_SEVERE(r_tmp, r)) {
+          r = r_tmp;
+          *msg = msg_out;
+        }
+      });
+  }
+  n_parsed += smartlist_len(list);
+  smartlist_free(list);
+
+  if (! *msg) {
+    if (!n_parsed) {
+      *msg = "No descriptors found in your POST.";
+      if (WRA_WAS_ADDED(r))
+        r = ROUTER_IS_ALREADY_KNOWN;
+    } else {
+      *msg = "(no message)";
+    }
+  }
+
+  return r;
+}
+
+/** Examine the parsed server descriptor in <b>ri</b> and maybe insert it into
+ * the list of server descriptors. Set *<b>msg</b> to a message that should be
+ * passed back to the origin of this descriptor, or NULL if there is no such
+ * message. Use <b>source</b> to produce better log messages.
+ *
+ * If <b>ri</b> is not added to the list of server descriptors, free it.
+ * That means the caller must not access <b>ri</b> after this function
+ * returns, since it might have been freed.
+ *
+ * Return the status of the operation.
+ *
+ * This function is only called when fresh descriptors are posted, not when
+ * we re-load the cache.
+ */
+was_router_added_t
+dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
+{
+  was_router_added_t r;
+  routerinfo_t *ri_old;
+  char *desc, *nickname;
+  const size_t desclen = ri->cache_info.signed_descriptor_len +
+      ri->cache_info.annotations_len;
+  const int key_pinning = get_options()->AuthDirPinKeys;
+  *msg = NULL;
+
+  /* If it's too big, refuse it now. Otherwise we'll cache it all over the
+   * network and it'll clog everything up. */
+  if (ri->cache_info.signed_descriptor_len > MAX_DESCRIPTOR_UPLOAD_SIZE) {
+    log_notice(LD_DIR, "Somebody attempted to publish a router descriptor '%s'"
+               " (source: %s) with size %d. Either this is an attack, or the "
+               "MAX_DESCRIPTOR_UPLOAD_SIZE (%d) constant is too low.",
+               ri->nickname, source, (int)ri->cache_info.signed_descriptor_len,
+               MAX_DESCRIPTOR_UPLOAD_SIZE);
+    *msg = "Router descriptor was too large.";
+    r = ROUTER_AUTHDIR_REJECTS;
+    goto fail;
+  }
+
+  /* Check whether this descriptor is semantically identical to the last one
+   * from this server.  (We do this here and not in router_add_to_routerlist
+   * because we want to be able to accept the newest router descriptor that
+   * another authority has, so we all converge on the same one.) */
+  ri_old = router_get_mutable_by_digest(ri->cache_info.identity_digest);
+  if (ri_old && ri_old->cache_info.published_on < ri->cache_info.published_on
+      && router_differences_are_cosmetic(ri_old, ri)
+      && !router_is_me(ri)) {
+    log_info(LD_DIRSERV,
+             "Not replacing descriptor from %s (source: %s); "
+             "differences are cosmetic.",
+             router_describe(ri), source);
+    *msg = "Not replacing router descriptor; no information has changed since "
+      "the last one with this identity.";
+    r = ROUTER_IS_ALREADY_KNOWN;
+    goto fail;
+  }
+
+  /* Do keypinning again ... this time, to add the pin if appropriate */
+  int keypin_status;
+  if (ri->cache_info.signing_key_cert) {
+    ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key;
+    /* First let's validate this pubkey before pinning it */
+    if (ed25519_validate_pubkey(pkey) < 0) {
+      log_warn(LD_DIRSERV, "Received bad key from %s (source %s)",
+               router_describe(ri), source);
+      routerinfo_free(ri);
+      return ROUTER_AUTHDIR_REJECTS;
+    }
+
+    /* Now pin it! */
+    keypin_status = keypin_check_and_add(
+      (const uint8_t*)ri->cache_info.identity_digest,
+      pkey->pubkey, ! key_pinning);
+  } else {
+    keypin_status = keypin_check_lone_rsa(
+      (const uint8_t*)ri->cache_info.identity_digest);
+#ifndef DISABLE_DISABLING_ED25519
+    if (keypin_status == KEYPIN_MISMATCH)
+      keypin_status = KEYPIN_NOT_FOUND;
+#endif
+  }
+  if (keypin_status == KEYPIN_MISMATCH && key_pinning) {
+    log_info(LD_DIRSERV, "Dropping descriptor from %s (source: %s) because "
+             "its key did not match an older RSA/Ed25519 keypair",
+             router_describe(ri), source);
+    *msg = "Looks like your keypair has changed? This authority previously "
+      "recorded a different RSA identity for this Ed25519 identity (or vice "
+      "versa.) Did you replace or copy some of your key files, but not "
+      "the others? You should either restore the expected keypair, or "
+      "delete your keys and restart Tor to start your relay with a new "
+      "identity.";
+    r = ROUTER_AUTHDIR_REJECTS;
+    goto fail;
+  }
+
+  /* Make a copy of desc, since router_add_to_routerlist might free
+   * ri and its associated signed_descriptor_t. */
+  desc = tor_strndup(ri->cache_info.signed_descriptor_body, desclen);
+  nickname = tor_strdup(ri->nickname);
+
+  /* Tell if we're about to need to launch a test if we add this. */
+  ri->needs_retest_if_added =
+    dirserv_should_launch_reachability_test(ri, ri_old);
+
+  r = router_add_to_routerlist(ri, msg, 0, 0);
+  if (!WRA_WAS_ADDED(r)) {
+    /* unless the routerinfo was fine, just out-of-date */
+    log_info(LD_DIRSERV,
+             "Did not add descriptor from '%s' (source: %s): %s.",
+             nickname, source, *msg ? *msg : "(no message)");
+  } else {
+    smartlist_t *changed;
+
+    changed = smartlist_new();
+    smartlist_add(changed, ri);
+    routerlist_descriptors_added(changed, 0);
+    smartlist_free(changed);
+    if (!*msg) {
+      *msg =  "Descriptor accepted";
+    }
+    log_info(LD_DIRSERV,
+             "Added descriptor from '%s' (source: %s): %s.",
+             nickname, source, *msg);
+  }
+  tor_free(desc);
+  tor_free(nickname);
+  return r;
+ fail:
+  {
+    const char *desc_digest = ri->cache_info.signed_descriptor_digest;
+    download_status_t *dls =
+      router_get_dl_status_by_descriptor_digest(desc_digest);
+    if (dls) {
+      log_info(LD_GENERAL, "Marking router with descriptor %s as rejected, "
+               "and therefore undownloadable",
+               hex_str(desc_digest, DIGEST_LEN));
+      download_status_mark_impossible(dls);
+    }
+    routerinfo_free(ri);
+  }
+  return r;
+}
+
+/** As dirserv_add_descriptor, but for an extrainfo_t <b>ei</b>. */
+static was_router_added_t
+dirserv_add_extrainfo(extrainfo_t *ei, const char **msg)
+{
+  routerinfo_t *ri;
+  int r;
+  was_router_added_t rv;
+  tor_assert(msg);
+  *msg = NULL;
+
+  /* Needs to be mutable so routerinfo_incompatible_with_extrainfo
+   * can mess with some of the flags in ri->cache_info. */
+  ri = router_get_mutable_by_digest(ei->cache_info.identity_digest);
+  if (!ri) {
+    *msg = "No corresponding router descriptor for extra-info descriptor";
+    rv = ROUTER_BAD_EI;
+    goto fail;
+  }
+
+  /* If it's too big, refuse it now. Otherwise we'll cache it all over the
+   * network and it'll clog everything up. */
+  if (ei->cache_info.signed_descriptor_len > MAX_EXTRAINFO_UPLOAD_SIZE) {
+    log_notice(LD_DIR, "Somebody attempted to publish an extrainfo "
+               "with size %d. Either this is an attack, or the "
+               "MAX_EXTRAINFO_UPLOAD_SIZE (%d) constant is too low.",
+               (int)ei->cache_info.signed_descriptor_len,
+               MAX_EXTRAINFO_UPLOAD_SIZE);
+    *msg = "Extrainfo document was too large";
+    rv = ROUTER_BAD_EI;
+    goto fail;
+  }
+
+  if ((r = routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei,
+                                                  &ri->cache_info, msg))) {
+    if (r<0) {
+      extrainfo_free(ei);
+      return ROUTER_IS_ALREADY_KNOWN;
+    }
+    rv = ROUTER_BAD_EI;
+    goto fail;
+  }
+  router_add_extrainfo_to_routerlist(ei, msg, 0, 0);
+  return ROUTER_ADDED_SUCCESSFULLY;
+ fail:
+  {
+    const char *d = ei->cache_info.signed_descriptor_digest;
+    signed_descriptor_t *sd = router_get_by_extrainfo_digest((char*)d);
+    if (sd) {
+      log_info(LD_GENERAL, "Marking extrainfo with descriptor %s as "
+               "rejected, and therefore undownloadable",
+               hex_str((char*)d,DIGEST_LEN));
+      download_status_mark_impossible(&sd->ei_dl_status);
+    }
+    extrainfo_free(ei);
+  }
+  return rv;
+}
+
+/** Remove all descriptors whose nicknames or fingerprints no longer
+ * are allowed by our fingerprint list. (Descriptors that used to be
+ * good can become bad when we reload the fingerprint list.)
+ */
+static void
+directory_remove_invalid(void)
+{
+  routerlist_t *rl = router_get_routerlist();
+  smartlist_t *nodes = smartlist_new();
+  smartlist_add_all(nodes, nodelist_get_list());
+
+  SMARTLIST_FOREACH_BEGIN(nodes, node_t *, node) {
+    const char *msg = NULL;
+    const char *description;
+    routerinfo_t *ent = node->ri;
+    uint32_t r;
+    if (!ent)
+      continue;
+    r = dirserv_router_get_status(ent, &msg, LOG_INFO);
+    description = router_describe(ent);
+    if (r & FP_REJECT) {
+      log_info(LD_DIRSERV, "Router %s is now rejected: %s",
+               description, msg?msg:"");
+      routerlist_remove(rl, ent, 0, time(NULL));
+      continue;
+    }
+    if (bool_neq((r & FP_INVALID), !node->is_valid)) {
+      log_info(LD_DIRSERV, "Router '%s' is now %svalid.", description,
+               (r&FP_INVALID) ? "in" : "");
+      node->is_valid = (r&FP_INVALID)?0:1;
+    }
+    if (bool_neq((r & FP_BADEXIT), node->is_bad_exit)) {
+      log_info(LD_DIRSERV, "Router '%s' is now a %s exit", description,
+               (r & FP_BADEXIT) ? "bad" : "good");
+      node->is_bad_exit = (r&FP_BADEXIT) ? 1: 0;
+    }
+  } SMARTLIST_FOREACH_END(node);
+
+  routerlist_assert_ok(rl);
+  smartlist_free(nodes);
+}
diff --git a/src/feature/dirauth/process_descs.h b/src/feature/dirauth/process_descs.h
new file mode 100644
index 000000000..ad9d5c3d4
--- /dev/null
+++ b/src/feature/dirauth/process_descs.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file process_descs.h
+ * \brief Header file for process_descs.c.
+ **/
+
+#ifndef TOR_RECV_UPLOADS_H
+#define TOR_RECV_UPLOADS_H
+
+int dirserv_load_fingerprint_file(void);
+void dirserv_free_fingerprint_list(void);
+int dirserv_add_own_fingerprint(crypto_pk_t *pk);
+
+enum was_router_added_t dirserv_add_multiple_descriptors(
+                                     const char *desc, uint8_t purpose,
+                                     const char *source,
+                                     const char **msg);
+enum was_router_added_t dirserv_add_descriptor(routerinfo_t *ri,
+                                               const char **msg,
+                                               const char *source);
+
+int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg,
+                                   int complain,
+                                   int *valid_out);
+uint32_t dirserv_router_get_status(const routerinfo_t *router,
+                                   const char **msg,
+                                   int severity);
+void dirserv_set_node_flags_from_authoritative_status(node_t *node,
+                                                      uint32_t authstatus);
+
+int dirserv_would_reject_router(const routerstatus_t *rs);
+
+#endif
diff --git a/src/feature/dirauth/reachability.c b/src/feature/dirauth/reachability.c
new file mode 100644
index 000000000..abc7249b6
--- /dev/null
+++ b/src/feature/dirauth/reachability.c
@@ -0,0 +1,205 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file reachability.c
+ * \brief Router reachability testing; run by authorities to tell who is
+ * running.
+ */
+
+#include "core/or/or.h"
+#include "feature/dirauth/reachability.h"
+
+#include "app/config/config.h"
+#include "core/or/channel.h"
+#include "core/or/channeltls.h"
+#include "core/or/command.h"
+#include "feature/nodelist/nodelist.h"
+#include "feature/nodelist/routerlist.h"
+#include "feature/nodelist/torcert.h"
+#include "feature/relay/router.h"
+#include "feature/stats/rephist.h"
+
+#include "feature/nodelist/node_st.h"
+#include "feature/nodelist/routerinfo_st.h"
+#include "feature/nodelist/routerlist_st.h"
+
+/** Called when a TLS handshake has completed successfully with a
+ * router listening at <b>address</b>:<b>or_port</b>, and has yielded
+ * a certificate with digest <b>digest_rcvd</b>.
+ *
+ * Inform the reachability checker that we could get to this relay.
+ */
+void
+dirserv_orconn_tls_done(const tor_addr_t *addr,
+                        uint16_t or_port,
+                        const char *digest_rcvd,
+                        const ed25519_public_key_t *ed_id_rcvd)
+{
+  node_t *node = NULL;
+  tor_addr_port_t orport;
+  routerinfo_t *ri = NULL;
+  time_t now = time(NULL);
+  tor_assert(addr);
+  tor_assert(digest_rcvd);
+
+  node = node_get_mutable_by_id(digest_rcvd);
+  if (node == NULL || node->ri == NULL)
+    return;
+
+  ri = node->ri;
+
+  if (get_options()->AuthDirTestEd25519LinkKeys &&
+      node_supports_ed25519_link_authentication(node, 1) &&
+      ri->cache_info.signing_key_cert) {
+    /* We allow the node to have an ed25519 key if we haven't been told one in
+     * the routerinfo, but if we *HAVE* been told one in the routerinfo, it
+     * needs to match. */
+    const ed25519_public_key_t *expected_id =
+      &ri->cache_info.signing_key_cert->signing_key;
+    tor_assert(!ed25519_public_key_is_zero(expected_id));
+    if (! ed_id_rcvd || ! ed25519_pubkey_eq(ed_id_rcvd, expected_id)) {
+      log_info(LD_DIRSERV, "Router at %s:%d with RSA ID %s "
+               "did not present expected Ed25519 ID.",
+               fmt_addr(addr), or_port, hex_str(digest_rcvd, DIGEST_LEN));
+      return; /* Don't mark it as reachable. */
+    }
+  }
+
+  tor_addr_copy(&orport.addr, addr);
+  orport.port = or_port;
+  if (router_has_orport(ri, &orport)) {
+    /* Found the right router.  */
+    if (!authdir_mode_bridge(get_options()) ||
+        ri->purpose == ROUTER_PURPOSE_BRIDGE) {
+      char addrstr[TOR_ADDR_BUF_LEN];
+      /* This is a bridge or we're not a bridge authority --
+         mark it as reachable.  */
+      log_info(LD_DIRSERV, "Found router %s to be reachable at %s:%d. Yay.",
+               router_describe(ri),
+               tor_addr_to_str(addrstr, addr, sizeof(addrstr), 1),
+               ri->or_port);
+      if (tor_addr_family(addr) == AF_INET) {
+        rep_hist_note_router_reachable(digest_rcvd, addr, or_port, now);
+        node->last_reachable = now;
+      } else if (tor_addr_family(addr) == AF_INET6) {
+        /* No rephist for IPv6.  */
+        node->last_reachable6 = now;
+      }
+    }
+  }
+}
+
+/** Called when we, as an authority, receive a new router descriptor either as
+ * an upload or a download.  Used to decide whether to relaunch reachability
+ * testing for the server. */
+int
+dirserv_should_launch_reachability_test(const routerinfo_t *ri,
+                                        const routerinfo_t *ri_old)
+{
+  if (!authdir_mode_handles_descs(get_options(), ri->purpose))
+    return 0;
+  if (!ri_old) {
+    /* New router: Launch an immediate reachability test, so we will have an
+     * opinion soon in case we're generating a consensus soon */
+    return 1;
+  }
+  if (ri_old->is_hibernating && !ri->is_hibernating) {
+    /* It just came out of hibernation; launch a reachability test */
+    return 1;
+  }
+  if (! routers_have_same_or_addrs(ri, ri_old)) {
+    /* Address or port changed; launch a reachability test */
+    return 1;
+  }
+  return 0;
+}
+
+/** Helper function for dirserv_test_reachability(). Start a TLS
+ * connection to <b>router</b>, and annotate it with when we started
+ * the test. */
+void
+dirserv_single_reachability_test(time_t now, routerinfo_t *router)
+{
+  const or_options_t *options = get_options();
+  channel_t *chan = NULL;
+  const node_t *node = NULL;
+  tor_addr_t router_addr;
+  const ed25519_public_key_t *ed_id_key;
+  (void) now;
+
+  tor_assert(router);
+  node = node_get_by_id(router->cache_info.identity_digest);
+  tor_assert(node);
+
+  if (options->AuthDirTestEd25519LinkKeys &&
+      node_supports_ed25519_link_authentication(node, 1) &&
+      router->cache_info.signing_key_cert) {
+    ed_id_key = &router->cache_info.signing_key_cert->signing_key;
+  } else {
+    ed_id_key = NULL;
+  }
+
+  /* IPv4. */
+  log_debug(LD_OR,"Testing reachability of %s at %s:%u.",
+            router->nickname, fmt_addr32(router->addr), router->or_port);
+  tor_addr_from_ipv4h(&router_addr, router->addr);
+  chan = channel_tls_connect(&router_addr, router->or_port,
+                             router->cache_info.identity_digest,
+                             ed_id_key);
+  if (chan) command_setup_channel(chan);
+
+  /* Possible IPv6. */
+  if (get_options()->AuthDirHasIPv6Connectivity == 1 &&
+      !tor_addr_is_null(&router->ipv6_addr)) {
+    char addrstr[TOR_ADDR_BUF_LEN];
+    log_debug(LD_OR, "Testing reachability of %s at %s:%u.",
+              router->nickname,
+              tor_addr_to_str(addrstr, &router->ipv6_addr, sizeof(addrstr), 1),
+              router->ipv6_orport);
+    chan = channel_tls_connect(&router->ipv6_addr, router->ipv6_orport,
+                               router->cache_info.identity_digest,
+                               ed_id_key);
+    if (chan) command_setup_channel(chan);
+  }
+}
+
+/** Auth dir server only: load balance such that we only
+ * try a few connections per call.
+ *
+ * The load balancing is such that if we get called once every ten
+ * seconds, we will cycle through all the tests in
+ * REACHABILITY_TEST_CYCLE_PERIOD seconds (a bit over 20 minutes).
+ */
+void
+dirserv_test_reachability(time_t now)
+{
+  /* XXX decide what to do here; see or-talk thread "purging old router
+   * information, revocation." -NM
+   * We can't afford to mess with this in 0.1.2.x. The reason is that
+   * if we stop doing reachability tests on some of routerlist, then
+   * we'll for-sure think they're down, which may have unexpected
+   * effects in other parts of the code. It doesn't hurt much to do
+   * the testing, and directory authorities are easy to upgrade. Let's
+   * wait til 0.2.0. -RD */
+//  time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
+  routerlist_t *rl = router_get_routerlist();
+  static char ctr = 0;
+  int bridge_auth = authdir_mode_bridge(get_options());
+
+  SMARTLIST_FOREACH_BEGIN(rl->routers, routerinfo_t *, router) {
+    const char *id_digest = router->cache_info.identity_digest;
+    if (router_is_me(router))
+      continue;
+    if (bridge_auth && router->purpose != ROUTER_PURPOSE_BRIDGE)
+      continue; /* bridge authorities only test reachability on bridges */
+//    if (router->cache_info.published_on > cutoff)
+//      continue;
+    if ((((uint8_t)id_digest[0]) % REACHABILITY_MODULO_PER_TEST) == ctr) {
+      dirserv_single_reachability_test(now, router);
+    }
+  } SMARTLIST_FOREACH_END(router);
+  ctr = (ctr + 1) % REACHABILITY_MODULO_PER_TEST; /* increment ctr */
+}
diff --git a/src/feature/dirauth/reachability.h b/src/feature/dirauth/reachability.h
new file mode 100644
index 000000000..6e4bf28ca
--- /dev/null
+++ b/src/feature/dirauth/reachability.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file reachability.h
+ * \brief Header file for reachability.c.
+ **/
+
+#ifndef TOR_REACHABILITY_H
+#define TOR_REACHABILITY_H
+
+/** What fraction (1 over this number) of the relay ID space do we
+ * (as a directory authority) launch connections to at each reachability
+ * test? */
+#define REACHABILITY_MODULO_PER_TEST 128
+
+/** How often (in seconds) do we launch reachability tests? */
+#define REACHABILITY_TEST_INTERVAL 10
+
+/** How many seconds apart are the reachability tests for a given relay? */
+#define REACHABILITY_TEST_CYCLE_PERIOD \
+  (REACHABILITY_TEST_INTERVAL*REACHABILITY_MODULO_PER_TEST)
+
+void dirserv_orconn_tls_done(const tor_addr_t *addr,
+                             uint16_t or_port,
+                             const char *digest_rcvd,
+                             const struct ed25519_public_key_t *ed_id_rcvd);
+int dirserv_should_launch_reachability_test(const routerinfo_t *ri,
+                                            const routerinfo_t *ri_old);
+void dirserv_single_reachability_test(time_t now, routerinfo_t *router);
+void dirserv_test_reachability(time_t now);
+
+#endif
diff --git a/src/feature/dirauth/recommend_pkg.c b/src/feature/dirauth/recommend_pkg.c
new file mode 100644
index 000000000..41c091455
--- /dev/null
+++ b/src/feature/dirauth/recommend_pkg.c
@@ -0,0 +1,90 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file recommend_pkg.c
+ * \brief Code related to the recommended-packages subsystem.
+ *
+ * Currently unused.
+ **/
+
+#include "core/or/or.h"
+#include "feature/dirauth/recommend_pkg.h"
+
+/** Return true iff <b>line</b> is a valid RecommendedPackages line.
+ */
+/*
+  The grammar is:
+
+    "package" SP PACKAGENAME SP VERSION SP URL SP DIGESTS NL
+
+      PACKAGENAME = NONSPACE
+      VERSION = NONSPACE
+      URL = NONSPACE
+      DIGESTS = DIGEST | DIGESTS SP DIGEST
+      DIGEST = DIGESTTYPE "=" DIGESTVAL
+
+      NONSPACE = one or more non-space printing characters
+
+      DIGESTVAL = DIGESTTYPE = one or more non-=, non-" " characters.
+
+      SP = " "
+      NL = a newline
+
+ */
+int
+validate_recommended_package_line(const char *line)
+{
+  const char *cp = line;
+
+#define WORD()                                  \
+  do {                                          \
+    if (*cp == ' ')                             \
+      return 0;                                 \
+    cp = strchr(cp, ' ');                       \
+    if (!cp)                                    \
+      return 0;                                 \
+  } while (0)
+
+  WORD(); /* skip packagename */
+  ++cp;
+  WORD(); /* skip version */
+  ++cp;
+  WORD(); /* Skip URL */
+  ++cp;
+
+  /* Skip digesttype=digestval + */
+  int n_entries = 0;
+  while (1) {
+    const char *start_of_word = cp;
+    const char *end_of_word = strchr(cp, ' ');
+    if (! end_of_word)
+      end_of_word = cp + strlen(cp);
+
+    if (start_of_word == end_of_word)
+      return 0;
+
+    const char *eq = memchr(start_of_word, '=', end_of_word - start_of_word);
+
+    if (!eq)
+      return 0;
+    if (eq == start_of_word)
+      return 0;
+    if (eq == end_of_word - 1)
+      return 0;
+    if (memchr(eq+1, '=', end_of_word - (eq+1)))
+      return 0;
+
+    ++n_entries;
+    if (0 == *end_of_word)
+      break;
+
+    cp = end_of_word + 1;
+  }
+
+  /* If we reach this point, we have at least 1 entry. */
+  tor_assert(n_entries > 0);
+  return 1;
+}
diff --git a/src/feature/dirauth/recommend_pkg.h b/src/feature/dirauth/recommend_pkg.h
new file mode 100644
index 000000000..29a41d6df
--- /dev/null
+++ b/src/feature/dirauth/recommend_pkg.h
@@ -0,0 +1,17 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file recommend_pkg.h
+ * \brief Header file for recommend_pkg.c
+ **/
+
+#ifndef TOR_RECOMMEND_PKG_H
+#define TOR_RECOMMEND_PKG_H
+
+int validate_recommended_package_line(const char *line);
+
+#endif
diff --git a/src/feature/dirauth/voteflags.c b/src/feature/dirauth/voteflags.c
new file mode 100644
index 000000000..d5ace761d
--- /dev/null
+++ b/src/feature/dirauth/voteflags.c
@@ -0,0 +1,644 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file voteflags.c
+ * \brief Authority code for deciding the performance thresholds for flags,
+ *   and assigning flags to routers.
+ **/
+
+#define VOTEFLAGS_PRIVATE
+#include "core/or/or.h"
+#include "feature/dirauth/voteflags.h"
+
+#include "app/config/config.h"
+#include "core/mainloop/main.h"
+#include "core/or/policies.h"
+#include "feature/dirauth/bwauth.h"
+#include "feature/dirauth/reachability.h"
+#include "feature/hibernate/hibernate.h"
+#include "feature/nodelist/dirlist.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/nodelist.h"
+#include "feature/nodelist/routerlist.h"
+#include "feature/nodelist/routerset.h"
+#include "feature/relay/router.h"
+#include "feature/stats/rephist.h"
+
+#include "feature/nodelist/node_st.h"
+#include "feature/nodelist/routerinfo_st.h"
+#include "feature/nodelist/vote_routerstatus_st.h"
+
+#include "lib/container/order.h"
+
+/** If a router's uptime is at least this value, then it is always
+ * considered stable, regardless of the rest of the network. This
+ * way we resist attacks where an attacker doubles the size of the
+ * network using allegedly high-uptime nodes, displacing all the
+ * current guards. */
+#define UPTIME_TO_GUARANTEE_STABLE (3600*24*30)
+/** If a router's MTBF is at least this value, then it is always stable.
+ * See above.  (Corresponds to about 7 days for current decay rates.) */
+#define MTBF_TO_GUARANTEE_STABLE (60*60*24*5)
+/** Similarly, every node with at least this much weighted time known can be
+ * considered familiar enough to be a guard.  Corresponds to about 20 days for
+ * current decay rates.
+ */
+#define TIME_KNOWN_TO_GUARANTEE_FAMILIAR (8*24*60*60)
+/** Similarly, every node with sufficient WFU is around enough to be a guard.
+ */
+#define WFU_TO_GUARANTEE_GUARD (0.98)
+
+/* Thresholds for server performance: set by
+ * dirserv_compute_performance_thresholds, and used by
+ * generate_v2_networkstatus */
+
+/** Any router with an uptime of at least this value is stable. */
+static uint32_t stable_uptime = 0; /* start at a safe value */
+/** Any router with an mtbf of at least this value is stable. */
+static double stable_mtbf = 0.0;
+/** If true, we have measured enough mtbf info to look at stable_mtbf rather
+ * than stable_uptime. */
+static int enough_mtbf_info = 0;
+/** Any router with a weighted fractional uptime of at least this much might
+ * be good as a guard. */
+static double guard_wfu = 0.0;
+/** Don't call a router a guard unless we've known about it for at least this
+ * many seconds. */
+static long guard_tk = 0;
+/** Any router with a bandwidth at least this high is "Fast" */
+static uint32_t fast_bandwidth_kb = 0;
+/** If exits can be guards, then all guards must have a bandwidth this
+ * high. */
+static uint32_t guard_bandwidth_including_exits_kb = 0;
+/** If exits can't be guards, then all guards must have a bandwidth this
+ * high. */
+static uint32_t guard_bandwidth_excluding_exits_kb = 0;
+
+/** Helper: estimate the uptime of a router given its stated uptime and the
+ * amount of time since it last stated its stated uptime. */
+static inline long
+real_uptime(const routerinfo_t *router, time_t now)
+{
+  if (now < router->cache_info.published_on)
+    return router->uptime;
+  else
+    return router->uptime + (now - router->cache_info.published_on);
+}
+
+/** Return 1 if <b>router</b> is not suitable for these parameters, else 0.
+ * If <b>need_uptime</b> is non-zero, we require a minimum uptime.
+ * If <b>need_capacity</b> is non-zero, we require a minimum advertised
+ * bandwidth.
+ */
+static int
+dirserv_thinks_router_is_unreliable(time_t now,
+                                    routerinfo_t *router,
+                                    int need_uptime, int need_capacity)
+{
+  if (need_uptime) {
+    if (!enough_mtbf_info) {
+      /* XXXX We should change the rule from
+       * "use uptime if we don't have mtbf data" to "don't advertise Stable on
+       * v3 if we don't have enough mtbf data."  Or maybe not, since if we ever
+       * hit a point where we need to reset a lot of authorities at once,
+       * none of them would be in a position to declare Stable.
+       */
+      long uptime = real_uptime(router, now);
+      if ((unsigned)uptime < stable_uptime &&
+          (unsigned)uptime < UPTIME_TO_GUARANTEE_STABLE)
+        return 1;
+    } else {
+      double mtbf =
+        rep_hist_get_stability(router->cache_info.identity_digest, now);
+      if (mtbf < stable_mtbf &&
+          mtbf < MTBF_TO_GUARANTEE_STABLE)
+        return 1;
+    }
+  }
+  if (need_capacity) {
+    uint32_t bw_kb = dirserv_get_credible_bandwidth_kb(router);
+    if (bw_kb < fast_bandwidth_kb)
+      return 1;
+  }
+  return 0;
+}
+
+/** Return 1 if <b>ri</b>'s descriptor is "active" -- running, valid,
+ * not hibernating, having observed bw greater 0, and not too old. Else
+ * return 0.
+ */
+static int
+router_is_active(const routerinfo_t *ri, const node_t *node, time_t now)
+{
+  time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
+  if (ri->cache_info.published_on < cutoff) {
+    return 0;
+  }
+  if (!node->is_running || !node->is_valid || ri->is_hibernating) {
+    return 0;
+  }
+  /* Only require bandwidth capacity in non-test networks, or
+   * if TestingTorNetwork, and TestingMinExitFlagThreshold is non-zero */
+  if (!ri->bandwidthcapacity) {
+    if (get_options()->TestingTorNetwork) {
+      if (get_options()->TestingMinExitFlagThreshold > 0) {
+        /* If we're in a TestingTorNetwork, and TestingMinExitFlagThreshold is,
+         * then require bandwidthcapacity */
+        return 0;
+      }
+    } else {
+      /* If we're not in a TestingTorNetwork, then require bandwidthcapacity */
+      return 0;
+    }
+  }
+  return 1;
+}
+
+/** 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,
+ * it's a directory cache, it has the Stable and Fast flags, and it's currently
+ * considered Running.
+ *
+ * This function needs to be called after router-\>is_running has
+ * been set.
+ */
+static int
+dirserv_thinks_router_is_hs_dir(const routerinfo_t *router,
+                                const node_t *node, time_t now)
+{
+
+  long uptime;
+
+  /* If we haven't been running for at least
+   * get_options()->MinUptimeHidServDirectoryV2 seconds, we can't
+   * have accurate data telling us a relay has been up for at least
+   * that long. We also want to allow a bit of slack: Reachability
+   * tests aren't instant. If we haven't been running long enough,
+   * trust the relay. */
+
+  if (get_uptime() >
+      get_options()->MinUptimeHidServDirectoryV2 * 1.1)
+    uptime = MIN(rep_hist_get_uptime(router->cache_info.identity_digest, now),
+                 real_uptime(router, now));
+  else
+    uptime = real_uptime(router, now);
+
+  return (router->wants_to_be_hs_dir &&
+          router->supports_tunnelled_dir_requests &&
+          node->is_stable && node->is_fast &&
+          uptime >= get_options()->MinUptimeHidServDirectoryV2 &&
+          router_is_active(router, node, now));
+}
+
+/** Don't consider routers with less bandwidth than this when computing
+ * thresholds. */
+#define ABSOLUTE_MIN_BW_VALUE_TO_CONSIDER_KB 4
+
+/** Helper for dirserv_compute_performance_thresholds(): Decide whether to
+ * include a router in our calculations, and return true iff we should; the
+ * require_mbw parameter is passed in by
+ * dirserv_compute_performance_thresholds() and controls whether we ever
+ * count routers with only advertised bandwidths */
+static int
+router_counts_toward_thresholds(const node_t *node, time_t now,
+                                const digestmap_t *omit_as_sybil,
+                                int require_mbw)
+{
+  /* Have measured bw? */
+  int have_mbw =
+    dirserv_has_measured_bw(node->identity);
+  uint64_t min_bw_kb = ABSOLUTE_MIN_BW_VALUE_TO_CONSIDER_KB;
+  const or_options_t *options = get_options();
+
+  if (options->TestingTorNetwork) {
+    min_bw_kb = (int64_t)options->TestingMinExitFlagThreshold / 1000;
+  }
+
+  return node->ri && router_is_active(node->ri, node, now) &&
+    !digestmap_get(omit_as_sybil, node->identity) &&
+    (dirserv_get_credible_bandwidth_kb(node->ri) >= min_bw_kb) &&
+    (have_mbw || !require_mbw);
+}
+
+/** Look through the routerlist, the Mean Time Between Failure history, and
+ * the Weighted Fractional Uptime history, and use them to set thresholds for
+ * the Stable, Fast, and Guard flags.  Update the fields stable_uptime,
+ * stable_mtbf, enough_mtbf_info, guard_wfu, guard_tk, fast_bandwidth,
+ * guard_bandwidth_including_exits, and guard_bandwidth_excluding_exits.
+ *
+ * Also, set the is_exit flag of each router appropriately. */
+void
+dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil)
+{
+  int n_active, n_active_nonexit, n_familiar;
+  uint32_t *uptimes, *bandwidths_kb, *bandwidths_excluding_exits_kb;
+  long *tks;
+  double *mtbfs, *wfus;
+  smartlist_t *nodelist;
+  time_t now = time(NULL);
+  const or_options_t *options = get_options();
+
+  /* Require mbw? */
+  int require_mbw =
+    (dirserv_get_last_n_measured_bws() >
+     options->MinMeasuredBWsForAuthToIgnoreAdvertised) ? 1 : 0;
+
+  /* initialize these all here, in case there are no routers */
+  stable_uptime = 0;
+  stable_mtbf = 0;
+  fast_bandwidth_kb = 0;
+  guard_bandwidth_including_exits_kb = 0;
+  guard_bandwidth_excluding_exits_kb = 0;
+  guard_tk = 0;
+  guard_wfu = 0;
+
+  nodelist_assert_ok();
+  nodelist = nodelist_get_list();
+
+  /* Initialize arrays that will hold values for each router.  We'll
+   * sort them and use that to compute thresholds. */
+  n_active = n_active_nonexit = 0;
+  /* Uptime for every active router. */
+  uptimes = tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
+  /* Bandwidth for every active router. */
+  bandwidths_kb = tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
+  /* Bandwidth for every active non-exit router. */
+  bandwidths_excluding_exits_kb =
+    tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
+  /* Weighted mean time between failure for each active router. */
+  mtbfs = tor_calloc(smartlist_len(nodelist), sizeof(double));
+  /* Time-known for each active router. */
+  tks = tor_calloc(smartlist_len(nodelist), sizeof(long));
+  /* Weighted fractional uptime for each active router. */
+  wfus = tor_calloc(smartlist_len(nodelist), sizeof(double));
+
+  /* Now, fill in the arrays. */
+  SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) {
+    if (options->BridgeAuthoritativeDir &&
+        node->ri &&
+        node->ri->purpose != ROUTER_PURPOSE_BRIDGE)
+      continue;
+
+    routerinfo_t *ri = node->ri;
+    if (ri) {
+      node->is_exit = (!router_exit_policy_rejects_all(ri) &&
+                       exit_policy_is_general_exit(ri->exit_policy));
+    }
+
+    if (router_counts_toward_thresholds(node, now, omit_as_sybil,
+                                        require_mbw)) {
+      const char *id = node->identity;
+      uint32_t bw_kb;
+
+      /* resolve spurious clang shallow analysis null pointer errors */
+      tor_assert(ri);
+
+      uptimes[n_active] = (uint32_t)real_uptime(ri, now);
+      mtbfs[n_active] = rep_hist_get_stability(id, now);
+      tks  [n_active] = rep_hist_get_weighted_time_known(id, now);
+      bandwidths_kb[n_active] = bw_kb = dirserv_get_credible_bandwidth_kb(ri);
+      if (!node->is_exit || node->is_bad_exit) {
+        bandwidths_excluding_exits_kb[n_active_nonexit] = bw_kb;
+        ++n_active_nonexit;
+      }
+      ++n_active;
+    }
+  } SMARTLIST_FOREACH_END(node);
+
+  /* Now, compute thresholds. */
+  if (n_active) {
+    /* The median uptime is stable. */
+    stable_uptime = median_uint32(uptimes, n_active);
+    /* The median mtbf is stable, if we have enough mtbf info */
+    stable_mtbf = median_double(mtbfs, n_active);
+    /* The 12.5th percentile bandwidth is fast. */
+    fast_bandwidth_kb = find_nth_uint32(bandwidths_kb, n_active, n_active/8);
+    /* (Now bandwidths is sorted.) */
+    if (fast_bandwidth_kb < RELAY_REQUIRED_MIN_BANDWIDTH/(2 * 1000))
+      fast_bandwidth_kb = bandwidths_kb[n_active/4];
+    guard_bandwidth_including_exits_kb =
+      third_quartile_uint32(bandwidths_kb, n_active);
+    guard_tk = find_nth_long(tks, n_active, n_active/8);
+  }
+
+  if (guard_tk > TIME_KNOWN_TO_GUARANTEE_FAMILIAR)
+    guard_tk = TIME_KNOWN_TO_GUARANTEE_FAMILIAR;
+
+  {
+    /* We can vote on a parameter for the minimum and maximum. */
+#define ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG 4
+    int32_t min_fast_kb, max_fast_kb, min_fast, max_fast;
+    min_fast = networkstatus_get_param(NULL, "FastFlagMinThreshold",
+      ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG,
+      ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG,
+      INT32_MAX);
+    if (options->TestingTorNetwork) {
+      min_fast = (int32_t)options->TestingMinFastFlagThreshold;
+    }
+    max_fast = networkstatus_get_param(NULL, "FastFlagMaxThreshold",
+                                       INT32_MAX, min_fast, INT32_MAX);
+    min_fast_kb = min_fast / 1000;
+    max_fast_kb = max_fast / 1000;
+
+    if (fast_bandwidth_kb < (uint32_t)min_fast_kb)
+      fast_bandwidth_kb = min_fast_kb;
+    if (fast_bandwidth_kb > (uint32_t)max_fast_kb)
+      fast_bandwidth_kb = max_fast_kb;
+  }
+  /* Protect sufficiently fast nodes from being pushed out of the set
+   * of Fast nodes. */
+  if (options->AuthDirFastGuarantee &&
+      fast_bandwidth_kb > options->AuthDirFastGuarantee/1000)
+    fast_bandwidth_kb = (uint32_t)options->AuthDirFastGuarantee/1000;
+
+  /* Now that we have a time-known that 7/8 routers are known longer than,
+   * fill wfus with the wfu of every such "familiar" router. */
+  n_familiar = 0;
+
+  SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) {
+      if (router_counts_toward_thresholds(node, now,
+                                          omit_as_sybil, require_mbw)) {
+        routerinfo_t *ri = node->ri;
+        const char *id = ri->cache_info.identity_digest;
+        long tk = rep_hist_get_weighted_time_known(id, now);
+        if (tk < guard_tk)
+          continue;
+        wfus[n_familiar++] = rep_hist_get_weighted_fractional_uptime(id, now);
+      }
+  } SMARTLIST_FOREACH_END(node);
+  if (n_familiar)
+    guard_wfu = median_double(wfus, n_familiar);
+  if (guard_wfu > WFU_TO_GUARANTEE_GUARD)
+    guard_wfu = WFU_TO_GUARANTEE_GUARD;
+
+  enough_mtbf_info = rep_hist_have_measured_enough_stability();
+
+  if (n_active_nonexit) {
+    guard_bandwidth_excluding_exits_kb =
+      find_nth_uint32(bandwidths_excluding_exits_kb,
+                      n_active_nonexit, n_active_nonexit*3/4);
+  }
+
+  log_info(LD_DIRSERV,
+      "Cutoffs: For Stable, %lu sec uptime, %lu sec MTBF. "
+      "For Fast: %lu kilobytes/sec. "
+      "For Guard: WFU %.03f%%, time-known %lu sec, "
+      "and bandwidth %lu or %lu kilobytes/sec. "
+      "We%s have enough stability data.",
+      (unsigned long)stable_uptime,
+      (unsigned long)stable_mtbf,
+      (unsigned long)fast_bandwidth_kb,
+      guard_wfu*100,
+      (unsigned long)guard_tk,
+      (unsigned long)guard_bandwidth_including_exits_kb,
+      (unsigned long)guard_bandwidth_excluding_exits_kb,
+      enough_mtbf_info ? "" : " don't");
+
+  tor_free(uptimes);
+  tor_free(mtbfs);
+  tor_free(bandwidths_kb);
+  tor_free(bandwidths_excluding_exits_kb);
+  tor_free(tks);
+  tor_free(wfus);
+}
+
+/* Use dirserv_compute_performance_thresholds() to compute the thresholds
+ * for the status flags, specifically for bridges.
+ *
+ * This is only called by a Bridge Authority from
+ * networkstatus_getinfo_by_purpose().
+ */
+void
+dirserv_compute_bridge_flag_thresholds(void)
+{
+  digestmap_t *omit_as_sybil = digestmap_new();
+  dirserv_compute_performance_thresholds(omit_as_sybil);
+  digestmap_free(omit_as_sybil, NULL);
+}
+
+/** Give a statement of our current performance thresholds for inclusion
+ * in a vote document. */
+char *
+dirserv_get_flag_thresholds_line(void)
+{
+  char *result=NULL;
+  const int measured_threshold =
+    get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised;
+  const int enough_measured_bw =
+    dirserv_get_last_n_measured_bws() > measured_threshold;
+
+  tor_asprintf(&result,
+      "stable-uptime=%lu stable-mtbf=%lu "
+      "fast-speed=%lu "
+      "guard-wfu=%.03f%% guard-tk=%lu "
+      "guard-bw-inc-exits=%lu guard-bw-exc-exits=%lu "
+      "enough-mtbf=%d ignoring-advertised-bws=%d",
+      (unsigned long)stable_uptime,
+      (unsigned long)stable_mtbf,
+      (unsigned long)fast_bandwidth_kb*1000,
+      guard_wfu*100,
+      (unsigned long)guard_tk,
+      (unsigned long)guard_bandwidth_including_exits_kb*1000,
+      (unsigned long)guard_bandwidth_excluding_exits_kb*1000,
+      enough_mtbf_info ? 1 : 0,
+      enough_measured_bw ? 1 : 0);
+
+  return result;
+}
+
+/* DOCDOC running_long_enough_to_decide_unreachable */
+int
+running_long_enough_to_decide_unreachable(void)
+{
+  return time_of_process_start
+    + get_options()->TestingAuthDirTimeToLearnReachability < approx_time();
+}
+
+/** Each server needs to have passed a reachability test no more
+ * than this number of seconds ago, or it is listed as down in
+ * the directory. */
+#define REACHABLE_TIMEOUT (45*60)
+
+/** If we tested a router and found it reachable _at least this long_ after it
+ * declared itself hibernating, it is probably done hibernating and we just
+ * missed a descriptor from it. */
+#define HIBERNATION_PUBLICATION_SKEW (60*60)
+
+/** Treat a router as alive if
+ *    - It's me, and I'm not hibernating.
+ * or - We've found it reachable recently. */
+void
+dirserv_set_router_is_running(routerinfo_t *router, time_t now)
+{
+  /*XXXX This function is a mess.  Separate out the part that calculates
+    whether it's reachable and the part that tells rephist that the router was
+    unreachable.
+   */
+  int answer;
+  const or_options_t *options = get_options();
+  node_t *node = node_get_mutable_by_id(router->cache_info.identity_digest);
+  tor_assert(node);
+
+  if (router_is_me(router)) {
+    /* We always know if we are shutting down or hibernating ourselves. */
+    answer = ! we_are_hibernating();
+  } else if (router->is_hibernating &&
+             (router->cache_info.published_on +
+              HIBERNATION_PUBLICATION_SKEW) > node->last_reachable) {
+    /* A hibernating router is down unless we (somehow) had contact with it
+     * since it declared itself to be hibernating. */
+    answer = 0;
+  } else if (options->AssumeReachable) {
+    /* If AssumeReachable, everybody is up unless they say they are down! */
+    answer = 1;
+  } else {
+    /* Otherwise, a router counts as up if we found all announced OR
+       ports reachable in the last REACHABLE_TIMEOUT seconds.
+
+       XXX prop186 For now there's always one IPv4 and at most one
+       IPv6 OR port.
+
+       If we're not on IPv6, don't consider reachability of potential
+       IPv6 OR port since that'd kill all dual stack relays until a
+       majority of the dir auths have IPv6 connectivity. */
+    answer = (now < node->last_reachable + REACHABLE_TIMEOUT &&
+              (options->AuthDirHasIPv6Connectivity != 1 ||
+               tor_addr_is_null(&router->ipv6_addr) ||
+               now < node->last_reachable6 + REACHABLE_TIMEOUT));
+  }
+
+  if (!answer && running_long_enough_to_decide_unreachable()) {
+    /* Not considered reachable. tell rephist about that.
+
+       Because we launch a reachability test for each router every
+       REACHABILITY_TEST_CYCLE_PERIOD seconds, then the router has probably
+       been down since at least that time after we last successfully reached
+       it.
+
+       XXX ipv6
+     */
+    time_t when = now;
+    if (node->last_reachable &&
+        node->last_reachable + REACHABILITY_TEST_CYCLE_PERIOD < now)
+      when = node->last_reachable + REACHABILITY_TEST_CYCLE_PERIOD;
+    rep_hist_note_router_unreachable(router->cache_info.identity_digest, when);
+  }
+
+  node->is_running = answer;
+}
+
+/** Extract status information from <b>ri</b> and from other authority
+ * functions and store it in <b>rs</b>. <b>rs</b> is zeroed out before it is
+ * set.
+ *
+ * We assume that ri-\>is_running has already been set, e.g. by
+ *   dirserv_set_router_is_running(ri, now);
+ */
+void
+set_routerstatus_from_routerinfo(routerstatus_t *rs,
+                                 node_t *node,
+                                 routerinfo_t *ri,
+                                 time_t now,
+                                 int listbadexits)
+{
+  const or_options_t *options = get_options();
+  uint32_t routerbw_kb = dirserv_get_credible_bandwidth_kb(ri);
+
+  memset(rs, 0, sizeof(routerstatus_t));
+
+  rs->is_authority =
+    router_digest_is_trusted_dir(ri->cache_info.identity_digest);
+
+  /* Already set by compute_performance_thresholds. */
+  rs->is_exit = node->is_exit;
+  rs->is_stable = node->is_stable =
+    !dirserv_thinks_router_is_unreliable(now, ri, 1, 0);
+  rs->is_fast = node->is_fast =
+    !dirserv_thinks_router_is_unreliable(now, ri, 0, 1);
+  rs->is_flagged_running = node->is_running; /* computed above */
+
+  rs->is_valid = node->is_valid;
+
+  if (node->is_fast && node->is_stable &&
+      ri->supports_tunnelled_dir_requests &&
+      ((options->AuthDirGuardBWGuarantee &&
+        routerbw_kb >= options->AuthDirGuardBWGuarantee/1000) ||
+       routerbw_kb >= MIN(guard_bandwidth_including_exits_kb,
+                          guard_bandwidth_excluding_exits_kb))) {
+    long tk = rep_hist_get_weighted_time_known(
+                                      node->identity, now);
+    double wfu = rep_hist_get_weighted_fractional_uptime(
+                                      node->identity, now);
+    rs->is_possible_guard = (wfu >= guard_wfu && tk >= guard_tk) ? 1 : 0;
+  } else {
+    rs->is_possible_guard = 0;
+  }
+
+  rs->is_bad_exit = listbadexits && node->is_bad_exit;
+  rs->is_hs_dir = node->is_hs_dir =
+    dirserv_thinks_router_is_hs_dir(ri, node, now);
+
+  rs->is_named = rs->is_unnamed = 0;
+
+  rs->published_on = ri->cache_info.published_on;
+  memcpy(rs->identity_digest, node->identity, DIGEST_LEN);
+  memcpy(rs->descriptor_digest, ri->cache_info.signed_descriptor_digest,
+         DIGEST_LEN);
+  rs->addr = ri->addr;
+  strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname));
+  rs->or_port = ri->or_port;
+  rs->dir_port = ri->dir_port;
+  rs->is_v2_dir = ri->supports_tunnelled_dir_requests;
+  if (options->AuthDirHasIPv6Connectivity == 1 &&
+      !tor_addr_is_null(&ri->ipv6_addr) &&
+      node->last_reachable6 >= now - REACHABLE_TIMEOUT) {
+    /* We're configured as having IPv6 connectivity. There's an IPv6
+       OR port and it's reachable so copy it to the routerstatus.  */
+    tor_addr_copy(&rs->ipv6_addr, &ri->ipv6_addr);
+    rs->ipv6_orport = ri->ipv6_orport;
+  } else {
+    tor_addr_make_null(&rs->ipv6_addr, AF_INET6);
+    rs->ipv6_orport = 0;
+  }
+
+  if (options->TestingTorNetwork) {
+    dirserv_set_routerstatus_testing(rs);
+  }
+}
+
+/** Use TestingDirAuthVoteExit, TestingDirAuthVoteGuard, and
+ * TestingDirAuthVoteHSDir to give out the Exit, Guard, and HSDir flags,
+ * respectively. But don't set the corresponding node flags.
+ * Should only be called if TestingTorNetwork is set. */
+STATIC void
+dirserv_set_routerstatus_testing(routerstatus_t *rs)
+{
+  const or_options_t *options = get_options();
+
+  tor_assert(options->TestingTorNetwork);
+
+  if (routerset_contains_routerstatus(options->TestingDirAuthVoteExit,
+                                      rs, 0)) {
+    rs->is_exit = 1;
+  } else if (options->TestingDirAuthVoteExitIsStrict) {
+    rs->is_exit = 0;
+  }
+
+  if (routerset_contains_routerstatus(options->TestingDirAuthVoteGuard,
+                                      rs, 0)) {
+    rs->is_possible_guard = 1;
+  } else if (options->TestingDirAuthVoteGuardIsStrict) {
+    rs->is_possible_guard = 0;
+  }
+
+  if (routerset_contains_routerstatus(options->TestingDirAuthVoteHSDir,
+                                      rs, 0)) {
+    rs->is_hs_dir = 1;
+  } else if (options->TestingDirAuthVoteHSDirIsStrict) {
+    rs->is_hs_dir = 0;
+  }
+}
diff --git a/src/feature/dirauth/voteflags.h b/src/feature/dirauth/voteflags.h
new file mode 100644
index 000000000..2f0e061ea
--- /dev/null
+++ b/src/feature/dirauth/voteflags.h
@@ -0,0 +1,31 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file voteflags.h
+ * \brief Header file for voteflags.c
+ **/
+
+#ifndef TOR_VOTEFLAGS_H
+#define TOR_VOTEFLAGS_H
+
+void dirserv_set_router_is_running(routerinfo_t *router, time_t now);
+char *dirserv_get_flag_thresholds_line(void);
+void dirserv_compute_bridge_flag_thresholds(void);
+int running_long_enough_to_decide_unreachable(void);
+
+void set_routerstatus_from_routerinfo(routerstatus_t *rs,
+                                      node_t *node,
+                                      routerinfo_t *ri, time_t now,
+                                      int listbadexits);
+
+void dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil);
+
+#ifdef VOTEFLAGS_PRIVATE
+STATIC void dirserv_set_routerstatus_testing(routerstatus_t *rs);
+#endif
+
+#endif
diff --git a/src/feature/dircache/directory.c b/src/feature/dircache/directory.c
index 8628a35c5..34305bf66 100644
--- a/src/feature/dircache/directory.c
+++ b/src/feature/dircache/directory.c
@@ -22,6 +22,7 @@
 #include "lib/crypt_ops/crypto_util.h"
 #include "feature/dircache/directory.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/process_descs.h"
 #include "feature/client/entrynodes.h"
 #include "feature/dircommon/fp_pair.h"
 #include "feature/stats/geoip.h"
diff --git a/src/feature/dircache/dirserv.c b/src/feature/dircache/dirserv.c
index b3cd83e21..2fd2572a2 100644
--- a/src/feature/dircache/dirserv.c
+++ b/src/feature/dircache/dirserv.c
@@ -5,55 +5,27 @@
 
 #define DIRSERV_PRIVATE
 #include "core/or/or.h"
-#include "lib/container/buffers.h"
+
 #include "app/config/config.h"
-#include "app/config/confparse.h"
-#include "core/or/channel.h"
-#include "core/or/channeltls.h"
-#include "core/or/command.h"
 #include "core/mainloop/connection.h"
-#include "core/or/connection_or.h"
 #include "feature/dircache/conscache.h"
 #include "feature/dircache/consdiffmgr.h"
-#include "feature/control/control.h"
 #include "feature/dircache/directory.h"
 #include "feature/dircache/dirserv.h"
-#include "feature/hibernate/hibernate.h"
-#include "feature/dirauth/keypin.h"
-#include "core/mainloop/main.h"
 #include "feature/nodelist/microdesc.h"
-#include "feature/nodelist/networkstatus.h"
-#include "feature/nodelist/nodelist.h"
-#include "core/or/policies.h"
-#include "core/or/protover.h"
-#include "feature/stats/rephist.h"
-#include "feature/relay/router.h"
-#include "feature/nodelist/dirlist.h"
 #include "feature/nodelist/routerlist.h"
-
-#include "feature/nodelist/routerparse.h"
-#include "feature/nodelist/routerset.h"
-#include "feature/nodelist/torcert.h"
-#include "feature/dircommon/voting_schedule.h"
-
-#include "feature/dirauth/dirvote.h"
+#include "feature/relay/router.h"
+#include "feature/stats/rephist.h"
 
 #include "feature/dircache/cached_dir_st.h"
 #include "feature/dircommon/dir_connection_st.h"
 #include "feature/nodelist/extrainfo_st.h"
 #include "feature/nodelist/microdesc_st.h"
-#include "feature/nodelist/node_st.h"
 #include "feature/nodelist/routerinfo_st.h"
 #include "feature/nodelist/routerlist_st.h"
-#include "core/or/tor_version_st.h"
-#include "feature/nodelist/vote_routerstatus_st.h"
 
 #include "lib/compress/compress.h"
-#include "lib/container/order.h"
-#include "lib/crypt_ops/crypto_format.h"
-#include "lib/encoding/confline.h"
 
-#include "lib/encoding/keyval.h"
 /**
  * \file dirserv.c
  * \brief Directory server core implementation. Manages directory
@@ -78,35 +50,10 @@
  * shared among the v1, v2, and v3 directory code.)
  */
 
-/** How far in the future do we allow a router to get? (seconds) */
-#define ROUTER_ALLOW_SKEW (60*60*12)
-/** How many seconds do we wait before regenerating the directory? */
-#define DIR_REGEN_SLACK_TIME 30
-/** If we're a cache, keep this many networkstatuses around from non-trusted
- * directory authorities. */
-#define MAX_UNTRUSTED_NETWORKSTATUSES 16
-
-/** Total number of routers with measured bandwidth; this is set by
- * dirserv_count_measured_bws() before the loop in
- * dirserv_generate_networkstatus_vote_obj() and checked by
- * dirserv_get_credible_bandwidth() and
- * dirserv_compute_performance_thresholds() */
-static int routers_with_measured_bw = 0;
-
-static void directory_remove_invalid(void);
-struct authdir_config_t;
-static uint32_t
-dirserv_get_status_impl(const char *fp, const char *nickname,
-                        uint32_t addr, uint16_t or_port,
-                        const char *platform, const char **msg,
-                        int severity);
 static void clear_cached_dir(cached_dir_t *d);
 static const signed_descriptor_t *get_signed_descriptor_by_fp(
                                                         const uint8_t *fp,
                                                         int extrainfo);
-static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei,
-                                                const char **msg);
-static uint32_t dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri);
 
 static int spooled_resource_lookup_body(const spooled_resource_t *spooled,
                                         int conn_is_encrypted,
@@ -118,994 +65,6 @@ static cached_dir_t *spooled_resource_lookup_cached_dir(
                                    time_t *published_out);
 static cached_dir_t *lookup_cached_dir_by_fp(const uint8_t *fp);
 
-/************** Fingerprint handling code ************/
-
-/*                 1  Historically used to indicate Named */
-#define FP_INVALID 2  /**< Believed invalid. */
-#define FP_REJECT  4  /**< We will not publish this router. */
-/*                 8  Historically used to avoid using this as a dir. */
-#define FP_BADEXIT 16 /**< We'll tell clients not to use this as an exit. */
-/*                 32 Historically used to indicade Unnamed */
-
-/** Target of status_by_digest map. */
-typedef uint32_t router_status_t;
-
-static void add_fingerprint_to_dir(const char *fp,
-                                   struct authdir_config_t *list,
-                                   router_status_t add_status);
-
-/** List of nickname-\>identity fingerprint mappings for all the routers
- * that we name.  Used to prevent router impersonation. */
-typedef struct authdir_config_t {
-  strmap_t *fp_by_name; /**< Map from lc nickname to fingerprint. */
-  digestmap_t *status_by_digest; /**< Map from digest to router_status_t. */
-} authdir_config_t;
-
-/** Should be static; exposed for testing. */
-static authdir_config_t *fingerprint_list = NULL;
-
-/** Allocate and return a new, empty, authdir_config_t. */
-static authdir_config_t *
-authdir_config_new(void)
-{
-  authdir_config_t *list = tor_malloc_zero(sizeof(authdir_config_t));
-  list->fp_by_name = strmap_new();
-  list->status_by_digest = digestmap_new();
-  return list;
-}
-
-/** Add the fingerprint <b>fp</b> to the smartlist of fingerprint_entry_t's
- * <b>list</b>, or-ing the currently set status flags with
- * <b>add_status</b>.
- */
-/* static */ void
-add_fingerprint_to_dir(const char *fp, authdir_config_t *list,
-                       router_status_t add_status)
-{
-  char *fingerprint;
-  char d[DIGEST_LEN];
-  router_status_t *status;
-  tor_assert(fp);
-  tor_assert(list);
-
-  fingerprint = tor_strdup(fp);
-  tor_strstrip(fingerprint, " ");
-  if (base16_decode(d, DIGEST_LEN,
-                    fingerprint, strlen(fingerprint)) != DIGEST_LEN) {
-    log_warn(LD_DIRSERV, "Couldn't decode fingerprint \"%s\"",
-             escaped(fp));
-    tor_free(fingerprint);
-    return;
-  }
-
-  status = digestmap_get(list->status_by_digest, d);
-  if (!status) {
-    status = tor_malloc_zero(sizeof(router_status_t));
-    digestmap_set(list->status_by_digest, d, status);
-  }
-
-  tor_free(fingerprint);
-  *status |= add_status;
-  return;
-}
-
-/** Add the fingerprint for this OR to the global list of recognized
- * identity key fingerprints. */
-int
-dirserv_add_own_fingerprint(crypto_pk_t *pk)
-{
-  char fp[FINGERPRINT_LEN+1];
-  if (crypto_pk_get_fingerprint(pk, fp, 0)<0) {
-    log_err(LD_BUG, "Error computing fingerprint");
-    return -1;
-  }
-  if (!fingerprint_list)
-    fingerprint_list = authdir_config_new();
-  add_fingerprint_to_dir(fp, fingerprint_list, 0);
-  return 0;
-}
-
-/** Load the nickname-\>fingerprint mappings stored in the approved-routers
- * file.  The file format is line-based, with each non-blank holding one
- * nickname, some space, and a fingerprint for that nickname.  On success,
- * replace the current fingerprint list with the new list and return 0.  On
- * failure, leave the current fingerprint list untouched, and return -1. */
-int
-dirserv_load_fingerprint_file(void)
-{
-  char *fname;
-  char *cf;
-  char *nickname, *fingerprint;
-  authdir_config_t *fingerprint_list_new;
-  int result;
-  config_line_t *front=NULL, *list;
-
-  fname = get_datadir_fname("approved-routers");
-  log_info(LD_GENERAL,
-           "Reloading approved fingerprints from \"%s\"...", fname);
-
-  cf = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL);
-  if (!cf) {
-    log_warn(LD_FS, "Cannot open fingerprint file '%s'. That's ok.", fname);
-    tor_free(fname);
-    return 0;
-  }
-  tor_free(fname);
-
-  result = config_get_lines(cf, &front, 0);
-  tor_free(cf);
-  if (result < 0) {
-    log_warn(LD_CONFIG, "Error reading from fingerprint file");
-    return -1;
-  }
-
-  fingerprint_list_new = authdir_config_new();
-
-  for (list=front; list; list=list->next) {
-    char digest_tmp[DIGEST_LEN];
-    router_status_t add_status = 0;
-    nickname = list->key; fingerprint = list->value;
-    tor_strstrip(fingerprint, " "); /* remove spaces */
-    if (strlen(fingerprint) != HEX_DIGEST_LEN ||
-        base16_decode(digest_tmp, sizeof(digest_tmp),
-                      fingerprint, HEX_DIGEST_LEN) != sizeof(digest_tmp)) {
-      log_notice(LD_CONFIG,
-                 "Invalid fingerprint (nickname '%s', "
-                 "fingerprint %s). Skipping.",
-                 nickname, fingerprint);
-      continue;
-    }
-    if (!strcasecmp(nickname, "!reject")) {
-        add_status = FP_REJECT;
-    } else if (!strcasecmp(nickname, "!badexit")) {
-        add_status = FP_BADEXIT;
-    } else if (!strcasecmp(nickname, "!invalid")) {
-        add_status = FP_INVALID;
-    }
-    add_fingerprint_to_dir(fingerprint, fingerprint_list_new, add_status);
-  }
-
-  config_free_lines(front);
-  dirserv_free_fingerprint_list();
-  fingerprint_list = fingerprint_list_new;
-  /* Delete any routers whose fingerprints we no longer recognize */
-  directory_remove_invalid();
-  return 0;
-}
-
-/* If this is set, then we don't allow routers that have advertised an Ed25519
- * identity to stop doing so.  This is going to be essential for good identity
- * security: otherwise anybody who can attack RSA-1024 but not Ed25519 could
- * just sign fake descriptors missing the Ed25519 key.  But we won't actually
- * be able to prevent that kind of thing until we're confident that there isn't
- * actually a legit reason to downgrade to 0.2.5.  Now we are not recommending
- * 0.2.5 anymore so there is no reason to keep the #undef.
- */
-
-#define DISABLE_DISABLING_ED25519
-
-/** Check whether <b>router</b> has a nickname/identity key combination that
- * we recognize from the fingerprint list, or an IP we automatically act on
- * according to our configuration.  Return the appropriate router status.
- *
- * If the status is 'FP_REJECT' and <b>msg</b> is provided, set
- * *<b>msg</b> to an explanation of why. */
-uint32_t
-dirserv_router_get_status(const routerinfo_t *router, const char **msg,
-                          int severity)
-{
-  char d[DIGEST_LEN];
-  const int key_pinning = get_options()->AuthDirPinKeys;
-
-  if (crypto_pk_get_digest(router->identity_pkey, d)) {
-    log_warn(LD_BUG,"Error computing fingerprint");
-    if (msg)
-      *msg = "Bug: Error computing fingerprint";
-    return FP_REJECT;
-  }
-
-  /* Check for the more usual versions to reject a router first. */
-  const uint32_t r = dirserv_get_status_impl(d, router->nickname,
-                                             router->addr, router->or_port,
-                                             router->platform, msg, severity);
-  if (r)
-    return r;
-
-  /* dirserv_get_status_impl already rejects versions older than 0.2.4.18-rc,
-   * and onion_curve25519_pkey was introduced in 0.2.4.8-alpha.
-   * But just in case a relay doesn't provide or lies about its version, or
-   * doesn't include an ntor key in its descriptor, check that it exists,
-   * and is non-zero (clients check that it's non-zero before using it). */
-  if (!routerinfo_has_curve25519_onion_key(router)) {
-    log_fn(severity, LD_DIR,
-           "Descriptor from router %s is missing an ntor curve25519 onion "
-           "key.", router_describe(router));
-    if (msg)
-      *msg = "Missing ntor curve25519 onion key. Please upgrade!";
-    return FP_REJECT;
-  }
-
-  if (router->cache_info.signing_key_cert) {
-    /* This has an ed25519 identity key. */
-    if (KEYPIN_MISMATCH ==
-        keypin_check((const uint8_t*)router->cache_info.identity_digest,
-                   router->cache_info.signing_key_cert->signing_key.pubkey)) {
-      log_fn(severity, LD_DIR,
-             "Descriptor from router %s has an Ed25519 key, "
-               "but the <rsa,ed25519> keys don't match what they were before.",
-               router_describe(router));
-      if (key_pinning) {
-        if (msg) {
-          *msg = "Ed25519 identity key or RSA identity key has changed.";
-        }
-        return FP_REJECT;
-      }
-    }
-  } else {
-    /* No ed25519 key */
-    if (KEYPIN_MISMATCH == keypin_check_lone_rsa(
-                        (const uint8_t*)router->cache_info.identity_digest)) {
-      log_fn(severity, LD_DIR,
-               "Descriptor from router %s has no Ed25519 key, "
-               "when we previously knew an Ed25519 for it. Ignoring for now, "
-               "since Ed25519 keys are fairly new.",
-               router_describe(router));
-#ifdef DISABLE_DISABLING_ED25519
-      if (key_pinning) {
-        if (msg) {
-          *msg = "Ed25519 identity key has disappeared.";
-        }
-        return FP_REJECT;
-      }
-#endif /* defined(DISABLE_DISABLING_ED25519) */
-    }
-  }
-
-  return 0;
-}
-
-/** Return true if there is no point in downloading the router described by
- * <b>rs</b> because this directory would reject it. */
-int
-dirserv_would_reject_router(const routerstatus_t *rs)
-{
-  uint32_t res;
-
-  res = dirserv_get_status_impl(rs->identity_digest, rs->nickname,
-                                rs->addr, rs->or_port,
-                                NULL, NULL, LOG_DEBUG);
-
-  return (res & FP_REJECT) != 0;
-}
-
-/** Helper: As dirserv_router_get_status, but takes the router fingerprint
- * (hex, no spaces), nickname, address (used for logging only), IP address, OR
- * port and platform (logging only) as arguments.
- *
- * Log messages at 'severity'. (There's not much point in
- * logging that we're rejecting servers we'll not download.)
- */
-static uint32_t
-dirserv_get_status_impl(const char *id_digest, const char *nickname,
-                        uint32_t addr, uint16_t or_port,
-                        const char *platform, const char **msg, int severity)
-{
-  uint32_t result = 0;
-  router_status_t *status_by_digest;
-
-  if (!fingerprint_list)
-    fingerprint_list = authdir_config_new();
-
-  log_debug(LD_DIRSERV, "%d fingerprints, %d digests known.",
-            strmap_size(fingerprint_list->fp_by_name),
-            digestmap_size(fingerprint_list->status_by_digest));
-
-  if (platform) {
-    tor_version_t ver_tmp;
-    if (tor_version_parse_platform(platform, &ver_tmp, 1) < 0) {
-      if (msg) {
-        *msg = "Malformed platform string.";
-      }
-      return FP_REJECT;
-    }
-  }
-
-  /* Versions before Tor 0.2.4.18-rc are too old to support, and are
-   * missing some important security fixes too. Disable them. */
-  if (platform && !tor_version_as_new_as(platform,"0.2.4.18-rc")) {
-    if (msg)
-      *msg = "Tor version is insecure or unsupported. Please upgrade!";
-    return FP_REJECT;
-  }
-
-  /* Tor 0.2.9.x where x<5 suffers from bug #20499, where relays don't
-   * keep their consensus up to date so they make bad guards.
-   * The simple fix is to just drop them from the network. */
-  if (platform &&
-      tor_version_as_new_as(platform,"0.2.9.0-alpha") &&
-      !tor_version_as_new_as(platform,"0.2.9.5-alpha")) {
-    if (msg)
-      *msg = "Tor version contains bug 20499. Please upgrade!";
-    return FP_REJECT;
-  }
-
-  status_by_digest = digestmap_get(fingerprint_list->status_by_digest,
-                                   id_digest);
-  if (status_by_digest)
-    result |= *status_by_digest;
-
-  if (result & FP_REJECT) {
-    if (msg)
-      *msg = "Fingerprint is marked rejected -- if you think this is a "
-             "mistake please set a valid email address in ContactInfo and "
-             "send an email to bad-relays at lists.torproject.org mentioning "
-             "your fingerprint(s)?";
-    return FP_REJECT;
-  } else if (result & FP_INVALID) {
-    if (msg)
-      *msg = "Fingerprint is marked invalid";
-  }
-
-  if (authdir_policy_badexit_address(addr, or_port)) {
-    log_fn(severity, LD_DIRSERV,
-           "Marking '%s' as bad exit because of address '%s'",
-               nickname, fmt_addr32(addr));
-    result |= FP_BADEXIT;
-  }
-
-  if (!authdir_policy_permits_address(addr, or_port)) {
-    log_fn(severity, LD_DIRSERV, "Rejecting '%s' because of address '%s'",
-               nickname, fmt_addr32(addr));
-    if (msg)
-      *msg = "Suspicious relay address range -- if you think this is a "
-             "mistake please set a valid email address in ContactInfo and "
-             "send an email to bad-relays at lists.torproject.org mentioning "
-             "your address(es) and fingerprint(s)?";
-    return FP_REJECT;
-  }
-  if (!authdir_policy_valid_address(addr, or_port)) {
-    log_fn(severity, LD_DIRSERV,
-           "Not marking '%s' valid because of address '%s'",
-               nickname, fmt_addr32(addr));
-    result |= FP_INVALID;
-  }
-
-  return result;
-}
-
-/** Clear the current fingerprint list. */
-void
-dirserv_free_fingerprint_list(void)
-{
-  if (!fingerprint_list)
-    return;
-
-  strmap_free(fingerprint_list->fp_by_name, tor_free_);
-  digestmap_free(fingerprint_list->status_by_digest, tor_free_);
-  tor_free(fingerprint_list);
-}
-
-/*
- *    Descriptor list
- */
-
-/** Return -1 if <b>ri</b> has a private or otherwise bad address,
- * unless we're configured to not care. Return 0 if all ok. */
-static int
-dirserv_router_has_valid_address(routerinfo_t *ri)
-{
-  tor_addr_t addr;
-  if (get_options()->DirAllowPrivateAddresses)
-    return 0; /* whatever it is, we're fine with it */
-  tor_addr_from_ipv4h(&addr, ri->addr);
-
-  if (tor_addr_is_internal(&addr, 0)) {
-    log_info(LD_DIRSERV,
-             "Router %s published internal IP address. Refusing.",
-             router_describe(ri));
-    return -1; /* it's a private IP, we should reject it */
-  }
-  return 0;
-}
-
-/** Check whether we, as a directory server, want to accept <b>ri</b>.  If so,
- * set its is_valid,running fields and return 0.  Otherwise, return -1.
- *
- * If the router is rejected, set *<b>msg</b> to an explanation of why.
- *
- * If <b>complain</b> then explain at log-level 'notice' why we refused
- * a descriptor; else explain at log-level 'info'.
- */
-int
-authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg,
-                               int complain, int *valid_out)
-{
-  /* Okay.  Now check whether the fingerprint is recognized. */
-  time_t now;
-  int severity = (complain && ri->contact_info) ? LOG_NOTICE : LOG_INFO;
-  uint32_t status = dirserv_router_get_status(ri, msg, severity);
-  tor_assert(msg);
-  if (status & FP_REJECT)
-    return -1; /* msg is already set. */
-
-  /* Is there too much clock skew? */
-  now = time(NULL);
-  if (ri->cache_info.published_on > now+ROUTER_ALLOW_SKEW) {
-    log_fn(severity, LD_DIRSERV, "Publication time for %s is too "
-           "far (%d minutes) in the future; possible clock skew. Not adding "
-           "(%s)",
-           router_describe(ri),
-           (int)((ri->cache_info.published_on-now)/60),
-           esc_router_info(ri));
-    *msg = "Rejected: Your clock is set too far in the future, or your "
-      "timezone is not correct.";
-    return -1;
-  }
-  if (ri->cache_info.published_on < now-ROUTER_MAX_AGE_TO_PUBLISH) {
-    log_fn(severity, LD_DIRSERV,
-           "Publication time for %s is too far "
-           "(%d minutes) in the past. Not adding (%s)",
-           router_describe(ri),
-           (int)((now-ri->cache_info.published_on)/60),
-           esc_router_info(ri));
-    *msg = "Rejected: Server is expired, or your clock is too far in the past,"
-      " or your timezone is not correct.";
-    return -1;
-  }
-  if (dirserv_router_has_valid_address(ri) < 0) {
-    log_fn(severity, LD_DIRSERV,
-           "Router %s has invalid address. Not adding (%s).",
-           router_describe(ri),
-           esc_router_info(ri));
-    *msg = "Rejected: Address is a private address.";
-    return -1;
-  }
-
-  *valid_out = ! (status & FP_INVALID);
-
-  return 0;
-}
-
-/** Update the relevant flags of <b>node</b> based on our opinion as a
- * directory authority in <b>authstatus</b>, as returned by
- * dirserv_router_get_status or equivalent.  */
-void
-dirserv_set_node_flags_from_authoritative_status(node_t *node,
-                                                 uint32_t authstatus)
-{
-  node->is_valid = (authstatus & FP_INVALID) ? 0 : 1;
-  node->is_bad_exit = (authstatus & FP_BADEXIT) ? 1 : 0;
-}
-
-/** True iff <b>a</b> is more severe than <b>b</b>. */
-static int
-WRA_MORE_SEVERE(was_router_added_t a, was_router_added_t b)
-{
-  return a < b;
-}
-
-/** As for dirserv_add_descriptor(), but accepts multiple documents, and
- * returns the most severe error that occurred for any one of them. */
-was_router_added_t
-dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose,
-                                 const char *source,
-                                 const char **msg)
-{
-  was_router_added_t r, r_tmp;
-  const char *msg_out;
-  smartlist_t *list;
-  const char *s;
-  int n_parsed = 0;
-  time_t now = time(NULL);
-  char annotation_buf[ROUTER_ANNOTATION_BUF_LEN];
-  char time_buf[ISO_TIME_LEN+1];
-  int general = purpose == ROUTER_PURPOSE_GENERAL;
-  tor_assert(msg);
-
-  r=ROUTER_ADDED_SUCCESSFULLY; /*Least severe return value. */
-
-  format_iso_time(time_buf, now);
-  if (tor_snprintf(annotation_buf, sizeof(annotation_buf),
-                   "@uploaded-at %s\n"
-                   "@source %s\n"
-                   "%s%s%s", time_buf, escaped(source),
-                   !general ? "@purpose " : "",
-                   !general ? router_purpose_to_string(purpose) : "",
-                   !general ? "\n" : "")<0) {
-    *msg = "Couldn't format annotations";
-    /* XXX Not cool: we return -1 below, but (was_router_added_t)-1 is
-     * ROUTER_BAD_EI, which isn't what's gone wrong here. :( */
-    return -1;
-  }
-
-  s = desc;
-  list = smartlist_new();
-  if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 0, 0,
-                                     annotation_buf, NULL)) {
-    SMARTLIST_FOREACH(list, routerinfo_t *, ri, {
-        msg_out = NULL;
-        tor_assert(ri->purpose == purpose);
-        r_tmp = dirserv_add_descriptor(ri, &msg_out, source);
-        if (WRA_MORE_SEVERE(r_tmp, r)) {
-          r = r_tmp;
-          *msg = msg_out;
-        }
-      });
-  }
-  n_parsed += smartlist_len(list);
-  smartlist_clear(list);
-
-  s = desc;
-  if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 1, 0,
-                                     NULL, NULL)) {
-    SMARTLIST_FOREACH(list, extrainfo_t *, ei, {
-        msg_out = NULL;
-
-        r_tmp = dirserv_add_extrainfo(ei, &msg_out);
-        if (WRA_MORE_SEVERE(r_tmp, r)) {
-          r = r_tmp;
-          *msg = msg_out;
-        }
-      });
-  }
-  n_parsed += smartlist_len(list);
-  smartlist_free(list);
-
-  if (! *msg) {
-    if (!n_parsed) {
-      *msg = "No descriptors found in your POST.";
-      if (WRA_WAS_ADDED(r))
-        r = ROUTER_IS_ALREADY_KNOWN;
-    } else {
-      *msg = "(no message)";
-    }
-  }
-
-  return r;
-}
-
-/** Examine the parsed server descriptor in <b>ri</b> and maybe insert it into
- * the list of server descriptors. Set *<b>msg</b> to a message that should be
- * passed back to the origin of this descriptor, or NULL if there is no such
- * message. Use <b>source</b> to produce better log messages.
- *
- * If <b>ri</b> is not added to the list of server descriptors, free it.
- * That means the caller must not access <b>ri</b> after this function
- * returns, since it might have been freed.
- *
- * Return the status of the operation.
- *
- * This function is only called when fresh descriptors are posted, not when
- * we re-load the cache.
- */
-was_router_added_t
-dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
-{
-  was_router_added_t r;
-  routerinfo_t *ri_old;
-  char *desc, *nickname;
-  const size_t desclen = ri->cache_info.signed_descriptor_len +
-      ri->cache_info.annotations_len;
-  const int key_pinning = get_options()->AuthDirPinKeys;
-  *msg = NULL;
-
-  /* If it's too big, refuse it now. Otherwise we'll cache it all over the
-   * network and it'll clog everything up. */
-  if (ri->cache_info.signed_descriptor_len > MAX_DESCRIPTOR_UPLOAD_SIZE) {
-    log_notice(LD_DIR, "Somebody attempted to publish a router descriptor '%s'"
-               " (source: %s) with size %d. Either this is an attack, or the "
-               "MAX_DESCRIPTOR_UPLOAD_SIZE (%d) constant is too low.",
-               ri->nickname, source, (int)ri->cache_info.signed_descriptor_len,
-               MAX_DESCRIPTOR_UPLOAD_SIZE);
-    *msg = "Router descriptor was too large.";
-    r = ROUTER_AUTHDIR_REJECTS;
-    goto fail;
-  }
-
-  /* Check whether this descriptor is semantically identical to the last one
-   * from this server.  (We do this here and not in router_add_to_routerlist
-   * because we want to be able to accept the newest router descriptor that
-   * another authority has, so we all converge on the same one.) */
-  ri_old = router_get_mutable_by_digest(ri->cache_info.identity_digest);
-  if (ri_old && ri_old->cache_info.published_on < ri->cache_info.published_on
-      && router_differences_are_cosmetic(ri_old, ri)
-      && !router_is_me(ri)) {
-    log_info(LD_DIRSERV,
-             "Not replacing descriptor from %s (source: %s); "
-             "differences are cosmetic.",
-             router_describe(ri), source);
-    *msg = "Not replacing router descriptor; no information has changed since "
-      "the last one with this identity.";
-    r = ROUTER_IS_ALREADY_KNOWN;
-    goto fail;
-  }
-
-  /* Do keypinning again ... this time, to add the pin if appropriate */
-  int keypin_status;
-  if (ri->cache_info.signing_key_cert) {
-    ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key;
-    /* First let's validate this pubkey before pinning it */
-    if (ed25519_validate_pubkey(pkey) < 0) {
-      log_warn(LD_DIRSERV, "Received bad key from %s (source %s)",
-               router_describe(ri), source);
-      routerinfo_free(ri);
-      return ROUTER_AUTHDIR_REJECTS;
-    }
-
-    /* Now pin it! */
-    keypin_status = keypin_check_and_add(
-      (const uint8_t*)ri->cache_info.identity_digest,
-      pkey->pubkey, ! key_pinning);
-  } else {
-    keypin_status = keypin_check_lone_rsa(
-      (const uint8_t*)ri->cache_info.identity_digest);
-#ifndef DISABLE_DISABLING_ED25519
-    if (keypin_status == KEYPIN_MISMATCH)
-      keypin_status = KEYPIN_NOT_FOUND;
-#endif
-  }
-  if (keypin_status == KEYPIN_MISMATCH && key_pinning) {
-    log_info(LD_DIRSERV, "Dropping descriptor from %s (source: %s) because "
-             "its key did not match an older RSA/Ed25519 keypair",
-             router_describe(ri), source);
-    *msg = "Looks like your keypair has changed? This authority previously "
-      "recorded a different RSA identity for this Ed25519 identity (or vice "
-      "versa.) Did you replace or copy some of your key files, but not "
-      "the others? You should either restore the expected keypair, or "
-      "delete your keys and restart Tor to start your relay with a new "
-      "identity.";
-    r = ROUTER_AUTHDIR_REJECTS;
-    goto fail;
-  }
-
-  /* Make a copy of desc, since router_add_to_routerlist might free
-   * ri and its associated signed_descriptor_t. */
-  desc = tor_strndup(ri->cache_info.signed_descriptor_body, desclen);
-  nickname = tor_strdup(ri->nickname);
-
-  /* Tell if we're about to need to launch a test if we add this. */
-  ri->needs_retest_if_added =
-    dirserv_should_launch_reachability_test(ri, ri_old);
-
-  r = router_add_to_routerlist(ri, msg, 0, 0);
-  if (!WRA_WAS_ADDED(r)) {
-    /* unless the routerinfo was fine, just out-of-date */
-    log_info(LD_DIRSERV,
-             "Did not add descriptor from '%s' (source: %s): %s.",
-             nickname, source, *msg ? *msg : "(no message)");
-  } else {
-    smartlist_t *changed;
-
-    changed = smartlist_new();
-    smartlist_add(changed, ri);
-    routerlist_descriptors_added(changed, 0);
-    smartlist_free(changed);
-    if (!*msg) {
-      *msg =  "Descriptor accepted";
-    }
-    log_info(LD_DIRSERV,
-             "Added descriptor from '%s' (source: %s): %s.",
-             nickname, source, *msg);
-  }
-  tor_free(desc);
-  tor_free(nickname);
-  return r;
- fail:
-  {
-    const char *desc_digest = ri->cache_info.signed_descriptor_digest;
-    download_status_t *dls =
-      router_get_dl_status_by_descriptor_digest(desc_digest);
-    if (dls) {
-      log_info(LD_GENERAL, "Marking router with descriptor %s as rejected, "
-               "and therefore undownloadable",
-               hex_str(desc_digest, DIGEST_LEN));
-      download_status_mark_impossible(dls);
-    }
-    routerinfo_free(ri);
-  }
-  return r;
-}
-
-/** As dirserv_add_descriptor, but for an extrainfo_t <b>ei</b>. */
-static was_router_added_t
-dirserv_add_extrainfo(extrainfo_t *ei, const char **msg)
-{
-  routerinfo_t *ri;
-  int r;
-  was_router_added_t rv;
-  tor_assert(msg);
-  *msg = NULL;
-
-  /* Needs to be mutable so routerinfo_incompatible_with_extrainfo
-   * can mess with some of the flags in ri->cache_info. */
-  ri = router_get_mutable_by_digest(ei->cache_info.identity_digest);
-  if (!ri) {
-    *msg = "No corresponding router descriptor for extra-info descriptor";
-    rv = ROUTER_BAD_EI;
-    goto fail;
-  }
-
-  /* If it's too big, refuse it now. Otherwise we'll cache it all over the
-   * network and it'll clog everything up. */
-  if (ei->cache_info.signed_descriptor_len > MAX_EXTRAINFO_UPLOAD_SIZE) {
-    log_notice(LD_DIR, "Somebody attempted to publish an extrainfo "
-               "with size %d. Either this is an attack, or the "
-               "MAX_EXTRAINFO_UPLOAD_SIZE (%d) constant is too low.",
-               (int)ei->cache_info.signed_descriptor_len,
-               MAX_EXTRAINFO_UPLOAD_SIZE);
-    *msg = "Extrainfo document was too large";
-    rv = ROUTER_BAD_EI;
-    goto fail;
-  }
-
-  if ((r = routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei,
-                                                  &ri->cache_info, msg))) {
-    if (r<0) {
-      extrainfo_free(ei);
-      return ROUTER_IS_ALREADY_KNOWN;
-    }
-    rv = ROUTER_BAD_EI;
-    goto fail;
-  }
-  router_add_extrainfo_to_routerlist(ei, msg, 0, 0);
-  return ROUTER_ADDED_SUCCESSFULLY;
- fail:
-  {
-    const char *d = ei->cache_info.signed_descriptor_digest;
-    signed_descriptor_t *sd = router_get_by_extrainfo_digest((char*)d);
-    if (sd) {
-      log_info(LD_GENERAL, "Marking extrainfo with descriptor %s as "
-               "rejected, and therefore undownloadable",
-               hex_str((char*)d,DIGEST_LEN));
-      download_status_mark_impossible(&sd->ei_dl_status);
-    }
-    extrainfo_free(ei);
-  }
-  return rv;
-}
-
-/** Remove all descriptors whose nicknames or fingerprints no longer
- * are allowed by our fingerprint list. (Descriptors that used to be
- * good can become bad when we reload the fingerprint list.)
- */
-static void
-directory_remove_invalid(void)
-{
-  routerlist_t *rl = router_get_routerlist();
-  smartlist_t *nodes = smartlist_new();
-  smartlist_add_all(nodes, nodelist_get_list());
-
-  SMARTLIST_FOREACH_BEGIN(nodes, node_t *, node) {
-    const char *msg = NULL;
-    const char *description;
-    routerinfo_t *ent = node->ri;
-    uint32_t r;
-    if (!ent)
-      continue;
-    r = dirserv_router_get_status(ent, &msg, LOG_INFO);
-    description = router_describe(ent);
-    if (r & FP_REJECT) {
-      log_info(LD_DIRSERV, "Router %s is now rejected: %s",
-               description, msg?msg:"");
-      routerlist_remove(rl, ent, 0, time(NULL));
-      continue;
-    }
-    if (bool_neq((r & FP_INVALID), !node->is_valid)) {
-      log_info(LD_DIRSERV, "Router '%s' is now %svalid.", description,
-               (r&FP_INVALID) ? "in" : "");
-      node->is_valid = (r&FP_INVALID)?0:1;
-    }
-    if (bool_neq((r & FP_BADEXIT), node->is_bad_exit)) {
-      log_info(LD_DIRSERV, "Router '%s' is now a %s exit", description,
-               (r & FP_BADEXIT) ? "bad" : "good");
-      node->is_bad_exit = (r&FP_BADEXIT) ? 1: 0;
-    }
-  } SMARTLIST_FOREACH_END(node);
-
-  routerlist_assert_ok(rl);
-  smartlist_free(nodes);
-}
-
-/**
- * Allocate and return a description of the status of the server <b>desc</b>,
- * for use in a v1-style router-status line.  The server is listed
- * as running iff <b>is_live</b> is true.
- *
- * This is deprecated: it's only used for controllers that want outputs in
- * the old format.
- */
-static char *
-list_single_server_status(const routerinfo_t *desc, int is_live)
-{
-  char buf[MAX_NICKNAME_LEN+HEX_DIGEST_LEN+4]; /* !nickname=$hexdigest\0 */
-  char *cp;
-  const node_t *node;
-
-  tor_assert(desc);
-
-  cp = buf;
-  if (!is_live) {
-    *cp++ = '!';
-  }
-  node = node_get_by_id(desc->cache_info.identity_digest);
-  if (node && node->is_valid) {
-    strlcpy(cp, desc->nickname, sizeof(buf)-(cp-buf));
-    cp += strlen(cp);
-    *cp++ = '=';
-  }
-  *cp++ = '$';
-  base16_encode(cp, HEX_DIGEST_LEN+1, desc->cache_info.identity_digest,
-                DIGEST_LEN);
-  return tor_strdup(buf);
-}
-
-/* DOCDOC running_long_enough_to_decide_unreachable */
-int
-running_long_enough_to_decide_unreachable(void)
-{
-  return time_of_process_start
-    + get_options()->TestingAuthDirTimeToLearnReachability < approx_time();
-}
-
-/** Each server needs to have passed a reachability test no more
- * than this number of seconds ago, or it is listed as down in
- * the directory. */
-#define REACHABLE_TIMEOUT (45*60)
-
-/** If we tested a router and found it reachable _at least this long_ after it
- * declared itself hibernating, it is probably done hibernating and we just
- * missed a descriptor from it. */
-#define HIBERNATION_PUBLICATION_SKEW (60*60)
-
-/** Treat a router as alive if
- *    - It's me, and I'm not hibernating.
- * or - We've found it reachable recently. */
-void
-dirserv_set_router_is_running(routerinfo_t *router, time_t now)
-{
-  /*XXXX This function is a mess.  Separate out the part that calculates
-    whether it's reachable and the part that tells rephist that the router was
-    unreachable.
-   */
-  int answer;
-  const or_options_t *options = get_options();
-  node_t *node = node_get_mutable_by_id(router->cache_info.identity_digest);
-  tor_assert(node);
-
-  if (router_is_me(router)) {
-    /* We always know if we are shutting down or hibernating ourselves. */
-    answer = ! we_are_hibernating();
-  } else if (router->is_hibernating &&
-             (router->cache_info.published_on +
-              HIBERNATION_PUBLICATION_SKEW) > node->last_reachable) {
-    /* A hibernating router is down unless we (somehow) had contact with it
-     * since it declared itself to be hibernating. */
-    answer = 0;
-  } else if (options->AssumeReachable) {
-    /* If AssumeReachable, everybody is up unless they say they are down! */
-    answer = 1;
-  } else {
-    /* Otherwise, a router counts as up if we found all announced OR
-       ports reachable in the last REACHABLE_TIMEOUT seconds.
-
-       XXX prop186 For now there's always one IPv4 and at most one
-       IPv6 OR port.
-
-       If we're not on IPv6, don't consider reachability of potential
-       IPv6 OR port since that'd kill all dual stack relays until a
-       majority of the dir auths have IPv6 connectivity. */
-    answer = (now < node->last_reachable + REACHABLE_TIMEOUT &&
-              (options->AuthDirHasIPv6Connectivity != 1 ||
-               tor_addr_is_null(&router->ipv6_addr) ||
-               now < node->last_reachable6 + REACHABLE_TIMEOUT));
-  }
-
-  if (!answer && running_long_enough_to_decide_unreachable()) {
-    /* Not considered reachable. tell rephist about that.
-
-       Because we launch a reachability test for each router every
-       REACHABILITY_TEST_CYCLE_PERIOD seconds, then the router has probably
-       been down since at least that time after we last successfully reached
-       it.
-
-       XXX ipv6
-     */
-    time_t when = now;
-    if (node->last_reachable &&
-        node->last_reachable + REACHABILITY_TEST_CYCLE_PERIOD < now)
-      when = node->last_reachable + REACHABILITY_TEST_CYCLE_PERIOD;
-    rep_hist_note_router_unreachable(router->cache_info.identity_digest, when);
-  }
-
-  node->is_running = answer;
-}
-
-/** Based on the routerinfo_ts in <b>routers</b>, allocate the
- * contents of a v1-style router-status line, and store it in
- * *<b>router_status_out</b>.  Return 0 on success, -1 on failure.
- *
- * If for_controller is true, include the routers with very old descriptors.
- *
- * This is deprecated: it's only used for controllers that want outputs in
- * the old format.
- */
-int
-list_server_status_v1(smartlist_t *routers, char **router_status_out,
-                      int for_controller)
-{
-  /* List of entries in a router-status style: An optional !, then an optional
-   * equals-suffixed nickname, then a dollar-prefixed hexdigest. */
-  smartlist_t *rs_entries;
-  time_t now = time(NULL);
-  time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
-  const or_options_t *options = get_options();
-  /* We include v2 dir auths here too, because they need to answer
-   * controllers. Eventually we'll deprecate this whole function;
-   * see also networkstatus_getinfo_by_purpose(). */
-  int authdir = authdir_mode_publishes_statuses(options);
-  tor_assert(router_status_out);
-
-  rs_entries = smartlist_new();
-
-  SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
-    const node_t *node = node_get_by_id(ri->cache_info.identity_digest);
-    tor_assert(node);
-    if (authdir) {
-      /* Update router status in routerinfo_t. */
-      dirserv_set_router_is_running(ri, now);
-    }
-    if (for_controller) {
-      char name_buf[MAX_VERBOSE_NICKNAME_LEN+2];
-      char *cp = name_buf;
-      if (!node->is_running)
-        *cp++ = '!';
-      router_get_verbose_nickname(cp, ri);
-      smartlist_add_strdup(rs_entries, name_buf);
-    } else if (ri->cache_info.published_on >= cutoff) {
-      smartlist_add(rs_entries, list_single_server_status(ri,
-                                                          node->is_running));
-    }
-  } SMARTLIST_FOREACH_END(ri);
-
-  *router_status_out = smartlist_join_strings(rs_entries, " ", 0, NULL);
-
-  SMARTLIST_FOREACH(rs_entries, char *, cp, tor_free(cp));
-  smartlist_free(rs_entries);
-
-  return 0;
-}
-
-/** Return 1 if <b>ri</b>'s descriptor is "active" -- running, valid,
- * not hibernating, having observed bw greater 0, and not too old. Else
- * return 0.
- */
-static int
-router_is_active(const routerinfo_t *ri, const node_t *node, time_t now)
-{
-  time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
-  if (ri->cache_info.published_on < cutoff) {
-    return 0;
-  }
-  if (!node->is_running || !node->is_valid || ri->is_hibernating) {
-    return 0;
-  }
-  /* Only require bandwidth capacity in non-test networks, or
-   * if TestingTorNetwork, and TestingMinExitFlagThreshold is non-zero */
-  if (!ri->bandwidthcapacity) {
-    if (get_options()->TestingTorNetwork) {
-      if (get_options()->TestingMinExitFlagThreshold > 0) {
-        /* If we're in a TestingTorNetwork, and TestingMinExitFlagThreshold is,
-         * then require bandwidthcapacity */
-        return 0;
-      }
-    } else {
-      /* If we're not in a TestingTorNetwork, then require bandwidthcapacity */
-      return 0;
-    }
-  }
-  return 1;
-}
-
 /********************************************************************/
 
 /* A set of functions to answer questions about how we'd like to behave
@@ -1132,1591 +91,176 @@ directory_fetches_from_authorities(const or_options_t *options)
     return 0;
   if (!server_mode(options) || !advertised_server_mode())
     return 0;
-  me = router_get_my_routerinfo();
-  if (!me || (!me->supports_tunnelled_dir_requests && !refuseunknown))
-    return 0; /* if we don't service directory requests, return 0 too */
-  return 1;
-}
-
-/** Return 1 if we should fetch new networkstatuses, descriptors, etc
- * on the "mirror" schedule rather than the "client" schedule.
- */
-int
-directory_fetches_dir_info_early(const or_options_t *options)
-{
-  return directory_fetches_from_authorities(options);
-}
-
-/** Return 1 if we should fetch new networkstatuses, descriptors, etc
- * on a very passive schedule -- waiting long enough for ordinary clients
- * to probably have the info we want. These would include bridge users,
- * and maybe others in the future e.g. if a Tor client uses another Tor
- * client as a directory guard.
- */
-int
-directory_fetches_dir_info_later(const or_options_t *options)
-{
-  return options->UseBridges != 0;
-}
-
-/** Return true iff we want to serve certificates for authorities
- * that we don't acknowledge as authorities ourself.
- * Use we_want_to_fetch_unknown_auth_certs to check if we want to fetch
- * and keep these certificates.
- */
-int
-directory_caches_unknown_auth_certs(const or_options_t *options)
-{
-  return dir_server_mode(options) || options->BridgeRelay;
-}
-
-/** Return 1 if we want to fetch and serve descriptors, networkstatuses, etc
- * Else return 0.
- * Check options->DirPort_set and directory_permits_begindir_requests()
- * to see if we are willing to serve these directory documents to others via
- * the DirPort and begindir-over-ORPort, respectively.
- *
- * To check if we should fetch documents, use we_want_to_fetch_flavor and
- * we_want_to_fetch_unknown_auth_certs instead of this function.
- */
-int
-directory_caches_dir_info(const or_options_t *options)
-{
-  if (options->BridgeRelay || dir_server_mode(options))
-    return 1;
-  if (!server_mode(options) || !advertised_server_mode())
-    return 0;
-  /* We need an up-to-date view of network info if we're going to try to
-   * block exit attempts from unknown relays. */
-  return ! router_my_exit_policy_is_reject_star() &&
-    should_refuse_unknown_exits(options);
-}
-
-/** Return 1 if we want to allow remote clients to ask us directory
- * requests via the "begin_dir" interface, which doesn't require
- * having any separate port open. */
-int
-directory_permits_begindir_requests(const or_options_t *options)
-{
-  return options->BridgeRelay != 0 || dir_server_mode(options);
-}
-
-/** Return 1 if we have no need to fetch new descriptors. This generally
- * happens when we're not a dir cache and we haven't built any circuits
- * lately.
- */
-int
-directory_too_idle_to_fetch_descriptors(const or_options_t *options,
-                                        time_t now)
-{
-  return !directory_caches_dir_info(options) &&
-         !options->FetchUselessDescriptors &&
-         rep_hist_circbuilding_dormant(now);
-}
-
-/********************************************************************/
-
-/** Map from flavor name to the cached_dir_t for the v3 consensuses that we're
- * currently serving. */
-static strmap_t *cached_consensuses = NULL;
-
-/** Decrement the reference count on <b>d</b>, and free it if it no longer has
- * any references. */
-void
-cached_dir_decref(cached_dir_t *d)
-{
-  if (!d || --d->refcnt > 0)
-    return;
-  clear_cached_dir(d);
-  tor_free(d);
-}
-
-/** Allocate and return a new cached_dir_t containing the string <b>s</b>,
- * published at <b>published</b>. */
-cached_dir_t *
-new_cached_dir(char *s, time_t published)
-{
-  cached_dir_t *d = tor_malloc_zero(sizeof(cached_dir_t));
-  d->refcnt = 1;
-  d->dir = s;
-  d->dir_len = strlen(s);
-  d->published = published;
-  if (tor_compress(&(d->dir_compressed), &(d->dir_compressed_len),
-                   d->dir, d->dir_len, ZLIB_METHOD)) {
-    log_warn(LD_BUG, "Error compressing directory");
-  }
-  return d;
-}
-
-/** Remove all storage held in <b>d</b>, but do not free <b>d</b> itself. */
-static void
-clear_cached_dir(cached_dir_t *d)
-{
-  tor_free(d->dir);
-  tor_free(d->dir_compressed);
-  memset(d, 0, sizeof(cached_dir_t));
-}
-
-/** Free all storage held by the cached_dir_t in <b>d</b>. */
-static void
-free_cached_dir_(void *_d)
-{
-  cached_dir_t *d;
-  if (!_d)
-    return;
-
-  d = (cached_dir_t *)_d;
-  cached_dir_decref(d);
-}
-
-/** Replace the v3 consensus networkstatus of type <b>flavor_name</b> that
- * we're serving with <b>networkstatus</b>, published at <b>published</b>.  No
- * validation is performed. */
-void
-dirserv_set_cached_consensus_networkstatus(const char *networkstatus,
-                                           const char *flavor_name,
-                                           const common_digests_t *digests,
-                                           const uint8_t *sha3_as_signed,
-                                           time_t published)
-{
-  cached_dir_t *new_networkstatus;
-  cached_dir_t *old_networkstatus;
-  if (!cached_consensuses)
-    cached_consensuses = strmap_new();
-
-  new_networkstatus = new_cached_dir(tor_strdup(networkstatus), published);
-  memcpy(&new_networkstatus->digests, digests, sizeof(common_digests_t));
-  memcpy(&new_networkstatus->digest_sha3_as_signed, sha3_as_signed,
-         DIGEST256_LEN);
-  old_networkstatus = strmap_set(cached_consensuses, flavor_name,
-                                 new_networkstatus);
-  if (old_networkstatus)
-    cached_dir_decref(old_networkstatus);
-}
-
-/** Return the latest downloaded consensus networkstatus in encoded, signed,
- * optionally compressed format, suitable for sending to clients. */
-cached_dir_t *
-dirserv_get_consensus(const char *flavor_name)
-{
-  if (!cached_consensuses)
-    return NULL;
-  return strmap_get(cached_consensuses, flavor_name);
-}
-
-/** If a router's uptime is at least this value, then it is always
- * considered stable, regardless of the rest of the network. This
- * way we resist attacks where an attacker doubles the size of the
- * network using allegedly high-uptime nodes, displacing all the
- * current guards. */
-#define UPTIME_TO_GUARANTEE_STABLE (3600*24*30)
-/** If a router's MTBF is at least this value, then it is always stable.
- * See above.  (Corresponds to about 7 days for current decay rates.) */
-#define MTBF_TO_GUARANTEE_STABLE (60*60*24*5)
-/** Similarly, every node with at least this much weighted time known can be
- * considered familiar enough to be a guard.  Corresponds to about 20 days for
- * current decay rates.
- */
-#define TIME_KNOWN_TO_GUARANTEE_FAMILIAR (8*24*60*60)
-/** Similarly, every node with sufficient WFU is around enough to be a guard.
- */
-#define WFU_TO_GUARANTEE_GUARD (0.98)
-
-/* Thresholds for server performance: set by
- * dirserv_compute_performance_thresholds, and used by
- * generate_v2_networkstatus */
-
-/** Any router with an uptime of at least this value is stable. */
-static uint32_t stable_uptime = 0; /* start at a safe value */
-/** Any router with an mtbf of at least this value is stable. */
-static double stable_mtbf = 0.0;
-/** If true, we have measured enough mtbf info to look at stable_mtbf rather
- * than stable_uptime. */
-static int enough_mtbf_info = 0;
-/** Any router with a weighted fractional uptime of at least this much might
- * be good as a guard. */
-static double guard_wfu = 0.0;
-/** Don't call a router a guard unless we've known about it for at least this
- * many seconds. */
-static long guard_tk = 0;
-/** Any router with a bandwidth at least this high is "Fast" */
-static uint32_t fast_bandwidth_kb = 0;
-/** If exits can be guards, then all guards must have a bandwidth this
- * high. */
-static uint32_t guard_bandwidth_including_exits_kb = 0;
-/** If exits can't be guards, then all guards must have a bandwidth this
- * high. */
-static uint32_t guard_bandwidth_excluding_exits_kb = 0;
-
-/** Helper: estimate the uptime of a router given its stated uptime and the
- * amount of time since it last stated its stated uptime. */
-static inline long
-real_uptime(const routerinfo_t *router, time_t now)
-{
-  if (now < router->cache_info.published_on)
-    return router->uptime;
-  else
-    return router->uptime + (now - router->cache_info.published_on);
-}
-
-/** Return 1 if <b>router</b> is not suitable for these parameters, else 0.
- * If <b>need_uptime</b> is non-zero, we require a minimum uptime.
- * If <b>need_capacity</b> is non-zero, we require a minimum advertised
- * bandwidth.
- */
-static int
-dirserv_thinks_router_is_unreliable(time_t now,
-                                    routerinfo_t *router,
-                                    int need_uptime, int need_capacity)
-{
-  if (need_uptime) {
-    if (!enough_mtbf_info) {
-      /* XXXX We should change the rule from
-       * "use uptime if we don't have mtbf data" to "don't advertise Stable on
-       * v3 if we don't have enough mtbf data."  Or maybe not, since if we ever
-       * hit a point where we need to reset a lot of authorities at once,
-       * none of them would be in a position to declare Stable.
-       */
-      long uptime = real_uptime(router, now);
-      if ((unsigned)uptime < stable_uptime &&
-          (unsigned)uptime < UPTIME_TO_GUARANTEE_STABLE)
-        return 1;
-    } else {
-      double mtbf =
-        rep_hist_get_stability(router->cache_info.identity_digest, now);
-      if (mtbf < stable_mtbf &&
-          mtbf < MTBF_TO_GUARANTEE_STABLE)
-        return 1;
-    }
-  }
-  if (need_capacity) {
-    uint32_t bw_kb = dirserv_get_credible_bandwidth_kb(router);
-    if (bw_kb < fast_bandwidth_kb)
-      return 1;
-  }
-  return 0;
-}
-
-/** 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,
- * it's a directory cache, it has the Stable and Fast flags, and it's currently
- * considered Running.
- *
- * This function needs to be called after router-\>is_running has
- * been set.
- */
-static int
-dirserv_thinks_router_is_hs_dir(const routerinfo_t *router,
-                                const node_t *node, time_t now)
-{
-
-  long uptime;
-
-  /* If we haven't been running for at least
-   * get_options()->MinUptimeHidServDirectoryV2 seconds, we can't
-   * have accurate data telling us a relay has been up for at least
-   * that long. We also want to allow a bit of slack: Reachability
-   * tests aren't instant. If we haven't been running long enough,
-   * trust the relay. */
-
-  if (get_uptime() >
-      get_options()->MinUptimeHidServDirectoryV2 * 1.1)
-    uptime = MIN(rep_hist_get_uptime(router->cache_info.identity_digest, now),
-                 real_uptime(router, now));
-  else
-    uptime = real_uptime(router, now);
-
-  return (router->wants_to_be_hs_dir &&
-          router->supports_tunnelled_dir_requests &&
-          node->is_stable && node->is_fast &&
-          uptime >= get_options()->MinUptimeHidServDirectoryV2 &&
-          router_is_active(router, node, now));
-}
-
-/** Don't consider routers with less bandwidth than this when computing
- * thresholds. */
-#define ABSOLUTE_MIN_BW_VALUE_TO_CONSIDER_KB 4
-
-/** Helper for dirserv_compute_performance_thresholds(): Decide whether to
- * include a router in our calculations, and return true iff we should; the
- * require_mbw parameter is passed in by
- * dirserv_compute_performance_thresholds() and controls whether we ever
- * count routers with only advertised bandwidths */
-static int
-router_counts_toward_thresholds(const node_t *node, time_t now,
-                                const digestmap_t *omit_as_sybil,
-                                int require_mbw)
-{
-  /* Have measured bw? */
-  int have_mbw =
-    dirserv_has_measured_bw(node->identity);
-  uint64_t min_bw_kb = ABSOLUTE_MIN_BW_VALUE_TO_CONSIDER_KB;
-  const or_options_t *options = get_options();
-
-  if (options->TestingTorNetwork) {
-    min_bw_kb = (int64_t)options->TestingMinExitFlagThreshold / 1000;
-  }
-
-  return node->ri && router_is_active(node->ri, node, now) &&
-    !digestmap_get(omit_as_sybil, node->identity) &&
-    (dirserv_get_credible_bandwidth_kb(node->ri) >= min_bw_kb) &&
-    (have_mbw || !require_mbw);
-}
-
-/** Look through the routerlist, and using the measured bandwidth cache count
- * how many measured bandwidths we know.  This is used to decide whether we
- * ever trust advertised bandwidths for purposes of assigning flags. */
-void
-dirserv_count_measured_bws(const smartlist_t *routers)
-{
-  /* Initialize this first */
-  routers_with_measured_bw = 0;
-
-  /* Iterate over the routerlist and count measured bandwidths */
-  SMARTLIST_FOREACH_BEGIN(routers, const routerinfo_t *, ri) {
-    /* Check if we know a measured bandwidth for this one */
-    if (dirserv_has_measured_bw(ri->cache_info.identity_digest)) {
-      ++routers_with_measured_bw;
-    }
-  } SMARTLIST_FOREACH_END(ri);
-}
-
-/** Look through the routerlist, the Mean Time Between Failure history, and
- * the Weighted Fractional Uptime history, and use them to set thresholds for
- * the Stable, Fast, and Guard flags.  Update the fields stable_uptime,
- * stable_mtbf, enough_mtbf_info, guard_wfu, guard_tk, fast_bandwidth,
- * guard_bandwidth_including_exits, and guard_bandwidth_excluding_exits.
- *
- * Also, set the is_exit flag of each router appropriately. */
-void
-dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil)
-{
-  int n_active, n_active_nonexit, n_familiar;
-  uint32_t *uptimes, *bandwidths_kb, *bandwidths_excluding_exits_kb;
-  long *tks;
-  double *mtbfs, *wfus;
-  smartlist_t *nodelist;
-  time_t now = time(NULL);
-  const or_options_t *options = get_options();
-
-  /* Require mbw? */
-  int require_mbw =
-    (routers_with_measured_bw >
-     options->MinMeasuredBWsForAuthToIgnoreAdvertised) ? 1 : 0;
-
-  /* initialize these all here, in case there are no routers */
-  stable_uptime = 0;
-  stable_mtbf = 0;
-  fast_bandwidth_kb = 0;
-  guard_bandwidth_including_exits_kb = 0;
-  guard_bandwidth_excluding_exits_kb = 0;
-  guard_tk = 0;
-  guard_wfu = 0;
-
-  nodelist_assert_ok();
-  nodelist = nodelist_get_list();
-
-  /* Initialize arrays that will hold values for each router.  We'll
-   * sort them and use that to compute thresholds. */
-  n_active = n_active_nonexit = 0;
-  /* Uptime for every active router. */
-  uptimes = tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
-  /* Bandwidth for every active router. */
-  bandwidths_kb = tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
-  /* Bandwidth for every active non-exit router. */
-  bandwidths_excluding_exits_kb =
-    tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
-  /* Weighted mean time between failure for each active router. */
-  mtbfs = tor_calloc(smartlist_len(nodelist), sizeof(double));
-  /* Time-known for each active router. */
-  tks = tor_calloc(smartlist_len(nodelist), sizeof(long));
-  /* Weighted fractional uptime for each active router. */
-  wfus = tor_calloc(smartlist_len(nodelist), sizeof(double));
-
-  /* Now, fill in the arrays. */
-  SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) {
-    if (options->BridgeAuthoritativeDir &&
-        node->ri &&
-        node->ri->purpose != ROUTER_PURPOSE_BRIDGE)
-      continue;
-
-    routerinfo_t *ri = node->ri;
-    if (ri) {
-      node->is_exit = (!router_exit_policy_rejects_all(ri) &&
-                       exit_policy_is_general_exit(ri->exit_policy));
-    }
-
-    if (router_counts_toward_thresholds(node, now, omit_as_sybil,
-                                        require_mbw)) {
-      const char *id = node->identity;
-      uint32_t bw_kb;
-
-      /* resolve spurious clang shallow analysis null pointer errors */
-      tor_assert(ri);
-
-      uptimes[n_active] = (uint32_t)real_uptime(ri, now);
-      mtbfs[n_active] = rep_hist_get_stability(id, now);
-      tks  [n_active] = rep_hist_get_weighted_time_known(id, now);
-      bandwidths_kb[n_active] = bw_kb = dirserv_get_credible_bandwidth_kb(ri);
-      if (!node->is_exit || node->is_bad_exit) {
-        bandwidths_excluding_exits_kb[n_active_nonexit] = bw_kb;
-        ++n_active_nonexit;
-      }
-      ++n_active;
-    }
-  } SMARTLIST_FOREACH_END(node);
-
-  /* Now, compute thresholds. */
-  if (n_active) {
-    /* The median uptime is stable. */
-    stable_uptime = median_uint32(uptimes, n_active);
-    /* The median mtbf is stable, if we have enough mtbf info */
-    stable_mtbf = median_double(mtbfs, n_active);
-    /* The 12.5th percentile bandwidth is fast. */
-    fast_bandwidth_kb = find_nth_uint32(bandwidths_kb, n_active, n_active/8);
-    /* (Now bandwidths is sorted.) */
-    if (fast_bandwidth_kb < RELAY_REQUIRED_MIN_BANDWIDTH/(2 * 1000))
-      fast_bandwidth_kb = bandwidths_kb[n_active/4];
-    guard_bandwidth_including_exits_kb =
-      third_quartile_uint32(bandwidths_kb, n_active);
-    guard_tk = find_nth_long(tks, n_active, n_active/8);
-  }
-
-  if (guard_tk > TIME_KNOWN_TO_GUARANTEE_FAMILIAR)
-    guard_tk = TIME_KNOWN_TO_GUARANTEE_FAMILIAR;
-
-  {
-    /* We can vote on a parameter for the minimum and maximum. */
-#define ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG 4
-    int32_t min_fast_kb, max_fast_kb, min_fast, max_fast;
-    min_fast = networkstatus_get_param(NULL, "FastFlagMinThreshold",
-      ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG,
-      ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG,
-      INT32_MAX);
-    if (options->TestingTorNetwork) {
-      min_fast = (int32_t)options->TestingMinFastFlagThreshold;
-    }
-    max_fast = networkstatus_get_param(NULL, "FastFlagMaxThreshold",
-                                       INT32_MAX, min_fast, INT32_MAX);
-    min_fast_kb = min_fast / 1000;
-    max_fast_kb = max_fast / 1000;
-
-    if (fast_bandwidth_kb < (uint32_t)min_fast_kb)
-      fast_bandwidth_kb = min_fast_kb;
-    if (fast_bandwidth_kb > (uint32_t)max_fast_kb)
-      fast_bandwidth_kb = max_fast_kb;
-  }
-  /* Protect sufficiently fast nodes from being pushed out of the set
-   * of Fast nodes. */
-  if (options->AuthDirFastGuarantee &&
-      fast_bandwidth_kb > options->AuthDirFastGuarantee/1000)
-    fast_bandwidth_kb = (uint32_t)options->AuthDirFastGuarantee/1000;
-
-  /* Now that we have a time-known that 7/8 routers are known longer than,
-   * fill wfus with the wfu of every such "familiar" router. */
-  n_familiar = 0;
-
-  SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) {
-      if (router_counts_toward_thresholds(node, now,
-                                          omit_as_sybil, require_mbw)) {
-        routerinfo_t *ri = node->ri;
-        const char *id = ri->cache_info.identity_digest;
-        long tk = rep_hist_get_weighted_time_known(id, now);
-        if (tk < guard_tk)
-          continue;
-        wfus[n_familiar++] = rep_hist_get_weighted_fractional_uptime(id, now);
-      }
-  } SMARTLIST_FOREACH_END(node);
-  if (n_familiar)
-    guard_wfu = median_double(wfus, n_familiar);
-  if (guard_wfu > WFU_TO_GUARANTEE_GUARD)
-    guard_wfu = WFU_TO_GUARANTEE_GUARD;
-
-  enough_mtbf_info = rep_hist_have_measured_enough_stability();
-
-  if (n_active_nonexit) {
-    guard_bandwidth_excluding_exits_kb =
-      find_nth_uint32(bandwidths_excluding_exits_kb,
-                      n_active_nonexit, n_active_nonexit*3/4);
-  }
-
-  log_info(LD_DIRSERV,
-      "Cutoffs: For Stable, %lu sec uptime, %lu sec MTBF. "
-      "For Fast: %lu kilobytes/sec. "
-      "For Guard: WFU %.03f%%, time-known %lu sec, "
-      "and bandwidth %lu or %lu kilobytes/sec. "
-      "We%s have enough stability data.",
-      (unsigned long)stable_uptime,
-      (unsigned long)stable_mtbf,
-      (unsigned long)fast_bandwidth_kb,
-      guard_wfu*100,
-      (unsigned long)guard_tk,
-      (unsigned long)guard_bandwidth_including_exits_kb,
-      (unsigned long)guard_bandwidth_excluding_exits_kb,
-      enough_mtbf_info ? "" : " don't");
-
-  tor_free(uptimes);
-  tor_free(mtbfs);
-  tor_free(bandwidths_kb);
-  tor_free(bandwidths_excluding_exits_kb);
-  tor_free(tks);
-  tor_free(wfus);
-}
-
-/* Use dirserv_compute_performance_thresholds() to compute the thresholds
- * for the status flags, specifically for bridges.
- *
- * This is only called by a Bridge Authority from
- * networkstatus_getinfo_by_purpose().
- */
-void
-dirserv_compute_bridge_flag_thresholds(void)
-{
-  digestmap_t *omit_as_sybil = digestmap_new();
-  dirserv_compute_performance_thresholds(omit_as_sybil);
-  digestmap_free(omit_as_sybil, NULL);
-}
-
-/** Measured bandwidth cache entry */
-typedef struct mbw_cache_entry_s {
-  long mbw_kb;
-  time_t as_of;
-} mbw_cache_entry_t;
-
-/** Measured bandwidth cache - keys are identity_digests, values are
- * mbw_cache_entry_t *. */
-static digestmap_t *mbw_cache = NULL;
-
-/** Store a measured bandwidth cache entry when reading the measured
- * bandwidths file. */
-STATIC void
-dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line,
-                          time_t as_of)
-{
-  mbw_cache_entry_t *e = NULL;
-
-  tor_assert(parsed_line);
-
-  /* Allocate a cache if we need */
-  if (!mbw_cache) mbw_cache = digestmap_new();
-
-  /* Check if we have an existing entry */
-  e = digestmap_get(mbw_cache, parsed_line->node_id);
-  /* If we do, we can re-use it */
-  if (e) {
-    /* Check that we really are newer, and update */
-    if (as_of > e->as_of) {
-      e->mbw_kb = parsed_line->bw_kb;
-      e->as_of = as_of;
-    }
-  } else {
-    /* We'll have to insert a new entry */
-    e = tor_malloc(sizeof(*e));
-    e->mbw_kb = parsed_line->bw_kb;
-    e->as_of = as_of;
-    digestmap_set(mbw_cache, parsed_line->node_id, e);
-  }
-}
-
-/** Clear and free the measured bandwidth cache */
-void
-dirserv_clear_measured_bw_cache(void)
-{
-  if (mbw_cache) {
-    /* Free the map and all entries */
-    digestmap_free(mbw_cache, tor_free_);
-    mbw_cache = NULL;
-  }
-}
-
-/** Scan the measured bandwidth cache and remove expired entries */
-STATIC void
-dirserv_expire_measured_bw_cache(time_t now)
-{
-
-  if (mbw_cache) {
-    /* Iterate through the cache and check each entry */
-    DIGESTMAP_FOREACH_MODIFY(mbw_cache, k, mbw_cache_entry_t *, e) {
-      if (now > e->as_of + MAX_MEASUREMENT_AGE) {
-        tor_free(e);
-        MAP_DEL_CURRENT(k);
-      }
-    } DIGESTMAP_FOREACH_END;
-
-    /* Check if we cleared the whole thing and free if so */
-    if (digestmap_size(mbw_cache) == 0) {
-      digestmap_free(mbw_cache, tor_free_);
-      mbw_cache = 0;
-    }
-  }
-}
-
-/** Query the cache by identity digest, return value indicates whether
- * we found it. The bw_out and as_of_out pointers receive the cached
- * bandwidth value and the time it was cached if not NULL. */
-int
-dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out,
-                                   time_t *as_of_out)
-{
-  mbw_cache_entry_t *v = NULL;
-  int rv = 0;
-
-  if (mbw_cache && node_id) {
-    v = digestmap_get(mbw_cache, node_id);
-    if (v) {
-      /* Found something */
-      rv = 1;
-      if (bw_kb_out) *bw_kb_out = v->mbw_kb;
-      if (as_of_out) *as_of_out = v->as_of;
-    }
-  }
-
-  return rv;
-}
-
-/** Predicate wrapper for dirserv_query_measured_bw_cache() */
-int
-dirserv_has_measured_bw(const char *node_id)
-{
-  return dirserv_query_measured_bw_cache_kb(node_id, NULL, NULL);
-}
-
-/** Get the current size of the measured bandwidth cache */
-int
-dirserv_get_measured_bw_cache_size(void)
-{
-  if (mbw_cache) return digestmap_size(mbw_cache);
-  else return 0;
-}
-
-/** Return the bandwidth we believe for assigning flags; prefer measured
- * over advertised, and if we have above a threshold quantity of measured
- * bandwidths, we don't want to ever give flags to unmeasured routers, so
- * return 0. */
-static uint32_t
-dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri)
-{
-  int threshold;
-  uint32_t bw_kb = 0;
-  long mbw_kb;
-
-  tor_assert(ri);
-  /* Check if we have a measured bandwidth, and check the threshold if not */
-  if (!(dirserv_query_measured_bw_cache_kb(ri->cache_info.identity_digest,
-                                       &mbw_kb, NULL))) {
-    threshold = get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised;
-    if (routers_with_measured_bw > threshold) {
-      /* Return zero for unmeasured bandwidth if we are above threshold */
-      bw_kb = 0;
-    } else {
-      /* Return an advertised bandwidth otherwise */
-      bw_kb = router_get_advertised_bandwidth_capped(ri) / 1000;
-    }
-  } else {
-    /* We have the measured bandwidth in mbw */
-    bw_kb = (uint32_t)mbw_kb;
-  }
-
-  return bw_kb;
-}
-
-/** Give a statement of our current performance thresholds for inclusion
- * in a vote document. */
-char *
-dirserv_get_flag_thresholds_line(void)
-{
-  char *result=NULL;
-  const int measured_threshold =
-    get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised;
-  const int enough_measured_bw = routers_with_measured_bw > measured_threshold;
-
-  tor_asprintf(&result,
-      "stable-uptime=%lu stable-mtbf=%lu "
-      "fast-speed=%lu "
-      "guard-wfu=%.03f%% guard-tk=%lu "
-      "guard-bw-inc-exits=%lu guard-bw-exc-exits=%lu "
-      "enough-mtbf=%d ignoring-advertised-bws=%d",
-      (unsigned long)stable_uptime,
-      (unsigned long)stable_mtbf,
-      (unsigned long)fast_bandwidth_kb*1000,
-      guard_wfu*100,
-      (unsigned long)guard_tk,
-      (unsigned long)guard_bandwidth_including_exits_kb*1000,
-      (unsigned long)guard_bandwidth_excluding_exits_kb*1000,
-      enough_mtbf_info ? 1 : 0,
-      enough_measured_bw ? 1 : 0);
-
-  return result;
-}
-
-/** Helper: write the router-status information in <b>rs</b> into a newly
- * allocated character buffer.  Use the same format as in network-status
- * documents.  If <b>version</b> is non-NULL, add a "v" line for the platform.
- *
- * consensus_method is the current consensus method when format is
- * NS_V3_CONSENSUS or NS_V3_CONSENSUS_MICRODESC. It is ignored for other
- * formats: pass ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD.
- *
- * Return 0 on success, -1 on failure.
- *
- * The format argument has one of the following values:
- *   NS_V2 - Output an entry suitable for a V2 NS opinion document
- *   NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry
- *        for consensus_method.
- *   NS_V3_CONSENSUS_MICRODESC - Output the first portion of a V3 microdesc
- *        consensus entry for consensus_method.
- *   NS_V3_VOTE - Output a complete V3 NS vote. If <b>vrs</b> is present,
- *        it contains additional information for the vote.
- *   NS_CONTROL_PORT - Output a NS document for the control port.
- */
-char *
-routerstatus_format_entry(const routerstatus_t *rs, const char *version,
-                          const char *protocols,
-                          routerstatus_format_type_t format,
-                          int consensus_method,
-                          const vote_routerstatus_t *vrs)
-{
-  char *summary;
-  char *result = NULL;
-
-  char published[ISO_TIME_LEN+1];
-  char identity64[BASE64_DIGEST_LEN+1];
-  char digest64[BASE64_DIGEST_LEN+1];
-  smartlist_t *chunks = smartlist_new();
-
-  format_iso_time(published, rs->published_on);
-  digest_to_base64(identity64, rs->identity_digest);
-  digest_to_base64(digest64, rs->descriptor_digest);
-
-  smartlist_add_asprintf(chunks,
-                   "r %s %s %s%s%s %s %d %d\n",
-                   rs->nickname,
-                   identity64,
-                   (format==NS_V3_CONSENSUS_MICRODESC)?"":digest64,
-                   (format==NS_V3_CONSENSUS_MICRODESC)?"":" ",
-                   published,
-                   fmt_addr32(rs->addr),
-                   (int)rs->or_port,
-                   (int)rs->dir_port);
-
-  /* TODO: Maybe we want to pass in what we need to build the rest of
-   * this here, instead of in the caller. Then we could use the
-   * networkstatus_type_t values, with an additional control port value
-   * added -MP */
-
-  /* V3 microdesc consensuses only have "a" lines in later consensus methods
-   */
-  if (format == NS_V3_CONSENSUS_MICRODESC &&
-      consensus_method < MIN_METHOD_FOR_A_LINES_IN_MICRODESC_CONSENSUS)
-    goto done;
-
-  /* Possible "a" line. At most one for now. */
-  if (!tor_addr_is_null(&rs->ipv6_addr)) {
-    smartlist_add_asprintf(chunks, "a %s\n",
-                           fmt_addrport(&rs->ipv6_addr, rs->ipv6_orport));
-  }
-
-  if (format == NS_V3_CONSENSUS || format == NS_V3_CONSENSUS_MICRODESC)
-    goto done;
-
-  smartlist_add_asprintf(chunks,
-                   "s%s%s%s%s%s%s%s%s%s%s\n",
-                  /* These must stay in alphabetical order. */
-                   rs->is_authority?" Authority":"",
-                   rs->is_bad_exit?" BadExit":"",
-                   rs->is_exit?" Exit":"",
-                   rs->is_fast?" Fast":"",
-                   rs->is_possible_guard?" Guard":"",
-                   rs->is_hs_dir?" HSDir":"",
-                   rs->is_flagged_running?" Running":"",
-                   rs->is_stable?" Stable":"",
-                   rs->is_v2_dir?" V2Dir":"",
-                   rs->is_valid?" Valid":"");
-
-  /* length of "opt v \n" */
-#define V_LINE_OVERHEAD 7
-  if (version && strlen(version) < MAX_V_LINE_LEN - V_LINE_OVERHEAD) {
-    smartlist_add_asprintf(chunks, "v %s\n", version);
-  }
-  if (protocols) {
-    smartlist_add_asprintf(chunks, "pr %s\n", protocols);
-  }
-
-  if (format != NS_V2) {
-    const routerinfo_t* desc = router_get_by_id_digest(rs->identity_digest);
-    uint32_t bw_kb;
-
-    if (format != NS_CONTROL_PORT) {
-      /* Blow up more or less nicely if we didn't get anything or not the
-       * thing we expected.
-       */
-      if (!desc) {
-        char id[HEX_DIGEST_LEN+1];
-        char dd[HEX_DIGEST_LEN+1];
-
-        base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
-        base16_encode(dd, sizeof(dd), rs->descriptor_digest, DIGEST_LEN);
-        log_warn(LD_BUG, "Cannot get any descriptor for %s "
-            "(wanted descriptor %s).",
-            id, dd);
-        goto err;
-      }
-
-      /* This assert could fire for the control port, because
-       * it can request NS documents before all descriptors
-       * have been fetched. Therefore, we only do this test when
-       * format != NS_CONTROL_PORT. */
-      if (tor_memneq(desc->cache_info.signed_descriptor_digest,
-            rs->descriptor_digest,
-            DIGEST_LEN)) {
-        char rl_d[HEX_DIGEST_LEN+1];
-        char rs_d[HEX_DIGEST_LEN+1];
-        char id[HEX_DIGEST_LEN+1];
-
-        base16_encode(rl_d, sizeof(rl_d),
-            desc->cache_info.signed_descriptor_digest, DIGEST_LEN);
-        base16_encode(rs_d, sizeof(rs_d), rs->descriptor_digest, DIGEST_LEN);
-        base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
-        log_err(LD_BUG, "descriptor digest in routerlist does not match "
-            "the one in routerstatus: %s vs %s "
-            "(router %s)\n",
-            rl_d, rs_d, id);
-
-        tor_assert(tor_memeq(desc->cache_info.signed_descriptor_digest,
-              rs->descriptor_digest,
-              DIGEST_LEN));
-      }
-    }
-
-    if (format == NS_CONTROL_PORT && rs->has_bandwidth) {
-      bw_kb = rs->bandwidth_kb;
-    } else {
-      tor_assert(desc);
-      bw_kb = router_get_advertised_bandwidth_capped(desc) / 1000;
-    }
-    smartlist_add_asprintf(chunks,
-                     "w Bandwidth=%d", bw_kb);
-
-    if (format == NS_V3_VOTE && vrs && vrs->has_measured_bw) {
-      smartlist_add_asprintf(chunks,
-                       " Measured=%d", vrs->measured_bw_kb);
-    }
-    /* Write down guardfraction information if we have it. */
-    if (format == NS_V3_VOTE && vrs && vrs->status.has_guardfraction) {
-      smartlist_add_asprintf(chunks,
-                             " GuardFraction=%d",
-                             vrs->status.guardfraction_percentage);
-    }
-
-    smartlist_add_strdup(chunks, "\n");
-
-    if (desc) {
-      summary = policy_summarize(desc->exit_policy, AF_INET);
-      smartlist_add_asprintf(chunks, "p %s\n", summary);
-      tor_free(summary);
-    }
-
-    if (format == NS_V3_VOTE && vrs) {
-      if (tor_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) {
-        smartlist_add_strdup(chunks, "id ed25519 none\n");
-      } else {
-        char ed_b64[BASE64_DIGEST256_LEN+1];
-        digest256_to_base64(ed_b64, (const char*)vrs->ed25519_id);
-        smartlist_add_asprintf(chunks, "id ed25519 %s\n", ed_b64);
-      }
-    }
-  }
-
- done:
-  result = smartlist_join_strings(chunks, "", 0, NULL);
-
- err:
-  SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
-  smartlist_free(chunks);
-
-  return result;
-}
-
-/** Extract status information from <b>ri</b> and from other authority
- * functions and store it in <b>rs</b>. <b>rs</b> is zeroed out before it is
- * set.
- *
- * We assume that ri-\>is_running has already been set, e.g. by
- *   dirserv_set_router_is_running(ri, now);
- */
-void
-set_routerstatus_from_routerinfo(routerstatus_t *rs,
-                                 node_t *node,
-                                 routerinfo_t *ri,
-                                 time_t now,
-                                 int listbadexits)
-{
-  const or_options_t *options = get_options();
-  uint32_t routerbw_kb = dirserv_get_credible_bandwidth_kb(ri);
-
-  memset(rs, 0, sizeof(routerstatus_t));
-
-  rs->is_authority =
-    router_digest_is_trusted_dir(ri->cache_info.identity_digest);
-
-  /* Already set by compute_performance_thresholds. */
-  rs->is_exit = node->is_exit;
-  rs->is_stable = node->is_stable =
-    !dirserv_thinks_router_is_unreliable(now, ri, 1, 0);
-  rs->is_fast = node->is_fast =
-    !dirserv_thinks_router_is_unreliable(now, ri, 0, 1);
-  rs->is_flagged_running = node->is_running; /* computed above */
-
-  rs->is_valid = node->is_valid;
-
-  if (node->is_fast && node->is_stable &&
-      ri->supports_tunnelled_dir_requests &&
-      ((options->AuthDirGuardBWGuarantee &&
-        routerbw_kb >= options->AuthDirGuardBWGuarantee/1000) ||
-       routerbw_kb >= MIN(guard_bandwidth_including_exits_kb,
-                          guard_bandwidth_excluding_exits_kb))) {
-    long tk = rep_hist_get_weighted_time_known(
-                                      node->identity, now);
-    double wfu = rep_hist_get_weighted_fractional_uptime(
-                                      node->identity, now);
-    rs->is_possible_guard = (wfu >= guard_wfu && tk >= guard_tk) ? 1 : 0;
-  } else {
-    rs->is_possible_guard = 0;
-  }
-
-  rs->is_bad_exit = listbadexits && node->is_bad_exit;
-  rs->is_hs_dir = node->is_hs_dir =
-    dirserv_thinks_router_is_hs_dir(ri, node, now);
-
-  rs->is_named = rs->is_unnamed = 0;
-
-  rs->published_on = ri->cache_info.published_on;
-  memcpy(rs->identity_digest, node->identity, DIGEST_LEN);
-  memcpy(rs->descriptor_digest, ri->cache_info.signed_descriptor_digest,
-         DIGEST_LEN);
-  rs->addr = ri->addr;
-  strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname));
-  rs->or_port = ri->or_port;
-  rs->dir_port = ri->dir_port;
-  rs->is_v2_dir = ri->supports_tunnelled_dir_requests;
-  if (options->AuthDirHasIPv6Connectivity == 1 &&
-      !tor_addr_is_null(&ri->ipv6_addr) &&
-      node->last_reachable6 >= now - REACHABLE_TIMEOUT) {
-    /* We're configured as having IPv6 connectivity. There's an IPv6
-       OR port and it's reachable so copy it to the routerstatus.  */
-    tor_addr_copy(&rs->ipv6_addr, &ri->ipv6_addr);
-    rs->ipv6_orport = ri->ipv6_orport;
-  } else {
-    tor_addr_make_null(&rs->ipv6_addr, AF_INET6);
-    rs->ipv6_orport = 0;
-  }
-
-  if (options->TestingTorNetwork) {
-    dirserv_set_routerstatus_testing(rs);
-  }
-}
-
-/** Use TestingDirAuthVoteExit, TestingDirAuthVoteGuard, and
- * TestingDirAuthVoteHSDir to give out the Exit, Guard, and HSDir flags,
- * respectively. But don't set the corresponding node flags.
- * Should only be called if TestingTorNetwork is set. */
-STATIC void
-dirserv_set_routerstatus_testing(routerstatus_t *rs)
-{
-  const or_options_t *options = get_options();
-
-  tor_assert(options->TestingTorNetwork);
-
-  if (routerset_contains_routerstatus(options->TestingDirAuthVoteExit,
-                                      rs, 0)) {
-    rs->is_exit = 1;
-  } else if (options->TestingDirAuthVoteExitIsStrict) {
-    rs->is_exit = 0;
-  }
-
-  if (routerset_contains_routerstatus(options->TestingDirAuthVoteGuard,
-                                      rs, 0)) {
-    rs->is_possible_guard = 1;
-  } else if (options->TestingDirAuthVoteGuardIsStrict) {
-    rs->is_possible_guard = 0;
-  }
-
-  if (routerset_contains_routerstatus(options->TestingDirAuthVoteHSDir,
-                                      rs, 0)) {
-    rs->is_hs_dir = 1;
-  } else if (options->TestingDirAuthVoteHSDirIsStrict) {
-    rs->is_hs_dir = 0;
-  }
-}
-
-/** The guardfraction of the guard with identity fingerprint <b>guard_id</b>
- *  is <b>guardfraction_percentage</b>. See if we have a vote routerstatus for
- *  this guard in <b>vote_routerstatuses</b>, and if we do, register the
- *  information to it.
- *
- *  Return 1 if we applied the information and 0 if we couldn't find a
- *  matching guard.
- *
- * Requires that <b>vote_routerstatuses</b> be sorted.
- */
-static int
-guardfraction_line_apply(const char *guard_id,
-                      uint32_t guardfraction_percentage,
-                      smartlist_t *vote_routerstatuses)
-{
-  vote_routerstatus_t *vrs = NULL;
-
-  tor_assert(vote_routerstatuses);
-
-  vrs = smartlist_bsearch(vote_routerstatuses, guard_id,
-                         compare_digest_to_vote_routerstatus_entry);
-
-  if (!vrs) {
-    return 0;
-  }
-
-  vrs->status.has_guardfraction = 1;
-  vrs->status.guardfraction_percentage = guardfraction_percentage;
-
-  return 1;
-}
-
-/* Given a guard line from a guardfraction file, parse it and register
- * its information to <b>vote_routerstatuses</b>.
- *
- * Return:
- * * 1 if the line was proper and its information got registered.
- * * 0 if the line was proper but no currently active guard was found
- *     to register the guardfraction information to.
- * * -1 if the line could not be parsed and set <b>err_msg</b> to a
-      newly allocated string containing the error message.
- */
-static int
-guardfraction_file_parse_guard_line(const char *guard_line,
-                                    smartlist_t *vote_routerstatuses,
-                                    char **err_msg)
-{
-  char guard_id[DIGEST_LEN];
-  uint32_t guardfraction;
-  char *inputs_tmp = NULL;
-  int num_ok = 1;
-
-  smartlist_t *sl = smartlist_new();
-  int retval = -1;
-
-  tor_assert(err_msg);
-
-  /* guard_line should contain something like this:
-     <hex digest> <guardfraction> <appearances> */
-  smartlist_split_string(sl, guard_line, " ",
-                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3);
-  if (smartlist_len(sl) < 3) {
-    tor_asprintf(err_msg, "bad line '%s'", guard_line);
-    goto done;
-  }
-
-  inputs_tmp = smartlist_get(sl, 0);
-  if (strlen(inputs_tmp) != HEX_DIGEST_LEN ||
-      base16_decode(guard_id, DIGEST_LEN,
-                    inputs_tmp, HEX_DIGEST_LEN) != DIGEST_LEN) {
-    tor_asprintf(err_msg, "bad digest '%s'", inputs_tmp);
-    goto done;
-  }
-
-  inputs_tmp = smartlist_get(sl, 1);
-  /* Guardfraction is an integer in [0, 100]. */
-  guardfraction =
-    (uint32_t) tor_parse_long(inputs_tmp, 10, 0, 100, &num_ok, NULL);
-  if (!num_ok) {
-    tor_asprintf(err_msg, "wrong percentage '%s'", inputs_tmp);
-    goto done;
-  }
-
-  /* If routerstatuses were provided, apply this info to actual routers. */
-  if (vote_routerstatuses) {
-    retval = guardfraction_line_apply(guard_id, guardfraction,
-                                      vote_routerstatuses);
-  } else {
-    retval = 0; /* If we got this far, line was correctly formatted. */
-  }
-
- done:
-
-  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
-  smartlist_free(sl);
-
-  return retval;
-}
-
-/** Given an inputs line from a guardfraction file, parse it and
- *  register its information to <b>total_consensuses</b> and
- *  <b>total_days</b>.
- *
- *  Return 0 if it parsed well. Return -1 if there was an error, and
- *  set <b>err_msg</b> to a newly allocated string containing the
- *  error message.
- */
-static int
-guardfraction_file_parse_inputs_line(const char *inputs_line,
-                                     int *total_consensuses,
-                                     int *total_days,
-                                     char **err_msg)
-{
-  int retval = -1;
-  char *inputs_tmp = NULL;
-  int num_ok = 1;
-  smartlist_t *sl = smartlist_new();
-
-  tor_assert(err_msg);
-
-  /* Second line is inputs information:
-   *   n-inputs <total_consensuses> <total_days>. */
-  smartlist_split_string(sl, inputs_line, " ",
-                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3);
-  if (smartlist_len(sl) < 2) {
-    tor_asprintf(err_msg, "incomplete line '%s'", inputs_line);
-    goto done;
-  }
-
-  inputs_tmp = smartlist_get(sl, 0);
-  *total_consensuses =
-    (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL);
-  if (!num_ok) {
-    tor_asprintf(err_msg, "unparseable consensus '%s'", inputs_tmp);
-    goto done;
-  }
-
-  inputs_tmp = smartlist_get(sl, 1);
-  *total_days =
-    (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL);
-  if (!num_ok) {
-    tor_asprintf(err_msg, "unparseable days '%s'", inputs_tmp);
-    goto done;
-  }
-
-  retval = 0;
-
- done:
-  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
-  smartlist_free(sl);
-
-  return retval;
-}
-
-/* Maximum age of a guardfraction file that we are willing to accept. */
-#define MAX_GUARDFRACTION_FILE_AGE (7*24*60*60) /* approx a week */
-
-/** Static strings of guardfraction files. */
-#define GUARDFRACTION_DATE_STR "written-at"
-#define GUARDFRACTION_INPUTS "n-inputs"
-#define GUARDFRACTION_GUARD "guard-seen"
-#define GUARDFRACTION_VERSION "guardfraction-file-version"
-
-/** Given a guardfraction file in a string, parse it and register the
- *  guardfraction information to the provided vote routerstatuses.
- *
- *  This is the rough format of the guardfraction file:
- *
- *      guardfraction-file-version 1
- *      written-at <date and time>
- *      n-inputs <number of consesuses parsed> <number of days considered>
- *
- *      guard-seen <fpr 1> <guardfraction percentage> <consensus appearances>
- *      guard-seen <fpr 2> <guardfraction percentage> <consensus appearances>
- *      guard-seen <fpr 3> <guardfraction percentage> <consensus appearances>
- *      guard-seen <fpr 4> <guardfraction percentage> <consensus appearances>
- *      guard-seen <fpr 5> <guardfraction percentage> <consensus appearances>
- *      ...
- *
- *  Return -1 if the parsing failed and 0 if it went smoothly. Parsing
- *  should tolerate errors in all lines but the written-at header.
- */
-STATIC int
-dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
-                                      smartlist_t *vote_routerstatuses)
-{
-  config_line_t *front=NULL, *line;
-  int ret_tmp;
-  int retval = -1;
-  int current_line_n = 0; /* line counter for better log messages */
-
-  /* Guardfraction info to be parsed */
-  int total_consensuses = 0;
-  int total_days = 0;
-
-  /* Stats */
-  int guards_read_n = 0;
-  int guards_applied_n = 0;
-
-  /* Parse file and split it in lines */
-  ret_tmp = config_get_lines(guardfraction_file_str, &front, 0);
-  if (ret_tmp < 0) {
-    log_warn(LD_CONFIG, "Error reading from guardfraction file");
-    goto done;
-  }
-
-  /* Sort routerstatuses (needed later when applying guardfraction info) */
-  if (vote_routerstatuses)
-    smartlist_sort(vote_routerstatuses, compare_vote_routerstatus_entries);
-
-  for (line = front; line; line=line->next) {
-    current_line_n++;
-
-    if (!strcmp(line->key, GUARDFRACTION_VERSION)) {
-      int num_ok = 1;
-      unsigned int version;
-
-      version =
-        (unsigned int) tor_parse_long(line->value,
-                                      10, 0, INT_MAX, &num_ok, NULL);
-
-      if (!num_ok || version != 1) {
-        log_warn(LD_GENERAL, "Got unknown guardfraction version %d.", version);
-        goto done;
-      }
-    } else if (!strcmp(line->key, GUARDFRACTION_DATE_STR)) {
-      time_t file_written_at;
-      time_t now = time(NULL);
-
-      /* First line is 'written-at <date>' */
-      if (parse_iso_time(line->value, &file_written_at) < 0) {
-        log_warn(LD_CONFIG, "Guardfraction:%d: Bad date '%s'. Ignoring",
-                 current_line_n, line->value);
-        goto done; /* don't tolerate failure here. */
-      }
-      if (file_written_at < now - MAX_GUARDFRACTION_FILE_AGE) {
-        log_warn(LD_CONFIG, "Guardfraction:%d: was written very long ago '%s'",
-                 current_line_n, line->value);
-        goto done; /* don't tolerate failure here. */
-      }
-    } else if (!strcmp(line->key, GUARDFRACTION_INPUTS)) {
-      char *err_msg = NULL;
-
-      if (guardfraction_file_parse_inputs_line(line->value,
-                                               &total_consensuses,
-                                               &total_days,
-                                               &err_msg) < 0) {
-        log_warn(LD_CONFIG, "Guardfraction:%d: %s",
-                 current_line_n, err_msg);
-        tor_free(err_msg);
-        continue;
-      }
-
-    } else if (!strcmp(line->key, GUARDFRACTION_GUARD)) {
-      char *err_msg = NULL;
-
-      ret_tmp = guardfraction_file_parse_guard_line(line->value,
-                                                    vote_routerstatuses,
-                                                    &err_msg);
-      if (ret_tmp < 0) { /* failed while parsing the guard line */
-        log_warn(LD_CONFIG, "Guardfraction:%d: %s",
-                 current_line_n, err_msg);
-        tor_free(err_msg);
-        continue;
-      }
-
-      /* Successfully parsed guard line. Check if it was applied properly. */
-      guards_read_n++;
-      if (ret_tmp > 0) {
-        guards_applied_n++;
-      }
-    } else {
-      log_warn(LD_CONFIG, "Unknown guardfraction line %d (%s %s)",
-               current_line_n, line->key, line->value);
-    }
-  }
-
-  retval = 0;
-
-  log_info(LD_CONFIG,
-           "Successfully parsed guardfraction file with %d consensuses over "
-           "%d days. Parsed %d nodes and applied %d of them%s.",
-           total_consensuses, total_days, guards_read_n, guards_applied_n,
-           vote_routerstatuses ? "" : " (no routerstatus provided)" );
-
- done:
-  config_free_lines(front);
-
-  if (retval < 0) {
-    return retval;
-  } else {
-    return guards_read_n;
-  }
+  me = router_get_my_routerinfo();
+  if (!me || (!me->supports_tunnelled_dir_requests && !refuseunknown))
+    return 0; /* if we don't service directory requests, return 0 too */
+  return 1;
 }
 
-/** Read a guardfraction file at <b>fname</b> and load all its
- *  information to <b>vote_routerstatuses</b>. */
+/** Return 1 if we should fetch new networkstatuses, descriptors, etc
+ * on the "mirror" schedule rather than the "client" schedule.
+ */
 int
-dirserv_read_guardfraction_file(const char *fname,
-                             smartlist_t *vote_routerstatuses)
+directory_fetches_dir_info_early(const or_options_t *options)
 {
-  char *guardfraction_file_str;
-
-  /* Read file to a string */
-  guardfraction_file_str = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL);
-  if (!guardfraction_file_str) {
-      log_warn(LD_FS, "Cannot open guardfraction file '%s'. Failing.", fname);
-      return -1;
-  }
-
-  return dirserv_read_guardfraction_file_from_str(guardfraction_file_str,
-                                               vote_routerstatuses);
+  return directory_fetches_from_authorities(options);
 }
 
-/**
- * Helper function to parse out a line in the measured bandwidth file
- * into a measured_bw_line_t output structure.
- *
- * If <b>line_is_after_headers</b> is true, then if we encounter an incomplete
- * bw line, return -1 and warn, since we are after the headers and we should
- * only parse bw lines. Return 0 otherwise.
- *
- * If <b>line_is_after_headers</b> is false then it means that we are not past
- * the header block yet. If we encounter an incomplete bw line, return -1 but
- * don't warn since there could be additional header lines coming. If we
- * encounter a proper bw line, return 0 (and we got past the headers).
+/** Return 1 if we should fetch new networkstatuses, descriptors, etc
+ * on a very passive schedule -- waiting long enough for ordinary clients
+ * to probably have the info we want. These would include bridge users,
+ * and maybe others in the future e.g. if a Tor client uses another Tor
+ * client as a directory guard.
  */
-STATIC int
-measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line,
-                       int line_is_after_headers)
+int
+directory_fetches_dir_info_later(const or_options_t *options)
 {
-  char *line = tor_strdup(orig_line);
-  char *cp = line;
-  int got_bw = 0;
-  int got_node_id = 0;
-  char *strtok_state; /* lame sauce d'jour */
-
-  if (strlen(line) == 0) {
-    log_warn(LD_DIRSERV, "Empty line in bandwidth file");
-    tor_free(line);
-    return -1;
-  }
-
-  /* Remove end of line character, so that is not part of the token */
-  if (line[strlen(line) - 1] == '\n') {
-    line[strlen(line) - 1] = '\0';
-  }
-
-  cp = tor_strtok_r(cp, " \t", &strtok_state);
-
-  if (!cp) {
-    log_warn(LD_DIRSERV, "Invalid line in bandwidth file: %s",
-             escaped(orig_line));
-    tor_free(line);
-    return -1;
-  }
-
-  if (orig_line[strlen(orig_line)-1] != '\n') {
-    log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
-             escaped(orig_line));
-    tor_free(line);
-    return -1;
-  }
-
-  do {
-    if (strcmpstart(cp, "bw=") == 0) {
-      int parse_ok = 0;
-      char *endptr;
-      if (got_bw) {
-        log_warn(LD_DIRSERV, "Double bw= in bandwidth file line: %s",
-                 escaped(orig_line));
-        tor_free(line);
-        return -1;
-      }
-      cp+=strlen("bw=");
-
-      out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr);
-      if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) {
-        log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s",
-                 escaped(orig_line));
-        tor_free(line);
-        return -1;
-      }
-      got_bw=1;
-    } else if (strcmpstart(cp, "node_id=$") == 0) {
-      if (got_node_id) {
-        log_warn(LD_DIRSERV, "Double node_id= in bandwidth file line: %s",
-                 escaped(orig_line));
-        tor_free(line);
-        return -1;
-      }
-      cp+=strlen("node_id=$");
-
-      if (strlen(cp) != HEX_DIGEST_LEN ||
-          base16_decode(out->node_id, DIGEST_LEN,
-                        cp, HEX_DIGEST_LEN) != DIGEST_LEN) {
-        log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s",
-                 escaped(orig_line));
-        tor_free(line);
-        return -1;
-      }
-      strlcpy(out->node_hex, cp, sizeof(out->node_hex));
-      got_node_id=1;
-    }
-  } while ((cp = tor_strtok_r(NULL, " \t", &strtok_state)));
+  return options->UseBridges != 0;
+}
 
-  if (got_bw && got_node_id) {
-    tor_free(line);
-    return 0;
-  } else if (line_is_after_headers == 0) {
-    /* There could be additional header lines, therefore do not give warnings
-     * but returns -1 since it's not a complete bw line. */
-    log_debug(LD_DIRSERV, "Missing bw or node_id in bandwidth file line: %s",
-             escaped(orig_line));
-    tor_free(line);
-    return -1;
-  } else {
-    log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
-             escaped(orig_line));
-    tor_free(line);
-    return -1;
-  }
+/** Return true iff we want to serve certificates for authorities
+ * that we don't acknowledge as authorities ourself.
+ * Use we_want_to_fetch_unknown_auth_certs to check if we want to fetch
+ * and keep these certificates.
+ */
+int
+directory_caches_unknown_auth_certs(const or_options_t *options)
+{
+  return dir_server_mode(options) || options->BridgeRelay;
 }
 
-/**
- * Helper function to apply a parsed measurement line to a list
- * of bandwidth statuses. Returns true if a line is found,
- * false otherwise.
+/** Return 1 if we want to fetch and serve descriptors, networkstatuses, etc
+ * Else return 0.
+ * Check options->DirPort_set and directory_permits_begindir_requests()
+ * to see if we are willing to serve these directory documents to others via
+ * the DirPort and begindir-over-ORPort, respectively.
+ *
+ * To check if we should fetch documents, use we_want_to_fetch_flavor and
+ * we_want_to_fetch_unknown_auth_certs instead of this function.
  */
-STATIC int
-measured_bw_line_apply(measured_bw_line_t *parsed_line,
-                       smartlist_t *routerstatuses)
+int
+directory_caches_dir_info(const or_options_t *options)
 {
-  vote_routerstatus_t *rs = NULL;
-  if (!routerstatuses)
+  if (options->BridgeRelay || dir_server_mode(options))
+    return 1;
+  if (!server_mode(options) || !advertised_server_mode())
     return 0;
+  /* We need an up-to-date view of network info if we're going to try to
+   * block exit attempts from unknown relays. */
+  return ! router_my_exit_policy_is_reject_star() &&
+    should_refuse_unknown_exits(options);
+}
 
-  rs = smartlist_bsearch(routerstatuses, parsed_line->node_id,
-                         compare_digest_to_vote_routerstatus_entry);
-
-  if (rs) {
-    rs->has_measured_bw = 1;
-    rs->measured_bw_kb = (uint32_t)parsed_line->bw_kb;
-  } else {
-    log_info(LD_DIRSERV, "Node ID %s not found in routerstatus list",
-             parsed_line->node_hex);
-  }
-
-  return rs != NULL;
+/** Return 1 if we want to allow remote clients to ask us directory
+ * requests via the "begin_dir" interface, which doesn't require
+ * having any separate port open. */
+int
+directory_permits_begindir_requests(const or_options_t *options)
+{
+  return options->BridgeRelay != 0 || dir_server_mode(options);
 }
 
-/**
- * Read the measured bandwidth list file, apply it to the list of
- * vote_routerstatus_t and store all the headers in <b>bw_file_headers</b>.
- * Returns -1 on error, 0 otherwise.
+/** Return 1 if we have no need to fetch new descriptors. This generally
+ * happens when we're not a dir cache and we haven't built any circuits
+ * lately.
  */
 int
-dirserv_read_measured_bandwidths(const char *from_file,
-                                 smartlist_t *routerstatuses,
-                                 smartlist_t *bw_file_headers)
+directory_too_idle_to_fetch_descriptors(const or_options_t *options,
+                                        time_t now)
 {
-  FILE *fp = tor_fopen_cloexec(from_file, "r");
-  int applied_lines = 0;
-  time_t file_time, now;
-  int ok;
-   /* This flag will be 1 only when the first successful bw measurement line
-   * has been encountered, so that measured_bw_line_parse don't give warnings
-   * if there are additional header lines, as introduced in Bandwidth List spec
-   * version 1.1.0 */
-  int line_is_after_headers = 0;
-  int rv = -1;
-  char *line = NULL;
-  size_t n = 0;
-
-  /* Initialise line, so that we can't possibly run off the end. */
-
-  if (fp == NULL) {
-    log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s",
-             from_file);
-    goto err;
-  }
+  return !directory_caches_dir_info(options) &&
+         !options->FetchUselessDescriptors &&
+         rep_hist_circbuilding_dormant(now);
+}
 
-  /* If fgets fails, line is either unmodified, or indeterminate. */
-  if (tor_getline(&line,&n,fp) <= 0) {
-    log_warn(LD_DIRSERV, "Empty bandwidth file");
-    goto err;
-  }
+/********************************************************************/
 
-  if (!strlen(line) || line[strlen(line)-1] != '\n') {
-    log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s",
-             escaped(line));
-    goto err;
-  }
+/** Map from flavor name to the cached_dir_t for the v3 consensuses that we're
+ * currently serving. */
+static strmap_t *cached_consensuses = NULL;
 
-  line[strlen(line)-1] = '\0';
-  file_time = (time_t)tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL);
-  if (!ok) {
-    log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s",
-             escaped(line));
-    goto err;
-  }
+/** Decrement the reference count on <b>d</b>, and free it if it no longer has
+ * any references. */
+void
+cached_dir_decref(cached_dir_t *d)
+{
+  if (!d || --d->refcnt > 0)
+    return;
+  clear_cached_dir(d);
+  tor_free(d);
+}
 
-  now = time(NULL);
-  if ((now - file_time) > MAX_MEASUREMENT_AGE) {
-    log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u",
-             (unsigned)(time(NULL) - file_time));
-    goto err;
+/** Allocate and return a new cached_dir_t containing the string <b>s</b>,
+ * published at <b>published</b>. */
+cached_dir_t *
+new_cached_dir(char *s, time_t published)
+{
+  cached_dir_t *d = tor_malloc_zero(sizeof(cached_dir_t));
+  d->refcnt = 1;
+  d->dir = s;
+  d->dir_len = strlen(s);
+  d->published = published;
+  if (tor_compress(&(d->dir_compressed), &(d->dir_compressed_len),
+                   d->dir, d->dir_len, ZLIB_METHOD)) {
+    log_warn(LD_BUG, "Error compressing directory");
   }
+  return d;
+}
 
-  /* If timestamp was correct and bw_file_headers is not NULL,
-   * add timestamp to bw_file_headers */
-  if (bw_file_headers)
-    smartlist_add_asprintf(bw_file_headers, "timestamp=%lu",
-                           (unsigned long)file_time);
-
-  if (routerstatuses)
-    smartlist_sort(routerstatuses, compare_vote_routerstatus_entries);
-
-  while (!feof(fp)) {
-    measured_bw_line_t parsed_line;
-    if (tor_getline(&line, &n, fp) >= 0) {
-      if (measured_bw_line_parse(&parsed_line, line,
-                                 line_is_after_headers) != -1) {
-        /* This condition will be true when the first complete valid bw line
-         * has been encountered, which means the end of the header lines. */
-        line_is_after_headers = 1;
-        /* Also cache the line for dirserv_get_bandwidth_for_router() */
-        dirserv_cache_measured_bw(&parsed_line, file_time);
-        if (measured_bw_line_apply(&parsed_line, routerstatuses) > 0)
-          applied_lines++;
-      /* if the terminator is found, it is the end of header lines, set the
-       * flag but do not store anything */
-      } else if (strcmp(line, BW_FILE_HEADERS_TERMINATOR) == 0) {
-        line_is_after_headers = 1;
-      /* if the line was not a correct relay line nor the terminator and
-       * the end of the header lines has not been detected yet
-       * and it is key_value and bw_file_headers did not reach the maximum
-       * number of headers,
-       * then assume this line is a header and add it to bw_file_headers */
-      } else if (bw_file_headers &&
-              (line_is_after_headers == 0) &&
-              string_is_key_value(LOG_DEBUG, line) &&
-              !strchr(line, ' ') &&
-              (smartlist_len(bw_file_headers)
-               < MAX_BW_FILE_HEADER_COUNT_IN_VOTE)) {
-        line[strlen(line)-1] = '\0';
-        smartlist_add_strdup(bw_file_headers, line);
-      };
-    }
-  }
+/** Remove all storage held in <b>d</b>, but do not free <b>d</b> itself. */
+static void
+clear_cached_dir(cached_dir_t *d)
+{
+  tor_free(d->dir);
+  tor_free(d->dir_compressed);
+  memset(d, 0, sizeof(cached_dir_t));
+}
 
-  /* Now would be a nice time to clean the cache, too */
-  dirserv_expire_measured_bw_cache(now);
+/** Free all storage held by the cached_dir_t in <b>d</b>. */
+static void
+free_cached_dir_(void *_d)
+{
+  cached_dir_t *d;
+  if (!_d)
+    return;
 
-  log_info(LD_DIRSERV,
-           "Bandwidth measurement file successfully read. "
-           "Applied %d measurements.", applied_lines);
-  rv = 0;
+  d = (cached_dir_t *)_d;
+  cached_dir_decref(d);
+}
 
- err:
-  if (line) {
-    // we need to raw_free this buffer because we got it from tor_getdelim()
-    raw_free(line);
-  }
-  if (fp)
-    fclose(fp);
-  return rv;
+/** Replace the v3 consensus networkstatus of type <b>flavor_name</b> that
+ * we're serving with <b>networkstatus</b>, published at <b>published</b>.  No
+ * validation is performed. */
+void
+dirserv_set_cached_consensus_networkstatus(const char *networkstatus,
+                                           const char *flavor_name,
+                                           const common_digests_t *digests,
+                                           const uint8_t *sha3_as_signed,
+                                           time_t published)
+{
+  cached_dir_t *new_networkstatus;
+  cached_dir_t *old_networkstatus;
+  if (!cached_consensuses)
+    cached_consensuses = strmap_new();
+
+  new_networkstatus = new_cached_dir(tor_strdup(networkstatus), published);
+  memcpy(&new_networkstatus->digests, digests, sizeof(common_digests_t));
+  memcpy(&new_networkstatus->digest_sha3_as_signed, sha3_as_signed,
+         DIGEST256_LEN);
+  old_networkstatus = strmap_set(cached_consensuses, flavor_name,
+                                 new_networkstatus);
+  if (old_networkstatus)
+    cached_dir_decref(old_networkstatus);
+}
+
+/** Return the latest downloaded consensus networkstatus in encoded, signed,
+ * optionally compressed format, suitable for sending to clients. */
+cached_dir_t *
+dirserv_get_consensus(const char *flavor_name)
+{
+  if (!cached_consensuses)
+    return NULL;
+  return strmap_get(cached_consensuses, flavor_name);
 }
 
 /** As dirserv_get_routerdescs(), but instead of getting signed_descriptor_t
@@ -2866,184 +410,6 @@ dirserv_get_routerdescs(smartlist_t *descs_out, const char *key,
   return 0;
 }
 
-/** Called when a TLS handshake has completed successfully with a
- * router listening at <b>address</b>:<b>or_port</b>, and has yielded
- * a certificate with digest <b>digest_rcvd</b>.
- *
- * Inform the reachability checker that we could get to this relay.
- */
-void
-dirserv_orconn_tls_done(const tor_addr_t *addr,
-                        uint16_t or_port,
-                        const char *digest_rcvd,
-                        const ed25519_public_key_t *ed_id_rcvd)
-{
-  node_t *node = NULL;
-  tor_addr_port_t orport;
-  routerinfo_t *ri = NULL;
-  time_t now = time(NULL);
-  tor_assert(addr);
-  tor_assert(digest_rcvd);
-
-  node = node_get_mutable_by_id(digest_rcvd);
-  if (node == NULL || node->ri == NULL)
-    return;
-
-  ri = node->ri;
-
-  if (get_options()->AuthDirTestEd25519LinkKeys &&
-      node_supports_ed25519_link_authentication(node, 1) &&
-      ri->cache_info.signing_key_cert) {
-    /* We allow the node to have an ed25519 key if we haven't been told one in
-     * the routerinfo, but if we *HAVE* been told one in the routerinfo, it
-     * needs to match. */
-    const ed25519_public_key_t *expected_id =
-      &ri->cache_info.signing_key_cert->signing_key;
-    tor_assert(!ed25519_public_key_is_zero(expected_id));
-    if (! ed_id_rcvd || ! ed25519_pubkey_eq(ed_id_rcvd, expected_id)) {
-      log_info(LD_DIRSERV, "Router at %s:%d with RSA ID %s "
-               "did not present expected Ed25519 ID.",
-               fmt_addr(addr), or_port, hex_str(digest_rcvd, DIGEST_LEN));
-      return; /* Don't mark it as reachable. */
-    }
-  }
-
-  tor_addr_copy(&orport.addr, addr);
-  orport.port = or_port;
-  if (router_has_orport(ri, &orport)) {
-    /* Found the right router.  */
-    if (!authdir_mode_bridge(get_options()) ||
-        ri->purpose == ROUTER_PURPOSE_BRIDGE) {
-      char addrstr[TOR_ADDR_BUF_LEN];
-      /* This is a bridge or we're not a bridge authority --
-         mark it as reachable.  */
-      log_info(LD_DIRSERV, "Found router %s to be reachable at %s:%d. Yay.",
-               router_describe(ri),
-               tor_addr_to_str(addrstr, addr, sizeof(addrstr), 1),
-               ri->or_port);
-      if (tor_addr_family(addr) == AF_INET) {
-        rep_hist_note_router_reachable(digest_rcvd, addr, or_port, now);
-        node->last_reachable = now;
-      } else if (tor_addr_family(addr) == AF_INET6) {
-        /* No rephist for IPv6.  */
-        node->last_reachable6 = now;
-      }
-    }
-  }
-}
-
-/** Called when we, as an authority, receive a new router descriptor either as
- * an upload or a download.  Used to decide whether to relaunch reachability
- * testing for the server. */
-int
-dirserv_should_launch_reachability_test(const routerinfo_t *ri,
-                                        const routerinfo_t *ri_old)
-{
-  if (!authdir_mode_handles_descs(get_options(), ri->purpose))
-    return 0;
-  if (!ri_old) {
-    /* New router: Launch an immediate reachability test, so we will have an
-     * opinion soon in case we're generating a consensus soon */
-    return 1;
-  }
-  if (ri_old->is_hibernating && !ri->is_hibernating) {
-    /* It just came out of hibernation; launch a reachability test */
-    return 1;
-  }
-  if (! routers_have_same_or_addrs(ri, ri_old)) {
-    /* Address or port changed; launch a reachability test */
-    return 1;
-  }
-  return 0;
-}
-
-/** Helper function for dirserv_test_reachability(). Start a TLS
- * connection to <b>router</b>, and annotate it with when we started
- * the test. */
-void
-dirserv_single_reachability_test(time_t now, routerinfo_t *router)
-{
-  const or_options_t *options = get_options();
-  channel_t *chan = NULL;
-  const node_t *node = NULL;
-  tor_addr_t router_addr;
-  const ed25519_public_key_t *ed_id_key;
-  (void) now;
-
-  tor_assert(router);
-  node = node_get_by_id(router->cache_info.identity_digest);
-  tor_assert(node);
-
-  if (options->AuthDirTestEd25519LinkKeys &&
-      node_supports_ed25519_link_authentication(node, 1) &&
-      router->cache_info.signing_key_cert) {
-    ed_id_key = &router->cache_info.signing_key_cert->signing_key;
-  } else {
-    ed_id_key = NULL;
-  }
-
-  /* IPv4. */
-  log_debug(LD_OR,"Testing reachability of %s at %s:%u.",
-            router->nickname, fmt_addr32(router->addr), router->or_port);
-  tor_addr_from_ipv4h(&router_addr, router->addr);
-  chan = channel_tls_connect(&router_addr, router->or_port,
-                             router->cache_info.identity_digest,
-                             ed_id_key);
-  if (chan) command_setup_channel(chan);
-
-  /* Possible IPv6. */
-  if (get_options()->AuthDirHasIPv6Connectivity == 1 &&
-      !tor_addr_is_null(&router->ipv6_addr)) {
-    char addrstr[TOR_ADDR_BUF_LEN];
-    log_debug(LD_OR, "Testing reachability of %s at %s:%u.",
-              router->nickname,
-              tor_addr_to_str(addrstr, &router->ipv6_addr, sizeof(addrstr), 1),
-              router->ipv6_orport);
-    chan = channel_tls_connect(&router->ipv6_addr, router->ipv6_orport,
-                               router->cache_info.identity_digest,
-                               ed_id_key);
-    if (chan) command_setup_channel(chan);
-  }
-}
-
-/** Auth dir server only: load balance such that we only
- * try a few connections per call.
- *
- * The load balancing is such that if we get called once every ten
- * seconds, we will cycle through all the tests in
- * REACHABILITY_TEST_CYCLE_PERIOD seconds (a bit over 20 minutes).
- */
-void
-dirserv_test_reachability(time_t now)
-{
-  /* XXX decide what to do here; see or-talk thread "purging old router
-   * information, revocation." -NM
-   * We can't afford to mess with this in 0.1.2.x. The reason is that
-   * if we stop doing reachability tests on some of routerlist, then
-   * we'll for-sure think they're down, which may have unexpected
-   * effects in other parts of the code. It doesn't hurt much to do
-   * the testing, and directory authorities are easy to upgrade. Let's
-   * wait til 0.2.0. -RD */
-//  time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
-  routerlist_t *rl = router_get_routerlist();
-  static char ctr = 0;
-  int bridge_auth = authdir_mode_bridge(get_options());
-
-  SMARTLIST_FOREACH_BEGIN(rl->routers, routerinfo_t *, router) {
-    const char *id_digest = router->cache_info.identity_digest;
-    if (router_is_me(router))
-      continue;
-    if (bridge_auth && router->purpose != ROUTER_PURPOSE_BRIDGE)
-      continue; /* bridge authorities only test reachability on bridges */
-//    if (router->cache_info.published_on > cutoff)
-//      continue;
-    if ((((uint8_t)id_digest[0]) % REACHABILITY_MODULO_PER_TEST) == ctr) {
-      dirserv_single_reachability_test(now, router);
-    }
-  } SMARTLIST_FOREACH_END(router);
-  ctr = (ctr + 1) % REACHABILITY_MODULO_PER_TEST; /* increment ctr */
-}
-
 /* ==========
  * Spooling code.
  * ========== */
@@ -3542,90 +908,10 @@ dir_conn_clear_spool(dir_connection_t *conn)
   conn->spool = NULL;
 }
 
-/** Return true iff <b>line</b> is a valid RecommendedPackages line.
- */
-/*
-  The grammar is:
-
-    "package" SP PACKAGENAME SP VERSION SP URL SP DIGESTS NL
-
-      PACKAGENAME = NONSPACE
-      VERSION = NONSPACE
-      URL = NONSPACE
-      DIGESTS = DIGEST | DIGESTS SP DIGEST
-      DIGEST = DIGESTTYPE "=" DIGESTVAL
-
-      NONSPACE = one or more non-space printing characters
-
-      DIGESTVAL = DIGESTTYPE = one or more non-=, non-" " characters.
-
-      SP = " "
-      NL = a newline
-
- */
-int
-validate_recommended_package_line(const char *line)
-{
-  const char *cp = line;
-
-#define WORD()                                  \
-  do {                                          \
-    if (*cp == ' ')                             \
-      return 0;                                 \
-    cp = strchr(cp, ' ');                       \
-    if (!cp)                                    \
-      return 0;                                 \
-  } while (0)
-
-  WORD(); /* skip packagename */
-  ++cp;
-  WORD(); /* skip version */
-  ++cp;
-  WORD(); /* Skip URL */
-  ++cp;
-
-  /* Skip digesttype=digestval + */
-  int n_entries = 0;
-  while (1) {
-    const char *start_of_word = cp;
-    const char *end_of_word = strchr(cp, ' ');
-    if (! end_of_word)
-      end_of_word = cp + strlen(cp);
-
-    if (start_of_word == end_of_word)
-      return 0;
-
-    const char *eq = memchr(start_of_word, '=', end_of_word - start_of_word);
-
-    if (!eq)
-      return 0;
-    if (eq == start_of_word)
-      return 0;
-    if (eq == end_of_word - 1)
-      return 0;
-    if (memchr(eq+1, '=', end_of_word - (eq+1)))
-      return 0;
-
-    ++n_entries;
-    if (0 == *end_of_word)
-      break;
-
-    cp = end_of_word + 1;
-  }
-
-  /* If we reach this point, we have at least 1 entry. */
-  tor_assert(n_entries > 0);
-  return 1;
-}
-
 /** Release all storage used by the directory server. */
 void
 dirserv_free_all(void)
 {
-  dirserv_free_fingerprint_list();
-
   strmap_free(cached_consensuses, free_cached_dir_);
   cached_consensuses = NULL;
-
-  dirserv_clear_measured_bw_cache();
 }
diff --git a/src/feature/dircache/dirserv.h b/src/feature/dircache/dirserv.h
index 9be4bf9db..41e137668 100644
--- a/src/feature/dircache/dirserv.h
+++ b/src/feature/dircache/dirserv.h
@@ -16,46 +16,6 @@ struct ed25519_public_key_t;
 
 #include "lib/testsupport/testsupport.h"
 
-/** An enum to describe what format we're generating a routerstatus line in.
- */
-typedef enum {
-  /** For use in a v2 opinion */
-  NS_V2,
-  /** For use in a consensus networkstatus document (ns flavor) */
-  NS_V3_CONSENSUS,
-  /** For use in a vote networkstatus document */
-  NS_V3_VOTE,
-  /** For passing to the controlport in response to a GETINFO request */
-  NS_CONTROL_PORT,
-  /** For use in a consensus networkstatus document (microdesc flavor) */
-  NS_V3_CONSENSUS_MICRODESC
-} routerstatus_format_type_t;
-
-/** What fraction (1 over this number) of the relay ID space do we
- * (as a directory authority) launch connections to at each reachability
- * test? */
-#define REACHABILITY_MODULO_PER_TEST 128
-
-/** How often (in seconds) do we launch reachability tests? */
-#define REACHABILITY_TEST_INTERVAL 10
-
-/** How many seconds apart are the reachability tests for a given relay? */
-#define REACHABILITY_TEST_CYCLE_PERIOD \
-  (REACHABILITY_TEST_INTERVAL*REACHABILITY_MODULO_PER_TEST)
-
-/** Maximum length of an exit policy summary. */
-#define MAX_EXITPOLICY_SUMMARY_LEN 1000
-
-/** Maximum allowable length of a version line in a networkstatus. */
-#define MAX_V_LINE_LEN 128
-
-/** Maximum allowable length of bandwidth headers in a bandwidth file */
-#define MAX_BW_FILE_HEADER_COUNT_IN_VOTE 50
-
-/** Terminatore that separates bandwidth file headers from bandwidth file
- * relay lines */
-#define BW_FILE_HEADERS_TERMINATOR "=====\n"
-
 /** Ways to convert a spoolable_resource_t to a bunch of bytes. */
 typedef enum dir_spool_source_t {
     DIR_SPOOL_SERVER_BY_DIGEST=1, DIR_SPOOL_SERVER_BY_FP,
@@ -111,32 +71,8 @@ typedef struct spooled_resource_t {
   off_t cached_dir_offset;
 } spooled_resource_t;
 
-#ifdef DIRSERV_PRIVATE
-typedef struct measured_bw_line_t {
-  char node_id[DIGEST_LEN];
-  char node_hex[MAX_HEX_NICKNAME_LEN+1];
-  long int bw_kb;
-} measured_bw_line_t;
-#endif /* defined(DIRSERV_PRIVATE) */
-
 int connection_dirserv_flushed_some(dir_connection_t *conn);
 
-int dirserv_add_own_fingerprint(crypto_pk_t *pk);
-int dirserv_load_fingerprint_file(void);
-void dirserv_free_fingerprint_list(void);
-enum was_router_added_t dirserv_add_multiple_descriptors(
-                                     const char *desc, uint8_t purpose,
-                                     const char *source,
-                                     const char **msg);
-enum was_router_added_t dirserv_add_descriptor(routerinfo_t *ri,
-                                               const char **msg,
-                                               const char *source);
-void dirserv_set_router_is_running(routerinfo_t *router, time_t now);
-int list_server_status_v1(smartlist_t *routers, char **router_status_out,
-                          int for_controller);
-char *dirserv_get_flag_thresholds_line(void);
-void dirserv_compute_bridge_flag_thresholds(void);
-
 int directory_fetches_from_authorities(const or_options_t *options);
 int directory_fetches_dir_info_early(const or_options_t *options);
 int directory_fetches_dir_info_later(const or_options_t *options);
@@ -159,76 +95,10 @@ int dirserv_get_routerdesc_spool(smartlist_t *spools_out, const char *key,
                                  const char **msg_out);
 int dirserv_get_routerdescs(smartlist_t *descs_out, const char *key,
                             const char **msg);
-void dirserv_orconn_tls_done(const tor_addr_t *addr,
-                             uint16_t or_port,
-                             const char *digest_rcvd,
-                             const struct ed25519_public_key_t *ed_id_rcvd);
-int dirserv_should_launch_reachability_test(const routerinfo_t *ri,
-                                            const routerinfo_t *ri_old);
-void dirserv_single_reachability_test(time_t now, routerinfo_t *router);
-void dirserv_test_reachability(time_t now);
-int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg,
-                                   int complain,
-                                   int *valid_out);
-uint32_t dirserv_router_get_status(const routerinfo_t *router,
-                                   const char **msg,
-                                   int severity);
-void dirserv_set_node_flags_from_authoritative_status(node_t *node,
-                                                      uint32_t authstatus);
 
-int dirserv_would_reject_router(const routerstatus_t *rs);
-char *routerstatus_format_entry(
-                              const routerstatus_t *rs,
-                              const char *version,
-                              const char *protocols,
-                              routerstatus_format_type_t format,
-                              int consensus_method,
-                              const vote_routerstatus_t *vrs);
 void dirserv_free_all(void);
 void cached_dir_decref(cached_dir_t *d);
 cached_dir_t *new_cached_dir(char *s, time_t published);
-struct config_line_t;
-char *format_recommended_version_list(const struct config_line_t *line,
-                                      int warn);
-int validate_recommended_package_line(const char *line);
-int dirserv_query_measured_bw_cache_kb(const char *node_id,
-                                       long *bw_out,
-                                       time_t *as_of_out);
-void dirserv_clear_measured_bw_cache(void);
-int dirserv_has_measured_bw(const char *node_id);
-int dirserv_get_measured_bw_cache_size(void);
-void dirserv_count_measured_bws(const smartlist_t *routers);
-int running_long_enough_to_decide_unreachable(void);
-void dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil);
-
-#ifdef DIRSERV_PRIVATE
-
-STATIC void dirserv_set_routerstatus_testing(routerstatus_t *rs);
-
-/* Put the MAX_MEASUREMENT_AGE #define here so unit tests can see it */
-#define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */
-
-STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *line,
-                                  int line_is_after_headers);
-
-STATIC int measured_bw_line_apply(measured_bw_line_t *parsed_line,
-                           smartlist_t *routerstatuses);
-
-STATIC void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line,
-                               time_t as_of);
-STATIC void dirserv_expire_measured_bw_cache(time_t now);
-
-STATIC int
-dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
-                                      smartlist_t *vote_routerstatuses);
-#endif /* defined(DIRSERV_PRIVATE) */
-
-int dirserv_read_measured_bandwidths(const char *from_file,
-                                     smartlist_t *routerstatuses,
-                                     smartlist_t *bw_file_headers);
-
-int dirserv_read_guardfraction_file(const char *fname,
-                                 smartlist_t *vote_routerstatuses);
 
 spooled_resource_t *spooled_resource_new(dir_spool_source_t source,
                                          const uint8_t *digest,
diff --git a/src/feature/nodelist/fmt_routerstatus.c b/src/feature/nodelist/fmt_routerstatus.c
new file mode 100644
index 000000000..d97d4ae6e
--- /dev/null
+++ b/src/feature/nodelist/fmt_routerstatus.c
@@ -0,0 +1,253 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file fmt_routerstatus.h
+ * \brief Format routerstatus entries for controller, vote, or consensus.
+ *
+ * (Because controllers consume this format, we can't make this
+ * code dirauth-only.)
+ **/
+
+#include "core/or/or.h"
+#include "feature/nodelist/fmt_routerstatus.h"
+
+/* #include "lib/container/buffers.h" */
+/* #include "app/config/config.h" */
+/* #include "app/config/confparse.h" */
+/* #include "core/or/channel.h" */
+/* #include "core/or/channeltls.h" */
+/* #include "core/or/command.h" */
+/* #include "core/mainloop/connection.h" */
+/* #include "core/or/connection_or.h" */
+/* #include "feature/dircache/conscache.h" */
+/* #include "feature/dircache/consdiffmgr.h" */
+/* #include "feature/control/control.h" */
+/* #include "feature/dircache/directory.h" */
+/* #include "feature/dircache/dirserv.h" */
+/* #include "feature/hibernate/hibernate.h" */
+/* #include "feature/dirauth/keypin.h" */
+/* #include "core/mainloop/main.h" */
+/* #include "feature/nodelist/microdesc.h" */
+/* #include "feature/nodelist/networkstatus.h" */
+/* #include "feature/nodelist/nodelist.h" */
+#include "core/or/policies.h"
+/* #include "core/or/protover.h" */
+/* #include "feature/stats/rephist.h" */
+/* #include "feature/relay/router.h" */
+/* #include "feature/nodelist/dirlist.h" */
+#include "feature/nodelist/routerlist.h"
+
+/* #include "feature/nodelist/routerparse.h" */
+/* #include "feature/nodelist/routerset.h" */
+/* #include "feature/nodelist/torcert.h" */
+/* #include "feature/dircommon/voting_schedule.h" */
+
+#include "feature/dirauth/dirvote.h"
+
+/* #include "feature/dircache/cached_dir_st.h" */
+/* #include "feature/dircommon/dir_connection_st.h" */
+/* #include "feature/nodelist/extrainfo_st.h" */
+/* #include "feature/nodelist/microdesc_st.h" */
+/* #include "feature/nodelist/node_st.h" */
+#include "feature/nodelist/routerinfo_st.h"
+/* #include "feature/nodelist/routerlist_st.h" */
+/* #include "core/or/tor_version_st.h" */
+#include "feature/nodelist/vote_routerstatus_st.h"
+
+/* #include "lib/compress/compress.h" */
+/* #include "lib/container/order.h" */
+#include "lib/crypt_ops/crypto_format.h"
+/* #include "lib/encoding/confline.h" */
+
+/* #include "lib/encoding/keyval.h" */
+
+/** Helper: write the router-status information in <b>rs</b> into a newly
+ * allocated character buffer.  Use the same format as in network-status
+ * documents.  If <b>version</b> is non-NULL, add a "v" line for the platform.
+ *
+ * consensus_method is the current consensus method when format is
+ * NS_V3_CONSENSUS or NS_V3_CONSENSUS_MICRODESC. It is ignored for other
+ * formats: pass ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD.
+ *
+ * Return 0 on success, -1 on failure.
+ *
+ * The format argument has one of the following values:
+ *   NS_V2 - Output an entry suitable for a V2 NS opinion document
+ *   NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry
+ *        for consensus_method.
+ *   NS_V3_CONSENSUS_MICRODESC - Output the first portion of a V3 microdesc
+ *        consensus entry for consensus_method.
+ *   NS_V3_VOTE - Output a complete V3 NS vote. If <b>vrs</b> is present,
+ *        it contains additional information for the vote.
+ *   NS_CONTROL_PORT - Output a NS document for the control port.
+ */
+char *
+routerstatus_format_entry(const routerstatus_t *rs, const char *version,
+                          const char *protocols,
+                          routerstatus_format_type_t format,
+                          int consensus_method,
+                          const vote_routerstatus_t *vrs)
+{
+  char *summary;
+  char *result = NULL;
+
+  char published[ISO_TIME_LEN+1];
+  char identity64[BASE64_DIGEST_LEN+1];
+  char digest64[BASE64_DIGEST_LEN+1];
+  smartlist_t *chunks = smartlist_new();
+
+  format_iso_time(published, rs->published_on);
+  digest_to_base64(identity64, rs->identity_digest);
+  digest_to_base64(digest64, rs->descriptor_digest);
+
+  smartlist_add_asprintf(chunks,
+                   "r %s %s %s%s%s %s %d %d\n",
+                   rs->nickname,
+                   identity64,
+                   (format==NS_V3_CONSENSUS_MICRODESC)?"":digest64,
+                   (format==NS_V3_CONSENSUS_MICRODESC)?"":" ",
+                   published,
+                   fmt_addr32(rs->addr),
+                   (int)rs->or_port,
+                   (int)rs->dir_port);
+
+  /* TODO: Maybe we want to pass in what we need to build the rest of
+   * this here, instead of in the caller. Then we could use the
+   * networkstatus_type_t values, with an additional control port value
+   * added -MP */
+
+  /* V3 microdesc consensuses only have "a" lines in later consensus methods
+   */
+  if (format == NS_V3_CONSENSUS_MICRODESC &&
+      consensus_method < MIN_METHOD_FOR_A_LINES_IN_MICRODESC_CONSENSUS)
+    goto done;
+
+  /* Possible "a" line. At most one for now. */
+  if (!tor_addr_is_null(&rs->ipv6_addr)) {
+    smartlist_add_asprintf(chunks, "a %s\n",
+                           fmt_addrport(&rs->ipv6_addr, rs->ipv6_orport));
+  }
+
+  if (format == NS_V3_CONSENSUS || format == NS_V3_CONSENSUS_MICRODESC)
+    goto done;
+
+  smartlist_add_asprintf(chunks,
+                   "s%s%s%s%s%s%s%s%s%s%s\n",
+                  /* These must stay in alphabetical order. */
+                   rs->is_authority?" Authority":"",
+                   rs->is_bad_exit?" BadExit":"",
+                   rs->is_exit?" Exit":"",
+                   rs->is_fast?" Fast":"",
+                   rs->is_possible_guard?" Guard":"",
+                   rs->is_hs_dir?" HSDir":"",
+                   rs->is_flagged_running?" Running":"",
+                   rs->is_stable?" Stable":"",
+                   rs->is_v2_dir?" V2Dir":"",
+                   rs->is_valid?" Valid":"");
+
+  /* length of "opt v \n" */
+#define V_LINE_OVERHEAD 7
+  if (version && strlen(version) < MAX_V_LINE_LEN - V_LINE_OVERHEAD) {
+    smartlist_add_asprintf(chunks, "v %s\n", version);
+  }
+  if (protocols) {
+    smartlist_add_asprintf(chunks, "pr %s\n", protocols);
+  }
+
+  if (format != NS_V2) {
+    const routerinfo_t* desc = router_get_by_id_digest(rs->identity_digest);
+    uint32_t bw_kb;
+
+    if (format != NS_CONTROL_PORT) {
+      /* Blow up more or less nicely if we didn't get anything or not the
+       * thing we expected.
+       */
+      if (!desc) {
+        char id[HEX_DIGEST_LEN+1];
+        char dd[HEX_DIGEST_LEN+1];
+
+        base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
+        base16_encode(dd, sizeof(dd), rs->descriptor_digest, DIGEST_LEN);
+        log_warn(LD_BUG, "Cannot get any descriptor for %s "
+            "(wanted descriptor %s).",
+            id, dd);
+        goto err;
+      }
+
+      /* This assert could fire for the control port, because
+       * it can request NS documents before all descriptors
+       * have been fetched. Therefore, we only do this test when
+       * format != NS_CONTROL_PORT. */
+      if (tor_memneq(desc->cache_info.signed_descriptor_digest,
+            rs->descriptor_digest,
+            DIGEST_LEN)) {
+        char rl_d[HEX_DIGEST_LEN+1];
+        char rs_d[HEX_DIGEST_LEN+1];
+        char id[HEX_DIGEST_LEN+1];
+
+        base16_encode(rl_d, sizeof(rl_d),
+            desc->cache_info.signed_descriptor_digest, DIGEST_LEN);
+        base16_encode(rs_d, sizeof(rs_d), rs->descriptor_digest, DIGEST_LEN);
+        base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
+        log_err(LD_BUG, "descriptor digest in routerlist does not match "
+            "the one in routerstatus: %s vs %s "
+            "(router %s)\n",
+            rl_d, rs_d, id);
+
+        tor_assert(tor_memeq(desc->cache_info.signed_descriptor_digest,
+              rs->descriptor_digest,
+              DIGEST_LEN));
+      }
+    }
+
+    if (format == NS_CONTROL_PORT && rs->has_bandwidth) {
+      bw_kb = rs->bandwidth_kb;
+    } else {
+      tor_assert(desc);
+      bw_kb = router_get_advertised_bandwidth_capped(desc) / 1000;
+    }
+    smartlist_add_asprintf(chunks,
+                     "w Bandwidth=%d", bw_kb);
+
+    if (format == NS_V3_VOTE && vrs && vrs->has_measured_bw) {
+      smartlist_add_asprintf(chunks,
+                       " Measured=%d", vrs->measured_bw_kb);
+    }
+    /* Write down guardfraction information if we have it. */
+    if (format == NS_V3_VOTE && vrs && vrs->status.has_guardfraction) {
+      smartlist_add_asprintf(chunks,
+                             " GuardFraction=%d",
+                             vrs->status.guardfraction_percentage);
+    }
+
+    smartlist_add_strdup(chunks, "\n");
+
+    if (desc) {
+      summary = policy_summarize(desc->exit_policy, AF_INET);
+      smartlist_add_asprintf(chunks, "p %s\n", summary);
+      tor_free(summary);
+    }
+
+    if (format == NS_V3_VOTE && vrs) {
+      if (tor_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) {
+        smartlist_add_strdup(chunks, "id ed25519 none\n");
+      } else {
+        char ed_b64[BASE64_DIGEST256_LEN+1];
+        digest256_to_base64(ed_b64, (const char*)vrs->ed25519_id);
+        smartlist_add_asprintf(chunks, "id ed25519 %s\n", ed_b64);
+      }
+    }
+  }
+
+ done:
+  result = smartlist_join_strings(chunks, "", 0, NULL);
+
+ err:
+  SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+  smartlist_free(chunks);
+
+  return result;
+}
diff --git a/src/feature/nodelist/fmt_routerstatus.h b/src/feature/nodelist/fmt_routerstatus.h
new file mode 100644
index 000000000..1a6630d26
--- /dev/null
+++ b/src/feature/nodelist/fmt_routerstatus.h
@@ -0,0 +1,41 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file fmt_routerstatus.h
+ * \brief Header file for fmt_routerstatus.c.
+ **/
+
+#ifndef TOR_FMT_ROUTERSTATUS_H
+#define TOR_FMT_ROUTERSTATUS_H
+
+/** An enum to describe what format we're generating a routerstatus line in.
+ */
+typedef enum {
+  /** For use in a v2 opinion */
+  NS_V2,
+  /** For use in a consensus networkstatus document (ns flavor) */
+  NS_V3_CONSENSUS,
+  /** For use in a vote networkstatus document */
+  NS_V3_VOTE,
+  /** For passing to the controlport in response to a GETINFO request */
+  NS_CONTROL_PORT,
+  /** For use in a consensus networkstatus document (microdesc flavor) */
+  NS_V3_CONSENSUS_MICRODESC
+} routerstatus_format_type_t;
+
+/** Maximum allowable length of a version line in a networkstatus. */
+#define MAX_V_LINE_LEN 128
+
+char *routerstatus_format_entry(
+                              const routerstatus_t *rs,
+                              const char *version,
+                              const char *protocols,
+                              routerstatus_format_type_t format,
+                              int consensus_method,
+                              const vote_routerstatus_t *vrs);
+
+#endif /* !defined(TOR_FMT_ROUTERSTATUS_H) */
diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c
index d4340b6ca..c5d8d42c4 100644
--- a/src/feature/nodelist/networkstatus.c
+++ b/src/feature/nodelist/networkstatus.c
@@ -53,6 +53,7 @@
 #include "lib/crypt_ops/crypto_util.h"
 #include "feature/dircache/directory.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/reachability.h"
 #include "core/or/dos.h"
 #include "feature/client/entrynodes.h"
 #include "feature/hibernate/hibernate.h"
@@ -73,10 +74,12 @@
 #include "feature/nodelist/torcert.h"
 #include "core/or/channelpadding.h"
 #include "feature/dircommon/voting_schedule.h"
+#include "feature/nodelist/fmt_routerstatus.h"
 
 #include "feature/dirauth/dirvote.h"
 #include "feature/dirauth/mode.h"
 #include "feature/dirauth/shared_random.h"
+#include "feature/dirauth/voteflags.h"
 
 #include "feature/nodelist/authority_cert_st.h"
 #include "feature/dircommon/dir_connection_st.h"
diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c
index c990e6940..9cde06f37 100644
--- a/src/feature/nodelist/nodelist.c
+++ b/src/feature/nodelist/nodelist.c
@@ -47,6 +47,7 @@
 #include "app/config/config.h"
 #include "feature/control/control.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/process_descs.h"
 #include "feature/client/entrynodes.h"
 #include "feature/stats/geoip.h"
 #include "feature/hs/hs_common.h"
diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c
index 547b54bb9..93597f89b 100644
--- a/src/feature/nodelist/routerlist.c
+++ b/src/feature/nodelist/routerlist.c
@@ -71,6 +71,8 @@
 #include "feature/dirauth/mode.h"
 #include "feature/dircache/directory.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/reachability.h"
+#include "feature/dirauth/process_descs.h"
 #include "feature/nodelist/authcert.h"
 #include "feature/nodelist/dirlist.h"
 #include "feature/nodelist/microdesc.h"
diff --git a/src/feature/nodelist/routerparse.c b/src/feature/nodelist/routerparse.c
index 5b82c2adf..a72cf98f5 100644
--- a/src/feature/nodelist/routerparse.c
+++ b/src/feature/nodelist/routerparse.c
@@ -61,7 +61,6 @@
 #include "lib/crypt_ops/crypto_format.h"
 #include "lib/crypt_ops/crypto_util.h"
 #include "feature/dirauth/shared_random.h"
-#include "feature/dircache/dirserv.h"
 #include "feature/client/entrynodes.h"
 #include "lib/memarea/memarea.h"
 #include "feature/nodelist/microdesc.h"
diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c
index d136eaeb6..8029edfb7 100644
--- a/src/feature/relay/router.c
+++ b/src/feature/relay/router.c
@@ -18,6 +18,7 @@
 #include "lib/crypt_ops/crypto_curve25519.h"
 #include "feature/dircache/directory.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/process_descs.h"
 #include "feature/relay/dns.h"
 #include "feature/stats/geoip.h"
 #include "feature/hibernate/hibernate.h"
diff --git a/src/test/test_dir.c b/src/test/test_dir.c
index 363539ce4..b2c3f4426 100644
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@ -6,17 +6,19 @@
 #include "orconfig.h"
 #include <math.h>
 
+#define BWAUTH_PRIVATE
 #define CONFIG_PRIVATE
 #define CONTROL_PRIVATE
 #define DIRSERV_PRIVATE
 #define DIRVOTE_PRIVATE
-#define ROUTER_PRIVATE
-#define ROUTERLIST_PRIVATE
-#define ROUTERPARSE_PRIVATE
 #define HIBERNATE_PRIVATE
 #define NETWORKSTATUS_PRIVATE
 #define NODE_SELECT_PRIVATE
 #define RELAY_PRIVATE
+#define ROUTERLIST_PRIVATE
+#define ROUTERPARSE_PRIVATE
+#define ROUTER_PRIVATE
+#define VOTEFLAGS_PRIVATE
 
 #include "core/or/or.h"
 #include "feature/client/bridges.h"
@@ -29,8 +31,12 @@
 #include "lib/crypt_ops/crypto_format.h"
 #include "lib/crypt_ops/crypto_rand.h"
 #include "feature/dircache/directory.h"
+#include "feature/dirauth/bwauth.h"
 #include "feature/dircache/dirserv.h"
+#include "feature/dirauth/process_descs.h"
 #include "feature/dirauth/dirvote.h"
+#include "feature/dirauth/recommend_pkg.h"
+#include "feature/dirauth/voteflags.h"
 #include "feature/client/entrynodes.h"
 #include "feature/dircommon/fp_pair.h"
 #include "feature/hibernate/hibernate.h"
diff --git a/src/test/test_guardfraction.c b/src/test/test_guardfraction.c
index f45a72329..7d4a959bb 100644
--- a/src/test/test_guardfraction.c
+++ b/src/test/test_guardfraction.c
@@ -1,14 +1,14 @@
 /* Copyright (c) 2014-2018, The Tor Project, Inc. */
 /* See LICENSE for licensing information */
 
-#define DIRSERV_PRIVATE
+#define GUARDFRACTION_PRIVATE
 #define ROUTERPARSE_PRIVATE
 #define NETWORKSTATUS_PRIVATE
 
 #include "orconfig.h"
 #include "core/or/or.h"
 #include "app/config/config.h"
-#include "feature/dircache/dirserv.h"
+#include "feature/dirauth/guardfraction.h"
 #include "feature/client/entrynodes.h"
 #include "feature/nodelist/routerparse.h"
 #include "feature/nodelist/networkstatus.h"
@@ -422,4 +422,3 @@ struct testcase_t guardfraction_tests[] = {
 
   END_OF_TESTCASES
 };
-





More information about the tor-commits mailing list