Browse Source

Merge branch 'bug9321_rerebase'

Conflicts:
	src/or/dirvote.h
	src/test/include.am
	src/test/test_entrynodes.c
Nick Mathewson 9 years ago
parent
commit
96211bcf71

+ 7 - 0
changes/bug9321

@@ -0,0 +1,7 @@
+  o Major features:
+    - Introduce the Guardfraction feature which improves the load
+      balancing of path selection towards guard nodes. Specifically,
+      it aims to reduce the traffic gap that guard nodes experience
+      when they first get the Guard flag. This is a required step if
+      we want to increase the guard lifetime to 9 months or greater.
+      Resolves ticket 9321.

+ 11 - 0
doc/tor.1.txt

@@ -1114,6 +1114,17 @@ The following options are useful only for clients (that is, if
     download any non-default directory material.  It doesn't currently
     do anything when we lack a live consensus. (Default: 1)
 
+[[GuardfractionFile]] **GuardfractionFile** __FILENAME__::
+    V3 authoritative directories only. Configures the location of the
+    guardfraction file which contains information about how long relays
+    have been guards. (Default: unset)
+
+[[UseGuardFraction]] **UseGuardFraction** **0**|**1**|**auto**::
+    This torrc option specifies whether clients should use the
+    guardfraction information found in the consensus during path
+    selection. If it's set to 'auto', clients will do what the
+    UseGuardFraction consensus parameter tells them to do.
+
 [[NumEntryGuards]] **NumEntryGuards** __NUM__::
     If UseEntryGuards is set to 1, we will try to pick a total of NUM routers
     as long-term entries for our circuits. If NUM is 0, we try to learn

+ 6 - 0
src/or/config.c

@@ -417,6 +417,7 @@ static config_var_t option_vars_[] = {
   V(UseBridges,                  BOOL,     "0"),
   V(UseEntryGuards,              BOOL,     "1"),
   V(UseEntryGuardsAsDirGuards,   BOOL,     "1"),
+  V(UseGuardFraction,            AUTOBOOL, "auto"),
   V(UseMicrodescriptors,         AUTOBOOL, "auto"),
   V(UseNTorHandshake,            AUTOBOOL, "1"),
   V(User,                        STRING,   NULL),
@@ -434,6 +435,7 @@ static config_var_t option_vars_[] = {
   V(V3AuthNIntervalsValid,       UINT,     "3"),
   V(V3AuthUseLegacyKey,          BOOL,     "0"),
   V(V3BandwidthsFile,            FILENAME, NULL),
+  V(GuardfractionFile,           FILENAME, NULL),
   VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"),
   V(VirtualAddrNetworkIPv4,      STRING,   "127.192.0.0/10"),
   V(VirtualAddrNetworkIPv6,      STRING,   "[FE80::]/10"),
@@ -2790,6 +2792,10 @@ options_validate(or_options_t *old_options, or_options_t *options,
     if (options->V3BandwidthsFile && !old_options) {
       dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL);
     }
+    /* same for guardfraction file */
+    if (options->GuardfractionFile && !old_options) {
+      dirserv_read_guardfraction_file(options->GuardfractionFile, NULL);
+    }
   }
 
   if (options->AuthoritativeDir && !options->DirPort_set)

+ 326 - 0
src/or/dirserv.c

@@ -1915,6 +1915,13 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
       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(chunks, tor_strdup("\n"));
 
     if (desc) {
@@ -2144,6 +2151,319 @@ clear_status_flags_on_sybil(routerstatus_t *rs)
    * forget to add it to this clause. */
 }
 
+/** 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)) {
+    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);
+}
+
 /**
  * Helper function to parse out a line in the measured bandwidth file
  * into a measured_bw_line_t output structure. Returns -1 on failure
@@ -2456,6 +2776,12 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
   smartlist_free(routers);
   digestmap_free(omit_as_sybil, NULL);
 
+  /* Apply guardfraction information to routerstatuses. */
+  if (options->GuardfractionFile) {
+    dirserv_read_guardfraction_file(options->GuardfractionFile,
+                                    routerstatuses);
+  }
+
   /* This pass through applies the measured bw lines to the routerstatuses */
   if (options->V3BandwidthsFile) {
     dirserv_read_measured_bandwidths(options->V3BandwidthsFile,

+ 7 - 0
src/or/dirserv.h

@@ -125,10 +125,17 @@ STATIC int dirserv_query_measured_bw_cache_kb(const char *node_id,
                                               long *bw_out,
                                               time_t *as_of_out);
 STATIC int dirserv_has_measured_bw(const char *node_id);
+
+STATIC int
+dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
+                                      smartlist_t *vote_routerstatuses);
 #endif
 
 int dirserv_read_measured_bandwidths(const char *from_file,
                                      smartlist_t *routerstatuses);
 
+int dirserv_read_guardfraction_file(const char *fname,
+                                 smartlist_t *vote_routerstatuses);
+
 #endif
 

+ 119 - 17
src/or/dirvote.c

@@ -16,6 +16,7 @@
 #include "router.h"
 #include "routerlist.h"
 #include "routerparse.h"
+#include "entrynodes.h" /* needed for guardfraction methods */
 
 /**
  * \file dirvote.c
@@ -1023,6 +1024,86 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
   return 1;
 }
 
+/** Update total bandwidth weights (G/M/E/D/T) with the bandwidth of
+ *  the router in <b>rs</b>. */
+static void
+update_total_bandwidth_weights(const routerstatus_t *rs,
+                               int is_exit, int is_guard,
+                               int64_t *G, int64_t *M, int64_t *E, int64_t *D,
+                               int64_t *T)
+{
+  int default_bandwidth = rs->bandwidth_kb;
+  int guardfraction_bandwidth = 0;
+
+  if (!rs->has_bandwidth) {
+    log_info(LD_BUG, "Missing consensus bandwidth for router %s",
+             rs->nickname);
+    return;
+  }
+
+  /* If this routerstatus represents a guard that we have
+   * guardfraction information on, use it to calculate its actual
+   * bandwidth. From proposal236:
+   *
+   *    Similarly, when calculating the bandwidth-weights line as in
+   *    section 3.8.3 of dir-spec.txt, directory authorities should treat N
+   *    as if fraction F of its bandwidth has the guard flag and (1-F) does
+   *    not.  So when computing the totals G,M,E,D, each relay N with guard
+   *    visibility fraction F and bandwidth B should be added as follows:
+   *
+   *    G' = G + F*B, if N does not have the exit flag
+   *    M' = M + (1-F)*B, if N does not have the exit flag
+   *
+   *    or
+   *
+   *    D' = D + F*B, if N has the exit flag
+   *    E' = E + (1-F)*B, if N has the exit flag
+   *
+   * In this block of code, we prepare the bandwidth values by setting
+   * the default_bandwidth to F*B and guardfraction_bandwidth to (1-F)*B. */
+  if (rs->has_guardfraction) {
+    guardfraction_bandwidth_t guardfraction_bw;
+
+    tor_assert(is_guard);
+
+    guard_get_guardfraction_bandwidth(&guardfraction_bw,
+                                      rs->bandwidth_kb,
+                                      rs->guardfraction_percentage);
+
+    default_bandwidth = guardfraction_bw.guard_bw;
+    guardfraction_bandwidth = guardfraction_bw.non_guard_bw;
+  }
+
+  /* Now calculate the total bandwidth weights with or without
+     guardfraction. Depending on the flags of the relay, add its
+     bandwidth to the appropriate weight pool. If it's a guard and
+     guardfraction is enabled, add its bandwidth to both pools as
+     indicated by the previous comment. */
+  *T += default_bandwidth;
+  if (is_exit && is_guard) {
+
+    *D += default_bandwidth;
+    if (rs->has_guardfraction) {
+      *E += guardfraction_bandwidth;
+    }
+
+  } else if (is_exit) {
+
+    *E += default_bandwidth;
+
+  } else if (is_guard) {
+
+    *G += default_bandwidth;
+    if (rs->has_guardfraction) {
+      *M += guardfraction_bandwidth;
+    }
+
+  } else {
+
+    *M += default_bandwidth;
+  }
+}
+
 /** Given a list of vote networkstatus_t in <b>votes</b>, our public
  * authority <b>identity_key</b>, our private authority <b>signing_key</b>,
  * and the number of <b>total_authorities</b> that we believe exist in our
@@ -1291,8 +1372,11 @@ networkstatus_compute_consensus(smartlist_t *votes,
                                          sizeof(uint32_t));
     uint32_t *measured_bws_kb = tor_calloc(smartlist_len(votes),
                                            sizeof(uint32_t));
+    uint32_t *measured_guardfraction = tor_calloc(smartlist_len(votes),
+                                                  sizeof(uint32_t));
     int num_bandwidths;
     int num_mbws;
+    int num_guardfraction_inputs;
 
     int *n_voter_flags; /* n_voter_flags[j] is the number of flags that
                          * votes[j] knows about. */
@@ -1401,7 +1485,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
 
     /* We need to know how many votes measure bandwidth. */
     n_authorities_measuring_bandwidth = 0;
-    SMARTLIST_FOREACH(votes, networkstatus_t *, v,
+    SMARTLIST_FOREACH(votes, const networkstatus_t *, v,
        if (v->has_measured_bws) {
          ++n_authorities_measuring_bandwidth;
        }
@@ -1443,6 +1527,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
       smartlist_clear(versions);
       num_bandwidths = 0;
       num_mbws = 0;
+      num_guardfraction_inputs = 0;
 
       /* Okay, go through all the entries for this digest. */
       SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
@@ -1476,6 +1561,12 @@ networkstatus_compute_consensus(smartlist_t *votes,
           chosen_name = rs->status.nickname;
         }
 
+        /* Count guardfraction votes and note down the values. */
+        if (rs->status.has_guardfraction) {
+          measured_guardfraction[num_guardfraction_inputs++] =
+            rs->status.guardfraction_percentage;
+        }
+
         /* count bandwidths */
         if (rs->has_measured_bw)
           measured_bws_kb[num_mbws++] = rs->measured_bw_kb;
@@ -1565,6 +1656,17 @@ networkstatus_compute_consensus(smartlist_t *votes,
         chosen_version = NULL;
       }
 
+      /* If it's a guard and we have enough guardfraction votes,
+         calculate its consensus guardfraction value. */
+      if (is_guard && num_guardfraction_inputs > 2 &&
+          consensus_method >= MIN_METHOD_FOR_GUARDFRACTION) {
+        rs_out.has_guardfraction = 1;
+        rs_out.guardfraction_percentage = median_uint32(measured_guardfraction,
+                                                     num_guardfraction_inputs);
+        /* final value should be an integer percentage! */
+        tor_assert(rs_out.guardfraction_percentage <= 100);
+      }
+
       /* Pick a bandwidth */
       if (num_mbws > 2) {
         rs_out.has_bandwidth = 1;
@@ -1586,21 +1688,11 @@ networkstatus_compute_consensus(smartlist_t *votes,
       /* Fix bug 2203: Do not count BadExit nodes as Exits for bw weights */
       is_exit = is_exit && !is_bad_exit;
 
+      /* Update total bandwidth weights with the bandwidths of this router. */
       {
-        if (rs_out.has_bandwidth) {
-          T += rs_out.bandwidth_kb;
-          if (is_exit && is_guard)
-            D += rs_out.bandwidth_kb;
-          else if (is_exit)
-            E += rs_out.bandwidth_kb;
-          else if (is_guard)
-            G += rs_out.bandwidth_kb;
-          else
-            M += rs_out.bandwidth_kb;
-        } else {
-          log_warn(LD_BUG, "Missing consensus bandwidth for router %s",
-              rs_out.nickname);
-        }
+        update_total_bandwidth_weights(&rs_out,
+                                       is_exit, is_guard,
+                                       &G, &M, &E, &D, &T);
       }
 
       /* Ok, we already picked a descriptor digest we want to list
@@ -1719,11 +1811,21 @@ networkstatus_compute_consensus(smartlist_t *votes,
       smartlist_add(chunks, tor_strdup("\n"));
       /*     Now the weight line. */
       if (rs_out.has_bandwidth) {
+        char *guardfraction_str = NULL;
         int unmeasured = rs_out.bw_is_unmeasured &&
           consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW;
-        smartlist_add_asprintf(chunks, "w Bandwidth=%d%s\n",
+
+        /* If we have guardfraction info, include it in the 'w' line. */
+        if (rs_out.has_guardfraction) {
+          tor_asprintf(&guardfraction_str,
+                       " GuardFraction=%u", rs_out.guardfraction_percentage);
+        }
+        smartlist_add_asprintf(chunks, "w Bandwidth=%d%s%s\n",
                                rs_out.bandwidth_kb,
-                               unmeasured?" Unmeasured=1":"");
+                               unmeasured?" Unmeasured=1":"",
+                               guardfraction_str ? guardfraction_str : "");
+
+        tor_free(guardfraction_str);
       }
 
       /*     Now the exitpolicy summary line. */

+ 7 - 2
src/or/dirvote.h

@@ -55,7 +55,7 @@
 #define MIN_SUPPORTED_CONSENSUS_METHOD 13
 
 /** The highest consensus method that we currently support. */
-#define MAX_SUPPORTED_CONSENSUS_METHOD 19
+#define MAX_SUPPORTED_CONSENSUS_METHOD 20
 
 /** Lowest consensus method where microdesc consensuses omit any entry
  * with no microdesc. */
@@ -82,8 +82,13 @@
 /** Lowest consensus method where we include "package" lines*/
 #define MIN_METHOD_FOR_PACKAGE_LINES 19
 
+/** Lowest consensus method where authorities may include
+ * GuardFraction information in microdescriptors. */
+#define MIN_METHOD_FOR_GUARDFRACTION 20
+
 /** Default bandwidth to clip unmeasured bandwidths to using method >=
- * MIN_METHOD_TO_CLIP_UNMEASURED_BW */
+ * MIN_METHOD_TO_CLIP_UNMEASURED_BW.  (This is not a consensus method; do not
+ * get confused with the above macros.) */
 #define DEFAULT_MAX_UNMEASURED_BW_KB 20
 
 void dirvote_free_all(void);

+ 57 - 0
src/or/entrynodes.c

@@ -1697,6 +1697,63 @@ getinfo_helper_entry_guards(control_connection_t *conn,
   return 0;
 }
 
+/** Return 0 if we should apply guardfraction information found in the
+ *  consensus. A specific consensus can be specified with the
+ *  <b>ns</b> argument, if NULL the most recent one will be picked.*/
+int
+should_apply_guardfraction(const networkstatus_t *ns)
+{
+  /* We need to check the corresponding torrc option and the consensus
+   * parameter if we need to. */
+  const or_options_t *options = get_options();
+
+  /* If UseGuardFraction is 'auto' then check the same-named consensus
+   * parameter. If the consensus parameter is not present, default to
+   * "off". */
+  if (options->UseGuardFraction == -1) {
+    return networkstatus_get_param(ns, "UseGuardFraction",
+                                   0, /* default to "off" */
+                                   0, 1);
+  }
+
+  return options->UseGuardFraction;
+}
+
+/* Given the original bandwidth of a guard and its guardfraction,
+ * calculate how much bandwidth the guard should have as a guard and
+ * as a non-guard.
+ *
+ * Quoting from proposal236:
+ *
+ *   Let Wpf denote the weight from the 'bandwidth-weights' line a
+ *   client would apply to N for position p if it had the guard
+ *   flag, Wpn the weight if it did not have the guard flag, and B the
+ *   measured bandwidth of N in the consensus.  Then instead of choosing
+ *   N for position p proportionally to Wpf*B or Wpn*B, clients should
+ *   choose N proportionally to F*Wpf*B + (1-F)*Wpn*B.
+ *
+ * This function fills the <b>guardfraction_bw</b> structure. It sets
+ * <b>guard_bw</b> to F*B and <b>non_guard_bw</b> to (1-F)*B.
+ */
+void
+guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
+                                  int orig_bandwidth,
+                                  uint32_t guardfraction_percentage)
+{
+  double guardfraction_fraction;
+
+  /* Turn the percentage into a fraction. */
+  tor_assert(guardfraction_percentage <= 100);
+  guardfraction_fraction = guardfraction_percentage / 100.0;
+
+  long guard_bw = tor_lround(guardfraction_fraction * orig_bandwidth);
+  tor_assert(guard_bw <= INT_MAX);
+
+  guardfraction_bw->guard_bw = (int) guard_bw;
+
+  guardfraction_bw->non_guard_bw = orig_bandwidth - guard_bw;
+}
+
 /** A list of configured bridges. Whenever we actually get a descriptor
  * for one, we add it as an entry guard.  Note that the order of bridges
  * in this list does not necessarily correspond to the order of bridges

+ 16 - 0
src/or/entrynodes.h

@@ -160,5 +160,21 @@ int validate_pluggable_transports_config(void);
 double pathbias_get_close_success_count(entry_guard_t *guard);
 double pathbias_get_use_success_count(entry_guard_t *guard);
 
+/** Contains the bandwidth of a relay as a guard and as a non-guard
+ *  after the guardfraction has been considered.  */
+typedef struct guardfraction_bandwidth_t {
+  /* Bandwidth as a guard after guardfraction has been considered. */
+  int guard_bw;
+  /* Bandwidth as a non-guard after guardfraction has been considered. */
+  int non_guard_bw;
+} guardfraction_bandwidth_t;
+
+int should_apply_guardfraction(const networkstatus_t *ns);
+
+void
+guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
+                                  int orig_bandwidth,
+                                  uint32_t guardfraction_percentage);
+
 #endif
 

+ 15 - 0
src/or/or.h

@@ -2145,6 +2145,12 @@ typedef struct routerstatus_t {
 
   uint32_t bandwidth_kb; /**< Bandwidth (capacity) of the router as reported in
                        * the vote/consensus, in kilobytes/sec. */
+
+  /** The consensus has guardfraction information for this router. */
+  unsigned int has_guardfraction:1;
+  /** The guardfraction value of this router. */
+  uint32_t guardfraction_percentage;
+
   char *exitsummary; /**< exit policy summary -
                       * XXX weasel: this probably should not stay a string. */
 
@@ -3816,6 +3822,12 @@ typedef struct {
   int NumEntryGuards; /**< How many entry guards do we try to establish? */
   int UseEntryGuardsAsDirGuards; /** Boolean: Do we try to get directory info
                                   * from a smallish number of fixed nodes? */
+
+  /** If 1, we use any guardfraction information we see in the
+   * consensus.  If 0, we don't.  If -1, let the consensus parameter
+   * decide. */
+  int UseGuardFraction;
+
   int NumDirectoryGuards; /**< How many dir guards do we try to establish?
                            * If 0, use value from NumEntryGuards. */
   int RephistTrackTime; /**< How many seconds do we keep rephist info? */
@@ -3951,6 +3963,9 @@ typedef struct {
   /** Location of bandwidth measurement file */
   char *V3BandwidthsFile;
 
+  /** Location of guardfraction file */
+  char *GuardfractionFile;
+
   /** Authority only: key=value pairs that we add to our networkstatus
    * consensus vote on the 'params' line. */
   char *ConsensusParams;

+ 41 - 1
src/or/routerlist.c

@@ -2003,6 +2003,7 @@ compute_weighted_bandwidths(const smartlist_t *sl,
   double Wg = -1, Wm = -1, We = -1, Wd = -1;
   double Wgb = -1, Wmb = -1, Web = -1, Wdb = -1;
   uint64_t weighted_bw = 0;
+  guardfraction_bandwidth_t guardfraction_bw;
   u64_dbl_t *bandwidths;
 
   /* Can't choose exit and guard at same time */
@@ -2092,6 +2093,8 @@ compute_weighted_bandwidths(const smartlist_t *sl,
   SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) {
     int is_exit = 0, is_guard = 0, is_dir = 0, this_bw = 0;
     double weight = 1;
+    double weight_without_guard_flag = 0; /* Used for guardfraction */
+    double final_weight = 0;
     is_exit = node->is_exit && ! node->is_bad_exit;
     is_guard = node->is_possible_guard;
     is_dir = node_is_dir(node);
@@ -2119,8 +2122,10 @@ compute_weighted_bandwidths(const smartlist_t *sl,
 
     if (is_guard && is_exit) {
       weight = (is_dir ? Wdb*Wd : Wd);
+      weight_without_guard_flag = (is_dir ? Web*We : We);
     } else if (is_guard) {
       weight = (is_dir ? Wgb*Wg : Wg);
+      weight_without_guard_flag = (is_dir ? Wmb*Wm : Wm);
     } else if (is_exit) {
       weight = (is_dir ? Web*We : We);
     } else { // middle
@@ -2132,8 +2137,43 @@ compute_weighted_bandwidths(const smartlist_t *sl,
       this_bw = 0;
     if (weight < 0.0)
       weight = 0.0;
+    if (weight_without_guard_flag < 0.0)
+      weight_without_guard_flag = 0.0;
 
-    bandwidths[node_sl_idx].dbl = weight*this_bw + 0.5;
+    /* If guardfraction information is available in the consensus, we
+     * want to calculate this router's bandwidth according to its
+     * guardfraction. Quoting from proposal236:
+     *
+     *    Let Wpf denote the weight from the 'bandwidth-weights' line a
+     *    client would apply to N for position p if it had the guard
+     *    flag, Wpn the weight if it did not have the guard flag, and B the
+     *    measured bandwidth of N in the consensus.  Then instead of choosing
+     *    N for position p proportionally to Wpf*B or Wpn*B, clients should
+     *    choose N proportionally to F*Wpf*B + (1-F)*Wpn*B.
+     */
+    if (node->rs && node->rs->has_guardfraction && rule != WEIGHT_FOR_GUARD) {
+      /* XXX The assert should actually check for is_guard. However,
+       * that crashes dirauths because of #13297. This should be
+       * equivalent: */
+      tor_assert(node->rs->is_possible_guard);
+
+      guard_get_guardfraction_bandwidth(&guardfraction_bw,
+                                        this_bw,
+                                        node->rs->guardfraction_percentage);
+
+      /* Calculate final_weight = F*Wpf*B + (1-F)*Wpn*B */
+      final_weight =
+        guardfraction_bw.guard_bw * weight +
+        guardfraction_bw.non_guard_bw * weight_without_guard_flag;
+
+      log_debug(LD_GENERAL, "%s: Guardfraction weight %f instead of %f (%s)",
+                node->rs->nickname, final_weight, weight*this_bw,
+                bandwidth_weight_rule_to_string(rule));
+    } else { /* no guardfraction information. calculate the weight normally. */
+      final_weight = weight*this_bw;
+    }
+
+    bandwidths[node_sl_idx].dbl = final_weight + 0.5;
   } SMARTLIST_FOREACH_END(node);
 
   log_debug(LD_CIRC, "Generated weighted bandwidths for rule %s based "

+ 65 - 0
src/or/routerparse.c

@@ -9,6 +9,8 @@
  * \brief Code to parse and validate router descriptors and directories.
  **/
 
+#define ROUTERPARSE_PRIVATE
+
 #include "or.h"
 #include "config.h"
 #include "circuitstats.h"
@@ -23,6 +25,7 @@
 #include "networkstatus.h"
 #include "rephist.h"
 #include "routerparse.h"
+#include "entrynodes.h"
 #undef log
 #include <math.h>
 
@@ -1794,6 +1797,63 @@ find_start_of_next_routerstatus(const char *s)
     return eos;
 }
 
+/** Parse the GuardFraction string from a consensus or vote.
+ *
+ *  If <b>vote</b> or <b>vote_rs</b> are set the document getting
+ *  parsed is a vote routerstatus. Otherwise it's a consensus. This is
+ *  the same semantic as in routerstatus_parse_entry_from_string(). */
+STATIC int
+routerstatus_parse_guardfraction(const char *guardfraction_str,
+                                 networkstatus_t *vote,
+                                 vote_routerstatus_t *vote_rs,
+                                 routerstatus_t *rs)
+{
+  int ok;
+  const char *end_of_header = NULL;
+  int is_consensus = !vote_rs;
+  uint32_t guardfraction;
+
+  tor_assert(bool_eq(vote, vote_rs));
+
+  /* If this info comes from a consensus, but we should't apply
+     guardfraction, just exit. */
+  if (is_consensus && !should_apply_guardfraction(NULL)) {
+    return 0;
+  }
+
+  end_of_header = strchr(guardfraction_str, '=');
+  if (!end_of_header) {
+    return -1;
+  }
+
+  guardfraction = (uint32_t)tor_parse_ulong(end_of_header+1,
+                                         10, 0, 100, &ok, NULL);
+  if (!ok) {
+    log_warn(LD_DIR, "Invalid GuardFraction %s", escaped(guardfraction_str));
+    return -1;
+  }
+
+  log_debug(LD_GENERAL, "[*] Parsed %s guardfraction '%s' for '%s'.",
+            is_consensus ? "consensus" : "vote",
+            guardfraction_str, rs->nickname);
+
+  if (!is_consensus) { /* We are parsing a vote */
+    vote_rs->status.guardfraction_percentage = guardfraction;
+    vote_rs->status.has_guardfraction = 1;
+  } else {
+    /* We are parsing a consensus. Only apply guardfraction to guards. */
+    if (rs->is_possible_guard) {
+      rs->guardfraction_percentage = guardfraction;
+      rs->has_guardfraction = 1;
+    } else {
+      log_warn(LD_BUG, "Got GuardFraction for non-guard %s. "
+               "This is not supposed to happen. Not applying. ", rs->nickname);
+    }
+  }
+
+  return 0;
+}
+
 /** Given a string at *<b>s</b>, containing a routerstatus object, and an
  * empty smartlist at <b>tokens</b>, parse and return the first router status
  * object in the string, and advance *<b>s</b> to just after the end of the
@@ -1996,6 +2056,11 @@ routerstatus_parse_entry_from_string(memarea_t *area,
         vote->has_measured_bws = 1;
       } else if (!strcmpstart(tok->args[i], "Unmeasured=1")) {
         rs->bw_is_unmeasured = 1;
+      } else if (!strcmpstart(tok->args[i], "GuardFraction=")) {
+        if (routerstatus_parse_guardfraction(tok->args[i],
+                                             vote, vote_rs, rs) < 0) {
+          goto err;
+        }
       }
     }
   }

+ 7 - 0
src/or/routerparse.h

@@ -85,5 +85,12 @@ int rend_parse_introduction_points(rend_service_descriptor_t *parsed,
                                    size_t intro_points_encoded_size);
 int rend_parse_client_keys(strmap_t *parsed_clients, const char *str);
 
+#ifdef ROUTERPARSE_PRIVATE
+STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str,
+                                            networkstatus_t *vote,
+                                            vote_routerstatus_t *vote_rs,
+                                            routerstatus_t *rs);
+#endif
+
 #endif
 

+ 3 - 0
src/test/include.am

@@ -40,6 +40,7 @@ src_test_test_SOURCES = \
 	src/test/test_dir.c \
 	src/test/test_entryconn.c \
 	src/test/test_entrynodes.c \
+	src/test/test_guardfraction.c \
 	src/test/test_extorport.c \
 	src/test/test_hs.c \
 	src/test/test_introduce.c \
@@ -61,6 +62,7 @@ src_test_test_SOURCES = \
 	src/test/test_status.c \
 	src/test/test_threads.c \
 	src/test/test_util.c \
+	src/test/test_helpers.c \
 	src/test/testing_common.c \
 	src/test/testhelper.c \
 	src/ext/tinytest.c
@@ -121,6 +123,7 @@ noinst_HEADERS+= \
 	src/test/fakechans.h \
 	src/test/test.h \
 	src/test/testhelper.h \
+	src/test/test_helpers.h \
 	src/test/test_descriptors.inc \
 	src/test/example_extrainfo.inc \
 	src/test/failing_routerdescs.inc \

+ 2 - 0
src/test/test.c

@@ -1135,6 +1135,7 @@ extern struct testcase_t crypto_tests[];
 extern struct testcase_t dir_tests[];
 extern struct testcase_t entryconn_tests[];
 extern struct testcase_t entrynodes_tests[];
+extern struct testcase_t guardfraction_tests[];
 extern struct testcase_t extorport_tests[];
 extern struct testcase_t hs_tests[];
 extern struct testcase_t introduce_tests[];
@@ -1179,6 +1180,7 @@ struct testgroup_t testgroups[] = {
   { "dir/md/", microdesc_tests },
   { "entryconn/", entryconn_tests },
   { "entrynodes/", entrynodes_tests },
+  { "guardfraction/", guardfraction_tests },
   { "extorport/", extorport_tests },
   { "hs/", hs_tests },
   { "introduce/", introduce_tests },

+ 1 - 13
src/test/test_entrynodes.c

@@ -19,6 +19,7 @@
 #include "config.h"
 
 #include "testhelper.h"
+#include "test_helpers.h"
 
 /* TODO:
  * choose_random_entry() test with state set.
@@ -271,19 +272,6 @@ state_lines_free(smartlist_t *entry_guard_lines)
   smartlist_free(entry_guard_lines);
 }
 
-/* Return a statically allocated string representing yesterday's date
- * in ISO format. We use it so that state file items are not found to
- * be outdated. */
-static const char *
-get_yesterday_date_str(void)
-{
-  static char buf[ISO_TIME_LEN+1];
-
-  time_t yesterday = time(NULL) - 24*60*60;
-  format_iso_time(buf, yesterday);
-  return buf;
-}
-
 /* Tests entry_guards_parse_state(). It creates a fake Tor state with
    a saved entry guard and makes sure that Tor can parse it and
    creates the right entry node out of it.

+ 418 - 0
src/test/test_guardfraction.c

@@ -0,0 +1,418 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#define DIRSERV_PRIVATE
+#define ROUTERPARSE_PRIVATE
+#define NETWORKSTATUS_PRIVATE
+
+#include "orconfig.h"
+#include "or.h"
+#include "config.h"
+#include "dirserv.h"
+#include "container.h"
+#include "entrynodes.h"
+#include "util.h"
+#include "routerparse.h"
+#include "networkstatus.h"
+
+#include "test.h"
+#include "test_helpers.h"
+
+/* Generate a vote_routerstatus_t for a router with identity digest
+   <b>digest_in_hex</b>. */
+static vote_routerstatus_t *
+gen_vote_routerstatus_for_tests(const char *digest_in_hex, int is_guard)
+{
+  int retval;
+  vote_routerstatus_t *vrs = NULL;
+  routerstatus_t *rs;
+
+  vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
+  rs = &vrs->status;
+
+  { /* Useful information for tests */
+    char digest_tmp[DIGEST_LEN];
+
+    /* Guard or not? */
+    rs->is_possible_guard = is_guard;
+
+    /* Fill in the fpr */
+    tt_int_op(strlen(digest_in_hex), ==, HEX_DIGEST_LEN);
+    retval = base16_decode(digest_tmp, sizeof(digest_tmp),
+                           digest_in_hex, HEX_DIGEST_LEN);
+    tt_int_op(retval, ==, 0);
+    memcpy(rs->identity_digest, digest_tmp, DIGEST_LEN);
+  }
+
+  { /* Misc info (maybe not used in tests) */
+    vrs->version = tor_strdup("0.1.2.14");
+    strlcpy(rs->nickname, "router2", sizeof(rs->nickname));
+    memset(rs->descriptor_digest, 78, DIGEST_LEN);
+    rs->addr = 0x99008801;
+    rs->or_port = 443;
+    rs->dir_port = 8000;
+    /* all flags but running cleared */
+    rs->is_flagged_running = 1;
+    vrs->has_measured_bw = 1;
+    rs->has_bandwidth = 1;
+  }
+
+  return vrs;
+
+ done:
+  vote_routerstatus_free(vrs);
+
+  return NULL;
+}
+
+/** Make sure our parsers reject corrupted guardfraction files. */
+static void
+test_parse_guardfraction_file_bad(void *arg)
+{
+  int retval;
+  char *guardfraction_bad = NULL;
+  const char *yesterday_date_str = get_yesterday_date_str();
+
+  (void) arg;
+
+  /* Start parsing all those corrupted guardfraction files! */
+
+  /* Guardfraction file version is not a number! */
+  tor_asprintf(&guardfraction_bad,
+               "guardfraction-file-version nan\n"
+               "written-at %s\n"
+               "n-inputs 420 3\n"
+               "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n"
+               "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n",
+               yesterday_date_str);
+
+  retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
+  tt_int_op(retval, ==, -1);
+  tor_free(guardfraction_bad);
+
+  /* This one does not have a date! Parsing should fail. */
+  tor_asprintf(&guardfraction_bad,
+               "guardfraction-file-version 1\n"
+               "written-at not_date\n"
+               "n-inputs 420 3\n"
+               "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n"
+               "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n");
+
+  retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
+  tt_int_op(retval, ==, -1);
+  tor_free(guardfraction_bad);
+
+  /* This one has an incomplete n-inputs line, but parsing should
+     still continue. */
+  tor_asprintf(&guardfraction_bad,
+               "guardfraction-file-version 1\n"
+               "written-at %s\n"
+               "n-inputs biggie\n"
+               "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n"
+               "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n",
+               yesterday_date_str);
+
+  retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
+  tt_int_op(retval, ==, 2);
+  tor_free(guardfraction_bad);
+
+  /* This one does not have a fingerprint in the guard line! */
+  tor_asprintf(&guardfraction_bad,
+               "guardfraction-file-version 1\n"
+               "written-at %s\n"
+               "n-inputs 420 3\n"
+               "guard-seen not_a_fingerprint 100 420\n",
+               yesterday_date_str);
+
+  retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
+  tt_int_op(retval, ==, 0);
+  tor_free(guardfraction_bad);
+
+  /* This one does not even have an integer guardfraction value. */
+  tor_asprintf(&guardfraction_bad,
+               "guardfraction-file-version 1\n"
+               "written-at %s\n"
+               "n-inputs 420 3\n"
+               "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 NaN 420\n"
+               "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n",
+               yesterday_date_str);
+
+  retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
+  tt_int_op(retval, ==, 1);
+  tor_free(guardfraction_bad);
+
+  /* This one is not a percentage (not in [0, 100]) */
+  tor_asprintf(&guardfraction_bad,
+               "guardfraction-file-version 1\n"
+               "written-at %s\n"
+               "n-inputs 420 3\n"
+               "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 666 420\n"
+               "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n",
+               yesterday_date_str);
+
+  retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
+  tt_int_op(retval, ==, 1);
+  tor_free(guardfraction_bad);
+
+  /* This one is not a percentage either (not in [0, 100]) */
+  tor_asprintf(&guardfraction_bad,
+               "guardfraction-file-version 1\n"
+               "written-at %s\n"
+               "n-inputs 420 3\n"
+               "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 -3 420\n",
+               yesterday_date_str);
+
+  retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
+  tt_int_op(retval, ==, 0);
+
+ done:
+  tor_free(guardfraction_bad);
+}
+
+/* Make sure that our test guardfraction file gets parsed properly, and
+ * its information are applied properly to our routerstatuses. */
+static void
+test_parse_guardfraction_file_good(void *arg)
+{
+  int retval;
+  vote_routerstatus_t *vrs_guard = NULL;
+  vote_routerstatus_t *vrs_dummy = NULL;
+  char *guardfraction_good = NULL;
+  const char *yesterday_date_str = get_yesterday_date_str();
+  smartlist_t *routerstatuses = smartlist_new();
+
+  /* Some test values that we need to validate later */
+  const char fpr_guard[] = "D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777";
+  const char fpr_unlisted[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+  const int guardfraction_value = 42;
+
+  (void) arg;
+
+  {
+    /* Populate the smartlist with some fake routerstatuses, so that
+       after parsing the guardfraction file we can check that their
+       elements got filled properly. */
+
+    /* This one is a guard */
+    vrs_guard = gen_vote_routerstatus_for_tests(fpr_guard, 1);
+    tt_assert(vrs_guard);
+    smartlist_add(routerstatuses, vrs_guard);
+
+    /* This one is a guard but it's not in the guardfraction file */
+    vrs_dummy = gen_vote_routerstatus_for_tests(fpr_unlisted, 1);
+    tt_assert(vrs_dummy);
+    smartlist_add(routerstatuses, vrs_dummy);
+  }
+
+  tor_asprintf(&guardfraction_good,
+               "guardfraction-file-version 1\n"
+               "written-at %s\n"
+               "n-inputs 420 3\n"
+               "guard-seen %s %d 420\n",
+               yesterday_date_str,
+               fpr_guard, guardfraction_value);
+
+  /* Read the guardfraction file */
+  retval = dirserv_read_guardfraction_file_from_str(guardfraction_good,
+                                                    routerstatuses);
+  tt_int_op(retval, ==, 1);
+
+  { /* Test that routerstatus fields got filled properly */
+
+    /* The guardfraction fields of the guard should be filled. */
+    tt_assert(vrs_guard->status.has_guardfraction);
+    tt_int_op(vrs_guard->status.guardfraction_percentage,
+              ==,
+              guardfraction_value);
+
+    /* The guard that was not in the guardfraction file should not have
+       been touched either. */
+    tt_assert(!vrs_dummy->status.has_guardfraction);
+  }
+
+ done:
+  vote_routerstatus_free(vrs_guard);
+  vote_routerstatus_free(vrs_dummy);
+  smartlist_free(routerstatuses);
+  tor_free(guardfraction_good);
+}
+
+/** Make sure that the guardfraction bandwidths get calculated properly. */
+static void
+test_get_guardfraction_bandwidth(void *arg)
+{
+  guardfraction_bandwidth_t gf_bw;
+  const int orig_bw = 1000;
+
+  (void) arg;
+
+  /* A guard with bandwidth 1000 and GuardFraction 0.25, should have
+     bandwidth 250 as a guard and bandwidth 750 as a non-guard.  */
+  guard_get_guardfraction_bandwidth(&gf_bw,
+                                    orig_bw, 25);
+
+  tt_int_op(gf_bw.guard_bw, ==, 250);
+  tt_int_op(gf_bw.non_guard_bw, ==, 750);
+
+  /* Also check the 'guard_bw + non_guard_bw == original_bw'
+   * invariant. */
+  tt_int_op(gf_bw.non_guard_bw + gf_bw.guard_bw, ==, orig_bw);
+
+ done:
+  ;
+}
+
+/** Parse the GuardFraction element of the consensus, and make sure it
+ *  gets parsed correctly. */
+static void
+test_parse_guardfraction_consensus(void *arg)
+{
+  int retval;
+  or_options_t *options = get_options_mutable();
+
+  const char *guardfraction_str_good = "GuardFraction=66";
+  routerstatus_t rs_good;
+  routerstatus_t rs_no_guard;
+
+  const char *guardfraction_str_bad1 = "GuardFraction="; /* no value */
+  routerstatus_t rs_bad1;
+
+  const char *guardfraction_str_bad2 = "GuardFraction=166"; /* no percentage */
+  routerstatus_t rs_bad2;
+
+  (void) arg;
+
+  /* GuardFraction use is currently disabled by default. So we need to
+     manually enable it. */
+  options->UseGuardFraction = 1;
+
+  { /* Properly formatted GuardFraction. Check that it gets applied
+       correctly. */
+    memset(&rs_good, 0, sizeof(routerstatus_t));
+    rs_good.is_possible_guard = 1;
+
+    retval = routerstatus_parse_guardfraction(guardfraction_str_good,
+                                              NULL, NULL,
+                                              &rs_good);
+    tt_int_op(retval, ==, 0);
+    tt_assert(rs_good.has_guardfraction);
+    tt_int_op(rs_good.guardfraction_percentage, ==, 66);
+  }
+
+  { /* Properly formatted GuardFraction but router is not a
+       guard. GuardFraction should not get applied. */
+    memset(&rs_no_guard, 0, sizeof(routerstatus_t));
+    tt_assert(!rs_no_guard.is_possible_guard);
+
+    retval = routerstatus_parse_guardfraction(guardfraction_str_good,
+                                              NULL, NULL,
+                                              &rs_no_guard);
+    tt_int_op(retval, ==, 0);
+    tt_assert(!rs_no_guard.has_guardfraction);
+  }
+
+  { /* Bad GuardFraction. Function should fail and not apply. */
+    memset(&rs_bad1, 0, sizeof(routerstatus_t));
+    rs_bad1.is_possible_guard = 1;
+
+    retval = routerstatus_parse_guardfraction(guardfraction_str_bad1,
+                                              NULL, NULL,
+                                              &rs_bad1);
+    tt_int_op(retval, ==, -1);
+    tt_assert(!rs_bad1.has_guardfraction);
+  }
+
+  { /* Bad GuardFraction. Function should fail and not apply. */
+    memset(&rs_bad2, 0, sizeof(routerstatus_t));
+    rs_bad2.is_possible_guard = 1;
+
+    retval = routerstatus_parse_guardfraction(guardfraction_str_bad2,
+                                              NULL, NULL,
+                                              &rs_bad2);
+    tt_int_op(retval, ==, -1);
+    tt_assert(!rs_bad2.has_guardfraction);
+  }
+
+ done:
+  ;
+}
+
+/* Make sure that we use GuardFraction information when we should,
+   according to the torrc option and consensus parameter. */
+static void
+test_should_apply_guardfraction(void *arg)
+{
+  networkstatus_t vote_enabled, vote_disabled, vote_missing;
+  or_options_t *options = get_options_mutable();
+
+  (void) arg;
+
+  { /* Fill the votes for later */
+    /* This one suggests enabled GuardFraction. */
+    memset(&vote_enabled, 0, sizeof(vote_enabled));
+    vote_enabled.net_params = smartlist_new();
+    smartlist_split_string(vote_enabled.net_params,
+                           "UseGuardFraction=1", NULL, 0, 0);
+
+    /* This one suggests disabled GuardFraction. */
+    memset(&vote_disabled, 0, sizeof(vote_disabled));
+    vote_disabled.net_params = smartlist_new();
+    smartlist_split_string(vote_disabled.net_params,
+                           "UseGuardFraction=0", NULL, 0, 0);
+
+    /* This one doesn't have GuardFraction at all. */
+    memset(&vote_missing, 0, sizeof(vote_missing));
+    vote_missing.net_params = smartlist_new();
+    smartlist_split_string(vote_missing.net_params,
+                           "leon=trout", NULL, 0, 0);
+  }
+
+  /* If torrc option is set to yes, we should always use
+   * guardfraction.*/
+  options->UseGuardFraction = 1;
+  tt_int_op(should_apply_guardfraction(&vote_disabled), ==, 1);
+
+  /* If torrc option is set to no, we should never use
+   * guardfraction.*/
+  options->UseGuardFraction = 0;
+  tt_int_op(should_apply_guardfraction(&vote_enabled), ==, 0);
+
+  /* Now let's test torrc option set to auto. */
+  options->UseGuardFraction = -1;
+
+  /* If torrc option is set to auto, and consensus parameter is set to
+   * yes, we should use guardfraction. */
+  tt_int_op(should_apply_guardfraction(&vote_enabled), ==, 1);
+
+  /* If torrc option is set to auto, and consensus parameter is set to
+   * no, we should use guardfraction. */
+  tt_int_op(should_apply_guardfraction(&vote_disabled), ==, 0);
+
+  /* If torrc option is set to auto, and consensus parameter is not
+   * set, we should fallback to "no". */
+  tt_int_op(should_apply_guardfraction(&vote_missing), ==, 0);
+
+ done:
+  SMARTLIST_FOREACH(vote_enabled.net_params, char *, cp, tor_free(cp));
+  SMARTLIST_FOREACH(vote_disabled.net_params, char *, cp, tor_free(cp));
+  SMARTLIST_FOREACH(vote_missing.net_params, char *, cp, tor_free(cp));
+  smartlist_free(vote_enabled.net_params);
+  smartlist_free(vote_disabled.net_params);
+  smartlist_free(vote_missing.net_params);
+}
+
+struct testcase_t guardfraction_tests[] = {
+  { "parse_guardfraction_file_bad", test_parse_guardfraction_file_bad,
+    TT_FORK, NULL, NULL },
+  { "parse_guardfraction_file_good", test_parse_guardfraction_file_good,
+    TT_FORK, NULL, NULL },
+  { "parse_guardfraction_consensus", test_parse_guardfraction_consensus,
+    TT_FORK, NULL, NULL },
+  { "get_guardfraction_bandwidth", test_get_guardfraction_bandwidth,
+    TT_FORK, NULL, NULL },
+  { "should_apply_guardfraction", test_should_apply_guardfraction,
+    TT_FORK, NULL, NULL },
+
+  END_OF_TESTCASES
+};
+

+ 26 - 0
src/test/test_helpers.c

@@ -0,0 +1,26 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_helpers.c
+ * \brief Some helper functions to avoid code duplication in unit tests.
+ */
+
+#include "orconfig.h"
+#include "or.h"
+
+#include "test_helpers.h"
+
+/* Return a statically allocated string representing yesterday's date
+ * in ISO format. We use it so that state file items are not found to
+ * be outdated. */
+const char *
+get_yesterday_date_str(void)
+{
+  static char buf[ISO_TIME_LEN+1];
+
+  time_t yesterday = time(NULL) - 24*60*60;
+  format_iso_time(buf, yesterday);
+  return buf;
+}
+

+ 10 - 0
src/test/test_helpers.h

@@ -0,0 +1,10 @@
+/* Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_TEST_HELPERS_H
+#define TOR_TEST_HELPERS_H
+
+const char *get_yesterday_date_str(void);
+
+#endif
+

+ 2 - 0
src/test/testhelper.h

@@ -6,5 +6,7 @@
 
 void helper_setup_fake_routerlist(void);
 
+extern const char TEST_DESCRIPTORS[];
+
 #endif