mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2025-02-25 07:07:52 +01:00
prop224: Introduction circuit creation
Signed-off-by: David Goulet <dgoulet@torproject.org>
This commit is contained in:
parent
00a02a3a59
commit
6a21ac7f98
4 changed files with 286 additions and 7 deletions
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue