commit 6a21ac7f9809963287dd678c9f2c494b3f9ebba3 Author: David Goulet dgoulet@torproject.org Date: Thu Feb 16 15:55:12 2017 -0500
prop224: Introduction circuit creation
Signed-off-by: David Goulet dgoulet@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; }