From 2070765c7c062c505358d0f1c83f2846181d1667 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 5 Nov 2018 16:09:13 -0500 Subject: [PATCH 01/18] Use macros to make the periodic event table less verbose. --- src/core/mainloop/mainloop.c | 78 ++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 7eff82fee4..12820888f2 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1370,74 +1370,66 @@ CALLBACK(write_stats_file); #undef CALLBACK /* Now we declare an array of periodic_event_item_t for each periodic event */ -#define CALLBACK(name, r, f) PERIODIC_EVENT(name, r, f) +#define CALLBACK(name, r, f) \ + PERIODIC_EVENT(name, PERIODIC_EVENT_ROLE_ ## r, f) +#define FL(name) (PERIODIC_EVENT_FLAG_ ## name) STATIC periodic_event_item_t periodic_events[] = { /* Everyone needs to run those. */ - CALLBACK(add_entropy, PERIODIC_EVENT_ROLE_ALL, 0), - CALLBACK(check_expired_networkstatus, PERIODIC_EVENT_ROLE_ALL, 0), - CALLBACK(clean_caches, PERIODIC_EVENT_ROLE_ALL, 0), - CALLBACK(fetch_networkstatus, PERIODIC_EVENT_ROLE_ALL, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(heartbeat, PERIODIC_EVENT_ROLE_ALL, 0), - CALLBACK(launch_descriptor_fetches, PERIODIC_EVENT_ROLE_ALL, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(reset_padding_counts, PERIODIC_EVENT_ROLE_ALL, 0), - CALLBACK(retry_listeners, PERIODIC_EVENT_ROLE_ALL, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(save_state, PERIODIC_EVENT_ROLE_ALL, 0), - CALLBACK(rotate_x509_certificate, PERIODIC_EVENT_ROLE_ALL, 0), - CALLBACK(write_stats_file, PERIODIC_EVENT_ROLE_ALL, 0), + CALLBACK(add_entropy, ALL, 0), + CALLBACK(check_expired_networkstatus, ALL, 0), + CALLBACK(clean_caches, ALL, 0), + CALLBACK(fetch_networkstatus, ALL, 0), + CALLBACK(heartbeat, ALL, 0), + CALLBACK(launch_descriptor_fetches, ALL, FL(NEED_NET)), + CALLBACK(reset_padding_counts, ALL, 0), + CALLBACK(retry_listeners, ALL, FL(NEED_NET)), + CALLBACK(save_state, ALL, 0), + CALLBACK(rotate_x509_certificate, ALL, 0), + CALLBACK(write_stats_file, ALL, 0), /* Routers (bridge and relay) only. */ - CALLBACK(check_descriptor, PERIODIC_EVENT_ROLE_ROUTER, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(check_ed_keys, PERIODIC_EVENT_ROLE_ROUTER, 0), - CALLBACK(check_for_reachability_bw, PERIODIC_EVENT_ROLE_ROUTER, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(check_onion_keys_expiry_time, PERIODIC_EVENT_ROLE_ROUTER, 0), - CALLBACK(expire_old_ciruits_serverside, PERIODIC_EVENT_ROLE_ROUTER, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(reachability_warnings, PERIODIC_EVENT_ROLE_ROUTER, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(retry_dns, PERIODIC_EVENT_ROLE_ROUTER, 0), - CALLBACK(rotate_onion_key, PERIODIC_EVENT_ROLE_ROUTER, 0), + CALLBACK(check_descriptor, ROUTER, FL(NEED_NET)), + CALLBACK(check_ed_keys, ROUTER, 0), + CALLBACK(check_for_reachability_bw, ROUTER, FL(NEED_NET)), + CALLBACK(check_onion_keys_expiry_time, ROUTER, 0), + CALLBACK(expire_old_ciruits_serverside, ROUTER, FL(NEED_NET)), + CALLBACK(reachability_warnings, ROUTER, FL(NEED_NET)), + CALLBACK(retry_dns, ROUTER, 0), + CALLBACK(rotate_onion_key, ROUTER, 0), /* Authorities (bridge and directory) only. */ - CALLBACK(downrate_stability, PERIODIC_EVENT_ROLE_AUTHORITIES, 0), - CALLBACK(launch_reachability_tests, PERIODIC_EVENT_ROLE_AUTHORITIES, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(save_stability, PERIODIC_EVENT_ROLE_AUTHORITIES, 0), + CALLBACK(downrate_stability, AUTHORITIES, 0), + CALLBACK(launch_reachability_tests, AUTHORITIES, FL(NEED_NET)), + CALLBACK(save_stability, AUTHORITIES, 0), /* Directory authority only. */ - CALLBACK(check_authority_cert, PERIODIC_EVENT_ROLE_DIRAUTH, 0), - CALLBACK(dirvote, PERIODIC_EVENT_ROLE_DIRAUTH, PERIODIC_EVENT_FLAG_NEED_NET), + CALLBACK(check_authority_cert, DIRAUTH, 0), + CALLBACK(dirvote, DIRAUTH, FL(NEED_NET)), /* Relay only. */ - CALLBACK(check_canonical_channels, PERIODIC_EVENT_ROLE_RELAY, - PERIODIC_EVENT_FLAG_NEED_NET), - CALLBACK(check_dns_honesty, PERIODIC_EVENT_ROLE_RELAY, - PERIODIC_EVENT_FLAG_NEED_NET), + CALLBACK(check_canonical_channels, RELAY, FL(NEED_NET)), + CALLBACK(check_dns_honesty, RELAY, FL(NEED_NET)), /* Hidden Service service only. */ - CALLBACK(hs_service, PERIODIC_EVENT_ROLE_HS_SERVICE, - PERIODIC_EVENT_FLAG_NEED_NET), + CALLBACK(hs_service, HS_SERVICE, FL(NEED_NET)), /* Bridge only. */ - CALLBACK(record_bridge_stats, PERIODIC_EVENT_ROLE_BRIDGE, 0), + CALLBACK(record_bridge_stats, BRIDGE, 0), /* Client only. */ - CALLBACK(rend_cache_failure_clean, PERIODIC_EVENT_ROLE_CLIENT, 0), + CALLBACK(rend_cache_failure_clean, CLIENT, 0), /* Bridge Authority only. */ - CALLBACK(write_bridge_ns, PERIODIC_EVENT_ROLE_BRIDGEAUTH, 0), + CALLBACK(write_bridge_ns, BRIDGEAUTH, 0), /* Directory server only. */ - CALLBACK(clean_consdiffmgr, PERIODIC_EVENT_ROLE_DIRSERVER, 0), + CALLBACK(clean_consdiffmgr, DIRSERVER, 0), END_OF_PERIODIC_EVENTS }; #undef CALLBACK +#undef FL /* These are pointers to members of periodic_events[] that are used to * implement particular callbacks. We keep them separate here so that we From 6d84972eb8e27d5e9f1adea36fcc9a9879d718ad Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 5 Nov 2018 16:24:10 -0500 Subject: [PATCH 02/18] Add a function to schedule a periodic event once, then disable it --- src/core/mainloop/mainloop.c | 6 +++++- src/core/mainloop/periodic.c | 22 +++++++++++++++++----- src/core/mainloop/periodic.h | 7 ++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 12820888f2..42f6fb50c7 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1600,7 +1600,11 @@ rescan_periodic_events(const or_options_t *options) periodic_event_enable(item); } else { log_debug(LD_GENERAL, "Disabling periodic event %s", item->name); - periodic_event_disable(item); + if (item->flags & PERIODIC_EVENT_FLAG_FLUSH_ON_DISABLE) { + periodic_event_flush_and_disable(item); + } else { + periodic_event_disable(item); + } } } } diff --git a/src/core/mainloop/periodic.c b/src/core/mainloop/periodic.c index c1785eb38f..c290c3744e 100644 --- a/src/core/mainloop/periodic.c +++ b/src/core/mainloop/periodic.c @@ -45,10 +45,6 @@ periodic_event_dispatch(mainloop_event_t *ev, void *data) periodic_event_item_t *event = data; tor_assert(ev == event->ev); - if (BUG(!periodic_event_is_enabled(event))) { - return; - } - time_t now = time(NULL); update_current_time(now); const or_options_t *options = get_options(); @@ -57,7 +53,7 @@ periodic_event_dispatch(mainloop_event_t *ev, void *data) int next_interval = 0; if (!periodic_event_is_enabled(event)) { - /* The event got disabled from inside its callback; no need to + /* The event got disabled from inside its callback, or before: no need to * reschedule. */ return; } @@ -172,3 +168,19 @@ periodic_event_disable(periodic_event_item_t *event) mainloop_event_cancel(event->ev); event->enabled = 0; } + +/** + * Disable an event, then schedule it to run once. + * Do nothing if the event was already disabled. + */ +void +periodic_event_flush_and_disable(periodic_event_item_t *event) +{ + tor_assert(event); + if (!periodic_event_is_enabled(event)) + return; + + periodic_event_disable(event); + + mainloop_event_activate(event->ev); +} diff --git a/src/core/mainloop/periodic.h b/src/core/mainloop/periodic.h index 4c8c3c96cc..7c71be7bc4 100644 --- a/src/core/mainloop/periodic.h +++ b/src/core/mainloop/periodic.h @@ -39,6 +39,11 @@ * the net_is_disabled() check. */ #define PERIODIC_EVENT_FLAG_NEED_NET (1U << 0) +/* Indicate that it the event is enabled, it event needs to be run once before + * it becomes disabled. + */ +#define PERIODIC_EVENT_FLAG_FLUSH_ON_DISABLE (1U << 1) + /** Callback function for a periodic event to take action. The return value * influences the next time the function will get called. Return * PERIODIC_EVENT_NO_UPDATE to not update last_action_time and be polled @@ -83,6 +88,6 @@ void periodic_event_destroy(periodic_event_item_t *event); void periodic_event_reschedule(periodic_event_item_t *event); void periodic_event_enable(periodic_event_item_t *event); void periodic_event_disable(periodic_event_item_t *event); +void periodic_event_flush_and_disable(periodic_event_item_t *event); #endif /* !defined(TOR_PERIODIC_H) */ - From b9a88bd53ae79a29c292275381bc7dbaa3804034 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 6 Nov 2018 07:27:31 -0500 Subject: [PATCH 03/18] Add new "ALL" and "NET_PARTICIPANT" roles for periodic events The previous "ALL" role was the OR of a bunch of other roles, which is a mistake: it's better if "ALL" means "all". The "NET_PARTICIPANT" role refers to the anything that is actively building circuits, downloading directory information, and participating in the Tor network. For now, it is set to !net_is_disabled(), but we're going to use it to implement a new "extra dormant mode". Closes ticket 28336. --- src/core/mainloop/mainloop.c | 36 ++++++++++++++++++------- src/core/mainloop/periodic.h | 7 +++-- src/test/test_periodic_event.c | 49 +++++++++++++++++++++------------- 3 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 42f6fb50c7..3c3f441a91 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1377,16 +1377,27 @@ CALLBACK(write_stats_file); STATIC periodic_event_item_t periodic_events[] = { /* Everyone needs to run those. */ CALLBACK(add_entropy, ALL, 0), - CALLBACK(check_expired_networkstatus, ALL, 0), - CALLBACK(clean_caches, ALL, 0), - CALLBACK(fetch_networkstatus, ALL, 0), CALLBACK(heartbeat, ALL, 0), - CALLBACK(launch_descriptor_fetches, ALL, FL(NEED_NET)), - CALLBACK(reset_padding_counts, ALL, 0), + + /* XXXX Do we have a reason to do this on a callback? */ CALLBACK(retry_listeners, ALL, FL(NEED_NET)), - CALLBACK(save_state, ALL, 0), - CALLBACK(rotate_x509_certificate, ALL, 0), - CALLBACK(write_stats_file, ALL, 0), + + /* We need to do these if we're participating in the Tor network. */ + CALLBACK(check_expired_networkstatus, NET_PARTICIPANT, 0), + CALLBACK(fetch_networkstatus, NET_PARTICIPANT, 0), + CALLBACK(launch_descriptor_fetches, NET_PARTICIPANT, FL(NEED_NET)), + CALLBACK(rotate_x509_certificate, NET_PARTICIPANT, 0), + + /* We need to do these if we're participating in the Tor network, and + * immediately before we stop. */ + CALLBACK(clean_caches, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), + CALLBACK(save_state, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), + + /* XXXX investigate this. */ + CALLBACK(write_stats_file, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), + + /* XXXX investigate this. */ + CALLBACK(reset_padding_counts, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), /* Routers (bridge and relay) only. */ CALLBACK(check_descriptor, ROUTER, FL(NEED_NET)), @@ -1418,7 +1429,8 @@ STATIC periodic_event_item_t periodic_events[] = { CALLBACK(record_bridge_stats, BRIDGE, 0), /* Client only. */ - CALLBACK(rend_cache_failure_clean, CLIENT, 0), + /* XXXX this could be restricted to CLIENT even. */ + CALLBACK(rend_cache_failure_clean, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), /* Bridge Authority only. */ CALLBACK(write_bridge_ns, BRIDGEAUTH, 0), @@ -1477,7 +1489,7 @@ get_my_roles(const or_options_t *options) { tor_assert(options); - int roles = 0; + int roles = PERIODIC_EVENT_ROLE_ALL; int is_bridge = options->BridgeRelay; int is_relay = server_mode(options); int is_dirauth = authdir_mode_v3(options); @@ -1492,6 +1504,9 @@ get_my_roles(const or_options_t *options) options->ControlPort_set || options->OwningControllerFD != UINT64_MAX; + /* We actually want a better definition here for our work on dormancy. */ + int is_net_participant = ! net_is_disabled(); + if (is_bridge) roles |= PERIODIC_EVENT_ROLE_BRIDGE; if (is_client) roles |= PERIODIC_EVENT_ROLE_CLIENT; if (is_relay) roles |= PERIODIC_EVENT_ROLE_RELAY; @@ -1499,6 +1514,7 @@ get_my_roles(const or_options_t *options) if (is_bridgeauth) roles |= PERIODIC_EVENT_ROLE_BRIDGEAUTH; if (is_hidden_service) roles |= PERIODIC_EVENT_ROLE_HS_SERVICE; if (is_dirserver) roles |= PERIODIC_EVENT_ROLE_DIRSERVER; + if (is_net_participant) roles |= PERIODIC_EVENT_ROLE_NET_PARTICIPANT; return roles; } diff --git a/src/core/mainloop/periodic.h b/src/core/mainloop/periodic.h index 7c71be7bc4..23459ff2b3 100644 --- a/src/core/mainloop/periodic.h +++ b/src/core/mainloop/periodic.h @@ -16,6 +16,9 @@ #define PERIODIC_EVENT_ROLE_HS_SERVICE (1U << 5) #define PERIODIC_EVENT_ROLE_DIRSERVER (1U << 6) +#define PERIODIC_EVENT_ROLE_NET_PARTICIPANT (1U << 7) +#define PERIODIC_EVENT_ROLE_ALL (1U << 8) + /* Helper macro to make it a bit less annoying to defined groups of roles that * are often used. */ @@ -25,10 +28,6 @@ /* Authorities that is both bridge and directory. */ #define PERIODIC_EVENT_ROLE_AUTHORITIES \ (PERIODIC_EVENT_ROLE_BRIDGEAUTH | PERIODIC_EVENT_ROLE_DIRAUTH) -/* All roles. */ -#define PERIODIC_EVENT_ROLE_ALL \ - (PERIODIC_EVENT_ROLE_AUTHORITIES | PERIODIC_EVENT_ROLE_CLIENT | \ - PERIODIC_EVENT_ROLE_HS_SERVICE | PERIODIC_EVENT_ROLE_ROUTER) /* * Event flags which can change the behavior of an event. diff --git a/src/test/test_periodic_event.c b/src/test/test_periodic_event.c index 86dedd85d8..f63adf8e3a 100644 --- a/src/test/test_periodic_event.c +++ b/src/test/test_periodic_event.c @@ -19,6 +19,7 @@ #include "feature/hibernate/hibernate.h" #include "feature/hs/hs_service.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/netstatus.h" #include "core/mainloop/periodic.h" /** Helper function: This is replaced in some tests for the event callbacks so @@ -59,7 +60,9 @@ test_pe_initialize(void *arg) tt_u64_op(item->last_action_time, OP_EQ, 0); /* Every event must have role(s) assign to it. This is done statically. */ tt_u64_op(item->roles, OP_NE, 0); - tt_uint_op(periodic_event_is_enabled(item), OP_EQ, 0); + int should_be_enabled = (item->roles & PERIODIC_EVENT_ROLE_ALL) && + !(item->flags & PERIODIC_EVENT_FLAG_NEED_NET); + tt_uint_op(periodic_event_is_enabled(item), OP_EQ, should_be_enabled); } done: @@ -106,13 +109,12 @@ test_pe_launch(void *arg) /* Now that we've initialized, rescan the list to launch. */ periodic_events_on_new_options(options); + int mask = PERIODIC_EVENT_ROLE_CLIENT|PERIODIC_EVENT_ROLE_ALL| + PERIODIC_EVENT_ROLE_NET_PARTICIPANT; for (int i = 0; periodic_events[i].name; ++i) { periodic_event_item_t *item = &periodic_events[i]; - if (item->roles & PERIODIC_EVENT_ROLE_CLIENT) { - tt_int_op(periodic_event_is_enabled(item), OP_EQ, 1); - } else { - tt_int_op(periodic_event_is_enabled(item), OP_EQ, 0); - } + int should_be_enabled = !!(item->roles & mask); + tt_int_op(periodic_event_is_enabled(item), OP_EQ, should_be_enabled); // enabled or not, the event has not yet been run. tt_u64_op(item->last_action_time, OP_EQ, 0); } @@ -124,7 +126,8 @@ test_pe_launch(void *arg) unsigned roles = get_my_roles(options); tt_uint_op(roles, OP_EQ, - PERIODIC_EVENT_ROLE_RELAY|PERIODIC_EVENT_ROLE_DIRSERVER); + PERIODIC_EVENT_ROLE_RELAY|PERIODIC_EVENT_ROLE_DIRSERVER| + PERIODIC_EVENT_ROLE_ALL|PERIODIC_EVENT_ROLE_NET_PARTICIPANT); for (int i = 0; periodic_events[i].name; ++i) { periodic_event_item_t *item = &periodic_events[i]; @@ -144,17 +147,21 @@ test_pe_launch(void *arg) /* Disable everything and we'll enable them ALL. */ options->SocksPort_set = 0; options->ORPort_set = 0; + options->DisableNetwork = 1; periodic_events_on_new_options(options); for (int i = 0; periodic_events[i].name; ++i) { periodic_event_item_t *item = &periodic_events[i]; - tt_int_op(periodic_event_is_enabled(item), OP_EQ, 0); + int should_be_enabled = (item->roles & PERIODIC_EVENT_ROLE_ALL) && + !(item->flags & PERIODIC_EVENT_FLAG_NEED_NET); + tt_int_op(periodic_event_is_enabled(item), OP_EQ, should_be_enabled); } /* Enable everything. */ options->SocksPort_set = 1; options->ORPort_set = 1; options->BridgeRelay = 1; options->AuthoritativeDir = 1; options->V3AuthoritativeDir = 1; options->BridgeAuthoritativeDir = 1; + options->DisableNetwork = 0; register_dummy_hidden_service(&service); periodic_events_on_new_options(options); /* Note down the reference because we need to remove this service from the @@ -188,41 +195,46 @@ test_pe_get_roles(void *arg) or_options_t *options = get_options_mutable(); tt_assert(options); + const int ALL = PERIODIC_EVENT_ROLE_ALL; + /* Nothing configured, should be no roles. */ + tt_assert(net_is_disabled()); roles = get_my_roles(options); - tt_int_op(roles, OP_EQ, 0); + tt_int_op(roles, OP_EQ, ALL); /* Indicate we have a SocksPort, roles should be come Client. */ options->SocksPort_set = 1; roles = get_my_roles(options); - tt_int_op(roles, OP_EQ, PERIODIC_EVENT_ROLE_CLIENT); + tt_int_op(roles, OP_EQ, PERIODIC_EVENT_ROLE_CLIENT|ALL); /* Now, we'll add a ORPort so should now be a Relay + Client. */ options->ORPort_set = 1; roles = get_my_roles(options); tt_int_op(roles, OP_EQ, (PERIODIC_EVENT_ROLE_CLIENT | PERIODIC_EVENT_ROLE_RELAY | - PERIODIC_EVENT_ROLE_DIRSERVER)); + PERIODIC_EVENT_ROLE_DIRSERVER | ALL)); /* Now add a Bridge. */ options->BridgeRelay = 1; roles = get_my_roles(options); tt_int_op(roles, OP_EQ, (PERIODIC_EVENT_ROLE_CLIENT | PERIODIC_EVENT_ROLE_RELAY | - PERIODIC_EVENT_ROLE_BRIDGE | PERIODIC_EVENT_ROLE_DIRSERVER)); + PERIODIC_EVENT_ROLE_BRIDGE | PERIODIC_EVENT_ROLE_DIRSERVER | + ALL)); tt_assert(roles & PERIODIC_EVENT_ROLE_ROUTER); /* Unset client so we can solely test Router role. */ options->SocksPort_set = 0; roles = get_my_roles(options); tt_int_op(roles, OP_EQ, - PERIODIC_EVENT_ROLE_ROUTER | PERIODIC_EVENT_ROLE_DIRSERVER); + PERIODIC_EVENT_ROLE_ROUTER | PERIODIC_EVENT_ROLE_DIRSERVER | + ALL); /* Reset options so we can test authorities. */ options->SocksPort_set = 0; options->ORPort_set = 0; options->BridgeRelay = 0; roles = get_my_roles(options); - tt_int_op(roles, OP_EQ, 0); + tt_int_op(roles, OP_EQ, ALL); /* Now upgrade to Dirauth. */ options->DirPort_set = 1; @@ -230,7 +242,7 @@ test_pe_get_roles(void *arg) options->V3AuthoritativeDir = 1; roles = get_my_roles(options); tt_int_op(roles, OP_EQ, - PERIODIC_EVENT_ROLE_DIRAUTH|PERIODIC_EVENT_ROLE_DIRSERVER); + PERIODIC_EVENT_ROLE_DIRAUTH|PERIODIC_EVENT_ROLE_DIRSERVER|ALL); tt_assert(roles & PERIODIC_EVENT_ROLE_AUTHORITIES); /* Now Bridge Authority. */ @@ -238,7 +250,7 @@ test_pe_get_roles(void *arg) options->BridgeAuthoritativeDir = 1; roles = get_my_roles(options); tt_int_op(roles, OP_EQ, - PERIODIC_EVENT_ROLE_BRIDGEAUTH|PERIODIC_EVENT_ROLE_DIRSERVER); + PERIODIC_EVENT_ROLE_BRIDGEAUTH|PERIODIC_EVENT_ROLE_DIRSERVER|ALL); tt_assert(roles & PERIODIC_EVENT_ROLE_AUTHORITIES); /* Move that bridge auth to become a relay. */ @@ -246,7 +258,7 @@ test_pe_get_roles(void *arg) roles = get_my_roles(options); tt_int_op(roles, OP_EQ, (PERIODIC_EVENT_ROLE_BRIDGEAUTH | PERIODIC_EVENT_ROLE_RELAY - | PERIODIC_EVENT_ROLE_DIRSERVER)); + | PERIODIC_EVENT_ROLE_DIRSERVER|ALL)); tt_assert(roles & PERIODIC_EVENT_ROLE_AUTHORITIES); /* And now an Hidden service. */ @@ -257,7 +269,8 @@ test_pe_get_roles(void *arg) remove_service(get_hs_service_map(), &service); tt_int_op(roles, OP_EQ, (PERIODIC_EVENT_ROLE_BRIDGEAUTH | PERIODIC_EVENT_ROLE_RELAY | - PERIODIC_EVENT_ROLE_HS_SERVICE | PERIODIC_EVENT_ROLE_DIRSERVER)); + PERIODIC_EVENT_ROLE_HS_SERVICE | PERIODIC_EVENT_ROLE_DIRSERVER | + ALL)); tt_assert(roles & PERIODIC_EVENT_ROLE_AUTHORITIES); done: From db53bfe8f74dad1b45ba381a5ee3366148a30237 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 6 Nov 2018 11:14:50 -0500 Subject: [PATCH 04/18] Annotate 1/s callback elements with NET_PARTICIPANT status. --- src/core/mainloop/mainloop.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 3c3f441a91..7e5e5d0efb 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1379,8 +1379,9 @@ STATIC periodic_event_item_t periodic_events[] = { CALLBACK(add_entropy, ALL, 0), CALLBACK(heartbeat, ALL, 0), - /* XXXX Do we have a reason to do this on a callback? */ - CALLBACK(retry_listeners, ALL, 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)), /* We need to do these if we're participating in the Tor network. */ CALLBACK(check_expired_networkstatus, NET_PARTICIPANT, 0), @@ -1393,10 +1394,10 @@ STATIC periodic_event_item_t periodic_events[] = { CALLBACK(clean_caches, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), CALLBACK(save_state, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), - /* XXXX investigate this. */ + /* XXXX investigate this. ??? */ CALLBACK(write_stats_file, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), - /* XXXX investigate this. */ + /* XXXX investigate this. ???? */ CALLBACK(reset_padding_counts, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), /* Routers (bridge and relay) only. */ @@ -1423,13 +1424,13 @@ STATIC periodic_event_item_t periodic_events[] = { CALLBACK(check_dns_honesty, RELAY, FL(NEED_NET)), /* Hidden Service service only. */ - CALLBACK(hs_service, HS_SERVICE, FL(NEED_NET)), + CALLBACK(hs_service, HS_SERVICE, FL(NEED_NET)), // XXXX break this down more /* Bridge only. */ CALLBACK(record_bridge_stats, BRIDGE, 0), /* Client only. */ - /* XXXX this could be restricted to CLIENT even. */ + /* XXXX this could be restricted to CLIENT+NET_PARTICIPANT */ CALLBACK(rend_cache_failure_clean, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), /* Bridge Authority only. */ @@ -1730,18 +1731,24 @@ run_scheduled_events(time_t now) * expired; or if our bandwidth limits are exhausted and we * should hibernate; or if it's time to wake up from hibernation. */ + // TODO: Refactor or rewrite, or NET_PARTICIPANT. Needs separate wakeup + // handling. consider_hibernation(now); /* Maybe enough time elapsed for us to reconsider a circuit. */ + // TODO: NET_PARTICIPANT circuit_upgrade_circuits_from_guard_wait(); if (options->UseBridges && !net_is_disabled()) { /* Note: this check uses net_is_disabled(), not should_delay_dir_fetches() * -- the latter is only for fetching consensus-derived directory info. */ + // TODO: client+NET_PARTICIPANT. + // Also, schedule this rather than probing 1x / sec fetch_bridge_descriptors(options, now); } if (accounting_is_enabled(options)) { + // TODO: refactor or rewrite; NET_PARTICIPANT accounting_run_housekeeping(now); } @@ -1752,6 +1759,7 @@ run_scheduled_events(time_t now) */ /* (If our circuit build timeout can ever become lower than a second (which * it can't, currently), we should do this more often.) */ + // TODO: All expire stuff can become NET_PARTICIPANT, FLUSH_ON_DISABLE circuit_expire_building(); circuit_expire_waiting_for_better_guard(); @@ -1760,10 +1768,12 @@ run_scheduled_events(time_t now) * Do this before step 4, so we can put them back into pending * state to be picked up by the new circuit. */ + // TODO: All expire stuff can become NET_PARTICIPANT, FLUSH_ON_DISABLE connection_ap_expire_beginning(); /* 3c. And expire connections that we've held open for too long. */ + // TODO: All expire stuff can become NET_PARTICIPANT, FLUSH_ON_DISABLE connection_expire_held_open(); /* 4. Every second, we try a new circuit if there are no valid @@ -1773,19 +1783,24 @@ run_scheduled_events(time_t now) */ const int have_dir_info = router_have_minimum_dir_info(); if (have_dir_info && !net_is_disabled()) { + // TODO: NET_PARTICIPANT. circuit_build_needed_circs(now); } else { + // TODO: NET_PARTICIPANT, FLUSH_ON_DISABLE circuit_expire_old_circs_as_needed(now); } /* 5. We do housekeeping for each connection... */ + // TODO: NET_PARTICIPANT channel_update_bad_for_new_circs(NULL, 0); int i; for (i=0;i Date: Tue, 13 Nov 2018 08:22:58 -0500 Subject: [PATCH 05/18] Move control_per_second_events() into a callback with its own role Part of making extra-dormant mode work; closes ticket 28421. --- src/core/mainloop/mainloop.c | 29 +++++++++++++++++++++++------ src/core/mainloop/periodic.h | 5 +++-- src/feature/control/control.c | 2 +- src/test/test_periodic_event.c | 3 ++- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 7e5e5d0efb..a9d5d8155a 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1366,6 +1366,7 @@ CALLBACK(save_stability); CALLBACK(save_state); CALLBACK(write_bridge_ns); CALLBACK(write_stats_file); +CALLBACK(control_per_second_events); #undef CALLBACK @@ -1439,6 +1440,9 @@ STATIC periodic_event_item_t periodic_events[] = { /* Directory server only. */ CALLBACK(clean_consdiffmgr, DIRSERVER, 0), + /* Controller with per-second events only. */ + CALLBACK(control_per_second_events, CONTROLEV, 0), + END_OF_PERIODIC_EVENTS }; #undef CALLBACK @@ -1498,6 +1502,8 @@ get_my_roles(const or_options_t *options) int is_hidden_service = !!hs_service_get_num_services() || !!rend_num_services(); int is_dirserver = dir_server_mode(options); + int sending_control_events = control_any_per_second_event_enabled(); + /* We also consider tor to have the role of a client if the ControlPort is * set because a lot of things can be done over the control port which * requires tor to have basic functionnalities. */ @@ -1516,6 +1522,7 @@ get_my_roles(const or_options_t *options) if (is_hidden_service) roles |= PERIODIC_EVENT_ROLE_HS_SERVICE; if (is_dirserver) roles |= PERIODIC_EVENT_ROLE_DIRSERVER; if (is_net_participant) roles |= PERIODIC_EVENT_ROLE_NET_PARTICIPANT; + if (sending_control_events) roles |= PERIODIC_EVENT_ROLE_CONTROLEV; return roles; } @@ -2524,6 +2531,21 @@ hs_service_callback(time_t now, const or_options_t *options) return 1; } +/* + * Periodic callback: Send once-per-second events to the controller(s). + * This is called every second. + */ +static int +control_per_second_events_callback(time_t now, const or_options_t *options) +{ + (void) options; + (void) now; + + control_per_second_events(); + + return 1; +} + /** Timer: used to invoke second_elapsed_callback() once per second. */ static periodic_timer_t *second_timer = NULL; @@ -2546,8 +2568,7 @@ reschedule_per_second_timer(void) tor_assert(second_timer); } - const bool run_per_second_events = - control_any_per_second_event_enabled() || ! net_is_completely_disabled(); + const bool run_per_second_events = ! net_is_completely_disabled(); if (run_per_second_events) { periodic_timer_launch(second_timer, &one_second); @@ -2640,10 +2661,6 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) */ update_current_time(now); - // TODO XXXX Turn this into a separate event. - /* Maybe some controller events are ready to fire */ - control_per_second_events(); - run_scheduled_events(now); } diff --git a/src/core/mainloop/periodic.h b/src/core/mainloop/periodic.h index 23459ff2b3..52d5450ee8 100644 --- a/src/core/mainloop/periodic.h +++ b/src/core/mainloop/periodic.h @@ -15,9 +15,10 @@ #define PERIODIC_EVENT_ROLE_BRIDGEAUTH (1U << 4) #define PERIODIC_EVENT_ROLE_HS_SERVICE (1U << 5) #define PERIODIC_EVENT_ROLE_DIRSERVER (1U << 6) +#define PERIODIC_EVENT_ROLE_CONTROLEV (1U << 7) -#define PERIODIC_EVENT_ROLE_NET_PARTICIPANT (1U << 7) -#define PERIODIC_EVENT_ROLE_ALL (1U << 8) +#define PERIODIC_EVENT_ROLE_NET_PARTICIPANT (1U << 8) +#define PERIODIC_EVENT_ROLE_ALL (1U << 9) /* Helper macro to make it a bit less annoying to defined groups of roles that * are often used. */ diff --git a/src/feature/control/control.c b/src/feature/control/control.c index b31b448e96..a5b6ab3bf5 100644 --- a/src/feature/control/control.c +++ b/src/feature/control/control.c @@ -368,7 +368,7 @@ control_update_global_event_mask(void) control_get_bytes_rw_last_sec(&r, &w); } if (any_old_per_sec_events != control_any_per_second_event_enabled()) { - reschedule_per_second_timer(); + rescan_periodic_events(get_options()); } #undef NEWLY_ENABLED diff --git a/src/test/test_periodic_event.c b/src/test/test_periodic_event.c index f63adf8e3a..6a3e320b2e 100644 --- a/src/test/test_periodic_event.c +++ b/src/test/test_periodic_event.c @@ -172,7 +172,8 @@ test_pe_launch(void *arg) for (int i = 0; periodic_events[i].name; ++i) { periodic_event_item_t *item = &periodic_events[i]; - tt_int_op(periodic_event_is_enabled(item), OP_EQ, 1); + tt_int_op(periodic_event_is_enabled(item), OP_EQ, + (item->roles != PERIODIC_EVENT_ROLE_CONTROLEV)); } done: From e535ec8542a1d42243c0b6ae28036aec8262269b Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 13 Nov 2018 08:36:38 -0500 Subject: [PATCH 06/18] Remove run_scheduled_events() as a separate function. (There was nothing else in second_elapsed_callbck() that couldn't go here.) --- src/core/mainloop/mainloop.c | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index a9d5d8155a..ebf9735d49 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1727,13 +1727,22 @@ safe_timer_diff(time_t now, time_t next) } /** Perform regular maintenance tasks. This function gets run once per - * second by second_elapsed_callback(). + * second. */ static void -run_scheduled_events(time_t now) +second_elapsed_callback(periodic_timer_t *timer, void *arg) { + (void) timer; + (void) arg; + const time_t now = time(NULL); const or_options_t *options = get_options(); + /* We don't need to do this once-per-second any more: time-updating is + * only in this callback _because it is a callback_. It should be fine + * to disable this callback, and the time will still get updated. + */ + update_current_time(now); + /* 0. See if we've been asked to shut down and our timeout has * expired; or if our bandwidth limits are exhausted and we * should hibernate; or if it's time to wake up from hibernation. @@ -2642,28 +2651,6 @@ update_current_time(time_t now) current_second = now; } -/** Libevent callback: invoked once every second. */ -static void -second_elapsed_callback(periodic_timer_t *timer, void *arg) -{ - /* XXXX This could be sensibly refactored into multiple callbacks, and we - * could use Libevent's timers for this rather than checking the current - * time against a bunch of timeouts every second. */ - time_t now; - (void)timer; - (void)arg; - - now = time(NULL); - - /* We don't need to do this once-per-second any more: time-updating is - * only in this callback _because it is a callback_. It should be fine - * to disable this callback, and the time will still get updated. - */ - update_current_time(now); - - run_scheduled_events(now); -} - #ifdef HAVE_SYSTEMD_209 static periodic_timer_t *systemd_watchdog_timer = NULL; From 303e5c70e08d72614ea95fd0c5aad7e05852b6b7 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 13 Nov 2018 09:04:11 -0500 Subject: [PATCH 07/18] Move the responsibility for delayed shutdown into the mainloop This is part of 28422, so we don't have to call consider_hibernation() once per second when we're dormant. This commit does not remove delayed shutdown from hibernate.c: it uses it as a backup shutdown mechanism, in case the regular shutdown timer mechanism fails for some reason. --- src/core/mainloop/mainloop.c | 36 ++++++++++++++++++++++++++----- src/core/mainloop/mainloop.h | 2 ++ src/feature/hibernate/hibernate.c | 20 +++++++++++------ 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index ebf9735d49..d5f3cb13f9 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1703,6 +1703,30 @@ mainloop_schedule_postloop_cleanup(void) mainloop_event_activate(postloop_cleanup_ev); } +/** Event to run 'scheduled_shutdown_cb' */ +static mainloop_event_t *scheduled_shutdown_ev=NULL; + +/** Callback: run a scheduled shutdown */ +static void +scheduled_shutdown_cb(mainloop_event_t *ev, void *arg) +{ + (void)ev; + (void)arg; + log_notice(LD_GENERAL, "Clean shutdown finished. Exiting."); + tor_shutdown_event_loop_and_exit(0); +} + +/** Schedule the mainloop to exit after delay_sec seconds. */ +void +mainloop_schedule_shutdown(int delay_sec) +{ + const struct timeval delay_tv = { delay_sec, 0 }; + if (! scheduled_shutdown_ev) { + scheduled_shutdown_ev = mainloop_event_new(scheduled_shutdown_cb, NULL); + } + mainloop_event_schedule(scheduled_shutdown_ev, &delay_tv); +} + #define LONGEST_TIMER_PERIOD (30 * 86400) /** Helper: Return the number of seconds between now and next, * clipped to the range [1 second, LONGEST_TIMER_PERIOD]. */ @@ -1743,12 +1767,13 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) */ update_current_time(now); - /* 0. See if we've been asked to shut down and our timeout has - * expired; or if our bandwidth limits are exhausted and we - * should hibernate; or if it's time to wake up from hibernation. + /* 0. See if our bandwidth limits are exhausted and we should hibernate; or + * if it's time to wake up from hibernation; or if we have a scheduled + * shutdown and it's time to run it. + * + * Note: we have redundant mechanisms to handle the */ - // TODO: Refactor or rewrite, or NET_PARTICIPANT. Needs separate wakeup - // handling. + // TODO: NET_PARTICIPANT. consider_hibernation(now); /* Maybe enough time elapsed for us to reconsider a circuit. */ @@ -2936,6 +2961,7 @@ tor_mainloop_free_all(void) mainloop_event_free(schedule_active_linked_connections_event); mainloop_event_free(postloop_cleanup_ev); mainloop_event_free(handle_deferred_signewnym_ev); + mainloop_event_free(scheduled_shutdown_ev); #ifdef HAVE_SYSTEMD_209 periodic_timer_free(systemd_watchdog_timer); diff --git a/src/core/mainloop/mainloop.h b/src/core/mainloop/mainloop.h index 632733d9a6..6f7b716858 100644 --- a/src/core/mainloop/mainloop.h +++ b/src/core/mainloop/mainloop.h @@ -86,6 +86,8 @@ void reschedule_per_second_timer(void); void do_signewnym(time_t); time_t get_last_signewnym_time(void); +void mainloop_schedule_shutdown(int delay_sec); + void tor_init_connection_lists(void); void initialize_mainloop_events(void); void tor_mainloop_free_all(void); diff --git a/src/feature/hibernate/hibernate.c b/src/feature/hibernate/hibernate.c index 6f8795cecc..968c39dd6d 100644 --- a/src/feature/hibernate/hibernate.c +++ b/src/feature/hibernate/hibernate.c @@ -66,8 +66,9 @@ static hibernate_state_t hibernate_state = HIBERNATE_STATE_INITIAL; /** If are hibernating, when do we plan to wake up? Set to 0 if we * aren't hibernating. */ static time_t hibernate_end_time = 0; -/** If we are shutting down, when do we plan finally exit? Set to 0 if - * we aren't shutting down. */ +/** If we are shutting down, when do we plan finally exit? Set to 0 if we + * aren't shutting down. (This is obsolete; scheduled shutdowns are supposed + * to happen from mainloop_schedule_shutdown() now.) */ static time_t shutdown_time = 0; /** A timed event that we'll use when it's time to wake up from @@ -867,7 +868,13 @@ hibernate_begin(hibernate_state_t new_state, time_t now) log_notice(LD_GENERAL,"Interrupt: we have stopped accepting new " "connections, and will shut down in %d seconds. Interrupt " "again to exit now.", options->ShutdownWaitLength); - shutdown_time = time(NULL) + options->ShutdownWaitLength; + /* We add an arbitrary delay here so that even if something goes wrong + * with the mainloop shutdown code, we can still shutdown from + * consider_hibernation() if we call it... but so that the + * mainloop_schedule_shutdown() mechanism will be the first one called. + */ + shutdown_time = time(NULL) + options->ShutdownWaitLength + 5; + mainloop_schedule_shutdown(options->ShutdownWaitLength); #ifdef HAVE_SYSTEMD /* tell systemd that we may need more than the default 90 seconds to shut * down so they don't kill us. add some extra time to actually finish @@ -1096,11 +1103,12 @@ consider_hibernation(time_t now) hibernate_state_t prev_state = hibernate_state; /* If we're in 'exiting' mode, then we just shut down after the interval - * elapses. */ + * elapses. The mainloop was supposed to catch this via + * mainloop_schedule_shutdown(), but apparently it didn't. */ if (hibernate_state == HIBERNATE_STATE_EXITING) { tor_assert(shutdown_time); if (shutdown_time <= now) { - log_notice(LD_GENERAL, "Clean shutdown finished. Exiting."); + log_notice(LD_BUG, "Mainloop did not catch shutdown event; exiting."); tor_shutdown_event_loop_and_exit(0); } return; /* if exiting soon, don't worry about bandwidth limits */ @@ -1112,7 +1120,7 @@ consider_hibernation(time_t now) if (hibernate_end_time > now && accounting_enabled) { /* If we're hibernating, don't wake up until it's time, regardless of * whether we're in a new interval. */ - return ; + return; } else { hibernate_end_time_elapsed(now); } From 4bf79fa4fa99fe66f6b1ad413cbf475325803e04 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 13 Nov 2018 09:30:51 -0500 Subject: [PATCH 08/18] Turn second_elapsed_callback into a normal periodic event. --- src/app/config/config.c | 3 -- src/core/mainloop/mainloop.c | 81 +++++++------------------------ src/core/mainloop/mainloop.h | 1 - src/feature/hibernate/hibernate.c | 2 - 4 files changed, 17 insertions(+), 70 deletions(-) diff --git a/src/app/config/config.c b/src/app/config/config.c index 45a23d67d5..8aa0c1f4bd 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -1993,9 +1993,6 @@ options_act(const or_options_t *old_options) finish_daemon(options->DataDirectory); } - /* See whether we need to enable/disable our once-a-second timer. */ - reschedule_per_second_timer(); - /* We want to reinit keys as needed before we do much of anything else: keys are important, and other things can depend on them. */ if (transition_affects_workers || diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index d5f3cb13f9..176399b333 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -205,7 +205,6 @@ static void connection_start_reading_from_linked_conn(connection_t *conn); static int connection_should_read_from_linked_conn(connection_t *conn); static void conn_read_callback(evutil_socket_t fd, short event, void *_conn); static void conn_write_callback(evutil_socket_t fd, short event, void *_conn); -static void second_elapsed_callback(periodic_timer_t *timer, void *args); static void shutdown_did_not_work_callback(evutil_socket_t fd, short event, void *arg) ATTR_NORETURN; @@ -1367,6 +1366,7 @@ CALLBACK(save_state); CALLBACK(write_bridge_ns); CALLBACK(write_stats_file); CALLBACK(control_per_second_events); +CALLBACK(second_elapsed); #undef CALLBACK @@ -1380,6 +1380,11 @@ STATIC periodic_event_item_t periodic_events[] = { CALLBACK(add_entropy, ALL, 0), CALLBACK(heartbeat, ALL, 0), + /* This is a legacy catch-all callback that runs once per second if + * we are online and active. */ + CALLBACK(second_elapsed, NET_PARTICIPANT, + FL(NEED_NET)|FL(FLUSH_ON_DISABLE)), + /* 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)), @@ -1753,43 +1758,30 @@ safe_timer_diff(time_t now, time_t next) /** Perform regular maintenance tasks. This function gets run once per * second. */ -static void -second_elapsed_callback(periodic_timer_t *timer, void *arg) +static int +second_elapsed_callback(time_t now, const or_options_t *options) { - (void) timer; - (void) arg; - const time_t now = time(NULL); - const or_options_t *options = get_options(); - - /* We don't need to do this once-per-second any more: time-updating is - * only in this callback _because it is a callback_. It should be fine - * to disable this callback, and the time will still get updated. - */ - update_current_time(now); - - /* 0. See if our bandwidth limits are exhausted and we should hibernate; or - * if it's time to wake up from hibernation; or if we have a scheduled - * shutdown and it's time to run it. + /* 0. See if our bandwidth limits are exhausted and we should hibernate * - * Note: we have redundant mechanisms to handle the + * Note: we have redundant mechanisms to handle the case where it's + * time to wake up from hibernation; or where we have a scheduled + * shutdown and it's time to run it, but this will also handle those. */ - // TODO: NET_PARTICIPANT. consider_hibernation(now); /* Maybe enough time elapsed for us to reconsider a circuit. */ - // TODO: NET_PARTICIPANT circuit_upgrade_circuits_from_guard_wait(); if (options->UseBridges && !net_is_disabled()) { /* Note: this check uses net_is_disabled(), not should_delay_dir_fetches() * -- the latter is only for fetching consensus-derived directory info. */ - // TODO: client+NET_PARTICIPANT. + // TODO: client // Also, schedule this rather than probing 1x / sec fetch_bridge_descriptors(options, now); } if (accounting_is_enabled(options)) { - // TODO: refactor or rewrite; NET_PARTICIPANT + // TODO: refactor or rewrite? accounting_run_housekeeping(now); } @@ -1809,12 +1801,10 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) * Do this before step 4, so we can put them back into pending * state to be picked up by the new circuit. */ - // TODO: All expire stuff can become NET_PARTICIPANT, FLUSH_ON_DISABLE connection_ap_expire_beginning(); /* 3c. And expire connections that we've held open for too long. */ - // TODO: All expire stuff can become NET_PARTICIPANT, FLUSH_ON_DISABLE connection_expire_held_open(); /* 4. Every second, we try a new circuit if there are no valid @@ -1824,26 +1814,24 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) */ const int have_dir_info = router_have_minimum_dir_info(); if (have_dir_info && !net_is_disabled()) { - // TODO: NET_PARTICIPANT. circuit_build_needed_circs(now); } else { - // TODO: NET_PARTICIPANT, FLUSH_ON_DISABLE circuit_expire_old_circs_as_needed(now); } /* 5. We do housekeeping for each connection... */ - // TODO: NET_PARTICIPANT channel_update_bad_for_new_circs(NULL, 0); int i; for (i=0;i Date: Tue, 13 Nov 2018 10:43:08 -0500 Subject: [PATCH 09/18] reset_padding_counts is only once per 24h; it can be all. --- src/core/mainloop/mainloop.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 176399b333..6df51062a7 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1376,9 +1376,11 @@ CALLBACK(second_elapsed); #define FL(name) (PERIODIC_EVENT_FLAG_ ## name) STATIC periodic_event_item_t periodic_events[] = { - /* Everyone needs to run those. */ + /* Everyone needs to run these. They need to have very long timeouts for + * that to be safe. */ CALLBACK(add_entropy, ALL, 0), CALLBACK(heartbeat, ALL, 0), + CALLBACK(reset_padding_counts, ALL, 0), /* This is a legacy catch-all callback that runs once per second if * we are online and active. */ @@ -1403,9 +1405,6 @@ STATIC periodic_event_item_t periodic_events[] = { /* XXXX investigate this. ??? */ CALLBACK(write_stats_file, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), - /* XXXX investigate this. ???? */ - CALLBACK(reset_padding_counts, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), - /* Routers (bridge and relay) only. */ CALLBACK(check_descriptor, ROUTER, FL(NEED_NET)), CALLBACK(check_ed_keys, ROUTER, 0), From ccbb36048f5167b9d5011b7c8b0d2c346ce567e8 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 13 Nov 2018 10:44:04 -0500 Subject: [PATCH 10/18] write_stats_file() is indeed NET_PARTICIPANT; remove comment. --- src/core/mainloop/mainloop.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 6df51062a7..e67ebdb4a8 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1401,8 +1401,6 @@ STATIC periodic_event_item_t periodic_events[] = { * immediately before we stop. */ CALLBACK(clean_caches, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), CALLBACK(save_state, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), - - /* XXXX investigate this. ??? */ CALLBACK(write_stats_file, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), /* Routers (bridge and relay) only. */ From 2c15b6538123047c258987b00475fa658ca14878 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 13 Nov 2018 15:33:46 -0500 Subject: [PATCH 11/18] Make the NET_PARTICIPANT role dependent on user activity This patch implements all of 28337, except for the part where we turn off the role if we've been idle for a long time. --- src/app/main/main.c | 15 +++++++ src/core/mainloop/connection.c | 3 ++ src/core/mainloop/mainloop.c | 34 ++++++++++++++- src/core/mainloop/mainloop.h | 1 + src/core/mainloop/netstatus.c | 73 +++++++++++++++++++++++++++++++++ src/core/mainloop/netstatus.h | 7 ++++ src/core/or/or.h | 2 + src/feature/client/dnsserv.c | 4 ++ src/feature/control/control.c | 2 + src/test/test_compat_libevent.c | 1 - src/test/test_periodic_event.c | 11 ++++- 11 files changed, 149 insertions(+), 4 deletions(-) diff --git a/src/app/main/main.c b/src/app/main/main.c index b8dcb852d2..03b3a95d03 100644 --- a/src/app/main/main.c +++ b/src/app/main/main.c @@ -303,6 +303,19 @@ process_signal(int sig) log_heartbeat(time(NULL)); control_event_signal(sig); break; + case SIGACTIVE: + /* "SIGACTIVE" counts as ersatz user activity. */ + note_user_activity(approx_time()); + control_event_signal(sig); + break; + case SIGDORMANT: + /* "SIGDORMANT" means to ignore past user activity */ + log_notice(LD_GENERAL, "Going dormant because of controller request."); + reset_user_activity(0); + set_network_participation(false); + schedule_rescan_periodic_events(); + control_event_signal(sig); + break; } } @@ -472,6 +485,8 @@ static struct { { SIGNEWNYM, 0, NULL }, { SIGCLEARDNSCACHE, 0, NULL }, { SIGHEARTBEAT, 0, NULL }, + { SIGACTIVE, 0, NULL }, + { SIGDORMANT, 0, NULL }, { -1, -1, NULL } }; diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 1198a01ad9..e0f1680c91 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -1874,6 +1874,9 @@ connection_init_accepted_conn(connection_t *conn, TO_ENTRY_CONN(conn)->nym_epoch = get_signewnym_epoch(); TO_ENTRY_CONN(conn)->socks_request->listener_type = listener->base_.type; + /* Any incoming connection on an entry port counts as user activity. */ + note_user_activity(approx_time()); + switch (TO_CONN(listener)->type) { case CONN_TYPE_AP_LISTENER: conn->state = AP_CONN_STATE_SOCKS_WAIT; diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index e67ebdb4a8..f18db2898a 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1513,8 +1513,7 @@ get_my_roles(const or_options_t *options) options->ControlPort_set || options->OwningControllerFD != UINT64_MAX; - /* We actually want a better definition here for our work on dormancy. */ - int is_net_participant = ! net_is_disabled(); + int is_net_participant = is_participating_on_network(); if (is_bridge) roles |= PERIODIC_EVENT_ROLE_BRIDGE; if (is_client) roles |= PERIODIC_EVENT_ROLE_CLIENT; @@ -1592,6 +1591,30 @@ teardown_periodic_events(void) periodic_events_initialized = 0; } +static mainloop_event_t *rescan_periodic_events_ev = NULL; + +/** Callback: rescan the periodic event list. */ +static void +rescan_periodic_events_cb(mainloop_event_t *event, void *arg) +{ + (void)event; + (void)arg; + rescan_periodic_events(get_options()); +} + +/** + * Schedule an event that will rescan which periodic events should run. + **/ +void +schedule_rescan_periodic_events(void) +{ + if (!rescan_periodic_events_ev) { + rescan_periodic_events_ev = + mainloop_event_new(rescan_periodic_events_cb, NULL); + } + mainloop_event_activate(rescan_periodic_events_ev); +} + /** Do a pass at all our periodic events, disable those we don't need anymore * and enable those we need now using the given options. */ void @@ -2714,6 +2737,12 @@ initialize_mainloop_events(void) int do_main_loop(void) { + /* For now, starting Tor always counts as user activity. Later, we might + * have an option to control this. + */ + reset_user_activity(approx_time()); + set_network_participation(true); + /* initialize the periodic events first, so that code that depends on the * events being present does not assert. */ @@ -2912,6 +2941,7 @@ tor_mainloop_free_all(void) mainloop_event_free(postloop_cleanup_ev); mainloop_event_free(handle_deferred_signewnym_ev); mainloop_event_free(scheduled_shutdown_ev); + mainloop_event_free(rescan_periodic_events_ev); #ifdef HAVE_SYSTEMD_209 periodic_timer_free(systemd_watchdog_timer); diff --git a/src/core/mainloop/mainloop.h b/src/core/mainloop/mainloop.h index be642d81f9..7f27ef9a52 100644 --- a/src/core/mainloop/mainloop.h +++ b/src/core/mainloop/mainloop.h @@ -65,6 +65,7 @@ void reschedule_or_state_save(void); void reschedule_dirvote(const or_options_t *options); void mainloop_schedule_postloop_cleanup(void); void rescan_periodic_events(const or_options_t *options); +void schedule_rescan_periodic_events(void); void update_current_time(time_t now); diff --git a/src/core/mainloop/netstatus.c b/src/core/mainloop/netstatus.c index f026474494..ed7c952dcd 100644 --- a/src/core/mainloop/netstatus.c +++ b/src/core/mainloop/netstatus.c @@ -6,6 +6,7 @@ #include "core/or/or.h" #include "core/mainloop/netstatus.h" +#include "core/mainloop/mainloop.h" #include "app/config/config.h" #include "feature/hibernate/hibernate.h" @@ -26,3 +27,75 @@ net_is_completely_disabled(void) { return get_options()->DisableNetwork || we_are_fully_hibernating(); } + +/** + * The time at which we've last seen "user activity" -- that is, any activity + * that should keep us as a participant on the network. + */ +static time_t last_user_activity_seen = 0; + +/** + * True iff we are currently a "network participant" -- that is, we + * are building circuits, fetching directory information, and so on. + **/ +static bool participating_on_network = false; + +/** + * Record the fact that we have seen "user activity" at the time now. Move + * "last activity seen" time forwards, but never backwards. + * + * If we were previously not participating on the network, set our + * participation status to true, and launch periodic events as appropriate. + **/ +void +note_user_activity(time_t now) +{ + last_user_activity_seen = MAX(now, last_user_activity_seen); + + if (! participating_on_network) { + log_notice(LD_GENERAL, "Tor is no longer dormant."); + set_network_participation(true); + schedule_rescan_periodic_events(); + } +} + +/** + * Change the time at which "user activitiy" was last seen to now. + * + * Unlike note_user_actity, this function sets the time without checking + * whether it is in the past, and without causing any rescan of periodic events + * or change in participation status. + */ +void +reset_user_activity(time_t now) +{ + last_user_activity_seen = now; +} + +/** + * Return the most recent time at which we recorded "user activity". + **/ +time_t +get_last_user_activity_time(void) +{ + return last_user_activity_seen; +} + +/** + * Set the field that remembers whether we are currently participating on the + * network. Does not schedule or un-schedule periodic events. + **/ +void +set_network_participation(bool participation) +{ + participating_on_network = participation; +} + +/** + * Return true iff we are currently participating on the network. + **/ +bool +is_participating_on_network(void) +{ + return participating_on_network; +} diff --git a/src/core/mainloop/netstatus.h b/src/core/mainloop/netstatus.h index e9310c2929..58c994fd14 100644 --- a/src/core/mainloop/netstatus.h +++ b/src/core/mainloop/netstatus.h @@ -10,4 +10,11 @@ int net_is_disabled(void); int net_is_completely_disabled(void); +void note_user_activity(time_t now); +void reset_user_activity(time_t now); +time_t get_last_user_activity_time(void); + +void set_network_participation(bool participation); +bool is_participating_on_network(void); + #endif diff --git a/src/core/or/or.h b/src/core/or/or.h index acf092c8dc..e4b374b122 100644 --- a/src/core/or/or.h +++ b/src/core/or/or.h @@ -97,6 +97,8 @@ struct curve25519_public_key_t; #define SIGNEWNYM 129 #define SIGCLEARDNSCACHE 130 #define SIGHEARTBEAT 131 +#define SIGACTIVE 132 +#define SIGDORMANT 133 #if (SIZEOF_CELL_T != 0) /* On Irix, stdlib.h defines a cell_t type, so we need to make sure diff --git a/src/feature/client/dnsserv.c b/src/feature/client/dnsserv.c index ea4951f915..e5abe5c6aa 100644 --- a/src/feature/client/dnsserv.c +++ b/src/feature/client/dnsserv.c @@ -28,6 +28,7 @@ #include "core/or/connection_edge.h" #include "feature/control/control.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/netstatus.h" #include "core/or/policies.h" #include "feature/control/control_connection_st.h" @@ -213,6 +214,9 @@ dnsserv_launch_request(const char *name, int reverse, edge_connection_t *conn; char *q_name; + /* Launching a request for a user counts as user activity. */ + note_user_activity(approx_time()); + /* Make a new dummy AP connection, and attach the request to it. */ entry_conn = entry_connection_new(CONN_TYPE_AP, AF_INET); entry_conn->entry_cfg.dns_request = 1; diff --git a/src/feature/control/control.c b/src/feature/control/control.c index a5b6ab3bf5..1344d66a3d 100644 --- a/src/feature/control/control.c +++ b/src/feature/control/control.c @@ -1681,6 +1681,8 @@ static const struct signal_t signal_table[] = { { SIGNEWNYM, "NEWNYM" }, { SIGCLEARDNSCACHE, "CLEARDNSCACHE"}, { SIGHEARTBEAT, "HEARTBEAT"}, + { SIGACTIVE, "ACTIVE" }, + { SIGDORMANT, "DORMANT" }, { 0, NULL }, }; diff --git a/src/test/test_compat_libevent.c b/src/test/test_compat_libevent.c index 3f505d013b..ade76bdb07 100644 --- a/src/test/test_compat_libevent.c +++ b/src/test/test_compat_libevent.c @@ -187,4 +187,3 @@ struct testcase_t compat_libevent_tests[] = { TT_FORK, NULL, NULL }, END_OF_TESTCASES }; - diff --git a/src/test/test_periodic_event.c b/src/test/test_periodic_event.c index 6a3e320b2e..f3d518eb7b 100644 --- a/src/test/test_periodic_event.c +++ b/src/test/test_periodic_event.c @@ -51,6 +51,8 @@ test_pe_initialize(void *arg) * need to run the main loop and then wait for a second delaying the unit * tests. Instead, we'll test the callback work indepedently elsewhere. */ initialize_periodic_events(); + set_network_participation(false); + rescan_periodic_events(get_options()); /* Validate that all events have been set up. */ for (int i = 0; periodic_events[i].name; ++i) { @@ -82,6 +84,8 @@ test_pe_launch(void *arg) * network gets enabled. */ consider_hibernation(time(NULL)); + set_network_participation(true); + /* Hack: We'll set a dumb fn() of each events so they don't get called when * dispatching them. We just want to test the state of the callbacks, not * the whole code path. */ @@ -93,6 +97,7 @@ test_pe_launch(void *arg) options = get_options_mutable(); options->SocksPort_set = 1; periodic_events_on_new_options(options); + #if 0 /* Lets make sure that before intialization, we can't scan the periodic * events list and launch them. Lets try by being a Client. */ @@ -148,6 +153,7 @@ test_pe_launch(void *arg) options->SocksPort_set = 0; options->ORPort_set = 0; options->DisableNetwork = 1; + set_network_participation(false); periodic_events_on_new_options(options); for (int i = 0; periodic_events[i].name; ++i) { @@ -162,6 +168,7 @@ test_pe_launch(void *arg) options->BridgeRelay = 1; options->AuthoritativeDir = 1; options->V3AuthoritativeDir = 1; options->BridgeAuthoritativeDir = 1; options->DisableNetwork = 0; + set_network_participation(true); register_dummy_hidden_service(&service); periodic_events_on_new_options(options); /* Note down the reference because we need to remove this service from the @@ -195,8 +202,10 @@ test_pe_get_roles(void *arg) or_options_t *options = get_options_mutable(); tt_assert(options); + set_network_participation(true); - const int ALL = PERIODIC_EVENT_ROLE_ALL; + const int ALL = PERIODIC_EVENT_ROLE_ALL | + PERIODIC_EVENT_ROLE_NET_PARTICIPANT; /* Nothing configured, should be no roles. */ tt_assert(net_is_disabled()); From ce6209cee4a113c6a224f0c98244852354ccdb40 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 13 Nov 2018 15:51:53 -0500 Subject: [PATCH 12/18] Add a periodic event to become dormant. This event makes us become dormant if we have seen no activity in a long time. Note that being any kind of a server, or running an onion service, always counts as being active. Note that right now, just having an open stream that Tor did not open on its own (for a directory request) counts as "being active", so if you have an idle ssh connection, that will keep Tor from becoming dormant. Many of the features here should become configurable; I'd like feedback on which. --- src/core/mainloop/connection.c | 10 +++++++ src/core/mainloop/connection.h | 1 + src/core/mainloop/mainloop.c | 50 ++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index e0f1680c91..25224fd999 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -4429,6 +4429,16 @@ connection_get_by_type_state(int type, int state) CONN_GET_TEMPLATE(conn, conn->type == type && conn->state == state); } +/** + * Return a connection of type type that is not an internally linked + * connection, and is not marked for close. + **/ +connection_t * +connection_get_by_type_nonlinked(int type) +{ + CONN_GET_TEMPLATE(conn, conn->type == type && !conn->linked); +} + /** Return a connection of type type that has rendquery equal * to rendquery, and that is not marked for close. If state * is non-zero, conn must be of that state too. diff --git a/src/core/mainloop/connection.h b/src/core/mainloop/connection.h index b569bb038e..9f1a23c6f2 100644 --- a/src/core/mainloop/connection.h +++ b/src/core/mainloop/connection.h @@ -240,6 +240,7 @@ size_t connection_get_outbuf_len(connection_t *conn); connection_t *connection_get_by_global_id(uint64_t id); connection_t *connection_get_by_type(int type); +connection_t *connection_get_by_type_nonlinked(int type); MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type, const tor_addr_t *addr, uint16_t port, int purpose)); diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index f18db2898a..e6dee94fcc 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1367,6 +1367,7 @@ CALLBACK(write_bridge_ns); CALLBACK(write_stats_file); CALLBACK(control_per_second_events); CALLBACK(second_elapsed); +CALLBACK(check_network_participation); #undef CALLBACK @@ -1396,6 +1397,7 @@ STATIC periodic_event_item_t periodic_events[] = { CALLBACK(fetch_networkstatus, NET_PARTICIPANT, 0), CALLBACK(launch_descriptor_fetches, NET_PARTICIPANT, FL(NEED_NET)), CALLBACK(rotate_x509_certificate, NET_PARTICIPANT, 0), + CALLBACK(check_network_participation, NET_PARTICIPANT, 0), /* We need to do these if we're participating in the Tor network, and * immediately before we stop. */ @@ -1998,6 +2000,54 @@ add_entropy_callback(time_t now, const or_options_t *options) return ENTROPY_INTERVAL; } +/** Periodic callback: if there has been no network usage in a while, + * enter a dormant state. */ +static int +check_network_participation_callback(time_t now, const or_options_t *options) +{ + /* If we're a server, we can't become dormant. */ + if (server_mode(options)) { + goto found_activity; + } + + /* If we're running an onion service, we can't become dormant. */ + /* XXXX this would be nice to change, so that we can be dormant with a + * service. */ + if (hs_service_get_num_services() || rend_num_services()) { + goto found_activity; + } + + /* XXXX Add an option to never become dormant. */ + + /* If we have any currently open entry streams other than "linked" + * connections used for directory requests, those count as user activity. + */ + /* XXXX make this configurable? */ + if (connection_get_by_type_nonlinked(CONN_TYPE_AP) != NULL) { + goto found_activity; + } + + /* XXXX Make this configurable? */ +/** How often do we check whether we have had network activity? */ +#define CHECK_PARTICIPATION_INTERVAL (5*60) + + /** Become dormant if there has been no user activity in this long. */ + /* XXXX make this configurable! */ +#define BECOME_DORMANT_AFTER_INACTIVITY (24*60*60) + if (get_last_user_activity_time() + BECOME_DORMANT_AFTER_INACTIVITY >= now) { + log_notice(LD_GENERAL, "No user activity in a long time: becoming" + " dormant."); + set_network_participation(false); + rescan_periodic_events(options); + } + + return CHECK_PARTICIPATION_INTERVAL; + + found_activity: + note_user_activity(now); + return CHECK_PARTICIPATION_INTERVAL; +} + /** * Periodic callback: if we're an authority, make sure we test * the routers on the network for reachability. From d0e6abd0876f0d4adb0b82f5528220a81b34962e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 13 Nov 2018 15:57:18 -0500 Subject: [PATCH 13/18] Reset dormancy status when the clock jumps. --- src/core/mainloop/mainloop.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index e6dee94fcc..2a68e8c098 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -2674,6 +2674,11 @@ update_current_time(time_t now) if (seconds_elapsed < -NUM_JUMPED_SECONDS_BEFORE_WARN) { // moving back in time is always a bad sign. circuit_note_clock_jumped(seconds_elapsed, false); + + /* Don't go dormant just because we jumped in time. */ + if (is_participating_on_network()) { + reset_user_activity(now); + } } else if (seconds_elapsed >= NUM_JUMPED_SECONDS_BEFORE_WARN) { /* Compare the monotonic clock to the result of time(). */ const int32_t monotime_msec_passed = @@ -2695,6 +2700,11 @@ update_current_time(time_t now) if (clock_jumped || seconds_elapsed >= NUM_IDLE_SECONDS_BEFORE_WARN) { circuit_note_clock_jumped(seconds_elapsed, ! clock_jumped); } + + /* Don't go dormant just because we jumped in time. */ + if (is_participating_on_network()) { + reset_user_activity(now); + } } else if (seconds_elapsed > 0) { stats_n_seconds_working += seconds_elapsed; } From 2f28cd1dc8e797b140271e5c33b9e4f823f7f2d8 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 14 Nov 2018 13:42:09 -0500 Subject: [PATCH 14/18] Rename and fix docs on FLUSH_ON_DISABLE Also rename "...flush_and_disable()" to "...schedule_and_disable()" --- src/core/mainloop/mainloop.c | 16 ++++++++-------- src/core/mainloop/periodic.c | 2 +- src/core/mainloop/periodic.h | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 2a68e8c098..9f45f3c869 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1386,7 +1386,7 @@ STATIC periodic_event_item_t periodic_events[] = { /* This is a legacy catch-all callback that runs once per second if * we are online and active. */ CALLBACK(second_elapsed, NET_PARTICIPANT, - FL(NEED_NET)|FL(FLUSH_ON_DISABLE)), + FL(NEED_NET)|FL(RUN_ON_DISABLE)), /* 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. */ @@ -1401,9 +1401,9 @@ STATIC periodic_event_item_t periodic_events[] = { /* We need to do these if we're participating in the Tor network, and * immediately before we stop. */ - CALLBACK(clean_caches, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), - CALLBACK(save_state, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), - CALLBACK(write_stats_file, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), + CALLBACK(clean_caches, NET_PARTICIPANT, FL(RUN_ON_DISABLE)), + CALLBACK(save_state, NET_PARTICIPANT, FL(RUN_ON_DISABLE)), + CALLBACK(write_stats_file, NET_PARTICIPANT, FL(RUN_ON_DISABLE)), /* Routers (bridge and relay) only. */ CALLBACK(check_descriptor, ROUTER, FL(NEED_NET)), @@ -1436,7 +1436,7 @@ STATIC periodic_event_item_t periodic_events[] = { /* Client only. */ /* XXXX this could be restricted to CLIENT+NET_PARTICIPANT */ - CALLBACK(rend_cache_failure_clean, NET_PARTICIPANT, FL(FLUSH_ON_DISABLE)), + CALLBACK(rend_cache_failure_clean, NET_PARTICIPANT, FL(RUN_ON_DISABLE)), /* Bridge Authority only. */ CALLBACK(write_bridge_ns, BRIDGEAUTH, 0), @@ -1651,8 +1651,8 @@ rescan_periodic_events(const or_options_t *options) periodic_event_enable(item); } else { log_debug(LD_GENERAL, "Disabling periodic event %s", item->name); - if (item->flags & PERIODIC_EVENT_FLAG_FLUSH_ON_DISABLE) { - periodic_event_flush_and_disable(item); + if (item->flags & PERIODIC_EVENT_FLAG_RUN_ON_DISABLE) { + periodic_event_schedule_and_disable(item); } else { periodic_event_disable(item); } @@ -1814,7 +1814,7 @@ second_elapsed_callback(time_t now, const or_options_t *options) */ /* (If our circuit build timeout can ever become lower than a second (which * it can't, currently), we should do this more often.) */ - // TODO: All expire stuff can become NET_PARTICIPANT, FLUSH_ON_DISABLE + // TODO: All expire stuff can become NET_PARTICIPANT, RUN_ON_DISABLE circuit_expire_building(); circuit_expire_waiting_for_better_guard(); diff --git a/src/core/mainloop/periodic.c b/src/core/mainloop/periodic.c index c290c3744e..9f9b178e43 100644 --- a/src/core/mainloop/periodic.c +++ b/src/core/mainloop/periodic.c @@ -174,7 +174,7 @@ periodic_event_disable(periodic_event_item_t *event) * Do nothing if the event was already disabled. */ void -periodic_event_flush_and_disable(periodic_event_item_t *event) +periodic_event_schedule_and_disable(periodic_event_item_t *event) { tor_assert(event); if (!periodic_event_is_enabled(event)) diff --git a/src/core/mainloop/periodic.h b/src/core/mainloop/periodic.h index 52d5450ee8..05ba4297f3 100644 --- a/src/core/mainloop/periodic.h +++ b/src/core/mainloop/periodic.h @@ -39,10 +39,10 @@ * the net_is_disabled() check. */ #define PERIODIC_EVENT_FLAG_NEED_NET (1U << 0) -/* Indicate that it the event is enabled, it event needs to be run once before +/* Indicate that if the event is enabled, it needs to be run once before * it becomes disabled. */ -#define PERIODIC_EVENT_FLAG_FLUSH_ON_DISABLE (1U << 1) +#define PERIODIC_EVENT_FLAG_RUN_ON_DISABLE (1U << 1) /** Callback function for a periodic event to take action. The return value * influences the next time the function will get called. Return @@ -88,6 +88,6 @@ void periodic_event_destroy(periodic_event_item_t *event); void periodic_event_reschedule(periodic_event_item_t *event); void periodic_event_enable(periodic_event_item_t *event); void periodic_event_disable(periodic_event_item_t *event); -void periodic_event_flush_and_disable(periodic_event_item_t *event); +void periodic_event_schedule_and_disable(periodic_event_item_t *event); #endif /* !defined(TOR_PERIODIC_H) */ From 53ccdb6945f0d4a9b27a9939211a3c9125ca4427 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 14 Nov 2018 15:05:05 -0500 Subject: [PATCH 15/18] Make sure that we are always a net participant when being a server Otherwise, if we're dormant, and we set ORPort, nothing makes us become non-dormant. --- src/core/mainloop/mainloop.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 9f45f3c869..2d12e26485 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1515,7 +1515,8 @@ get_my_roles(const or_options_t *options) options->ControlPort_set || options->OwningControllerFD != UINT64_MAX; - int is_net_participant = is_participating_on_network(); + int is_net_participant = is_participating_on_network() || + is_relay || is_hidden_service; if (is_bridge) roles |= PERIODIC_EVENT_ROLE_BRIDGE; if (is_client) roles |= PERIODIC_EVENT_ROLE_CLIENT; From 3743f7969587079a2f2bb03d0b7e5038557fd64a Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 15 Nov 2018 13:16:58 -0500 Subject: [PATCH 16/18] Add options to control dormant-client feature. The DormantClientTimeout option controls how long Tor will wait before going dormant. It also provides a way to disable the feature by setting DormantClientTimeout to e.g. "50 years". The DormantTimeoutDisabledByIdleStreams option controls whether open but inactive streams count as "client activity". To implement it, I had to make it so that reading or writing on a client stream *always* counts as activity. Closes ticket 28429. --- doc/tor.1.txt | 13 +++++++++++++ src/app/config/config.c | 6 ++++++ src/app/config/or_options_st.h | 10 ++++++++++ src/core/mainloop/mainloop.c | 19 ++++++++++--------- src/core/or/connection_edge.c | 11 +++++++++++ src/test/test_options.c | 1 + 6 files changed, 51 insertions(+), 9 deletions(-) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index b147ad68aa..47bddea097 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -1789,6 +1789,19 @@ The following options are useful only for clients (that is, if Try this many simultaneous connections to download a consensus before waiting for one to complete, timeout, or error out. (Default: 3) +[[DormantClientTimeout]] **DormantClientTimeout** __N__ **minutes**|**hours**|**days**|**weeks**:: + If Tor spends this much time without any client activity, + enter a dormant state where automatic circuits are not built, and + directory information is not fetched. + Does not affect servers or onion services. Must be at least 10 minutes. + (Default: 24 hours) + +[[DormantTimeoutDisabledByIdleStreams]] **DormantTimeoutDisabledByIdleStreams **0**|**1**:: + If true, then any open client stream (even one not reading or writing) + counts as client activity for the purpose of DormantClientTimeout. + If false, then only network activity counts. (Default: 1) + + SERVER OPTIONS -------------- diff --git a/src/app/config/config.c b/src/app/config/config.c index 8aa0c1f4bd..90eae50fdd 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -389,6 +389,8 @@ static config_var_t option_vars_[] = { OBSOLETE("DynamicDHGroups"), VPORT(DNSPort), OBSOLETE("DNSListenAddress"), + V(DormantClientTimeout, INTERVAL, "24 hours"), + V(DormantTimeoutDisabledByIdleStreams, BOOL, "1"), /* DoS circuit creation options. */ V(DoSCircuitCreationEnabled, AUTOBOOL, "auto"), V(DoSCircuitCreationMinConnections, UINT, "0"), @@ -3836,6 +3838,10 @@ options_validate(or_options_t *old_options, or_options_t *options, "default."); } + if (options->DormantClientTimeout < 10*60 && !options->TestingTorNetwork) { + REJECT("DormantClientTimeout is too low. It must be at least 10 minutes."); + } + if (options->PathBiasNoticeRate > 1.0) { tor_asprintf(msg, "PathBiasNoticeRate is too high. " diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index 3524b99b53..6cbc86ec18 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -1072,6 +1072,16 @@ struct or_options_t { /** Autobool: Do we refuse single hop client rendezvous? */ int DoSRefuseSingleHopClientRendezvous; + + /** Interval: how long without activity does it take for a client + * to become dormant? + **/ + int DormantClientTimeout; + + /** Boolean: true if having an idle stream is sufficient to prevent a client + * from becoming dormant. + **/ + int DormantTimeoutDisabledByIdleStreams; }; #endif diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 2d12e26485..1bd186d856 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -2018,24 +2018,25 @@ check_network_participation_callback(time_t now, const or_options_t *options) goto found_activity; } - /* XXXX Add an option to never become dormant. */ - /* If we have any currently open entry streams other than "linked" * connections used for directory requests, those count as user activity. */ - /* XXXX make this configurable? */ - if (connection_get_by_type_nonlinked(CONN_TYPE_AP) != NULL) { - goto found_activity; + if (options->DormantTimeoutDisabledByIdleStreams) { + if (connection_get_by_type_nonlinked(CONN_TYPE_AP) != NULL) { + goto found_activity; + } } /* XXXX Make this configurable? */ /** How often do we check whether we have had network activity? */ #define CHECK_PARTICIPATION_INTERVAL (5*60) - /** Become dormant if there has been no user activity in this long. */ - /* XXXX make this configurable! */ -#define BECOME_DORMANT_AFTER_INACTIVITY (24*60*60) - if (get_last_user_activity_time() + BECOME_DORMANT_AFTER_INACTIVITY >= now) { + /* Become dormant if there has been no user activity in a long time. + * (The funny checks below are in order to prevent overflow.) */ + time_t time_since_last_activity = 0; + if (get_last_user_activity_time() < now) + time_since_last_activity = now - get_last_user_activity_time(); + if (time_since_last_activity >= options->DormantClientTimeout) { log_notice(LD_GENERAL, "No user activity in a long time: becoming" " dormant."); set_network_participation(false); diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c index 58aefcf8f2..7b51313e8a 100644 --- a/src/core/or/connection_edge.c +++ b/src/core/or/connection_edge.c @@ -62,6 +62,7 @@ #include "app/config/config.h" #include "core/mainloop/connection.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/netstatus.h" #include "core/or/channel.h" #include "core/or/circuitbuild.h" #include "core/or/circuitlist.h" @@ -297,6 +298,11 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) } return 0; case AP_CONN_STATE_OPEN: + if (! conn->base_.linked) { + note_user_activity(approx_time()); + } + + /* falls through. */ case EXIT_CONN_STATE_OPEN: if (connection_edge_package_raw_inbuf(conn, package_partial, NULL) < 0) { /* (We already sent an end cell if possible) */ @@ -751,6 +757,11 @@ connection_edge_flushed_some(edge_connection_t *conn) { switch (conn->base_.state) { case AP_CONN_STATE_OPEN: + if (! conn->base_.linked) { + note_user_activity(approx_time()); + } + + /* falls through. */ case EXIT_CONN_STATE_OPEN: connection_edge_consider_sending_sendme(conn); break; diff --git a/src/test/test_options.c b/src/test/test_options.c index f14e620eeb..376d77626f 100644 --- a/src/test/test_options.c +++ b/src/test/test_options.c @@ -425,6 +425,7 @@ get_options_test_data(const char *conf) // with options_init(), but about a dozen tests break when I do that. // Being kinda lame and just fixing the immedate breakage for now.. result->opt->ConnectionPadding = -1; // default must be "auto" + result->opt->DormantClientTimeout = 1800; // must be over 600. rv = config_get_lines(conf, &cl, 1); tt_int_op(rv, OP_EQ, 0); From 55512ef022de39770e0787e9dfc2e29e762652ae Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 19 Nov 2018 15:35:55 -0500 Subject: [PATCH 17/18] Test netstatus.c tracking of user participation status --- src/core/mainloop/mainloop.c | 4 +-- src/core/mainloop/mainloop.h | 2 +- src/test/test_mainloop.c | 57 +++++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 1bd186d856..331f7021a4 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1608,8 +1608,8 @@ rescan_periodic_events_cb(mainloop_event_t *event, void *arg) /** * Schedule an event that will rescan which periodic events should run. **/ -void -schedule_rescan_periodic_events(void) +MOCK_IMPL(void, +schedule_rescan_periodic_events,(void)) { if (!rescan_periodic_events_ev) { rescan_periodic_events_ev = diff --git a/src/core/mainloop/mainloop.h b/src/core/mainloop/mainloop.h index 7f27ef9a52..e5e730fc8e 100644 --- a/src/core/mainloop/mainloop.h +++ b/src/core/mainloop/mainloop.h @@ -65,7 +65,7 @@ void reschedule_or_state_save(void); void reschedule_dirvote(const or_options_t *options); void mainloop_schedule_postloop_cleanup(void); void rescan_periodic_events(const or_options_t *options); -void schedule_rescan_periodic_events(void); +MOCK_DECL(void, schedule_rescan_periodic_events,(void)); void update_current_time(time_t now); diff --git a/src/test/test_mainloop.c b/src/test/test_mainloop.c index 92ce2e9918..94b684fb3a 100644 --- a/src/test/test_mainloop.c +++ b/src/test/test_mainloop.c @@ -11,6 +11,7 @@ #include "core/or/or.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/netstatus.h" static const uint64_t BILLION = 1000000000; @@ -131,12 +132,66 @@ test_mainloop_update_time_jumps(void *arg) monotime_disable_test_mocking(); } +static int schedule_rescan_called = 0; +static void +mock_schedule_rescan_periodic_events(void) +{ + ++schedule_rescan_called; +} + +static void +test_mainloop_user_activity(void *arg) +{ + (void)arg; + const time_t start = 1542658829; + update_approx_time(start); + + MOCK(schedule_rescan_periodic_events, mock_schedule_rescan_periodic_events); + + reset_user_activity(start); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start); + + set_network_participation(false); + + // reset can move backwards and forwards, but does not change network + // participation. + reset_user_activity(start-10); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start-10); + reset_user_activity(start+10); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+10); + + tt_int_op(schedule_rescan_called, OP_EQ, 0); + tt_int_op(false, OP_EQ, is_participating_on_network()); + + // "note" can only move forward. Calling it from a non-participating + // state makes us rescan the periodic callbacks and set participation. + note_user_activity(start+20); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+20); + tt_int_op(true, OP_EQ, is_participating_on_network()); + tt_int_op(schedule_rescan_called, OP_EQ, 1); + + // Calling it again will move us forward, but not call rescan again. + note_user_activity(start+25); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+25); + tt_int_op(true, OP_EQ, is_participating_on_network()); + tt_int_op(schedule_rescan_called, OP_EQ, 1); + + // We won't move backwards. + note_user_activity(start+20); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+25); + tt_int_op(true, OP_EQ, is_participating_on_network()); + tt_int_op(schedule_rescan_called, OP_EQ, 1); + + done: + UNMOCK(schedule_rescan_periodic_events); +} + #define MAINLOOP_TEST(name) \ { #name, test_mainloop_## name , TT_FORK, NULL, NULL } struct testcase_t mainloop_tests[] = { MAINLOOP_TEST(update_time_normal), MAINLOOP_TEST(update_time_jumps), + MAINLOOP_TEST(user_activity), END_OF_TESTCASES }; - From 02843c4a4e2fab9c5d9cdb95c425c37ff3d1a4ae Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 19 Nov 2018 16:31:50 -0500 Subject: [PATCH 18/18] Test for check_network_participation_callback() --- src/core/mainloop/connection.c | 4 +- src/core/mainloop/connection.h | 2 +- src/core/mainloop/mainloop.c | 3 +- src/core/mainloop/mainloop.h | 3 ++ src/feature/hs/hs_service.c | 4 +- src/feature/hs/hs_service.h | 2 +- src/test/test_mainloop.c | 88 ++++++++++++++++++++++++++++++++++ 7 files changed, 98 insertions(+), 8 deletions(-) diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 25224fd999..c1c7c3678b 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -4433,8 +4433,8 @@ connection_get_by_type_state(int type, int state) * Return a connection of type type that is not an internally linked * connection, and is not marked for close. **/ -connection_t * -connection_get_by_type_nonlinked(int type) +MOCK_IMPL(connection_t *, +connection_get_by_type_nonlinked,(int type)) { CONN_GET_TEMPLATE(conn, conn->type == type && !conn->linked); } diff --git a/src/core/mainloop/connection.h b/src/core/mainloop/connection.h index 9f1a23c6f2..07b8df4138 100644 --- a/src/core/mainloop/connection.h +++ b/src/core/mainloop/connection.h @@ -240,7 +240,7 @@ size_t connection_get_outbuf_len(connection_t *conn); connection_t *connection_get_by_global_id(uint64_t id); connection_t *connection_get_by_type(int type); -connection_t *connection_get_by_type_nonlinked(int type); +MOCK_DECL(connection_t *,connection_get_by_type_nonlinked,(int type)); MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type, const tor_addr_t *addr, uint16_t port, int purpose)); diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 331f7021a4..42df1038a8 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1367,7 +1367,6 @@ CALLBACK(write_bridge_ns); CALLBACK(write_stats_file); CALLBACK(control_per_second_events); CALLBACK(second_elapsed); -CALLBACK(check_network_participation); #undef CALLBACK @@ -2003,7 +2002,7 @@ add_entropy_callback(time_t now, const or_options_t *options) /** Periodic callback: if there has been no network usage in a while, * enter a dormant state. */ -static int +STATIC int check_network_participation_callback(time_t now, const or_options_t *options) { /* If we're a server, we can't become dormant. */ diff --git a/src/core/mainloop/mainloop.h b/src/core/mainloop/mainloop.h index e5e730fc8e..14e80ebb21 100644 --- a/src/core/mainloop/mainloop.h +++ b/src/core/mainloop/mainloop.h @@ -104,6 +104,9 @@ STATIC void close_closeable_connections(void); STATIC void initialize_periodic_events(void); STATIC void teardown_periodic_events(void); STATIC int get_my_roles(const or_options_t *); +STATIC int check_network_participation_callback(time_t now, + const or_options_t *options); + #ifdef TOR_UNIT_TESTS extern smartlist_t *connection_array; diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c index c288e28e80..ee0b64a969 100644 --- a/src/feature/hs/hs_service.c +++ b/src/feature/hs/hs_service.c @@ -3667,8 +3667,8 @@ hs_service_lookup_current_desc(const ed25519_public_key_t *pk) } /* Return the number of service we have configured and usable. */ -unsigned int -hs_service_get_num_services(void) +MOCK_IMPL(unsigned int, +hs_service_get_num_services,(void)) { if (hs_service_map == NULL) { return 0; diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h index a8a9faaea9..be1155bcd1 100644 --- a/src/feature/hs/hs_service.h +++ b/src/feature/hs/hs_service.h @@ -310,7 +310,7 @@ hs_service_t *hs_service_new(const or_options_t *options); void hs_service_free_(hs_service_t *service); #define hs_service_free(s) FREE_AND_NULL(hs_service_t, hs_service_free_, (s)) -unsigned int hs_service_get_num_services(void); +MOCK_DECL(unsigned int, hs_service_get_num_services,(void)); void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); int hs_service_get_version_from_key(const hs_service_t *service); diff --git a/src/test/test_mainloop.c b/src/test/test_mainloop.c index 94b684fb3a..8dfd5f619a 100644 --- a/src/test/test_mainloop.c +++ b/src/test/test_mainloop.c @@ -6,13 +6,21 @@ * \brief Tests for functions closely related to the Tor main loop */ +#define CONFIG_PRIVATE +#define MAINLOOP_PRIVATE + #include "test/test.h" #include "test/log_test_helpers.h" #include "core/or/or.h" +#include "core/mainloop/connection.h" #include "core/mainloop/mainloop.h" #include "core/mainloop/netstatus.h" +#include "feature/hs/hs_service.h" + +#include "app/config/config.h" + static const uint64_t BILLION = 1000000000; static void @@ -186,6 +194,85 @@ test_mainloop_user_activity(void *arg) UNMOCK(schedule_rescan_periodic_events); } +static unsigned int +mock_get_num_services(void) +{ + return 1; +} + +static connection_t * +mock_connection_gbtu(int type) +{ + (void) type; + return (void *)"hello fellow connections"; +} + +static void +test_mainloop_check_participation(void *arg) +{ + (void)arg; + or_options_t *options = options_new(); + const time_t start = 1542658829; + const time_t ONE_DAY = 24*60*60; + + // Suppose we've been idle for a day or two + reset_user_activity(start - 2*ONE_DAY); + set_network_participation(true); + check_network_participation_callback(start, options); + tt_int_op(is_participating_on_network(), OP_EQ, false); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start-2*ONE_DAY); + + // suppose we've been idle for 2 days... but we are a server. + reset_user_activity(start - 2*ONE_DAY); + options->ORPort_set = 1; + set_network_participation(true); + check_network_participation_callback(start+2, options); + tt_int_op(is_participating_on_network(), OP_EQ, true); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+2); + options->ORPort_set = 0; + + // idle for 2 days, but we have a hidden service. + reset_user_activity(start - 2*ONE_DAY); + set_network_participation(true); + MOCK(hs_service_get_num_services, mock_get_num_services); + check_network_participation_callback(start+3, options); + tt_int_op(is_participating_on_network(), OP_EQ, true); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+3); + UNMOCK(hs_service_get_num_services); + + // idle for 2 days but we have at least one user connection + MOCK(connection_get_by_type_nonlinked, mock_connection_gbtu); + reset_user_activity(start - 2*ONE_DAY); + set_network_participation(true); + options->DormantTimeoutDisabledByIdleStreams = 1; + check_network_participation_callback(start+10, options); + tt_int_op(is_participating_on_network(), OP_EQ, true); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+10); + + // as above, but DormantTimeoutDisabledByIdleStreams is not set + reset_user_activity(start - 2*ONE_DAY); + set_network_participation(true); + options->DormantTimeoutDisabledByIdleStreams = 0; + check_network_participation_callback(start+13, options); + tt_int_op(is_participating_on_network(), OP_EQ, false); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start-2*ONE_DAY); + UNMOCK(connection_get_by_type_nonlinked); + options->DormantTimeoutDisabledByIdleStreams = 1; + + // idle for 2 days but DormantClientTimeout is 3 days + reset_user_activity(start - 2*ONE_DAY); + set_network_participation(true); + options->DormantClientTimeout = ONE_DAY * 3; + check_network_participation_callback(start+30, options); + tt_int_op(is_participating_on_network(), OP_EQ, true); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start-2*ONE_DAY); + + done: + or_options_free(options); + UNMOCK(hs_service_get_num_services); + UNMOCK(connection_get_by_type_nonlinked); +} + #define MAINLOOP_TEST(name) \ { #name, test_mainloop_## name , TT_FORK, NULL, NULL } @@ -193,5 +280,6 @@ struct testcase_t mainloop_tests[] = { MAINLOOP_TEST(update_time_normal), MAINLOOP_TEST(update_time_jumps), MAINLOOP_TEST(user_activity), + MAINLOOP_TEST(check_participation), END_OF_TESTCASES };