prop224: Implement a service intro point failure cache

Imagine a Tor network where you have only 8 nodes available due to some
reasons. And your hidden service wants 8 introduction points. Everything is
fine but then a node goes down bringing the network to 7. The service will
retry 3 times that node and then give up but keep it in a failure cache for 5
minutes (INTRO_CIRC_RETRY_PERIOD) so it doesn't retry it non stop and exhaust
the maximum number of circuit retry.

In the real public network today, this is unlikely to happen unless the
ExcludeNodes list is extremely restrictive.

Signed-off-by: David Goulet <dgoulet@torproject.org>
This commit is contained in:
David Goulet 2017-05-09 14:31:17 -04:00 committed by Nick Mathewson
parent ac848777f9
commit feed375f19
2 changed files with 86 additions and 1 deletions

View File

@ -889,6 +889,7 @@ service_descriptor_free(hs_service_descriptor_t *desc)
smartlist_free(desc->hsdir_missing_info);
/* Cleanup all intro points. */
digest256map_free(desc->intro_points.map, service_intro_point_free_);
digestmap_free(desc->intro_points.failed_id, tor_free_);
tor_free(desc);
}
@ -900,10 +901,71 @@ service_descriptor_new(void)
sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t));
/* Initialize the intro points map. */
sdesc->intro_points.map = digest256map_new();
sdesc->intro_points.failed_id = digestmap_new();
sdesc->hsdir_missing_info = smartlist_new();
return sdesc;
}
/* From the given service, remove all expired failing intro points for each
* descriptor. */
static void
remove_expired_failing_intro(hs_service_t *service, time_t now)
{
tor_assert(service);
/* For both descriptors, cleanup the failing intro points list. */
FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) {
time_t failure_time = *t;
if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) {
MAP_DEL_CURRENT(key);
tor_free(t);
}
} DIGESTMAP_FOREACH_END;
} FOR_EACH_DESCRIPTOR_END;
}
/* For the given descriptor desc, put all node_t object found from its failing
* intro point list and put them in the given node_list. */
static void
setup_intro_point_exclude_list(const hs_service_descriptor_t *desc,
smartlist_t *node_list)
{
tor_assert(desc);
tor_assert(node_list);
DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) {
(void) t; /* Make gcc happy. */
const node_t *node = node_get_by_id(key);
if (node) {
smartlist_add(node_list, (void *) node);
}
} DIGESTMAP_FOREACH_END;
}
/* For the given failing intro point ip, we add its time of failure to the
* failed map and index it by identity digest (legacy ID) in the descriptor
* desc failed id map. */
static void
remember_failing_intro_point(const hs_service_intro_point_t *ip,
hs_service_descriptor_t *desc, time_t now)
{
time_t *time_of_failure, *prev_ptr;
const hs_desc_link_specifier_t *legacy_ls;
tor_assert(ip);
tor_assert(desc);
time_of_failure = tor_malloc_zero(sizeof(time_t));
*time_of_failure = now;
legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID);
tor_assert(legacy_ls);
prev_ptr = digestmap_set(desc->intro_points.failed_id,
(const char *) legacy_ls->u.legacy_id,
time_of_failure);
tor_free(prev_ptr);
}
/* Copy the descriptor link specifier object from src to dst. */
static void
link_specifier_copy(hs_desc_link_specifier_t *dst,
@ -1318,6 +1380,9 @@ pick_needed_intro_points(hs_service_t *service,
hs_service_intro_point_t *, ip) {
smartlist_add(exclude_nodes, (void *) get_node_from_intro_point(ip));
} DIGEST256MAP_FOREACH_END;
/* Also, add the failing intro points that our descriptor encounteered in
* the exclude node list. */
setup_intro_point_exclude_list(desc, exclude_nodes);
for (i = 0; i < num_needed_ip; i++) {
hs_service_intro_point_t *ip;
@ -1462,9 +1527,19 @@ cleanup_intro_points(hs_service_t *service, time_t now)
* reached the maximum number of retry with a non existing circuit. */
if (has_expired || node == NULL ||
(ocirc == NULL &&
ip->circuit_retries >= MAX_INTRO_POINT_CIRCUIT_RETRIES)) {
ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES)) {
/* Remove intro point from descriptor map. We'll add it to the failed
* map if we retried it too many times. */
MAP_DEL_CURRENT(key);
/* We've retried too many times, remember it has a failed intro point
* so we don't pick it up again. It will be retried in
* INTRO_CIRC_RETRY_PERIOD seconds. */
if (ip->circuit_retries >= MAX_INTRO_POINT_CIRCUIT_RETRIES) {
remember_failing_intro_point(ip, desc, now);
}
service_intro_point_free(ip);
/* XXX: Legacy code does NOT do that, it keeps the circuit open until
* a new descriptor is uploaded and then closed all expiring intro
* point circuit. Here, we close immediately and because we just
@ -1550,6 +1625,10 @@ run_housekeeping_event(time_t now)
/* Cleanup invalid intro points from the service descriptor. */
cleanup_intro_points(service, now);
/* Remove expired failing intro point from the descriptor failed list. We
* reset them at each INTRO_CIRC_RETRY_PERIOD. */
remove_expired_failing_intro(service, now);
/* At this point, the service is now ready to go through the scheduled
* events guaranteeing a valid state. Intro points might be missing from
* the descriptors after the cleanup but the update/build process will

View File

@ -87,6 +87,12 @@ typedef struct hs_service_intropoints_t {
/* Contains the current hs_service_intro_point_t objects indexed by
* authentication public key. */
digest256map_t *map;
/* Contains node's identity key digest that were introduction point for this
* descriptor but were retried to many times. We keep those so we avoid
* re-picking them over and over for a circuit retry period.
* XXX: Once we have #22173, change this to only use ed25519 identity. */
digestmap_t *failed_id;
} hs_service_intropoints_t;
/* Representation of a service descriptor. */