aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2018-11-26 16:33:31 -0500
committerNick Mathewson <nickm@torproject.org>2018-11-26 16:33:31 -0500
commit7d8e0cc9abf2a74789e635d2fd4d0e18b8e0d1fe (patch)
tree0ac25bf4798d16765392039a43db7b878a46b293 /src/core
parent2b9a907bdccc588203d2110866d4e7bb72a25773 (diff)
parent02843c4a4e2fab9c5d9cdb95c425c37ff3d1a4ae (diff)
downloadtor-7d8e0cc9abf2a74789e635d2fd4d0e18b8e0d1fe.tar.gz
tor-7d8e0cc9abf2a74789e635d2fd4d0e18b8e0d1fe.zip
Merge branch 'dormant_v2_squashed'
Diffstat (limited to 'src/core')
-rw-r--r--src/core/mainloop/connection.c13
-rw-r--r--src/core/mainloop/connection.h1
-rw-r--r--src/core/mainloop/mainloop.c319
-rw-r--r--src/core/mainloop/mainloop.h7
-rw-r--r--src/core/mainloop/netstatus.c73
-rw-r--r--src/core/mainloop/netstatus.h7
-rw-r--r--src/core/mainloop/periodic.c22
-rw-r--r--src/core/mainloop/periodic.h15
-rw-r--r--src/core/or/connection_edge.c11
-rw-r--r--src/core/or/or.h2
10 files changed, 349 insertions, 121 deletions
diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c
index 1198a01ad9..c1c7c3678b 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;
@@ -4426,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 <b>type</b> that is not an internally linked
+ * connection, and is not marked for close.
+ **/
+MOCK_IMPL(connection_t *,
+connection_get_by_type_nonlinked,(int type))
+{
+ CONN_GET_TEMPLATE(conn, conn->type == type && !conn->linked);
+}
+
/** Return a connection of type <b>type</b> that has rendquery equal
* to <b>rendquery</b>, 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..07b8df4138 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);
+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 7eff82fee4..42df1038a8 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;
@@ -1366,78 +1365,91 @@ CALLBACK(save_stability);
CALLBACK(save_state);
CALLBACK(write_bridge_ns);
CALLBACK(write_stats_file);
+CALLBACK(control_per_second_events);
+CALLBACK(second_elapsed);
#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),
+ /* 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. */
+ CALLBACK(second_elapsed, NET_PARTICIPANT,
+ 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. */
+ 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),
+ 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. */
+ 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, 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)), // XXXX break this down more
/* 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),
+ /* XXXX this could be restricted to CLIENT+NET_PARTICIPANT */
+ CALLBACK(rend_cache_failure_clean, NET_PARTICIPANT, FL(RUN_ON_DISABLE)),
/* 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),
+
+ /* Controller with per-second events only. */
+ CALLBACK(control_per_second_events, CONTROLEV, 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
@@ -1485,7 +1497,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);
@@ -1493,6 +1505,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. */
@@ -1500,6 +1514,9 @@ get_my_roles(const or_options_t *options)
options->ControlPort_set ||
options->OwningControllerFD != UINT64_MAX;
+ 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;
if (is_relay) roles |= PERIODIC_EVENT_ROLE_RELAY;
@@ -1507,6 +1524,8 @@ 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;
+ if (sending_control_events) roles |= PERIODIC_EVENT_ROLE_CONTROLEV;
return roles;
}
@@ -1574,6 +1593,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.
+ **/
+MOCK_IMPL(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
@@ -1608,7 +1651,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_RUN_ON_DISABLE) {
+ periodic_event_schedule_and_disable(item);
+ } else {
+ periodic_event_disable(item);
+ }
}
}
}
@@ -1683,6 +1730,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 <b>delay_sec</b> 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 <b>now</b> and <b>next</b>,
* clipped to the range [1 second, LONGEST_TIMER_PERIOD]. */
@@ -1707,16 +1778,16 @@ 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)
+static int
+second_elapsed_callback(time_t now, const or_options_t *options)
{
- const or_options_t *options = get_options();
-
- /* 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
+ *
+ * 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.
*/
consider_hibernation(now);
@@ -1726,10 +1797,13 @@ run_scheduled_events(time_t now)
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
+ // Also, schedule this rather than probing 1x / sec
fetch_bridge_descriptors(options, now);
}
if (accounting_is_enabled(options)) {
+ // TODO: refactor or rewrite?
accounting_run_housekeeping(now);
}
@@ -1740,6 +1814,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, RUN_ON_DISABLE
circuit_expire_building();
circuit_expire_waiting_for_better_guard();
@@ -1776,6 +1851,9 @@ run_scheduled_events(time_t now)
/* 11b. check pending unconfigured managed proxies */
if (!net_is_disabled() && pt_proxies_configuration_pending())
pt_configure_remaining_proxies();
+
+ /* Run again in a second. */
+ return 1;
}
/* Periodic callback: rotate the onion keys after the period defined by the
@@ -1922,6 +2000,55 @@ 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;
+ }
+
+ /* If we have any currently open entry streams other than "linked"
+ * connections used for directory requests, those count as user 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 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);
+ 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.
@@ -2497,36 +2624,19 @@ hs_service_callback(time_t now, const or_options_t *options)
return 1;
}
-/** 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.
+/*
+ * Periodic callback: Send once-per-second events to the controller(s).
+ * This is called every second.
*/
-void
-reschedule_per_second_timer(void)
+static int
+control_per_second_events_callback(time_t now, const or_options_t *options)
{
- 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);
- }
+ (void) options;
+ (void) now;
- const bool run_per_second_events =
- control_any_per_second_event_enabled() || ! net_is_completely_disabled();
+ control_per_second_events();
- if (run_per_second_events) {
- periodic_timer_launch(second_timer, &one_second);
- } else {
- periodic_timer_disable(second_timer);
- }
+ return 1;
}
/** Last time that update_current_time was called. */
@@ -2565,6 +2675,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 =
@@ -2586,6 +2701,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;
}
@@ -2594,31 +2714,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);
-
- /* Maybe some controller events are ready to fire */
- control_per_second_events();
-
- run_scheduled_events(now);
-}
-
#ifdef HAVE_SYSTEMD_209
static periodic_timer_t *systemd_watchdog_timer = NULL;
@@ -2703,15 +2798,18 @@ 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.
*/
initialize_periodic_events();
initialize_mainloop_events();
- /* set up once-a-second callback. */
- reschedule_per_second_timer();
-
#ifdef HAVE_SYSTEMD_209
uint64_t watchdog_delay;
/* set up systemd watchdog notification. */
@@ -2896,7 +2994,6 @@ tor_mainloop_free_all(void)
smartlist_free(connection_array);
smartlist_free(closeable_connection_lst);
smartlist_free(active_linked_connection_lst);
- periodic_timer_free(second_timer);
teardown_periodic_events();
tor_event_free(shutdown_did_not_work_event);
tor_event_free(initialize_periodic_events_event);
@@ -2904,6 +3001,8 @@ 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);
+ 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 632733d9a6..14e80ebb21 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);
+MOCK_DECL(void, schedule_rescan_periodic_events,(void));
void update_current_time(time_t now);
@@ -81,11 +82,12 @@ 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);
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);
@@ -102,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/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 <b>now</b>.
+ *
+ * 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/mainloop/periodic.c b/src/core/mainloop/periodic.c
index c1785eb38f..9f9b178e43 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_schedule_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..05ba4297f3 100644
--- a/src/core/mainloop/periodic.h
+++ b/src/core/mainloop/periodic.h
@@ -15,6 +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 << 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. */
@@ -25,10 +29,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.
@@ -39,6 +39,11 @@
* the net_is_disabled() check. */
#define PERIODIC_EVENT_FLAG_NEED_NET (1U << 0)
+/* Indicate that if the event is enabled, it needs to be run once before
+ * it becomes disabled.
+ */
+#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
* PERIODIC_EVENT_NO_UPDATE to not update <b>last_action_time</b> 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_schedule_and_disable(periodic_event_item_t *event);
#endif /* !defined(TOR_PERIODIC_H) */
-
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/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