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.
     (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
 --------------

+ 1 - 0
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"),

+ 4 - 0
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

+ 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'. */
   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

+ 8 - 0
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);
 

+ 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));
   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? */
 #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.
     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 =
@@ -2721,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;
   }
@@ -2818,12 +2820,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.
    */

+ 59 - 0
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,56 @@ 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.
+    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);
 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

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

@@ -36,4 +36,7 @@
     ((v) > (max)) ? (max) :                     \
     (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) */

+ 76 - 0
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
 };