Browse Source

Merge branch 'dormant_persist_squashed'

Nick Mathewson 6 years ago
parent
commit
69264f96f3

+ 5 - 0
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.

+ 10 - 1
doc/tor.1.txt

@@ -1809,11 +1809,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.
     Does not affect servers or onion services. Must be at least 10 minutes.
     (Default: 24 hours)
     (Default: 24 hours)
 
 
-[[DormantTimeoutDisabledByIdleStreams]] **DormantTimeoutDisabledByIdleStreams  **0**|**1**::
+[[DormantTimeoutDisabledByIdleStreams]] **DormantTimeoutDisabledByIdleStreams**  **0**|**1**::
     If true, then any open client stream (even one not reading or writing)
     If true, then any open client stream (even one not reading or writing)
     counts as client activity for the purpose of DormantClientTimeout.
     counts as client activity for the purpose of DormantClientTimeout.
     If false, then only network activity counts. (Default: 1)
     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
 SERVER OPTIONS
 --------------
 --------------

+ 1 - 0
src/app/config/config.c

@@ -392,6 +392,7 @@ static config_var_t option_vars_[] = {
   OBSOLETE("DNSListenAddress"),
   OBSOLETE("DNSListenAddress"),
   V(DormantClientTimeout,         INTERVAL, "24 hours"),
   V(DormantClientTimeout,         INTERVAL, "24 hours"),
   V(DormantTimeoutDisabledByIdleStreams, BOOL,     "1"),
   V(DormantTimeoutDisabledByIdleStreams, BOOL,     "1"),
+  V(DormantOnFirstStartup,       BOOL,      "0"),
   /* DoS circuit creation options. */
   /* DoS circuit creation options. */
   V(DoSCircuitCreationEnabled,   AUTOBOOL, "auto"),
   V(DoSCircuitCreationEnabled,   AUTOBOOL, "auto"),
   V(DoSCircuitCreationMinConnections,      UINT, "0"),
   V(DoSCircuitCreationMinConnections,      UINT, "0"),

+ 4 - 0
src/app/config/or_options_st.h

@@ -1085,6 +1085,10 @@ struct or_options_t {
    * from becoming dormant.
    * from becoming dormant.
    **/
    **/
   int DormantTimeoutDisabledByIdleStreams;
   int DormantTimeoutDisabledByIdleStreams;
+
+  /** Boolean: true if Tor should be dormant the first time it starts with
+   * a datadirectory; false otherwise. */
+  int DormantOnFirstStartup;
 };
 };
 
 
 #endif
 #endif

+ 7 - 0
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'. */
   /** When did we last rotate our onion key?  "0" for 'no idea'. */
   time_t LastRotatedOnionKey;
   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
 #endif

+ 8 - 0
src/app/config/statefile.c

@@ -34,6 +34,7 @@
 #include "app/config/config.h"
 #include "app/config/config.h"
 #include "app/config/confparse.h"
 #include "app/config/confparse.h"
 #include "core/mainloop/mainloop.h"
 #include "core/mainloop/mainloop.h"
+#include "core/mainloop/netstatus.h"
 #include "core/mainloop/connection.h"
 #include "core/mainloop/connection.h"
 #include "feature/control/control.h"
 #include "feature/control/control.h"
 #include "feature/client/entrynodes.h"
 #include "feature/client/entrynodes.h"
@@ -132,6 +133,9 @@ static config_var_t state_vars_[] = {
   VAR("CircuitBuildTimeBin",          LINELIST_S, BuildtimeHistogram, NULL),
   VAR("CircuitBuildTimeBin",          LINELIST_S, BuildtimeHistogram, NULL),
   VAR("BuildtimeHistogram",           LINELIST_V, BuildtimeHistogram, NULL),
   VAR("BuildtimeHistogram",           LINELIST_V, BuildtimeHistogram, NULL),
 
 
+  V(MinutesSinceUserActivity,         UINT,     NULL),
+  V(Dormant,                          AUTOBOOL, "auto"),
+
   END_OF_CONFIG_VARS
   END_OF_CONFIG_VARS
 };
 };
 
 
@@ -309,6 +313,8 @@ or_state_set(or_state_t *new_state)
       get_circuit_build_times_mutable(),global_state) < 0) {
       get_circuit_build_times_mutable(),global_state) < 0) {
     ret = -1;
     ret = -1;
   }
   }
+  netstatus_load_from_state(global_state, time(NULL));
+
   return ret;
   return ret;
 }
 }
 
 
@@ -500,6 +506,8 @@ or_state_save(time_t now)
   entry_guards_update_state(global_state);
   entry_guards_update_state(global_state);
   rep_hist_update_state(global_state);
   rep_hist_update_state(global_state);
   circuit_build_times_update_state(get_circuit_build_times(), 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()))
   if (accounting_is_enabled(get_options()))
     accounting_run_housekeeping(now);
     accounting_run_housekeeping(now);
 
 

+ 11 - 15
src/core/mainloop/mainloop.c

@@ -2686,6 +2686,17 @@ update_current_time(time_t now)
   memcpy(&last_updated, &current_second_last_changed, sizeof(last_updated));
   memcpy(&last_updated, &current_second_last_changed, sizeof(last_updated));
   monotime_coarse_get(&current_second_last_changed);
   monotime_coarse_get(&current_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? */
   /** How much clock jumping do we tolerate? */
 #define NUM_JUMPED_SECONDS_BEFORE_WARN 100
 #define NUM_JUMPED_SECONDS_BEFORE_WARN 100
 
 
@@ -2696,10 +2707,6 @@ update_current_time(time_t now)
     // moving back in time is always a bad sign.
     // moving back in time is always a bad sign.
     circuit_note_clock_jumped(seconds_elapsed, false);
     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) {
   } else if (seconds_elapsed >= NUM_JUMPED_SECONDS_BEFORE_WARN) {
     /* Compare the monotonic clock to the result of time(). */
     /* Compare the monotonic clock to the result of time(). */
     const int32_t monotime_msec_passed =
     const int32_t monotime_msec_passed =
@@ -2721,11 +2728,6 @@ update_current_time(time_t now)
     if (clock_jumped || seconds_elapsed >= NUM_IDLE_SECONDS_BEFORE_WARN) {
     if (clock_jumped || seconds_elapsed >= NUM_IDLE_SECONDS_BEFORE_WARN) {
       circuit_note_clock_jumped(seconds_elapsed, ! clock_jumped);
       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) {
   } else if (seconds_elapsed > 0) {
     stats_n_seconds_working += seconds_elapsed;
     stats_n_seconds_working += seconds_elapsed;
   }
   }
@@ -2818,12 +2820,6 @@ initialize_mainloop_events(void)
 int
 int
 do_main_loop(void)
 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
   /* initialize the periodic events first, so that code that depends on the
    * events being present does not assert.
    * events being present does not assert.
    */
    */

+ 59 - 0
src/core/mainloop/netstatus.c

@@ -10,6 +10,8 @@
 #include "app/config/config.h"
 #include "app/config/config.h"
 #include "feature/hibernate/hibernate.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:
 /** Return true iff our network is in some sense disabled or shutting down:
  * either we're hibernating, entering hibernation, or the network is turned
  * either we're hibernating, entering hibernation, or the network is turned
  * off with DisableNetwork. */
  * 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
  * The time at which we've last seen "user activity" -- that is, any activity
  * that should keep us as a participant on the network.
  * 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;
 static time_t last_user_activity_seen = 0;
 
 
@@ -99,3 +105,56 @@ is_participating_on_network(void)
 {
 {
   return participating_on_network;
   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.
+    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;
+  } else {
+    last_activity = now - 60 * state->MinutesSinceUserActivity;
+    participating_on_network = true;
+  }
+  reset_user_activity(last_activity);
+}
+
+/**
+ * Adjust the time at which the user was last active by <b>seconds_diff</b>
+ * 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);
+}

+ 4 - 0
src/core/mainloop/netstatus.h

@@ -17,4 +17,8 @@ time_t get_last_user_activity_time(void);
 void set_network_participation(bool participation);
 void set_network_participation(bool participation);
 bool is_participating_on_network(void);
 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
 #endif

+ 3 - 0
src/lib/intmath/cmp.h

@@ -36,4 +36,7 @@
     ((v) > (max)) ? (max) :                     \
     ((v) > (max)) ? (max) :                     \
     (v) )
     (v) )
 
 
+/** Give the absolute value of <b>x</b>, independent of its type. */
+#define ABS(x) ( ((x)<0) ? -(x) : (x) )
+
 #endif /* !defined(TOR_INTMATH_CMP_H) */
 #endif /* !defined(TOR_INTMATH_CMP_H) */

+ 76 - 0
src/test/test_mainloop.c

@@ -8,6 +8,7 @@
 
 
 #define CONFIG_PRIVATE
 #define CONFIG_PRIVATE
 #define MAINLOOP_PRIVATE
 #define MAINLOOP_PRIVATE
+#define STATEFILE_PRIVATE
 
 
 #include "test/test.h"
 #include "test/test.h"
 #include "test/log_test_helpers.h"
 #include "test/log_test_helpers.h"
@@ -20,6 +21,8 @@
 #include "feature/hs/hs_service.h"
 #include "feature/hs/hs_service.h"
 
 
 #include "app/config/config.h"
 #include "app/config/config.h"
+#include "app/config/statefile.h"
+#include "app/config/or_state_st.h"
 
 
 static const uint64_t BILLION = 1000000000;
 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(true, OP_EQ, is_participating_on_network());
   tt_int_op(schedule_rescan_called, OP_EQ, 1);
   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:
  done:
   UNMOCK(schedule_rescan_periodic_events);
   UNMOCK(schedule_rescan_periodic_events);
 }
 }
@@ -273,6 +283,70 @@ test_mainloop_check_participation(void *arg)
   UNMOCK(connection_get_by_type_nonlinked);
   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) \
 #define MAINLOOP_TEST(name) \
   { #name, test_mainloop_## name , TT_FORK, NULL, NULL }
   { #name, test_mainloop_## name , TT_FORK, NULL, NULL }
 
 
@@ -281,5 +355,7 @@ struct testcase_t mainloop_tests[] = {
   MAINLOOP_TEST(update_time_jumps),
   MAINLOOP_TEST(update_time_jumps),
   MAINLOOP_TEST(user_activity),
   MAINLOOP_TEST(user_activity),
   MAINLOOP_TEST(check_participation),
   MAINLOOP_TEST(check_participation),
+  MAINLOOP_TEST(dormant_load_state),
+  MAINLOOP_TEST(dormant_save_state),
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };