aboutsummaryrefslogtreecommitdiff
path: root/src/or/relay.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/or/relay.c')
-rw-r--r--src/or/relay.c452
1 files changed, 289 insertions, 163 deletions
diff --git a/src/or/relay.c b/src/or/relay.c
index 1c791e02cc..4c1a8ed96d 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -1,30 +1,68 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file relay.c
* \brief Handle relay cell encryption/decryption, plus packaging and
* receiving from circuits, plus queuing on circuits.
+ *
+ * This is a core modules that makes Tor work. It's responsible for
+ * dealing with RELAY cells (the ones that travel more than one hop along a
+ * circuit), by:
+ * <ul>
+ * <li>constructing relays cells,
+ * <li>encrypting relay cells,
+ * <li>decrypting relay cells,
+ * <li>demultiplexing relay cells as they arrive on a connection,
+ * <li>queueing relay cells for retransmission,
+ * <li>or handling relay cells that are for us to receive (as an exit or a
+ * client).
+ * </ul>
+ *
+ * RELAY cells are generated throughout the code at the client or relay side,
+ * using relay_send_command_from_edge() or one of the functions like
+ * connection_edge_send_command() that calls it. Of particular interest is
+ * connection_edge_package_raw_inbuf(), which takes information that has
+ * arrived on an edge connection socket, and packages it as a RELAY_DATA cell
+ * -- this is how information is actually sent across the Tor network. The
+ * cryptography for these functions is handled deep in
+ * circuit_package_relay_cell(), which either adds a single layer of
+ * encryption (if we're an exit), or multiple layers (if we're the origin of
+ * the circuit). After construction and encryption, the RELAY cells are
+ * passed to append_cell_to_circuit_queue(), which queues them for
+ * transmission and tells the circuitmux (see circuitmux.c) that the circuit
+ * is waiting to send something.
+ *
+ * Incoming RELAY cells arrive at circuit_receive_relay_cell(), called from
+ * command.c. There they are decrypted and, if they are for us, are passed to
+ * connection_edge_process_relay_cell(). If they're not for us, they're
+ * re-queued for retransmission again with append_cell_to_circuit_queue().
+ *
+ * The connection_edge_process_relay_cell() function handles all the different
+ * types of relay cells, launching requests or transmitting data as needed.
**/
#define RELAY_PRIVATE
#include "or.h"
#include "addressmap.h"
+#include "backtrace.h"
#include "buffers.h"
#include "channel.h"
#include "circpathbias.h"
#include "circuitbuild.h"
#include "circuitlist.h"
#include "circuituse.h"
+#include "compress.h"
#include "config.h"
#include "connection.h"
#include "connection_edge.h"
#include "connection_or.h"
#include "control.h"
#include "geoip.h"
+#include "hs_cache.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -38,6 +76,7 @@
#include "routerlist.h"
#include "routerparse.h"
#include "scheduler.h"
+#include "rephist.h"
static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell,
cell_direction_t cell_direction,
@@ -60,9 +99,6 @@ static void adjust_exit_policy_from_exitpolicy_failure(origin_circuit_t *circ,
entry_connection_t *conn,
node_t *node,
const tor_addr_t *addr);
-#if 0
-static int get_max_middle_cells(void);
-#endif
/** Stop reading on edge connections when we have this many cells
* waiting on the appropriate queue. */
@@ -79,6 +115,9 @@ uint64_t stats_n_relay_cells_relayed = 0;
* hop?
*/
uint64_t stats_n_relay_cells_delivered = 0;
+/** Stats: how many circuits have we closed due to the cell queue limit being
+ * reached (see append_cell_to_circuit_queue()) */
+uint64_t stats_n_circ_max_cell_reached = 0;
/** Used to tell which stream to read from first on a circuit. */
static tor_weak_rng_t stream_choice_rng = TOR_WEAK_RNG_INIT;
@@ -146,18 +185,88 @@ relay_digest_matches(crypto_digest_t *digest, cell_t *cell)
/** Apply <b>cipher</b> to CELL_PAYLOAD_SIZE bytes of <b>in</b>
* (in place).
*
- * If <b>encrypt_mode</b> is 1 then encrypt, else decrypt.
- *
- * Returns 0.
+ * Note that we use the same operation for encrypting and for decrypting.
*/
-static int
-relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in,
- int encrypt_mode)
+static void
+relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in)
{
- (void)encrypt_mode;
crypto_cipher_crypt_inplace(cipher, (char*) in, CELL_PAYLOAD_SIZE);
+}
- return 0;
+/**
+ * Update channel usage state based on the type of relay cell and
+ * circuit properties.
+ *
+ * This is needed to determine if a client channel is being
+ * used for application traffic, and if a relay channel is being
+ * used for multihop circuits and application traffic. The decision
+ * to pad in channelpadding.c depends upon this info (as well as
+ * consensus parameters) to decide what channels to pad.
+ */
+static void
+circuit_update_channel_usage(circuit_t *circ, cell_t *cell)
+{
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ /*
+ * The client state was first set much earlier in
+ * circuit_send_next_onion_skin(), so we can start padding as early as
+ * possible.
+ *
+ * However, if padding turns out to be expensive, we may want to not do
+ * it until actual application traffic starts flowing (which is controlled
+ * via consensus param nf_pad_before_usage).
+ *
+ * So: If we're an origin circuit and we've created a full length circuit,
+ * then any CELL_RELAY cell means application data. Increase the usage
+ * state of the channel to indicate this.
+ *
+ * We want to wait for CELL_RELAY specifically here, so we know that
+ * the channel was definitely being used for data and not for extends.
+ * By default, we pad as soon as a channel has been used for *any*
+ * circuits, so this state is irrelevant to the padding decision in
+ * the default case. However, if padding turns out to be expensive,
+ * we would like the ability to avoid padding until we're absolutely
+ * sure that a channel is used for enough application data to be worth
+ * padding.
+ *
+ * (So it does not matter that CELL_RELAY_EARLY can actually contain
+ * application data. This is only a load reducing option and that edge
+ * case does not matter if we're desperately trying to reduce overhead
+ * anyway. See also consensus parameter nf_pad_before_usage).
+ */
+ if (BUG(!circ->n_chan))
+ return;
+
+ if (circ->n_chan->channel_usage == CHANNEL_USED_FOR_FULL_CIRCS &&
+ cell->command == CELL_RELAY) {
+ circ->n_chan->channel_usage = CHANNEL_USED_FOR_USER_TRAFFIC;
+ }
+ } else {
+ /* If we're a relay circuit, the question is more complicated. Basically:
+ * we only want to pad connections that carry multihop (anonymous)
+ * circuits.
+ *
+ * We assume we're more than one hop if either the previous hop
+ * is not a client, or if the previous hop is a client and there's
+ * a next hop. Then, circuit traffic starts at RELAY_EARLY, and
+ * user application traffic starts when we see RELAY cells.
+ */
+ or_circuit_t *or_circ = TO_OR_CIRCUIT(circ);
+
+ if (BUG(!or_circ->p_chan))
+ return;
+
+ if (!channel_is_client(or_circ->p_chan) ||
+ (channel_is_client(or_circ->p_chan) && circ->n_chan)) {
+ if (cell->command == CELL_RELAY_EARLY) {
+ if (or_circ->p_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) {
+ or_circ->p_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS;
+ }
+ } else if (cell->command == CELL_RELAY) {
+ or_circ->p_chan->channel_usage = CHANNEL_USED_FOR_USER_TRAFFIC;
+ }
+ }
+ }
}
/** Receive a relay cell:
@@ -189,10 +298,13 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
return 0;
if (relay_crypt(circ, cell, cell_direction, &layer_hint, &recognized) < 0) {
- log_warn(LD_BUG,"relay crypt failed. Dropping connection.");
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "relay crypt failed. Dropping connection.");
return -END_CIRC_REASON_INTERNAL;
}
+ circuit_update_channel_usage(circ, cell);
+
if (recognized) {
edge_connection_t *conn = NULL;
@@ -221,8 +333,13 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
log_debug(LD_OR,"Sending to origin.");
if ((reason = connection_edge_process_relay_cell(cell, circ, conn,
layer_hint)) < 0) {
- log_warn(LD_OR,
- "connection_edge_process_relay_cell (at origin) failed.");
+ /* If a client is trying to connect to unknown hidden service port,
+ * END_CIRC_AT_ORIGIN is sent back so we can then close the circuit.
+ * Do not log warn as this is an expected behavior for a service. */
+ if (reason != END_CIRC_AT_ORIGIN) {
+ log_warn(LD_OR,
+ "connection_edge_process_relay_cell (at origin) failed.");
+ }
return reason;
}
}
@@ -327,8 +444,8 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction,
do { /* Remember: cpath is in forward order, that is, first hop first. */
tor_assert(thishop);
- if (relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0)
- return -1;
+ /* decrypt one layer */
+ relay_crypt_one_payload(thishop->b_crypto, cell->payload);
relay_header_unpack(&rh, cell->payload);
if (rh.recognized == 0) {
@@ -345,19 +462,14 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction,
log_fn(LOG_PROTOCOL_WARN, LD_OR,
"Incoming cell at client not recognized. Closing.");
return -1;
- } else { /* we're in the middle. Just one crypt. */
- if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto,
- cell->payload, 1) < 0)
- return -1;
-// log_fn(LOG_DEBUG,"Skipping recognized check, because we're not "
-// "the client.");
+ } else {
+ /* We're in the middle. Encrypt one layer. */
+ relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, cell->payload);
}
} else /* cell_direction == CELL_DIRECTION_OUT */ {
- /* we're in the middle. Just one crypt. */
+ /* We're in the middle. Decrypt one layer. */
- if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto,
- cell->payload, 0) < 0)
- return -1;
+ relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, cell->payload);
relay_header_unpack(&rh, cell->payload);
if (rh.recognized == 0) {
@@ -393,12 +505,22 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
chan = circ->n_chan;
if (!chan) {
log_warn(LD_BUG,"outgoing relay cell sent from %s:%d has n_chan==NULL."
- " Dropping.", filename, lineno);
+ " Dropping. Circuit is in state %s (%d), and is "
+ "%smarked for close. (%s:%d, %d)", filename, lineno,
+ circuit_state_to_string(circ->state), circ->state,
+ circ->marked_for_close ? "" : "not ",
+ circ->marked_for_close_file?circ->marked_for_close_file:"",
+ circ->marked_for_close, circ->marked_for_close_reason);
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ circuit_log_path(LOG_WARN, LD_BUG, TO_ORIGIN_CIRCUIT(circ));
+ }
+ log_backtrace(LOG_WARN,LD_BUG,"");
return 0; /* just drop it */
}
if (!CIRCUIT_IS_ORIGIN(circ)) {
log_warn(LD_BUG,"outgoing relay cell sent from %s:%d on non-origin "
"circ. Dropping.", filename, lineno);
+ log_backtrace(LOG_WARN,LD_BUG,"");
return 0; /* just drop it */
}
@@ -408,11 +530,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
/* moving from farthest to nearest hop */
do {
tor_assert(thishop);
- /* XXXX RD This is a bug, right? */
- log_debug(LD_OR,"crypting a layer of the relay cell.");
- if (relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) {
- return -1;
- }
+ log_debug(LD_OR,"encrypting a layer of the relay cell.");
+ relay_crypt_one_payload(thishop->f_crypto, cell->payload);
thishop = thishop->prev;
} while (thishop != TO_ORIGIN_CIRCUIT(circ)->cpath->prev);
@@ -429,8 +548,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
or_circ = TO_OR_CIRCUIT(circ);
chan = or_circ->p_chan;
relay_set_digest(or_circ->p_digest, cell);
- if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0)
- return -1;
+ /* encrypt one layer */
+ relay_crypt_one_payload(or_circ->p_crypto, cell->payload);
}
++stats_n_relay_cells_relayed;
@@ -564,11 +683,11 @@ relay_command_to_string(uint8_t command)
* If you can't send the cell, mark the circuit for close and return -1. Else
* return 0.
*/
-int
-relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
- uint8_t relay_command, const char *payload,
- size_t payload_len, crypt_path_t *cpath_layer,
- const char *filename, int lineno)
+MOCK_IMPL(int,
+relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ,
+ uint8_t relay_command, const char *payload,
+ size_t payload_len, crypt_path_t *cpath_layer,
+ const char *filename, int lineno))
{
cell_t cell;
relay_header_t rh;
@@ -580,14 +699,14 @@ relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
memset(&cell, 0, sizeof(cell_t));
cell.command = CELL_RELAY;
- if (cpath_layer) {
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ tor_assert(cpath_layer);
cell.circ_id = circ->n_circ_id;
cell_direction = CELL_DIRECTION_OUT;
- } else if (! CIRCUIT_IS_ORIGIN(circ)) {
+ } else {
+ tor_assert(! cpath_layer);
cell.circ_id = TO_OR_CIRCUIT(circ)->p_circ_id;
cell_direction = CELL_DIRECTION_IN;
- } else {
- return -1;
}
memset(&rh, 0, sizeof(rh));
@@ -601,6 +720,9 @@ relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
log_debug(LD_OR,"delivering %d cell %s.", relay_command,
cell_direction == CELL_DIRECTION_OUT ? "forward" : "backward");
+ if (relay_command == RELAY_COMMAND_DROP)
+ rep_hist_padding_count_write(PADDING_TYPE_DROP);
+
/* If we are sending an END cell and this circuit is used for a tunneled
* directory request, advance its state. */
if (relay_command == RELAY_COMMAND_END && circ->dirreq_id)
@@ -707,6 +829,16 @@ connection_edge_send_command(edge_connection_t *fromconn,
return -1;
}
+#ifdef MEASUREMENTS_21206
+ /* Keep track of the number of RELAY_DATA cells sent for directory
+ * connections. */
+ connection_t *linked_conn = TO_CONN(fromconn)->linked_conn;
+
+ if (linked_conn && linked_conn->type == CONN_TYPE_DIR) {
+ ++(TO_DIR_CONN(linked_conn)->data_cells_sent);
+ }
+#endif /* defined(MEASUREMENTS_21206) */
+
return relay_send_command_from_edge(fromconn->stream_id, circ,
relay_command, payload,
payload_len, cpath_layer);
@@ -1026,7 +1158,7 @@ connected_cell_parse(const relay_header_t *rh, const cell_t *cell,
/** Drop all storage held by <b>addr</b>. */
STATIC void
-address_ttl_free(address_ttl_t *addr)
+address_ttl_free_(address_ttl_t *addr)
{
if (!addr)
return;
@@ -1358,8 +1490,9 @@ connection_edge_process_relay_cell_not_open(
circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ));
/* don't send a socks reply to transparent conns */
tor_assert(entry_conn->socks_request != NULL);
- if (!entry_conn->socks_request->has_finished)
+ if (!entry_conn->socks_request->has_finished) {
connection_ap_handshake_socks_reply(entry_conn, NULL, 0, 0);
+ }
/* Was it a linked dir conn? If so, a dir request just started to
* fetch something; this could be a bootstrap status milestone. */
@@ -1490,6 +1623,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
switch (rh.command) {
case RELAY_COMMAND_DROP:
+ rep_hist_padding_count_read(PADDING_TYPE_DROP);
// log_info(domain,"Got a relay-level padding cell. Dropping.");
return 0;
case RELAY_COMMAND_BEGIN:
@@ -1560,9 +1694,19 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
}
stats_n_data_bytes_received += rh.length;
- connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE),
+ connection_buf_add((char*)(cell->payload + RELAY_HEADER_SIZE),
rh.length, TO_CONN(conn));
+#ifdef MEASUREMENTS_21206
+ /* Count number of RELAY_DATA cells received on a linked directory
+ * connection. */
+ connection_t *linked_conn = TO_CONN(conn)->linked_conn;
+
+ if (linked_conn && linked_conn->type == CONN_TYPE_DIR) {
+ ++(TO_DIR_CONN(linked_conn)->data_cells_received);
+ }
+#endif /* defined(MEASUREMENTS_21206) */
+
if (!optimistic_data) {
/* Only send a SENDME if we're not getting optimistic data; otherwise
* a SENDME could arrive before the CONNECTED.
@@ -1918,13 +2062,13 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial,
/* XXXX We could be more efficient here by sometimes packing
* previously-sent optimistic data in the same cell with data
* from the inbuf. */
- fetch_from_buf(payload, length, entry_conn->sending_optimistic_data);
+ buf_get_bytes(entry_conn->sending_optimistic_data, payload, length);
if (!buf_datalen(entry_conn->sending_optimistic_data)) {
buf_free(entry_conn->sending_optimistic_data);
entry_conn->sending_optimistic_data = NULL;
}
} else {
- connection_fetch_from_buf(payload, length, TO_CONN(conn));
+ connection_buf_get_bytes(payload, length, TO_CONN(conn));
}
log_debug(domain,TOR_SOCKET_T_FORMAT": Packaging %d bytes (%d waiting).",
@@ -1936,7 +2080,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial,
retry */
if (!entry_conn->pending_optimistic_data)
entry_conn->pending_optimistic_data = buf_new();
- write_to_buf(payload, length, entry_conn->pending_optimistic_data);
+ buf_add(entry_conn->pending_optimistic_data, payload, length);
}
if (connection_edge_send_command(conn, RELAY_COMMAND_DATA,
@@ -2259,7 +2403,7 @@ circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint)
assert_circuit_mux_okay(chan)
#else
#define assert_cmux_ok_paranoid(chan)
-#endif
+#endif /* defined(ACTIVE_CIRCUITS_PARANOIA) */
/** The total number of cells we have allocated. */
static size_t total_cells_allocated = 0;
@@ -2282,7 +2426,7 @@ packed_cell_new(void)
/** Return a packed cell used outside by channel_t lower layer */
void
-packed_cell_free(packed_cell_t *cell)
+packed_cell_free_(packed_cell_t *cell)
{
if (!cell)
return;
@@ -2339,7 +2483,7 @@ cell_queue_append_packed_copy(circuit_t *circ, cell_queue_t *queue,
(void)exitward;
(void)use_stats;
- copy->inserted_time = (uint32_t) monotime_coarse_absolute_msec();
+ copy->inserted_timestamp = monotime_coarse_get_stamp();
cell_queue_append(queue, copy);
}
@@ -2422,7 +2566,7 @@ destroy_cell_queue_append(destroy_cell_queue_t *queue,
cell->circid = circid;
cell->reason = reason;
/* Not yet used, but will be required for OOM handling. */
- cell->inserted_time = (uint32_t) monotime_coarse_absolute_msec();
+ cell->inserted_timestamp = monotime_coarse_get_stamp();
TOR_SIMPLEQ_INSERT_TAIL(&queue->head, cell, next);
++queue->n;
@@ -2453,7 +2597,7 @@ packed_cell_mem_cost(void)
}
/* DOCDOC */
-STATIC size_t
+size_t
cell_queues_get_total_allocation(void)
{
return total_cells_allocated * packed_cell_mem_cost();
@@ -2473,7 +2617,7 @@ cell_queues_check_size(void)
time_t now = time(NULL);
size_t alloc = cell_queues_get_total_allocation();
alloc += buf_get_total_allocation();
- alloc += tor_zlib_get_total_allocation();
+ alloc += tor_compress_get_total_allocation();
const size_t rend_cache_total = rend_cache_get_total_allocation();
alloc += rend_cache_total;
const size_t geoip_client_cache_total =
@@ -2488,9 +2632,7 @@ cell_queues_check_size(void)
if (rend_cache_total > get_options()->MaxMemInQueues / 5) {
const size_t bytes_to_remove =
rend_cache_total - (size_t)(get_options()->MaxMemInQueues / 10);
- rend_cache_clean_v2_descs_as_dir(now, bytes_to_remove);
- alloc -= rend_cache_total;
- alloc += rend_cache_get_total_allocation();
+ alloc -= hs_cache_handle_oom(now, bytes_to_remove);
}
if (geoip_client_cache_total > get_options()->MaxMemInQueues / 5) {
const size_t bytes_to_remove =
@@ -2689,8 +2831,13 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
tor_assert(dcell);
/* frees dcell */
cell = destroy_cell_to_packed_cell(dcell, chan->wide_circ_ids);
- /* frees cell */
- channel_write_packed_cell(chan, cell);
+ /* Send the DESTROY cell. It is very unlikely that this fails but just
+ * in case, get rid of the channel. */
+ if (channel_write_packed_cell(chan, cell) < 0) {
+ /* The cell has been freed. */
+ channel_mark_for_close(chan);
+ continue;
+ }
/* Update the cmux destroy counter */
circuitmux_notify_xmit_destroy(cmux);
cell = NULL;
@@ -2733,9 +2880,10 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
/* Calculate the exact time that this cell has spent in the queue. */
if (get_options()->CellStatistics ||
get_options()->TestingEnableCellStatsEvent) {
- uint32_t msec_waiting;
- uint32_t msec_now = (uint32_t)monotime_coarse_absolute_msec();
- msec_waiting = msec_now - cell->inserted_time;
+ uint32_t timestamp_now = monotime_coarse_get_stamp();
+ uint32_t msec_waiting =
+ (uint32_t) monotime_coarse_stamp_units_to_approx_msec(
+ timestamp_now - cell->inserted_timestamp);
if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) {
or_circ = TO_OR_CIRCUIT(circ);
@@ -2766,8 +2914,13 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
DIRREQ_TUNNELED,
DIRREQ_CIRC_QUEUE_FLUSHED);
- /* Now send the cell */
- channel_write_packed_cell(chan, cell);
+ /* Now send the cell. It is very unlikely that this fails but just in
+ * case, get rid of the channel. */
+ if (channel_write_packed_cell(chan, cell) < 0) {
+ /* The cell has been freed at this point. */
+ channel_mark_for_close(chan);
+ continue;
+ }
cell = NULL;
/*
@@ -2802,22 +2955,67 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
return n_flushed;
}
-#if 0
-/** Indicate the current preferred cap for middle circuits; zero disables
- * the cap. Right now it's just a constant, ORCIRC_MAX_MIDDLE_CELLS, but
- * the logic in append_cell_to_circuit_queue() is written to be correct
- * if we want to base it on a consensus param or something that might change
- * in the future.
- */
-static int
-get_max_middle_cells(void)
+/* Minimum value is the maximum circuit window size.
+ *
+ * SENDME cells makes it that we can control how many cells can be inflight on
+ * a circuit from end to end. This logic makes it that on any circuit cell
+ * queue, we have a maximum of cells possible.
+ *
+ * Because the Tor protocol allows for a client to exit at any hop in a
+ * circuit and a circuit can be of a maximum of 8 hops, so in theory the
+ * normal worst case will be the circuit window start value times the maximum
+ * number of hops (8). Having more cells then that means something is wrong.
+ *
+ * However, because padding cells aren't counted in the package window, we set
+ * the maximum size to a reasonably large size for which we expect that we'll
+ * never reach in theory. And if we ever do because of future changes, we'll
+ * be able to control it with a consensus parameter.
+ *
+ * XXX: Unfortunately, END cells aren't accounted for in the circuit window
+ * which means that for instance if a client opens 8001 streams, the 8001
+ * following END cells will queue up in the circuit which will get closed if
+ * the max limit is 8000. Which is sad because it is allowed by the Tor
+ * protocol. But, we need an upper bound on circuit queue in order to avoid
+ * DoS memory pressure so the default size is a middle ground between not
+ * having any limit and having a very restricted one. This is why we can also
+ * control it through a consensus parameter. */
+#define RELAY_CIRC_CELL_QUEUE_SIZE_MIN CIRCWINDOW_START_MAX
+/* We can't have a consensus parameter above this value. */
+#define RELAY_CIRC_CELL_QUEUE_SIZE_MAX INT32_MAX
+/* Default value is set to a large value so we can handle padding cells
+ * properly which aren't accounted for in the SENDME window. Default is 50000
+ * allowed cells in the queue resulting in ~25MB. */
+#define RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT \
+ (50 * RELAY_CIRC_CELL_QUEUE_SIZE_MIN)
+
+/* The maximum number of cell a circuit queue can contain. This is updated at
+ * every new consensus and controlled by a parameter. */
+static int32_t max_circuit_cell_queue_size =
+ RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT;
+
+/* Called when the consensus has changed. At this stage, the global consensus
+ * object has NOT been updated. It is called from
+ * notify_before_networkstatus_changes(). */
+void
+relay_consensus_has_changed(const networkstatus_t *ns)
{
- return ORCIRC_MAX_MIDDLE_CELLS;
+ tor_assert(ns);
+
+ /* Update the circuit max cell queue size from the consensus. */
+ max_circuit_cell_queue_size =
+ networkstatus_get_param(ns, "circ_max_cell_queue_size",
+ RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT,
+ RELAY_CIRC_CELL_QUEUE_SIZE_MIN,
+ RELAY_CIRC_CELL_QUEUE_SIZE_MAX);
}
-#endif
/** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>chan</b>
- * transmitting in <b>direction</b>. */
+ * transmitting in <b>direction</b>.
+ *
+ * The given <b>cell</b> is copied over the circuit queue so the caller must
+ * cleanup the memory.
+ *
+ * This function is part of the fast path. */
void
append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan,
cell_t *cell, cell_direction_t direction,
@@ -2826,10 +3024,6 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan,
or_circuit_t *orcirc = NULL;
cell_queue_t *queue;
int streams_blocked;
-#if 0
- uint32_t tgt_max_middle_cells, p_len, n_len, tmp, hard_max_middle_cells;
-#endif
-
int exitward;
if (circ->marked_for_close)
return;
@@ -2844,93 +3038,25 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan,
streams_blocked = circ->streams_blocked_on_p_chan;
}
- /*
- * Disabling this for now because of a possible guard discovery attack
- */
-#if 0
- /* Are we a middle circuit about to exceed ORCIRC_MAX_MIDDLE_CELLS? */
- if ((circ->n_chan != NULL) && CIRCUIT_IS_ORCIRC(circ)) {
- orcirc = TO_OR_CIRCUIT(circ);
- if (orcirc->p_chan) {
- /* We are a middle circuit if we have both n_chan and p_chan */
- /* We'll need to know the current preferred maximum */
- tgt_max_middle_cells = get_max_middle_cells();
- if (tgt_max_middle_cells > 0) {
- /* Do we need to initialize middle_max_cells? */
- if (orcirc->max_middle_cells == 0) {
- orcirc->max_middle_cells = tgt_max_middle_cells;
- } else {
- if (tgt_max_middle_cells > orcirc->max_middle_cells) {
- /* If we want to increase the cap, we can do so right away */
- orcirc->max_middle_cells = tgt_max_middle_cells;
- } else if (tgt_max_middle_cells < orcirc->max_middle_cells) {
- /*
- * If we're shrinking the cap, we can't shrink past either queue;
- * compare tgt_max_middle_cells rather than tgt_max_middle_cells *
- * ORCIRC_MAX_MIDDLE_KILL_THRESH so the queues don't shrink enough
- * to generate spurious warnings, either.
- */
- n_len = circ->n_chan_cells.n;
- p_len = orcirc->p_chan_cells.n;
- tmp = tgt_max_middle_cells;
- if (tmp < n_len) tmp = n_len;
- if (tmp < p_len) tmp = p_len;
- orcirc->max_middle_cells = tmp;
- }
- /* else no change */
- }
- } else {
- /* tgt_max_middle_cells == 0 indicates we should disable the cap */
- orcirc->max_middle_cells = 0;
- }
-
- /* Now we know orcirc->max_middle_cells is set correctly */
- if (orcirc->max_middle_cells > 0) {
- hard_max_middle_cells =
- (uint32_t)(((double)orcirc->max_middle_cells) *
- ORCIRC_MAX_MIDDLE_KILL_THRESH);
-
- if ((unsigned)queue->n + 1 >= hard_max_middle_cells) {
- /* Queueing this cell would put queue over the kill theshold */
- log_warn(LD_CIRC,
- "Got a cell exceeding the hard cap of %u in the "
- "%s direction on middle circ ID %u on chan ID "
- U64_FORMAT "; killing the circuit.",
- hard_max_middle_cells,
- (direction == CELL_DIRECTION_OUT) ? "n" : "p",
- (direction == CELL_DIRECTION_OUT) ?
- circ->n_circ_id : orcirc->p_circ_id,
- U64_PRINTF_ARG(
- (direction == CELL_DIRECTION_OUT) ?
- circ->n_chan->global_identifier :
- orcirc->p_chan->global_identifier));
- circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT);
- return;
- } else if ((unsigned)queue->n + 1 == orcirc->max_middle_cells) {
- /* Only use ==, not >= for this test so we don't spam the log */
- log_warn(LD_CIRC,
- "While trying to queue a cell, reached the soft cap of %u "
- "in the %s direction on middle circ ID %u "
- "on chan ID " U64_FORMAT ".",
- orcirc->max_middle_cells,
- (direction == CELL_DIRECTION_OUT) ? "n" : "p",
- (direction == CELL_DIRECTION_OUT) ?
- circ->n_circ_id : orcirc->p_circ_id,
- U64_PRINTF_ARG(
- (direction == CELL_DIRECTION_OUT) ?
- circ->n_chan->global_identifier :
- orcirc->p_chan->global_identifier));
- }
- }
- }
+ if (PREDICT_UNLIKELY(queue->n >= max_circuit_cell_queue_size)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "%s circuit has %d cells in its queue, maximum allowed is %d. "
+ "Closing circuit for safety reasons.",
+ (exitward) ? "Outbound" : "Inbound", queue->n,
+ max_circuit_cell_queue_size);
+ circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT);
+ stats_n_circ_max_cell_reached++;
+ return;
}
-#endif
+ /* Very important that we copy to the circuit queue because all calls to
+ * this function use the stack for the cell memory. */
cell_queue_append_packed_copy(circ, queue, exitward, cell,
chan->wide_circ_ids, 1);
+ /* Check and run the OOM if needed. */
if (PREDICT_UNLIKELY(cell_queues_check_size())) {
- /* We ran the OOM handler */
+ /* We ran the OOM handler which might have closed this circuit. */
if (circ->marked_for_close)
return;
}