From b25b8150c282c28d3fa23330891fda7adbbfe584 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 4 Dec 2018 11:56:05 -0500 Subject: [PATCH 1/5] Remember in our state file how long we've spent since user activity Rather than initializing the "Dormant" status to "off" and the "last activity" count to "now", initialize them based on our state file: stay dormant if we were dormant, or remember the amount of time we've spent inactive. --- src/app/config/or_state_st.h | 7 ++++++ src/app/config/statefile.c | 8 +++++++ src/core/mainloop/mainloop.c | 7 +----- src/core/mainloop/netstatus.c | 41 +++++++++++++++++++++++++++++++++++ src/core/mainloop/netstatus.h | 3 +++ 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/app/config/or_state_st.h b/src/app/config/or_state_st.h index d95df6236b..00968d3731 100644 --- a/src/app/config/or_state_st.h +++ b/src/app/config/or_state_st.h @@ -87,6 +87,13 @@ struct or_state_t { /** When did we last rotate our onion key? "0" for 'no idea'. */ time_t LastRotatedOnionKey; + + /** Number of minutes since the last user-initiated request (as defined by + * the dormant net-status system.) Set to zero if we are dormant. */ + int MinutesSinceUserActivity; + /** True if we were dormant when we last wrote the file; false if we + * weren't. "auto" on initial startup. */ + int Dormant; }; #endif diff --git a/src/app/config/statefile.c b/src/app/config/statefile.c index 4ba7be1519..97b96f1149 100644 --- a/src/app/config/statefile.c +++ b/src/app/config/statefile.c @@ -34,6 +34,7 @@ #include "app/config/config.h" #include "app/config/confparse.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/netstatus.h" #include "core/mainloop/connection.h" #include "feature/control/control.h" #include "feature/client/entrynodes.h" @@ -132,6 +133,9 @@ static config_var_t state_vars_[] = { VAR("CircuitBuildTimeBin", LINELIST_S, BuildtimeHistogram, NULL), VAR("BuildtimeHistogram", LINELIST_V, BuildtimeHistogram, NULL), + V(MinutesSinceUserActivity, UINT, NULL), + V(Dormant, AUTOBOOL, "auto"), + END_OF_CONFIG_VARS }; @@ -309,6 +313,8 @@ or_state_set(or_state_t *new_state) get_circuit_build_times_mutable(),global_state) < 0) { ret = -1; } + netstatus_load_from_state(global_state, time(NULL)); + return ret; } @@ -500,6 +506,8 @@ or_state_save(time_t now) entry_guards_update_state(global_state); rep_hist_update_state(global_state); circuit_build_times_update_state(get_circuit_build_times(), global_state); + netstatus_flush_to_state(global_state, now); + if (accounting_is_enabled(get_options())) accounting_run_housekeeping(now); diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 2e2ae876d4..cd955e0ca0 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1627,6 +1627,7 @@ schedule_rescan_periodic_events,(void)) void rescan_periodic_events(const or_options_t *options) { + puts("RESCAN"); tor_assert(options); /* Avoid scanning the event list if we haven't initialized it yet. This is @@ -2818,12 +2819,6 @@ 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. */ diff --git a/src/core/mainloop/netstatus.c b/src/core/mainloop/netstatus.c index ed7c952dcd..59fd8f8037 100644 --- a/src/core/mainloop/netstatus.c +++ b/src/core/mainloop/netstatus.c @@ -10,6 +10,8 @@ #include "app/config/config.h" #include "feature/hibernate/hibernate.h" +#include "app/config/or_state_st.h" + /** Return true iff our network is in some sense disabled or shutting down: * either we're hibernating, entering hibernation, or the network is turned * off with DisableNetwork. */ @@ -31,6 +33,10 @@ net_is_completely_disabled(void) /** * The time at which we've last seen "user activity" -- that is, any activity * that should keep us as a participant on the network. + * + * This is not actually the true time. We will adjust this forward if + * our clock jumps, or if Tor is shut down for a while, so that the time + * since our last activity remains as it was before the jump or shutdown. */ static time_t last_user_activity_seen = 0; @@ -99,3 +105,38 @@ is_participating_on_network(void) { return participating_on_network; } + +/** + * Update 'state' with the last time at which we were active on the network. + **/ +void +netstatus_flush_to_state(or_state_t *state, time_t now) +{ + state->Dormant = ! participating_on_network; + if (participating_on_network) { + time_t sec_since_activity = MAX(0, now - last_user_activity_seen); + state->MinutesSinceUserActivity = (int)(sec_since_activity / 60); + } else { + state->MinutesSinceUserActivity = 0; + } +} + +/** + * Update our current view of network participation from an or_state_t object. + **/ +void +netstatus_load_from_state(const or_state_t *state, time_t now) +{ + time_t last_activity; + if (state->Dormant == -1) { // Initial setup. + last_activity = now; + participating_on_network = true; + } else if (state->Dormant) { + last_activity = 0; + participating_on_network = false; + } else { + last_activity = now - 60 * state->MinutesSinceUserActivity; + participating_on_network = true; + } + reset_user_activity(last_activity); +} diff --git a/src/core/mainloop/netstatus.h b/src/core/mainloop/netstatus.h index 58c994fd14..4b008e4cfa 100644 --- a/src/core/mainloop/netstatus.h +++ b/src/core/mainloop/netstatus.h @@ -17,4 +17,7 @@ time_t get_last_user_activity_time(void); void set_network_participation(bool participation); bool is_participating_on_network(void); +void netstatus_flush_to_state(or_state_t *state, time_t now); +void netstatus_load_from_state(const or_state_t *state, time_t now); + #endif From 4f558843151f0c49c077d3d9a1cd8a187904a024 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 4 Dec 2018 12:07:23 -0500 Subject: [PATCH 2/5] Add an option to start tor in dormant mode for the first time. --- doc/tor.1.txt | 11 ++++++++++- src/app/config/config.c | 1 + src/app/config/or_options_st.h | 4 ++++ src/core/mainloop/netstatus.c | 10 ++++++++-- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 8e6ec7f1a2..82c9e34de4 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -1803,11 +1803,20 @@ The following options are useful only for clients (that is, if Does not affect servers or onion services. Must be at least 10 minutes. (Default: 24 hours) -[[DormantTimeoutDisabledByIdleStreams]] **DormantTimeoutDisabledByIdleStreams **0**|**1**:: +[[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) +[[DormantOnFirstStartup]] **DormantOnFirstStartup** **0**|**1**:: + If true, then the first time Tor starts up with a fresh DataDirectory, + it starts in dormant mode, and takes no actions until the user has made + a request. (This mode is recommended if installing a Tor client for a + user who might not actually use it.) If false, Tor bootstraps the first + time it is started, whether it sees a user request or not. + + + After the first time Tor starts, it begins in dormant mode if it was + dormant before, and not otherwise. (Default: 0) SERVER OPTIONS -------------- diff --git a/src/app/config/config.c b/src/app/config/config.c index d40e362b32..dcefa3d6a4 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -392,6 +392,7 @@ static config_var_t option_vars_[] = { OBSOLETE("DNSListenAddress"), V(DormantClientTimeout, INTERVAL, "24 hours"), V(DormantTimeoutDisabledByIdleStreams, BOOL, "1"), + V(DormantOnFirstStartup, BOOL, "0"), /* DoS circuit creation options. */ V(DoSCircuitCreationEnabled, AUTOBOOL, "auto"), V(DoSCircuitCreationMinConnections, UINT, "0"), diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index 9065248a9c..c2bc1079a5 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -1085,6 +1085,10 @@ struct or_options_t { * from becoming dormant. **/ int DormantTimeoutDisabledByIdleStreams; + + /** Boolean: true if Tor should be dormant the first time it starts with + * a datadirectory; false otherwise. */ + int DormantOnFirstStartup; }; #endif diff --git a/src/core/mainloop/netstatus.c b/src/core/mainloop/netstatus.c index 59fd8f8037..2426baae34 100644 --- a/src/core/mainloop/netstatus.c +++ b/src/core/mainloop/netstatus.c @@ -129,8 +129,14 @@ netstatus_load_from_state(const or_state_t *state, time_t now) { time_t last_activity; if (state->Dormant == -1) { // Initial setup. - last_activity = now; - participating_on_network = true; + if (get_options()->DormantOnFirstStartup) { + last_activity = 0; + participating_on_network = false; + } else { + // Start up as active, treat activity as happening now. + last_activity = now; + participating_on_network = true; + } } else if (state->Dormant) { last_activity = 0; participating_on_network = false; From c037bf58173db56766381a7c1cd5973789f0fd0f Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 4 Dec 2018 12:11:42 -0500 Subject: [PATCH 3/5] changes file for ticket26864 --- changes/ticket28624 | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changes/ticket28624 diff --git a/changes/ticket28624 b/changes/ticket28624 new file mode 100644 index 0000000000..353f962be9 --- /dev/null +++ b/changes/ticket28624 @@ -0,0 +1,5 @@ + o Minor features (battery management, client, dormant mode): + - The client's memory of whether it is "dormant", and how long it has + spend idle, persists across invocations. Implements ticket 28624. + - There is a DormantOnFirstStartup option that integrators can use if + they expect that in many cases, Tor will be installed but not used. From b5c04173c88369d0c4cdaf8a34c2474dc25c79fa Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 4 Dec 2018 12:21:14 -0500 Subject: [PATCH 4/5] Change interaction between dormant mode and clock jumps. When the clock jumps, and we have a record of last user activity, adjust that record. This way if I'm inactive for 10 minutes and then the laptop is sleeping for an hour, I'll still count as having been inactive for 10 minutes. Previously, we treat every jump as if it were activity, which is ridiculous, and would prevent a Tor instance with a jumpy clock from ever going dormant. --- src/core/mainloop/mainloop.c | 21 +++++++++++---------- src/core/mainloop/netstatus.c | 12 ++++++++++++ src/core/mainloop/netstatus.h | 1 + src/lib/intmath/cmp.h | 3 +++ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index cd955e0ca0..6b9c03798e 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1627,7 +1627,6 @@ schedule_rescan_periodic_events,(void)) void rescan_periodic_events(const or_options_t *options) { - puts("RESCAN"); tor_assert(options); /* Avoid scanning the event list if we haven't initialized it yet. This is @@ -2687,6 +2686,17 @@ update_current_time(time_t now) memcpy(&last_updated, ¤t_second_last_changed, sizeof(last_updated)); monotime_coarse_get(¤t_second_last_changed); + /** How much clock jumping means that we should adjust our idea of when + * to go dormant? */ +#define NUM_JUMPED_SECONDS_BEFORE_NETSTATUS_UPDATE 20 + + /* Don't go dormant early or late just because we jumped in time. */ + if (ABS(seconds_elapsed) >= NUM_JUMPED_SECONDS_BEFORE_NETSTATUS_UPDATE) { + if (is_participating_on_network()) { + netstatus_note_clock_jumped(seconds_elapsed); + } + } + /** How much clock jumping do we tolerate? */ #define NUM_JUMPED_SECONDS_BEFORE_WARN 100 @@ -2697,10 +2707,6 @@ update_current_time(time_t now) // 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 = @@ -2722,11 +2728,6 @@ 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; } diff --git a/src/core/mainloop/netstatus.c b/src/core/mainloop/netstatus.c index 2426baae34..d1989cb839 100644 --- a/src/core/mainloop/netstatus.c +++ b/src/core/mainloop/netstatus.c @@ -146,3 +146,15 @@ netstatus_load_from_state(const or_state_t *state, time_t now) } reset_user_activity(last_activity); } + +/** + * Adjust the time at which the user was last active by seconds_diff + * in response to a clock jump. + */ +void +netstatus_note_clock_jumped(time_t seconds_diff) +{ + time_t last_active = get_last_user_activity_time(); + if (last_active) + reset_user_activity(last_active + seconds_diff); +} diff --git a/src/core/mainloop/netstatus.h b/src/core/mainloop/netstatus.h index 4b008e4cfa..9a0fa410fd 100644 --- a/src/core/mainloop/netstatus.h +++ b/src/core/mainloop/netstatus.h @@ -19,5 +19,6 @@ bool is_participating_on_network(void); void netstatus_flush_to_state(or_state_t *state, time_t now); void netstatus_load_from_state(const or_state_t *state, time_t now); +void netstatus_note_clock_jumped(time_t seconds_diff); #endif diff --git a/src/lib/intmath/cmp.h b/src/lib/intmath/cmp.h index 16952bee3e..11b6fdf98e 100644 --- a/src/lib/intmath/cmp.h +++ b/src/lib/intmath/cmp.h @@ -36,4 +36,7 @@ ((v) > (max)) ? (max) : \ (v) ) +/** Give the absolute value of x, independent of its type. */ +#define ABS(x) ( ((x)<0) ? -(x) : (x) ) + #endif /* !defined(TOR_INTMATH_CMP_H) */ From e3b7fd2a8129f0d2a7879976e57496b6fd4a7d00 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 4 Dec 2018 17:52:06 -0500 Subject: [PATCH 5/5] Unit tests for back-end functions for persistent dormant state --- src/test/test_mainloop.c | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/test/test_mainloop.c b/src/test/test_mainloop.c index 8dfd5f619a..d797417912 100644 --- a/src/test/test_mainloop.c +++ b/src/test/test_mainloop.c @@ -8,6 +8,7 @@ #define CONFIG_PRIVATE #define MAINLOOP_PRIVATE +#define STATEFILE_PRIVATE #include "test/test.h" #include "test/log_test_helpers.h" @@ -20,6 +21,8 @@ #include "feature/hs/hs_service.h" #include "app/config/config.h" +#include "app/config/statefile.h" +#include "app/config/or_state_st.h" static const uint64_t BILLION = 1000000000; @@ -190,6 +193,13 @@ test_mainloop_user_activity(void *arg) tt_int_op(true, OP_EQ, is_participating_on_network()); tt_int_op(schedule_rescan_called, OP_EQ, 1); + // We _will_ adjust if the clock jumps though. + netstatus_note_clock_jumped(500); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+525); + + netstatus_note_clock_jumped(-400); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start+125); + done: UNMOCK(schedule_rescan_periodic_events); } @@ -273,6 +283,70 @@ test_mainloop_check_participation(void *arg) UNMOCK(connection_get_by_type_nonlinked); } +static void +test_mainloop_dormant_load_state(void *arg) +{ + (void)arg; + or_state_t *state = or_state_new(); + const time_t start = 1543956575; + + reset_user_activity(0); + set_network_participation(false); + + // When we construct a new state, it starts out in "auto" mode. + tt_int_op(state->Dormant, OP_EQ, -1); + + // Initializing from "auto" makes us start out (by default) non-Dormant, + // with activity right now. + netstatus_load_from_state(state, start); + tt_assert(is_participating_on_network()); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start); + + // Initializing from dormant clears the last user activity time, and + // makes us dormant. + state->Dormant = 1; + netstatus_load_from_state(state, start); + tt_assert(! is_participating_on_network()); + tt_i64_op(get_last_user_activity_time(), OP_EQ, 0); + + // Initializing from non-dormant sets the last user activity time, and + // makes us non-dormant. + state->Dormant = 0; + state->MinutesSinceUserActivity = 123; + netstatus_load_from_state(state, start); + tt_assert(is_participating_on_network()); + tt_i64_op(get_last_user_activity_time(), OP_EQ, start - 123*60); + + done: + or_state_free(state); +} + +static void +test_mainloop_dormant_save_state(void *arg) +{ + (void)arg; + or_state_t *state = or_state_new(); + const time_t start = 1543956575; + + // Can we save a non-dormant state correctly? + reset_user_activity(start - 1000); + set_network_participation(true); + netstatus_flush_to_state(state, start); + + tt_int_op(state->Dormant, OP_EQ, 0); + tt_int_op(state->MinutesSinceUserActivity, OP_EQ, 1000 / 60); + + // Can we save a dormant state correctly? + set_network_participation(false); + netstatus_flush_to_state(state, start); + + tt_int_op(state->Dormant, OP_EQ, 1); + tt_int_op(state->MinutesSinceUserActivity, OP_EQ, 0); + + done: + or_state_free(state); +} + #define MAINLOOP_TEST(name) \ { #name, test_mainloop_## name , TT_FORK, NULL, NULL } @@ -281,5 +355,7 @@ struct testcase_t mainloop_tests[] = { MAINLOOP_TEST(update_time_jumps), MAINLOOP_TEST(user_activity), MAINLOOP_TEST(check_participation), + MAINLOOP_TEST(dormant_load_state), + MAINLOOP_TEST(dormant_save_state), END_OF_TESTCASES };