aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/ticket262886
-rw-r--r--scripts/maint/practracker/exceptions.txt10
-rw-r--r--src/core/crypto/relay_crypto.c30
-rw-r--r--src/core/crypto/relay_crypto.h3
-rw-r--r--src/core/include.am2
-rw-r--r--src/core/or/circuit_st.h20
-rw-r--r--src/core/or/circuitlist.c6
-rw-r--r--src/core/or/connection_edge.c24
-rw-r--r--src/core/or/connection_edge.h1
-rw-r--r--src/core/or/relay.c344
-rw-r--r--src/core/or/relay.h1
-rw-r--r--src/core/or/relay_crypto_st.h2
-rw-r--r--src/core/or/sendme.c604
-rw-r--r--src/core/or/sendme.h68
-rw-r--r--src/test/include.am1
-rw-r--r--src/test/test.c1
-rw-r--r--src/test/test.h1
-rw-r--r--src/test/test_sendme.c267
-rw-r--r--src/trunnel/include.am3
-rw-r--r--src/trunnel/sendme.c347
-rw-r--r--src/trunnel/sendme.h101
-rw-r--r--src/trunnel/sendme.trunnel19
22 files changed, 1676 insertions, 185 deletions
diff --git a/changes/ticket26288 b/changes/ticket26288
new file mode 100644
index 0000000000..59bb856dd2
--- /dev/null
+++ b/changes/ticket26288
@@ -0,0 +1,6 @@
+ o Major features (flow control):
+ - Implement authenticated SENDMEs detailed in proposal 289. A SENDME cell
+ now includes the digest of the last cell received so once the end point
+ receives the SENDME, it can confirm the other side's knowledge of the
+ previous cells that were sent. This behavior is controlled by two new
+ consensus parameters, see proposal for more details. Fixes ticket 26288.
diff --git a/scripts/maint/practracker/exceptions.txt b/scripts/maint/practracker/exceptions.txt
index c41b0a0b10..9b4c03a72f 100644
--- a/scripts/maint/practracker/exceptions.txt
+++ b/scripts/maint/practracker/exceptions.txt
@@ -85,7 +85,7 @@ problem function-size /src/core/or/circuitbuild.c:circuit_extend() 147
problem function-size /src/core/or/circuitbuild.c:choose_good_exit_server_general() 206
problem include-count /src/core/or/circuitlist.c 54
problem function-size /src/core/or/circuitlist.c:HT_PROTOTYPE() 128
-problem function-size /src/core/or/circuitlist.c:circuit_free_() 137
+problem function-size /src/core/or/circuitlist.c:circuit_free_() 143
problem function-size /src/core/or/circuitlist.c:circuit_find_to_cannibalize() 102
problem function-size /src/core/or/circuitlist.c:circuit_about_to_free() 120
problem function-size /src/core/or/circuitlist.c:circuits_handle_oom() 117
@@ -102,8 +102,8 @@ problem function-size /src/core/or/circuituse.c:circuit_get_open_circ_or_launch(
problem function-size /src/core/or/circuituse.c:connection_ap_handshake_attach_circuit() 244
problem function-size /src/core/or/command.c:command_process_create_cell() 156
problem function-size /src/core/or/command.c:command_process_relay_cell() 132
-problem file-size /src/core/or/connection_edge.c 4575
-problem include-count /src/core/or/connection_edge.c 64
+problem file-size /src/core/or/connection_edge.c 4595
+problem include-count /src/core/or/connection_edge.c 65
problem function-size /src/core/or/connection_edge.c:connection_ap_expire_beginning() 117
problem function-size /src/core/or/connection_edge.c:connection_ap_handshake_rewrite() 192
problem function-size /src/core/or/connection_edge.c:connection_ap_handle_onion() 188
@@ -122,11 +122,11 @@ problem function-size /src/core/or/policies.c:policy_summarize() 107
problem function-size /src/core/or/protover.c:protover_all_supported() 117
problem file-size /src/core/or/relay.c 3173
problem function-size /src/core/or/relay.c:circuit_receive_relay_cell() 123
-problem function-size /src/core/or/relay.c:relay_send_command_from_edge_() 101
+problem function-size /src/core/or/relay.c:relay_send_command_from_edge_() 112
problem function-size /src/core/or/relay.c:connection_ap_process_end_not_open() 194
problem function-size /src/core/or/relay.c:connection_edge_process_relay_cell_not_open() 139
problem function-size /src/core/or/relay.c:connection_edge_process_relay_cell() 520
-problem function-size /src/core/or/relay.c:connection_edge_package_raw_inbuf() 130
+problem function-size /src/core/or/relay.c:connection_edge_package_raw_inbuf() 132
problem function-size /src/core/or/relay.c:circuit_resume_edge_reading_helper() 148
problem function-size /src/core/or/scheduler_kist.c:kist_scheduler_run() 171
problem function-size /src/core/or/scheduler_vanilla.c:vanilla_scheduler_run() 109
diff --git a/src/core/crypto/relay_crypto.c b/src/core/crypto/relay_crypto.c
index 0b83b2d0a5..8931163161 100644
--- a/src/core/crypto/relay_crypto.c
+++ b/src/core/crypto/relay_crypto.c
@@ -12,6 +12,7 @@
#include "core/crypto/hs_ntor.h" // for HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN
#include "core/or/relay.h"
#include "core/crypto/relay_crypto.h"
+#include "core/or/sendme.h"
#include "core/or/cell_st.h"
#include "core/or/or_circuit_st.h"
@@ -90,6 +91,23 @@ relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in)
crypto_cipher_crypt_inplace(cipher, (char*) in, CELL_PAYLOAD_SIZE);
}
+/** Return the sendme_digest within the <b>crypto</b> object. */
+uint8_t *
+relay_crypto_get_sendme_digest(relay_crypto_t *crypto)
+{
+ tor_assert(crypto);
+ return crypto->sendme_digest;
+}
+
+/** Record the b_digest from <b>crypto</b> and put it in the sendme_digest. */
+void
+relay_crypto_record_sendme_digest(relay_crypto_t *crypto)
+{
+ tor_assert(crypto);
+ crypto_digest_get_digest(crypto->b_digest, (char *) crypto->sendme_digest,
+ sizeof(crypto->sendme_digest));
+}
+
/** Do the appropriate en/decryptions for <b>cell</b> arriving on
* <b>circ</b> in direction <b>cell_direction</b>.
*
@@ -142,6 +160,11 @@ relay_decrypt_cell(circuit_t *circ, cell_t *cell,
if (relay_digest_matches(thishop->crypto.b_digest, cell)) {
*recognized = 1;
*layer_hint = thishop;
+ /* This cell is for us. Keep a record of this cell because we will
+ * use it in the next SENDME cell. */
+ if (sendme_circuit_cell_is_next(thishop->deliver_window)) {
+ sendme_circuit_record_inbound_cell(thishop);
+ }
return 0;
}
}
@@ -212,6 +235,13 @@ relay_encrypt_cell_inbound(cell_t *cell,
or_circuit_t *or_circ)
{
relay_set_digest(or_circ->crypto.b_digest, cell);
+
+ /* We are about to send this cell outbound on the circuit. Keep a record of
+ * this cell if we are expecting that the next cell is a SENDME. */
+ if (sendme_circuit_cell_is_next(TO_CIRCUIT(or_circ)->package_window)) {
+ sendme_circuit_record_outbound_cell(or_circ);
+ }
+
/* encrypt one layer */
relay_crypt_one_payload(or_circ->crypto.b_crypto, cell->payload);
}
diff --git a/src/core/crypto/relay_crypto.h b/src/core/crypto/relay_crypto.h
index 45a21d14ab..bcc1531838 100644
--- a/src/core/crypto/relay_crypto.h
+++ b/src/core/crypto/relay_crypto.h
@@ -27,5 +27,8 @@ void relay_crypto_clear(relay_crypto_t *crypto);
void relay_crypto_assert_ok(const relay_crypto_t *crypto);
+uint8_t *relay_crypto_get_sendme_digest(relay_crypto_t *crypto);
+void relay_crypto_record_sendme_digest(relay_crypto_t *crypto);
+
#endif /* !defined(TOR_RELAY_CRYPTO_H) */
diff --git a/src/core/include.am b/src/core/include.am
index 4796248a0f..c228735f1b 100644
--- a/src/core/include.am
+++ b/src/core/include.am
@@ -54,6 +54,7 @@ LIBTOR_APP_A_SOURCES = \
src/core/or/scheduler.c \
src/core/or/scheduler_kist.c \
src/core/or/scheduler_vanilla.c \
+ src/core/or/sendme.c \
src/core/or/status.c \
src/core/or/versions.c \
src/core/proto/proto_cell.c \
@@ -274,6 +275,7 @@ noinst_HEADERS += \
src/core/or/relay.h \
src/core/or/relay_crypto_st.h \
src/core/or/scheduler.h \
+ src/core/or/sendme.h \
src/core/or/server_port_cfg_st.h \
src/core/or/socks_request_st.h \
src/core/or/status.h \
diff --git a/src/core/or/circuit_st.h b/src/core/or/circuit_st.h
index cc21cf62f7..a68547ecb1 100644
--- a/src/core/or/circuit_st.h
+++ b/src/core/or/circuit_st.h
@@ -104,6 +104,26 @@ struct circuit_t {
* circuit-level sendme cells to indicate that we're willing to accept
* more. */
int deliver_window;
+ /** FIFO containing the digest of the cells that are just before a SENDME is
+ * sent by the client. It is done at the last cell before our package_window
+ * goes down to 0 which is when we expect a SENDME.
+ *
+ * Our current circuit package window is capped to 1000
+ * (CIRCWINDOW_START_MAX) which is also the start value. The increment is
+ * set to 100 (CIRCWINDOW_INCREMENT) which means we don't allow more than
+ * 1000/100 = 10 outstanding SENDME cells worth of data. Meaning that this
+ * list can not contain more than 10 digests of DIGEST_LEN bytes (20).
+ *
+ * At position i in the list, the digest corresponds to the
+ * ((CIRCWINDOW_INCREMENT * i) - 1)-nth cell received since we expect the
+ * (CIRCWINDOW_INCREMENT * i)-nth cell to be the SENDME and thus containing
+ * the previous cell digest.
+ *
+ * For example, position 2 (starting at 0) means that we've received 299
+ * cells and the 299th cell digest is kept at index 2.
+ *
+ * At maximum, this list contains 200 bytes plus the smartlist overhead. */
+ smartlist_t *sendme_last_digests;
/** Temporary field used during circuits_handle_oom. */
uint32_t age_tmp;
diff --git a/src/core/or/circuitlist.c b/src/core/or/circuitlist.c
index afbde06434..6428cdb8a7 100644
--- a/src/core/or/circuitlist.c
+++ b/src/core/or/circuitlist.c
@@ -1227,6 +1227,12 @@ circuit_free_(circuit_t *circ)
* "active" checks will be violated. */
cell_queue_clear(&circ->n_chan_cells);
+ /* Cleanup possible SENDME state. */
+ if (circ->sendme_last_digests) {
+ SMARTLIST_FOREACH(circ->sendme_last_digests, uint8_t *, d, tor_free(d));
+ smartlist_free(circ->sendme_last_digests);
+ }
+
log_info(LD_CIRC, "Circuit %u (id: %" PRIu32 ") has been freed.",
n_circ_id,
CIRCUIT_IS_ORIGIN(circ) ?
diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c
index 33ba723971..8ed4034560 100644
--- a/src/core/or/connection_edge.c
+++ b/src/core/or/connection_edge.c
@@ -73,6 +73,7 @@
#include "core/or/policies.h"
#include "core/or/reasons.h"
#include "core/or/relay.h"
+#include "core/or/sendme.h"
#include "core/proto/proto_http.h"
#include "core/proto/proto_socks.h"
#include "feature/client/addressmap.h"
@@ -767,7 +768,7 @@ connection_edge_flushed_some(edge_connection_t *conn)
/* falls through. */
case EXIT_CONN_STATE_OPEN:
- connection_edge_consider_sending_sendme(conn);
+ sendme_connection_edge_consider_sending(conn);
break;
}
return 0;
@@ -791,7 +792,7 @@ connection_edge_finished_flushing(edge_connection_t *conn)
switch (conn->base_.state) {
case AP_CONN_STATE_OPEN:
case EXIT_CONN_STATE_OPEN:
- connection_edge_consider_sending_sendme(conn);
+ sendme_connection_edge_consider_sending(conn);
return 0;
case AP_CONN_STATE_SOCKS_WAIT:
case AP_CONN_STATE_NATD_WAIT:
@@ -4564,6 +4565,25 @@ circuit_clear_isolation(origin_circuit_t *circ)
circ->socks_username_len = circ->socks_password_len = 0;
}
+/** Send an END and mark for close the given edge connection conn using the
+ * given reason that has to be a stream reason.
+ *
+ * Note: We don't unattached the AP connection (if applicable) because we
+ * don't want to flush the remaining data. This function aims at ending
+ * everything quickly regardless of the connection state.
+ *
+ * This function can't fail and does nothing if conn is NULL. */
+void
+connection_edge_end_close(edge_connection_t *conn, uint8_t reason)
+{
+ if (!conn) {
+ return;
+ }
+
+ connection_edge_end(conn, reason);
+ connection_mark_for_close(TO_CONN(conn));
+}
+
/** Free all storage held in module-scoped variables for connection_edge.c */
void
connection_edge_free_all(void)
diff --git a/src/core/or/connection_edge.h b/src/core/or/connection_edge.h
index 68d8b19a11..e82b6bd765 100644
--- a/src/core/or/connection_edge.h
+++ b/src/core/or/connection_edge.h
@@ -80,6 +80,7 @@ int connection_edge_process_inbuf(edge_connection_t *conn,
int connection_edge_destroy(circid_t circ_id, edge_connection_t *conn);
int connection_edge_end(edge_connection_t *conn, uint8_t reason);
int connection_edge_end_errno(edge_connection_t *conn);
+void connection_edge_end_close(edge_connection_t *conn, uint8_t reason);
int connection_edge_flushed_some(edge_connection_t *conn);
int connection_edge_finished_flushing(edge_connection_t *conn);
int connection_edge_finished_connecting(edge_connection_t *conn);
diff --git a/src/core/or/relay.c b/src/core/or/relay.c
index a166904a5f..1b2aafb866 100644
--- a/src/core/or/relay.c
+++ b/src/core/or/relay.c
@@ -93,13 +93,12 @@
#include "core/or/origin_circuit_st.h"
#include "feature/nodelist/routerinfo_st.h"
#include "core/or/socks_request_st.h"
+#include "core/or/sendme.h"
static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell,
cell_direction_t cell_direction,
crypt_path_t *layer_hint);
-static void circuit_consider_sending_sendme(circuit_t *circ,
- crypt_path_t *layer_hint);
static void circuit_resume_edge_reading(circuit_t *circ,
crypt_path_t *layer_hint);
static int circuit_resume_edge_reading_helper(edge_connection_t *conn,
@@ -530,6 +529,60 @@ relay_command_to_string(uint8_t command)
}
}
+/** Return the offset where the padding should start. The <b>data_len</b> is
+ * the relay payload length expected to be put in the cell. It can not be
+ * bigger than RELAY_PAYLOAD_SIZE else this function assert().
+ *
+ * Value will always be smaller than CELL_PAYLOAD_SIZE because this offset is
+ * for the entire cell length not just the data payload length. Zero is
+ * returned if there is no room for padding.
+ *
+ * This function always skips the first 4 bytes after the payload because
+ * having some unused zero bytes has saved us a lot of times in the past. */
+
+STATIC size_t
+get_pad_cell_offset(size_t data_len)
+{
+ /* This is never suppose to happen but in case it does, stop right away
+ * because if tor is tricked somehow into not adding random bytes to the
+ * payload with this function returning 0 for a bad data_len, the entire
+ * authenticated SENDME design can be bypassed leading to bad denial of
+ * service attacks. */
+ tor_assert(data_len <= RELAY_PAYLOAD_SIZE);
+
+ /* If the offset is larger than the cell payload size, we return an offset
+ * of zero indicating that no padding needs to be added. */
+ size_t offset = RELAY_HEADER_SIZE + data_len + 4;
+ if (offset >= CELL_PAYLOAD_SIZE) {
+ return 0;
+ }
+ return offset;
+}
+
+/* Add random bytes to the unused portion of the payload, to foil attacks
+ * where the other side can predict all of the bytes in the payload and thus
+ * compute the authenticated SENDME cells without seeing the traffic. See
+ * proposal 289. */
+static void
+pad_cell_payload(uint8_t *cell_payload, size_t data_len)
+{
+ size_t pad_offset, pad_len;
+
+ tor_assert(cell_payload);
+
+ pad_offset = get_pad_cell_offset(data_len);
+ if (pad_offset == 0) {
+ /* We can't add padding so we are done. */
+ return;
+ }
+
+ /* Remember here that the cell_payload is the length of the header and
+ * payload size so we offset it using the full lenght of the cell. */
+ pad_len = CELL_PAYLOAD_SIZE - pad_offset;
+ crypto_fast_rng_getbytes(get_thread_fast_rng(),
+ cell_payload + pad_offset, pad_len);
+}
+
/** Make a relay cell out of <b>relay_command</b> and <b>payload</b>, and send
* it onto the open circuit <b>circ</b>. <b>stream_id</b> is the ID on
* <b>circ</b> for the stream that's sending the relay cell, or 0 if it's a
@@ -573,6 +626,9 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ,
if (payload_len)
memcpy(cell.payload+RELAY_HEADER_SIZE, payload, payload_len);
+ /* Add random padding to the cell if we can. */
+ pad_cell_payload(cell.payload, payload_len);
+
log_debug(LD_OR,"delivering %d cell %s.", relay_command,
cell_direction == CELL_DIRECTION_OUT ? "forward" : "backward");
@@ -640,6 +696,14 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ,
circuit_mark_for_close(circ, END_CIRC_REASON_INTERNAL);
return -1;
}
+
+ /* If applicable, note the cell digest for the SENDME version 1 purpose if
+ * we need to. This call needs to be after the circuit_package_relay_cell()
+ * because the cell digest is set within that function. */
+ if (relay_command == RELAY_COMMAND_DATA) {
+ sendme_record_cell_digest(circ);
+ }
+
return 0;
}
@@ -1429,6 +1493,81 @@ connection_edge_process_relay_cell_not_open(
// return -1;
}
+/** Process a SENDME cell that arrived on <b>circ</b>. If it is a stream level
+ * cell, it is destined for the given <b>conn</b>. If it is a circuit level
+ * cell, it is destined for the <b>layer_hint</b>. The <b>domain</b> is the
+ * logging domain that should be used.
+ *
+ * Return 0 if everything went well or a negative value representing a circuit
+ * end reason on error for which the caller is responsible for closing it. */
+static int
+process_sendme_cell(const relay_header_t *rh, const cell_t *cell,
+ circuit_t *circ, edge_connection_t *conn,
+ crypt_path_t *layer_hint, int domain)
+{
+ int ret;
+
+ tor_assert(rh);
+
+ if (!rh->stream_id) {
+ /* Circuit level SENDME cell. */
+ ret = sendme_process_circuit_level(layer_hint, circ,
+ cell->payload + RELAY_HEADER_SIZE,
+ rh->length);
+ if (ret < 0) {
+ return ret;
+ }
+ /* Resume reading on any streams now that we've processed a valid
+ * SENDME cell that updated our package window. */
+ circuit_resume_edge_reading(circ, layer_hint);
+ /* We are done, the rest of the code is for the stream level. */
+ return 0;
+ }
+
+ /* No connection, might be half edge state. We are done if so. */
+ if (!conn) {
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
+ if (connection_half_edge_is_valid_sendme(ocirc->half_streams,
+ rh->stream_id)) {
+ circuit_read_valid_data(ocirc, rh->length);
+ log_info(domain, "Sendme cell on circ %u valid on half-closed "
+ "stream id %d",
+ ocirc->global_identifier, rh->stream_id);
+ }
+ }
+
+ log_info(domain, "SENDME cell dropped, unknown stream (streamid %d).",
+ rh->stream_id);
+ return 0;
+ }
+
+ /* Stream level SENDME cell. */
+ ret = sendme_process_stream_level(conn, circ, rh->length);
+ if (ret < 0) {
+ /* Means we need to close the circuit with reason ret. */
+ return ret;
+ }
+
+ /* We've now processed properly a SENDME cell, all windows have been
+ * properly updated, we'll read on the edge connection to see if we can
+ * get data out towards the end point (Exit or client) since we are now
+ * allowed to deliver more cells. */
+
+ if (circuit_queue_streams_are_blocked(circ)) {
+ /* Still waiting for queue to flush; don't touch conn */
+ return 0;
+ }
+ connection_start_reading(TO_CONN(conn));
+ /* handle whatever might still be on the inbuf */
+ if (connection_edge_package_raw_inbuf(conn, 1, NULL) < 0) {
+ /* (We already sent an end cell if possible) */
+ connection_mark_for_close(TO_CONN(conn));
+ return 0;
+ }
+ return 0;
+}
+
/** An incoming relay cell has arrived on circuit <b>circ</b>. If
* <b>conn</b> is NULL this is a control cell, else <b>cell</b> is
* destined for <b>conn</b>.
@@ -1549,22 +1688,19 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
return connection_exit_begin_conn(cell, circ);
case RELAY_COMMAND_DATA:
++stats_n_data_cells_received;
- if (( layer_hint && --layer_hint->deliver_window < 0) ||
- (!layer_hint && --circ->deliver_window < 0)) {
+
+ /* Update our circuit-level deliver window that we received a DATA cell.
+ * If the deliver window goes below 0, we end the circuit and stream due
+ * to a protocol failure. */
+ if (sendme_circuit_data_received(circ, layer_hint) < 0) {
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"(relay data) circ deliver_window below 0. Killing.");
- if (conn) {
- /* XXXX Do we actually need to do this? Will killing the circuit
- * not send an END and mark the stream for close as appropriate? */
- connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL);
- connection_mark_for_close(TO_CONN(conn));
- }
+ connection_edge_end_close(conn, END_STREAM_REASON_TORPROTOCOL);
return -END_CIRC_REASON_TORPROTOCOL;
}
- log_debug(domain,"circ deliver_window now %d.", layer_hint ?
- layer_hint->deliver_window : circ->deliver_window);
- circuit_consider_sending_sendme(circ, layer_hint);
+ /* Consider sending a circuit-level SENDME cell. */
+ sendme_circuit_consider_sending(circ, layer_hint);
if (rh.stream_id == 0) {
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Relay data cell with zero "
@@ -1587,9 +1723,14 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
return 0;
}
- if (--conn->deliver_window < 0) { /* is it below 0 after decrement? */
+ /* Update our stream-level deliver window that we just received a DATA
+ * cell. Going below 0 means we have a protocol level error so the
+ * stream and circuit are closed. */
+
+ if (sendme_stream_data_received(conn) < 0) {
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"(relay data) conn deliver_window below 0. Killing.");
+ connection_edge_end_close(conn, END_STREAM_REASON_TORPROTOCOL);
return -END_CIRC_REASON_TORPROTOCOL;
}
/* Total all valid application bytes delivered */
@@ -1615,7 +1756,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
/* Only send a SENDME if we're not getting optimistic data; otherwise
* a SENDME could arrive before the CONNECTED.
*/
- connection_edge_consider_sending_sendme(conn);
+ sendme_connection_edge_consider_sending(conn);
}
return 0;
@@ -1808,99 +1949,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
(unsigned)circ->n_circ_id, rh.stream_id);
return 0;
case RELAY_COMMAND_SENDME:
- if (!rh.stream_id) {
- if (layer_hint) {
- if (layer_hint->package_window + CIRCWINDOW_INCREMENT >
- CIRCWINDOW_START_MAX) {
- static struct ratelim_t exit_warn_ratelim = RATELIM_INIT(600);
- log_fn_ratelim(&exit_warn_ratelim, LOG_WARN, LD_PROTOCOL,
- "Unexpected sendme cell from exit relay. "
- "Closing circ.");
- return -END_CIRC_REASON_TORPROTOCOL;
- }
- layer_hint->package_window += CIRCWINDOW_INCREMENT;
- log_debug(LD_APP,"circ-level sendme at origin, packagewindow %d.",
- layer_hint->package_window);
- circuit_resume_edge_reading(circ, layer_hint);
-
- /* We count circuit-level sendme's as valid delivered data because
- * they are rate limited.
- */
- if (CIRCUIT_IS_ORIGIN(circ)) {
- circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ),
- rh.length);
- }
-
- } else {
- if (circ->package_window + CIRCWINDOW_INCREMENT >
- CIRCWINDOW_START_MAX) {
- static struct ratelim_t client_warn_ratelim = RATELIM_INIT(600);
- log_fn_ratelim(&client_warn_ratelim,LOG_PROTOCOL_WARN, LD_PROTOCOL,
- "Unexpected sendme cell from client. "
- "Closing circ (window %d).",
- circ->package_window);
- return -END_CIRC_REASON_TORPROTOCOL;
- }
- circ->package_window += CIRCWINDOW_INCREMENT;
- log_debug(LD_APP,
- "circ-level sendme at non-origin, packagewindow %d.",
- circ->package_window);
- circuit_resume_edge_reading(circ, layer_hint);
- }
- return 0;
- }
- if (!conn) {
- if (CIRCUIT_IS_ORIGIN(circ)) {
- origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
- if (connection_half_edge_is_valid_sendme(ocirc->half_streams,
- rh.stream_id)) {
- circuit_read_valid_data(ocirc, rh.length);
- log_info(domain,
- "sendme cell on circ %u valid on half-closed "
- "stream id %d", ocirc->global_identifier, rh.stream_id);
- }
- }
-
- log_info(domain,"sendme cell dropped, unknown stream (streamid %d).",
- rh.stream_id);
- return 0;
- }
-
- /* Don't allow the other endpoint to request more than our maximum
- * (i.e. initial) stream SENDME window worth of data. Well-behaved
- * stock clients will not request more than this max (as per the check
- * in the while loop of connection_edge_consider_sending_sendme()).
- */
- if (conn->package_window + STREAMWINDOW_INCREMENT >
- STREAMWINDOW_START_MAX) {
- static struct ratelim_t stream_warn_ratelim = RATELIM_INIT(600);
- log_fn_ratelim(&stream_warn_ratelim, LOG_PROTOCOL_WARN, LD_PROTOCOL,
- "Unexpected stream sendme cell. Closing circ (window %d).",
- conn->package_window);
- return -END_CIRC_REASON_TORPROTOCOL;
- }
-
- /* At this point, the stream sendme is valid */
- if (CIRCUIT_IS_ORIGIN(circ)) {
- circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ),
- rh.length);
- }
-
- conn->package_window += STREAMWINDOW_INCREMENT;
- log_debug(domain,"stream-level sendme, packagewindow now %d.",
- conn->package_window);
- if (circuit_queue_streams_are_blocked(circ)) {
- /* Still waiting for queue to flush; don't touch conn */
- return 0;
- }
- connection_start_reading(TO_CONN(conn));
- /* handle whatever might still be on the inbuf */
- if (connection_edge_package_raw_inbuf(conn, 1, NULL) < 0) {
- /* (We already sent an end cell if possible) */
- connection_mark_for_close(TO_CONN(conn));
- return 0;
- }
- return 0;
+ return process_sendme_cell(&rh, cell, circ, conn, layer_hint, domain);
case RELAY_COMMAND_RESOLVE:
if (layer_hint) {
log_fn(LOG_PROTOCOL_WARN, LD_APP,
@@ -2091,15 +2140,17 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial,
return 0;
}
- if (!cpath_layer) { /* non-rendezvous exit */
- tor_assert(circ->package_window > 0);
- circ->package_window--;
- } else { /* we're an AP, or an exit on a rendezvous circ */
- tor_assert(cpath_layer->package_window > 0);
- cpath_layer->package_window--;
+ /* Handle the circuit-level SENDME package window. */
+ if (sendme_note_circuit_data_packaged(circ, cpath_layer) < 0) {
+ /* Package window has gone under 0. Protocol issue. */
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Circuit package window is below 0. Closing circuit.");
+ conn->end_reason = END_STREAM_REASON_TORPROTOCOL;
+ return -1;
}
- if (--conn->package_window <= 0) { /* is it 0 after decrement? */
+ /* Handle the stream-level SENDME package window. */
+ if (sendme_note_stream_data_packaged(conn) < 0) {
connection_stop_reading(TO_CONN(conn));
log_debug(domain,"conn->package_window reached 0.");
circuit_consider_stop_edge_reading(circ, cpath_layer);
@@ -2117,42 +2168,6 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial,
goto repeat_connection_edge_package_raw_inbuf;
}
-/** Called when we've just received a relay data cell, when
- * we've just finished flushing all bytes to stream <b>conn</b>,
- * or when we've flushed *some* bytes to the stream <b>conn</b>.
- *
- * If conn->outbuf is not too full, and our deliver window is
- * low, send back a suitable number of stream-level sendme cells.
- */
-void
-connection_edge_consider_sending_sendme(edge_connection_t *conn)
-{
- circuit_t *circ;
-
- if (connection_outbuf_too_full(TO_CONN(conn)))
- return;
-
- circ = circuit_get_by_edge_conn(conn);
- if (!circ) {
- /* this can legitimately happen if the destroy has already
- * arrived and torn down the circuit */
- log_info(LD_APP,"No circuit associated with conn. Skipping.");
- return;
- }
-
- while (conn->deliver_window <= STREAMWINDOW_START - STREAMWINDOW_INCREMENT) {
- log_debug(conn->base_.type == CONN_TYPE_AP ?LD_APP:LD_EXIT,
- "Outbuf %d, Queuing stream sendme.",
- (int)conn->base_.outbuf_flushlen);
- conn->deliver_window += STREAMWINDOW_INCREMENT;
- if (connection_edge_send_command(conn, RELAY_COMMAND_SENDME,
- NULL, 0) < 0) {
- log_warn(LD_APP,"connection_edge_send_command failed. Skipping.");
- return; /* the circuit's closed, don't continue */
- }
- }
-}
-
/** The circuit <b>circ</b> has received a circuit-level sendme
* (on hop <b>layer_hint</b>, if we're the OP). Go through all the
* attached streams and let them resume reading and packaging, if
@@ -2369,33 +2384,6 @@ circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint)
return 0;
}
-/** Check if the deliver_window for circuit <b>circ</b> (at hop
- * <b>layer_hint</b> if it's defined) is low enough that we should
- * send a circuit-level sendme back down the circuit. If so, send
- * enough sendmes that the window would be overfull if we sent any
- * more.
- */
-static void
-circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint)
-{
-// log_fn(LOG_INFO,"Considering: layer_hint is %s",
-// layer_hint ? "defined" : "null");
- while ((layer_hint ? layer_hint->deliver_window : circ->deliver_window) <=
- CIRCWINDOW_START - CIRCWINDOW_INCREMENT) {
- log_debug(LD_CIRC,"Queuing circuit sendme.");
- if (layer_hint)
- layer_hint->deliver_window += CIRCWINDOW_INCREMENT;
- else
- circ->deliver_window += CIRCWINDOW_INCREMENT;
- if (relay_send_command_from_edge(0, circ, RELAY_COMMAND_SENDME,
- NULL, 0, layer_hint) < 0) {
- log_warn(LD_CIRC,
- "relay_send_command_from_edge failed. Circuit's closed.");
- return; /* the circuit's closed, don't continue */
- }
- }
-}
-
/** The total number of cells we have allocated. */
static size_t total_cells_allocated = 0;
diff --git a/src/core/or/relay.h b/src/core/or/relay.h
index ea1b358ffb..2248cdf381 100644
--- a/src/core/or/relay.h
+++ b/src/core/or/relay.h
@@ -120,6 +120,7 @@ STATIC int cell_queues_check_size(void);
STATIC int connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
edge_connection_t *conn,
crypt_path_t *layer_hint);
+STATIC size_t get_pad_cell_offset(size_t payload_len);
#endif /* defined(RELAY_PRIVATE) */
diff --git a/src/core/or/relay_crypto_st.h b/src/core/or/relay_crypto_st.h
index dafce257c7..1f243ccdc8 100644
--- a/src/core/or/relay_crypto_st.h
+++ b/src/core/or/relay_crypto_st.h
@@ -25,6 +25,8 @@ struct relay_crypto_t {
/** Digest state for cells heading away from the OR at this step. */
struct crypto_digest_t *b_digest;
+ /** Digest used for the next SENDME cell if any. */
+ uint8_t sendme_digest[DIGEST_LEN];
};
#undef crypto_cipher_t
diff --git a/src/core/or/sendme.c b/src/core/or/sendme.c
new file mode 100644
index 0000000000..70ff3798ba
--- /dev/null
+++ b/src/core/or/sendme.c
@@ -0,0 +1,604 @@
+/* Copyright (c) 2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file sendme.c
+ * \brief Code that is related to SENDME cells both in terms of
+ * creating/parsing cells and handling the content.
+ */
+
+#define SENDME_PRIVATE
+
+#include "core/or/or.h"
+
+#include "app/config/config.h"
+#include "core/crypto/relay_crypto.h"
+#include "core/mainloop/connection.h"
+#include "core/or/cell_st.h"
+#include "core/or/circuitlist.h"
+#include "core/or/circuituse.h"
+#include "core/or/or_circuit_st.h"
+#include "core/or/relay.h"
+#include "core/or/sendme.h"
+#include "feature/nodelist/networkstatus.h"
+#include "lib/ctime/di_ops.h"
+#include "trunnel/sendme.h"
+
+/* The maximum supported version. Above that value, the cell can't be
+ * recognized as a valid SENDME. */
+#define SENDME_MAX_SUPPORTED_VERSION 1
+
+/* The cell version constants for when emitting a cell. */
+#define SENDME_EMIT_MIN_VERSION_DEFAULT 0
+#define SENDME_EMIT_MIN_VERSION_MIN 0
+#define SENDME_EMIT_MIN_VERSION_MAX UINT8_MAX
+
+/* The cell version constants for when accepting a cell. */
+#define SENDME_ACCEPT_MIN_VERSION_DEFAULT 0
+#define SENDME_ACCEPT_MIN_VERSION_MIN 0
+#define SENDME_ACCEPT_MIN_VERSION_MAX UINT8_MAX
+
+/* Return the minimum version given by the consensus (if any) that should be
+ * used when emitting a SENDME cell. */
+STATIC int
+get_emit_min_version(void)
+{
+ return networkstatus_get_param(NULL, "sendme_emit_min_version",
+ SENDME_EMIT_MIN_VERSION_DEFAULT,
+ SENDME_EMIT_MIN_VERSION_MIN,
+ SENDME_EMIT_MIN_VERSION_MAX);
+}
+
+/* Return the minimum version given by the consensus (if any) that should be
+ * accepted when receiving a SENDME cell. */
+STATIC int
+get_accept_min_version(void)
+{
+ return networkstatus_get_param(NULL, "sendme_accept_min_version",
+ SENDME_ACCEPT_MIN_VERSION_DEFAULT,
+ SENDME_ACCEPT_MIN_VERSION_MIN,
+ SENDME_ACCEPT_MIN_VERSION_MAX);
+}
+
+/* Return true iff the given cell digest matches the first digest in the
+ * circuit sendme list. */
+static bool
+v1_digest_matches(const circuit_t *circ, const uint8_t *cell_digest)
+{
+ bool ret = false;
+ uint8_t *circ_digest = NULL;
+
+ tor_assert(circ);
+ tor_assert(cell_digest);
+
+ /* We shouldn't have received a SENDME if we have no digests. Log at
+ * protocol warning because it can be tricked by sending many SENDMEs
+ * without prior data cell. */
+ if (circ->sendme_last_digests == NULL ||
+ smartlist_len(circ->sendme_last_digests) == 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "We received a SENDME but we have no cell digests to match. "
+ "Closing circuit.");
+ goto no_match;
+ }
+
+ /* Pop the first element that was added (FIFO) and compare it. */
+ circ_digest = smartlist_get(circ->sendme_last_digests, 0);
+ smartlist_del_keeporder(circ->sendme_last_digests, 0);
+
+ /* Compare the digest with the one in the SENDME. This cell is invalid
+ * without a perfect match. */
+ if (tor_memneq(circ_digest, cell_digest, TRUNNEL_SENDME_V1_DIGEST_LEN)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "SENDME v1 cell digest do not match.");
+ goto no_match;
+ }
+ /* Digests matches! */
+ ret = true;
+
+ no_match:
+ /* This digest was popped from the circuit list. Regardless of what happens,
+ * we have no more use for it. */
+ tor_free(circ_digest);
+ return ret;
+}
+
+/* Return true iff the given decoded SENDME version 1 cell is valid and
+ * matches the expected digest on the circuit.
+ *
+ * Validation is done by comparing the digest in the cell from the previous
+ * cell we saw which tells us that the other side has in fact seen that cell.
+ * See proposal 289 for more details. */
+static bool
+cell_v1_is_valid(const sendme_cell_t *cell, const circuit_t *circ)
+{
+ tor_assert(cell);
+ tor_assert(circ);
+
+ const uint8_t *cell_digest = sendme_cell_getconstarray_data_v1_digest(cell);
+ return v1_digest_matches(circ, cell_digest);
+}
+
+/* Return true iff the given cell version can be handled or if the minimum
+ * accepted version from the consensus is known to us. */
+STATIC bool
+cell_version_is_valid(uint8_t cell_version)
+{
+ int accept_version = get_accept_min_version();
+
+ /* Can we handle this version? */
+ if (accept_version > SENDME_MAX_SUPPORTED_VERSION) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Unable to accept SENDME version %u (from consensus). "
+ "We only support <= %d. Probably your tor is too old?",
+ accept_version, cell_version);
+ goto invalid;
+ }
+
+ /* We only accept a SENDME cell from what the consensus tells us. */
+ if (cell_version < accept_version) {
+ log_info(LD_PROTOCOL, "Unacceptable SENDME version %d. Only "
+ "accepting %u (from consensus). Closing circuit.",
+ cell_version, accept_version);
+ goto invalid;
+ }
+
+ return 1;
+ invalid:
+ return 0;
+}
+
+/* Return true iff the encoded SENDME cell in cell_payload of length
+ * cell_payload_len is valid. For each version:
+ *
+ * 0: No validation
+ * 1: Authenticated with last cell digest.
+ *
+ * This is the main critical function to make sure we can continue to
+ * send/recv cells on a circuit. If the SENDME is invalid, the circuit should
+ * be mark for close. */
+STATIC bool
+sendme_is_valid(const circuit_t *circ, const uint8_t *cell_payload,
+ size_t cell_payload_len)
+{
+ uint8_t cell_version;
+ sendme_cell_t *cell = NULL;
+
+ tor_assert(circ);
+ tor_assert(cell_payload);
+
+ /* An empty payload means version 0 so skip trunnel parsing. We won't be
+ * able to parse a 0 length buffer into a valid SENDME cell. */
+ if (cell_payload_len == 0) {
+ cell_version = 0;
+ } else {
+ /* First we'll decode the cell so we can get the version. */
+ if (sendme_cell_parse(&cell, cell_payload, cell_payload_len) < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Unparseable SENDME cell received. Closing circuit.");
+ goto invalid;
+ }
+ cell_version = sendme_cell_get_version(cell);
+ }
+
+ /* Validate that we can handle this cell version. */
+ if (!cell_version_is_valid(cell_version)) {
+ goto invalid;
+ }
+
+ /* Validate depending on the version now. */
+ switch (cell_version) {
+ case 0x01:
+ if (!cell_v1_is_valid(cell, circ)) {
+ goto invalid;
+ }
+ break;
+ case 0x00:
+ /* Fallthrough. Version 0, there is no work to be done on the payload so
+ * it is necessarily valid if we pass the version validation. */
+ default:
+ /* Unknown version means we can't handle it so fallback to version 0. */
+ break;
+ }
+
+ /* Valid cell. */
+ sendme_cell_free(cell);
+ return 1;
+ invalid:
+ sendme_cell_free(cell);
+ return 0;
+}
+
+/* Build and encode a version 1 SENDME cell into payload, which must be at
+ * least of RELAY_PAYLOAD_SIZE bytes, using the digest for the cell data.
+ *
+ * Return the size in bytes of the encoded cell in payload. A negative value
+ * is returned on encoding failure. */
+STATIC ssize_t
+build_cell_payload_v1(const uint8_t *cell_digest, uint8_t *payload)
+{
+ ssize_t len = -1;
+ sendme_cell_t *cell = NULL;
+
+ tor_assert(cell_digest);
+ tor_assert(payload);
+
+ cell = sendme_cell_new();
+
+ /* Building a payload for version 1. */
+ sendme_cell_set_version(cell, 0x01);
+ /* Set the data length field for v1. */
+ sendme_cell_set_data_len(cell, TRUNNEL_SENDME_V1_DIGEST_LEN);
+
+ /* Copy the digest into the data payload. */
+ memcpy(sendme_cell_getarray_data_v1_digest(cell), cell_digest,
+ sendme_cell_get_data_len(cell));
+
+ /* Finally, encode the cell into the payload. */
+ len = sendme_cell_encode(payload, RELAY_PAYLOAD_SIZE, cell);
+
+ sendme_cell_free(cell);
+ return len;
+}
+
+/* Send a circuit-level SENDME on the given circuit using the layer_hint if
+ * not NULL. The digest is only used for version 1.
+ *
+ * Return 0 on success else a negative value and the circuit will be closed
+ * because we failed to send the cell on it. */
+static int
+send_circuit_level_sendme(circuit_t *circ, crypt_path_t *layer_hint,
+ const uint8_t *cell_digest)
+{
+ uint8_t emit_version;
+ uint8_t payload[RELAY_PAYLOAD_SIZE];
+ ssize_t payload_len;
+
+ tor_assert(circ);
+ tor_assert(cell_digest);
+
+ emit_version = get_emit_min_version();
+ switch (emit_version) {
+ case 0x01:
+ payload_len = build_cell_payload_v1(cell_digest, payload);
+ if (BUG(payload_len < 0)) {
+ /* Unable to encode the cell, abort. We can recover from this by closing
+ * the circuit but in theory it should never happen. */
+ return -1;
+ }
+ log_debug(LD_PROTOCOL, "Emitting SENDME version 1 cell.");
+ break;
+ case 0x00:
+ /* Fallthrough because default is to use v0. */
+ default:
+ /* Unknown version, fallback to version 0 meaning no payload. */
+ payload_len = 0;
+ break;
+ }
+
+ if (relay_send_command_from_edge(0, circ, RELAY_COMMAND_SENDME,
+ (char *) payload, payload_len,
+ layer_hint) < 0) {
+ log_warn(LD_CIRC,
+ "SENDME relay_send_command_from_edge failed. Circuit's closed.");
+ return -1; /* the circuit's closed, don't continue */
+ }
+ return 0;
+}
+
+/*
+ * Public API
+ */
+
+/** Keep the current inbound cell digest for the next SENDME digest. This part
+ * is only done by the client as the circuit came back from the Exit. */
+void
+sendme_circuit_record_outbound_cell(or_circuit_t *or_circ)
+{
+ tor_assert(or_circ);
+ relay_crypto_record_sendme_digest(&or_circ->crypto);
+}
+
+/** Keep the current inbound cell digest for the next SENDME digest. This part
+ * is only done by the client as the circuit came back from the Exit. */
+void
+sendme_circuit_record_inbound_cell(crypt_path_t *cpath)
+{
+ tor_assert(cpath);
+ relay_crypto_record_sendme_digest(&cpath->crypto);
+}
+
+/** Return true iff the next cell for the given cell window is expected to be
+ * a SENDME.
+ *
+ * We are able to know that because the package or deliver window value minus
+ * one cell (the possible SENDME cell) should be a multiple of the increment
+ * window value. */
+bool
+sendme_circuit_cell_is_next(int window)
+{
+ /* Is this the last cell before a SENDME? The idea is that if the package or
+ * deliver window reaches a multiple of the increment, after this cell, we
+ * should expect a SENDME. */
+ if (((window - 1) % CIRCWINDOW_INCREMENT) != 0) {
+ return false;
+ }
+ /* Next cell is expected to be a SENDME. */
+ return true;
+}
+
+/** Called when we've just received a relay data cell, when we've just
+ * finished flushing all bytes to stream <b>conn</b>, or when we've flushed
+ * *some* bytes to the stream <b>conn</b>.
+ *
+ * If conn->outbuf is not too full, and our deliver window is low, send back a
+ * suitable number of stream-level sendme cells.
+ */
+void
+sendme_connection_edge_consider_sending(edge_connection_t *conn)
+{
+ tor_assert(conn);
+
+ int log_domain = TO_CONN(conn)->type == CONN_TYPE_AP ? LD_APP : LD_EXIT;
+
+ /* Don't send it if we still have data to deliver. */
+ if (connection_outbuf_too_full(TO_CONN(conn))) {
+ goto end;
+ }
+
+ if (circuit_get_by_edge_conn(conn) == NULL) {
+ /* This can legitimately happen if the destroy has already arrived and
+ * torn down the circuit. */
+ log_info(log_domain, "No circuit associated with edge connection. "
+ "Skipping sending SENDME.");
+ goto end;
+ }
+
+ while (conn->deliver_window <=
+ (STREAMWINDOW_START - STREAMWINDOW_INCREMENT)) {
+ log_debug(log_domain, "Outbuf %" TOR_PRIuSZ ", queuing stream SENDME.",
+ TO_CONN(conn)->outbuf_flushlen);
+ conn->deliver_window += STREAMWINDOW_INCREMENT;
+ if (connection_edge_send_command(conn, RELAY_COMMAND_SENDME,
+ NULL, 0) < 0) {
+ log_warn(LD_BUG, "connection_edge_send_command failed while sending "
+ "a SENDME. Circuit probably closed, skipping.");
+ goto end; /* The circuit's closed, don't continue */
+ }
+ }
+
+ end:
+ return;
+}
+
+/** Check if the deliver_window for circuit <b>circ</b> (at hop
+ * <b>layer_hint</b> if it's defined) is low enough that we should
+ * send a circuit-level sendme back down the circuit. If so, send
+ * enough sendmes that the window would be overfull if we sent any
+ * more.
+ */
+void
+sendme_circuit_consider_sending(circuit_t *circ, crypt_path_t *layer_hint)
+{
+ const uint8_t *digest;
+
+ while ((layer_hint ? layer_hint->deliver_window : circ->deliver_window) <=
+ CIRCWINDOW_START - CIRCWINDOW_INCREMENT) {
+ log_debug(LD_CIRC,"Queuing circuit sendme.");
+ if (layer_hint) {
+ layer_hint->deliver_window += CIRCWINDOW_INCREMENT;
+ digest = relay_crypto_get_sendme_digest(&layer_hint->crypto);
+ } else {
+ circ->deliver_window += CIRCWINDOW_INCREMENT;
+ digest = relay_crypto_get_sendme_digest(&TO_OR_CIRCUIT(circ)->crypto);
+ }
+ if (send_circuit_level_sendme(circ, layer_hint, digest) < 0) {
+ return; /* The circuit's closed, don't continue */
+ }
+ }
+}
+
+/* Process a circuit-level SENDME cell that we just received. The layer_hint,
+ * if not NULL, is the Exit hop of the connection which means that we are a
+ * client. In that case, circ must be an origin circuit. The cell_body_len is
+ * the length of the SENDME cell payload (excluding the header). The
+ * cell_payload is the payload.
+ *
+ * Return 0 on success that is the SENDME is valid and the package window has
+ * been updated properly.
+ *
+ * On error, a negative value is returned which indicate that the circuit must
+ * be closed using the value as the reason for it. */
+int
+sendme_process_circuit_level(crypt_path_t *layer_hint,
+ circuit_t *circ, const uint8_t *cell_payload,
+ uint16_t cell_payload_len)
+{
+ tor_assert(circ);
+ tor_assert(cell_payload);
+
+ /* If we are the origin of the circuit, we are the Client so we use the
+ * layer hint (the Exit hop) for the package window tracking. */
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ if ((layer_hint->package_window + CIRCWINDOW_INCREMENT) >
+ CIRCWINDOW_START_MAX) {
+ static struct ratelim_t exit_warn_ratelim = RATELIM_INIT(600);
+ log_fn_ratelim(&exit_warn_ratelim, LOG_WARN, LD_PROTOCOL,
+ "Unexpected sendme cell from exit relay. "
+ "Closing circ.");
+ return -END_CIRC_REASON_TORPROTOCOL;
+ }
+ layer_hint->package_window += CIRCWINDOW_INCREMENT;
+ log_debug(LD_APP, "circ-level sendme at origin, packagewindow %d.",
+ layer_hint->package_window);
+
+ /* We count circuit-level sendme's as valid delivered data because they
+ * are rate limited. */
+ circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), cell_payload_len);
+ } else {
+ /* Validate the SENDME cell. Depending on the version, different
+ * validation can be done. An invalid SENDME requires us to close the
+ * circuit. It is only done if we are the Exit of the circuit. */
+ if (!sendme_is_valid(circ, cell_payload, cell_payload_len)) {
+ return -END_CIRC_REASON_TORPROTOCOL;
+ }
+
+ /* We aren't the origin of this circuit so we are the Exit and thus we
+ * track the package window with the circuit object. */
+ if ((circ->package_window + CIRCWINDOW_INCREMENT) >
+ CIRCWINDOW_START_MAX) {
+ static struct ratelim_t client_warn_ratelim = RATELIM_INIT(600);
+ log_fn_ratelim(&client_warn_ratelim, LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Unexpected sendme cell from client. "
+ "Closing circ (window %d).", circ->package_window);
+ return -END_CIRC_REASON_TORPROTOCOL;
+ }
+ circ->package_window += CIRCWINDOW_INCREMENT;
+ log_debug(LD_EXIT, "circ-level sendme at non-origin, packagewindow %d.",
+ circ->package_window);
+ }
+
+ return 0;
+}
+
+/* Process a stream-level SENDME cell that we just received. The conn is the
+ * edge connection (stream) that the circuit circ is associated with. The
+ * cell_body_len is the length of the payload (excluding the header).
+ *
+ * Return 0 on success that is the SENDME is valid and the package window has
+ * been updated properly.
+ *
+ * On error, a negative value is returned which indicate that the circuit must
+ * be closed using the value as the reason for it. */
+int
+sendme_process_stream_level(edge_connection_t *conn, circuit_t *circ,
+ uint16_t cell_body_len)
+{
+ tor_assert(conn);
+ tor_assert(circ);
+
+ /* Don't allow the other endpoint to request more than our maximum (i.e.
+ * initial) stream SENDME window worth of data. Well-behaved stock clients
+ * will not request more than this max (as per the check in the while loop
+ * of sendme_connection_edge_consider_sending()). */
+ if ((conn->package_window + STREAMWINDOW_INCREMENT) >
+ STREAMWINDOW_START_MAX) {
+ static struct ratelim_t stream_warn_ratelim = RATELIM_INIT(600);
+ log_fn_ratelim(&stream_warn_ratelim, LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Unexpected stream sendme cell. Closing circ (window %d).",
+ conn->package_window);
+ return -END_CIRC_REASON_TORPROTOCOL;
+ }
+ /* At this point, the stream sendme is valid */
+ conn->package_window += STREAMWINDOW_INCREMENT;
+
+ /* We count circuit-level sendme's as valid delivered data because they are
+ * rate limited. */
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), cell_body_len);
+ }
+
+ log_debug(CIRCUIT_IS_ORIGIN(circ) ? LD_APP : LD_EXIT,
+ "stream-level sendme, package_window now %d.",
+ conn->package_window);
+ return 0;
+}
+
+/* Called when a relay DATA cell is received on the given circuit. If
+ * layer_hint is NULL, this means we are the Exit end point else we are the
+ * Client. Update the deliver window and return its new value. */
+int
+sendme_circuit_data_received(circuit_t *circ, crypt_path_t *layer_hint)
+{
+ int deliver_window, domain;
+
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ tor_assert(layer_hint);
+ --layer_hint->deliver_window;
+ deliver_window = layer_hint->deliver_window;
+ domain = LD_APP;
+ } else {
+ tor_assert(!layer_hint);
+ --circ->deliver_window;
+ deliver_window = circ->deliver_window;
+ domain = LD_EXIT;
+ }
+
+ log_debug(domain, "Circuit deliver_window now %d.", deliver_window);
+ return deliver_window;
+}
+
+/* Called when a relay DATA cell is received for the given edge connection
+ * conn. Update the deliver window and return its new value. */
+int
+sendme_stream_data_received(edge_connection_t *conn)
+{
+ tor_assert(conn);
+ return --conn->deliver_window;
+}
+
+/* Called when a relay DATA cell is packaged on the given circuit. If
+ * layer_hint is NULL, this means we are the Exit end point else we are the
+ * Client. Update the package window and return its new value. */
+int
+sendme_note_circuit_data_packaged(circuit_t *circ, crypt_path_t *layer_hint)
+{
+ int package_window, domain;
+
+ tor_assert(circ);
+
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ /* Client side. */
+ tor_assert(layer_hint);
+ --layer_hint->package_window;
+ package_window = layer_hint->package_window;
+ domain = LD_APP;
+ } else {
+ /* Exit side. */
+ tor_assert(!layer_hint);
+ --circ->package_window;
+ package_window = circ->package_window;
+ domain = LD_EXIT;
+ }
+
+ log_debug(domain, "Circuit package_window now %d.", package_window);
+ return package_window;
+}
+
+/* Called when a relay DATA cell is packaged for the given edge connection
+ * conn. Update the package window and return its new value. */
+int
+sendme_note_stream_data_packaged(edge_connection_t *conn)
+{
+ tor_assert(conn);
+ return --conn->package_window;
+}
+
+/* Note the cell digest in the circuit sendme last digests FIFO if applicable.
+ * It is safe to pass a circuit that isn't meant to track those digests. */
+void
+sendme_record_cell_digest(circuit_t *circ)
+{
+ const uint8_t *digest;
+
+ tor_assert(circ);
+
+ /* We only keep the cell digest if we are the Exit on that circuit and if
+ * this cell is the last one before the client should send a SENDME. */
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ return;
+ }
+ /* Is this the last cell before a SENDME? The idea is that if the
+ * package_window reaches a multiple of the increment, after this cell, we
+ * should expect a SENDME. */
+ if (!sendme_circuit_cell_is_next(circ->package_window)) {
+ return;
+ }
+
+ /* Add the digest to the last seen list in the circuit. */
+ digest = relay_crypto_get_sendme_digest(&TO_OR_CIRCUIT(circ)->crypto);
+ if (circ->sendme_last_digests == NULL) {
+ circ->sendme_last_digests = smartlist_new();
+ }
+ smartlist_add(circ->sendme_last_digests, tor_memdup(digest, DIGEST_LEN));
+}
diff --git a/src/core/or/sendme.h b/src/core/or/sendme.h
new file mode 100644
index 0000000000..78273eb9a8
--- /dev/null
+++ b/src/core/or/sendme.h
@@ -0,0 +1,68 @@
+/* Copyright (c) 2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file sendme.h
+ * \brief Header file for sendme.c.
+ **/
+
+#ifndef TOR_SENDME_H
+#define TOR_SENDME_H
+
+#include "core/or/edge_connection_st.h"
+#include "core/or/crypt_path_st.h"
+#include "core/or/circuit_st.h"
+
+/* Sending SENDME cell. */
+void sendme_connection_edge_consider_sending(edge_connection_t *edge_conn);
+void sendme_circuit_consider_sending(circuit_t *circ,
+ crypt_path_t *layer_hint);
+
+/* Processing SENDME cell. */
+int sendme_process_circuit_level(crypt_path_t *layer_hint,
+ circuit_t *circ, const uint8_t *cell_payload,
+ uint16_t cell_payload_len);
+int sendme_process_stream_level(edge_connection_t *conn, circuit_t *circ,
+ uint16_t cell_body_len);
+
+/* Update deliver window functions. */
+int sendme_stream_data_received(edge_connection_t *conn);
+int sendme_circuit_data_received(circuit_t *circ, crypt_path_t *layer_hint);
+
+/* Update package window functions. */
+int sendme_note_circuit_data_packaged(circuit_t *circ,
+ crypt_path_t *layer_hint);
+int sendme_note_stream_data_packaged(edge_connection_t *conn);
+
+/* Track cell digest. */
+void sendme_record_cell_digest(circuit_t *circ);
+void sendme_circuit_record_inbound_cell(crypt_path_t *cpath);
+void sendme_circuit_record_outbound_cell(or_circuit_t *or_circ);
+
+/* Circuit level information. */
+bool sendme_circuit_cell_is_next(int window);
+
+/* Private section starts. */
+#ifdef SENDME_PRIVATE
+
+/*
+ * Unit tests declaractions.
+ */
+#ifdef TOR_UNIT_TESTS
+
+STATIC int get_emit_min_version(void);
+STATIC int get_accept_min_version(void);
+
+STATIC bool cell_version_is_valid(uint8_t cell_version);
+
+STATIC ssize_t build_cell_payload_v1(const uint8_t *cell_digest,
+ uint8_t *payload);
+STATIC bool sendme_is_valid(const circuit_t *circ,
+ const uint8_t *cell_payload,
+ size_t cell_payload_len);
+
+#endif /* TOR_UNIT_TESTS */
+
+#endif /* SENDME_PRIVATE */
+
+#endif /* !defined(TOR_SENDME_H) */
diff --git a/src/test/include.am b/src/test/include.am
index 5d991f8399..899e0de7d9 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -182,6 +182,7 @@ src_test_test_SOURCES += \
src/test/test_routerlist.c \
src/test/test_routerset.c \
src/test/test_scheduler.c \
+ src/test/test_sendme.c \
src/test/test_shared_random.c \
src/test/test_socks.c \
src/test/test_status.c \
diff --git a/src/test/test.c b/src/test/test.c
index be5cb12b1e..b19bf3bd88 100644
--- a/src/test/test.c
+++ b/src/test/test.c
@@ -911,6 +911,7 @@ struct testgroup_t testgroups[] = {
{ "routerlist/", routerlist_tests },
{ "routerset/" , routerset_tests },
{ "scheduler/", scheduler_tests },
+ { "sendme/", sendme_tests },
{ "shared-random/", sr_tests },
{ "socks/", socks_tests },
{ "status/" , status_tests },
diff --git a/src/test/test.h b/src/test/test.h
index 7d19af9b20..167fd090ac 100644
--- a/src/test/test.h
+++ b/src/test/test.h
@@ -266,6 +266,7 @@ extern struct testcase_t routerkeys_tests[];
extern struct testcase_t routerlist_tests[];
extern struct testcase_t routerset_tests[];
extern struct testcase_t scheduler_tests[];
+extern struct testcase_t sendme_tests[];
extern struct testcase_t socks_tests[];
extern struct testcase_t sr_tests[];
extern struct testcase_t status_tests[];
diff --git a/src/test/test_sendme.c b/src/test/test_sendme.c
new file mode 100644
index 0000000000..d40fbaf862
--- /dev/null
+++ b/src/test/test_sendme.c
@@ -0,0 +1,267 @@
+/* Copyright (c) 2014-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/* Unit tests for handling different kinds of relay cell */
+
+#define CIRCUITLIST_PRIVATE
+#define NETWORKSTATUS_PRIVATE
+#define SENDME_PRIVATE
+#define RELAY_PRIVATE
+
+#include "core/or/circuit_st.h"
+#include "core/or/or_circuit_st.h"
+#include "core/or/origin_circuit_st.h"
+#include "core/or/circuitlist.h"
+#include "core/or/relay.h"
+#include "core/or/sendme.h"
+
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/networkstatus_st.h"
+
+#include "lib/crypt_ops/crypto_digest.h"
+
+#include "test/test.h"
+#include "test/log_test_helpers.h"
+
+static void
+setup_mock_consensus(void)
+{
+ current_md_consensus = current_ns_consensus =
+ tor_malloc_zero(sizeof(networkstatus_t));
+ current_md_consensus->net_params = smartlist_new();
+ current_md_consensus->routerstatus_list = smartlist_new();
+}
+
+static void
+free_mock_consensus(void)
+{
+ SMARTLIST_FOREACH(current_md_consensus->routerstatus_list, void *, r,
+ tor_free(r));
+ smartlist_free(current_md_consensus->routerstatus_list);
+ smartlist_free(current_ns_consensus->net_params);
+ tor_free(current_ns_consensus);
+}
+
+static void
+test_v1_record_digest(void *arg)
+{
+ or_circuit_t *or_circ = NULL;
+ origin_circuit_t *orig_circ = NULL;
+ circuit_t *circ = NULL;
+
+ (void) arg;
+
+ /* Create our dummy circuits. */
+ orig_circ = origin_circuit_new();
+ tt_assert(orig_circ);
+ or_circ = or_circuit_new(1, NULL);
+
+ /* Start by pointing to the origin circuit. */
+ circ = TO_CIRCUIT(orig_circ);
+ circ->purpose = CIRCUIT_PURPOSE_S_REND_JOINED;
+
+ /* We should never note SENDME digest on origin circuit. */
+ sendme_record_cell_digest(circ);
+ tt_assert(!circ->sendme_last_digests);
+ /* We do not need the origin circuit for now. */
+ orig_circ = NULL;
+ circuit_free_(circ);
+ /* Points it to the OR circuit now. */
+ circ = TO_CIRCUIT(or_circ);
+
+ /* The package window has to be a multiple of CIRCWINDOW_INCREMENT minus 1
+ * in order to catched the CIRCWINDOW_INCREMENT-nth cell. Try something that
+ * shouldn't be noted. */
+ circ->package_window = CIRCWINDOW_INCREMENT;
+ sendme_record_cell_digest(circ);
+ tt_assert(!circ->sendme_last_digests);
+
+ /* This should work now. Package window at CIRCWINDOW_INCREMENT + 1. */
+ circ->package_window++;
+ sendme_record_cell_digest(circ);
+ tt_assert(circ->sendme_last_digests);
+ tt_int_op(smartlist_len(circ->sendme_last_digests), OP_EQ, 1);
+
+ /* Next cell in the package window shouldn't do anything. */
+ circ->package_window++;
+ sendme_record_cell_digest(circ);
+ tt_int_op(smartlist_len(circ->sendme_last_digests), OP_EQ, 1);
+
+ /* The next CIRCWINDOW_INCREMENT should add one more digest. */
+ circ->package_window = (CIRCWINDOW_INCREMENT * 2) + 1;
+ sendme_record_cell_digest(circ);
+ tt_int_op(smartlist_len(circ->sendme_last_digests), OP_EQ, 2);
+
+ done:
+ circuit_free_(circ);
+}
+
+static void
+test_v1_consensus_params(void *arg)
+{
+ (void) arg;
+
+ setup_mock_consensus();
+ tt_assert(current_md_consensus);
+
+ /* Both zeroes. */
+ smartlist_add(current_md_consensus->net_params,
+ (void *) "sendme_emit_min_version=0");
+ smartlist_add(current_md_consensus->net_params,
+ (void *) "sendme_accept_min_version=0");
+ tt_int_op(get_emit_min_version(), OP_EQ, 0);
+ tt_int_op(get_accept_min_version(), OP_EQ, 0);
+ smartlist_clear(current_md_consensus->net_params);
+
+ /* Both ones. */
+ smartlist_add(current_md_consensus->net_params,
+ (void *) "sendme_emit_min_version=1");
+ smartlist_add(current_md_consensus->net_params,
+ (void *) "sendme_accept_min_version=1");
+ tt_int_op(get_emit_min_version(), OP_EQ, 1);
+ tt_int_op(get_accept_min_version(), OP_EQ, 1);
+ smartlist_clear(current_md_consensus->net_params);
+
+ /* Different values from each other. */
+ smartlist_add(current_md_consensus->net_params,
+ (void *) "sendme_emit_min_version=1");
+ smartlist_add(current_md_consensus->net_params,
+ (void *) "sendme_accept_min_version=0");
+ tt_int_op(get_emit_min_version(), OP_EQ, 1);
+ tt_int_op(get_accept_min_version(), OP_EQ, 0);
+ smartlist_clear(current_md_consensus->net_params);
+
+ /* Validate is the cell version is coherent with our internal default value
+ * and the one in the consensus. */
+ smartlist_add(current_md_consensus->net_params,
+ (void *) "sendme_accept_min_version=1");
+ /* Minimum acceptable value is 1. */
+ tt_int_op(cell_version_is_valid(1), OP_EQ, true);
+ /* Minimum acceptable value is 1 so a cell version of 0 is refused. */
+ tt_int_op(cell_version_is_valid(0), OP_EQ, false);
+
+ done:
+ free_mock_consensus();
+}
+
+static void
+test_v1_build_cell(void *arg)
+{
+ uint8_t payload[RELAY_PAYLOAD_SIZE], digest[DIGEST_LEN];
+ ssize_t ret;
+ crypto_digest_t *cell_digest = NULL;
+ or_circuit_t *or_circ = NULL;
+ circuit_t *circ = NULL;
+
+ (void) arg;
+
+ or_circ = or_circuit_new(1, NULL);
+ circ = TO_CIRCUIT(or_circ);
+
+ cell_digest = crypto_digest_new();
+ tt_assert(cell_digest);
+ crypto_digest_add_bytes(cell_digest, "AAAAAAAAAAAAAAAAAAAA", 20);
+ crypto_digest_get_digest(cell_digest, (char *) digest, sizeof(digest));
+
+ /* SENDME v1 payload is 3 bytes + 20 bytes digest. See spec. */
+ ret = build_cell_payload_v1(digest, payload);
+ tt_int_op(ret, OP_EQ, 23);
+
+ /* Validation. */
+
+ /* An empty payload means SENDME version 0 thus valid. */
+ tt_int_op(sendme_is_valid(circ, payload, 0), OP_EQ, true);
+
+ /* An unparseable cell means invalid. */
+ setup_full_capture_of_logs(LOG_INFO);
+ tt_int_op(sendme_is_valid(circ, (const uint8_t *) "A", 1), OP_EQ, false);
+ expect_log_msg_containing("Unparseable SENDME cell received. "
+ "Closing circuit.");
+ teardown_capture_of_logs();
+
+ /* No cell digest recorded for this. */
+ setup_full_capture_of_logs(LOG_INFO);
+ tt_int_op(sendme_is_valid(circ, payload, sizeof(payload)), OP_EQ, false);
+ expect_log_msg_containing("We received a SENDME but we have no cell digests "
+ "to match. Closing circuit.");
+ teardown_capture_of_logs();
+
+ /* Note the wrong digest in the circuit, cell should fail validation. */
+ circ->package_window = CIRCWINDOW_INCREMENT + 1;
+ sendme_record_cell_digest(circ);
+ tt_int_op(smartlist_len(circ->sendme_last_digests), OP_EQ, 1);
+ setup_full_capture_of_logs(LOG_INFO);
+ tt_int_op(sendme_is_valid(circ, payload, sizeof(payload)), OP_EQ, false);
+ /* After a validation, the last digests is always popped out. */
+ tt_int_op(smartlist_len(circ->sendme_last_digests), OP_EQ, 0);
+ expect_log_msg_containing("SENDME v1 cell digest do not match.");
+ teardown_capture_of_logs();
+
+ /* Record the cell digest into the circuit, cell should validate. */
+ memcpy(or_circ->crypto.sendme_digest, digest, sizeof(digest));
+ circ->package_window = CIRCWINDOW_INCREMENT + 1;
+ sendme_record_cell_digest(circ);
+ tt_int_op(smartlist_len(circ->sendme_last_digests), OP_EQ, 1);
+ tt_int_op(sendme_is_valid(circ, payload, sizeof(payload)), OP_EQ, true);
+ /* After a validation, the last digests is always popped out. */
+ tt_int_op(smartlist_len(circ->sendme_last_digests), OP_EQ, 0);
+
+ done:
+ crypto_digest_free(cell_digest);
+ circuit_free_(circ);
+}
+
+static void
+test_cell_payload_pad(void *arg)
+{
+ size_t pad_offset, payload_len, expected_offset;
+
+ (void) arg;
+
+ /* Offset should be 0, not enough room for padding. */
+ payload_len = RELAY_PAYLOAD_SIZE;
+ pad_offset = get_pad_cell_offset(payload_len);
+ tt_int_op(pad_offset, OP_EQ, 0);
+ tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
+
+ /* Still no room because we keep 4 extra bytes. */
+ pad_offset = get_pad_cell_offset(payload_len - 4);
+ tt_int_op(pad_offset, OP_EQ, 0);
+ tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
+
+ /* We should have 1 byte of padding. Meaning, the offset should be the
+ * CELL_PAYLOAD_SIZE minus 1 byte. */
+ expected_offset = CELL_PAYLOAD_SIZE - 1;
+ pad_offset = get_pad_cell_offset(payload_len - 5);
+ tt_int_op(pad_offset, OP_EQ, expected_offset);
+ tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
+
+ /* Now some arbitrary small payload length. The cell size is header + 10 +
+ * extra 4 bytes we keep so the offset should be there. */
+ expected_offset = RELAY_HEADER_SIZE + 10 + 4;
+ pad_offset = get_pad_cell_offset(10);
+ tt_int_op(pad_offset, OP_EQ, expected_offset);
+ tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
+
+ /* Data length of 0. */
+ expected_offset = RELAY_HEADER_SIZE + 4;
+ pad_offset = get_pad_cell_offset(0);
+ tt_int_op(pad_offset, OP_EQ, expected_offset);
+ tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
+
+ done:
+ ;
+}
+
+struct testcase_t sendme_tests[] = {
+ { "v1_record_digest", test_v1_record_digest, TT_FORK,
+ NULL, NULL },
+ { "v1_consensus_params", test_v1_consensus_params, TT_FORK,
+ NULL, NULL },
+ { "v1_build_cell", test_v1_build_cell, TT_FORK,
+ NULL, NULL },
+ { "cell_payload_pad", test_cell_payload_pad, TT_FORK,
+ NULL, NULL },
+
+ END_OF_TESTCASES
+};
diff --git a/src/trunnel/include.am b/src/trunnel/include.am
index 4f4f1d3624..82e7a66959 100644
--- a/src/trunnel/include.am
+++ b/src/trunnel/include.am
@@ -11,6 +11,7 @@ TRUNNELINPUTS = \
src/trunnel/link_handshake.trunnel \
src/trunnel/pwbox.trunnel \
src/trunnel/channelpadding_negotiation.trunnel \
+ src/trunnel/sendme.trunnelĀ \
src/trunnel/socks5.trunnel \
src/trunnel/circpad_negotiation.trunnel
@@ -24,6 +25,7 @@ TRUNNELSOURCES = \
src/trunnel/hs/cell_introduce1.c \
src/trunnel/hs/cell_rendezvous.c \
src/trunnel/channelpadding_negotiation.c \
+ src/trunnel/sendme.c \
src/trunnel/socks5.c \
src/trunnel/netinfo.c \
src/trunnel/circpad_negotiation.c
@@ -40,6 +42,7 @@ TRUNNELHEADERS = \
src/trunnel/hs/cell_introduce1.h \
src/trunnel/hs/cell_rendezvous.h \
src/trunnel/channelpadding_negotiation.h \
+ src/trunnel/sendme.h \
src/trunnel/socks5.h \
src/trunnel/netinfo.h \
src/trunnel/circpad_negotiation.h
diff --git a/src/trunnel/sendme.c b/src/trunnel/sendme.c
new file mode 100644
index 0000000000..262b915234
--- /dev/null
+++ b/src/trunnel/sendme.c
@@ -0,0 +1,347 @@
+/* sendme.c -- generated by Trunnel v1.5.2.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#include <stdlib.h>
+#include "trunnel-impl.h"
+
+#include "sendme.h"
+
+#define TRUNNEL_SET_ERROR_CODE(obj) \
+ do { \
+ (obj)->trunnel_error_code_ = 1; \
+ } while (0)
+
+#if defined(__COVERITY__) || defined(__clang_analyzer__)
+/* If we're running a static analysis tool, we don't want it to complain
+ * that some of our remaining-bytes checks are dead-code. */
+int sendme_deadcode_dummy__ = 0;
+#define OR_DEADCODE_DUMMY || sendme_deadcode_dummy__
+#else
+#define OR_DEADCODE_DUMMY
+#endif
+
+#define CHECK_REMAINING(nbytes, label) \
+ do { \
+ if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \
+ goto label; \
+ } \
+ } while (0)
+
+sendme_cell_t *
+sendme_cell_new(void)
+{
+ sendme_cell_t *val = trunnel_calloc(1, sizeof(sendme_cell_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+sendme_cell_clear(sendme_cell_t *obj)
+{
+ (void) obj;
+}
+
+void
+sendme_cell_free(sendme_cell_t *obj)
+{
+ if (obj == NULL)
+ return;
+ sendme_cell_clear(obj);
+ trunnel_memwipe(obj, sizeof(sendme_cell_t));
+ trunnel_free_(obj);
+}
+
+uint8_t
+sendme_cell_get_version(const sendme_cell_t *inp)
+{
+ return inp->version;
+}
+int
+sendme_cell_set_version(sendme_cell_t *inp, uint8_t val)
+{
+ if (! ((val == 0 || val == 1))) {
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+ }
+ inp->version = val;
+ return 0;
+}
+uint16_t
+sendme_cell_get_data_len(const sendme_cell_t *inp)
+{
+ return inp->data_len;
+}
+int
+sendme_cell_set_data_len(sendme_cell_t *inp, uint16_t val)
+{
+ inp->data_len = val;
+ return 0;
+}
+size_t
+sendme_cell_getlen_data_v1_digest(const sendme_cell_t *inp)
+{
+ (void)inp; return TRUNNEL_SENDME_V1_DIGEST_LEN;
+}
+
+uint8_t
+sendme_cell_get_data_v1_digest(sendme_cell_t *inp, size_t idx)
+{
+ trunnel_assert(idx < TRUNNEL_SENDME_V1_DIGEST_LEN);
+ return inp->data_v1_digest[idx];
+}
+
+uint8_t
+sendme_cell_getconst_data_v1_digest(const sendme_cell_t *inp, size_t idx)
+{
+ return sendme_cell_get_data_v1_digest((sendme_cell_t*)inp, idx);
+}
+int
+sendme_cell_set_data_v1_digest(sendme_cell_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < TRUNNEL_SENDME_V1_DIGEST_LEN);
+ inp->data_v1_digest[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+sendme_cell_getarray_data_v1_digest(sendme_cell_t *inp)
+{
+ return inp->data_v1_digest;
+}
+const uint8_t *
+sendme_cell_getconstarray_data_v1_digest(const sendme_cell_t *inp)
+{
+ return (const uint8_t *)sendme_cell_getarray_data_v1_digest((sendme_cell_t*)inp);
+}
+const char *
+sendme_cell_check(const sendme_cell_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ if (! (obj->version == 0 || obj->version == 1))
+ return "Integer out of bounds";
+ switch (obj->version) {
+
+ case 0:
+ break;
+
+ case 1:
+ break;
+
+ default:
+ return "Bad tag for union";
+ break;
+ }
+ return NULL;
+}
+
+ssize_t
+sendme_cell_encoded_len(const sendme_cell_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != sendme_cell_check(obj))
+ return -1;
+
+
+ /* Length of u8 version IN [0, 1] */
+ result += 1;
+
+ /* Length of u16 data_len */
+ result += 2;
+ switch (obj->version) {
+
+ case 0:
+ break;
+
+ case 1:
+
+ /* Length of u8 data_v1_digest[TRUNNEL_SENDME_V1_DIGEST_LEN] */
+ result += TRUNNEL_SENDME_V1_DIGEST_LEN;
+ break;
+
+ default:
+ trunnel_assert(0);
+ break;
+ }
+ return result;
+}
+int
+sendme_cell_clear_errors(sendme_cell_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+sendme_cell_encode(uint8_t *output, const size_t avail, const sendme_cell_t *obj)
+{
+ ssize_t result = 0;
+ size_t written = 0;
+ uint8_t *ptr = output;
+ const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ const ssize_t encoded_len = sendme_cell_encoded_len(obj);
+#endif
+
+ uint8_t *backptr_data_len = NULL;
+
+ if (NULL != (msg = sendme_cell_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 version IN [0, 1] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->version));
+ written += 1; ptr += 1;
+
+ /* Encode u16 data_len */
+ backptr_data_len = ptr;
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->data_len));
+ written += 2; ptr += 2;
+ {
+ size_t written_before_union = written;
+
+ /* Encode union data[version] */
+ trunnel_assert(written <= avail);
+ switch (obj->version) {
+
+ case 0:
+ break;
+
+ case 1:
+
+ /* Encode u8 data_v1_digest[TRUNNEL_SENDME_V1_DIGEST_LEN] */
+ trunnel_assert(written <= avail);
+ if (avail - written < TRUNNEL_SENDME_V1_DIGEST_LEN)
+ goto truncated;
+ memcpy(ptr, obj->data_v1_digest, TRUNNEL_SENDME_V1_DIGEST_LEN);
+ written += TRUNNEL_SENDME_V1_DIGEST_LEN; ptr += TRUNNEL_SENDME_V1_DIGEST_LEN;
+ break;
+
+ default:
+ trunnel_assert(0);
+ break;
+ }
+ /* Write the length field back to data_len */
+ trunnel_assert(written >= written_before_union);
+#if UINT16_MAX < SIZE_MAX
+ if (written - written_before_union > UINT16_MAX)
+ goto check_failed;
+#endif
+ trunnel_set_uint16(backptr_data_len, trunnel_htons(written - written_before_union));
+ }
+
+
+ trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ {
+ trunnel_assert(encoded_len >= 0);
+ trunnel_assert((size_t)encoded_len == written);
+ }
+
+#endif
+
+ return written;
+
+ truncated:
+ result = -2;
+ goto fail;
+ check_failed:
+ (void)msg;
+ result = -1;
+ goto fail;
+ fail:
+ trunnel_assert(result < 0);
+ return result;
+}
+
+/** As sendme_cell_parse(), but do not allocate the output object.
+ */
+static ssize_t
+sendme_cell_parse_into(sendme_cell_t *obj, const uint8_t *input, const size_t len_in)
+{
+ const uint8_t *ptr = input;
+ size_t remaining = len_in;
+ ssize_t result = 0;
+ (void)result;
+
+ /* Parse u8 version IN [0, 1] */
+ CHECK_REMAINING(1, truncated);
+ obj->version = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+ if (! (obj->version == 0 || obj->version == 1))
+ goto fail;
+
+ /* Parse u16 data_len */
+ CHECK_REMAINING(2, truncated);
+ obj->data_len = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+ {
+ size_t remaining_after;
+ CHECK_REMAINING(obj->data_len, truncated);
+ remaining_after = remaining - obj->data_len;
+ remaining = obj->data_len;
+
+ /* Parse union data[version] */
+ switch (obj->version) {
+
+ case 0:
+ /* Skip to end of union */
+ ptr += remaining; remaining = 0;
+ break;
+
+ case 1:
+
+ /* Parse u8 data_v1_digest[TRUNNEL_SENDME_V1_DIGEST_LEN] */
+ CHECK_REMAINING(TRUNNEL_SENDME_V1_DIGEST_LEN, fail);
+ memcpy(obj->data_v1_digest, ptr, TRUNNEL_SENDME_V1_DIGEST_LEN);
+ remaining -= TRUNNEL_SENDME_V1_DIGEST_LEN; ptr += TRUNNEL_SENDME_V1_DIGEST_LEN;
+ break;
+
+ default:
+ goto fail;
+ break;
+ }
+ if (remaining != 0)
+ goto fail;
+ remaining = remaining_after;
+ }
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ fail:
+ result = -1;
+ return result;
+}
+
+ssize_t
+sendme_cell_parse(sendme_cell_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = sendme_cell_new();
+ if (NULL == *output)
+ return -1;
+ result = sendme_cell_parse_into(*output, input, len_in);
+ if (result < 0) {
+ sendme_cell_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
diff --git a/src/trunnel/sendme.h b/src/trunnel/sendme.h
new file mode 100644
index 0000000000..f3c3dd78c4
--- /dev/null
+++ b/src/trunnel/sendme.h
@@ -0,0 +1,101 @@
+/* sendme.h -- generated by Trunnel v1.5.2.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#ifndef TRUNNEL_SENDME_H
+#define TRUNNEL_SENDME_H
+
+#include <stdint.h>
+#include "trunnel.h"
+
+#define TRUNNEL_SENDME_V1_DIGEST_LEN 20
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SENDME_CELL)
+struct sendme_cell_st {
+ uint8_t version;
+ uint16_t data_len;
+ uint8_t data_v1_digest[TRUNNEL_SENDME_V1_DIGEST_LEN];
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct sendme_cell_st sendme_cell_t;
+/** Return a newly allocated sendme_cell with all elements set to
+ * zero.
+ */
+sendme_cell_t *sendme_cell_new(void);
+/** Release all storage held by the sendme_cell in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void sendme_cell_free(sendme_cell_t *victim);
+/** Try to parse a sendme_cell from the buffer in 'input', using up to
+ * 'len_in' bytes from the input buffer. On success, return the number
+ * of bytes consumed and set *output to the newly allocated
+ * sendme_cell_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t sendme_cell_parse(sendme_cell_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * sendme_cell in 'obj'. On failure, return a negative value. Note
+ * that this value may be an overestimate, and can even be an
+ * underestimate for certain unencodeable objects.
+ */
+ssize_t sendme_cell_encoded_len(const sendme_cell_t *obj);
+/** Try to encode the sendme_cell from 'input' into the buffer at
+ * 'output', using up to 'avail' bytes of the output buffer. On
+ * success, return the number of bytes used. On failure, return -2 if
+ * the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t sendme_cell_encode(uint8_t *output, size_t avail, const sendme_cell_t *input);
+/** Check whether the internal state of the sendme_cell in 'obj' is
+ * consistent. Return NULL if it is, and a short message if it is not.
+ */
+const char *sendme_cell_check(const sendme_cell_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int sendme_cell_clear_errors(sendme_cell_t *obj);
+/** Return the value of the version field of the sendme_cell_t in
+ * 'inp'
+ */
+uint8_t sendme_cell_get_version(const sendme_cell_t *inp);
+/** Set the value of the version field of the sendme_cell_t in 'inp'
+ * to 'val'. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int sendme_cell_set_version(sendme_cell_t *inp, uint8_t val);
+/** Return the value of the data_len field of the sendme_cell_t in
+ * 'inp'
+ */
+uint16_t sendme_cell_get_data_len(const sendme_cell_t *inp);
+/** Set the value of the data_len field of the sendme_cell_t in 'inp'
+ * to 'val'. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int sendme_cell_set_data_len(sendme_cell_t *inp, uint16_t val);
+/** Return the (constant) length of the array holding the
+ * data_v1_digest field of the sendme_cell_t in 'inp'.
+ */
+size_t sendme_cell_getlen_data_v1_digest(const sendme_cell_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * data_v1_digest of the sendme_cell_t in 'inp'.
+ */
+uint8_t sendme_cell_get_data_v1_digest(sendme_cell_t *inp, size_t idx);
+/** As sendme_cell_get_data_v1_digest, but take and return a const
+ * pointer
+ */
+uint8_t sendme_cell_getconst_data_v1_digest(const sendme_cell_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * data_v1_digest of the sendme_cell_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int sendme_cell_set_data_v1_digest(sendme_cell_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the TRUNNEL_SENDME_V1_DIGEST_LEN-element array
+ * field data_v1_digest of 'inp'.
+ */
+uint8_t * sendme_cell_getarray_data_v1_digest(sendme_cell_t *inp);
+/** As sendme_cell_get_data_v1_digest, but take and return a const
+ * pointer
+ */
+const uint8_t * sendme_cell_getconstarray_data_v1_digest(const sendme_cell_t *inp);
+
+
+#endif
diff --git a/src/trunnel/sendme.trunnel b/src/trunnel/sendme.trunnel
new file mode 100644
index 0000000000..300963e679
--- /dev/null
+++ b/src/trunnel/sendme.trunnel
@@ -0,0 +1,19 @@
+/* This file contains the SENDME cell definition. */
+
+/* v1 digest length in bytes. */
+const TRUNNEL_SENDME_V1_DIGEST_LEN = 20;
+
+/* SENDME cell declaration. */
+struct sendme_cell {
+ /* Version field. */
+ u8 version IN [0x00, 0x01];
+
+ /* Length of data contained in this cell. */
+ u16 data_len;
+
+ /* The data content depends on the version. */
+ union data[version] with length data_len {
+ 0x00: ignore;
+ 0x01: u8 v1_digest[TRUNNEL_SENDME_V1_DIGEST_LEN];
+ };
+}