prop224: Add a client intro point state cache

This cache keeps track of the state of intro points which is needed when we
have failures when using them. It is similar to the failure cache of the
legacy system.

At this commit, it is unused but initialized, cleanup and freed.

Signed-off-by: David Goulet <dgoulet@torproject.org>
This commit is contained in:
David Goulet 2017-07-27 15:51:32 -04:00
parent 88b843608a
commit 2671399e67
3 changed files with 267 additions and 0 deletions

View File

@ -324,6 +324,11 @@ hs_cache_clean_as_dir(time_t now)
/* 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;
/* Remove a given descriptor from our cache. */
static void
remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc)
@ -410,6 +415,172 @@ cache_client_desc_free_(void *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. */
@ -554,6 +725,59 @@ hs_cache_clean_as_client(time_t now)
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
@ -629,6 +853,9 @@ hs_cache_init(void)
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. */
@ -640,5 +867,9 @@ hs_cache_free_all(void)
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 {
@ -59,6 +85,15 @@ 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. */

View File

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