[tor-commits] [tor/master] prop224: Introduction circuit creation

nickm at torproject.org nickm at torproject.org
Wed Aug 9 00:36:37 UTC 2017


commit 6a21ac7f9809963287dd678c9f2c494b3f9ebba3
Author: David Goulet <dgoulet at torproject.org>
Date:   Thu Feb 16 15:55:12 2017 -0500

    prop224: Introduction circuit creation
    
    Signed-off-by: David Goulet <dgoulet at torproject.org>
---
 src/or/circuitlist.c |   1 +
 src/or/hs_circuit.c  |  99 +++++++++++++++++++++++++++
 src/or/hs_circuit.h  |   9 +++
 src/or/hs_service.c  | 184 +++++++++++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 286 insertions(+), 7 deletions(-)

diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index f44343e11..39ebeb5f3 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -65,6 +65,7 @@
 #include "control.h"
 #include "entrynodes.h"
 #include "main.h"
+#include "hs_circuit.h"
 #include "hs_circuitmap.h"
 #include "hs_common.h"
 #include "hs_ident.h"
diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c
index 2f595d72e..482ba30f3 100644
--- a/src/or/hs_circuit.c
+++ b/src/or/hs_circuit.c
@@ -10,10 +10,17 @@
 #include "circuitlist.h"
 #include "circuituse.h"
 #include "config.h"
+#include "rephist.h"
+#include "router.h"
 
 #include "hs_circuit.h"
 #include "hs_ident.h"
 #include "hs_ntor.h"
+#include "hs_service.h"
+
+/* Trunnel. */
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
 
 /* A circuit is about to become an e2e rendezvous circuit. Check
  * <b>circ_purpose</b> and ensure that it's properly set. Return true iff
@@ -167,6 +174,98 @@ finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
   }
 }
 
+/* For a given circuit and a service introduction point object, register the
+ * intro circuit to the circuitmap. This supports legacy intro point. */
+static void
+register_intro_circ(const hs_service_intro_point_t *ip,
+                    origin_circuit_t *circ)
+{
+  tor_assert(ip);
+  tor_assert(circ);
+
+  if (ip->base.is_only_legacy) {
+    uint8_t digest[DIGEST_LEN];
+    if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
+      return;
+    }
+    hs_circuitmap_register_intro_circ_v2_service_side(circ, digest);
+  } else {
+    hs_circuitmap_register_intro_circ_v3_service_side(circ,
+                                         &ip->auth_key_kp.pubkey);
+  }
+}
+
+/* From a given service and service intro point, create an introduction point
+ * circuit identifier. This can't fail. */
+static hs_ident_circuit_t *
+create_intro_circuit_identifier(const hs_service_t *service,
+                                const hs_service_intro_point_t *ip)
+{
+  hs_ident_circuit_t *ident;
+
+  tor_assert(service);
+  tor_assert(ip);
+
+  ident = hs_ident_circuit_new(&service->keys.identity_pk,
+                               HS_IDENT_CIRCUIT_INTRO);
+  ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey);
+
+  return ident;
+}
+
+/* For a given service and a service intro point, launch a circuit to the
+ * extend info ei. If the service is a single onion, a one-hop circuit will be
+ * requested. Return 0 if the circuit was successfully launched and tagged
+ * with the correct identifier. On error, a negative value is returned. */
+int
+hs_circ_launch_intro_point(hs_service_t *service,
+                           const hs_service_intro_point_t *ip,
+                           extend_info_t *ei, time_t now)
+{
+  /* Standard flags for introduction circuit. */
+  int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+  origin_circuit_t *circ;
+
+  tor_assert(service);
+  tor_assert(ip);
+  tor_assert(ei);
+
+  /* Update circuit flags in case of a single onion service that requires a
+   * direct connection. */
+  if (service->config.is_single_onion) {
+    circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
+  }
+
+  log_info(LD_REND, "Launching a circuit to intro point %s for service %s.",
+           safe_str_client(extend_info_describe(ei)),
+           safe_str_client(service->onion_address));
+
+  /* Note down that we are about to use an internal circuit. */
+  rep_hist_note_used_internal(now, circ_flags & CIRCLAUNCH_NEED_UPTIME,
+                              circ_flags & CIRCLAUNCH_NEED_CAPACITY);
+
+  /* Note down the launch for the retry period. Even if the circuit fails to
+   * be launched, we still want to respect the retry period to avoid stress on
+   * the circuit subsystem. */
+  service->state.num_intro_circ_launched++;
+  circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
+                                       ei, circ_flags);
+  if (circ == NULL) {
+    goto end;
+  }
+
+  /* Setup the circuit identifier and attach it to it. */
+  circ->hs_ident = create_intro_circuit_identifier(service, ip);
+  tor_assert(circ->hs_ident);
+  /* Register circuit in the global circuitmap. */
+  register_intro_circ(ip, circ);
+
+  /* Success. */
+  ret = 0;
+ end:
+  return ret;
+}
+
 /* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key
  * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to
  * serve as a rendezvous end-to-end circuit between the client and the
diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h
index 71ce5c333..8738438eb 100644
--- a/src/or/hs_circuit.h
+++ b/src/or/hs_circuit.h
@@ -10,6 +10,15 @@
 #define TOR_HS_CIRCUIT_H
 
 #include "or.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#include "hs_service.h"
+
+/* Circuit API. */
+int hs_circ_launch_intro_point(hs_service_t *service,
+                               const hs_service_intro_point_t *ip,
+                               extend_info_t *ei, time_t now);
 
 /* e2e circuit API. */
 
diff --git a/src/or/hs_service.c b/src/or/hs_service.c
index 3cde2fe1b..06ab7b983 100644
--- a/src/or/hs_service.c
+++ b/src/or/hs_service.c
@@ -13,6 +13,7 @@
 #include "circuitbuild.h"
 #include "circuitlist.h"
 #include "config.h"
+#include "main.h"
 #include "networkstatus.h"
 #include "nodelist.h"
 #include "relay.h"
@@ -21,6 +22,7 @@
 #include "routerkeys.h"
 #include "routerlist.h"
 
+#include "hs_circuit.h"
 #include "hs_common.h"
 #include "hs_config.h"
 #include "hs_circuit.h"
@@ -385,6 +387,35 @@ get_node_from_intro_point(const hs_service_intro_point_t *ip)
   return node_get_by_id((const char *) ls->u.legacy_id);
 }
 
+/* Given a service intro point, return the extend_info_t for it. This can
+ * return NULL if the node can't be found for the intro point or the extend
+ * info can't be created for the found node. If direct_conn is set, the extend
+ * info is validated on if we can connect directly. */
+static extend_info_t *
+get_extend_info_from_intro_point(const hs_service_intro_point_t *ip,
+                                 unsigned int direct_conn)
+{
+  extend_info_t *info = NULL;
+  const node_t *node;
+
+  tor_assert(ip);
+
+  node = get_node_from_intro_point(ip);
+  if (node == NULL) {
+    /* This can happen if the relay serving as intro point has been removed
+     * from the consensus. In that case, the intro point will be removed from
+     * the descriptor during the scheduled events. */
+    goto end;
+  }
+
+  /* In the case of a direct connection (single onion service), it is possible
+   * our firewall policy won't allow it so this can return a NULL value. */
+  info = extend_info_from_node(node, direct_conn);
+
+ end:
+  return info;
+}
+
 /* Return an introduction point circuit matching the given intro point object.
  * NULL is returned is no such circuit can be found. */
 static origin_circuit_t *
@@ -1425,14 +1456,149 @@ run_build_descriptor_event(time_t now)
   update_all_descriptors(now);
 }
 
+/* For the given service, launch any intro point circuits that could be
+ * needed. This considers every descriptor of the service. */
+static void
+launch_intro_point_circuits(hs_service_t *service, time_t now)
+{
+  tor_assert(service);
+
+  /* For both descriptors, try to launch any missing introduction point
+   * circuits using the current map. */
+  FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+    /* Keep a ref on if we need a direct connection. We use this often. */
+    unsigned int direct_conn = service->config.is_single_onion;
+
+    DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
+                                hs_service_intro_point_t *, ip) {
+      extend_info_t *ei;
+
+      /* Skip the intro point that already has an existing circuit
+       * (established or not). */
+      if (get_intro_circuit(ip)) {
+        continue;
+      }
+
+      ei = get_extend_info_from_intro_point(ip, direct_conn);
+      if (ei == NULL) {
+        if (!direct_conn) {
+          /* In case of a multi-hop connection, it should never happen that we
+           * can't get the extend info from the node. Avoid connection and
+           * remove intro point from descriptor in order to recover from this
+           * potential bug. */
+          tor_assert_nonfatal(ei);
+        }
+        MAP_DEL_CURRENT(key);
+        service_intro_point_free(ip);
+        continue;
+      }
+
+      /* Launch a circuit to the intro point. */
+      ip->circuit_retries++;
+      if (hs_circ_launch_intro_point(service, ip, ei, now) < 0) {
+        log_warn(LD_REND, "Unable to launch intro circuit to node %s "
+                          "for service %s.",
+                 safe_str_client(extend_info_describe(ei)),
+                 safe_str_client(service->onion_address));
+        /* Intro point will be retried if possible after this. */
+      }
+      extend_info_free(ei);
+    } DIGEST256MAP_FOREACH_END;
+  } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Don't try to build more than this many circuits before giving up for a
+ * while. Dynamically calculated based on the configured number of intro
+ * points for the given service and how many descriptor exists. The default
+ * use case of 3 introduction points and two descriptors will allow 28
+ * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */
+static unsigned int
+get_max_intro_circ_per_period(const hs_service_t *service)
+{
+  unsigned int count = 0;
+  unsigned int multiplier = 0;
+  unsigned int num_wanted_ip;
+
+  tor_assert(service);
+  tor_assert(service->config.num_intro_points <=
+             HS_CONFIG_V3_MAX_INTRO_POINTS);
+
+  num_wanted_ip = service->config.num_intro_points;
+
+  /* The calculation is as follow. We have a number of intro points that we
+   * want configured as a torrc option (num_intro_points). We then add an
+   * extra value so we can launch multiple circuits at once and pick the
+   * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll
+   * pick 5 intros and launch 5 circuits. */
+  count += (num_wanted_ip + NUM_INTRO_POINTS_EXTRA);
+
+  /* Then we add the number of retries that is possible to do for each intro
+   * point. If we want 3 intros, we'll allow 3 times the number of possible
+   * retry. */
+  count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES);
+
+  /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we
+   * have none.  */
+  multiplier += (service->desc_current) ? 1 : 0;
+  multiplier += (service->desc_next) ? 1 : 0;
+
+  return (count * multiplier);
+}
+
+/* For the given service, return 1 if the service is allowed to launch more
+ * introduction circuits else 0 if the maximum has been reached for the retry
+ * period of INTRO_CIRC_RETRY_PERIOD. */
+static int
+can_service_launch_intro_circuit(hs_service_t *service, time_t now)
+{
+  tor_assert(service);
+
+  /* Consider the intro circuit retry period of the service. */
+  if (now > (service->state.intro_circ_retry_started_time +
+             INTRO_CIRC_RETRY_PERIOD)) {
+    service->state.intro_circ_retry_started_time = now;
+    service->state.num_intro_circ_launched = 0;
+    goto allow;
+  }
+  /* Check if we can still launch more circuits in this period. */
+  if (service->state.num_intro_circ_launched <=
+      get_max_intro_circ_per_period(service)) {
+    goto allow;
+  }
+
+  /* Rate limit log that we've reached our circuit creation limit. */
+  {
+    char *msg;
+    time_t elapsed_time = now - service->state.intro_circ_retry_started_time;
+    static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD);
+    if ((msg = rate_limit_log(&rlimit, now))) {
+      log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit "
+                        "of %u per %d seconds. It launched %u circuits in "
+                        "the last %ld seconds. Will retry in %ld seconds.",
+               safe_str_client(service->onion_address),
+               get_max_intro_circ_per_period(service),
+               INTRO_CIRC_RETRY_PERIOD,
+               service->state.num_intro_circ_launched, elapsed_time,
+               INTRO_CIRC_RETRY_PERIOD - elapsed_time);
+      tor_free(msg);
+    }
+  }
+
+  /* Not allow. */
+  return 0;
+ allow:
+  return 1;
+}
+
 /* Scheduled event run from the main loop. Make sure we have all the circuits
  * we need for each service. */
 static void
 run_build_circuit_event(time_t now)
 {
-  /* Make sure we can actually have enough information to build internal
-   * circuits as required by services. */
-  if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
+  /* Make sure we can actually have enough information or able to build
+   * internal circuits as required by services. */
+  if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN ||
+      !have_completed_a_circuit()) {
     return;
   }
 
@@ -1443,10 +1609,14 @@ run_build_circuit_event(time_t now)
 
   /* Run v3+ check. */
   FOR_EACH_SERVICE_BEGIN(service) {
-    /* XXX: Check every service for validity of circuits. */
-    /* XXX: Make sure we have a retry period so we don't stress circuit
-     * creation. */
-    (void) service;
+    /* For introduction circuit, we need to make sure we don't stress too much
+     * circuit creation so make sure this service is respecting that limit. */
+    if (can_service_launch_intro_circuit(service, now)) {
+      /* Launch intro point circuits if needed. */
+      launch_intro_point_circuits(service, now);
+      /* Once the circuits have opened, we'll make sure to update the
+       * descriptor intro point list and cleanup any extraneous. */
+    }
   } FOR_EACH_SERVICE_END;
 }
 





More information about the tor-commits mailing list