mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-12-03 00:53:32 +01:00
481 lines
14 KiB
C
481 lines
14 KiB
C
/* Copyright (c) 2013-2021, The Tor Project, Inc. */
|
|
/* See LICENSE for licensing information */
|
|
|
|
#define CHANNEL_OBJECT_PRIVATE
|
|
#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/fakechans.h"
|
|
#include "test/fakecircs.h"
|
|
#include "test/test.h"
|
|
|
|
#include <math.h>
|
|
|
|
static int
|
|
mock_has_queued_writes_true(channel_t *c)
|
|
{
|
|
(void) c;
|
|
return 1;
|
|
}
|
|
|
|
/** Test destroy cell queue with no interference from other queues. */
|
|
static void
|
|
test_cmux_destroy_cell_queue(void *arg)
|
|
{
|
|
circuitmux_t *cmux = NULL;
|
|
channel_t *ch = NULL;
|
|
circuit_t *circ = NULL;
|
|
destroy_cell_queue_t *cq = NULL;
|
|
packed_cell_t *pc = NULL;
|
|
destroy_cell_t *dc = NULL;
|
|
|
|
MOCK(scheduler_release_channel, scheduler_release_channel_mock);
|
|
|
|
(void) arg;
|
|
|
|
ch = new_fake_channel();
|
|
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);
|
|
tt_ptr_op(cq, OP_EQ, NULL);
|
|
|
|
circuitmux_append_destroy_cell(ch, cmux, 100, 10);
|
|
circuitmux_append_destroy_cell(ch, cmux, 190, 6);
|
|
circuitmux_append_destroy_cell(ch, cmux, 30, 1);
|
|
|
|
tt_int_op(circuitmux_num_cells(cmux), OP_EQ, 3);
|
|
|
|
circ = circuitmux_get_first_active_circuit(cmux, &cq);
|
|
tt_ptr_op(circ, OP_EQ, NULL);
|
|
tt_assert(cq);
|
|
|
|
tt_int_op(cq->n, OP_EQ, 3);
|
|
|
|
dc = destroy_cell_queue_pop(cq);
|
|
tt_assert(dc);
|
|
tt_uint_op(dc->circid, OP_EQ, 100);
|
|
|
|
tt_int_op(circuitmux_num_cells(cmux), OP_EQ, 2);
|
|
|
|
done:
|
|
free_fake_channel(ch);
|
|
packed_cell_free(pc);
|
|
tor_free(dc);
|
|
|
|
UNMOCK(scheduler_release_channel);
|
|
}
|
|
|
|
static void
|
|
test_cmux_compute_ticks(void *arg)
|
|
{
|
|
const int64_t NS_PER_S = 1000 * 1000 * 1000;
|
|
const int64_t START_NS = UINT64_C(1217709000)*NS_PER_S;
|
|
int64_t now;
|
|
double rem;
|
|
unsigned tick;
|
|
(void)arg;
|
|
circuitmux_ewma_free_all();
|
|
monotime_enable_test_mocking();
|
|
|
|
monotime_coarse_set_mock_time_nsec(START_NS);
|
|
cell_ewma_initialize_ticks();
|
|
const unsigned tick_zero = cell_ewma_get_current_tick_and_fraction(&rem);
|
|
tt_double_op(rem, OP_GT, -1e-9);
|
|
tt_double_op(rem, OP_LT, 1e-9);
|
|
|
|
/* 1.5 second later and we should still be in the same tick. */
|
|
now = START_NS + NS_PER_S + NS_PER_S/2;
|
|
monotime_coarse_set_mock_time_nsec(now);
|
|
tick = cell_ewma_get_current_tick_and_fraction(&rem);
|
|
tt_uint_op(tick, OP_EQ, tick_zero);
|
|
#ifdef USING_32BIT_MSEC_HACK
|
|
const double tolerance = .0005;
|
|
#else
|
|
const double tolerance = .00000001;
|
|
#endif
|
|
tt_double_op(fabs(rem - .15), OP_LT, tolerance);
|
|
|
|
/* 25 second later and we should be in another tick. */
|
|
now = START_NS + NS_PER_S * 25;
|
|
monotime_coarse_set_mock_time_nsec(now);
|
|
tick = cell_ewma_get_current_tick_and_fraction(&rem);
|
|
tt_uint_op(tick, OP_EQ, tick_zero + 2);
|
|
tt_double_op(fabs(rem - .5), OP_LT, tolerance);
|
|
|
|
done:
|
|
;
|
|
}
|
|
|
|
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_i64_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
|
|
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 subsystem 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_i64_op(pchan->cmux->destroy_ctr, OP_EQ, 1);
|
|
tt_int_op(pchan->cmux->destroy_cell_queue.n, OP_EQ, 1);
|
|
tt_i64_op(circuitmux_count_queued_destroy_cells(pchan, pchan->cmux),
|
|
OP_EQ, 1);
|
|
|
|
/* Emit the DESTROY cell. */
|
|
circuitmux_notify_xmit_destroy(pchan->cmux);
|
|
tt_i64_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)
|
|
{
|
|
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[] = {
|
|
/* 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),
|
|
TEST_CMUX(destroy_cell_queue),
|
|
|
|
END_OF_TESTCASES
|
|
};
|