mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2025-02-26 15:42:34 +01:00
When we tell the periodic event manager about an event, we are "registering" that event. The event sits around without being usable, however, until we "connect" the event to libevent. In the end, we "disconnect" the event and remove its libevent parts. Previously, we called these operations "add", "setup", and "destroy", which led to confusion.
359 lines
12 KiB
C
359 lines
12 KiB
C
/* Copyright (c) 2018-2019, 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
|
|
|
|
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
|
|
};
|