/** 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; }
/** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell * down introcirc if possible. */ int rend_client_send_introduction(origin_circuit_t *introcirc, origin_circuit_t *rendcirc) { const or_options_t *options = get_options(); size_t payload_len; int r, v3_shift = 0; char payload[RELAY_PAYLOAD_SIZE]; char tmp[RELAY_PAYLOAD_SIZE]; rend_cache_entry_t *entry = NULL; crypt_path_t *cpath; off_t dh_offset; crypto_pk_t *intro_key = NULL; int status = 0; const char *onion_address; tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); tor_assert(rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY); tor_assert(introcirc->rend_data); tor_assert(rendcirc->rend_data); tor_assert(!rend_cmp_service_ids(rend_data_get_address(introcirc->rend_data), rend_data_get_address(rendcirc->rend_data))); assert_circ_anonymity_ok(introcirc, options); assert_circ_anonymity_ok(rendcirc, options); onion_address = rend_data_get_address(introcirc->rend_data); r = rend_cache_lookup_entry(onion_address, -1, &entry); /* An invalid onion address is not possible else we have a big issue. */ tor_assert(r != -EINVAL); if (r < 0 || !rend_client_any_intro_points_usable(entry)) { /* If the descriptor is not found or the intro points are not usable * anymore, trigger a fetch. */ log_info(LD_REND, "query %s didn't have valid rend desc in cache. " "Refetching descriptor.", safe_str_client(onion_address)); rend_client_refetch_v2_renddesc(introcirc->rend_data); { connection_t *conn; while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT, onion_address))) { connection_ap_mark_as_waiting_for_renddesc(TO_ENTRY_CONN(conn)); } } status = -1; goto cleanup; } /* first 20 bytes of payload are the hash of the service's pk */ intro_key = NULL; SMARTLIST_FOREACH(entry->parsed->intro_nodes, rend_intro_point_t *, intro, { if (tor_memeq(introcirc->build_state->chosen_exit->identity_digest, intro->extend_info->identity_digest, DIGEST_LEN)) { intro_key = intro->intro_key; break; } });
/* Using the query which is the base64 encoded blinded key of a version 3 * descriptor, lookup in our directory cache the entry. If found, 1 is * returned and desc_out is populated with a newly allocated string being the * encoded descriptor. If not found, 0 is returned and desc_out is untouched. * On error, a negative value is returned and desc_out is untouched. */ static int cache_lookup_v3_as_dir(const char *query, const char **desc_out) { int found = 0; ed25519_public_key_t blinded_key; const hs_cache_dir_descriptor_t *entry; tor_assert(query); /* Decode blinded key using the given query value. */ if (ed25519_public_from_base64(&blinded_key, query) < 0) { log_info(LD_REND, "Unable to decode the v3 HSDir query %s.", safe_str_client(query)); goto err; } entry = lookup_v3_desc_as_dir(blinded_key.pubkey); if (entry != NULL) { found = 1; if (desc_out) { *desc_out = entry->encoded_desc; } } return found; err: return -1; }
/* 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; }
/* 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; }
/* 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 introduction point and an introduction circuit, send the * ESTABLISH_INTRO cell. The service object is used for logging. This can fail * and if so, the circuit is closed and the intro point object is flagged * that the circuit is not established anymore which is important for the * retry mechanism. */ static void send_establish_intro(const hs_service_t *service, hs_service_intro_point_t *ip, origin_circuit_t *circ) { ssize_t cell_len; uint8_t payload[RELAY_PAYLOAD_SIZE]; tor_assert(service); tor_assert(ip); tor_assert(circ); /* Encode establish intro cell. */ cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, ip, payload); if (cell_len < 0) { log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " "on circuit %u. Closing circuit.", safe_str_client(service->onion_address), TO_CIRCUIT(circ)->n_circ_id); goto err; } /* Send the cell on the circuit. */ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), RELAY_COMMAND_ESTABLISH_INTRO, (char *) payload, cell_len, circ->cpath->prev) < 0) { log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s " "on circuit %u.", safe_str_client(service->onion_address), TO_CIRCUIT(circ)->n_circ_id); /* On error, the circuit has been closed. */ goto done; } /* Record the attempt to use this circuit. */ pathbias_count_use_attempt(circ); goto done; err: circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); done: memwipe(payload, 0, sizeof(payload)); }
/** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell * down introcirc if possible. */ int rend_client_send_introduction(origin_circuit_t *introcirc, origin_circuit_t *rendcirc) { size_t payload_len; int r, v3_shift = 0; char payload[RELAY_PAYLOAD_SIZE]; char tmp[RELAY_PAYLOAD_SIZE]; rend_cache_entry_t *entry; crypt_path_t *cpath; off_t dh_offset; crypto_pk_t *intro_key = NULL; int status = 0; tor_assert(introcirc->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); tor_assert(rendcirc->_base.purpose == CIRCUIT_PURPOSE_C_REND_READY); tor_assert(introcirc->rend_data); tor_assert(rendcirc->rend_data); tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address, rendcirc->rend_data->onion_address)); #ifndef NON_ANONYMOUS_MODE_ENABLED tor_assert(!(introcirc->build_state->onehop_tunnel)); tor_assert(!(rendcirc->build_state->onehop_tunnel)); #endif if (rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1, &entry) < 1) { log_info(LD_REND, "query %s didn't have valid rend desc in cache. " "Refetching descriptor.", safe_str_client(introcirc->rend_data->onion_address)); rend_client_refetch_v2_renddesc(introcirc->rend_data); { connection_t *conn; while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT, introcirc->rend_data->onion_address))) { conn->state = AP_CONN_STATE_RENDDESC_WAIT; } } status = -1; goto cleanup; } /* first 20 bytes of payload are the hash of Bob's pk */ intro_key = NULL; SMARTLIST_FOREACH(entry->parsed->intro_nodes, rend_intro_point_t *, intro, { if (tor_memeq(introcirc->build_state->chosen_exit->identity_digest, intro->extend_info->identity_digest, DIGEST_LEN)) { intro_key = intro->intro_key; break; } });
/* Clean the v3 cache by removing any entry that has expired using the * <b>global_cutoff</b> value. If <b>global_cutoff</b> is 0, the cleaning * process will use the lifetime found in the plaintext data section. Return * the number of bytes cleaned. */ STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff) { size_t bytes_removed = 0; /* Code flow error if this ever happens. */ tor_assert(global_cutoff >= 0); if (!hs_cache_v3_dir) { /* No cache to clean. Just return. */ return 0; } DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_dir, key, hs_cache_dir_descriptor_t *, entry) { size_t entry_size; time_t cutoff = global_cutoff; if (!cutoff) { /* Cutoff is the lifetime of the entry found in the descriptor. */ cutoff = now - entry->plaintext_data->lifetime_sec; } /* If the entry has been created _after_ the cutoff, not expired so * continue to the next entry in our v3 cache. */ if (entry->created_ts > cutoff) { continue; } /* Here, our entry has expired, remove and free. */ MAP_DEL_CURRENT(key); entry_size = cache_get_dir_entry_size(entry); bytes_removed += entry_size; /* Entry is not in the cache anymore, destroy it. */ cache_dir_desc_free(entry); /* Update our cache entry allocation size for the OOM. */ rend_cache_decrement_allocation(entry_size); /* Logging. */ { char key_b64[BASE64_DIGEST256_LEN + 1]; digest256_to_base64(key_b64, (const char *) key); log_info(LD_REND, "Removing v3 descriptor '%s' from HSDir cache", safe_str_client(key_b64)); } } DIGEST256MAP_FOREACH_END;
/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the * given introduction circuit circ. The service is only used for logging * purposes. Return 0 on success else a negative value. */ int hs_circ_handle_intro_established(const hs_service_t *service, const hs_service_intro_point_t *ip, origin_circuit_t *circ, const uint8_t *payload, size_t payload_len) { int ret = -1; tor_assert(service); tor_assert(ip); tor_assert(circ); tor_assert(payload); if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { goto done; } /* Try to parse the payload into a cell making sure we do actually have a * valid cell. For a legacy node, it's an empty payload so as long as we * have the cell, we are good. */ if (!ip->base.is_only_legacy && hs_cell_parse_intro_established(payload, payload_len) < 0) { log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on " "circuit %u for service %s", TO_CIRCUIT(circ)->n_circ_id, safe_str_client(service->onion_address)); goto done; } /* Switch the purpose to a fully working intro point. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO); /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the * circuit so update our pathbias subsystem. */ pathbias_mark_use_success(circ); /* Success. */ ret = 0; done: return ret; }
/** Process a 'netinfo' cell: read and act on its contents, and set the * connection state to "open". */ static void command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) { time_t timestamp; uint8_t my_addr_type; uint8_t my_addr_len; const uint8_t *my_addr_ptr; const uint8_t *cp, *end; uint8_t n_other_addrs; time_t now = time(NULL); long apparent_skew = 0; uint32_t my_apparent_addr = 0; if (conn->link_proto < 2) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Received a NETINFO cell on %s connection; dropping.", conn->link_proto == 0 ? "non-versioned" : "a v1"); return; } if (conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING_V2 && conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING_V3) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Received a NETINFO cell on non-handshaking connection; dropping."); return; } tor_assert(conn->handshake_state && conn->handshake_state->received_versions); if (conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { tor_assert(conn->link_proto >= 3); if (conn->handshake_state->started_here) { if (!conn->handshake_state->authenticated) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Got a NETINFO cell from server, " "but no authentication. Closing the connection."); connection_mark_for_close(TO_CONN(conn)); return; } } else { /* we're the server. If the client never authenticated, we have some housekeeping to do.*/ if (!conn->handshake_state->authenticated) { tor_assert(tor_digest_is_zero( (const char*)conn->handshake_state->authenticated_peer_id)); connection_or_set_circid_type(conn, NULL); connection_or_init_conn_from_address(conn, &conn->_base.addr, conn->_base.port, (const char*)conn->handshake_state->authenticated_peer_id, 0); } } } /* Decode the cell. */ timestamp = ntohl(get_uint32(cell->payload)); if (labs(now - conn->handshake_state->sent_versions_at) < 180) { apparent_skew = now - timestamp; } my_addr_type = (uint8_t) cell->payload[4]; my_addr_len = (uint8_t) cell->payload[5]; my_addr_ptr = (uint8_t*) cell->payload + 6; end = cell->payload + CELL_PAYLOAD_SIZE; cp = cell->payload + 6 + my_addr_len; if (cp >= end) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Addresses too long in netinfo cell; closing connection."); connection_mark_for_close(TO_CONN(conn)); return; } else if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) { my_apparent_addr = ntohl(get_uint32(my_addr_ptr)); } n_other_addrs = (uint8_t) *cp++; while (n_other_addrs && cp < end-2) { /* Consider all the other addresses; if any matches, this connection is * "canonical." */ tor_addr_t addr; const uint8_t *next = decode_address_from_payload(&addr, cp, (int)(end-cp)); if (next == NULL) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Bad address in netinfo cell; closing connection."); connection_mark_for_close(TO_CONN(conn)); return; } if (tor_addr_eq(&addr, &conn->real_addr)) { conn->is_canonical = 1; break; } cp = next; --n_other_addrs; } /* Act on apparent skew. */ /** Warn when we get a netinfo skew with at least this value. */ #define NETINFO_NOTICE_SKEW 3600 if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && router_get_by_id_digest(conn->identity_digest)) { char dbuf[64]; int severity; /*XXXX be smarter about when everybody says we are skewed. */ if (router_digest_is_trusted_dir(conn->identity_digest)) severity = LOG_WARN; else severity = LOG_INFO; format_time_interval(dbuf, sizeof(dbuf), apparent_skew); log_fn(severity, LD_GENERAL, "Received NETINFO cell with skewed time from " "server at %s:%d. It seems that our clock is %s by %s, or " "that theirs is %s. Tor requires an accurate clock to work: " "please check your time and date settings.", conn->_base.address, (int)conn->_base.port, apparent_skew>0 ? "ahead" : "behind", dbuf, apparent_skew>0 ? "behind" : "ahead"); if (severity == LOG_WARN) /* only tell the controller if an authority */ control_event_general_status(LOG_WARN, "CLOCK_SKEW SKEW=%ld SOURCE=OR:%s:%d", apparent_skew, conn->_base.address, conn->_base.port); } /* XXX maybe act on my_apparent_addr, if the source is sufficiently * trustworthy. */ (void)my_apparent_addr; if (connection_or_set_state_open(conn)<0) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Got good NETINFO cell from %s:%d; but " "was unable to make the OR connection become open.", safe_str_client(conn->_base.address), conn->_base.port); connection_mark_for_close(TO_CONN(conn)); } else { log_info(LD_OR, "Got good NETINFO cell from %s:%d; OR connection is now " "open, using protocol version %d. Its ID digest is %s", safe_str_client(conn->_base.address), conn->_base.port, (int)conn->link_proto, hex_str(conn->identity_digest, DIGEST_LEN)); } assert_connection_ok(TO_CONN(conn),time(NULL)); }
/** Process a 'versions' cell. The current link protocol version must be 0 * to indicate that no version has yet been negotiated. We compare the * versions in the cell to the list of versions we support, pick the * highest version we have in common, and continue the negotiation from * there. */ static void command_process_versions_cell(var_cell_t *cell, or_connection_t *conn) { int highest_supported_version = 0; const uint8_t *cp, *end; const int started_here = connection_or_nonopen_was_started_here(conn); if (conn->link_proto != 0 || (conn->handshake_state && conn->handshake_state->received_versions)) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Received a VERSIONS cell on a connection with its version " "already set to %d; dropping", (int) conn->link_proto); return; } switch (conn->_base.state) { case OR_CONN_STATE_OR_HANDSHAKING_V2: case OR_CONN_STATE_OR_HANDSHAKING_V3: break; case OR_CONN_STATE_TLS_HANDSHAKING: case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: default: log_fn(LOG_PROTOCOL_WARN, LD_OR, "VERSIONS cell while in unexpected state"); return; } tor_assert(conn->handshake_state); end = cell->payload + cell->payload_len; for (cp = cell->payload; cp+1 < end; ++cp) { uint16_t v = ntohs(get_uint16(cp)); if (is_or_protocol_version_known(v) && v > highest_supported_version) highest_supported_version = v; } if (!highest_supported_version) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Couldn't find a version in common between my version list and the " "list in the VERSIONS cell; closing connection."); connection_mark_for_close(TO_CONN(conn)); return; } else if (highest_supported_version == 1) { /* Negotiating version 1 makes no sense, since version 1 has no VERSIONS * cells. */ log_fn(LOG_PROTOCOL_WARN, LD_OR, "Used version negotiation protocol to negotiate a v1 connection. " "That's crazily non-compliant. Closing connection."); connection_mark_for_close(TO_CONN(conn)); return; } else if (highest_supported_version < 3 && conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Negotiated link protocol 2 or lower after doing a v3 TLS " "handshake. Closing connection."); connection_mark_for_close(TO_CONN(conn)); return; } conn->link_proto = highest_supported_version; conn->handshake_state->received_versions = 1; if (conn->link_proto == 2) { log_info(LD_OR, "Negotiated version %d with %s:%d; sending NETINFO.", highest_supported_version, safe_str_client(conn->_base.address), conn->_base.port); if (connection_or_send_netinfo(conn) < 0) { connection_mark_for_close(TO_CONN(conn)); return; } } else { const int send_versions = !started_here; /* If we want to authenticate, send a CERTS cell */ const int send_certs = !started_here || public_server_mode(get_options()); /* If we're a relay that got a connection, ask for authentication. */ const int send_chall = !started_here && public_server_mode(get_options()); /* If our certs cell will authenticate us, or if we have no intention of * authenticating, send a netinfo cell right now. */ const int send_netinfo = !(started_here && public_server_mode(get_options())); const int send_any = send_versions || send_certs || send_chall || send_netinfo; tor_assert(conn->link_proto >= 3); log_info(LD_OR, "Negotiated version %d with %s:%d; %s%s%s%s%s", highest_supported_version, safe_str_client(conn->_base.address), conn->_base.port, send_any ? "Sending cells:" : "Waiting for CERTS cell", send_versions ? " VERSIONS" : "", send_certs ? " CERTS" : "", send_chall ? " AUTH_CHALLENGE" : "", send_netinfo ? " NETINFO" : ""); #ifdef DISABLE_V3_LINKPROTO_SERVERSIDE if (1) { connection_mark_for_close(TO_CONN(conn)); return; } #endif if (send_versions) { if (connection_or_send_versions(conn, 1) < 0) { log_warn(LD_OR, "Couldn't send versions cell"); connection_mark_for_close(TO_CONN(conn)); return; } } if (send_certs) { if (connection_or_send_certs_cell(conn) < 0) { log_warn(LD_OR, "Couldn't send certs cell"); connection_mark_for_close(TO_CONN(conn)); return; } } if (send_chall) { if (connection_or_send_auth_challenge_cell(conn) < 0) { log_warn(LD_OR, "Couldn't send auth_challenge cell"); connection_mark_for_close(TO_CONN(conn)); return; } } if (send_netinfo) { if (connection_or_send_netinfo(conn) < 0) { log_warn(LD_OR, "Couldn't send netinfo cell"); connection_mark_for_close(TO_CONN(conn)); return; } } } }
/* Called when a service rendezvous point circuit is done building. Given the * service and the circuit, this function will send a RENDEZVOUS1 cell on the * circuit using the information in the circuit identifier. If the cell can't * be sent, the circuit is closed. */ void hs_circ_service_rp_has_opened(const hs_service_t *service, origin_circuit_t *circ) { size_t payload_len; uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; tor_assert(service); tor_assert(circ); tor_assert(circ->hs_ident); /* Some useful logging. */ log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s " "for service %s", TO_CIRCUIT(circ)->n_circ_id, hex_str((const char *) circ->hs_ident->rendezvous_cookie, REND_COOKIE_LEN), safe_str_client(service->onion_address)); circuit_log_path(LOG_INFO, LD_REND, circ); /* This can't fail. */ payload_len = hs_cell_build_rendezvous1( circ->hs_ident->rendezvous_cookie, sizeof(circ->hs_ident->rendezvous_cookie), circ->hs_ident->rendezvous_handshake_info, sizeof(circ->hs_ident->rendezvous_handshake_info), payload); /* Pad the payload with random bytes so it matches the size of a legacy cell * which is normally always bigger. Also, the size of a legacy cell is * always smaller than the RELAY_PAYLOAD_SIZE so this is safe. */ if (payload_len < HS_LEGACY_RENDEZVOUS_CELL_SIZE) { crypto_rand((char *) payload + payload_len, HS_LEGACY_RENDEZVOUS_CELL_SIZE - payload_len); payload_len = HS_LEGACY_RENDEZVOUS_CELL_SIZE; } if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), RELAY_COMMAND_RENDEZVOUS1, (const char *) payload, payload_len, circ->cpath->prev) < 0) { /* On error, circuit is closed. */ log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u " "for service %s", TO_CIRCUIT(circ)->n_circ_id, safe_str_client(service->onion_address)); goto done; } /* Setup end-to-end rendezvous circuit between the client and us. */ if (hs_circuit_setup_e2e_rend_circ(circ, circ->hs_ident->rendezvous_ntor_key_seed, sizeof(circ->hs_ident->rendezvous_ntor_key_seed), 1) < 0) { log_warn(LD_GENERAL, "Failed to setup circ"); goto done; } done: memwipe(payload, 0, sizeof(payload)); }
/* Called when a service introduction point circuit is done building. Given * the service and intro point object, this function will send the * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the * circuit has been repurposed to General because we already have too many * opened. */ int hs_circ_service_intro_has_opened(hs_service_t *service, hs_service_intro_point_t *ip, const hs_service_descriptor_t *desc, origin_circuit_t *circ) { int ret = 0; unsigned int num_intro_circ, num_needed_circ; tor_assert(service); tor_assert(ip); tor_assert(desc); tor_assert(circ); /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already * established introduction circuits */ num_intro_circ = count_opened_desc_intro_point_circuits(service, desc); num_needed_circ = service->config.num_intro_points; if (num_intro_circ > num_needed_circ) { /* There are too many opened valid intro circuit for what the service * needs so repurpose this one. */ /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just * closes the circuit. I have NO idea why it does that so it hasn't been * added here. I can only assume in case our ExcludeNodes list changes but * in that case, all circuit are flagged unusable (config.c). --dgoulet */ log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we " "have enough for service %s. Repurposing " "it to general and leaving internal.", safe_str_client(service->onion_address)); tor_assert(circ->build_state->is_internal); /* Remove it from the circuitmap. */ hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); /* Cleaning up the hidden service identifier and repurpose. */ hs_ident_circuit_free(circ->hs_ident); circ->hs_ident = NULL; if (circuit_should_use_vanguards(TO_CIRCUIT(circ)->purpose)) circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_HS_VANGUARDS); else circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL); /* Inform that this circuit just opened for this new purpose. */ circuit_has_opened(circ); /* This return value indicate to the caller that the IP object should be * removed from the service because it's corresponding circuit has just * been repurposed. */ ret = 1; goto done; } log_info(LD_REND, "Introduction circuit %u established for service %s.", TO_CIRCUIT(circ)->n_circ_id, safe_str_client(service->onion_address)); circuit_log_path(LOG_INFO, LD_REND, circ); /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call * makes sure the circuit gets closed. */ send_establish_intro(service, ip, circ); done: 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); }
/** Mark <b>circ</b> to be closed next time we call * circuit_close_all_marked(). Do any cleanup needed: * - If state is onionskin_pending, remove circ from the onion_pending * list. * - If circ isn't open yet: call circuit_build_failed() if we're * the origin, and in either case call circuit_rep_hist_note_result() * to note stats. * - If purpose is C_INTRODUCE_ACK_WAIT, report the intro point * failure we just had to the hidden service client module. * - If purpose is C_INTRODUCING and <b>reason</b> isn't TIMEOUT, * report to the hidden service client module that the intro point * we just tried may be unreachable. * - Send appropriate destroys and edge_destroys for conns and * streams attached to circ. * - If circ->rend_splice is set (we are the midpoint of a joined * rendezvous stream), then mark the other circuit to close as well. */ void _circuit_mark_for_close(circuit_t *circ, int reason, int line, const char *file) { int orig_reason = reason; /* Passed to the controller */ assert_circuit_ok(circ); tor_assert(line); tor_assert(file); if (circ->marked_for_close) { log(LOG_WARN,LD_BUG, "Duplicate call to circuit_mark_for_close at %s:%d" " (first at %s:%d)", file, line, circ->marked_for_close_file, circ->marked_for_close); return; } if (reason == END_CIRC_AT_ORIGIN) { if (!CIRCUIT_IS_ORIGIN(circ)) { log_warn(LD_BUG, "Specified 'at-origin' non-reason for ending circuit, " "but circuit was not at origin. (called %s:%d, purpose=%d)", file, line, circ->purpose); } reason = END_CIRC_REASON_NONE; } if (CIRCUIT_IS_ORIGIN(circ)) { /* We don't send reasons when closing circuits at the origin. */ reason = END_CIRC_REASON_NONE; } if (reason & END_CIRC_REASON_FLAG_REMOTE) reason &= ~END_CIRC_REASON_FLAG_REMOTE; if (reason < _END_CIRC_REASON_MIN || reason > _END_CIRC_REASON_MAX) { if (!(orig_reason & END_CIRC_REASON_FLAG_REMOTE)) log_warn(LD_BUG, "Reason %d out of range at %s:%d", reason, file, line); reason = END_CIRC_REASON_NONE; } if (circ->state == CIRCUIT_STATE_ONIONSKIN_PENDING) { onion_pending_remove(TO_OR_CIRCUIT(circ)); } /* If the circuit ever became OPEN, we sent it to the reputation history * module then. If it isn't OPEN, we send it there now to remember which * links worked and which didn't. */ if (circ->state != CIRCUIT_STATE_OPEN) { if (CIRCUIT_IS_ORIGIN(circ)) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); circuit_build_failed(ocirc); /* take actions if necessary */ circuit_rep_hist_note_result(ocirc); } } if (circ->state == CIRCUIT_STATE_OR_WAIT) { if (circuits_pending_or_conns) smartlist_remove(circuits_pending_or_conns, circ); } if (CIRCUIT_IS_ORIGIN(circ)) { control_event_circuit_status(TO_ORIGIN_CIRCUIT(circ), (circ->state == CIRCUIT_STATE_OPEN)?CIRC_EVENT_CLOSED:CIRC_EVENT_FAILED, orig_reason); } if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); int timed_out = (reason == END_CIRC_REASON_TIMEOUT); tor_assert(circ->state == CIRCUIT_STATE_OPEN); tor_assert(ocirc->build_state->chosen_exit); tor_assert(ocirc->rend_data); /* treat this like getting a nack from it */ log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", safe_str_client(ocirc->rend_data->onion_address), safe_str_client(build_state_get_exit_nickname(ocirc->build_state)), timed_out ? "Recording timeout." : "Removing from descriptor."); rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, ocirc->rend_data, timed_out ? INTRO_POINT_FAILURE_TIMEOUT : INTRO_POINT_FAILURE_GENERIC); } else if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCING && reason != END_CIRC_REASON_TIMEOUT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); if (ocirc->build_state->chosen_exit && ocirc->rend_data) { log_info(LD_REND, "Failed intro circ %s to %s " "(building circuit to intro point). " "Marking intro point as possibly unreachable.", safe_str_client(ocirc->rend_data->onion_address), safe_str_client(build_state_get_exit_nickname(ocirc->build_state))); rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, ocirc->rend_data, INTRO_POINT_FAILURE_UNREACHABLE); } } if (circ->n_conn) { circuit_clear_cell_queue(circ, circ->n_conn); connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason); } if (! CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); edge_connection_t *conn; for (conn=or_circ->n_streams; conn; conn=conn->next_stream) connection_edge_destroy(or_circ->p_circ_id, conn); or_circ->n_streams = NULL; while (or_circ->resolving_streams) { conn = or_circ->resolving_streams; or_circ->resolving_streams = conn->next_stream; if (!conn->_base.marked_for_close) { /* The client will see a DESTROY, and infer that the connections * are closing because the circuit is getting torn down. No need * to send an end cell. */ conn->edge_has_sent_end = 1; conn->end_reason = END_STREAM_REASON_DESTROY; conn->end_reason |= END_STREAM_REASON_FLAG_ALREADY_SENT_CLOSED; connection_mark_for_close(TO_CONN(conn)); } conn->on_circuit = NULL; } if (or_circ->p_conn) { circuit_clear_cell_queue(circ, or_circ->p_conn); connection_or_send_destroy(or_circ->p_circ_id, or_circ->p_conn, reason); } } else { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); edge_connection_t *conn; for (conn=ocirc->p_streams; conn; conn=conn->next_stream) connection_edge_destroy(circ->n_circ_id, conn); ocirc->p_streams = NULL; } circ->marked_for_close = line; circ->marked_for_close_file = file; if (!CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); if (or_circ->rend_splice) { if (!or_circ->rend_splice->_base.marked_for_close) { /* do this after marking this circuit, to avoid infinite recursion. */ circuit_mark_for_close(TO_CIRCUIT(or_circ->rend_splice), reason); } or_circ->rend_splice = NULL; } } }