Browse Source

Merge branch 'ticket26063_squashed'

Nick Mathewson 6 years ago
parent
commit
1eede00a4b

+ 5 - 0
changes/ticket26063

@@ -0,0 +1,5 @@
+  o Major features (CPU usage, mobile):
+    - When Tor is disabled (via DisableNetwork or via hibernation), it
+      no longer needs to run any per-second events.  This change should
+      make it easier for mobile applications to disable Tor while the
+      device is sleeping, or Tor is not running.  Closes ticket 26063.

+ 30 - 1
src/common/compat_libevent.c

@@ -253,10 +253,39 @@ periodic_timer_new(struct event_base *base,
   }
   timer->cb = cb;
   timer->data = data;
-  event_add(timer->ev, (struct timeval *)tv); /*drop const for old libevent*/
+  periodic_timer_launch(timer, tv);
   return timer;
 }
 
+/**
+ * Launch the timer <b>timer</b> to run at <b>tv</b> from now, and every
+ * <b>tv</b> thereafter.
+ *
+ * If the timer is already enabled, this function does nothing.
+ */
+void
+periodic_timer_launch(periodic_timer_t *timer, const struct timeval *tv)
+{
+  tor_assert(timer);
+  if (event_pending(timer->ev, EV_TIMEOUT, NULL))
+    return;
+  event_add(timer->ev, tv);
+}
+
+/**
+ * Disable the provided <b>timer</b>, but do not free it.
+ *
+ * You can reenable the same timer later with periodic_timer_launch.
+ *
+ * If the timer is already disabled, this function does nothing.
+ */
+void
+periodic_timer_disable(periodic_timer_t *timer)
+{
+  tor_assert(timer);
+  event_del(timer->ev);
+}
+
 /** Stop and free a periodic timer */
 void
 periodic_timer_free_(periodic_timer_t *timer)

+ 2 - 0
src/common/compat_libevent.h

@@ -31,6 +31,8 @@ periodic_timer_t *periodic_timer_new(struct event_base *base,
              void (*cb)(periodic_timer_t *timer, void *data),
              void *data);
 void periodic_timer_free_(periodic_timer_t *);
+void periodic_timer_launch(periodic_timer_t *, const struct timeval *tv);
+void periodic_timer_disable(periodic_timer_t *);
 #define periodic_timer_free(t) \
   FREE_AND_NULL(periodic_timer_t, periodic_timer_free_, (t))
 

+ 6 - 3
src/or/config.c

@@ -1449,9 +1449,9 @@ options_act_reversible(const or_options_t *old_options, char **msg)
     consider_hibernation(time(NULL));
 
     /* Launch the listeners.  (We do this before we setuid, so we can bind to
-     * ports under 1024.)  We don't want to rebind if we're hibernating. If
-     * networking is disabled, this will close all but the control listeners,
-     * but disable those. */
+     * ports under 1024.)  We don't want to rebind if we're hibernating or
+     * shutting down. If networking is disabled, this will close all but the
+     * control listeners, but disable those. */
     if (!we_are_hibernating()) {
       if (retry_all_listeners(replaced_listeners, new_listeners,
                               options->DisableNetwork) < 0) {
@@ -2001,6 +2001,9 @@ 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 ||

+ 6 - 6
src/or/connection.c

@@ -1762,13 +1762,13 @@ connection_connect_sockaddr,(connection_t *conn,
   tor_assert(sa);
   tor_assert(socket_error);
 
-  if (get_options()->DisableNetwork) {
-    /* We should never even try to connect anyplace if DisableNetwork is set.
-     * Warn if we do, and refuse to make the connection.
+  if (net_is_completely_disabled()) {
+    /* We should never even try to connect anyplace if the network is
+     * completely shut off.
      *
-     * We only check DisableNetwork here, not we_are_hibernating(), since
-     * we'll still try to fulfill client requests sometimes in the latter case
-     * (if it is soft hibernation) */
+     * (We don't check net_is_disabled() here, since we still sometimes
+     * want to open connections when we're in soft hibernation.)
+     */
     static ratelim_t disablenet_violated = RATELIM_INIT(30*60);
     *socket_error = SOCK_ERRNO(ENETUNREACH);
     log_fn_ratelim(&disablenet_violated, LOG_WARN, LD_BUG,

+ 1 - 0
src/or/connection_edge.c

@@ -3537,6 +3537,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
   n_stream->base_.state = EXIT_CONN_STATE_RESOLVEFAILED;
   /* default to failed, change in dns_resolve if it turns out not to fail */
 
+  /* If we're hibernating or shutting down, we refuse to open new streams. */
   if (we_are_hibernating()) {
     relay_send_end_cell_from_edge(rh.stream_id, circ,
                                   END_STREAM_REASON_HIBERNATING, NULL);

+ 85 - 4
src/or/control.c

@@ -1,3 +1,4 @@
+
 /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
  * Copyright (c) 2007-2017, The Tor Project, Inc. */
 /* See LICENSE for licensing information */
@@ -112,6 +113,10 @@ static int disable_log_messages = 0;
 #define EVENT_IS_INTERESTING(e) \
   (!! (global_event_mask & EVENT_MASK_(e)))
 
+/** Macro: true if any event from the bitfield 'e' is interesting. */
+#define ANY_EVENT_IS_INTERESTING(e) \
+  EVENT_IS_INTERESTING(e)
+
 /** If we're using cookie-type authentication, how long should our cookies be?
  */
 #define AUTHENTICATION_COOKIE_LEN 32
@@ -219,6 +224,7 @@ static void set_cached_network_liveness(int liveness);
 static void flush_queued_events_cb(mainloop_event_t *event, void *arg);
 
 static char * download_status_to_string(const download_status_t *dl);
+static void control_get_bytes_rw_last_sec(uint64_t *r, uint64_t *w);
 
 /** Given a control event code for a message event, return the corresponding
  * log severity. */
@@ -271,6 +277,7 @@ control_update_global_event_mask(void)
   smartlist_t *conns = get_connection_array();
   event_mask_t old_mask, new_mask;
   old_mask = global_event_mask;
+  int any_old_per_sec_events = control_any_per_second_event_enabled();
 
   global_event_mask = 0;
   SMARTLIST_FOREACH(conns, connection_t *, _conn,
@@ -288,10 +295,13 @@ control_update_global_event_mask(void)
    * we want to hear...*/
   control_adjust_event_log_severity();
 
+  /* Macro: true if ev was false before and is true now. */
+#define NEWLY_ENABLED(ev) \
+  (! (old_mask & (ev)) && (new_mask & (ev)))
+
   /* ...then, if we've started logging stream or circ bw, clear the
    * appropriate fields. */
-  if (! (old_mask & EVENT_STREAM_BANDWIDTH_USED) &&
-      (new_mask & EVENT_STREAM_BANDWIDTH_USED)) {
+  if (NEWLY_ENABLED(EVENT_STREAM_BANDWIDTH_USED)) {
     SMARTLIST_FOREACH(conns, connection_t *, conn,
     {
       if (conn->type == CONN_TYPE_AP) {
@@ -300,10 +310,18 @@ control_update_global_event_mask(void)
       }
     });
   }
-  if (! (old_mask & EVENT_CIRC_BANDWIDTH_USED) &&
-      (new_mask & EVENT_CIRC_BANDWIDTH_USED)) {
+  if (NEWLY_ENABLED(EVENT_CIRC_BANDWIDTH_USED)) {
     clear_circ_bw_fields();
   }
+  if (NEWLY_ENABLED(EVENT_BANDWIDTH_USED)) {
+    uint64_t r, w;
+    control_get_bytes_rw_last_sec(&r, &w);
+  }
+  if (any_old_per_sec_events != control_any_per_second_event_enabled()) {
+    reschedule_per_second_timer();
+  }
+
+#undef NEWLY_ENABLED
 }
 
 /** Adjust the log severities that result in control_event_logmsg being called
@@ -352,6 +370,65 @@ control_event_is_interesting(int event)
   return EVENT_IS_INTERESTING(event);
 }
 
+/** Return true if any event that needs to fire once a second is enabled. */
+int
+control_any_per_second_event_enabled(void)
+{
+  return ANY_EVENT_IS_INTERESTING(
+      EVENT_BANDWIDTH_USED |
+      EVENT_CELL_STATS |
+      EVENT_CIRC_BANDWIDTH_USED |
+      EVENT_CONN_BW |
+      EVENT_STREAM_BANDWIDTH_USED
+  );
+}
+
+/* The value of 'get_bytes_read()' the previous time that
+ * control_get_bytes_rw_last_sec() as called. */
+static uint64_t stats_prev_n_read = 0;
+/* The value of 'get_bytes_written()' the previous time that
+ * control_get_bytes_rw_last_sec() as called. */
+static uint64_t stats_prev_n_written = 0;
+
+/**
+ * Set <b>n_read</b> and <b>n_written</b> to the total number of bytes read
+ * and written by Tor since the last call to this function.
+ *
+ * Call this only from the main thread.
+ */
+static void
+control_get_bytes_rw_last_sec(uint64_t *n_read,
+                              uint64_t *n_written)
+{
+  const uint64_t stats_n_bytes_read = get_bytes_read();
+  const uint64_t stats_n_bytes_written = get_bytes_written();
+
+  *n_read = stats_n_bytes_read - stats_prev_n_read;
+  *n_written = stats_n_bytes_written - stats_prev_n_written;
+  stats_prev_n_read = stats_n_bytes_read;
+  stats_prev_n_written = stats_n_bytes_written;
+}
+
+/**
+ * Run all the controller events (if any) that are scheduled to trigger once
+ * per second.
+ */
+void
+control_per_second_events(void)
+{
+  if (!control_any_per_second_event_enabled())
+    return;
+
+  uint64_t bytes_read, bytes_written;
+  control_get_bytes_rw_last_sec(&bytes_read, &bytes_written);
+  control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written);
+
+  control_event_stream_bandwidth_used();
+  control_event_conn_bandwidth_used();
+  control_event_circ_bandwidth_used();
+  control_event_circuit_cell_stats();
+}
+
 /** Append a NUL-terminated string <b>s</b> to the end of
  * <b>conn</b>-\>outbuf.
  */
@@ -7035,6 +7112,8 @@ control_event_bootstrap_problem(const char *warn, const char *reason,
   if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD)
     dowarn = 1;
 
+  /* Don't warn about our bootstrapping status if we are hibernating or
+   * shutting down. */
   if (we_are_hibernating())
     dowarn = 0;
 
@@ -7606,6 +7685,8 @@ control_free_all(void)
 {
   smartlist_t *queued_events = NULL;
 
+  stats_prev_n_read = stats_prev_n_written = 0;
+
   if (authentication_cookie) /* Free the auth cookie */
     tor_free(authentication_cookie);
   if (detached_onion_services) { /* Free the detached onion services */

+ 3 - 0
src/or/control.h

@@ -40,6 +40,9 @@ int connection_control_process_inbuf(control_connection_t *conn);
 #define EVENT_NS 0x000F
 int control_event_is_interesting(int event);
 
+void control_per_second_events(void);
+int control_any_per_second_event_enabled(void);
+
 int control_event_circuit_status(origin_circuit_t *circ,
                                  circuit_status_event_t e, int reason);
 int control_event_circuit_purpose_changed(origin_circuit_t *circ,

+ 1 - 1
src/or/dirserv.c

@@ -955,7 +955,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now)
   tor_assert(node);
 
   if (router_is_me(router)) {
-    /* We always know if we are down ourselves. */
+    /* We always know if we are shutting down or hibernating ourselves. */
     answer = ! we_are_hibernating();
   } else if (router->is_hibernating &&
              (router->cache_info.published_on +

+ 16 - 1
src/or/hibernate.c

@@ -883,13 +883,26 @@ hibernate_begin_shutdown(void)
   hibernate_begin(HIBERNATE_STATE_EXITING, time(NULL));
 }
 
-/** Return true iff we are currently hibernating. */
+/**
+ * Return true iff we are currently hibernating -- that is, if we are in
+ * any non-live state.
+ */
 MOCK_IMPL(int,
 we_are_hibernating,(void))
 {
   return hibernate_state != HIBERNATE_STATE_LIVE;
 }
 
+/**
+ * Return true iff we are currently _fully_ hibernating -- that is, if we are
+ * in a state where we expect to handle no network activity at all.
+ */
+MOCK_IMPL(int,
+we_are_fully_hibernating,(void))
+{
+  return hibernate_state == HIBERNATE_STATE_DORMANT;
+}
+
 /** If we aren't currently dormant, close all connections and become
  * dormant. */
 static void
@@ -1187,6 +1200,8 @@ on_hibernate_state_change(hibernate_state_t prev_state)
   if (prev_state != HIBERNATE_STATE_INITIAL) {
     rescan_periodic_events(get_options());
   }
+
+  reschedule_per_second_timer();
 }
 
 /** Free all resources held by the accounting module */

+ 1 - 0
src/or/hibernate.h

@@ -25,6 +25,7 @@ void accounting_add_bytes(size_t n_read, size_t n_written, int seconds);
 int accounting_record_bandwidth_usage(time_t now, or_state_t *state);
 void hibernate_begin_shutdown(void);
 MOCK_DECL(int, we_are_hibernating, (void));
+MOCK_DECL(int, we_are_fully_hibernating,(void));
 void consider_hibernation(time_t now);
 int getinfo_helper_accounting(control_connection_t *conn,
                               const char *question, char **answer,

+ 35 - 32
src/or/main.c

@@ -163,11 +163,6 @@ token_bucket_rw_t global_bucket;
 /* Token bucket for relayed traffic. */
 token_bucket_rw_t global_relayed_bucket;
 
-/* DOCDOC stats_prev_n_read */
-static uint64_t stats_prev_n_read = 0;
-/* DOCDOC stats_prev_n_written */
-static uint64_t stats_prev_n_written = 0;
-
 /* XXX we might want to keep stats about global_relayed_*_bucket too. Or not.*/
 /** How many bytes have we read since we started the process? */
 static uint64_t stats_n_bytes_read = 0;
@@ -1258,7 +1253,8 @@ run_connection_housekeeping(int i, time_t now)
   } else if (we_are_hibernating() &&
              ! have_any_circuits &&
              !connection_get_outbuf_len(conn)) {
-    /* We're hibernating, there's no circuits, and nothing to flush.*/
+    /* We're hibernating or shutting down, there's no circuits, and nothing to
+     * flush.*/
     log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) "
              "[Hibernating or exiting].",
              (int)conn->s,conn->address, conn->port);
@@ -2495,6 +2491,36 @@ hs_service_callback(time_t now, const or_options_t *options)
 
 /** Timer: used to invoke second_elapsed_callback() once per second. */
 static periodic_timer_t *second_timer = NULL;
+
+/**
+ * Enable or disable the per-second timer as appropriate, creating it if
+ * necessary.
+ */
+void
+reschedule_per_second_timer(void)
+{
+  struct timeval one_second;
+  one_second.tv_sec = 1;
+  one_second.tv_usec = 0;
+
+  if (! second_timer) {
+    second_timer = periodic_timer_new(tor_libevent_get_base(),
+                                      &one_second,
+                                      second_elapsed_callback,
+                                      NULL);
+    tor_assert(second_timer);
+  }
+
+  const bool run_per_second_events =
+    control_any_per_second_event_enabled() || ! net_is_completely_disabled();
+
+  if (run_per_second_events) {
+    periodic_timer_launch(second_timer, &one_second);
+  } else {
+    periodic_timer_disable(second_timer);
+  }
+}
+
 /** Last time that update_current_time was called. */
 static time_t current_second = 0;
 /** Last time that update_current_time updated current_second. */
@@ -2568,8 +2594,6 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg)
    * could use Libevent's timers for this rather than checking the current
    * time against a bunch of timeouts every second. */
   time_t now;
-  size_t bytes_written;
-  size_t bytes_read;
   (void)timer;
   (void)arg;
 
@@ -2581,18 +2605,8 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg)
    */
   update_current_time(now);
 
-  /* the second has rolled over. check more stuff. */
-  // remove this once it's unneeded
-  bytes_read = (size_t)(stats_n_bytes_read - stats_prev_n_read);
-  bytes_written = (size_t)(stats_n_bytes_written - stats_prev_n_written);
-  stats_prev_n_read = stats_n_bytes_read;
-  stats_prev_n_written = stats_n_bytes_written;
-
-  control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written);
-  control_event_stream_bandwidth_used();
-  control_event_conn_bandwidth_used();
-  control_event_circ_bandwidth_used();
-  control_event_circuit_cell_stats();
+  /* Maybe some controller events are ready to fire */
+  control_per_second_events();
 
   run_scheduled_events(now);
 }
@@ -2872,17 +2886,7 @@ do_main_loop(void)
   }
 
   /* set up once-a-second callback. */
-  if (! second_timer) {
-    struct timeval one_second;
-    one_second.tv_sec = 1;
-    one_second.tv_usec = 0;
-
-    second_timer = periodic_timer_new(tor_libevent_get_base(),
-                                      &one_second,
-                                      second_elapsed_callback,
-                                      NULL);
-    tor_assert(second_timer);
-  }
+  reschedule_per_second_timer();
 
 #ifdef HAVE_SYSTEMD_209
   uint64_t watchdog_delay;
@@ -3697,7 +3701,6 @@ tor_free_all(int postfork)
 
   memset(&global_bucket, 0, sizeof(global_bucket));
   memset(&global_relayed_bucket, 0, sizeof(global_relayed_bucket));
-  stats_prev_n_read = stats_prev_n_written = 0;
   stats_n_bytes_read = stats_n_bytes_written = 0;
   time_of_process_start = 0;
   time_of_last_signewnym = 0;

+ 1 - 0
src/or/main.h

@@ -94,6 +94,7 @@ uint64_t get_main_loop_error_count(void);
 uint64_t get_main_loop_idle_count(void);
 
 void periodic_events_on_new_options(const or_options_t *options);
+void reschedule_per_second_timer(void);
 
 extern time_t time_of_process_start;
 extern int quiet_level;

+ 15 - 3
src/or/router.c

@@ -1599,15 +1599,24 @@ router_perform_bandwidth_test(int num_circs, time_t now)
   }
 }
 
-/** Return true iff our network is in some sense disabled: either we're
- * hibernating, entering hibernation, or the network is turned off with
- * DisableNetwork. */
+/** 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. */
 int
 net_is_disabled(void)
 {
   return get_options()->DisableNetwork || we_are_hibernating();
 }
 
+/** Return true iff our network is in some sense "completely disabled" either
+ * we're fully hibernating or the network is turned off with
+ * DisableNetwork. */
+int
+net_is_completely_disabled(void)
+{
+  return get_options()->DisableNetwork || we_are_fully_hibernating();
+}
+
 /** Return true iff we believe ourselves to be an authoritative
  * directory server.
  */
@@ -2268,6 +2277,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
   /* and compute ri->bandwidthburst similarly */
   ri->bandwidthburst = get_effective_bwburst(options);
 
+  /* Report bandwidth, unless we're hibernating or shutting down */
   ri->bandwidthcapacity = hibernating ? 0 : rep_hist_bandwidth_assess();
 
   if (dns_seems_to_be_broken() || has_dns_init_failed()) {
@@ -2538,6 +2548,8 @@ check_descriptor_bandwidth_changed(time_t now)
     return;
 
   prev = router_get_my_routerinfo()->bandwidthcapacity;
+  /* Consider ourselves to have zero bandwidth if we're hibernating or
+   * shutting down. */
   cur = we_are_hibernating() ? 0 : rep_hist_bandwidth_assess();
   if ((prev != cur && (!prev || !cur)) ||
       cur > prev*2 ||

+ 1 - 0
src/or/router.h

@@ -53,6 +53,7 @@ void router_dirport_found_reachable(void);
 void router_perform_bandwidth_test(int num_circs, time_t now);
 
 int net_is_disabled(void);
+int net_is_completely_disabled(void);
 
 int authdir_mode(const or_options_t *options);
 int authdir_mode_handles_descs(const or_options_t *options, int purpose);