/** Process a 'destroy' <b>cell</b> that just arrived from * <b>chan</b>. Find the circ that it refers to (if any). * * If the circ is in state * onionskin_pending, then call onion_pending_remove() to remove it * from the pending onion list (note that if it's already being * processed by the cpuworker, it won't be in the list anymore; but * when the cpuworker returns it, the circuit will be gone, and the * cpuworker response will be dropped). * * Then mark the circuit for close (which marks all edges for close, * and passes the destroy cell onward if necessary). */ static void command_process_destroy_cell(cell_t *cell, channel_t *chan) { circuit_t *circ; int reason; circ = circuit_get_by_circid_channel(cell->circ_id, chan); if (!circ) { log_info(LD_OR,"unknown circuit %d on connection from %s. Dropping.", cell->circ_id, channel_get_canonical_remote_descr(chan)); return; } log_debug(LD_OR,"Received for circID %d.",cell->circ_id); reason = (uint8_t)cell->payload[0]; if (!CIRCUIT_IS_ORIGIN(circ) && cell->circ_id == TO_OR_CIRCUIT(circ)->p_circ_id) { /* the destroy came from behind */ circuit_set_p_circid_chan(TO_OR_CIRCUIT(circ), 0, NULL); circuit_mark_for_close(circ, reason|END_CIRC_REASON_FLAG_REMOTE); } else { /* the destroy came from ahead */ circuit_set_n_circid_chan(circ, 0, NULL); if (CIRCUIT_IS_ORIGIN(circ)) { circuit_mark_for_close(circ, reason|END_CIRC_REASON_FLAG_REMOTE); } else { char payload[1]; log_debug(LD_OR, "Delivering 'truncated' back."); payload[0] = (char)reason; relay_send_command_from_edge(0, circ, RELAY_COMMAND_TRUNCATED, payload, sizeof(payload), NULL); } } }
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 test_clist_maps(void *arg) { channel_t *ch1 = new_fake_channel(); channel_t *ch2 = new_fake_channel(); channel_t *ch3 = new_fake_channel(); or_circuit_t *or_c1=NULL, *or_c2=NULL; (void) arg; MOCK(circuitmux_attach_circuit, circuitmux_attach_mock); MOCK(circuitmux_detach_circuit, circuitmux_detach_mock); memset(&cam, 0, sizeof(cam)); memset(&cdm, 0, sizeof(cdm)); tt_assert(ch1); tt_assert(ch2); tt_assert(ch3); ch1->cmux = tor_malloc(1); ch2->cmux = tor_malloc(1); ch3->cmux = tor_malloc(1); or_c1 = or_circuit_new(100, ch2); tt_assert(or_c1); GOT_CMUX_ATTACH(ch2->cmux, or_c1, CELL_DIRECTION_IN); tt_int_op(or_c1->p_circ_id, OP_EQ, 100); tt_ptr_op(or_c1->p_chan, OP_EQ, ch2); or_c2 = or_circuit_new(100, ch1); tt_assert(or_c2); GOT_CMUX_ATTACH(ch1->cmux, or_c2, CELL_DIRECTION_IN); tt_int_op(or_c2->p_circ_id, OP_EQ, 100); tt_ptr_op(or_c2->p_chan, OP_EQ, ch1); circuit_set_n_circid_chan(TO_CIRCUIT(or_c1), 200, ch1); GOT_CMUX_ATTACH(ch1->cmux, or_c1, CELL_DIRECTION_OUT); circuit_set_n_circid_chan(TO_CIRCUIT(or_c2), 200, ch2); GOT_CMUX_ATTACH(ch2->cmux, or_c2, CELL_DIRECTION_OUT); tt_ptr_op(circuit_get_by_circid_channel(200, ch1), OP_EQ, TO_CIRCUIT(or_c1)); tt_ptr_op(circuit_get_by_circid_channel(200, ch2), OP_EQ, TO_CIRCUIT(or_c2)); tt_ptr_op(circuit_get_by_circid_channel(100, ch2), OP_EQ, TO_CIRCUIT(or_c1)); /* Try the same thing again, to test the "fast" path. */ tt_ptr_op(circuit_get_by_circid_channel(100, ch2), OP_EQ, TO_CIRCUIT(or_c1)); tt_assert(circuit_id_in_use_on_channel(100, ch2)); tt_assert(! circuit_id_in_use_on_channel(101, ch2)); /* Try changing the circuitid and channel of that circuit. */ circuit_set_p_circid_chan(or_c1, 500, ch3); GOT_CMUX_DETACH(ch2->cmux, TO_CIRCUIT(or_c1)); GOT_CMUX_ATTACH(ch3->cmux, TO_CIRCUIT(or_c1), CELL_DIRECTION_IN); tt_ptr_op(circuit_get_by_circid_channel(100, ch2), OP_EQ, NULL); tt_assert(! circuit_id_in_use_on_channel(100, ch2)); tt_ptr_op(circuit_get_by_circid_channel(500, ch3), OP_EQ, TO_CIRCUIT(or_c1)); /* Now let's see about destroy handling. */ tt_assert(! circuit_id_in_use_on_channel(205, ch2)); tt_assert(circuit_id_in_use_on_channel(200, ch2)); channel_note_destroy_pending(ch2, 200); channel_note_destroy_pending(ch2, 205); channel_note_destroy_pending(ch1, 100); tt_assert(circuit_id_in_use_on_channel(205, ch2)) tt_assert(circuit_id_in_use_on_channel(200, ch2)); tt_assert(circuit_id_in_use_on_channel(100, ch1)); tt_assert(TO_CIRCUIT(or_c2)->n_delete_pending != 0); tt_ptr_op(circuit_get_by_circid_channel(200, ch2), OP_EQ, TO_CIRCUIT(or_c2)); tt_ptr_op(circuit_get_by_circid_channel(100, ch1), OP_EQ, TO_CIRCUIT(or_c2)); /* Okay, now free ch2 and make sure that the circuit ID is STILL not * usable, because we haven't declared the destroy to be nonpending */ tt_int_op(cdm.ncalls, OP_EQ, 0); circuit_free(TO_CIRCUIT(or_c2)); or_c2 = NULL; /* prevent free */ tt_int_op(cdm.ncalls, OP_EQ, 2); memset(&cdm, 0, sizeof(cdm)); tt_assert(circuit_id_in_use_on_channel(200, ch2)); tt_assert(circuit_id_in_use_on_channel(100, ch1)); tt_ptr_op(circuit_get_by_circid_channel(200, ch2), OP_EQ, NULL); tt_ptr_op(circuit_get_by_circid_channel(100, ch1), OP_EQ, NULL); /* Now say that the destroy is nonpending */ channel_note_destroy_not_pending(ch2, 200); tt_ptr_op(circuit_get_by_circid_channel(200, ch2), OP_EQ, NULL); channel_note_destroy_not_pending(ch1, 100); tt_ptr_op(circuit_get_by_circid_channel(100, ch1), OP_EQ, NULL); tt_assert(! circuit_id_in_use_on_channel(200, ch2)); tt_assert(! circuit_id_in_use_on_channel(100, ch1)); done: if (or_c1) circuit_free(TO_CIRCUIT(or_c1)); if (or_c2) circuit_free(TO_CIRCUIT(or_c2)); if (ch1) tor_free(ch1->cmux); if (ch2) tor_free(ch2->cmux); if (ch3) tor_free(ch3->cmux); tor_free(ch1); tor_free(ch2); tor_free(ch3); UNMOCK(circuitmux_attach_circuit); UNMOCK(circuitmux_detach_circuit); }
/* Test outbound cell. The callstack is: * channel_flush_some_cells() * -> channel_flush_from_first_active_circuit() * -> channel_write_packed_cell() * -> write_packed_cell() * -> chan->write_packed_cell() fct ptr. * * This test goes from a cell in a circuit up to the channel write handler * that should put them on the connection outbuf. */ static void test_channel_outbound_cell(void *arg) { int old_count; channel_t *chan = NULL; packed_cell_t *p_cell = NULL, *p_cell2 = NULL; origin_circuit_t *circ = NULL; cell_queue_t *queue; (void) arg; /* Set the test time to be mocked, since this test assumes that no * time will pass, ewma values will not need to be re-scaled, and so on */ monotime_enable_test_mocking(); monotime_set_mock_time_nsec(U64_LITERAL(1000000000) * 12345); cmux_ewma_set_options(NULL,NULL); /* The channel will be freed so we need to hijack this so the scheduler * doesn't get confused. */ MOCK(scheduler_release_channel, scheduler_release_channel_mock); /* Accept cells to lower layer */ test_chan_accept_cells = 1; /* Setup a valid circuit to queue a cell. */ circ = origin_circuit_new(); tt_assert(circ); /* Circuit needs an origin purpose to be considered origin. */ TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_C_GENERAL; TO_CIRCUIT(circ)->n_circ_id = 42; /* This is the outbound test so use the next channel queue. */ queue = &TO_CIRCUIT(circ)->n_chan_cells; /* Setup packed cell to queue on the circuit. */ p_cell = packed_cell_new(); tt_assert(p_cell); p_cell2 = packed_cell_new(); tt_assert(p_cell2); /* Setup a channel to put the circuit on. */ chan = new_fake_channel(); tt_assert(chan); chan->state = CHANNEL_STATE_OPENING; channel_change_state_open(chan); /* Outbound channel. */ channel_mark_outgoing(chan); /* Try to register it so we can clean it through the channel cleanup * process. */ channel_register(chan); tt_int_op(chan->registered, OP_EQ, 1); /* Set EWMA policy so we can pick it when flushing. */ circuitmux_set_policy(chan->cmux, &ewma_policy); tt_ptr_op(circuitmux_get_policy(chan->cmux), OP_EQ, &ewma_policy); /* Register circuit to the channel circid map which will attach the circuit * to the channel's cmux as well. */ circuit_set_n_circid_chan(TO_CIRCUIT(circ), 42, chan); tt_int_op(channel_num_circuits(chan), OP_EQ, 1); /* Test the cmux state. */ tt_ptr_op(TO_CIRCUIT(circ)->n_mux, OP_EQ, chan->cmux); tt_int_op(circuitmux_is_circuit_attached(chan->cmux, TO_CIRCUIT(circ)), OP_EQ, 1); /* Flush the channel without any cell on it. */ old_count = test_cells_written; ssize_t flushed = channel_flush_some_cells(chan, 1); tt_i64_op(flushed, OP_EQ, 0); tt_int_op(test_cells_written, OP_EQ, old_count); tt_int_op(channel_more_to_flush(chan), OP_EQ, 0); tt_int_op(circuitmux_num_active_circuits(chan->cmux), OP_EQ, 0); tt_int_op(circuitmux_num_cells(chan->cmux), OP_EQ, 0); tt_int_op(circuitmux_is_circuit_active(chan->cmux, TO_CIRCUIT(circ)), OP_EQ, 0); tt_u64_op(chan->n_cells_xmitted, OP_EQ, 0); tt_u64_op(chan->n_bytes_xmitted, OP_EQ, 0); /* Queue cell onto the next queue that is the outbound direction. Than * update its cmux so the circuit can be picked when flushing cells. */ cell_queue_append(queue, p_cell); p_cell = NULL; tt_int_op(queue->n, OP_EQ, 1); cell_queue_append(queue, p_cell2); p_cell2 = NULL; tt_int_op(queue->n, OP_EQ, 2); update_circuit_on_cmux(TO_CIRCUIT(circ), CELL_DIRECTION_OUT); tt_int_op(circuitmux_num_active_circuits(chan->cmux), OP_EQ, 1); tt_int_op(circuitmux_num_cells(chan->cmux), OP_EQ, 2); tt_int_op(circuitmux_is_circuit_active(chan->cmux, TO_CIRCUIT(circ)), OP_EQ, 1); /* From this point on, we have a queued cell on an active circuit attached * to the channel's cmux. */ /* Flush the first cell. This is going to go down the call stack. */ old_count = test_cells_written; flushed = channel_flush_some_cells(chan, 1); tt_i64_op(flushed, OP_EQ, 1); tt_int_op(test_cells_written, OP_EQ, old_count + 1); tt_int_op(circuitmux_num_cells(chan->cmux), OP_EQ, 1); tt_int_op(channel_more_to_flush(chan), OP_EQ, 1); /* Circuit should remain active because there is a second cell queued. */ tt_int_op(circuitmux_is_circuit_active(chan->cmux, TO_CIRCUIT(circ)), OP_EQ, 1); /* Should still be attached. */ tt_int_op(circuitmux_is_circuit_attached(chan->cmux, TO_CIRCUIT(circ)), OP_EQ, 1); tt_u64_op(chan->n_cells_xmitted, OP_EQ, 1); tt_u64_op(chan->n_bytes_xmitted, OP_EQ, get_cell_network_size(0)); /* Flush second cell. This is going to go down the call stack. */ old_count = test_cells_written; flushed = channel_flush_some_cells(chan, 1); tt_i64_op(flushed, OP_EQ, 1); tt_int_op(test_cells_written, OP_EQ, old_count + 1); tt_int_op(circuitmux_num_cells(chan->cmux), OP_EQ, 0); tt_int_op(channel_more_to_flush(chan), OP_EQ, 0); /* No more cells should make the circuit inactive. */ tt_int_op(circuitmux_is_circuit_active(chan->cmux, TO_CIRCUIT(circ)), OP_EQ, 0); /* Should still be attached. */ tt_int_op(circuitmux_is_circuit_attached(chan->cmux, TO_CIRCUIT(circ)), OP_EQ, 1); tt_u64_op(chan->n_cells_xmitted, OP_EQ, 2); tt_u64_op(chan->n_bytes_xmitted, OP_EQ, get_cell_network_size(0) * 2); done: if (circ) { circuit_free_(TO_CIRCUIT(circ)); } tor_free(p_cell); channel_free_all(); UNMOCK(scheduler_release_channel); monotime_disable_test_mocking(); }