mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-09-20 13:06:20 +02:00
Merge branch 'tor-github/pr/986'
This commit is contained in:
commit
b2c2cb9287
6
changes/ticket26288
Normal file
6
changes/ticket26288
Normal 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.
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) */
|
||||
|
||||
|
@ -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 \
|
||||
|
@ -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;
|
||||
|
@ -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) ?
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) */
|
||||
|
||||
|
@ -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
604
src/core/or/sendme.c
Normal 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
68
src/core/or/sendme.h
Normal 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) */
|
@ -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 \
|
||||
|
@ -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 },
|
||||
|
@ -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
267
src/test/test_sendme.c
Normal 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
|
||||
};
|
@ -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
347
src/trunnel/sendme.c
Normal 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
101
src/trunnel/sendme.h
Normal 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
|
19
src/trunnel/sendme.trunnel
Normal file
19
src/trunnel/sendme.trunnel
Normal 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];
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user