tor/src/test/test_controller_events.c
Taylor Yu 617160895c Defer reporting directory bootstrap progress
Existing cached directory information can cause misleadingly high
bootstrap percentages.  To improve user experience, defer reporting of
directory information progress until at least one connection has
succeeded to a relay or bridge.

Closes ticket 27169.
2018-09-10 15:20:50 -05:00

403 lines
14 KiB
C

/* Copyright (c) 2013-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#define CONNECTION_PRIVATE
#define TOR_CHANNEL_INTERNAL_
#define CONTROL_PRIVATE
#include "core/or/or.h"
#include "core/or/channel.h"
#include "core/or/channeltls.h"
#include "core/or/circuitlist.h"
#include "core/mainloop/connection.h"
#include "feature/control/control.h"
#include "test/test.h"
#include "core/or/or_circuit_st.h"
#include "core/or/origin_circuit_st.h"
static void
add_testing_cell_stats_entry(circuit_t *circ, uint8_t command,
unsigned int waiting_time,
unsigned int removed, unsigned int exitward)
{
testing_cell_stats_entry_t *ent = tor_malloc_zero(
sizeof(testing_cell_stats_entry_t));
ent->command = command;
ent->waiting_time = waiting_time;
ent->removed = removed;
ent->exitward = exitward;
if (!circ->testing_cell_stats)
circ->testing_cell_stats = smartlist_new();
smartlist_add(circ->testing_cell_stats, ent);
}
static void
test_cntev_sum_up_cell_stats(void *arg)
{
or_circuit_t *or_circ;
circuit_t *circ;
cell_stats_t *cell_stats = NULL;
(void)arg;
/* This circuit is fake. */
or_circ = tor_malloc_zero(sizeof(or_circuit_t));
or_circ->base_.magic = OR_CIRCUIT_MAGIC;
or_circ->base_.purpose = CIRCUIT_PURPOSE_OR;
circ = TO_CIRCUIT(or_circ);
/* A single RELAY cell was added to the appward queue. */
cell_stats = tor_malloc_zero(sizeof(cell_stats_t));
add_testing_cell_stats_entry(circ, CELL_RELAY, 0, 0, 0);
sum_up_cell_stats_by_command(circ, cell_stats);
tt_u64_op(1, OP_EQ, cell_stats->added_cells_appward[CELL_RELAY]);
/* A single RELAY cell was added to the exitward queue. */
add_testing_cell_stats_entry(circ, CELL_RELAY, 0, 0, 1);
sum_up_cell_stats_by_command(circ, cell_stats);
tt_u64_op(1, OP_EQ, cell_stats->added_cells_exitward[CELL_RELAY]);
/* A single RELAY cell was removed from the appward queue where it spent
* 20 msec. */
add_testing_cell_stats_entry(circ, CELL_RELAY, 2, 1, 0);
sum_up_cell_stats_by_command(circ, cell_stats);
tt_u64_op(20, OP_EQ, cell_stats->total_time_appward[CELL_RELAY]);
tt_u64_op(1, OP_EQ, cell_stats->removed_cells_appward[CELL_RELAY]);
/* A single RELAY cell was removed from the exitward queue where it
* spent 30 msec. */
add_testing_cell_stats_entry(circ, CELL_RELAY, 3, 1, 1);
sum_up_cell_stats_by_command(circ, cell_stats);
tt_u64_op(30, OP_EQ, cell_stats->total_time_exitward[CELL_RELAY]);
tt_u64_op(1, OP_EQ, cell_stats->removed_cells_exitward[CELL_RELAY]);
done:
tor_free(cell_stats);
tor_free(or_circ);
}
static void
test_cntev_append_cell_stats(void *arg)
{
smartlist_t *event_parts;
char *cp = NULL;
const char *key = "Z";
uint64_t include_if_non_zero[CELL_COMMAND_MAX_ + 1],
number_to_include[CELL_COMMAND_MAX_ + 1];
(void)arg;
event_parts = smartlist_new();
memset(include_if_non_zero, 0,
(CELL_COMMAND_MAX_ + 1) * sizeof(uint64_t));
memset(number_to_include, 0,
(CELL_COMMAND_MAX_ + 1) * sizeof(uint64_t));
/* All array entries empty. */
append_cell_stats_by_command(event_parts, key,
include_if_non_zero,
number_to_include);
tt_int_op(0, OP_EQ, smartlist_len(event_parts));
/* There's a RELAY cell to include, but the corresponding field in
* include_if_non_zero is still zero. */
number_to_include[CELL_RELAY] = 1;
append_cell_stats_by_command(event_parts, key,
include_if_non_zero,
number_to_include);
tt_int_op(0, OP_EQ, smartlist_len(event_parts));
/* Now include single RELAY cell. */
include_if_non_zero[CELL_RELAY] = 2;
append_cell_stats_by_command(event_parts, key,
include_if_non_zero,
number_to_include);
cp = smartlist_pop_last(event_parts);
tt_str_op("Z=relay:1", OP_EQ, cp);
tor_free(cp);
/* Add four CREATE cells. */
include_if_non_zero[CELL_CREATE] = 3;
number_to_include[CELL_CREATE] = 4;
append_cell_stats_by_command(event_parts, key,
include_if_non_zero,
number_to_include);
cp = smartlist_pop_last(event_parts);
tt_str_op("Z=create:4,relay:1", OP_EQ, cp);
done:
tor_free(cp);
smartlist_free(event_parts);
}
static void
test_cntev_format_cell_stats(void *arg)
{
char *event_string = NULL;
origin_circuit_t *ocirc = NULL;
or_circuit_t *or_circ = NULL;
cell_stats_t *cell_stats = NULL;
channel_tls_t *n_chan=NULL, *p_chan=NULL;
(void)arg;
n_chan = tor_malloc_zero(sizeof(channel_tls_t));
n_chan->base_.global_identifier = 1;
ocirc = tor_malloc_zero(sizeof(origin_circuit_t));
ocirc->base_.magic = ORIGIN_CIRCUIT_MAGIC;
ocirc->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL;
ocirc->global_identifier = 2;
ocirc->base_.n_circ_id = 3;
ocirc->base_.n_chan = &(n_chan->base_);
/* Origin circuit was completely idle. */
cell_stats = tor_malloc_zero(sizeof(cell_stats_t));
format_cell_stats(&event_string, TO_CIRCUIT(ocirc), cell_stats);
tt_str_op("ID=2 OutboundQueue=3 OutboundConn=1", OP_EQ, event_string);
tor_free(event_string);
/* Origin circuit had 4 RELAY cells added to its exitward queue. */
cell_stats->added_cells_exitward[CELL_RELAY] = 4;
format_cell_stats(&event_string, TO_CIRCUIT(ocirc), cell_stats);
tt_str_op("ID=2 OutboundQueue=3 OutboundConn=1 OutboundAdded=relay:4",
OP_EQ, event_string);
tor_free(event_string);
/* Origin circuit also had 5 CREATE2 cells added to its exitward
* queue. */
cell_stats->added_cells_exitward[CELL_CREATE2] = 5;
format_cell_stats(&event_string, TO_CIRCUIT(ocirc), cell_stats);
tt_str_op("ID=2 OutboundQueue=3 OutboundConn=1 OutboundAdded=relay:4,"
"create2:5", OP_EQ, event_string);
tor_free(event_string);
/* Origin circuit also had 7 RELAY cells removed from its exitward queue
* which together spent 6 msec in the queue. */
cell_stats->total_time_exitward[CELL_RELAY] = 6;
cell_stats->removed_cells_exitward[CELL_RELAY] = 7;
format_cell_stats(&event_string, TO_CIRCUIT(ocirc), cell_stats);
tt_str_op("ID=2 OutboundQueue=3 OutboundConn=1 OutboundAdded=relay:4,"
"create2:5 OutboundRemoved=relay:7 OutboundTime=relay:6",
OP_EQ, event_string);
tor_free(event_string);
p_chan = tor_malloc_zero(sizeof(channel_tls_t));
p_chan->base_.global_identifier = 2;
or_circ = tor_malloc_zero(sizeof(or_circuit_t));
or_circ->base_.magic = OR_CIRCUIT_MAGIC;
or_circ->base_.purpose = CIRCUIT_PURPOSE_OR;
or_circ->p_circ_id = 8;
or_circ->p_chan = &(p_chan->base_);
or_circ->base_.n_circ_id = 9;
or_circ->base_.n_chan = &(n_chan->base_);
tor_free(cell_stats);
/* OR circuit was idle. */
cell_stats = tor_malloc_zero(sizeof(cell_stats_t));
format_cell_stats(&event_string, TO_CIRCUIT(or_circ), cell_stats);
tt_str_op("InboundQueue=8 InboundConn=2 OutboundQueue=9 OutboundConn=1",
OP_EQ, event_string);
tor_free(event_string);
/* OR circuit had 3 RELAY cells added to its appward queue. */
cell_stats->added_cells_appward[CELL_RELAY] = 3;
format_cell_stats(&event_string, TO_CIRCUIT(or_circ), cell_stats);
tt_str_op("InboundQueue=8 InboundConn=2 InboundAdded=relay:3 "
"OutboundQueue=9 OutboundConn=1", OP_EQ, event_string);
tor_free(event_string);
/* OR circuit had 7 RELAY cells removed from its appward queue which
* together spent 6 msec in the queue. */
cell_stats->total_time_appward[CELL_RELAY] = 6;
cell_stats->removed_cells_appward[CELL_RELAY] = 7;
format_cell_stats(&event_string, TO_CIRCUIT(or_circ), cell_stats);
tt_str_op("InboundQueue=8 InboundConn=2 InboundAdded=relay:3 "
"InboundRemoved=relay:7 InboundTime=relay:6 "
"OutboundQueue=9 OutboundConn=1", OP_EQ, event_string);
done:
tor_free(cell_stats);
tor_free(event_string);
tor_free(or_circ);
tor_free(ocirc);
tor_free(p_chan);
tor_free(n_chan);
}
static void
test_cntev_event_mask(void *arg)
{
unsigned int test_event, selected_event;
(void)arg;
/* Check that nothing is interesting when no events are set */
control_testing_set_global_event_mask(EVENT_MASK_NONE_);
/* Check that nothing is interesting between EVENT_MIN_ and EVENT_MAX_ */
for (test_event = EVENT_MIN_; test_event <= EVENT_MAX_; test_event++)
tt_assert(!control_event_is_interesting(test_event));
/* Check that nothing is interesting outside EVENT_MIN_ to EVENT_MAX_
* This will break if control_event_is_interesting() checks its arguments */
for (test_event = 0; test_event < EVENT_MIN_; test_event++)
tt_assert(!control_event_is_interesting(test_event));
for (test_event = EVENT_MAX_ + 1;
test_event < EVENT_CAPACITY_;
test_event++)
tt_assert(!control_event_is_interesting(test_event));
/* Check that all valid events are interesting when all events are set */
control_testing_set_global_event_mask(EVENT_MASK_ALL_);
/* Check that everything is interesting between EVENT_MIN_ and EVENT_MAX_ */
for (test_event = EVENT_MIN_; test_event <= EVENT_MAX_; test_event++)
tt_assert(control_event_is_interesting(test_event));
/* Check that nothing is interesting outside EVENT_MIN_ to EVENT_MAX_
* This will break if control_event_is_interesting() checks its arguments */
for (test_event = 0; test_event < EVENT_MIN_; test_event++)
tt_assert(!control_event_is_interesting(test_event));
for (test_event = EVENT_MAX_ + 1;
test_event < EVENT_CAPACITY_;
test_event++)
tt_assert(!control_event_is_interesting(test_event));
/* Check that only that event is interesting when a single event is set */
for (selected_event = EVENT_MIN_;
selected_event <= EVENT_MAX_;
selected_event++) {
control_testing_set_global_event_mask(EVENT_MASK_(selected_event));
/* Check that only this event is interesting
* between EVENT_MIN_ and EVENT_MAX_ */
for (test_event = EVENT_MIN_; test_event <= EVENT_MAX_; test_event++) {
if (test_event == selected_event) {
tt_assert(control_event_is_interesting(test_event));
} else {
tt_assert(!control_event_is_interesting(test_event));
}
}
/* Check that nothing is interesting outside EVENT_MIN_ to EVENT_MAX_
* This will break if control_event_is_interesting checks its arguments */
for (test_event = 0; test_event < EVENT_MIN_; test_event++)
tt_assert(!control_event_is_interesting(test_event));
for (test_event = EVENT_MAX_ + 1;
test_event < EVENT_CAPACITY_;
test_event++)
tt_assert(!control_event_is_interesting(test_event));
}
/* Check that only that event is not-interesting
* when a single event is un-set */
for (selected_event = EVENT_MIN_;
selected_event <= EVENT_MAX_;
selected_event++) {
control_testing_set_global_event_mask(
EVENT_MASK_ALL_
& ~(EVENT_MASK_(selected_event))
);
/* Check that only this event is not-interesting
* between EVENT_MIN_ and EVENT_MAX_ */
for (test_event = EVENT_MIN_; test_event <= EVENT_MAX_; test_event++) {
if (test_event == selected_event) {
tt_assert(!control_event_is_interesting(test_event));
} else {
tt_assert(control_event_is_interesting(test_event));
}
}
/* Check that nothing is interesting outside EVENT_MIN_ to EVENT_MAX_
* This will break if control_event_is_interesting checks its arguments */
for (test_event = 0; test_event < EVENT_MIN_; test_event++)
tt_assert(!control_event_is_interesting(test_event));
for (test_event = EVENT_MAX_ + 1;
test_event < EVENT_CAPACITY_;
test_event++)
tt_assert(!control_event_is_interesting(test_event));
}
done:
;
}
static char *saved_event_str = NULL;
static void
mock_queue_control_event_string(uint16_t event, char *msg)
{
(void)event;
tor_free(saved_event_str);
saved_event_str = msg;
}
/* Helper macro for checking bootstrap control event strings */
#define assert_bootmsg(s) \
tt_ptr_op(strstr(saved_event_str, "650 STATUS_CLIENT NOTICE " \
"BOOTSTRAP PROGRESS=" s), OP_EQ, saved_event_str)
/* Test deferral of directory bootstrap messages (requesting_descriptors) */
static void
test_cntev_dirboot_defer_desc(void *arg)
{
(void)arg;
MOCK(queue_control_event_string, mock_queue_control_event_string);
control_testing_set_global_event_mask(EVENT_MASK_(EVENT_STATUS_CLIENT));
control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0);
assert_bootmsg("0 TAG=starting");
/* This event should get deferred */
control_event_boot_dir(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0);
assert_bootmsg("0 TAG=starting");
control_event_bootstrap(BOOTSTRAP_STATUS_CONN_DIR, 0);
assert_bootmsg("5 TAG=conn_dir");
control_event_bootstrap(BOOTSTRAP_STATUS_HANDSHAKE, 0);
assert_bootmsg("10 TAG=handshake_dir");
/* The deferred event should appear */
control_event_boot_first_orconn();
assert_bootmsg("45 TAG=requesting_descriptors");
done:
tor_free(saved_event_str);
UNMOCK(queue_control_event_string);
}
/* Test deferral of directory bootstrap messages (conn_or) */
static void
test_cntev_dirboot_defer_orconn(void *arg)
{
(void)arg;
MOCK(queue_control_event_string, mock_queue_control_event_string);
control_testing_set_global_event_mask(EVENT_MASK_(EVENT_STATUS_CLIENT));
control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0);
assert_bootmsg("0 TAG=starting");
/* This event should get deferred */
control_event_boot_dir(BOOTSTRAP_STATUS_CONN_OR, 0);
assert_bootmsg("0 TAG=starting");
control_event_bootstrap(BOOTSTRAP_STATUS_CONN_DIR, 0);
assert_bootmsg("5 TAG=conn_dir");
control_event_bootstrap(BOOTSTRAP_STATUS_HANDSHAKE, 0);
assert_bootmsg("10 TAG=handshake_dir");
/* The deferred event should appear */
control_event_boot_first_orconn();
assert_bootmsg("80 TAG=conn_or");
done:
tor_free(saved_event_str);
UNMOCK(queue_control_event_string);
}
#define TEST(name, flags) \
{ #name, test_cntev_ ## name, flags, 0, NULL }
struct testcase_t controller_event_tests[] = {
TEST(sum_up_cell_stats, TT_FORK),
TEST(append_cell_stats, TT_FORK),
TEST(format_cell_stats, TT_FORK),
TEST(event_mask, TT_FORK),
TEST(dirboot_defer_desc, TT_FORK),
TEST(dirboot_defer_orconn, TT_FORK),
END_OF_TESTCASES
};