diff --git a/changes/bug6363 b/changes/bug6363
new file mode 100644
index 0000000000000000000000000000000000000000..de99b72ac5114c9d3b6f1d21b7dea3775f7de55d
--- /dev/null
+++ b/changes/bug6363
@@ -0,0 +1,3 @@
+  o Major features:
+    - Directory authorities vote on IPv6 OR ports using new consensus
+      method 14. Implements ticket 6363.
diff --git a/src/or/dirserv.c b/src/or/dirserv.c
index f4ba02b4adc35c1a079a9588a8165c72b10482ab..5814171c5be947a2a76a167951623006baa5b93b 100644
--- a/src/or/dirserv.c
+++ b/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",
diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index b3de90b5c0811043ec224cbac139b86c226e1c69..9056cf59231af1eb04649a8fdb1db67cd8501176 100644
--- a/src/or/dirvote.c
+++ b/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;
 
diff --git a/src/or/routerparse.c b/src/or/routerparse.c
index 60a2eae75f63a699142c7fceafdda4655da0d56c..cda1c00664eac4e18503607a11da97dcfecfb296 100644
--- a/src/or/routerparse.c
+++ b/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;
diff --git a/src/test/test_dir.c b/src/test/test_dir.c
index 84705d0e0254aa28be0bef6ef5ac923f36b06fa0..af878a696f01ec6ec7ce3ff4d9dbcb72393b01e6 100644
--- a/src/test/test_dir.c
+++ b/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);