mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2025-02-25 15:10:48 +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 "control.h"
|
||||||
#include "entrynodes.h"
|
#include "entrynodes.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "hs_circuit.h"
|
||||||
#include "hs_circuitmap.h"
|
#include "hs_circuitmap.h"
|
||||||
#include "hs_common.h"
|
#include "hs_common.h"
|
||||||
#include "hs_ident.h"
|
#include "hs_ident.h"
|
||||||
|
|
|
@ -10,10 +10,17 @@
|
||||||
#include "circuitlist.h"
|
#include "circuitlist.h"
|
||||||
#include "circuituse.h"
|
#include "circuituse.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "rephist.h"
|
||||||
|
#include "router.h"
|
||||||
|
|
||||||
#include "hs_circuit.h"
|
#include "hs_circuit.h"
|
||||||
#include "hs_ident.h"
|
#include "hs_ident.h"
|
||||||
#include "hs_ntor.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
|
/* 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
|
* <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
|
/* 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
|
* 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
|
* serve as a rendezvous end-to-end circuit between the client and the
|
||||||
|
|
|
@ -10,6 +10,15 @@
|
||||||
#define TOR_HS_CIRCUIT_H
|
#define TOR_HS_CIRCUIT_H
|
||||||
|
|
||||||
#include "or.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. */
|
/* e2e circuit API. */
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "circuitbuild.h"
|
#include "circuitbuild.h"
|
||||||
#include "circuitlist.h"
|
#include "circuitlist.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "main.h"
|
||||||
#include "networkstatus.h"
|
#include "networkstatus.h"
|
||||||
#include "nodelist.h"
|
#include "nodelist.h"
|
||||||
#include "relay.h"
|
#include "relay.h"
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
#include "routerkeys.h"
|
#include "routerkeys.h"
|
||||||
#include "routerlist.h"
|
#include "routerlist.h"
|
||||||
|
|
||||||
|
#include "hs_circuit.h"
|
||||||
#include "hs_common.h"
|
#include "hs_common.h"
|
||||||
#include "hs_config.h"
|
#include "hs_config.h"
|
||||||
#include "hs_circuit.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);
|
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.
|
/* Return an introduction point circuit matching the given intro point object.
|
||||||
* NULL is returned is no such circuit can be found. */
|
* NULL is returned is no such circuit can be found. */
|
||||||
static origin_circuit_t *
|
static origin_circuit_t *
|
||||||
|
@ -1425,14 +1456,149 @@ run_build_descriptor_event(time_t now)
|
||||||
update_all_descriptors(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
|
/* Scheduled event run from the main loop. Make sure we have all the circuits
|
||||||
* we need for each service. */
|
* we need for each service. */
|
||||||
static void
|
static void
|
||||||
run_build_circuit_event(time_t now)
|
run_build_circuit_event(time_t now)
|
||||||
{
|
{
|
||||||
/* Make sure we can actually have enough information to build internal
|
/* Make sure we can actually have enough information or able to build
|
||||||
* circuits as required by services. */
|
* internal circuits as required by services. */
|
||||||
if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
|
if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN ||
|
||||||
|
!have_completed_a_circuit()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1443,10 +1609,14 @@ run_build_circuit_event(time_t now)
|
||||||
|
|
||||||
/* Run v3+ check. */
|
/* Run v3+ check. */
|
||||||
FOR_EACH_SERVICE_BEGIN(service) {
|
FOR_EACH_SERVICE_BEGIN(service) {
|
||||||
/* XXX: Check every service for validity of circuits. */
|
/* For introduction circuit, we need to make sure we don't stress too much
|
||||||
/* XXX: Make sure we have a retry period so we don't stress circuit
|
* circuit creation so make sure this service is respecting that limit. */
|
||||||
* creation. */
|
if (can_service_launch_intro_circuit(service, now)) {
|
||||||
(void) service;
|
/* 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;
|
} FOR_EACH_SERVICE_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue