diff options
author | George Kadianakis <desnacked@riseup.net> | 2021-07-01 17:42:34 +0300 |
---|---|---|
committer | George Kadianakis <desnacked@riseup.net> | 2021-07-01 18:15:55 +0300 |
commit | 314a6b42c59c7d9ea240b758ccffd796963efd0f (patch) | |
tree | 0fefab4b7fabf6ec93ae6772e95265caa5ee1e53 /src | |
parent | e71db3a4be1cab933f721d63d4ed98f50dddd7c0 (diff) | |
download | tor-314a6b42c59c7d9ea240b758ccffd796963efd0f.tar.gz tor-314a6b42c59c7d9ea240b758ccffd796963efd0f.zip |
Introduce vanguards-lite subsystem and some of its entry points
Co-authored-by: Mike Perry <mikeperry-git@torproject.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/core/mainloop/mainloop.c | 20 | ||||
-rw-r--r-- | src/feature/client/entrynodes.c | 202 | ||||
-rw-r--r-- | src/feature/client/entrynodes.h | 4 | ||||
-rw-r--r-- | src/test/test_entrynodes.c | 25 |
4 files changed, 251 insertions, 0 deletions
diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 69606c0d53..68e8ee8f20 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1293,6 +1293,7 @@ signewnym_impl(time_t now) circuit_mark_all_dirty_circs_as_unusable(); addressmap_clear_transient(); hs_client_purge_state(); + purge_vanguards_lite(); time_of_last_signewnym = now; signewnym_is_pending = 0; @@ -1370,6 +1371,7 @@ CALLBACK(save_state); CALLBACK(write_stats_file); CALLBACK(control_per_second_events); CALLBACK(second_elapsed); +CALLBACK(manage_vglite); #undef CALLBACK @@ -1392,6 +1394,9 @@ STATIC periodic_event_item_t mainloop_periodic_events[] = { CALLBACK(second_elapsed, NET_PARTICIPANT, FL(RUN_ON_DISABLE)), + /* Update vanguards-lite once per hour, if we have networking */ + CALLBACK(manage_vglite, NET_PARTICIPANT, FL(NEED_NET)), + /* XXXX Do we have a reason to do this on a callback? Does it do any good at * all? For now, if we're dormant, we can let our listeners decay. */ CALLBACK(retry_listeners, NET_PARTICIPANT, FL(NEED_NET)), @@ -1662,6 +1667,21 @@ mainloop_schedule_shutdown(int delay_sec) mainloop_event_schedule(scheduled_shutdown_ev, &delay_tv); } +/** + * Update vanguards-lite layer2 nodes, once per hour + */ +static int +manage_vglite_callback(time_t now, const or_options_t *options) +{ + (void)now; + (void)options; +#define VANGUARDS_LITE_INTERVAL (60*60) + + maintain_layer2_guards(); + + return VANGUARDS_LITE_INTERVAL; +} + /** Perform regular maintenance tasks. This function gets run once per * second. */ diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c index 502cb99690..58faa8033c 100644 --- a/src/feature/client/entrynodes.c +++ b/src/feature/client/entrynodes.c @@ -3930,6 +3930,197 @@ guard_selection_free_(guard_selection_t *gs) tor_free(gs); } +/**********************************************************************/ + +/** Layer2 guard subsystem used for client-side onion service circuits. */ + +/** A simple representation of a layer2 guard. We just need its identity so + * that we feed it into a routerset, and a sampled timestamp to do expiration + * checks. */ +typedef struct layer2_guard_t { + /** Identity of the guard */ + char identity[DIGEST_LEN]; + /** When does this guard expire? (randomized timestamp) */ + time_t expire_on_date; +} layer2_guard_t; + +/** Global list and routerset of L2 guards. They are both synced and they get + * updated periodically. We need both the list and the routerset: we use the + * smartlist to keep track of expiration times and the routerset is what we + * return to the users of this subsystem. */ +static smartlist_t *layer2_guards = NULL; +static routerset_t *layer2_routerset = NULL; + +/** Number of L2 guards */ +#define NUMBER_SECOND_GUARDS 4 +/** Lifetime of L2 guards: + * 1 to 12 days, for an average of a week using the max(x,x) distribution */ +#define MIN_SECOND_GUARD_LIFETIME (3600*24) +#define MAX_SECOND_GUARD_LIFETIME (3600*24*12) + +/** Return the number of guards our L2 guardset should have */ +static int +get_number_of_layer2_hs_guards(void) +{ + return (int) networkstatus_get_param(NULL, + "guard-hs-l2-number", + NUMBER_SECOND_GUARDS, + 1, INT32_MAX); +} + +/** Return the minimum lifetime of L2 guards */ +static int +get_min_lifetime_of_layer2_hs_guards(void) +{ + return (int) networkstatus_get_param(NULL, + "guard-hs-l2-lifetime-min", + MIN_SECOND_GUARD_LIFETIME, + 1, INT32_MAX); +} + +/** Return the maximum lifetime of L2 guards */ +static int +get_max_lifetime_of_layer2_hs_guards(void) +{ + return (int) networkstatus_get_param(NULL, + "guard-hs-l2-lifetime-max", + MAX_SECOND_GUARD_LIFETIME, + 1, INT32_MAX); +} + +/** + * Sample and return a lifetime for an L2 guard. + * + * Lifetime randomized uniformly between min and max consensus params. + */ +static int +get_layer2_hs_guard_lifetime(void) +{ + return crypto_rand_int_range(get_min_lifetime_of_layer2_hs_guards(), + get_max_lifetime_of_layer2_hs_guards()); +} + +/** Maintain the L2 guard list. Make sure the list contains enough guards, do + * expirations as necessary, and keep all the data structures of this + * subsystem synchronized */ +void +maintain_layer2_guards(void) +{ + if (!router_have_minimum_dir_info()) { + return; + } + + /* Create the list if it doesn't exist */ + if (!layer2_guards) { + layer2_guards = smartlist_new(); + } + + /* Go through the list and perform any needed expirations */ + SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { + /* Expire based on expiration date */ + if (g->expire_on_date <= approx_time()) { + log_info(LD_GENERAL, "Removing expired Layer2 guard %s", + safe_str_client(hex_str(g->identity, DIGEST_LEN))); + // Nickname may be gone from consensus and doesn't matter anyway + control_event_guard("None", g->identity, "BAD_L2"); + tor_free(g); + SMARTLIST_DEL_CURRENT_KEEPORDER(layer2_guards, g); + continue; + } + + /* Expire if relay has left consensus */ + if (router_get_consensus_status_by_id(g->identity) == NULL) { + log_info(LD_GENERAL, "Removing missing Layer2 guard %s", + safe_str_client(hex_str(g->identity, DIGEST_LEN))); + // Nickname may be gone from consensus and doesn't matter anyway + control_event_guard("None", g->identity, "BAD_L2"); + tor_free(g); + SMARTLIST_DEL_CURRENT_KEEPORDER(layer2_guards, g); + continue; + } + } SMARTLIST_FOREACH_END(g); + + /* Find out how many guards we need to add */ + int new_guards_needed_n = + get_number_of_layer2_hs_guards() - smartlist_len(layer2_guards); + if (new_guards_needed_n <= 0) { + return; + } + + log_info(LD_GENERAL, "Adding %d guards to Layer2 routerset", + new_guards_needed_n); + + /* Add required guards to the list */ + for (int i = 0; i < new_guards_needed_n; i++) { + const node_t *choice = NULL; + const or_options_t *options = get_options(); + /* Pick Stable nodes */ + router_crn_flags_t flags = CRN_NEED_DESC|CRN_NEED_UPTIME; + choice = router_choose_random_node(NULL, options->ExcludeNodes, flags); + if (choice) { + /* We found our node: create an L2 guard out of it */ + layer2_guard_t *layer2_guard = tor_malloc_zero(sizeof(layer2_guard_t)); + memcpy(layer2_guard->identity, choice->identity, DIGEST_LEN); + layer2_guard->expire_on_date = approx_time() + + get_layer2_hs_guard_lifetime(); + smartlist_add(layer2_guards, layer2_guard); + log_info(LD_GENERAL, "Adding Layer2 guard %s", + safe_str_client(hex_str(layer2_guard->identity, DIGEST_LEN))); + // Nickname can also be None here because it is looked up later + control_event_guard("None", layer2_guard->identity, + "GOOD_L2"); + } + } + + /* Now that the list is up to date, synchronize the routerset */ + routerset_free(layer2_routerset); + layer2_routerset = routerset_new(); + + SMARTLIST_FOREACH_BEGIN (layer2_guards, layer2_guard_t *, g) { + routerset_parse(layer2_routerset, + hex_str(g->identity, DIGEST_LEN), + "l2 guards"); + } SMARTLIST_FOREACH_END(g); +} + +/** + * Reset vanguards-lite list(s). + * + * Used for SIGNAL NEWNYM. + */ +void +purge_vanguards_lite(void) +{ + if (!layer2_guards) + return; + + /* Go through the list and perform any needed expirations */ + SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { + tor_free(g); + } SMARTLIST_FOREACH_END(g); + + smartlist_clear(layer2_guards); + + /* Pick new l2 guards */ + maintain_layer2_guards(); +} + +/** Return a routerset containing the L2 guards or NULL if it's not yet + * initialized. Callers must not free the routerset. Designed for use in + * pick_vanguard_middle_node() and should not be used anywhere else (because + * the routerset pointer can dangle under your feet) */ +routerset_t * +get_layer2_guards(void) +{ + if (!layer2_guards) { + maintain_layer2_guards(); + } + + return layer2_routerset; +} + +/*****************************************************************************/ + /** Release all storage held by the list of entry guards and related * memory structs. */ void @@ -3946,4 +4137,15 @@ entry_guards_free_all(void) guard_contexts = NULL; } circuit_build_times_free_timeouts(get_circuit_build_times_mutable()); + + if (!layer2_guards) { + return; + } + + SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { + tor_free(g); + } SMARTLIST_FOREACH_END(g); + + smartlist_free(layer2_guards); + routerset_free(layer2_routerset); } diff --git a/src/feature/client/entrynodes.h b/src/feature/client/entrynodes.h index 88ed8f649e..c1bc5b41c1 100644 --- a/src/feature/client/entrynodes.h +++ b/src/feature/client/entrynodes.h @@ -651,4 +651,8 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, int orig_bandwidth, uint32_t guardfraction_percentage); +routerset_t *get_layer2_guards(void); +void maintain_layer2_guards(void); +void purge_vanguards_lite(void); + #endif /* !defined(TOR_ENTRYNODES_H) */ diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index c94b5d6a23..785ce296d3 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -92,6 +92,12 @@ bfn_mock_node_get_by_id(const char *id) return NULL; } +static int +mock_router_have_minimum_dir_info(void) +{ + return 1; +} + /* Helper function to free a test node. */ static void test_node_free(node_t *n) @@ -3087,6 +3093,23 @@ test_entry_guard_vanguard_path_selection(void *arg) circuit_free_(circ); } +static void +test_entry_guard_layer2_guards(void *arg) +{ + (void) arg; + MOCK(router_have_minimum_dir_info, mock_router_have_minimum_dir_info); + + /* Create the guardset */ + maintain_layer2_guards(); + + routerset_t *l2_guards = get_layer2_guards(); + tt_assert(l2_guards); + tt_int_op(routerset_len(l2_guards), OP_EQ, 4); + + done: + UNMOCK(router_have_minimum_dir_info); +} + static const struct testcase_setup_t big_fake_network = { big_fake_network_setup, big_fake_network_cleanup }; @@ -3152,6 +3175,8 @@ struct testcase_t entrynodes_tests[] = { BFN_TEST(manage_primary), BFN_TEST(correct_cascading_order), + BFN_TEST(layer2_guards), + EN_TEST_FORK(guard_preferred), BFN_TEST(select_for_circuit_no_confirmed), |