From 4c950226582f4b7d71559b23d7eaf510964859d2 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Oct 2019 11:03:11 -0400 Subject: [PATCH 01/10] core: Add missing include in destroy_cell_queue_st.h Signed-off-by: David Goulet --- src/core/or/destroy_cell_queue_st.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/or/destroy_cell_queue_st.h b/src/core/or/destroy_cell_queue_st.h index fc817c1b42..3c4df050c2 100644 --- a/src/core/or/destroy_cell_queue_st.h +++ b/src/core/or/destroy_cell_queue_st.h @@ -12,6 +12,8 @@ #ifndef DESTROY_CELL_QUEUE_ST_H #define DESTROY_CELL_QUEUE_ST_H +#include "core/or/cell_queue_st.h" + /** A single queued destroy cell. */ struct destroy_cell_t { TOR_SIMPLEQ_ENTRY(destroy_cell_t) next; From 7678022e85736fc8a78b2a4252bf55db0043f1c5 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Oct 2019 11:03:57 -0400 Subject: [PATCH 02/10] cmux: Move circuitmux_s object into header in private section Step needed in order to access members of the object for unit tests. Signed-off-by: David Goulet --- src/core/or/circuitmux.c | 56 ++-------------------------------------- src/core/or/circuitmux.h | 56 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/src/core/or/circuitmux.c b/src/core/or/circuitmux.c index b2628bec3f..f92a53eb27 100644 --- a/src/core/or/circuitmux.c +++ b/src/core/or/circuitmux.c @@ -69,26 +69,20 @@ * made to attach all existing circuits to the new policy. **/ +#define CIRCUITMUX_PRIVATE + #include "core/or/or.h" #include "core/or/channel.h" #include "core/or/circuitlist.h" #include "core/or/circuitmux.h" #include "core/or/relay.h" -#include "core/or/cell_queue_st.h" -#include "core/or/destroy_cell_queue_st.h" #include "core/or/or_circuit_st.h" /* * Private typedefs for circuitmux.c */ -/* - * Map of muxinfos for circuitmux_t to use; struct is defined below (name - * of struct must match HT_HEAD line). - */ -typedef struct chanid_circid_muxinfo_map chanid_circid_muxinfo_map_t; - /* * Hash table entry (yeah, calling it chanid_circid_muxinfo_s seems to * break the hash table code). @@ -102,49 +96,6 @@ typedef struct chanid_circid_muxinfo_t chanid_circid_muxinfo_t; typedef struct circuit_muxinfo_s circuit_muxinfo_t; -/* - * Structures for circuitmux.c - */ - -struct circuitmux_s { - /* Keep count of attached, active circuits */ - unsigned int n_circuits, n_active_circuits; - - /* Total number of queued cells on all circuits */ - unsigned int n_cells; - - /* - * Map from (channel ID, circuit ID) pairs to circuit_muxinfo_t - */ - chanid_circid_muxinfo_map_t *chanid_circid_map; - - /** List of queued destroy cells */ - destroy_cell_queue_t destroy_cell_queue; - /** Boolean: True iff the last cell to circuitmux_get_first_active_circuit - * returned the destroy queue. Used to force alternation between - * destroy/non-destroy cells. - * - * XXXX There is no reason to think that alternating is a particularly good - * approach -- it's just designed to prevent destroys from starving other - * cells completely. - */ - unsigned int last_cell_was_destroy : 1; - /** Destroy counter: increment this when a destroy gets queued, decrement - * when we unqueue it, so we can test to make sure they don't starve. - */ - int64_t destroy_ctr; - - /* - * Circuitmux policy; if this is non-NULL, it can override the built- - * in round-robin active circuits behavior. This is how EWMA works in - * the new circuitmux_t world. - */ - const circuitmux_policy_t *policy; - - /* Policy-specific data */ - circuitmux_policy_data_t *policy_data; -}; - /* * This struct holds whatever we want to store per attached circuit on a * circuitmux_t; right now, just the count of queued cells and the direction. @@ -221,9 +172,6 @@ chanid_circid_entry_hash(chanid_circid_muxinfo_t *a) ((unsigned int)(a->chan_id & 0xffffffff))); } -/* Declare the struct chanid_circid_muxinfo_map type */ -HT_HEAD(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t); - /* Emit a bunch of hash table stuff */ HT_PROTOTYPE(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t, node, chanid_circid_entry_hash, chanid_circid_entries_eq) diff --git a/src/core/or/circuitmux.h b/src/core/or/circuitmux.h index 67cd9bcdd8..c68c31b29a 100644 --- a/src/core/or/circuitmux.h +++ b/src/core/or/circuitmux.h @@ -158,5 +158,61 @@ void circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, MOCK_DECL(int, circuitmux_compare_muxes, (circuitmux_t *cmux_1, circuitmux_t *cmux_2)); +#ifdef CIRCUITMUX_PRIVATE + +#include "core/or/destroy_cell_queue_st.h" + +/* + * Map of muxinfos for circuitmux_t to use; struct is defined below (name + * of struct must match HT_HEAD line). + */ +typedef HT_HEAD(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t) + chanid_circid_muxinfo_map_t; + +/* + * Structures for circuitmux.c + */ + +struct circuitmux_s { + /* Keep count of attached, active circuits */ + unsigned int n_circuits, n_active_circuits; + + /* Total number of queued cells on all circuits */ + unsigned int n_cells; + + /* + * Map from (channel ID, circuit ID) pairs to circuit_muxinfo_t + */ + chanid_circid_muxinfo_map_t *chanid_circid_map; + + /** List of queued destroy cells */ + destroy_cell_queue_t destroy_cell_queue; + /** Boolean: True iff the last cell to circuitmux_get_first_active_circuit + * returned the destroy queue. Used to force alternation between + * destroy/non-destroy cells. + * + * XXXX There is no reason to think that alternating is a particularly good + * approach -- it's just designed to prevent destroys from starving other + * cells completely. + */ + unsigned int last_cell_was_destroy : 1; + /** Destroy counter: increment this when a destroy gets queued, decrement + * when we unqueue it, so we can test to make sure they don't starve. + */ + int64_t destroy_ctr; + + /* + * Circuitmux policy; if this is non-NULL, it can override the built- + * in round-robin active circuits behavior. This is how EWMA works in + * the new circuitmux_t world. + */ + const circuitmux_policy_t *policy; + + /* Policy-specific data */ + circuitmux_policy_data_t *policy_data; +}; + +#endif /* CIRCUITMUX_PRIVATE */ + #endif /* !defined(TOR_CIRCUITMUX_H) */ From 839bc4814e8f99a15709994645bfa61f010d6dc0 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Oct 2019 10:37:35 -0400 Subject: [PATCH 03/10] test: Add testcase setup object for test_cmux Also remove a scheduler_init() from a test and MOCK the appropriate function so the test can pass. This is done in order to minimize initialization functions in the unit test and try to only go through the testcase setup object. Signed-off-by: David Goulet --- src/test/test_circuitmux.c | 49 +++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/test/test_circuitmux.c b/src/test/test_circuitmux.c index a2b3e62fe8..75bb4df5f6 100644 --- a/src/test/test_circuitmux.c +++ b/src/test/test_circuitmux.c @@ -17,6 +17,16 @@ #include +/** + * MOCKED functions. + */ +static void +scheduler_release_channel_mock(channel_t *chan) +{ + (void) chan; + return; +} + /* XXXX duplicated function from test_circuitlist.c */ static channel_t * new_fake_channel(void) @@ -44,7 +54,7 @@ test_cmux_destroy_cell_queue(void *arg) packed_cell_t *pc = NULL; destroy_cell_t *dc = NULL; - scheduler_init(); + MOCK(scheduler_release_channel, scheduler_release_channel_mock); (void) arg; @@ -82,6 +92,8 @@ test_cmux_destroy_cell_queue(void *arg) channel_free(ch); packed_cell_free(pc); tor_free(dc); + + UNMOCK(scheduler_release_channel); } static void @@ -125,9 +137,40 @@ test_cmux_compute_ticks(void *arg) ; } +static void * +cmux_setup_test(const struct testcase_t *tc) +{ + static int whatever; + + (void) tc; + + cell_ewma_initialize_ticks(); + return &whatever; +} + +static int +cmux_cleanup_test(const struct testcase_t *tc, void *ptr) +{ + (void) tc; + (void) ptr; + + circuitmux_ewma_free_all(); + + return 1; +} + +static struct testcase_setup_t cmux_test_setup = { + .setup_fn = cmux_setup_test, + .cleanup_fn = cmux_cleanup_test, +}; + +#define TEST_CMUX(name) \ + { #name, test_cmux_##name, TT_FORK, &cmux_test_setup, NULL } + struct testcase_t circuitmux_tests[] = { - { "destroy_cell_queue", test_cmux_destroy_cell_queue, TT_FORK, NULL, NULL }, - { "compute_ticks", test_cmux_compute_ticks, TT_FORK, NULL, NULL }, + TEST_CMUX(compute_ticks), + TEST_CMUX(destroy_cell_queue), + END_OF_TESTCASES }; From d2e51aca7da8851e81d4ed0edb2a093652817385 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Oct 2019 10:46:13 -0400 Subject: [PATCH 04/10] test: Remove circuitmux/destroy_cell_queue code duplication This also rename a function to improve code clarity. Signed-off-by: David Goulet --- src/test/test_circuitmux.c | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/src/test/test_circuitmux.c b/src/test/test_circuitmux.c index 75bb4df5f6..cb57b1bd60 100644 --- a/src/test/test_circuitmux.c +++ b/src/test/test_circuitmux.c @@ -5,39 +5,22 @@ #define CIRCUITMUX_PRIVATE #define CIRCUITMUX_EWMA_PRIVATE #define RELAY_PRIVATE + #include "core/or/or.h" #include "core/or/channel.h" #include "core/or/circuitmux.h" #include "core/or/circuitmux_ewma.h" +#include "core/or/destroy_cell_queue_st.h" #include "core/or/relay.h" #include "core/or/scheduler.h" -#include "test/test.h" -#include "core/or/destroy_cell_queue_st.h" +#include "test/fakechans.h" +#include "test/test.h" #include -/** - * MOCKED functions. - */ -static void -scheduler_release_channel_mock(channel_t *chan) -{ - (void) chan; - return; -} - -/* XXXX duplicated function from test_circuitlist.c */ -static channel_t * -new_fake_channel(void) -{ - channel_t *chan = tor_malloc_zero(sizeof(channel_t)); - channel_init(chan); - return chan; -} - static int -has_queued_writes(channel_t *c) +mock_has_queued_writes_true(channel_t *c) { (void) c; return 1; @@ -58,12 +41,10 @@ test_cmux_destroy_cell_queue(void *arg) (void) arg; - cmux = circuitmux_alloc(); - tt_assert(cmux); ch = new_fake_channel(); - circuitmux_set_policy(cmux, &ewma_policy); - ch->has_queued_writes = has_queued_writes; + ch->has_queued_writes = mock_has_queued_writes_true; ch->wide_circ_ids = 1; + cmux = ch->cmux; circ = circuitmux_get_first_active_circuit(cmux, &cq); tt_ptr_op(circ, OP_EQ, NULL); @@ -88,8 +69,7 @@ test_cmux_destroy_cell_queue(void *arg) tt_int_op(circuitmux_num_cells(cmux), OP_EQ, 2); done: - circuitmux_free(cmux); - channel_free(ch); + free_fake_channel(ch); packed_cell_free(pc); tor_free(dc); From a41ec8491480ea27588225c8db108503b2d7f757 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Oct 2019 11:04:35 -0400 Subject: [PATCH 05/10] test: Implement cmux allocate unit test Signed-off-by: David Goulet --- src/test/test_circuitmux.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/test_circuitmux.c b/src/test/test_circuitmux.c index cb57b1bd60..d4d99660cc 100644 --- a/src/test/test_circuitmux.c +++ b/src/test/test_circuitmux.c @@ -117,6 +117,31 @@ test_cmux_compute_ticks(void *arg) ; } +static void +test_cmux_allocate(void *arg) +{ + circuitmux_t *cmux = NULL; + + (void) arg; + + cmux = circuitmux_alloc(); + tt_assert(cmux); + tt_assert(cmux->chanid_circid_map); + tt_int_op(HT_SIZE(cmux->chanid_circid_map), OP_EQ, 0); + tt_uint_op(cmux->n_circuits, OP_EQ, 0); + tt_uint_op(cmux->n_active_circuits, OP_EQ, 0); + tt_uint_op(cmux->n_cells, OP_EQ, 0); + tt_uint_op(cmux->last_cell_was_destroy, OP_EQ, 0); + tt_int_op(cmux->destroy_ctr, OP_EQ, 0); + tt_ptr_op(cmux->policy, OP_EQ, NULL); + tt_ptr_op(cmux->policy_data, OP_EQ, NULL); + + tt_assert(TOR_SIMPLEQ_EMPTY(&cmux->destroy_cell_queue.head)); + + done: + circuitmux_free(cmux); +} + static void * cmux_setup_test(const struct testcase_t *tc) { @@ -148,6 +173,10 @@ static struct testcase_setup_t cmux_test_setup = { { #name, test_cmux_##name, TT_FORK, &cmux_test_setup, NULL } struct testcase_t circuitmux_tests[] = { + /* Test circuitmux_t object */ + TEST_CMUX(allocate), + + /* Misc. */ TEST_CMUX(compute_ticks), TEST_CMUX(destroy_cell_queue), From dba249bc735b02b6cafe92e5be7e8df049c53ae6 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Oct 2019 11:35:53 -0400 Subject: [PATCH 06/10] test: Add fakecircs.{h|c} helper Fake circuits are created everywhere in the unit tests. This is an attempt at centralizing a "fake circuit creation" API like fakechans.c does for channel. This commit introduces fakecircs.c and changes test_relay.c and test_circpadding.c which were using roughly the same code. This will allow easier OR circuit creation for the future tests in test_circuitmux.c Signed-off-by: David Goulet --- src/test/fakecircs.c | 88 ++++++++++++++++++++++++++++++++++ src/test/fakecircs.h | 17 +++++++ src/test/include.am | 2 + src/test/test_circuitpadding.c | 81 +++++-------------------------- src/test/test_relay.c | 38 ++------------- 5 files changed, 122 insertions(+), 104 deletions(-) create mode 100644 src/test/fakecircs.c create mode 100644 src/test/fakecircs.h diff --git a/src/test/fakecircs.c b/src/test/fakecircs.c new file mode 100644 index 0000000000..62027e0339 --- /dev/null +++ b/src/test/fakecircs.c @@ -0,0 +1,88 @@ +/* Copyright (c) 2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file fakecircs.c + * \brief Fake circuits API for unit test. + **/ + +#define CHANNEL_PRIVATE +#define CIRCUITBUILD_PRIVATE +#define CIRCUITLIST_PRIVATE +#define CRYPT_PATH_PRIVATE + +#include "core/or/or.h" + +#include "core/crypto/relay_crypto.h" +#include "core/or/channel.h" +#include "core/or/circuitbuild.h" +#include "core/or/circuitlist.h" +#include "core/or/circuitpadding.h" +#include "core/or/crypt_path.h" +#include "core/or/relay.h" +#include "core/or/relay_crypto_st.h" + +#include "fakecircs.h" + +/** Return newly allocated OR circuit using the given nchan and pchan. It must + * be freed with the free_fake_orcirc(). */ +or_circuit_t * +new_fake_orcirc(channel_t *nchan, channel_t *pchan) +{ + or_circuit_t *orcirc = NULL; + circuit_t *circ = NULL; + crypt_path_t tmp_cpath; + char whatevs_key[CPATH_KEY_MATERIAL_LEN]; + + orcirc = tor_malloc_zero(sizeof(*orcirc)); + circ = &(orcirc->base_); + circ->magic = OR_CIRCUIT_MAGIC; + + circuit_set_n_circid_chan(circ, get_unique_circ_id_by_chan(nchan), nchan); + cell_queue_init(&(circ->n_chan_cells)); + + circ->n_hop = NULL; + circ->streams_blocked_on_n_chan = 0; + circ->streams_blocked_on_p_chan = 0; + circ->n_delete_pending = 0; + circ->p_delete_pending = 0; + circ->received_destroy = 0; + circ->state = CIRCUIT_STATE_OPEN; + circ->purpose = CIRCUIT_PURPOSE_OR; + circ->package_window = CIRCWINDOW_START_MAX; + circ->deliver_window = CIRCWINDOW_START_MAX; + circ->n_chan_create_cell = NULL; + + circuit_set_p_circid_chan(orcirc, get_unique_circ_id_by_chan(pchan), pchan); + cell_queue_init(&(orcirc->p_chan_cells)); + + memset(&tmp_cpath, 0, sizeof(tmp_cpath)); + if (cpath_init_circuit_crypto(&tmp_cpath, whatevs_key, + sizeof(whatevs_key), 0, 0)<0) { + log_warn(LD_BUG,"Circuit initialization failed"); + return NULL; + } + orcirc->crypto = tmp_cpath.pvt_crypto; + + return orcirc; +} + +/** Free fake OR circuit which MUST be created by new_fake_orcirc(). */ +void +free_fake_orcirc(or_circuit_t *orcirc) +{ + circuit_t *circ = TO_CIRCUIT(orcirc); + + relay_crypto_clear(&orcirc->crypto); + + circpad_circuit_free_all_machineinfos(circ); + + if (orcirc->p_chan && orcirc->p_chan->cmux) { + circuitmux_detach_circuit(orcirc->p_chan->cmux, circ); + } + if (circ->n_chan && circ->n_chan->cmux) { + circuitmux_detach_circuit(circ->n_chan->cmux, circ); + } + + tor_free_(circ); +} diff --git a/src/test/fakecircs.h b/src/test/fakecircs.h new file mode 100644 index 0000000000..5fd02027f0 --- /dev/null +++ b/src/test/fakecircs.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file fakecircs.h + * \brief Declarations for fake circuits for test suite use. + **/ + +#ifndef TOR_FAKECIRCS_H +#define TOR_FAKECIRCS_H + +#include "core/or/or_circuit_st.h" + +or_circuit_t *new_fake_orcirc(channel_t *nchan, channel_t *pchan); +void free_fake_orcirc(or_circuit_t *orcirc); + +#endif /* TOR_FAKECIRCS_H */ diff --git a/src/test/include.am b/src/test/include.am index d8e25dea9f..294b9f309c 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -99,6 +99,7 @@ if UNITTESTS_ENABLED # ADD_C_FILE: INSERT SOURCES HERE. src_test_test_SOURCES += \ + src/test/fakecircs.c \ src/test/log_test_helpers.c \ src/test/hs_test_helpers.c \ src/test/rend_test_helpers.c \ @@ -339,6 +340,7 @@ src_test_test_timers_LDFLAGS = $(src_test_test_LDFLAGS) # ADD_C_FILE: INSERT HEADERS HERE. noinst_HEADERS+= \ src/test/fakechans.h \ + src/test/fakecircs.h \ src/test/hs_test_helpers.h \ src/test/log_test_helpers.h \ src/test/rend_test_helpers.h \ diff --git a/src/test/test_circuitpadding.c b/src/test/test_circuitpadding.c index 934ddb0208..70e2081c55 100644 --- a/src/test/test_circuitpadding.c +++ b/src/test/test_circuitpadding.c @@ -38,6 +38,7 @@ #include "core/or/or_circuit_st.h" #include "core/or/origin_circuit_st.h" +#include "test/fakecircs.h" #include "test/rng_test_helpers.h" /* Start our monotime mocking at 1 second past whatever monotime_init() @@ -53,7 +54,6 @@ circid_t get_unique_circ_id_by_chan(channel_t *chan); void helper_create_basic_machine(void); static void helper_create_conditional_machines(void); -static or_circuit_t * new_fake_orcirc(channel_t *nchan, channel_t *pchan); channel_t *new_fake_channel(void); void test_circuitpadding_negotiation(void *arg); void test_circuitpadding_wronghop(void *arg); @@ -67,7 +67,6 @@ void test_circuitpadding_state_length(void *arg); static void simulate_single_hop_extend(circuit_t *client, circuit_t *mid_relay, int padding); -void free_fake_orcirc(circuit_t *circ); void free_fake_origin_circuit(origin_circuit_t *circ); static int deliver_negotiated = 1; @@ -127,62 +126,6 @@ circuit_get_nth_node_mock(origin_circuit_t *circ, int hop) return &padding_node; } -static or_circuit_t * -new_fake_orcirc(channel_t *nchan, channel_t *pchan) -{ - or_circuit_t *orcirc = NULL; - circuit_t *circ = NULL; - crypt_path_t tmp_cpath; - char whatevs_key[CPATH_KEY_MATERIAL_LEN]; - - orcirc = tor_malloc_zero(sizeof(*orcirc)); - circ = &(orcirc->base_); - circ->magic = OR_CIRCUIT_MAGIC; - - //circ->n_chan = nchan; - circ->n_circ_id = get_unique_circ_id_by_chan(nchan); - cell_queue_init(&(circ->n_chan_cells)); - circ->n_hop = NULL; - circ->streams_blocked_on_n_chan = 0; - circ->streams_blocked_on_p_chan = 0; - circ->n_delete_pending = 0; - circ->p_delete_pending = 0; - circ->received_destroy = 0; - circ->state = CIRCUIT_STATE_OPEN; - circ->purpose = CIRCUIT_PURPOSE_OR; - circ->package_window = CIRCWINDOW_START_MAX; - circ->deliver_window = CIRCWINDOW_START_MAX; - circ->n_chan_create_cell = NULL; - - //orcirc->p_chan = pchan; - orcirc->p_circ_id = get_unique_circ_id_by_chan(pchan); - cell_queue_init(&(orcirc->p_chan_cells)); - - circuit_set_p_circid_chan(orcirc, orcirc->p_circ_id, pchan); - circuit_set_n_circid_chan(circ, circ->n_circ_id, nchan); - - memset(&tmp_cpath, 0, sizeof(tmp_cpath)); - if (cpath_init_circuit_crypto(&tmp_cpath, whatevs_key, - sizeof(whatevs_key), 0, 0)<0) { - log_warn(LD_BUG,"Circuit initialization failed"); - return NULL; - } - orcirc->crypto = tmp_cpath.pvt_crypto; - - return orcirc; -} - -void -free_fake_orcirc(circuit_t *circ) -{ - or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); - - relay_crypto_clear(&orcirc->crypto); - - circpad_circuit_free_all_machineinfos(circ); - tor_free(circ); -} - void free_fake_origin_circuit(origin_circuit_t *circ) { @@ -413,7 +356,7 @@ test_circuitpadding_rtt(void *arg) circpad_machine_current_state( client_side->padding_info[0])->histogram_edges[0]); done: - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); circuitmux_free(dummy_channel.cmux); timers_shutdown(); @@ -1439,7 +1382,7 @@ test_circuitpadding_wronghop(void *arg) /* Test 2: Test no padding */ free_fake_origin_circuit(TO_ORIGIN_CIRCUIT(client_side)); - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); client_side = TO_CIRCUIT(origin_circuit_new()); relay_side = TO_CIRCUIT(new_fake_orcirc(&dummy_channel, @@ -1484,7 +1427,7 @@ test_circuitpadding_wronghop(void *arg) done: free_fake_origin_circuit(TO_ORIGIN_CIRCUIT(client_side)); - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); circuitmux_free(dummy_channel.cmux); monotime_disable_test_mocking(); @@ -1553,7 +1496,7 @@ test_circuitpadding_negotiation(void *arg) /* Test 2: Test no padding */ free_fake_origin_circuit(TO_ORIGIN_CIRCUIT(client_side)); - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); client_side = TO_CIRCUIT(origin_circuit_new()); relay_side = TO_CIRCUIT(new_fake_orcirc(&dummy_channel, &dummy_channel)); @@ -1591,7 +1534,7 @@ test_circuitpadding_negotiation(void *arg) /* 3. Test failure to negotiate a machine due to desync */ free_fake_origin_circuit(TO_ORIGIN_CIRCUIT(client_side)); - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); client_side = TO_CIRCUIT(origin_circuit_new()); relay_side = TO_CIRCUIT(new_fake_orcirc(&dummy_channel, &dummy_channel)); @@ -1619,7 +1562,7 @@ test_circuitpadding_negotiation(void *arg) done: free_fake_origin_circuit(TO_ORIGIN_CIRCUIT(client_side)); - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); circuitmux_free(dummy_channel.cmux); monotime_disable_test_mocking(); @@ -1939,7 +1882,7 @@ test_circuitpadding_state_length(void *arg) tor_free(client_machine); free_fake_origin_circuit(TO_ORIGIN_CIRCUIT(client_side)); - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); circuitmux_free(dummy_channel.cmux); @@ -2312,7 +2255,7 @@ test_circuitpadding_circuitsetup_machine(void *arg) tt_u64_op(relay_side->padding_info[0]->padding_scheduled_at_usec, OP_NE, 0); circuit_mark_for_close(client_side, END_CIRC_REASON_FLAG_REMOTE); - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); timers_advance_and_run(5000); /* No cells sent */ @@ -2616,7 +2559,7 @@ test_circuitpadding_global_rate_limiting(void *arg) tt_int_op(retval, OP_EQ, 0); done: - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); circuitmux_free(dummy_channel.cmux); SMARTLIST_FOREACH(vote1.net_params, char *, cp, tor_free(cp)); @@ -2769,7 +2712,7 @@ test_circuitpadding_reduce_disable(void *arg) tt_ptr_op(relay_side->padding_machine[0], OP_EQ, NULL); done: - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); circuitmux_free(dummy_channel.cmux); testing_disable_reproducible_rng(); @@ -3075,7 +3018,7 @@ helper_test_hs_machines(bool test_intro_circs) } done: - free_fake_orcirc(relay_side); + free_fake_orcirc(TO_OR_CIRCUIT(relay_side)); circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); circuitmux_free(dummy_channel.cmux); free_fake_origin_circuit(TO_ORIGIN_CIRCUIT(client_side)); diff --git a/src/test/test_relay.c b/src/test/test_relay.c index 0b7a7be332..f7809b47ef 100644 --- a/src/test/test_relay.c +++ b/src/test/test_relay.c @@ -21,42 +21,10 @@ /* Test suite stuff */ #include "test/test.h" #include "test/fakechans.h" - -static or_circuit_t * new_fake_orcirc(channel_t *nchan, channel_t *pchan); +#include "test/fakecircs.h" static void test_relay_append_cell_to_circuit_queue(void *arg); -static or_circuit_t * -new_fake_orcirc(channel_t *nchan, channel_t *pchan) -{ - or_circuit_t *orcirc = NULL; - circuit_t *circ = NULL; - - orcirc = tor_malloc_zero(sizeof(*orcirc)); - circ = &(orcirc->base_); - circ->magic = OR_CIRCUIT_MAGIC; - - circuit_set_n_circid_chan(circ, get_unique_circ_id_by_chan(nchan), nchan); - cell_queue_init(&(circ->n_chan_cells)); - - circ->n_hop = NULL; - circ->streams_blocked_on_n_chan = 0; - circ->streams_blocked_on_p_chan = 0; - circ->n_delete_pending = 0; - circ->p_delete_pending = 0; - circ->received_destroy = 0; - circ->state = CIRCUIT_STATE_OPEN; - circ->purpose = CIRCUIT_PURPOSE_OR; - circ->package_window = CIRCWINDOW_START_MAX; - circ->deliver_window = CIRCWINDOW_START_MAX; - circ->n_chan_create_cell = NULL; - - circuit_set_p_circid_chan(orcirc, get_unique_circ_id_by_chan(pchan), pchan); - cell_queue_init(&(orcirc->p_chan_cells)); - - return orcirc; -} - static void assert_circuit_ok_mock(const circuit_t *c) { @@ -145,7 +113,7 @@ test_relay_close_circuit(void *arg) cell_queue_clear(&orcirc->base_.n_chan_cells); cell_queue_clear(&orcirc->p_chan_cells); } - tor_free(orcirc); + free_fake_orcirc(orcirc); free_fake_channel(nchan); free_fake_channel(pchan); UNMOCK(assert_circuit_ok); @@ -218,7 +186,7 @@ test_relay_append_cell_to_circuit_queue(void *arg) cell_queue_clear(&orcirc->base_.n_chan_cells); cell_queue_clear(&orcirc->p_chan_cells); } - tor_free(orcirc); + free_fake_orcirc(orcirc); free_fake_channel(nchan); free_fake_channel(pchan); From bbcded554a7e85caea7333037ee6a906dc4c7c35 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Oct 2019 12:38:31 -0400 Subject: [PATCH 07/10] test: Implement unit tests for circuitmux.c Signed-off-by: David Goulet --- src/test/test_circuitmux.c | 296 +++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) diff --git a/src/test/test_circuitmux.c b/src/test/test_circuitmux.c index d4d99660cc..6d9e5c472d 100644 --- a/src/test/test_circuitmux.c +++ b/src/test/test_circuitmux.c @@ -15,6 +15,7 @@ #include "core/or/scheduler.h" #include "test/fakechans.h" +#include "test/fakecircs.h" #include "test/test.h" #include @@ -142,6 +143,296 @@ test_cmux_allocate(void *arg) circuitmux_free(cmux); } +static void +test_cmux_attach_circuit(void *arg) +{ + circuit_t *circ = NULL; + or_circuit_t *orcirc = NULL; + channel_t *pchan = NULL, *nchan = NULL; + cell_direction_t cdir; + unsigned int n_cells; + + (void) arg; + + pchan = new_fake_channel(); + tt_assert(pchan); + nchan = new_fake_channel(); + tt_assert(nchan); + + orcirc = new_fake_orcirc(nchan, pchan); + tt_assert(orcirc); + circ = TO_CIRCUIT(orcirc); + + /* While assigning a new circuit IDs, the circuitmux_attach_circuit() is + * called for a new channel on the circuit. This means, we should now have + * the created circuit attached on both the pchan and nchan cmux. */ + tt_uint_op(circuitmux_num_circuits(pchan->cmux), OP_EQ, 1); + tt_uint_op(circuitmux_num_circuits(nchan->cmux), OP_EQ, 1); + + /* There should be _no_ active circuit due to no queued cells. */ + tt_uint_op(circuitmux_num_active_circuits(pchan->cmux), OP_EQ, 0); + tt_uint_op(circuitmux_num_active_circuits(nchan->cmux), OP_EQ, 0); + + /* Circuit should not be active on the cmux. */ + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_active(nchan->cmux, circ), OP_EQ, 0); + + /* Not active so no cells. */ + n_cells = circuitmux_num_cells_for_circuit(pchan->cmux, circ); + tt_uint_op(n_cells, OP_EQ, 0); + n_cells = circuitmux_num_cells(pchan->cmux); + tt_uint_op(n_cells, OP_EQ, 0); + n_cells = circuitmux_num_cells_for_circuit(nchan->cmux, circ); + tt_uint_op(n_cells, OP_EQ, 0); + n_cells = circuitmux_num_cells(nchan->cmux); + tt_uint_op(n_cells, OP_EQ, 0); + + /* So it should be attached :) */ + tt_int_op(circuitmux_is_circuit_attached(pchan->cmux, circ), OP_EQ, 1); + tt_int_op(circuitmux_is_circuit_attached(nchan->cmux, circ), OP_EQ, 1); + + /* Query the chanid<->circid map in the cmux subsytem with what we just + * created and validate the cell direction. */ + cdir = circuitmux_attached_circuit_direction(pchan->cmux, circ); + tt_int_op(cdir, OP_EQ, CELL_DIRECTION_IN); + cdir = circuitmux_attached_circuit_direction(nchan->cmux, circ); + tt_int_op(cdir, OP_EQ, CELL_DIRECTION_OUT); + + /* + * We'll activate->deactivate->activate to test all code paths of + * circuitmux_set_num_cells(). + */ + + /* Activate circuit. */ + circuitmux_set_num_cells(pchan->cmux, circ, 4); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 1); + + /* Deactivate. */ + circuitmux_clear_num_cells(pchan->cmux, circ); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 0); + tt_uint_op(circuitmux_num_cells_for_circuit(pchan->cmux, circ), OP_EQ, 0); + + /* Re-activate. */ + circuitmux_set_num_cells(pchan->cmux, circ, 4); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 1); + + /* Once re-attached, it should become inactive because the circuit has no + * cells while the chanid<->circid object has some. The attach code will + * reset the count on the cmux for that circuit: + * + * if (chanid_circid_muxinfo_t->muxinfo.cell_count > 0 && cell_count == 0) { + */ + circuitmux_attach_circuit(pchan->cmux, circ, CELL_DIRECTION_IN); + n_cells = circuitmux_num_cells_for_circuit(pchan->cmux, circ); + tt_uint_op(n_cells, OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 0); + tt_uint_op(circuitmux_num_active_circuits(pchan->cmux), OP_EQ, 0); + + /* Lets queue a cell on the circuit now so it becomes active when + * re-attaching: + * + * else if (chanid_circid_muxinfo_t->muxinfo.cell_count == 0 && + * cell_count > 0) { + */ + orcirc->p_chan_cells.n = 1; + circuitmux_attach_circuit(pchan->cmux, circ, CELL_DIRECTION_IN); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 1); + + done: + free_fake_orcirc(orcirc); + free_fake_channel(pchan); + free_fake_channel(nchan); +} + +static void +test_cmux_detach_circuit(void *arg) +{ + circuit_t *circ = NULL; + or_circuit_t *orcirc = NULL; + channel_t *pchan = NULL, *nchan = NULL; + + (void) arg; + + pchan = new_fake_channel(); + tt_assert(pchan); + nchan = new_fake_channel(); + tt_assert(nchan); + + orcirc = new_fake_orcirc(nchan, pchan); + tt_assert(orcirc); + circ = TO_CIRCUIT(orcirc); + + /* While assigning a new circuit IDs, the circuitmux_attach_circuit() is + * called for a new channel on the circuit. This means, we should now have + * the created circuit attached on both the pchan and nchan cmux. */ + tt_uint_op(circuitmux_num_circuits(pchan->cmux), OP_EQ, 1); + tt_uint_op(circuitmux_num_circuits(nchan->cmux), OP_EQ, 1); + tt_int_op(circuitmux_is_circuit_attached(pchan->cmux, circ), OP_EQ, 1); + tt_int_op(circuitmux_is_circuit_attached(nchan->cmux, circ), OP_EQ, 1); + + /* Now, detach the circuit from pchan and then nchan. */ + circuitmux_detach_circuit(pchan->cmux, circ); + tt_uint_op(circuitmux_num_circuits(pchan->cmux), OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_attached(pchan->cmux, circ), OP_EQ, 0); + circuitmux_detach_circuit(nchan->cmux, circ); + tt_uint_op(circuitmux_num_circuits(nchan->cmux), OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_attached(nchan->cmux, circ), OP_EQ, 0); + + done: + free_fake_orcirc(orcirc); + free_fake_channel(pchan); + free_fake_channel(nchan); +} + +static void +test_cmux_detach_all_circuits(void *arg) +{ + circuit_t *circ = NULL; + or_circuit_t *orcirc = NULL; + channel_t *pchan = NULL, *nchan = NULL; + smartlist_t *detached_out = smartlist_new(); + + (void) arg; + + /* Channels need to be registered in order for the detach all circuit + * function to find them. */ + pchan = new_fake_channel(); + tt_assert(pchan); + channel_register(pchan); + nchan = new_fake_channel(); + tt_assert(nchan); + channel_register(nchan); + + orcirc = new_fake_orcirc(nchan, pchan); + tt_assert(orcirc); + circ = TO_CIRCUIT(orcirc); + + /* Just make sure it is attached. */ + tt_uint_op(circuitmux_num_circuits(pchan->cmux), OP_EQ, 1); + tt_uint_op(circuitmux_num_circuits(nchan->cmux), OP_EQ, 1); + tt_int_op(circuitmux_is_circuit_attached(pchan->cmux, circ), OP_EQ, 1); + tt_int_op(circuitmux_is_circuit_attached(nchan->cmux, circ), OP_EQ, 1); + + /* Queue some cells so we can test if the circuit becomes inactive on the + * cmux after the mass detach. */ + circuitmux_set_num_cells(pchan->cmux, circ, 4); + circuitmux_set_num_cells(nchan->cmux, circ, 4); + + /* Detach all on pchan and then nchan. */ + circuitmux_detach_all_circuits(pchan->cmux, detached_out); + tt_uint_op(circuitmux_num_circuits(pchan->cmux), OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_attached(pchan->cmux, circ), OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 0); + tt_int_op(smartlist_len(detached_out), OP_EQ, 1); + circuitmux_detach_all_circuits(nchan->cmux, NULL); + tt_uint_op(circuitmux_num_circuits(nchan->cmux), OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_attached(nchan->cmux, circ), OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_active(nchan->cmux, circ), OP_EQ, 0); + + done: + smartlist_free(detached_out); + free_fake_orcirc(orcirc); + free_fake_channel(pchan); + free_fake_channel(nchan); +} + +static void +test_cmux_policy(void *arg) +{ + circuit_t *circ = NULL; + or_circuit_t *orcirc = NULL; + channel_t *pchan = NULL, *nchan = NULL; + + (void) arg; + + pchan = new_fake_channel(); + tt_assert(pchan); + channel_register(pchan); + nchan = new_fake_channel(); + tt_assert(nchan); + channel_register(nchan); + + orcirc = new_fake_orcirc(nchan, pchan); + tt_assert(orcirc); + circ = TO_CIRCUIT(orcirc); + + /* Confirm we have the EWMA policy by default for new channels. */ + tt_ptr_op(circuitmux_get_policy(pchan->cmux), OP_EQ, &ewma_policy); + tt_ptr_op(circuitmux_get_policy(nchan->cmux), OP_EQ, &ewma_policy); + + /* Putting cell on the cmux means will make the notify policy code path to + * trigger. */ + circuitmux_set_num_cells(pchan->cmux, circ, 4); + + /* Clear it out. */ + circuitmux_clear_policy(pchan->cmux); + + /* Set back the EWMA policy. */ + circuitmux_set_policy(pchan->cmux, &ewma_policy); + + done: + free_fake_orcirc(orcirc); + free_fake_channel(pchan); + free_fake_channel(nchan); +} + +static void +test_cmux_xmit_cell(void *arg) +{ + circuit_t *circ = NULL; + or_circuit_t *orcirc = NULL; + channel_t *pchan = NULL, *nchan = NULL; + + (void) arg; + + pchan = new_fake_channel(); + tt_assert(pchan); + nchan = new_fake_channel(); + tt_assert(nchan); + + orcirc = new_fake_orcirc(nchan, pchan); + tt_assert(orcirc); + circ = TO_CIRCUIT(orcirc); + + /* Queue 4 cells on the circuit. */ + circuitmux_set_num_cells(pchan->cmux, circ, 4); + tt_uint_op(circuitmux_num_cells_for_circuit(pchan->cmux, circ), OP_EQ, 4); + tt_uint_op(circuitmux_num_cells(pchan->cmux), OP_EQ, 4); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 1); + tt_uint_op(circuitmux_num_active_circuits(pchan->cmux), OP_EQ, 1); + + /* Emit the first cell. Circuit should still be active. */ + circuitmux_notify_xmit_cells(pchan->cmux, circ, 1); + tt_uint_op(circuitmux_num_cells(pchan->cmux), OP_EQ, 3); + tt_uint_op(circuitmux_num_cells_for_circuit(pchan->cmux, circ), OP_EQ, 3); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 1); + tt_uint_op(circuitmux_num_active_circuits(pchan->cmux), OP_EQ, 1); + + /* Emit the last 3 cells. Circuit should become inactive. */ + circuitmux_notify_xmit_cells(pchan->cmux, circ, 3); + tt_uint_op(circuitmux_num_cells(pchan->cmux), OP_EQ, 0); + tt_uint_op(circuitmux_num_cells_for_circuit(pchan->cmux, circ), OP_EQ, 0); + tt_int_op(circuitmux_is_circuit_active(pchan->cmux, circ), OP_EQ, 0); + tt_uint_op(circuitmux_num_active_circuits(pchan->cmux), OP_EQ, 0); + + /* Queue a DESTROY cell. */ + pchan->has_queued_writes = mock_has_queued_writes_true; + circuitmux_append_destroy_cell(pchan, pchan->cmux, orcirc->p_circ_id, 0); + tt_int_op(pchan->cmux->destroy_ctr, OP_EQ, 1); + tt_int_op(pchan->cmux->destroy_cell_queue.n, OP_EQ, 1); + tt_int_op(circuitmux_count_queued_destroy_cells(pchan, pchan->cmux), + OP_EQ, 1); + + /* Emit the DESTROY cell. */ + circuitmux_notify_xmit_destroy(pchan->cmux); + tt_int_op(pchan->cmux->destroy_ctr, OP_EQ, 0); + + done: + free_fake_orcirc(orcirc); + free_fake_channel(pchan); + free_fake_channel(nchan); +} + static void * cmux_setup_test(const struct testcase_t *tc) { @@ -175,6 +466,11 @@ static struct testcase_setup_t cmux_test_setup = { struct testcase_t circuitmux_tests[] = { /* Test circuitmux_t object */ TEST_CMUX(allocate), + TEST_CMUX(attach_circuit), + TEST_CMUX(detach_circuit), + TEST_CMUX(detach_all_circuits), + TEST_CMUX(policy), + TEST_CMUX(xmit_cell), /* Misc. */ TEST_CMUX(compute_ticks), From 48781c32caf8314f910c50f8ad0215c62356e2f7 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 28 Oct 2019 11:11:41 -0400 Subject: [PATCH 08/10] ewma: Move private struct into header private section Facilitate testing. Part of #32196. Signed-off-by: David Goulet --- src/core/or/circuitmux_ewma.c | 109 ---------------------------------- src/core/or/circuitmux_ewma.h | 107 ++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 110 deletions(-) diff --git a/src/core/or/circuitmux_ewma.c b/src/core/or/circuitmux_ewma.c index 3f83c3fd5a..5c9eac1c3f 100644 --- a/src/core/or/circuitmux_ewma.c +++ b/src/core/or/circuitmux_ewma.c @@ -58,115 +58,6 @@ /** The natural logarithm of 0.5. */ #define LOG_ONEHALF -0.69314718055994529 -/*** EWMA structures ***/ - -typedef struct cell_ewma_s cell_ewma_t; -typedef struct ewma_policy_data_s ewma_policy_data_t; -typedef struct ewma_policy_circ_data_s ewma_policy_circ_data_t; - -/** - * The cell_ewma_t structure keeps track of how many cells a circuit has - * transferred recently. It keeps an EWMA (exponentially weighted moving - * average) of the number of cells flushed from the circuit queue onto a - * connection in channel_flush_from_first_active_circuit(). - */ - -struct cell_ewma_s { - /** The last 'tick' at which we recalibrated cell_count. - * - * A cell sent at exactly the start of this tick has weight 1.0. Cells sent - * since the start of this tick have weight greater than 1.0; ones sent - * earlier have less weight. */ - unsigned int last_adjusted_tick; - /** The EWMA of the cell count. */ - double cell_count; - /** True iff this is the cell count for a circuit's previous - * channel. */ - unsigned int is_for_p_chan : 1; - /** The position of the circuit within the OR connection's priority - * queue. */ - int heap_index; -}; - -struct ewma_policy_data_s { - circuitmux_policy_data_t base_; - - /** - * Priority queue of cell_ewma_t for circuits with queued cells waiting - * for room to free up on the channel that owns this circuitmux. Kept - * in heap order according to EWMA. This was formerly in channel_t, and - * in or_connection_t before that. - */ - smartlist_t *active_circuit_pqueue; - - /** - * The tick on which the cell_ewma_ts in active_circuit_pqueue last had - * their ewma values rescaled. This was formerly in channel_t, and in - * or_connection_t before that. - */ - unsigned int active_circuit_pqueue_last_recalibrated; -}; - -struct ewma_policy_circ_data_s { - circuitmux_policy_circ_data_t base_; - - /** - * The EWMA count for the number of cells flushed from this circuit - * onto this circuitmux. Used to determine which circuit to flush - * from next. This was formerly in circuit_t and or_circuit_t. - */ - cell_ewma_t cell_ewma; - - /** - * Pointer back to the circuit_t this is for; since we're separating - * out circuit selection policy like this, we can't attach cell_ewma_t - * to the circuit_t any more, so we can't use SUBTYPE_P directly to a - * circuit_t like before; instead get it here. - */ - circuit_t *circ; -}; - -#define EWMA_POL_DATA_MAGIC 0x2fd8b16aU -#define EWMA_POL_CIRC_DATA_MAGIC 0x761e7747U - -/*** Downcasts for the above types ***/ - -static ewma_policy_data_t * -TO_EWMA_POL_DATA(circuitmux_policy_data_t *); - -static ewma_policy_circ_data_t * -TO_EWMA_POL_CIRC_DATA(circuitmux_policy_circ_data_t *); - -/** - * Downcast a circuitmux_policy_data_t to an ewma_policy_data_t and assert - * if the cast is impossible. - */ - -static inline ewma_policy_data_t * -TO_EWMA_POL_DATA(circuitmux_policy_data_t *pol) -{ - if (!pol) return NULL; - else { - tor_assert(pol->magic == EWMA_POL_DATA_MAGIC); - return DOWNCAST(ewma_policy_data_t, pol); - } -} - -/** - * Downcast a circuitmux_policy_circ_data_t to an ewma_policy_circ_data_t - * and assert if the cast is impossible. - */ - -static inline ewma_policy_circ_data_t * -TO_EWMA_POL_CIRC_DATA(circuitmux_policy_circ_data_t *pol) -{ - if (!pol) return NULL; - else { - tor_assert(pol->magic == EWMA_POL_CIRC_DATA_MAGIC); - return DOWNCAST(ewma_policy_circ_data_t, pol); - } -} - /*** Static declarations for circuitmux_ewma.c ***/ static void add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma); diff --git a/src/core/or/circuitmux_ewma.h b/src/core/or/circuitmux_ewma.h index b45ce1f916..dcfbc17a82 100644 --- a/src/core/or/circuitmux_ewma.h +++ b/src/core/or/circuitmux_ewma.h @@ -22,9 +22,114 @@ void cmux_ewma_set_options(const or_options_t *options, void circuitmux_ewma_free_all(void); #ifdef CIRCUITMUX_EWMA_PRIVATE + +/*** EWMA structures ***/ + +typedef struct cell_ewma_s cell_ewma_t; +typedef struct ewma_policy_data_s ewma_policy_data_t; +typedef struct ewma_policy_circ_data_s ewma_policy_circ_data_t; + +/** + * The cell_ewma_t structure keeps track of how many cells a circuit has + * transferred recently. It keeps an EWMA (exponentially weighted moving + * average) of the number of cells flushed from the circuit queue onto a + * connection in channel_flush_from_first_active_circuit(). + */ + +struct cell_ewma_s { + /** The last 'tick' at which we recalibrated cell_count. + * + * A cell sent at exactly the start of this tick has weight 1.0. Cells sent + * since the start of this tick have weight greater than 1.0; ones sent + * earlier have less weight. */ + unsigned int last_adjusted_tick; + /** The EWMA of the cell count. */ + double cell_count; + /** True iff this is the cell count for a circuit's previous + * channel. */ + unsigned int is_for_p_chan : 1; + /** The position of the circuit within the OR connection's priority + * queue. */ + int heap_index; +}; + +struct ewma_policy_data_s { + circuitmux_policy_data_t base_; + + /** + * Priority queue of cell_ewma_t for circuits with queued cells waiting + * for room to free up on the channel that owns this circuitmux. Kept + * in heap order according to EWMA. This was formerly in channel_t, and + * in or_connection_t before that. + */ + smartlist_t *active_circuit_pqueue; + + /** + * The tick on which the cell_ewma_ts in active_circuit_pqueue last had + * their ewma values rescaled. This was formerly in channel_t, and in + * or_connection_t before that. + */ + unsigned int active_circuit_pqueue_last_recalibrated; +}; + +struct ewma_policy_circ_data_s { + circuitmux_policy_circ_data_t base_; + + /** + * The EWMA count for the number of cells flushed from this circuit + * onto this circuitmux. Used to determine which circuit to flush + * from next. This was formerly in circuit_t and or_circuit_t. + */ + cell_ewma_t cell_ewma; + + /** + * Pointer back to the circuit_t this is for; since we're separating + * out circuit selection policy like this, we can't attach cell_ewma_t + * to the circuit_t any more, so we can't use SUBTYPE_P directly to a + * circuit_t like before; instead get it here. + */ + circuit_t *circ; +}; + +#define EWMA_POL_DATA_MAGIC 0x2fd8b16aU +#define EWMA_POL_CIRC_DATA_MAGIC 0x761e7747U + +/*** Downcasts for the above types ***/ + +/** + * Downcast a circuitmux_policy_data_t to an ewma_policy_data_t and assert + * if the cast is impossible. + */ + +static inline ewma_policy_data_t * +TO_EWMA_POL_DATA(circuitmux_policy_data_t *pol) +{ + if (!pol) return NULL; + else { + tor_assert(pol->magic == EWMA_POL_DATA_MAGIC); + return DOWNCAST(ewma_policy_data_t, pol); + } +} + +/** + * Downcast a circuitmux_policy_circ_data_t to an ewma_policy_circ_data_t + * and assert if the cast is impossible. + */ + +static inline ewma_policy_circ_data_t * +TO_EWMA_POL_CIRC_DATA(circuitmux_policy_circ_data_t *pol) +{ + if (!pol) return NULL; + else { + tor_assert(pol->magic == EWMA_POL_CIRC_DATA_MAGIC); + return DOWNCAST(ewma_policy_circ_data_t, pol); + } +} + STATIC unsigned cell_ewma_get_current_tick_and_fraction(double *remainder_out); STATIC void cell_ewma_initialize_ticks(void); -#endif + +#endif /* CIRCUITMUX_EWMA_PRIVATE */ #endif /* !defined(TOR_CIRCUITMUX_EWMA_H) */ From ceca6e7c3549bd80013df18da47381d3b1d6800f Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 28 Oct 2019 13:15:56 -0400 Subject: [PATCH 09/10] ewma: Implement unit tests At this commit, 93.9% of line coverage and 95.5% of function coverage. Closes #32196 Signed-off-by: David Goulet --- src/test/include.am | 1 + src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_circuitmux_ewma.c | 228 ++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+) create mode 100644 src/test/test_circuitmux_ewma.c diff --git a/src/test/include.am b/src/test/include.am index 294b9f309c..667bbf5368 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -123,6 +123,7 @@ src_test_test_SOURCES += \ src/test/test_checkdir.c \ src/test/test_circuitlist.c \ src/test/test_circuitmux.c \ + src/test/test_circuitmux_ewma.c \ src/test/test_circuitbuild.c \ src/test/test_circuituse.c \ src/test/test_circuitstats.c \ diff --git a/src/test/test.c b/src/test/test.c index 6dbec26fa8..c4227dca50 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -836,6 +836,7 @@ struct testgroup_t testgroups[] = { { "circuitpadding/", circuitpadding_tests }, { "circuitlist/", circuitlist_tests }, { "circuitmux/", circuitmux_tests }, + { "circuitmux_ewma/", circuitmux_ewma_tests }, { "circuitstats/", circuitstats_tests }, { "circuituse/", circuituse_tests }, { "compat/libevent/", compat_libevent_tests }, diff --git a/src/test/test.h b/src/test/test.h index feaa13a3a5..b2dc552c82 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -199,6 +199,7 @@ extern struct testcase_t checkdir_tests[]; extern struct testcase_t circuitbuild_tests[]; extern struct testcase_t circuitlist_tests[]; extern struct testcase_t circuitmux_tests[]; +extern struct testcase_t circuitmux_ewma_tests[]; extern struct testcase_t circuitstats_tests[]; extern struct testcase_t circuituse_tests[]; extern struct testcase_t compat_libevent_tests[]; diff --git a/src/test/test_circuitmux_ewma.c b/src/test/test_circuitmux_ewma.c new file mode 100644 index 0000000000..8b3edf2b06 --- /dev/null +++ b/src/test/test_circuitmux_ewma.c @@ -0,0 +1,228 @@ +/* Copyright (c) 2013-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CIRCUITMUX_PRIVATE +#define CIRCUITMUX_EWMA_PRIVATE + +#include "core/or/or.h" +#include "core/or/circuitmux.h" +#include "core/or/circuitmux_ewma.h" + +#include "test/fakechans.h" +#include "test/fakecircs.h" +#include "test/test.h" + +static void +test_cmux_ewma_active_circuit(void *arg) +{ + circuitmux_t cmux; /* garbage */ + circuitmux_policy_data_t *pol_data = NULL; + circuit_t circ; /* garbage */ + circuitmux_policy_circ_data_t *circ_data = NULL; + + (void) arg; + + pol_data = ewma_policy.alloc_cmux_data(&cmux); + tt_assert(pol_data); + circ_data = ewma_policy.alloc_circ_data(&cmux, pol_data, &circ, + CELL_DIRECTION_OUT, 42); + tt_assert(circ_data); + + /* Get EWMA specific objects. */ + + /* Make circuit active. */ + ewma_policy.notify_circ_active(&cmux, pol_data, &circ, circ_data); + + circuit_t *entry = ewma_policy.pick_active_circuit(&cmux, pol_data); + tt_mem_op(entry, OP_EQ, &circ, sizeof(circ)); + + done: + ewma_policy.free_circ_data(&cmux, pol_data, &circ, circ_data); + ewma_policy.free_cmux_data(&cmux, pol_data); +} + +static void +test_cmux_ewma_xmit_cell(void *arg) +{ + circuitmux_t cmux; /* garbage */ + circuitmux_policy_data_t *pol_data = NULL; + circuit_t circ; /* garbage */ + circuitmux_policy_circ_data_t *circ_data = NULL; + ewma_policy_data_t *ewma_pol_data; + ewma_policy_circ_data_t *ewma_data; + double old_cell_count; + + (void) arg; + + pol_data = ewma_policy.alloc_cmux_data(&cmux); + tt_assert(pol_data); + circ_data = ewma_policy.alloc_circ_data(&cmux, pol_data, &circ, + CELL_DIRECTION_OUT, 42); + tt_assert(circ_data); + ewma_pol_data = TO_EWMA_POL_DATA(pol_data); + ewma_data = TO_EWMA_POL_CIRC_DATA(circ_data); + + /* Make circuit active. */ + ewma_policy.notify_circ_active(&cmux, pol_data, &circ, circ_data); + + /* Move back in time the last time we calibrated so we scale the active + * circuit when emitting a cell. */ + ewma_pol_data->active_circuit_pqueue_last_recalibrated -= 100; + ewma_data->cell_ewma.last_adjusted_tick = + ewma_pol_data->active_circuit_pqueue_last_recalibrated; + + /* Grab old cell count. */ + old_cell_count = ewma_data->cell_ewma.cell_count; + + ewma_policy.notify_xmit_cells(&cmux, pol_data, &circ, circ_data, 1); + + /* Our old cell count should be lower to what we have since we just emitted + * a cell and thus we scale. */ + tt_double_op(old_cell_count, OP_LT, ewma_data->cell_ewma.cell_count); + + done: + ewma_policy.free_circ_data(&cmux, pol_data, &circ, circ_data); + ewma_policy.free_cmux_data(&cmux, pol_data); +} + +static void +test_cmux_ewma_notify_circ(void *arg) +{ + circuitmux_t cmux; /* garbage */ + circuitmux_policy_data_t *pol_data = NULL; + circuit_t circ; /* garbage */ + circuitmux_policy_circ_data_t *circ_data = NULL; + const ewma_policy_data_t *ewma_pol_data; + + (void) arg; + + pol_data = ewma_policy.alloc_cmux_data(&cmux); + tt_assert(pol_data); + circ_data = ewma_policy.alloc_circ_data(&cmux, pol_data, &circ, + CELL_DIRECTION_OUT, 42); + tt_assert(circ_data); + + /* Currently, notify_circ_active() ignores cmux and circ. They can not be + * NULL so it is fine to pass garbage. */ + ewma_policy.notify_circ_active(&cmux, pol_data, &circ, circ_data); + + /* We should have an active circuit in the queue so its EWMA value can be + * tracked. */ + ewma_pol_data = TO_EWMA_POL_DATA(pol_data); + tt_int_op(smartlist_len(ewma_pol_data->active_circuit_pqueue), OP_EQ, 1); + tt_uint_op(ewma_pol_data->active_circuit_pqueue_last_recalibrated, OP_NE, 0); + + ewma_policy.notify_circ_inactive(&cmux, pol_data, &circ, circ_data); + /* Should be removed from the active queue. */ + ewma_pol_data = TO_EWMA_POL_DATA(pol_data); + tt_int_op(smartlist_len(ewma_pol_data->active_circuit_pqueue), OP_EQ, 0); + tt_uint_op(ewma_pol_data->active_circuit_pqueue_last_recalibrated, OP_NE, 0); + + done: + ewma_policy.free_circ_data(&cmux, pol_data, &circ, circ_data); + ewma_policy.free_cmux_data(&cmux, pol_data); +} + +static void +test_cmux_ewma_policy_circ_data(void *arg) +{ + circuitmux_t cmux; /* garbage */ + circuitmux_policy_data_t pol_data; /* garbage */ + circuit_t circ; /* garbage */ + circuitmux_policy_circ_data_t *circ_data = NULL; + const ewma_policy_circ_data_t *ewma_data; + + (void) arg; + + /* Currently, alloc_circ_data() ignores every parameter _except_ the cell + * direction so it is OK to pass garbage. They can not be NULL. */ + circ_data = ewma_policy.alloc_circ_data(&cmux, &pol_data, &circ, + CELL_DIRECTION_OUT, 42); + tt_assert(circ_data); + tt_uint_op(circ_data->magic, OP_EQ, EWMA_POL_CIRC_DATA_MAGIC); + + ewma_data = TO_EWMA_POL_CIRC_DATA(circ_data); + tt_mem_op(ewma_data->circ, OP_EQ, &circ, sizeof(circuit_t)); + tt_double_op(ewma_data->cell_ewma.cell_count, OP_LE, 0.0); + tt_int_op(ewma_data->cell_ewma.heap_index, OP_EQ, -1); + tt_uint_op(ewma_data->cell_ewma.is_for_p_chan, OP_EQ, 0); + ewma_policy.free_circ_data(&cmux, &pol_data, &circ, circ_data); + + circ_data = ewma_policy.alloc_circ_data(&cmux, &pol_data, &circ, + CELL_DIRECTION_IN, 42); + tt_assert(circ_data); + tt_uint_op(circ_data->magic, OP_EQ, EWMA_POL_CIRC_DATA_MAGIC); + + ewma_data = TO_EWMA_POL_CIRC_DATA(circ_data); + tt_mem_op(ewma_data->circ, OP_EQ, &circ, sizeof(circuit_t)); + tt_double_op(ewma_data->cell_ewma.cell_count, OP_LE, 0.0); + tt_int_op(ewma_data->cell_ewma.heap_index, OP_EQ, -1); + tt_uint_op(ewma_data->cell_ewma.is_for_p_chan, OP_EQ, 1); + + done: + ewma_policy.free_circ_data(&cmux, &pol_data, &circ, circ_data); +} + +static void +test_cmux_ewma_policy_data(void *arg) +{ + circuitmux_t cmux; /* garbage. */ + circuitmux_policy_data_t *pol_data = NULL; + const ewma_policy_data_t *ewma_pol_data; + + (void) arg; + + pol_data = ewma_policy.alloc_cmux_data(&cmux); + tt_assert(pol_data); + tt_uint_op(pol_data->magic, OP_EQ, EWMA_POL_DATA_MAGIC); + + /* Test EWMA object. */ + ewma_pol_data = TO_EWMA_POL_DATA(pol_data); + tt_assert(ewma_pol_data->active_circuit_pqueue); + tt_uint_op(ewma_pol_data->active_circuit_pqueue_last_recalibrated, OP_NE, 0); + + done: + ewma_policy.free_cmux_data(&cmux, pol_data); +} + +static void * +cmux_ewma_setup_test(const struct testcase_t *tc) +{ + static int whatever; + + (void) tc; + + cell_ewma_initialize_ticks(); + cmux_ewma_set_options(NULL, NULL); + + return &whatever; +} + +static int +cmux_ewma_cleanup_test(const struct testcase_t *tc, void *ptr) +{ + (void) tc; + (void) ptr; + + circuitmux_ewma_free_all(); + + return 1; +} + +static struct testcase_setup_t cmux_ewma_test_setup = { + .setup_fn = cmux_ewma_setup_test, + .cleanup_fn = cmux_ewma_cleanup_test, +}; + +#define TEST_CMUX_EWMA(name) \ + { #name, test_cmux_ewma_##name, TT_FORK, &cmux_ewma_test_setup, NULL } + +struct testcase_t circuitmux_ewma_tests[] = { + TEST_CMUX_EWMA(active_circuit), + TEST_CMUX_EWMA(policy_data), + TEST_CMUX_EWMA(policy_circ_data), + TEST_CMUX_EWMA(notify_circ), + TEST_CMUX_EWMA(xmit_cell), + + END_OF_TESTCASES +}; From d67db64cedf9e7bb3e84c88a6f23d8bd7624ce29 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 28 Oct 2019 13:21:08 -0400 Subject: [PATCH 10/10] changes: Add file for ticket 32196 Signed-off-by: David Goulet --- changes/ticket32196 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/ticket32196 diff --git a/changes/ticket32196 b/changes/ticket32196 new file mode 100644 index 0000000000..d642478fe4 --- /dev/null +++ b/changes/ticket32196 @@ -0,0 +1,2 @@ + o Testing (circuit, EWMA): + - Add unit tests for circuitmux and EWMA subsystems. Closes ticket 32196.