Merge remote-tracking branch 'dgoulet/ticket17242_032_03-squashed'

This commit is contained in:
Nick Mathewson 2017-08-24 15:12:16 -04:00
commit 91c6bc160b
48 changed files with 4371 additions and 767 deletions

View File

@ -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.",

View File

@ -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);

View File

@ -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) {
/* write the service_id into circ */
circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data);
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(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);

View File

@ -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) \

View File

@ -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);

View File

@ -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,7 +155,9 @@ 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)) {
rend_client_note_connection_attempt_ended(edge_conn->rend_data);
if (edge_conn->rend_data) {
rend_client_note_connection_attempt_ended(edge_conn->rend_data);
}
}
if (base_conn->marked_for_close) {
@ -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 */

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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,

View File

@ -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;
}

View File

@ -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 */

View File

@ -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));
}

View File

@ -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 */

View File

@ -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,8 +364,9 @@ 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,
service->config.is_single_onion);
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. */
goto end;
@ -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;
}

View File

@ -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. */

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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 */

View File

@ -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

View File

@ -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);

View File

@ -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;
link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec);
if (ls) {
link_specifier_list_add_spec(lslist, ls);
}
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_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,
const ed25519_keypair_t *signing_kp,
char **encoded_out)
MOCK_IMPL(int,
hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
const ed25519_keypair_t *signing_kp,
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;
}

View File

@ -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,
const ed25519_keypair_t *signing_kp,
char **encoded_out);
MOCK_DECL(int,
hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
const ed25519_keypair_t *signing_kp,
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. */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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)

View File

@ -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 */

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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. */

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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 */

View File

@ -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();

View File

@ -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

View File

@ -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
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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. */

View File

@ -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;
}

View File

@ -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

View File

@ -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];
};