Merge branch 'tor-github/pr/986'

This commit is contained in:
George Kadianakis 2019-05-02 18:12:52 +03:00
commit b2c2cb9287
22 changed files with 1676 additions and 185 deletions

6
changes/ticket26288 Normal file
View File

@ -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.

View File

@ -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

View File

@ -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);
}

View File

@ -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) */

View File

@ -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 \

View File

@ -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;

View File

@ -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) ?

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -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) */

View File

@ -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

604
src/core/or/sendme.c Normal file
View File

@ -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));
}

68
src/core/or/sendme.h Normal file
View File

@ -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) */

View File

@ -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 \

View File

@ -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 },

View File

@ -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[];

267
src/test/test_sendme.c Normal file
View File

@ -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
};

View File

@ -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

347
src/trunnel/sendme.c Normal file
View File

@ -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;
}

101
src/trunnel/sendme.h Normal file
View File

@ -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

View File

@ -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];
};
}