Browse Source

Merge remote-tracking branch 'dgoulet/ticket17242_032_03-squashed'

Nick Mathewson 6 years ago
parent
commit
91c6bc160b

+ 4 - 3
src/or/circuitlist.c

@@ -1961,8 +1961,8 @@ circuit_about_to_free(circuit_t *circ)
     int timed_out = (reason == END_CIRC_REASON_TIMEOUT);
     tor_assert(circ->state == CIRCUIT_STATE_OPEN);
     tor_assert(ocirc->build_state->chosen_exit);
-    tor_assert(ocirc->rend_data);
-    if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) {
+    if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT &&
+        ocirc->rend_data) {
       /* treat this like getting a nack from it */
       log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s",
           safe_str_client(rend_data_get_address(ocirc->rend_data)),
@@ -1978,7 +1978,8 @@ circuit_about_to_free(circuit_t *circ)
              reason != END_CIRC_REASON_TIMEOUT) {
     origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
     if (ocirc->build_state->chosen_exit && ocirc->rend_data) {
-      if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) {
+      if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT &&
+          ocirc->rend_data) {
         log_info(LD_REND, "Failed intro circ %s to %s "
             "(building circuit to intro point). "
             "Marking intro point as possibly unreachable.",

+ 1 - 0
src/or/circuitlist.h

@@ -13,6 +13,7 @@
 #define TOR_CIRCUITLIST_H
 
 #include "testsupport.h"
+#include "hs_ident.h"
 
 MOCK_DECL(smartlist_t *, circuit_get_global_list, (void));
 smartlist_t *circuit_get_global_origin_circuit_list(void);

+ 28 - 14
src/or/circuituse.c

@@ -337,7 +337,8 @@ circuit_get_best(const entry_connection_t *conn,
     /* Log an info message if we're going to launch a new intro circ in
      * parallel */
     if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
-        !must_be_open && origin_circ->hs_circ_has_timed_out) {
+        !must_be_open && origin_circ->hs_circ_has_timed_out &&
+        !circ->marked_for_close) {
         intro_going_on_but_too_old = 1;
         continue;
     }
@@ -650,6 +651,7 @@ circuit_expire_building(void)
            * because that's set when they switch purposes
            */
           if (TO_ORIGIN_CIRCUIT(victim)->rend_data ||
+              TO_ORIGIN_CIRCUIT(victim)->hs_ident ||
               victim->timestamp_dirty > cutoff.tv_sec)
             continue;
           break;
@@ -1636,7 +1638,7 @@ circuit_has_opened(origin_circuit_t *circ)
 
   switch (TO_CIRCUIT(circ)->purpose) {
     case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
-      rend_client_rendcirc_has_opened(circ);
+      hs_client_circuit_has_opened(circ);
       /* Start building an intro circ if we don't have one yet. */
       connection_ap_attach_pending(1);
       /* This isn't a call to circuit_try_attaching_streams because a
@@ -1648,7 +1650,7 @@ circuit_has_opened(origin_circuit_t *circ)
        * state. */
       break;
     case CIRCUIT_PURPOSE_C_INTRODUCING:
-      rend_client_introcirc_has_opened(circ);
+      hs_client_circuit_has_opened(circ);
       break;
     case CIRCUIT_PURPOSE_C_GENERAL:
       /* Tell any AP connections that have been waiting for a new
@@ -2174,22 +2176,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
     /* If this is a hidden service trying to start an introduction point,
      * handle that case. */
     if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
+      const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
       /* need to pick an intro point */
-      rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data;
-      tor_assert(rend_data);
-      extend_info = rend_client_get_random_intro(rend_data);
+      extend_info = hs_client_get_random_intro_from_edge(edge_conn);
       if (!extend_info) {
-        log_info(LD_REND,
-                 "No intro points for '%s': re-fetching service descriptor.",
-                 safe_str_client(rend_data_get_address(rend_data)));
-        rend_client_refetch_v2_renddesc(rend_data);
+        log_info(LD_REND, "No intro points: re-fetching service descriptor.");
+        if (edge_conn->rend_data) {
+          rend_client_refetch_v2_renddesc(edge_conn->rend_data);
+        } else {
+          hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
+        }
         connection_ap_mark_as_non_pending_circuit(conn);
         ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT;
         return 0;
       }
       log_info(LD_REND,"Chose %s as intro point for '%s'.",
                extend_info_describe(extend_info),
-               safe_str_client(rend_data_get_address(rend_data)));
+               (edge_conn->rend_data) ?
+               safe_str_client(rend_data_get_address(edge_conn->rend_data)) :
+               "service");
     }
 
     /* If we have specified a particular exit node for our
@@ -2308,8 +2313,15 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
       /* help predict this next time */
       rep_hist_note_used_internal(time(NULL), need_uptime, 1);
       if (circ) {
-        /* write the service_id into circ */
-        circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data);
+        const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
+        if (edge_conn->rend_data) {
+          /* write the service_id into circ */
+          circ->rend_data = rend_data_dup(edge_conn->rend_data);
+        } else if (edge_conn->hs_ident) {
+          circ->hs_ident =
+            hs_ident_circuit_new(&edge_conn->hs_ident->identity_pk,
+                                 HS_IDENT_CIRCUIT_INTRO);
+        }
         if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
             circ->base_.state == CIRCUIT_STATE_OPEN)
           circuit_has_opened(circ);
@@ -2737,12 +2749,14 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
 
       tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
       if (introcirc->base_.state == CIRCUIT_STATE_OPEN) {
+        int ret;
         log_info(LD_REND,"found open intro circ %u (rend %u); sending "
                  "introduction. (stream %d sec old)",
                  (unsigned)introcirc->base_.n_circ_id,
                  (unsigned)rendcirc->base_.n_circ_id,
                  conn_age);
-        switch (rend_client_send_introduction(introcirc, rendcirc)) {
+        ret = hs_client_send_introduce1(introcirc, rendcirc);
+        switch (ret) {
         case 0: /* success */
           rendcirc->base_.timestamp_dirty = time(NULL);
           introcirc->base_.timestamp_dirty = time(NULL);

+ 21 - 0
src/or/connection.c

@@ -4102,6 +4102,27 @@ connection_write_to_buf_impl_,(const char *string, size_t len,
   }
 }
 
+#define CONN_GET_ALL_TEMPLATE(var, test) \
+  STMT_BEGIN \
+    smartlist_t *conns = get_connection_array();   \
+    smartlist_t *ret_conns = smartlist_new();     \
+    SMARTLIST_FOREACH_BEGIN(conns, connection_t *, var) { \
+      if (var && (test) && !var->marked_for_close) \
+        smartlist_add(ret_conns, var); \
+    } SMARTLIST_FOREACH_END(var);                                            \
+    return ret_conns; \
+  STMT_END
+
+/* Return a list of connections that aren't close and matches the given state.
+ * The returned list can be empty and must be freed using smartlist_free().
+ * The caller does NOT have owernship of the objects in the list so it must
+ * not free them nor reference them as they can disapear. */
+smartlist_t *
+connection_list_by_type_state(int type, int state)
+{
+  CONN_GET_ALL_TEMPLATE(conn, (conn->type == type && conn->state == state));
+}
+
 /** Return a connection_t * from get_connection_array() that satisfies test on
  * var, and that is not marked for close. */
 #define CONN_GET_TEMPLATE(var, test)               \

+ 1 - 0
src/or/connection.h

@@ -182,6 +182,7 @@ MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type,
 connection_t *connection_get_by_type_state(int type, int state);
 connection_t *connection_get_by_type_state_rendquery(int type, int state,
                                                      const char *rendquery);
+smartlist_t *connection_list_by_type_state(int type, int state);
 smartlist_t *connection_dir_list_by_purpose_and_resource(
                                                   int purpose,
                                                   const char *resource);

+ 195 - 118
src/or/connection_edge.c

@@ -76,6 +76,8 @@
 #include "dirserv.h"
 #include "hibernate.h"
 #include "hs_common.h"
+#include "hs_cache.h"
+#include "hs_client.h"
 #include "hs_circuit.h"
 #include "main.h"
 #include "nodelist.h"
@@ -153,7 +155,9 @@ connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason,
    * but we should fix it someday anyway. */
   if ((edge_conn->on_circuit != NULL || edge_conn->edge_has_sent_end) &&
       connection_edge_is_rendezvous_stream(edge_conn)) {
-    rend_client_note_connection_attempt_ended(edge_conn->rend_data);
+    if (edge_conn->rend_data) {
+      rend_client_note_connection_attempt_ended(edge_conn->rend_data);
+    }
   }
 
   if (base_conn->marked_for_close) {
@@ -1392,6 +1396,180 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
   }
 }
 
+/** We just received a SOCKS request in <b>conn</b> to an onion address of type
+ *  <b>addresstype</b>. Start connecting to the onion service. */
+static int
+connection_ap_handle_onion(entry_connection_t *conn,
+                           socks_request_t *socks,
+                           origin_circuit_t *circ,
+                           hostname_type_t addresstype)
+{
+  time_t now = approx_time();
+  connection_t *base_conn = ENTRY_TO_CONN(conn);
+
+  /* If .onion address requests are disabled, refuse the request */
+  if (!conn->entry_cfg.onion_traffic) {
+    log_warn(LD_APP, "Onion address %s requested from a port with .onion "
+             "disabled", safe_str_client(socks->address));
+    connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+    return -1;
+  }
+
+  /* Check whether it's RESOLVE or RESOLVE_PTR.  We don't handle those
+   * for hidden service addresses. */
+  if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
+    /* if it's a resolve request, fail it right now, rather than
+     * building all the circuits and then realizing it won't work. */
+    log_warn(LD_APP,
+             "Resolve requests to hidden services not allowed. Failing.");
+    connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,
+                                           0,NULL,-1,TIME_MAX);
+    connection_mark_unattached_ap(conn,
+                               END_STREAM_REASON_SOCKSPROTOCOL |
+                               END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
+    return -1;
+  }
+
+  /* If we were passed a circuit, then we need to fail.  .onion addresses
+   * only work when we launch our own circuits for now. */
+  if (circ) {
+    log_warn(LD_CONTROL, "Attachstream to a circuit is not "
+             "supported for .onion addresses currently. Failing.");
+    connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
+    return -1;
+  }
+
+  /* Interface: Regardless of HS version after the block below we should have
+     set onion_address, rend_cache_lookup_result, and descriptor_is_usable. */
+  const char *onion_address = NULL;
+  int rend_cache_lookup_result = -ENOENT;
+  int descriptor_is_usable = 0;
+
+  if (addresstype == ONION_V2_HOSTNAME) { /* it's a v2 hidden service */
+    rend_cache_entry_t *entry = NULL;
+    /* Look up if we have client authorization configured for this hidden
+     * service.  If we do, associate it with the rend_data. */
+    rend_service_authorization_t *client_auth =
+      rend_client_lookup_service_authorization(socks->address);
+
+    const uint8_t *cookie = NULL;
+    rend_auth_type_t auth_type = REND_NO_AUTH;
+    if (client_auth) {
+      log_info(LD_REND, "Using previously configured client authorization "
+               "for hidden service request.");
+      auth_type = client_auth->auth_type;
+      cookie = client_auth->descriptor_cookie;
+    }
+
+    /* Fill in the rend_data field so we can start doing a connection to
+     * a hidden service. */
+    rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data =
+      rend_data_client_create(socks->address, NULL, (char *) cookie,
+                              auth_type);
+    if (rend_data == NULL) {
+      return -1;
+    }
+    onion_address = rend_data_get_address(rend_data);
+    log_info(LD_REND,"Got a hidden service request for ID '%s'",
+             safe_str_client(onion_address));
+
+    rend_cache_lookup_result = rend_cache_lookup_entry(onion_address,-1,
+                                                       &entry);
+    if (!rend_cache_lookup_result && entry) {
+      descriptor_is_usable = rend_client_any_intro_points_usable(entry);
+    }
+  } else { /* it's a v3 hidden service */
+    tor_assert(addresstype == ONION_V3_HOSTNAME);
+    const hs_descriptor_t *cached_desc = NULL;
+    int retval;
+    /* Create HS conn identifier with HS pubkey */
+    hs_ident_edge_conn_t *hs_conn_ident =
+      tor_malloc_zero(sizeof(hs_ident_edge_conn_t));
+
+    retval = hs_parse_address(socks->address, &hs_conn_ident->identity_pk,
+                              NULL, NULL);
+    if (retval < 0) {
+      log_warn(LD_GENERAL, "failed to parse hs address");
+      tor_free(hs_conn_ident);
+      return -1;
+    }
+    ENTRY_TO_EDGE_CONN(conn)->hs_ident = hs_conn_ident;
+
+    onion_address = socks->address;
+
+    /* Check the v3 desc cache */
+    cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk);
+    if (cached_desc) {
+      rend_cache_lookup_result = 0;
+      descriptor_is_usable =
+        hs_client_any_intro_points_usable(&hs_conn_ident->identity_pk,
+                                          cached_desc);
+      log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.",
+               (descriptor_is_usable) ? "usable" : "unusable",
+               safe_str_client(onion_address),
+               (descriptor_is_usable) ? "Not fetching." : "Refecting.");
+    } else {
+      rend_cache_lookup_result = -ENOENT;
+    }
+  }
+
+  /* Lookup the given onion address. If invalid, stop right now.
+   * Otherwise, we might have it in the cache or not. */
+  unsigned int refetch_desc = 0;
+  if (rend_cache_lookup_result < 0) {
+    switch (-rend_cache_lookup_result) {
+    case EINVAL:
+      /* We should already have rejected this address! */
+      log_warn(LD_BUG,"Invalid service name '%s'",
+               safe_str_client(onion_address));
+      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
+      return -1;
+    case ENOENT:
+      /* We didn't have this; we should look it up. */
+      log_info(LD_REND, "No descriptor found in our cache for %s. Fetching.",
+               safe_str_client(onion_address));
+      refetch_desc = 1;
+      break;
+    default:
+      log_warn(LD_BUG, "Unknown cache lookup error %d",
+               rend_cache_lookup_result);
+      return -1;
+    }
+  }
+
+  /* Help predict that we'll want to do hidden service circuits in the
+   * future. We're not sure if it will need a stable circuit yet, but
+   * we know we'll need *something*. */
+  rep_hist_note_used_internal(now, 0, 1);
+
+  /* Now we have a descriptor but is it usable or not? If not, refetch.
+   * Also, a fetch could have been requested if the onion address was not
+   * found in the cache previously. */
+  if (refetch_desc || !descriptor_is_usable) {
+    edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
+    connection_ap_mark_as_non_pending_circuit(conn);
+    base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
+    if (addresstype == ONION_V2_HOSTNAME) {
+      tor_assert(edge_conn->rend_data);
+      rend_client_refetch_v2_renddesc(edge_conn->rend_data);
+    } else {
+      tor_assert(addresstype == ONION_V3_HOSTNAME);
+      tor_assert(edge_conn->hs_ident);
+      hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
+    }
+    return 0;
+  }
+
+  /* We have the descriptor!  So launch a connection to the HS. */
+  base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
+  log_info(LD_REND, "Descriptor is here. Great.");
+
+  /* We'll try to attach it at the next event loop, or whenever
+   * we call connection_ap_attach_pending() */
+  connection_ap_mark_as_pending_circuit(conn);
+  return 0;
+}
+
 /** Connection <b>conn</b> just finished its socks handshake, or the
  * controller asked us to take care of it. If <b>circ</b> is defined,
  * then that's where we'll want to attach it. Otherwise we have to
@@ -1558,7 +1736,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
   }
 
   /* Now, we handle everything that isn't a .onion address. */
-  if (addresstype != ONION_HOSTNAME) {
+  if (addresstype != ONION_V2_HOSTNAME && addresstype != ONION_V3_HOSTNAME) {
     /* Not a hidden-service request.  It's either a hostname or an IP,
      * possibly with a .exit that we stripped off.  We're going to check
      * if we're allowed to connect/resolve there, and then launch the
@@ -1836,116 +2014,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
     return 0;
   } else {
     /* If we get here, it's a request for a .onion address! */
+    tor_assert(addresstype == ONION_V2_HOSTNAME ||
+               addresstype == ONION_V3_HOSTNAME);
     tor_assert(!automap);
-
-    /* If .onion address requests are disabled, refuse the request */
-    if (!conn->entry_cfg.onion_traffic) {
-      log_warn(LD_APP, "Onion address %s requested from a port with .onion "
-                       "disabled", safe_str_client(socks->address));
-      connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
-      return -1;
-    }
-
-    /* Check whether it's RESOLVE or RESOLVE_PTR.  We don't handle those
-     * for hidden service addresses. */
-    if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
-      /* if it's a resolve request, fail it right now, rather than
-       * building all the circuits and then realizing it won't work. */
-      log_warn(LD_APP,
-               "Resolve requests to hidden services not allowed. Failing.");
-      connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,
-                                             0,NULL,-1,TIME_MAX);
-      connection_mark_unattached_ap(conn,
-                                END_STREAM_REASON_SOCKSPROTOCOL |
-                                END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
-      return -1;
-    }
-
-    /* If we were passed a circuit, then we need to fail.  .onion addresses
-     * only work when we launch our own circuits for now. */
-    if (circ) {
-      log_warn(LD_CONTROL, "Attachstream to a circuit is not "
-               "supported for .onion addresses currently. Failing.");
-      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
-      return -1;
-    }
-
-    /* Look up if we have client authorization configured for this hidden
-     * service.  If we do, associate it with the rend_data. */
-    rend_service_authorization_t *client_auth =
-      rend_client_lookup_service_authorization(socks->address);
-
-    const uint8_t *cookie = NULL;
-    rend_auth_type_t auth_type = REND_NO_AUTH;
-    if (client_auth) {
-      log_info(LD_REND, "Using previously configured client authorization "
-               "for hidden service request.");
-      auth_type = client_auth->auth_type;
-      cookie = client_auth->descriptor_cookie;
-    }
-
-    /* Fill in the rend_data field so we can start doing a connection to
-     * a hidden service. */
-    rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data =
-      rend_data_client_create(socks->address, NULL, (char *) cookie,
-                              auth_type);
-    if (rend_data == NULL) {
-      return -1;
-    }
-    const char *onion_address = rend_data_get_address(rend_data);
-    log_info(LD_REND,"Got a hidden service request for ID '%s'",
-             safe_str_client(onion_address));
-
-    /* Lookup the given onion address. If invalid, stop right now.
-     * Otherwise, we might have it in the cache or not. */
-    unsigned int refetch_desc = 0;
-    rend_cache_entry_t *entry = NULL;
-    const int rend_cache_lookup_result =
-      rend_cache_lookup_entry(onion_address, -1, &entry);
-    if (rend_cache_lookup_result < 0) {
-      switch (-rend_cache_lookup_result) {
-      case EINVAL:
-        /* We should already have rejected this address! */
-        log_warn(LD_BUG,"Invalid service name '%s'",
-                 safe_str_client(onion_address));
-        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
-        return -1;
-      case ENOENT:
-        /* We didn't have this; we should look it up. */
-        refetch_desc = 1;
-        break;
-      default:
-        log_warn(LD_BUG, "Unknown cache lookup error %d",
-            rend_cache_lookup_result);
-        return -1;
-      }
-    }
-
-    /* Help predict that we'll want to do hidden service circuits in the
-     * future. We're not sure if it will need a stable circuit yet, but
-     * we know we'll need *something*. */
-    rep_hist_note_used_internal(now, 0, 1);
-
-    /* Now we have a descriptor but is it usable or not? If not, refetch.
-     * Also, a fetch could have been requested if the onion address was not
-     * found in the cache previously. */
-    if (refetch_desc || !rend_client_any_intro_points_usable(entry)) {
-      connection_ap_mark_as_non_pending_circuit(conn);
-      base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
-      log_info(LD_REND, "Unknown descriptor %s. Fetching.",
-               safe_str_client(onion_address));
-      rend_client_refetch_v2_renddesc(rend_data);
-      return 0;
-    }
-
-    /* We have the descriptor!  So launch a connection to the HS. */
-    base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
-    log_info(LD_REND, "Descriptor is here. Great.");
-
-    /* We'll try to attach it at the next event loop, or whenever
-     * we call connection_ap_attach_pending() */
-    connection_ap_mark_as_pending_circuit(conn);
-    return 0;
+    return connection_ap_handle_onion(conn, socks, circ, addresstype);
   }
 
   return 0; /* unreached but keeps the compiler happy */
@@ -3679,10 +3751,12 @@ connection_ap_can_use_exit(const entry_connection_t *conn,
 }
 
 /** If address is of the form "y.onion" with a well-formed handle y:
- *     Put a NUL after y, lower-case it, and return ONION_HOSTNAME.
+ *     Put a NUL after y, lower-case it, and return ONION_V2_HOSTNAME or
+ *     ONION_V3_HOSTNAME depending on the HS version.
  *
  *  If address is of the form "x.y.onion" with a well-formed handle x:
- *     Drop "x.", put a NUL after y, lower-case it, and return ONION_HOSTNAME.
+ *     Drop "x.", put a NUL after y, lower-case it, and return
+ *     ONION_V2_HOSTNAME or ONION_V3_HOSTNAME depending on the HS version.
  *
  * If address is of the form "y.onion" with a badly-formed handle y:
  *     Return BAD_HOSTNAME and log a message.
@@ -3698,7 +3772,7 @@ parse_extended_hostname(char *address)
 {
     char *s;
     char *q;
-    char query[REND_SERVICE_ID_LEN_BASE32+1];
+    char query[HS_SERVICE_ADDR_LEN_BASE32+1];
 
     s = strrchr(address,'.');
     if (!s)
@@ -3718,14 +3792,17 @@ parse_extended_hostname(char *address)
       goto failed; /* reject sub-domain, as DNS does */
     }
     q = (NULL == q) ? address : q + 1;
-    if (strlcpy(query, q, REND_SERVICE_ID_LEN_BASE32+1) >=
-        REND_SERVICE_ID_LEN_BASE32+1)
+    if (strlcpy(query, q, HS_SERVICE_ADDR_LEN_BASE32+1) >=
+        HS_SERVICE_ADDR_LEN_BASE32+1)
       goto failed;
     if (q != address) {
       memmove(address, q, strlen(q) + 1 /* also get \0 */);
     }
-    if (rend_valid_service_id(query)) {
-      return ONION_HOSTNAME; /* success */
+    if (rend_valid_v2_service_id(query)) {
+      return ONION_V2_HOSTNAME; /* success */
+    }
+    if (hs_address_is_valid(query)) {
+      return ONION_V3_HOSTNAME;
     }
  failed:
     /* otherwise, return to previous state and return 0 */

+ 2 - 1
src/or/connection_edge.h

@@ -98,7 +98,8 @@ int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
 
 /** Possible return values for parse_extended_hostname. */
 typedef enum hostname_type_t {
-  NORMAL_HOSTNAME, ONION_HOSTNAME, EXIT_HOSTNAME, BAD_HOSTNAME
+  NORMAL_HOSTNAME, ONION_V2_HOSTNAME, ONION_V3_HOSTNAME,
+  EXIT_HOSTNAME, BAD_HOSTNAME
 } hostname_type_t;
 hostname_type_t parse_extended_hostname(char *address);
 

+ 2 - 2
src/or/control.c

@@ -4140,7 +4140,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
   /* Extract the first argument (either HSAddress or DescID). */
   arg1 = smartlist_get(args, 0);
   /* Test if it's an HS address without the .onion part. */
-  if (rend_valid_service_id(arg1)) {
+  if (rend_valid_v2_service_id(arg1)) {
     hsaddress = arg1;
   } else if (strcmpstart(arg1, v2_str) == 0 &&
              rend_valid_descriptor_id(arg1 + v2_str_len) &&
@@ -4779,7 +4779,7 @@ handle_control_del_onion(control_connection_t *conn,
     return 0;
 
   const char *service_id = smartlist_get(args, 0);
-  if (!rend_valid_service_id(service_id)) {
+  if (!rend_valid_v2_service_id(service_id)) {
     connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n");
     goto out;
   }

+ 110 - 19
src/or/directory.c

@@ -25,6 +25,7 @@
 #include "geoip.h"
 #include "hs_cache.h"
 #include "hs_common.h"
+#include "hs_client.h"
 #include "main.h"
 #include "microdesc.h"
 #include "networkstatus.h"
@@ -183,6 +184,7 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
     case DIR_PURPOSE_FETCH_EXTRAINFO:
     case DIR_PURPOSE_FETCH_MICRODESC:
       return 0;
+    case DIR_PURPOSE_HAS_FETCHED_HSDESC:
     case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2:
     case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
     case DIR_PURPOSE_FETCH_RENDDESC_V2:
@@ -1125,6 +1127,7 @@ directory_request_new(uint8_t dir_purpose)
   tor_assert(dir_purpose <= DIR_PURPOSE_MAX_);
   tor_assert(dir_purpose != DIR_PURPOSE_SERVER);
   tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2);
+  tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_HSDESC);
 
   directory_request_t *result = tor_malloc_zero(sizeof(*result));
   tor_addr_make_null(&result->or_addr_port.addr, AF_INET);
@@ -1289,6 +1292,20 @@ directory_request_upload_set_hs_ident(directory_request_t *req,
   }
   req->hs_ident = ident;
 }
+/**
+ * Set an object containing HS connection identifier to be associated with
+ * this fetch request. Note that only an alias to <b>ident</b> is stored, so
+ * the <b>ident</b> object must outlive the request.
+ */
+void
+directory_request_fetch_set_hs_ident(directory_request_t *req,
+                                     const hs_ident_dir_conn_t *ident)
+{
+  if (ident) {
+    tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC);
+  }
+  req->hs_ident = ident;
+}
 /** Set a static circuit_guard_state_t object to affliate with the request in
  * <b>req</b>.  This object will receive notification when the attempt to
  * connect to the guard either succeeds or fails. */
@@ -1859,6 +1876,13 @@ directory_send_command(dir_connection_t *conn,
       httpcommand = "GET";
       tor_asprintf(&url, "/tor/rendezvous2/%s", resource);
       break;
+    case DIR_PURPOSE_FETCH_HSDESC:
+      tor_assert(resource);
+      tor_assert(strlen(resource) <= ED25519_BASE64_LEN);
+      tor_assert(!payload);
+      httpcommand = "GET";
+      tor_asprintf(&url, "/tor/hs/3/%s", resource);
+      break;
     case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
       tor_assert(!resource);
       tor_assert(payload);
@@ -2193,16 +2217,6 @@ load_downloaded_routers(const char *body, smartlist_t *which,
   return added;
 }
 
-/** A structure to hold arguments passed into each directory response
- * handler */
-typedef struct response_handler_args_t {
-  int status_code;
-  const char *reason;
-  const char *body;
-  size_t body_len;
-  const char *headers;
-} response_handler_args_t;
-
 static int handle_response_fetch_consensus(dir_connection_t *,
                                            const response_handler_args_t *);
 static int handle_response_fetch_certificate(dir_connection_t *,
@@ -2530,6 +2544,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
     case DIR_PURPOSE_UPLOAD_HSDESC:
       rv = handle_response_upload_hsdesc(conn, &args);
       break;
+    case DIR_PURPOSE_FETCH_HSDESC:
+      rv = handle_response_fetch_hsdesc_v3(conn, &args);
+      break;
     default:
       tor_assert_nonfatal_unreached();
       rv = -1;
@@ -3074,6 +3091,60 @@ handle_response_upload_signatures(dir_connection_t *conn,
   return 0;
 }
 
+/**
+ * Handler function: processes a response to a request for a v3 hidden service
+ * descriptor.
+ **/
+STATIC int
+handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
+                                const response_handler_args_t *args)
+{
+  const int status_code = args->status_code;
+  const char *reason = args->reason;
+  const char *body = args->body;
+  const size_t body_len = args->body_len;
+
+  tor_assert(conn->hs_ident);
+
+  log_info(LD_REND,"Received v3 hsdesc (body size %d, status %d (%s))",
+           (int)body_len, status_code, escaped(reason));
+
+  switch (status_code) {
+  case 200:
+    /* We got something: Try storing it in the cache. */
+    if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) {
+      log_warn(LD_REND, "Failed to store hidden service descriptor");
+    } else {
+      log_info(LD_REND, "Stored hidden service descriptor successfully.");
+      TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC;
+      hs_client_desc_has_arrived(conn->hs_ident);
+    }
+    break;
+  case 404:
+    /* Not there. We'll retry when connection_about_to_close_connection()
+     * tries to clean this conn up. */
+    log_info(LD_REND, "Fetching hidden service v3 descriptor not found: "
+                      "Retrying at another directory.");
+    /* TODO: Inform the control port */
+    break;
+  case 400:
+    log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: "
+                      "http status 400 (%s). Dirserver didn't like our "
+                      "query? Retrying at another directory.",
+             escaped(reason));
+    break;
+  default:
+    log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: "
+             "http status %d (%s) response unexpected from HSDir server "
+             "'%s:%d'. Retrying at another directory.",
+             status_code, escaped(reason), TO_CONN(conn)->address,
+             TO_CONN(conn)->port);
+    break;
+  }
+
+  return 0;
+}
+
 /**
  * Handler function: processes a response to a request for a v2 hidden service
  * descriptor.
@@ -3338,6 +3409,33 @@ connection_dir_process_inbuf(dir_connection_t *conn)
   return 0;
 }
 
+/** We are closing a dir connection: If <b>dir_conn</b> is a dir connection
+ *  that tried to fetch an HS descriptor, check if it successfuly fetched it,
+ *  or if we need to try again. */
+static void
+refetch_hsdesc_if_needed(dir_connection_t *dir_conn)
+{
+  connection_t *conn = TO_CONN(dir_conn);
+
+  /* If we were trying to fetch a v2 rend desc and did not succeed, retry as
+   * needed. (If a fetch is successful, the connection state is changed to
+   * DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 or DIR_PURPOSE_HAS_FETCHED_HSDESC to
+   * mark that refetching is unnecessary.) */
+  if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
+      dir_conn->rend_data &&
+      rend_valid_v2_service_id(
+           rend_data_get_address(dir_conn->rend_data))) {
+    rend_client_refetch_v2_renddesc(dir_conn->rend_data);
+  }
+
+  /* Check for v3 rend desc fetch */
+  if (conn->purpose == DIR_PURPOSE_FETCH_HSDESC &&
+      dir_conn->hs_ident &&
+      !ed25519_public_key_is_zero(&dir_conn->hs_ident->identity_pk)) {
+    hs_client_refetch_hsdesc(&dir_conn->hs_ident->identity_pk);
+  }
+}
+
 /** Called when we're about to finally unlink and free a directory connection:
  * perform necessary accounting and cleanup */
 void
@@ -3350,15 +3448,8 @@ connection_dir_about_to_close(dir_connection_t *dir_conn)
      * failed: forget about this router, and maybe try again. */
     connection_dir_request_failed(dir_conn);
   }
-  /* If we were trying to fetch a v2 rend desc and did not succeed,
-   * retry as needed. (If a fetch is successful, the connection state
-   * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that
-   * refetching is unnecessary.) */
-  if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
-      dir_conn->rend_data &&
-      strlen(rend_data_get_address(dir_conn->rend_data)) ==
-             REND_SERVICE_ID_LEN_BASE32)
-    rend_client_refetch_v2_renddesc(dir_conn->rend_data);
+
+  refetch_hsdesc_if_needed(dir_conn);
 }
 
 /** Create an http response for the client <b>conn</b> out of

+ 17 - 1
src/or/directory.h

@@ -75,6 +75,8 @@ void directory_request_set_rend_query(directory_request_t *req,
                                       const rend_data_t *query);
 void directory_request_upload_set_hs_ident(directory_request_t *req,
                                            const hs_ident_dir_conn_t *ident);
+void directory_request_fetch_set_hs_ident(directory_request_t *req,
+                                          const hs_ident_dir_conn_t *ident);
 
 void directory_request_set_routerstatus(directory_request_t *req,
                                         const routerstatus_t *rs);
@@ -168,6 +170,16 @@ int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
 
 #ifdef DIRECTORY_PRIVATE
 
+/** A structure to hold arguments passed into each directory response
+ * handler */
+typedef struct response_handler_args_t {
+  int status_code;
+  const char *reason;
+  const char *body;
+  size_t body_len;
+  const char *headers;
+} response_handler_args_t;
+
 struct get_handler_args_t;
 STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn,
                                        const struct get_handler_args_t *args);
@@ -176,10 +188,14 @@ STATIC char *accept_encoding_header(void);
 STATIC int allowed_anonymous_connection_compression_method(compress_method_t);
 STATIC void warn_disallowed_anonymous_compression_method(compress_method_t);
 
+typedef struct response_handler_args_t response_handler_args_t;
+STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
+                                          const response_handler_args_t *args);
+
 #endif
 
 #ifdef TOR_UNIT_TESTS
-/* Used only by test_dir.c */
+/* Used only by test_dir.c and test_hs_cache.c */
 
 STATIC int parse_http_url(const char *headers, char **url);
 STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose,

+ 486 - 10
src/or/hs_cache.c

@@ -9,15 +9,19 @@
 /* For unit tests.*/
 #define HS_CACHE_PRIVATE
 
-#include "hs_cache.h"
-
 #include "or.h"
 #include "config.h"
+#include "hs_ident.h"
 #include "hs_common.h"
+#include "hs_client.h"
 #include "hs_descriptor.h"
 #include "networkstatus.h"
 #include "rendcache.h"
 
+#include "hs_cache.h"
+
+/********************** Directory HS cache ******************/
+
 /* Directory descriptor cache. Map indexed by blinded key. */
 static digest256map_t *hs_cache_v3_dir;
 
@@ -98,7 +102,7 @@ cache_dir_desc_new(const char *desc)
 
 /* Return the size of a cache entry in bytes. */
 static size_t
-cache_get_entry_size(const hs_cache_dir_descriptor_t *entry)
+cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry)
 {
   return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data)
           + strlen(entry->encoded_desc));
@@ -134,7 +138,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
      * remove the entry we currently have from our cache so we can then
      * store the new one. */
     remove_v3_desc_as_dir(cache_entry);
-    rend_cache_decrement_allocation(cache_get_entry_size(cache_entry));
+    rend_cache_decrement_allocation(cache_get_dir_entry_size(cache_entry));
     cache_dir_desc_free(cache_entry);
   }
   /* Store the descriptor we just got. We are sure here that either we
@@ -144,7 +148,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
 
   /* Update our total cache size with this entry for the OOM. This uses the
    * old HS protocol cache subsystem for which we are tied with. */
-  rend_cache_increment_allocation(cache_get_entry_size(desc));
+  rend_cache_increment_allocation(cache_get_dir_entry_size(desc));
 
   /* XXX: Update HS statistics. We should have specific stats for v3. */
 
@@ -221,7 +225,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff)
     }
     /* Here, our entry has expired, remove and free. */
     MAP_DEL_CURRENT(key);
-    entry_size = cache_get_entry_size(entry);
+    entry_size = cache_get_dir_entry_size(entry);
     bytes_removed += entry_size;
     /* Entry is not in the cache anymore, destroy it. */
     cache_dir_desc_free(entry);
@@ -315,6 +319,468 @@ hs_cache_clean_as_dir(time_t now)
   cache_clean_v3_as_dir(now, 0);
 }
 
+/********************** Client-side HS cache ******************/
+
+/* Client-side HS descriptor cache. Map indexed by service identity key. */
+static digest256map_t *hs_cache_v3_client;
+
+/* Client-side introduction point state cache. Map indexed by service public
+ * identity key (onion address). It contains hs_cache_client_intro_state_t
+ * objects all related to a specific service. */
+static digest256map_t *hs_cache_client_intro_state;
+
+/* Return the size of a client cache entry in bytes. */
+static size_t
+cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry)
+{
+  return sizeof(*entry) +
+         strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc);
+}
+
+/* Remove a given descriptor from our cache. */
+static void
+remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc)
+{
+  tor_assert(desc);
+  digest256map_remove(hs_cache_v3_client, desc->key.pubkey);
+  /* Update cache size with this entry for the OOM handler. */
+  rend_cache_decrement_allocation(cache_get_client_entry_size(desc));
+}
+
+/* Store a given descriptor in our cache. */
+static void
+store_v3_desc_as_client(hs_cache_client_descriptor_t *desc)
+{
+  tor_assert(desc);
+  digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc);
+  /* Update cache size with this entry for the OOM handler. */
+  rend_cache_increment_allocation(cache_get_client_entry_size(desc));
+}
+
+/* Query our cache and return the entry or NULL if not found. */
+STATIC hs_cache_client_descriptor_t *
+lookup_v3_desc_as_client(const uint8_t *key)
+{
+  tor_assert(key);
+  return digest256map_get(hs_cache_v3_client, key);
+}
+
+/* Parse the encoded descriptor in <b>desc_str</b> using
+ * <b>service_identity_pk<b> to decrypt it first.
+ *
+ * If everything goes well, allocate and return a new
+ * hs_cache_client_descriptor_t object. In case of error, return NULL. */
+static hs_cache_client_descriptor_t *
+cache_client_desc_new(const char *desc_str,
+                      const ed25519_public_key_t *service_identity_pk)
+{
+  hs_descriptor_t *desc = NULL;
+  hs_cache_client_descriptor_t *client_desc = NULL;
+
+  tor_assert(desc_str);
+  tor_assert(service_identity_pk);
+
+  /* Decode the descriptor we just fetched. */
+  if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) {
+    goto end;
+  }
+  tor_assert(desc);
+
+  /* All is good: make a cache object for this descriptor */
+  client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t));
+  ed25519_pubkey_copy(&client_desc->key, service_identity_pk);
+  client_desc->created_ts = approx_time();
+  client_desc->desc = desc;
+  client_desc->encoded_desc = tor_strdup(desc_str);
+
+ end:
+  return client_desc;
+}
+
+/** Free memory allocated by <b>desc</b>. */
+static void
+cache_client_desc_free(hs_cache_client_descriptor_t *desc)
+{
+  if (desc == NULL) {
+    return;
+  }
+  hs_descriptor_free(desc->desc);
+  memwipe(&desc->key, 0, sizeof(desc->key));
+  memwipe(desc->encoded_desc, 0, strlen(desc->encoded_desc));
+  tor_free(desc->encoded_desc);
+  tor_free(desc);
+}
+
+/** Helper function: Use by the free all function to clear the client cache */
+static void
+cache_client_desc_free_(void *ptr)
+{
+  hs_cache_client_descriptor_t *desc = ptr;
+  cache_client_desc_free(desc);
+}
+
+/* Return a newly allocated and initialized hs_cache_intro_state_t object. */
+static hs_cache_intro_state_t *
+cache_intro_state_new(void)
+{
+  hs_cache_intro_state_t *state = tor_malloc_zero(sizeof(*state));
+  state->created_ts = approx_time();
+  return state;
+}
+
+/* Free an hs_cache_intro_state_t object. */
+static void
+cache_intro_state_free(hs_cache_intro_state_t *state)
+{
+  tor_free(state);
+}
+
+/* Helper function: use by the free all function. */
+static void
+cache_intro_state_free_(void *state)
+{
+  cache_intro_state_free(state);
+}
+
+/* Return a newly allocated and initialized hs_cache_client_intro_state_t
+ * object. */
+static hs_cache_client_intro_state_t *
+cache_client_intro_state_new(void)
+{
+  hs_cache_client_intro_state_t *cache = tor_malloc_zero(sizeof(*cache));
+  cache->intro_points = digest256map_new();
+  return cache;
+}
+
+/* Free a cache client intro state object. */
+static void
+cache_client_intro_state_free(hs_cache_client_intro_state_t *cache)
+{
+  if (cache == NULL) {
+    return;
+  }
+  digest256map_free(cache->intro_points, cache_intro_state_free_);
+  tor_free(cache);
+}
+
+/* Helper function: use by the free all function. */
+static void
+cache_client_intro_state_free_(void *entry)
+{
+  cache_client_intro_state_free(entry);
+}
+
+/* For the given service identity key service_pk and an introduction
+ * authentication key auth_key, lookup the intro state object. Return 1 if
+ * found and put it in entry if not NULL. Return 0 if not found and entry is
+ * untouched. */
+static int
+cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk,
+                                const ed25519_public_key_t *auth_key,
+                                hs_cache_intro_state_t **entry)
+{
+  hs_cache_intro_state_t *state;
+  hs_cache_client_intro_state_t *cache;
+
+  tor_assert(service_pk);
+  tor_assert(auth_key);
+
+  /* Lookup the intro state cache for this service key. */
+  cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
+  if (cache == NULL) {
+    goto not_found;
+  }
+
+  /* From the cache we just found for the service, lookup in the introduction
+   * points map for the given authentication key. */
+  state = digest256map_get(cache->intro_points, auth_key->pubkey);
+  if (state == NULL) {
+    goto not_found;
+  }
+  if (entry) {
+    *entry = state;
+  }
+  return 1;
+ not_found:
+  return 0;
+}
+
+/* Note the given failure in state. */
+static void
+cache_client_intro_state_note(hs_cache_intro_state_t *state,
+                              rend_intro_point_failure_t failure)
+{
+  tor_assert(state);
+  switch (failure) {
+  case INTRO_POINT_FAILURE_GENERIC:
+    state->error = 1;
+    break;
+  case INTRO_POINT_FAILURE_TIMEOUT:
+    state->timed_out = 1;
+    break;
+  case INTRO_POINT_FAILURE_UNREACHABLE:
+    state->unreachable_count++;
+    break;
+  default:
+    tor_assert_nonfatal_unreached();
+    return;
+  }
+}
+
+/* For the given service identity key service_pk and an introduction
+ * authentication key auth_key, add an entry in the client intro state cache
+ * If no entry exists for the service, it will create one. If state is non
+ * NULL, it will point to the new intro state entry. */
+static void
+cache_client_intro_state_add(const ed25519_public_key_t *service_pk,
+                             const ed25519_public_key_t *auth_key,
+                             hs_cache_intro_state_t **state)
+{
+  hs_cache_intro_state_t *entry, *old_entry;
+  hs_cache_client_intro_state_t *cache;
+
+  tor_assert(service_pk);
+  tor_assert(auth_key);
+
+  /* Lookup the state cache for this service key. */
+  cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
+  if (cache == NULL) {
+    cache = cache_client_intro_state_new();
+    digest256map_set(hs_cache_client_intro_state, service_pk->pubkey, cache);
+  }
+
+  entry = cache_intro_state_new();
+  old_entry = digest256map_set(cache->intro_points, auth_key->pubkey, entry);
+  /* This should never happened because the code flow is to lookup the entry
+   * before adding it. But, just in case, non fatal assert and free it. */
+  tor_assert_nonfatal(old_entry == NULL);
+  tor_free(old_entry);
+
+  if (state) {
+    *state = entry;
+  }
+}
+
+/* Remove every intro point state entry from cache that has been created
+ * before or at the cutoff. */
+static void
+cache_client_intro_state_clean(time_t cutoff,
+                               hs_cache_client_intro_state_t *cache)
+{
+  tor_assert(cache);
+
+  DIGEST256MAP_FOREACH_MODIFY(cache->intro_points, key,
+                              hs_cache_intro_state_t *, entry) {
+    if (entry->created_ts <= cutoff) {
+      cache_intro_state_free(entry);
+      MAP_DEL_CURRENT(key);
+    }
+  } DIGEST256MAP_FOREACH_END;
+}
+
+/* Return true iff no intro points are in this cache. */
+static int
+cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache)
+{
+  return digest256map_isempty(cache->intro_points);
+}
+
+/** Check whether <b>client_desc</b> is useful for us, and store it in the
+ *  client-side HS cache if so. The client_desc is freed if we already have a
+ *  fresher (higher revision counter count) in the cache. */
+static int
+cache_store_as_client(hs_cache_client_descriptor_t *client_desc)
+{
+  hs_cache_client_descriptor_t *cache_entry;
+
+  /* TODO: Heavy code duplication with cache_store_as_dir(). Consider
+   * refactoring and uniting! */
+
+  tor_assert(client_desc);
+
+  /* Check if we already have a descriptor from this HS in cache. If we do,
+   * check if this descriptor is newer than the cached one */
+  cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey);
+  if (cache_entry != NULL) {
+    /* If we have an entry in our cache that has a revision counter greater
+     * than the one we just fetched, discard the one we fetched. */
+    if (cache_entry->desc->plaintext_data.revision_counter >
+        client_desc->desc->plaintext_data.revision_counter) {
+      log_info(LD_REND, "We already have fresher descriptor. Ignoring.");
+      cache_client_desc_free(client_desc);
+      goto done;
+    }
+    /* Remove old entry. Make space for the new one! */
+    remove_v3_desc_as_client(cache_entry);
+    cache_client_desc_free(cache_entry);
+  }
+
+  /* Store descriptor in cache */
+  store_v3_desc_as_client(client_desc);
+
+ done:
+  return 0;
+}
+
+/* Clean the client cache using now as the current time. Return the total size
+ * of removed bytes from the cache. */
+static size_t
+cache_clean_v3_as_client(time_t now)
+{
+  size_t bytes_removed = 0;
+
+  if (!hs_cache_v3_client) { /* No cache to clean. Just return. */
+    return 0;
+  }
+
+  DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key,
+                              hs_cache_client_descriptor_t *, entry) {
+    size_t entry_size;
+    time_t cutoff = now - rend_cache_max_entry_lifetime();
+
+    /* If the entry has been created _after_ the cutoff, not expired so
+     * continue to the next entry in our v3 cache. */
+    if (entry->created_ts > cutoff) {
+      continue;
+    }
+    /* Here, our entry has expired, remove and free. */
+    MAP_DEL_CURRENT(key);
+    entry_size = cache_get_client_entry_size(entry);
+    bytes_removed += entry_size;
+    /* Entry is not in the cache anymore, destroy it. */
+    cache_client_desc_free(entry);
+    /* Update our OOM. We didn't use the remove() function because we are in
+     * a loop so we have to explicitely decrement. */
+    rend_cache_decrement_allocation(entry_size);
+    /* Logging. */
+    {
+      char key_b64[BASE64_DIGEST256_LEN + 1];
+      base64_encode(key_b64, sizeof(key_b64), (const char *) key,
+                    DIGEST256_LEN, 0);
+      log_info(LD_REND, "Removing hidden service v3 descriptor '%s' "
+                        "from client cache",
+               safe_str_client(key_b64));
+    }
+  } DIGEST256MAP_FOREACH_END;
+
+  return bytes_removed;
+}
+
+/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return
+ *  its HS descriptor if it's stored in our cache, or NULL if not. */
+const hs_descriptor_t *
+hs_cache_lookup_as_client(const ed25519_public_key_t *key)
+{
+  hs_cache_client_descriptor_t *cached_desc = NULL;
+
+  tor_assert(key);
+
+  cached_desc = lookup_v3_desc_as_client(key->pubkey);
+  if (cached_desc) {
+    tor_assert(cached_desc->desc);
+    return cached_desc->desc;
+  }
+
+  return NULL;
+}
+
+/** Public API: Given an encoded descriptor, store it in the client HS
+ *  cache. Return -1 on error, 0 on success .*/
+int
+hs_cache_store_as_client(const char *desc_str,
+                         const ed25519_public_key_t *identity_pk)
+{
+  hs_cache_client_descriptor_t *client_desc = NULL;
+
+  tor_assert(desc_str);
+  tor_assert(identity_pk);
+
+  /* Create client cache descriptor object */
+  client_desc = cache_client_desc_new(desc_str, identity_pk);
+  if (!client_desc) {
+    log_warn(LD_GENERAL, "Failed to parse received descriptor %s.",
+             escaped(desc_str));
+    goto err;
+  }
+
+  /* Push it to the cache */
+  if (cache_store_as_client(client_desc) < 0) {
+    goto err;
+  }
+
+  return 0;
+
+ err:
+  cache_client_desc_free(client_desc);
+  return -1;
+}
+
+/* Clean all client caches using the current time now. */
+void
+hs_cache_clean_as_client(time_t now)
+{
+  /* Start with v2 cache cleaning. */
+  rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
+  /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function
+   * to compute the cutoff by itself using the lifetime value. */
+  cache_clean_v3_as_client(now);
+}
+
+/* For a given service identity public key and an introduction authentication
+ * key, note the given failure in the client intro state cache. */
+void
+hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk,
+                                 const ed25519_public_key_t *auth_key,
+                                 rend_intro_point_failure_t failure)
+{
+  int found;
+  hs_cache_intro_state_t *entry;
+
+  tor_assert(service_pk);
+  tor_assert(auth_key);
+
+  found = cache_client_intro_state_lookup(service_pk, auth_key, &entry);
+  if (!found) {
+    /* Create a new entry and add it to the cache. */
+    cache_client_intro_state_add(service_pk, auth_key, &entry);
+  }
+  /* Note down the entry. */
+  cache_client_intro_state_note(entry, failure);
+}
+
+/* For a given service identity public key and an introduction authentication
+ * key, return true iff it is present in the failure cache. */
+const hs_cache_intro_state_t *
+hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk,
+                                 const ed25519_public_key_t *auth_key)
+{
+  hs_cache_intro_state_t *state = NULL;
+  cache_client_intro_state_lookup(service_pk, auth_key, &state);
+  return state;
+}
+
+/* Cleanup the client introduction state cache. */
+void
+hs_cache_client_intro_state_clean(time_t now)
+{
+  time_t cutoff = now - HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE;
+
+  DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key,
+                              hs_cache_client_intro_state_t *, cache) {
+    /* Cleanup intro points failure. */
+    cache_client_intro_state_clean(cutoff, cache);
+
+    /* Is this cache empty for this service key? If yes, remove it from the
+     * cache. Else keep it. */
+    if (cache_client_intro_state_is_empty(cache)) {
+      cache_client_intro_state_free(cache);
+      MAP_DEL_CURRENT(key);
+    }
+  } DIGEST256MAP_FOREACH_END;
+}
+
+/**************** Generics *********************************/
+
 /* Do a round of OOM cleanup on all directory caches. Return the amount of
  * removed bytes. It is possible that the returned value is lower than
  * min_remove_bytes if the caches get emptied out so the caller should be
@@ -369,10 +835,7 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
   return bytes_removed;
 }
 
-/**
- * Return the maximum size of an HS descriptor we are willing to accept as an
- * HSDir.
- */
+/* Return the maximum size of a v3 HS descriptor. */
 unsigned int
 hs_cache_get_max_descriptor_size(void)
 {
@@ -388,6 +851,12 @@ hs_cache_init(void)
   /* Calling this twice is very wrong code flow. */
   tor_assert(!hs_cache_v3_dir);
   hs_cache_v3_dir = digest256map_new();
+
+  tor_assert(!hs_cache_v3_client);
+  hs_cache_v3_client = digest256map_new();
+
+  tor_assert(!hs_cache_client_intro_state);
+  hs_cache_client_intro_state = digest256map_new();
 }
 
 /* Cleanup the hidden service cache subsystem. */
@@ -396,5 +865,12 @@ hs_cache_free_all(void)
 {
   digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_);
   hs_cache_v3_dir = NULL;
+
+  digest256map_free(hs_cache_v3_client, cache_client_desc_free_);
+  hs_cache_v3_client = NULL;
+
+  digest256map_free(hs_cache_client_intro_state,
+                    cache_client_intro_state_free_);
+  hs_cache_client_intro_state = NULL;
 }
 

+ 60 - 0
src/or/hs_cache.h

@@ -15,8 +15,34 @@
 #include "crypto_ed25519.h"
 #include "hs_common.h"
 #include "hs_descriptor.h"
+#include "rendcommon.h"
 #include "torcert.h"
 
+/* This is the maximum time an introduction point state object can stay in the
+ * client cache in seconds (2 mins or 120 seconds). */
+#define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60)
+
+/* Introduction point state. */
+typedef struct hs_cache_intro_state_t {
+  /* When this entry was created and put in the cache. */
+  time_t created_ts;
+
+  /* Did it suffered a generic error? */
+  unsigned int error : 1;
+
+  /* Did it timed out? */
+  unsigned int timed_out : 1;
+
+  /* How many times we tried to reached it and it was unreachable. */
+  uint32_t unreachable_count;
+} hs_cache_intro_state_t;
+
+typedef struct hs_cache_client_intro_state_t {
+  /* Contains hs_cache_intro_state_t object indexed by introduction point
+   * authentication key. */
+  digest256map_t *intro_points;
+} hs_cache_client_intro_state_t;
+
 /* Descriptor representation on the directory side which is a subset of
  * information that the HSDir can decode and serve it. */
 typedef struct hs_cache_dir_descriptor_t {
@@ -53,10 +79,44 @@ int hs_cache_store_as_dir(const char *desc);
 int hs_cache_lookup_as_dir(uint32_t version, const char *query,
                            const char **desc_out);
 
+const hs_descriptor_t *
+hs_cache_lookup_as_client(const ed25519_public_key_t *key);
+int hs_cache_store_as_client(const char *desc_str,
+                             const ed25519_public_key_t *identity_pk);
+void hs_cache_clean_as_client(time_t now);
+
+/* Client failure cache. */
+void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk,
+                                      const ed25519_public_key_t *auth_key,
+                                      rend_intro_point_failure_t failure);
+const hs_cache_intro_state_t *hs_cache_client_intro_state_find(
+                                       const ed25519_public_key_t *service_pk,
+                                       const ed25519_public_key_t *auth_key);
+void hs_cache_client_intro_state_clean(time_t now);
+
 #ifdef HS_CACHE_PRIVATE
 
+/** Represents a locally cached HS descriptor on a hidden service client. */
+typedef struct hs_cache_client_descriptor_t {
+  /* This object is indexed using the service identity public key */
+  ed25519_public_key_t key;
+
+  /* When was this entry created. Used to expire entries. */
+  time_t created_ts;
+
+  /* The cached descriptor, this object is the owner. It can't be NULL. A
+   * cache object without a valid descriptor is not possible. */
+  hs_descriptor_t *desc;
+
+  /* Encoded descriptor in string form. Can't be NULL. */
+  char *encoded_desc;
+} hs_cache_client_descriptor_t;
+
 STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff);
 
+STATIC hs_cache_client_descriptor_t *
+lookup_v3_desc_as_client(const uint8_t *key);
+
 #endif /* HS_CACHE_PRIVATE */
 
 #endif /* TOR_HS_CACHE_H */

+ 364 - 0
src/or/hs_cell.c

@@ -10,6 +10,7 @@
 #include "config.h"
 #include "rendservice.h"
 #include "replaycache.h"
+#include "util.h"
 
 #include "hs_cell.h"
 #include "hs_ntor.h"
@@ -245,6 +246,229 @@ parse_introduce2_cell(const hs_service_t *service,
   return -1;
 }
 
+/* Set the onion public key onion_pk in cell, the encrypted section of an
+ * INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell,
+                                   const uint8_t *onion_pk)
+{
+  tor_assert(cell);
+  tor_assert(onion_pk);
+  /* There is only one possible key type for a non legacy cell. */
+  trn_cell_introduce_encrypted_set_onion_key_type(cell,
+                                                  HS_CELL_ONION_KEY_TYPE_NTOR);
+  trn_cell_introduce_encrypted_set_onion_key_len(cell, CURVE25519_PUBKEY_LEN);
+  trn_cell_introduce_encrypted_setlen_onion_key(cell, CURVE25519_PUBKEY_LEN);
+  memcpy(trn_cell_introduce_encrypted_getarray_onion_key(cell), onion_pk,
+         trn_cell_introduce_encrypted_getlen_onion_key(cell));
+}
+
+/* Set the link specifiers in lspecs in cell, the encrypted section of an
+ * INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell,
+                                   const smartlist_t *lspecs)
+{
+  tor_assert(cell);
+  tor_assert(lspecs);
+  tor_assert(smartlist_len(lspecs) > 0);
+  tor_assert(smartlist_len(lspecs) <= UINT8_MAX);
+
+  uint8_t lspecs_num = (uint8_t) smartlist_len(lspecs);
+  trn_cell_introduce_encrypted_set_nspec(cell, lspecs_num);
+  /* We aren't duplicating the link specifiers object here which means that
+   * the ownership goes to the trn_cell_introduce_encrypted_t cell and those
+   * object will be freed when the cell is. */
+  SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls,
+                    trn_cell_introduce_encrypted_add_nspecs(cell, ls));
+}
+
+/* Set padding in the enc_cell only if needed that is the total length of both
+ * sections are below the mininum required for an INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell,
+                                 trn_cell_introduce_encrypted_t *enc_cell)
+{
+  tor_assert(cell);
+  tor_assert(enc_cell);
+  /* This is the length we expect to have once encoded of the whole cell. */
+  ssize_t full_len = trn_cell_introduce1_encoded_len(cell) +
+                     trn_cell_introduce_encrypted_encoded_len(enc_cell);
+  tor_assert(full_len > 0);
+  if (full_len < HS_CELL_INTRODUCE1_MIN_SIZE) {
+    size_t padding = HS_CELL_INTRODUCE1_MIN_SIZE - full_len;
+    trn_cell_introduce_encrypted_setlen_pad(enc_cell, padding);
+    memset(trn_cell_introduce_encrypted_getarray_pad(enc_cell), 0,
+           trn_cell_introduce_encrypted_getlen_pad(enc_cell));
+  }
+}
+
+/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell
+ * and the INTRODUCE1 data.
+ *
+ * This can't fail but it is very important that the caller sets every field
+ * in data so the computation of the INTRODUCE1 keys doesn't fail. */
+static void
+introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell,
+                              const trn_cell_introduce_encrypted_t *enc_cell,
+                              const hs_cell_introduce1_data_t *data)
+{
+  size_t offset = 0;
+  ssize_t encrypted_len;
+  ssize_t encoded_cell_len, encoded_enc_cell_len;
+  uint8_t encoded_cell[RELAY_PAYLOAD_SIZE] = {0};
+  uint8_t encoded_enc_cell[RELAY_PAYLOAD_SIZE] = {0};
+  uint8_t *encrypted = NULL;
+  uint8_t mac[DIGEST256_LEN];
+  crypto_cipher_t *cipher = NULL;
+  hs_ntor_intro_cell_keys_t keys;
+
+  tor_assert(cell);
+  tor_assert(enc_cell);
+  tor_assert(data);
+
+  /* Encode the cells up to now of what we have to we can perform the MAC
+   * computation on it. */
+  encoded_cell_len = trn_cell_introduce1_encode(encoded_cell,
+                                                sizeof(encoded_cell), cell);
+  /* We have a much more serious issue if this isn't true. */
+  tor_assert(encoded_cell_len > 0);
+
+  encoded_enc_cell_len =
+    trn_cell_introduce_encrypted_encode(encoded_enc_cell,
+                                        sizeof(encoded_enc_cell), enc_cell);
+  /* We have a much more serious issue if this isn't true. */
+  tor_assert(encoded_enc_cell_len > 0);
+
+  /* Get the key material for the encryption. */
+  if (hs_ntor_client_get_introduce1_keys(data->auth_pk, data->enc_pk,
+                                         data->client_kp,
+                                         data->subcredential, &keys) < 0) {
+    tor_assert_unreached();
+  }
+
+  /* Prepare cipher with the encryption key just computed. */
+  cipher = crypto_cipher_new_with_bits((const char *) keys.enc_key,
+                                       sizeof(keys.enc_key) * 8);
+  tor_assert(cipher);
+
+  /* Compute the length of the ENCRYPTED section which is the CLIENT_PK,
+   * ENCRYPTED_DATA and MAC length. */
+  encrypted_len = sizeof(data->client_kp->pubkey) + encoded_enc_cell_len +
+                  sizeof(mac);
+  tor_assert(encrypted_len < RELAY_PAYLOAD_SIZE);
+  encrypted = tor_malloc_zero(encrypted_len);
+
+  /* Put the CLIENT_PK first. */
+  memcpy(encrypted, data->client_kp->pubkey.public_key,
+         sizeof(data->client_kp->pubkey.public_key));
+  offset += sizeof(data->client_kp->pubkey.public_key);
+  /* Then encrypt and set the ENCRYPTED_DATA. This can't fail. */
+  crypto_cipher_encrypt(cipher, (char *) encrypted + offset,
+                        (const char *) encoded_enc_cell, encoded_enc_cell_len);
+  crypto_cipher_free(cipher);
+  offset += encoded_enc_cell_len;
+  /* Compute MAC from the above and put it in the buffer. This function will
+   * make the adjustment to the encryptled_len to ommit the MAC length. */
+  compute_introduce_mac(encoded_cell, encoded_cell_len,
+                        encrypted, encrypted_len,
+                        keys.mac_key, sizeof(keys.mac_key),
+                        mac, sizeof(mac));
+  memcpy(encrypted + offset, mac, sizeof(mac));
+  offset += sizeof(mac);
+  tor_assert(offset == (size_t) encrypted_len);
+
+  /* Set the ENCRYPTED section in the cell. */
+  trn_cell_introduce1_setlen_encrypted(cell, encrypted_len);
+  memcpy(trn_cell_introduce1_getarray_encrypted(cell),
+         encrypted, encrypted_len);
+
+  /* Cleanup. */
+  memwipe(&keys, 0, sizeof(keys));
+  memwipe(mac, 0, sizeof(mac));
+  memwipe(encrypted, 0, sizeof(encrypted_len));
+  memwipe(encoded_enc_cell, 0, sizeof(encoded_enc_cell));
+  tor_free(encrypted);
+}
+
+/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means
+ * set it, encrypt it and encode it. */
+static void
+introduce1_set_encrypted(trn_cell_introduce1_t *cell,
+                         const hs_cell_introduce1_data_t *data)
+{
+  trn_cell_introduce_encrypted_t *enc_cell;
+  trn_cell_extension_t *ext;
+
+  tor_assert(cell);
+  tor_assert(data);
+
+  enc_cell = trn_cell_introduce_encrypted_new();
+  tor_assert(enc_cell);
+
+  /* Set extension data. None are used. */
+  ext = trn_cell_extension_new();
+  tor_assert(ext);
+  trn_cell_extension_set_num(ext, 0);
+  trn_cell_introduce_encrypted_set_extensions(enc_cell, ext);
+
+  /* Set the rendezvous cookie. */
+  memcpy(trn_cell_introduce_encrypted_getarray_rend_cookie(enc_cell),
+         data->rendezvous_cookie, REND_COOKIE_LEN);
+
+  /* Set the onion public key. */
+  introduce1_set_encrypted_onion_key(enc_cell, data->onion_pk->public_key);
+
+  /* Set the link specifiers. */
+  introduce1_set_encrypted_link_spec(enc_cell, data->link_specifiers);
+
+  /* Set padding. */
+  introduce1_set_encrypted_padding(cell, enc_cell);
+
+  /* Encrypt and encode it in the cell. */
+  introduce1_encrypt_and_encode(cell, enc_cell, data);
+
+  /* Cleanup. */
+  trn_cell_introduce_encrypted_free(enc_cell);
+}
+
+/* Set the authentication key in the INTRODUCE1 cell from the given data. */
+static void
+introduce1_set_auth_key(trn_cell_introduce1_t *cell,
+                        const hs_cell_introduce1_data_t *data)
+{
+  tor_assert(cell);
+  tor_assert(data);
+  /* There is only one possible type for a non legacy cell. */
+  trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519);
+  trn_cell_introduce1_set_auth_key_len(cell, ED25519_PUBKEY_LEN);
+  trn_cell_introduce1_setlen_auth_key(cell, ED25519_PUBKEY_LEN);
+  memcpy(trn_cell_introduce1_getarray_auth_key(cell),
+         data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell));
+}
+
+/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */
+static void
+introduce1_set_legacy_id(trn_cell_introduce1_t *cell,
+                         const hs_cell_introduce1_data_t *data)
+{
+  tor_assert(cell);
+  tor_assert(data);
+
+  if (data->is_legacy) {
+    uint8_t digest[DIGEST_LEN];
+    if (BUG(crypto_pk_get_digest(data->legacy_key, (char *) digest) < 0)) {
+      return;
+    }
+    memcpy(trn_cell_introduce1_getarray_legacy_key_id(cell),
+           digest, trn_cell_introduce1_getlen_legacy_key_id(cell));
+  } else {
+    /* We have to zeroed the LEGACY_KEY_ID field. */
+    memset(trn_cell_introduce1_getarray_legacy_key_id(cell), 0,
+           trn_cell_introduce1_getlen_legacy_key_id(cell));
+  }
+}
+
 /* ========== */
 /* Public API */
 /* ========== */
@@ -582,3 +806,143 @@ hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
   return cell_len;
 }
 
+/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in
+ * cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the
+ * encoded length is returned else a negative value and the content of
+ * cell_out should be ignored. */
+ssize_t
+hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data,
+                         uint8_t *cell_out)
+{
+  ssize_t cell_len;
+  trn_cell_introduce1_t *cell;
+  trn_cell_extension_t *ext;
+
+  tor_assert(data);
+  tor_assert(cell_out);
+
+  cell = trn_cell_introduce1_new();
+  tor_assert(cell);
+
+  /* Set extension data. None are used. */
+  ext = trn_cell_extension_new();
+  tor_assert(ext);
+  trn_cell_extension_set_num(ext, 0);
+  trn_cell_introduce1_set_extensions(cell, ext);
+
+  /* Set the legacy ID field. */
+  introduce1_set_legacy_id(cell, data);
+
+  /* Set the authentication key. */
+  introduce1_set_auth_key(cell, data);
+
+  /* Set the encrypted section. This will set, encrypt and encode the
+   * ENCRYPTED section in the cell. After this, we'll be ready to encode. */
+  introduce1_set_encrypted(cell, data);
+
+  /* Final encoding. */
+  cell_len = trn_cell_introduce1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell);
+
+  trn_cell_introduce1_free(cell);
+  return cell_len;
+}
+
+/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The
+ * encoded cell is put in cell_out which must be of at least
+ * RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the
+ * caller should clear up the content of the cell.
+ *
+ * This function can't fail. */
+ssize_t
+hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie,
+                                   uint8_t *cell_out)
+{
+  tor_assert(rendezvous_cookie);
+  tor_assert(cell_out);
+
+  memcpy(cell_out, rendezvous_cookie, HS_REND_COOKIE_LEN);
+  return HS_REND_COOKIE_LEN;
+}
+
+/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len.
+ * Return the status code on success else a negative value if the cell as not
+ * decodable. */
+int
+hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len)
+{
+  int ret = -1;
+  trn_cell_introduce_ack_t *cell = NULL;
+
+  tor_assert(payload);
+
+  /* If it is a legacy IP, rend-spec.txt specifies that a ACK is 0 byte and a
+   * NACK is 1 byte. We can't use the legacy function for this so we have to
+   * do a special case. */
+  if (payload_len <= 1) {
+    if (payload_len == 0) {
+      ret = HS_CELL_INTRO_ACK_SUCCESS;
+    } else {
+      ret = HS_CELL_INTRO_ACK_FAILURE;
+    }
+    goto end;
+  }
+
+  if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) {
+    log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it.");
+    goto end;
+  }
+
+  ret = trn_cell_introduce_ack_get_status(cell);
+
+ end:
+  trn_cell_introduce_ack_free(cell);
+  return ret;
+}
+
+/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On
+ * success, handshake_info contains the data in the HANDSHAKE_INFO field, and
+ * 0 is returned. On error, a negative value is returned. */
+int
+hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
+                          uint8_t *handshake_info, size_t handshake_info_len)
+{
+  int ret = -1;
+  trn_cell_rendezvous2_t *cell = NULL;
+
+  tor_assert(payload);
+  tor_assert(handshake_info);
+
+  if (trn_cell_rendezvous2_parse(&cell, payload, payload_len) < 0) {
+    log_info(LD_REND, "Invalid RENDEZVOUS2 cell. Unable to parse it.");
+    goto end;
+  }
+
+  /* Static size, we should never have an issue with this else we messed up
+   * our code flow. */
+  tor_assert(trn_cell_rendezvous2_getlen_handshake_info(cell) ==
+             handshake_info_len);
+  memcpy(handshake_info,
+         trn_cell_rendezvous2_getconstarray_handshake_info(cell),
+         handshake_info_len);
+  ret = 0;
+
+ end:
+  trn_cell_rendezvous2_free(cell);
+  return ret;
+}
+
+/* Clear the given INTRODUCE1 data structure data. */
+void
+hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data)
+{
+  if (data == NULL) {
+    return;
+  }
+  /* Object in this list have been moved to the cell object when building it
+   * so they've been freed earlier. We do that in order to avoid duplicating
+   * them leading to more memory and CPU time being used for nothing. */
+  smartlist_free(data->link_specifiers);
+  /* The data object has no ownership of any members. */
+  memwipe(data, 0, sizeof(hs_cell_introduce1_data_t));
+}
+

+ 47 - 0
src/or/hs_cell.h

@@ -12,11 +12,47 @@
 #include "or.h"
 #include "hs_service.h"
 
+/* An INTRODUCE1 cell requires at least this amount of bytes (see section
+ * 3.2.2 of the specification). Below this value, the cell must be padded. */
+#define HS_CELL_INTRODUCE1_MIN_SIZE 246
+
+/* Status code of an INTRODUCE_ACK cell. */
+typedef enum {
+  HS_CELL_INTRO_ACK_SUCCESS = 0x0000, /* Cell relayed to service. */
+  HS_CELL_INTRO_ACK_FAILURE = 0x0001, /* Service ID not recognized */
+  HS_CELL_INTRO_ACK_BADFMT  = 0x0002, /* Bad message format */
+  HS_CELL_INTRO_ACK_NORELAY = 0x0003, /* Can't relay cell to service */
+} hs_cell_introd_ack_status_t;
+
 /* Onion key type found in the INTRODUCE1 cell. */
 typedef enum {
   HS_CELL_ONION_KEY_TYPE_NTOR = 1,
 } hs_cell_onion_key_type_t;
 
+/* This data structure contains data that we need to build an INTRODUCE1 cell
+ * used by the INTRODUCE1 build function. */
+typedef struct hs_cell_introduce1_data_t {
+  /* Is this a legacy introduction point? */
+  unsigned int is_legacy : 1;
+  /* (Legacy only) The encryption key for a legacy intro point. Only set if
+   * is_legacy is true. */
+  const crypto_pk_t *legacy_key;
+  /* Introduction point authentication public key. */
+  const ed25519_public_key_t *auth_pk;
+  /* Introduction point encryption public key. */
+  const curve25519_public_key_t *enc_pk;
+  /* Subcredentials of the service. */
+  const uint8_t *subcredential;
+  /* Onion public key for the ntor handshake. */
+  const curve25519_public_key_t *onion_pk;
+  /* Rendezvous cookie. */
+  const uint8_t *rendezvous_cookie;
+  /* Public key put before the encrypted data (CLIENT_PK). */
+  const curve25519_keypair_t *client_kp;
+  /* Rendezvous point link specifiers. */
+  smartlist_t *link_specifiers;
+} hs_cell_introduce1_data_t;
+
 /* This data structure contains data that we need to parse an INTRODUCE2 cell
  * which is used by the INTRODUCE2 cell parsing function. On a successful
  * parsing, the onion_pk and rendezvous_cookie will be populated with the
@@ -63,6 +99,10 @@ ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
                                   const uint8_t *rendezvous_handshake_info,
                                   size_t rendezvous_handshake_info_len,
                                   uint8_t *cell_out);
+ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data,
+                                 uint8_t *cell_out);
+ssize_t hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie,
+                                           uint8_t *cell_out);
 
 /* Parse cell API. */
 ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
@@ -70,6 +110,13 @@ ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
 ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
                                  const origin_circuit_t *circ,
                                  const hs_service_t *service);
+int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len);
+int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
+                              uint8_t *handshake_info,
+                              size_t handshake_info_len);
+
+/* Util API. */
+void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data);
 
 #endif /* TOR_HS_CELL_H */
 

+ 197 - 121
src/or/hs_circuit.c

@@ -341,125 +341,6 @@ send_establish_intro(const hs_service_t *service,
   memwipe(payload, 0, sizeof(payload));
 }
 
-/* From a list of link specifier, an onion key and if we are requesting a
- * direct connection (ex: single onion service), return a newly allocated
- * extend_info_t object. This function checks the firewall policies and if we
- * are allowed to extend to the chosen address.
- *
- *  if either IPv4 or legacy ID is missing, error.
- *  if not direct_conn, IPv4 is prefered.
- *  if direct_conn, IPv6 is prefered if we have one available.
- *  if firewall does not allow the chosen address, error.
- *
- * Return NULL if we can't fulfill the conditions. */
-static extend_info_t *
-get_rp_extend_info(const smartlist_t *link_specifiers,
-                   const curve25519_public_key_t *onion_key, int direct_conn)
-{
-  int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
-  char legacy_id[DIGEST_LEN] = {0};
-  uint16_t port_v4 = 0, port_v6 = 0, port = 0;
-  tor_addr_t addr_v4, addr_v6, *addr = NULL;
-  ed25519_public_key_t ed25519_pk;
-  extend_info_t *info = NULL;
-
-  tor_assert(link_specifiers);
-  tor_assert(onion_key);
-
-  SMARTLIST_FOREACH_BEGIN(link_specifiers, const link_specifier_t *, ls) {
-    switch (link_specifier_get_ls_type(ls)) {
-    case LS_IPV4:
-      /* Skip if we already seen a v4. */
-      if (have_v4) continue;
-      tor_addr_from_ipv4h(&addr_v4,
-                          link_specifier_get_un_ipv4_addr(ls));
-      port_v4 = link_specifier_get_un_ipv4_port(ls);
-      have_v4 = 1;
-      break;
-    case LS_IPV6:
-      /* Skip if we already seen a v6. */
-      if (have_v6) continue;
-      tor_addr_from_ipv6_bytes(&addr_v6,
-          (const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
-      port_v6 = link_specifier_get_un_ipv6_port(ls);
-      have_v6 = 1;
-      break;
-    case LS_LEGACY_ID:
-      /* Make sure we do have enough bytes for the legacy ID. */
-      if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
-        break;
-      }
-      memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls),
-             sizeof(legacy_id));
-      have_legacy_id = 1;
-      break;
-    case LS_ED25519_ID:
-      memcpy(ed25519_pk.pubkey,
-             link_specifier_getconstarray_un_ed25519_id(ls),
-             ED25519_PUBKEY_LEN);
-      have_ed25519_id = 1;
-      break;
-    default:
-      /* Ignore unknown. */
-      break;
-    }
-  } SMARTLIST_FOREACH_END(ls);
-
-  /* IPv4, legacy ID are mandatory for rend points.
-   * ed25519 keys and ipv6 are optional for rend points */
-  if (!have_v4 || !have_legacy_id) {
-    goto done;
-  }
-  /* By default, we pick IPv4 but this might change to v6 if certain
-   * conditions are met. */
-  addr = &addr_v4; port = port_v4;
-
-  /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
-   * circuit so we can't extend in IPv6. And at this point, we do have an IPv4
-   * address available so go to validation. */
-  if (!direct_conn) {
-    goto validate;
-  }
-
-  /* From this point on, we have a request for a direct connection to the
-   * rendezvous point so make sure we can actually connect through our
-   * firewall. We'll prefer IPv6. */
-
-  /* IPv6 test. */
-  if (have_v6 &&
-      fascist_firewall_allows_address_addr(&addr_v6, port_v6,
-                                           FIREWALL_OR_CONNECTION, 1, 1)) {
-    /* Direct connection and we can reach it in IPv6 so go for it. */
-    addr = &addr_v6; port = port_v6;
-    goto validate;
-  }
-  /* IPv4 test and we are sure we have a v4 because of the check above. */
-  if (fascist_firewall_allows_address_addr(&addr_v4, port_v4,
-                                           FIREWALL_OR_CONNECTION, 0, 0)) {
-    /* Direct connection and we can reach it in IPv4 so go for it. */
-    addr = &addr_v4; port = port_v4;
-    goto validate;
-  }
-
- validate:
-  /* We'll validate now that the address we've picked isn't a private one. If
-   * it is, are we allowing to extend to private address? */
-  if (!extend_info_addr_is_allowed(addr)) {
-    log_warn(LD_REND, "Rendezvous point address is private and it is not "
-                      "allowed to extend to it: %s:%u",
-             fmt_addr(&addr_v4), port_v4);
-    goto done;
-  }
-
-  /* We do have everything for which we think we can connect successfully. */
-  info = extend_info_new(NULL, legacy_id,
-                         have_ed25519_id ? &ed25519_pk : NULL,
-                         NULL, onion_key,
-                         addr, port);
- done:
-  return info;
-}
-
 /* For a given service, the ntor onion key and a rendezvous cookie, launch a
  * circuit to the rendezvous point specified by the link specifiers. On
  * success, a circuit identifier is attached to the circuit with the needed
@@ -483,8 +364,9 @@ launch_rendezvous_point_circuit(const hs_service_t *service,
 
   /* Get the extend info data structure for the chosen rendezvous point
    * specified by the given link specifiers. */
-  info = get_rp_extend_info(data->link_specifiers, &data->onion_pk,
-                            service->config.is_single_onion);
+  info = hs_get_extend_info_from_lspecs(data->link_specifiers,
+                                        &data->onion_pk,
+                                        service->config.is_single_onion);
   if (info == NULL) {
     /* We are done here, we can't extend to the rendezvous point. */
     goto end;
@@ -648,6 +530,83 @@ retry_service_rendezvous_point(const origin_circuit_t *circ)
   return;
 }
 
+/* Using an extend info object ei, set all possible link specifiers in lspecs.
+ * IPv4, legacy ID and ed25519 ID are mandatory thus MUST be present in ei. */
+static void
+get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs)
+{
+  link_specifier_t *ls;
+
+  tor_assert(ei);
+  tor_assert(lspecs);
+
+  /* IPv4 is mandatory. */
+  ls = link_specifier_new();
+  link_specifier_set_ls_type(ls, LS_IPV4);
+  link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr));
+  link_specifier_set_un_ipv4_port(ls, ei->port);
+  /* Four bytes IPv4 and two bytes port. */
+  link_specifier_set_ls_len(ls, sizeof(ei->addr.addr.in_addr) +
+                            sizeof(ei->port));
+  smartlist_add(lspecs, ls);
+
+  /* Legacy ID is mandatory. */
+  ls = link_specifier_new();
+  link_specifier_set_ls_type(ls, LS_LEGACY_ID);
+  memcpy(link_specifier_getarray_un_legacy_id(ls), ei->identity_digest,
+         link_specifier_getlen_un_legacy_id(ls));
+  link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls));
+  smartlist_add(lspecs, ls);
+
+  /* ed25519 ID is mandatory. */
+  ls = link_specifier_new();
+  link_specifier_set_ls_type(ls, LS_ED25519_ID);
+  memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity,
+         link_specifier_getlen_un_ed25519_id(ls));
+  link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls));
+  smartlist_add(lspecs, ls);
+
+  /* XXX: IPv6 is not clearly a thing in extend_info_t? */
+}
+
+/* Using the given descriptor intro point ip, the extend information of the
+ * rendezvous point rp_ei and the service's subcredential, populate the
+ * already allocated intro1_data object with the needed key material and link
+ * specifiers.
+ *
+ * This can't fail but the ip MUST be a valid object containing the needed
+ * keys and authentication method. */
+static void
+setup_introduce1_data(const hs_desc_intro_point_t *ip,
+                      const extend_info_t *rp_ei,
+                      const uint8_t *subcredential,
+                      hs_cell_introduce1_data_t *intro1_data)
+{
+  smartlist_t *rp_lspecs;
+
+  tor_assert(ip);
+  tor_assert(rp_ei);
+  tor_assert(subcredential);
+  tor_assert(intro1_data);
+
+  /* Build the link specifiers from the extend information of the rendezvous
+   * circuit that we've picked previously. */
+  rp_lspecs = smartlist_new();
+  get_lspecs_from_extend_info(rp_ei, rp_lspecs);
+
+  /* Populate the introduce1 data object. */
+  memset(intro1_data, 0, sizeof(hs_cell_introduce1_data_t));
+  if (ip->legacy.key != NULL) {
+    intro1_data->is_legacy = 1;
+    intro1_data->legacy_key = ip->legacy.key;
+  }
+  intro1_data->auth_pk = &ip->auth_key_cert->signed_key;
+  intro1_data->enc_pk = &ip->enc_key;
+  intro1_data->subcredential = subcredential;
+  intro1_data->onion_pk = &rp_ei->curve25519_onion_key;
+  intro1_data->link_specifiers = rp_lspecs;
+}
+
 /* ========== */
 /* Public API */
 /* ========== */
@@ -1055,3 +1014,120 @@ hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
   return 0;
 }
 
+/* Given the introduction circuit intro_circ, the rendezvous circuit
+ * rend_circ, a descriptor intro point object ip and the service's
+ * subcredential, send an INTRODUCE1 cell on intro_circ.
+ *
+ * This will also setup the circuit identifier on rend_circ containing the key
+ * material for the handshake and e2e encryption. Return 0 on success else
+ * negative value. Because relay_send_command_from_edge() closes the circuit
+ * on error, it is possible that intro_circ is closed on error. */
+int
+hs_circ_send_introduce1(origin_circuit_t *intro_circ,
+                        origin_circuit_t *rend_circ,
+                        const hs_desc_intro_point_t *ip,
+                        const uint8_t *subcredential)
+{
+  int ret = -1;
+  ssize_t payload_len;
+  uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
+  hs_cell_introduce1_data_t intro1_data;
+
+  tor_assert(intro_circ);
+  tor_assert(rend_circ);
+  tor_assert(ip);
+  tor_assert(subcredential);
+
+  /* This takes various objects in order to populate the introduce1 data
+   * object which is used to build the content of the cell. */
+  setup_introduce1_data(ip, rend_circ->build_state->chosen_exit,
+                        subcredential, &intro1_data);
+
+  /* Final step before we encode a cell, we setup the circuit identifier which
+   * will generate both the rendezvous cookie and client keypair for this
+   * connection. Those are put in the ident. */
+  intro1_data.rendezvous_cookie = rend_circ->hs_ident->rendezvous_cookie;
+  intro1_data.client_kp = &rend_circ->hs_ident->rendezvous_client_kp;
+
+  memcpy(intro_circ->hs_ident->rendezvous_cookie,
+         rend_circ->hs_ident->rendezvous_cookie,
+         sizeof(intro_circ->hs_ident->rendezvous_cookie));
+
+  /* From the introduce1 data object, this will encode the INTRODUCE1 cell
+   * into payload which is then ready to be sent as is. */
+  payload_len = hs_cell_build_introduce1(&intro1_data, payload);
+  if (BUG(payload_len < 0)) {
+    goto done;
+  }
+
+  if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(intro_circ),
+                                   RELAY_COMMAND_INTRODUCE1,
+                                   (const char *) payload, payload_len,
+                                   intro_circ->cpath->prev) < 0) {
+    /* On error, circuit is closed. */
+    log_warn(LD_REND, "Unable to send INTRODUCE1 cell on circuit %u.",
+             TO_CIRCUIT(intro_circ)->n_circ_id);
+    goto done;
+  }
+
+  /* Success. */
+  ret = 0;
+  goto done;
+
+ done:
+  hs_cell_introduce1_data_clear(&intro1_data);
+  memwipe(payload, 0, sizeof(payload));
+  return ret;
+}
+
+/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On
+ * success, 0 is returned else -1 and the circuit is marked for close. */
+int
+hs_circ_send_establish_rendezvous(origin_circuit_t *circ)
+{
+  ssize_t cell_len = 0;
+  uint8_t cell[RELAY_PAYLOAD_SIZE] = {0};
+
+  tor_assert(circ);
+  tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+
+  log_info(LD_REND, "Send an ESTABLISH_RENDEZVOUS cell on circuit %u",
+           TO_CIRCUIT(circ)->n_circ_id);
+
+  /* Set timestamp_dirty, because circuit_expire_building expects it,
+   * and the rend cookie also means we've used the circ. */
+  TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+
+  /* We've attempted to use this circuit. Probe it if we fail */
+  pathbias_count_use_attempt(circ);
+
+  /* Generate the RENDEZVOUS_COOKIE and place it in the identifier so we can
+   * complete the handshake when receiving the acknowledgement. */
+  crypto_rand((char *) circ->hs_ident->rendezvous_cookie, HS_REND_COOKIE_LEN);
+  /* Generate the client keypair. No need to be extra strong, not long term */
+  curve25519_keypair_generate(&circ->hs_ident->rendezvous_client_kp, 0);
+
+  cell_len =
+    hs_cell_build_establish_rendezvous(circ->hs_ident->rendezvous_cookie,
+                                       cell);
+  if (BUG(cell_len < 0)) {
+    goto err;
+  }
+
+  if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+                                   RELAY_COMMAND_ESTABLISH_RENDEZVOUS,
+                                   (const char *) cell, cell_len,
+                                   circ->cpath->prev) < 0) {
+    /* Circuit has been marked for close */
+    log_warn(LD_REND, "Unable to send ESTABLISH_RENDEZVOUS cell on "
+                      "circuit %u", TO_CIRCUIT(circ)->n_circ_id);
+    memwipe(cell, 0, cell_len);
+    goto err;
+  }
+
+  memwipe(cell, 0, cell_len);
+  return 0;
+ err:
+  return -1;
+}
+

+ 5 - 0
src/or/hs_circuit.h

@@ -44,6 +44,11 @@ int hs_circ_handle_introduce2(const hs_service_t *service,
                               hs_service_intro_point_t *ip,
                               const uint8_t *subcredential,
                               const uint8_t *payload, size_t payload_len);
+int hs_circ_send_introduce1(origin_circuit_t *intro_circ,
+                            origin_circuit_t *rend_circ,
+                            const hs_desc_intro_point_t *ip,
+                            const uint8_t *subcredential);
+int hs_circ_send_establish_rendezvous(origin_circuit_t *circ);
 
 /* e2e circuit API. */
 

+ 50 - 2
src/or/hs_circuitmap.c

@@ -5,8 +5,10 @@
  * \file hs_circuitmap.c
  *
  * \brief Hidden service circuitmap: A hash table that maps binary tokens to
- *  introduction and rendezvous circuits; it's used both by relays acting as
- *  intro points and rendezvous points, and also by hidden services themselves.
+ *  introduction and rendezvous circuits; it's used:
+ *  (a) by relays acting as intro points and rendezvous points
+ *  (b) by hidden services to find intro and rend circuits and
+ *  (c) by HS clients to find rendezvous circuits.
  **/
 
 #define HS_CIRCUITMAP_PRIVATE
@@ -404,6 +406,37 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie)
   return circ;
 }
 
+/* Public function: Return client-side rendezvous circuit with rendezvous
+ * <b>cookie</b>. It will first lookup for the CIRCUIT_PURPOSE_C_REND_READY
+ * purpose and then try for CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED and then
+ * finally tries for CIRCUIT_PURPOSE_C_ESTABLISH_REND.
+ *
+ * Return NULL if no such circuit is found in the circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie)
+{
+  origin_circuit_t *circ = NULL;
+
+  circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+                                          REND_TOKEN_LEN, cookie,
+                                          CIRCUIT_PURPOSE_C_REND_READY);
+  if (circ) {
+    return circ;
+  }
+
+  circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+                                          REND_TOKEN_LEN, cookie,
+                                 CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
+  if (circ) {
+    return circ;
+  }
+
+  circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+                                          REND_TOKEN_LEN, cookie,
+                                          CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+  return circ;
+}
+
 /**** Public servide-side setters: */
 
 /* Public function: Register v2 intro circuit with key <b>digest</b> to the
@@ -439,6 +472,21 @@ hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ,
                                  REND_TOKEN_LEN, cookie);
 }
 
+/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
+ * client-side circuitmap. */
+void
+hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ,
+                                             const uint8_t *cookie)
+{
+  circuit_t *circ = TO_CIRCUIT(or_circ);
+  { /* Basic circ purpose sanity checking */
+    tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+  }
+
+  hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE,
+                                 REND_TOKEN_LEN, cookie);
+}
+
 /**** Misc public functions: */
 
 /** Public function: Remove this circuit from the HS circuitmap. Clear its HS

+ 8 - 0
src/or/hs_circuitmap.h

@@ -43,6 +43,8 @@ struct origin_circuit_t *
 hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest);
 struct origin_circuit_t *
 hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie);
+struct origin_circuit_t *
+hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie);
 
 void hs_circuitmap_register_intro_circ_v2_service_side(
                                         struct origin_circuit_t *circ,
@@ -53,6 +55,9 @@ void hs_circuitmap_register_intro_circ_v3_service_side(
 void hs_circuitmap_register_rend_circ_service_side(
                                         struct origin_circuit_t *circ,
                                         const uint8_t *cookie);
+void hs_circuitmap_register_rend_circ_client_side(
+                                      struct origin_circuit_t *circ,
+                                      const uint8_t *cookie);
 
 void hs_circuitmap_remove_circuit(struct circuit_t *circ);
 
@@ -76,6 +81,9 @@ typedef enum {
   HS_TOKEN_INTRO_V2_SERVICE_SIDE,
   /** A v3 introduction point pubkey on a hidden service (256bit) */
   HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+
+  /** A rendezvous cookie on the client side (128bit) */
+  HS_TOKEN_REND_CLIENT_SIDE,
 } hs_token_type_t;
 
 /** Represents a token used in the HS protocol. Each such token maps to a

+ 1175 - 6
src/or/hs_client.c

@@ -2,7 +2,7 @@
 /* See LICENSE for licensing information */
 
 /**
- * \file hs_service.c
+ * \file hs_client.c
  * \brief Implement next generation hidden service client functionality
  **/
 
@@ -10,14 +10,57 @@
 #include "hs_circuit.h"
 #include "hs_ident.h"
 #include "connection_edge.h"
+#include "container.h"
 #include "rendclient.h"
-
+#include "hs_descriptor.h"
+#include "hs_cache.h"
+#include "hs_cell.h"
+#include "hs_ident.h"
+#include "config.h"
+#include "directory.h"
 #include "hs_client.h"
+#include "router.h"
+#include "routerset.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "connection.h"
+#include "circpathbias.h"
+#include "connection.h"
+#include "hs_ntor.h"
+#include "circuitbuild.h"
+
+/* Get all connections that are waiting on a circuit and flag them back to
+ * waiting for a hidden service descriptor for the given service key
+ * service_identity_pk. */
+static void
+flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk)
+{
+  tor_assert(service_identity_pk);
+
+  smartlist_t *conns =
+    connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT);
+
+  SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
+    edge_connection_t *edge_conn;
+    if (BUG(!CONN_IS_EDGE(conn))) {
+      continue;
+    }
+    edge_conn = TO_EDGE_CONN(conn);
+    if (edge_conn->hs_ident &&
+        ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
+                          service_identity_pk)) {
+      connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn));
+      conn->state = AP_CONN_STATE_RENDDESC_WAIT;
+    }
+  } SMARTLIST_FOREACH_END(conn);
+
+  smartlist_free(conns);
+}
 
-/** A prop224 v3 HS circuit successfully connected to the hidden
- *  service. Update the stream state at <b>hs_conn_ident</b> appropriately. */
+/* A v3 HS circuit successfully connected to the hidden service. Update the
+ * stream state at <b>hs_conn_ident</b> appropriately. */
 static void
-hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
+note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
 {
   (void) hs_conn_ident;
 
@@ -25,6 +68,756 @@ hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
   return;
 }
 
+/* Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its
+ * descriptor by launching a dir connection to <b>hsdir</b>. Return 1 on
+ * success or -1 on error. */
+static int
+directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk,
+                               const routerstatus_t *hsdir)
+{
+  uint64_t current_time_period = hs_get_time_period_num(approx_time());
+  ed25519_public_key_t blinded_pubkey;
+  char base64_blinded_pubkey[ED25519_BASE64_LEN + 1];
+  hs_ident_dir_conn_t hs_conn_dir_ident;
+  int retval;
+
+  tor_assert(hsdir);
+  tor_assert(onion_identity_pk);
+
+  /* Get blinded pubkey */
+  hs_build_blinded_pubkey(onion_identity_pk, NULL, 0,
+                          current_time_period, &blinded_pubkey);
+  /* ...and base64 it. */
+  retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey);
+  if (BUG(retval < 0)) {
+    return -1;
+  }
+
+  /* Copy onion pk to a dir_ident so that we attach it to the dir conn */
+  ed25519_pubkey_copy(&hs_conn_dir_ident.identity_pk, onion_identity_pk);
+
+  /* Setup directory request */
+  directory_request_t *req =
+    directory_request_new(DIR_PURPOSE_FETCH_HSDESC);
+  directory_request_set_routerstatus(req, hsdir);
+  directory_request_set_indirection(req, DIRIND_ANONYMOUS);
+  directory_request_set_resource(req, base64_blinded_pubkey);
+  directory_request_fetch_set_hs_ident(req, &hs_conn_dir_ident);
+  directory_initiate_request(req);
+  directory_request_free(req);
+
+  log_info(LD_REND, "Descriptor fetch request for service %s with blinded "
+                    "key %s to directory %s",
+           safe_str_client(ed25519_fmt(onion_identity_pk)),
+           safe_str_client(base64_blinded_pubkey),
+           safe_str_client(routerstatus_describe(hsdir)));
+
+  /* Cleanup memory. */
+  memwipe(&blinded_pubkey, 0, sizeof(blinded_pubkey));
+  memwipe(base64_blinded_pubkey, 0, sizeof(base64_blinded_pubkey));
+  memwipe(&hs_conn_dir_ident, 0, sizeof(hs_conn_dir_ident));
+
+  return 1;
+}
+
+/** Return the HSDir we should use to fetch the descriptor of the hidden
+ *  service with identity key <b>onion_identity_pk</b>. */
+static routerstatus_t *
+pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk)
+{
+  int retval;
+  char base64_blinded_pubkey[ED25519_BASE64_LEN + 1];
+  uint64_t current_time_period = hs_get_time_period_num(approx_time());
+  smartlist_t *responsible_hsdirs;
+  ed25519_public_key_t blinded_pubkey;
+  routerstatus_t *hsdir_rs = NULL;
+
+  tor_assert(onion_identity_pk);
+
+  responsible_hsdirs = smartlist_new();
+
+  /* Get blinded pubkey of hidden service */
+  hs_build_blinded_pubkey(onion_identity_pk, NULL, 0,
+                          current_time_period, &blinded_pubkey);
+  /* ...and base64 it. */
+  retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey);
+  if (BUG(retval < 0)) {
+    return NULL;
+  }
+
+  /* Get responsible hsdirs of service for this time period */
+  hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period, 0, 1,
+                            responsible_hsdirs);
+
+  log_debug(LD_REND, "Found %d responsible HSDirs and about to pick one.",
+           smartlist_len(responsible_hsdirs));
+
+  /* Pick an HSDir from the responsible ones. The ownership of
+   * responsible_hsdirs is given to this function so no need to free it. */
+  hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey);
+
+  return hsdir_rs;
+}
+
+/** Fetch a v3 descriptor using the given <b>onion_identity_pk</b>.
+ *
+ * On success, 1 is returned. If no hidden service is left to ask, return 0.
+ * On error, -1 is returned. */
+static int
+fetch_v3_desc(const ed25519_public_key_t *onion_identity_pk)
+{
+  routerstatus_t *hsdir_rs =NULL;
+
+  tor_assert(onion_identity_pk);
+
+  hsdir_rs = pick_hsdir_v3(onion_identity_pk);
+  if (!hsdir_rs) {
+    log_info(LD_REND, "Couldn't pick a v3 hsdir.");
+    return 0;
+  }
+
+  return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs);
+}
+
+/* Make sure that the given v3 origin circuit circ is a valid correct
+ * introduction circuit. This will BUG() on any problems and hard assert if
+ * the anonymity of the circuit is not ok. Return 0 on success else -1 where
+ * the circuit should be mark for closed immediately. */
+static int
+intro_circ_is_ok(const origin_circuit_t *circ)
+{
+  int ret = 0;
+
+  tor_assert(circ);
+
+  if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCING &&
+          TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
+          TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) {
+    ret = -1;
+  }
+  if (BUG(circ->hs_ident == NULL)) {
+    ret = -1;
+  }
+  if (BUG(!hs_ident_intro_circ_is_valid(circ->hs_ident))) {
+    ret = -1;
+  }
+
+  /* This can stop the tor daemon but we want that since if we don't have
+   * anonymity on this circuit, something went really wrong. */
+  assert_circ_anonymity_ok(circ, get_options());
+  return ret;
+}
+
+/* Find a descriptor intro point object that matches the given ident in the
+ * given descriptor desc. Return NULL if not found. */
+static const hs_desc_intro_point_t *
+find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident,
+                               const hs_descriptor_t *desc)
+{
+  const hs_desc_intro_point_t *intro_point = NULL;
+
+  tor_assert(ident);
+  tor_assert(desc);
+
+  SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+                          const hs_desc_intro_point_t *, ip) {
+    if (ed25519_pubkey_eq(&ident->intro_auth_pk,
+                          &ip->auth_key_cert->signed_key)) {
+      intro_point = ip;
+      break;
+    }
+  } SMARTLIST_FOREACH_END(ip);
+
+  return intro_point;
+}
+
+/* Find a descriptor intro point object from the descriptor object desc that
+ * matches the given legacy identity digest in legacy_id. Return NULL if not
+ * found. */
+static hs_desc_intro_point_t *
+find_desc_intro_point_by_legacy_id(const char *legacy_id,
+                                   const hs_descriptor_t *desc)
+{
+  hs_desc_intro_point_t *ret_ip = NULL;
+
+  tor_assert(legacy_id);
+  tor_assert(desc);
+
+  /* We will go over every intro point and try to find which one is linked to
+   * that circuit. Those lists are small so it's not that expensive. */
+  SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+                          hs_desc_intro_point_t *, ip) {
+    SMARTLIST_FOREACH_BEGIN(ip->link_specifiers,
+                            const hs_desc_link_specifier_t *, lspec) {
+      /* Not all tor node have an ed25519 identity key so we still rely on the
+       * legacy identity digest. */
+      if (lspec->type != LS_LEGACY_ID) {
+        continue;
+      }
+      if (fast_memneq(legacy_id, lspec->u.legacy_id, DIGEST_LEN)) {
+        break;
+      }
+      /* Found it. */
+      ret_ip = ip;
+      goto end;
+    } SMARTLIST_FOREACH_END(lspec);
+  } SMARTLIST_FOREACH_END(ip);
+
+ end:
+  return ret_ip;
+}
+
+/* Send an INTRODUCE1 cell along the intro circuit and populate the rend
+ * circuit identifier with the needed key material for the e2e encryption.
+ * Return 0 on success, -1 if there is a transient error such that an action
+ * has been taken to recover and -2 if there is a permanent error indicating
+ * that both circuits were closed. */
+static int
+send_introduce1(origin_circuit_t *intro_circ,
+                origin_circuit_t *rend_circ)
+{
+  int status;
+  char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+  const ed25519_public_key_t *service_identity_pk = NULL;
+  const hs_desc_intro_point_t *ip;
+
+  tor_assert(rend_circ);
+  if (intro_circ_is_ok(intro_circ) < 0) {
+    goto perm_err;
+  }
+
+  service_identity_pk = &intro_circ->hs_ident->identity_pk;
+  /* For logging purposes. There will be a time where the hs_ident will have a
+   * version number but for now there is none because it's all v3. */
+  hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address);
+
+  log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u",
+           safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id);
+
+  /* 1) Get descriptor from our cache. */
+  const hs_descriptor_t *desc =
+    hs_cache_lookup_as_client(service_identity_pk);
+  if (desc == NULL || !hs_client_any_intro_points_usable(service_identity_pk,
+                                                         desc)) {
+    log_info(LD_REND, "Request to %s %s. Trying to fetch a new descriptor.",
+             safe_str_client(onion_address),
+             (desc) ? "didn't have usable intro points" :
+             "didn't have a descriptor");
+    hs_client_refetch_hsdesc(service_identity_pk);
+    /* We just triggered a refetch, make sure every connections are back
+     * waiting for that descriptor. */
+    flag_all_conn_wait_desc(service_identity_pk);
+    /* We just asked for a refetch so this is a transient error. */
+    goto tran_err;
+  }
+
+  /* We need to find which intro point in the descriptor we are connected to
+   * on intro_circ. */
+  ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc);
+  if (BUG(ip == NULL)) {
+    /* If we can find a descriptor from this introduction circuit ident, we
+     * must have a valid intro point object. Permanent error. */
+    goto perm_err;
+  }
+
+  /* Send the INTRODUCE1 cell. */
+  if (hs_circ_send_introduce1(intro_circ, rend_circ, ip,
+                              desc->subcredential) < 0) {
+    /* Unable to send the cell, the intro circuit has been marked for close so
+     * this is a permanent error. */
+    tor_assert_nonfatal(TO_CIRCUIT(intro_circ)->marked_for_close);
+    goto perm_err;
+  }
+
+  /* Cell has been sent successfully. Copy the introduction point
+   * authentication and encryption key in the rendezvous circuit identifier so
+   * we can compute the ntor keys when we receive the RENDEZVOUS2 cell. */
+  memcpy(&rend_circ->hs_ident->intro_enc_pk, &ip->enc_key,
+         sizeof(rend_circ->hs_ident->intro_enc_pk));
+  ed25519_pubkey_copy(&rend_circ->hs_ident->intro_auth_pk,
+                      &intro_circ->hs_ident->intro_auth_pk);
+
+  /* Now, we wait for an ACK or NAK on this circuit. */
+  circuit_change_purpose(TO_CIRCUIT(intro_circ),
+                         CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT);
+  /* Set timestamp_dirty, because circuit_expire_building expects it to
+   * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */
+  TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL);
+  pathbias_count_use_attempt(intro_circ);
+
+  /* Success. */
+  status = 0;
+  goto end;
+
+ perm_err:
+  /* Permanent error: it is possible that the intro circuit was closed prior
+   * because we weren't able to send the cell. Make sure we don't double close
+   * it which would result in a warning. */
+  if (!TO_CIRCUIT(intro_circ)->marked_for_close) {
+    circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_INTERNAL);
+  }
+  circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_INTERNAL);
+  status = -2;
+  goto end;
+
+ tran_err:
+  status = -1;
+
+ end:
+  memwipe(onion_address, 0, sizeof(onion_address));
+  return status;
+}
+
+/* Using the introduction circuit circ, setup the authentication key of the
+ * intro point this circuit has extended to. */
+static void
+setup_intro_circ_auth_key(origin_circuit_t *circ)
+{
+  const hs_descriptor_t *desc;
+  const hs_desc_intro_point_t *ip;
+
+  tor_assert(circ);
+
+  desc = hs_cache_lookup_as_client(&circ->hs_ident->identity_pk);
+  if (BUG(desc == NULL)) {
+    /* Opening intro circuit without the descriptor is no good... */
+    goto end;
+  }
+
+  /* We will go over every intro point and try to find which one is linked to
+   * that circuit. Those lists are small so it's not that expensive. */
+  ip = find_desc_intro_point_by_legacy_id(
+                       circ->build_state->chosen_exit->identity_digest, desc);
+  if (ip) {
+    /* We got it, copy its authentication key to the identifier. */
+    ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk,
+                        &ip->auth_key_cert->signed_key);
+    goto end;
+  }
+
+  /* Reaching this point means we didn't find any intro point for this circuit
+   * which is not suppose to happen. */
+  tor_assert_nonfatal_unreached();
+
+ end:
+  return;
+}
+
+/* Called when an introduction circuit has opened. */
+static void
+client_intro_circ_has_opened(origin_circuit_t *circ)
+{
+  tor_assert(circ);
+  tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
+  log_info(LD_REND, "Introduction circuit %u has opened. Attaching streams.",
+           (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+
+  /* This is an introduction circuit so we'll attach the correct
+   * authentication key to the circuit identifier so it can be identified
+   * properly later on. */
+  setup_intro_circ_auth_key(circ);
+
+  connection_ap_attach_pending(1);
+}
+
+/* Called when a rendezvous circuit has opened. */
+static void
+client_rendezvous_circ_has_opened(origin_circuit_t *circ)
+{
+  tor_assert(circ);
+  tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+
+  log_info(LD_REND, "Rendezvous circuit has opened to %s.",
+           safe_str_client(
+                extend_info_describe(circ->build_state->chosen_exit)));
+
+  /* Ignore returned value, nothing we can really do. On failure, the circuit
+   * will be marked for close. */
+  hs_circ_send_establish_rendezvous(circ);
+
+  /* Register rend circuit in circuitmap if it's still alive. */
+  if (!TO_CIRCUIT(circ)->marked_for_close) {
+    hs_circuitmap_register_rend_circ_client_side(circ,
+                                     circ->hs_ident->rendezvous_cookie);
+  }
+}
+
+/* This is an helper function that convert a descriptor intro point object ip
+ * to a newly allocated extend_info_t object fully initialized. Return NULL if
+ * we can't convert it for which chances are that we are missing or malformed
+ * link specifiers. */
+static extend_info_t *
+desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip)
+{
+  extend_info_t *ei;
+  smartlist_t *lspecs = smartlist_new();
+
+  tor_assert(ip);
+
+  /* We first encode the descriptor link specifiers into the binary
+   * representation which is a trunnel object. */
+  SMARTLIST_FOREACH_BEGIN(ip->link_specifiers,
+                          const hs_desc_link_specifier_t *, desc_lspec) {
+    link_specifier_t *lspec = hs_desc_lspec_to_trunnel(desc_lspec);
+    smartlist_add(lspecs, lspec);
+  } SMARTLIST_FOREACH_END(desc_lspec);
+
+  /* Explicitely put the direct connection option to 0 because this is client
+   * side and there is no such thing as a non anonymous client. */
+  ei = hs_get_extend_info_from_lspecs(lspecs, &ip->onion_key, 0);
+
+  SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, link_specifier_free(ls));
+  smartlist_free(lspecs);
+  return ei;
+}
+
+/* Return true iff the intro point ip for the service service_pk is usable.
+ * This function checks if the intro point is in the client intro state cache
+ * and checks at the failures. It is considered usable if:
+ *   - No error happened (INTRO_POINT_FAILURE_GENERIC)
+ *   - It is not flagged as timed out (INTRO_POINT_FAILURE_TIMEOUT)
+ *   - The unreachable count is lower than
+ *     MAX_INTRO_POINT_REACHABILITY_FAILURES (INTRO_POINT_FAILURE_UNREACHABLE)
+ */
+static int
+intro_point_is_usable(const ed25519_public_key_t *service_pk,
+                      const hs_desc_intro_point_t *ip)
+{
+  const hs_cache_intro_state_t *state;
+
+  tor_assert(service_pk);
+  tor_assert(ip);
+
+  state = hs_cache_client_intro_state_find(service_pk,
+                                           &ip->auth_key_cert->signed_key);
+  if (state == NULL) {
+    /* This means we've never encountered any problem thus usable. */
+    goto usable;
+  }
+  if (state->error) {
+    log_info(LD_REND, "Intro point with auth key %s had an error. Not usable",
+             safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+    goto not_usable;
+  }
+  if (state->timed_out) {
+    log_info(LD_REND, "Intro point with auth key %s timed out. Not usable",
+             safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+    goto not_usable;
+  }
+  if (state->unreachable_count >= MAX_INTRO_POINT_REACHABILITY_FAILURES) {
+    log_info(LD_REND, "Intro point with auth key %s unreachable. Not usable",
+             safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+    goto not_usable;
+  }
+
+ usable:
+  return 1;
+ not_usable:
+  return 0;
+}
+
+/* Using a descriptor desc, return a newly allocated extend_info_t object of a
+ * randomly picked introduction point from its list. Return NULL if none are
+ * usable. */
+static extend_info_t *
+client_get_random_intro(const ed25519_public_key_t *service_pk)
+{
+  extend_info_t *ei = NULL, *ei_excluded = NULL;
+  smartlist_t *usable_ips = NULL;
+  const hs_descriptor_t *desc;
+  const hs_desc_encrypted_data_t *enc_data;
+  const or_options_t *options = get_options();
+
+  tor_assert(service_pk);
+
+  desc = hs_cache_lookup_as_client(service_pk);
+  if (desc == NULL || !hs_client_any_intro_points_usable(service_pk,
+                                                         desc)) {
+    log_info(LD_REND, "Unable to randomly select an introduction point "
+                      "because descriptor %s.",
+             (desc) ? "doesn't have usable intro point" : "is missing");
+    goto end;
+  }
+
+  enc_data = &desc->encrypted_data;
+  usable_ips = smartlist_new();
+  smartlist_add_all(usable_ips, enc_data->intro_points);
+  while (smartlist_len(usable_ips) != 0) {
+    int idx;
+    const hs_desc_intro_point_t *ip;
+
+    /* Pick a random intro point and immediately remove it from the usable
+     * list so we don't pick it again if we have to iterate more. */
+    idx = crypto_rand_int(smartlist_len(usable_ips));
+    ip = smartlist_get(usable_ips, idx);
+    smartlist_del(usable_ips, idx);
+
+    /* We need to make sure we have a usable intro points which is in a good
+     * state in our cache. */
+    if (!intro_point_is_usable(service_pk, ip)) {
+      continue;
+    }
+
+    /* Generate an extend info object from the intro point object. */
+    ei = desc_intro_point_to_extend_info(ip);
+    if (ei == NULL) {
+      /* We can get here for instance if the intro point is a private address
+       * and we aren't allowed to extend to those. */
+      continue;
+    }
+
+    /* Test the pick against ExcludeNodes. */
+    if (routerset_contains_extendinfo(options->ExcludeNodes, ei)) {
+      /* If this pick is in the ExcludeNodes list, we keep its reference so if
+       * we ever end up not being able to pick anything else and StrictNodes is
+       * unset, we'll use it. */
+      ei_excluded = ei;
+      continue;
+    }
+
+    /* Good pick! Let's go with this. */
+    goto end;
+  }
+
+  /* Reaching this point means a couple of things. Either we can't use any of
+   * the intro point listed because the IP address can't be extended to or it
+   * is listed in the ExcludeNodes list. In the later case, if StrictNodes is
+   * set, we are forced to not use anything. */
+  ei = ei_excluded;
+  if (options->StrictNodes) {
+    log_warn(LD_REND, "Every introduction points are in the ExcludeNodes set "
+             "and StrictNodes is set. We can't connect.");
+    ei = NULL;
+  }
+
+ end:
+  smartlist_free(usable_ips);
+  return ei;
+}
+
+/* For this introduction circuit, we'll look at if we have any usable
+ * introduction point left for this service. If so, we'll use the circuit to
+ * re-extend to a new intro point. Else, we'll close the circuit and its
+ * corresponding rendezvous circuit. Return 0 if we are re-extending else -1
+ * if we are closing the circuits.
+ *
+ * This is called when getting an INTRODUCE_ACK cell with a NACK. */
+static int
+close_or_reextend_intro_circ(origin_circuit_t *intro_circ)
+{
+  int ret = -1;
+  const hs_descriptor_t *desc;
+  origin_circuit_t *rend_circ;
+
+  tor_assert(intro_circ);
+
+  desc = hs_cache_lookup_as_client(&intro_circ->hs_ident->identity_pk);
+  if (BUG(desc == NULL)) {
+    /* We can't continue without a descriptor. */
+    goto close;
+  }
+  /* We still have the descriptor, great! Let's try to see if we can
+   * re-extend by looking up if there are any usable intro points. */
+  if (!hs_client_any_intro_points_usable(&intro_circ->hs_ident->identity_pk,
+                                         desc)) {
+    goto close;
+  }
+  /* Try to re-extend now. */
+  if (hs_client_reextend_intro_circuit(intro_circ) < 0) {
+    goto close;
+  }
+  /* Success on re-extending. Don't return an error. */
+  ret = 0;
+  goto end;
+
+ close:
+  /* Change the intro circuit purpose before so we don't report an intro point
+   * failure again triggering an extra descriptor fetch. The circuit can
+   * already be closed on failure to re-extend. */
+  if (!TO_CIRCUIT(intro_circ)->marked_for_close) {
+    circuit_change_purpose(TO_CIRCUIT(intro_circ),
+                           CIRCUIT_PURPOSE_C_INTRODUCE_ACKED);
+    circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED);
+  }
+  /* Close the related rendezvous circuit. */
+  rend_circ = hs_circuitmap_get_rend_circ_client_side(
+                                     intro_circ->hs_ident->rendezvous_cookie);
+  /* The rendezvous circuit might have collapsed while the INTRODUCE_ACK was
+   * inflight so we can't expect one every time. */
+  if (rend_circ) {
+    circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_FINISHED);
+  }
+
+ end:
+  return ret;
+}
+
+/* Called when we get an INTRODUCE_ACK success status code. Do the appropriate
+ * actions for the rendezvous point and finally close intro_circ. */
+static void
+handle_introduce_ack_success(origin_circuit_t *intro_circ)
+{
+  origin_circuit_t *rend_circ = NULL;
+
+  tor_assert(intro_circ);
+
+  log_info(LD_REND, "Received INTRODUCE_ACK ack! Informing rendezvous");
+
+  /* Get the rendezvous circuit for this rendezvous cookie. */
+  uint8_t *rendezvous_cookie = intro_circ->hs_ident->rendezvous_cookie;
+  rend_circ = hs_circuitmap_get_rend_circ_client_side(rendezvous_cookie);
+  if (rend_circ == NULL) {
+    log_warn(LD_REND, "Can't find any rendezvous circuit. Stopping");
+    goto end;
+  }
+
+  assert_circ_anonymity_ok(rend_circ, get_options());
+  circuit_change_purpose(TO_CIRCUIT(rend_circ),
+                         CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
+  /* Set timestamp_dirty, because circuit_expire_building expects it to
+   * specify when a circuit entered the
+   * CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED state. */
+  TO_CIRCUIT(rend_circ)->timestamp_dirty = time(NULL);
+
+ end:
+  /* We don't need the intro circuit anymore. It did what it had to do! */
+  circuit_change_purpose(TO_CIRCUIT(intro_circ),
+                         CIRCUIT_PURPOSE_C_INTRODUCE_ACKED);
+  circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED);
+
+  /* XXX: Close pending intro circuits we might have in parallel. */
+  return;
+}
+
+/* Called when we get an INTRODUCE_ACK failure status code. Depending on our
+ * failure cache status, either close the circuit or re-extend to a new
+ * introduction point. */
+static void
+handle_introduce_ack_bad(origin_circuit_t *circ, int status)
+{
+  tor_assert(circ);
+
+  log_info(LD_REND, "Received INTRODUCE_ACK nack by %s. Reason: %u",
+      safe_str_client(extend_info_describe(circ->build_state->chosen_exit)),
+      status);
+
+  /* It's a NAK. The introduction point didn't relay our request. */
+  circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING);
+
+  /* Note down this failure in the intro point failure cache. Depending on how
+   * many times we've tried this intro point, close it or reextend. */
+  hs_cache_client_intro_state_note(&circ->hs_ident->identity_pk,
+                                   &circ->hs_ident->intro_auth_pk,
+                                   INTRO_POINT_FAILURE_GENERIC);
+}
+
+/* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded
+ * cell is in payload of length payload_len. Return 0 on success else a
+ * negative value. The circuit is either close or reuse to re-extend to a new
+ * introduction point. */
+static int
+handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload,
+                     size_t payload_len)
+{
+  int status, ret = -1;
+
+  tor_assert(circ);
+  tor_assert(circ->build_state);
+  tor_assert(circ->build_state->chosen_exit);
+  assert_circ_anonymity_ok(circ, get_options());
+  tor_assert(payload);
+
+  status = hs_cell_parse_introduce_ack(payload, payload_len);
+  switch (status) {
+  case HS_CELL_INTRO_ACK_SUCCESS:
+    ret = 0;
+    handle_introduce_ack_success(circ);
+    goto end;
+  case HS_CELL_INTRO_ACK_FAILURE:
+  case HS_CELL_INTRO_ACK_BADFMT:
+  case HS_CELL_INTRO_ACK_NORELAY:
+    handle_introduce_ack_bad(circ, status);
+    /* We are going to see if we have to close the circuits (IP and RP) or we
+     * can re-extend to a new intro point. */
+    ret = close_or_reextend_intro_circ(circ);
+    break;
+  default:
+    log_info(LD_PROTOCOL, "Unknown INTRODUCE_ACK status code %u from %s",
+        status,
+        safe_str_client(extend_info_describe(circ->build_state->chosen_exit)));
+    break;
+  }
+
+ end:
+  return ret;
+}
+
+/* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The
+ * encoded cell is in payload of length payload_len. Return 0 on success or a
+ * negative value on error. On error, the circuit is marked for close. */
+static int
+handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload,
+                   size_t payload_len)
+{
+  int ret = -1;
+  curve25519_public_key_t server_pk;
+  uint8_t auth_mac[DIGEST256_LEN] = {0};
+  uint8_t handshake_info[CURVE25519_PUBKEY_LEN + sizeof(auth_mac)] = {0};
+  hs_ntor_rend_cell_keys_t keys;
+  const hs_ident_circuit_t *ident;
+
+  tor_assert(circ);
+  tor_assert(payload);
+
+  /* Make things easier. */
+  ident = circ->hs_ident;
+  tor_assert(ident);
+
+  if (hs_cell_parse_rendezvous2(payload, payload_len, handshake_info,
+                                sizeof(handshake_info)) < 0) {
+    goto err;
+  }
+  /* Get from the handshake info the SERVER_PK and AUTH_MAC. */
+  memcpy(&server_pk, handshake_info, CURVE25519_PUBKEY_LEN);
+  memcpy(auth_mac, handshake_info + CURVE25519_PUBKEY_LEN, sizeof(auth_mac));
+
+  /* Generate the handshake info. */
+  if (hs_ntor_client_get_rendezvous1_keys(&ident->intro_auth_pk,
+                                          &ident->rendezvous_client_kp,
+                                          &ident->intro_enc_pk, &server_pk,
+                                          &keys) < 0) {
+    log_info(LD_REND, "Unable to compute the rendezvous keys.");
+    goto err;
+  }
+
+  /* Critical check, make sure that the MAC matches what we got with what we
+   * computed just above. */
+  if (!hs_ntor_client_rendezvous2_mac_is_good(&keys, auth_mac)) {
+    log_info(LD_REND, "Invalid MAC in RENDEZVOUS2. Rejecting cell.");
+    goto err;
+  }
+
+  /* Setup the e2e encryption on the circuit and finalize its state. */
+  if (hs_circuit_setup_e2e_rend_circ(circ, keys.ntor_key_seed,
+                                     sizeof(keys.ntor_key_seed), 0) < 0) {
+    log_info(LD_REND, "Unable to setup the e2e encryption.");
+    goto err;
+  }
+  /* Success. Hidden service connection finalized! */
+  ret = 0;
+  goto end;
+
+ err:
+  circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ end:
+  memwipe(&keys, 0, sizeof(keys));
+  return ret;
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
 /** A circuit just finished connecting to a hidden service that the stream
  *  <b>conn</b> has been waiting for. Let the HS subsystem know about this. */
 void
@@ -38,7 +831,7 @@ hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
   }
 
   if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */
-    hs_client_attempt_succeeded(conn->hs_ident);
+    note_connection_attempt_succeeded(conn->hs_ident);
     return;
   } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */
     rend_client_note_connection_attempt_ended(conn->rend_data);
@@ -46,3 +839,379 @@ hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
   }
 }
 
+/* With the given encoded descriptor in desc_str and the service key in
+ * service_identity_pk, decode the descriptor and set the desc pointer with a
+ * newly allocated descriptor object.
+ *
+ * Return 0 on success else a negative value and desc is set to NULL. */
+int
+hs_client_decode_descriptor(const char *desc_str,
+                            const ed25519_public_key_t *service_identity_pk,
+                            hs_descriptor_t **desc)
+{
+  int ret;
+  uint8_t subcredential[DIGEST256_LEN];
+  ed25519_public_key_t blinded_pubkey;
+
+  tor_assert(desc_str);
+  tor_assert(service_identity_pk);
+  tor_assert(desc);
+
+  /* Create subcredential for this HS so that we can decrypt */
+  {
+    uint64_t current_time_period = hs_get_time_period_num(approx_time());
+    hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period,
+                            &blinded_pubkey);
+    hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential);
+  }
+
+  /* Parse descriptor */
+  ret = hs_desc_decode_descriptor(desc_str, subcredential, desc);
+  memwipe(subcredential, 0, sizeof(subcredential));
+  if (ret < 0) {
+    log_warn(LD_GENERAL, "Could not parse received descriptor as client");
+    goto err;
+  }
+
+  /* Make sure the descriptor signing key cross certifies with the computed
+   * blinded key. Without this validation, anyone knowing the subcredential
+   * and onion address can forge a descriptor. */
+  if (tor_cert_checksig((*desc)->plaintext_data.signing_key_cert,
+                        &blinded_pubkey, approx_time()) < 0) {
+    log_warn(LD_GENERAL, "Descriptor signing key certificate signature "
+                         "doesn't validate with computed blinded key.");
+    goto err;
+  }
+
+  return 0;
+ err:
+  return -1;
+}
+
+/* Return true iff there are at least one usable intro point in the service
+ * descriptor desc. */
+int
+hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
+                                  const hs_descriptor_t *desc)
+{
+  tor_assert(service_pk);
+  tor_assert(desc);
+
+  SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+                          const hs_desc_intro_point_t *, ip) {
+    if (intro_point_is_usable(service_pk, ip)) {
+      goto usable;
+    }
+  } SMARTLIST_FOREACH_END(ip);
+
+  return 0;
+ usable:
+  return 1;
+}
+
+/** Launch a connection to a hidden service directory to fetch a hidden
+ * service descriptor using <b>identity_pk</b> to get the necessary keys.
+ *
+ * On success, 1 is returned. If no hidden service is left to ask, return 0.
+ * On error, -1 is returned. (retval is only used by unittests right now) */
+int
+hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk)
+{
+  tor_assert(identity_pk);
+
+  /* Are we configured to fetch descriptors? */
+  if (!get_options()->FetchHidServDescriptors) {
+    log_warn(LD_REND, "We received an onion address for a hidden service "
+                      "descriptor but we are configured to not fetch.");
+    return 0;
+  }
+
+  /* Check if fetching a desc for this HS is useful to us right now */
+  {
+    const hs_descriptor_t *cached_desc = NULL;
+    cached_desc = hs_cache_lookup_as_client(identity_pk);
+    if (cached_desc && hs_client_any_intro_points_usable(identity_pk,
+                                                         cached_desc)) {
+      log_warn(LD_GENERAL, "We would fetch a v3 hidden service descriptor "
+                            "but we already have a useable descriprot.");
+      return 0;
+    }
+  }
+
+  return fetch_v3_desc(identity_pk);
+}
+
+/* This is called when we are trying to attach an AP connection to these
+ * hidden service circuits from connection_ap_handshake_attach_circuit().
+ * Return 0 on success, -1 for a transient error that is actions were
+ * triggered to recover or -2 for a permenent error where both circuits will
+ * marked for close.
+ *
+ * The following supports every hidden service version. */
+int
+hs_client_send_introduce1(origin_circuit_t *intro_circ,
+                          origin_circuit_t *rend_circ)
+{
+  return (intro_circ->hs_ident) ? send_introduce1(intro_circ, rend_circ) :
+                                  rend_client_send_introduction(intro_circ,
+                                                                rend_circ);
+}
+
+/* Called when the client circuit circ has been established. It can be either
+ * an introduction or rendezvous circuit. This function handles all hidden
+ * service versions. */
+void
+hs_client_circuit_has_opened(origin_circuit_t *circ)
+{
+  tor_assert(circ);
+
+  /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+   * identifier hs_ident. Can't be both. */
+  switch (TO_CIRCUIT(circ)->purpose) {
+  case CIRCUIT_PURPOSE_C_INTRODUCING:
+    if (circ->hs_ident) {
+      client_intro_circ_has_opened(circ);
+    } else {
+      rend_client_introcirc_has_opened(circ);
+    }
+    break;
+  case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+    if (circ->hs_ident) {
+      client_rendezvous_circ_has_opened(circ);
+    } else {
+      rend_client_rendcirc_has_opened(circ);
+    }
+    break;
+  default:
+    tor_assert_nonfatal_unreached();
+  }
+}
+
+/* Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of
+ * the circuit to CIRCUIT_PURPOSE_C_REND_READY. Return 0 on success else a
+ * negative value and the circuit marked for close. */
+int
+hs_client_receive_rendezvous_acked(origin_circuit_t *circ,
+                                   const uint8_t *payload, size_t payload_len)
+{
+  tor_assert(circ);
+  tor_assert(payload);
+
+  (void) payload_len;
+
+  if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) {
+    log_warn(LD_PROTOCOL, "Got a RENDEZVOUS_ESTABLISHED but we were not "
+                          "expecting one. Closing circuit.");
+    goto err;
+  }
+
+  log_info(LD_REND, "Received an RENDEZVOUS_ESTABLISHED. This circuit is "
+                    "now ready for rendezvous.");
+  circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY);
+
+  /* Set timestamp_dirty, because circuit_expire_building expects it to
+   * specify when a circuit entered the _C_REND_READY state. */
+  TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+
+  /* From a path bias point of view, this circuit is now successfully used.
+   * Waiting any longer opens us up to attacks from malicious hidden services.
+   * They could induce the client to attempt to connect to their hidden
+   * service and never reply to the client's rend requests */
+  pathbias_mark_use_success(circ);
+
+  /* If we already have the introduction circuit built, make sure we send
+   * the INTRODUCE cell _now_ */
+  connection_ap_attach_pending(1);
+
+  return 0;
+ err:
+  circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+  return -1;
+}
+
+/* This is called when a descriptor has arrived following a fetch request and
+ * has been stored in the client cache. Every entry connection that matches
+ * the service identity key in the ident will get attached to the hidden
+ * service circuit. */
+void
+hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident)
+{
+  time_t now = time(NULL);
+  smartlist_t *conns = NULL;
+
+  tor_assert(ident);
+
+  conns = connection_list_by_type_state(CONN_TYPE_AP,
+                                        AP_CONN_STATE_RENDDESC_WAIT);
+  SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
+    const hs_descriptor_t *desc;
+    entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn);
+    const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn);
+
+    /* Only consider the entry connections that matches the service for which
+     * we just fetched its descriptor. */
+    if (!edge_conn->hs_ident ||
+        !ed25519_pubkey_eq(&ident->identity_pk,
+                           &edge_conn->hs_ident->identity_pk)) {
+      continue;
+    }
+    assert_connection_ok(base_conn, now);
+
+    /* We were just called because we stored the descriptor for this service
+     * so not finding a descriptor means we have a bigger problem. */
+    desc = hs_cache_lookup_as_client(&ident->identity_pk);
+    if (BUG(desc == NULL)) {
+      goto end;
+    }
+
+    if (!hs_client_any_intro_points_usable(&ident->identity_pk, desc)) {
+      log_info(LD_REND, "Hidden service descriptor is unusable. "
+                        "Closing streams.");
+      connection_mark_unattached_ap(entry_conn,
+                                    END_STREAM_REASON_RESOLVEFAILED);
+      /* XXX: Note the connection attempt. */
+      goto end;
+    }
+
+    log_info(LD_REND, "Descriptor has arrived. Launching circuits.");
+
+    /* Restart their timeout values, so they get a fair shake at connecting to
+     * the hidden service. XXX: Improve comment on why this is needed. */
+    base_conn->timestamp_created = now;
+    base_conn->timestamp_lastread = now;
+    base_conn->timestamp_lastwritten = now;
+    /* Change connection's state into waiting for a circuit. */
+    base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
+
+    connection_ap_mark_as_pending_circuit(entry_conn);
+  } SMARTLIST_FOREACH_END(base_conn);
+
+ end:
+  /* We don't have ownership of the objects in this list. */
+  smartlist_free(conns);
+}
+
+/* Return a newly allocated extend_info_t for a randomly chosen introduction
+ * point for the given edge connection identifier ident. Return NULL if we
+ * can't pick any usable introduction points. */
+extend_info_t *
+hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn)
+{
+  tor_assert(edge_conn);
+
+  return (edge_conn->hs_ident) ?
+    client_get_random_intro(&edge_conn->hs_ident->identity_pk) :
+    rend_client_get_random_intro(edge_conn->rend_data);
+}
+/* Called when get an INTRODUCE_ACK cell on the introduction circuit circ.
+ * Return 0 on success else a negative value is returned. The circuit will be
+ * closed or reuse to extend again to another intro point. */
+int
+hs_client_receive_introduce_ack(origin_circuit_t *circ,
+                                const uint8_t *payload, size_t payload_len)
+{
+  int ret = -1;
+
+  tor_assert(circ);
+  tor_assert(payload);
+
+  if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
+    log_warn(LD_PROTOCOL, "Unexpected INTRODUCE_ACK on circuit %u.",
+             (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+    goto end;
+  }
+
+  ret = (circ->hs_ident) ? handle_introduce_ack(circ, payload, payload_len) :
+                           rend_client_introduction_acked(circ, payload,
+                                                          payload_len);
+  /* For path bias: This circuit was used successfully. NACK or ACK counts. */
+  pathbias_mark_use_success(circ);
+
+ end:
+  return ret;
+}
+
+/* Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ.  Return
+ * 0 on success else a negative value is returned. The circuit will be closed
+ * on error. */
+int
+hs_client_receive_rendezvous2(origin_circuit_t *circ,
+                              const uint8_t *payload, size_t payload_len)
+{
+  int ret = -1;
+
+  tor_assert(circ);
+  tor_assert(payload);
+
+  /* Circuit can possibly be in both state because we could receive a
+   * RENDEZVOUS2 cell before the INTRODUCE_ACK has been received. */
+  if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+      TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+    log_warn(LD_PROTOCOL, "Unexpected RENDEZVOUS2 cell on circuit %u. "
+                          "Closing circuit.",
+             (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+    goto end;
+  }
+
+  log_info(LD_REND, "Got RENDEZVOUS2 cell from hidden service on circuit %u.",
+           TO_CIRCUIT(circ)->n_circ_id);
+
+  ret = (circ->hs_ident) ? handle_rendezvous2(circ, payload, payload_len) :
+                           rend_client_receive_rendezvous(circ, payload,
+                                                          payload_len);
+ end:
+  return ret;
+}
+
+/* Extend the introduction circuit circ to another valid introduction point
+ * for the hidden service it is trying to connect to, or mark it and launch a
+ * new circuit if we can't extend it.  Return 0 on success or possible
+ * success. Return -1 and mark the introduction circuit for close on permanent
+ * failure.
+ *
+ * On failure, the caller is responsible for marking the associated rendezvous
+ * circuit for close. */
+int
+hs_client_reextend_intro_circuit(origin_circuit_t *circ)
+{
+  int ret = -1;
+  extend_info_t *ei;
+
+  tor_assert(circ);
+
+  ei = (circ->hs_ident) ?
+    client_get_random_intro(&circ->hs_ident->identity_pk) :
+    rend_client_get_random_intro(circ->rend_data);
+  if (ei == NULL) {
+    log_warn(LD_REND, "No usable introduction points left. Closing.");
+    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+    goto end;
+  }
+
+  if (circ->remaining_relay_early_cells) {
+    log_info(LD_REND, "Re-extending circ %u, this time to %s.",
+             (unsigned int) TO_CIRCUIT(circ)->n_circ_id,
+             safe_str_client(extend_info_describe(ei)));
+    ret = circuit_extend_to_new_exit(circ, ei);
+    if (ret == 0) {
+      /* We were able to extend so update the timestamp so we avoid expiring
+       * this circuit too early. The intro circuit is short live so the
+       * linkability issue is minimized, we just need the circuit to hold a
+       * bit longer so we can introduce. */
+      TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+    }
+  } else {
+    log_info(LD_REND, "Closing intro circ %u (out of RELAY_EARLY cells).",
+             (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+    /* connection_ap_handshake_attach_circuit will launch a new intro circ. */
+    ret = 0;
+  }
+
+ end:
+  extend_info_free(ei);
+  return ret;
+}
+

+ 34 - 0
src/or/hs_client.h

@@ -9,8 +9,42 @@
 #ifndef TOR_HS_CLIENT_H
 #define TOR_HS_CLIENT_H
 
+#include "crypto_ed25519.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
+
 void hs_client_note_connection_attempt_succeeded(
                                        const edge_connection_t *conn);
 
+int hs_client_decode_descriptor(
+                     const char *desc_str,
+                     const ed25519_public_key_t *service_identity_pk,
+                     hs_descriptor_t **desc);
+int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
+                                      const hs_descriptor_t *desc);
+int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk);
+
+int hs_client_send_introduce1(origin_circuit_t *intro_circ,
+                              origin_circuit_t *rend_circ);
+
+void hs_client_circuit_has_opened(origin_circuit_t *circ);
+
+int hs_client_receive_rendezvous_acked(origin_circuit_t *circ,
+                                       const uint8_t *payload,
+                                       size_t payload_len);
+int hs_client_receive_introduce_ack(origin_circuit_t *circ,
+                                    const uint8_t *payload,
+                                    size_t payload_len);
+int hs_client_receive_rendezvous2(origin_circuit_t *circ,
+                                  const uint8_t *payload,
+                                  size_t payload_len);
+
+void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident);
+
+extend_info_t *hs_client_get_random_intro_from_edge(
+                                          const edge_connection_t *edge_conn);
+
+int hs_client_reextend_intro_circuit(origin_circuit_t *circ);
+
 #endif /* TOR_HS_CLIENT_H */
 

+ 372 - 2
src/or/hs_common.c

@@ -14,17 +14,25 @@
 #include "or.h"
 
 #include "config.h"
+#include "circuitbuild.h"
 #include "networkstatus.h"
 #include "nodelist.h"
 #include "hs_cache.h"
 #include "hs_common.h"
+#include "hs_ident.h"
 #include "hs_service.h"
+#include "policies.h"
 #include "rendcommon.h"
 #include "rendservice.h"
+#include "routerset.h"
 #include "router.h"
+#include "routerset.h"
 #include "shared_random.h"
 #include "shared_random_state.h"
 
+/* Trunnel */
+#include "ed25519_cert.h"
+
 /* Ed25519 Basepoint value. Taken from section 5 of
  * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */
 static const char *str_ed25519_basepoint =
@@ -1180,9 +1188,10 @@ hs_get_hsdir_spread_store(void)
 }
 
 /** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index.
+ *  If <b>is_for_next_period</b> is set, also check the next HSDir index field.
  *  Return 0 if everything is as expected, else return -1. */
 static int
-node_has_hsdir_index(const node_t *node)
+node_has_hsdir_index(const node_t *node, int is_for_next_period)
 {
   tor_assert(node_supports_v3_hsdir(node));
 
@@ -1200,6 +1209,12 @@ node_has_hsdir_index(const node_t *node)
     return 0;
   }
 
+  if (is_for_next_period &&
+      BUG(tor_mem_is_zero((const char*)node->hsdir_index->next,
+                          DIGEST256_LEN))) {
+    return 0;
+  }
+
   return 1;
 }
 
@@ -1244,7 +1259,7 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
       node_t *n = node_get_mutable_by_id(rs->identity_digest);
       tor_assert(n);
       if (node_supports_v3_hsdir(n) && rs->is_hs_dir) {
-        if (!node_has_hsdir_index(n)) {
+        if (!node_has_hsdir_index(n, is_next_period)) {
           log_info(LD_GENERAL, "Node %s was found without hsdir index.",
                    node_describe(n));
           continue;
@@ -1313,6 +1328,361 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
   smartlist_free(sorted_nodes);
 }
 
+/*********************** HSDir request tracking ***************************/
+
+/** Return the period for which a hidden service directory cannot be queried
+ * for the same descriptor ID again, taking TestingTorNetwork into account. */
+time_t
+hs_hsdir_requery_period(const or_options_t *options)
+{
+  tor_assert(options);
+
+  if (options->TestingTorNetwork) {
+    return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING;
+  } else {
+    return REND_HID_SERV_DIR_REQUERY_PERIOD;
+  }
+}
+
+/** Tracks requests for fetching hidden service descriptors. It's used by
+ *  hidden service clients, to avoid querying HSDirs that have already failed
+ *  giving back a descriptor. The same data structure is used to track both v2
+ *  and v3 HS descriptor requests.
+ *
+ * The string map is a key/value store that contains the last request times to
+ * hidden service directories for certain queries. Specifically:
+ *
+ *   key = base32(hsdir_identity) + base32(hs_identity)
+ *   value = time_t of last request for that hs_identity to that HSDir
+ *
+ * where 'hsdir_identity' is the identity digest of the HSDir node, and
+ * 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519
+ * identity public key of the HS in the v3 case. */
+static strmap_t *last_hid_serv_requests_ = NULL;
+
+/** Returns last_hid_serv_requests_, initializing it to a new strmap if
+ * necessary. */
+STATIC strmap_t *
+get_last_hid_serv_requests(void)
+{
+  if (!last_hid_serv_requests_)
+    last_hid_serv_requests_ = strmap_new();
+  return last_hid_serv_requests_;
+}
+
+/** Look up the last request time to hidden service directory <b>hs_dir</b>
+ * for descriptor request key <b>req_key_str</b> which is the descriptor ID
+ * for a v2 service or the blinded key for v3. If <b>set</b> is non-zero,
+ * assign the current time <b>now</b> and return that.  Otherwise, return the
+ * most recent request time, or 0 if no such request has been sent before. */
+time_t
+hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir,
+                                const char *req_key_str,
+                                time_t now, int set)
+{
+  char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
+  char *hsdir_desc_comb_id = NULL;
+  time_t *last_request_ptr;
+  strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+
+  /* Create the key */
+  base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32),
+                hs_dir->identity_digest, DIGEST_LEN);
+  tor_asprintf(&hsdir_desc_comb_id, "%s%s", hsdir_id_base32, req_key_str);
+
+  if (set) {
+    time_t *oldptr;
+    last_request_ptr = tor_malloc_zero(sizeof(time_t));
+    *last_request_ptr = now;
+    oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id,
+                        last_request_ptr);
+    tor_free(oldptr);
+  } else {
+    last_request_ptr = strmap_get(last_hid_serv_requests,
+                                  hsdir_desc_comb_id);
+  }
+
+  tor_free(hsdir_desc_comb_id);
+  return (last_request_ptr) ? *last_request_ptr : 0;
+}
+
+/** Clean the history of request times to hidden service directories, so that
+ * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD
+ * seconds any more. */
+void
+hs_clean_last_hid_serv_requests(time_t now)
+{
+  strmap_iter_t *iter;
+  time_t cutoff = now - hs_hsdir_requery_period(get_options());
+  strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+  for (iter = strmap_iter_init(last_hid_serv_requests);
+       !strmap_iter_done(iter); ) {
+    const char *key;
+    void *val;
+    time_t *ent;
+    strmap_iter_get(iter, &key, &val);
+    ent = (time_t *) val;
+    if (*ent < cutoff) {
+      iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
+      tor_free(ent);
+    } else {
+      iter = strmap_iter_next(last_hid_serv_requests, iter);
+    }
+  }
+}
+
+/** Remove all requests related to the descriptor request key string
+ * <b>req_key_str</b> from the history of times of requests to hidden service
+ * directories.
+ *
+ * This is called from rend_client_note_connection_attempt_ended(), which
+ * must be idempotent, so any future changes to this function must leave it
+ * idempotent too. */
+void
+hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str)
+{
+  strmap_iter_t *iter;
+  strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+
+  for (iter = strmap_iter_init(last_hid_serv_requests);
+       !strmap_iter_done(iter); ) {
+    const char *key;
+    void *val;
+    strmap_iter_get(iter, &key, &val);
+
+    /* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of
+     * semantic, see #23305. */
+
+    /* Length check on the strings we are about to compare. The "key" contains
+     * both the base32 HSDir identity digest and the requested key at the
+     * directory. The "req_key_str" can either be a base32 descriptor ID or a
+     * base64 blinded key which should be the second part of "key". BUG on
+     * this check because both strings are internally controlled so this
+     * should never happen. */
+    if (BUG((strlen(req_key_str) + REND_DESC_ID_V2_LEN_BASE32) <
+            strlen(key))) {
+      iter = strmap_iter_next(last_hid_serv_requests, iter);
+      continue;
+    }
+
+    /* Check if the tracked request matches our request key */
+    if (tor_memeq(key + REND_DESC_ID_V2_LEN_BASE32, req_key_str,
+                  strlen(req_key_str))) {
+      iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
+      tor_free(val);
+    } else {
+      iter = strmap_iter_next(last_hid_serv_requests, iter);
+    }
+  }
+}
+
+/** Purge the history of request times to hidden service directories,
+ * so that future lookups of an HS descriptor will not fail because we
+ * accessed all of the HSDir relays responsible for the descriptor
+ * recently. */
+void
+hs_purge_last_hid_serv_requests(void)
+{
+ /* Don't create the table if it doesn't exist yet (and it may very
+   * well not exist if the user hasn't accessed any HSes)... */
+  strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_;
+  /* ... and let get_last_hid_serv_requests re-create it for us if
+   * necessary. */
+  last_hid_serv_requests_ = NULL;
+
+  if (old_last_hid_serv_requests != NULL) {
+    log_info(LD_REND, "Purging client last-HS-desc-request-time table");
+    strmap_free(old_last_hid_serv_requests, tor_free_);
+  }
+}
+
+/***********************************************************************/
+
+/** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the
+ *  one that we should use to fetch a descriptor right now. Take into account
+ *  previous failed attempts at fetching this descriptor from HSDirs using the
+ *  string identifier <b>req_key_str</b>.
+ *
+ *  Steals ownership of <b>responsible_dirs</b>.
+ *
+ *  Return the routerstatus of the chosen HSDir if successful, otherwise return
+ *  NULL if no HSDirs are worth trying right now. */
+routerstatus_t *
+hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str)
+{
+  smartlist_t *usable_responsible_dirs = smartlist_new();
+  const or_options_t *options = get_options();
+  routerstatus_t *hs_dir;
+  time_t now = time(NULL);
+  int excluded_some;
+
+  tor_assert(req_key_str);
+
+  /* Clean outdated request history first. */
+  hs_clean_last_hid_serv_requests(now);
+
+  /* Only select those hidden service directories to which we did not send a
+   * request recently and for which we have a router descriptor here. */
+  SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) {
+    time_t last = hs_lookup_last_hid_serv_request(dir, req_key_str, 0, 0);
+    const node_t *node = node_get_by_id(dir->identity_digest);
+    if (last + hs_hsdir_requery_period(options) >= now ||
+        !node || !node_has_descriptor(node)) {
+      SMARTLIST_DEL_CURRENT(responsible_dirs, dir);
+      continue;
+    }
+    if (!routerset_contains_node(options->ExcludeNodes, node)) {
+      smartlist_add(usable_responsible_dirs, dir);
+    }
+  } SMARTLIST_FOREACH_END(dir);
+
+  excluded_some =
+    smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs);
+
+  hs_dir = smartlist_choose(usable_responsible_dirs);
+  if (!hs_dir && !options->StrictNodes) {
+    hs_dir = smartlist_choose(responsible_dirs);
+  }
+
+  smartlist_free(responsible_dirs);
+  smartlist_free(usable_responsible_dirs);
+  if (!hs_dir) {
+    log_info(LD_REND, "Could not pick one of the responsible hidden "
+                      "service directories, because we requested them all "
+                      "recently without success.");
+    if (options->StrictNodes && excluded_some) {
+      log_warn(LD_REND, "Could not pick a hidden service directory for the "
+               "requested hidden service: they are all either down or "
+               "excluded, and StrictNodes is set.");
+    }
+  } else {
+    /* Remember that we are requesting a descriptor from this hidden service
+     * directory now. */
+    hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1);
+  }
+
+  return hs_dir;
+}
+
+/* From a list of link specifier, an onion key and if we are requesting a
+ * direct connection (ex: single onion service), return a newly allocated
+ * extend_info_t object. This function checks the firewall policies and if we
+ * are allowed to extend to the chosen address.
+ *
+ *  if either IPv4 or legacy ID is missing, error.
+ *  if not direct_conn, IPv4 is prefered.
+ *  if direct_conn, IPv6 is prefered if we have one available.
+ *  if firewall does not allow the chosen address, error.
+ *
+ * Return NULL if we can fulfill the conditions. */
+extend_info_t *
+hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
+                               const curve25519_public_key_t *onion_key,
+                               int direct_conn)
+{
+  int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
+  char legacy_id[DIGEST_LEN] = {0};
+  uint16_t port_v4 = 0, port_v6 = 0, port = 0;
+  tor_addr_t addr_v4, addr_v6, *addr = NULL;
+  ed25519_public_key_t ed25519_pk;
+  extend_info_t *info = NULL;
+
+  tor_assert(lspecs);
+
+  SMARTLIST_FOREACH_BEGIN(lspecs, const link_specifier_t *, ls) {
+    switch (link_specifier_get_ls_type(ls)) {
+    case LS_IPV4:
+      /* Skip if we already seen a v4. */
+      if (have_v4) continue;
+      tor_addr_from_ipv4h(&addr_v4,
+                          link_specifier_get_un_ipv4_addr(ls));
+      port_v4 = link_specifier_get_un_ipv4_port(ls);
+      have_v4 = 1;
+      break;
+    case LS_IPV6:
+      /* Skip if we already seen a v6. */
+      if (have_v6) continue;
+      tor_addr_from_ipv6_bytes(&addr_v6,
+          (const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
+      port_v6 = link_specifier_get_un_ipv6_port(ls);
+      have_v6 = 1;
+      break;
+    case LS_LEGACY_ID:
+      /* Make sure we do have enough bytes for the legacy ID. */
+      if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
+        break;
+      }
+      memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls),
+             sizeof(legacy_id));
+      have_legacy_id = 1;
+      break;
+    case LS_ED25519_ID:
+      memcpy(ed25519_pk.pubkey,
+             link_specifier_getconstarray_un_ed25519_id(ls),
+             ED25519_PUBKEY_LEN);
+      have_ed25519_id = 1;
+      break;
+    default:
+      /* Ignore unknown. */
+      break;
+    }
+  } SMARTLIST_FOREACH_END(ls);
+
+  /* IPv4 and legacy ID are mandatory. */
+  if (!have_v4 || !have_legacy_id) {
+    goto done;
+  }
+  /* By default, we pick IPv4 but this might change to v6 if certain
+   * conditions are met. */
+  addr = &addr_v4; port = port_v4;
+
+  /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
+   * circuit so we can't extend in IPv6. And at this point, we do have an IPv4
+   * address available so go to validation. */
+  if (!direct_conn) {
+    goto validate;
+  }
+
+  /* From this point on, we have a request for a direct connection to the
+   * rendezvous point so make sure we can actually connect through our
+   * firewall. We'll prefer IPv6. */
+
+  /* IPv6 test. */
+  if (have_v6 &&
+      fascist_firewall_allows_address_addr(&addr_v6, port_v6,
+                                           FIREWALL_OR_CONNECTION, 1, 1)) {
+    /* Direct connection and we can reach it in IPv6 so go for it. */
+    addr = &addr_v6; port = port_v6;
+    goto validate;
+  }
+  /* IPv4 test and we are sure we have a v4 because of the check above. */
+  if (fascist_firewall_allows_address_addr(&addr_v4, port_v4,
+                                           FIREWALL_OR_CONNECTION, 0, 0)) {
+    /* Direct connection and we can reach it in IPv4 so go for it. */
+    addr = &addr_v4; port = port_v4;
+    goto validate;
+  }
+
+ validate:
+  /* We'll validate now that the address we've picked isn't a private one. If
+   * it is, are we allowing to extend to private address? */
+  if (!extend_info_addr_is_allowed(addr)) {
+    log_warn(LD_REND, "Requested address is private and it is not "
+                      "allowed to extend to it: %s:%u",
+             fmt_addr(&addr_v4), port_v4);
+    goto done;
+  }
+
+  /* We do have everything for which we think we can connect successfully. */
+  info = extend_info_new(NULL, legacy_id,
+                         (have_ed25519_id) ? &ed25519_pk : NULL, NULL,
+                         onion_key, addr, port);
+ done:
+  return info;
+}
+
+/***********************************************************************/
+
 /* Initialize the entire HS subsytem. This is called in tor_init() before any
  * torrc options are loaded. Only for >= v3. */
 void

+ 24 - 0
src/or/hs_common.h

@@ -187,6 +187,8 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data,
 const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
                                        size_t *len_out);
 
+routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32);
+
 void hs_get_subcredential(const ed25519_public_key_t *identity_pk,
                           const ed25519_public_key_t *blinded_pk,
                           uint8_t *subcred_out);
@@ -219,18 +221,40 @@ int32_t hs_get_hsdir_spread_store(void);
 void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
                                uint64_t time_period_num, int is_next_period,
                                int is_client, smartlist_t *responsible_dirs);
+routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs,
+                              const char *req_key_str);
+
+time_t hs_hsdir_requery_period(const or_options_t *options);
+time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir,
+                                       const char *desc_id_base32,
+                                       time_t now, int set);
+void hs_clean_last_hid_serv_requests(time_t now);
+void hs_purge_hid_serv_from_last_hid_serv_requests(const char *desc_id);
+void hs_purge_last_hid_serv_requests(void);
 
 int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn);
 
 void hs_inc_rdv_stream_counter(origin_circuit_t *circ);
 void hs_dec_rdv_stream_counter(origin_circuit_t *circ);
 
+extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
+                                  const curve25519_public_key_t *onion_key,
+                                  int direct_conn);
+
 #ifdef HS_COMMON_PRIVATE
 
 STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out);
 
+/** The period for which a hidden service directory cannot be queried for
+ * the same descriptor ID again. */
+#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60)
+/** Test networks generate a new consensus every 5 or 10 seconds.
+ * So allow them to requery HSDirs much faster. */
+#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5)
+
 #ifdef TOR_UNIT_TESTS
 
+STATIC strmap_t *get_last_hid_serv_requests(void);
 STATIC uint64_t get_time_period_length(void);
 
 STATIC uint8_t *get_first_cached_disaster_srv(void);

+ 96 - 50
src/or/hs_descriptor.c

@@ -55,11 +55,10 @@
 /* For unit tests.*/
 #define HS_DESCRIPTOR_PRIVATE
 
-#include "hs_descriptor.h"
-
 #include "or.h"
-#include "circuitbuild.h"
 #include "ed25519_cert.h" /* Trunnel interface. */
+#include "hs_descriptor.h"
+#include "circuitbuild.h"
 #include "parsecommon.h"
 #include "rendcache.h"
 #include "hs_cache.h"
@@ -332,50 +331,10 @@ encode_link_specifiers(const smartlist_t *specs)
 
   SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *,
                           spec) {
-    link_specifier_t *ls = link_specifier_new();
-    link_specifier_set_ls_type(ls, spec->type);
-
-    switch (spec->type) {
-    case LS_IPV4:
-      link_specifier_set_un_ipv4_addr(ls,
-                                      tor_addr_to_ipv4h(&spec->u.ap.addr));
-      link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
-      /* Four bytes IPv4 and two bytes port. */
-      link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
-                                    sizeof(spec->u.ap.port));
-      break;
-    case LS_IPV6:
-    {
-      size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
-      const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
-      uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
-      memcpy(ipv6_array, in6_addr, addr_len);
-      link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
-      /* Sixteen bytes IPv6 and two bytes port. */
-      link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
-      break;
-    }
-    case LS_LEGACY_ID:
-    {
-      size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
-      uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
-      memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
-      link_specifier_set_ls_len(ls, legacy_id_len);
-      break;
-    }
-    case LS_ED25519_ID:
-    {
-      size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls);
-      uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls);
-      memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len);
-      link_specifier_set_ls_len(ls, ed25519_id_len);
-      break;
-    }
-    default:
-      tor_assert(0);
+    link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec);
+    if (ls) {
+      link_specifier_list_add_spec(lslist, ls);
     }
-
-    link_specifier_list_add_spec(lslist, ls);
   } SMARTLIST_FOREACH_END(spec);
 
   {
@@ -2358,10 +2317,10 @@ static int
  *
  * Return 0 on success and encoded_out is a valid pointer. On error, -1 is
  * returned and encoded_out is set to NULL. */
-int
-hs_desc_encode_descriptor(const hs_descriptor_t *desc,
-                          const ed25519_keypair_t *signing_kp,
-                          char **encoded_out)
+MOCK_IMPL(int,
+hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
+                           const ed25519_keypair_t *signing_kp,
+                           char **encoded_out))
 {
   int ret = -1;
   uint32_t version;
@@ -2438,6 +2397,37 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data)
           data->superencrypted_blob_size);
 }
 
+/* Return the size in bytes of the given encrypted data object. Used by OOM
+ * subsystem. */
+static size_t
+hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data)
+{
+  tor_assert(data);
+  size_t intro_size = 0;
+  if (data->intro_auth_types) {
+    intro_size +=
+      smartlist_len(data->intro_auth_types) * sizeof(intro_auth_types);
+  }
+  if (data->intro_points) {
+    /* XXX could follow pointers here and get more accurate size */
+    intro_size +=
+      smartlist_len(data->intro_points) * sizeof(hs_desc_intro_point_t);
+  }
+
+  return sizeof(*data) + intro_size;
+}
+
+/* Return the size in bytes of the given descriptor object. Used by OOM
+ * subsystem. */
+  size_t
+hs_desc_obj_size(const hs_descriptor_t *data)
+{
+  tor_assert(data);
+  return (hs_desc_plaintext_obj_size(&data->plaintext_data) +
+          hs_desc_encrypted_obj_size(&data->encrypted_data) +
+          sizeof(data->subcredential));
+}
+
 /* Return a newly allocated descriptor intro point. */
 hs_desc_intro_point_t *
 hs_desc_intro_point_new(void)
@@ -2545,3 +2535,59 @@ hs_descriptor_clear_intro_points(hs_descriptor_t *desc)
   }
 }
 
+/* From a descriptor link specifier object spec, returned a newly allocated
+ * link specifier object that is the encoded representation of spec. Return
+ * NULL on error. */
+link_specifier_t *
+hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec)
+{
+  tor_assert(spec);
+
+  link_specifier_t *ls = link_specifier_new();
+  link_specifier_set_ls_type(ls, spec->type);
+
+  switch (spec->type) {
+  case LS_IPV4:
+    link_specifier_set_un_ipv4_addr(ls,
+                                    tor_addr_to_ipv4h(&spec->u.ap.addr));
+    link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
+    /* Four bytes IPv4 and two bytes port. */
+    link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
+                                  sizeof(spec->u.ap.port));
+    break;
+  case LS_IPV6:
+  {
+    size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
+    const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
+    uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
+    memcpy(ipv6_array, in6_addr, addr_len);
+    link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
+    /* Sixteen bytes IPv6 and two bytes port. */
+    link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
+    break;
+  }
+  case LS_LEGACY_ID:
+  {
+    size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
+    uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
+    memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
+    link_specifier_set_ls_len(ls, legacy_id_len);
+    break;
+  }
+  case LS_ED25519_ID:
+  {
+    size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls);
+    uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls);
+    memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len);
+    link_specifier_set_ls_len(ls, ed25519_id_len);
+    break;
+  }
+  default:
+    tor_assert_nonfatal_unreached();
+    link_specifier_free(ls);
+    ls = NULL;
+  }
+
+  return ls;
+}
+

+ 11 - 3
src/or/hs_descriptor.h

@@ -18,6 +18,9 @@
 #include "crypto_ed25519.h"
 #include "torcert.h"
 
+/* Trunnel */
+struct link_specifier_t;
+
 /* The earliest descriptor format version we support. */
 #define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3
 /* The latest descriptor format version we support. */
@@ -211,9 +214,10 @@ hs_desc_link_specifier_t *hs_desc_link_specifier_new(
                                   const extend_info_t *info, uint8_t type);
 void hs_descriptor_clear_intro_points(hs_descriptor_t *desc);
 
-int hs_desc_encode_descriptor(const hs_descriptor_t *desc,
-                              const ed25519_keypair_t *signing_kp,
-                              char **encoded_out);
+MOCK_DECL(int,
+          hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
+                                     const ed25519_keypair_t *signing_kp,
+                                     char **encoded_out));
 
 int hs_desc_decode_descriptor(const char *encoded,
                               const uint8_t *subcredential,
@@ -223,11 +227,15 @@ int hs_desc_decode_plaintext(const char *encoded,
 int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
                              hs_desc_encrypted_data_t *desc_out);
 
+size_t hs_desc_obj_size(const hs_descriptor_t *data);
 size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data);
 
 hs_desc_intro_point_t *hs_desc_intro_point_new(void);
 void hs_desc_intro_point_free(hs_desc_intro_point_t *ip);
 
+link_specifier_t *hs_desc_lspec_to_trunnel(
+                                   const hs_desc_link_specifier_t *spec);
+
 #ifdef HS_DESCRIPTOR_PRIVATE
 
 /* Encoding. */

+ 22 - 0
src/or/hs_ident.c

@@ -86,3 +86,25 @@ hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident)
   tor_free(ident);
 }
 
+/* Return true if the given ident is valid for an introduction circuit. */
+int
+hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident)
+{
+  if (ident == NULL) {
+    goto invalid;
+  }
+
+  if (ed25519_public_key_is_zero(&ident->identity_pk)) {
+    goto invalid;
+  }
+
+  if (ed25519_public_key_is_zero(&ident->intro_auth_pk)) {
+    goto invalid;
+  }
+
+  /* Valid. */
+  return 1;
+ invalid:
+  return 0;
+}
+

+ 3 - 0
src/or/hs_ident.h

@@ -126,5 +126,8 @@ hs_ident_edge_conn_t *hs_ident_edge_conn_new(
                                     const ed25519_public_key_t *identity_pk);
 void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident);
 
+/* Validators */
+int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident);
+
 #endif /* TOR_HS_IDENT_H */
 

+ 152 - 2
src/or/hs_service.c

@@ -972,6 +972,10 @@ service_descriptor_free(hs_service_descriptor_t *desc)
   /* Cleanup all intro points. */
   digest256map_free(desc->intro_points.map, service_intro_point_free_);
   digestmap_free(desc->intro_points.failed_id, tor_free_);
+  if (desc->previous_hsdirs) {
+    SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s));
+    smartlist_free(desc->previous_hsdirs);
+  }
   tor_free(desc);
 }
 
@@ -985,6 +989,7 @@ service_descriptor_new(void)
   sdesc->intro_points.map = digest256map_new();
   sdesc->intro_points.failed_id = digestmap_new();
   sdesc->hsdir_missing_info = smartlist_new();
+  sdesc->previous_hsdirs = smartlist_new();
   return sdesc;
 }
 
@@ -1511,6 +1516,52 @@ pick_needed_intro_points(hs_service_t *service,
   return i;
 }
 
+/** Clear previous cached HSDirs in <b>desc</b>. */
+static void
+service_desc_clear_previous_hsdirs(hs_service_descriptor_t *desc)
+{
+  if (BUG(!desc->previous_hsdirs)) {
+    return;
+  }
+
+  SMARTLIST_FOREACH(desc->previous_hsdirs, char*, s, tor_free(s));
+  smartlist_clear(desc->previous_hsdirs);
+}
+
+/** Note that we attempted to upload <b>desc</b> to <b>hsdir</b>. */
+static void
+service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir)
+{
+  char b64_digest[BASE64_DIGEST_LEN+1] = {0};
+  digest_to_base64(b64_digest, hsdir->identity);
+
+  if (BUG(!desc->previous_hsdirs)) {
+    return;
+  }
+
+  if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) {
+    smartlist_add_strdup(desc->previous_hsdirs, b64_digest);
+    smartlist_sort_strings(desc->previous_hsdirs);
+  }
+}
+
+/** Schedule an upload of <b>desc</b>. If <b>descriptor_changed</b> is set, it
+ *  means that this descriptor is dirty. */
+STATIC void
+service_desc_schedule_upload(hs_service_descriptor_t *desc,
+                             time_t now,
+                             int descriptor_changed)
+
+{
+  desc->next_upload_time = now;
+
+  /* If the descriptor changed, clean up the old HSDirs list. We want to
+   * re-upload no matter what. */
+  if (descriptor_changed) {
+    service_desc_clear_previous_hsdirs(desc);
+  }
+}
+
 /* Update the given descriptor from the given service. The possible update
  * actions includes:
  *    - Picking missing intro points if needed.
@@ -1543,7 +1594,7 @@ update_service_descriptor(hs_service_t *service,
       /* We'll build those introduction point into the descriptor once we have
        * confirmation that the circuits are opened and ready. However,
        * indicate that this descriptor should be uploaded from now on. */
-      desc->next_upload_time = now;
+      service_desc_schedule_upload(desc, now, 1);
     }
     /* Were we able to pick all the intro points we needed? If not, we'll
      * flag the descriptor that it's missing intro points because it
@@ -1688,6 +1739,13 @@ rotate_all_descriptors(time_t now)
      * it in order to make sure we don't rotate at next check. */
     service->state.in_overlap_period = 1;
 
+    /* We just entered overlap period: recompute all HSDir indices. We need to
+     * do this otherwise nodes can get stuck with old HSDir indices until we
+     * fetch a new consensus, and we might need to reupload our desc before
+     * that. */
+    /* XXX find a better place than rotate_all_descriptors() to do this */
+    nodelist_recompute_all_hsdir_indices();
+
     /* If we have a next descriptor lined up, rotate the descriptors so that it
      * becomes current. */
     if (service->desc_next) {
@@ -1972,6 +2030,9 @@ upload_descriptor_to_hsdir(const hs_service_t *service,
   directory_initiate_request(dir_req);
   directory_request_free(dir_req);
 
+  /* Add this node to previous_hsdirs list */
+  service_desc_note_upload(desc, hsdir);
+
   /* Logging so we know where it was sent. */
   {
     int is_next_desc = (service->desc_next == desc);
@@ -2189,7 +2250,7 @@ set_descriptor_revision_counter(hs_descriptor_t *hs_desc)
  * responsible hidden service directories. If for_next_period is true, the set
  * of directories are selected using the next hsdir_index. This does nothing
  * if PublishHidServDescriptors is false. */
-static void
+STATIC void
 upload_descriptor_to_all(const hs_service_t *service,
                          hs_service_descriptor_t *desc, int for_next_period)
 {
@@ -2288,6 +2349,17 @@ should_service_upload_descriptor(const hs_service_t *service,
     goto cannot;
   }
 
+  /* Don't upload desc if we don't have a live consensus */
+  if (!networkstatus_get_live_consensus(now)) {
+    goto cannot;
+  }
+
+  /* Do we know enough router descriptors to have adequate vision of the HSDir
+     hash ring? */
+  if (!router_have_minimum_dir_info()) {
+    goto cannot;
+  }
+
   /* Can upload! */
   return 1;
  cannot:
@@ -2618,10 +2690,88 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list)
   smartlist_add(list, hs_path_from_filename(s_dir, fname));
 }
 
+/** The set of HSDirs have changed: check if the change affects our descriptor
+ *  HSDir placement, and if it does, reupload the desc. */
+static int
+service_desc_hsdirs_changed(const hs_service_t *service,
+                            const hs_service_descriptor_t *desc)
+{
+  int retval = 0;
+  smartlist_t *responsible_dirs = smartlist_new();
+  smartlist_t *b64_responsible_dirs = smartlist_new();
+
+  /* No desc upload has happened yet: it will happen eventually */
+  if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) {
+    goto done;
+  }
+
+  /* Get list of responsible hsdirs */
+  hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+                            service->desc_next == desc, 0, responsible_dirs);
+
+  /* Make a second list with their b64ed identity digests, so that we can
+   * compare it with out previous list of hsdirs */
+  SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) {
+    char b64_digest[BASE64_DIGEST_LEN+1] = {0};
+    digest_to_base64(b64_digest, hsdir_rs->identity_digest);
+    smartlist_add_strdup(b64_responsible_dirs, b64_digest);
+  } SMARTLIST_FOREACH_END(hsdir_rs);
+
+  /* Sort this new smartlist so that we can compare it with the other one */
+  smartlist_sort_strings(b64_responsible_dirs);
+
+  /* Check whether the set of HSDirs changed */
+  if (!smartlist_strings_eq(b64_responsible_dirs, desc->previous_hsdirs)) {
+    log_info(LD_GENERAL, "Received new dirinfo and set of hsdirs changed!");
+    retval = 1;
+  } else {
+    log_debug(LD_GENERAL, "No change in hsdir set!");
+  }
+
+ done:
+  smartlist_free(responsible_dirs);
+
+  SMARTLIST_FOREACH(b64_responsible_dirs, char*, s, tor_free(s));
+  smartlist_free(b64_responsible_dirs);
+
+  return retval;
+}
+
 /* ========== */
 /* Public API */
 /* ========== */
 
+/* We just received a new batch of descriptors which might affect the shape of
+ * the HSDir hash ring. Signal that we should re-upload our HS descriptors. */
+void
+hs_hsdir_set_changed_consider_reupload(void)
+{
+  time_t now = approx_time();
+
+  /* Check if HS subsystem is initialized */
+  if (!hs_service_map) {
+    return;
+  }
+
+  /* Basic test: If we have not bootstrapped 100% yet, no point in even trying
+     to upload descriptor. */
+  if (!router_have_minimum_dir_info()) {
+    return;
+  }
+
+  log_info(LD_GENERAL, "Received new dirinfo: Checking hash ring for changes");
+
+  /* Go over all descriptors and check if the set of HSDirs changed for any of
+   * them. Schedule reupload if so. */
+  FOR_EACH_SERVICE_BEGIN(service) {
+    FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+      if (service_desc_hsdirs_changed(service, desc)) {
+        service_desc_schedule_upload(desc, now, 0);
+      }
+    } FOR_EACH_DESCRIPTOR_END;
+  } FOR_EACH_SERVICE_END;
+}
+
 /* Return the number of service we have configured and usable. */
 unsigned int
 hs_service_get_num_services(void)

+ 15 - 0
src/or/hs_service.h

@@ -129,6 +129,12 @@ typedef struct hs_service_descriptor_t {
    * list are re-tried to upload this descriptor when our directory information
    * have been updated. */
   smartlist_t *hsdir_missing_info;
+
+  /** List of the responsible HSDirs (their b64ed identity digest) last time we
+   *  uploaded this descriptor. If the set of responsible HSDirs is different
+   *  from this list, this means we received new dirinfo and we need to
+   *  reupload our descriptor. This list is always sorted lexicographically. */
+  smartlist_t *previous_hsdirs;
 } hs_service_descriptor_t;
 
 /* Service key material. */
@@ -260,6 +266,7 @@ void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
                                          smartlist_t *dir_list);
 int hs_service_set_conn_addr_port(const origin_circuit_t *circ,
                                   edge_connection_t *conn);
+void hs_hsdir_set_changed_consider_reupload(void);
 
 void hs_service_dir_info_changed(void);
 void hs_service_run_scheduled_events(time_t now);
@@ -338,6 +345,14 @@ check_state_line_for_service_rev_counter(const char *state_line,
 STATIC int
 write_address_to_file(const hs_service_t *service, const char *fname_);
 
+STATIC void upload_descriptor_to_all(const hs_service_t *service,
+                                     hs_service_descriptor_t *desc,
+                                     int for_next_period);
+
+STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc,
+                                         time_t now,
+                                         int descriptor_changed);
+
 #endif /* TOR_UNIT_TESTS */
 
 #endif /* HS_SERVICE_PRIVATE */

+ 2 - 1
src/or/main.c

@@ -1827,8 +1827,8 @@ clean_caches_callback(time_t now, const or_options_t *options)
 {
   /* Remove old information from rephist and the rend cache. */
   rep_history_clean(now - options->RephistTrackTime);
-  rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
   rend_cache_clean(now, REND_CACHE_TYPE_SERVICE);
+  hs_cache_clean_as_client(now);
   hs_cache_clean_as_dir(now);
   microdesc_cache_rebuild(NULL, 0);
 #define CLEAN_CACHES_INTERVAL (30*60)
@@ -1847,6 +1847,7 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options)
    * clean it as soon as we can since we want to make sure the client waits
    * as little as possible for reachability reasons. */
   rend_cache_failure_clean(now);
+  hs_cache_client_intro_state_clean(now);
   return 30;
 }
 

+ 24 - 2
src/or/nodelist.c

@@ -238,6 +238,27 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns)
   return;
 }
 
+/** Recompute all node hsdir indices. */
+void
+nodelist_recompute_all_hsdir_indices(void)
+{
+  networkstatus_t *consensus;
+  if (!the_nodelist) {
+    return;
+  }
+
+  /* Get a live consensus. Abort if not found */
+  consensus = networkstatus_get_live_consensus(approx_time());
+  if (!consensus) {
+    return;
+  }
+
+  /* Recompute all hsdir indices */
+  SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) {
+    node_set_hsdir_index(node, consensus);
+  } SMARTLIST_FOREACH_END(node);
+}
+
 /** Called when a node's address changes. */
 static void
 node_addrs_changed(node_t *node)
@@ -1741,8 +1762,8 @@ static char dir_info_status[512] = "";
  * no exits in the consensus."
  * To obtain the final weighted bandwidth, we multiply the
  * weighted bandwidth fraction for each position (guard, middle, exit). */
-int
-router_have_minimum_dir_info(void)
+MOCK_IMPL(int,
+router_have_minimum_dir_info,(void))
 {
   static int logged_delay=0;
   const char *delay_fetches_msg = NULL;
@@ -1789,6 +1810,7 @@ router_dir_info_changed(void)
 {
   need_to_update_have_min_dir_info = 1;
   rend_hsdir_routers_changed();
+  hs_hsdir_set_changed_consider_reupload();
 }
 
 /** Return a string describing what we're missing before we have enough

+ 3 - 1
src/or/nodelist.h

@@ -28,6 +28,8 @@ void nodelist_remove_routerinfo(routerinfo_t *ri);
 void nodelist_purge(void);
 smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md);
 
+void nodelist_recompute_all_hsdir_indices(void);
+
 void nodelist_free_all(void);
 void nodelist_assert_ok(void);
 
@@ -105,7 +107,7 @@ int addrs_in_same_network_family(const tor_addr_t *a1,
  * no exits in the consensus, we wait for enough info to create internal
  * paths, and should avoid creating exit paths, as they will simply fail.
  * We make sure we create all available circuit types at the same time. */
-int router_have_minimum_dir_info(void);
+MOCK_DECL(int, router_have_minimum_dir_info,(void));
 
 /** Set to CONSENSUS_PATH_EXIT if there is at least one exit node
  * in the consensus. We update this flag in compute_frac_paths_available if

+ 4 - 1
src/or/or.h

@@ -425,7 +425,10 @@ typedef enum {
 #define DIR_PURPOSE_UPLOAD_HSDESC 20
 /** A connection to a hidden service directory: fetch a v3 descriptor. */
 #define DIR_PURPOSE_FETCH_HSDESC 21
-#define DIR_PURPOSE_MAX_ 21
+/** A connection to a directory server: set after a hidden service descriptor
+ * is downloaded. */
+#define DIR_PURPOSE_HAS_FETCHED_HSDESC 22
+#define DIR_PURPOSE_MAX_ 22
 
 /** True iff <b>p</b> is a purpose corresponding to uploading
  * data to a directory server. */

+ 2 - 2
src/or/rendcache.c

@@ -512,7 +512,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e)
   tor_assert(rend_cache);
   tor_assert(query);
 
-  if (!rend_valid_service_id(query)) {
+  if (!rend_valid_v2_service_id(query)) {
     ret = -EINVAL;
     goto end;
   }
@@ -558,7 +558,7 @@ rend_cache_lookup_v2_desc_as_service(const char *query, rend_cache_entry_t **e)
   tor_assert(rend_cache_local_service);
   tor_assert(query);
 
-  if (!rend_valid_service_id(query)) {
+  if (!rend_valid_v2_service_id(query)) {
     ret = -EINVAL;
     goto end;
   }

+ 28 - 334
src/or/rendclient.c

@@ -18,6 +18,7 @@
 #include "directory.h"
 #include "hs_common.h"
 #include "hs_circuit.h"
+#include "hs_client.h"
 #include "main.h"
 #include "networkstatus.h"
 #include "nodelist.h"
@@ -42,7 +43,7 @@ rend_client_purge_state(void)
   rend_cache_purge();
   rend_cache_failure_purge();
   rend_client_cancel_descriptor_fetches();
-  rend_client_purge_last_hid_serv_requests();
+  hs_purge_last_hid_serv_requests();
 }
 
 /** Called when we've established a circuit to an introduction point:
@@ -89,46 +90,6 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ)
   return 0;
 }
 
-/** Extend the introduction circuit <b>circ</b> to another valid
- * introduction point for the hidden service it is trying to connect
- * to, or mark it and launch a new circuit if we can't extend it.
- * Return 0 on success or possible success.  Return -1 and mark the
- * introduction circuit for close on permanent failure.
- *
- * On failure, the caller is responsible for marking the associated
- * rendezvous circuit for close. */
-static int
-rend_client_reextend_intro_circuit(origin_circuit_t *circ)
-{
-  extend_info_t *extend_info;
-  int result;
-  extend_info = rend_client_get_random_intro(circ->rend_data);
-  if (!extend_info) {
-    log_warn(LD_REND,
-             "No usable introduction points left for %s. Closing.",
-             safe_str_client(rend_data_get_address(circ->rend_data)));
-    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
-    return -1;
-  }
-  // XXX: should we not re-extend if hs_circ_has_timed_out?
-  if (circ->remaining_relay_early_cells) {
-    log_info(LD_REND,
-             "Re-extending circ %u, this time to %s.",
-             (unsigned)circ->base_.n_circ_id,
-             safe_str_client(extend_info_describe(extend_info)));
-    result = circuit_extend_to_new_exit(circ, extend_info);
-  } else {
-    log_info(LD_REND,
-             "Closing intro circ %u (out of RELAY_EARLY cells).",
-             (unsigned)circ->base_.n_circ_id);
-    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
-    /* connection_ap_handshake_attach_circuit will launch a new intro circ. */
-    result = 0;
-  }
-  extend_info_free(extend_info);
-  return result;
-}
-
 /** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell
  * down introcirc if possible.
  */
@@ -202,7 +163,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
                                    introcirc->build_state->chosen_exit)),
              smartlist_len(entry->parsed->intro_nodes));
 
-    if (rend_client_reextend_intro_circuit(introcirc)) {
+    if (hs_client_reextend_intro_circuit(introcirc)) {
       status = -2;
       goto perm_err;
     } else {
@@ -391,23 +352,11 @@ rend_client_introduction_acked(origin_circuit_t *circ,
   origin_circuit_t *rendcirc;
   (void) request; // XXXX Use this.
 
-  if (circ->base_.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
-    log_warn(LD_PROTOCOL,
-             "Received REND_INTRODUCE_ACK on unexpected circuit %u.",
-             (unsigned)circ->base_.n_circ_id);
-    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
-    return -1;
-  }
-
   tor_assert(circ->build_state);
   tor_assert(circ->build_state->chosen_exit);
   assert_circ_anonymity_ok(circ, options);
   tor_assert(circ->rend_data);
 
-  /* For path bias: This circuit was used successfully. Valid
-   * nacks and acks count. */
-  pathbias_mark_use_success(circ);
-
   if (request_len == 0) {
     /* It's an ACK; the introduction point relayed our introduction request. */
     /* Locate the rend circ which is waiting to hear about this ack,
@@ -449,7 +398,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
                                              INTRO_POINT_FAILURE_GENERIC)>0) {
       /* There are introduction points left. Re-extend the circuit to
        * another intro point and try again. */
-      int result = rend_client_reextend_intro_circuit(circ);
+      int result = hs_client_reextend_intro_circuit(circ);
       /* XXXX If that call failed, should we close the rend circuit,
        * too? */
       return result;
@@ -465,230 +414,6 @@ rend_client_introduction_acked(origin_circuit_t *circ,
   return 0;
 }
 
-/** The period for which a hidden service directory cannot be queried for
- * the same descriptor ID again. */
-#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60)
-/** Test networks generate a new consensus every 5 or 10 seconds.
- * So allow them to requery HSDirs much faster. */
-#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5)
-
-/** Return the period for which a hidden service directory cannot be queried
- * for the same descriptor ID again, taking TestingTorNetwork into account. */
-static time_t
-hsdir_requery_period(const or_options_t *options)
-{
-  tor_assert(options);
-
-  if (options->TestingTorNetwork) {
-    return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING;
-  } else {
-    return REND_HID_SERV_DIR_REQUERY_PERIOD;
-  }
-}
-
-/** Contains the last request times to hidden service directories for
- * certain queries; each key is a string consisting of the
- * concatenation of a base32-encoded HS directory identity digest and
- * base32-encoded HS descriptor ID; each value is a pointer to a time_t
- * holding the time of the last request for that descriptor ID to that
- * HS directory. */
-static strmap_t *last_hid_serv_requests_ = NULL;
-
-/** Returns last_hid_serv_requests_, initializing it to a new strmap if
- * necessary. */
-static strmap_t *
-get_last_hid_serv_requests(void)
-{
-  if (!last_hid_serv_requests_)
-    last_hid_serv_requests_ = strmap_new();
-  return last_hid_serv_requests_;
-}
-
-#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \
-                                       REND_DESC_ID_V2_LEN_BASE32)
-
-/** Look up the last request time to hidden service directory <b>hs_dir</b>
- * for descriptor ID <b>desc_id_base32</b>. If <b>set</b> is non-zero,
- * assign the current time <b>now</b> and return that. Otherwise, return the
- * most recent request time, or 0 if no such request has been sent before.
- */
-static time_t
-lookup_last_hid_serv_request(routerstatus_t *hs_dir,
-                             const char *desc_id_base32,
-                             time_t now, int set)
-{
-  char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
-  char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1];
-  time_t *last_request_ptr;
-  strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
-  base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32),
-                hs_dir->identity_digest, DIGEST_LEN);
-  tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s",
-               hsdir_id_base32,
-               desc_id_base32);
-  /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) ==
-                       LAST_HID_SERV_REQUEST_KEY_LEN); */
-  if (set) {
-    time_t *oldptr;
-    last_request_ptr = tor_malloc_zero(sizeof(time_t));
-    *last_request_ptr = now;
-    oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id,
-                        last_request_ptr);
-    tor_free(oldptr);
-  } else
-    last_request_ptr = strmap_get_lc(last_hid_serv_requests,
-                                     hsdir_desc_comb_id);
-  return (last_request_ptr) ? *last_request_ptr : 0;
-}
-
-/** Clean the history of request times to hidden service directories, so that
- * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD
- * seconds any more. */
-static void
-directory_clean_last_hid_serv_requests(time_t now)
-{
-  strmap_iter_t *iter;
-  time_t cutoff = now - hsdir_requery_period(get_options());
-  strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
-  for (iter = strmap_iter_init(last_hid_serv_requests);
-       !strmap_iter_done(iter); ) {
-    const char *key;
-    void *val;
-    time_t *ent;
-    strmap_iter_get(iter, &key, &val);
-    ent = (time_t *) val;
-    if (*ent < cutoff) {
-      iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
-      tor_free(ent);
-    } else {
-      iter = strmap_iter_next(last_hid_serv_requests, iter);
-    }
-  }
-}
-
-/** Remove all requests related to the descriptor ID <b>desc_id</b> from the
- * history of times of requests to hidden service directories.
- * <b>desc_id</b> is an unencoded descriptor ID of size DIGEST_LEN.
- *
- * This is called from rend_client_note_connection_attempt_ended(), which
- * must be idempotent, so any future changes to this function must leave it
- * idempotent too. */
-static void
-purge_hid_serv_from_last_hid_serv_requests(const char *desc_id)
-{
-  strmap_iter_t *iter;
-  strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
-  char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
-
-  /* Key is stored with the base32 encoded desc_id. */
-  base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id,
-                DIGEST_LEN);
-  for (iter = strmap_iter_init(last_hid_serv_requests);
-       !strmap_iter_done(iter); ) {
-    const char *key;
-    void *val;
-    strmap_iter_get(iter, &key, &val);
-    /* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */
-    if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN -
-                  REND_DESC_ID_V2_LEN_BASE32,
-                  desc_id_base32,
-                  REND_DESC_ID_V2_LEN_BASE32)) {
-      iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
-      tor_free(val);
-    } else {
-      iter = strmap_iter_next(last_hid_serv_requests, iter);
-    }
-  }
-}
-
-/** Purge the history of request times to hidden service directories,
- * so that future lookups of an HS descriptor will not fail because we
- * accessed all of the HSDir relays responsible for the descriptor
- * recently. */
-void
-rend_client_purge_last_hid_serv_requests(void)
-{
-  /* Don't create the table if it doesn't exist yet (and it may very
-   * well not exist if the user hasn't accessed any HSes)... */
-  strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_;
-  /* ... and let get_last_hid_serv_requests re-create it for us if
-   * necessary. */
-  last_hid_serv_requests_ = NULL;
-
-  if (old_last_hid_serv_requests != NULL) {
-    log_info(LD_REND, "Purging client last-HS-desc-request-time table");
-    strmap_free(old_last_hid_serv_requests, tor_free_);
-  }
-}
-
-/** This returns a good valid hs dir that should be used for the given
- * descriptor id.
- *
- * Return NULL on error else the hsdir node pointer. */
-static routerstatus_t *
-pick_hsdir(const char *desc_id, const char *desc_id_base32)
-{
-  smartlist_t *responsible_dirs = smartlist_new();
-  smartlist_t *usable_responsible_dirs = smartlist_new();
-  const or_options_t *options = get_options();
-  routerstatus_t *hs_dir;
-  time_t now = time(NULL);
-  int excluded_some;
-
-  tor_assert(desc_id);
-  tor_assert(desc_id_base32);
-
-  /* Determine responsible dirs. Even if we can't get all we want, work with
-   * the ones we have. If it's empty, we'll notice below. */
-  hid_serv_get_responsible_directories(responsible_dirs, desc_id);
-
-  /* Clean request history first. */
-  directory_clean_last_hid_serv_requests(now);
-
-  /* Only select those hidden service directories to which we did not send a
-   * request recently and for which we have a router descriptor here. */
-  SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) {
-    time_t last = lookup_last_hid_serv_request(dir, desc_id_base32,
-                                               0, 0);
-    const node_t *node = node_get_by_id(dir->identity_digest);
-    if (last + hsdir_requery_period(options) >= now ||
-        !node || !node_has_descriptor(node)) {
-      SMARTLIST_DEL_CURRENT(responsible_dirs, dir);
-      continue;
-    }
-    if (!routerset_contains_node(options->ExcludeNodes, node)) {
-      smartlist_add(usable_responsible_dirs, dir);
-    }
-  } SMARTLIST_FOREACH_END(dir);
-
-  excluded_some =
-    smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs);
-
-  hs_dir = smartlist_choose(usable_responsible_dirs);
-  if (!hs_dir && !options->StrictNodes) {
-    hs_dir = smartlist_choose(responsible_dirs);
-  }
-
-  smartlist_free(responsible_dirs);
-  smartlist_free(usable_responsible_dirs);
-  if (!hs_dir) {
-    log_info(LD_REND, "Could not pick one of the responsible hidden "
-                      "service directories, because we requested them all "
-                      "recently without success.");
-    if (options->StrictNodes && excluded_some) {
-      log_warn(LD_REND, "Could not pick a hidden service directory for the "
-               "requested hidden service: they are all either down or "
-               "excluded, and StrictNodes is set.");
-    }
-  } else {
-    /* Remember that we are requesting a descriptor from this hidden service
-     * directory now. */
-    lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1);
-  }
-
-  return hs_dir;
-}
-
 /** Determine the responsible hidden service directories for <b>desc_id</b>
  * and fetch the descriptor with that ID from one of them. Only
  * send a request to a hidden service directory that we have not yet tried
@@ -721,7 +446,12 @@ directory_get_from_hs_dir(const char *desc_id,
 
   /* Automatically pick an hs dir if none given. */
   if (!rs_hsdir) {
-    hs_dir = pick_hsdir(desc_id, desc_id_base32);
+    /* Determine responsible dirs. Even if we can't get all we want, work with
+     * the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */
+    smartlist_t *responsible_dirs = smartlist_new();
+    hid_serv_get_responsible_directories(responsible_dirs, desc_id);
+
+    hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32);
     if (!hs_dir) {
       /* No suitable hs dir can be found, stop right now. */
       control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR");
@@ -786,6 +516,20 @@ directory_get_from_hs_dir(const char *desc_id,
   return 1;
 }
 
+/** Remove tracked HSDir requests from our history for this hidden service
+ *  descriptor <b>desc_id</b> (of size DIGEST_LEN) */
+static void
+purge_v2_hidserv_req(const char *desc_id)
+{
+  char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
+
+  /* The hsdir request tracker stores v2 keys using the base32 encoded
+     desc_id. Do it: */
+  base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id,
+                DIGEST_LEN);
+  hs_purge_hid_serv_from_last_hid_serv_requests(desc_id_base32);
+}
+
 /** Fetch a v2 descriptor using the given descriptor id. If any hsdir(s) are
  * given, they will be used instead.
  *
@@ -860,8 +604,7 @@ fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs)
                    sizeof(descriptor_id)) != 0) {
       /* Not equal from what we currently have so purge the last hid serv
        * request cache and update the descriptor ID with the new value. */
-      purge_hid_serv_from_last_hid_serv_requests(
-                                     rend_data->descriptor_id[chosen_replica]);
+      purge_v2_hidserv_req(rend_data->descriptor_id[chosen_replica]);
       memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id,
              sizeof(rend_data->descriptor_id[chosen_replica]));
     }
@@ -1107,66 +850,17 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
   return 1;
 }
 
-/** Called when we receive a RENDEZVOUS_ESTABLISHED cell; changes the state of
- * the circuit to C_REND_READY.
- */
-int
-rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request,
-                             size_t request_len)
-{
-  (void) request;
-  (void) request_len;
-  /* we just got an ack for our establish-rendezvous. switch purposes. */
-  if (circ->base_.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) {
-    log_warn(LD_PROTOCOL,"Got a rendezvous ack when we weren't expecting one. "
-             "Closing circ.");
-    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
-    return -1;
-  }
-  log_info(LD_REND,"Got rendezvous ack. This circuit is now ready for "
-           "rendezvous.");
-  circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY);
-  /* Set timestamp_dirty, because circuit_expire_building expects it
-   * to specify when a circuit entered the _C_REND_READY state. */
-  circ->base_.timestamp_dirty = time(NULL);
-
-  /* From a path bias point of view, this circuit is now successfully used.
-   * Waiting any longer opens us up to attacks from malicious hidden services.
-   * They could induce the client to attempt to connect to their hidden
-   * service and never reply to the client's rend requests */
-  pathbias_mark_use_success(circ);
-
-  /* XXXX++ This is a pretty brute-force approach. It'd be better to
-   * attach only the connections that are waiting on this circuit, rather
-   * than trying to attach them all. See comments bug 743. */
-  /* If we already have the introduction circuit built, make sure we send
-   * the INTRODUCE cell _now_ */
-  connection_ap_attach_pending(1);
-  return 0;
-}
-
 /** The service sent us a rendezvous cell; join the circuits. */
 int
 rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
                                size_t request_len)
 {
-  if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY &&
-       circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)
-      || !circ->build_state->pending_final_cpath) {
-    log_warn(LD_PROTOCOL,"Got rendezvous2 cell from hidden service, but not "
-             "expecting it. Closing.");
-    circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
-    return -1;
-  }
-
   if (request_len != DH_KEY_LEN+DIGEST_LEN) {
     log_warn(LD_PROTOCOL,"Incorrect length (%d) on RENDEZVOUS2 cell.",
              (int)request_len);
     goto err;
   }
 
-  log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service.");
-
   if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) {
     log_warn(LD_GENERAL, "Failed to setup circ");
     goto err;
@@ -1260,14 +954,14 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data)
     for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id);
          replica++) {
       const char *desc_id = rend_data_v2->descriptor_id[replica];
-      purge_hid_serv_from_last_hid_serv_requests(desc_id);
+      purge_v2_hidserv_req(desc_id);
     }
     log_info(LD_REND, "Connection attempt for %s has ended; "
              "cleaning up temporary state.",
              safe_str_client(onion_address));
   } else {
     /* We only have an ID for a fetch. Probably used by HSFETCH. */
-    purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch);
+    purge_v2_hidserv_req(rend_data_v2->desc_id_fetch);
   }
 }
 
@@ -1466,7 +1160,7 @@ rend_parse_service_authorization(const or_options_t *options,
       goto err;
     }
     strlcpy(auth->onion_address, onion_address, REND_SERVICE_ID_LEN_BASE32+1);
-    if (!rend_valid_service_id(auth->onion_address)) {
+    if (!rend_valid_v2_service_id(auth->onion_address)) {
       log_warn(LD_CONFIG, "Onion address has wrong format: '%s'",
                onion_address);
       goto err;

+ 0 - 4
src/or/rendclient.h

@@ -24,15 +24,11 @@ int rend_client_introduction_acked(origin_circuit_t *circ,
 void rend_client_refetch_v2_renddesc(rend_data_t *rend_query);
 int rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs);
 void rend_client_cancel_descriptor_fetches(void);
-void rend_client_purge_last_hid_serv_requests(void);
 
 int rend_client_report_intro_point_failure(extend_info_t *failed_intro,
                                            rend_data_t *rend_data,
                                            unsigned int failure_type);
 
-int rend_client_rendezvous_acked(origin_circuit_t *circ,
-                                 const uint8_t *request,
-                                 size_t request_len);
 int rend_client_receive_rendezvous(origin_circuit_t *circ,
                                    const uint8_t *request,
                                    size_t request_len);

+ 6 - 5
src/or/rendcommon.c

@@ -19,6 +19,7 @@
 #include "rendcommon.h"
 #include "rendmid.h"
 #include "hs_intropoint.h"
+#include "hs_client.h"
 #include "rendservice.h"
 #include "rephist.h"
 #include "router.h"
@@ -695,7 +696,7 @@ rend_get_service_id(crypto_pk_t *pk, char *out)
 /** Return true iff <b>query</b> is a syntactically valid service ID (as
  * generated by rend_get_service_id).  */
 int
-rend_valid_service_id(const char *query)
+rend_valid_v2_service_id(const char *query)
 {
   if (strlen(query) != REND_SERVICE_ID_LEN_BASE32)
     return 0;
@@ -781,7 +782,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
       break;
     case RELAY_COMMAND_INTRODUCE_ACK:
       if (origin_circ)
-        r = rend_client_introduction_acked(origin_circ,payload,length);
+        r = hs_client_receive_introduce_ack(origin_circ,payload,length);
       break;
     case RELAY_COMMAND_RENDEZVOUS1:
       if (or_circ)
@@ -789,7 +790,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
       break;
     case RELAY_COMMAND_RENDEZVOUS2:
       if (origin_circ)
-        r = rend_client_receive_rendezvous(origin_circ,payload,length);
+        r = hs_client_receive_rendezvous2(origin_circ,payload,length);
       break;
     case RELAY_COMMAND_INTRO_ESTABLISHED:
       if (origin_circ)
@@ -797,7 +798,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
       break;
     case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED:
       if (origin_circ)
-        r = rend_client_rendezvous_acked(origin_circ,payload,length);
+        r = hs_client_receive_rendezvous_acked(origin_circ,payload,length);
       break;
     default:
       tor_fragile_assert();
@@ -990,7 +991,7 @@ rend_non_anonymous_mode_enabled(const or_options_t *options)
  * service.
  */
 void
-assert_circ_anonymity_ok(origin_circuit_t *circ,
+assert_circ_anonymity_ok(const origin_circuit_t *circ,
                          const or_options_t *options)
 {
   tor_assert(options);

+ 2 - 2
src/or/rendcommon.h

@@ -30,7 +30,7 @@ void rend_encoded_v2_service_descriptor_free(
                                rend_encoded_v2_service_descriptor_t *desc);
 void rend_intro_point_free(rend_intro_point_t *intro);
 
-int rend_valid_service_id(const char *query);
+int rend_valid_v2_service_id(const char *query);
 int rend_valid_descriptor_id(const char *query);
 int rend_valid_client_name(const char *client_name);
 int rend_encode_v2_descriptors(smartlist_t *descs_out,
@@ -60,7 +60,7 @@ int rend_auth_decode_cookie(const char *cookie_in,
 int rend_allow_non_anonymous_connection(const or_options_t* options);
 int rend_non_anonymous_mode_enabled(const or_options_t *options);
 
-void assert_circ_anonymity_ok(origin_circuit_t *circ,
+void assert_circ_anonymity_ok(const origin_circuit_t *circ,
                               const or_options_t *options);
 
 #ifdef RENDCOMMON_PRIVATE

+ 1 - 1
src/or/rendservice.c

@@ -890,7 +890,7 @@ int
 rend_service_del_ephemeral(const char *service_id)
 {
   rend_service_t *s;
-  if (!rend_valid_service_id(service_id)) {
+  if (!rend_valid_v2_service_id(service_id)) {
     log_warn(LD_CONFIG, "Requested malformed Onion Service id for removal.");
     return -1;
   }

+ 1 - 1
src/or/shared_random_state.c

@@ -139,7 +139,7 @@ get_start_time_of_current_round(time_t now)
   const or_options_t *options = get_options();
   int voting_interval = get_voting_interval();
   voting_schedule_t *new_voting_schedule =
-    get_voting_schedule(options, now, LOG_INFO);
+    get_voting_schedule(options, now, LOG_DEBUG);
   tor_assert(new_voting_schedule);
 
   /* First, get the start time of the next round */

+ 0 - 17
src/test/test.c

@@ -534,25 +534,8 @@ test_rend_fns(void *arg)
   size_t intro_points_size;
   size_t encoded_size;
   int i;
-  char address1[] = "fooaddress.onion";
-  char address2[] = "aaaaaaaaaaaaaaaa.onion";
-  char address3[] = "fooaddress.exit";
-  char address4[] = "www.torproject.org";
-  char address5[] = "foo.abcdefghijklmnop.onion";
-  char address6[] = "foo.bar.abcdefghijklmnop.onion";
-  char address7[] = ".abcdefghijklmnop.onion";
 
   (void)arg;
-  tt_assert(BAD_HOSTNAME == parse_extended_hostname(address1));
-  tt_assert(ONION_HOSTNAME == parse_extended_hostname(address2));
-  tt_str_op(address2,OP_EQ, "aaaaaaaaaaaaaaaa");
-  tt_assert(EXIT_HOSTNAME == parse_extended_hostname(address3));
-  tt_assert(NORMAL_HOSTNAME == parse_extended_hostname(address4));
-  tt_assert(ONION_HOSTNAME == parse_extended_hostname(address5));
-  tt_str_op(address5,OP_EQ, "abcdefghijklmnop");
-  tt_assert(ONION_HOSTNAME == parse_extended_hostname(address6));
-  tt_str_op(address6,OP_EQ, "abcdefghijklmnop");
-  tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7));
 
   /* Initialize the service cache. */
   rend_cache_init();

+ 11 - 0
src/test/test_circuitlist.c

@@ -180,6 +180,7 @@ static void
 test_rend_token_maps(void *arg)
 {
   or_circuit_t *c1, *c2, *c3, *c4;
+  origin_circuit_t *c5;
   const uint8_t tok1[REND_TOKEN_LEN] = "The cat can't tell y";
   const uint8_t tok2[REND_TOKEN_LEN] = "ou its name, and it ";
   const uint8_t tok3[REND_TOKEN_LEN] = "doesn't really care.";
@@ -194,6 +195,7 @@ test_rend_token_maps(void *arg)
   c2 = or_circuit_new(0, NULL);
   c3 = or_circuit_new(0, NULL);
   c4 = or_circuit_new(0, NULL);
+  c5 = origin_circuit_new();
 
   /* Make sure we really filled up the tok* variables */
   tt_int_op(tok1[REND_TOKEN_LEN-1], OP_EQ, 'y');
@@ -264,6 +266,13 @@ test_rend_token_maps(void *arg)
   tt_ptr_op(TO_CIRCUIT(c4)->hs_token, OP_EQ, NULL);
   tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3));
 
+  /* Now let's do a check for the client-side rend circuitmap */
+  c5->base_.purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND;
+  hs_circuitmap_register_rend_circ_client_side(c5, tok1);
+
+  tt_ptr_op(c5, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok1));
+  tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok2));
+
  done:
   if (c1)
     circuit_free(TO_CIRCUIT(c1));
@@ -273,6 +282,8 @@ test_rend_token_maps(void *arg)
     circuit_free(TO_CIRCUIT(c3));
   if (c4)
     circuit_free(TO_CIRCUIT(c4));
+  if (c5)
+    circuit_free(TO_CIRCUIT(c5));
 }
 
 static void

+ 87 - 0
src/test/test_entryconn.c

@@ -14,6 +14,10 @@
 #include "confparse.h"
 #include "connection.h"
 #include "connection_edge.h"
+#include "nodelist.h"
+
+#include "hs_cache.h"
+#include "rendcache.h"
 
 static void *
 entryconn_rewrite_setup(const struct testcase_t *tc)
@@ -743,6 +747,87 @@ test_entryconn_rewrite_mapaddress_automap_onion4(void *arg)
   test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 0, 1);
 }
 
+/** Test that rewrite functions can handle v2 addresses */
+static void
+test_entryconn_rewrite_onion_v2(void *arg)
+{
+  int retval;
+  entry_connection_t *conn = arg;
+
+  (void) arg;
+
+  rend_cache_init();
+
+  /* Make a SOCKS request */
+  conn->socks_request->command = SOCKS_COMMAND_CONNECT;
+  strlcpy(conn->socks_request->address,
+          "pqeed46efnwmfuid.onion",
+          sizeof(conn->socks_request->address));
+
+  /* Make an onion connection using the SOCKS request */
+  conn->entry_cfg.onion_traffic = 1;
+  ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_SOCKS_WAIT;
+  tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data);
+
+  /* Handle SOCKS and rewrite! */
+  retval = connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /* Check connection state after rewrite */
+  tt_int_op(ENTRY_TO_CONN(conn)->state, OP_EQ, AP_CONN_STATE_RENDDESC_WAIT);
+  /* check that the address got rewritten */
+  tt_str_op(conn->socks_request->address, OP_EQ,
+            "pqeed46efnwmfuid");
+  /* check that HS information got attached to the connection */
+  tt_assert(ENTRY_TO_EDGE_CONN(conn)->rend_data);
+  tt_assert(!ENTRY_TO_EDGE_CONN(conn)->hs_ident);
+
+ done:
+  rend_cache_free_all();
+  /* 'conn' is cleaned by handler */
+}
+
+/** Test that rewrite functions can handle v3 onion addresses */
+static void
+test_entryconn_rewrite_onion_v3(void *arg)
+{
+  int retval;
+  entry_connection_t *conn = arg;
+
+  (void) arg;
+
+  hs_cache_init();
+
+  /* Make a SOCKS request */
+  conn->socks_request->command = SOCKS_COMMAND_CONNECT;
+  strlcpy(conn->socks_request->address,
+          "git.p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad.onion",
+          sizeof(conn->socks_request->address));
+
+  /* Make an onion connection using the SOCKS request */
+  conn->entry_cfg.onion_traffic = 1;
+  ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_SOCKS_WAIT;
+  tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data);
+  tt_assert(!ENTRY_TO_EDGE_CONN(conn)->hs_ident);
+
+  /* Handle SOCKS and rewrite! */
+  retval = connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /* Check connection state after rewrite */
+  tt_int_op(ENTRY_TO_CONN(conn)->state, OP_EQ, AP_CONN_STATE_RENDDESC_WAIT);
+  /* check that the address got rewritten */
+  tt_str_op(conn->socks_request->address, OP_EQ,
+            "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad");
+  /* check that HS information got attached to the connection */
+  tt_assert(ENTRY_TO_EDGE_CONN(conn)->hs_ident);
+  tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data);
+
+ done:
+  hs_free_all();
+  /* 'conn' is cleaned by handler */
+}
+
 #define REWRITE(name)                           \
   { #name, test_entryconn_##name, TT_FORK, &test_rewrite_setup, NULL }
 
@@ -763,6 +848,8 @@ struct testcase_t entryconn_tests[] = {
   REWRITE(rewrite_mapaddress_automap_onion2),
   REWRITE(rewrite_mapaddress_automap_onion3),
   REWRITE(rewrite_mapaddress_automap_onion4),
+  REWRITE(rewrite_onion_v2),
+  REWRITE(rewrite_onion_v3),
 
   END_OF_TESTCASES
 };

+ 66 - 0
src/test/test_hs_cache.c

@@ -7,6 +7,7 @@
  */
 
 #define CONNECTION_PRIVATE
+#define DIRECTORY_PRIVATE
 #define HS_CACHE_PRIVATE
 
 #include "ed25519_cert.h"
@@ -431,6 +432,69 @@ test_hsdir_revision_counter_check(void *arg)
   tor_free(published_desc_str);
 }
 
+/** Test that we can store HS descriptors in the client HS cache. */
+static void
+test_client_cache(void *arg)
+{
+  int retval;
+  ed25519_keypair_t signing_kp;
+  hs_descriptor_t *published_desc = NULL;
+  char *published_desc_str = NULL;
+
+  response_handler_args_t *args = NULL;
+  dir_connection_t *conn = NULL;
+
+  (void) arg;
+
+  /* Initialize HSDir cache subsystem */
+  init_test();
+
+  /* Generate a valid descriptor with normal values. */
+  {
+    retval = ed25519_keypair_generate(&signing_kp, 0);
+    tt_int_op(retval, ==, 0);
+    published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
+    tt_assert(published_desc);
+    retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
+                                       &published_desc_str);
+    tt_int_op(retval, OP_EQ, 0);
+  }
+
+  /* Test handle_response_fetch_hsdesc_v3() */
+  {
+    args = tor_malloc_zero(sizeof(response_handler_args_t));
+    args->status_code = 200;
+    args->reason = NULL;
+    args->body = published_desc_str;
+    args->body_len = strlen(published_desc_str);
+
+    conn = tor_malloc_zero(sizeof(dir_connection_t));
+    conn->hs_ident = tor_malloc_zero(sizeof(hs_ident_dir_conn_t));
+    ed25519_pubkey_copy(&conn->hs_ident->identity_pk, &signing_kp.pubkey);
+  }
+
+  /* store the descriptor! */
+  retval = handle_response_fetch_hsdesc_v3(conn, args);
+  tt_int_op(retval, == , 0);
+
+  /* fetch the descriptor and make sure it's there */
+  {
+    hs_cache_client_descriptor_t *cached_desc = NULL;
+    cached_desc = lookup_v3_desc_as_client(signing_kp.pubkey.pubkey);
+    tt_assert(cached_desc);
+    tt_str_op(cached_desc->encoded_desc, OP_EQ, published_desc_str);
+  }
+
+ done:
+  tor_free(args);
+  hs_descriptor_free(published_desc);
+  tor_free(published_desc_str);
+  if (conn) {
+    tor_free(conn->hs_ident);
+    tor_free(conn);
+  }
+}
+
 struct testcase_t hs_cache[] = {
   /* Encoding tests. */
   { "directory", test_directory, TT_FORK,
@@ -441,6 +505,8 @@ struct testcase_t hs_cache[] = {
     NULL, NULL },
   { "upload_and_download_hs_desc", test_upload_and_download_hs_desc, TT_FORK,
     NULL, NULL },
+  { "client_cache", test_client_cache, TT_FORK,
+    NULL, NULL },
 
   END_OF_TESTCASES
 };

+ 372 - 23
src/test/test_hs_common.c

@@ -14,11 +14,14 @@
 #include "log_test_helpers.h"
 #include "hs_test_helpers.h"
 
+#include "connection_edge.h"
 #include "hs_common.h"
 #include "hs_service.h"
 #include "config.h"
 #include "networkstatus.h"
+#include "directory.h"
 #include "nodelist.h"
+#include "statefile.h"
 
 /** Test the validation of HS v3 addresses */
 static void
@@ -357,6 +360,37 @@ test_desc_overlap_period_testnet(void *arg)
   tor_free(dummy_consensus);
 }
 
+static void
+helper_add_hsdir_to_networkstatus(networkstatus_t *ns,
+                                  const uint8_t *identity,
+                                  const uint8_t *curr_hsdir_index,
+                                  const char *nickname,
+                                  int is_hsdir)
+{
+  routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t));
+  routerinfo_t *ri = tor_malloc_zero(sizeof(routerinfo_t));
+
+  tor_addr_t ipv4_addr;
+  memcpy(rs->identity_digest, identity, DIGEST_LEN);
+  rs->is_hs_dir = is_hsdir;
+  rs->supports_v3_hsdir = 1;
+  tor_addr_parse(&ipv4_addr, "1.2.3.4");
+  ri->addr = tor_addr_to_ipv4h(&ipv4_addr);
+  ri->nickname = tor_strdup(nickname);
+  ri->protocol_list = tor_strdup("HSDir=1-2 LinkAuth=3");
+  memcpy(ri->cache_info.identity_digest, identity, DIGEST_LEN);
+  tt_assert(nodelist_set_routerinfo(ri, NULL));
+  node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest);
+  tt_assert(node);
+  node->rs = rs;
+  memcpy(node->hsdir_index->current, curr_hsdir_index,
+         sizeof(node->hsdir_index->current));
+  smartlist_add(ns->routerstatus_list, rs);
+
+ done:
+  ;
+}
+
 static networkstatus_t *mock_ns = NULL;
 
 static networkstatus_t *
@@ -389,7 +423,7 @@ test_responsible_hsdirs(void *arg)
   time_t now = approx_time();
   smartlist_t *responsible_dirs = smartlist_new();
   networkstatus_t *ns = NULL;
-  routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t));
+  int retval;
 
   (void) arg;
 
@@ -401,39 +435,217 @@ test_responsible_hsdirs(void *arg)
   ns = networkstatus_get_latest_consensus();
 
   { /* First router: HSdir */
-    tor_addr_t ipv4_addr;
-    memset(rs->identity_digest, 'A', DIGEST_LEN);
-    rs->is_hs_dir = 1;
-    rs->supports_v3_hsdir = 1;
-    routerinfo_t ri;
-    memset(&ri, 0 ,sizeof(routerinfo_t));
-    tor_addr_parse(&ipv4_addr, "127.0.0.1");
-    ri.addr = tor_addr_to_ipv4h(&ipv4_addr);
-    ri.nickname = (char *) "fatal";
-    ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3";
-    memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN);
-    tt_assert(nodelist_set_routerinfo(&ri, NULL));
-    node_t *node = node_get_mutable_by_id(ri.cache_info.identity_digest);
-    memset(node->hsdir_index->current, 'Z',
-           sizeof(node->hsdir_index->current));
-    smartlist_add(ns->routerstatus_list, rs);
+    uint8_t identity[DIGEST_LEN];
+    uint8_t curr_hsdir_index[DIGEST256_LEN];
+    char nickname[] = "let_me";
+    memset(identity, 1, sizeof(identity));
+    memset(curr_hsdir_index, 1, sizeof(curr_hsdir_index));
+
+    helper_add_hsdir_to_networkstatus(ns, identity,
+                                      curr_hsdir_index, nickname, 1);
+  }
+
+  { /* Second HSDir */
+    uint8_t identity[DIGEST_LEN];
+    uint8_t curr_hsdir_index[DIGEST256_LEN];
+    char nickname[] = "show_you";
+    memset(identity, 2, sizeof(identity));
+    memset(curr_hsdir_index, 2, sizeof(curr_hsdir_index));
+
+    helper_add_hsdir_to_networkstatus(ns, identity,
+                                      curr_hsdir_index, nickname, 1);
+  }
+
+  { /* Third relay but not HSDir */
+    uint8_t identity[DIGEST_LEN];
+    uint8_t curr_hsdir_index[DIGEST256_LEN];
+    char nickname[] = "how_to_dance";
+    memset(identity, 3, sizeof(identity));
+    memset(curr_hsdir_index, 3, sizeof(curr_hsdir_index));
+
+    helper_add_hsdir_to_networkstatus(ns, identity,
+                                      curr_hsdir_index, nickname, 0);
   }
 
-  ed25519_public_key_t blinded_pk;
+  ed25519_keypair_t kp;
+  retval = ed25519_keypair_generate(&kp, 0);
+  tt_int_op(retval, OP_EQ , 0);
+
   uint64_t time_period_num = hs_get_time_period_num(now);
-  hs_get_responsible_hsdirs(&blinded_pk, time_period_num,
+  hs_get_responsible_hsdirs(&kp.pubkey, time_period_num,
                             0, 0, responsible_dirs);
-  tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 1);
+
+  /* Make sure that we only found 2 responsible HSDirs.
+   * The third relay was not an hsdir! */
+  tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 2);
 
   /** TODO: Build a bigger network and do more tests here */
 
  done:
-  routerstatus_free(rs);
+  SMARTLIST_FOREACH(ns->routerstatus_list,
+                    routerstatus_t *, rs, routerstatus_free(rs));
   smartlist_free(responsible_dirs);
   smartlist_clear(ns->routerstatus_list);
   networkstatus_vote_free(mock_ns);
 }
 
+static void
+mock_directory_initiate_request(directory_request_t *req)
+{
+  (void)req;
+  return;
+}
+
+static int
+mock_hs_desc_encode_descriptor(const hs_descriptor_t *desc,
+                           const ed25519_keypair_t *signing_kp,
+                           char **encoded_out)
+{
+  (void)desc;
+  (void)signing_kp;
+
+  tor_asprintf(encoded_out, "lulu");
+  return 0;
+}
+
+static or_state_t dummy_state;
+
+/* Mock function to get fake or state (used for rev counters) */
+static or_state_t *
+get_or_state_replacement(void)
+{
+  return &dummy_state;
+}
+
+static int
+mock_router_have_minimum_dir_info(void)
+{
+  return 1;
+}
+
+/** Test that we correctly detect when the HSDir hash ring changes so that we
+ *  reupload our descriptor. */
+static void
+test_desc_reupload_logic(void *arg)
+{
+  networkstatus_t *ns = NULL;
+
+  (void) arg;
+
+  hs_init();
+
+  MOCK(router_have_minimum_dir_info,
+       mock_router_have_minimum_dir_info);
+  MOCK(get_or_state,
+       get_or_state_replacement);
+  MOCK(networkstatus_get_latest_consensus,
+       mock_networkstatus_get_latest_consensus);
+  MOCK(directory_initiate_request,
+       mock_directory_initiate_request);
+  MOCK(hs_desc_encode_descriptor,
+       mock_hs_desc_encode_descriptor);
+
+  ns = networkstatus_get_latest_consensus();
+
+  /** Test logic:
+   *  1) Upload descriptor to HSDirs
+   *     CHECK that previous_hsdirs list was populated.
+   *  2) Then call router_dir_info_changed() without an HSDir set change.
+   *     CHECK that no reuplod occurs.
+   *  3) Now change the HSDir set, and call dir_info_changed() again.
+   *     CHECK that reupload occurs.
+   *  4) Finally call service_desc_schedule_upload().
+   *     CHECK that previous_hsdirs list was cleared.
+   **/
+
+  /* Let's start by building our descriptor and service */
+  hs_service_descriptor_t *desc = service_descriptor_new();
+  hs_service_t *service = NULL;
+  char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+  ed25519_public_key_t pubkey;
+  memset(&pubkey, '\x42', sizeof(pubkey));
+  hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr);
+  service = tor_malloc_zero(sizeof(hs_service_t));
+  memcpy(service->onion_address, onion_addr, sizeof(service->onion_address));
+  ed25519_secret_key_generate(&service->keys.identity_sk, 0);
+  ed25519_public_key_generate(&service->keys.identity_pk,
+                              &service->keys.identity_sk);
+  service->desc_current = desc;
+  /* Also add service to service map */
+  hs_service_ht *service_map = get_hs_service_map();
+  tt_assert(service_map);
+  tt_int_op(hs_service_get_num_services(), OP_EQ, 0);
+  register_service(service_map, service);
+  tt_int_op(hs_service_get_num_services(), OP_EQ, 1);
+
+  /* Now let's create our hash ring: */
+  { /* First HSDir */
+    uint8_t identity[DIGEST_LEN];
+    uint8_t curr_hsdir_index[DIGEST256_LEN];
+    char nickname[] = "let_me";
+    memset(identity, 1, sizeof(identity));
+    memset(curr_hsdir_index, 1, sizeof(curr_hsdir_index));
+
+    helper_add_hsdir_to_networkstatus(ns, identity,
+                                      curr_hsdir_index, nickname, 1);
+  }
+
+  { /* Second HSDir */
+    uint8_t identity[DIGEST_LEN];
+    uint8_t curr_hsdir_index[DIGEST256_LEN];
+    char nickname[] = "show_you";
+    memset(identity, 2, sizeof(identity));
+    memset(curr_hsdir_index, 2, sizeof(curr_hsdir_index));
+
+    helper_add_hsdir_to_networkstatus(ns, identity,
+                                      curr_hsdir_index, nickname, 1);
+  }
+
+  /* Now let's upload our desc to all hsdirs */
+  upload_descriptor_to_all(service, desc, 0);
+  /* Check that previous hsdirs were populated */
+  tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 2);
+
+  /* Poison next upload time so that we can see if it was changed by
+   * router_dir_info_changed(). No changes in hash ring so far, so the upload
+   * time should stay as is. */
+  desc->next_upload_time = 42;
+  router_dir_info_changed();
+  tt_int_op(desc->next_upload_time, OP_EQ, 42);
+
+  /* Now change the HSDir hash ring by adding another node */
+
+  { /* Third HSDir */
+    uint8_t identity[DIGEST_LEN];
+    uint8_t curr_hsdir_index[DIGEST256_LEN];
+    char nickname[] = "how_to_dance";
+    memset(identity, 3, sizeof(identity));
+    memset(curr_hsdir_index, 3, sizeof(curr_hsdir_index));
+
+    helper_add_hsdir_to_networkstatus(ns, identity,
+                                      curr_hsdir_index, nickname, 1);
+  }
+
+  /* Now call router_dir_info_changed() again and see that it detected the hash
+     ring change and updated the upload time */
+  time_t now = approx_time();
+  tt_assert(now);
+  router_dir_info_changed();
+  tt_int_op(desc->next_upload_time, OP_EQ, now);
+
+  /* Now pretend that the descriptor changed, and order a reupload to all
+     HSDirs. Make sure that the set of previous HSDirs was cleared. */
+  service_desc_schedule_upload(desc, now, 1);
+  tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 0);
+
+  /* Now reupload again: see that the prev hsdir set got populated again. */
+  upload_descriptor_to_all(service, desc, 0);
+  tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 3);
+
+ done:
+  hs_free_all();
+}
+
 /** Test disaster SRV computation and caching */
 static void
 test_disaster_srv(void *arg)
@@ -485,6 +697,136 @@ test_disaster_srv(void *arg)
   ;
 }
 
+/** Test our HS descriptor request tracker by making various requests and
+ *  checking whether they get tracked properly. */
+static void
+test_hid_serv_request_tracker(void *arg)
+{
+  (void) arg;
+  time_t retval;
+  routerstatus_t *hsdir = NULL, *hsdir2 = NULL;
+  time_t now = approx_time();
+
+  const char *req_key_str_first =
+ "vd4zb6zesaubtrjvdqcr2w7x7lhw2up4Xnw4526ThUNbL5o1go+EdUuEqlKxHkNbnK41pRzizzs";
+  const char *req_key_str_second =
+ "g53o7iavcd62oihswhr24u6czmqws5kpXnw4526ThUNbL5o1go+EdUuEqlKxHkNbnK41pRzizzs";
+
+  /*************************** basic test *******************************/
+
+  /* Get request tracker and make sure it's empty */
+  strmap_t *request_tracker = get_last_hid_serv_requests();
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 0);
+
+  /* Let's register a hid serv request */
+  hsdir = tor_malloc_zero(sizeof(routerstatus_t));
+  memset(hsdir->identity_digest, 'Z', DIGEST_LEN);
+  retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first,
+                                           now, 1);
+  tt_int_op(retval, OP_EQ, now);
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
+
+  /* Let's lookup a non-existent hidserv request */
+  retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_second,
+                                           now+1, 0);
+  tt_int_op(retval, OP_EQ, 0);
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
+
+  /* Let's lookup a real hidserv request */
+  retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first,
+                                           now+2, 0);
+  tt_int_op(retval, OP_EQ, now); /* we got it */
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
+
+  /**********************************************************************/
+
+  /* Let's add another request for the same HS but on a different HSDir. */
+  hsdir2 = tor_malloc_zero(sizeof(routerstatus_t));
+  memset(hsdir->identity_digest, 2, DIGEST_LEN);
+  retval = hs_lookup_last_hid_serv_request(hsdir2, req_key_str_first,
+                                           now+3, 1);
+  tt_int_op(retval, OP_EQ, now+3);
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 2);
+
+  /* Check that we can clean the first request based on time */
+  hs_clean_last_hid_serv_requests(now+3+REND_HID_SERV_DIR_REQUERY_PERIOD);
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
+  /* Check that it doesn't exist anymore */
+  retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first,
+                                           now+2, 0);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /*************************** deleting entries **************************/
+
+  /* Add another request with very short key */
+  retval = hs_lookup_last_hid_serv_request(hsdir, "l",  now, 1);
+
+  /* Try deleting entries with a dummy key. Check that our previous requests
+   * are still there */
+  tor_capture_bugs_(1);
+  hs_purge_hid_serv_from_last_hid_serv_requests("a");
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 2);
+  tor_end_capture_bugs_();
+
+  /* Try another dummy key. Check that requests are still there */
+  {
+    char dummy[2000];
+    memset(dummy, 'Z', 2000);
+    dummy[1999] = '\x00';
+    hs_purge_hid_serv_from_last_hid_serv_requests(dummy);
+    tt_int_op(strmap_size(request_tracker),OP_EQ, 2);
+  }
+
+  /* Another dummy key! */
+  hs_purge_hid_serv_from_last_hid_serv_requests(req_key_str_second);
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 2);
+
+  /* Now actually delete a request! */
+  hs_purge_hid_serv_from_last_hid_serv_requests(req_key_str_first);
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
+
+  /* Purge it all! */
+  hs_purge_last_hid_serv_requests();
+  request_tracker = get_last_hid_serv_requests();
+  tt_int_op(strmap_size(request_tracker),OP_EQ, 0);
+
+ done:
+  tor_free(hsdir);
+  tor_free(hsdir2);
+}
+
+static void
+test_parse_extended_hostname(void *arg)
+{
+  (void) arg;
+
+  char address1[] = "fooaddress.onion";
+  char address2[] = "aaaaaaaaaaaaaaaa.onion";
+  char address3[] = "fooaddress.exit";
+  char address4[] = "www.torproject.org";
+  char address5[] = "foo.abcdefghijklmnop.onion";
+  char address6[] = "foo.bar.abcdefghijklmnop.onion";
+  char address7[] = ".abcdefghijklmnop.onion";
+  char address8[] =
+    "www.p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad.onion";
+
+  tt_assert(BAD_HOSTNAME == parse_extended_hostname(address1));
+  tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address2));
+  tt_str_op(address2,OP_EQ, "aaaaaaaaaaaaaaaa");
+  tt_assert(EXIT_HOSTNAME == parse_extended_hostname(address3));
+  tt_assert(NORMAL_HOSTNAME == parse_extended_hostname(address4));
+  tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address5));
+  tt_str_op(address5,OP_EQ, "abcdefghijklmnop");
+  tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address6));
+  tt_str_op(address6,OP_EQ, "abcdefghijklmnop");
+  tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7));
+  tt_assert(ONION_V3_HOSTNAME == parse_extended_hostname(address8));
+  tt_str_op(address8, OP_EQ,
+            "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad");
+
+ done: ;
+}
+
 struct testcase_t hs_common_tests[] = {
   { "build_address", test_build_address, TT_FORK,
     NULL, NULL },
@@ -498,9 +840,16 @@ struct testcase_t hs_common_tests[] = {
     NULL, NULL },
   { "desc_overlap_period_testnet", test_desc_overlap_period_testnet, TT_FORK,
     NULL, NULL },
-  { "desc_responsible_hsdirs", test_responsible_hsdirs, TT_FORK,
+  { "responsible_hsdirs", test_responsible_hsdirs, TT_FORK,
+    NULL, NULL },
+  { "desc_reupload_logic", test_desc_reupload_logic, TT_FORK,
+    NULL, NULL },
+  { "disaster_srv", test_disaster_srv, TT_FORK,
+    NULL, NULL },
+  { "hid_serv_request_tracker", test_hid_serv_request_tracker, TT_FORK,
+    NULL, NULL },
+  { "parse_extended_hostname", test_parse_extended_hostname, TT_FORK,
     NULL, NULL },
-  { "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL },
 
   END_OF_TESTCASES
 };

+ 1 - 18
src/test/test_hs_service.c

@@ -1183,7 +1183,6 @@ test_upload_descriptors(void *arg)
   int ret;
   time_t now = time(NULL);
   hs_service_t *service;
-  hs_service_intro_point_t *ip;
 
   (void) arg;
 
@@ -1191,7 +1190,6 @@ test_upload_descriptors(void *arg)
   MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true);
   MOCK(get_or_state,
        get_or_state_replacement);
-
   dummy_state = tor_malloc_zero(sizeof(or_state_t));
 
   /* Create a service with no descriptor. It's added to the global map. */
@@ -1222,25 +1220,10 @@ test_upload_descriptors(void *arg)
   /* If no upload happened, this should be untouched. */
   tt_u64_op(service->desc_current->next_upload_time, OP_EQ, now + 1000);
 
-  /* Set our upload time in the past so we trigger an upload. */
-  service->desc_current->next_upload_time = now - 1000;
-  service->desc_next->next_upload_time = now - 1000;
-  ip = helper_create_service_ip();
-  ip->circuit_established = 1;
-  service_intro_point_add(service->desc_current->intro_points.map, ip);
-
-  setup_full_capture_of_logs(LOG_WARN);
-  run_upload_descriptor_event(now);
-  expect_log_msg_containing("No valid consensus so we can't get the");
-  teardown_capture_of_logs();
-  tt_u64_op(service->desc_current->next_upload_time, OP_GE,
-            now + HS_SERVICE_NEXT_UPLOAD_TIME_MIN);
-  tt_u64_op(service->desc_current->next_upload_time, OP_LE,
-            now + HS_SERVICE_NEXT_UPLOAD_TIME_MAX);
-
  done:
   hs_free_all();
   UNMOCK(hs_overlap_mode_is_active);
+  UNMOCK(get_or_state);
 }
 
 /** Test the functions that save and load HS revision counters to state. */

+ 178 - 0
src/trunnel/hs/cell_rendezvous.c

@@ -290,3 +290,181 @@ trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input
   }
   return result;
 }
+trn_cell_rendezvous2_t *
+trn_cell_rendezvous2_new(void)
+{
+  trn_cell_rendezvous2_t *val = trunnel_calloc(1, sizeof(trn_cell_rendezvous2_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+trn_cell_rendezvous2_clear(trn_cell_rendezvous2_t *obj)
+{
+  (void) obj;
+}
+
+void
+trn_cell_rendezvous2_free(trn_cell_rendezvous2_t *obj)
+{
+  if (obj == NULL)
+    return;
+  trn_cell_rendezvous2_clear(obj);
+  trunnel_memwipe(obj, sizeof(trn_cell_rendezvous2_t));
+  trunnel_free_(obj);
+}
+
+size_t
+trn_cell_rendezvous2_getlen_handshake_info(const trn_cell_rendezvous2_t *inp)
+{
+  (void)inp;  return TRUNNEL_HANDSHAKE_INFO_LEN;
+}
+
+uint8_t
+trn_cell_rendezvous2_get_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx)
+{
+  trunnel_assert(idx < TRUNNEL_HANDSHAKE_INFO_LEN);
+  return inp->handshake_info[idx];
+}
+
+uint8_t
+trn_cell_rendezvous2_getconst_handshake_info(const trn_cell_rendezvous2_t *inp, size_t idx)
+{
+  return trn_cell_rendezvous2_get_handshake_info((trn_cell_rendezvous2_t*)inp, idx);
+}
+int
+trn_cell_rendezvous2_set_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < TRUNNEL_HANDSHAKE_INFO_LEN);
+  inp->handshake_info[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+trn_cell_rendezvous2_getarray_handshake_info(trn_cell_rendezvous2_t *inp)
+{
+  return inp->handshake_info;
+}
+const uint8_t  *
+trn_cell_rendezvous2_getconstarray_handshake_info(const trn_cell_rendezvous2_t *inp)
+{
+  return (const uint8_t  *)trn_cell_rendezvous2_getarray_handshake_info((trn_cell_rendezvous2_t*)inp);
+}
+const char *
+trn_cell_rendezvous2_check(const trn_cell_rendezvous2_t *obj)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  return NULL;
+}
+
+ssize_t
+trn_cell_rendezvous2_encoded_len(const trn_cell_rendezvous2_t *obj)
+{
+  ssize_t result = 0;
+
+  if (NULL != trn_cell_rendezvous2_check(obj))
+     return -1;
+
+
+  /* Length of u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */
+  result += TRUNNEL_HANDSHAKE_INFO_LEN;
+  return result;
+}
+int
+trn_cell_rendezvous2_clear_errors(trn_cell_rendezvous2_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+trn_cell_rendezvous2_encode(uint8_t *output, const size_t avail, const trn_cell_rendezvous2_t *obj)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = trn_cell_rendezvous2_encoded_len(obj);
+#endif
+
+  if (NULL != (msg = trn_cell_rendezvous2_check(obj)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */
+  trunnel_assert(written <= avail);
+  if (avail - written < TRUNNEL_HANDSHAKE_INFO_LEN)
+    goto truncated;
+  memcpy(ptr, obj->handshake_info, TRUNNEL_HANDSHAKE_INFO_LEN);
+  written += TRUNNEL_HANDSHAKE_INFO_LEN; ptr += TRUNNEL_HANDSHAKE_INFO_LEN;
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As trn_cell_rendezvous2_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+trn_cell_rendezvous2_parse_into(trn_cell_rendezvous2_t *obj, const uint8_t *input, const size_t len_in)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+
+  /* Parse u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */
+  CHECK_REMAINING(TRUNNEL_HANDSHAKE_INFO_LEN, truncated);
+  memcpy(obj->handshake_info, ptr, TRUNNEL_HANDSHAKE_INFO_LEN);
+  remaining -= TRUNNEL_HANDSHAKE_INFO_LEN; ptr += TRUNNEL_HANDSHAKE_INFO_LEN;
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+}
+
+ssize_t
+trn_cell_rendezvous2_parse(trn_cell_rendezvous2_t **output, const uint8_t *input, const size_t len_in)
+{
+  ssize_t result;
+  *output = trn_cell_rendezvous2_new();
+  if (NULL == *output)
+    return -1;
+  result = trn_cell_rendezvous2_parse_into(*output, input, len_in);
+  if (result < 0) {
+    trn_cell_rendezvous2_free(*output);
+    *output = NULL;
+  }
+  return result;
+}

+ 69 - 0
src/trunnel/hs/cell_rendezvous.h

@@ -9,6 +9,7 @@
 #include "trunnel.h"
 
 #define TRUNNEL_REND_COOKIE_LEN 20
+#define TRUNNEL_HANDSHAKE_INFO_LEN 64
 #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS1)
 struct trn_cell_rendezvous1_st {
   uint8_t rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN];
@@ -17,6 +18,13 @@ struct trn_cell_rendezvous1_st {
 };
 #endif
 typedef struct trn_cell_rendezvous1_st trn_cell_rendezvous1_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS2)
+struct trn_cell_rendezvous2_st {
+  uint8_t handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN];
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct trn_cell_rendezvous2_st trn_cell_rendezvous2_t;
 /** Return a newly allocated trn_cell_rendezvous1 with all elements
  * set to zero.
  */
@@ -113,6 +121,67 @@ const uint8_t  * trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cel
  * failure.
  */
 int trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen);
+/** Return a newly allocated trn_cell_rendezvous2 with all elements
+ * set to zero.
+ */
+trn_cell_rendezvous2_t *trn_cell_rendezvous2_new(void);
+/** Release all storage held by the trn_cell_rendezvous2 in 'victim'.
+ * (Do nothing if 'victim' is NULL.)
+ */
+void trn_cell_rendezvous2_free(trn_cell_rendezvous2_t *victim);
+/** Try to parse a trn_cell_rendezvous2 from the buffer in 'input',
+ * using up to 'len_in' bytes from the input buffer. On success,
+ * return the number of bytes consumed and set *output to the newly
+ * allocated trn_cell_rendezvous2_t. On failure, return -2 if the
+ * input appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t trn_cell_rendezvous2_parse(trn_cell_rendezvous2_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * trn_cell_rendezvous2 in 'obj'. On failure, return a negative value.
+ * Note that this value may be an overestimate, and can even be an
+ * underestimate for certain unencodeable objects.
+ */
+ssize_t trn_cell_rendezvous2_encoded_len(const trn_cell_rendezvous2_t *obj);
+/** Try to encode the trn_cell_rendezvous2 from 'input' into the
+ * buffer at 'output', using up to 'avail' bytes of the output buffer.
+ * On success, return the number of bytes used. On failure, return -2
+ * if the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t trn_cell_rendezvous2_encode(uint8_t *output, size_t avail, const trn_cell_rendezvous2_t *input);
+/** Check whether the internal state of the trn_cell_rendezvous2 in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *trn_cell_rendezvous2_check(const trn_cell_rendezvous2_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int trn_cell_rendezvous2_clear_errors(trn_cell_rendezvous2_t *obj);
+/** Return the (constant) length of the array holding the
+ * handshake_info field of the trn_cell_rendezvous2_t in 'inp'.
+ */
+size_t trn_cell_rendezvous2_getlen_handshake_info(const trn_cell_rendezvous2_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * handshake_info of the trn_cell_rendezvous2_t in 'inp'.
+ */
+uint8_t trn_cell_rendezvous2_get_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx);
+/** As trn_cell_rendezvous2_get_handshake_info, but take and return a
+ * const pointer
+ */
+uint8_t trn_cell_rendezvous2_getconst_handshake_info(const trn_cell_rendezvous2_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * handshake_info of the trn_cell_rendezvous2_t in 'inp', so that it
+ * will hold the value 'elt'.
+ */
+int trn_cell_rendezvous2_set_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the TRUNNEL_HANDSHAKE_INFO_LEN-element array
+ * field handshake_info of 'inp'.
+ */
+uint8_t * trn_cell_rendezvous2_getarray_handshake_info(trn_cell_rendezvous2_t *inp);
+/** As trn_cell_rendezvous2_get_handshake_info, but take and return a
+ * const pointer
+ */
+const uint8_t  * trn_cell_rendezvous2_getconstarray_handshake_info(const trn_cell_rendezvous2_t *inp);
 
 
 #endif

+ 12 - 1
src/trunnel/hs/cell_rendezvous.trunnel

@@ -1,11 +1,16 @@
 /*
- * This contains the definition of the RENDEZVOUS1 cell for onion service
+ * This contains the definition of the RENDEZVOUS1/2 cell for onion service
  * version 3 and onward. The following format is specified in proposal 224
  * section 4.2.
  */
 
 /* Rendezvous cookie length. */
 const TRUNNEL_REND_COOKIE_LEN = 20;
+/* The HANDSHAKE_INFO field layout is as follow:
+ *    SERVER_PK   [PK_PUBKEY_LEN bytes]
+ *    AUTH        [MAC_LEN bytes]
+ * This means, the size is 32 bytes + 32 bytes. */
+const TRUNNEL_HANDSHAKE_INFO_LEN = 64;
 
 /* RENDEZVOUS1 payload. See details in section 4.2. */
 struct trn_cell_rendezvous1 {
@@ -16,3 +21,9 @@ struct trn_cell_rendezvous1 {
    * handshake type used. */
   u8 handshake_info[];
 };
+
+/* RENDEZVOUS2 payload. See details in section 4.2. */
+struct trn_cell_rendezvous2 {
+  /* The HANDSHAKE_INFO field. */
+  u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN];
+};