mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2025-02-25 07:07:52 +01:00
Introduce vanguards-lite subsystem and some of its entry points
Co-authored-by: Mike Perry <mikeperry-git@torproject.org>
This commit is contained in:
parent
e71db3a4be
commit
314a6b42c5
5 changed files with 260 additions and 0 deletions
9
changes/ticket40363
Normal file
9
changes/ticket40363
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
o Major features (Proposal 332, onion services, guard selection algorithm):
|
||||||
|
- Clients and onion services now choose four long-lived "layer 2" guard
|
||||||
|
relays for use as the middle hop in all onion circuits. These relays are
|
||||||
|
kept in place for a randomized duration averaging 1 week each. This
|
||||||
|
mitigates guard discovery attacks against clients and short-lived onion
|
||||||
|
services such as OnionShare. Long-lived onion services that need high
|
||||||
|
security should still use the Vanguards addon
|
||||||
|
(https://github.com/mikeperry-tor/vanguards). Closes ticket 40363;
|
||||||
|
implements proposal 332.
|
|
@ -1293,6 +1293,7 @@ signewnym_impl(time_t now)
|
||||||
circuit_mark_all_dirty_circs_as_unusable();
|
circuit_mark_all_dirty_circs_as_unusable();
|
||||||
addressmap_clear_transient();
|
addressmap_clear_transient();
|
||||||
hs_client_purge_state();
|
hs_client_purge_state();
|
||||||
|
purge_vanguards_lite();
|
||||||
time_of_last_signewnym = now;
|
time_of_last_signewnym = now;
|
||||||
signewnym_is_pending = 0;
|
signewnym_is_pending = 0;
|
||||||
|
|
||||||
|
@ -1370,6 +1371,7 @@ CALLBACK(save_state);
|
||||||
CALLBACK(write_stats_file);
|
CALLBACK(write_stats_file);
|
||||||
CALLBACK(control_per_second_events);
|
CALLBACK(control_per_second_events);
|
||||||
CALLBACK(second_elapsed);
|
CALLBACK(second_elapsed);
|
||||||
|
CALLBACK(manage_vglite);
|
||||||
|
|
||||||
#undef CALLBACK
|
#undef CALLBACK
|
||||||
|
|
||||||
|
@ -1392,6 +1394,9 @@ STATIC periodic_event_item_t mainloop_periodic_events[] = {
|
||||||
CALLBACK(second_elapsed, NET_PARTICIPANT,
|
CALLBACK(second_elapsed, NET_PARTICIPANT,
|
||||||
FL(RUN_ON_DISABLE)),
|
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
|
/* 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. */
|
* all? For now, if we're dormant, we can let our listeners decay. */
|
||||||
CALLBACK(retry_listeners, NET_PARTICIPANT, FL(NEED_NET)),
|
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);
|
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
|
/** Perform regular maintenance tasks. This function gets run once per
|
||||||
* second.
|
* second.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3930,6 +3930,197 @@ guard_selection_free_(guard_selection_t *gs)
|
||||||
tor_free(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
|
/** Release all storage held by the list of entry guards and related
|
||||||
* memory structs. */
|
* memory structs. */
|
||||||
void
|
void
|
||||||
|
@ -3946,4 +4137,15 @@ entry_guards_free_all(void)
|
||||||
guard_contexts = NULL;
|
guard_contexts = NULL;
|
||||||
}
|
}
|
||||||
circuit_build_times_free_timeouts(get_circuit_build_times_mutable());
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -651,4 +651,8 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
|
||||||
int orig_bandwidth,
|
int orig_bandwidth,
|
||||||
uint32_t guardfraction_percentage);
|
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) */
|
#endif /* !defined(TOR_ENTRYNODES_H) */
|
||||||
|
|
|
@ -92,6 +92,12 @@ bfn_mock_node_get_by_id(const char *id)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mock_router_have_minimum_dir_info(void)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Helper function to free a test node. */
|
/* Helper function to free a test node. */
|
||||||
static void
|
static void
|
||||||
test_node_free(node_t *n)
|
test_node_free(node_t *n)
|
||||||
|
@ -3087,6 +3093,23 @@ test_entry_guard_vanguard_path_selection(void *arg)
|
||||||
circuit_free_(circ);
|
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 = {
|
static const struct testcase_setup_t big_fake_network = {
|
||||||
big_fake_network_setup, big_fake_network_cleanup
|
big_fake_network_setup, big_fake_network_cleanup
|
||||||
};
|
};
|
||||||
|
@ -3152,6 +3175,8 @@ struct testcase_t entrynodes_tests[] = {
|
||||||
BFN_TEST(manage_primary),
|
BFN_TEST(manage_primary),
|
||||||
BFN_TEST(correct_cascading_order),
|
BFN_TEST(correct_cascading_order),
|
||||||
|
|
||||||
|
BFN_TEST(layer2_guards),
|
||||||
|
|
||||||
EN_TEST_FORK(guard_preferred),
|
EN_TEST_FORK(guard_preferred),
|
||||||
|
|
||||||
BFN_TEST(select_for_circuit_no_confirmed),
|
BFN_TEST(select_for_circuit_no_confirmed),
|
||||||
|
|
Loading…
Add table
Reference in a new issue