mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-28 06:13:31 +01:00
Merge remote-tracking branch 'dgoulet/ticket17242_032_03-squashed'
This commit is contained in:
commit
91c6bc160b
@ -1961,8 +1961,8 @@ circuit_about_to_free(circuit_t *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);
|
||||
if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) {
|
||||
if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT &&
|
||||
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(rend_data_get_address(ocirc->rend_data)),
|
||||
@ -1978,7 +1978,8 @@ circuit_about_to_free(circuit_t *circ)
|
||||
reason != END_CIRC_REASON_TIMEOUT) {
|
||||
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
|
||||
if (ocirc->build_state->chosen_exit && ocirc->rend_data) {
|
||||
if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) {
|
||||
if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT &&
|
||||
ocirc->rend_data) {
|
||||
log_info(LD_REND, "Failed intro circ %s to %s "
|
||||
"(building circuit to intro point). "
|
||||
"Marking intro point as possibly unreachable.",
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define TOR_CIRCUITLIST_H
|
||||
|
||||
#include "testsupport.h"
|
||||
#include "hs_ident.h"
|
||||
|
||||
MOCK_DECL(smartlist_t *, circuit_get_global_list, (void));
|
||||
smartlist_t *circuit_get_global_origin_circuit_list(void);
|
||||
|
@ -337,7 +337,8 @@ circuit_get_best(const entry_connection_t *conn,
|
||||
/* Log an info message if we're going to launch a new intro circ in
|
||||
* parallel */
|
||||
if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
|
||||
!must_be_open && origin_circ->hs_circ_has_timed_out) {
|
||||
!must_be_open && origin_circ->hs_circ_has_timed_out &&
|
||||
!circ->marked_for_close) {
|
||||
intro_going_on_but_too_old = 1;
|
||||
continue;
|
||||
}
|
||||
@ -650,6 +651,7 @@ circuit_expire_building(void)
|
||||
* because that's set when they switch purposes
|
||||
*/
|
||||
if (TO_ORIGIN_CIRCUIT(victim)->rend_data ||
|
||||
TO_ORIGIN_CIRCUIT(victim)->hs_ident ||
|
||||
victim->timestamp_dirty > cutoff.tv_sec)
|
||||
continue;
|
||||
break;
|
||||
@ -1636,7 +1638,7 @@ circuit_has_opened(origin_circuit_t *circ)
|
||||
|
||||
switch (TO_CIRCUIT(circ)->purpose) {
|
||||
case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
|
||||
rend_client_rendcirc_has_opened(circ);
|
||||
hs_client_circuit_has_opened(circ);
|
||||
/* Start building an intro circ if we don't have one yet. */
|
||||
connection_ap_attach_pending(1);
|
||||
/* This isn't a call to circuit_try_attaching_streams because a
|
||||
@ -1648,7 +1650,7 @@ circuit_has_opened(origin_circuit_t *circ)
|
||||
* state. */
|
||||
break;
|
||||
case CIRCUIT_PURPOSE_C_INTRODUCING:
|
||||
rend_client_introcirc_has_opened(circ);
|
||||
hs_client_circuit_has_opened(circ);
|
||||
break;
|
||||
case CIRCUIT_PURPOSE_C_GENERAL:
|
||||
/* Tell any AP connections that have been waiting for a new
|
||||
@ -2174,22 +2176,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
|
||||
/* If this is a hidden service trying to start an introduction point,
|
||||
* handle that case. */
|
||||
if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
|
||||
const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
|
||||
/* need to pick an intro point */
|
||||
rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data;
|
||||
tor_assert(rend_data);
|
||||
extend_info = rend_client_get_random_intro(rend_data);
|
||||
extend_info = hs_client_get_random_intro_from_edge(edge_conn);
|
||||
if (!extend_info) {
|
||||
log_info(LD_REND,
|
||||
"No intro points for '%s': re-fetching service descriptor.",
|
||||
safe_str_client(rend_data_get_address(rend_data)));
|
||||
rend_client_refetch_v2_renddesc(rend_data);
|
||||
log_info(LD_REND, "No intro points: re-fetching service descriptor.");
|
||||
if (edge_conn->rend_data) {
|
||||
rend_client_refetch_v2_renddesc(edge_conn->rend_data);
|
||||
} else {
|
||||
hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
|
||||
}
|
||||
connection_ap_mark_as_non_pending_circuit(conn);
|
||||
ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT;
|
||||
return 0;
|
||||
}
|
||||
log_info(LD_REND,"Chose %s as intro point for '%s'.",
|
||||
extend_info_describe(extend_info),
|
||||
safe_str_client(rend_data_get_address(rend_data)));
|
||||
(edge_conn->rend_data) ?
|
||||
safe_str_client(rend_data_get_address(edge_conn->rend_data)) :
|
||||
"service");
|
||||
}
|
||||
|
||||
/* If we have specified a particular exit node for our
|
||||
@ -2308,8 +2313,15 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
|
||||
/* help predict this next time */
|
||||
rep_hist_note_used_internal(time(NULL), need_uptime, 1);
|
||||
if (circ) {
|
||||
const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
|
||||
if (edge_conn->rend_data) {
|
||||
/* write the service_id into circ */
|
||||
circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data);
|
||||
circ->rend_data = rend_data_dup(edge_conn->rend_data);
|
||||
} else if (edge_conn->hs_ident) {
|
||||
circ->hs_ident =
|
||||
hs_ident_circuit_new(&edge_conn->hs_ident->identity_pk,
|
||||
HS_IDENT_CIRCUIT_INTRO);
|
||||
}
|
||||
if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
|
||||
circ->base_.state == CIRCUIT_STATE_OPEN)
|
||||
circuit_has_opened(circ);
|
||||
@ -2737,12 +2749,14 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
|
||||
|
||||
tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
|
||||
if (introcirc->base_.state == CIRCUIT_STATE_OPEN) {
|
||||
int ret;
|
||||
log_info(LD_REND,"found open intro circ %u (rend %u); sending "
|
||||
"introduction. (stream %d sec old)",
|
||||
(unsigned)introcirc->base_.n_circ_id,
|
||||
(unsigned)rendcirc->base_.n_circ_id,
|
||||
conn_age);
|
||||
switch (rend_client_send_introduction(introcirc, rendcirc)) {
|
||||
ret = hs_client_send_introduce1(introcirc, rendcirc);
|
||||
switch (ret) {
|
||||
case 0: /* success */
|
||||
rendcirc->base_.timestamp_dirty = time(NULL);
|
||||
introcirc->base_.timestamp_dirty = time(NULL);
|
||||
|
@ -4102,6 +4102,27 @@ connection_write_to_buf_impl_,(const char *string, size_t len,
|
||||
}
|
||||
}
|
||||
|
||||
#define CONN_GET_ALL_TEMPLATE(var, test) \
|
||||
STMT_BEGIN \
|
||||
smartlist_t *conns = get_connection_array(); \
|
||||
smartlist_t *ret_conns = smartlist_new(); \
|
||||
SMARTLIST_FOREACH_BEGIN(conns, connection_t *, var) { \
|
||||
if (var && (test) && !var->marked_for_close) \
|
||||
smartlist_add(ret_conns, var); \
|
||||
} SMARTLIST_FOREACH_END(var); \
|
||||
return ret_conns; \
|
||||
STMT_END
|
||||
|
||||
/* Return a list of connections that aren't close and matches the given state.
|
||||
* The returned list can be empty and must be freed using smartlist_free().
|
||||
* The caller does NOT have owernship of the objects in the list so it must
|
||||
* not free them nor reference them as they can disapear. */
|
||||
smartlist_t *
|
||||
connection_list_by_type_state(int type, int state)
|
||||
{
|
||||
CONN_GET_ALL_TEMPLATE(conn, (conn->type == type && conn->state == state));
|
||||
}
|
||||
|
||||
/** Return a connection_t * from get_connection_array() that satisfies test on
|
||||
* var, and that is not marked for close. */
|
||||
#define CONN_GET_TEMPLATE(var, test) \
|
||||
|
@ -182,6 +182,7 @@ MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type,
|
||||
connection_t *connection_get_by_type_state(int type, int state);
|
||||
connection_t *connection_get_by_type_state_rendquery(int type, int state,
|
||||
const char *rendquery);
|
||||
smartlist_t *connection_list_by_type_state(int type, int state);
|
||||
smartlist_t *connection_dir_list_by_purpose_and_resource(
|
||||
int purpose,
|
||||
const char *resource);
|
||||
|
@ -76,6 +76,8 @@
|
||||
#include "dirserv.h"
|
||||
#include "hibernate.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_cache.h"
|
||||
#include "hs_client.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "main.h"
|
||||
#include "nodelist.h"
|
||||
@ -153,8 +155,10 @@ connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason,
|
||||
* but we should fix it someday anyway. */
|
||||
if ((edge_conn->on_circuit != NULL || edge_conn->edge_has_sent_end) &&
|
||||
connection_edge_is_rendezvous_stream(edge_conn)) {
|
||||
if (edge_conn->rend_data) {
|
||||
rend_client_note_connection_attempt_ended(edge_conn->rend_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (base_conn->marked_for_close) {
|
||||
/* This call will warn as appropriate. */
|
||||
@ -1392,6 +1396,180 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
|
||||
}
|
||||
}
|
||||
|
||||
/** We just received a SOCKS request in <b>conn</b> to an onion address of type
|
||||
* <b>addresstype</b>. Start connecting to the onion service. */
|
||||
static int
|
||||
connection_ap_handle_onion(entry_connection_t *conn,
|
||||
socks_request_t *socks,
|
||||
origin_circuit_t *circ,
|
||||
hostname_type_t addresstype)
|
||||
{
|
||||
time_t now = approx_time();
|
||||
connection_t *base_conn = ENTRY_TO_CONN(conn);
|
||||
|
||||
/* If .onion address requests are disabled, refuse the request */
|
||||
if (!conn->entry_cfg.onion_traffic) {
|
||||
log_warn(LD_APP, "Onion address %s requested from a port with .onion "
|
||||
"disabled", safe_str_client(socks->address));
|
||||
connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those
|
||||
* for hidden service addresses. */
|
||||
if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
|
||||
/* if it's a resolve request, fail it right now, rather than
|
||||
* building all the circuits and then realizing it won't work. */
|
||||
log_warn(LD_APP,
|
||||
"Resolve requests to hidden services not allowed. Failing.");
|
||||
connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,
|
||||
0,NULL,-1,TIME_MAX);
|
||||
connection_mark_unattached_ap(conn,
|
||||
END_STREAM_REASON_SOCKSPROTOCOL |
|
||||
END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If we were passed a circuit, then we need to fail. .onion addresses
|
||||
* only work when we launch our own circuits for now. */
|
||||
if (circ) {
|
||||
log_warn(LD_CONTROL, "Attachstream to a circuit is not "
|
||||
"supported for .onion addresses currently. Failing.");
|
||||
connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Interface: Regardless of HS version after the block below we should have
|
||||
set onion_address, rend_cache_lookup_result, and descriptor_is_usable. */
|
||||
const char *onion_address = NULL;
|
||||
int rend_cache_lookup_result = -ENOENT;
|
||||
int descriptor_is_usable = 0;
|
||||
|
||||
if (addresstype == ONION_V2_HOSTNAME) { /* it's a v2 hidden service */
|
||||
rend_cache_entry_t *entry = NULL;
|
||||
/* Look up if we have client authorization configured for this hidden
|
||||
* service. If we do, associate it with the rend_data. */
|
||||
rend_service_authorization_t *client_auth =
|
||||
rend_client_lookup_service_authorization(socks->address);
|
||||
|
||||
const uint8_t *cookie = NULL;
|
||||
rend_auth_type_t auth_type = REND_NO_AUTH;
|
||||
if (client_auth) {
|
||||
log_info(LD_REND, "Using previously configured client authorization "
|
||||
"for hidden service request.");
|
||||
auth_type = client_auth->auth_type;
|
||||
cookie = client_auth->descriptor_cookie;
|
||||
}
|
||||
|
||||
/* Fill in the rend_data field so we can start doing a connection to
|
||||
* a hidden service. */
|
||||
rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data =
|
||||
rend_data_client_create(socks->address, NULL, (char *) cookie,
|
||||
auth_type);
|
||||
if (rend_data == NULL) {
|
||||
return -1;
|
||||
}
|
||||
onion_address = rend_data_get_address(rend_data);
|
||||
log_info(LD_REND,"Got a hidden service request for ID '%s'",
|
||||
safe_str_client(onion_address));
|
||||
|
||||
rend_cache_lookup_result = rend_cache_lookup_entry(onion_address,-1,
|
||||
&entry);
|
||||
if (!rend_cache_lookup_result && entry) {
|
||||
descriptor_is_usable = rend_client_any_intro_points_usable(entry);
|
||||
}
|
||||
} else { /* it's a v3 hidden service */
|
||||
tor_assert(addresstype == ONION_V3_HOSTNAME);
|
||||
const hs_descriptor_t *cached_desc = NULL;
|
||||
int retval;
|
||||
/* Create HS conn identifier with HS pubkey */
|
||||
hs_ident_edge_conn_t *hs_conn_ident =
|
||||
tor_malloc_zero(sizeof(hs_ident_edge_conn_t));
|
||||
|
||||
retval = hs_parse_address(socks->address, &hs_conn_ident->identity_pk,
|
||||
NULL, NULL);
|
||||
if (retval < 0) {
|
||||
log_warn(LD_GENERAL, "failed to parse hs address");
|
||||
tor_free(hs_conn_ident);
|
||||
return -1;
|
||||
}
|
||||
ENTRY_TO_EDGE_CONN(conn)->hs_ident = hs_conn_ident;
|
||||
|
||||
onion_address = socks->address;
|
||||
|
||||
/* Check the v3 desc cache */
|
||||
cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk);
|
||||
if (cached_desc) {
|
||||
rend_cache_lookup_result = 0;
|
||||
descriptor_is_usable =
|
||||
hs_client_any_intro_points_usable(&hs_conn_ident->identity_pk,
|
||||
cached_desc);
|
||||
log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.",
|
||||
(descriptor_is_usable) ? "usable" : "unusable",
|
||||
safe_str_client(onion_address),
|
||||
(descriptor_is_usable) ? "Not fetching." : "Refecting.");
|
||||
} else {
|
||||
rend_cache_lookup_result = -ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lookup the given onion address. If invalid, stop right now.
|
||||
* Otherwise, we might have it in the cache or not. */
|
||||
unsigned int refetch_desc = 0;
|
||||
if (rend_cache_lookup_result < 0) {
|
||||
switch (-rend_cache_lookup_result) {
|
||||
case EINVAL:
|
||||
/* We should already have rejected this address! */
|
||||
log_warn(LD_BUG,"Invalid service name '%s'",
|
||||
safe_str_client(onion_address));
|
||||
connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
|
||||
return -1;
|
||||
case ENOENT:
|
||||
/* We didn't have this; we should look it up. */
|
||||
log_info(LD_REND, "No descriptor found in our cache for %s. Fetching.",
|
||||
safe_str_client(onion_address));
|
||||
refetch_desc = 1;
|
||||
break;
|
||||
default:
|
||||
log_warn(LD_BUG, "Unknown cache lookup error %d",
|
||||
rend_cache_lookup_result);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Help predict that we'll want to do hidden service circuits in the
|
||||
* future. We're not sure if it will need a stable circuit yet, but
|
||||
* we know we'll need *something*. */
|
||||
rep_hist_note_used_internal(now, 0, 1);
|
||||
|
||||
/* Now we have a descriptor but is it usable or not? If not, refetch.
|
||||
* Also, a fetch could have been requested if the onion address was not
|
||||
* found in the cache previously. */
|
||||
if (refetch_desc || !descriptor_is_usable) {
|
||||
edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
|
||||
connection_ap_mark_as_non_pending_circuit(conn);
|
||||
base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
|
||||
if (addresstype == ONION_V2_HOSTNAME) {
|
||||
tor_assert(edge_conn->rend_data);
|
||||
rend_client_refetch_v2_renddesc(edge_conn->rend_data);
|
||||
} else {
|
||||
tor_assert(addresstype == ONION_V3_HOSTNAME);
|
||||
tor_assert(edge_conn->hs_ident);
|
||||
hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We have the descriptor! So launch a connection to the HS. */
|
||||
base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
|
||||
log_info(LD_REND, "Descriptor is here. Great.");
|
||||
|
||||
/* We'll try to attach it at the next event loop, or whenever
|
||||
* we call connection_ap_attach_pending() */
|
||||
connection_ap_mark_as_pending_circuit(conn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Connection <b>conn</b> just finished its socks handshake, or the
|
||||
* controller asked us to take care of it. If <b>circ</b> is defined,
|
||||
* then that's where we'll want to attach it. Otherwise we have to
|
||||
@ -1558,7 +1736,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
|
||||
}
|
||||
|
||||
/* Now, we handle everything that isn't a .onion address. */
|
||||
if (addresstype != ONION_HOSTNAME) {
|
||||
if (addresstype != ONION_V2_HOSTNAME && addresstype != ONION_V3_HOSTNAME) {
|
||||
/* Not a hidden-service request. It's either a hostname or an IP,
|
||||
* possibly with a .exit that we stripped off. We're going to check
|
||||
* if we're allowed to connect/resolve there, and then launch the
|
||||
@ -1836,116 +2014,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
|
||||
return 0;
|
||||
} else {
|
||||
/* If we get here, it's a request for a .onion address! */
|
||||
tor_assert(addresstype == ONION_V2_HOSTNAME ||
|
||||
addresstype == ONION_V3_HOSTNAME);
|
||||
tor_assert(!automap);
|
||||
|
||||
/* If .onion address requests are disabled, refuse the request */
|
||||
if (!conn->entry_cfg.onion_traffic) {
|
||||
log_warn(LD_APP, "Onion address %s requested from a port with .onion "
|
||||
"disabled", safe_str_client(socks->address));
|
||||
connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those
|
||||
* for hidden service addresses. */
|
||||
if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
|
||||
/* if it's a resolve request, fail it right now, rather than
|
||||
* building all the circuits and then realizing it won't work. */
|
||||
log_warn(LD_APP,
|
||||
"Resolve requests to hidden services not allowed. Failing.");
|
||||
connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,
|
||||
0,NULL,-1,TIME_MAX);
|
||||
connection_mark_unattached_ap(conn,
|
||||
END_STREAM_REASON_SOCKSPROTOCOL |
|
||||
END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If we were passed a circuit, then we need to fail. .onion addresses
|
||||
* only work when we launch our own circuits for now. */
|
||||
if (circ) {
|
||||
log_warn(LD_CONTROL, "Attachstream to a circuit is not "
|
||||
"supported for .onion addresses currently. Failing.");
|
||||
connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Look up if we have client authorization configured for this hidden
|
||||
* service. If we do, associate it with the rend_data. */
|
||||
rend_service_authorization_t *client_auth =
|
||||
rend_client_lookup_service_authorization(socks->address);
|
||||
|
||||
const uint8_t *cookie = NULL;
|
||||
rend_auth_type_t auth_type = REND_NO_AUTH;
|
||||
if (client_auth) {
|
||||
log_info(LD_REND, "Using previously configured client authorization "
|
||||
"for hidden service request.");
|
||||
auth_type = client_auth->auth_type;
|
||||
cookie = client_auth->descriptor_cookie;
|
||||
}
|
||||
|
||||
/* Fill in the rend_data field so we can start doing a connection to
|
||||
* a hidden service. */
|
||||
rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data =
|
||||
rend_data_client_create(socks->address, NULL, (char *) cookie,
|
||||
auth_type);
|
||||
if (rend_data == NULL) {
|
||||
return -1;
|
||||
}
|
||||
const char *onion_address = rend_data_get_address(rend_data);
|
||||
log_info(LD_REND,"Got a hidden service request for ID '%s'",
|
||||
safe_str_client(onion_address));
|
||||
|
||||
/* Lookup the given onion address. If invalid, stop right now.
|
||||
* Otherwise, we might have it in the cache or not. */
|
||||
unsigned int refetch_desc = 0;
|
||||
rend_cache_entry_t *entry = NULL;
|
||||
const int rend_cache_lookup_result =
|
||||
rend_cache_lookup_entry(onion_address, -1, &entry);
|
||||
if (rend_cache_lookup_result < 0) {
|
||||
switch (-rend_cache_lookup_result) {
|
||||
case EINVAL:
|
||||
/* We should already have rejected this address! */
|
||||
log_warn(LD_BUG,"Invalid service name '%s'",
|
||||
safe_str_client(onion_address));
|
||||
connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
|
||||
return -1;
|
||||
case ENOENT:
|
||||
/* We didn't have this; we should look it up. */
|
||||
refetch_desc = 1;
|
||||
break;
|
||||
default:
|
||||
log_warn(LD_BUG, "Unknown cache lookup error %d",
|
||||
rend_cache_lookup_result);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Help predict that we'll want to do hidden service circuits in the
|
||||
* future. We're not sure if it will need a stable circuit yet, but
|
||||
* we know we'll need *something*. */
|
||||
rep_hist_note_used_internal(now, 0, 1);
|
||||
|
||||
/* Now we have a descriptor but is it usable or not? If not, refetch.
|
||||
* Also, a fetch could have been requested if the onion address was not
|
||||
* found in the cache previously. */
|
||||
if (refetch_desc || !rend_client_any_intro_points_usable(entry)) {
|
||||
connection_ap_mark_as_non_pending_circuit(conn);
|
||||
base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
|
||||
log_info(LD_REND, "Unknown descriptor %s. Fetching.",
|
||||
safe_str_client(onion_address));
|
||||
rend_client_refetch_v2_renddesc(rend_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We have the descriptor! So launch a connection to the HS. */
|
||||
base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
|
||||
log_info(LD_REND, "Descriptor is here. Great.");
|
||||
|
||||
/* We'll try to attach it at the next event loop, or whenever
|
||||
* we call connection_ap_attach_pending() */
|
||||
connection_ap_mark_as_pending_circuit(conn);
|
||||
return 0;
|
||||
return connection_ap_handle_onion(conn, socks, circ, addresstype);
|
||||
}
|
||||
|
||||
return 0; /* unreached but keeps the compiler happy */
|
||||
@ -3679,10 +3751,12 @@ connection_ap_can_use_exit(const entry_connection_t *conn,
|
||||
}
|
||||
|
||||
/** If address is of the form "y.onion" with a well-formed handle y:
|
||||
* Put a NUL after y, lower-case it, and return ONION_HOSTNAME.
|
||||
* Put a NUL after y, lower-case it, and return ONION_V2_HOSTNAME or
|
||||
* ONION_V3_HOSTNAME depending on the HS version.
|
||||
*
|
||||
* If address is of the form "x.y.onion" with a well-formed handle x:
|
||||
* Drop "x.", put a NUL after y, lower-case it, and return ONION_HOSTNAME.
|
||||
* Drop "x.", put a NUL after y, lower-case it, and return
|
||||
* ONION_V2_HOSTNAME or ONION_V3_HOSTNAME depending on the HS version.
|
||||
*
|
||||
* If address is of the form "y.onion" with a badly-formed handle y:
|
||||
* Return BAD_HOSTNAME and log a message.
|
||||
@ -3698,7 +3772,7 @@ parse_extended_hostname(char *address)
|
||||
{
|
||||
char *s;
|
||||
char *q;
|
||||
char query[REND_SERVICE_ID_LEN_BASE32+1];
|
||||
char query[HS_SERVICE_ADDR_LEN_BASE32+1];
|
||||
|
||||
s = strrchr(address,'.');
|
||||
if (!s)
|
||||
@ -3718,14 +3792,17 @@ parse_extended_hostname(char *address)
|
||||
goto failed; /* reject sub-domain, as DNS does */
|
||||
}
|
||||
q = (NULL == q) ? address : q + 1;
|
||||
if (strlcpy(query, q, REND_SERVICE_ID_LEN_BASE32+1) >=
|
||||
REND_SERVICE_ID_LEN_BASE32+1)
|
||||
if (strlcpy(query, q, HS_SERVICE_ADDR_LEN_BASE32+1) >=
|
||||
HS_SERVICE_ADDR_LEN_BASE32+1)
|
||||
goto failed;
|
||||
if (q != address) {
|
||||
memmove(address, q, strlen(q) + 1 /* also get \0 */);
|
||||
}
|
||||
if (rend_valid_service_id(query)) {
|
||||
return ONION_HOSTNAME; /* success */
|
||||
if (rend_valid_v2_service_id(query)) {
|
||||
return ONION_V2_HOSTNAME; /* success */
|
||||
}
|
||||
if (hs_address_is_valid(query)) {
|
||||
return ONION_V3_HOSTNAME;
|
||||
}
|
||||
failed:
|
||||
/* otherwise, return to previous state and return 0 */
|
||||
|
@ -98,7 +98,8 @@ int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
|
||||
|
||||
/** Possible return values for parse_extended_hostname. */
|
||||
typedef enum hostname_type_t {
|
||||
NORMAL_HOSTNAME, ONION_HOSTNAME, EXIT_HOSTNAME, BAD_HOSTNAME
|
||||
NORMAL_HOSTNAME, ONION_V2_HOSTNAME, ONION_V3_HOSTNAME,
|
||||
EXIT_HOSTNAME, BAD_HOSTNAME
|
||||
} hostname_type_t;
|
||||
hostname_type_t parse_extended_hostname(char *address);
|
||||
|
||||
|
@ -4140,7 +4140,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
|
||||
/* Extract the first argument (either HSAddress or DescID). */
|
||||
arg1 = smartlist_get(args, 0);
|
||||
/* Test if it's an HS address without the .onion part. */
|
||||
if (rend_valid_service_id(arg1)) {
|
||||
if (rend_valid_v2_service_id(arg1)) {
|
||||
hsaddress = arg1;
|
||||
} else if (strcmpstart(arg1, v2_str) == 0 &&
|
||||
rend_valid_descriptor_id(arg1 + v2_str_len) &&
|
||||
@ -4779,7 +4779,7 @@ handle_control_del_onion(control_connection_t *conn,
|
||||
return 0;
|
||||
|
||||
const char *service_id = smartlist_get(args, 0);
|
||||
if (!rend_valid_service_id(service_id)) {
|
||||
if (!rend_valid_v2_service_id(service_id)) {
|
||||
connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n");
|
||||
goto out;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "geoip.h"
|
||||
#include "hs_cache.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_client.h"
|
||||
#include "main.h"
|
||||
#include "microdesc.h"
|
||||
#include "networkstatus.h"
|
||||
@ -183,6 +184,7 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
|
||||
case DIR_PURPOSE_FETCH_EXTRAINFO:
|
||||
case DIR_PURPOSE_FETCH_MICRODESC:
|
||||
return 0;
|
||||
case DIR_PURPOSE_HAS_FETCHED_HSDESC:
|
||||
case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2:
|
||||
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
|
||||
case DIR_PURPOSE_FETCH_RENDDESC_V2:
|
||||
@ -1125,6 +1127,7 @@ directory_request_new(uint8_t dir_purpose)
|
||||
tor_assert(dir_purpose <= DIR_PURPOSE_MAX_);
|
||||
tor_assert(dir_purpose != DIR_PURPOSE_SERVER);
|
||||
tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2);
|
||||
tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_HSDESC);
|
||||
|
||||
directory_request_t *result = tor_malloc_zero(sizeof(*result));
|
||||
tor_addr_make_null(&result->or_addr_port.addr, AF_INET);
|
||||
@ -1289,6 +1292,20 @@ directory_request_upload_set_hs_ident(directory_request_t *req,
|
||||
}
|
||||
req->hs_ident = ident;
|
||||
}
|
||||
/**
|
||||
* Set an object containing HS connection identifier to be associated with
|
||||
* this fetch request. Note that only an alias to <b>ident</b> is stored, so
|
||||
* the <b>ident</b> object must outlive the request.
|
||||
*/
|
||||
void
|
||||
directory_request_fetch_set_hs_ident(directory_request_t *req,
|
||||
const hs_ident_dir_conn_t *ident)
|
||||
{
|
||||
if (ident) {
|
||||
tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC);
|
||||
}
|
||||
req->hs_ident = ident;
|
||||
}
|
||||
/** Set a static circuit_guard_state_t object to affliate with the request in
|
||||
* <b>req</b>. This object will receive notification when the attempt to
|
||||
* connect to the guard either succeeds or fails. */
|
||||
@ -1859,6 +1876,13 @@ directory_send_command(dir_connection_t *conn,
|
||||
httpcommand = "GET";
|
||||
tor_asprintf(&url, "/tor/rendezvous2/%s", resource);
|
||||
break;
|
||||
case DIR_PURPOSE_FETCH_HSDESC:
|
||||
tor_assert(resource);
|
||||
tor_assert(strlen(resource) <= ED25519_BASE64_LEN);
|
||||
tor_assert(!payload);
|
||||
httpcommand = "GET";
|
||||
tor_asprintf(&url, "/tor/hs/3/%s", resource);
|
||||
break;
|
||||
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
|
||||
tor_assert(!resource);
|
||||
tor_assert(payload);
|
||||
@ -2193,16 +2217,6 @@ load_downloaded_routers(const char *body, smartlist_t *which,
|
||||
return added;
|
||||
}
|
||||
|
||||
/** A structure to hold arguments passed into each directory response
|
||||
* handler */
|
||||
typedef struct response_handler_args_t {
|
||||
int status_code;
|
||||
const char *reason;
|
||||
const char *body;
|
||||
size_t body_len;
|
||||
const char *headers;
|
||||
} response_handler_args_t;
|
||||
|
||||
static int handle_response_fetch_consensus(dir_connection_t *,
|
||||
const response_handler_args_t *);
|
||||
static int handle_response_fetch_certificate(dir_connection_t *,
|
||||
@ -2530,6 +2544,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
|
||||
case DIR_PURPOSE_UPLOAD_HSDESC:
|
||||
rv = handle_response_upload_hsdesc(conn, &args);
|
||||
break;
|
||||
case DIR_PURPOSE_FETCH_HSDESC:
|
||||
rv = handle_response_fetch_hsdesc_v3(conn, &args);
|
||||
break;
|
||||
default:
|
||||
tor_assert_nonfatal_unreached();
|
||||
rv = -1;
|
||||
@ -3074,6 +3091,60 @@ handle_response_upload_signatures(dir_connection_t *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function: processes a response to a request for a v3 hidden service
|
||||
* descriptor.
|
||||
**/
|
||||
STATIC int
|
||||
handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
|
||||
const response_handler_args_t *args)
|
||||
{
|
||||
const int status_code = args->status_code;
|
||||
const char *reason = args->reason;
|
||||
const char *body = args->body;
|
||||
const size_t body_len = args->body_len;
|
||||
|
||||
tor_assert(conn->hs_ident);
|
||||
|
||||
log_info(LD_REND,"Received v3 hsdesc (body size %d, status %d (%s))",
|
||||
(int)body_len, status_code, escaped(reason));
|
||||
|
||||
switch (status_code) {
|
||||
case 200:
|
||||
/* We got something: Try storing it in the cache. */
|
||||
if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) {
|
||||
log_warn(LD_REND, "Failed to store hidden service descriptor");
|
||||
} else {
|
||||
log_info(LD_REND, "Stored hidden service descriptor successfully.");
|
||||
TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC;
|
||||
hs_client_desc_has_arrived(conn->hs_ident);
|
||||
}
|
||||
break;
|
||||
case 404:
|
||||
/* Not there. We'll retry when connection_about_to_close_connection()
|
||||
* tries to clean this conn up. */
|
||||
log_info(LD_REND, "Fetching hidden service v3 descriptor not found: "
|
||||
"Retrying at another directory.");
|
||||
/* TODO: Inform the control port */
|
||||
break;
|
||||
case 400:
|
||||
log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: "
|
||||
"http status 400 (%s). Dirserver didn't like our "
|
||||
"query? Retrying at another directory.",
|
||||
escaped(reason));
|
||||
break;
|
||||
default:
|
||||
log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: "
|
||||
"http status %d (%s) response unexpected from HSDir server "
|
||||
"'%s:%d'. Retrying at another directory.",
|
||||
status_code, escaped(reason), TO_CONN(conn)->address,
|
||||
TO_CONN(conn)->port);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function: processes a response to a request for a v2 hidden service
|
||||
* descriptor.
|
||||
@ -3338,6 +3409,33 @@ connection_dir_process_inbuf(dir_connection_t *conn)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** We are closing a dir connection: If <b>dir_conn</b> is a dir connection
|
||||
* that tried to fetch an HS descriptor, check if it successfuly fetched it,
|
||||
* or if we need to try again. */
|
||||
static void
|
||||
refetch_hsdesc_if_needed(dir_connection_t *dir_conn)
|
||||
{
|
||||
connection_t *conn = TO_CONN(dir_conn);
|
||||
|
||||
/* If we were trying to fetch a v2 rend desc and did not succeed, retry as
|
||||
* needed. (If a fetch is successful, the connection state is changed to
|
||||
* DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 or DIR_PURPOSE_HAS_FETCHED_HSDESC to
|
||||
* mark that refetching is unnecessary.) */
|
||||
if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
|
||||
dir_conn->rend_data &&
|
||||
rend_valid_v2_service_id(
|
||||
rend_data_get_address(dir_conn->rend_data))) {
|
||||
rend_client_refetch_v2_renddesc(dir_conn->rend_data);
|
||||
}
|
||||
|
||||
/* Check for v3 rend desc fetch */
|
||||
if (conn->purpose == DIR_PURPOSE_FETCH_HSDESC &&
|
||||
dir_conn->hs_ident &&
|
||||
!ed25519_public_key_is_zero(&dir_conn->hs_ident->identity_pk)) {
|
||||
hs_client_refetch_hsdesc(&dir_conn->hs_ident->identity_pk);
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when we're about to finally unlink and free a directory connection:
|
||||
* perform necessary accounting and cleanup */
|
||||
void
|
||||
@ -3350,15 +3448,8 @@ connection_dir_about_to_close(dir_connection_t *dir_conn)
|
||||
* failed: forget about this router, and maybe try again. */
|
||||
connection_dir_request_failed(dir_conn);
|
||||
}
|
||||
/* If we were trying to fetch a v2 rend desc and did not succeed,
|
||||
* retry as needed. (If a fetch is successful, the connection state
|
||||
* is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that
|
||||
* refetching is unnecessary.) */
|
||||
if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
|
||||
dir_conn->rend_data &&
|
||||
strlen(rend_data_get_address(dir_conn->rend_data)) ==
|
||||
REND_SERVICE_ID_LEN_BASE32)
|
||||
rend_client_refetch_v2_renddesc(dir_conn->rend_data);
|
||||
|
||||
refetch_hsdesc_if_needed(dir_conn);
|
||||
}
|
||||
|
||||
/** Create an http response for the client <b>conn</b> out of
|
||||
|
@ -75,6 +75,8 @@ void directory_request_set_rend_query(directory_request_t *req,
|
||||
const rend_data_t *query);
|
||||
void directory_request_upload_set_hs_ident(directory_request_t *req,
|
||||
const hs_ident_dir_conn_t *ident);
|
||||
void directory_request_fetch_set_hs_ident(directory_request_t *req,
|
||||
const hs_ident_dir_conn_t *ident);
|
||||
|
||||
void directory_request_set_routerstatus(directory_request_t *req,
|
||||
const routerstatus_t *rs);
|
||||
@ -168,6 +170,16 @@ int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
|
||||
|
||||
#ifdef DIRECTORY_PRIVATE
|
||||
|
||||
/** A structure to hold arguments passed into each directory response
|
||||
* handler */
|
||||
typedef struct response_handler_args_t {
|
||||
int status_code;
|
||||
const char *reason;
|
||||
const char *body;
|
||||
size_t body_len;
|
||||
const char *headers;
|
||||
} response_handler_args_t;
|
||||
|
||||
struct get_handler_args_t;
|
||||
STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn,
|
||||
const struct get_handler_args_t *args);
|
||||
@ -176,10 +188,14 @@ STATIC char *accept_encoding_header(void);
|
||||
STATIC int allowed_anonymous_connection_compression_method(compress_method_t);
|
||||
STATIC void warn_disallowed_anonymous_compression_method(compress_method_t);
|
||||
|
||||
typedef struct response_handler_args_t response_handler_args_t;
|
||||
STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
|
||||
const response_handler_args_t *args);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
/* Used only by test_dir.c */
|
||||
/* Used only by test_dir.c and test_hs_cache.c */
|
||||
|
||||
STATIC int parse_http_url(const char *headers, char **url);
|
||||
STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose,
|
||||
|
@ -9,15 +9,19 @@
|
||||
/* For unit tests.*/
|
||||
#define HS_CACHE_PRIVATE
|
||||
|
||||
#include "hs_cache.h"
|
||||
|
||||
#include "or.h"
|
||||
#include "config.h"
|
||||
#include "hs_ident.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_client.h"
|
||||
#include "hs_descriptor.h"
|
||||
#include "networkstatus.h"
|
||||
#include "rendcache.h"
|
||||
|
||||
#include "hs_cache.h"
|
||||
|
||||
/********************** Directory HS cache ******************/
|
||||
|
||||
/* Directory descriptor cache. Map indexed by blinded key. */
|
||||
static digest256map_t *hs_cache_v3_dir;
|
||||
|
||||
@ -98,7 +102,7 @@ cache_dir_desc_new(const char *desc)
|
||||
|
||||
/* Return the size of a cache entry in bytes. */
|
||||
static size_t
|
||||
cache_get_entry_size(const hs_cache_dir_descriptor_t *entry)
|
||||
cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry)
|
||||
{
|
||||
return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data)
|
||||
+ strlen(entry->encoded_desc));
|
||||
@ -134,7 +138,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
|
||||
* remove the entry we currently have from our cache so we can then
|
||||
* store the new one. */
|
||||
remove_v3_desc_as_dir(cache_entry);
|
||||
rend_cache_decrement_allocation(cache_get_entry_size(cache_entry));
|
||||
rend_cache_decrement_allocation(cache_get_dir_entry_size(cache_entry));
|
||||
cache_dir_desc_free(cache_entry);
|
||||
}
|
||||
/* Store the descriptor we just got. We are sure here that either we
|
||||
@ -144,7 +148,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
|
||||
|
||||
/* Update our total cache size with this entry for the OOM. This uses the
|
||||
* old HS protocol cache subsystem for which we are tied with. */
|
||||
rend_cache_increment_allocation(cache_get_entry_size(desc));
|
||||
rend_cache_increment_allocation(cache_get_dir_entry_size(desc));
|
||||
|
||||
/* XXX: Update HS statistics. We should have specific stats for v3. */
|
||||
|
||||
@ -221,7 +225,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff)
|
||||
}
|
||||
/* Here, our entry has expired, remove and free. */
|
||||
MAP_DEL_CURRENT(key);
|
||||
entry_size = cache_get_entry_size(entry);
|
||||
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);
|
||||
@ -315,6 +319,468 @@ hs_cache_clean_as_dir(time_t now)
|
||||
cache_clean_v3_as_dir(now, 0);
|
||||
}
|
||||
|
||||
/********************** Client-side HS cache ******************/
|
||||
|
||||
/* Client-side HS descriptor cache. Map indexed by service identity key. */
|
||||
static digest256map_t *hs_cache_v3_client;
|
||||
|
||||
/* Client-side introduction point state cache. Map indexed by service public
|
||||
* identity key (onion address). It contains hs_cache_client_intro_state_t
|
||||
* objects all related to a specific service. */
|
||||
static digest256map_t *hs_cache_client_intro_state;
|
||||
|
||||
/* Return the size of a client cache entry in bytes. */
|
||||
static size_t
|
||||
cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry)
|
||||
{
|
||||
return sizeof(*entry) +
|
||||
strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc);
|
||||
}
|
||||
|
||||
/* Remove a given descriptor from our cache. */
|
||||
static void
|
||||
remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc)
|
||||
{
|
||||
tor_assert(desc);
|
||||
digest256map_remove(hs_cache_v3_client, desc->key.pubkey);
|
||||
/* Update cache size with this entry for the OOM handler. */
|
||||
rend_cache_decrement_allocation(cache_get_client_entry_size(desc));
|
||||
}
|
||||
|
||||
/* Store a given descriptor in our cache. */
|
||||
static void
|
||||
store_v3_desc_as_client(hs_cache_client_descriptor_t *desc)
|
||||
{
|
||||
tor_assert(desc);
|
||||
digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc);
|
||||
/* Update cache size with this entry for the OOM handler. */
|
||||
rend_cache_increment_allocation(cache_get_client_entry_size(desc));
|
||||
}
|
||||
|
||||
/* Query our cache and return the entry or NULL if not found. */
|
||||
STATIC hs_cache_client_descriptor_t *
|
||||
lookup_v3_desc_as_client(const uint8_t *key)
|
||||
{
|
||||
tor_assert(key);
|
||||
return digest256map_get(hs_cache_v3_client, key);
|
||||
}
|
||||
|
||||
/* Parse the encoded descriptor in <b>desc_str</b> using
|
||||
* <b>service_identity_pk<b> to decrypt it first.
|
||||
*
|
||||
* If everything goes well, allocate and return a new
|
||||
* hs_cache_client_descriptor_t object. In case of error, return NULL. */
|
||||
static hs_cache_client_descriptor_t *
|
||||
cache_client_desc_new(const char *desc_str,
|
||||
const ed25519_public_key_t *service_identity_pk)
|
||||
{
|
||||
hs_descriptor_t *desc = NULL;
|
||||
hs_cache_client_descriptor_t *client_desc = NULL;
|
||||
|
||||
tor_assert(desc_str);
|
||||
tor_assert(service_identity_pk);
|
||||
|
||||
/* Decode the descriptor we just fetched. */
|
||||
if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) {
|
||||
goto end;
|
||||
}
|
||||
tor_assert(desc);
|
||||
|
||||
/* All is good: make a cache object for this descriptor */
|
||||
client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t));
|
||||
ed25519_pubkey_copy(&client_desc->key, service_identity_pk);
|
||||
client_desc->created_ts = approx_time();
|
||||
client_desc->desc = desc;
|
||||
client_desc->encoded_desc = tor_strdup(desc_str);
|
||||
|
||||
end:
|
||||
return client_desc;
|
||||
}
|
||||
|
||||
/** Free memory allocated by <b>desc</b>. */
|
||||
static void
|
||||
cache_client_desc_free(hs_cache_client_descriptor_t *desc)
|
||||
{
|
||||
if (desc == NULL) {
|
||||
return;
|
||||
}
|
||||
hs_descriptor_free(desc->desc);
|
||||
memwipe(&desc->key, 0, sizeof(desc->key));
|
||||
memwipe(desc->encoded_desc, 0, strlen(desc->encoded_desc));
|
||||
tor_free(desc->encoded_desc);
|
||||
tor_free(desc);
|
||||
}
|
||||
|
||||
/** Helper function: Use by the free all function to clear the client cache */
|
||||
static void
|
||||
cache_client_desc_free_(void *ptr)
|
||||
{
|
||||
hs_cache_client_descriptor_t *desc = ptr;
|
||||
cache_client_desc_free(desc);
|
||||
}
|
||||
|
||||
/* Return a newly allocated and initialized hs_cache_intro_state_t object. */
|
||||
static hs_cache_intro_state_t *
|
||||
cache_intro_state_new(void)
|
||||
{
|
||||
hs_cache_intro_state_t *state = tor_malloc_zero(sizeof(*state));
|
||||
state->created_ts = approx_time();
|
||||
return state;
|
||||
}
|
||||
|
||||
/* Free an hs_cache_intro_state_t object. */
|
||||
static void
|
||||
cache_intro_state_free(hs_cache_intro_state_t *state)
|
||||
{
|
||||
tor_free(state);
|
||||
}
|
||||
|
||||
/* Helper function: use by the free all function. */
|
||||
static void
|
||||
cache_intro_state_free_(void *state)
|
||||
{
|
||||
cache_intro_state_free(state);
|
||||
}
|
||||
|
||||
/* Return a newly allocated and initialized hs_cache_client_intro_state_t
|
||||
* object. */
|
||||
static hs_cache_client_intro_state_t *
|
||||
cache_client_intro_state_new(void)
|
||||
{
|
||||
hs_cache_client_intro_state_t *cache = tor_malloc_zero(sizeof(*cache));
|
||||
cache->intro_points = digest256map_new();
|
||||
return cache;
|
||||
}
|
||||
|
||||
/* Free a cache client intro state object. */
|
||||
static void
|
||||
cache_client_intro_state_free(hs_cache_client_intro_state_t *cache)
|
||||
{
|
||||
if (cache == NULL) {
|
||||
return;
|
||||
}
|
||||
digest256map_free(cache->intro_points, cache_intro_state_free_);
|
||||
tor_free(cache);
|
||||
}
|
||||
|
||||
/* Helper function: use by the free all function. */
|
||||
static void
|
||||
cache_client_intro_state_free_(void *entry)
|
||||
{
|
||||
cache_client_intro_state_free(entry);
|
||||
}
|
||||
|
||||
/* For the given service identity key service_pk and an introduction
|
||||
* authentication key auth_key, lookup the intro state object. Return 1 if
|
||||
* found and put it in entry if not NULL. Return 0 if not found and entry is
|
||||
* untouched. */
|
||||
static int
|
||||
cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk,
|
||||
const ed25519_public_key_t *auth_key,
|
||||
hs_cache_intro_state_t **entry)
|
||||
{
|
||||
hs_cache_intro_state_t *state;
|
||||
hs_cache_client_intro_state_t *cache;
|
||||
|
||||
tor_assert(service_pk);
|
||||
tor_assert(auth_key);
|
||||
|
||||
/* Lookup the intro state cache for this service key. */
|
||||
cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
|
||||
if (cache == NULL) {
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
/* From the cache we just found for the service, lookup in the introduction
|
||||
* points map for the given authentication key. */
|
||||
state = digest256map_get(cache->intro_points, auth_key->pubkey);
|
||||
if (state == NULL) {
|
||||
goto not_found;
|
||||
}
|
||||
if (entry) {
|
||||
*entry = state;
|
||||
}
|
||||
return 1;
|
||||
not_found:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Note the given failure in state. */
|
||||
static void
|
||||
cache_client_intro_state_note(hs_cache_intro_state_t *state,
|
||||
rend_intro_point_failure_t failure)
|
||||
{
|
||||
tor_assert(state);
|
||||
switch (failure) {
|
||||
case INTRO_POINT_FAILURE_GENERIC:
|
||||
state->error = 1;
|
||||
break;
|
||||
case INTRO_POINT_FAILURE_TIMEOUT:
|
||||
state->timed_out = 1;
|
||||
break;
|
||||
case INTRO_POINT_FAILURE_UNREACHABLE:
|
||||
state->unreachable_count++;
|
||||
break;
|
||||
default:
|
||||
tor_assert_nonfatal_unreached();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* For the given service identity key service_pk and an introduction
|
||||
* authentication key auth_key, add an entry in the client intro state cache
|
||||
* If no entry exists for the service, it will create one. If state is non
|
||||
* NULL, it will point to the new intro state entry. */
|
||||
static void
|
||||
cache_client_intro_state_add(const ed25519_public_key_t *service_pk,
|
||||
const ed25519_public_key_t *auth_key,
|
||||
hs_cache_intro_state_t **state)
|
||||
{
|
||||
hs_cache_intro_state_t *entry, *old_entry;
|
||||
hs_cache_client_intro_state_t *cache;
|
||||
|
||||
tor_assert(service_pk);
|
||||
tor_assert(auth_key);
|
||||
|
||||
/* Lookup the state cache for this service key. */
|
||||
cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
|
||||
if (cache == NULL) {
|
||||
cache = cache_client_intro_state_new();
|
||||
digest256map_set(hs_cache_client_intro_state, service_pk->pubkey, cache);
|
||||
}
|
||||
|
||||
entry = cache_intro_state_new();
|
||||
old_entry = digest256map_set(cache->intro_points, auth_key->pubkey, entry);
|
||||
/* This should never happened because the code flow is to lookup the entry
|
||||
* before adding it. But, just in case, non fatal assert and free it. */
|
||||
tor_assert_nonfatal(old_entry == NULL);
|
||||
tor_free(old_entry);
|
||||
|
||||
if (state) {
|
||||
*state = entry;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove every intro point state entry from cache that has been created
|
||||
* before or at the cutoff. */
|
||||
static void
|
||||
cache_client_intro_state_clean(time_t cutoff,
|
||||
hs_cache_client_intro_state_t *cache)
|
||||
{
|
||||
tor_assert(cache);
|
||||
|
||||
DIGEST256MAP_FOREACH_MODIFY(cache->intro_points, key,
|
||||
hs_cache_intro_state_t *, entry) {
|
||||
if (entry->created_ts <= cutoff) {
|
||||
cache_intro_state_free(entry);
|
||||
MAP_DEL_CURRENT(key);
|
||||
}
|
||||
} DIGEST256MAP_FOREACH_END;
|
||||
}
|
||||
|
||||
/* Return true iff no intro points are in this cache. */
|
||||
static int
|
||||
cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache)
|
||||
{
|
||||
return digest256map_isempty(cache->intro_points);
|
||||
}
|
||||
|
||||
/** Check whether <b>client_desc</b> is useful for us, and store it in the
|
||||
* client-side HS cache if so. The client_desc is freed if we already have a
|
||||
* fresher (higher revision counter count) in the cache. */
|
||||
static int
|
||||
cache_store_as_client(hs_cache_client_descriptor_t *client_desc)
|
||||
{
|
||||
hs_cache_client_descriptor_t *cache_entry;
|
||||
|
||||
/* TODO: Heavy code duplication with cache_store_as_dir(). Consider
|
||||
* refactoring and uniting! */
|
||||
|
||||
tor_assert(client_desc);
|
||||
|
||||
/* Check if we already have a descriptor from this HS in cache. If we do,
|
||||
* check if this descriptor is newer than the cached one */
|
||||
cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey);
|
||||
if (cache_entry != NULL) {
|
||||
/* If we have an entry in our cache that has a revision counter greater
|
||||
* than the one we just fetched, discard the one we fetched. */
|
||||
if (cache_entry->desc->plaintext_data.revision_counter >
|
||||
client_desc->desc->plaintext_data.revision_counter) {
|
||||
log_info(LD_REND, "We already have fresher descriptor. Ignoring.");
|
||||
cache_client_desc_free(client_desc);
|
||||
goto done;
|
||||
}
|
||||
/* Remove old entry. Make space for the new one! */
|
||||
remove_v3_desc_as_client(cache_entry);
|
||||
cache_client_desc_free(cache_entry);
|
||||
}
|
||||
|
||||
/* Store descriptor in cache */
|
||||
store_v3_desc_as_client(client_desc);
|
||||
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Clean the client cache using now as the current time. Return the total size
|
||||
* of removed bytes from the cache. */
|
||||
static size_t
|
||||
cache_clean_v3_as_client(time_t now)
|
||||
{
|
||||
size_t bytes_removed = 0;
|
||||
|
||||
if (!hs_cache_v3_client) { /* No cache to clean. Just return. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key,
|
||||
hs_cache_client_descriptor_t *, entry) {
|
||||
size_t entry_size;
|
||||
time_t cutoff = now - rend_cache_max_entry_lifetime();
|
||||
|
||||
/* 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_client_entry_size(entry);
|
||||
bytes_removed += entry_size;
|
||||
/* Entry is not in the cache anymore, destroy it. */
|
||||
cache_client_desc_free(entry);
|
||||
/* Update our OOM. We didn't use the remove() function because we are in
|
||||
* a loop so we have to explicitely decrement. */
|
||||
rend_cache_decrement_allocation(entry_size);
|
||||
/* Logging. */
|
||||
{
|
||||
char key_b64[BASE64_DIGEST256_LEN + 1];
|
||||
base64_encode(key_b64, sizeof(key_b64), (const char *) key,
|
||||
DIGEST256_LEN, 0);
|
||||
log_info(LD_REND, "Removing hidden service v3 descriptor '%s' "
|
||||
"from client cache",
|
||||
safe_str_client(key_b64));
|
||||
}
|
||||
} DIGEST256MAP_FOREACH_END;
|
||||
|
||||
return bytes_removed;
|
||||
}
|
||||
|
||||
/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return
|
||||
* its HS descriptor if it's stored in our cache, or NULL if not. */
|
||||
const hs_descriptor_t *
|
||||
hs_cache_lookup_as_client(const ed25519_public_key_t *key)
|
||||
{
|
||||
hs_cache_client_descriptor_t *cached_desc = NULL;
|
||||
|
||||
tor_assert(key);
|
||||
|
||||
cached_desc = lookup_v3_desc_as_client(key->pubkey);
|
||||
if (cached_desc) {
|
||||
tor_assert(cached_desc->desc);
|
||||
return cached_desc->desc;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Public API: Given an encoded descriptor, store it in the client HS
|
||||
* cache. Return -1 on error, 0 on success .*/
|
||||
int
|
||||
hs_cache_store_as_client(const char *desc_str,
|
||||
const ed25519_public_key_t *identity_pk)
|
||||
{
|
||||
hs_cache_client_descriptor_t *client_desc = NULL;
|
||||
|
||||
tor_assert(desc_str);
|
||||
tor_assert(identity_pk);
|
||||
|
||||
/* Create client cache descriptor object */
|
||||
client_desc = cache_client_desc_new(desc_str, identity_pk);
|
||||
if (!client_desc) {
|
||||
log_warn(LD_GENERAL, "Failed to parse received descriptor %s.",
|
||||
escaped(desc_str));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Push it to the cache */
|
||||
if (cache_store_as_client(client_desc) < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
cache_client_desc_free(client_desc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Clean all client caches using the current time now. */
|
||||
void
|
||||
hs_cache_clean_as_client(time_t now)
|
||||
{
|
||||
/* Start with v2 cache cleaning. */
|
||||
rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
|
||||
/* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function
|
||||
* to compute the cutoff by itself using the lifetime value. */
|
||||
cache_clean_v3_as_client(now);
|
||||
}
|
||||
|
||||
/* For a given service identity public key and an introduction authentication
|
||||
* key, note the given failure in the client intro state cache. */
|
||||
void
|
||||
hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk,
|
||||
const ed25519_public_key_t *auth_key,
|
||||
rend_intro_point_failure_t failure)
|
||||
{
|
||||
int found;
|
||||
hs_cache_intro_state_t *entry;
|
||||
|
||||
tor_assert(service_pk);
|
||||
tor_assert(auth_key);
|
||||
|
||||
found = cache_client_intro_state_lookup(service_pk, auth_key, &entry);
|
||||
if (!found) {
|
||||
/* Create a new entry and add it to the cache. */
|
||||
cache_client_intro_state_add(service_pk, auth_key, &entry);
|
||||
}
|
||||
/* Note down the entry. */
|
||||
cache_client_intro_state_note(entry, failure);
|
||||
}
|
||||
|
||||
/* For a given service identity public key and an introduction authentication
|
||||
* key, return true iff it is present in the failure cache. */
|
||||
const hs_cache_intro_state_t *
|
||||
hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk,
|
||||
const ed25519_public_key_t *auth_key)
|
||||
{
|
||||
hs_cache_intro_state_t *state = NULL;
|
||||
cache_client_intro_state_lookup(service_pk, auth_key, &state);
|
||||
return state;
|
||||
}
|
||||
|
||||
/* Cleanup the client introduction state cache. */
|
||||
void
|
||||
hs_cache_client_intro_state_clean(time_t now)
|
||||
{
|
||||
time_t cutoff = now - HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE;
|
||||
|
||||
DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key,
|
||||
hs_cache_client_intro_state_t *, cache) {
|
||||
/* Cleanup intro points failure. */
|
||||
cache_client_intro_state_clean(cutoff, cache);
|
||||
|
||||
/* Is this cache empty for this service key? If yes, remove it from the
|
||||
* cache. Else keep it. */
|
||||
if (cache_client_intro_state_is_empty(cache)) {
|
||||
cache_client_intro_state_free(cache);
|
||||
MAP_DEL_CURRENT(key);
|
||||
}
|
||||
} DIGEST256MAP_FOREACH_END;
|
||||
}
|
||||
|
||||
/**************** Generics *********************************/
|
||||
|
||||
/* Do a round of OOM cleanup on all directory caches. Return the amount of
|
||||
* removed bytes. It is possible that the returned value is lower than
|
||||
* min_remove_bytes if the caches get emptied out so the caller should be
|
||||
@ -369,10 +835,7 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
|
||||
return bytes_removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum size of an HS descriptor we are willing to accept as an
|
||||
* HSDir.
|
||||
*/
|
||||
/* Return the maximum size of a v3 HS descriptor. */
|
||||
unsigned int
|
||||
hs_cache_get_max_descriptor_size(void)
|
||||
{
|
||||
@ -388,6 +851,12 @@ hs_cache_init(void)
|
||||
/* Calling this twice is very wrong code flow. */
|
||||
tor_assert(!hs_cache_v3_dir);
|
||||
hs_cache_v3_dir = digest256map_new();
|
||||
|
||||
tor_assert(!hs_cache_v3_client);
|
||||
hs_cache_v3_client = digest256map_new();
|
||||
|
||||
tor_assert(!hs_cache_client_intro_state);
|
||||
hs_cache_client_intro_state = digest256map_new();
|
||||
}
|
||||
|
||||
/* Cleanup the hidden service cache subsystem. */
|
||||
@ -396,5 +865,12 @@ hs_cache_free_all(void)
|
||||
{
|
||||
digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_);
|
||||
hs_cache_v3_dir = NULL;
|
||||
|
||||
digest256map_free(hs_cache_v3_client, cache_client_desc_free_);
|
||||
hs_cache_v3_client = NULL;
|
||||
|
||||
digest256map_free(hs_cache_client_intro_state,
|
||||
cache_client_intro_state_free_);
|
||||
hs_cache_client_intro_state = NULL;
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,34 @@
|
||||
#include "crypto_ed25519.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_descriptor.h"
|
||||
#include "rendcommon.h"
|
||||
#include "torcert.h"
|
||||
|
||||
/* This is the maximum time an introduction point state object can stay in the
|
||||
* client cache in seconds (2 mins or 120 seconds). */
|
||||
#define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60)
|
||||
|
||||
/* Introduction point state. */
|
||||
typedef struct hs_cache_intro_state_t {
|
||||
/* When this entry was created and put in the cache. */
|
||||
time_t created_ts;
|
||||
|
||||
/* Did it suffered a generic error? */
|
||||
unsigned int error : 1;
|
||||
|
||||
/* Did it timed out? */
|
||||
unsigned int timed_out : 1;
|
||||
|
||||
/* How many times we tried to reached it and it was unreachable. */
|
||||
uint32_t unreachable_count;
|
||||
} hs_cache_intro_state_t;
|
||||
|
||||
typedef struct hs_cache_client_intro_state_t {
|
||||
/* Contains hs_cache_intro_state_t object indexed by introduction point
|
||||
* authentication key. */
|
||||
digest256map_t *intro_points;
|
||||
} hs_cache_client_intro_state_t;
|
||||
|
||||
/* Descriptor representation on the directory side which is a subset of
|
||||
* information that the HSDir can decode and serve it. */
|
||||
typedef struct hs_cache_dir_descriptor_t {
|
||||
@ -53,10 +79,44 @@ int hs_cache_store_as_dir(const char *desc);
|
||||
int hs_cache_lookup_as_dir(uint32_t version, const char *query,
|
||||
const char **desc_out);
|
||||
|
||||
const hs_descriptor_t *
|
||||
hs_cache_lookup_as_client(const ed25519_public_key_t *key);
|
||||
int hs_cache_store_as_client(const char *desc_str,
|
||||
const ed25519_public_key_t *identity_pk);
|
||||
void hs_cache_clean_as_client(time_t now);
|
||||
|
||||
/* Client failure cache. */
|
||||
void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk,
|
||||
const ed25519_public_key_t *auth_key,
|
||||
rend_intro_point_failure_t failure);
|
||||
const hs_cache_intro_state_t *hs_cache_client_intro_state_find(
|
||||
const ed25519_public_key_t *service_pk,
|
||||
const ed25519_public_key_t *auth_key);
|
||||
void hs_cache_client_intro_state_clean(time_t now);
|
||||
|
||||
#ifdef HS_CACHE_PRIVATE
|
||||
|
||||
/** Represents a locally cached HS descriptor on a hidden service client. */
|
||||
typedef struct hs_cache_client_descriptor_t {
|
||||
/* This object is indexed using the service identity public key */
|
||||
ed25519_public_key_t key;
|
||||
|
||||
/* When was this entry created. Used to expire entries. */
|
||||
time_t created_ts;
|
||||
|
||||
/* The cached descriptor, this object is the owner. It can't be NULL. A
|
||||
* cache object without a valid descriptor is not possible. */
|
||||
hs_descriptor_t *desc;
|
||||
|
||||
/* Encoded descriptor in string form. Can't be NULL. */
|
||||
char *encoded_desc;
|
||||
} hs_cache_client_descriptor_t;
|
||||
|
||||
STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff);
|
||||
|
||||
STATIC hs_cache_client_descriptor_t *
|
||||
lookup_v3_desc_as_client(const uint8_t *key);
|
||||
|
||||
#endif /* HS_CACHE_PRIVATE */
|
||||
|
||||
#endif /* TOR_HS_CACHE_H */
|
||||
|
364
src/or/hs_cell.c
364
src/or/hs_cell.c
@ -10,6 +10,7 @@
|
||||
#include "config.h"
|
||||
#include "rendservice.h"
|
||||
#include "replaycache.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "hs_cell.h"
|
||||
#include "hs_ntor.h"
|
||||
@ -245,6 +246,229 @@ parse_introduce2_cell(const hs_service_t *service,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set the onion public key onion_pk in cell, the encrypted section of an
|
||||
* INTRODUCE1 cell. */
|
||||
static void
|
||||
introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell,
|
||||
const uint8_t *onion_pk)
|
||||
{
|
||||
tor_assert(cell);
|
||||
tor_assert(onion_pk);
|
||||
/* There is only one possible key type for a non legacy cell. */
|
||||
trn_cell_introduce_encrypted_set_onion_key_type(cell,
|
||||
HS_CELL_ONION_KEY_TYPE_NTOR);
|
||||
trn_cell_introduce_encrypted_set_onion_key_len(cell, CURVE25519_PUBKEY_LEN);
|
||||
trn_cell_introduce_encrypted_setlen_onion_key(cell, CURVE25519_PUBKEY_LEN);
|
||||
memcpy(trn_cell_introduce_encrypted_getarray_onion_key(cell), onion_pk,
|
||||
trn_cell_introduce_encrypted_getlen_onion_key(cell));
|
||||
}
|
||||
|
||||
/* Set the link specifiers in lspecs in cell, the encrypted section of an
|
||||
* INTRODUCE1 cell. */
|
||||
static void
|
||||
introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell,
|
||||
const smartlist_t *lspecs)
|
||||
{
|
||||
tor_assert(cell);
|
||||
tor_assert(lspecs);
|
||||
tor_assert(smartlist_len(lspecs) > 0);
|
||||
tor_assert(smartlist_len(lspecs) <= UINT8_MAX);
|
||||
|
||||
uint8_t lspecs_num = (uint8_t) smartlist_len(lspecs);
|
||||
trn_cell_introduce_encrypted_set_nspec(cell, lspecs_num);
|
||||
/* We aren't duplicating the link specifiers object here which means that
|
||||
* the ownership goes to the trn_cell_introduce_encrypted_t cell and those
|
||||
* object will be freed when the cell is. */
|
||||
SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls,
|
||||
trn_cell_introduce_encrypted_add_nspecs(cell, ls));
|
||||
}
|
||||
|
||||
/* Set padding in the enc_cell only if needed that is the total length of both
|
||||
* sections are below the mininum required for an INTRODUCE1 cell. */
|
||||
static void
|
||||
introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell,
|
||||
trn_cell_introduce_encrypted_t *enc_cell)
|
||||
{
|
||||
tor_assert(cell);
|
||||
tor_assert(enc_cell);
|
||||
/* This is the length we expect to have once encoded of the whole cell. */
|
||||
ssize_t full_len = trn_cell_introduce1_encoded_len(cell) +
|
||||
trn_cell_introduce_encrypted_encoded_len(enc_cell);
|
||||
tor_assert(full_len > 0);
|
||||
if (full_len < HS_CELL_INTRODUCE1_MIN_SIZE) {
|
||||
size_t padding = HS_CELL_INTRODUCE1_MIN_SIZE - full_len;
|
||||
trn_cell_introduce_encrypted_setlen_pad(enc_cell, padding);
|
||||
memset(trn_cell_introduce_encrypted_getarray_pad(enc_cell), 0,
|
||||
trn_cell_introduce_encrypted_getlen_pad(enc_cell));
|
||||
}
|
||||
}
|
||||
|
||||
/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell
|
||||
* and the INTRODUCE1 data.
|
||||
*
|
||||
* This can't fail but it is very important that the caller sets every field
|
||||
* in data so the computation of the INTRODUCE1 keys doesn't fail. */
|
||||
static void
|
||||
introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell,
|
||||
const trn_cell_introduce_encrypted_t *enc_cell,
|
||||
const hs_cell_introduce1_data_t *data)
|
||||
{
|
||||
size_t offset = 0;
|
||||
ssize_t encrypted_len;
|
||||
ssize_t encoded_cell_len, encoded_enc_cell_len;
|
||||
uint8_t encoded_cell[RELAY_PAYLOAD_SIZE] = {0};
|
||||
uint8_t encoded_enc_cell[RELAY_PAYLOAD_SIZE] = {0};
|
||||
uint8_t *encrypted = NULL;
|
||||
uint8_t mac[DIGEST256_LEN];
|
||||
crypto_cipher_t *cipher = NULL;
|
||||
hs_ntor_intro_cell_keys_t keys;
|
||||
|
||||
tor_assert(cell);
|
||||
tor_assert(enc_cell);
|
||||
tor_assert(data);
|
||||
|
||||
/* Encode the cells up to now of what we have to we can perform the MAC
|
||||
* computation on it. */
|
||||
encoded_cell_len = trn_cell_introduce1_encode(encoded_cell,
|
||||
sizeof(encoded_cell), cell);
|
||||
/* We have a much more serious issue if this isn't true. */
|
||||
tor_assert(encoded_cell_len > 0);
|
||||
|
||||
encoded_enc_cell_len =
|
||||
trn_cell_introduce_encrypted_encode(encoded_enc_cell,
|
||||
sizeof(encoded_enc_cell), enc_cell);
|
||||
/* We have a much more serious issue if this isn't true. */
|
||||
tor_assert(encoded_enc_cell_len > 0);
|
||||
|
||||
/* Get the key material for the encryption. */
|
||||
if (hs_ntor_client_get_introduce1_keys(data->auth_pk, data->enc_pk,
|
||||
data->client_kp,
|
||||
data->subcredential, &keys) < 0) {
|
||||
tor_assert_unreached();
|
||||
}
|
||||
|
||||
/* Prepare cipher with the encryption key just computed. */
|
||||
cipher = crypto_cipher_new_with_bits((const char *) keys.enc_key,
|
||||
sizeof(keys.enc_key) * 8);
|
||||
tor_assert(cipher);
|
||||
|
||||
/* Compute the length of the ENCRYPTED section which is the CLIENT_PK,
|
||||
* ENCRYPTED_DATA and MAC length. */
|
||||
encrypted_len = sizeof(data->client_kp->pubkey) + encoded_enc_cell_len +
|
||||
sizeof(mac);
|
||||
tor_assert(encrypted_len < RELAY_PAYLOAD_SIZE);
|
||||
encrypted = tor_malloc_zero(encrypted_len);
|
||||
|
||||
/* Put the CLIENT_PK first. */
|
||||
memcpy(encrypted, data->client_kp->pubkey.public_key,
|
||||
sizeof(data->client_kp->pubkey.public_key));
|
||||
offset += sizeof(data->client_kp->pubkey.public_key);
|
||||
/* Then encrypt and set the ENCRYPTED_DATA. This can't fail. */
|
||||
crypto_cipher_encrypt(cipher, (char *) encrypted + offset,
|
||||
(const char *) encoded_enc_cell, encoded_enc_cell_len);
|
||||
crypto_cipher_free(cipher);
|
||||
offset += encoded_enc_cell_len;
|
||||
/* Compute MAC from the above and put it in the buffer. This function will
|
||||
* make the adjustment to the encryptled_len to ommit the MAC length. */
|
||||
compute_introduce_mac(encoded_cell, encoded_cell_len,
|
||||
encrypted, encrypted_len,
|
||||
keys.mac_key, sizeof(keys.mac_key),
|
||||
mac, sizeof(mac));
|
||||
memcpy(encrypted + offset, mac, sizeof(mac));
|
||||
offset += sizeof(mac);
|
||||
tor_assert(offset == (size_t) encrypted_len);
|
||||
|
||||
/* Set the ENCRYPTED section in the cell. */
|
||||
trn_cell_introduce1_setlen_encrypted(cell, encrypted_len);
|
||||
memcpy(trn_cell_introduce1_getarray_encrypted(cell),
|
||||
encrypted, encrypted_len);
|
||||
|
||||
/* Cleanup. */
|
||||
memwipe(&keys, 0, sizeof(keys));
|
||||
memwipe(mac, 0, sizeof(mac));
|
||||
memwipe(encrypted, 0, sizeof(encrypted_len));
|
||||
memwipe(encoded_enc_cell, 0, sizeof(encoded_enc_cell));
|
||||
tor_free(encrypted);
|
||||
}
|
||||
|
||||
/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means
|
||||
* set it, encrypt it and encode it. */
|
||||
static void
|
||||
introduce1_set_encrypted(trn_cell_introduce1_t *cell,
|
||||
const hs_cell_introduce1_data_t *data)
|
||||
{
|
||||
trn_cell_introduce_encrypted_t *enc_cell;
|
||||
trn_cell_extension_t *ext;
|
||||
|
||||
tor_assert(cell);
|
||||
tor_assert(data);
|
||||
|
||||
enc_cell = trn_cell_introduce_encrypted_new();
|
||||
tor_assert(enc_cell);
|
||||
|
||||
/* Set extension data. None are used. */
|
||||
ext = trn_cell_extension_new();
|
||||
tor_assert(ext);
|
||||
trn_cell_extension_set_num(ext, 0);
|
||||
trn_cell_introduce_encrypted_set_extensions(enc_cell, ext);
|
||||
|
||||
/* Set the rendezvous cookie. */
|
||||
memcpy(trn_cell_introduce_encrypted_getarray_rend_cookie(enc_cell),
|
||||
data->rendezvous_cookie, REND_COOKIE_LEN);
|
||||
|
||||
/* Set the onion public key. */
|
||||
introduce1_set_encrypted_onion_key(enc_cell, data->onion_pk->public_key);
|
||||
|
||||
/* Set the link specifiers. */
|
||||
introduce1_set_encrypted_link_spec(enc_cell, data->link_specifiers);
|
||||
|
||||
/* Set padding. */
|
||||
introduce1_set_encrypted_padding(cell, enc_cell);
|
||||
|
||||
/* Encrypt and encode it in the cell. */
|
||||
introduce1_encrypt_and_encode(cell, enc_cell, data);
|
||||
|
||||
/* Cleanup. */
|
||||
trn_cell_introduce_encrypted_free(enc_cell);
|
||||
}
|
||||
|
||||
/* Set the authentication key in the INTRODUCE1 cell from the given data. */
|
||||
static void
|
||||
introduce1_set_auth_key(trn_cell_introduce1_t *cell,
|
||||
const hs_cell_introduce1_data_t *data)
|
||||
{
|
||||
tor_assert(cell);
|
||||
tor_assert(data);
|
||||
/* There is only one possible type for a non legacy cell. */
|
||||
trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519);
|
||||
trn_cell_introduce1_set_auth_key_len(cell, ED25519_PUBKEY_LEN);
|
||||
trn_cell_introduce1_setlen_auth_key(cell, ED25519_PUBKEY_LEN);
|
||||
memcpy(trn_cell_introduce1_getarray_auth_key(cell),
|
||||
data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell));
|
||||
}
|
||||
|
||||
/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */
|
||||
static void
|
||||
introduce1_set_legacy_id(trn_cell_introduce1_t *cell,
|
||||
const hs_cell_introduce1_data_t *data)
|
||||
{
|
||||
tor_assert(cell);
|
||||
tor_assert(data);
|
||||
|
||||
if (data->is_legacy) {
|
||||
uint8_t digest[DIGEST_LEN];
|
||||
if (BUG(crypto_pk_get_digest(data->legacy_key, (char *) digest) < 0)) {
|
||||
return;
|
||||
}
|
||||
memcpy(trn_cell_introduce1_getarray_legacy_key_id(cell),
|
||||
digest, trn_cell_introduce1_getlen_legacy_key_id(cell));
|
||||
} else {
|
||||
/* We have to zeroed the LEGACY_KEY_ID field. */
|
||||
memset(trn_cell_introduce1_getarray_legacy_key_id(cell), 0,
|
||||
trn_cell_introduce1_getlen_legacy_key_id(cell));
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== */
|
||||
/* Public API */
|
||||
/* ========== */
|
||||
@ -582,3 +806,143 @@ hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
|
||||
return cell_len;
|
||||
}
|
||||
|
||||
/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in
|
||||
* cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the
|
||||
* encoded length is returned else a negative value and the content of
|
||||
* cell_out should be ignored. */
|
||||
ssize_t
|
||||
hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data,
|
||||
uint8_t *cell_out)
|
||||
{
|
||||
ssize_t cell_len;
|
||||
trn_cell_introduce1_t *cell;
|
||||
trn_cell_extension_t *ext;
|
||||
|
||||
tor_assert(data);
|
||||
tor_assert(cell_out);
|
||||
|
||||
cell = trn_cell_introduce1_new();
|
||||
tor_assert(cell);
|
||||
|
||||
/* Set extension data. None are used. */
|
||||
ext = trn_cell_extension_new();
|
||||
tor_assert(ext);
|
||||
trn_cell_extension_set_num(ext, 0);
|
||||
trn_cell_introduce1_set_extensions(cell, ext);
|
||||
|
||||
/* Set the legacy ID field. */
|
||||
introduce1_set_legacy_id(cell, data);
|
||||
|
||||
/* Set the authentication key. */
|
||||
introduce1_set_auth_key(cell, data);
|
||||
|
||||
/* Set the encrypted section. This will set, encrypt and encode the
|
||||
* ENCRYPTED section in the cell. After this, we'll be ready to encode. */
|
||||
introduce1_set_encrypted(cell, data);
|
||||
|
||||
/* Final encoding. */
|
||||
cell_len = trn_cell_introduce1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell);
|
||||
|
||||
trn_cell_introduce1_free(cell);
|
||||
return cell_len;
|
||||
}
|
||||
|
||||
/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The
|
||||
* encoded cell is put in cell_out which must be of at least
|
||||
* RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the
|
||||
* caller should clear up the content of the cell.
|
||||
*
|
||||
* This function can't fail. */
|
||||
ssize_t
|
||||
hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie,
|
||||
uint8_t *cell_out)
|
||||
{
|
||||
tor_assert(rendezvous_cookie);
|
||||
tor_assert(cell_out);
|
||||
|
||||
memcpy(cell_out, rendezvous_cookie, HS_REND_COOKIE_LEN);
|
||||
return HS_REND_COOKIE_LEN;
|
||||
}
|
||||
|
||||
/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len.
|
||||
* Return the status code on success else a negative value if the cell as not
|
||||
* decodable. */
|
||||
int
|
||||
hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len)
|
||||
{
|
||||
int ret = -1;
|
||||
trn_cell_introduce_ack_t *cell = NULL;
|
||||
|
||||
tor_assert(payload);
|
||||
|
||||
/* If it is a legacy IP, rend-spec.txt specifies that a ACK is 0 byte and a
|
||||
* NACK is 1 byte. We can't use the legacy function for this so we have to
|
||||
* do a special case. */
|
||||
if (payload_len <= 1) {
|
||||
if (payload_len == 0) {
|
||||
ret = HS_CELL_INTRO_ACK_SUCCESS;
|
||||
} else {
|
||||
ret = HS_CELL_INTRO_ACK_FAILURE;
|
||||
}
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) {
|
||||
log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it.");
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = trn_cell_introduce_ack_get_status(cell);
|
||||
|
||||
end:
|
||||
trn_cell_introduce_ack_free(cell);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On
|
||||
* success, handshake_info contains the data in the HANDSHAKE_INFO field, and
|
||||
* 0 is returned. On error, a negative value is returned. */
|
||||
int
|
||||
hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *handshake_info, size_t handshake_info_len)
|
||||
{
|
||||
int ret = -1;
|
||||
trn_cell_rendezvous2_t *cell = NULL;
|
||||
|
||||
tor_assert(payload);
|
||||
tor_assert(handshake_info);
|
||||
|
||||
if (trn_cell_rendezvous2_parse(&cell, payload, payload_len) < 0) {
|
||||
log_info(LD_REND, "Invalid RENDEZVOUS2 cell. Unable to parse it.");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Static size, we should never have an issue with this else we messed up
|
||||
* our code flow. */
|
||||
tor_assert(trn_cell_rendezvous2_getlen_handshake_info(cell) ==
|
||||
handshake_info_len);
|
||||
memcpy(handshake_info,
|
||||
trn_cell_rendezvous2_getconstarray_handshake_info(cell),
|
||||
handshake_info_len);
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
trn_cell_rendezvous2_free(cell);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Clear the given INTRODUCE1 data structure data. */
|
||||
void
|
||||
hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data)
|
||||
{
|
||||
if (data == NULL) {
|
||||
return;
|
||||
}
|
||||
/* Object in this list have been moved to the cell object when building it
|
||||
* so they've been freed earlier. We do that in order to avoid duplicating
|
||||
* them leading to more memory and CPU time being used for nothing. */
|
||||
smartlist_free(data->link_specifiers);
|
||||
/* The data object has no ownership of any members. */
|
||||
memwipe(data, 0, sizeof(hs_cell_introduce1_data_t));
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,47 @@
|
||||
#include "or.h"
|
||||
#include "hs_service.h"
|
||||
|
||||
/* An INTRODUCE1 cell requires at least this amount of bytes (see section
|
||||
* 3.2.2 of the specification). Below this value, the cell must be padded. */
|
||||
#define HS_CELL_INTRODUCE1_MIN_SIZE 246
|
||||
|
||||
/* Status code of an INTRODUCE_ACK cell. */
|
||||
typedef enum {
|
||||
HS_CELL_INTRO_ACK_SUCCESS = 0x0000, /* Cell relayed to service. */
|
||||
HS_CELL_INTRO_ACK_FAILURE = 0x0001, /* Service ID not recognized */
|
||||
HS_CELL_INTRO_ACK_BADFMT = 0x0002, /* Bad message format */
|
||||
HS_CELL_INTRO_ACK_NORELAY = 0x0003, /* Can't relay cell to service */
|
||||
} hs_cell_introd_ack_status_t;
|
||||
|
||||
/* Onion key type found in the INTRODUCE1 cell. */
|
||||
typedef enum {
|
||||
HS_CELL_ONION_KEY_TYPE_NTOR = 1,
|
||||
} hs_cell_onion_key_type_t;
|
||||
|
||||
/* This data structure contains data that we need to build an INTRODUCE1 cell
|
||||
* used by the INTRODUCE1 build function. */
|
||||
typedef struct hs_cell_introduce1_data_t {
|
||||
/* Is this a legacy introduction point? */
|
||||
unsigned int is_legacy : 1;
|
||||
/* (Legacy only) The encryption key for a legacy intro point. Only set if
|
||||
* is_legacy is true. */
|
||||
const crypto_pk_t *legacy_key;
|
||||
/* Introduction point authentication public key. */
|
||||
const ed25519_public_key_t *auth_pk;
|
||||
/* Introduction point encryption public key. */
|
||||
const curve25519_public_key_t *enc_pk;
|
||||
/* Subcredentials of the service. */
|
||||
const uint8_t *subcredential;
|
||||
/* Onion public key for the ntor handshake. */
|
||||
const curve25519_public_key_t *onion_pk;
|
||||
/* Rendezvous cookie. */
|
||||
const uint8_t *rendezvous_cookie;
|
||||
/* Public key put before the encrypted data (CLIENT_PK). */
|
||||
const curve25519_keypair_t *client_kp;
|
||||
/* Rendezvous point link specifiers. */
|
||||
smartlist_t *link_specifiers;
|
||||
} hs_cell_introduce1_data_t;
|
||||
|
||||
/* This data structure contains data that we need to parse an INTRODUCE2 cell
|
||||
* which is used by the INTRODUCE2 cell parsing function. On a successful
|
||||
* parsing, the onion_pk and rendezvous_cookie will be populated with the
|
||||
@ -63,6 +99,10 @@ ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
|
||||
const uint8_t *rendezvous_handshake_info,
|
||||
size_t rendezvous_handshake_info_len,
|
||||
uint8_t *cell_out);
|
||||
ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data,
|
||||
uint8_t *cell_out);
|
||||
ssize_t hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie,
|
||||
uint8_t *cell_out);
|
||||
|
||||
/* Parse cell API. */
|
||||
ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
|
||||
@ -70,6 +110,13 @@ ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
|
||||
ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
|
||||
const origin_circuit_t *circ,
|
||||
const hs_service_t *service);
|
||||
int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len);
|
||||
int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *handshake_info,
|
||||
size_t handshake_info_len);
|
||||
|
||||
/* Util API. */
|
||||
void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data);
|
||||
|
||||
#endif /* TOR_HS_CELL_H */
|
||||
|
||||
|
@ -341,125 +341,6 @@ send_establish_intro(const hs_service_t *service,
|
||||
memwipe(payload, 0, sizeof(payload));
|
||||
}
|
||||
|
||||
/* From a list of link specifier, an onion key and if we are requesting a
|
||||
* direct connection (ex: single onion service), return a newly allocated
|
||||
* extend_info_t object. This function checks the firewall policies and if we
|
||||
* are allowed to extend to the chosen address.
|
||||
*
|
||||
* if either IPv4 or legacy ID is missing, error.
|
||||
* if not direct_conn, IPv4 is prefered.
|
||||
* if direct_conn, IPv6 is prefered if we have one available.
|
||||
* if firewall does not allow the chosen address, error.
|
||||
*
|
||||
* Return NULL if we can't fulfill the conditions. */
|
||||
static extend_info_t *
|
||||
get_rp_extend_info(const smartlist_t *link_specifiers,
|
||||
const curve25519_public_key_t *onion_key, int direct_conn)
|
||||
{
|
||||
int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
|
||||
char legacy_id[DIGEST_LEN] = {0};
|
||||
uint16_t port_v4 = 0, port_v6 = 0, port = 0;
|
||||
tor_addr_t addr_v4, addr_v6, *addr = NULL;
|
||||
ed25519_public_key_t ed25519_pk;
|
||||
extend_info_t *info = NULL;
|
||||
|
||||
tor_assert(link_specifiers);
|
||||
tor_assert(onion_key);
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(link_specifiers, const link_specifier_t *, ls) {
|
||||
switch (link_specifier_get_ls_type(ls)) {
|
||||
case LS_IPV4:
|
||||
/* Skip if we already seen a v4. */
|
||||
if (have_v4) continue;
|
||||
tor_addr_from_ipv4h(&addr_v4,
|
||||
link_specifier_get_un_ipv4_addr(ls));
|
||||
port_v4 = link_specifier_get_un_ipv4_port(ls);
|
||||
have_v4 = 1;
|
||||
break;
|
||||
case LS_IPV6:
|
||||
/* Skip if we already seen a v6. */
|
||||
if (have_v6) continue;
|
||||
tor_addr_from_ipv6_bytes(&addr_v6,
|
||||
(const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
|
||||
port_v6 = link_specifier_get_un_ipv6_port(ls);
|
||||
have_v6 = 1;
|
||||
break;
|
||||
case LS_LEGACY_ID:
|
||||
/* Make sure we do have enough bytes for the legacy ID. */
|
||||
if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
|
||||
break;
|
||||
}
|
||||
memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls),
|
||||
sizeof(legacy_id));
|
||||
have_legacy_id = 1;
|
||||
break;
|
||||
case LS_ED25519_ID:
|
||||
memcpy(ed25519_pk.pubkey,
|
||||
link_specifier_getconstarray_un_ed25519_id(ls),
|
||||
ED25519_PUBKEY_LEN);
|
||||
have_ed25519_id = 1;
|
||||
break;
|
||||
default:
|
||||
/* Ignore unknown. */
|
||||
break;
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(ls);
|
||||
|
||||
/* IPv4, legacy ID are mandatory for rend points.
|
||||
* ed25519 keys and ipv6 are optional for rend points */
|
||||
if (!have_v4 || !have_legacy_id) {
|
||||
goto done;
|
||||
}
|
||||
/* By default, we pick IPv4 but this might change to v6 if certain
|
||||
* conditions are met. */
|
||||
addr = &addr_v4; port = port_v4;
|
||||
|
||||
/* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
|
||||
* circuit so we can't extend in IPv6. And at this point, we do have an IPv4
|
||||
* address available so go to validation. */
|
||||
if (!direct_conn) {
|
||||
goto validate;
|
||||
}
|
||||
|
||||
/* From this point on, we have a request for a direct connection to the
|
||||
* rendezvous point so make sure we can actually connect through our
|
||||
* firewall. We'll prefer IPv6. */
|
||||
|
||||
/* IPv6 test. */
|
||||
if (have_v6 &&
|
||||
fascist_firewall_allows_address_addr(&addr_v6, port_v6,
|
||||
FIREWALL_OR_CONNECTION, 1, 1)) {
|
||||
/* Direct connection and we can reach it in IPv6 so go for it. */
|
||||
addr = &addr_v6; port = port_v6;
|
||||
goto validate;
|
||||
}
|
||||
/* IPv4 test and we are sure we have a v4 because of the check above. */
|
||||
if (fascist_firewall_allows_address_addr(&addr_v4, port_v4,
|
||||
FIREWALL_OR_CONNECTION, 0, 0)) {
|
||||
/* Direct connection and we can reach it in IPv4 so go for it. */
|
||||
addr = &addr_v4; port = port_v4;
|
||||
goto validate;
|
||||
}
|
||||
|
||||
validate:
|
||||
/* We'll validate now that the address we've picked isn't a private one. If
|
||||
* it is, are we allowing to extend to private address? */
|
||||
if (!extend_info_addr_is_allowed(addr)) {
|
||||
log_warn(LD_REND, "Rendezvous point address is private and it is not "
|
||||
"allowed to extend to it: %s:%u",
|
||||
fmt_addr(&addr_v4), port_v4);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* We do have everything for which we think we can connect successfully. */
|
||||
info = extend_info_new(NULL, legacy_id,
|
||||
have_ed25519_id ? &ed25519_pk : NULL,
|
||||
NULL, onion_key,
|
||||
addr, port);
|
||||
done:
|
||||
return info;
|
||||
}
|
||||
|
||||
/* 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
|
||||
@ -483,7 +364,8 @@ launch_rendezvous_point_circuit(const hs_service_t *service,
|
||||
|
||||
/* Get the extend info data structure for the chosen rendezvous point
|
||||
* specified by the given link specifiers. */
|
||||
info = get_rp_extend_info(data->link_specifiers, &data->onion_pk,
|
||||
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. */
|
||||
@ -648,6 +530,83 @@ retry_service_rendezvous_point(const origin_circuit_t *circ)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Using an extend info object ei, set all possible link specifiers in lspecs.
|
||||
* IPv4, legacy ID and ed25519 ID are mandatory thus MUST be present in ei. */
|
||||
static void
|
||||
get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs)
|
||||
{
|
||||
link_specifier_t *ls;
|
||||
|
||||
tor_assert(ei);
|
||||
tor_assert(lspecs);
|
||||
|
||||
/* IPv4 is mandatory. */
|
||||
ls = link_specifier_new();
|
||||
link_specifier_set_ls_type(ls, LS_IPV4);
|
||||
link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr));
|
||||
link_specifier_set_un_ipv4_port(ls, ei->port);
|
||||
/* Four bytes IPv4 and two bytes port. */
|
||||
link_specifier_set_ls_len(ls, sizeof(ei->addr.addr.in_addr) +
|
||||
sizeof(ei->port));
|
||||
smartlist_add(lspecs, ls);
|
||||
|
||||
/* Legacy ID is mandatory. */
|
||||
ls = link_specifier_new();
|
||||
link_specifier_set_ls_type(ls, LS_LEGACY_ID);
|
||||
memcpy(link_specifier_getarray_un_legacy_id(ls), ei->identity_digest,
|
||||
link_specifier_getlen_un_legacy_id(ls));
|
||||
link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls));
|
||||
smartlist_add(lspecs, ls);
|
||||
|
||||
/* ed25519 ID is mandatory. */
|
||||
ls = link_specifier_new();
|
||||
link_specifier_set_ls_type(ls, LS_ED25519_ID);
|
||||
memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity,
|
||||
link_specifier_getlen_un_ed25519_id(ls));
|
||||
link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls));
|
||||
smartlist_add(lspecs, ls);
|
||||
|
||||
/* XXX: IPv6 is not clearly a thing in extend_info_t? */
|
||||
}
|
||||
|
||||
/* Using the given descriptor intro point ip, the extend information of the
|
||||
* rendezvous point rp_ei and the service's subcredential, populate the
|
||||
* already allocated intro1_data object with the needed key material and link
|
||||
* specifiers.
|
||||
*
|
||||
* This can't fail but the ip MUST be a valid object containing the needed
|
||||
* keys and authentication method. */
|
||||
static void
|
||||
setup_introduce1_data(const hs_desc_intro_point_t *ip,
|
||||
const extend_info_t *rp_ei,
|
||||
const uint8_t *subcredential,
|
||||
hs_cell_introduce1_data_t *intro1_data)
|
||||
{
|
||||
smartlist_t *rp_lspecs;
|
||||
|
||||
tor_assert(ip);
|
||||
tor_assert(rp_ei);
|
||||
tor_assert(subcredential);
|
||||
tor_assert(intro1_data);
|
||||
|
||||
/* Build the link specifiers from the extend information of the rendezvous
|
||||
* circuit that we've picked previously. */
|
||||
rp_lspecs = smartlist_new();
|
||||
get_lspecs_from_extend_info(rp_ei, rp_lspecs);
|
||||
|
||||
/* Populate the introduce1 data object. */
|
||||
memset(intro1_data, 0, sizeof(hs_cell_introduce1_data_t));
|
||||
if (ip->legacy.key != NULL) {
|
||||
intro1_data->is_legacy = 1;
|
||||
intro1_data->legacy_key = ip->legacy.key;
|
||||
}
|
||||
intro1_data->auth_pk = &ip->auth_key_cert->signed_key;
|
||||
intro1_data->enc_pk = &ip->enc_key;
|
||||
intro1_data->subcredential = subcredential;
|
||||
intro1_data->onion_pk = &rp_ei->curve25519_onion_key;
|
||||
intro1_data->link_specifiers = rp_lspecs;
|
||||
}
|
||||
|
||||
/* ========== */
|
||||
/* Public API */
|
||||
/* ========== */
|
||||
@ -1055,3 +1014,120 @@ hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Given the introduction circuit intro_circ, the rendezvous circuit
|
||||
* rend_circ, a descriptor intro point object ip and the service's
|
||||
* subcredential, send an INTRODUCE1 cell on intro_circ.
|
||||
*
|
||||
* This will also setup the circuit identifier on rend_circ containing the key
|
||||
* material for the handshake and e2e encryption. Return 0 on success else
|
||||
* negative value. Because relay_send_command_from_edge() closes the circuit
|
||||
* on error, it is possible that intro_circ is closed on error. */
|
||||
int
|
||||
hs_circ_send_introduce1(origin_circuit_t *intro_circ,
|
||||
origin_circuit_t *rend_circ,
|
||||
const hs_desc_intro_point_t *ip,
|
||||
const uint8_t *subcredential)
|
||||
{
|
||||
int ret = -1;
|
||||
ssize_t payload_len;
|
||||
uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
|
||||
hs_cell_introduce1_data_t intro1_data;
|
||||
|
||||
tor_assert(intro_circ);
|
||||
tor_assert(rend_circ);
|
||||
tor_assert(ip);
|
||||
tor_assert(subcredential);
|
||||
|
||||
/* This takes various objects in order to populate the introduce1 data
|
||||
* object which is used to build the content of the cell. */
|
||||
setup_introduce1_data(ip, rend_circ->build_state->chosen_exit,
|
||||
subcredential, &intro1_data);
|
||||
|
||||
/* Final step before we encode a cell, we setup the circuit identifier which
|
||||
* will generate both the rendezvous cookie and client keypair for this
|
||||
* connection. Those are put in the ident. */
|
||||
intro1_data.rendezvous_cookie = rend_circ->hs_ident->rendezvous_cookie;
|
||||
intro1_data.client_kp = &rend_circ->hs_ident->rendezvous_client_kp;
|
||||
|
||||
memcpy(intro_circ->hs_ident->rendezvous_cookie,
|
||||
rend_circ->hs_ident->rendezvous_cookie,
|
||||
sizeof(intro_circ->hs_ident->rendezvous_cookie));
|
||||
|
||||
/* From the introduce1 data object, this will encode the INTRODUCE1 cell
|
||||
* into payload which is then ready to be sent as is. */
|
||||
payload_len = hs_cell_build_introduce1(&intro1_data, payload);
|
||||
if (BUG(payload_len < 0)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(intro_circ),
|
||||
RELAY_COMMAND_INTRODUCE1,
|
||||
(const char *) payload, payload_len,
|
||||
intro_circ->cpath->prev) < 0) {
|
||||
/* On error, circuit is closed. */
|
||||
log_warn(LD_REND, "Unable to send INTRODUCE1 cell on circuit %u.",
|
||||
TO_CIRCUIT(intro_circ)->n_circ_id);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
goto done;
|
||||
|
||||
done:
|
||||
hs_cell_introduce1_data_clear(&intro1_data);
|
||||
memwipe(payload, 0, sizeof(payload));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On
|
||||
* success, 0 is returned else -1 and the circuit is marked for close. */
|
||||
int
|
||||
hs_circ_send_establish_rendezvous(origin_circuit_t *circ)
|
||||
{
|
||||
ssize_t cell_len = 0;
|
||||
uint8_t cell[RELAY_PAYLOAD_SIZE] = {0};
|
||||
|
||||
tor_assert(circ);
|
||||
tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
|
||||
|
||||
log_info(LD_REND, "Send an ESTABLISH_RENDEZVOUS cell on circuit %u",
|
||||
TO_CIRCUIT(circ)->n_circ_id);
|
||||
|
||||
/* Set timestamp_dirty, because circuit_expire_building expects it,
|
||||
* and the rend cookie also means we've used the circ. */
|
||||
TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
|
||||
|
||||
/* We've attempted to use this circuit. Probe it if we fail */
|
||||
pathbias_count_use_attempt(circ);
|
||||
|
||||
/* Generate the RENDEZVOUS_COOKIE and place it in the identifier so we can
|
||||
* complete the handshake when receiving the acknowledgement. */
|
||||
crypto_rand((char *) circ->hs_ident->rendezvous_cookie, HS_REND_COOKIE_LEN);
|
||||
/* Generate the client keypair. No need to be extra strong, not long term */
|
||||
curve25519_keypair_generate(&circ->hs_ident->rendezvous_client_kp, 0);
|
||||
|
||||
cell_len =
|
||||
hs_cell_build_establish_rendezvous(circ->hs_ident->rendezvous_cookie,
|
||||
cell);
|
||||
if (BUG(cell_len < 0)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
|
||||
RELAY_COMMAND_ESTABLISH_RENDEZVOUS,
|
||||
(const char *) cell, cell_len,
|
||||
circ->cpath->prev) < 0) {
|
||||
/* Circuit has been marked for close */
|
||||
log_warn(LD_REND, "Unable to send ESTABLISH_RENDEZVOUS cell on "
|
||||
"circuit %u", TO_CIRCUIT(circ)->n_circ_id);
|
||||
memwipe(cell, 0, cell_len);
|
||||
goto err;
|
||||
}
|
||||
|
||||
memwipe(cell, 0, cell_len);
|
||||
return 0;
|
||||
err:
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,11 @@ int hs_circ_handle_introduce2(const hs_service_t *service,
|
||||
hs_service_intro_point_t *ip,
|
||||
const uint8_t *subcredential,
|
||||
const uint8_t *payload, size_t payload_len);
|
||||
int hs_circ_send_introduce1(origin_circuit_t *intro_circ,
|
||||
origin_circuit_t *rend_circ,
|
||||
const hs_desc_intro_point_t *ip,
|
||||
const uint8_t *subcredential);
|
||||
int hs_circ_send_establish_rendezvous(origin_circuit_t *circ);
|
||||
|
||||
/* e2e circuit API. */
|
||||
|
||||
|
@ -5,8 +5,10 @@
|
||||
* \file hs_circuitmap.c
|
||||
*
|
||||
* \brief Hidden service circuitmap: A hash table that maps binary tokens to
|
||||
* introduction and rendezvous circuits; it's used both by relays acting as
|
||||
* intro points and rendezvous points, and also by hidden services themselves.
|
||||
* introduction and rendezvous circuits; it's used:
|
||||
* (a) by relays acting as intro points and rendezvous points
|
||||
* (b) by hidden services to find intro and rend circuits and
|
||||
* (c) by HS clients to find rendezvous circuits.
|
||||
**/
|
||||
|
||||
#define HS_CIRCUITMAP_PRIVATE
|
||||
@ -404,6 +406,37 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie)
|
||||
return circ;
|
||||
}
|
||||
|
||||
/* Public function: Return client-side rendezvous circuit with rendezvous
|
||||
* <b>cookie</b>. It will first lookup for the CIRCUIT_PURPOSE_C_REND_READY
|
||||
* purpose and then try for CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED and then
|
||||
* finally tries for CIRCUIT_PURPOSE_C_ESTABLISH_REND.
|
||||
*
|
||||
* Return NULL if no such circuit is found in the circuitmap. */
|
||||
origin_circuit_t *
|
||||
hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie)
|
||||
{
|
||||
origin_circuit_t *circ = NULL;
|
||||
|
||||
circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
|
||||
REND_TOKEN_LEN, cookie,
|
||||
CIRCUIT_PURPOSE_C_REND_READY);
|
||||
if (circ) {
|
||||
return circ;
|
||||
}
|
||||
|
||||
circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
|
||||
REND_TOKEN_LEN, cookie,
|
||||
CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
|
||||
if (circ) {
|
||||
return circ;
|
||||
}
|
||||
|
||||
circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
|
||||
REND_TOKEN_LEN, cookie,
|
||||
CIRCUIT_PURPOSE_C_ESTABLISH_REND);
|
||||
return circ;
|
||||
}
|
||||
|
||||
/**** Public servide-side setters: */
|
||||
|
||||
/* Public function: Register v2 intro circuit with key <b>digest</b> to the
|
||||
@ -439,6 +472,21 @@ hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ,
|
||||
REND_TOKEN_LEN, cookie);
|
||||
}
|
||||
|
||||
/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
|
||||
* client-side circuitmap. */
|
||||
void
|
||||
hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ,
|
||||
const uint8_t *cookie)
|
||||
{
|
||||
circuit_t *circ = TO_CIRCUIT(or_circ);
|
||||
{ /* Basic circ purpose sanity checking */
|
||||
tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
|
||||
}
|
||||
|
||||
hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE,
|
||||
REND_TOKEN_LEN, cookie);
|
||||
}
|
||||
|
||||
/**** Misc public functions: */
|
||||
|
||||
/** Public function: Remove this circuit from the HS circuitmap. Clear its HS
|
||||
|
@ -43,6 +43,8 @@ struct origin_circuit_t *
|
||||
hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest);
|
||||
struct origin_circuit_t *
|
||||
hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie);
|
||||
struct origin_circuit_t *
|
||||
hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie);
|
||||
|
||||
void hs_circuitmap_register_intro_circ_v2_service_side(
|
||||
struct origin_circuit_t *circ,
|
||||
@ -53,6 +55,9 @@ void hs_circuitmap_register_intro_circ_v3_service_side(
|
||||
void hs_circuitmap_register_rend_circ_service_side(
|
||||
struct origin_circuit_t *circ,
|
||||
const uint8_t *cookie);
|
||||
void hs_circuitmap_register_rend_circ_client_side(
|
||||
struct origin_circuit_t *circ,
|
||||
const uint8_t *cookie);
|
||||
|
||||
void hs_circuitmap_remove_circuit(struct circuit_t *circ);
|
||||
|
||||
@ -76,6 +81,9 @@ typedef enum {
|
||||
HS_TOKEN_INTRO_V2_SERVICE_SIDE,
|
||||
/** A v3 introduction point pubkey on a hidden service (256bit) */
|
||||
HS_TOKEN_INTRO_V3_SERVICE_SIDE,
|
||||
|
||||
/** A rendezvous cookie on the client side (128bit) */
|
||||
HS_TOKEN_REND_CLIENT_SIDE,
|
||||
} hs_token_type_t;
|
||||
|
||||
/** Represents a token used in the HS protocol. Each such token maps to a
|
||||
|
1181
src/or/hs_client.c
1181
src/or/hs_client.c
File diff suppressed because it is too large
Load Diff
@ -9,8 +9,42 @@
|
||||
#ifndef TOR_HS_CLIENT_H
|
||||
#define TOR_HS_CLIENT_H
|
||||
|
||||
#include "crypto_ed25519.h"
|
||||
#include "hs_descriptor.h"
|
||||
#include "hs_ident.h"
|
||||
|
||||
void hs_client_note_connection_attempt_succeeded(
|
||||
const edge_connection_t *conn);
|
||||
|
||||
int hs_client_decode_descriptor(
|
||||
const char *desc_str,
|
||||
const ed25519_public_key_t *service_identity_pk,
|
||||
hs_descriptor_t **desc);
|
||||
int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
|
||||
const hs_descriptor_t *desc);
|
||||
int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk);
|
||||
|
||||
int hs_client_send_introduce1(origin_circuit_t *intro_circ,
|
||||
origin_circuit_t *rend_circ);
|
||||
|
||||
void hs_client_circuit_has_opened(origin_circuit_t *circ);
|
||||
|
||||
int hs_client_receive_rendezvous_acked(origin_circuit_t *circ,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len);
|
||||
int hs_client_receive_introduce_ack(origin_circuit_t *circ,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len);
|
||||
int hs_client_receive_rendezvous2(origin_circuit_t *circ,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len);
|
||||
|
||||
void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident);
|
||||
|
||||
extend_info_t *hs_client_get_random_intro_from_edge(
|
||||
const edge_connection_t *edge_conn);
|
||||
|
||||
int hs_client_reextend_intro_circuit(origin_circuit_t *circ);
|
||||
|
||||
#endif /* TOR_HS_CLIENT_H */
|
||||
|
||||
|
@ -14,17 +14,25 @@
|
||||
#include "or.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "networkstatus.h"
|
||||
#include "nodelist.h"
|
||||
#include "hs_cache.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_ident.h"
|
||||
#include "hs_service.h"
|
||||
#include "policies.h"
|
||||
#include "rendcommon.h"
|
||||
#include "rendservice.h"
|
||||
#include "routerset.h"
|
||||
#include "router.h"
|
||||
#include "routerset.h"
|
||||
#include "shared_random.h"
|
||||
#include "shared_random_state.h"
|
||||
|
||||
/* Trunnel */
|
||||
#include "ed25519_cert.h"
|
||||
|
||||
/* Ed25519 Basepoint value. Taken from section 5 of
|
||||
* https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */
|
||||
static const char *str_ed25519_basepoint =
|
||||
@ -1180,9 +1188,10 @@ hs_get_hsdir_spread_store(void)
|
||||
}
|
||||
|
||||
/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index.
|
||||
* If <b>is_for_next_period</b> is set, also check the next HSDir index field.
|
||||
* Return 0 if everything is as expected, else return -1. */
|
||||
static int
|
||||
node_has_hsdir_index(const node_t *node)
|
||||
node_has_hsdir_index(const node_t *node, int is_for_next_period)
|
||||
{
|
||||
tor_assert(node_supports_v3_hsdir(node));
|
||||
|
||||
@ -1200,6 +1209,12 @@ node_has_hsdir_index(const node_t *node)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_for_next_period &&
|
||||
BUG(tor_mem_is_zero((const char*)node->hsdir_index->next,
|
||||
DIGEST256_LEN))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1244,7 +1259,7 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
|
||||
node_t *n = node_get_mutable_by_id(rs->identity_digest);
|
||||
tor_assert(n);
|
||||
if (node_supports_v3_hsdir(n) && rs->is_hs_dir) {
|
||||
if (!node_has_hsdir_index(n)) {
|
||||
if (!node_has_hsdir_index(n, is_next_period)) {
|
||||
log_info(LD_GENERAL, "Node %s was found without hsdir index.",
|
||||
node_describe(n));
|
||||
continue;
|
||||
@ -1313,6 +1328,361 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
|
||||
smartlist_free(sorted_nodes);
|
||||
}
|
||||
|
||||
/*********************** HSDir request tracking ***************************/
|
||||
|
||||
/** Return the period for which a hidden service directory cannot be queried
|
||||
* for the same descriptor ID again, taking TestingTorNetwork into account. */
|
||||
time_t
|
||||
hs_hsdir_requery_period(const or_options_t *options)
|
||||
{
|
||||
tor_assert(options);
|
||||
|
||||
if (options->TestingTorNetwork) {
|
||||
return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING;
|
||||
} else {
|
||||
return REND_HID_SERV_DIR_REQUERY_PERIOD;
|
||||
}
|
||||
}
|
||||
|
||||
/** Tracks requests for fetching hidden service descriptors. It's used by
|
||||
* hidden service clients, to avoid querying HSDirs that have already failed
|
||||
* giving back a descriptor. The same data structure is used to track both v2
|
||||
* and v3 HS descriptor requests.
|
||||
*
|
||||
* The string map is a key/value store that contains the last request times to
|
||||
* hidden service directories for certain queries. Specifically:
|
||||
*
|
||||
* key = base32(hsdir_identity) + base32(hs_identity)
|
||||
* value = time_t of last request for that hs_identity to that HSDir
|
||||
*
|
||||
* where 'hsdir_identity' is the identity digest of the HSDir node, and
|
||||
* 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519
|
||||
* identity public key of the HS in the v3 case. */
|
||||
static strmap_t *last_hid_serv_requests_ = NULL;
|
||||
|
||||
/** Returns last_hid_serv_requests_, initializing it to a new strmap if
|
||||
* necessary. */
|
||||
STATIC strmap_t *
|
||||
get_last_hid_serv_requests(void)
|
||||
{
|
||||
if (!last_hid_serv_requests_)
|
||||
last_hid_serv_requests_ = strmap_new();
|
||||
return last_hid_serv_requests_;
|
||||
}
|
||||
|
||||
/** Look up the last request time to hidden service directory <b>hs_dir</b>
|
||||
* for descriptor request key <b>req_key_str</b> which is the descriptor ID
|
||||
* for a v2 service or the blinded key for v3. If <b>set</b> is non-zero,
|
||||
* assign the current time <b>now</b> and return that. Otherwise, return the
|
||||
* most recent request time, or 0 if no such request has been sent before. */
|
||||
time_t
|
||||
hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir,
|
||||
const char *req_key_str,
|
||||
time_t now, int set)
|
||||
{
|
||||
char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
|
||||
char *hsdir_desc_comb_id = NULL;
|
||||
time_t *last_request_ptr;
|
||||
strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
|
||||
|
||||
/* Create the key */
|
||||
base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32),
|
||||
hs_dir->identity_digest, DIGEST_LEN);
|
||||
tor_asprintf(&hsdir_desc_comb_id, "%s%s", hsdir_id_base32, req_key_str);
|
||||
|
||||
if (set) {
|
||||
time_t *oldptr;
|
||||
last_request_ptr = tor_malloc_zero(sizeof(time_t));
|
||||
*last_request_ptr = now;
|
||||
oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id,
|
||||
last_request_ptr);
|
||||
tor_free(oldptr);
|
||||
} else {
|
||||
last_request_ptr = strmap_get(last_hid_serv_requests,
|
||||
hsdir_desc_comb_id);
|
||||
}
|
||||
|
||||
tor_free(hsdir_desc_comb_id);
|
||||
return (last_request_ptr) ? *last_request_ptr : 0;
|
||||
}
|
||||
|
||||
/** Clean the history of request times to hidden service directories, so that
|
||||
* it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD
|
||||
* seconds any more. */
|
||||
void
|
||||
hs_clean_last_hid_serv_requests(time_t now)
|
||||
{
|
||||
strmap_iter_t *iter;
|
||||
time_t cutoff = now - hs_hsdir_requery_period(get_options());
|
||||
strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
|
||||
for (iter = strmap_iter_init(last_hid_serv_requests);
|
||||
!strmap_iter_done(iter); ) {
|
||||
const char *key;
|
||||
void *val;
|
||||
time_t *ent;
|
||||
strmap_iter_get(iter, &key, &val);
|
||||
ent = (time_t *) val;
|
||||
if (*ent < cutoff) {
|
||||
iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
|
||||
tor_free(ent);
|
||||
} else {
|
||||
iter = strmap_iter_next(last_hid_serv_requests, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove all requests related to the descriptor request key string
|
||||
* <b>req_key_str</b> from the history of times of requests to hidden service
|
||||
* directories.
|
||||
*
|
||||
* This is called from rend_client_note_connection_attempt_ended(), which
|
||||
* must be idempotent, so any future changes to this function must leave it
|
||||
* idempotent too. */
|
||||
void
|
||||
hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str)
|
||||
{
|
||||
strmap_iter_t *iter;
|
||||
strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
|
||||
|
||||
for (iter = strmap_iter_init(last_hid_serv_requests);
|
||||
!strmap_iter_done(iter); ) {
|
||||
const char *key;
|
||||
void *val;
|
||||
strmap_iter_get(iter, &key, &val);
|
||||
|
||||
/* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of
|
||||
* semantic, see #23305. */
|
||||
|
||||
/* Length check on the strings we are about to compare. The "key" contains
|
||||
* both the base32 HSDir identity digest and the requested key at the
|
||||
* directory. The "req_key_str" can either be a base32 descriptor ID or a
|
||||
* base64 blinded key which should be the second part of "key". BUG on
|
||||
* this check because both strings are internally controlled so this
|
||||
* should never happen. */
|
||||
if (BUG((strlen(req_key_str) + REND_DESC_ID_V2_LEN_BASE32) <
|
||||
strlen(key))) {
|
||||
iter = strmap_iter_next(last_hid_serv_requests, iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if the tracked request matches our request key */
|
||||
if (tor_memeq(key + REND_DESC_ID_V2_LEN_BASE32, req_key_str,
|
||||
strlen(req_key_str))) {
|
||||
iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
|
||||
tor_free(val);
|
||||
} else {
|
||||
iter = strmap_iter_next(last_hid_serv_requests, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Purge the history of request times to hidden service directories,
|
||||
* so that future lookups of an HS descriptor will not fail because we
|
||||
* accessed all of the HSDir relays responsible for the descriptor
|
||||
* recently. */
|
||||
void
|
||||
hs_purge_last_hid_serv_requests(void)
|
||||
{
|
||||
/* Don't create the table if it doesn't exist yet (and it may very
|
||||
* well not exist if the user hasn't accessed any HSes)... */
|
||||
strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_;
|
||||
/* ... and let get_last_hid_serv_requests re-create it for us if
|
||||
* necessary. */
|
||||
last_hid_serv_requests_ = NULL;
|
||||
|
||||
if (old_last_hid_serv_requests != NULL) {
|
||||
log_info(LD_REND, "Purging client last-HS-desc-request-time table");
|
||||
strmap_free(old_last_hid_serv_requests, tor_free_);
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
/** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the
|
||||
* one that we should use to fetch a descriptor right now. Take into account
|
||||
* previous failed attempts at fetching this descriptor from HSDirs using the
|
||||
* string identifier <b>req_key_str</b>.
|
||||
*
|
||||
* Steals ownership of <b>responsible_dirs</b>.
|
||||
*
|
||||
* Return the routerstatus of the chosen HSDir if successful, otherwise return
|
||||
* NULL if no HSDirs are worth trying right now. */
|
||||
routerstatus_t *
|
||||
hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str)
|
||||
{
|
||||
smartlist_t *usable_responsible_dirs = smartlist_new();
|
||||
const or_options_t *options = get_options();
|
||||
routerstatus_t *hs_dir;
|
||||
time_t now = time(NULL);
|
||||
int excluded_some;
|
||||
|
||||
tor_assert(req_key_str);
|
||||
|
||||
/* Clean outdated request history first. */
|
||||
hs_clean_last_hid_serv_requests(now);
|
||||
|
||||
/* Only select those hidden service directories to which we did not send a
|
||||
* request recently and for which we have a router descriptor here. */
|
||||
SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) {
|
||||
time_t last = hs_lookup_last_hid_serv_request(dir, req_key_str, 0, 0);
|
||||
const node_t *node = node_get_by_id(dir->identity_digest);
|
||||
if (last + hs_hsdir_requery_period(options) >= now ||
|
||||
!node || !node_has_descriptor(node)) {
|
||||
SMARTLIST_DEL_CURRENT(responsible_dirs, dir);
|
||||
continue;
|
||||
}
|
||||
if (!routerset_contains_node(options->ExcludeNodes, node)) {
|
||||
smartlist_add(usable_responsible_dirs, dir);
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(dir);
|
||||
|
||||
excluded_some =
|
||||
smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs);
|
||||
|
||||
hs_dir = smartlist_choose(usable_responsible_dirs);
|
||||
if (!hs_dir && !options->StrictNodes) {
|
||||
hs_dir = smartlist_choose(responsible_dirs);
|
||||
}
|
||||
|
||||
smartlist_free(responsible_dirs);
|
||||
smartlist_free(usable_responsible_dirs);
|
||||
if (!hs_dir) {
|
||||
log_info(LD_REND, "Could not pick one of the responsible hidden "
|
||||
"service directories, because we requested them all "
|
||||
"recently without success.");
|
||||
if (options->StrictNodes && excluded_some) {
|
||||
log_warn(LD_REND, "Could not pick a hidden service directory for the "
|
||||
"requested hidden service: they are all either down or "
|
||||
"excluded, and StrictNodes is set.");
|
||||
}
|
||||
} else {
|
||||
/* Remember that we are requesting a descriptor from this hidden service
|
||||
* directory now. */
|
||||
hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1);
|
||||
}
|
||||
|
||||
return hs_dir;
|
||||
}
|
||||
|
||||
/* From a list of link specifier, an onion key and if we are requesting a
|
||||
* direct connection (ex: single onion service), return a newly allocated
|
||||
* extend_info_t object. This function checks the firewall policies and if we
|
||||
* are allowed to extend to the chosen address.
|
||||
*
|
||||
* if either IPv4 or legacy ID is missing, error.
|
||||
* if not direct_conn, IPv4 is prefered.
|
||||
* if direct_conn, IPv6 is prefered if we have one available.
|
||||
* if firewall does not allow the chosen address, error.
|
||||
*
|
||||
* Return NULL if we can fulfill the conditions. */
|
||||
extend_info_t *
|
||||
hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
|
||||
const curve25519_public_key_t *onion_key,
|
||||
int direct_conn)
|
||||
{
|
||||
int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
|
||||
char legacy_id[DIGEST_LEN] = {0};
|
||||
uint16_t port_v4 = 0, port_v6 = 0, port = 0;
|
||||
tor_addr_t addr_v4, addr_v6, *addr = NULL;
|
||||
ed25519_public_key_t ed25519_pk;
|
||||
extend_info_t *info = NULL;
|
||||
|
||||
tor_assert(lspecs);
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(lspecs, const link_specifier_t *, ls) {
|
||||
switch (link_specifier_get_ls_type(ls)) {
|
||||
case LS_IPV4:
|
||||
/* Skip if we already seen a v4. */
|
||||
if (have_v4) continue;
|
||||
tor_addr_from_ipv4h(&addr_v4,
|
||||
link_specifier_get_un_ipv4_addr(ls));
|
||||
port_v4 = link_specifier_get_un_ipv4_port(ls);
|
||||
have_v4 = 1;
|
||||
break;
|
||||
case LS_IPV6:
|
||||
/* Skip if we already seen a v6. */
|
||||
if (have_v6) continue;
|
||||
tor_addr_from_ipv6_bytes(&addr_v6,
|
||||
(const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
|
||||
port_v6 = link_specifier_get_un_ipv6_port(ls);
|
||||
have_v6 = 1;
|
||||
break;
|
||||
case LS_LEGACY_ID:
|
||||
/* Make sure we do have enough bytes for the legacy ID. */
|
||||
if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
|
||||
break;
|
||||
}
|
||||
memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls),
|
||||
sizeof(legacy_id));
|
||||
have_legacy_id = 1;
|
||||
break;
|
||||
case LS_ED25519_ID:
|
||||
memcpy(ed25519_pk.pubkey,
|
||||
link_specifier_getconstarray_un_ed25519_id(ls),
|
||||
ED25519_PUBKEY_LEN);
|
||||
have_ed25519_id = 1;
|
||||
break;
|
||||
default:
|
||||
/* Ignore unknown. */
|
||||
break;
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(ls);
|
||||
|
||||
/* IPv4 and legacy ID are mandatory. */
|
||||
if (!have_v4 || !have_legacy_id) {
|
||||
goto done;
|
||||
}
|
||||
/* By default, we pick IPv4 but this might change to v6 if certain
|
||||
* conditions are met. */
|
||||
addr = &addr_v4; port = port_v4;
|
||||
|
||||
/* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
|
||||
* circuit so we can't extend in IPv6. And at this point, we do have an IPv4
|
||||
* address available so go to validation. */
|
||||
if (!direct_conn) {
|
||||
goto validate;
|
||||
}
|
||||
|
||||
/* From this point on, we have a request for a direct connection to the
|
||||
* rendezvous point so make sure we can actually connect through our
|
||||
* firewall. We'll prefer IPv6. */
|
||||
|
||||
/* IPv6 test. */
|
||||
if (have_v6 &&
|
||||
fascist_firewall_allows_address_addr(&addr_v6, port_v6,
|
||||
FIREWALL_OR_CONNECTION, 1, 1)) {
|
||||
/* Direct connection and we can reach it in IPv6 so go for it. */
|
||||
addr = &addr_v6; port = port_v6;
|
||||
goto validate;
|
||||
}
|
||||
/* IPv4 test and we are sure we have a v4 because of the check above. */
|
||||
if (fascist_firewall_allows_address_addr(&addr_v4, port_v4,
|
||||
FIREWALL_OR_CONNECTION, 0, 0)) {
|
||||
/* Direct connection and we can reach it in IPv4 so go for it. */
|
||||
addr = &addr_v4; port = port_v4;
|
||||
goto validate;
|
||||
}
|
||||
|
||||
validate:
|
||||
/* We'll validate now that the address we've picked isn't a private one. If
|
||||
* it is, are we allowing to extend to private address? */
|
||||
if (!extend_info_addr_is_allowed(addr)) {
|
||||
log_warn(LD_REND, "Requested address is private and it is not "
|
||||
"allowed to extend to it: %s:%u",
|
||||
fmt_addr(&addr_v4), port_v4);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* We do have everything for which we think we can connect successfully. */
|
||||
info = extend_info_new(NULL, legacy_id,
|
||||
(have_ed25519_id) ? &ed25519_pk : NULL, NULL,
|
||||
onion_key, addr, port);
|
||||
done:
|
||||
return info;
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
/* Initialize the entire HS subsytem. This is called in tor_init() before any
|
||||
* torrc options are loaded. Only for >= v3. */
|
||||
void
|
||||
|
@ -187,6 +187,8 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data,
|
||||
const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
|
||||
size_t *len_out);
|
||||
|
||||
routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32);
|
||||
|
||||
void hs_get_subcredential(const ed25519_public_key_t *identity_pk,
|
||||
const ed25519_public_key_t *blinded_pk,
|
||||
uint8_t *subcred_out);
|
||||
@ -219,18 +221,40 @@ int32_t hs_get_hsdir_spread_store(void);
|
||||
void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
|
||||
uint64_t time_period_num, int is_next_period,
|
||||
int is_client, smartlist_t *responsible_dirs);
|
||||
routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs,
|
||||
const char *req_key_str);
|
||||
|
||||
time_t hs_hsdir_requery_period(const or_options_t *options);
|
||||
time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir,
|
||||
const char *desc_id_base32,
|
||||
time_t now, int set);
|
||||
void hs_clean_last_hid_serv_requests(time_t now);
|
||||
void hs_purge_hid_serv_from_last_hid_serv_requests(const char *desc_id);
|
||||
void hs_purge_last_hid_serv_requests(void);
|
||||
|
||||
int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn);
|
||||
|
||||
void hs_inc_rdv_stream_counter(origin_circuit_t *circ);
|
||||
void hs_dec_rdv_stream_counter(origin_circuit_t *circ);
|
||||
|
||||
extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
|
||||
const curve25519_public_key_t *onion_key,
|
||||
int direct_conn);
|
||||
|
||||
#ifdef HS_COMMON_PRIVATE
|
||||
|
||||
STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out);
|
||||
|
||||
/** The period for which a hidden service directory cannot be queried for
|
||||
* the same descriptor ID again. */
|
||||
#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60)
|
||||
/** Test networks generate a new consensus every 5 or 10 seconds.
|
||||
* So allow them to requery HSDirs much faster. */
|
||||
#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5)
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
STATIC strmap_t *get_last_hid_serv_requests(void);
|
||||
STATIC uint64_t get_time_period_length(void);
|
||||
|
||||
STATIC uint8_t *get_first_cached_disaster_srv(void);
|
||||
|
@ -55,11 +55,10 @@
|
||||
/* For unit tests.*/
|
||||
#define HS_DESCRIPTOR_PRIVATE
|
||||
|
||||
#include "hs_descriptor.h"
|
||||
|
||||
#include "or.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "ed25519_cert.h" /* Trunnel interface. */
|
||||
#include "hs_descriptor.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "parsecommon.h"
|
||||
#include "rendcache.h"
|
||||
#include "hs_cache.h"
|
||||
@ -332,50 +331,10 @@ encode_link_specifiers(const smartlist_t *specs)
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *,
|
||||
spec) {
|
||||
link_specifier_t *ls = link_specifier_new();
|
||||
link_specifier_set_ls_type(ls, spec->type);
|
||||
|
||||
switch (spec->type) {
|
||||
case LS_IPV4:
|
||||
link_specifier_set_un_ipv4_addr(ls,
|
||||
tor_addr_to_ipv4h(&spec->u.ap.addr));
|
||||
link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
|
||||
/* Four bytes IPv4 and two bytes port. */
|
||||
link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
|
||||
sizeof(spec->u.ap.port));
|
||||
break;
|
||||
case LS_IPV6:
|
||||
{
|
||||
size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
|
||||
const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
|
||||
uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
|
||||
memcpy(ipv6_array, in6_addr, addr_len);
|
||||
link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
|
||||
/* Sixteen bytes IPv6 and two bytes port. */
|
||||
link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
|
||||
break;
|
||||
}
|
||||
case LS_LEGACY_ID:
|
||||
{
|
||||
size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
|
||||
uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
|
||||
memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
|
||||
link_specifier_set_ls_len(ls, legacy_id_len);
|
||||
break;
|
||||
}
|
||||
case LS_ED25519_ID:
|
||||
{
|
||||
size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls);
|
||||
uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls);
|
||||
memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len);
|
||||
link_specifier_set_ls_len(ls, ed25519_id_len);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
tor_assert(0);
|
||||
}
|
||||
|
||||
link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec);
|
||||
if (ls) {
|
||||
link_specifier_list_add_spec(lslist, ls);
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(spec);
|
||||
|
||||
{
|
||||
@ -2358,10 +2317,10 @@ static int
|
||||
*
|
||||
* Return 0 on success and encoded_out is a valid pointer. On error, -1 is
|
||||
* returned and encoded_out is set to NULL. */
|
||||
int
|
||||
hs_desc_encode_descriptor(const hs_descriptor_t *desc,
|
||||
MOCK_IMPL(int,
|
||||
hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
|
||||
const ed25519_keypair_t *signing_kp,
|
||||
char **encoded_out)
|
||||
char **encoded_out))
|
||||
{
|
||||
int ret = -1;
|
||||
uint32_t version;
|
||||
@ -2438,6 +2397,37 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data)
|
||||
data->superencrypted_blob_size);
|
||||
}
|
||||
|
||||
/* Return the size in bytes of the given encrypted data object. Used by OOM
|
||||
* subsystem. */
|
||||
static size_t
|
||||
hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data)
|
||||
{
|
||||
tor_assert(data);
|
||||
size_t intro_size = 0;
|
||||
if (data->intro_auth_types) {
|
||||
intro_size +=
|
||||
smartlist_len(data->intro_auth_types) * sizeof(intro_auth_types);
|
||||
}
|
||||
if (data->intro_points) {
|
||||
/* XXX could follow pointers here and get more accurate size */
|
||||
intro_size +=
|
||||
smartlist_len(data->intro_points) * sizeof(hs_desc_intro_point_t);
|
||||
}
|
||||
|
||||
return sizeof(*data) + intro_size;
|
||||
}
|
||||
|
||||
/* Return the size in bytes of the given descriptor object. Used by OOM
|
||||
* subsystem. */
|
||||
size_t
|
||||
hs_desc_obj_size(const hs_descriptor_t *data)
|
||||
{
|
||||
tor_assert(data);
|
||||
return (hs_desc_plaintext_obj_size(&data->plaintext_data) +
|
||||
hs_desc_encrypted_obj_size(&data->encrypted_data) +
|
||||
sizeof(data->subcredential));
|
||||
}
|
||||
|
||||
/* Return a newly allocated descriptor intro point. */
|
||||
hs_desc_intro_point_t *
|
||||
hs_desc_intro_point_new(void)
|
||||
@ -2545,3 +2535,59 @@ hs_descriptor_clear_intro_points(hs_descriptor_t *desc)
|
||||
}
|
||||
}
|
||||
|
||||
/* From a descriptor link specifier object spec, returned a newly allocated
|
||||
* link specifier object that is the encoded representation of spec. Return
|
||||
* NULL on error. */
|
||||
link_specifier_t *
|
||||
hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec)
|
||||
{
|
||||
tor_assert(spec);
|
||||
|
||||
link_specifier_t *ls = link_specifier_new();
|
||||
link_specifier_set_ls_type(ls, spec->type);
|
||||
|
||||
switch (spec->type) {
|
||||
case LS_IPV4:
|
||||
link_specifier_set_un_ipv4_addr(ls,
|
||||
tor_addr_to_ipv4h(&spec->u.ap.addr));
|
||||
link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
|
||||
/* Four bytes IPv4 and two bytes port. */
|
||||
link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
|
||||
sizeof(spec->u.ap.port));
|
||||
break;
|
||||
case LS_IPV6:
|
||||
{
|
||||
size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
|
||||
const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
|
||||
uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
|
||||
memcpy(ipv6_array, in6_addr, addr_len);
|
||||
link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
|
||||
/* Sixteen bytes IPv6 and two bytes port. */
|
||||
link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
|
||||
break;
|
||||
}
|
||||
case LS_LEGACY_ID:
|
||||
{
|
||||
size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
|
||||
uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
|
||||
memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
|
||||
link_specifier_set_ls_len(ls, legacy_id_len);
|
||||
break;
|
||||
}
|
||||
case LS_ED25519_ID:
|
||||
{
|
||||
size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls);
|
||||
uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls);
|
||||
memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len);
|
||||
link_specifier_set_ls_len(ls, ed25519_id_len);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
tor_assert_nonfatal_unreached();
|
||||
link_specifier_free(ls);
|
||||
ls = NULL;
|
||||
}
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,9 @@
|
||||
#include "crypto_ed25519.h"
|
||||
#include "torcert.h"
|
||||
|
||||
/* Trunnel */
|
||||
struct link_specifier_t;
|
||||
|
||||
/* The earliest descriptor format version we support. */
|
||||
#define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3
|
||||
/* The latest descriptor format version we support. */
|
||||
@ -211,9 +214,10 @@ hs_desc_link_specifier_t *hs_desc_link_specifier_new(
|
||||
const extend_info_t *info, uint8_t type);
|
||||
void hs_descriptor_clear_intro_points(hs_descriptor_t *desc);
|
||||
|
||||
int hs_desc_encode_descriptor(const hs_descriptor_t *desc,
|
||||
MOCK_DECL(int,
|
||||
hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
|
||||
const ed25519_keypair_t *signing_kp,
|
||||
char **encoded_out);
|
||||
char **encoded_out));
|
||||
|
||||
int hs_desc_decode_descriptor(const char *encoded,
|
||||
const uint8_t *subcredential,
|
||||
@ -223,11 +227,15 @@ int hs_desc_decode_plaintext(const char *encoded,
|
||||
int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
|
||||
hs_desc_encrypted_data_t *desc_out);
|
||||
|
||||
size_t hs_desc_obj_size(const hs_descriptor_t *data);
|
||||
size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data);
|
||||
|
||||
hs_desc_intro_point_t *hs_desc_intro_point_new(void);
|
||||
void hs_desc_intro_point_free(hs_desc_intro_point_t *ip);
|
||||
|
||||
link_specifier_t *hs_desc_lspec_to_trunnel(
|
||||
const hs_desc_link_specifier_t *spec);
|
||||
|
||||
#ifdef HS_DESCRIPTOR_PRIVATE
|
||||
|
||||
/* Encoding. */
|
||||
|
@ -86,3 +86,25 @@ hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident)
|
||||
tor_free(ident);
|
||||
}
|
||||
|
||||
/* Return true if the given ident is valid for an introduction circuit. */
|
||||
int
|
||||
hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident)
|
||||
{
|
||||
if (ident == NULL) {
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
if (ed25519_public_key_is_zero(&ident->identity_pk)) {
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
if (ed25519_public_key_is_zero(&ident->intro_auth_pk)) {
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
/* Valid. */
|
||||
return 1;
|
||||
invalid:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -126,5 +126,8 @@ hs_ident_edge_conn_t *hs_ident_edge_conn_new(
|
||||
const ed25519_public_key_t *identity_pk);
|
||||
void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident);
|
||||
|
||||
/* Validators */
|
||||
int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident);
|
||||
|
||||
#endif /* TOR_HS_IDENT_H */
|
||||
|
||||
|
@ -972,6 +972,10 @@ service_descriptor_free(hs_service_descriptor_t *desc)
|
||||
/* Cleanup all intro points. */
|
||||
digest256map_free(desc->intro_points.map, service_intro_point_free_);
|
||||
digestmap_free(desc->intro_points.failed_id, tor_free_);
|
||||
if (desc->previous_hsdirs) {
|
||||
SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s));
|
||||
smartlist_free(desc->previous_hsdirs);
|
||||
}
|
||||
tor_free(desc);
|
||||
}
|
||||
|
||||
@ -985,6 +989,7 @@ service_descriptor_new(void)
|
||||
sdesc->intro_points.map = digest256map_new();
|
||||
sdesc->intro_points.failed_id = digestmap_new();
|
||||
sdesc->hsdir_missing_info = smartlist_new();
|
||||
sdesc->previous_hsdirs = smartlist_new();
|
||||
return sdesc;
|
||||
}
|
||||
|
||||
@ -1511,6 +1516,52 @@ pick_needed_intro_points(hs_service_t *service,
|
||||
return i;
|
||||
}
|
||||
|
||||
/** Clear previous cached HSDirs in <b>desc</b>. */
|
||||
static void
|
||||
service_desc_clear_previous_hsdirs(hs_service_descriptor_t *desc)
|
||||
{
|
||||
if (BUG(!desc->previous_hsdirs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SMARTLIST_FOREACH(desc->previous_hsdirs, char*, s, tor_free(s));
|
||||
smartlist_clear(desc->previous_hsdirs);
|
||||
}
|
||||
|
||||
/** Note that we attempted to upload <b>desc</b> to <b>hsdir</b>. */
|
||||
static void
|
||||
service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir)
|
||||
{
|
||||
char b64_digest[BASE64_DIGEST_LEN+1] = {0};
|
||||
digest_to_base64(b64_digest, hsdir->identity);
|
||||
|
||||
if (BUG(!desc->previous_hsdirs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) {
|
||||
smartlist_add_strdup(desc->previous_hsdirs, b64_digest);
|
||||
smartlist_sort_strings(desc->previous_hsdirs);
|
||||
}
|
||||
}
|
||||
|
||||
/** Schedule an upload of <b>desc</b>. If <b>descriptor_changed</b> is set, it
|
||||
* means that this descriptor is dirty. */
|
||||
STATIC void
|
||||
service_desc_schedule_upload(hs_service_descriptor_t *desc,
|
||||
time_t now,
|
||||
int descriptor_changed)
|
||||
|
||||
{
|
||||
desc->next_upload_time = now;
|
||||
|
||||
/* If the descriptor changed, clean up the old HSDirs list. We want to
|
||||
* re-upload no matter what. */
|
||||
if (descriptor_changed) {
|
||||
service_desc_clear_previous_hsdirs(desc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the given descriptor from the given service. The possible update
|
||||
* actions includes:
|
||||
* - Picking missing intro points if needed.
|
||||
@ -1543,7 +1594,7 @@ update_service_descriptor(hs_service_t *service,
|
||||
/* We'll build those introduction point into the descriptor once we have
|
||||
* confirmation that the circuits are opened and ready. However,
|
||||
* indicate that this descriptor should be uploaded from now on. */
|
||||
desc->next_upload_time = now;
|
||||
service_desc_schedule_upload(desc, now, 1);
|
||||
}
|
||||
/* Were we able to pick all the intro points we needed? If not, we'll
|
||||
* flag the descriptor that it's missing intro points because it
|
||||
@ -1688,6 +1739,13 @@ rotate_all_descriptors(time_t now)
|
||||
* it in order to make sure we don't rotate at next check. */
|
||||
service->state.in_overlap_period = 1;
|
||||
|
||||
/* We just entered overlap period: recompute all HSDir indices. We need to
|
||||
* do this otherwise nodes can get stuck with old HSDir indices until we
|
||||
* fetch a new consensus, and we might need to reupload our desc before
|
||||
* that. */
|
||||
/* XXX find a better place than rotate_all_descriptors() to do this */
|
||||
nodelist_recompute_all_hsdir_indices();
|
||||
|
||||
/* If we have a next descriptor lined up, rotate the descriptors so that it
|
||||
* becomes current. */
|
||||
if (service->desc_next) {
|
||||
@ -1972,6 +2030,9 @@ upload_descriptor_to_hsdir(const hs_service_t *service,
|
||||
directory_initiate_request(dir_req);
|
||||
directory_request_free(dir_req);
|
||||
|
||||
/* Add this node to previous_hsdirs list */
|
||||
service_desc_note_upload(desc, hsdir);
|
||||
|
||||
/* Logging so we know where it was sent. */
|
||||
{
|
||||
int is_next_desc = (service->desc_next == desc);
|
||||
@ -2189,7 +2250,7 @@ set_descriptor_revision_counter(hs_descriptor_t *hs_desc)
|
||||
* responsible hidden service directories. If for_next_period is true, the set
|
||||
* of directories are selected using the next hsdir_index. This does nothing
|
||||
* if PublishHidServDescriptors is false. */
|
||||
static void
|
||||
STATIC void
|
||||
upload_descriptor_to_all(const hs_service_t *service,
|
||||
hs_service_descriptor_t *desc, int for_next_period)
|
||||
{
|
||||
@ -2288,6 +2349,17 @@ should_service_upload_descriptor(const hs_service_t *service,
|
||||
goto cannot;
|
||||
}
|
||||
|
||||
/* Don't upload desc if we don't have a live consensus */
|
||||
if (!networkstatus_get_live_consensus(now)) {
|
||||
goto cannot;
|
||||
}
|
||||
|
||||
/* Do we know enough router descriptors to have adequate vision of the HSDir
|
||||
hash ring? */
|
||||
if (!router_have_minimum_dir_info()) {
|
||||
goto cannot;
|
||||
}
|
||||
|
||||
/* Can upload! */
|
||||
return 1;
|
||||
cannot:
|
||||
@ -2618,10 +2690,88 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list)
|
||||
smartlist_add(list, hs_path_from_filename(s_dir, fname));
|
||||
}
|
||||
|
||||
/** The set of HSDirs have changed: check if the change affects our descriptor
|
||||
* HSDir placement, and if it does, reupload the desc. */
|
||||
static int
|
||||
service_desc_hsdirs_changed(const hs_service_t *service,
|
||||
const hs_service_descriptor_t *desc)
|
||||
{
|
||||
int retval = 0;
|
||||
smartlist_t *responsible_dirs = smartlist_new();
|
||||
smartlist_t *b64_responsible_dirs = smartlist_new();
|
||||
|
||||
/* No desc upload has happened yet: it will happen eventually */
|
||||
if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Get list of responsible hsdirs */
|
||||
hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
|
||||
service->desc_next == desc, 0, responsible_dirs);
|
||||
|
||||
/* Make a second list with their b64ed identity digests, so that we can
|
||||
* compare it with out previous list of hsdirs */
|
||||
SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) {
|
||||
char b64_digest[BASE64_DIGEST_LEN+1] = {0};
|
||||
digest_to_base64(b64_digest, hsdir_rs->identity_digest);
|
||||
smartlist_add_strdup(b64_responsible_dirs, b64_digest);
|
||||
} SMARTLIST_FOREACH_END(hsdir_rs);
|
||||
|
||||
/* Sort this new smartlist so that we can compare it with the other one */
|
||||
smartlist_sort_strings(b64_responsible_dirs);
|
||||
|
||||
/* Check whether the set of HSDirs changed */
|
||||
if (!smartlist_strings_eq(b64_responsible_dirs, desc->previous_hsdirs)) {
|
||||
log_info(LD_GENERAL, "Received new dirinfo and set of hsdirs changed!");
|
||||
retval = 1;
|
||||
} else {
|
||||
log_debug(LD_GENERAL, "No change in hsdir set!");
|
||||
}
|
||||
|
||||
done:
|
||||
smartlist_free(responsible_dirs);
|
||||
|
||||
SMARTLIST_FOREACH(b64_responsible_dirs, char*, s, tor_free(s));
|
||||
smartlist_free(b64_responsible_dirs);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* ========== */
|
||||
/* Public API */
|
||||
/* ========== */
|
||||
|
||||
/* We just received a new batch of descriptors which might affect the shape of
|
||||
* the HSDir hash ring. Signal that we should re-upload our HS descriptors. */
|
||||
void
|
||||
hs_hsdir_set_changed_consider_reupload(void)
|
||||
{
|
||||
time_t now = approx_time();
|
||||
|
||||
/* Check if HS subsystem is initialized */
|
||||
if (!hs_service_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Basic test: If we have not bootstrapped 100% yet, no point in even trying
|
||||
to upload descriptor. */
|
||||
if (!router_have_minimum_dir_info()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_info(LD_GENERAL, "Received new dirinfo: Checking hash ring for changes");
|
||||
|
||||
/* Go over all descriptors and check if the set of HSDirs changed for any of
|
||||
* them. Schedule reupload if so. */
|
||||
FOR_EACH_SERVICE_BEGIN(service) {
|
||||
FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
|
||||
if (service_desc_hsdirs_changed(service, desc)) {
|
||||
service_desc_schedule_upload(desc, now, 0);
|
||||
}
|
||||
} FOR_EACH_DESCRIPTOR_END;
|
||||
} FOR_EACH_SERVICE_END;
|
||||
}
|
||||
|
||||
/* Return the number of service we have configured and usable. */
|
||||
unsigned int
|
||||
hs_service_get_num_services(void)
|
||||
|
@ -129,6 +129,12 @@ typedef struct hs_service_descriptor_t {
|
||||
* list are re-tried to upload this descriptor when our directory information
|
||||
* have been updated. */
|
||||
smartlist_t *hsdir_missing_info;
|
||||
|
||||
/** List of the responsible HSDirs (their b64ed identity digest) last time we
|
||||
* uploaded this descriptor. If the set of responsible HSDirs is different
|
||||
* from this list, this means we received new dirinfo and we need to
|
||||
* reupload our descriptor. This list is always sorted lexicographically. */
|
||||
smartlist_t *previous_hsdirs;
|
||||
} hs_service_descriptor_t;
|
||||
|
||||
/* Service key material. */
|
||||
@ -260,6 +266,7 @@ void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
|
||||
smartlist_t *dir_list);
|
||||
int hs_service_set_conn_addr_port(const origin_circuit_t *circ,
|
||||
edge_connection_t *conn);
|
||||
void hs_hsdir_set_changed_consider_reupload(void);
|
||||
|
||||
void hs_service_dir_info_changed(void);
|
||||
void hs_service_run_scheduled_events(time_t now);
|
||||
@ -338,6 +345,14 @@ check_state_line_for_service_rev_counter(const char *state_line,
|
||||
STATIC int
|
||||
write_address_to_file(const hs_service_t *service, const char *fname_);
|
||||
|
||||
STATIC void upload_descriptor_to_all(const hs_service_t *service,
|
||||
hs_service_descriptor_t *desc,
|
||||
int for_next_period);
|
||||
|
||||
STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc,
|
||||
time_t now,
|
||||
int descriptor_changed);
|
||||
|
||||
#endif /* TOR_UNIT_TESTS */
|
||||
|
||||
#endif /* HS_SERVICE_PRIVATE */
|
||||
|
@ -1827,8 +1827,8 @@ clean_caches_callback(time_t now, const or_options_t *options)
|
||||
{
|
||||
/* Remove old information from rephist and the rend cache. */
|
||||
rep_history_clean(now - options->RephistTrackTime);
|
||||
rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
|
||||
rend_cache_clean(now, REND_CACHE_TYPE_SERVICE);
|
||||
hs_cache_clean_as_client(now);
|
||||
hs_cache_clean_as_dir(now);
|
||||
microdesc_cache_rebuild(NULL, 0);
|
||||
#define CLEAN_CACHES_INTERVAL (30*60)
|
||||
@ -1847,6 +1847,7 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options)
|
||||
* clean it as soon as we can since we want to make sure the client waits
|
||||
* as little as possible for reachability reasons. */
|
||||
rend_cache_failure_clean(now);
|
||||
hs_cache_client_intro_state_clean(now);
|
||||
return 30;
|
||||
}
|
||||
|
||||
|
@ -238,6 +238,27 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns)
|
||||
return;
|
||||
}
|
||||
|
||||
/** Recompute all node hsdir indices. */
|
||||
void
|
||||
nodelist_recompute_all_hsdir_indices(void)
|
||||
{
|
||||
networkstatus_t *consensus;
|
||||
if (!the_nodelist) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get a live consensus. Abort if not found */
|
||||
consensus = networkstatus_get_live_consensus(approx_time());
|
||||
if (!consensus) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Recompute all hsdir indices */
|
||||
SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) {
|
||||
node_set_hsdir_index(node, consensus);
|
||||
} SMARTLIST_FOREACH_END(node);
|
||||
}
|
||||
|
||||
/** Called when a node's address changes. */
|
||||
static void
|
||||
node_addrs_changed(node_t *node)
|
||||
@ -1741,8 +1762,8 @@ static char dir_info_status[512] = "";
|
||||
* no exits in the consensus."
|
||||
* To obtain the final weighted bandwidth, we multiply the
|
||||
* weighted bandwidth fraction for each position (guard, middle, exit). */
|
||||
int
|
||||
router_have_minimum_dir_info(void)
|
||||
MOCK_IMPL(int,
|
||||
router_have_minimum_dir_info,(void))
|
||||
{
|
||||
static int logged_delay=0;
|
||||
const char *delay_fetches_msg = NULL;
|
||||
@ -1789,6 +1810,7 @@ router_dir_info_changed(void)
|
||||
{
|
||||
need_to_update_have_min_dir_info = 1;
|
||||
rend_hsdir_routers_changed();
|
||||
hs_hsdir_set_changed_consider_reupload();
|
||||
}
|
||||
|
||||
/** Return a string describing what we're missing before we have enough
|
||||
|
@ -28,6 +28,8 @@ void nodelist_remove_routerinfo(routerinfo_t *ri);
|
||||
void nodelist_purge(void);
|
||||
smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md);
|
||||
|
||||
void nodelist_recompute_all_hsdir_indices(void);
|
||||
|
||||
void nodelist_free_all(void);
|
||||
void nodelist_assert_ok(void);
|
||||
|
||||
@ -105,7 +107,7 @@ int addrs_in_same_network_family(const tor_addr_t *a1,
|
||||
* no exits in the consensus, we wait for enough info to create internal
|
||||
* paths, and should avoid creating exit paths, as they will simply fail.
|
||||
* We make sure we create all available circuit types at the same time. */
|
||||
int router_have_minimum_dir_info(void);
|
||||
MOCK_DECL(int, router_have_minimum_dir_info,(void));
|
||||
|
||||
/** Set to CONSENSUS_PATH_EXIT if there is at least one exit node
|
||||
* in the consensus. We update this flag in compute_frac_paths_available if
|
||||
|
@ -425,7 +425,10 @@ typedef enum {
|
||||
#define DIR_PURPOSE_UPLOAD_HSDESC 20
|
||||
/** A connection to a hidden service directory: fetch a v3 descriptor. */
|
||||
#define DIR_PURPOSE_FETCH_HSDESC 21
|
||||
#define DIR_PURPOSE_MAX_ 21
|
||||
/** A connection to a directory server: set after a hidden service descriptor
|
||||
* is downloaded. */
|
||||
#define DIR_PURPOSE_HAS_FETCHED_HSDESC 22
|
||||
#define DIR_PURPOSE_MAX_ 22
|
||||
|
||||
/** True iff <b>p</b> is a purpose corresponding to uploading
|
||||
* data to a directory server. */
|
||||
|
@ -512,7 +512,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e)
|
||||
tor_assert(rend_cache);
|
||||
tor_assert(query);
|
||||
|
||||
if (!rend_valid_service_id(query)) {
|
||||
if (!rend_valid_v2_service_id(query)) {
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
@ -558,7 +558,7 @@ rend_cache_lookup_v2_desc_as_service(const char *query, rend_cache_entry_t **e)
|
||||
tor_assert(rend_cache_local_service);
|
||||
tor_assert(query);
|
||||
|
||||
if (!rend_valid_service_id(query)) {
|
||||
if (!rend_valid_v2_service_id(query)) {
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "directory.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "hs_client.h"
|
||||
#include "main.h"
|
||||
#include "networkstatus.h"
|
||||
#include "nodelist.h"
|
||||
@ -42,7 +43,7 @@ rend_client_purge_state(void)
|
||||
rend_cache_purge();
|
||||
rend_cache_failure_purge();
|
||||
rend_client_cancel_descriptor_fetches();
|
||||
rend_client_purge_last_hid_serv_requests();
|
||||
hs_purge_last_hid_serv_requests();
|
||||
}
|
||||
|
||||
/** Called when we've established a circuit to an introduction point:
|
||||
@ -89,46 +90,6 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** 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(rend_data_get_address(circ->rend_data)));
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
|
||||
return -1;
|
||||
}
|
||||
// XXX: should we not re-extend if hs_circ_has_timed_out?
|
||||
if (circ->remaining_relay_early_cells) {
|
||||
log_info(LD_REND,
|
||||
"Re-extending circ %u, this time to %s.",
|
||||
(unsigned)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 %u (out of RELAY_EARLY cells).",
|
||||
(unsigned)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.
|
||||
*/
|
||||
@ -202,7 +163,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
|
||||
introcirc->build_state->chosen_exit)),
|
||||
smartlist_len(entry->parsed->intro_nodes));
|
||||
|
||||
if (rend_client_reextend_intro_circuit(introcirc)) {
|
||||
if (hs_client_reextend_intro_circuit(introcirc)) {
|
||||
status = -2;
|
||||
goto perm_err;
|
||||
} else {
|
||||
@ -391,23 +352,11 @@ rend_client_introduction_acked(origin_circuit_t *circ,
|
||||
origin_circuit_t *rendcirc;
|
||||
(void) request; // XXXX Use this.
|
||||
|
||||
if (circ->base_.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
|
||||
log_warn(LD_PROTOCOL,
|
||||
"Received REND_INTRODUCE_ACK on unexpected circuit %u.",
|
||||
(unsigned)circ->base_.n_circ_id);
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tor_assert(circ->build_state);
|
||||
tor_assert(circ->build_state->chosen_exit);
|
||||
assert_circ_anonymity_ok(circ, options);
|
||||
tor_assert(circ->rend_data);
|
||||
|
||||
/* For path bias: This circuit was used successfully. Valid
|
||||
* nacks and acks count. */
|
||||
pathbias_mark_use_success(circ);
|
||||
|
||||
if (request_len == 0) {
|
||||
/* It's an ACK; the introduction point relayed our introduction request. */
|
||||
/* Locate the rend circ which is waiting to hear about this ack,
|
||||
@ -449,7 +398,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
|
||||
INTRO_POINT_FAILURE_GENERIC)>0) {
|
||||
/* There are introduction points left. Re-extend the circuit to
|
||||
* another intro point and try again. */
|
||||
int result = rend_client_reextend_intro_circuit(circ);
|
||||
int result = hs_client_reextend_intro_circuit(circ);
|
||||
/* XXXX If that call failed, should we close the rend circuit,
|
||||
* too? */
|
||||
return result;
|
||||
@ -465,230 +414,6 @@ rend_client_introduction_acked(origin_circuit_t *circ,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** The period for which a hidden service directory cannot be queried for
|
||||
* the same descriptor ID again. */
|
||||
#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60)
|
||||
/** Test networks generate a new consensus every 5 or 10 seconds.
|
||||
* So allow them to requery HSDirs much faster. */
|
||||
#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5)
|
||||
|
||||
/** Return the period for which a hidden service directory cannot be queried
|
||||
* for the same descriptor ID again, taking TestingTorNetwork into account. */
|
||||
static time_t
|
||||
hsdir_requery_period(const or_options_t *options)
|
||||
{
|
||||
tor_assert(options);
|
||||
|
||||
if (options->TestingTorNetwork) {
|
||||
return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING;
|
||||
} else {
|
||||
return REND_HID_SERV_DIR_REQUERY_PERIOD;
|
||||
}
|
||||
}
|
||||
|
||||
/** Contains the last request times to hidden service directories for
|
||||
* certain queries; each key is a string consisting of the
|
||||
* concatenation of a base32-encoded HS directory identity digest and
|
||||
* base32-encoded HS descriptor ID; each value is a pointer to a time_t
|
||||
* holding the time of the last request for that descriptor ID to that
|
||||
* HS directory. */
|
||||
static strmap_t *last_hid_serv_requests_ = NULL;
|
||||
|
||||
/** Returns last_hid_serv_requests_, initializing it to a new strmap if
|
||||
* necessary. */
|
||||
static strmap_t *
|
||||
get_last_hid_serv_requests(void)
|
||||
{
|
||||
if (!last_hid_serv_requests_)
|
||||
last_hid_serv_requests_ = strmap_new();
|
||||
return last_hid_serv_requests_;
|
||||
}
|
||||
|
||||
#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \
|
||||
REND_DESC_ID_V2_LEN_BASE32)
|
||||
|
||||
/** Look up the last request time to hidden service directory <b>hs_dir</b>
|
||||
* for descriptor ID <b>desc_id_base32</b>. If <b>set</b> is non-zero,
|
||||
* assign the current time <b>now</b> and return that. Otherwise, return the
|
||||
* most recent request time, or 0 if no such request has been sent before.
|
||||
*/
|
||||
static time_t
|
||||
lookup_last_hid_serv_request(routerstatus_t *hs_dir,
|
||||
const char *desc_id_base32,
|
||||
time_t now, int set)
|
||||
{
|
||||
char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
|
||||
char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1];
|
||||
time_t *last_request_ptr;
|
||||
strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
|
||||
base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32),
|
||||
hs_dir->identity_digest, DIGEST_LEN);
|
||||
tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s",
|
||||
hsdir_id_base32,
|
||||
desc_id_base32);
|
||||
/* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) ==
|
||||
LAST_HID_SERV_REQUEST_KEY_LEN); */
|
||||
if (set) {
|
||||
time_t *oldptr;
|
||||
last_request_ptr = tor_malloc_zero(sizeof(time_t));
|
||||
*last_request_ptr = now;
|
||||
oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id,
|
||||
last_request_ptr);
|
||||
tor_free(oldptr);
|
||||
} else
|
||||
last_request_ptr = strmap_get_lc(last_hid_serv_requests,
|
||||
hsdir_desc_comb_id);
|
||||
return (last_request_ptr) ? *last_request_ptr : 0;
|
||||
}
|
||||
|
||||
/** Clean the history of request times to hidden service directories, so that
|
||||
* it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD
|
||||
* seconds any more. */
|
||||
static void
|
||||
directory_clean_last_hid_serv_requests(time_t now)
|
||||
{
|
||||
strmap_iter_t *iter;
|
||||
time_t cutoff = now - hsdir_requery_period(get_options());
|
||||
strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
|
||||
for (iter = strmap_iter_init(last_hid_serv_requests);
|
||||
!strmap_iter_done(iter); ) {
|
||||
const char *key;
|
||||
void *val;
|
||||
time_t *ent;
|
||||
strmap_iter_get(iter, &key, &val);
|
||||
ent = (time_t *) val;
|
||||
if (*ent < cutoff) {
|
||||
iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
|
||||
tor_free(ent);
|
||||
} else {
|
||||
iter = strmap_iter_next(last_hid_serv_requests, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove all requests related to the descriptor ID <b>desc_id</b> from the
|
||||
* history of times of requests to hidden service directories.
|
||||
* <b>desc_id</b> is an unencoded descriptor ID of size DIGEST_LEN.
|
||||
*
|
||||
* This is called from rend_client_note_connection_attempt_ended(), which
|
||||
* must be idempotent, so any future changes to this function must leave it
|
||||
* idempotent too. */
|
||||
static void
|
||||
purge_hid_serv_from_last_hid_serv_requests(const char *desc_id)
|
||||
{
|
||||
strmap_iter_t *iter;
|
||||
strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
|
||||
char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
|
||||
|
||||
/* Key is stored with the base32 encoded desc_id. */
|
||||
base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id,
|
||||
DIGEST_LEN);
|
||||
for (iter = strmap_iter_init(last_hid_serv_requests);
|
||||
!strmap_iter_done(iter); ) {
|
||||
const char *key;
|
||||
void *val;
|
||||
strmap_iter_get(iter, &key, &val);
|
||||
/* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */
|
||||
if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN -
|
||||
REND_DESC_ID_V2_LEN_BASE32,
|
||||
desc_id_base32,
|
||||
REND_DESC_ID_V2_LEN_BASE32)) {
|
||||
iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
|
||||
tor_free(val);
|
||||
} else {
|
||||
iter = strmap_iter_next(last_hid_serv_requests, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Purge the history of request times to hidden service directories,
|
||||
* so that future lookups of an HS descriptor will not fail because we
|
||||
* accessed all of the HSDir relays responsible for the descriptor
|
||||
* recently. */
|
||||
void
|
||||
rend_client_purge_last_hid_serv_requests(void)
|
||||
{
|
||||
/* Don't create the table if it doesn't exist yet (and it may very
|
||||
* well not exist if the user hasn't accessed any HSes)... */
|
||||
strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_;
|
||||
/* ... and let get_last_hid_serv_requests re-create it for us if
|
||||
* necessary. */
|
||||
last_hid_serv_requests_ = NULL;
|
||||
|
||||
if (old_last_hid_serv_requests != NULL) {
|
||||
log_info(LD_REND, "Purging client last-HS-desc-request-time table");
|
||||
strmap_free(old_last_hid_serv_requests, tor_free_);
|
||||
}
|
||||
}
|
||||
|
||||
/** This returns a good valid hs dir that should be used for the given
|
||||
* descriptor id.
|
||||
*
|
||||
* Return NULL on error else the hsdir node pointer. */
|
||||
static routerstatus_t *
|
||||
pick_hsdir(const char *desc_id, const char *desc_id_base32)
|
||||
{
|
||||
smartlist_t *responsible_dirs = smartlist_new();
|
||||
smartlist_t *usable_responsible_dirs = smartlist_new();
|
||||
const or_options_t *options = get_options();
|
||||
routerstatus_t *hs_dir;
|
||||
time_t now = time(NULL);
|
||||
int excluded_some;
|
||||
|
||||
tor_assert(desc_id);
|
||||
tor_assert(desc_id_base32);
|
||||
|
||||
/* Determine responsible dirs. Even if we can't get all we want, work with
|
||||
* the ones we have. If it's empty, we'll notice below. */
|
||||
hid_serv_get_responsible_directories(responsible_dirs, desc_id);
|
||||
|
||||
/* Clean request history first. */
|
||||
directory_clean_last_hid_serv_requests(now);
|
||||
|
||||
/* Only select those hidden service directories to which we did not send a
|
||||
* request recently and for which we have a router descriptor here. */
|
||||
SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) {
|
||||
time_t last = lookup_last_hid_serv_request(dir, desc_id_base32,
|
||||
0, 0);
|
||||
const node_t *node = node_get_by_id(dir->identity_digest);
|
||||
if (last + hsdir_requery_period(options) >= now ||
|
||||
!node || !node_has_descriptor(node)) {
|
||||
SMARTLIST_DEL_CURRENT(responsible_dirs, dir);
|
||||
continue;
|
||||
}
|
||||
if (!routerset_contains_node(options->ExcludeNodes, node)) {
|
||||
smartlist_add(usable_responsible_dirs, dir);
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(dir);
|
||||
|
||||
excluded_some =
|
||||
smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs);
|
||||
|
||||
hs_dir = smartlist_choose(usable_responsible_dirs);
|
||||
if (!hs_dir && !options->StrictNodes) {
|
||||
hs_dir = smartlist_choose(responsible_dirs);
|
||||
}
|
||||
|
||||
smartlist_free(responsible_dirs);
|
||||
smartlist_free(usable_responsible_dirs);
|
||||
if (!hs_dir) {
|
||||
log_info(LD_REND, "Could not pick one of the responsible hidden "
|
||||
"service directories, because we requested them all "
|
||||
"recently without success.");
|
||||
if (options->StrictNodes && excluded_some) {
|
||||
log_warn(LD_REND, "Could not pick a hidden service directory for the "
|
||||
"requested hidden service: they are all either down or "
|
||||
"excluded, and StrictNodes is set.");
|
||||
}
|
||||
} else {
|
||||
/* Remember that we are requesting a descriptor from this hidden service
|
||||
* directory now. */
|
||||
lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1);
|
||||
}
|
||||
|
||||
return hs_dir;
|
||||
}
|
||||
|
||||
/** Determine the responsible hidden service directories for <b>desc_id</b>
|
||||
* and fetch the descriptor with that ID from one of them. Only
|
||||
* send a request to a hidden service directory that we have not yet tried
|
||||
@ -721,7 +446,12 @@ directory_get_from_hs_dir(const char *desc_id,
|
||||
|
||||
/* Automatically pick an hs dir if none given. */
|
||||
if (!rs_hsdir) {
|
||||
hs_dir = pick_hsdir(desc_id, desc_id_base32);
|
||||
/* Determine responsible dirs. Even if we can't get all we want, work with
|
||||
* the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */
|
||||
smartlist_t *responsible_dirs = smartlist_new();
|
||||
hid_serv_get_responsible_directories(responsible_dirs, desc_id);
|
||||
|
||||
hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32);
|
||||
if (!hs_dir) {
|
||||
/* No suitable hs dir can be found, stop right now. */
|
||||
control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR");
|
||||
@ -786,6 +516,20 @@ directory_get_from_hs_dir(const char *desc_id,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Remove tracked HSDir requests from our history for this hidden service
|
||||
* descriptor <b>desc_id</b> (of size DIGEST_LEN) */
|
||||
static void
|
||||
purge_v2_hidserv_req(const char *desc_id)
|
||||
{
|
||||
char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
|
||||
|
||||
/* The hsdir request tracker stores v2 keys using the base32 encoded
|
||||
desc_id. Do it: */
|
||||
base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id,
|
||||
DIGEST_LEN);
|
||||
hs_purge_hid_serv_from_last_hid_serv_requests(desc_id_base32);
|
||||
}
|
||||
|
||||
/** Fetch a v2 descriptor using the given descriptor id. If any hsdir(s) are
|
||||
* given, they will be used instead.
|
||||
*
|
||||
@ -860,8 +604,7 @@ fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs)
|
||||
sizeof(descriptor_id)) != 0) {
|
||||
/* Not equal from what we currently have so purge the last hid serv
|
||||
* request cache and update the descriptor ID with the new value. */
|
||||
purge_hid_serv_from_last_hid_serv_requests(
|
||||
rend_data->descriptor_id[chosen_replica]);
|
||||
purge_v2_hidserv_req(rend_data->descriptor_id[chosen_replica]);
|
||||
memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id,
|
||||
sizeof(rend_data->descriptor_id[chosen_replica]));
|
||||
}
|
||||
@ -1107,66 +850,17 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Called when we receive a RENDEZVOUS_ESTABLISHED cell; changes the state of
|
||||
* the circuit to C_REND_READY.
|
||||
*/
|
||||
int
|
||||
rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request,
|
||||
size_t request_len)
|
||||
{
|
||||
(void) request;
|
||||
(void) request_len;
|
||||
/* we just got an ack for our establish-rendezvous. switch purposes. */
|
||||
if (circ->base_.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) {
|
||||
log_warn(LD_PROTOCOL,"Got a rendezvous ack when we weren't expecting one. "
|
||||
"Closing circ.");
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
|
||||
return -1;
|
||||
}
|
||||
log_info(LD_REND,"Got rendezvous ack. This circuit is now ready for "
|
||||
"rendezvous.");
|
||||
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY);
|
||||
/* Set timestamp_dirty, because circuit_expire_building expects it
|
||||
* to specify when a circuit entered the _C_REND_READY state. */
|
||||
circ->base_.timestamp_dirty = time(NULL);
|
||||
|
||||
/* From a path bias point of view, this circuit is now successfully used.
|
||||
* Waiting any longer opens us up to attacks from malicious hidden services.
|
||||
* They could induce the client to attempt to connect to their hidden
|
||||
* service and never reply to the client's rend requests */
|
||||
pathbias_mark_use_success(circ);
|
||||
|
||||
/* XXXX++ This is a pretty brute-force approach. It'd be better to
|
||||
* attach only the connections that are waiting on this circuit, rather
|
||||
* than trying to attach them all. See comments bug 743. */
|
||||
/* If we already have the introduction circuit built, make sure we send
|
||||
* the INTRODUCE cell _now_ */
|
||||
connection_ap_attach_pending(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** The service sent us a rendezvous cell; join the circuits. */
|
||||
int
|
||||
rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
|
||||
size_t request_len)
|
||||
{
|
||||
if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY &&
|
||||
circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)
|
||||
|| !circ->build_state->pending_final_cpath) {
|
||||
log_warn(LD_PROTOCOL,"Got rendezvous2 cell from hidden service, but not "
|
||||
"expecting it. Closing.");
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (request_len != DH_KEY_LEN+DIGEST_LEN) {
|
||||
log_warn(LD_PROTOCOL,"Incorrect length (%d) on RENDEZVOUS2 cell.",
|
||||
(int)request_len);
|
||||
goto err;
|
||||
}
|
||||
|
||||
log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service.");
|
||||
|
||||
if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) {
|
||||
log_warn(LD_GENERAL, "Failed to setup circ");
|
||||
goto err;
|
||||
@ -1260,14 +954,14 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data)
|
||||
for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id);
|
||||
replica++) {
|
||||
const char *desc_id = rend_data_v2->descriptor_id[replica];
|
||||
purge_hid_serv_from_last_hid_serv_requests(desc_id);
|
||||
purge_v2_hidserv_req(desc_id);
|
||||
}
|
||||
log_info(LD_REND, "Connection attempt for %s has ended; "
|
||||
"cleaning up temporary state.",
|
||||
safe_str_client(onion_address));
|
||||
} else {
|
||||
/* We only have an ID for a fetch. Probably used by HSFETCH. */
|
||||
purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch);
|
||||
purge_v2_hidserv_req(rend_data_v2->desc_id_fetch);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1466,7 +1160,7 @@ rend_parse_service_authorization(const or_options_t *options,
|
||||
goto err;
|
||||
}
|
||||
strlcpy(auth->onion_address, onion_address, REND_SERVICE_ID_LEN_BASE32+1);
|
||||
if (!rend_valid_service_id(auth->onion_address)) {
|
||||
if (!rend_valid_v2_service_id(auth->onion_address)) {
|
||||
log_warn(LD_CONFIG, "Onion address has wrong format: '%s'",
|
||||
onion_address);
|
||||
goto err;
|
||||
|
@ -24,15 +24,11 @@ int rend_client_introduction_acked(origin_circuit_t *circ,
|
||||
void rend_client_refetch_v2_renddesc(rend_data_t *rend_query);
|
||||
int rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs);
|
||||
void rend_client_cancel_descriptor_fetches(void);
|
||||
void rend_client_purge_last_hid_serv_requests(void);
|
||||
|
||||
int rend_client_report_intro_point_failure(extend_info_t *failed_intro,
|
||||
rend_data_t *rend_data,
|
||||
unsigned int failure_type);
|
||||
|
||||
int rend_client_rendezvous_acked(origin_circuit_t *circ,
|
||||
const uint8_t *request,
|
||||
size_t request_len);
|
||||
int rend_client_receive_rendezvous(origin_circuit_t *circ,
|
||||
const uint8_t *request,
|
||||
size_t request_len);
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "rendcommon.h"
|
||||
#include "rendmid.h"
|
||||
#include "hs_intropoint.h"
|
||||
#include "hs_client.h"
|
||||
#include "rendservice.h"
|
||||
#include "rephist.h"
|
||||
#include "router.h"
|
||||
@ -695,7 +696,7 @@ rend_get_service_id(crypto_pk_t *pk, char *out)
|
||||
/** Return true iff <b>query</b> is a syntactically valid service ID (as
|
||||
* generated by rend_get_service_id). */
|
||||
int
|
||||
rend_valid_service_id(const char *query)
|
||||
rend_valid_v2_service_id(const char *query)
|
||||
{
|
||||
if (strlen(query) != REND_SERVICE_ID_LEN_BASE32)
|
||||
return 0;
|
||||
@ -781,7 +782,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
|
||||
break;
|
||||
case RELAY_COMMAND_INTRODUCE_ACK:
|
||||
if (origin_circ)
|
||||
r = rend_client_introduction_acked(origin_circ,payload,length);
|
||||
r = hs_client_receive_introduce_ack(origin_circ,payload,length);
|
||||
break;
|
||||
case RELAY_COMMAND_RENDEZVOUS1:
|
||||
if (or_circ)
|
||||
@ -789,7 +790,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
|
||||
break;
|
||||
case RELAY_COMMAND_RENDEZVOUS2:
|
||||
if (origin_circ)
|
||||
r = rend_client_receive_rendezvous(origin_circ,payload,length);
|
||||
r = hs_client_receive_rendezvous2(origin_circ,payload,length);
|
||||
break;
|
||||
case RELAY_COMMAND_INTRO_ESTABLISHED:
|
||||
if (origin_circ)
|
||||
@ -797,7 +798,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
|
||||
break;
|
||||
case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED:
|
||||
if (origin_circ)
|
||||
r = rend_client_rendezvous_acked(origin_circ,payload,length);
|
||||
r = hs_client_receive_rendezvous_acked(origin_circ,payload,length);
|
||||
break;
|
||||
default:
|
||||
tor_fragile_assert();
|
||||
@ -990,7 +991,7 @@ rend_non_anonymous_mode_enabled(const or_options_t *options)
|
||||
* service.
|
||||
*/
|
||||
void
|
||||
assert_circ_anonymity_ok(origin_circuit_t *circ,
|
||||
assert_circ_anonymity_ok(const origin_circuit_t *circ,
|
||||
const or_options_t *options)
|
||||
{
|
||||
tor_assert(options);
|
||||
|
@ -30,7 +30,7 @@ void rend_encoded_v2_service_descriptor_free(
|
||||
rend_encoded_v2_service_descriptor_t *desc);
|
||||
void rend_intro_point_free(rend_intro_point_t *intro);
|
||||
|
||||
int rend_valid_service_id(const char *query);
|
||||
int rend_valid_v2_service_id(const char *query);
|
||||
int rend_valid_descriptor_id(const char *query);
|
||||
int rend_valid_client_name(const char *client_name);
|
||||
int rend_encode_v2_descriptors(smartlist_t *descs_out,
|
||||
@ -60,7 +60,7 @@ int rend_auth_decode_cookie(const char *cookie_in,
|
||||
int rend_allow_non_anonymous_connection(const or_options_t* options);
|
||||
int rend_non_anonymous_mode_enabled(const or_options_t *options);
|
||||
|
||||
void assert_circ_anonymity_ok(origin_circuit_t *circ,
|
||||
void assert_circ_anonymity_ok(const origin_circuit_t *circ,
|
||||
const or_options_t *options);
|
||||
|
||||
#ifdef RENDCOMMON_PRIVATE
|
||||
|
@ -890,7 +890,7 @@ int
|
||||
rend_service_del_ephemeral(const char *service_id)
|
||||
{
|
||||
rend_service_t *s;
|
||||
if (!rend_valid_service_id(service_id)) {
|
||||
if (!rend_valid_v2_service_id(service_id)) {
|
||||
log_warn(LD_CONFIG, "Requested malformed Onion Service id for removal.");
|
||||
return -1;
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ get_start_time_of_current_round(time_t now)
|
||||
const or_options_t *options = get_options();
|
||||
int voting_interval = get_voting_interval();
|
||||
voting_schedule_t *new_voting_schedule =
|
||||
get_voting_schedule(options, now, LOG_INFO);
|
||||
get_voting_schedule(options, now, LOG_DEBUG);
|
||||
tor_assert(new_voting_schedule);
|
||||
|
||||
/* First, get the start time of the next round */
|
||||
|
@ -534,25 +534,8 @@ test_rend_fns(void *arg)
|
||||
size_t intro_points_size;
|
||||
size_t encoded_size;
|
||||
int i;
|
||||
char address1[] = "fooaddress.onion";
|
||||
char address2[] = "aaaaaaaaaaaaaaaa.onion";
|
||||
char address3[] = "fooaddress.exit";
|
||||
char address4[] = "www.torproject.org";
|
||||
char address5[] = "foo.abcdefghijklmnop.onion";
|
||||
char address6[] = "foo.bar.abcdefghijklmnop.onion";
|
||||
char address7[] = ".abcdefghijklmnop.onion";
|
||||
|
||||
(void)arg;
|
||||
tt_assert(BAD_HOSTNAME == parse_extended_hostname(address1));
|
||||
tt_assert(ONION_HOSTNAME == parse_extended_hostname(address2));
|
||||
tt_str_op(address2,OP_EQ, "aaaaaaaaaaaaaaaa");
|
||||
tt_assert(EXIT_HOSTNAME == parse_extended_hostname(address3));
|
||||
tt_assert(NORMAL_HOSTNAME == parse_extended_hostname(address4));
|
||||
tt_assert(ONION_HOSTNAME == parse_extended_hostname(address5));
|
||||
tt_str_op(address5,OP_EQ, "abcdefghijklmnop");
|
||||
tt_assert(ONION_HOSTNAME == parse_extended_hostname(address6));
|
||||
tt_str_op(address6,OP_EQ, "abcdefghijklmnop");
|
||||
tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7));
|
||||
|
||||
/* Initialize the service cache. */
|
||||
rend_cache_init();
|
||||
|
@ -180,6 +180,7 @@ static void
|
||||
test_rend_token_maps(void *arg)
|
||||
{
|
||||
or_circuit_t *c1, *c2, *c3, *c4;
|
||||
origin_circuit_t *c5;
|
||||
const uint8_t tok1[REND_TOKEN_LEN] = "The cat can't tell y";
|
||||
const uint8_t tok2[REND_TOKEN_LEN] = "ou its name, and it ";
|
||||
const uint8_t tok3[REND_TOKEN_LEN] = "doesn't really care.";
|
||||
@ -194,6 +195,7 @@ test_rend_token_maps(void *arg)
|
||||
c2 = or_circuit_new(0, NULL);
|
||||
c3 = or_circuit_new(0, NULL);
|
||||
c4 = or_circuit_new(0, NULL);
|
||||
c5 = origin_circuit_new();
|
||||
|
||||
/* Make sure we really filled up the tok* variables */
|
||||
tt_int_op(tok1[REND_TOKEN_LEN-1], OP_EQ, 'y');
|
||||
@ -264,6 +266,13 @@ test_rend_token_maps(void *arg)
|
||||
tt_ptr_op(TO_CIRCUIT(c4)->hs_token, OP_EQ, NULL);
|
||||
tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3));
|
||||
|
||||
/* Now let's do a check for the client-side rend circuitmap */
|
||||
c5->base_.purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND;
|
||||
hs_circuitmap_register_rend_circ_client_side(c5, tok1);
|
||||
|
||||
tt_ptr_op(c5, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok1));
|
||||
tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok2));
|
||||
|
||||
done:
|
||||
if (c1)
|
||||
circuit_free(TO_CIRCUIT(c1));
|
||||
@ -273,6 +282,8 @@ test_rend_token_maps(void *arg)
|
||||
circuit_free(TO_CIRCUIT(c3));
|
||||
if (c4)
|
||||
circuit_free(TO_CIRCUIT(c4));
|
||||
if (c5)
|
||||
circuit_free(TO_CIRCUIT(c5));
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -14,6 +14,10 @@
|
||||
#include "confparse.h"
|
||||
#include "connection.h"
|
||||
#include "connection_edge.h"
|
||||
#include "nodelist.h"
|
||||
|
||||
#include "hs_cache.h"
|
||||
#include "rendcache.h"
|
||||
|
||||
static void *
|
||||
entryconn_rewrite_setup(const struct testcase_t *tc)
|
||||
@ -743,6 +747,87 @@ test_entryconn_rewrite_mapaddress_automap_onion4(void *arg)
|
||||
test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 0, 1);
|
||||
}
|
||||
|
||||
/** Test that rewrite functions can handle v2 addresses */
|
||||
static void
|
||||
test_entryconn_rewrite_onion_v2(void *arg)
|
||||
{
|
||||
int retval;
|
||||
entry_connection_t *conn = arg;
|
||||
|
||||
(void) arg;
|
||||
|
||||
rend_cache_init();
|
||||
|
||||
/* Make a SOCKS request */
|
||||
conn->socks_request->command = SOCKS_COMMAND_CONNECT;
|
||||
strlcpy(conn->socks_request->address,
|
||||
"pqeed46efnwmfuid.onion",
|
||||
sizeof(conn->socks_request->address));
|
||||
|
||||
/* Make an onion connection using the SOCKS request */
|
||||
conn->entry_cfg.onion_traffic = 1;
|
||||
ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_SOCKS_WAIT;
|
||||
tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data);
|
||||
|
||||
/* Handle SOCKS and rewrite! */
|
||||
retval = connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/* Check connection state after rewrite */
|
||||
tt_int_op(ENTRY_TO_CONN(conn)->state, OP_EQ, AP_CONN_STATE_RENDDESC_WAIT);
|
||||
/* check that the address got rewritten */
|
||||
tt_str_op(conn->socks_request->address, OP_EQ,
|
||||
"pqeed46efnwmfuid");
|
||||
/* check that HS information got attached to the connection */
|
||||
tt_assert(ENTRY_TO_EDGE_CONN(conn)->rend_data);
|
||||
tt_assert(!ENTRY_TO_EDGE_CONN(conn)->hs_ident);
|
||||
|
||||
done:
|
||||
rend_cache_free_all();
|
||||
/* 'conn' is cleaned by handler */
|
||||
}
|
||||
|
||||
/** Test that rewrite functions can handle v3 onion addresses */
|
||||
static void
|
||||
test_entryconn_rewrite_onion_v3(void *arg)
|
||||
{
|
||||
int retval;
|
||||
entry_connection_t *conn = arg;
|
||||
|
||||
(void) arg;
|
||||
|
||||
hs_cache_init();
|
||||
|
||||
/* Make a SOCKS request */
|
||||
conn->socks_request->command = SOCKS_COMMAND_CONNECT;
|
||||
strlcpy(conn->socks_request->address,
|
||||
"git.p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad.onion",
|
||||
sizeof(conn->socks_request->address));
|
||||
|
||||
/* Make an onion connection using the SOCKS request */
|
||||
conn->entry_cfg.onion_traffic = 1;
|
||||
ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_SOCKS_WAIT;
|
||||
tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data);
|
||||
tt_assert(!ENTRY_TO_EDGE_CONN(conn)->hs_ident);
|
||||
|
||||
/* Handle SOCKS and rewrite! */
|
||||
retval = connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/* Check connection state after rewrite */
|
||||
tt_int_op(ENTRY_TO_CONN(conn)->state, OP_EQ, AP_CONN_STATE_RENDDESC_WAIT);
|
||||
/* check that the address got rewritten */
|
||||
tt_str_op(conn->socks_request->address, OP_EQ,
|
||||
"p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad");
|
||||
/* check that HS information got attached to the connection */
|
||||
tt_assert(ENTRY_TO_EDGE_CONN(conn)->hs_ident);
|
||||
tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data);
|
||||
|
||||
done:
|
||||
hs_free_all();
|
||||
/* 'conn' is cleaned by handler */
|
||||
}
|
||||
|
||||
#define REWRITE(name) \
|
||||
{ #name, test_entryconn_##name, TT_FORK, &test_rewrite_setup, NULL }
|
||||
|
||||
@ -763,6 +848,8 @@ struct testcase_t entryconn_tests[] = {
|
||||
REWRITE(rewrite_mapaddress_automap_onion2),
|
||||
REWRITE(rewrite_mapaddress_automap_onion3),
|
||||
REWRITE(rewrite_mapaddress_automap_onion4),
|
||||
REWRITE(rewrite_onion_v2),
|
||||
REWRITE(rewrite_onion_v3),
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#define CONNECTION_PRIVATE
|
||||
#define DIRECTORY_PRIVATE
|
||||
#define HS_CACHE_PRIVATE
|
||||
|
||||
#include "ed25519_cert.h"
|
||||
@ -431,6 +432,69 @@ test_hsdir_revision_counter_check(void *arg)
|
||||
tor_free(published_desc_str);
|
||||
}
|
||||
|
||||
/** Test that we can store HS descriptors in the client HS cache. */
|
||||
static void
|
||||
test_client_cache(void *arg)
|
||||
{
|
||||
int retval;
|
||||
ed25519_keypair_t signing_kp;
|
||||
hs_descriptor_t *published_desc = NULL;
|
||||
char *published_desc_str = NULL;
|
||||
|
||||
response_handler_args_t *args = NULL;
|
||||
dir_connection_t *conn = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Initialize HSDir cache subsystem */
|
||||
init_test();
|
||||
|
||||
/* Generate a valid descriptor with normal values. */
|
||||
{
|
||||
retval = ed25519_keypair_generate(&signing_kp, 0);
|
||||
tt_int_op(retval, ==, 0);
|
||||
published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
|
||||
tt_assert(published_desc);
|
||||
retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
|
||||
&published_desc_str);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
}
|
||||
|
||||
/* Test handle_response_fetch_hsdesc_v3() */
|
||||
{
|
||||
args = tor_malloc_zero(sizeof(response_handler_args_t));
|
||||
args->status_code = 200;
|
||||
args->reason = NULL;
|
||||
args->body = published_desc_str;
|
||||
args->body_len = strlen(published_desc_str);
|
||||
|
||||
conn = tor_malloc_zero(sizeof(dir_connection_t));
|
||||
conn->hs_ident = tor_malloc_zero(sizeof(hs_ident_dir_conn_t));
|
||||
ed25519_pubkey_copy(&conn->hs_ident->identity_pk, &signing_kp.pubkey);
|
||||
}
|
||||
|
||||
/* store the descriptor! */
|
||||
retval = handle_response_fetch_hsdesc_v3(conn, args);
|
||||
tt_int_op(retval, == , 0);
|
||||
|
||||
/* fetch the descriptor and make sure it's there */
|
||||
{
|
||||
hs_cache_client_descriptor_t *cached_desc = NULL;
|
||||
cached_desc = lookup_v3_desc_as_client(signing_kp.pubkey.pubkey);
|
||||
tt_assert(cached_desc);
|
||||
tt_str_op(cached_desc->encoded_desc, OP_EQ, published_desc_str);
|
||||
}
|
||||
|
||||
done:
|
||||
tor_free(args);
|
||||
hs_descriptor_free(published_desc);
|
||||
tor_free(published_desc_str);
|
||||
if (conn) {
|
||||
tor_free(conn->hs_ident);
|
||||
tor_free(conn);
|
||||
}
|
||||
}
|
||||
|
||||
struct testcase_t hs_cache[] = {
|
||||
/* Encoding tests. */
|
||||
{ "directory", test_directory, TT_FORK,
|
||||
@ -441,6 +505,8 @@ struct testcase_t hs_cache[] = {
|
||||
NULL, NULL },
|
||||
{ "upload_and_download_hs_desc", test_upload_and_download_hs_desc, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "client_cache", test_client_cache, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
@ -14,11 +14,14 @@
|
||||
#include "log_test_helpers.h"
|
||||
#include "hs_test_helpers.h"
|
||||
|
||||
#include "connection_edge.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_service.h"
|
||||
#include "config.h"
|
||||
#include "networkstatus.h"
|
||||
#include "directory.h"
|
||||
#include "nodelist.h"
|
||||
#include "statefile.h"
|
||||
|
||||
/** Test the validation of HS v3 addresses */
|
||||
static void
|
||||
@ -357,6 +360,37 @@ test_desc_overlap_period_testnet(void *arg)
|
||||
tor_free(dummy_consensus);
|
||||
}
|
||||
|
||||
static void
|
||||
helper_add_hsdir_to_networkstatus(networkstatus_t *ns,
|
||||
const uint8_t *identity,
|
||||
const uint8_t *curr_hsdir_index,
|
||||
const char *nickname,
|
||||
int is_hsdir)
|
||||
{
|
||||
routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t));
|
||||
routerinfo_t *ri = tor_malloc_zero(sizeof(routerinfo_t));
|
||||
|
||||
tor_addr_t ipv4_addr;
|
||||
memcpy(rs->identity_digest, identity, DIGEST_LEN);
|
||||
rs->is_hs_dir = is_hsdir;
|
||||
rs->supports_v3_hsdir = 1;
|
||||
tor_addr_parse(&ipv4_addr, "1.2.3.4");
|
||||
ri->addr = tor_addr_to_ipv4h(&ipv4_addr);
|
||||
ri->nickname = tor_strdup(nickname);
|
||||
ri->protocol_list = tor_strdup("HSDir=1-2 LinkAuth=3");
|
||||
memcpy(ri->cache_info.identity_digest, identity, DIGEST_LEN);
|
||||
tt_assert(nodelist_set_routerinfo(ri, NULL));
|
||||
node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest);
|
||||
tt_assert(node);
|
||||
node->rs = rs;
|
||||
memcpy(node->hsdir_index->current, curr_hsdir_index,
|
||||
sizeof(node->hsdir_index->current));
|
||||
smartlist_add(ns->routerstatus_list, rs);
|
||||
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
static networkstatus_t *mock_ns = NULL;
|
||||
|
||||
static networkstatus_t *
|
||||
@ -389,7 +423,7 @@ test_responsible_hsdirs(void *arg)
|
||||
time_t now = approx_time();
|
||||
smartlist_t *responsible_dirs = smartlist_new();
|
||||
networkstatus_t *ns = NULL;
|
||||
routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t));
|
||||
int retval;
|
||||
|
||||
(void) arg;
|
||||
|
||||
@ -401,39 +435,217 @@ test_responsible_hsdirs(void *arg)
|
||||
ns = networkstatus_get_latest_consensus();
|
||||
|
||||
{ /* First router: HSdir */
|
||||
tor_addr_t ipv4_addr;
|
||||
memset(rs->identity_digest, 'A', DIGEST_LEN);
|
||||
rs->is_hs_dir = 1;
|
||||
rs->supports_v3_hsdir = 1;
|
||||
routerinfo_t ri;
|
||||
memset(&ri, 0 ,sizeof(routerinfo_t));
|
||||
tor_addr_parse(&ipv4_addr, "127.0.0.1");
|
||||
ri.addr = tor_addr_to_ipv4h(&ipv4_addr);
|
||||
ri.nickname = (char *) "fatal";
|
||||
ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3";
|
||||
memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN);
|
||||
tt_assert(nodelist_set_routerinfo(&ri, NULL));
|
||||
node_t *node = node_get_mutable_by_id(ri.cache_info.identity_digest);
|
||||
memset(node->hsdir_index->current, 'Z',
|
||||
sizeof(node->hsdir_index->current));
|
||||
smartlist_add(ns->routerstatus_list, rs);
|
||||
uint8_t identity[DIGEST_LEN];
|
||||
uint8_t curr_hsdir_index[DIGEST256_LEN];
|
||||
char nickname[] = "let_me";
|
||||
memset(identity, 1, sizeof(identity));
|
||||
memset(curr_hsdir_index, 1, sizeof(curr_hsdir_index));
|
||||
|
||||
helper_add_hsdir_to_networkstatus(ns, identity,
|
||||
curr_hsdir_index, nickname, 1);
|
||||
}
|
||||
|
||||
ed25519_public_key_t blinded_pk;
|
||||
{ /* Second HSDir */
|
||||
uint8_t identity[DIGEST_LEN];
|
||||
uint8_t curr_hsdir_index[DIGEST256_LEN];
|
||||
char nickname[] = "show_you";
|
||||
memset(identity, 2, sizeof(identity));
|
||||
memset(curr_hsdir_index, 2, sizeof(curr_hsdir_index));
|
||||
|
||||
helper_add_hsdir_to_networkstatus(ns, identity,
|
||||
curr_hsdir_index, nickname, 1);
|
||||
}
|
||||
|
||||
{ /* Third relay but not HSDir */
|
||||
uint8_t identity[DIGEST_LEN];
|
||||
uint8_t curr_hsdir_index[DIGEST256_LEN];
|
||||
char nickname[] = "how_to_dance";
|
||||
memset(identity, 3, sizeof(identity));
|
||||
memset(curr_hsdir_index, 3, sizeof(curr_hsdir_index));
|
||||
|
||||
helper_add_hsdir_to_networkstatus(ns, identity,
|
||||
curr_hsdir_index, nickname, 0);
|
||||
}
|
||||
|
||||
ed25519_keypair_t kp;
|
||||
retval = ed25519_keypair_generate(&kp, 0);
|
||||
tt_int_op(retval, OP_EQ , 0);
|
||||
|
||||
uint64_t time_period_num = hs_get_time_period_num(now);
|
||||
hs_get_responsible_hsdirs(&blinded_pk, time_period_num,
|
||||
hs_get_responsible_hsdirs(&kp.pubkey, time_period_num,
|
||||
0, 0, responsible_dirs);
|
||||
tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 1);
|
||||
|
||||
/* Make sure that we only found 2 responsible HSDirs.
|
||||
* The third relay was not an hsdir! */
|
||||
tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 2);
|
||||
|
||||
/** TODO: Build a bigger network and do more tests here */
|
||||
|
||||
done:
|
||||
routerstatus_free(rs);
|
||||
SMARTLIST_FOREACH(ns->routerstatus_list,
|
||||
routerstatus_t *, rs, routerstatus_free(rs));
|
||||
smartlist_free(responsible_dirs);
|
||||
smartlist_clear(ns->routerstatus_list);
|
||||
networkstatus_vote_free(mock_ns);
|
||||
}
|
||||
|
||||
static void
|
||||
mock_directory_initiate_request(directory_request_t *req)
|
||||
{
|
||||
(void)req;
|
||||
return;
|
||||
}
|
||||
|
||||
static int
|
||||
mock_hs_desc_encode_descriptor(const hs_descriptor_t *desc,
|
||||
const ed25519_keypair_t *signing_kp,
|
||||
char **encoded_out)
|
||||
{
|
||||
(void)desc;
|
||||
(void)signing_kp;
|
||||
|
||||
tor_asprintf(encoded_out, "lulu");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static or_state_t dummy_state;
|
||||
|
||||
/* Mock function to get fake or state (used for rev counters) */
|
||||
static or_state_t *
|
||||
get_or_state_replacement(void)
|
||||
{
|
||||
return &dummy_state;
|
||||
}
|
||||
|
||||
static int
|
||||
mock_router_have_minimum_dir_info(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Test that we correctly detect when the HSDir hash ring changes so that we
|
||||
* reupload our descriptor. */
|
||||
static void
|
||||
test_desc_reupload_logic(void *arg)
|
||||
{
|
||||
networkstatus_t *ns = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
hs_init();
|
||||
|
||||
MOCK(router_have_minimum_dir_info,
|
||||
mock_router_have_minimum_dir_info);
|
||||
MOCK(get_or_state,
|
||||
get_or_state_replacement);
|
||||
MOCK(networkstatus_get_latest_consensus,
|
||||
mock_networkstatus_get_latest_consensus);
|
||||
MOCK(directory_initiate_request,
|
||||
mock_directory_initiate_request);
|
||||
MOCK(hs_desc_encode_descriptor,
|
||||
mock_hs_desc_encode_descriptor);
|
||||
|
||||
ns = networkstatus_get_latest_consensus();
|
||||
|
||||
/** Test logic:
|
||||
* 1) Upload descriptor to HSDirs
|
||||
* CHECK that previous_hsdirs list was populated.
|
||||
* 2) Then call router_dir_info_changed() without an HSDir set change.
|
||||
* CHECK that no reuplod occurs.
|
||||
* 3) Now change the HSDir set, and call dir_info_changed() again.
|
||||
* CHECK that reupload occurs.
|
||||
* 4) Finally call service_desc_schedule_upload().
|
||||
* CHECK that previous_hsdirs list was cleared.
|
||||
**/
|
||||
|
||||
/* Let's start by building our descriptor and service */
|
||||
hs_service_descriptor_t *desc = service_descriptor_new();
|
||||
hs_service_t *service = NULL;
|
||||
char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1];
|
||||
ed25519_public_key_t pubkey;
|
||||
memset(&pubkey, '\x42', sizeof(pubkey));
|
||||
hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr);
|
||||
service = tor_malloc_zero(sizeof(hs_service_t));
|
||||
memcpy(service->onion_address, onion_addr, sizeof(service->onion_address));
|
||||
ed25519_secret_key_generate(&service->keys.identity_sk, 0);
|
||||
ed25519_public_key_generate(&service->keys.identity_pk,
|
||||
&service->keys.identity_sk);
|
||||
service->desc_current = desc;
|
||||
/* Also add service to service map */
|
||||
hs_service_ht *service_map = get_hs_service_map();
|
||||
tt_assert(service_map);
|
||||
tt_int_op(hs_service_get_num_services(), OP_EQ, 0);
|
||||
register_service(service_map, service);
|
||||
tt_int_op(hs_service_get_num_services(), OP_EQ, 1);
|
||||
|
||||
/* Now let's create our hash ring: */
|
||||
{ /* First HSDir */
|
||||
uint8_t identity[DIGEST_LEN];
|
||||
uint8_t curr_hsdir_index[DIGEST256_LEN];
|
||||
char nickname[] = "let_me";
|
||||
memset(identity, 1, sizeof(identity));
|
||||
memset(curr_hsdir_index, 1, sizeof(curr_hsdir_index));
|
||||
|
||||
helper_add_hsdir_to_networkstatus(ns, identity,
|
||||
curr_hsdir_index, nickname, 1);
|
||||
}
|
||||
|
||||
{ /* Second HSDir */
|
||||
uint8_t identity[DIGEST_LEN];
|
||||
uint8_t curr_hsdir_index[DIGEST256_LEN];
|
||||
char nickname[] = "show_you";
|
||||
memset(identity, 2, sizeof(identity));
|
||||
memset(curr_hsdir_index, 2, sizeof(curr_hsdir_index));
|
||||
|
||||
helper_add_hsdir_to_networkstatus(ns, identity,
|
||||
curr_hsdir_index, nickname, 1);
|
||||
}
|
||||
|
||||
/* Now let's upload our desc to all hsdirs */
|
||||
upload_descriptor_to_all(service, desc, 0);
|
||||
/* Check that previous hsdirs were populated */
|
||||
tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 2);
|
||||
|
||||
/* Poison next upload time so that we can see if it was changed by
|
||||
* router_dir_info_changed(). No changes in hash ring so far, so the upload
|
||||
* time should stay as is. */
|
||||
desc->next_upload_time = 42;
|
||||
router_dir_info_changed();
|
||||
tt_int_op(desc->next_upload_time, OP_EQ, 42);
|
||||
|
||||
/* Now change the HSDir hash ring by adding another node */
|
||||
|
||||
{ /* Third HSDir */
|
||||
uint8_t identity[DIGEST_LEN];
|
||||
uint8_t curr_hsdir_index[DIGEST256_LEN];
|
||||
char nickname[] = "how_to_dance";
|
||||
memset(identity, 3, sizeof(identity));
|
||||
memset(curr_hsdir_index, 3, sizeof(curr_hsdir_index));
|
||||
|
||||
helper_add_hsdir_to_networkstatus(ns, identity,
|
||||
curr_hsdir_index, nickname, 1);
|
||||
}
|
||||
|
||||
/* Now call router_dir_info_changed() again and see that it detected the hash
|
||||
ring change and updated the upload time */
|
||||
time_t now = approx_time();
|
||||
tt_assert(now);
|
||||
router_dir_info_changed();
|
||||
tt_int_op(desc->next_upload_time, OP_EQ, now);
|
||||
|
||||
/* Now pretend that the descriptor changed, and order a reupload to all
|
||||
HSDirs. Make sure that the set of previous HSDirs was cleared. */
|
||||
service_desc_schedule_upload(desc, now, 1);
|
||||
tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 0);
|
||||
|
||||
/* Now reupload again: see that the prev hsdir set got populated again. */
|
||||
upload_descriptor_to_all(service, desc, 0);
|
||||
tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 3);
|
||||
|
||||
done:
|
||||
hs_free_all();
|
||||
}
|
||||
|
||||
/** Test disaster SRV computation and caching */
|
||||
static void
|
||||
test_disaster_srv(void *arg)
|
||||
@ -485,6 +697,136 @@ test_disaster_srv(void *arg)
|
||||
;
|
||||
}
|
||||
|
||||
/** Test our HS descriptor request tracker by making various requests and
|
||||
* checking whether they get tracked properly. */
|
||||
static void
|
||||
test_hid_serv_request_tracker(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
time_t retval;
|
||||
routerstatus_t *hsdir = NULL, *hsdir2 = NULL;
|
||||
time_t now = approx_time();
|
||||
|
||||
const char *req_key_str_first =
|
||||
"vd4zb6zesaubtrjvdqcr2w7x7lhw2up4Xnw4526ThUNbL5o1go+EdUuEqlKxHkNbnK41pRzizzs";
|
||||
const char *req_key_str_second =
|
||||
"g53o7iavcd62oihswhr24u6czmqws5kpXnw4526ThUNbL5o1go+EdUuEqlKxHkNbnK41pRzizzs";
|
||||
|
||||
/*************************** basic test *******************************/
|
||||
|
||||
/* Get request tracker and make sure it's empty */
|
||||
strmap_t *request_tracker = get_last_hid_serv_requests();
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 0);
|
||||
|
||||
/* Let's register a hid serv request */
|
||||
hsdir = tor_malloc_zero(sizeof(routerstatus_t));
|
||||
memset(hsdir->identity_digest, 'Z', DIGEST_LEN);
|
||||
retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first,
|
||||
now, 1);
|
||||
tt_int_op(retval, OP_EQ, now);
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
|
||||
|
||||
/* Let's lookup a non-existent hidserv request */
|
||||
retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_second,
|
||||
now+1, 0);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
|
||||
|
||||
/* Let's lookup a real hidserv request */
|
||||
retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first,
|
||||
now+2, 0);
|
||||
tt_int_op(retval, OP_EQ, now); /* we got it */
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
/* Let's add another request for the same HS but on a different HSDir. */
|
||||
hsdir2 = tor_malloc_zero(sizeof(routerstatus_t));
|
||||
memset(hsdir->identity_digest, 2, DIGEST_LEN);
|
||||
retval = hs_lookup_last_hid_serv_request(hsdir2, req_key_str_first,
|
||||
now+3, 1);
|
||||
tt_int_op(retval, OP_EQ, now+3);
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 2);
|
||||
|
||||
/* Check that we can clean the first request based on time */
|
||||
hs_clean_last_hid_serv_requests(now+3+REND_HID_SERV_DIR_REQUERY_PERIOD);
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
|
||||
/* Check that it doesn't exist anymore */
|
||||
retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first,
|
||||
now+2, 0);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/*************************** deleting entries **************************/
|
||||
|
||||
/* Add another request with very short key */
|
||||
retval = hs_lookup_last_hid_serv_request(hsdir, "l", now, 1);
|
||||
|
||||
/* Try deleting entries with a dummy key. Check that our previous requests
|
||||
* are still there */
|
||||
tor_capture_bugs_(1);
|
||||
hs_purge_hid_serv_from_last_hid_serv_requests("a");
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 2);
|
||||
tor_end_capture_bugs_();
|
||||
|
||||
/* Try another dummy key. Check that requests are still there */
|
||||
{
|
||||
char dummy[2000];
|
||||
memset(dummy, 'Z', 2000);
|
||||
dummy[1999] = '\x00';
|
||||
hs_purge_hid_serv_from_last_hid_serv_requests(dummy);
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 2);
|
||||
}
|
||||
|
||||
/* Another dummy key! */
|
||||
hs_purge_hid_serv_from_last_hid_serv_requests(req_key_str_second);
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 2);
|
||||
|
||||
/* Now actually delete a request! */
|
||||
hs_purge_hid_serv_from_last_hid_serv_requests(req_key_str_first);
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 1);
|
||||
|
||||
/* Purge it all! */
|
||||
hs_purge_last_hid_serv_requests();
|
||||
request_tracker = get_last_hid_serv_requests();
|
||||
tt_int_op(strmap_size(request_tracker),OP_EQ, 0);
|
||||
|
||||
done:
|
||||
tor_free(hsdir);
|
||||
tor_free(hsdir2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_extended_hostname(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
|
||||
char address1[] = "fooaddress.onion";
|
||||
char address2[] = "aaaaaaaaaaaaaaaa.onion";
|
||||
char address3[] = "fooaddress.exit";
|
||||
char address4[] = "www.torproject.org";
|
||||
char address5[] = "foo.abcdefghijklmnop.onion";
|
||||
char address6[] = "foo.bar.abcdefghijklmnop.onion";
|
||||
char address7[] = ".abcdefghijklmnop.onion";
|
||||
char address8[] =
|
||||
"www.p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad.onion";
|
||||
|
||||
tt_assert(BAD_HOSTNAME == parse_extended_hostname(address1));
|
||||
tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address2));
|
||||
tt_str_op(address2,OP_EQ, "aaaaaaaaaaaaaaaa");
|
||||
tt_assert(EXIT_HOSTNAME == parse_extended_hostname(address3));
|
||||
tt_assert(NORMAL_HOSTNAME == parse_extended_hostname(address4));
|
||||
tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address5));
|
||||
tt_str_op(address5,OP_EQ, "abcdefghijklmnop");
|
||||
tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address6));
|
||||
tt_str_op(address6,OP_EQ, "abcdefghijklmnop");
|
||||
tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7));
|
||||
tt_assert(ONION_V3_HOSTNAME == parse_extended_hostname(address8));
|
||||
tt_str_op(address8, OP_EQ,
|
||||
"p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad");
|
||||
|
||||
done: ;
|
||||
}
|
||||
|
||||
struct testcase_t hs_common_tests[] = {
|
||||
{ "build_address", test_build_address, TT_FORK,
|
||||
NULL, NULL },
|
||||
@ -498,9 +840,16 @@ struct testcase_t hs_common_tests[] = {
|
||||
NULL, NULL },
|
||||
{ "desc_overlap_period_testnet", test_desc_overlap_period_testnet, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "desc_responsible_hsdirs", test_responsible_hsdirs, TT_FORK,
|
||||
{ "responsible_hsdirs", test_responsible_hsdirs, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "desc_reupload_logic", test_desc_reupload_logic, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "disaster_srv", test_disaster_srv, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "hid_serv_request_tracker", test_hid_serv_request_tracker, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "parse_extended_hostname", test_parse_extended_hostname, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
@ -1183,7 +1183,6 @@ test_upload_descriptors(void *arg)
|
||||
int ret;
|
||||
time_t now = time(NULL);
|
||||
hs_service_t *service;
|
||||
hs_service_intro_point_t *ip;
|
||||
|
||||
(void) arg;
|
||||
|
||||
@ -1191,7 +1190,6 @@ test_upload_descriptors(void *arg)
|
||||
MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true);
|
||||
MOCK(get_or_state,
|
||||
get_or_state_replacement);
|
||||
|
||||
dummy_state = tor_malloc_zero(sizeof(or_state_t));
|
||||
|
||||
/* Create a service with no descriptor. It's added to the global map. */
|
||||
@ -1222,25 +1220,10 @@ test_upload_descriptors(void *arg)
|
||||
/* If no upload happened, this should be untouched. */
|
||||
tt_u64_op(service->desc_current->next_upload_time, OP_EQ, now + 1000);
|
||||
|
||||
/* Set our upload time in the past so we trigger an upload. */
|
||||
service->desc_current->next_upload_time = now - 1000;
|
||||
service->desc_next->next_upload_time = now - 1000;
|
||||
ip = helper_create_service_ip();
|
||||
ip->circuit_established = 1;
|
||||
service_intro_point_add(service->desc_current->intro_points.map, ip);
|
||||
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
run_upload_descriptor_event(now);
|
||||
expect_log_msg_containing("No valid consensus so we can't get the");
|
||||
teardown_capture_of_logs();
|
||||
tt_u64_op(service->desc_current->next_upload_time, OP_GE,
|
||||
now + HS_SERVICE_NEXT_UPLOAD_TIME_MIN);
|
||||
tt_u64_op(service->desc_current->next_upload_time, OP_LE,
|
||||
now + HS_SERVICE_NEXT_UPLOAD_TIME_MAX);
|
||||
|
||||
done:
|
||||
hs_free_all();
|
||||
UNMOCK(hs_overlap_mode_is_active);
|
||||
UNMOCK(get_or_state);
|
||||
}
|
||||
|
||||
/** Test the functions that save and load HS revision counters to state. */
|
||||
|
@ -290,3 +290,181 @@ trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input
|
||||
}
|
||||
return result;
|
||||
}
|
||||
trn_cell_rendezvous2_t *
|
||||
trn_cell_rendezvous2_new(void)
|
||||
{
|
||||
trn_cell_rendezvous2_t *val = trunnel_calloc(1, sizeof(trn_cell_rendezvous2_t));
|
||||
if (NULL == val)
|
||||
return NULL;
|
||||
return val;
|
||||
}
|
||||
|
||||
/** Release all storage held inside 'obj', but do not free 'obj'.
|
||||
*/
|
||||
static void
|
||||
trn_cell_rendezvous2_clear(trn_cell_rendezvous2_t *obj)
|
||||
{
|
||||
(void) obj;
|
||||
}
|
||||
|
||||
void
|
||||
trn_cell_rendezvous2_free(trn_cell_rendezvous2_t *obj)
|
||||
{
|
||||
if (obj == NULL)
|
||||
return;
|
||||
trn_cell_rendezvous2_clear(obj);
|
||||
trunnel_memwipe(obj, sizeof(trn_cell_rendezvous2_t));
|
||||
trunnel_free_(obj);
|
||||
}
|
||||
|
||||
size_t
|
||||
trn_cell_rendezvous2_getlen_handshake_info(const trn_cell_rendezvous2_t *inp)
|
||||
{
|
||||
(void)inp; return TRUNNEL_HANDSHAKE_INFO_LEN;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
trn_cell_rendezvous2_get_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx)
|
||||
{
|
||||
trunnel_assert(idx < TRUNNEL_HANDSHAKE_INFO_LEN);
|
||||
return inp->handshake_info[idx];
|
||||
}
|
||||
|
||||
uint8_t
|
||||
trn_cell_rendezvous2_getconst_handshake_info(const trn_cell_rendezvous2_t *inp, size_t idx)
|
||||
{
|
||||
return trn_cell_rendezvous2_get_handshake_info((trn_cell_rendezvous2_t*)inp, idx);
|
||||
}
|
||||
int
|
||||
trn_cell_rendezvous2_set_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx, uint8_t elt)
|
||||
{
|
||||
trunnel_assert(idx < TRUNNEL_HANDSHAKE_INFO_LEN);
|
||||
inp->handshake_info[idx] = elt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t *
|
||||
trn_cell_rendezvous2_getarray_handshake_info(trn_cell_rendezvous2_t *inp)
|
||||
{
|
||||
return inp->handshake_info;
|
||||
}
|
||||
const uint8_t *
|
||||
trn_cell_rendezvous2_getconstarray_handshake_info(const trn_cell_rendezvous2_t *inp)
|
||||
{
|
||||
return (const uint8_t *)trn_cell_rendezvous2_getarray_handshake_info((trn_cell_rendezvous2_t*)inp);
|
||||
}
|
||||
const char *
|
||||
trn_cell_rendezvous2_check(const trn_cell_rendezvous2_t *obj)
|
||||
{
|
||||
if (obj == NULL)
|
||||
return "Object was NULL";
|
||||
if (obj->trunnel_error_code_)
|
||||
return "A set function failed on this object";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
trn_cell_rendezvous2_encoded_len(const trn_cell_rendezvous2_t *obj)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
|
||||
if (NULL != trn_cell_rendezvous2_check(obj))
|
||||
return -1;
|
||||
|
||||
|
||||
/* Length of u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */
|
||||
result += TRUNNEL_HANDSHAKE_INFO_LEN;
|
||||
return result;
|
||||
}
|
||||
int
|
||||
trn_cell_rendezvous2_clear_errors(trn_cell_rendezvous2_t *obj)
|
||||
{
|
||||
int r = obj->trunnel_error_code_;
|
||||
obj->trunnel_error_code_ = 0;
|
||||
return r;
|
||||
}
|
||||
ssize_t
|
||||
trn_cell_rendezvous2_encode(uint8_t *output, const size_t avail, const trn_cell_rendezvous2_t *obj)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
size_t written = 0;
|
||||
uint8_t *ptr = output;
|
||||
const char *msg;
|
||||
#ifdef TRUNNEL_CHECK_ENCODED_LEN
|
||||
const ssize_t encoded_len = trn_cell_rendezvous2_encoded_len(obj);
|
||||
#endif
|
||||
|
||||
if (NULL != (msg = trn_cell_rendezvous2_check(obj)))
|
||||
goto check_failed;
|
||||
|
||||
#ifdef TRUNNEL_CHECK_ENCODED_LEN
|
||||
trunnel_assert(encoded_len >= 0);
|
||||
#endif
|
||||
|
||||
/* Encode u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */
|
||||
trunnel_assert(written <= avail);
|
||||
if (avail - written < TRUNNEL_HANDSHAKE_INFO_LEN)
|
||||
goto truncated;
|
||||
memcpy(ptr, obj->handshake_info, TRUNNEL_HANDSHAKE_INFO_LEN);
|
||||
written += TRUNNEL_HANDSHAKE_INFO_LEN; ptr += TRUNNEL_HANDSHAKE_INFO_LEN;
|
||||
|
||||
|
||||
trunnel_assert(ptr == output + written);
|
||||
#ifdef TRUNNEL_CHECK_ENCODED_LEN
|
||||
{
|
||||
trunnel_assert(encoded_len >= 0);
|
||||
trunnel_assert((size_t)encoded_len == written);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return written;
|
||||
|
||||
truncated:
|
||||
result = -2;
|
||||
goto fail;
|
||||
check_failed:
|
||||
(void)msg;
|
||||
result = -1;
|
||||
goto fail;
|
||||
fail:
|
||||
trunnel_assert(result < 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** As trn_cell_rendezvous2_parse(), but do not allocate the output
|
||||
* object.
|
||||
*/
|
||||
static ssize_t
|
||||
trn_cell_rendezvous2_parse_into(trn_cell_rendezvous2_t *obj, const uint8_t *input, const size_t len_in)
|
||||
{
|
||||
const uint8_t *ptr = input;
|
||||
size_t remaining = len_in;
|
||||
ssize_t result = 0;
|
||||
(void)result;
|
||||
|
||||
/* Parse u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */
|
||||
CHECK_REMAINING(TRUNNEL_HANDSHAKE_INFO_LEN, truncated);
|
||||
memcpy(obj->handshake_info, ptr, TRUNNEL_HANDSHAKE_INFO_LEN);
|
||||
remaining -= TRUNNEL_HANDSHAKE_INFO_LEN; ptr += TRUNNEL_HANDSHAKE_INFO_LEN;
|
||||
trunnel_assert(ptr + remaining == input + len_in);
|
||||
return len_in - remaining;
|
||||
|
||||
truncated:
|
||||
return -2;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
trn_cell_rendezvous2_parse(trn_cell_rendezvous2_t **output, const uint8_t *input, const size_t len_in)
|
||||
{
|
||||
ssize_t result;
|
||||
*output = trn_cell_rendezvous2_new();
|
||||
if (NULL == *output)
|
||||
return -1;
|
||||
result = trn_cell_rendezvous2_parse_into(*output, input, len_in);
|
||||
if (result < 0) {
|
||||
trn_cell_rendezvous2_free(*output);
|
||||
*output = NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "trunnel.h"
|
||||
|
||||
#define TRUNNEL_REND_COOKIE_LEN 20
|
||||
#define TRUNNEL_HANDSHAKE_INFO_LEN 64
|
||||
#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS1)
|
||||
struct trn_cell_rendezvous1_st {
|
||||
uint8_t rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN];
|
||||
@ -17,6 +18,13 @@ struct trn_cell_rendezvous1_st {
|
||||
};
|
||||
#endif
|
||||
typedef struct trn_cell_rendezvous1_st trn_cell_rendezvous1_t;
|
||||
#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS2)
|
||||
struct trn_cell_rendezvous2_st {
|
||||
uint8_t handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN];
|
||||
uint8_t trunnel_error_code_;
|
||||
};
|
||||
#endif
|
||||
typedef struct trn_cell_rendezvous2_st trn_cell_rendezvous2_t;
|
||||
/** Return a newly allocated trn_cell_rendezvous1 with all elements
|
||||
* set to zero.
|
||||
*/
|
||||
@ -113,6 +121,67 @@ const uint8_t * trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cel
|
||||
* failure.
|
||||
*/
|
||||
int trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen);
|
||||
/** Return a newly allocated trn_cell_rendezvous2 with all elements
|
||||
* set to zero.
|
||||
*/
|
||||
trn_cell_rendezvous2_t *trn_cell_rendezvous2_new(void);
|
||||
/** Release all storage held by the trn_cell_rendezvous2 in 'victim'.
|
||||
* (Do nothing if 'victim' is NULL.)
|
||||
*/
|
||||
void trn_cell_rendezvous2_free(trn_cell_rendezvous2_t *victim);
|
||||
/** Try to parse a trn_cell_rendezvous2 from the buffer in 'input',
|
||||
* using up to 'len_in' bytes from the input buffer. On success,
|
||||
* return the number of bytes consumed and set *output to the newly
|
||||
* allocated trn_cell_rendezvous2_t. On failure, return -2 if the
|
||||
* input appears truncated, and -1 if the input is otherwise invalid.
|
||||
*/
|
||||
ssize_t trn_cell_rendezvous2_parse(trn_cell_rendezvous2_t **output, const uint8_t *input, const size_t len_in);
|
||||
/** Return the number of bytes we expect to need to encode the
|
||||
* trn_cell_rendezvous2 in 'obj'. On failure, return a negative value.
|
||||
* Note that this value may be an overestimate, and can even be an
|
||||
* underestimate for certain unencodeable objects.
|
||||
*/
|
||||
ssize_t trn_cell_rendezvous2_encoded_len(const trn_cell_rendezvous2_t *obj);
|
||||
/** Try to encode the trn_cell_rendezvous2 from 'input' into the
|
||||
* buffer at 'output', using up to 'avail' bytes of the output buffer.
|
||||
* On success, return the number of bytes used. On failure, return -2
|
||||
* if the buffer was not long enough, and -1 if the input was invalid.
|
||||
*/
|
||||
ssize_t trn_cell_rendezvous2_encode(uint8_t *output, size_t avail, const trn_cell_rendezvous2_t *input);
|
||||
/** Check whether the internal state of the trn_cell_rendezvous2 in
|
||||
* 'obj' is consistent. Return NULL if it is, and a short message if
|
||||
* it is not.
|
||||
*/
|
||||
const char *trn_cell_rendezvous2_check(const trn_cell_rendezvous2_t *obj);
|
||||
/** Clear any errors that were set on the object 'obj' by its setter
|
||||
* functions. Return true iff errors were cleared.
|
||||
*/
|
||||
int trn_cell_rendezvous2_clear_errors(trn_cell_rendezvous2_t *obj);
|
||||
/** Return the (constant) length of the array holding the
|
||||
* handshake_info field of the trn_cell_rendezvous2_t in 'inp'.
|
||||
*/
|
||||
size_t trn_cell_rendezvous2_getlen_handshake_info(const trn_cell_rendezvous2_t *inp);
|
||||
/** Return the element at position 'idx' of the fixed array field
|
||||
* handshake_info of the trn_cell_rendezvous2_t in 'inp'.
|
||||
*/
|
||||
uint8_t trn_cell_rendezvous2_get_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx);
|
||||
/** As trn_cell_rendezvous2_get_handshake_info, but take and return a
|
||||
* const pointer
|
||||
*/
|
||||
uint8_t trn_cell_rendezvous2_getconst_handshake_info(const trn_cell_rendezvous2_t *inp, size_t idx);
|
||||
/** Change the element at position 'idx' of the fixed array field
|
||||
* handshake_info of the trn_cell_rendezvous2_t in 'inp', so that it
|
||||
* will hold the value 'elt'.
|
||||
*/
|
||||
int trn_cell_rendezvous2_set_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx, uint8_t elt);
|
||||
/** Return a pointer to the TRUNNEL_HANDSHAKE_INFO_LEN-element array
|
||||
* field handshake_info of 'inp'.
|
||||
*/
|
||||
uint8_t * trn_cell_rendezvous2_getarray_handshake_info(trn_cell_rendezvous2_t *inp);
|
||||
/** As trn_cell_rendezvous2_get_handshake_info, but take and return a
|
||||
* const pointer
|
||||
*/
|
||||
const uint8_t * trn_cell_rendezvous2_getconstarray_handshake_info(const trn_cell_rendezvous2_t *inp);
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -1,11 +1,16 @@
|
||||
/*
|
||||
* This contains the definition of the RENDEZVOUS1 cell for onion service
|
||||
* This contains the definition of the RENDEZVOUS1/2 cell for onion service
|
||||
* version 3 and onward. The following format is specified in proposal 224
|
||||
* section 4.2.
|
||||
*/
|
||||
|
||||
/* Rendezvous cookie length. */
|
||||
const TRUNNEL_REND_COOKIE_LEN = 20;
|
||||
/* The HANDSHAKE_INFO field layout is as follow:
|
||||
* SERVER_PK [PK_PUBKEY_LEN bytes]
|
||||
* AUTH [MAC_LEN bytes]
|
||||
* This means, the size is 32 bytes + 32 bytes. */
|
||||
const TRUNNEL_HANDSHAKE_INFO_LEN = 64;
|
||||
|
||||
/* RENDEZVOUS1 payload. See details in section 4.2. */
|
||||
struct trn_cell_rendezvous1 {
|
||||
@ -16,3 +21,9 @@ struct trn_cell_rendezvous1 {
|
||||
* handshake type used. */
|
||||
u8 handshake_info[];
|
||||
};
|
||||
|
||||
/* RENDEZVOUS2 payload. See details in section 4.2. */
|
||||
struct trn_cell_rendezvous2 {
|
||||
/* The HANDSHAKE_INFO field. */
|
||||
u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN];
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user