prop224: Introduction circuit creation

Signed-off-by: David Goulet <dgoulet@torproject.org>
This commit is contained in:
David Goulet 2017-02-16 15:55:12 -05:00 committed by Nick Mathewson
parent 00a02a3a59
commit 6a21ac7f98
4 changed files with 286 additions and 7 deletions

View file

@ -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"

View file

@ -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

View file

@ -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. */

View file

@ -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;
}