aboutsummaryrefslogtreecommitdiff
path: root/src/core/or/conflux_util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/or/conflux_util.c')
-rw-r--r--src/core/or/conflux_util.c393
1 files changed, 393 insertions, 0 deletions
diff --git a/src/core/or/conflux_util.c b/src/core/or/conflux_util.c
new file mode 100644
index 0000000000..855d477428
--- /dev/null
+++ b/src/core/or/conflux_util.c
@@ -0,0 +1,393 @@
+/* Copyright (c) 2021, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file conflux_util.c
+ * \brief Conflux utility functions for stream blocking and management.
+ */
+
+#define TOR_CONFLUX_PRIVATE
+
+#include "core/or/or.h"
+
+#include "core/or/circuit_st.h"
+#include "core/or/sendme.h"
+#include "core/or/congestion_control_common.h"
+#include "core/or/congestion_control_st.h"
+#include "core/or/circuitlist.h"
+#include "core/or/origin_circuit_st.h"
+#include "core/or/or_circuit_st.h"
+#include "core/or/conflux.h"
+#include "core/or/conflux_params.h"
+#include "core/or/conflux_util.h"
+#include "core/or/conflux_st.h"
+#include "lib/time/compat_time.h"
+#include "app/config/config.h"
+
+/**
+ * This is a utility function that returns the package window circuit,
+ * regardless of if it has a conflux pair or not.
+ */
+int
+circuit_get_package_window(circuit_t *circ,
+ const crypt_path_t *cpath)
+{
+ if (circ->conflux) {
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ tor_assert_nonfatal(circ->purpose ==
+ CIRCUIT_PURPOSE_CONFLUX_LINKED);
+ }
+ circ = conflux_decide_next_circ(circ->conflux);
+
+ /* If conflux has no circuit to send on, the package window is 0. */
+ if (!circ) {
+ return 0;
+ }
+
+ /* If we are the origin, we need to get the last hop's cpath for
+ * congestion control information. */
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ cpath = CONST_TO_ORIGIN_CIRCUIT(circ)->cpath->prev;
+ } else {
+ if (BUG(cpath != NULL)) {
+ log_warn(LD_BUG, "cpath is not NULL for non-origin circuit");
+ }
+ }
+ }
+
+ return congestion_control_get_package_window(circ, cpath);
+}
+
+/**
+ * Returns true if conflux can send a data cell.
+ *
+ * Used to decide if we should block streams or not, for
+ * proccess_sendme_cell(), circuit_resume_edge_reading(),
+ * circuit_consider_stop_edge_reading(), circuit_resume_edge_reading_helper(),
+ * channel_flush_from_first_active_circuit()
+*/
+bool
+conflux_can_send(conflux_t *cfx)
+{
+ const circuit_t *send_circ = conflux_decide_next_circ(cfx);
+
+ /* If we have a circuit, we can send */
+ if (send_circ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * For a given conflux circuit, return the cpath of the destination.
+ *
+ * The cpath destination is the last hop of the circuit, or NULL if
+ * the circuit is a non-origin circuit.
+ */
+crypt_path_t *
+conflux_get_destination_hop(circuit_t *circ)
+{
+ if (BUG(!circ)) {
+ log_warn(LD_BUG, "No circuit to send on for conflux");
+ return NULL;
+ } else {
+ /* Conflux circuits always send multiplexed relay commands to
+ * to the last hop. (Non-multiplexed commands go on their
+ * original circuit and hop). */
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ return TO_ORIGIN_CIRCUIT(circ)->cpath->prev;
+ } else {
+ return NULL;
+ }
+ }
+}
+
+/**
+ * Validates that the source of a cell is from the last hop of the circuit
+ * for origin circuits, and that there are no further hops for non-origin
+ * circuits.
+ */
+bool
+conflux_validate_source_hop(circuit_t *in_circ,
+ crypt_path_t *layer_hint)
+{
+ crypt_path_t *dest = conflux_get_destination_hop(in_circ);
+
+ if (dest != layer_hint) {
+ log_warn(LD_CIRC, "Got conflux command from incorrect hop");
+ return false;
+ }
+
+ if (layer_hint == NULL) {
+ /* We should not have further hops attached to this circuit */
+ if (in_circ->n_chan) {
+ log_warn(LD_BUG, "Got conflux command on circuit with further hops");
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Returns true if the edge connection uses the given cpath.
+ *
+ * If there is a conflux object, we inspect all the last hops of the conflux
+ * circuits.
+ */
+bool
+edge_uses_cpath(const edge_connection_t *conn,
+ const crypt_path_t *cpath)
+{
+ if (!conn->on_circuit)
+ return false;
+
+ if (CIRCUIT_IS_ORIGIN(conn->on_circuit)) {
+ if (conn->on_circuit->conflux) {
+ tor_assert_nonfatal(conn->on_circuit->purpose ==
+ CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+ /* If the circuit is an origin circuit with a conflux object, the cpath
+ * is valid if it came from any of the conflux circuit's last hops. */
+ CONFLUX_FOR_EACH_LEG_BEGIN(conn->on_circuit->conflux, leg) {
+ const origin_circuit_t *ocirc = CONST_TO_ORIGIN_CIRCUIT(leg->circ);
+ if (ocirc->cpath->prev == cpath) {
+ return true;
+ }
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+ } else {
+ return cpath == conn->cpath_layer;
+ }
+ } else {
+ /* For non-origin circuits, cpath should be null */
+ return cpath == NULL;
+ }
+
+ return false;
+}
+
+/**
+ * Returns the max RTT for the circuit that carries this stream,
+ * as observed by congestion control. For conflux circuits,
+ * we return the max RTT across all circuits.
+ */
+uint64_t
+edge_get_max_rtt(const edge_connection_t *stream)
+{
+ if (!stream->on_circuit)
+ return 0;
+
+ if (stream->on_circuit->conflux) {
+ tor_assert_nonfatal(stream->on_circuit->purpose ==
+ CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+ /* Find the max rtt from the ccontrol object of each circuit. */
+ uint64_t max_rtt = 0;
+ CONFLUX_FOR_EACH_LEG_BEGIN(stream->on_circuit->conflux, leg) {
+ const congestion_control_t *cc = circuit_ccontrol(leg->circ);
+ if (cc->max_rtt_usec > max_rtt) {
+ max_rtt = cc->max_rtt_usec;
+ }
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+
+ return max_rtt;
+ } else {
+ if (stream->on_circuit && stream->on_circuit->ccontrol)
+ return stream->on_circuit->ccontrol->max_rtt_usec;
+ else if (stream->cpath_layer && stream->cpath_layer->ccontrol)
+ return stream->cpath_layer->ccontrol->max_rtt_usec;
+ }
+
+ return 0;
+}
+
+/**
+ * Return true iff our decryption layer_hint is from the last hop
+ * in a circuit.
+ */
+bool
+relay_crypt_from_last_hop(const origin_circuit_t *circ,
+ const crypt_path_t *layer_hint)
+{
+ tor_assert(circ);
+ tor_assert(layer_hint);
+ tor_assert(circ->cpath);
+
+ if (TO_CIRCUIT(circ)->conflux) {
+ tor_assert_nonfatal(TO_CIRCUIT(circ)->purpose ==
+ CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+ /* If we are a conflux circuit, we need to check if the layer_hint
+ * is from the last hop of any of the conflux circuits. */
+ CONFLUX_FOR_EACH_LEG_BEGIN(TO_CIRCUIT(circ)->conflux, leg) {
+ const origin_circuit_t *ocirc = CONST_TO_ORIGIN_CIRCUIT(leg->circ);
+ if (layer_hint == ocirc->cpath->prev) {
+ return true;
+ }
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+
+ log_fn(LOG_PROTOCOL_WARN, LD_CIRC,
+ "Got unexpected relay data from intermediate hop");
+ return false;
+ } else {
+ if (layer_hint != circ->cpath->prev) {
+ log_fn(LOG_PROTOCOL_WARN, LD_CIRC,
+ "Got unexpected relay data from intermediate hop");
+ return false;
+ }
+ return true;
+ }
+}
+
+/**
+ * Update the head of the n_streams list on all circuits in the conflux
+ * set.
+ */
+void
+conflux_update_p_streams(origin_circuit_t *circ, edge_connection_t *stream)
+{
+ tor_assert(circ);
+
+ if (TO_CIRCUIT(circ)->conflux) {
+ tor_assert_nonfatal(TO_CIRCUIT(circ)->purpose ==
+ CIRCUIT_PURPOSE_CONFLUX_LINKED);
+ CONFLUX_FOR_EACH_LEG_BEGIN(TO_CIRCUIT(circ)->conflux, leg) {
+ TO_ORIGIN_CIRCUIT(leg->circ)->p_streams = stream;
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+ }
+}
+
+/**
+ * Sync the next_stream_id, timestamp_dirty, and circuit_idle_timeout
+ * fields of a conflux set to the values in a particular circuit.
+ *
+ * This is called upon link, and whenever one of these fields
+ * changes on ref_circ. The ref_circ values are copied to all
+ * other circuits in the conflux set.
+*/
+void
+conflux_sync_circ_fields(conflux_t *cfx, origin_circuit_t *ref_circ)
+{
+ tor_assert(cfx);
+ tor_assert(ref_circ);
+
+ CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) {
+ if (leg->circ == TO_CIRCUIT(ref_circ)) {
+ continue;
+ }
+ origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(leg->circ);
+ ocirc->next_stream_id = ref_circ->next_stream_id;
+ leg->circ->timestamp_dirty = TO_CIRCUIT(ref_circ)->timestamp_dirty;
+ ocirc->circuit_idle_timeout = ref_circ->circuit_idle_timeout;
+ ocirc->unusable_for_new_conns = ref_circ->unusable_for_new_conns;
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+}
+
+/**
+ * Update the head of the n_streams list on all circuits in the conflux
+ * set.
+ */
+void
+conflux_update_n_streams(or_circuit_t *circ, edge_connection_t *stream)
+{
+ tor_assert(circ);
+
+ if (TO_CIRCUIT(circ)->conflux) {
+ CONFLUX_FOR_EACH_LEG_BEGIN(TO_CIRCUIT(circ)->conflux, leg) {
+ TO_OR_CIRCUIT(leg->circ)->n_streams = stream;
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+ }
+}
+
+/**
+ * Update the head of the resolving_streams list on all circuits in the conflux
+ * set.
+ */
+void
+conflux_update_resolving_streams(or_circuit_t *circ, edge_connection_t *stream)
+{
+ tor_assert(circ);
+
+ if (TO_CIRCUIT(circ)->conflux) {
+ CONFLUX_FOR_EACH_LEG_BEGIN(TO_CIRCUIT(circ)->conflux, leg) {
+ TO_OR_CIRCUIT(leg->circ)->resolving_streams = stream;
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+ }
+}
+
+/**
+ * Update the half_streams list on all circuits in the conflux
+ */
+void
+conflux_update_half_streams(origin_circuit_t *circ, smartlist_t *half_streams)
+{
+ tor_assert(circ);
+
+ if (TO_CIRCUIT(circ)->conflux) {
+ tor_assert_nonfatal(TO_CIRCUIT(circ)->purpose ==
+ CIRCUIT_PURPOSE_CONFLUX_LINKED);
+ CONFLUX_FOR_EACH_LEG_BEGIN(TO_CIRCUIT(circ)->conflux, leg) {
+ TO_ORIGIN_CIRCUIT(leg->circ)->half_streams = half_streams;
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+ }
+}
+
+/**
+ * Helper function that emits non-fatal asserts if the stream lists
+ * or next_stream_id is out of sync between any of the conflux legs.
+*/
+void
+conflux_validate_stream_lists(const conflux_t *cfx)
+{
+ const conflux_leg_t *first_leg = smartlist_get(cfx->legs, 0);
+ tor_assert(first_leg);
+
+ /* Compare the stream lists of the first leg to all other legs. */
+ if (CIRCUIT_IS_ORIGIN(first_leg->circ)) {
+ const origin_circuit_t *f_circ =
+ CONST_TO_ORIGIN_CIRCUIT(first_leg->circ);
+
+ CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) {
+ const origin_circuit_t *l_circ = CONST_TO_ORIGIN_CIRCUIT(leg->circ);
+ tor_assert_nonfatal(l_circ->p_streams == f_circ->p_streams);
+ tor_assert_nonfatal(l_circ->half_streams == f_circ->half_streams);
+ tor_assert_nonfatal(l_circ->next_stream_id == f_circ->next_stream_id);
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+ } else {
+ const or_circuit_t *f_circ = CONST_TO_OR_CIRCUIT(first_leg->circ);
+ CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) {
+ const or_circuit_t *l_circ = CONST_TO_OR_CIRCUIT(leg->circ);
+ tor_assert_nonfatal(l_circ->n_streams == f_circ->n_streams);
+ tor_assert_nonfatal(l_circ->resolving_streams ==
+ f_circ->resolving_streams);
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+ }
+}
+
+/**
+ * Validate the conflux set has two legs, and both circuits have
+ * no nonce, and for origin circuits, the purpose is CONFLUX_PURPOSE_LINKED.
+ */
+void
+conflux_validate_legs(const conflux_t *cfx)
+{
+ tor_assert(cfx);
+ // TODO-329-UDP: Eventually we want to allow three legs for the
+ // exit case, to allow reconnection of legs to hit an RTT target.
+ // For now, this validation helps find bugs.
+ if (BUG(smartlist_len(cfx->legs) > conflux_params_get_num_legs_set())) {
+ log_warn(LD_BUG, "Number of legs is above maximum of %d allowed: %d\n",
+ conflux_params_get_num_legs_set(), smartlist_len(cfx->legs));
+ }
+
+ CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) {
+ /* Ensure we have no pending nonce on the circ */
+ tor_assert_nonfatal(leg->circ->conflux_pending_nonce == NULL);
+ tor_assert_nonfatal(leg->circ->conflux != NULL);
+
+ if (CIRCUIT_IS_ORIGIN(leg->circ)) {
+ tor_assert_nonfatal(leg->circ->purpose ==
+ CIRCUIT_PURPOSE_CONFLUX_LINKED);
+ }
+ } CONFLUX_FOR_EACH_LEG_END(leg);
+}