/* 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.
*/
#include "core/or/or.h"
#include "app/config/config.h"
#include "core/mainloop/connection.h"
#include "core/or/circuitlist.h"
#include "core/or/circuituse.h"
#include "core/or/relay.h"
#include "core/or/sendme.h"
#include "feature/nodelist/networkstatus.h"
/* 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);
}
/** Called when we've just received a relay data cell, when we've just
* finished flushing all bytes to stream conn, or when we've flushed
* *some* bytes to the stream conn.
*
* 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 circ (at hop
* layer_hint 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)
{
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 */
}
}
}
/* 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).
*
* 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, uint16_t cell_body_len)
{
tor_assert(circ);
/* 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_body_len);
} else {
/* 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_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_stream_data_packaged(edge_connection_t *conn)
{
tor_assert(conn);
return --conn->package_window;
}