aboutsummaryrefslogtreecommitdiff
path: root/src/test/test_mainloop.c
blob: c4e60d9da570c84add0d18dcbe8bc89ee2ce8e87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/* Copyright (c) 2018-2020, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file test_mainloop.c
 * \brief Tests for functions closely related to the Tor main loop
 */

#define CONFIG_PRIVATE
#define MAINLOOP_PRIVATE
#define STATEFILE_PRIVATE

#include "test/test.h"
#include "test/log_test_helpers.h"

#include "lib/confmgt/confmgt.h"

#include "core/or/or.h"
#include "core/mainloop/connection.h"
#include "core/mainloop/mainloop.h"
#include "core/mainloop/mainloop_state_st.h"
#include "core/mainloop/mainloop_sys.h"
#include "core/mainloop/netstatus.h"

#include "feature/hs/hs_service.h"

#include "app/config/config.h"
#include "app/config/statefile.h"
#include "app/config/or_state_st.h"

#include "app/main/subsysmgr.h"

static const uint64_t BILLION = 1000000000;

static void
test_mainloop_update_time_normal(void *arg)
{
  (void)arg;

  monotime_enable_test_mocking();
  /* This is arbitrary */
  uint64_t mt_now = UINT64_C(7493289274986);
  /* This time is in the past as of when this test was written. */
  time_t now = 1525272090;
  monotime_coarse_set_mock_time_nsec(mt_now);
  reset_uptime();
  update_current_time(now);
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 0);

  update_current_time(now); // Same time as before is a no-op.
  tt_int_op(get_uptime(), OP_EQ, 0);

  now += 1;
  mt_now += BILLION;
  monotime_coarse_set_mock_time_nsec(mt_now);
  update_current_time(now);
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 1);

  now += 2; // two-second jump is unremarkable.
  mt_now += 2*BILLION;
  update_current_time(now);
  monotime_coarse_set_mock_time_nsec(mt_now);
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 3);

  now -= 1; // a one-second hop backwards is also unremarkable.
  update_current_time(now);
  tt_int_op(approx_time(), OP_EQ, now); // it changes the approx time...
  tt_int_op(get_uptime(), OP_EQ, 3); // but it doesn't roll back our uptime

 done:
  monotime_disable_test_mocking();
}

static void
test_mainloop_update_time_jumps(void *arg)
{
  (void)arg;

  monotime_enable_test_mocking();
  /* This is arbitrary */
  uint64_t mt_now = UINT64_C(7493289274986);
  /* This time is in the past as of when this test was written. */
  time_t now = 220897152;
  monotime_coarse_set_mock_time_nsec(mt_now);
  reset_uptime();
  update_current_time(now);
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 0);

  /* Put some uptime on the clock.. */
  now += 3;
  mt_now += 3*BILLION;
  monotime_coarse_set_mock_time_nsec(mt_now);
  update_current_time(now);
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 3);

  /* Now try jumping forward and backward, without updating the monotonic
   * clock.  */
  setup_capture_of_logs(LOG_NOTICE);
  now += 1800;
  update_current_time(now);
  expect_single_log_msg_containing(
               "Your system clock just jumped 1800 seconds forward");
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 3); // no uptime change.
  mock_clean_saved_logs();

  now -= 600;
  update_current_time(now);
  expect_single_log_msg_containing(
               "Your system clock just jumped 600 seconds backward");
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 3); // no uptime change.
  mock_clean_saved_logs();

  /* uptime tracking should go normally now if the clock moves sensibly. */
  now += 2;
  mt_now += 2*BILLION;
  update_current_time(now);
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 5);

  /* If we skip forward by a few minutes but the monotonic clock agrees,
   * we've just been idle: that counts as not worth warning about. */
  now += 1800;
  mt_now += 1800*BILLION;
  monotime_coarse_set_mock_time_nsec(mt_now);
  update_current_time(now);
  expect_no_log_entry();
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 5); // this doesn't count to uptime, though.

  /* If we skip forward by a long time, even if the clock agrees, it's
   * idnless that counts. */
  now += 4000;
  mt_now += 4000*BILLION;
  monotime_coarse_set_mock_time_nsec(mt_now);
  update_current_time(now);
  expect_single_log_msg_containing("Tor has been idle for 4000 seconds");
  tt_int_op(approx_time(), OP_EQ, now);
  tt_int_op(get_uptime(), OP_EQ, 5);

 done:
  teardown_capture_of_logs();
  monotime_disable_test_mocking();
}

static int schedule_rescan_called = 0;
static void
mock_schedule_rescan_periodic_events(void)
{
  ++schedule_rescan_called;
}

static void
test_mainloop_user_activity(void *arg)
{
  (void)arg;
  const time_t start = 1542658829;
  update_approx_time(start);

  MOCK(schedule_rescan_periodic_events, mock_schedule_rescan_periodic_events);

  reset_user_activity(start);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start);

  set_network_participation(false);

  // reset can move backwards and forwards, but does not change network
  // participation.
  reset_user_activity(start-10);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start-10);
  reset_user_activity(start+10);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start+10);

  tt_int_op(schedule_rescan_called, OP_EQ, 0);
  tt_int_op(false, OP_EQ, is_participating_on_network());

  // "note" can only move forward.  Calling it from a non-participating
  // state makes us rescan the periodic callbacks and set participation.
  note_user_activity(start+20);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start+20);
  tt_int_op(true, OP_EQ, is_participating_on_network());
  tt_int_op(schedule_rescan_called, OP_EQ, 1);

  // Calling it again will move us forward, but not call rescan again.
  note_user_activity(start+25);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start+25);
  tt_int_op(true, OP_EQ, is_participating_on_network());
  tt_int_op(schedule_rescan_called, OP_EQ, 1);

  // We won't move backwards.
  note_user_activity(start+20);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start+25);
  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);
}

static unsigned int
mock_get_num_services(void)
{
  return 1;
}

static connection_t *
mock_connection_gbtu(int type)
{
  (void) type;
  return (void *)"hello fellow connections";
}

static void
test_mainloop_check_participation(void *arg)
{
  (void)arg;
  or_options_t *options = options_new();
  const time_t start = 1542658829;
  const time_t ONE_DAY = 24*60*60;

  // Suppose we've been idle for a day or two
  reset_user_activity(start - 2*ONE_DAY);
  set_network_participation(true);
  check_network_participation_callback(start, options);
  tt_int_op(is_participating_on_network(), OP_EQ, false);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start-2*ONE_DAY);

  // suppose we've been idle for 2 days... but we are a server.
  reset_user_activity(start - 2*ONE_DAY);
  options->ORPort_set = 1;
  set_network_participation(true);
  check_network_participation_callback(start+2, options);
  tt_int_op(is_participating_on_network(), OP_EQ, true);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start+2);
  options->ORPort_set = 0;

  // idle for 2 days, but we have a hidden service.
  reset_user_activity(start - 2*ONE_DAY);
  set_network_participation(true);
  MOCK(hs_service_get_num_services, mock_get_num_services);
  check_network_participation_callback(start+3, options);
  tt_int_op(is_participating_on_network(), OP_EQ, true);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start+3);
  UNMOCK(hs_service_get_num_services);

  // idle for 2 days but we have at least one user connection
  MOCK(connection_get_by_type_nonlinked, mock_connection_gbtu);
  reset_user_activity(start - 2*ONE_DAY);
  set_network_participation(true);
  options->DormantTimeoutDisabledByIdleStreams = 1;
  check_network_participation_callback(start+10, options);
  tt_int_op(is_participating_on_network(), OP_EQ, true);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start+10);

  // as above, but DormantTimeoutDisabledByIdleStreams is not set
  reset_user_activity(start - 2*ONE_DAY);
  set_network_participation(true);
  options->DormantTimeoutDisabledByIdleStreams = 0;
  check_network_participation_callback(start+13, options);
  tt_int_op(is_participating_on_network(), OP_EQ, false);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start-2*ONE_DAY);
  UNMOCK(connection_get_by_type_nonlinked);
  options->DormantTimeoutDisabledByIdleStreams = 1;

  // idle for 2 days but DormantClientTimeout is 3 days
  reset_user_activity(start - 2*ONE_DAY);
  set_network_participation(true);
  options->DormantClientTimeout = ONE_DAY * 3;
  check_network_participation_callback(start+30, options);
  tt_int_op(is_participating_on_network(), OP_EQ, true);
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start-2*ONE_DAY);

 done:
  or_options_free(options);
  UNMOCK(hs_service_get_num_services);
  UNMOCK(connection_get_by_type_nonlinked);
}

static void
test_mainloop_dormant_load_state(void *arg)
{
  (void)arg;
  or_state_t *or_state = or_state_new();
  mainloop_state_t *state;
  {
    int idx = subsystems_get_state_idx(&sys_mainloop);
    tor_assert(idx >= 0);
    state = config_mgr_get_obj_mutable(get_state_mgr(), or_state, idx);
  }
  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);

  // If we would start dormant, but DormantCanceledByStartup is set, then
  // we start up non-dormant.
  state->Dormant = 1;
  get_options_mutable()->DormantCanceledByStartup = 1;
  netstatus_load_from_state(state, start);
  tt_assert(is_participating_on_network());
  tt_i64_op(get_last_user_activity_time(), OP_EQ, start);

 done:
  or_state_free(or_state);
}

static void
test_mainloop_dormant_save_state(void *arg)
{
  (void)arg;
  mainloop_state_t *state = tor_malloc_zero(sizeof(mainloop_state_t));
  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:
  tor_free(state);
}

#define MAINLOOP_TEST(name) \
  { #name, test_mainloop_## name , TT_FORK, NULL, NULL }

struct testcase_t mainloop_tests[] = {
  MAINLOOP_TEST(update_time_normal),
  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
};