/** Extend the introduction circuit <b>circ</b> to another valid * introduction point for the hidden service it is trying to connect * to, or mark it and launch a new circuit if we can't extend it. * Return 0 on success or possible success. Return -1 and mark the * introduction circuit for close on permanent failure. * * On failure, the caller is responsible for marking the associated * rendezvous circuit for close. */ static int rend_client_reextend_intro_circuit(origin_circuit_t *circ) { extend_info_t *extend_info; int result; extend_info = rend_client_get_random_intro(circ->rend_data); if (!extend_info) { log_warn(LD_REND, "No usable introduction points left for %s. Closing.", safe_str_client(circ->rend_data->onion_address)); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); return -1; } if (circ->remaining_relay_early_cells) { log_info(LD_REND, "Re-extending circ %d, this time to %s.", circ->_base.n_circ_id, safe_str_client(extend_info_describe(extend_info))); result = circuit_extend_to_new_exit(circ, extend_info); } else { log_info(LD_REND, "Closing intro circ %d (out of RELAY_EARLY cells).", circ->_base.n_circ_id); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ result = 0; } extend_info_free(extend_info); return result; }
/* Retry the rendezvous point of circ by launching a new circuit to it. */ static void retry_service_rendezvous_point(const origin_circuit_t *circ) { int flags = 0; origin_circuit_t *new_circ; cpath_build_state_t *bstate; tor_assert(circ); /* This is initialized when allocating an origin circuit. */ tor_assert(circ->build_state); tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); /* Ease our life. */ bstate = circ->build_state; log_info(LD_REND, "Retrying rendezvous point circuit to %s", safe_str_client(extend_info_describe(bstate->chosen_exit))); /* Get the current build state flags for the next circuit. */ flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0; flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0; flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0; /* We do NOT add the onehop tunnel flag even though it might be a single * onion service. The reason is that if we failed once to connect to the RP * with a direct connection, we consider that chances are that we will fail * again so try a 3-hop circuit and hope for the best. Because the service * has no anonymity (single onion), this change of behavior won't affect * security directly. */ new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, bstate->chosen_exit, flags); if (new_circ == NULL) { log_warn(LD_REND, "Failed to launch rendezvous circuit to %s", safe_str_client(extend_info_describe(bstate->chosen_exit))); goto done; } /* Transfer build state information to the new circuit state in part to * catch any other failures. */ new_circ->build_state->failure_count = bstate->failure_count+1; new_circ->build_state->expiry_time = bstate->expiry_time; new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident); done: return; }
/* Return true iff the given service rendezvous circuit circ is allowed for a * relaunch to the rendezvous point. */ static int can_relaunch_service_rendezvous_point(const origin_circuit_t *circ) { tor_assert(circ); /* This is initialized when allocating an origin circuit. */ tor_assert(circ->build_state); tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); /* XXX: Retrying under certain condition. This is related to #22455. */ /* Avoid to relaunch twice a circuit to the same rendezvous point at the * same time. */ if (circ->hs_service_side_rend_circ_has_been_relaunched) { log_info(LD_REND, "Rendezvous circuit to %s has already been retried. " "Skipping retry.", safe_str_client( extend_info_describe(circ->build_state->chosen_exit))); goto disallow; } /* We check failure_count >= hs_get_service_max_rend_failures()-1 below, and * the -1 is because we increment the failure count for our current failure * *after* this clause. */ int max_rend_failures = hs_get_service_max_rend_failures() - 1; /* A failure count that has reached maximum allowed or circuit that expired, * we skip relaunching. */ if (circ->build_state->failure_count > max_rend_failures || circ->build_state->expiry_time <= time(NULL)) { log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has " "failed with %d attempts and expiry time %ld. " "Giving up building.", safe_str_client( extend_info_describe(circ->build_state->chosen_exit)), circ->build_state->failure_count, (long int) circ->build_state->expiry_time); goto disallow; } /* Allowed to relaunch. */ return 1; disallow: return 0; }
/* 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) { /* 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 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; }
/* For a given service, the ntor onion key and a rendezvous cookie, launch a * circuit to the rendezvous point specified by the link specifiers. On * success, a circuit identifier is attached to the circuit with the needed * data. This function will try to open a circuit for a maximum value of * MAX_REND_FAILURES then it will give up. */ static void launch_rendezvous_point_circuit(const hs_service_t *service, const hs_service_intro_point_t *ip, const hs_cell_introduce2_data_t *data) { int circ_needs_uptime; time_t now = time(NULL); extend_info_t *info = NULL; origin_circuit_t *circ; tor_assert(service); tor_assert(ip); tor_assert(data); circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports); /* Get the extend info data structure for the chosen rendezvous point * specified by the given link specifiers. */ info = hs_get_extend_info_from_lspecs(data->link_specifiers, &data->onion_pk, service->config.is_single_onion); if (info == NULL) { /* We are done here, we can't extend to the rendezvous point. * If you're running an IPv6-only v3 single onion service on 0.3.2 or with * 0.3.2 clients, and somehow disable the option check, it will fail here. */ log_fn(LOG_PROTOCOL_WARN, LD_REND, "Not enough info to open a circuit to a rendezvous point for " "%s service %s.", get_service_anonymity_string(service), safe_str_client(service->onion_address)); goto end; } for (int i = 0; i < MAX_REND_FAILURES; i++) { int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; if (circ_needs_uptime) { circ_flags |= CIRCLAUNCH_NEED_UPTIME; } /* Firewall and policies are checked when getting the extend info. */ if (service->config.is_single_onion) { circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; } circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info, circ_flags); if (circ != NULL) { /* Stop retrying, we have a circuit! */ break; } } if (circ == NULL) { log_warn(LD_REND, "Giving up on launching a rendezvous circuit to %s " "for %s service %s", safe_str_client(extend_info_describe(info)), get_service_anonymity_string(service), safe_str_client(service->onion_address)); goto end; } log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s " "for %s service %s", safe_str_client(extend_info_describe(info)), safe_str_client(hex_str((const char *) data->rendezvous_cookie, REND_COOKIE_LEN)), get_service_anonymity_string(service), safe_str_client(service->onion_address)); tor_assert(circ->build_state); /* Rendezvous circuit have a specific timeout for the time spent on trying * to connect to the rendezvous point. */ circ->build_state->expiry_time = now + MAX_REND_TIMEOUT; /* Create circuit identifier and key material. */ { hs_ntor_rend_cell_keys_t keys; curve25519_keypair_t ephemeral_kp; /* No need for extra strong, this is only for this circuit life time. This * key will be used for the RENDEZVOUS1 cell that will be sent on the * circuit once opened. */ curve25519_keypair_generate(&ephemeral_kp, 0); if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey, &ip->enc_key_kp, &ephemeral_kp, &data->client_pk, &keys) < 0) { /* This should not really happened but just in case, don't make tor * freak out, close the circuit and move on. */ log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for " "service %s", safe_str_client(service->onion_address)); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); goto end; } circ->hs_ident = create_rp_circuit_identifier(service, data->rendezvous_cookie, &ephemeral_kp.pubkey, &keys); memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp)); memwipe(&keys, 0, sizeof(keys)); tor_assert(circ->hs_ident); } end: extend_info_free(info); }