commit 59f8dced114f20a147a5425ece67d7d44a81867b
Author: David Goulet <dgoulet(a)ev0ke.net>
Date: Tue Mar 10 16:46:56 2015 -0400
Refactor HS descriptor fetch to support descriptor ID
Big refactor of the HS client descriptor fetch functionnality. This allows
to fetch an HS descriptor using only a descriptor ID. Furthermore, it's also
possible to provide a list of HSDir(s) now that are used instead of the
automatically choosen one.
The approach taken was to add a descriptor_id field to the rend_data_t
structure so it can be used, if available, by the HS client. The onion
address field however has priority over it that is if both are set, the
onion address is used to fetch the descriptor.
A new public function is introduced called rend_client_fetch_v2_desc(...)
that does NOT lookup the client cache before fetching and can take a list of
HSDirs as a parameter.
The HSFETCH control command now uses this new function thus making it work
and final.
Signed-off-by: David Goulet <dgoulet(a)ev0ke.net>
---
src/or/control.c | 63 ++++++++----
src/or/or.h | 4 +
src/or/rendclient.c | 276 ++++++++++++++++++++++++++++++++++++---------------
src/or/rendclient.h | 2 +
src/or/rendcommon.c | 8 +-
5 files changed, 254 insertions(+), 99 deletions(-)
diff --git a/src/or/control.c b/src/or/control.c
index e7d8b5c..9258678 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -37,6 +37,7 @@
#include "nodelist.h"
#include "policies.h"
#include "reasons.h"
+#include "rendclient.h"
#include "rephist.h"
#include "router.h"
#include "routerlist.h"
@@ -3266,6 +3267,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
(void) len; /* body is nul-terminated; it's safe to ignore the length */
static const char *v2_str = "v2-";
const size_t v2_str_len = strlen(v2_str);
+ rend_data_t *rend_query = NULL;
/* Make sure we have at least one argument, the HSAddress. */
args = getargs_helper("HSFETCH", conn, body, 1, -1);
@@ -3273,10 +3275,10 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
goto done;
}
- /* Extract the HS address that should NOT contain the .onion part. */
+ /* Extract the first argument (either HSAddress or DescID). */
arg1 = smartlist_get(args, 0);
- /* Remove the HS address from the argument list so we can safely iterate
- * on all the rest to find optional argument(s). */
+ /* Remove it from the argument list so we can safely iterate on all the
+ * rest to find optional argument(s). */
smartlist_del(args, 0);
/* Test if it's an HS address without the .onion part. */
if (strlen(arg1) == REND_SERVICE_ID_LEN_BASE32 &&
@@ -3287,40 +3289,42 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
strlen(arg1 + v2_str_len) == REND_DESC_ID_V2_LEN_BASE32 &&
base32_decode(digest, sizeof(digest), arg1 + v2_str_len,
REND_DESC_ID_V2_LEN_BASE32) == 0) {
- /* We have a well formed version 2 descriptor ID. */
- desc_id = arg1 + v2_str_len;
+ /* We have a well formed version 2 descriptor ID. Keep the decoded value
+ * of the id. */
+ desc_id = digest;
} else {
connection_printf_to_buf(conn, "552 Unrecognized \"%s\"\r\n",
arg1);
goto done;
}
- /* Stores routerstatus_t object for each specified server. */
- hsdirs = smartlist_new();
-
/* Skip first argument because it's the HSAddress. */
SMARTLIST_FOREACH_BEGIN(args, char *, arg) {
const node_t *node;
static const char *opt_server = "SERVER=";
if (!strcasecmpstart(arg, opt_server)) {
+ char id[DIGEST_LEN] = {0};
const char *server;
- memset(digest, 0, sizeof(digest));
server = arg + strlen(opt_server);
- /* Is the server fingerprint valid?. */
+ /* Is the server's fingerprint valid?. */
if (!string_is_hex(server) || strlen(server) != HEX_DIGEST_LEN ||
- base16_decode(digest, sizeof(digest), server, HEX_DIGEST_LEN)) {
+ base16_decode(id, sizeof(id), server, HEX_DIGEST_LEN)) {
connection_printf_to_buf(conn, "552 Invalid fingerprint \"%s\"\r\n",
server);
goto done;
}
- node = node_get_by_id(digest);
+ node = node_get_by_id(id);
if (!node) {
connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n",
server);
goto done;
}
+ if (!hsdirs) {
+ /* Stores routerstatus_t object for each specified server. */
+ hsdirs = smartlist_new();
+ }
/* Valid server, add it to our local list. */
smartlist_add(hsdirs, node->rs);
} else {
@@ -3330,21 +3334,44 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
}
} SMARTLIST_FOREACH_END(arg);
- /* XXX: Actually trigger the fetch(es). */
- (void) hsaddress;
- (void) desc_id;
- (void) hsdirs;
+ rend_query = tor_malloc_zero(sizeof(*rend_query));
- /* All good, thanks and come again! */
+ if (hsaddress) {
+ strncpy(rend_query->onion_address, hsaddress,
+ sizeof(rend_query->onion_address));
+ } else if (desc_id) {
+ /* Using a descriptor ID, we force the user to provide at least one
+ * hsdir server using the SERVER= option. */
+ if (!hsdirs || !smartlist_len(hsdirs)) {
+ connection_printf_to_buf(conn, "552 SERVER= option is required\r\n");
+ goto done;
+ }
+ memcpy(rend_query->descriptor_id, desc_id,
+ sizeof(rend_query->descriptor_id));
+ } else {
+ /* We can't get in here because of the first argument check. */
+ tor_assert(0);
+ }
+ /* We are about to trigger HSDir fetch so send the OK now because after
+ * that 650 event(s) are possible so better to have the 250 OK before them
+ * to avoid out of order replies. */
send_control_done(conn);
+ /* Trigger the fetch using the built rend query and possibly a lit of HS
+ * directory to use. This function ignores the client cache thus this will
+ * always send a fetch command. */
+ rend_client_fetch_v2_desc(rend_query, hsdirs);
+
done:
+ if (hsdirs) {
+ smartlist_free(hsdirs);
+ }
if (args) {
SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
smartlist_free(args);
}
+ tor_free(rend_query);
tor_free(arg1);
- smartlist_free(hsdirs);
return 0;
}
diff --git a/src/or/or.h b/src/or/or.h
index d548aea..39c5ed6 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -799,6 +799,10 @@ typedef struct rend_data_t {
/** Authorization type for accessing a service used by a client. */
rend_auth_type_t auth_type;
+ /** Descriptor ID for a client request. The control port command HSFETCH
+ * can use this. */
+ char descriptor_id[DIGEST_LEN];
+
/** Hash of the hidden service's PK used by a service. */
char rend_pk_digest[DIGEST_LEN];
diff --git a/src/or/rendclient.c b/src/or/rendclient.c
index af08494..7941ba7 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -607,14 +607,12 @@ rend_client_purge_last_hid_serv_requests(void)
}
}
-/** 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
- * during this attempt to connect to this hidden service; on success, return 1,
- * in the case that no hidden service directory is left to ask for the
- * descriptor, return 0, and in case of a failure -1. */
-static int
-directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query)
+/** 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)
{
smartlist_t *responsible_dirs = smartlist_new();
smartlist_t *usable_responsible_dirs = smartlist_new();
@@ -622,49 +620,43 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query)
routerstatus_t *hs_dir;
char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
time_t now = time(NULL);
- char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
-#ifdef ENABLE_TOR2WEB_MODE
- const int tor2web_mode = options->Tor2webMode;
- const int how_to_fetch = tor2web_mode ? DIRIND_ONEHOP : DIRIND_ANONYMOUS;
-#else
- const int how_to_fetch = DIRIND_ANONYMOUS;
-#endif
int excluded_some;
+
tor_assert(desc_id);
- tor_assert(rend_query);
- /* 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);
base32_encode(desc_id_base32, sizeof(desc_id_base32),
desc_id, DIGEST_LEN);
- /* Only select those hidden service directories to which we did not send
- * a request recently and for which we have a router descriptor here. */
+ /* 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);
- SMARTLIST_FOREACH(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 + REND_HID_SERV_DIR_REQUERY_PERIOD >= 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);
- }
- });
+ /* 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 + REND_HID_SERV_DIR_REQUERY_PERIOD >= 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)
+ if (!hs_dir && !options->StrictNodes) {
hs_dir = smartlist_choose(responsible_dirs);
+ }
smartlist_free(responsible_dirs);
smartlist_free(usable_responsible_dirs);
@@ -677,14 +669,52 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query)
"requested hidden service: they are all either down or "
"excluded, and StrictNodes is set.");
}
- return 0;
+ } 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);
}
- /* 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
+ * during this attempt to connect to this hidden service; on success, return 1,
+ * in the case that no hidden service directory is left to ask for the
+ * descriptor, return 0, and in case of a failure -1. */
+static int
+directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
+ routerstatus_t *rs_hsdir)
+{
+ routerstatus_t *hs_dir = rs_hsdir;
+ char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
+ char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
+#ifdef ENABLE_TOR2WEB_MODE
+ const int tor2web_mode = get_options()->Tor2webMode;
+ const int how_to_fetch = tor2web_mode ? DIRIND_ONEHOP : DIRIND_ANONYMOUS;
+#else
+ const int how_to_fetch = DIRIND_ANONYMOUS;
+#endif
+
+ tor_assert(desc_id);
+
+ base32_encode(desc_id_base32, sizeof(desc_id_base32),
+ desc_id, DIGEST_LEN);
- /* Encode descriptor cookie for logging purposes. */
+ /* Automatically pick an hs dir if none given. */
+ if (!rs_hsdir) {
+ hs_dir = pick_hsdir(desc_id);
+ if (!hs_dir) {
+ /* No suitable hs dir can be found, stop right now. */
+ return 0;
+ }
+ }
+
+ /* Encode descriptor cookie for logging purposes. Also, if the cookie is
+ * malformed, no fetch is triggered thus this needs to be done before the
+ * fetch request. */
if (rend_query->auth_type != REND_NO_AUTH) {
if (base64_encode(descriptor_cookie_base64,
sizeof(descriptor_cookie_base64),
@@ -724,16 +754,136 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query)
return 1;
}
+/** Fetch a v2 descriptor using the given descriptor id. If any hsdir(s) are
+ * given, they will be used instead.
+ *
+ * On success, 1 is returned. If no hidden service is left to ask, return 0.
+ * On error, -1 is returned. */
+static int
+fetch_v2_desc_by_descid(const char *desc_id, const rend_data_t *rend_query,
+ smartlist_t *hsdirs)
+{
+ int ret;
+
+ tor_assert(rend_query);
+
+ if (!hsdirs) {
+ ret = directory_get_from_hs_dir(desc_id, rend_query, NULL);
+ goto end; /* either success or failure, but we're done */
+ }
+
+ /* Using the given hsdir list, trigger a fetch on each of them. */
+ SMARTLIST_FOREACH_BEGIN(hsdirs, routerstatus_t *, hs_dir) {
+ /* This should always be a success. */
+ ret = directory_get_from_hs_dir(desc_id, rend_query, hs_dir);
+ tor_assert(ret);
+ } SMARTLIST_FOREACH_END(hs_dir);
+
+ /* Everything went well. */
+ ret = 0;
+
+end:
+ return ret;
+}
+
+/** Fetch a v2 descriptor using the onion address in the given query object.
+ * This will compute the descriptor id for each replicas and fetch it on the
+ * given hsdir(s) if any or the responsible ones that are choosen
+ * automatically.
+ *
+ * On success, 1 is returned. If no hidden service is left to ask, return 0.
+ * On error, -1 is returned. */
+static int
+fetch_v2_desc_by_addr(const rend_data_t *query,
+ smartlist_t *hsdirs)
+{
+ char descriptor_id[DIGEST_LEN];
+ int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
+ int i, tries_left, ret;
+
+ tor_assert(query);
+
+ /* Randomly iterate over the replicas until a descriptor can be fetched
+ * from one of the consecutive nodes, or no options are left. */
+ for (i = 0; i < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; i++) {
+ replicas_left_to_try[i] = i;
+ }
+
+ tries_left = REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
+ while (tries_left > 0) {
+ int rand = crypto_rand_int(tries_left);
+ int chosen_replica = replicas_left_to_try[rand];
+ replicas_left_to_try[rand] = replicas_left_to_try[--tries_left];
+
+ ret = rend_compute_v2_desc_id(descriptor_id, query->onion_address,
+ query->auth_type == REND_STEALTH_AUTH ?
+ query->descriptor_cookie : NULL,
+ time(NULL), chosen_replica);
+ if (ret < 0) {
+ /* Normally, on failure the descriptor_id is untouched but let's be
+ * safe in general in case the function changes at some point. */
+ goto end;
+ }
+
+ /* Trigger the fetch with the computed descriptor ID. */
+ ret = fetch_v2_desc_by_descid(descriptor_id, query, hsdirs);
+ if (ret != 0) {
+ /* Either on success or failure, as long as we tried a fetch we are
+ * done here. */
+ goto end;
+ }
+ }
+
+ /* If we come here, there are no hidden service directories left. */
+ log_info(LD_REND, "Could not pick one of the responsible hidden "
+ "service directories to fetch descriptors, because "
+ "we already tried them all unsuccessfully.");
+ ret = 0;
+
+end:
+ memwipe(descriptor_id, 0, sizeof(descriptor_id));
+ return ret;
+}
+
+/** Fetch a v2 descriptor using the given query. If any hsdir are specified,
+ * use them for the fetch.
+ *
+ * On success, 1 is returned. If no hidden service is left to ask, return 0.
+ * On error, -1 is returned. */
+int
+rend_client_fetch_v2_desc(const rend_data_t *query,
+ smartlist_t *hsdirs)
+{
+ int ret;
+
+ tor_assert(query);
+
+ /* Depending on what's available in the rend data query object, we will
+ * trigger a fetch by HS address or using a descriptor ID. */
+
+ if (query->onion_address[0] != '\0') {
+ ret = fetch_v2_desc_by_addr(query, hsdirs);
+ } else if (query->descriptor_id[0] != '\0') {
+ ret = fetch_v2_desc_by_descid(query->descriptor_id, query, hsdirs);
+ } else {
+ /* Query data is invalid. */
+ ret = -1;
+ goto error;
+ }
+
+error:
+ return ret;
+}
+
/** Unless we already have a descriptor for <b>rend_query</b> with at least
* one (possibly) working introduction point in it, start a connection to a
* hidden service directory to fetch a v2 rendezvous service descriptor. */
void
rend_client_refetch_v2_renddesc(const rend_data_t *rend_query)
{
- char descriptor_id[DIGEST_LEN];
- int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
- int i, tries_left;
+ int ret;
rend_cache_entry_t *e = NULL;
+
tor_assert(rend_query);
/* Are we configured to fetch descriptors? */
if (!get_options()->FetchHidServDescriptors) {
@@ -750,44 +900,12 @@ rend_client_refetch_v2_renddesc(const rend_data_t *rend_query)
}
log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s",
safe_str_client(rend_query->onion_address));
- /* Randomly iterate over the replicas until a descriptor can be fetched
- * from one of the consecutive nodes, or no options are left. */
- tries_left = REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
- for (i = 0; i < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; i++)
- replicas_left_to_try[i] = i;
- while (tries_left > 0) {
- int rand = crypto_rand_int(tries_left);
- int chosen_replica = replicas_left_to_try[rand];
- replicas_left_to_try[rand] = replicas_left_to_try[--tries_left];
- if (rend_compute_v2_desc_id(descriptor_id, rend_query->onion_address,
- rend_query->auth_type == REND_STEALTH_AUTH ?
- rend_query->descriptor_cookie : NULL,
- time(NULL), chosen_replica) < 0) {
- log_warn(LD_REND, "Internal error: Computing v2 rendezvous "
- "descriptor ID did not succeed.");
- /*
- * Hmm, can this write anything to descriptor_id and still fail?
- * Let's clear it just to be safe.
- *
- * From here on, any returns should goto done which clears
- * descriptor_id so we don't leave key-derived material on the stack.
- */
- goto done;
- }
- if (directory_get_from_hs_dir(descriptor_id, rend_query) != 0)
- goto done; /* either success or failure, but we're done */
+ ret = rend_client_fetch_v2_desc(rend_query, NULL);
+ if (ret <= 0) {
+ /* Close pending connections on error or if no hsdir can be found. */
+ rend_client_desc_trynow(rend_query->onion_address);
}
- /* If we come here, there are no hidden service directories left. */
- log_info(LD_REND, "Could not pick one of the responsible hidden "
- "service directories to fetch descriptors, because "
- "we already tried them all unsuccessfully.");
- /* Close pending connections. */
- rend_client_desc_trynow(rend_query->onion_address);
-
- done:
- memwipe(descriptor_id, 0, sizeof(descriptor_id));
-
return;
}
diff --git a/src/or/rendclient.h b/src/or/rendclient.h
index 098c61d..d6fea67 100644
--- a/src/or/rendclient.h
+++ b/src/or/rendclient.h
@@ -20,6 +20,8 @@ int rend_client_introduction_acked(origin_circuit_t *circ,
const uint8_t *request,
size_t request_len);
void rend_client_refetch_v2_renddesc(const rend_data_t *rend_query);
+int rend_client_fetch_v2_desc(const rend_data_t *query,
+ smartlist_t *hsdirs);
void rend_client_cancel_descriptor_fetches(void);
void rend_client_purge_last_hid_serv_requests(void);
diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c
index 3fea07f..3d9cc23 100644
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@ -1174,6 +1174,9 @@ rend_cache_store_v2_desc_as_client(const char *desc,
tor_assert(desc);
tor_assert(desc_id_base32);
memset(want_desc_id, 0, sizeof(want_desc_id));
+ if (entry) {
+ *entry = NULL;
+ }
if (base32_decode(want_desc_id, sizeof(want_desc_id),
desc_id_base32, strlen(desc_id_base32)) != 0) {
log_warn(LD_BUG, "Couldn't decode base32 %s for descriptor id.",
@@ -1192,7 +1195,8 @@ rend_cache_store_v2_desc_as_client(const char *desc,
log_warn(LD_REND, "Couldn't compute service ID.");
goto err;
}
- if (strcmp(rend_query->onion_address, service_id)) {
+ if (rend_query->onion_address[0] != '\0' &&
+ strcmp(rend_query->onion_address, service_id)) {
log_warn(LD_REND, "Received service descriptor for service ID %s; "
"expected descriptor for service ID %s.",
service_id, safe_str(rend_query->onion_address));
@@ -1239,7 +1243,7 @@ rend_cache_store_v2_desc_as_client(const char *desc,
"service descriptor for %s. This is probably a (misguided) "
"attempt to improve reliability, but it could also be an "
"attempt to do a guard enumeration attack. Rejecting.",
- safe_str_client(rend_query->onion_address));
+ safe_str_client(service_id));
goto err;
}