diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 97b6162606..319acdb0d6 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -39,6 +39,7 @@ static smartlist_t *circuits_pending_or_conns=NULL; static void circuit_free(circuit_t *circ); static void circuit_free_cpath(crypt_path_t *cpath); static void circuit_free_cpath_node(crypt_path_t *victim); +static void cpath_ref_decref(crypt_path_reference_t *cpath_ref); /********* END VARIABLES ************/ @@ -600,6 +601,7 @@ circuit_free(circuit_t *circ) if (ocirc->build_state) { extend_info_free(ocirc->build_state->chosen_exit); circuit_free_cpath_node(ocirc->build_state->pending_final_cpath); + cpath_ref_decref(ocirc->build_state->service_pending_final_cpath_ref); } tor_free(ocirc->build_state); @@ -723,6 +725,18 @@ circuit_free_cpath_node(crypt_path_t *victim) tor_free(victim); } +/** Release a crypt_path_reference_t*, which may be NULL. */ +static void +cpath_ref_decref(crypt_path_reference_t *cpath_ref) +{ + if (cpath_ref != NULL) { + if (--(cpath_ref->refcount) == 0) { + circuit_free_cpath_node(cpath_ref->cpath); + tor_free(cpath_ref); + } + } +} + /** A helper function for circuit_dump_by_conn() below. Log a bunch * of information about circuit circ. */ diff --git a/src/or/or.h b/src/or/or.h index 2acb621014..8fe3e0fe57 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2406,6 +2406,18 @@ typedef struct crypt_path_t { * at this step? */ } crypt_path_t; +/** A reference-counted pointer to a crypt_path_t, used only to share + * the final rendezvous cpath to be used on a service-side rendezvous + * circuit among multiple circuits built in parallel to the same + * destination rendezvous point. */ +typedef struct { + /** The reference count. */ + unsigned int refcount; + /** The pointer. Set to NULL when the crypt_path_t is put into use + * on an opened rendezvous circuit. */ + crypt_path_t *cpath; +} crypt_path_reference_t; + #define CPATH_KEY_MATERIAL_LEN (20*2+16*2) #define DH_KEY_LEN DH_BYTES @@ -2432,6 +2444,9 @@ typedef struct { unsigned int onehop_tunnel : 1; /** The crypt_path_t to append after rendezvous: used for rendezvous. */ crypt_path_t *pending_final_cpath; + /** A ref-counted reference to the crypt_path_t to append after + * rendezvous; used on the service side. */ + crypt_path_reference_t *service_pending_final_cpath_ref; /** How many times has building a circuit for this task failed? */ int failure_count; /** At what time should we give up on this task? */ diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 7dcc68dad0..5154f740cb 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -1385,7 +1385,12 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, memcpy(launched->rend_data->rend_cookie, r_cookie, REND_COOKIE_LEN); strlcpy(launched->rend_data->onion_address, service->service_id, sizeof(launched->rend_data->onion_address)); - launched->build_state->pending_final_cpath = cpath = + + launched->build_state->service_pending_final_cpath_ref = + tor_malloc_zero(sizeof(crypt_path_reference_t)); + launched->build_state->service_pending_final_cpath_ref->refcount = 1; + + launched->build_state->service_pending_final_cpath_ref->cpath = cpath = tor_malloc_zero(sizeof(crypt_path_t)); cpath->magic = CRYPT_PATH_MAGIC; launched->build_state->expiry_time = now + MAX_REND_TIMEOUT; @@ -1445,7 +1450,7 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) oldstate = oldcirc->build_state; tor_assert(oldstate); - if (oldstate->pending_final_cpath == NULL) { + if (oldstate->service_pending_final_cpath_ref == NULL) { log_info(LD_REND,"Skipping relaunch of circ that failed on its first hop. " "Initiator will retry."); return; @@ -1467,8 +1472,9 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) tor_assert(newstate); newstate->failure_count = oldstate->failure_count+1; newstate->expiry_time = oldstate->expiry_time; - newstate->pending_final_cpath = oldstate->pending_final_cpath; - oldstate->pending_final_cpath = NULL; + newstate->service_pending_final_cpath_ref = + oldstate->service_pending_final_cpath_ref; + ++(newstate->service_pending_final_cpath_ref->refcount); newcirc->rend_data = rend_data_dup(oldcirc->rend_data); } @@ -1726,8 +1732,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) tor_assert(!(circuit->build_state->onehop_tunnel)); #endif tor_assert(circuit->rend_data); - hop = circuit->build_state->pending_final_cpath; - tor_assert(hop); + hop = circuit->build_state->service_pending_final_cpath_ref->cpath; base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, @@ -1743,6 +1748,22 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) * no effect on Tor's behaviour. */ circuit->hs_circ_has_timed_out = 0; + /* If hop is NULL, another rend circ has already connected to this + * rend point. Close this circ. */ + if (hop == NULL) { + log_info(LD_REND, "Another rend circ has already reached this rend point; " + "closing this rend circ."); + reason = END_CIRC_REASON_NONE; + goto err; + } + + /* Remove our final cpath element from the reference, so that no + * other circuit will try to use it. Store it in + * pending_final_cpath for now to ensure that it will be freed if + * our rendezvous attempt fails. */ + circuit->build_state->pending_final_cpath = hop; + circuit->build_state->service_pending_final_cpath_ref->cpath = NULL; + service = rend_service_get_by_pk_digest( circuit->rend_data->rend_pk_digest); if (!service) {