diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index d93e682dc3..28cb4d1bd9 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -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 client_desc 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; } diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h index 79456f69c8..2a4d2dbb2f 100644 --- a/src/or/hs_cache.h +++ b/src/or/hs_cache.h @@ -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. */ diff --git a/src/or/main.c b/src/or/main.c index 3db0b5e901..20fec50093 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -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; }