aboutsummaryrefslogtreecommitdiff
path: root/src/test/test_periodic_event.c
blob: b7f1785805961843bb62acd6517632ad1061ea7a (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
/* Copyright (c) 2018-2020, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file test_periodic_event.c
 * \brief Test the periodic events that Tor uses for different roles. They are
 *        part of the libevent mainloop
 */

#define CONFIG_PRIVATE
#define HS_SERVICE_PRIVATE
#define MAINLOOP_PRIVATE

#include "test/test.h"
#include "test/test_helpers.h"

#include "core/or/or.h"
#include "app/config/config.h"
#include "feature/hibernate/hibernate.h"
#include "feature/hs/hs_service.h"
#include "core/mainloop/mainloop.h"
#include "core/mainloop/netstatus.h"
#include "core/mainloop/periodic.h"

/** Helper function: This is replaced in some tests for the event callbacks so
 * we don't actually go into the code path of those callbacks. */
static int
dumb_event_fn(time_t now, const or_options_t *options)
{
  (void) now;
  (void) options;

  /* Will get rescheduled in 300 seconds. It just can't be 0. */
  return 300;
}

static void
register_dummy_hidden_service(hs_service_t *service)
{
  memset(service, 0, sizeof(hs_service_t));
  memset(&service->keys.identity_pk, 'A', sizeof(service->keys.identity_pk));
  (void) register_service(get_hs_service_map(), service);
}

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

  /* Initialize the events but the callback won't get called since we would
   * need to run the main loop and then wait for a second delaying the unit
   * tests. Instead, we'll test the callback work indepedently elsewhere. */
  initialize_periodic_events();
  periodic_events_connect_all();
  set_network_participation(false);
  rescan_periodic_events(get_options());

  /* Validate that all events have been set up. */
  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    tt_assert(item->ev);
    tt_assert(item->fn);
    tt_u64_op(item->last_action_time, OP_EQ, 0);
    /* Every event must have role(s) assign to it. This is done statically. */
    tt_u64_op(item->roles, OP_NE, 0);
    int should_be_enabled = (item->roles & PERIODIC_EVENT_ROLE_ALL) &&
      !(item->flags & PERIODIC_EVENT_FLAG_NEED_NET);
    tt_uint_op(periodic_event_is_enabled(item), OP_EQ, should_be_enabled);
  }

 done:
  teardown_periodic_events();
}

static void
test_pe_launch(void *arg)
{
  hs_service_t service, *to_remove = NULL;
  or_options_t *options;

  (void) arg;

  hs_init();
  /* We need to put tor in hibernation live state so the events requiring
   * network gets enabled. */
  consider_hibernation(time(NULL));

  set_network_participation(true);

  /* Hack: We'll set a dumb fn() of each events so they don't get called when
   * dispatching them. We just want to test the state of the callbacks, not
   * the whole code path. */
  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    item->fn = dumb_event_fn;
  }

  options = get_options_mutable();
  options->SocksPort_set = 1;
  periodic_events_on_new_options(options);

#if 0
  /* Lets make sure that before intialization, we can't scan the periodic
   * events list and launch them. Lets try by being a Client. */
  /* XXXX We make sure these events are initialized now way earlier than we
   * did before. */
  for (int i = 0; periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &periodic_events[i];
    tt_int_op(periodic_event_is_enabled(item), OP_EQ, 0);
  }
#endif /* 0 */

  initialize_periodic_events();
  periodic_events_connect_all();

  /* Now that we've initialized, rescan the list to launch. */
  periodic_events_on_new_options(options);

  int mask = PERIODIC_EVENT_ROLE_CLIENT|PERIODIC_EVENT_ROLE_ALL|
    PERIODIC_EVENT_ROLE_NET_PARTICIPANT;
  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    int should_be_enabled = !!(item->roles & mask);
    tt_int_op(periodic_event_is_enabled(item), OP_EQ, should_be_enabled);
    // enabled or not, the event has not yet been run.
    tt_u64_op(item->last_action_time, OP_EQ, 0);
  }

  /* Remove Client but become a Relay. */
  options->SocksPort_set = 0;
  options->ORPort_set = 1;
  periodic_events_on_new_options(options);

  unsigned roles = get_my_roles(options);
  tt_uint_op(roles, OP_EQ,
             PERIODIC_EVENT_ROLE_RELAY|PERIODIC_EVENT_ROLE_DIRSERVER|
             PERIODIC_EVENT_ROLE_ALL|PERIODIC_EVENT_ROLE_NET_PARTICIPANT);

  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    /* Only Client role should be disabled. */
    if (item->roles == PERIODIC_EVENT_ROLE_CLIENT) {
      tt_int_op(periodic_event_is_enabled(item), OP_EQ, 0);
    }
    if (item->roles & PERIODIC_EVENT_ROLE_RELAY) {
      tt_int_op(periodic_event_is_enabled(item), OP_EQ, 1);
    }
    /* Non Relay role should be disabled, except for Dirserver. */
    if (!(item->roles & roles)) {
      tt_int_op(periodic_event_is_enabled(item), OP_EQ, 0);
    }
  }

  /* Disable everything and we'll enable them ALL. */
  options->SocksPort_set = 0;
  options->ORPort_set = 0;
  options->DisableNetwork = 1;
  set_network_participation(false);
  periodic_events_on_new_options(options);

  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    int should_be_enabled = (item->roles & PERIODIC_EVENT_ROLE_ALL) &&
      !(item->flags & PERIODIC_EVENT_FLAG_NEED_NET);
    tt_int_op(periodic_event_is_enabled(item), OP_EQ, should_be_enabled);
  }

  /* Enable everything. */
  options->SocksPort_set = 1; options->ORPort_set = 1;
  options->BridgeRelay = 1; options->AuthoritativeDir = 1;
  options->V3AuthoritativeDir = 1; options->BridgeAuthoritativeDir = 1;
  options->DisableNetwork = 0;
  set_network_participation(true);
  register_dummy_hidden_service(&service);
  periodic_events_on_new_options(options);
  /* Note down the reference because we need to remove this service from the
   * global list before the hs_free_all() call so it doesn't try to free
   * memory on the stack. Furthermore, we can't remove it now else it will
   * trigger a rescan of the event disabling the HS service event. */
  to_remove = &service;

  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    tt_int_op(periodic_event_is_enabled(item), OP_EQ,
              (item->roles != PERIODIC_EVENT_ROLE_CONTROLEV));
  }

 done:
  if (to_remove) {
    remove_service(get_hs_service_map(), to_remove);
  }
  hs_free_all();
}

static void
test_pe_get_roles(void *arg)
{
  int roles;

  (void) arg;

  /* Just so the HS global map exists. */
  hs_init();

  or_options_t *options = get_options_mutable();
  tt_assert(options);
  set_network_participation(true);

  const int ALL = PERIODIC_EVENT_ROLE_ALL |
    PERIODIC_EVENT_ROLE_NET_PARTICIPANT;

  /* Nothing configured, should be no roles. */
  tt_assert(net_is_disabled());
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ, ALL);

  /* Indicate we have a SocksPort, roles should be come Client. */
  options->SocksPort_set = 1;
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ, PERIODIC_EVENT_ROLE_CLIENT|ALL);

  /* Now, we'll add a ORPort so should now be a Relay + Client. */
  options->ORPort_set = 1;
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ,
            (PERIODIC_EVENT_ROLE_CLIENT | PERIODIC_EVENT_ROLE_RELAY |
             PERIODIC_EVENT_ROLE_DIRSERVER | ALL));

  /* Now add a Bridge. */
  options->BridgeRelay = 1;
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ,
            (PERIODIC_EVENT_ROLE_CLIENT | PERIODIC_EVENT_ROLE_RELAY |
             PERIODIC_EVENT_ROLE_BRIDGE | PERIODIC_EVENT_ROLE_DIRSERVER |
             ALL));
  tt_assert(roles & PERIODIC_EVENT_ROLE_ROUTER);
  /* Unset client so we can solely test Router role. */
  options->SocksPort_set = 0;
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ,
            PERIODIC_EVENT_ROLE_ROUTER | PERIODIC_EVENT_ROLE_DIRSERVER |
            ALL);

  /* Reset options so we can test authorities. */
  options->SocksPort_set = 0;
  options->ORPort_set = 0;
  options->BridgeRelay = 0;
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ, ALL);

  /* Now upgrade to Dirauth. */
  options->DirPort_set = 1;
  options->AuthoritativeDir = 1;
  options->V3AuthoritativeDir = 1;
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ,
            PERIODIC_EVENT_ROLE_DIRAUTH|PERIODIC_EVENT_ROLE_DIRSERVER|ALL);
  tt_assert(roles & PERIODIC_EVENT_ROLE_AUTHORITIES);

  /* Now Bridge Authority. */
  options->V3AuthoritativeDir = 0;
  options->BridgeAuthoritativeDir = 1;
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ,
            PERIODIC_EVENT_ROLE_BRIDGEAUTH|PERIODIC_EVENT_ROLE_DIRSERVER|ALL);
  tt_assert(roles & PERIODIC_EVENT_ROLE_AUTHORITIES);

  /* Move that bridge auth to become a relay. */
  options->ORPort_set = 1;
  roles = get_my_roles(options);
  tt_int_op(roles, OP_EQ,
            (PERIODIC_EVENT_ROLE_BRIDGEAUTH | PERIODIC_EVENT_ROLE_RELAY
             | PERIODIC_EVENT_ROLE_DIRSERVER|ALL));
  tt_assert(roles & PERIODIC_EVENT_ROLE_AUTHORITIES);

  /* And now an Hidden service. */
  hs_service_t service;
  register_dummy_hidden_service(&service);
  roles = get_my_roles(options);
  /* Remove it now so the hs_free_all() doesn't try to free stack memory. */
  remove_service(get_hs_service_map(), &service);
  tt_int_op(roles, OP_EQ,
            (PERIODIC_EVENT_ROLE_BRIDGEAUTH | PERIODIC_EVENT_ROLE_RELAY |
             PERIODIC_EVENT_ROLE_HS_SERVICE | PERIODIC_EVENT_ROLE_DIRSERVER |
             ALL));
  tt_assert(roles & PERIODIC_EVENT_ROLE_AUTHORITIES);

 done:
  hs_free_all();
}

static void
test_pe_hs_service(void *arg)
{
  hs_service_t service, *to_remove = NULL;

  (void) arg;

  hs_init();
  /* We need to put tor in hibernation live state so the events requiring
   * network gets enabled. */
  consider_hibernation(time(NULL));
  /* Initialize the events so we can enable them */
  initialize_periodic_events();
  periodic_events_connect_all();

  /* Hack: We'll set a dumb fn() of each events so they don't get called when
   * dispatching them. We just want to test the state of the callbacks, not
   * the whole code path. */
  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    item->fn = dumb_event_fn;
  }

  /* This should trigger a rescan of the list and enable the HS service
   * events. */
  register_dummy_hidden_service(&service);
  /* Note down the reference because we need to remove this service from the
   * global list before the hs_free_all() call so it doesn't try to free
   * memory on the stack. Furthermore, we can't remove it now else it will
   * trigger a rescan of the event disabling the HS service event. */
  to_remove = &service;

  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    if (item->roles & PERIODIC_EVENT_ROLE_HS_SERVICE) {
      tt_int_op(periodic_event_is_enabled(item), OP_EQ, 1);
    }
  }
  to_remove = NULL;

  /* Remove the service from the global map, it should trigger a rescan and
   * disable the HS service events. */
  remove_service(get_hs_service_map(), &service);
  for (int i = 0; mainloop_periodic_events[i].name; ++i) {
    periodic_event_item_t *item = &mainloop_periodic_events[i];
    if (item->roles & PERIODIC_EVENT_ROLE_HS_SERVICE) {
      tt_int_op(periodic_event_is_enabled(item), OP_EQ, 0);
    }
  }

 done:
  if (to_remove) {
    remove_service(get_hs_service_map(), to_remove);
  }
  hs_free_all();
}

#define PE_TEST(name) \
  { #name, test_pe_## name , TT_FORK, NULL, NULL }

struct testcase_t periodic_event_tests[] = {
  PE_TEST(initialize),
  PE_TEST(launch),
  PE_TEST(get_roles),
  PE_TEST(hs_service),

  END_OF_TESTCASES
};