Browse Source

Have directory authorities vote on IPv6 OR ports according to the spec

Define new new consensus method 14 adding "a" lines to vote and
consensus documents.

From proposal 186:

  As with other data in the vote derived from the descriptor, the
  consensus will include whichever set of "a" lines are given by the
  most authorities who voted for the descriptor digest that will be
  used for the router.

This patch implements this.
Linus Nordberg 11 years ago
parent
commit
156ffef249
5 changed files with 131 additions and 23 deletions
  1. 3 0
      changes/bug6363
  2. 9 5
      src/or/dirserv.c
  3. 63 3
      src/or/dirvote.c
  4. 48 15
      src/or/routerparse.c
  5. 8 0
      src/test/test_dir.c

+ 3 - 0
changes/bug6363

@@ -0,0 +1,3 @@
+  o Major features:
+    - Directory authorities vote on IPv6 OR ports using new consensus
+      method 14. Implements ticket 6363.

+ 9 - 5
src/or/dirserv.c

@@ -2053,7 +2053,7 @@ version_from_platform(const char *platform)
  * non-NULL, add a "v" line for the platform.  Return 0 on success, -1 on
  * failure.
  *
- * The format argument has three possible values:
+ * 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
  *   NS_V3_CONSENSUS_MICRODESC - Output the first portion of a V3 microdesc
@@ -2092,17 +2092,18 @@ routerstatus_format_entry(char *buf, size_t buf_len,
     log_warn(LD_BUG, "Not enough space in buffer.");
     return -1;
   }
+  cp = buf + strlen(buf);
 
   /* 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 */
-  if (format == NS_V3_CONSENSUS || format == NS_V3_CONSENSUS_MICRODESC)
-    return 0;
 
-  cp = buf + strlen(buf);
+  /* V3 microdesc consensuses don't have "a" lines. */
+  if (format == NS_V3_CONSENSUS_MICRODESC)
+    return 0;
 
-  /* Possible "a" line, not included in consensus for now. */
+  /* Possible "a" line. At most one for now. */
   if (!tor_addr_is_null(&rs->ipv6_addr)) {
     const char *addr_str = fmt_and_decorate_addr(&rs->ipv6_addr);
     r = tor_snprintf(cp, buf_len - (cp-buf),
@@ -2116,6 +2117,9 @@ routerstatus_format_entry(char *buf, size_t buf_len,
     cp += strlen(cp);
   }
 
+  if (format == NS_V3_CONSENSUS)
+    return 0;
+
   /* NOTE: Whenever this list expands, be sure to increase MAX_FLAG_LINE_LEN*/
   r = tor_snprintf(cp, buf_len - (cp-buf),
                    "s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",

+ 63 - 3
src/or/dirvote.c

@@ -54,7 +54,7 @@ static int dirvote_publish_consensus(void);
 static char *make_consensus_method_list(int low, int high, const char *sep);
 
 /** The highest consensus method that we currently support. */
-#define MAX_SUPPORTED_CONSENSUS_METHOD 13
+#define MAX_SUPPORTED_CONSENSUS_METHOD 14
 
 /** Lowest consensus method that contains a 'directory-footer' marker */
 #define MIN_METHOD_FOR_FOOTER 9
@@ -76,6 +76,9 @@ static char *make_consensus_method_list(int low, int high, const char *sep);
  * with no microdesc. */
 #define MIN_METHOD_FOR_MANDATORY_MICRODESC 13
 
+/** Lowest consensus method that contains "a" lines. */
+#define MIN_METHOD_FOR_A_LINES 14
+
 /* =====
  * Voting
  * =====*/
@@ -430,6 +433,21 @@ _compare_vote_rs(const void **_a, const void **_b)
   return compare_vote_rs(a,b);
 }
 
+/** Helper for sorting OR ports. */
+static int
+_compare_orports(const void **_a, const void **_b)
+{
+  const tor_addr_port_t *a = *_a, *b = *_b;
+  int r;
+
+  if ((r = tor_addr_compare(&a->addr, &b->addr, CMP_EXACT)))
+    return r;
+  if ((r = (((int) b->port) - ((int) a->port))))
+    return r;
+
+  return 0;
+}
+
 /** Given a list of vote_routerstatus_t, all for the same router identity,
  * return whichever is most frequent, breaking ties in favor of more
  * recently published vote_routerstatus_t and in case of ties there,
@@ -437,7 +455,8 @@ _compare_vote_rs(const void **_a, const void **_b)
  */
 static vote_routerstatus_t *
 compute_routerstatus_consensus(smartlist_t *votes, int consensus_method,
-                               char *microdesc_digest256_out)
+                               char *microdesc_digest256_out,
+                               tor_addr_port_t *best_alt_orport_out)
 {
   vote_routerstatus_t *most = NULL, *cur = NULL;
   int most_n = 0, cur_n = 0;
@@ -473,6 +492,42 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method,
 
   tor_assert(most);
 
+  /* If we're producing "a" lines, vote on potential alternative (sets
+   * of) OR port(s) in the winning routerstatuses.
+   *
+   * XXX prop186 There's at most one alternative OR port (_the_ IPv6
+   * port) for now. */
+  if (consensus_method >= MIN_METHOD_FOR_A_LINES && best_alt_orport_out) {
+    smartlist_t *alt_orports = smartlist_new();
+    const tor_addr_port_t *most_alt_orport = NULL;
+
+    SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) {
+      if (compare_vote_rs(most, rs) == 0 &&
+          !tor_addr_is_null(&rs->status.ipv6_addr)
+          && rs->status.ipv6_orport) {
+        smartlist_add(alt_orports, tor_addr_port_alloc(&rs->status.ipv6_addr,
+                                                       rs->status.ipv6_orport));
+        log_debug(LD_DIR, "picking %s:%d (%s) for voting on \"a\" lines", /* FIXME: remove */
+                  fmt_and_decorate_addr(&rs->status.ipv6_addr), rs->status.ipv6_orport,
+                  rs->status.nickname);
+      }
+    } SMARTLIST_FOREACH_END(rs);
+
+    smartlist_sort(alt_orports, _compare_orports);
+    most_alt_orport = smartlist_get_most_frequent(alt_orports, _compare_orports);
+    if (most_alt_orport) {
+      memcpy(best_alt_orport_out, most_alt_orport, sizeof(tor_addr_port_t));
+
+      log_debug(LD_DIR, "\"a\" line winner for %s is %s:%d",
+                most->status.nickname,
+                fmt_and_decorate_addr(&most_alt_orport->addr),
+                most_alt_orport->port);
+    }
+
+    SMARTLIST_FOREACH(alt_orports, tor_addr_port_t *, ap, tor_free(ap));
+    smartlist_free(alt_orports);
+  }
+
   if (consensus_method >= MIN_METHOD_FOR_MICRODESC &&
       microdesc_digest256_out) {
     smartlist_t *digests = smartlist_new();
@@ -1685,6 +1740,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
       int n_listing = 0;
       int i;
       char microdesc_digest[DIGEST256_LEN];
+      tor_addr_port_t alt_orport = {TOR_ADDR_NULL, 0};
 
       /* Of the next-to-be-considered digest in each voter, which is first? */
       SMARTLIST_FOREACH(votes, networkstatus_t *, v, {
@@ -1754,7 +1810,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
        * routerinfo and its contents are. */
       memset(microdesc_digest, 0, sizeof(microdesc_digest));
       rs = compute_routerstatus_consensus(matching_descs, consensus_method,
-                                          microdesc_digest);
+                                          microdesc_digest, &alt_orport);
       /* Copy bits of that into rs_out. */
       memset(&rs_out, 0, sizeof(rs_out));
       tor_assert(fast_memeq(lowest_id, rs->status.identity_digest,DIGEST_LEN));
@@ -1765,6 +1821,10 @@ networkstatus_compute_consensus(smartlist_t *votes,
       rs_out.published_on = rs->status.published_on;
       rs_out.dir_port = rs->status.dir_port;
       rs_out.or_port = rs->status.or_port;
+      if (consensus_method >= MIN_METHOD_FOR_A_LINES) {
+        tor_addr_copy(&rs_out.ipv6_addr, &alt_orport.addr);
+        rs_out.ipv6_orport = alt_orport.port;
+      }
       rs_out.has_bandwidth = 0;
       rs_out.has_exitsummary = 0;
 

+ 48 - 15
src/or/routerparse.c

@@ -67,6 +67,7 @@ typedef enum {
   K_OR_ADDRESS,
   K_P,
   K_R,
+  K_A,
   K_S,
   K_V,
   K_W,
@@ -338,6 +339,7 @@ static token_rule_t extrainfo_token_table[] = {
 static token_rule_t rtrstatus_token_table[] = {
   T01("p",                   K_P,               CONCAT_ARGS, NO_OBJ ),
   T1( "r",                   K_R,                   GE(7),   NO_OBJ ),
+  T0N("a",                   K_A,                   GE(1),   NO_OBJ ),
   T1( "s",                   K_S,                   ARGS,    NO_OBJ ),
   T01("v",                   K_V,               CONCAT_ARGS, NO_OBJ ),
   T01("w",                   K_W,                   ARGS,    NO_OBJ ),
@@ -1257,6 +1259,42 @@ dump_distinct_digest_count(int severity)
 #endif
 }
 
+/** Try to find an IPv6 OR port in <b>list</b> of directory_token_t's
+ * with at least one argument (use GE(1) in setup). If found, store
+ * address and port number to <b>addr_out</b> and
+ * <b>port_out</b>. Return number of OR ports found. */
+static int
+find_single_ipv6_orport(const smartlist_t *list,
+                        tor_addr_t *addr_out,
+                        uint16_t *port_out)
+{
+  int ret = 0;
+  tor_assert(list != NULL);
+  tor_assert(addr_out != NULL);
+  tor_assert(port_out != NULL);
+
+  SMARTLIST_FOREACH_BEGIN(list, directory_token_t *, t) {
+    tor_addr_t a;
+    maskbits_t bits;
+    uint16_t port_min, port_max;
+    tor_assert(t->n_args >= 1);
+    /* XXXX Prop186 the full spec allows much more than this. */
+    if (tor_addr_parse_mask_ports(t->args[0], &a, &bits, &port_min,
+                                  &port_max) == AF_INET6 &&
+        bits == 128 &&
+        port_min == port_max) {
+      /* Okay, this is one we can understand. Use it and ignore
+         any potential more addresses in list. */
+      tor_addr_copy(addr_out, &a);
+      *port_out = port_min;
+      ret = 1;
+      break;
+    }
+  } SMARTLIST_FOREACH_END(t);
+
+  return ret;
+}
+
 /** Helper function: reads a single router entry from *<b>s</b> ...
  * *<b>end</b>.  Mallocs a new router and returns it if all goes well, else
  * returns NULL.  If <b>cache_copy</b> is true, duplicate the contents of
@@ -1513,21 +1551,8 @@ router_parse_entry_from_string(const char *s, const char *end,
   {
     smartlist_t *or_addresses = find_all_by_keyword(tokens, K_OR_ADDRESS);
     if (or_addresses) {
-      SMARTLIST_FOREACH_BEGIN(or_addresses, directory_token_t *, t) {
-        tor_addr_t a;
-        maskbits_t bits;
-        uint16_t port_min, port_max;
-        /* XXXX Prop186 the full spec allows much more than this. */
-        if (tor_addr_parse_mask_ports(t->args[0], &a, &bits, &port_min,
-                                      &port_max) == AF_INET6 &&
-            bits == 128 &&
-            port_min == port_max) {
-          /* Okay, this is one we can understand. */
-          tor_addr_copy(&router->ipv6_addr, &a);
-          router->ipv6_orport = port_min;
-          break;
-        }
-      } SMARTLIST_FOREACH_END(t);
+      find_single_ipv6_orport(or_addresses, &router->ipv6_addr,
+                              &router->ipv6_orport);
       smartlist_free(or_addresses);
     }
   }
@@ -2060,6 +2085,14 @@ routerstatus_parse_entry_from_string(memarea_t *area,
   rs->dir_port = (uint16_t) tor_parse_long(tok->args[7+offset],
                                            10,0,65535,NULL,NULL);
 
+  {
+    smartlist_t *a_lines = find_all_by_keyword(tokens, K_A);
+    if (a_lines) {
+      find_single_ipv6_orport(a_lines, &rs->ipv6_addr, &rs->ipv6_orport);
+      smartlist_free(a_lines);
+    }
+  }
+
   tok = find_opt_by_keyword(tokens, K_S);
   if (tok && vote) {
     int i;

+ 8 - 0
src/test/test_dir.c

@@ -797,6 +797,7 @@ test_dir_v3_networkstatus(void)
   networkstatus_t *vote=NULL, *v1=NULL, *v2=NULL, *v3=NULL, *con=NULL,
     *con_md=NULL;
   vote_routerstatus_t *vrs;
+  tor_addr_t addr_ipv6;
   routerstatus_t *rs;
   char *v1_text=NULL, *v2_text=NULL, *v3_text=NULL, *consensus_text=NULL, *cp;
   smartlist_t *votes = smartlist_new();
@@ -893,6 +894,9 @@ test_dir_v3_networkstatus(void)
   rs->addr = 0x99009901;
   rs->or_port = 443;
   rs->dir_port = 0;
+  tor_addr_parse(&addr_ipv6, "[1:2:3::4]");
+  tor_addr_copy(&rs->ipv6_addr, &addr_ipv6);
+  rs->ipv6_orport = 4711;
   rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running =
     rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1;
   smartlist_add(vote->routerstatus_list, vrs);
@@ -987,6 +991,8 @@ test_dir_v3_networkstatus(void)
   test_eq(rs->addr, 0x99009901);
   test_eq(rs->or_port, 443);
   test_eq(rs->dir_port, 0);
+  test_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6));
+  test_eq(rs->ipv6_orport, 4711);
   test_eq(vrs->flags, U64_LITERAL(254)); // all flags except "authority."
 
   {
@@ -1169,6 +1175,8 @@ test_dir_v3_networkstatus(void)
   test_eq(rs->addr, 0x99009901);
   test_eq(rs->or_port, 443);
   test_eq(rs->dir_port, 0);
+  test_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6));
+  test_eq(rs->ipv6_orport, 4711);
   test_assert(!rs->is_authority);
   test_assert(rs->is_exit);
   test_assert(rs->is_fast);