This is an automated email from the git hooks/post-receive script.
ahf pushed a change to branch main in repository tor.
from 447775a5e0 Merge branch 'maint-0.4.7' new a9fc6c937c protover: Support Relay=5 for Conflux (prop329) new 45432175fe trunnel: Add Conflux related cell definition new 8b185b2ac3 Prop#329 Cells: Building and parsing parsing conflux commands new 0d14d6b44a Prop#329 Tests: Add tests for conflux cells. new eac2bad86b Prop#329 params: Consensus parameter and torrc handling new a1794ef687 Prop#329 Headers: Header files for conflux new 2bd1eca78c Prop#329 Algs: Conflux multiplexed cell receive handling new e0881a669a Prop#329 Algs: Conflux multiplexed cell sending decision algs new b999051e44 Prop#329 OOM: Handle freeing conflux queues on OOM new cf715a56f1 Prop#329 sendme: Adjust sendme sending and tracking for conflux new a4ee0c29ee Prop#329: Add purposes for conflux circuits new 21c861bfa3 Refactor stream blocking due to channel cell queues new 2f865b4bba Prop#329 streams: Handle stream usage with conflux new 336a24754d Prop#329 Pool: Handle linking, unlinking, and relaunching conflux circuit legs. new 46e473f43e Prop#329 Pool: Avoid sharing Guards and Middles between circuits. new 39c2927d6f Prop#329 Pool: Handle pre-building and using conflux sets. new 8d4781e730 Prop#329 Tests: Add tests for the conflux pool new 731a50c8c4 Prop#329: Add conflux to build new 7c70f713c3 Avoid closing dirty circs with active half-edges new 2bb8988629 Fix cases where edge connections can stall.
The 20 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "add" were already present in the repository and have only been added to this reference.
Summary of changes: doc/man/tor.1.txt | 6 + src/app/config/config.c | 1 + src/app/config/or_options_st.h | 4 + src/app/main/shutdown.c | 2 + src/app/main/subsystem_list.c | 2 + src/core/mainloop/connection.c | 27 +- src/core/mainloop/connection.h | 1 + src/core/mainloop/mainloop.c | 12 +- src/core/mainloop/mainloop.h | 2 +- src/core/or/circuit_st.h | 29 +- src/core/or/circuitbuild.c | 80 +- src/core/or/circuitbuild.h | 9 +- src/core/or/circuitlist.c | 35 + src/core/or/circuitlist.h | 9 +- src/core/or/circuitpadding.c | 7 +- src/core/or/circuituse.c | 75 +- src/core/or/circuituse.h | 7 + src/core/or/conflux.c | 1008 ++++++++++++++++ src/core/or/conflux.h | 84 ++ src/core/or/conflux_cell.c | 355 ++++++ src/core/or/conflux_cell.h | 50 + src/core/or/conflux_params.c | 281 +++++ src/core/or/conflux_params.h | 29 + src/core/or/conflux_pool.c | 1999 +++++++++++++++++++++++++++++++ src/core/or/conflux_pool.h | 46 + src/core/or/conflux_st.h | 157 +++ src/core/or/conflux_sys.c | 37 + src/core/or/conflux_sys.h | 23 + src/core/or/conflux_util.c | 393 ++++++ src/core/or/conflux_util.h | 59 + src/core/or/congestion_control_common.c | 14 +- src/core/or/congestion_control_common.h | 2 +- src/core/or/congestion_control_flow.c | 47 +- src/core/or/congestion_control_flow.h | 2 - src/core/or/connection_edge.c | 35 +- src/core/or/connection_edge.h | 1 + src/core/or/cpath_build_state_st.h | 2 + src/core/or/edge_connection_st.h | 13 +- src/core/or/include.am | 13 + src/core/or/or.h | 12 + src/core/or/or_circuit_st.h | 12 +- src/core/or/origin_circuit_st.h | 11 +- src/core/or/protover.c | 4 + src/core/or/protover.h | 3 + src/core/or/relay.c | 383 ++++-- src/core/or/versions.c | 7 + src/feature/client/circpathbias.c | 13 +- src/feature/client/entrynodes.c | 42 +- src/feature/client/entrynodes.h | 14 +- src/feature/nodelist/networkstatus.c | 2 + src/feature/nodelist/node_select.h | 2 + src/feature/nodelist/nodelist.c | 11 +- src/feature/nodelist/nodelist.h | 1 + src/feature/nodelist/routerlist.c | 5 + src/feature/relay/dns.c | 4 + src/test/fakecircs.c | 10 +- src/test/include.am | 2 + src/test/test.c | 2 + src/test/test.h | 2 + src/test/test_conflux_cell.c | 60 + src/test/test_conflux_pool.c | 1338 +++++++++++++++++++++ src/trunnel/conflux.c | 1158 ++++++++++++++++++ src/trunnel/conflux.h | 422 +++++++ src/trunnel/conflux.trunnel | 66 + src/trunnel/include.am | 9 +- 65 files changed, 8327 insertions(+), 226 deletions(-) create mode 100644 src/core/or/conflux.c create mode 100644 src/core/or/conflux.h create mode 100644 src/core/or/conflux_cell.c create mode 100644 src/core/or/conflux_cell.h create mode 100644 src/core/or/conflux_params.c create mode 100644 src/core/or/conflux_params.h create mode 100644 src/core/or/conflux_pool.c create mode 100644 src/core/or/conflux_pool.h create mode 100644 src/core/or/conflux_st.h create mode 100644 src/core/or/conflux_sys.c create mode 100644 src/core/or/conflux_sys.h create mode 100644 src/core/or/conflux_util.c create mode 100644 src/core/or/conflux_util.h create mode 100644 src/test/test_conflux_cell.c create mode 100644 src/test/test_conflux_pool.c create mode 100644 src/trunnel/conflux.c create mode 100644 src/trunnel/conflux.h create mode 100644 src/trunnel/conflux.trunnel
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit a9fc6c937c42d879850ea46366a9fad8ff6d404b Author: David Goulet dgoulet@torproject.org AuthorDate: Wed Nov 30 13:48:53 2022 -0500
protover: Support Relay=5 for Conflux (prop329)
Closes #40721
Signed-off-by: David Goulet dgoulet@torproject.org --- src/core/or/or.h | 3 +++ src/core/or/protover.c | 4 ++++ src/core/or/protover.h | 3 +++ src/core/or/versions.c | 7 +++++++ src/feature/nodelist/nodelist.c | 11 ++++++++++- src/feature/nodelist/nodelist.h | 1 + 6 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/src/core/or/or.h b/src/core/or/or.h index c6d9864b53..ece2475613 100644 --- a/src/core/or/or.h +++ b/src/core/or/or.h @@ -731,6 +731,9 @@ typedef struct protover_summary_flags_t { /** True iff this router supports congestion control. * Requires both FlowCtrl=2 *and* Relay=4 */ unsigned int supports_congestion_control : 1; + + /** True iff this router supports conflux. Requires Relay=5 */ + unsigned int supports_conflux : 1; } protover_summary_flags_t;
typedef struct routerinfo_t routerinfo_t; diff --git a/src/core/or/protover.c b/src/core/or/protover.c index 4cd6510da7..175bfbdab0 100644 --- a/src/core/or/protover.c +++ b/src/core/or/protover.c @@ -55,6 +55,7 @@ static const struct { { PRT_PADDING, "Padding"}, { PRT_CONS, "Cons" }, { PRT_FLOWCTRL, "FlowCtrl"}, + { PRT_CONFLUX, "Conflux"}, };
#define N_PROTOCOL_NAMES ARRAY_LENGTH(PROTOCOL_NAMES) @@ -386,6 +387,7 @@ protocol_list_supports_protocol_or_later(const char *list, * XXX START OF HAZARDOUS ZONE XXX */ /* All protocol version that this relay version supports. */ +#define PR_CONFLUX_V "1" #define PR_CONS_V "1-2" #define PR_DESC_V "1-2" #define PR_DIRCACHE_V "2" @@ -409,6 +411,7 @@ const char * protover_get_supported(const protocol_type_t type) { switch (type) { + case PRT_CONFLUX: return PR_CONFLUX_V; case PRT_CONS: return PR_CONS_V; case PRT_DESC: return PR_DESC_V; case PRT_DIRCACHE: return PR_DIRCACHE_V; @@ -471,6 +474,7 @@ protover_get_supported_protocols(void) */
return + "Conflux=" PR_CONFLUX_V " " "Cons=" PR_CONS_V " " "Desc=" PR_DESC_V " " "DirCache=" PR_DIRCACHE_V " " diff --git a/src/core/or/protover.h b/src/core/or/protover.h index 8f15c02fb2..9d8eb4dcc5 100644 --- a/src/core/or/protover.h +++ b/src/core/or/protover.h @@ -37,6 +37,8 @@ struct smartlist_t; #define PROTOVER_RELAY_CANONICAL_IPV6 3 /** The protover version number where relays can accept ntorv3 */ #define PROTOVER_RELAY_NTOR_V3 4 +/** The protover that signals conflux support. */ +#define PROTOVER_CONFLUX_V1 1
/** The protover version number that signifies HSv3 intro point support */ #define PROTOVER_HS_INTRO_V3 4 @@ -72,6 +74,7 @@ typedef enum protocol_type_t { PRT_CONS = 9, PRT_PADDING = 10, PRT_FLOWCTRL = 11, + PRT_CONFLUX = 12, } protocol_type_t;
bool protover_list_is_invalid(const char *s); diff --git a/src/core/or/versions.c b/src/core/or/versions.c index 9913b3ee31..8f5503691e 100644 --- a/src/core/or/versions.c +++ b/src/core/or/versions.c @@ -488,6 +488,13 @@ memoize_protover_summary(protover_summary_flags_t *out, protocol_list_supports_protocol(protocols, PRT_RELAY, PROTOVER_RELAY_NTOR_V3);
+ /* Conflux requires congestion control. */ + out->supports_conflux = + protocol_list_supports_protocol(protocols, PRT_FLOWCTRL, + PROTOVER_FLOWCTRL_CC) && + protocol_list_supports_protocol(protocols, PRT_CONFLUX, + PROTOVER_CONFLUX_V1); + protover_summary_flags_t *new_cached = tor_memdup(out, sizeof(*out)); cached = strmap_set(protover_summary_map, protocols, new_cached); tor_assert(!cached); diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index b895a2c7f8..bbaa51a407 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -1205,7 +1205,7 @@ node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id) /** Dummy object that should be unreturnable. Used to ensure that * node_get_protover_summary_flags() always returns non-NULL. */ static const protover_summary_flags_t zero_protover_flags = { - 0,0,0,0,0,0,0,0,0,0,0,0,0 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
/** Return the protover_summary_flags for a given node. */ @@ -1341,6 +1341,15 @@ node_supports_accepting_ipv6_extends(const node_t *node, } }
+/** Return true iff the given node supports conflux (Relay=5) */ +bool +node_supports_conflux(const node_t *node) +{ + tor_assert(node); + + return node_get_protover_summary_flags(node)->supports_conflux; +} + /** Return the RSA ID key's SHA1 digest for the provided node. */ const uint8_t * node_get_rsa_id_digest(const node_t *node) diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index 5a45490dbb..3d5ad9c0ea 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -84,6 +84,7 @@ bool node_supports_establish_intro_dos_extension(const node_t *node); bool node_supports_initiating_ipv6_extends(const node_t *node); bool node_supports_accepting_ipv6_extends(const node_t *node, bool need_canonical_ipv6_conn); +bool node_supports_conflux(const node_t *node);
const uint8_t *node_get_rsa_id_digest(const node_t *node); MOCK_DECL(smartlist_t *,node_get_link_specifier_smartlist,(const node_t *node,
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 45432175fe6b0929274d6db94aa50aab9a39b452 Author: David Goulet dgoulet@torproject.org AuthorDate: Mon Dec 12 13:09:27 2022 -0500
trunnel: Add Conflux related cell definition
Signed-off-by: David Goulet dgoulet@torproject.org --- src/trunnel/conflux.c | 1158 +++++++++++++++++++++++++++++++++++++++++++ src/trunnel/conflux.h | 422 ++++++++++++++++ src/trunnel/conflux.trunnel | 66 +++ src/trunnel/include.am | 9 +- 4 files changed, 1652 insertions(+), 3 deletions(-)
diff --git a/src/trunnel/conflux.c b/src/trunnel/conflux.c new file mode 100644 index 0000000000..5f1cb41ff2 --- /dev/null +++ b/src/trunnel/conflux.c @@ -0,0 +1,1158 @@ +/* conflux.c -- generated by Trunnel v1.5.3. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#include <stdlib.h> +#include "trunnel-impl.h" + +#include "conflux.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 conflux_deadcode_dummy__ = 0; +#define OR_DEADCODE_DUMMY || conflux_deadcode_dummy__ +#else +#define OR_DEADCODE_DUMMY +#endif + +#define CHECK_REMAINING(nbytes, label) \ + do { \ + if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \ + goto label; \ + } \ + } while (0) + +trn_cell_conflux_link_t * +trn_cell_conflux_link_new(void) +{ + trn_cell_conflux_link_t *val = trunnel_calloc(1, sizeof(trn_cell_conflux_link_t)); + if (NULL == val) + return NULL; + val->version = 1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_conflux_link_clear(trn_cell_conflux_link_t *obj) +{ + (void) obj; + TRUNNEL_DYNARRAY_WIPE(&obj->payload); + TRUNNEL_DYNARRAY_CLEAR(&obj->payload); +} + +void +trn_cell_conflux_link_free(trn_cell_conflux_link_t *obj) +{ + if (obj == NULL) + return; + trn_cell_conflux_link_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_conflux_link_t)); + trunnel_free_(obj); +} + +uint8_t +trn_cell_conflux_link_get_version(const trn_cell_conflux_link_t *inp) +{ + return inp->version; +} +int +trn_cell_conflux_link_set_version(trn_cell_conflux_link_t *inp, uint8_t val) +{ + if (! ((val == 1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->version = val; + return 0; +} +size_t +trn_cell_conflux_link_getlen_payload(const trn_cell_conflux_link_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->payload); +} + +uint8_t +trn_cell_conflux_link_get_payload(trn_cell_conflux_link_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->payload, idx); +} + +uint8_t +trn_cell_conflux_link_getconst_payload(const trn_cell_conflux_link_t *inp, size_t idx) +{ + return trn_cell_conflux_link_get_payload((trn_cell_conflux_link_t*)inp, idx); +} +int +trn_cell_conflux_link_set_payload(trn_cell_conflux_link_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->payload, idx, elt); + return 0; +} +int +trn_cell_conflux_link_add_payload(trn_cell_conflux_link_t *inp, uint8_t elt) +{ + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->payload, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +trn_cell_conflux_link_getarray_payload(trn_cell_conflux_link_t *inp) +{ + return inp->payload.elts_; +} +const uint8_t * +trn_cell_conflux_link_getconstarray_payload(const trn_cell_conflux_link_t *inp) +{ + return (const uint8_t *)trn_cell_conflux_link_getarray_payload((trn_cell_conflux_link_t*)inp); +} +int +trn_cell_conflux_link_setlen_payload(trn_cell_conflux_link_t *inp, size_t newlen) +{ + uint8_t *newptr; + newptr = trunnel_dynarray_setlen(&inp->payload.allocated_, + &inp->payload.n_, inp->payload.elts_, newlen, + sizeof(inp->payload.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->payload.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +trn_cell_conflux_link_check(const trn_cell_conflux_link_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 == 1)) + return "Integer out of bounds"; + return NULL; +} + +ssize_t +trn_cell_conflux_link_encoded_len(const trn_cell_conflux_link_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_conflux_link_check(obj)) + return -1; + + + /* Length of u8 version IN [1] */ + result += 1; + + /* Length of u8 payload[] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->payload); + return result; +} +int +trn_cell_conflux_link_clear_errors(trn_cell_conflux_link_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_conflux_link_encode(uint8_t *output, const size_t avail, const trn_cell_conflux_link_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 = trn_cell_conflux_link_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_conflux_link_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 version IN [1] */ + trunnel_assert(written <= avail); + if (avail - written < 1) + goto truncated; + trunnel_set_uint8(ptr, (obj->version)); + written += 1; ptr += 1; + + /* Encode u8 payload[] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->payload); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->payload.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + + 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 trn_cell_conflux_link_parse(), but do not allocate the output + * object. + */ +static ssize_t +trn_cell_conflux_link_parse_into(trn_cell_conflux_link_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 [1] */ + CHECK_REMAINING(1, truncated); + obj->version = (trunnel_get_uint8(ptr)); + remaining -= 1; ptr += 1; + if (! (obj->version == 1)) + goto fail; + + /* Parse u8 payload[] */ + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->payload, remaining, {}); + obj->payload.n_ = remaining; + if (remaining) + memcpy(obj->payload.elts_, ptr, remaining); + ptr += remaining; remaining -= remaining; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + trunnel_alloc_failed: + return -1; + fail: + result = -1; + return result; +} + +ssize_t +trn_cell_conflux_link_parse(trn_cell_conflux_link_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_conflux_link_new(); + if (NULL == *output) + return -1; + result = trn_cell_conflux_link_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_conflux_link_free(*output); + *output = NULL; + } + return result; +} +trn_cell_conflux_link_payload_v1_t * +trn_cell_conflux_link_payload_v1_new(void) +{ + trn_cell_conflux_link_payload_v1_t *val = trunnel_calloc(1, sizeof(trn_cell_conflux_link_payload_v1_t)); + if (NULL == val) + return NULL; + val->desired_ux = CONFLUX_UX_HIGH_THROUGHPUT; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_conflux_link_payload_v1_clear(trn_cell_conflux_link_payload_v1_t *obj) +{ + (void) obj; +} + +void +trn_cell_conflux_link_payload_v1_free(trn_cell_conflux_link_payload_v1_t *obj) +{ + if (obj == NULL) + return; + trn_cell_conflux_link_payload_v1_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_conflux_link_payload_v1_t)); + trunnel_free_(obj); +} + +size_t +trn_cell_conflux_link_payload_v1_getlen_nonce(const trn_cell_conflux_link_payload_v1_t *inp) +{ + (void)inp; return 32; +} + +uint8_t +trn_cell_conflux_link_payload_v1_get_nonce(trn_cell_conflux_link_payload_v1_t *inp, size_t idx) +{ + trunnel_assert(idx < 32); + return inp->nonce[idx]; +} + +uint8_t +trn_cell_conflux_link_payload_v1_getconst_nonce(const trn_cell_conflux_link_payload_v1_t *inp, size_t idx) +{ + return trn_cell_conflux_link_payload_v1_get_nonce((trn_cell_conflux_link_payload_v1_t*)inp, idx); +} +int +trn_cell_conflux_link_payload_v1_set_nonce(trn_cell_conflux_link_payload_v1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 32); + inp->nonce[idx] = elt; + return 0; +} + +uint8_t * +trn_cell_conflux_link_payload_v1_getarray_nonce(trn_cell_conflux_link_payload_v1_t *inp) +{ + return inp->nonce; +} +const uint8_t * +trn_cell_conflux_link_payload_v1_getconstarray_nonce(const trn_cell_conflux_link_payload_v1_t *inp) +{ + return (const uint8_t *)trn_cell_conflux_link_payload_v1_getarray_nonce((trn_cell_conflux_link_payload_v1_t*)inp); +} +uint64_t +trn_cell_conflux_link_payload_v1_get_last_seqno_sent(const trn_cell_conflux_link_payload_v1_t *inp) +{ + return inp->last_seqno_sent; +} +int +trn_cell_conflux_link_payload_v1_set_last_seqno_sent(trn_cell_conflux_link_payload_v1_t *inp, uint64_t val) +{ + inp->last_seqno_sent = val; + return 0; +} +uint64_t +trn_cell_conflux_link_payload_v1_get_last_seqno_recv(const trn_cell_conflux_link_payload_v1_t *inp) +{ + return inp->last_seqno_recv; +} +int +trn_cell_conflux_link_payload_v1_set_last_seqno_recv(trn_cell_conflux_link_payload_v1_t *inp, uint64_t val) +{ + inp->last_seqno_recv = val; + return 0; +} +uint8_t +trn_cell_conflux_link_payload_v1_get_desired_ux(const trn_cell_conflux_link_payload_v1_t *inp) +{ + return inp->desired_ux; +} +int +trn_cell_conflux_link_payload_v1_set_desired_ux(trn_cell_conflux_link_payload_v1_t *inp, uint8_t val) +{ + if (! ((val == CONFLUX_UX_HIGH_THROUGHPUT || val == CONFLUX_UX_LOW_MEM_LATENCY || val == CONFLUX_UX_LOW_MEM_THROUGHPUT || val == CONFLUX_UX_MIN_LATENCY || val == CONFLUX_UX_NO_OPINION))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->desired_ux = val; + return 0; +} +const char * +trn_cell_conflux_link_payload_v1_check(const trn_cell_conflux_link_payload_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->desired_ux == CONFLUX_UX_HIGH_THROUGHPUT || obj->desired_ux == CONFLUX_UX_LOW_MEM_LATENCY || obj->desired_ux == CONFLUX_UX_LOW_MEM_THROUGHPUT || obj->desired_ux == CONFLUX_UX_MIN_LATENCY || obj->desired_ux == CONFLUX_UX_NO_OPINION)) + return "Integer out of bounds"; + return NULL; +} + +ssize_t +trn_cell_conflux_link_payload_v1_encoded_len(const trn_cell_conflux_link_payload_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_conflux_link_payload_v1_check(obj)) + return -1; + + + /* Length of u8 nonce[32] */ + result += 32; + + /* Length of u64 last_seqno_sent */ + result += 8; + + /* Length of u64 last_seqno_recv */ + result += 8; + + /* Length of u8 desired_ux IN [CONFLUX_UX_HIGH_THROUGHPUT, CONFLUX_UX_LOW_MEM_LATENCY, CONFLUX_UX_LOW_MEM_THROUGHPUT, CONFLUX_UX_MIN_LATENCY, CONFLUX_UX_NO_OPINION] */ + result += 1; + return result; +} +int +trn_cell_conflux_link_payload_v1_clear_errors(trn_cell_conflux_link_payload_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_conflux_link_payload_v1_encode(uint8_t *output, const size_t avail, const trn_cell_conflux_link_payload_v1_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 = trn_cell_conflux_link_payload_v1_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_conflux_link_payload_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 nonce[32] */ + trunnel_assert(written <= avail); + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->nonce, 32); + written += 32; ptr += 32; + + /* Encode u64 last_seqno_sent */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->last_seqno_sent)); + written += 8; ptr += 8; + + /* Encode u64 last_seqno_recv */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->last_seqno_recv)); + written += 8; ptr += 8; + + /* Encode u8 desired_ux IN [CONFLUX_UX_HIGH_THROUGHPUT, CONFLUX_UX_LOW_MEM_LATENCY, CONFLUX_UX_LOW_MEM_THROUGHPUT, CONFLUX_UX_MIN_LATENCY, CONFLUX_UX_NO_OPINION] */ + trunnel_assert(written <= avail); + if (avail - written < 1) + goto truncated; + trunnel_set_uint8(ptr, (obj->desired_ux)); + written += 1; ptr += 1; + + + 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 trn_cell_conflux_link_payload_v1_parse(), but do not allocate + * the output object. + */ +static ssize_t +trn_cell_conflux_link_payload_v1_parse_into(trn_cell_conflux_link_payload_v1_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 nonce[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->nonce, ptr, 32); + remaining -= 32; ptr += 32; + + /* Parse u64 last_seqno_sent */ + CHECK_REMAINING(8, truncated); + obj->last_seqno_sent = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 last_seqno_recv */ + CHECK_REMAINING(8, truncated); + obj->last_seqno_recv = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u8 desired_ux IN [CONFLUX_UX_HIGH_THROUGHPUT, CONFLUX_UX_LOW_MEM_LATENCY, CONFLUX_UX_LOW_MEM_THROUGHPUT, CONFLUX_UX_MIN_LATENCY, CONFLUX_UX_NO_OPINION] */ + CHECK_REMAINING(1, truncated); + obj->desired_ux = (trunnel_get_uint8(ptr)); + remaining -= 1; ptr += 1; + if (! (obj->desired_ux == CONFLUX_UX_HIGH_THROUGHPUT || obj->desired_ux == CONFLUX_UX_LOW_MEM_LATENCY || obj->desired_ux == CONFLUX_UX_LOW_MEM_THROUGHPUT || obj->desired_ux == CONFLUX_UX_MIN_LATENCY || obj->desired_ux == CONFLUX_UX_NO_OPINION)) + goto fail; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + fail: + result = -1; + return result; +} + +ssize_t +trn_cell_conflux_link_payload_v1_parse(trn_cell_conflux_link_payload_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_conflux_link_payload_v1_new(); + if (NULL == *output) + return -1; + result = trn_cell_conflux_link_payload_v1_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_conflux_link_payload_v1_free(*output); + *output = NULL; + } + return result; +} +trn_cell_conflux_linked_t * +trn_cell_conflux_linked_new(void) +{ + trn_cell_conflux_linked_t *val = trunnel_calloc(1, sizeof(trn_cell_conflux_linked_t)); + if (NULL == val) + return NULL; + val->version = 1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_conflux_linked_clear(trn_cell_conflux_linked_t *obj) +{ + (void) obj; + TRUNNEL_DYNARRAY_WIPE(&obj->payload); + TRUNNEL_DYNARRAY_CLEAR(&obj->payload); +} + +void +trn_cell_conflux_linked_free(trn_cell_conflux_linked_t *obj) +{ + if (obj == NULL) + return; + trn_cell_conflux_linked_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_conflux_linked_t)); + trunnel_free_(obj); +} + +uint8_t +trn_cell_conflux_linked_get_version(const trn_cell_conflux_linked_t *inp) +{ + return inp->version; +} +int +trn_cell_conflux_linked_set_version(trn_cell_conflux_linked_t *inp, uint8_t val) +{ + if (! ((val == 1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->version = val; + return 0; +} +size_t +trn_cell_conflux_linked_getlen_payload(const trn_cell_conflux_linked_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->payload); +} + +uint8_t +trn_cell_conflux_linked_get_payload(trn_cell_conflux_linked_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->payload, idx); +} + +uint8_t +trn_cell_conflux_linked_getconst_payload(const trn_cell_conflux_linked_t *inp, size_t idx) +{ + return trn_cell_conflux_linked_get_payload((trn_cell_conflux_linked_t*)inp, idx); +} +int +trn_cell_conflux_linked_set_payload(trn_cell_conflux_linked_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->payload, idx, elt); + return 0; +} +int +trn_cell_conflux_linked_add_payload(trn_cell_conflux_linked_t *inp, uint8_t elt) +{ + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->payload, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +trn_cell_conflux_linked_getarray_payload(trn_cell_conflux_linked_t *inp) +{ + return inp->payload.elts_; +} +const uint8_t * +trn_cell_conflux_linked_getconstarray_payload(const trn_cell_conflux_linked_t *inp) +{ + return (const uint8_t *)trn_cell_conflux_linked_getarray_payload((trn_cell_conflux_linked_t*)inp); +} +int +trn_cell_conflux_linked_setlen_payload(trn_cell_conflux_linked_t *inp, size_t newlen) +{ + uint8_t *newptr; + newptr = trunnel_dynarray_setlen(&inp->payload.allocated_, + &inp->payload.n_, inp->payload.elts_, newlen, + sizeof(inp->payload.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->payload.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +trn_cell_conflux_linked_check(const trn_cell_conflux_linked_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 == 1)) + return "Integer out of bounds"; + return NULL; +} + +ssize_t +trn_cell_conflux_linked_encoded_len(const trn_cell_conflux_linked_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_conflux_linked_check(obj)) + return -1; + + + /* Length of u8 version IN [1] */ + result += 1; + + /* Length of u8 payload[] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->payload); + return result; +} +int +trn_cell_conflux_linked_clear_errors(trn_cell_conflux_linked_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_conflux_linked_encode(uint8_t *output, const size_t avail, const trn_cell_conflux_linked_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 = trn_cell_conflux_linked_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_conflux_linked_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 version IN [1] */ + trunnel_assert(written <= avail); + if (avail - written < 1) + goto truncated; + trunnel_set_uint8(ptr, (obj->version)); + written += 1; ptr += 1; + + /* Encode u8 payload[] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->payload); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->payload.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + + 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 trn_cell_conflux_linked_parse(), but do not allocate the output + * object. + */ +static ssize_t +trn_cell_conflux_linked_parse_into(trn_cell_conflux_linked_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 [1] */ + CHECK_REMAINING(1, truncated); + obj->version = (trunnel_get_uint8(ptr)); + remaining -= 1; ptr += 1; + if (! (obj->version == 1)) + goto fail; + + /* Parse u8 payload[] */ + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->payload, remaining, {}); + obj->payload.n_ = remaining; + if (remaining) + memcpy(obj->payload.elts_, ptr, remaining); + ptr += remaining; remaining -= remaining; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + trunnel_alloc_failed: + return -1; + fail: + result = -1; + return result; +} + +ssize_t +trn_cell_conflux_linked_parse(trn_cell_conflux_linked_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_conflux_linked_new(); + if (NULL == *output) + return -1; + result = trn_cell_conflux_linked_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_conflux_linked_free(*output); + *output = NULL; + } + return result; +} +trn_cell_conflux_linked_ack_t * +trn_cell_conflux_linked_ack_new(void) +{ + trn_cell_conflux_linked_ack_t *val = trunnel_calloc(1, sizeof(trn_cell_conflux_linked_ack_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_conflux_linked_ack_clear(trn_cell_conflux_linked_ack_t *obj) +{ + (void) obj; + TRUNNEL_DYNARRAY_WIPE(&obj->payload); + TRUNNEL_DYNARRAY_CLEAR(&obj->payload); +} + +void +trn_cell_conflux_linked_ack_free(trn_cell_conflux_linked_ack_t *obj) +{ + if (obj == NULL) + return; + trn_cell_conflux_linked_ack_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_conflux_linked_ack_t)); + trunnel_free_(obj); +} + +size_t +trn_cell_conflux_linked_ack_getlen_payload(const trn_cell_conflux_linked_ack_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->payload); +} + +uint8_t +trn_cell_conflux_linked_ack_get_payload(trn_cell_conflux_linked_ack_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->payload, idx); +} + +uint8_t +trn_cell_conflux_linked_ack_getconst_payload(const trn_cell_conflux_linked_ack_t *inp, size_t idx) +{ + return trn_cell_conflux_linked_ack_get_payload((trn_cell_conflux_linked_ack_t*)inp, idx); +} +int +trn_cell_conflux_linked_ack_set_payload(trn_cell_conflux_linked_ack_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->payload, idx, elt); + return 0; +} +int +trn_cell_conflux_linked_ack_add_payload(trn_cell_conflux_linked_ack_t *inp, uint8_t elt) +{ + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->payload, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +trn_cell_conflux_linked_ack_getarray_payload(trn_cell_conflux_linked_ack_t *inp) +{ + return inp->payload.elts_; +} +const uint8_t * +trn_cell_conflux_linked_ack_getconstarray_payload(const trn_cell_conflux_linked_ack_t *inp) +{ + return (const uint8_t *)trn_cell_conflux_linked_ack_getarray_payload((trn_cell_conflux_linked_ack_t*)inp); +} +int +trn_cell_conflux_linked_ack_setlen_payload(trn_cell_conflux_linked_ack_t *inp, size_t newlen) +{ + uint8_t *newptr; + newptr = trunnel_dynarray_setlen(&inp->payload.allocated_, + &inp->payload.n_, inp->payload.elts_, newlen, + sizeof(inp->payload.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->payload.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +trn_cell_conflux_linked_ack_check(const trn_cell_conflux_linked_ack_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +trn_cell_conflux_linked_ack_encoded_len(const trn_cell_conflux_linked_ack_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_conflux_linked_ack_check(obj)) + return -1; + + + /* Length of u8 payload[] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->payload); + return result; +} +int +trn_cell_conflux_linked_ack_clear_errors(trn_cell_conflux_linked_ack_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_conflux_linked_ack_encode(uint8_t *output, const size_t avail, const trn_cell_conflux_linked_ack_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 = trn_cell_conflux_linked_ack_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_conflux_linked_ack_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 payload[] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->payload); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->payload.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + + 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 trn_cell_conflux_linked_ack_parse(), but do not allocate the + * output object. + */ +static ssize_t +trn_cell_conflux_linked_ack_parse_into(trn_cell_conflux_linked_ack_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 payload[] */ + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->payload, remaining, {}); + obj->payload.n_ = remaining; + if (remaining) + memcpy(obj->payload.elts_, ptr, remaining); + ptr += remaining; remaining -= remaining; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + trunnel_alloc_failed: + return -1; +} + +ssize_t +trn_cell_conflux_linked_ack_parse(trn_cell_conflux_linked_ack_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_conflux_linked_ack_new(); + if (NULL == *output) + return -1; + result = trn_cell_conflux_linked_ack_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_conflux_linked_ack_free(*output); + *output = NULL; + } + return result; +} +trn_cell_conflux_switch_t * +trn_cell_conflux_switch_new(void) +{ + trn_cell_conflux_switch_t *val = trunnel_calloc(1, sizeof(trn_cell_conflux_switch_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_conflux_switch_clear(trn_cell_conflux_switch_t *obj) +{ + (void) obj; +} + +void +trn_cell_conflux_switch_free(trn_cell_conflux_switch_t *obj) +{ + if (obj == NULL) + return; + trn_cell_conflux_switch_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_conflux_switch_t)); + trunnel_free_(obj); +} + +uint32_t +trn_cell_conflux_switch_get_seqnum(const trn_cell_conflux_switch_t *inp) +{ + return inp->seqnum; +} +int +trn_cell_conflux_switch_set_seqnum(trn_cell_conflux_switch_t *inp, uint32_t val) +{ + inp->seqnum = val; + return 0; +} +const char * +trn_cell_conflux_switch_check(const trn_cell_conflux_switch_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +trn_cell_conflux_switch_encoded_len(const trn_cell_conflux_switch_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_conflux_switch_check(obj)) + return -1; + + + /* Length of u32 seqnum */ + result += 4; + return result; +} +int +trn_cell_conflux_switch_clear_errors(trn_cell_conflux_switch_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_conflux_switch_encode(uint8_t *output, const size_t avail, const trn_cell_conflux_switch_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 = trn_cell_conflux_switch_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_conflux_switch_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u32 seqnum */ + trunnel_assert(written <= avail); + if (avail - written < 4) + goto truncated; + trunnel_set_uint32(ptr, trunnel_htonl(obj->seqnum)); + written += 4; ptr += 4; + + + 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 trn_cell_conflux_switch_parse(), but do not allocate the output + * object. + */ +static ssize_t +trn_cell_conflux_switch_parse_into(trn_cell_conflux_switch_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 u32 seqnum */ + CHECK_REMAINING(4, truncated); + obj->seqnum = trunnel_ntohl(trunnel_get_uint32(ptr)); + remaining -= 4; ptr += 4; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; +} + +ssize_t +trn_cell_conflux_switch_parse(trn_cell_conflux_switch_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_conflux_switch_new(); + if (NULL == *output) + return -1; + result = trn_cell_conflux_switch_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_conflux_switch_free(*output); + *output = NULL; + } + return result; +} diff --git a/src/trunnel/conflux.h b/src/trunnel/conflux.h new file mode 100644 index 0000000000..fa6f093b4f --- /dev/null +++ b/src/trunnel/conflux.h @@ -0,0 +1,422 @@ +/* conflux.h -- generated by Trunnel v1.5.3. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#ifndef TRUNNEL_CONFLUX_H +#define TRUNNEL_CONFLUX_H + +#include <stdint.h> +#include "trunnel.h" + +#define CONFLUX_UX_NO_OPINION 0 +#define CONFLUX_UX_MIN_LATENCY 1 +#define CONFLUX_UX_LOW_MEM_LATENCY 2 +#define CONFLUX_UX_HIGH_THROUGHPUT 3 +#define CONFLUX_UX_LOW_MEM_THROUGHPUT 4 +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_CONFLUX_LINK) +struct trn_cell_conflux_link_st { + uint8_t version; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) payload; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_conflux_link_st trn_cell_conflux_link_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_CONFLUX_LINK_PAYLOAD_V1) +struct trn_cell_conflux_link_payload_v1_st { + uint8_t nonce[32]; + uint64_t last_seqno_sent; + uint64_t last_seqno_recv; + uint8_t desired_ux; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_conflux_link_payload_v1_st trn_cell_conflux_link_payload_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_CONFLUX_LINKED) +struct trn_cell_conflux_linked_st { + uint8_t version; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) payload; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_conflux_linked_st trn_cell_conflux_linked_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_CONFLUX_LINKED_ACK) +struct trn_cell_conflux_linked_ack_st { + TRUNNEL_DYNARRAY_HEAD(, uint8_t) payload; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_conflux_linked_ack_st trn_cell_conflux_linked_ack_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_CONFLUX_SWITCH) +struct trn_cell_conflux_switch_st { + uint32_t seqnum; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_conflux_switch_st trn_cell_conflux_switch_t; +/** Return a newly allocated trn_cell_conflux_link with all elements + * set to zero. + */ +trn_cell_conflux_link_t *trn_cell_conflux_link_new(void); +/** Release all storage held by the trn_cell_conflux_link in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void trn_cell_conflux_link_free(trn_cell_conflux_link_t *victim); +/** Try to parse a trn_cell_conflux_link 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 trn_cell_conflux_link_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t trn_cell_conflux_link_parse(trn_cell_conflux_link_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_conflux_link 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 trn_cell_conflux_link_encoded_len(const trn_cell_conflux_link_t *obj); +/** Try to encode the trn_cell_conflux_link 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 trn_cell_conflux_link_encode(uint8_t *output, size_t avail, const trn_cell_conflux_link_t *input); +/** Check whether the internal state of the trn_cell_conflux_link in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *trn_cell_conflux_link_check(const trn_cell_conflux_link_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_conflux_link_clear_errors(trn_cell_conflux_link_t *obj); +/** Return the value of the version field of the + * trn_cell_conflux_link_t in 'inp' + */ +uint8_t trn_cell_conflux_link_get_version(const trn_cell_conflux_link_t *inp); +/** Set the value of the version field of the trn_cell_conflux_link_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int trn_cell_conflux_link_set_version(trn_cell_conflux_link_t *inp, uint8_t val); +/** Return the length of the dynamic array holding the payload field + * of the trn_cell_conflux_link_t in 'inp'. + */ +size_t trn_cell_conflux_link_getlen_payload(const trn_cell_conflux_link_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * payload of the trn_cell_conflux_link_t in 'inp'. + */ +uint8_t trn_cell_conflux_link_get_payload(trn_cell_conflux_link_t *inp, size_t idx); +/** As trn_cell_conflux_link_get_payload, but take and return a const + * pointer + */ +uint8_t trn_cell_conflux_link_getconst_payload(const trn_cell_conflux_link_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * payload of the trn_cell_conflux_link_t in 'inp', so that it will + * hold the value 'elt'. + */ +int trn_cell_conflux_link_set_payload(trn_cell_conflux_link_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field payload of + * the trn_cell_conflux_link_t in 'inp'. + */ +int trn_cell_conflux_link_add_payload(trn_cell_conflux_link_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field payload of + * 'inp'. + */ +uint8_t * trn_cell_conflux_link_getarray_payload(trn_cell_conflux_link_t *inp); +/** As trn_cell_conflux_link_get_payload, but take and return a const + * pointer + */ +const uint8_t * trn_cell_conflux_link_getconstarray_payload(const trn_cell_conflux_link_t *inp); +/** Change the length of the variable-length array field payload of + * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_conflux_link_setlen_payload(trn_cell_conflux_link_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_conflux_link_payload_v1 with all + * elements set to zero. + */ +trn_cell_conflux_link_payload_v1_t *trn_cell_conflux_link_payload_v1_new(void); +/** Release all storage held by the trn_cell_conflux_link_payload_v1 + * in 'victim'. (Do nothing if 'victim' is NULL.) + */ +void trn_cell_conflux_link_payload_v1_free(trn_cell_conflux_link_payload_v1_t *victim); +/** Try to parse a trn_cell_conflux_link_payload_v1 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 trn_cell_conflux_link_payload_v1_t. On failure, + * return -2 if the input appears truncated, and -1 if the input is + * otherwise invalid. + */ +ssize_t trn_cell_conflux_link_payload_v1_parse(trn_cell_conflux_link_payload_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_conflux_link_payload_v1 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 trn_cell_conflux_link_payload_v1_encoded_len(const trn_cell_conflux_link_payload_v1_t *obj); +/** Try to encode the trn_cell_conflux_link_payload_v1 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 trn_cell_conflux_link_payload_v1_encode(uint8_t *output, size_t avail, const trn_cell_conflux_link_payload_v1_t *input); +/** Check whether the internal state of the + * trn_cell_conflux_link_payload_v1 in 'obj' is consistent. Return + * NULL if it is, and a short message if it is not. + */ +const char *trn_cell_conflux_link_payload_v1_check(const trn_cell_conflux_link_payload_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_conflux_link_payload_v1_clear_errors(trn_cell_conflux_link_payload_v1_t *obj); +/** Return the (constant) length of the array holding the nonce field + * of the trn_cell_conflux_link_payload_v1_t in 'inp'. + */ +size_t trn_cell_conflux_link_payload_v1_getlen_nonce(const trn_cell_conflux_link_payload_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * nonce of the trn_cell_conflux_link_payload_v1_t in 'inp'. + */ +uint8_t trn_cell_conflux_link_payload_v1_get_nonce(trn_cell_conflux_link_payload_v1_t *inp, size_t idx); +/** As trn_cell_conflux_link_payload_v1_get_nonce, but take and return + * a const pointer + */ +uint8_t trn_cell_conflux_link_payload_v1_getconst_nonce(const trn_cell_conflux_link_payload_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * nonce of the trn_cell_conflux_link_payload_v1_t in 'inp', so that + * it will hold the value 'elt'. + */ +int trn_cell_conflux_link_payload_v1_set_nonce(trn_cell_conflux_link_payload_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field nonce of 'inp'. + */ +uint8_t * trn_cell_conflux_link_payload_v1_getarray_nonce(trn_cell_conflux_link_payload_v1_t *inp); +/** As trn_cell_conflux_link_payload_v1_get_nonce, but take and return + * a const pointer + */ +const uint8_t * trn_cell_conflux_link_payload_v1_getconstarray_nonce(const trn_cell_conflux_link_payload_v1_t *inp); +/** Return the value of the last_seqno_sent field of the + * trn_cell_conflux_link_payload_v1_t in 'inp' + */ +uint64_t trn_cell_conflux_link_payload_v1_get_last_seqno_sent(const trn_cell_conflux_link_payload_v1_t *inp); +/** Set the value of the last_seqno_sent field of the + * trn_cell_conflux_link_payload_v1_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_conflux_link_payload_v1_set_last_seqno_sent(trn_cell_conflux_link_payload_v1_t *inp, uint64_t val); +/** Return the value of the last_seqno_recv field of the + * trn_cell_conflux_link_payload_v1_t in 'inp' + */ +uint64_t trn_cell_conflux_link_payload_v1_get_last_seqno_recv(const trn_cell_conflux_link_payload_v1_t *inp); +/** Set the value of the last_seqno_recv field of the + * trn_cell_conflux_link_payload_v1_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_conflux_link_payload_v1_set_last_seqno_recv(trn_cell_conflux_link_payload_v1_t *inp, uint64_t val); +/** Return the value of the desired_ux field of the + * trn_cell_conflux_link_payload_v1_t in 'inp' + */ +uint8_t trn_cell_conflux_link_payload_v1_get_desired_ux(const trn_cell_conflux_link_payload_v1_t *inp); +/** Set the value of the desired_ux field of the + * trn_cell_conflux_link_payload_v1_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_conflux_link_payload_v1_set_desired_ux(trn_cell_conflux_link_payload_v1_t *inp, uint8_t val); +/** Return a newly allocated trn_cell_conflux_linked with all elements + * set to zero. + */ +trn_cell_conflux_linked_t *trn_cell_conflux_linked_new(void); +/** Release all storage held by the trn_cell_conflux_linked in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void trn_cell_conflux_linked_free(trn_cell_conflux_linked_t *victim); +/** Try to parse a trn_cell_conflux_linked 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 trn_cell_conflux_linked_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t trn_cell_conflux_linked_parse(trn_cell_conflux_linked_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_conflux_linked 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 trn_cell_conflux_linked_encoded_len(const trn_cell_conflux_linked_t *obj); +/** Try to encode the trn_cell_conflux_linked 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 trn_cell_conflux_linked_encode(uint8_t *output, size_t avail, const trn_cell_conflux_linked_t *input); +/** Check whether the internal state of the trn_cell_conflux_linked in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *trn_cell_conflux_linked_check(const trn_cell_conflux_linked_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_conflux_linked_clear_errors(trn_cell_conflux_linked_t *obj); +/** Return the value of the version field of the + * trn_cell_conflux_linked_t in 'inp' + */ +uint8_t trn_cell_conflux_linked_get_version(const trn_cell_conflux_linked_t *inp); +/** Set the value of the version field of the + * trn_cell_conflux_linked_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_conflux_linked_set_version(trn_cell_conflux_linked_t *inp, uint8_t val); +/** Return the length of the dynamic array holding the payload field + * of the trn_cell_conflux_linked_t in 'inp'. + */ +size_t trn_cell_conflux_linked_getlen_payload(const trn_cell_conflux_linked_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * payload of the trn_cell_conflux_linked_t in 'inp'. + */ +uint8_t trn_cell_conflux_linked_get_payload(trn_cell_conflux_linked_t *inp, size_t idx); +/** As trn_cell_conflux_linked_get_payload, but take and return a + * const pointer + */ +uint8_t trn_cell_conflux_linked_getconst_payload(const trn_cell_conflux_linked_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * payload of the trn_cell_conflux_linked_t in 'inp', so that it will + * hold the value 'elt'. + */ +int trn_cell_conflux_linked_set_payload(trn_cell_conflux_linked_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field payload of + * the trn_cell_conflux_linked_t in 'inp'. + */ +int trn_cell_conflux_linked_add_payload(trn_cell_conflux_linked_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field payload of + * 'inp'. + */ +uint8_t * trn_cell_conflux_linked_getarray_payload(trn_cell_conflux_linked_t *inp); +/** As trn_cell_conflux_linked_get_payload, but take and return a + * const pointer + */ +const uint8_t * trn_cell_conflux_linked_getconstarray_payload(const trn_cell_conflux_linked_t *inp); +/** Change the length of the variable-length array field payload of + * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_conflux_linked_setlen_payload(trn_cell_conflux_linked_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_conflux_linked_ack with all + * elements set to zero. + */ +trn_cell_conflux_linked_ack_t *trn_cell_conflux_linked_ack_new(void); +/** Release all storage held by the trn_cell_conflux_linked_ack in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void trn_cell_conflux_linked_ack_free(trn_cell_conflux_linked_ack_t *victim); +/** Try to parse a trn_cell_conflux_linked_ack 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 trn_cell_conflux_linked_ack_t. On failure, return + * -2 if the input appears truncated, and -1 if the input is otherwise + * invalid. + */ +ssize_t trn_cell_conflux_linked_ack_parse(trn_cell_conflux_linked_ack_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_conflux_linked_ack 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 trn_cell_conflux_linked_ack_encoded_len(const trn_cell_conflux_linked_ack_t *obj); +/** Try to encode the trn_cell_conflux_linked_ack 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 trn_cell_conflux_linked_ack_encode(uint8_t *output, size_t avail, const trn_cell_conflux_linked_ack_t *input); +/** Check whether the internal state of the + * trn_cell_conflux_linked_ack in 'obj' is consistent. Return NULL if + * it is, and a short message if it is not. + */ +const char *trn_cell_conflux_linked_ack_check(const trn_cell_conflux_linked_ack_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_conflux_linked_ack_clear_errors(trn_cell_conflux_linked_ack_t *obj); +/** Return the length of the dynamic array holding the payload field + * of the trn_cell_conflux_linked_ack_t in 'inp'. + */ +size_t trn_cell_conflux_linked_ack_getlen_payload(const trn_cell_conflux_linked_ack_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * payload of the trn_cell_conflux_linked_ack_t in 'inp'. + */ +uint8_t trn_cell_conflux_linked_ack_get_payload(trn_cell_conflux_linked_ack_t *inp, size_t idx); +/** As trn_cell_conflux_linked_ack_get_payload, but take and return a + * const pointer + */ +uint8_t trn_cell_conflux_linked_ack_getconst_payload(const trn_cell_conflux_linked_ack_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * payload of the trn_cell_conflux_linked_ack_t in 'inp', so that it + * will hold the value 'elt'. + */ +int trn_cell_conflux_linked_ack_set_payload(trn_cell_conflux_linked_ack_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field payload of + * the trn_cell_conflux_linked_ack_t in 'inp'. + */ +int trn_cell_conflux_linked_ack_add_payload(trn_cell_conflux_linked_ack_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field payload of + * 'inp'. + */ +uint8_t * trn_cell_conflux_linked_ack_getarray_payload(trn_cell_conflux_linked_ack_t *inp); +/** As trn_cell_conflux_linked_ack_get_payload, but take and return a + * const pointer + */ +const uint8_t * trn_cell_conflux_linked_ack_getconstarray_payload(const trn_cell_conflux_linked_ack_t *inp); +/** Change the length of the variable-length array field payload of + * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_conflux_linked_ack_setlen_payload(trn_cell_conflux_linked_ack_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_conflux_switch with all elements + * set to zero. + */ +trn_cell_conflux_switch_t *trn_cell_conflux_switch_new(void); +/** Release all storage held by the trn_cell_conflux_switch in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void trn_cell_conflux_switch_free(trn_cell_conflux_switch_t *victim); +/** Try to parse a trn_cell_conflux_switch 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 trn_cell_conflux_switch_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t trn_cell_conflux_switch_parse(trn_cell_conflux_switch_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_conflux_switch 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 trn_cell_conflux_switch_encoded_len(const trn_cell_conflux_switch_t *obj); +/** Try to encode the trn_cell_conflux_switch 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 trn_cell_conflux_switch_encode(uint8_t *output, size_t avail, const trn_cell_conflux_switch_t *input); +/** Check whether the internal state of the trn_cell_conflux_switch in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *trn_cell_conflux_switch_check(const trn_cell_conflux_switch_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_conflux_switch_clear_errors(trn_cell_conflux_switch_t *obj); +/** Return the value of the seqnum field of the + * trn_cell_conflux_switch_t in 'inp' + */ +uint32_t trn_cell_conflux_switch_get_seqnum(const trn_cell_conflux_switch_t *inp); +/** Set the value of the seqnum field of the trn_cell_conflux_switch_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int trn_cell_conflux_switch_set_seqnum(trn_cell_conflux_switch_t *inp, uint32_t val); + + +#endif diff --git a/src/trunnel/conflux.trunnel b/src/trunnel/conflux.trunnel new file mode 100644 index 0000000000..4f1855faf3 --- /dev/null +++ b/src/trunnel/conflux.trunnel @@ -0,0 +1,66 @@ +/* + * This file contains the definition for the Conflux related cells. See + * proposal 329. + */ + +/* No Opinion means the endpoint can choose whatever it thinks is best. */ +const CONFLUX_UX_NO_OPINION = 0x00; +/* Min latency always only uses the lowest RTT */ +const CONFLUX_UX_MIN_LATENCY = 0x01; +/* Min latency always only uses the lowest RTT */ +const CONFLUX_UX_LOW_MEM_LATENCY = 0x02; +/* Use a high-throughput algorithm that maximizes throughput + * by using the full congestion window of all circuits, at the expense + * of more reordering queue at the receiver */ +const CONFLUX_UX_HIGH_THROUGHPUT = 0x03; +/* THRPT_LO uses a high-throughput algorithm that tries to minimize + * out-of-order queues at the receiver */ +const CONFLUX_UX_LOW_MEM_THROUGHPUT = 0x04; + +/* The RELAY_CONFLUX_LINK definition. */ +struct trn_cell_conflux_link { + /* Version field. */ + u8 version IN [0x01]; + + /* Payload */ + u8 payload[]; +}; + +/* The RELAY_CIRCUIT_LINKED definition. */ +struct trn_cell_conflux_linked { + /* Version field. */ + u8 version IN [0x01]; + + /* Payload of the cell. */ + u8 payload[]; +}; + +/* The RELAY_CONFLUX_LINKED_ACK definition. */ +struct trn_cell_conflux_linked_ack { + /* Payload. At the moment, empty. */ + u8 payload[]; +}; + +/* The RELAY_CONFLUX_SWITCH definition. */ +struct trn_cell_conflux_switch { + /* Relative sequence number. */ + u32 seqnum; +}; + +/* The payload version 1 of RELAY_CONFLUX_LINK and RELAY_CIRCUIT_LINKED cells. + * */ +struct trn_cell_conflux_link_payload_v1 { + /* Used to identify the other conflux to link with. */ + u8 nonce[32]; + + /* Last sequence number sent and received. */ + u64 last_seqno_sent; + u64 last_seqno_recv; + + /* Desired user experience behavior */ + u8 desired_ux IN [CONFLUX_UX_NO_OPINION, + CONFLUX_UX_MIN_LATENCY, + CONFLUX_UX_LOW_MEM_LATENCY, + CONFLUX_UX_LOW_MEM_THROUGHPUT, + CONFLUX_UX_HIGH_THROUGHPUT]; +}; diff --git a/src/trunnel/include.am b/src/trunnel/include.am index b2aee81da9..8154a5e932 100644 --- a/src/trunnel/include.am +++ b/src/trunnel/include.am @@ -16,7 +16,8 @@ TRUNNELINPUTS = \ src/trunnel/flow_control_cells.trunnel \ src/trunnel/congestion_control.trunnel \ src/trunnel/socks5.trunnel \ - src/trunnel/circpad_negotiation.trunnel + src/trunnel/circpad_negotiation.trunnel \ + src/trunnel/conflux.trunnel
TRUNNELSOURCES = \ src/ext/trunnel/trunnel.c \ @@ -33,7 +34,8 @@ TRUNNELSOURCES = \ src/trunnel/congestion_control.c \ src/trunnel/socks5.c \ src/trunnel/netinfo.c \ - src/trunnel/circpad_negotiation.c + src/trunnel/circpad_negotiation.c \ + src/trunnel/conflux.c
TRUNNELHEADERS = \ src/ext/trunnel/trunnel.h \ @@ -52,7 +54,8 @@ TRUNNELHEADERS = \ src/trunnel/congestion_control.h \ src/trunnel/socks5.h \ src/trunnel/netinfo.h \ - src/trunnel/circpad_negotiation.h + src/trunnel/circpad_negotiation.h \ + src/trunnel/conflux.h
src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES) src_trunnel_libor_trunnel_a_CPPFLAGS = \
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 8b185b2ac3d8240fe5f8cda41699ab10d1167844 Author: David Goulet dgoulet@torproject.org AuthorDate: Wed Feb 1 21:51:58 2023 +0000
Prop#329 Cells: Building and parsing parsing conflux commands
Signed-off-by: David Goulet dgoulet@torproject.org --- src/core/or/conflux_cell.c | 355 +++++++++++++++++++++++++++++++++++++++++++++ src/core/or/conflux_cell.h | 50 +++++++ 2 files changed, 405 insertions(+)
diff --git a/src/core/or/conflux_cell.c b/src/core/or/conflux_cell.c new file mode 100644 index 0000000000..9d81a4ba3b --- /dev/null +++ b/src/core/or/conflux_cell.c @@ -0,0 +1,355 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_cell.c + * \brief XXX: Write a brief introduction to this module. + **/ + +#define CONFLUX_CELL_PRIVATE + +#include "app/config/config.h" + +#include "core/or/conflux.h" +#include "core/or/conflux_cell.h" +#include "core/or/relay.h" +#include "core/or/circuitlist.h" + +#include "lib/crypt_ops/crypto_rand.h" + +#include "trunnel/conflux.h" + +#include "core/or/crypt_path_st.h" +#include "core/or/or_circuit_st.h" +#include "core/or/origin_circuit_st.h" + +STATIC ssize_t +build_link_cell(const conflux_cell_link_t *link, uint8_t *cell_out) +{ + ssize_t cell_len = -1; + trn_cell_conflux_link_t *cell = NULL; + trn_cell_conflux_link_payload_v1_t *payload = NULL; + + tor_assert(cell_out); + + cell = trn_cell_conflux_link_new(); + trn_cell_conflux_link_set_version(cell, 0x01); + + payload = trn_cell_conflux_link_payload_v1_new(); + + /* Set the nonce. */ + size_t nonce_len = trn_cell_conflux_link_payload_v1_getlen_nonce(payload); + tor_assert(nonce_len == sizeof(link->nonce)); + memcpy(trn_cell_conflux_link_payload_v1_getarray_nonce(payload), + link->nonce, nonce_len); + + /* Set the sequence number. */ + trn_cell_conflux_link_payload_v1_set_last_seqno_recv(payload, + link->last_seqno_recv); + trn_cell_conflux_link_payload_v1_set_last_seqno_sent(payload, + link->last_seqno_sent); + + /* Set the algorithm */ + trn_cell_conflux_link_payload_v1_set_desired_ux(payload, link->desired_ux); + + /* Encode payload. */ + trn_cell_conflux_link_setlen_payload(cell, + trn_cell_conflux_link_payload_v1_encoded_len(payload)); + trn_cell_conflux_link_payload_v1_encode( + trn_cell_conflux_link_getarray_payload(cell), + trn_cell_conflux_link_getlen_payload(cell), payload); + + /* Encode cell. */ + cell_len = trn_cell_conflux_link_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + + trn_cell_conflux_link_payload_v1_free(payload); + trn_cell_conflux_link_free(cell); + return cell_len; +} + +static ssize_t +build_linked_cell(const conflux_cell_link_t *link, uint8_t *cell_out) +{ + /* Same payload. This might not be true in the future but for now, we don't + * need to duplicate the code as it is really the same. */ + return build_link_cell(link, cell_out); +} + +static ssize_t +build_linked_ack_cell(uint8_t *cell_out) +{ + ssize_t cell_len = -1; + trn_cell_conflux_linked_ack_t *cell = NULL; + + tor_assert(cell_out); + + cell = trn_cell_conflux_linked_ack_new(); + cell_len = trn_cell_conflux_linked_ack_encode(cell_out, RELAY_PAYLOAD_SIZE, + cell); + + trn_cell_conflux_linked_ack_free(cell); + return cell_len; +} + +bool +conflux_cell_send_link(const conflux_cell_link_t *link, origin_circuit_t *circ) +{ + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + ssize_t cell_len; + + tor_assert(link); + tor_assert(circ); + + log_info(LD_CIRC, "Sending CONFLUX_LINK cell onto origin circuit"); + + /* Build the CONFLUX_LINK cell. */ + cell_len = build_link_cell(link, payload); + if (BUG(cell_len < 0)) { + log_info(LD_CIRC, "Unable to build CONFLUX_LINK cell."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto err; + } + + /* Send the cell to the endpoint of the circuit. */ + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_CONFLUX_LINK, + (char *) payload, cell_len, + circ->cpath->prev) < 0) { + log_info(LD_CIRC, "Unable to send CONFLUX_LINK cell."); + goto err; + } + + return true; + + err: + return false; +} + +bool +conflux_cell_send_linked(const conflux_cell_link_t *link, or_circuit_t *circ) +{ + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + ssize_t cell_len; + + tor_assert(link); + tor_assert(circ); + + log_info(LD_CIRC, "Sending CONFLUX_LINKED cell onto OR circuit"); + + /* Build the CONFLUX_LINK cell. */ + cell_len = build_linked_cell(link, payload); + if (BUG(cell_len < 0)) { + log_info(LD_CIRC, "Unable to build CONFLUX_LINKED cell."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto err; + } + + /* Send back the LINKED cell. */ + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_CONFLUX_LINKED, + (char *) payload, cell_len, NULL) < 0) { + log_info(LD_CIRC, "Unable to send CONFLUX_LINKED cell."); + goto err; + } + + return true; + + err: + return false; +} + +bool +conflux_cell_send_linked_ack(origin_circuit_t *circ) +{ + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + ssize_t cell_len; + + tor_assert(circ); + + log_info(LD_CIRC, "Sending CONFLUX_LINKED_ACK cell onto origin circuit"); + + /* Build the CONFLUX_LINKED_ACK cell. */ + cell_len = build_linked_ack_cell(payload); + if (BUG(cell_len < 0)) { + log_info(LD_CIRC, "Unable to build CONFLUX_LINKED_ACK cell."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto err; + } + + /* Send the cell to the endpoint of the circuit. */ + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_CONFLUX_LINKED_ACK, + (char *) payload, cell_len, + circ->cpath->prev) < 0) { + log_info(LD_CIRC, "Unable to send CONFLUX_LINKED_ACK cell."); + goto err; + } + + return true; + + err: + return false; +} + +static conflux_cell_link_t * +conflux_cell_parse_link_v1(const trn_cell_conflux_link_t *trn_link) +{ + conflux_cell_link_t *link = NULL; + trn_cell_conflux_link_payload_v1_t *payload = NULL; + + if (trn_cell_conflux_link_payload_v1_parse(&payload, + trn_cell_conflux_link_getconstarray_payload(trn_link), + trn_cell_conflux_link_getlen_payload(trn_link)) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Unable to parse CONFLUX_LINK v1 payload."); + goto end; + } + + link = tor_malloc_zero(sizeof(*link)); + link->version = trn_cell_conflux_link_get_version(trn_link); + link->desired_ux = + trn_cell_conflux_link_payload_v1_get_desired_ux(payload); + link->last_seqno_recv = + trn_cell_conflux_link_payload_v1_get_last_seqno_recv(payload); + link->last_seqno_sent = + trn_cell_conflux_link_payload_v1_get_last_seqno_sent(payload); + memcpy(link->nonce, + trn_cell_conflux_link_payload_v1_getconstarray_nonce(payload), + trn_cell_conflux_link_payload_v1_getlen_nonce(payload)); + + end: + trn_cell_conflux_link_payload_v1_free(payload); + return link; +} + +conflux_cell_link_t * +conflux_cell_parse_link(const cell_t *cell, const uint16_t cell_len) +{ + conflux_cell_link_t *link = NULL; + trn_cell_conflux_link_t *trn_cell = NULL; + + tor_assert(cell); + + if (trn_cell_conflux_link_parse(&trn_cell, + cell->payload + RELAY_HEADER_SIZE, + cell_len) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Unable to parse CONFLUX_LINK cell."); + goto end; + } + + uint8_t version = trn_cell_conflux_link_get_version(trn_cell); + switch (version) { + case 0x01: + link = conflux_cell_parse_link_v1(trn_cell); + break; + default: + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Unsupported version %d in CONFLUX_LINK cell", version); + goto end; + } + + end: + trn_cell_conflux_link_free(trn_cell); + return link; +} + +conflux_cell_link_t * +conflux_cell_parse_linked(const cell_t *cell, const uint16_t cell_len) +{ + /* At the moment, same exact payload so avoid code duplication. */ + return conflux_cell_parse_link(cell, cell_len); +} + +conflux_cell_link_t * +conflux_cell_new_link(const uint8_t *nonce, uint64_t last_seqno_sent, + uint64_t last_seqno_recv, bool is_client) +{ + conflux_cell_link_t *link = tor_malloc_zero(sizeof(*link)); + + link->version = 0x01; + if (is_client) { + // TODO-329-TUNING: The default should probably be high-throughput, + // but mobile clients may want to use low-memory.. We may also want + // to move this choice upstairs, so that torrc can control it. + link->desired_ux = CONFLUX_UX_HIGH_THROUGHPUT; + } else { + // TODO-329-TUNING: For exits, the default should be min-latency + // but we need to fix the tests and evaluate this first. + //link->desired_ux = CONFLUX_UX_MIN_LATENCY; + link->desired_ux = CONFLUX_UX_HIGH_THROUGHPUT; + } + link->last_seqno_sent = last_seqno_sent; + link->last_seqno_recv = last_seqno_recv; + memcpy(link->nonce, nonce, sizeof(link->nonce)); + + return link; +} + +/** + * Extracts the sequence number from a switch cell. + */ +uint32_t +conflux_cell_parse_switch(const cell_t *cell, uint16_t rh_len) +{ + uint32_t seq = 0; + trn_cell_conflux_switch_t *switch_cell = NULL; + tor_assert(cell); + + if (trn_cell_conflux_switch_parse(&switch_cell, + cell->payload + RELAY_HEADER_SIZE, + rh_len) < 0) { + log_warn(LD_BUG, "Failed to parse switch cell"); + // Zero counts as a failure to the validation, since legs should + // not switch after 0 cells. + return 0; + } + + seq = trn_cell_conflux_switch_get_seqnum(switch_cell); + + trn_cell_conflux_switch_free(switch_cell); + + return seq; +} + +/** Send a RELAY_COMMAND_CONFLUX_SWITCH cell on the circuit. */ +bool +conflux_send_switch_command(circuit_t *send_circ, uint64_t relative_seq) +{ + trn_cell_conflux_switch_t *switch_cell = trn_cell_conflux_switch_new(); + cell_t cell; + bool ret = true; + + tor_assert(send_circ); + tor_assert(relative_seq < UINT32_MAX); + + memset(&cell, 0, sizeof(cell)); + + trn_cell_conflux_switch_set_seqnum(switch_cell, (uint32_t)relative_seq); + + if (trn_cell_conflux_switch_encode(cell.payload, RELAY_PAYLOAD_SIZE, + switch_cell) < 0) { + log_warn(LD_BUG, "Failed to encode conflux switch cell"); + ret = false; + goto end; + } + + /* Send the switch command to the new hop */ + if (CIRCUIT_IS_ORIGIN(send_circ)) { + relay_send_command_from_edge(0, send_circ, + RELAY_COMMAND_CONFLUX_SWITCH, + (const char*)cell.payload, + RELAY_PAYLOAD_SIZE, + TO_ORIGIN_CIRCUIT(send_circ)->cpath->prev); + } else { + relay_send_command_from_edge(0, send_circ, + RELAY_COMMAND_CONFLUX_SWITCH, + (const char*)cell.payload, + RELAY_PAYLOAD_SIZE, NULL); + } + +end: + trn_cell_conflux_switch_free(switch_cell); + return ret; +} + diff --git a/src/core/or/conflux_cell.h b/src/core/or/conflux_cell.h new file mode 100644 index 0000000000..afaecae6f5 --- /dev/null +++ b/src/core/or/conflux_cell.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_cell.h + * \brief Header file for conflux_cell.c. + **/ + +#ifndef TOR_CONFLUX_CELL_H +#define TOR_CONFLUX_CELL_H + +#include "core/or/or.h" + +typedef struct conflux_cell_link_t { + uint8_t version; + uint8_t desired_ux; + uint8_t nonce[DIGEST256_LEN]; + + uint64_t last_seqno_sent; + uint64_t last_seqno_recv; +} conflux_cell_link_t; + +conflux_cell_link_t *conflux_cell_new_link(const uint8_t *nonce, + uint64_t last_sent, + uint64_t last_recv, + bool is_client); + +conflux_cell_link_t *conflux_cell_parse_link(const cell_t *cell, + const uint16_t cell_len); +conflux_cell_link_t *conflux_cell_parse_linked(const cell_t *cell, + const uint16_t cell_le); +uint32_t conflux_cell_parse_switch(const cell_t *cell, + const uint16_t rh_len); + +bool conflux_cell_send_link(const conflux_cell_link_t *link, + origin_circuit_t *circ); +bool conflux_cell_send_linked(const conflux_cell_link_t *link, + or_circuit_t *circ); +bool conflux_cell_send_linked_ack(origin_circuit_t *circ); +bool conflux_send_switch_command(circuit_t *send_circ, uint64_t relative_seq); + +#ifdef TOR_UNIT_TESTS + +STATIC ssize_t +build_link_cell(const conflux_cell_link_t *link, uint8_t *cell_out); + +#endif /* TOR_UNIT_TESTS */ + +#endif /* TOR_CONFLUX_CELL_H */ +
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 0d14d6b44a7b42d860dabc643bb702fee530622c Author: David Goulet dgoulet@torproject.org AuthorDate: Wed Feb 1 22:32:33 2023 +0000
Prop#329 Tests: Add tests for conflux cells.
Signed-off-by: David Goulet dgoulet@torproject.org --- src/test/include.am | 1 + src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_conflux_cell.c | 60 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+)
diff --git a/src/test/include.am b/src/test/include.am index 2765cf27d0..1c2929c3ca 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -147,6 +147,7 @@ src_test_test_SOURCES += \ src/test/test_circuitstats.c \ src/test/test_compat_libevent.c \ src/test/test_config.c \ + src/test/test_conflux_cell.c \ src/test/test_confmgr.c \ src/test/test_confparse.c \ src/test/test_connection.c \ diff --git a/src/test/test.c b/src/test/test.c index 6b7e0b6442..e1252eda66 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -778,6 +778,7 @@ struct testgroup_t testgroups[] = { { "config/", config_tests }, { "config/mgr/", confmgr_tests }, { "config/parse/", confparse_tests }, + { "conflux/cell/", conflux_cell_tests }, { "connection/", connection_tests }, { "conscache/", conscache_tests }, { "consdiff/", consdiff_tests }, diff --git a/src/test/test.h b/src/test/test.h index e17bce427c..fb0ea98f93 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -107,6 +107,7 @@ extern struct testcase_t circuitstats_tests[]; extern struct testcase_t circuituse_tests[]; extern struct testcase_t compat_libevent_tests[]; extern struct testcase_t config_tests[]; +extern struct testcase_t conflux_cell_tests[]; extern struct testcase_t confmgr_tests[]; extern struct testcase_t confparse_tests[]; extern struct testcase_t connection_tests[]; diff --git a/src/test/test_conflux_cell.c b/src/test/test_conflux_cell.c new file mode 100644 index 0000000000..bb440d0d0a --- /dev/null +++ b/src/test/test_conflux_cell.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_conflux_cell. + * \brief Test conflux cells. + */ + +#include "test/test.h" +#include "test/test_helpers.h" +#include "test/log_test_helpers.h" + +#include "core/or/conflux_cell.h" +#include "core/or/conflux_st.h" +#include "trunnel/conflux.h" + +#include "lib/crypt_ops/crypto_rand.h" + +static void +test_link(void *arg) +{ + cell_t cell; + conflux_cell_link_t link; + conflux_cell_link_t *decoded_link = NULL; + + (void) arg; + + memset(&link, 0, sizeof(link)); + + link.desired_ux = CONFLUX_UX_HIGH_THROUGHPUT; + link.last_seqno_recv = 0; + link.last_seqno_sent = 0; + link.version = 0x01; + + crypto_rand((char *) link.nonce, sizeof(link.nonce)); + + ssize_t cell_len = build_link_cell(&link, cell.payload+RELAY_HEADER_SIZE); + tt_int_op(cell_len, OP_GT, 0); + + decoded_link = conflux_cell_parse_link(&cell, cell_len); + tt_assert(decoded_link); + + uint8_t buf[RELAY_PAYLOAD_SIZE]; + ssize_t enc_cell_len = build_link_cell(decoded_link, buf); + tt_int_op(cell_len, OP_EQ, enc_cell_len); + + /* Validate the original link object with the decoded one. */ + tt_mem_op(&link, OP_EQ, decoded_link, sizeof(link)); + tor_free(decoded_link); + + done: + tor_free(decoded_link); +} + +struct testcase_t conflux_cell_tests[] = { + { "link", test_link, TT_FORK, NULL, NULL }, + + END_OF_TESTCASES +}; +
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit eac2bad86b6491b73b0086528b2b2cffe6c0d862 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Tue Mar 7 19:11:38 2023 +0000
Prop#329 params: Consensus parameter and torrc handling
Signed-off-by: David Goulet dgoulet@torproject.org --- doc/man/tor.1.txt | 6 + src/app/config/config.c | 1 + src/app/config/or_options_st.h | 4 + src/core/or/conflux_params.c | 281 +++++++++++++++++++++++++++++++++++ src/core/or/conflux_params.h | 29 ++++ src/feature/nodelist/networkstatus.c | 2 + 6 files changed, 323 insertions(+)
diff --git a/doc/man/tor.1.txt b/doc/man/tor.1.txt index e81b290b55..57992cd8d2 100644 --- a/doc/man/tor.1.txt +++ b/doc/man/tor.1.txt @@ -348,6 +348,12 @@ forward slash (/) in the configuration file and on the command line. forwards its traffic to it. It's the duty of that proxy to properly forward the traffic to the bridge. (Default: none)
+[[ConfluxEnabled]] **ConfluxEnabled** **0**|**1**|**auto**:: + If this option is set to 1, general purpose traffic will use Conflux which + is traffic splitting among multiple legs (circuits). Onion services are not + supported at the moment. Default value is set to "auto" meaning the + consensus is used to decide unless set. (Default: auto) + [[ConnLimit]] **ConnLimit** __NUM__:: The minimum number of file descriptors that must be available to the Tor process before it will start. Tor will ask the OS for as many file diff --git a/src/app/config/config.c b/src/app/config/config.c index 6b07d58240..66a8fe5853 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -377,6 +377,7 @@ static const config_var_t option_vars_[] = { V(ClientTransportPlugin, LINELIST, NULL), V(ClientUseIPv6, BOOL, "0"), V(ClientUseIPv4, BOOL, "1"), + V(ConfluxEnabled, AUTOBOOL, "auto"), V(ConnLimit, POSINT, "1000"), V(ConnDirectionStatistics, BOOL, "0"), V(ConstrainedSockets, BOOL, "0"), diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index 0811af1388..056aa3b776 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -723,6 +723,10 @@ struct or_options_t { * accessing this value directly. */ int ClientPreferIPv6DirPort;
+ /** If true, the tor client will use conflux for its general purpose + * circuits which excludes onion service traffic. */ + int ConfluxEnabled; + /** The length of time that we think a consensus should be fresh. */ int V3AuthVotingInterval; /** The length of time we think it will take to distribute votes. */ diff --git a/src/core/or/conflux_params.c b/src/core/or/conflux_params.c new file mode 100644 index 0000000000..f149e3356c --- /dev/null +++ b/src/core/or/conflux_params.c @@ -0,0 +1,281 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_params.h + * \brief Header file for conflux_params.c. + **/ + +#include "core/or/or.h" + +#include "app/config/config.h" + +#include "core/or/conflux_params.h" +#include "core/or/congestion_control_common.h" +#include "core/or/circuitlist.h" + +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/networkstatus_st.h" +#include "feature/nodelist/routerstatus_st.h" +#include "feature/relay/routermode.h" + +#include "core/or/origin_circuit_st.h" + +/** + * Consensus parameters defaults, minimums and maximums. + */ + +/* For "cfx_enabled". */ +#define CONFLUX_ENABLED_MIN (0) +#define CONFLUX_ENABLED_MAX (1) +#define CONFLUX_ENABLED_DEFAULT (1) + +/* For "cfx_low_exit_threshold". This is a percentage scaled to 10000 so we can + * support two decimal points. For example, 65.78% would be 6578. */ +#define LOW_EXIT_THRESHOLD_MIN (0) +#define LOW_EXIT_THRESHOLD_MAX (10000) +#define LOW_EXIT_THRESHOLD_DEFAULT (6000) + +/* For "cfx_max_linked_set". */ +#define MAX_LINKED_SET_MIN (0) +#define MAX_LINKED_SET_MAX (UINT8_MAX) +#define MAX_LINKED_SET_DEFAULT (10) + +/* For "cfx_max_prebuilt_set". */ +#define MAX_PREBUILT_SET_MIN (0) +#define MAX_PREBUILT_SET_MAX (UINT8_MAX) +#define MAX_PREBUILT_SET_DEFAULT (3) + +/* For "cfx_max_leg_retry". */ +#define MAX_UNLINKED_LEG_RETRY_DEFAULT (3) +#define MAX_UNLINKED_LEG_RETRY_MIN (0) +#define MAX_UNLINKED_LEG_RETRY_MAX (UINT8_MAX) + +/* For "cfx_num_legs_set". */ +#define NUM_LEGS_SET_MIN (0) +#define NUM_LEGS_SET_MAX (UINT8_MAX) +#define NUM_LEGS_SET_DEFAULT (2) + +/* For "cfx_send_pct". */ +#define CFX_SEND_PCT_MIN (0) +#define CFX_SEND_PCT_MAX (255) +#define CFX_SEND_PCT_DFLT 100 + +/* For "cfx_drain_pct". */ +#define CFX_DRAIN_PCT_MIN (0) +#define CFX_DRAIN_PCT_MAX (255) +#define CFX_DRAIN_PCT_DFLT 0 + +/* + * Cached consensus parameters. + */ + +/* Indicate if conflux is enabled or disabled. */ +static bool conflux_enabled = CONFLUX_ENABLED_DEFAULT; +/* Maximum number of linked set we are allowed to have (even if in use). */ +static uint8_t max_linked_set = MAX_LINKED_SET_DEFAULT; +/* Maximum number of pre built set. */ +static uint8_t max_prebuilt_set = MAX_PREBUILT_SET_DEFAULT; +/* Maximum number of unlinked leg retry that is how many times are we allowed + * to retry a leg until it successfully links. */ +STATIC uint32_t max_unlinked_leg_retry = MAX_UNLINKED_LEG_RETRY_DEFAULT; +/* Number of legs per set. */ +static uint8_t num_legs_set = NUM_LEGS_SET_DEFAULT; +/* The low Exit relay threshold, as a ratio between 0 and 1, used as a limit to + * decide the amount of pre-built set we build depending on how many Exit relay + * supports conflux in our current consensus. */ +static double low_exit_threshold_ratio = + LOW_EXIT_THRESHOLD_DEFAULT / (double)LOW_EXIT_THRESHOLD_MAX; + +static uint8_t cfx_drain_pct = CFX_DRAIN_PCT_DFLT; +static uint8_t cfx_send_pct = CFX_SEND_PCT_DFLT; + +/* Ratio of Exit relays in our consensus supporting conflux. This is computed + * at every consensus and it is between 0 and 1. */ +static double exit_conflux_ratio = 0.0; + +/** Sets num_conflux_exit with the latest count of Exits in the given consensus + * that supports Conflux. */ +static void +count_exit_with_conflux_support(const networkstatus_t *ns) +{ + double supported = 0.0; + + if (!ns || smartlist_len(ns->routerstatus_list) == 0) { + return; + } + + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, const routerstatus_t *, rs) { + if (rs->pv.supports_conflux) { + supported++; + } + } SMARTLIST_FOREACH_END(rs); + + exit_conflux_ratio = + supported / smartlist_len(ns->routerstatus_list); + + log_info(LD_GENERAL, "Consensus has %.2f %% Exit relays supporting Conflux", + exit_conflux_ratio * 100.0); +} + +/** + * Return true iff conflux feature is enabled and usable for a given circuit. + * + * Circ may be NULL, in which case we only check the consensus and torrc. */ +bool +conflux_is_enabled(const circuit_t *circ) +{ + const or_options_t *opts = get_options(); + + /* Conflux CAN NOT operate properly without congestion control and so + * automatically disabled conflux if we don't have CC enabled. */ + if (!congestion_control_enabled()) { + return false; + } + + if (circ) { + /* If circuit is non-null, we need to check to see if congestion + * control was successfully negotiated. Conflux depends upon congestion + * control, and consensus checks are not enough because there can be a + * race between those checks and the consensus update to enable + * congestion control. This happens in Shadow, and at relay restart. */ + if (CIRCUIT_IS_ORIGIN(circ)) { + tor_assert(CONST_TO_ORIGIN_CIRCUIT(circ)->cpath); + tor_assert(CONST_TO_ORIGIN_CIRCUIT(circ)->cpath->prev); + if (!CONST_TO_ORIGIN_CIRCUIT(circ)->cpath->prev->ccontrol) + return false; + } else { + if (!circ->ccontrol) + return false; + } + } + + /* For clients, this is mostly for sbws. For relays, this is an emergency + * emergency override, in case a bug is discovered by a relay operator + * and we can't set a consensus param fast enough. Basically gives them + * an option other than downgrading. */ + if (opts->ConfluxEnabled != -1) { + if (server_mode(opts)) { + char *msg; + static ratelim_t rlimit = RATELIM_INIT(60 * 60); /* Hourly */ + if ((msg = rate_limit_log(&rlimit, time(NULL)))) { + log_warn(LD_GENERAL, + "This tor is a relay and ConfluxEnabled is set to 0. " + "We would ask you to please write to us on " + "tor-relay@lists.torproject.org or file a bug explaining " + "why you have disabled this option. Without news from you, " + "we might end up marking your relay as a BadExit."); + tor_free(msg); + } + } + return opts->ConfluxEnabled; + } + + return conflux_enabled; +} + +/** Return the maximum number of linked set we are allowed to have. */ +uint8_t +conflux_params_get_max_linked_set(void) +{ + return max_linked_set; +} + +/** Return the number of maximum pre built sets that is allowed to have. */ +uint8_t +conflux_params_get_max_prebuilt(void) +{ + /* Without any Exit supporting conflux, we won't be able to build a set. The + * float problem here is minimal because exit_conflux_ratio is either a flat + * 0 or else it means we do have at least an exit. */ + if (exit_conflux_ratio <= 0.0) { + return 0; + } + + /* Allow only 1 pre built set if we are lower than the low exit threshold + * parameter from the consensus. */ + if (exit_conflux_ratio < low_exit_threshold_ratio) { + return 1; + } + return max_prebuilt_set; +} + +/** Return the maximum number of retry we can do until a leg links. */ +uint8_t +conflux_params_get_max_unlinked_leg_retry(void) +{ + return max_unlinked_leg_retry; +} + +/** Return the number of legs per set. */ +uint8_t +conflux_params_get_num_legs_set(void) +{ + return num_legs_set; +} + +/** Return the drain percent we must hit before switching */ +uint8_t +conflux_params_get_drain_pct(void) +{ + return cfx_drain_pct; +} + +/** Return the percent of the congestion window to send before switching. */ +uint8_t +conflux_params_get_send_pct(void) +{ + return cfx_send_pct; +} + +/** Update global conflux related consensus parameter values, every consensus + * update. */ +void +conflux_params_new_consensus(const networkstatus_t *ns) +{ + /* Params used by conflux_pool.c */ + conflux_enabled = + networkstatus_get_param(ns, "cfx_enabled", + CONFLUX_ENABLED_DEFAULT, + CONFLUX_ENABLED_MIN, CONFLUX_ENABLED_MAX); + + low_exit_threshold_ratio = + networkstatus_get_param(ns, "cfx_low_exit_threshold", + LOW_EXIT_THRESHOLD_DEFAULT, + LOW_EXIT_THRESHOLD_MIN, LOW_EXIT_THRESHOLD_MAX) / + (double)LOW_EXIT_THRESHOLD_MAX; + + max_linked_set = + networkstatus_get_param(ns, "cfx_max_linked_set", + MAX_LINKED_SET_DEFAULT, + MAX_LINKED_SET_MIN, MAX_LINKED_SET_MAX); + + max_prebuilt_set = + networkstatus_get_param(ns, "cfx_max_prebuilt_set", + MAX_PREBUILT_SET_DEFAULT, + MAX_PREBUILT_SET_MIN, MAX_PREBUILT_SET_MAX); + + max_unlinked_leg_retry = + networkstatus_get_param(ns, "cfx_max_unlinked_leg_retry", + MAX_UNLINKED_LEG_RETRY_DEFAULT, + MAX_UNLINKED_LEG_RETRY_MIN, + MAX_UNLINKED_LEG_RETRY_MAX); + + num_legs_set = + networkstatus_get_param(ns, "cfx_num_legs_set", + NUM_LEGS_SET_DEFAULT, + NUM_LEGS_SET_MIN, NUM_LEGS_SET_MAX); + + /* Params used by conflux.c */ + cfx_send_pct = networkstatus_get_param(ns, "cfx_send_pct", + CFX_SEND_PCT_DFLT, + CFX_SEND_PCT_MIN, + CFX_SEND_PCT_MAX); + + cfx_drain_pct = networkstatus_get_param(ns, "cfx_drain_pct", + CFX_DRAIN_PCT_DFLT, + CFX_DRAIN_PCT_MIN, + CFX_DRAIN_PCT_MAX); + + count_exit_with_conflux_support(ns); +} diff --git a/src/core/or/conflux_params.h b/src/core/or/conflux_params.h new file mode 100644 index 0000000000..22c3e4ad1f --- /dev/null +++ b/src/core/or/conflux_params.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_params.h + * \brief Header file for conflux_params.c. + **/ + +#ifndef TOR_CONFLUX_PARAMS_H +#define TOR_CONFLUX_PARAMS_H + +#include "core/or/or.h" + +bool conflux_is_enabled(const struct circuit_t *circ); +uint8_t conflux_params_get_max_linked_set(void); +uint8_t conflux_params_get_max_prebuilt(void); +uint8_t conflux_params_get_max_unlinked_leg_retry(void); +uint8_t conflux_params_get_num_legs_set(void); +uint8_t conflux_params_get_drain_pct(void); +uint8_t conflux_params_get_send_pct(void); + +void conflux_params_new_consensus(const networkstatus_t *ns); + +#ifdef TOR_UNIT_TESTS +extern uint32_t max_unlinked_leg_retry; +#endif + +#endif /* TOR_CONFLUX_PARAMS_H */ + diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c index b994cfabc4..f14034c2b7 100644 --- a/src/feature/nodelist/networkstatus.c +++ b/src/feature/nodelist/networkstatus.c @@ -51,6 +51,7 @@ #include "core/or/circuitmux.h" #include "core/or/circuitmux_ewma.h" #include "core/or/circuitstats.h" +#include "core/or/conflux_params.h" #include "core/or/connection_edge.h" #include "core/or/connection_or.h" #include "core/or/dos.h" @@ -1711,6 +1712,7 @@ notify_after_networkstatus_changes(void) flow_control_new_consensus_params(c); hs_service_new_consensus_params(c); dns_new_consensus_params(c); + conflux_params_new_consensus(c);
/* Maintenance of our L2 guard list */ maintain_layer2_guards();
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit a1794ef687593793b36dfe7aecb06b35321f332b Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Wed Dec 14 21:03:32 2022 +0000
Prop#329 Headers: Header files for conflux --- src/core/or/conflux.h | 84 +++++++++++++++++++++++++ src/core/or/conflux_st.h | 157 +++++++++++++++++++++++++++++++++++++++++++++++ src/core/or/include.am | 2 + 3 files changed, 243 insertions(+)
diff --git a/src/core/or/conflux.h b/src/core/or/conflux.h new file mode 100644 index 0000000000..e01b323317 --- /dev/null +++ b/src/core/or/conflux.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2019-2021, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux.h + * \brief Public APIs for conflux multipath support + **/ + +#ifndef TOR_CONFLUX_H +#define TOR_CONFLUX_H + +#include "core/or/circuit_st.h" +#include "core/or/conflux_st.h" + +typedef struct conflux_t conflux_t; +typedef struct conflux_leg_t conflux_leg_t; + +/** Helpers to iterate over legs with better semantic. */ +#define CONFLUX_FOR_EACH_LEG_BEGIN(cfx, var) \ + SMARTLIST_FOREACH_BEGIN(cfx->legs, conflux_leg_t *, var) +#define CONFLUX_FOR_EACH_LEG_END(var) \ + SMARTLIST_FOREACH_END(var) + +/** Helper: Return the number of legs a conflux object has. */ +#define CONFLUX_NUM_LEGS(cfx) (smartlist_len(cfx->legs)) + +/** A cell for the out-of-order queue. + * XXX: Consider trying to use packed_cell_t instead here? */ +typedef struct { + /** + * Absolute sequence number of this cell, computed from the + * relative sequence number of the conflux cell. */ + uint64_t seq; + + /** + * Heap index of this cell, for use in in the conflux_t ooo_q heap. + */ + int heap_idx; + + /** The cell here is always guaranteed to have removed its + * extra conflux sequence number, for ease of processing */ + cell_t cell; +} conflux_cell_t; + +size_t conflux_handle_oom(size_t bytes_to_remove); +uint64_t conflux_get_total_bytes_allocation(void); +uint64_t conflux_get_circ_bytes_allocation(const circuit_t *circ); + +void conflux_update_rtt(conflux_t *cfx, circuit_t *circ, uint64_t rtt_usec); + +circuit_t *conflux_decide_circ_for_send(conflux_t *cfx, + circuit_t *orig_circ, + uint8_t relay_command); +circuit_t *conflux_decide_next_circ(conflux_t *cfx); + +int conflux_process_switch_command(circuit_t *in_circ, + crypt_path_t *layer_hint, cell_t *cell, + relay_header_t *rh); +bool conflux_should_multiplex(int relay_command); +bool conflux_process_cell(conflux_t *cfx, circuit_t *in_circ, + crypt_path_t *layer_hint, + cell_t *cell); +conflux_cell_t *conflux_dequeue_cell(conflux_t *cfx); +void conflux_note_cell_sent(conflux_t *cfx, circuit_t *circ, + uint8_t relay_command); + +/* Private section starts. */ +#ifdef TOR_CONFLUX_PRIVATE + +const struct congestion_control_t *circuit_ccontrol(const circuit_t *); +conflux_leg_t *conflux_get_leg(conflux_t *cfx, const circuit_t *circ); +uint64_t conflux_get_max_seq_recv(const conflux_t *cfx); +uint64_t conflux_get_max_seq_sent(const conflux_t *cfx); + +/* + * Unit tests declaractions. + */ +#ifdef TOR_UNIT_TESTS + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(TOR_CONFLUX_PRIVATE) */ + +#endif /* !defined(TOR_CONFLUX_H) */ diff --git a/src/core/or/conflux_st.h b/src/core/or/conflux_st.h new file mode 100644 index 0000000000..dae4845cb6 --- /dev/null +++ b/src/core/or/conflux_st.h @@ -0,0 +1,157 @@ +/* Copyright (c) 2019-2021, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_st.h + * \brief Structure definitions for conflux multipath + **/ + +#ifndef CONFLUX_ST_H +#define CONFLUX_ST_H + +#include "core/or/circuit_st.h" +#include "core/or/cell_st.h" +#include "lib/defs/digest_sizes.h" + +/** +* Specifies which conflux alg is in use. +*/ +typedef enum { + CONFLUX_ALG_MINRTT = 0, + CONFLUX_ALG_LOWRTT = 1, + CONFLUX_ALG_CWNDRTT = 2, + CONFLUX_ALG_MAXRATE = 3, + CONFLUX_ALG_HIGHRATE = 4 +} conflux_alg_t; + +/** + * Maximum number of linked circuits. + * + * We want to experiment with 3 guards, so we need at least 3 here. + * + * However, we need 1 more than this, to support using a test circuit to probe + * for a faster path, for applications that *require* a specific latency target + * (like VoIP). + * + * We may also want to raise this for traffic analysis defense evaluation. + */ +#define CONFLUX_MAX_CIRCS 4 + +/** XXX: Cached consensus params+scheduling alg */ +struct conflux_params_t { + conflux_alg_t alg; +}; + +struct conflux_leg_t { + /** + * For computing ooo_q insertion sequence numbers: Highest absolute + * sequence number recieved on each leg, before delivery. + * + * As a reciever, this allows us to compute the absolute sequence number + * of a cell for delivery or insertion into the ooo_q. When a SWITCH cell + * is recieved on a leg, the absolute sequence number of that cell is + * the relative sequence number in that cell, plus the absolute sequence + * number of that leg from this array. The leg's sequence number + * is then updated to this value immediately. + * + * In this way, we are able to assign absolute sequence numbers to cells + * immediately, regardless of how many legs or leg switches have ocurred, + * and regardless of the delivery status of each cell versus if it must be + * queued. + */ + uint64_t last_seq_recv; + + /** + * For relative sequencing: Highest absolute sequence number sent on each + * circuit. The overall absolute current sent sequence number is the highest + * of these values. + * + * As a sender, this allows us to compute a relative sequence number when + * switching legs. When switching legs, the sender looks up its current + * absolute sequence number as the maximum of all legs. The sender then + * compares that to the current sequence number on the leg it is about to + * send on, and then computes the relative sequence number as the difference + * between the overall absolute sequence number and the sequence number + * from the sending leg. + * + * In this way, we can use much smaller relative sequence numbers on the + * wire, as opposed to larger absolute values, at the expense of this + * bookkeeping overhead on each end. + */ + uint64_t last_seq_sent; + + /** + * Current round-trip of the circuit, in usec. + * + * XXX: In theory, we could use the congestion control RTTs directly off the + * circs, but congestion control code has assumptions about the RTT being 0 + * at the start of the circuit, which will *not* be the case here, because we + * get an RTT off the link circuit. */ + uint64_t circ_rtts_usec; + + /** Exit side only: When was the LINKED cell sent? Used for RTT measurement + * that sets circ_rtts_usec when the LINKED_ACK is received. */ + uint64_t linked_sent_usec; + + /** Circuit of this leg. */ + circuit_t *circ; +}; + +/** Fields for conflux multipath support */ +struct conflux_t { + /** Cached parameters for this circuit */ + struct conflux_params_t params; + + /** + * List of all linked conflux_leg_t for this set. Once a leg is in that list, + * it can be used to transmit data. */ + smartlist_t *legs; + + /** + * Out-of-order priority queue of conflux_cell_t *, heapified + * on conflux_cell_t.seq number (lowest at top of heap). + * + * XXX: We are most likely to insert cells at either the head or the tail. + * Verify that is fast-path wrt smartlist priority queues, and not a memmove + * nightmare. If so, we may need a real linked list, or a packed_cell_t list. + */ + smartlist_t *ooo_q; + + /** + * Absolute sequence number of cells delivered to streams since start. + * (ie: this is updated *after* dequeue from the ooo_q priority queue). */ + uint64_t last_seq_delivered; + + /** + * The estimated remaining number of cells we can send on this circuit + * before we are allowed to switch legs. */ + uint64_t cells_until_switch; + + /** Current circuit leg. Only use this with conflux_get_circ_for_leg() for + * bounds checking. */ + struct conflux_leg_t *curr_leg; + + /** Previous circuit leg. Only use this with conflux_get_circ_for_leg() for + * bounds checking. */ + struct conflux_leg_t *prev_leg; + + /** The nonce that joins these */ + uint8_t nonce[DIGEST256_LEN]; + + /** Indicate if this conflux set is in full teardown. We mark it at the first + * close in case of a total teardown so we avoid recursive calls of circuit + * mark for close. */ + bool in_full_teardown; + + /** Number of leg launch that we've done for this set. We keep this value + * because there is a maximum allowed in order to avoid side channel(s). */ + unsigned int num_leg_launch; + + /** + * PolicyHint: Predicted ports/protocol shorthand.. + * + * XXX: This might be redundant to the circuit's exitpolicy. + */ +}; + +#endif /* !defined(CONFLUX_ST_H) */ diff --git a/src/core/or/include.am b/src/core/or/include.am index b08f8509cc..9a4841e937 100644 --- a/src/core/or/include.am +++ b/src/core/or/include.am @@ -108,6 +108,8 @@ noinst_HEADERS += \ src/core/or/congestion_control_vegas.h \ src/core/or/congestion_control_nola.h \ src/core/or/congestion_control_westwood.h \ + src/core/or/conflux.h \ + src/core/or/conflux_st.h \ src/core/or/server_port_cfg_st.h \ src/core/or/socks_request_st.h \ src/core/or/status.h \
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 2bd1eca78c7771b9d16bb2a670420987c4a36c91 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Wed Dec 14 21:03:52 2022 +0000
Prop#329 Algs: Conflux multiplexed cell receive handling --- src/core/or/conflux.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/core/or/relay.c | 80 ++++++++++++++-- 2 files changed, 331 insertions(+), 9 deletions(-)
diff --git a/src/core/or/conflux.c b/src/core/or/conflux.c new file mode 100644 index 0000000000..6179fea279 --- /dev/null +++ b/src/core/or/conflux.c @@ -0,0 +1,260 @@ +/* Copyright (c) 2021, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux.c + * \brief Conflux multipath core algorithms + */ + +#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/origin_circuit_st.h" +#include "core/or/circuitlist.h" +#include "core/or/circuituse.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" + +#include "trunnel/extension.h" + +/** One million microseconds in a second */ +#define USEC_PER_SEC 1000000 + +/** + * Determine if we should multiplex a specific relay command or not. + * + * TODO: Version of this that is the set of forbidden commands + * on linked circuits + */ +bool +conflux_should_multiplex(int relay_command) +{ + switch (relay_command) { + /* These are all fine to multiplex, and must be + * so that ordering is preserved */ + case RELAY_COMMAND_BEGIN: + case RELAY_COMMAND_DATA: + case RELAY_COMMAND_END: + case RELAY_COMMAND_CONNECTED: + return true; + + /* We can't multiplex these because they are + * circuit-specific */ + case RELAY_COMMAND_SENDME: + case RELAY_COMMAND_EXTEND: + case RELAY_COMMAND_EXTENDED: + case RELAY_COMMAND_TRUNCATE: + case RELAY_COMMAND_TRUNCATED: + case RELAY_COMMAND_DROP: + return false; + + /* We must multiplex RESOLVEs because their ordering + * impacts begin/end. */ + case RELAY_COMMAND_RESOLVE: + case RELAY_COMMAND_RESOLVED: + return true; + + /* These are all circuit-specific */ + case RELAY_COMMAND_BEGIN_DIR: + case RELAY_COMMAND_EXTEND2: + case RELAY_COMMAND_EXTENDED2: + case RELAY_COMMAND_ESTABLISH_INTRO: + case RELAY_COMMAND_ESTABLISH_RENDEZVOUS: + case RELAY_COMMAND_INTRODUCE1: + case RELAY_COMMAND_INTRODUCE2: + case RELAY_COMMAND_RENDEZVOUS1: + case RELAY_COMMAND_RENDEZVOUS2: + case RELAY_COMMAND_INTRO_ESTABLISHED: + case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: + case RELAY_COMMAND_INTRODUCE_ACK: + case RELAY_COMMAND_PADDING_NEGOTIATE: + case RELAY_COMMAND_PADDING_NEGOTIATED: + return false; + + /* These must be multiplexed because their ordering + * relative to BEGIN/END must be preserved */ + case RELAY_COMMAND_XOFF: + case RELAY_COMMAND_XON: + return true; + + /* These two are not multiplexed, because they must + * be processed immediately to update sequence numbers + * before any other cells are processed on the circuit */ + case RELAY_COMMAND_CONFLUX_SWITCH: + case RELAY_COMMAND_CONFLUX_LINK: + case RELAY_COMMAND_CONFLUX_LINKED: + case RELAY_COMMAND_CONFLUX_LINKED_ACK: + return false; + + default: + log_warn(LD_BUG, "Conflux asked to multiplex unknown relay command %d", + relay_command); + return false; + } +} + +/** Return the leg for a circuit in a conflux set. Return NULL if not found. */ +conflux_leg_t * +conflux_get_leg(conflux_t *cfx, const circuit_t *circ) +{ + conflux_leg_t *leg_found = NULL; + + // Find the leg that the cell is written on + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) { + if (leg->circ == circ) { + leg_found = leg; + break; + } + } CONFLUX_FOR_EACH_LEG_END(leg); + + return leg_found; +} + +/** + * Comparison function for ooo_q pqueue. + * + * Ensures that lower sequence numbers are at the head of the pqueue. + */ +static int +conflux_queue_cmp(const void *a, const void *b) +{ + // Compare a and b as conflux_cell_t using the seq field, and return a + // comparison result such that the lowest seq is at the head of the pqueue. + const conflux_cell_t *cell_a = a; + const conflux_cell_t *cell_b = b; + + tor_assert(cell_a); + tor_assert(cell_b); + + if (cell_a->seq < cell_b->seq) { + return -1; + } else if (cell_a->seq > cell_b->seq) { + return 1; + } else { + return 0; + } +} + +/** + * Get the congestion control object for a conflux circuit. + * + * Because conflux can only be negotiated with the last hop, we + * can use the last hop of the cpath to obtain the congestion + * control object for origin circuits. For non-origin circuits, + * we can use the circuit itself. + */ +const congestion_control_t * +circuit_ccontrol(const circuit_t *circ) +{ + const congestion_control_t *ccontrol = NULL; + tor_assert(circ); + + if (CIRCUIT_IS_ORIGIN(circ)) { + tor_assert(CONST_TO_ORIGIN_CIRCUIT(circ)->cpath); + tor_assert(CONST_TO_ORIGIN_CIRCUIT(circ)->cpath->prev); + ccontrol = CONST_TO_ORIGIN_CIRCUIT(circ)->cpath->prev->ccontrol; + } else { + ccontrol = circ->ccontrol; + } + + /* Conflux circuits always have congestion control*/ + tor_assert(ccontrol); + return ccontrol; +} + +/** + * Process an incoming relay cell for conflux. Called from + * connection_edge_process_relay_cell(). + * + * Returns true if the conflux system now has well-ordered cells to deliver + * to streams, false otherwise. + */ +bool +conflux_process_cell(conflux_t *cfx, circuit_t *in_circ, + crypt_path_t *layer_hint, cell_t *cell) +{ + // TODO-329-TUNING: Temporarily validate legs here. We can remove + // this after tuning is complete. + conflux_validate_legs(cfx); + + conflux_leg_t *leg = conflux_get_leg(cfx, in_circ); + if (!leg) { + log_warn(LD_BUG, "Got a conflux cell on a circuit without " + "conflux leg. Closing circuit."); + circuit_mark_for_close(in_circ, END_CIRC_REASON_INTERNAL); + return false; + } + + /* We need to make sure this cell came from the expected hop, or + * else it could be a data corruption attack from a middle node. */ + if (!conflux_validate_source_hop(in_circ, layer_hint)) { + circuit_mark_for_close(in_circ, END_CIRC_REASON_TORPROTOCOL); + return false; + } + + /* Update the running absolute sequence number */ + leg->last_seq_recv++; + + /* If this cell is next, fast-path it by processing the cell in-place */ + if (leg->last_seq_recv == cfx->last_seq_delivered + 1) { + /* The cell is now ready to be processed, and rest of the queue should + * now be checked for remaining elements */ + cfx->last_seq_delivered++; + return true; + } else if (BUG(leg->last_seq_recv <= cfx->last_seq_delivered)) { + log_warn(LD_BUG, "Got a conflux cell with a sequence number " + "less than the last delivered. Closing circuit."); + circuit_mark_for_close(in_circ, END_CIRC_REASON_INTERNAL); + return false; + } else { + conflux_cell_t *c_cell = tor_malloc_zero(sizeof(conflux_cell_t)); + c_cell->seq = leg->last_seq_recv; + + memcpy(&c_cell->cell, cell, sizeof(cell_t)); + + smartlist_pqueue_add(cfx->ooo_q, conflux_queue_cmp, + offsetof(conflux_cell_t, heap_idx), c_cell); + total_ooo_q_bytes += sizeof(cell_t); + + /* This cell should not be processed yet, and the queue is not ready + * to process because the next absolute seqnum has not yet arrived */ + return false; + } +} + +/** + * Dequeue the top cell from our queue. + * + * Returns the cell as a conflux_cell_t, or NULL if the queue is empty + * or has a hole. + */ +conflux_cell_t * +conflux_dequeue_cell(conflux_t *cfx) +{ + conflux_cell_t *top = NULL; + if (smartlist_len(cfx->ooo_q) == 0) + return NULL; + + top = smartlist_get(cfx->ooo_q, 0); + + /* If the top cell is the next sequence number we need, then + * pop and return it. */ + if (top->seq == cfx->last_seq_delivered+1) { + smartlist_pqueue_pop(cfx->ooo_q, conflux_queue_cmp, + offsetof(conflux_cell_t, heap_idx)); + total_ooo_q_bytes -= sizeof(cell_t); + cfx->last_seq_delivered++; + return top; + } else { + return NULL; + } +} diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 7929f57ee6..26e52b0d95 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -99,6 +99,7 @@ #include "core/or/sendme.h" #include "core/or/congestion_control_common.h" #include "core/or/congestion_control_flow.h" +#include "core/or/conflux.h"
static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, @@ -116,6 +117,11 @@ static void adjust_exit_policy_from_exitpolicy_failure(origin_circuit_t *circ, entry_connection_t *conn, node_t *node, const tor_addr_t *addr); +static int connection_edge_process_ordered_relay_cell(cell_t *cell, + circuit_t *circ, + edge_connection_t *conn, + crypt_path_t *layer_hint, + relay_header_t *rh);
/** Stats: how many relay cells have originated at this hop, or have * been relayed onward (not recognized at this hop)? @@ -610,7 +616,7 @@ pad_cell_payload(uint8_t *cell_payload, size_t data_len) * return 0. */ MOCK_IMPL(int, -relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ, +relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *orig_circ, uint8_t relay_command, const char *payload, size_t payload_len, crypt_path_t *cpath_layer, const char *filename, int lineno)) @@ -640,6 +646,7 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ, rh.stream_id = stream_id; rh.length = payload_len; relay_header_pack(cell.payload, &rh); + if (payload_len) memcpy(cell.payload+RELAY_HEADER_SIZE, payload, payload_len);
@@ -2051,9 +2058,6 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, static int num_seen=0; relay_header_t rh; unsigned domain = layer_hint?LD_APP:LD_EXIT; - int optimistic_data = 0; /* Set to 1 if we receive data on a stream - * that's in the EXIT_CONN_STATE_RESOLVING - * or EXIT_CONN_STATE_CONNECTING states. */
tor_assert(cell); tor_assert(circ); @@ -2086,8 +2090,66 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } }
+ /* Conflux handling: If conflux is disabled, or the relay command is not + * multiplexed across circuits, then process it immediately. + * + * Otherwise, we need to process the relay cell against our conflux + * queues, and if doing so results in ordered cells to deliver, we + * dequeue and process those in-order until there are no more. + */ + if (!circ->conflux || !conflux_should_multiplex(rh.command)) { + return connection_edge_process_ordered_relay_cell(cell, circ, conn, + layer_hint, &rh); + } else { + // If conflux says this cell is in-order, then begin processing + // cells from queue until there are none. Otherwise, we do nothing + // until further cells arrive. + if (conflux_process_cell(circ->conflux, circ, layer_hint, cell)) { + conflux_cell_t *c_cell = NULL; + int ret = 0; + + /* First, process this cell */ + if ((ret = connection_edge_process_ordered_relay_cell(cell, circ, conn, + layer_hint, &rh)) < 0) { + return ret; + } + + /* Now, check queue for more */ + while ((c_cell = conflux_dequeue_cell(circ->conflux))) { + relay_header_unpack(&rh, c_cell->cell.payload); + conn = relay_lookup_conn(circ, &c_cell->cell, CELL_DIRECTION_OUT, + layer_hint); + if ((ret = connection_edge_process_ordered_relay_cell(&c_cell->cell, + circ, conn, layer_hint, + &rh)) < 0) { + /* Negative return value is a fatal error. Return early and tear down + * circuit */ + tor_free(c_cell); + return ret; + } + tor_free(c_cell); + } + } + } + + return 0; +} + +/** + * Helper function to process a relay cell that is in the proper order + * for processing right now. */ +static int +connection_edge_process_ordered_relay_cell(cell_t *cell, circuit_t *circ, + edge_connection_t *conn, + crypt_path_t *layer_hint, + relay_header_t *rh) +{ + int optimistic_data = 0; /* Set to 1 if we receive data on a stream + * that's in the EXIT_CONN_STATE_RESOLVING + * or EXIT_CONN_STATE_CONNECTING states. */ + /* Tell circpad that we've received a recognized cell */ - circpad_deliver_recognized_relay_cell_events(circ, rh.command, layer_hint); + circpad_deliver_recognized_relay_cell_events(circ, rh->command, layer_hint);
/* either conn is NULL, in which case we've got a control cell, or else * conn points to the recognized stream. */ @@ -2095,22 +2157,22 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, if (conn->base_.type == CONN_TYPE_EXIT && (conn->base_.state == EXIT_CONN_STATE_CONNECTING || conn->base_.state == EXIT_CONN_STATE_RESOLVING) && - rh.command == RELAY_COMMAND_DATA) { + rh->command == RELAY_COMMAND_DATA) { /* Allow DATA cells to be delivered to an exit node in state * EXIT_CONN_STATE_CONNECTING or EXIT_CONN_STATE_RESOLVING. * This speeds up HTTP, for example. */ optimistic_data = 1; - } else if (rh.stream_id == 0 && rh.command == RELAY_COMMAND_DATA) { + } else if (rh->stream_id == 0 && rh->command == RELAY_COMMAND_DATA) { log_warn(LD_BUG, "Somehow I had a connection that matched a " "data cell with stream ID 0."); } else { return connection_edge_process_relay_cell_not_open( - &rh, cell, circ, conn, layer_hint); + rh, cell, circ, conn, layer_hint); } }
return handle_relay_cell_command(cell, circ, conn, layer_hint, - &rh, optimistic_data); + rh, optimistic_data); }
/** How many relay_data cells have we built, ever? */
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit e0881a669a84d016ea108a5604f25bbcb45a7705 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Tue Jan 24 23:05:17 2023 +0000
Prop#329 Algs: Conflux multiplexed cell sending decision algs --- src/core/or/circuit_st.h | 13 + src/core/or/conflux.c | 713 +++++++++++++++++++++++++++++++- src/core/or/congestion_control_common.c | 7 +- src/core/or/congestion_control_common.h | 2 +- src/core/or/relay.c | 23 ++ 5 files changed, 754 insertions(+), 4 deletions(-)
diff --git a/src/core/or/circuit_st.h b/src/core/or/circuit_st.h index be6429438a..7f39c9337e 100644 --- a/src/core/or/circuit_st.h +++ b/src/core/or/circuit_st.h @@ -248,6 +248,19 @@ struct circuit_t {
/** Congestion control fields */ struct congestion_control_t *ccontrol; + + /** Conflux linked circuit information. + * + * If this is non-NULL, the circuit is linked and part of a usable set, + * and for origin_circuit_t subtypes, the circuit purpose is + * CIRCUIT_PURPOSE_CONFLUX_LINKED. + * + * If this is NULL, the circuit could still be part of a pending conflux + * object, in which case the conflux_pending_nonce field is set, and for + * origin_circuit_t subtypes, the purpose is + * CIRCUIT_PURPOSE_CONFLUX_UNLINKED. + */ + struct conflux_t *conflux; };
#endif /* !defined(CIRCUIT_ST_H) */ diff --git a/src/core/or/conflux.c b/src/core/or/conflux.c index 6179fea279..699d1b5c40 100644 --- a/src/core/or/conflux.c +++ b/src/core/or/conflux.c @@ -12,6 +12,7 @@
#include "core/or/circuit_st.h" #include "core/or/sendme.h" +#include "core/or/relay.h" #include "core/or/congestion_control_common.h" #include "core/or/congestion_control_st.h" #include "core/or/origin_circuit_st.h" @@ -21,14 +22,16 @@ #include "core/or/conflux_params.h" #include "core/or/conflux_util.h" #include "core/or/conflux_st.h" +#include "core/or/conflux_cell.h" #include "lib/time/compat_time.h" #include "app/config/config.h"
-#include "trunnel/extension.h" - /** One million microseconds in a second */ #define USEC_PER_SEC 1000000
+static inline uint64_t cwnd_sendable(const circuit_t *on_circ, + uint64_t in_usec, uint64_t our_usec); + /** * Determine if we should multiplex a specific relay command or not. * @@ -119,6 +122,612 @@ conflux_get_leg(conflux_t *cfx, const circuit_t *circ) return leg_found; }
+/** + * Gets the maximum last_seq_sent from all legs. + */ +uint64_t +conflux_get_max_seq_sent(const conflux_t *cfx) +{ + uint64_t max_seq_sent = 0; + + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) { + if (leg->last_seq_sent > max_seq_sent) { + max_seq_sent = leg->last_seq_sent; + } + } CONFLUX_FOR_EACH_LEG_END(leg); + + return max_seq_sent; +} + +/** + * Gets the maximum last_seq_recv from all legs. + */ +uint64_t +conflux_get_max_seq_recv(const conflux_t *cfx) +{ + uint64_t max_seq_recv = 0; + + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) { + if (leg->last_seq_recv > max_seq_recv) { + max_seq_recv = leg->last_seq_recv; + } + } CONFLUX_FOR_EACH_LEG_END(leg); + + return max_seq_recv; +} + +/** + * Returns true if a circuit has package window space to send, and is + * not blocked locally. + */ +static inline bool +circuit_ready_to_send(const circuit_t *circ) +{ + const congestion_control_t *cc = circuit_ccontrol(circ); + bool cc_sendable = true; + + /* We consider ourselves blocked if we're within 1 sendme of the + * cwnd, because inflight is decremented before this check */ + // TODO-329-TUNING: This subtraction not be right.. It depends + // on call order wrt decisions and sendme arrival + if (cc->inflight + cc->sendme_inc >= cc->cwnd) { + cc_sendable = false; + } + + /* Origin circuits use the package window of the last hop, and + * have an outbound cell direction (towards exit). Otherwise, + * there is no cpath and direction is inbound. */ + if (CIRCUIT_IS_ORIGIN(circ)) { + return cc_sendable && !circ->circuit_blocked_on_n_chan; + } else { + return cc_sendable && !circ->circuit_blocked_on_p_chan; + } +} + +/** + * Return the circuit with the minimum RTT. Do not use any + * other circuit. + * + * This algorithm will minimize RTT always, and will not provide + * any throughput benefit. We expect it to be useful for VoIP/UDP + * use cases. Because it only uses one circuit on a leg at a time, + * it can have more than one circuit per guard (ie: to find + * lower-latency middles for the path). + */ +static const circuit_t * +conflux_decide_circ_minrtt(const conflux_t *cfx) +{ + uint64_t min_rtt = UINT64_MAX; + const circuit_t *circ = NULL; + + /* Can't get here without any legs. */ + tor_assert(CONFLUX_NUM_LEGS(cfx)); + + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) { + if (leg->circ_rtts_usec < min_rtt) { + circ = leg->circ; + min_rtt = leg->circ_rtts_usec; + } + } CONFLUX_FOR_EACH_LEG_END(leg); + + /* If the minRTT circuit can't send, dont send on any circuit. */ + if (!circ || !circuit_ready_to_send(circ)) { + return NULL; + } + return circ; +} + +/** + * Favor the circuit with the lowest RTT that still has space in the + * congestion window. + * + * This algorithm will maximize total throughput at the expense of + * bloating out-of-order queues. + */ +static const circuit_t * +conflux_decide_circ_lowrtt(const conflux_t *cfx) +{ + uint64_t low_rtt = UINT64_MAX; + const circuit_t *circ = NULL; + + /* Can't get here without any legs. */ + tor_assert(CONFLUX_NUM_LEGS(cfx)); + + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) { + /* If the package window is full, skip it */ + if (!circuit_ready_to_send(leg->circ)) { + continue; + } + + if (leg->circ_rtts_usec < low_rtt) { + low_rtt = leg->circ_rtts_usec; + circ = leg->circ; + } + } CONFLUX_FOR_EACH_LEG_END(leg); + + /* At this point, if we found a circuit, we've already validated that its + * congestion window has room. */ + return circ; +} + +/** + * Return the amount of congestion window we can send on + * on_circ during in_usec. However, if we're still in + * slow-start, send the whole window to establish the true + * cwnd. + */ +static inline uint64_t +cwnd_sendable(const circuit_t *on_circ, uint64_t in_usec, + uint64_t our_usec) +{ + const congestion_control_t *cc = circuit_ccontrol(on_circ); + + tor_assert(cc); + + // TODO-329-TUNING: This function may want to consider inflight? + + if (our_usec == 0 || in_usec == 0) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "cwnd_sendable: Missing RTT data. in_usec: %" PRIu64 + " our_usec: %" PRIu64, in_usec, our_usec); + return cc->cwnd; + } + + if (cc->in_slow_start) { + return cc->cwnd; + } else { + uint64_t sendable = + conflux_params_get_send_pct()*cc->cwnd*in_usec/(100*our_usec); + return MIN(cc->cwnd, sendable); + } +} + +/** + * Returns the amount of room in a cwnd on a circuit. + */ +static inline uint64_t +cwnd_available(const circuit_t *on_circ) +{ + const congestion_control_t *cc = circuit_ccontrol(on_circ); + tor_assert(cc); + + if (cc->cwnd < cc->inflight) + return 0; + + return cc->cwnd - cc->inflight; +} + +/** + * Returns true if we can switch to a new circuit, false otherwise. + * + * This function assumes we're primarily switching between two circuits, + * the current and the prev. If we're using more than two circuits, we + * need to set cfx_drain_pct to 100. + */ +static inline bool +conflux_can_switch(const conflux_t *cfx) +{ + /* If we still expected to send more cells on this circuit, + * we're only allowed to switch if the previous circuit emptied. */ + if (cfx->cells_until_switch > 0) { + /* If there is no prev leg, skip the inflight check. */ + if (!cfx->prev_leg) { + return false; + } + const congestion_control_t *ccontrol = + circuit_ccontrol(cfx->prev_leg->circ); + + /* If the inflight count has drained to below cfx_drain_pct + * of the congestion window, then we can switch. + * We check the sendme_inc because there may be un-ackable + * data in inflight as well, and we can still switch then. */ + if (ccontrol->inflight < ccontrol->sendme_inc || + 100*ccontrol->inflight <= + conflux_params_get_drain_pct()*ccontrol->cwnd) { + return true; + } + + // TODO-329-TUNING: Should we try to switch if the prev_leg is + // ready to send? + + return false; + } + + return true; +} + +/** + * Favor the circuit with the lowest RTT that still has space in the + * congestion window up to the ratio of RTTs. + * + * This algorithm should only use auxillary legs up to the point + * where their data arrives roughly the same time as the lowest + * RTT leg. It will not utilize the full cwnd of auxillary legs, + * except in slow start. Therefore, out-of-order queue bloat should + * be minimized to just the slow-start phase. + */ +static const circuit_t * +conflux_decide_circ_cwndrtt(const conflux_t *cfx) +{ + uint64_t min_rtt = UINT64_MAX; + const conflux_leg_t *leg = NULL; + + /* Can't get here without any legs. */ + tor_assert(!CONFLUX_NUM_LEGS(cfx)); + + /* Find the leg with the minimum RTT.*/ + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, l) { + if (l->circ_rtts_usec < min_rtt) { + min_rtt = l->circ_rtts_usec; + leg = l; + } + } CONFLUX_FOR_EACH_LEG_END(l); + + /* If the package window is has room, use it */ + if (leg && circuit_ready_to_send(leg->circ)) { + return leg->circ; + } + + /* For any given leg, it has min_rtt/2 time before the 'primary' + * leg's acks start arriving. So, the amount of data this + * 'secondary' leg can send while the min_rtt leg transmits these + * acks is: + * (cwnd_leg/(leg_rtt/2))*min_rtt/2 = cwnd_leg*min_rtt/leg_rtt. + * So any leg with available room below that is no good. + */ + + leg = NULL; + + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, l) { + if (!circuit_ready_to_send(l->circ)) { + continue; + } + + /* Pick a 'min_leg' with the lowest RTT that still has + * room in the congestion window. Note that this works for + * min_leg itself, up to inflight. */ + if (cwnd_sendable(l->circ, min_rtt, l->circ_rtts_usec) <= + cwnd_available(l->circ)) { + leg = l; + } + } CONFLUX_FOR_EACH_LEG_END(l); + + /* If the circuit can't send, don't send on any circuit. */ + if (!leg || !circuit_ready_to_send(leg->circ)) { + return NULL; + } + return leg->circ; +} + +/** + * Favor the circuit with the highest send rate. + * + * Only spill over to other circuits if they are still in slow start. + * In steady-state, we only use the max throughput circuit. + */ +static const circuit_t * +conflux_decide_circ_maxrate(const conflux_t *cfx) +{ + uint64_t max_rate = 0; + const conflux_leg_t *leg = NULL; + + /* Find the highest bandwidth leg */ + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, l) { + uint64_t rate; + const congestion_control_t *cc = circuit_ccontrol(l->circ); + + rate = CELL_MAX_NETWORK_SIZE*USEC_PER_SEC * + cc->cwnd / l->circ_rtts_usec; + if (rate > max_rate) { + max_rate = rate; + leg = l; + } + } CONFLUX_FOR_EACH_LEG_END(l); + + /* If the package window is has room, use it */ + if (leg && circuit_ready_to_send(leg->circ)) { + return leg->circ; + } + + leg = NULL; + max_rate = 0; + + /* Find the circuit with the max rate where in_slow_start == 1: */ + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, l) { + uint64_t rate; + /* Ignore circuits with no room in the package window */ + if (!circuit_ready_to_send(l->circ)) { + continue; + } + + const congestion_control_t *cc = circuit_ccontrol(l->circ); + + rate = CELL_MAX_NETWORK_SIZE*USEC_PER_SEC * + cc->cwnd / l->circ_rtts_usec; + + if (rate > max_rate && cc->in_slow_start) { + max_rate = rate; + leg = l; + } + } CONFLUX_FOR_EACH_LEG_END(l); + + /* If no sendable leg was found, don't send on any circuit. */ + if (!leg) { + return NULL; + } + return leg->circ; +} + +/** + * Favor the circuit with the highest send rate that still has space + * in the congestion window, but when it is full, pick the next + * highest. + */ +static const circuit_t * +conflux_decide_circ_highrate(const conflux_t *cfx) +{ + uint64_t max_rate = 0; + uint64_t primary_leg_rtt = 0; + const conflux_leg_t *leg = NULL; + + /* Find the highest bandwidth leg */ + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, l) { + uint64_t rate; + const congestion_control_t *cc = circuit_ccontrol(l->circ); + + rate = CELL_MAX_NETWORK_SIZE*USEC_PER_SEC * + cc->cwnd / l->circ_rtts_usec; + + if (rate > max_rate) { + max_rate = rate; + primary_leg_rtt = l->circ_rtts_usec; + leg = l; + } + } CONFLUX_FOR_EACH_LEG_END(l); + + /* If the package window is has room, use it */ + if (leg && circuit_ready_to_send(leg->circ)) { + return leg->circ; + } + + /* Reset the max rate to find a new max */ + max_rate = 0; + leg = NULL; + + /* For any given leg, it has primary_leg_rtt/2 time before the 'primary' + * leg's acks start arriving. So, the amount of data a 'secondary' + * leg can send while the primary leg transmits these acks is: + * (cwnd_leg/(secondary_rtt/2))*primary_rtt/2 + * = cwnd_leg*primary_rtt/secondary_rtt. + * So any leg with available room below that that is no good. + */ + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, l) { + if (!circuit_ready_to_send(l->circ)) { + continue; + } + const congestion_control_t *cc = circuit_ccontrol(l->circ); + + uint64_t rate = CELL_MAX_NETWORK_SIZE*USEC_PER_SEC * + cc->cwnd / l->circ_rtts_usec; + + /* Pick the leg with the highest rate that still has room */ + if (rate > max_rate && + cwnd_sendable(l->circ, primary_leg_rtt, l->circ_rtts_usec) <= + cwnd_available(l->circ)) { + leg = l; + max_rate = rate; + } + } CONFLUX_FOR_EACH_LEG_END(l); + + /* If no sendable leg was found, don't send on any circuit. */ + if (!leg) { + return NULL; + } + return leg->circ; +} + +/** + * This function is called when we want to send a relay cell on a + * conflux, as well as when we want to compute available space in + * to package from streams. + * + * It determines the circuit that relay command should be sent on, + * and sends a SWITCH cell if necessary. + * + * It returns the circuit we should send on. If no circuits are ready + * to send, it returns NULL. + */ +circuit_t * +conflux_decide_circ_for_send(conflux_t *cfx, + circuit_t *orig_circ, + uint8_t relay_command) +{ + /* If this command should not be multiplexed, send it on the original + * circuit */ + if (!conflux_should_multiplex(relay_command)) { + return orig_circ; + } + + circuit_t *new_circ = conflux_decide_next_circ(cfx); + + /* Because our congestion window only cover relay data command, we can end up + * in a situation where we need to send non data command when all circuits + * are at capacity. For those cases, keep using the *current* leg, + * so these commands arrive in-order. */ + if (!new_circ && relay_command != RELAY_COMMAND_DATA) { + /* Curr leg should be set, because conflux_decide_next_circ() should + * have set it earlier. */ + tor_assert(cfx->curr_leg); + return cfx->curr_leg->circ; + } + + /* + * If we are switching to a new circuit, we need to send a SWITCH command. + * We also need to compute an estimate of how much data we can send on + * the new circuit before we are allowed to switch again, to rate + * limit the frequency of switching. + */ + if (new_circ) { + conflux_leg_t *new_leg = conflux_get_leg(cfx, new_circ); + tor_assert(cfx->curr_leg); + + if (new_circ != cfx->curr_leg->circ) { + cfx->cells_until_switch = + cwnd_sendable(new_circ,cfx->curr_leg->circ_rtts_usec, + new_leg->circ_rtts_usec); + + conflux_validate_stream_lists(cfx); + + cfx->prev_leg = cfx->curr_leg; + cfx->curr_leg = new_leg; + + tor_assert(cfx->prev_leg); + tor_assert(cfx->curr_leg); + + uint64_t relative_seq = cfx->prev_leg->last_seq_sent - + cfx->curr_leg->last_seq_sent; + + tor_assert(cfx->prev_leg->last_seq_sent >= + cfx->curr_leg->last_seq_sent); + conflux_send_switch_command(cfx->curr_leg->circ, relative_seq); + cfx->curr_leg->last_seq_sent = cfx->prev_leg->last_seq_sent; + } + } + + return new_circ; +} + +/** Called after conflux actually sent a cell on a circuit. + * This function updates sequence number counters, and + * switch counters. + */ +void +conflux_note_cell_sent(conflux_t *cfx, circuit_t *circ, uint8_t relay_command) +{ + conflux_leg_t *leg = NULL; + + if (!conflux_should_multiplex(relay_command)) { + return; + } + + leg = conflux_get_leg(cfx, circ); + tor_assert(leg); + + leg->last_seq_sent++; + + if (cfx->cells_until_switch > 0) { + cfx->cells_until_switch--; + } +} + +/** Find the leg with lowest non-zero curr_rtt_usec, and + * pick it for our current leg. */ +static inline void +conflux_pick_first_leg(conflux_t *cfx) +{ + conflux_leg_t *min_leg = NULL; + + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) { + /* We need to skip 0-RTT legs, since this can happen at the exit + * when there is a race between BEGIN and LINKED_ACK, and BEGIN + * wins the race. The good news is that because BEGIN won, + * we don't need to consider those other legs, since they are + * slower. */ + if (leg->circ_rtts_usec > 0) { + if (!min_leg || leg->circ_rtts_usec < min_leg->circ_rtts_usec) { + min_leg = leg; + } + } + } CONFLUX_FOR_EACH_LEG_END(leg); + + if (BUG(!min_leg)) { + // Get the 0th leg; if it does not exist, assert + tor_assert(smartlist_len(cfx->legs) > 0); + min_leg = smartlist_get(cfx->legs, 0); + tor_assert(min_leg); + } + + // TODO-329-TUNING: Does this create an edge condition by getting blocked, + // is it possible that we get full before this point and block? + // Esp if we switch to a new circuit that is not ready to + // send because it has unacked inflight data.... This might cause + // stalls? + // That is the thinking with this -1 here, but maybe it is not needed. + cfx->cells_until_switch = circuit_ccontrol(min_leg->circ)->cwnd - 1; + + cfx->curr_leg = min_leg; +} + +/** + * Returns the circuit that conflux would send on next, if + * conflux_decide_circ_for_send were called. This is used to compute + * available space in the package window. + */ +circuit_t * +conflux_decide_next_circ(conflux_t *cfx) +{ + // TODO-329-TUNING: Temporarily validate legs here. We can remove + // this once tuning is complete. + conflux_validate_legs(cfx); + + /* If we don't have a current leg yet, pick one. + * (This is the only non-const operation in this function). */ + if (!cfx->curr_leg) { + conflux_pick_first_leg(cfx); + } + + /* First, check if we can switch. */ + if (!conflux_can_switch(cfx)) { + tor_assert(cfx->curr_leg); + circuit_t *curr_circ = cfx->curr_leg->circ; + + /* If we can't switch, and the current circuit can't send, + * then return null. */ + if (circuit_ready_to_send(curr_circ)) { + return curr_circ; + } + log_info(LD_CIRC, "Conflux can't switch; no circuit to send on."); + return NULL; + } + + switch (cfx->params.alg) { + case CONFLUX_ALG_MINRTT: // latency (no ooq) + return (circuit_t*)conflux_decide_circ_minrtt(cfx); + case CONFLUX_ALG_LOWRTT: // high throughput (high oooq) + return (circuit_t*)conflux_decide_circ_lowrtt(cfx); + case CONFLUX_ALG_CWNDRTT: // throughput (low oooq) + return (circuit_t*)conflux_decide_circ_cwndrtt(cfx); + case CONFLUX_ALG_MAXRATE: // perf test (likely high ooq) + return (circuit_t*)conflux_decide_circ_maxrate(cfx); + case CONFLUX_ALG_HIGHRATE: // perf test (likely high ooq) + return (circuit_t*)conflux_decide_circ_highrate(cfx); + default: + return NULL; + } +} + +/** + * Called when we have a new RTT estimate for a circuit. + */ +void +conflux_update_rtt(conflux_t *cfx, circuit_t *circ, uint64_t rtt_usec) +{ + conflux_leg_t *leg = conflux_get_leg(cfx, circ); + + if (!leg) { + log_warn(LD_BUG, "Got RTT update for circuit not in conflux"); + return; + } + + // Update RTT + leg->circ_rtts_usec = rtt_usec; + + // TODO-329-ARTI: For UDP latency targeting, arti could decide to launch + // new a test leg to potentially replace this one, if a latency target + // was requested and we now exceed it. Since C-Tor client likely + // will not have UDP support, we aren't doing this here. +} + /** * Comparison function for ooo_q pqueue. * @@ -171,6 +780,106 @@ circuit_ccontrol(const circuit_t *circ) return ccontrol; }
+// TODO-329-TUNING: For LowRTT, we can at most switch every SENDME, +// but for BLEST, we should switch at most every cwnd.. But +// we do not know the other side's CWND here.. We can at best +// asssume it is above the cwnd_min +#define CONFLUX_MIN_LINK_INCREMENT 31 +/** + * Validate and handle RELAY_COMMAND_CONFLUX_SWITCH. + */ +int +conflux_process_switch_command(circuit_t *in_circ, + crypt_path_t *layer_hint, cell_t *cell, + relay_header_t *rh) +{ + tor_assert(in_circ); + tor_assert(cell); + tor_assert(rh); + + conflux_t *cfx = in_circ->conflux; + uint32_t relative_seq; + conflux_leg_t *leg; + + if (!conflux_is_enabled(in_circ)) { + circuit_mark_for_close(in_circ, END_CIRC_REASON_TORPROTOCOL); + return -1; + } + + /* If there is no conflux object negotiated, this is invalid. + * log and close circ */ + if (!cfx) { + log_warn(LD_BUG, "Got a conflux switch command on a circuit without " + "conflux negotiated. Closing circuit."); + + circuit_mark_for_close(in_circ, END_CIRC_REASON_TORPROTOCOL); + return -1; + } + + // TODO-329-TUNING: Temporarily validate that we have all legs. + // After tuning is complete, we can remove this. + conflux_validate_legs(cfx); + + leg = conflux_get_leg(cfx, in_circ); + + /* If we can't find the conflux leg, we got big problems.. + * Close the circuit. */ + if (!leg) { + log_warn(LD_BUG, "Got a conflux switch command on a circuit without " + "conflux leg. Closing circuit."); + circuit_mark_for_close(in_circ, END_CIRC_REASON_INTERNAL); + return -1; + } + + // Check source hop via layer_hint + if (!conflux_validate_source_hop(in_circ, layer_hint)) { + log_warn(LD_BUG, "Got a conflux switch command on a circuit with " + "invalid source hop. Closing circuit."); + circuit_mark_for_close(in_circ, END_CIRC_REASON_TORPROTOCOL); + return -1; + } + + relative_seq = conflux_cell_parse_switch(cell, rh->length); + + /* + * We have to make sure that the switch command is truely + * incrementing the sequence number, or else it becomes + * a side channel that can be spammed for traffic analysis. + */ + // TODO-329-TUNING: This can happen. Disabling for now.. + //if (relative_seq < CONFLUX_MIN_LINK_INCREMENT) { + // log_warn(LD_CIRC, "Got a conflux switch command with a relative " + // "sequence number less than the minimum increment. Closing " + // "circuit."); + // circuit_mark_for_close(in_circ, END_CIRC_REASON_TORPROTOCOL); + // return -1; + //} + + // TODO-329-UDP: When Prop#340 exits and was negotiated, ensure we're + // in a packed cell, with another cell following, otherwise + // this is a spammed side-channel. + // - We definitely should never get switches back-to-back. + // - We should not get switches across all legs with no data + // But before Prop#340, it doesn't make much sense to do this. + // C-Tor is riddled with side-channels like this anyway, unless + // vanguards is in use. And this feature is not supported by + // onion servicees in C-Tor, so we're good there. + + /* Update the absolute sequence number on this leg by the delta. + * Since this cell is not multiplexed, we do not count it towards + * absolute sequence numbers. We only increment the sequence + * numbers for multiplexed cells. Hence there is no +1 here. */ + leg->last_seq_recv += relative_seq; + + /* Mark this data as validated for controlport and vanguards + * dropped cell handling */ + if (CIRCUIT_IS_ORIGIN(in_circ)) { + circuit_read_valid_data(TO_ORIGIN_CIRCUIT(in_circ), rh->length); + } + + return 0; +} + /** * Process an incoming relay cell for conflux. Called from * connection_edge_process_relay_cell(). diff --git a/src/core/or/congestion_control_common.c b/src/core/or/congestion_control_common.c index b11edbad67..920b57cf00 100644 --- a/src/core/or/congestion_control_common.c +++ b/src/core/or/congestion_control_common.c @@ -23,6 +23,7 @@ #include "core/or/congestion_control_nola.h" #include "core/or/congestion_control_westwood.h" #include "core/or/congestion_control_st.h" +#include "core/or/conflux.h" #include "core/or/trace_probes_cc.h" #include "lib/time/compat_time.h" #include "feature/nodelist/networkstatus.h" @@ -1188,7 +1189,7 @@ congestion_control_update_circuit_bdp(congestion_control_t *cc, */ int congestion_control_dispatch_cc_alg(congestion_control_t *cc, - const circuit_t *circ, + circuit_t *circ, const crypt_path_t *layer_hint) { int ret = -END_CIRC_REASON_INTERNAL; @@ -1218,6 +1219,10 @@ congestion_control_dispatch_cc_alg(congestion_control_t *cc, cc->cwnd = cwnd_max; }
+ /* If we have a non-zero RTT measurement, update conflux. */ + if (circ->conflux && cc->ewma_rtt_usec) + conflux_update_rtt(circ->conflux, circ, cc->ewma_rtt_usec); + return ret; }
diff --git a/src/core/or/congestion_control_common.h b/src/core/or/congestion_control_common.h index fa8f67bb8b..cf3e9d4fdb 100644 --- a/src/core/or/congestion_control_common.h +++ b/src/core/or/congestion_control_common.h @@ -46,7 +46,7 @@ congestion_control_t *congestion_control_new( cc_path_t path);
int congestion_control_dispatch_cc_alg(congestion_control_t *cc, - const circuit_t *circ, + circuit_t *circ, const crypt_path_t *layer_hint);
void congestion_control_note_cell_sent(congestion_control_t *cc, diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 26e52b0d95..78a724a47c 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -624,6 +624,23 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *orig_circ, cell_t cell; relay_header_t rh; cell_direction_t cell_direction; + circuit_t *circ = orig_circ; + + /* If conflux is enabled, decide which leg to send on, and use that */ + if (orig_circ->conflux && conflux_should_multiplex(relay_command)) { + circ = conflux_decide_circ_for_send(orig_circ->conflux, orig_circ, + relay_command); + if (BUG(!circ)) { + log_warn(LD_BUG, "No circuit to send on for conflux"); + circ = orig_circ; + } else { + /* Conflux circuits always send multiplexed relay commands to + * to the last hop. (Non-multiplexed commands go on their + * original circuit and hop). */ + cpath_layer = conflux_get_destination_hop(circ); + } + } + /* XXXX NM Split this function into a separate versions per circuit type? */
tor_assert(circ); @@ -721,6 +738,10 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *orig_circ, return -1; }
+ if (circ->conflux) { + conflux_note_cell_sent(circ->conflux, circ, relay_command); + } + /* 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. */ @@ -1639,6 +1660,8 @@ handle_relay_cell_command(cell_t *cell, circuit_t *circ,
/* Now handle all the other commands */ switch (rh->command) { + case RELAY_COMMAND_CONFLUX_SWITCH: + return conflux_process_switch_command(circ, layer_hint, cell, rh); case RELAY_COMMAND_BEGIN: case RELAY_COMMAND_BEGIN_DIR: if (layer_hint &&
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit b999051e449f4293a25b676b4a08b86d2bc4c5ad Author: David Goulet dgoulet@torproject.org AuthorDate: Fri Mar 3 14:28:18 2023 -0500
Prop#329 OOM: Handle freeing conflux queues on OOM
We use the oldest-circ-first method here, since that seems good for conflux: queues could briefly spike, but the bad case is if they are maliciously bloated to stick around for a long time.
The tradeoff here is that it is possible to kill old circuits on a relay quickly, but that has always been the case with this algorithm choice.
Signed-off-by: David Goulet dgoulet@torproject.org --- src/core/or/circuitlist.c | 1 + src/core/or/conflux.c | 39 +++++++++++++++++++++++++++++++++++++++ src/core/or/relay.c | 10 ++++++++++ 3 files changed, 50 insertions(+)
diff --git a/src/core/or/circuitlist.c b/src/core/or/circuitlist.c index cea3a2136f..4ea55968ab 100644 --- a/src/core/or/circuitlist.c +++ b/src/core/or/circuitlist.c @@ -2739,6 +2739,7 @@ circuits_handle_oom(size_t current_allocation) mem_recovered += n * packed_cell_mem_cost(); mem_recovered += half_stream_alloc; mem_recovered += freed; + mem_recovered += conflux_get_circ_bytes_allocation(circ);
if (mem_recovered >= mem_to_recover) goto done_recovering_mem; diff --git a/src/core/or/conflux.c b/src/core/or/conflux.c index 699d1b5c40..e9a66b83e1 100644 --- a/src/core/or/conflux.c +++ b/src/core/or/conflux.c @@ -32,6 +32,10 @@ static inline uint64_t cwnd_sendable(const circuit_t *on_circ, uint64_t in_usec, uint64_t our_usec);
+/* Track the total number of bytes used by all ooo_q so it can be used by the + * OOM handler to assess. */ +static uint64_t total_ooo_q_bytes = 0; + /** * Determine if we should multiplex a specific relay command or not. * @@ -156,6 +160,41 @@ conflux_get_max_seq_recv(const conflux_t *cfx) return max_seq_recv; }
+/** Return the total memory allocation the circuit is using by conflux. If this + * circuit is not a Conflux circuit, 0 is returned. */ +uint64_t +conflux_get_circ_bytes_allocation(const circuit_t *circ) +{ + if (circ->conflux) { + return smartlist_len(circ->conflux->ooo_q) * sizeof(conflux_cell_t); + } + return 0; +} + +/** Return the total memory allocation in bytes by the subsystem. + * + * At the moment, only out of order queues are consiered. */ +uint64_t +conflux_get_total_bytes_allocation(void) +{ + return total_ooo_q_bytes; +} + +/** The OOM handler is asking us to try to free at least bytes_to_remove. */ +size_t +conflux_handle_oom(size_t bytes_to_remove) +{ + (void) bytes_to_remove; + + /* We are not doing anything on the sets, the OOM handler will trigger a + * circuit clean up which will affect conflux sets, by pruning oldest + * circuits. */ + + log_info(LD_CIRC, "OOM handler triggered. OOO queus allocation: %" PRIu64, + total_ooo_q_bytes); + return 0; +} + /** * Returns true if a circuit has package window space to send, and is * not blocked locally. diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 78a724a47c..0f8fd12401 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -2879,6 +2879,8 @@ cell_queues_check_size(void) alloc += geoip_client_cache_total; const size_t dns_cache_total = dns_cache_total_allocation(); alloc += dns_cache_total; + const size_t conflux_total = conflux_get_total_bytes_allocation(); + alloc += conflux_total; if (alloc >= get_options()->MaxMemInQueues_low_threshold) { last_time_under_memory_pressure = approx_time(); if (alloc >= get_options()->MaxMemInQueues) { @@ -2910,6 +2912,14 @@ cell_queues_check_size(void) oom_stats_n_bytes_removed_dns += removed; alloc -= removed; } + /* Like onion service above, try to go down to 10% if we are above 20% */ + if (conflux_total > get_options()->MaxMemInQueues / 5) { + const size_t bytes_to_remove = + conflux_total - (size_t)(get_options()->MaxMemInQueues / 10); + removed = conflux_handle_oom(bytes_to_remove); + oom_stats_n_bytes_removed_cell += removed; + alloc -= removed; + } removed = circuits_handle_oom(alloc); oom_stats_n_bytes_removed_cell += removed; return 1;
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit cf715a56f1e4c6873d2e99bdd96c4b1fb86af63d Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Wed Jan 18 22:48:43 2023 +0000
Prop#329 sendme: Adjust sendme sending and tracking for conflux
Because circuit-level sendmes are sent before relay data cells are processed, we can safely move this to before the conflux decision. In this way, regardless of conflux being negotiated, we still send sendmes as soon as data cells are recieved. This avoids introducing conflux queue delay into RTT measurement, which is important for measuring actual circuit capacity.
The circuit-level tracking must happen inside the call to send a data cell, since that call now chooses a circuit to send on. Turns out, we were already doing this kind of here, but only for the digest. Now we do both things here. --- src/core/or/relay.c | 52 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 22 deletions(-)
diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 0f8fd12401..38fb560e34 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -747,6 +747,15 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *orig_circ, * because the cell digest is set within that function. */ if (relay_command == RELAY_COMMAND_DATA) { sendme_record_cell_digest_on_circ(circ, cpath_layer); + + /* 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."); + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + return -1; + } }
return 0; @@ -1696,19 +1705,6 @@ handle_relay_cell_command(cell_t *cell, circuit_t *circ, case RELAY_COMMAND_DATA: ++stats_n_data_cells_received;
- /* 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."); - connection_edge_end_close(conn, END_STREAM_REASON_TORPROTOCOL); - return -END_CIRC_REASON_TORPROTOCOL; - } - - /* 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 " "stream_id. Dropping."); @@ -2113,6 +2109,26 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } }
+ /* Regardless of conflux or not, we always decide to send a SENDME + * for RELAY_DATA immediately + */ + if (rh.command == RELAY_COMMAND_DATA) { + /* 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."); + connection_edge_end_close(conn, END_STREAM_REASON_TORPROTOCOL); + return -END_CIRC_REASON_TORPROTOCOL; + } + + /* Consider sending a circuit-level SENDME cell. */ + sendme_circuit_consider_sending(circ, layer_hint); + + /* Continue on to process the data cell via conflux or not */ + } + /* Conflux handling: If conflux is disabled, or the relay command is not * multiplexed across circuits, then process it immediately. * @@ -2398,21 +2414,13 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, buf_add(entry_conn->pending_optimistic_data, payload, length); }
+ /* Send a data cell. This handles the circuit package window. */ if (connection_edge_send_command(conn, RELAY_COMMAND_DATA, payload, length) < 0 ) { /* circuit got marked for close, don't continue, don't need to mark conn */ return 0; }
- /* 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; - } - /* Handle the stream-level SENDME package window. */ if (sendme_note_stream_data_packaged(conn, length) < 0) { connection_stop_reading(TO_CONN(conn));
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit a4ee0c29ee52052f82692f0825a50e1a55e01e5c Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Thu Feb 9 01:43:14 2023 +0000
Prop#329: Add purposes for conflux circuits
Because UNLINKED circuits must never be used for streams, but LINKED circuits can be, we want these separate. --- src/core/or/circuitbuild.c | 11 +++++++++-- src/core/or/circuitlist.c | 20 ++++++++++++++++++++ src/core/or/circuitlist.h | 9 ++++++++- src/core/or/circuituse.c | 19 +++++++++++++++++-- src/core/or/connection_edge.c | 10 ++++++++-- src/feature/client/circpathbias.c | 13 ++++++++++++- 6 files changed, 74 insertions(+), 8 deletions(-)
diff --git a/src/core/or/circuitbuild.c b/src/core/or/circuitbuild.c index 257d33f1ab..8089f438a7 100644 --- a/src/core/or/circuitbuild.c +++ b/src/core/or/circuitbuild.c @@ -1444,6 +1444,7 @@ route_len_for_purpose(uint8_t purpose, extend_info_t *exit_ei) switch (purpose) { /* These purposes connect to a router that we chose, so DEFAULT_ROUTE_LEN * is safe: */ + case CIRCUIT_PURPOSE_CONFLUX_UNLINKED: case CIRCUIT_PURPOSE_TESTING: /* router reachability testing */ known_purpose = 1; @@ -1927,6 +1928,7 @@ choose_good_exit_server(origin_circuit_t *circ, * since it should be random. */ tor_assert_nonfatal(is_internal); FALLTHROUGH; + case CIRCUIT_PURPOSE_CONFLUX_UNLINKED: case CIRCUIT_PURPOSE_C_GENERAL: if (is_internal) /* pick it like a middle hop */ return router_choose_random_node(NULL, options->ExcludeNodes, flags); @@ -1974,6 +1976,8 @@ warn_if_last_router_excluded(origin_circuit_t *circ, case CIRCUIT_PURPOSE_S_HSDIR_POST: case CIRCUIT_PURPOSE_C_HSDIR_GET: case CIRCUIT_PURPOSE_C_GENERAL: + case CIRCUIT_PURPOSE_CONFLUX_UNLINKED: + case CIRCUIT_PURPOSE_CONFLUX_LINKED: if (circ->build_state->is_internal) return; description = "requested exit node"; @@ -2109,8 +2113,11 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei, } exit_ei = extend_info_from_node(node, state->onehop_tunnel, /* for_exit_use */ - !state->is_internal && TO_CIRCUIT(circ)->purpose == - CIRCUIT_PURPOSE_C_GENERAL); + !state->is_internal && ( + TO_CIRCUIT(circ)->purpose == + CIRCUIT_PURPOSE_C_GENERAL || + TO_CIRCUIT(circ)->purpose == + CIRCUIT_PURPOSE_CONFLUX_UNLINKED)); if (BUG(exit_ei == NULL)) return -1; } diff --git a/src/core/or/circuitlist.c b/src/core/or/circuitlist.c index 4ea55968ab..01baf7c795 100644 --- a/src/core/or/circuitlist.c +++ b/src/core/or/circuitlist.c @@ -841,6 +841,11 @@ circuit_purpose_to_controller_string(uint8_t purpose) case CIRCUIT_PURPOSE_C_CIRCUIT_PADDING: return "CIRCUIT_PADDING";
+ case CIRCUIT_PURPOSE_CONFLUX_UNLINKED: + return "CONFLUX_UNLINKED"; + case CIRCUIT_PURPOSE_CONFLUX_LINKED: + return "CONFLUX_LINKED"; + default: tor_snprintf(buf, sizeof(buf), "UNKNOWN_%d", (int)purpose); return buf; @@ -870,6 +875,8 @@ circuit_purpose_to_controller_hs_state_string(uint8_t purpose) case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: case CIRCUIT_PURPOSE_HS_VANGUARDS: case CIRCUIT_PURPOSE_C_CIRCUIT_PADDING: + case CIRCUIT_PURPOSE_CONFLUX_UNLINKED: + case CIRCUIT_PURPOSE_CONFLUX_LINKED: return NULL;
case CIRCUIT_PURPOSE_INTRO_POINT: @@ -973,6 +980,12 @@ circuit_purpose_to_string(uint8_t purpose) case CIRCUIT_PURPOSE_C_CIRCUIT_PADDING: return "Circuit kept open for padding";
+ case CIRCUIT_PURPOSE_CONFLUX_UNLINKED: + return "Unlinked conflux circuit"; + + case CIRCUIT_PURPOSE_CONFLUX_LINKED: + return "Linked conflux circuit"; + default: tor_snprintf(buf, sizeof(buf), "UNKNOWN_%d", (int)purpose); return buf; @@ -1841,6 +1854,9 @@ get_circuit_purpose_needed_to_cannibalize(uint8_t purpose) * circuits so that we get the same path construction logic. */ return CIRCUIT_PURPOSE_HS_VANGUARDS; } else { + /* Conflux purposes should never get here */ + tor_assert_nonfatal(purpose != CIRCUIT_PURPOSE_CONFLUX_UNLINKED && + purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED); /* If no vanguards are used just get a general circuit! */ return CIRCUIT_PURPOSE_C_GENERAL; } @@ -1886,6 +1902,10 @@ circuit_find_to_cannibalize(uint8_t purpose_to_produce, extend_info_t *info, tor_assert_nonfatal(purpose_to_search_for == CIRCUIT_PURPOSE_C_GENERAL || purpose_to_search_for == CIRCUIT_PURPOSE_HS_VANGUARDS);
+ tor_assert_nonfatal(purpose_to_search_for != + CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + tor_assert_nonfatal(purpose_to_produce != CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + log_debug(LD_CIRC, "Hunting for a circ to cannibalize: purpose %d, uptime %d, " "capacity %d, internal %d", diff --git a/src/core/or/circuitlist.h b/src/core/or/circuitlist.h index 49ded11f12..ca3c5bd0ee 100644 --- a/src/core/or/circuitlist.h +++ b/src/core/or/circuitlist.h @@ -130,7 +130,14 @@ * actual needed HS purpose. */ #define CIRCUIT_PURPOSE_HS_VANGUARDS 24
-#define CIRCUIT_PURPOSE_MAX_ 24 +/** + * These two purposes are for conflux. The first is for circuits that are + * being built, but not yet linked. The second is for circuits that are + * linked and ready to use for streams. */ +#define CIRCUIT_PURPOSE_CONFLUX_UNLINKED 25 +#define CIRCUIT_PURPOSE_CONFLUX_LINKED 26 + +#define CIRCUIT_PURPOSE_MAX_ 26 /** A catch-all for unrecognized purposes. Currently we don't expect * to make or see any circuits with this purpose. */ #define CIRCUIT_PURPOSE_UNKNOWN 255 diff --git a/src/core/or/circuituse.c b/src/core/or/circuituse.c index 77c5deaafb..9110252976 100644 --- a/src/core/or/circuituse.c +++ b/src/core/or/circuituse.c @@ -138,7 +138,8 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ, purpose == CIRCUIT_PURPOSE_C_HSDIR_GET || purpose == CIRCUIT_PURPOSE_S_HSDIR_POST || purpose == CIRCUIT_PURPOSE_HS_VANGUARDS || - purpose == CIRCUIT_PURPOSE_C_REND_JOINED) { + purpose == CIRCUIT_PURPOSE_C_REND_JOINED || + purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED) { if (circ->timestamp_dirty && circ->timestamp_dirty+get_options()->MaxCircuitDirtiness <= now) return 0; @@ -162,6 +163,8 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ, return 0;
if (purpose == CIRCUIT_PURPOSE_C_GENERAL || + purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED || + purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED || purpose == CIRCUIT_PURPOSE_S_HSDIR_POST || purpose == CIRCUIT_PURPOSE_C_HSDIR_GET) { tor_addr_t addr; @@ -1003,7 +1006,8 @@ circuit_stream_is_being_handled(entry_connection_t *conn, SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (CIRCUIT_IS_ORIGIN(circ) && !circ->marked_for_close && - circ->purpose == CIRCUIT_PURPOSE_C_GENERAL && + (circ->purpose == CIRCUIT_PURPOSE_C_GENERAL || + circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED) && (!circ->timestamp_dirty || circ->timestamp_dirty + get_options()->MaxCircuitDirtiness > now)) { origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); @@ -1450,6 +1454,8 @@ circuit_expire_old_circuits_clientside(void) if (timercmp(&circ->timestamp_began, &cutoff, OP_LT)) { if (circ->purpose == CIRCUIT_PURPOSE_C_GENERAL || circ->purpose == CIRCUIT_PURPOSE_C_HSDIR_GET || + circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED || + circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED || circ->purpose == CIRCUIT_PURPOSE_S_HSDIR_POST || circ->purpose == CIRCUIT_PURPOSE_HS_VANGUARDS || circ->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT || @@ -1655,6 +1661,8 @@ circuit_has_opened(origin_circuit_t *circ) hs_client_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_C_GENERAL: + circuit_try_attaching_streams(circ); + break; case CIRCUIT_PURPOSE_C_HSDIR_GET: case CIRCUIT_PURPOSE_S_HSDIR_POST: /* Tell any AP connections that have been waiting for a new @@ -2030,6 +2038,11 @@ circuit_should_cannibalize_to_build(uint8_t purpose_to_build, return 0; }
+ /* Do not cannibalize for conflux circuits */ + if (purpose_to_build == CIRCUIT_PURPOSE_CONFLUX_UNLINKED) { + return 0; + } + return 1; }
@@ -2607,6 +2620,8 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, exitnode = node_get_by_id(cpath->extend_info->identity_digest);
/* See if we can use optimistic data on this circuit */ + // TODO-329-PURPOSE: Can conflux use optimistic data? Does + // anything use optimistic data? Does anything use this? if (circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL || circ->base_.purpose == CIRCUIT_PURPOSE_C_HSDIR_GET || circ->base_.purpose == CIRCUIT_PURPOSE_S_HSDIR_POST || diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c index 7dd0935b47..b0ccedc27f 100644 --- a/src/core/or/connection_edge.c +++ b/src/core/or/connection_edge.c @@ -1236,6 +1236,7 @@ connection_ap_expire_beginning(void) }
if (circ->purpose != CIRCUIT_PURPOSE_C_GENERAL && + circ->purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED && circ->purpose != CIRCUIT_PURPOSE_CONTROLLER && circ->purpose != CIRCUIT_PURPOSE_C_HSDIR_GET && circ->purpose != CIRCUIT_PURPOSE_S_HSDIR_POST && @@ -3112,6 +3113,8 @@ connection_ap_supports_optimistic_data(const entry_connection_t *conn) const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); /* We can only send optimistic data if we're connected to an open general circuit. */ + // TODO-329-PURPOSE: Can conflux circuits use optimistic data? + // Does anything use optimistic data? if (edge_conn->on_circuit == NULL || edge_conn->on_circuit->state != CIRCUIT_STATE_OPEN || (edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_C_GENERAL && @@ -3138,7 +3141,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn) return 0;
/* No flags for hidden services. */ - if (edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_C_GENERAL) + if (edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_C_GENERAL && + edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED) return 0;
/* If only IPv4 is supported, no flags */ @@ -3222,6 +3226,7 @@ connection_ap_handshake_send_begin,(entry_connection_t *ap_conn))
tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:%d", (circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL || + circ->base_.purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED || circ->base_.purpose == CIRCUIT_PURPOSE_CONTROLLER) ? ap_conn->socks_request->address : "", ap_conn->socks_request->port); @@ -3323,7 +3328,8 @@ connection_ap_handshake_send_resolve(entry_connection_t *ap_conn) tor_assert(base_conn->type == CONN_TYPE_AP); tor_assert(base_conn->state == AP_CONN_STATE_CIRCUIT_WAIT); tor_assert(ap_conn->socks_request); - tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL); + tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL || + circ->base_.purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED);
command = ap_conn->socks_request->command; tor_assert(SOCKS_COMMAND_IS_RESOLVE(command)); diff --git a/src/feature/client/circpathbias.c b/src/feature/client/circpathbias.c index ff9e05a645..144a53c972 100644 --- a/src/feature/client/circpathbias.c +++ b/src/feature/client/circpathbias.c @@ -334,12 +334,23 @@ pathbias_should_count(origin_circuit_t *circ) * endpoint could be chosen maliciously. * Similarly, we can't count client-side intro attempts, * because clients can be manipulated into connecting to - * malicious intro points. */ + * malicious intro points. + * + * Finally, avoid counting conflux circuits for now, because + * a malicious exit could cause us to reconnect and blame + * our guard... + * + * TODO-329-PURPOSE: This is not quite right, we could + * instead avoid sending usable probes on conflux circs, + * and count only linked circs as failures, but it is + * not 100% clear that would result in accurate counts. */ if (get_options()->UseEntryGuards == 0 || circ->base_.purpose == CIRCUIT_PURPOSE_TESTING || circ->base_.purpose == CIRCUIT_PURPOSE_CONTROLLER || circ->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED || + circ->base_.purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED || + circ->base_.purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED || (circ->base_.purpose >= CIRCUIT_PURPOSE_C_INTRODUCING && circ->base_.purpose <= CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) {
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 21c861bfa3188444798a35e21f26579dd910a452 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Fri Jan 20 19:14:33 2023 +0000
Refactor stream blocking due to channel cell queues
Streams can get blocked on a circuit in two ways: 1. When the circuit package window is full 2. When the channel's cell queue is too high
Conflux needs to decouple stream blocking from both of these conditions, because streams can continue on another circuit, even if the primary circuit is blocked for either of these cases.
However, both conflux and congestion control need to know if the channel's cell queue hit the highwatermark and is still draining, because this condition is used by those components, independent of stream state.
Therefore, this commit renames the 'streams_blocked_on_chan' variable to signify that it refers to the cell queue state, and also refactors the actual stream blocking bits out, so they can be handled separately if conflux is present. --- src/core/mainloop/mainloop.c | 2 +- src/core/mainloop/mainloop.h | 2 +- src/core/or/circuit_st.h | 8 ++-- src/core/or/circuituse.c | 3 +- src/core/or/congestion_control_common.c | 4 +- src/core/or/edge_connection_st.h | 3 -- src/core/or/relay.c | 85 +++++++++++++++++++-------------- src/test/fakecircs.c | 4 +- 8 files changed, 60 insertions(+), 51 deletions(-)
diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index a1ea32220a..e1c9786b2e 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -497,7 +497,7 @@ connection_watch_events(connection_t *conn, watchable_events_t events)
/** Return true iff <b>conn</b> is listening for read events. */ int -connection_is_reading(connection_t *conn) +connection_is_reading(const connection_t *conn) { tor_assert(conn);
diff --git a/src/core/mainloop/mainloop.h b/src/core/mainloop/mainloop.h index 98d0b3a058..64782c1318 100644 --- a/src/core/mainloop/mainloop.h +++ b/src/core/mainloop/mainloop.h @@ -38,7 +38,7 @@ typedef enum watchable_events { WRITE_EVENT=0x04 /**< We want to know when a connection is writable */ } watchable_events_t; void connection_watch_events(connection_t *conn, watchable_events_t events); -int connection_is_reading(connection_t *conn); +int connection_is_reading(const connection_t *conn); MOCK_DECL(void,connection_stop_reading,(connection_t *conn)); MOCK_DECL(void,connection_start_reading,(connection_t *conn));
diff --git a/src/core/or/circuit_st.h b/src/core/or/circuit_st.h index 7f39c9337e..1afb4d4426 100644 --- a/src/core/or/circuit_st.h +++ b/src/core/or/circuit_st.h @@ -88,11 +88,11 @@ struct circuit_t { extend_info_t *n_hop;
/** True iff we are waiting for n_chan_cells to become less full before - * allowing p_streams to add any more cells. (Origin circuit only.) */ - unsigned int streams_blocked_on_n_chan : 1; + * allowing any more cells on this circuit. (Origin circuit only.) */ + unsigned int circuit_blocked_on_n_chan : 1; /** True iff we are waiting for p_chan_cells to become less full before - * allowing n_streams to add any more cells. (OR circuit only.) */ - unsigned int streams_blocked_on_p_chan : 1; + * allowing any more cells on this circuit. (OR circuit only.) */ + unsigned int circuit_blocked_on_p_chan : 1;
/** True iff we have queued a delete backwards on this circuit, but not put * it on the output buffer. */ diff --git a/src/core/or/circuituse.c b/src/core/or/circuituse.c index 9110252976..25401aea55 100644 --- a/src/core/or/circuituse.c +++ b/src/core/or/circuituse.c @@ -63,6 +63,7 @@ #include "lib/math/fp.h" #include "lib/time/tvdiff.h" #include "lib/trace/events.h" +#include "src/core/mainloop/mainloop.h"
#include "core/or/cpath_build_state_st.h" #include "feature/dircommon/dir_connection_st.h" @@ -938,7 +939,7 @@ circuit_log_ancient_one_hop_circuits(int age) c->marked_for_close, c->hold_open_until_flushed ? "" : "not ", conn->edge_has_sent_end ? "" : "not ", - conn->edge_blocked_on_circ ? "Blocked" : "Not blocked"); + connection_is_reading(c) ? "Not blocked" : "Blocked"); if (! c->linked_conn) continue;
diff --git a/src/core/or/congestion_control_common.c b/src/core/or/congestion_control_common.c index 920b57cf00..c7c950d0c8 100644 --- a/src/core/or/congestion_control_common.c +++ b/src/core/or/congestion_control_common.c @@ -954,11 +954,11 @@ congestion_control_update_circuit_bdp(congestion_control_t *cc, if (CIRCUIT_IS_ORIGIN(circ)) { /* origin circs use n_chan */ chan_q = circ->n_chan_cells.n; - blocked_on_chan = circ->streams_blocked_on_n_chan; + blocked_on_chan = circ->circuit_blocked_on_n_chan; } else { /* Both onion services and exits use or_circuit and p_chan */ chan_q = CONST_TO_OR_CIRCUIT(circ)->p_chan_cells.n; - blocked_on_chan = circ->streams_blocked_on_p_chan; + blocked_on_chan = circ->circuit_blocked_on_p_chan; }
/* If we have no EWMA RTT, it is because monotime has been stalled diff --git a/src/core/or/edge_connection_st.h b/src/core/or/edge_connection_st.h index 942991f139..22f9040d15 100644 --- a/src/core/or/edge_connection_st.h +++ b/src/core/or/edge_connection_st.h @@ -66,9 +66,6 @@ struct edge_connection_t { * connections. Set once we've set the stream end, * and check in connection_about_to_close_connection(). */ - /** True iff we've blocked reading until the circuit has fewer queued - * cells. */ - unsigned int edge_blocked_on_circ:1;
/** Unique ID for directory requests; this used to be in connection_t, but * that's going away and being used on channels instead. We still tag diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 38fb560e34..827f0c3e46 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -122,6 +122,8 @@ static int connection_edge_process_ordered_relay_cell(cell_t *cell, edge_connection_t *conn, crypt_path_t *layer_hint, relay_header_t *rh); +static void set_block_state_for_streams(edge_connection_t *stream_list, + int block, streamid_t stream_id);
/** Stats: how many relay cells have originated at this hop, or have * been relayed onward (not recognized at this hop)? @@ -3005,41 +3007,46 @@ channel_unlink_all_circuits(channel_t *chan, smartlist_t *circuits_out) chan->num_p_circuits = 0; }
-/** Block (if <b>block</b> is true) or unblock (if <b>block</b> is false) +/** + * Called when a circuit becomes blocked or unblocked due to the channel + * cell queue. + * + * Block (if <b>block</b> is true) or unblock (if <b>block</b> is false) * every edge connection that is using <b>circ</b> to write to <b>chan</b>, * and start or stop reading as appropriate. - * - * If <b>stream_id</b> is nonzero, block only the edge connection whose - * stream_id matches it. - * - * Returns the number of streams whose status we changed. */ -static int -set_streams_blocked_on_circ(circuit_t *circ, channel_t *chan, - int block, streamid_t stream_id) +static void +set_circuit_blocked_on_chan(circuit_t *circ, channel_t *chan, int block) { edge_connection_t *edge = NULL; - int n = 0; if (circ->n_chan == chan) { - circ->streams_blocked_on_n_chan = block; + circ->circuit_blocked_on_n_chan = block; if (CIRCUIT_IS_ORIGIN(circ)) edge = TO_ORIGIN_CIRCUIT(circ)->p_streams; } else { - circ->streams_blocked_on_p_chan = block; + circ->circuit_blocked_on_p_chan = block; tor_assert(!CIRCUIT_IS_ORIGIN(circ)); edge = TO_OR_CIRCUIT(circ)->n_streams; }
- for (; edge; edge = edge->next_stream) { + set_block_state_for_streams(edge, block, 0); +} + +/** + * Helper function to block or unblock streams in a stream list. + * + * If <b>stream_id</id> is 0, apply the <b>block</b> state to all streams + * in the stream list. If it is non-zero, only apply to that specific stream. + */ +static void +set_block_state_for_streams(edge_connection_t *stream_list, int block, + streamid_t stream_id) +{ + for (edge_connection_t *edge = stream_list; edge; edge = edge->next_stream) { connection_t *conn = TO_CONN(edge); if (stream_id && edge->stream_id != stream_id) continue;
- if (edge->edge_blocked_on_circ != block) { - ++n; - edge->edge_blocked_on_circ = block; - } - if (!conn->read_event) { /* This connection is a placeholder for something; probably a DNS * request. It can't actually stop or start reading.*/ @@ -3055,8 +3062,6 @@ set_streams_blocked_on_circ(circuit_t *circ, channel_t *chan, connection_start_reading(conn); } } - - return n; }
/** Extract the command from a packed cell. */ @@ -3094,7 +3099,7 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max)) destroy_cell_queue_t *destroy_queue=NULL; circuit_t *circ; or_circuit_t *or_circ; - int streams_blocked; + int circ_blocked; packed_cell_t *cell;
/* Get the cmux */ @@ -3134,12 +3139,12 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
if (circ->n_chan == chan) { queue = &circ->n_chan_cells; - streams_blocked = circ->streams_blocked_on_n_chan; + circ_blocked = circ->circuit_blocked_on_n_chan; } else { or_circ = TO_OR_CIRCUIT(circ); tor_assert(or_circ->p_chan == chan); queue = &TO_OR_CIRCUIT(circ)->p_chan_cells; - streams_blocked = circ->streams_blocked_on_p_chan; + circ_blocked = circ->circuit_blocked_on_p_chan; }
/* Circuitmux told us this was active, so it should have cells. @@ -3240,8 +3245,8 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
/* Is the cell queue low enough to unblock all the streams that are waiting * to write to this circuit? */ - if (streams_blocked && queue->n <= cell_queue_lowwatermark()) - set_streams_blocked_on_circ(circ, chan, 0, 0); /* unblock streams */ + if (circ_blocked && queue->n <= cell_queue_lowwatermark()) + set_circuit_blocked_on_chan(circ, chan, 0); /* unblock streams */
/* If n_flushed < max still, loop around and pick another circuit */ } @@ -3346,9 +3351,10 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, streamid_t fromstream) { or_circuit_t *orcirc = NULL; + edge_connection_t *stream_list = NULL; cell_queue_t *queue; int32_t max_queue_size; - int streams_blocked; + int circ_blocked; int exitward; if (circ->marked_for_close) return; @@ -3356,13 +3362,16 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, exitward = (direction == CELL_DIRECTION_OUT); if (exitward) { queue = &circ->n_chan_cells; - streams_blocked = circ->streams_blocked_on_n_chan; + circ_blocked = circ->circuit_blocked_on_n_chan; max_queue_size = max_circuit_cell_queue_size_out; + if (CIRCUIT_IS_ORIGIN(circ)) + stream_list = TO_ORIGIN_CIRCUIT(circ)->p_streams; } else { orcirc = TO_OR_CIRCUIT(circ); queue = &orcirc->p_chan_cells; - streams_blocked = circ->streams_blocked_on_p_chan; + circ_blocked = circ->circuit_blocked_on_p_chan; max_queue_size = max_circuit_cell_queue_size; + stream_list = TO_OR_CIRCUIT(circ)->n_streams; }
if (PREDICT_UNLIKELY(queue->n >= max_queue_size)) { @@ -3395,14 +3404,16 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, return; }
- /* If we have too many cells on the circuit, we should stop reading from - * the edge streams for a while. */ - if (!streams_blocked && queue->n >= cell_queue_highwatermark()) - set_streams_blocked_on_circ(circ, chan, 1, 0); /* block streams */ + /* If we have too many cells on the circuit, note that it should + * be blocked from new cells. */ + if (!circ_blocked && queue->n >= cell_queue_highwatermark()) + set_circuit_blocked_on_chan(circ, chan, 1);
- if (streams_blocked && fromstream) { - /* This edge connection is apparently not blocked; block it. */ - set_streams_blocked_on_circ(circ, chan, 1, fromstream); + if (circ_blocked && fromstream) { + /* This edge connection is apparently not blocked; this can happen for + * new streams on a blocked circuit, for their CONNECTED response. + * block it now. */ + set_block_state_for_streams(stream_list, 1, fromstream); }
update_circuit_on_cmux(circ, direction); @@ -3508,8 +3519,8 @@ static int circuit_queue_streams_are_blocked(circuit_t *circ) { if (CIRCUIT_IS_ORIGIN(circ)) { - return circ->streams_blocked_on_n_chan; + return circ->circuit_blocked_on_n_chan; } else { - return circ->streams_blocked_on_p_chan; + return circ->circuit_blocked_on_p_chan; } } diff --git a/src/test/fakecircs.c b/src/test/fakecircs.c index cca3b43483..caeacd84ef 100644 --- a/src/test/fakecircs.c +++ b/src/test/fakecircs.c @@ -41,8 +41,8 @@ new_fake_orcirc(channel_t *nchan, channel_t *pchan) cell_queue_init(&(circ->n_chan_cells));
circ->n_hop = NULL; - circ->streams_blocked_on_n_chan = 0; - circ->streams_blocked_on_p_chan = 0; + circ->circuit_blocked_on_n_chan = 0; + circ->circuit_blocked_on_p_chan = 0; circ->n_delete_pending = 0; circ->p_delete_pending = 0; circ->received_destroy = 0;
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 2f865b4bba1bedc96f17b696916d74c392c83e1b Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Sun Apr 2 21:06:20 2023 +0000
Prop#329 streams: Handle stream usage with conflux
This adds utility functions to help stream block decisions, as well as cpath layer_hint checks for stream cell acceptance, and syncing stream lists for conflux circuits.
These functions are then called throughout the codebase to properly manage conflux streams. --- src/core/or/circuitlist.c | 1 + src/core/or/circuitpadding.c | 7 +- src/core/or/circuituse.c | 21 +- src/core/or/conflux_util.c | 393 ++++++++++++++++++++++++++++++++ src/core/or/conflux_util.h | 59 +++++ src/core/or/congestion_control_common.c | 3 +- src/core/or/congestion_control_flow.c | 47 +--- src/core/or/congestion_control_flow.h | 2 - src/core/or/connection_edge.c | 6 + src/core/or/edge_connection_st.h | 10 +- src/core/or/or_circuit_st.h | 12 +- src/core/or/origin_circuit_st.h | 11 +- src/core/or/relay.c | 127 ++++++----- src/feature/relay/dns.c | 4 + 14 files changed, 596 insertions(+), 107 deletions(-)
diff --git a/src/core/or/circuitlist.c b/src/core/or/circuitlist.c index 01baf7c795..8c5beebbf3 100644 --- a/src/core/or/circuitlist.c +++ b/src/core/or/circuitlist.c @@ -2368,6 +2368,7 @@ circuit_about_to_free(circuit_t *circ) if (! CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); edge_connection_t *conn; + for (conn=or_circ->n_streams; conn; conn=conn->next_stream) connection_edge_destroy(or_circ->p_circ_id, conn); or_circ->n_streams = NULL; diff --git a/src/core/or/circuitpadding.c b/src/core/or/circuitpadding.c index 99dc5f9d83..590a2d60c9 100644 --- a/src/core/or/circuitpadding.c +++ b/src/core/or/circuitpadding.c @@ -78,6 +78,8 @@ #include "core/crypto/relay_crypto.h" #include "feature/nodelist/nodelist.h"
+#include "src/core/or/conflux_util.h" + #include "app/config/config.h"
static inline circpad_circuit_state_t circpad_circuit_state( @@ -251,8 +253,11 @@ circpad_marked_circuit_for_padding(circuit_t *circ, int reason) * has shut down, but using the MaxCircuitDirtiness timer instead of * the idle circuit timer (again, we want this because we're not * supposed to look idle to Guard nodes that can see our lifespan). */ - if (!circ->timestamp_dirty) + if (!circ->timestamp_dirty) { circ->timestamp_dirty = approx_time(); + if (circ->conflux && CIRCUIT_IS_ORIGIN(circ)) + conflux_sync_circ_fields(circ->conflux, TO_ORIGIN_CIRCUIT(circ)); + }
/* Take ownership of the circuit */ circuit_change_purpose(circ, CIRCUIT_PURPOSE_C_CIRCUIT_PADDING); diff --git a/src/core/or/circuituse.c b/src/core/or/circuituse.c index 25401aea55..f5d5cb4397 100644 --- a/src/core/or/circuituse.c +++ b/src/core/or/circuituse.c @@ -64,6 +64,7 @@ #include "lib/time/tvdiff.h" #include "lib/trace/events.h" #include "src/core/mainloop/mainloop.h" +#include "core/or/conflux.h"
#include "core/or/cpath_build_state_st.h" #include "feature/dircommon/dir_connection_st.h" @@ -700,7 +701,6 @@ circuit_expire_building(void) } else { /* circuit not open, consider recording failure as timeout */ int first_hop_succeeded = TO_ORIGIN_CIRCUIT(victim)->cpath && TO_ORIGIN_CIRCUIT(victim)->cpath->state == CPATH_STATE_OPEN; - if (TO_ORIGIN_CIRCUIT(victim)->p_streams != NULL) { log_warn(LD_BUG, "Circuit %d (purpose %d, %s) has timed out, " "yet has attached streams!", @@ -1351,6 +1351,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) int removed = 0; if (conn == origin_circ->p_streams) { origin_circ->p_streams = conn->next_stream; + conflux_update_p_streams(origin_circ, conn->next_stream); removed = 1; } else { for (prevconn = origin_circ->p_streams; @@ -1383,10 +1384,12 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); if (conn == or_circ->n_streams) { or_circ->n_streams = conn->next_stream; + conflux_update_n_streams(or_circ, conn->next_stream); return; } if (conn == or_circ->resolving_streams) { or_circ->resolving_streams = conn->next_stream; + conflux_update_resolving_streams(or_circ, conn->next_stream); return; }
@@ -2556,7 +2559,13 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, }
/** Return true iff <b>crypt_path</b> is one of the crypt_paths for - * <b>circ</b>. */ + * <b>circ</b>. + * + * WARNING: This function only validates that the cpath is on the *current* + * circuit, for internal consistency checking. For codepaths involving streams, + * or cpaths or layer_hints that could be from a different circuit due to + * conflux, use edge_uses_cpath() or conflux_validate_source_hop() instead. + */ static int cpath_is_on_circuit(origin_circuit_t *circ, crypt_path_t *crypt_path) { @@ -2594,6 +2603,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, ENTRY_TO_EDGE_CONN(apconn)->on_circuit = TO_CIRCUIT(circ); /* assert_connection_ok(conn, time(NULL)); */ circ->p_streams = ENTRY_TO_EDGE_CONN(apconn); + conflux_update_p_streams(circ, ENTRY_TO_EDGE_CONN(apconn));
if (connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(apconn))) { /* We are attaching a stream to a rendezvous circuit. That means @@ -2733,6 +2743,9 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn, /* When stream isolation is in use and controlled by an application * we are willing to keep using the stream. */ circ->base_.timestamp_dirty = approx_time(); + if (TO_CIRCUIT(circ)->conflux) { + conflux_sync_circ_fields(TO_CIRCUIT(circ)->conflux, circ); + } }
pathbias_count_use_attempt(circ); @@ -3103,6 +3116,10 @@ mark_circuit_unusable_for_new_conns(origin_circuit_t *circ) circ->base_.timestamp_dirty -= options->MaxCircuitDirtiness;
circ->unusable_for_new_conns = 1; + + if (TO_CIRCUIT(circ)->conflux) { + conflux_sync_circ_fields(TO_CIRCUIT(circ)->conflux, circ); + } }
/** 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); +} diff --git a/src/core/or/conflux_util.h b/src/core/or/conflux_util.h new file mode 100644 index 0000000000..c556ae1848 --- /dev/null +++ b/src/core/or/conflux_util.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_util.h + * \brief Header file for conflux_util.c. + **/ + +#ifndef TOR_CONFLUX_UTIL_H +#define TOR_CONFLUX_UTIL_H + +/* Forward decls */ +typedef struct edge_connection_t edge_connection_t; +typedef struct crypt_path_t crypt_path_t; +typedef struct origin_circuit_t origin_circuit_t; +typedef struct conflux_t conflux_t; + +/* True iff the given circuit_t circ is conflux related. */ +static inline bool +CIRCUIT_IS_CONFLUX(const circuit_t *circ) +{ + if (circ->conflux_pending_nonce) { + if (CIRCUIT_IS_ORIGIN(circ)) + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + return true; + } else if (circ->conflux) { + if (CIRCUIT_IS_ORIGIN(circ)) + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED); + return true; + } else { + tor_assert_nonfatal(circ->purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED); + tor_assert_nonfatal(circ->purpose != CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + return false; + } +} + +int circuit_get_package_window(circuit_t *circ, + const crypt_path_t *cpath); +bool conflux_can_send(conflux_t *cfx); + +bool edge_uses_cpath(const edge_connection_t *conn, + const crypt_path_t *cpath); +crypt_path_t *conflux_get_destination_hop(circuit_t *circ); +bool conflux_validate_source_hop(circuit_t *in_circ, + crypt_path_t *layer_hint); +uint64_t edge_get_max_rtt(const edge_connection_t *stream); +bool relay_crypt_from_last_hop(const origin_circuit_t *circ, + const crypt_path_t *layer_hint); + +void conflux_update_p_streams(origin_circuit_t *, edge_connection_t *); +void conflux_update_half_streams(origin_circuit_t *, smartlist_t *); +void conflux_update_n_streams(or_circuit_t *, edge_connection_t *); +void conflux_update_resolving_streams(or_circuit_t *, edge_connection_t *); +void conflux_sync_circ_fields(conflux_t *cfx, origin_circuit_t *ref_circ); +void conflux_validate_stream_lists(const conflux_t *cfx); +void conflux_validate_legs(const conflux_t *cfx); + +#endif /* TOR_CONFLUX_UTIL_H */ + diff --git a/src/core/or/congestion_control_common.c b/src/core/or/congestion_control_common.c index c7c950d0c8..1e0f504df1 100644 --- a/src/core/or/congestion_control_common.c +++ b/src/core/or/congestion_control_common.c @@ -24,6 +24,7 @@ #include "core/or/congestion_control_westwood.h" #include "core/or/congestion_control_st.h" #include "core/or/conflux.h" +#include "core/or/conflux_util.h" #include "core/or/trace_probes_cc.h" #include "lib/time/compat_time.h" #include "feature/nodelist/networkstatus.h" @@ -703,7 +704,7 @@ circuit_has_active_streams(const circuit_t *circ, if (conn->base_.marked_for_close) continue;
- if (!layer_hint || conn->cpath_layer == layer_hint) { + if (edge_uses_cpath(conn, layer_hint)) { if (connection_get_inbuf_len(TO_CONN(conn)) > 0) { log_info(LD_CIRC, "CC: More in edge inbuf..."); return 1; diff --git a/src/core/or/congestion_control_flow.c b/src/core/or/congestion_control_flow.c index 90b1927ef9..fa9455a8a1 100644 --- a/src/core/or/congestion_control_flow.c +++ b/src/core/or/congestion_control_flow.c @@ -28,6 +28,7 @@ #include "core/or/connection_st.h" #include "core/or/cell_st.h" #include "app/config/config.h" +#include "core/or/conflux_util.h"
/** Cache consensus parameters */ static uint32_t xoff_client; @@ -60,27 +61,6 @@ double cc_stats_flow_xon_outbuf_ma = 0; #define ONE_MEGABYTE (UINT64_C(1) << 20) #define TOTAL_XMIT_SCALE_AT (10 * ONE_MEGABYTE)
-/** - * Return the congestion control object of the given edge connection. - * - * Returns NULL if the edge connection doesn't have a cpath_layer or not - * attached to a circuit. But also if the cpath_layer or circuit doesn't have a - * congestion control object. - */ -static inline const congestion_control_t * -edge_get_ccontrol(const edge_connection_t *edge) -{ - congestion_control_t *ccontrol = NULL; - - if (edge->on_circuit && edge->on_circuit->ccontrol) { - ccontrol = edge->on_circuit->ccontrol; - } else if (edge->cpath_layer && edge->cpath_layer->ccontrol) { - ccontrol = edge->cpath_layer->ccontrol; - } - - return ccontrol; -} - /** * Update global congestion control related consensus parameter values, every * consensus update. @@ -265,13 +245,13 @@ circuit_process_stream_xoff(edge_connection_t *conn, }
/* Make sure this XOFF came from the right hop */ - if (layer_hint && layer_hint != conn->cpath_layer) { + if (!edge_uses_cpath(conn, layer_hint)) { log_fn(LOG_PROTOCOL_WARN, LD_EDGE, "Got XOFF from wrong hop."); return false; }
- if (edge_get_ccontrol(conn) == NULL) { + if (!edge_uses_flow_control(conn)) { log_fn(LOG_PROTOCOL_WARN, LD_EDGE, "Got XOFF for non-congestion control circuit"); return false; @@ -359,13 +339,13 @@ circuit_process_stream_xon(edge_connection_t *conn, }
/* Make sure this XON came from the right hop */ - if (layer_hint && layer_hint != conn->cpath_layer) { + if (!edge_uses_cpath(conn, layer_hint)) { log_fn(LOG_PROTOCOL_WARN, LD_EDGE, "Got XON from wrong hop."); return false; }
- if (edge_get_ccontrol(conn) == NULL) { + if (!edge_uses_flow_control(conn)) { log_fn(LOG_PROTOCOL_WARN, LD_EDGE, "Got XON for non-congestion control circuit"); return false; @@ -464,7 +444,7 @@ flow_control_decide_xoff(edge_connection_t *stream) size_t total_buffered = connection_get_outbuf_len(TO_CONN(stream)); uint32_t buffer_limit_xoff = 0;
- if (BUG(edge_get_ccontrol(stream) == NULL)) { + if (BUG(!edge_uses_flow_control(stream))) { log_err(LD_BUG, "Flow control called for non-congestion control circuit"); return -1; } @@ -717,21 +697,6 @@ edge_uses_flow_control(const edge_connection_t *stream) return ret; }
-/** - * Returns the max RTT for the circuit that carries this stream, - * as observed by congestion control. - */ -uint64_t -edge_get_max_rtt(const edge_connection_t *stream) -{ - 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; -} - /** Returns true if a connection is an edge conn that uses flow control */ bool conn_uses_flow_control(connection_t *conn) diff --git a/src/core/or/congestion_control_flow.h b/src/core/or/congestion_control_flow.h index 5c735cce23..05e25c44d0 100644 --- a/src/core/or/congestion_control_flow.h +++ b/src/core/or/congestion_control_flow.h @@ -31,8 +31,6 @@ bool edge_uses_flow_control(const edge_connection_t *stream);
bool conn_uses_flow_control(connection_t *stream);
-uint64_t edge_get_max_rtt(const edge_connection_t *); - /** Metricsport externs */ extern uint64_t cc_stats_flow_num_xoff_sent; extern uint64_t cc_stats_flow_num_xon_sent; diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c index b0ccedc27f..ee6ab8596c 100644 --- a/src/core/or/connection_edge.c +++ b/src/core/or/connection_edge.c @@ -70,6 +70,7 @@ #include "core/or/circuitpadding.h" #include "core/or/connection_edge.h" #include "core/or/congestion_control_flow.h" +#include "core/or/conflux_util.h" #include "core/or/circuitstats.h" #include "core/or/connection_or.h" #include "core/or/extendinfo.h" @@ -628,6 +629,7 @@ connection_half_edge_add(const edge_connection_t *conn,
if (!circ->half_streams) { circ->half_streams = smartlist_new(); + conflux_update_half_streams(circ, circ->half_streams); }
half_conn->stream_id = conn->stream_id; @@ -3102,6 +3104,10 @@ get_unique_stream_id_by_circ(origin_circuit_t *circ) test_stream_id)) goto again;
+ if (TO_CIRCUIT(circ)->conflux) { + conflux_sync_circ_fields(TO_CIRCUIT(circ)->conflux, circ); + } + return test_stream_id; }
diff --git a/src/core/or/edge_connection_st.h b/src/core/or/edge_connection_st.h index 22f9040d15..e8a3039b33 100644 --- a/src/core/or/edge_connection_st.h +++ b/src/core/or/edge_connection_st.h @@ -28,11 +28,15 @@ struct edge_connection_t { * circuit? */ int deliver_window; /**< How many more relay cells can end at me? */
- struct circuit_t *on_circuit; /**< The circuit (if any) that this edge - * connection is using. */ + /** The circuit (if any) that this edge connection is using. + * Note that edges that use conflux should use the helpers + * in conflux_util.c instead of accessing this directly. */ + struct circuit_t *on_circuit;
/** A pointer to which node in the circ this conn exits at. Set for AP - * connections and for hidden service exit connections. */ + * connections and for hidden service exit connections. + * Note that edges that use conflux should use the helpers + * in conflux_util.c instead of accessing this directly. */ struct crypt_path_t *cpath_layer;
/* Hidden service connection identifier for edge connections. Used by the HS diff --git a/src/core/or/or_circuit_st.h b/src/core/or/or_circuit_st.h index 11695ec301..d5a7007928 100644 --- a/src/core/or/or_circuit_st.h +++ b/src/core/or/or_circuit_st.h @@ -35,10 +35,18 @@ struct or_circuit_t { cell_queue_t p_chan_cells; /** The channel that is previous in this circuit. */ channel_t *p_chan; - /** Linked list of Exit streams associated with this circuit. */ + /** Linked list of Exit streams associated with this circuit. + * + * Note that any updates to this pointer must be followed with + * conflux_update_n_streams() to keep the other legs n_streams + * in sync. */ edge_connection_t *n_streams; /** Linked list of Exit streams associated with this circuit that are - * still being resolved. */ + * still being resolved. + * + * Just like with n_streams, any updates to this pointer must + * be followed with conflux_update_resolving_streams(). + */ edge_connection_t *resolving_streams;
/** Cryptographic state used for encrypting and authenticating relay diff --git a/src/core/or/origin_circuit_st.h b/src/core/or/origin_circuit_st.h index 73b971f72d..c5c255bb49 100644 --- a/src/core/or/origin_circuit_st.h +++ b/src/core/or/origin_circuit_st.h @@ -80,11 +80,18 @@ struct origin_circuit_t { circuit_t base_;
/** Linked list of AP streams (or EXIT streams if hidden service) - * associated with this circuit. */ + * associated with this circuit. + * + * Any updates to this pointer must be followed with + * conflux_update_p_streams(). */ edge_connection_t *p_streams;
/** Smartlist of half-closed streams (half_edge_t*) that still - * have pending activity */ + * have pending activity. + * + * Any updates to this pointer must be followed with + * conflux_update_half_streams(). + */ smartlist_t *half_streams;
/** Bytes read on this circuit since last call to diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 827f0c3e46..58e48df902 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -122,7 +122,8 @@ static int connection_edge_process_ordered_relay_cell(cell_t *cell, edge_connection_t *conn, crypt_path_t *layer_hint, relay_header_t *rh); -static void set_block_state_for_streams(edge_connection_t *stream_list, +static void set_block_state_for_streams(circuit_t *circ, + edge_connection_t *stream_list, int block, streamid_t stream_id);
/** Stats: how many relay cells have originated at this hop, or have @@ -455,7 +456,7 @@ relay_lookup_conn(circuit_t *circ, cell_t *cell, tmpconn=tmpconn->next_stream) { if (rh.stream_id == tmpconn->stream_id && !tmpconn->base_.marked_for_close && - tmpconn->cpath_layer == layer_hint) { + edge_uses_cpath(tmpconn, layer_hint)) { log_debug(LD_APP,"found conn for stream %d.", rh.stream_id); return tmpconn; } @@ -1549,25 +1550,6 @@ connection_edge_process_relay_cell_not_open( // return -1; }
-/** - * Return true iff our decryption layer_hint is from the last hop - * in a circuit. - */ -static bool -relay_crypt_from_last_hop(origin_circuit_t *circ, crypt_path_t *layer_hint) -{ - tor_assert(circ); - tor_assert(layer_hint); - tor_assert(circ->cpath); - - if (layer_hint != circ->cpath->prev) { - log_fn(LOG_PROTOCOL_WARN, LD_CIRC, - "Got unexpected relay data from intermediate hop"); - return false; - } - return true; -} - /** 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 @@ -2454,6 +2436,15 @@ circuit_resume_edge_reading(circuit_t *circ, crypt_path_t *layer_hint) log_debug(layer_hint?LD_APP:LD_EXIT,"Too big queue, no resuming"); return; } + + /* If we have a conflux negotiated, and it still can't send on + * any circuit, then do not resume sending. */ + if (circ->conflux && !conflux_can_send(circ->conflux)) { + log_debug(layer_hint?LD_APP:LD_EXIT, + "Conflux can't send, not resuming edges"); + return; + } + log_debug(layer_hint?LD_APP:LD_EXIT,"resuming");
if (CIRCUIT_IS_ORIGIN(circ)) @@ -2487,20 +2478,6 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, return 0; }
- /* How many cells do we have space for? It will be the minimum of - * the number needed to exhaust the package window, and the minimum - * needed to fill the cell queue. */ - - max_to_package = congestion_control_get_package_window(circ, layer_hint); - if (CIRCUIT_IS_ORIGIN(circ)) { - cells_on_queue = circ->n_chan_cells.n; - } else { - or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); - cells_on_queue = or_circ->p_chan_cells.n; - } - if (cell_queue_highwatermark() - cells_on_queue < max_to_package) - max_to_package = cell_queue_highwatermark() - cells_on_queue; - /* Once we used to start listening on the streams in the order they * appeared in the linked list. That leads to starvation on the * streams that appeared later on the list, since the first streams @@ -2539,11 +2516,13 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, /* Activate reading starting from the chosen stream */ for (conn=chosen_stream; conn; conn = conn->next_stream) { /* Start reading for the streams starting from here */ - if (conn->base_.marked_for_close || conn->package_window <= 0 || - conn->xoff_received) + if (conn->base_.marked_for_close || conn->package_window <= 0) continue; - if (!layer_hint || conn->cpath_layer == layer_hint) { - connection_start_reading(TO_CONN(conn)); + + if (edge_uses_cpath(conn, layer_hint)) { + if (!conn->xoff_received) { + connection_start_reading(TO_CONN(conn)); + }
if (connection_get_inbuf_len(TO_CONN(conn)) > 0) ++n_packaging_streams; @@ -2551,11 +2530,13 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, } /* Go back and do the ones we skipped, circular-style */ for (conn = first_conn; conn != chosen_stream; conn = conn->next_stream) { - if (conn->base_.marked_for_close || conn->package_window <= 0 || - conn->xoff_received) + if (conn->base_.marked_for_close || conn->package_window <= 0) continue; - if (!layer_hint || conn->cpath_layer == layer_hint) { - connection_start_reading(TO_CONN(conn)); + + if (edge_uses_cpath(conn, layer_hint)) { + if (!conn->xoff_received) { + connection_start_reading(TO_CONN(conn)); + }
if (connection_get_inbuf_len(TO_CONN(conn)) > 0) ++n_packaging_streams; @@ -2567,6 +2548,32 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn,
again:
+ /* If we're using conflux, the circuit we decide to send on may change + * after we're sending. Get it again, and re-check package windows + * for it */ + if (circ->conflux) { + if (circuit_consider_stop_edge_reading(circ, layer_hint)) + return -1; + + circ = conflux_decide_next_circ(circ->conflux); + + /* Get the destination layer hint for this circuit */ + layer_hint = conflux_get_destination_hop(circ); + } + + /* How many cells do we have space for? It will be the minimum of + * the number needed to exhaust the package window, and the minimum + * needed to fill the cell queue. */ + max_to_package = congestion_control_get_package_window(circ, layer_hint); + if (CIRCUIT_IS_ORIGIN(circ)) { + cells_on_queue = circ->n_chan_cells.n; + } else { + or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); + cells_on_queue = or_circ->p_chan_cells.n; + } + if (cell_queue_highwatermark() - cells_on_queue < max_to_package) + max_to_package = cell_queue_highwatermark() - cells_on_queue; + cells_per_conn = CEIL_DIV(max_to_package, n_packaging_streams);
packaged_this_round = 0; @@ -2580,7 +2587,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, for (conn=first_conn; conn; conn=conn->next_stream) { if (conn->base_.marked_for_close || conn->package_window <= 0) continue; - if (!layer_hint || conn->cpath_layer == layer_hint) { + if (edge_uses_cpath(conn, layer_hint)) { int n = cells_per_conn, r; /* handle whatever might still be on the inbuf */ r = connection_edge_package_raw_inbuf(conn, 1, &n); @@ -2638,7 +2645,7 @@ circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint) or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); log_debug(domain,"considering circ->package_window %d", circ->package_window); - if (congestion_control_get_package_window(circ, layer_hint) <= 0) { + if (circuit_get_package_window(circ, layer_hint) <= 0) { log_debug(domain,"yes, not-at-origin. stopped."); for (conn = or_circ->n_streams; conn; conn=conn->next_stream) connection_stop_reading(TO_CONN(conn)); @@ -2649,11 +2656,11 @@ circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint) /* else, layer hint is defined, use it */ log_debug(domain,"considering layer_hint->package_window %d", layer_hint->package_window); - if (congestion_control_get_package_window(circ, layer_hint) <= 0) { + if (circuit_get_package_window(circ, layer_hint) <= 0) { log_debug(domain,"yes, at-origin. stopped."); for (conn = TO_ORIGIN_CIRCUIT(circ)->p_streams; conn; conn=conn->next_stream) { - if (conn->cpath_layer == layer_hint) + if (edge_uses_cpath(conn, layer_hint)) connection_stop_reading(TO_CONN(conn)); } return 1; @@ -3029,7 +3036,7 @@ set_circuit_blocked_on_chan(circuit_t *circ, channel_t *chan, int block) edge = TO_OR_CIRCUIT(circ)->n_streams; }
- set_block_state_for_streams(edge, block, 0); + set_block_state_for_streams(circ, edge, block, 0); }
/** @@ -3039,15 +3046,29 @@ set_circuit_blocked_on_chan(circuit_t *circ, channel_t *chan, int block) * in the stream list. If it is non-zero, only apply to that specific stream. */ static void -set_block_state_for_streams(edge_connection_t *stream_list, int block, - streamid_t stream_id) +set_block_state_for_streams(circuit_t *circ, edge_connection_t *stream_list, + int block, streamid_t stream_id) { + /* If we have a conflux object, we need to examine its status before + * blocking and unblocking streams. */ + if (circ->conflux) { + bool can_send = conflux_can_send(circ->conflux); + + if (block && can_send) { + /* Don't actually block streams, since conflux can send*/ + return; + } else if (!block && !can_send) { + /* Don't actually unblock streams, since conflux still can't send */ + return; + } + } + for (edge_connection_t *edge = stream_list; edge; edge = edge->next_stream) { connection_t *conn = TO_CONN(edge); if (stream_id && edge->stream_id != stream_id) continue;
- if (!conn->read_event) { + if (!conn->read_event || edge->xoff_received) { /* This connection is a placeholder for something; probably a DNS * request. It can't actually stop or start reading.*/ continue; @@ -3412,8 +3433,8 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, if (circ_blocked && fromstream) { /* This edge connection is apparently not blocked; this can happen for * new streams on a blocked circuit, for their CONNECTED response. - * block it now. */ - set_block_state_for_streams(stream_list, 1, fromstream); + * block it now, unless we have conflux. */ + set_block_state_for_streams(circ, stream_list, 1, fromstream); }
update_circuit_on_cmux(circ, direction); diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c index 7267ca06dd..f6a020d061 100644 --- a/src/feature/relay/dns.c +++ b/src/feature/relay/dns.c @@ -71,6 +71,7 @@
#include "core/or/edge_connection_st.h" #include "core/or/or_circuit_st.h" +#include "core/or/conflux_util.h"
#include "ht.h"
@@ -650,6 +651,7 @@ dns_resolve(edge_connection_t *exitconn) * connected cell. */ exitconn->next_stream = oncirc->n_streams; oncirc->n_streams = exitconn; + conflux_update_n_streams(oncirc, exitconn); } break; case 0: @@ -658,6 +660,7 @@ dns_resolve(edge_connection_t *exitconn) exitconn->base_.state = EXIT_CONN_STATE_RESOLVING; exitconn->next_stream = oncirc->resolving_streams; oncirc->resolving_streams = exitconn; + conflux_update_resolving_streams(oncirc, exitconn); break; case -2: case -1: @@ -1234,6 +1237,7 @@ inform_pending_connections(cached_resolve_t *resolve) pend->conn->next_stream = TO_OR_CIRCUIT(circ)->n_streams; pend->conn->on_circuit = circ; TO_OR_CIRCUIT(circ)->n_streams = pend->conn; + conflux_update_n_streams(TO_OR_CIRCUIT(circ), pend->conn);
connection_exit_connect(pend->conn); } else {
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 336a24754d117b46793ce6824e35ff6b7962bf9d Author: David Goulet dgoulet@torproject.org AuthorDate: Wed Feb 1 22:41:03 2023 +0000
Prop#329 Pool: Handle linking, unlinking, and relaunching conflux circuit legs.
Signed-off-by: David Goulet dgoulet@torproject.org --- src/app/main/shutdown.c | 2 + src/app/main/subsystem_list.c | 2 + src/core/or/circuit_st.h | 8 + src/core/or/circuitbuild.c | 45 + src/core/or/circuitbuild.h | 6 + src/core/or/circuitlist.c | 13 + src/core/or/circuituse.c | 14 + src/core/or/circuituse.h | 3 +- src/core/or/conflux_pool.c | 1745 ++++++++++++++++++++++++++++++++++++ src/core/or/conflux_pool.h | 41 + src/core/or/conflux_sys.c | 37 + src/core/or/conflux_sys.h | 23 + src/core/or/cpath_build_state_st.h | 2 + src/core/or/or.h | 9 + src/core/or/relay.c | 18 +- src/feature/nodelist/node_select.h | 2 + src/feature/nodelist/routerlist.c | 5 + 17 files changed, 1972 insertions(+), 3 deletions(-)
diff --git a/src/app/main/shutdown.c b/src/app/main/shutdown.c index a6065db5da..b3f1c6d058 100644 --- a/src/app/main/shutdown.c +++ b/src/app/main/shutdown.c @@ -23,6 +23,7 @@ #include "core/or/circuitlist.h" #include "core/or/circuitmux_ewma.h" #include "core/or/circuitpadding.h" +#include "core/or/conflux_pool.h" #include "core/or/connection_edge.h" #include "core/or/dos.h" #include "core/or/scheduler.h" @@ -120,6 +121,7 @@ tor_free_all(int postfork) rep_hist_free_all(); bwhist_free_all(); circuit_free_all(); + conflux_pool_free_all(); circpad_machines_free(); entry_guards_free_all(); pt_free_all(); diff --git a/src/app/main/subsystem_list.c b/src/app/main/subsystem_list.c index 0333077164..4a66e21a79 100644 --- a/src/app/main/subsystem_list.c +++ b/src/app/main/subsystem_list.c @@ -14,6 +14,7 @@ #include "lib/cc/torint.h"
#include "core/mainloop/mainloop_sys.h" +#include "core/or/conflux_sys.h" #include "core/or/dos_sys.h" #include "core/or/or_sys.h" #include "feature/control/btrack_sys.h" @@ -64,6 +65,7 @@ const subsys_fns_t *tor_subsystems[] = { &sys_process,
&sys_mainloop, + &sys_conflux, &sys_or, &sys_dos,
diff --git a/src/core/or/circuit_st.h b/src/core/or/circuit_st.h index 1afb4d4426..c28900e59f 100644 --- a/src/core/or/circuit_st.h +++ b/src/core/or/circuit_st.h @@ -261,6 +261,14 @@ struct circuit_t { * CIRCUIT_PURPOSE_CONFLUX_UNLINKED. */ struct conflux_t *conflux; + + /** If set, this circuit is considered *unlinked* and in the pending pool. + * The nonce value is used to find the other legs. Origin circuits that + * have this set are in the CIRCUIT_PURPOSE_CONFLUX_UNLINKED purpose. + * + * If this is NULL, and conflux object is set, it means this circuit is + * linked and thus part of a usable set. */ + uint8_t *conflux_pending_nonce; };
#endif /* !defined(CIRCUIT_ST_H) */ diff --git a/src/core/or/circuitbuild.c b/src/core/or/circuitbuild.c index 8089f438a7..743d67acde 100644 --- a/src/core/or/circuitbuild.c +++ b/src/core/or/circuitbuild.c @@ -465,6 +465,8 @@ origin_circuit_init(uint8_t purpose, int flags) ((flags & CIRCLAUNCH_IS_INTERNAL) ? 1 : 0); circ->build_state->is_ipv6_selftest = ((flags & CIRCLAUNCH_IS_IPV6_SELFTEST) ? 1 : 0); + circ->build_state->need_conflux = + ((flags & CIRCLAUNCH_NEED_CONFLUX) ? 1 : 0); circ->base_.purpose = purpose; return circ; } @@ -507,6 +509,47 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, int flags) return circ; }
+/** + * Build a new conflux circuit for <b>purpose</b>. If <b>exit</b> is defined, + * then use that as your exit router, else choose a suitable exit node. + * The <b>flags</b> argument is a bitfield of CIRCLAUNCH_* flags, see + * circuit_launch_by_extend_info() for more details. + * + * Also launch a connection to the first OR in the chosen path, if + * it's not open already. + */ +MOCK_IMPL(origin_circuit_t *, +circuit_establish_circuit_conflux,(const uint8_t *conflux_nonce, + uint8_t purpose, extend_info_t *exit_ei, + int flags)) +{ + origin_circuit_t *circ; + int err_reason = 0; + + /* Right now, only conflux client circuits use this function */ + tor_assert(purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + + circ = origin_circuit_init(purpose, flags); + TO_CIRCUIT(circ)->conflux_pending_nonce = + tor_memdup(conflux_nonce, DIGEST256_LEN); + + if (onion_pick_cpath_exit(circ, exit_ei, 0) < 0 || + onion_populate_cpath(circ) < 0) { + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOPATH); + return NULL; + } + + circuit_event_status(circ, CIRC_EVENT_LAUNCHED, 0); + + if ((err_reason = circuit_handle_first_hop(circ)) < 0) { + circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); + return NULL; + } + + tor_trace(TR_SUBSYS(circuit), TR_EV(establish), circ); + return circ; +} + /** Return the guard state associated with <b>circ</b>, which may be NULL. */ circuit_guard_state_t * origin_circuit_get_guard_state(origin_circuit_t *circ) @@ -2105,6 +2148,8 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei, flags |= CRN_DIRECT_CONN; if (is_hs_v3_rp_circuit) flags |= CRN_RENDEZVOUS_V3; + if (state->need_conflux) + flags |= CRN_CONFLUX; const node_t *node = choose_good_exit_server(circ, flags, state->is_internal); if (!node) { diff --git a/src/core/or/circuitbuild.h b/src/core/or/circuitbuild.h index a66c611132..4235ee96b2 100644 --- a/src/core/or/circuitbuild.h +++ b/src/core/or/circuitbuild.h @@ -24,6 +24,12 @@ origin_circuit_t *origin_circuit_init(uint8_t purpose, int flags); origin_circuit_t *circuit_establish_circuit(uint8_t purpose, extend_info_t *exit, int flags); +MOCK_DECL(origin_circuit_t *, circuit_establish_circuit_conflux, ( + const uint8_t *nonce, + uint8_t purpose, + extend_info_t *exit, + int flags)); + struct circuit_guard_state_t *origin_circuit_get_guard_state( origin_circuit_t *circ); int circuit_handle_first_hop(origin_circuit_t *circ); diff --git a/src/core/or/circuitlist.c b/src/core/or/circuitlist.c index 8c5beebbf3..b90c7ebb58 100644 --- a/src/core/or/circuitlist.c +++ b/src/core/or/circuitlist.c @@ -62,6 +62,8 @@ #include "core/or/circuituse.h" #include "core/or/circuitstats.h" #include "core/or/circuitpadding.h" +#include "core/or/conflux.h" +#include "core/or/conflux_pool.h" #include "core/or/crypt_path.h" #include "core/or/extendinfo.h" #include "core/or/status.h" @@ -118,6 +120,7 @@ #include "core/or/or_circuit_st.h" #include "core/or/origin_circuit_st.h"
+#include "core/or/conflux_util.h" /********* START VARIABLES **********/
/** A global list of all circuits at this hop. */ @@ -2254,6 +2257,11 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, /* Notify the HS subsystem that this circuit is closing. */ hs_circ_cleanup_on_close(circ);
+ /* Specific actions if this is a conflux related circuit. */ + if (CIRCUIT_IS_CONFLUX(circ)) { + conflux_circuit_has_closed(circ); + } + /* Update stats. */ if (circ->ccontrol) { if (circ->ccontrol->in_slow_start) { @@ -2297,6 +2305,8 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, static void circuit_about_to_free_atexit(circuit_t *circ) { + /* Cleanup conflux specifics. */ + conflux_circuit_about_to_free(circ);
if (circ->n_chan) { circuit_clear_cell_queue(circ, circ->n_chan); @@ -2326,6 +2336,9 @@ circuit_about_to_free(circuit_t *circ) int reason = circ->marked_for_close_reason; int orig_reason = circ->marked_for_close_orig_reason;
+ /* Cleanup conflux specifics. */ + conflux_circuit_about_to_free(circ); + if (circ->state == CIRCUIT_STATE_ONIONSKIN_PENDING) { onion_pending_remove(TO_OR_CIRCUIT(circ)); } diff --git a/src/core/or/circuituse.c b/src/core/or/circuituse.c index f5d5cb4397..7ea9cddbd9 100644 --- a/src/core/or/circuituse.c +++ b/src/core/or/circuituse.c @@ -36,6 +36,7 @@ #include "core/or/circuitstats.h" #include "core/or/circuituse.h" #include "core/or/circuitpadding.h" +#include "core/or/conflux_pool.h" #include "core/or/connection_edge.h" #include "core/or/extendinfo.h" #include "core/or/policies.h" @@ -74,6 +75,8 @@ #include "core/or/origin_circuit_st.h" #include "core/or/socks_request_st.h"
+#include "core/or/conflux_util.h" + STATIC void circuit_expire_old_circuits_clientside(void); static void circuit_increment_failure_count(void);
@@ -1217,6 +1220,7 @@ circuit_predict_and_launch_new(void) log_info(LD_CIRC, "Have %d clean circs (%d internal), need another exit circ.", num, num_internal); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } @@ -1664,6 +1668,9 @@ circuit_has_opened(origin_circuit_t *circ) case CIRCUIT_PURPOSE_C_INTRODUCING: hs_client_circuit_has_opened(circ); break; + case CIRCUIT_PURPOSE_CONFLUX_UNLINKED: + conflux_circuit_has_opened(circ); + break; case CIRCUIT_PURPOSE_C_GENERAL: circuit_try_attaching_streams(circ); break; @@ -3063,6 +3070,13 @@ circuit_change_purpose(circuit_t *circ, uint8_t new_purpose)
if (circ->purpose == new_purpose) return;
+ /* If this is a conflux circuit, a purpose change means we have closed */ + if (CIRCUIT_IS_CONFLUX(circ)) { + /* If we're not transitioning to the linked purpose, we're closed. */ + if (new_purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED) + conflux_circuit_has_closed(circ); + } + if (CIRCUIT_IS_ORIGIN(circ)) { char old_purpose_desc[80] = "";
diff --git a/src/core/or/circuituse.h b/src/core/or/circuituse.h index c737ff1c9d..cda6d7ee2e 100644 --- a/src/core/or/circuituse.h +++ b/src/core/or/circuituse.h @@ -52,6 +52,8 @@ void circuit_build_failed(origin_circuit_t *circ); * node in the circuit. (We are both the client and the last node in the * circuit.) */ #define CIRCLAUNCH_IS_IPV6_SELFTEST (1<<5) +/** Flag to set when a circuit needs the exit to support conflux. */ +#define CIRCLAUNCH_NEED_CONFLUX (1<<6)
origin_circuit_t *circuit_launch_by_extend_info(uint8_t purpose, extend_info_t *info, @@ -77,7 +79,6 @@ bool circuit_purpose_is_hs_service(const uint8_t purpose); bool circuit_purpose_is_hs_vanguards(const uint8_t purpose);
bool circuit_is_hs_v3(const circuit_t *circ); - int circuit_should_use_vanguards(uint8_t); void circuit_sent_valid_data(origin_circuit_t *circ, uint16_t relay_body_len); void circuit_read_valid_data(origin_circuit_t *circ, uint16_t relay_body_len); diff --git a/src/core/or/conflux_pool.c b/src/core/or/conflux_pool.c new file mode 100644 index 0000000000..66a405d4c1 --- /dev/null +++ b/src/core/or/conflux_pool.c @@ -0,0 +1,1745 @@ +/* Copyright (c) 2021, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_pool.c + * \brief Conflux circuit pool management + */ + +#define TOR_CONFLUX_PRIVATE +#define CONFLUX_CELL_PRIVATE + +#include "core/or/or.h" + +#include "app/config/config.h" + +#include "core/or/circuitbuild.h" +#include "core/or/circuitlist.h" +#include "core/or/circuitstats.h" +#include "core/or/circuituse.h" +#include "core/or/congestion_control_st.h" +#include "core/or/conflux.h" +#include "core/or/conflux_cell.h" +#include "trunnel/conflux.h" +#include "core/or/conflux_params.h" +#include "core/or/conflux_pool.h" +#include "core/or/conflux_util.h" +#include "core/or/relay.h" +#include "core/or/connection_edge.h" +#include "core/or/edge_connection_st.h" + +#include "core/or/crypt_path_st.h" +#include "core/or/or_circuit_st.h" +#include "core/or/origin_circuit_st.h" + +#include "feature/nodelist/nodelist.h" + +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" + +/* Indicate if we are shutting down. This is used so we avoid recovering a + * conflux set on total shutdown. */ +static bool shutting_down = false; + +/** The pool of client-side conflux_t that are built, linked, and ready + * to be used. Indexed by nonce. */ +static digest256map_t *client_linked_pool; + +/** The pool of origin unlinked_circuits_t indexed by nonce. */ +static digest256map_t *client_unlinked_pool; + +/** The pool of relay conflux_t indexed by nonce. We call these "server" + * because they could be onion-service side too (even though we likely will + * only implement onion service conflux in Arti). The code is littered with + * asserts to ensure there are no origin circuits in here for now, too. */ +static digest256map_t *server_linked_pool; + +/** The pool of relay unlinked_circuits_t indexed by nonce. */ +static digest256map_t *server_unlinked_pool; + +/* A leg is essentially a circuit for a conflux set. We use this object for the + * unlinked pool. */ +typedef struct leg_t { + /* The circuit of the leg. */ + circuit_t *circ; + + /* The LINK cell content which is used to put the information back in the + * conflux_t object once all legs have linked and validate the ack. */ + conflux_cell_link_t *link; + + /* Indicate if the leg has received the LINKED or the LINKED_ACK cell + * depending on its side of the circuit. When all legs are linked, we then + * finalize the conflux_t object and move it to the linked pool. */ + bool linked; + + /* What time did we send the LINK/LINKED (depending on which side) so we can + * calculate the RTT. */ + uint64_t link_sent_usec; + + /* The RTT value in usec takend from the LINK <--> LINKED round trip. */ + uint64_t rtt_usec; +} leg_t; + +/* Object used to track unlinked circuits which are kept in the unlinked pool + * until they are linked and moved to the linked pool and global circuit set. + */ +typedef struct unlinked_circuits_t { + /* If true, indicate that this unlinked set is client side as in the legs are + * origin circuits. Else, it is on the exit side and thus or circuits. */ + bool is_client; + + /* If true, indicate if the conflux_t is related to a linked set. */ + bool is_for_linked_set; + + /* Conflux object that will be set in each leg once all linked. */ + conflux_t *cfx; + + /* Legs. */ + smartlist_t *legs; +} unlinked_circuits_t; + +/** Error code used when linking circuits. Based on those, we decide to + * relaunch or not. */ +typedef enum link_circ_err_t { + /* Linking was successful. */ + ERR_LINK_CIRC_OK = 0, + /* The RTT was not acceptable. */ + ERR_LINK_CIRC_BAD_RTT = 1, + /* The leg can't be found. */ + ERR_LINK_CIRC_MISSING_LEG = 2, + /* The set can't be found. */ + ERR_LINK_CIRC_MISSING_SET = 3, + /* Invalid leg as in not pass validation. */ + ERR_LINK_CIRC_INVALID_LEG = 4, +} link_circ_err_t; + +#ifdef TOR_UNIT_TESTS +digest256map_t * +get_unlinked_pool(bool is_client) +{ + return is_client ? client_unlinked_pool : server_unlinked_pool; +} + +digest256map_t * +get_linked_pool(bool is_client) +{ + return is_client ? client_linked_pool : server_linked_pool; +} +#endif + +/** Helper: Format at 8 bytes the nonce for logging. */ +static inline const char * +fmt_nonce(const uint8_t *nonce) +{ + return hex_str((char *) nonce, 8); +} + +/** + * Return the conflux algorithm for a desired UX value. + */ +static uint8_t +conflux_choose_algorithm(uint8_t desired_ux) +{ + /* TODO-329-TUNING: Pick better algs here*/ + switch (desired_ux) { + case CONFLUX_UX_NO_OPINION: + return CONFLUX_ALG_LOWRTT; + case CONFLUX_UX_MIN_LATENCY: + return CONFLUX_ALG_MINRTT; + case CONFLUX_UX_LOW_MEM_LATENCY: + return CONFLUX_ALG_MINRTT; + case CONFLUX_UX_LOW_MEM_THROUGHPUT: + return CONFLUX_ALG_CWNDRTT; + case CONFLUX_UX_HIGH_THROUGHPUT: + return CONFLUX_ALG_LOWRTT; + default: + /* Trunnel should protect us from this */ + tor_assert_nonfatal_unreached(); + return CONFLUX_ALG_LOWRTT; + } +} + +/** Return a newly allocated conflux_t object. */ +static conflux_t * +conflux_new(void) +{ + conflux_t *cfx = tor_malloc_zero(sizeof(*cfx)); + + cfx->ooo_q = smartlist_new(); + cfx->legs = smartlist_new(); + + return cfx; +} + +static void +conflux_free_(conflux_t *cfx) +{ + if (!cfx) { + return; + } + + SMARTLIST_FOREACH_BEGIN(cfx->legs, conflux_leg_t *, leg) { + SMARTLIST_DEL_CURRENT(cfx->legs, leg); + tor_free(leg); + } SMARTLIST_FOREACH_END(leg); + smartlist_free(cfx->legs); + + SMARTLIST_FOREACH(cfx->ooo_q, conflux_cell_t *, cell, tor_free(cell)); + smartlist_free(cfx->ooo_q); + + memwipe(cfx->nonce, 0, sizeof(cfx->nonce)); + tor_free(cfx); +} + +/** Wrapper for the free function, set the cfx pointer to NULL after free */ +#define conflux_free(cfx) \ + FREE_AND_NULL(conflux_t, conflux_free_, cfx) + +/** Helper: Free function for the digest256map_free(). */ +static inline void +free_conflux_void_(void *ptr) +{ + conflux_t *cfx = (conflux_t *)ptr; + conflux_free(cfx); +} + +/** Return a newly allocated leg object containing the given circuit and link + * pointer (no copy). */ +static leg_t * +leg_new(circuit_t *circ, conflux_cell_link_t *link) +{ + leg_t *leg = tor_malloc_zero(sizeof(*leg)); + leg->circ = circ; + leg->link = link; + return leg; +} + +/** Free the given leg object. Passing NULL is safe. */ +static void +leg_free(leg_t *leg) +{ + if (!leg) { + return; + } + if (leg->circ) { + tor_free(leg->circ->conflux_pending_nonce); + leg->circ->conflux_pending_nonce = NULL; + } + tor_free(leg->link); + tor_free(leg); +} + +/** Return a newly allocated unlinked set object for the given nonce. A new + * conflux object is also created. */ +static unlinked_circuits_t * +unlinked_new(const uint8_t *nonce, bool is_client) +{ + unlinked_circuits_t *unlinked = tor_malloc_zero(sizeof(*unlinked)); + unlinked->cfx = conflux_new(); + unlinked->legs = smartlist_new(); + unlinked->is_client = is_client; + memcpy(unlinked->cfx->nonce, nonce, sizeof(unlinked->cfx->nonce)); + + return unlinked; +} + +/** Free the given unlinked object. */ +static void +unlinked_free(unlinked_circuits_t *unlinked) +{ + if (!unlinked) { + return; + } + /* This cfx is pointing to a linked set. */ + if (!unlinked->is_for_linked_set) { + conflux_free(unlinked->cfx); + } + SMARTLIST_FOREACH(unlinked->legs, leg_t *, leg, leg_free(leg)); + smartlist_free(unlinked->legs); + tor_free(unlinked); +} + +/** Add the given unlinked object to the unlinked pool. */ +static void +unlinked_pool_add(unlinked_circuits_t *unlinked, bool is_client) +{ + tor_assert(unlinked); + if (is_client) { + digest256map_set(client_unlinked_pool, unlinked->cfx->nonce, unlinked); + } else { + digest256map_set(server_unlinked_pool, unlinked->cfx->nonce, unlinked); + } +} + +/** Delete the given unlinked object from the unlinked pool. */ +static void +unlinked_pool_del(unlinked_circuits_t *unlinked, bool is_client) +{ + tor_assert(unlinked); + + if (is_client) { + digest256map_remove(client_unlinked_pool, unlinked->cfx->nonce); + } else { + digest256map_remove(server_unlinked_pool, unlinked->cfx->nonce); + } +} + +/** Return an unlinked object for the given nonce else NULL. */ +static unlinked_circuits_t * +unlinked_pool_get(const uint8_t *nonce, bool is_client) +{ + tor_assert(nonce); + if (is_client) { + return digest256map_get(client_unlinked_pool, nonce); + } else { + return digest256map_get(server_unlinked_pool, nonce); + } +} + +/** Delete from the pool and free the given unlinked object. */ +static void +unlinked_pool_del_and_free(unlinked_circuits_t *unlinked, bool is_client) +{ + tor_assert(unlinked); + unlinked_pool_del(unlinked, is_client); + unlinked_free(unlinked); +} + +/** Add the given conflux object to the linked conflux set. */ +static void +linked_pool_add(conflux_t *cfx, bool is_client) +{ + tor_assert(cfx); + if (is_client) { + digest256map_set(client_linked_pool, cfx->nonce, cfx); + } else { + digest256map_set(server_linked_pool, cfx->nonce, cfx); + } +} + +/** Delete from the linked conflux set the given nonce. */ +static void +linked_pool_del(const uint8_t *nonce, bool is_client) +{ + tor_assert(nonce); + if (is_client) { + digest256map_remove(client_linked_pool, nonce); + } else { + digest256map_remove(server_linked_pool, nonce); + } +} + +/** Return a conflux_t object for the given nonce from the linked set. */ +static conflux_t * +linked_pool_get(const uint8_t *nonce, bool is_client) +{ + tor_assert(nonce); + if (is_client) { + return digest256map_get(client_linked_pool, nonce); + } else { + return digest256map_get(server_linked_pool, nonce); + } +} + +/** Add the given leg to the given unlinked object. */ +static inline void +unlinked_leg_add(unlinked_circuits_t *unlinked, leg_t *leg) +{ + tor_assert(unlinked); + tor_assert(leg); + + smartlist_add(unlinked->legs, leg); +} + +/** Return an unlinked leg for the given unlinked object and for the given + * circuit. */ +static inline leg_t * +leg_find(const unlinked_circuits_t *unlinked, const circuit_t *circ) +{ + SMARTLIST_FOREACH_BEGIN(unlinked->legs, leg_t *, leg) { + if (leg->circ == circ) { + return leg; + } + } SMARTLIST_FOREACH_END(leg); + return NULL; +} + +/** Return the given circuit leg from its unlinked set (if any). */ +static leg_t * +unlinked_leg_find(const circuit_t *circ, bool is_client) +{ + unlinked_circuits_t *unlinked = + unlinked_pool_get(circ->conflux_pending_nonce, is_client); + if (!unlinked) { + return NULL; + } + return leg_find(unlinked, circ); +} + +static void +unlinked_leg_del_and_free(unlinked_circuits_t *unlinked, + const circuit_t *circ) +{ + tor_assert(circ); + tor_assert(unlinked); + + SMARTLIST_FOREACH_BEGIN(unlinked->legs, leg_t *, leg) { + if (leg->circ == circ) { + SMARTLIST_DEL_CURRENT(unlinked->legs, leg); + leg_free(leg); + break; + } + } SMARTLIST_FOREACH_END(leg); +} + +/** + * Ensure that the given circuit has no attached streams. + * + * This validation function is called at various stages for + * unlinked circuits, to make sure they have no streams. + */ +static void +validate_circ_has_no_streams(circuit_t *circ) +{ + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + if (BUG(ocirc->p_streams)) { + log_warn(LD_BUG, + "Unlinked Conflux circuit %u has attached streams.", + ocirc->global_identifier); + ocirc->p_streams = NULL; + } + if (BUG(ocirc->half_streams)) { + log_warn(LD_BUG, + "Unlinked conflux circ %u has half streams.", + ocirc->global_identifier); + ocirc->half_streams = NULL; + } + } else { + or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + if (BUG(orcirc->n_streams)) { + log_warn(LD_BUG, + "Unlinked conflux circuit has attached streams."); + orcirc->n_streams = NULL; + } + if (BUG(orcirc->resolving_streams)) { + log_warn(LD_BUG, + "Unlinked conflux circuit has resolving streams."); + orcirc->resolving_streams = NULL; + } + } +} + +/** Return true iff the legs in the given unlinked set are valid and coherent + * to be a linked set. */ +static bool +validate_unlinked_legs(unlinked_circuits_t *unlinked) +{ + bool valid = true; + uint8_t version; + uint8_t *nonce = NULL; + + tor_assert(unlinked); + + SMARTLIST_FOREACH_BEGIN(unlinked->legs, const leg_t *, leg) { + if (!nonce) { + nonce = leg->link->nonce; + version = leg->link->version; + } else { + /* Version and nonce must match in all legs. */ + valid &= (leg->link->version == version && + tor_memeq(leg->link->nonce, nonce, sizeof(leg->link->nonce))); + } + + // If the other ends last sent sequence number is higher than the + // last sequence number we delivered, we have data loss, and cannot link. + if (leg->link->last_seqno_sent > unlinked->cfx->last_seq_delivered) { + log_fn(unlinked->is_client ? LOG_NOTICE : LOG_PROTOCOL_WARN, LD_CIRC, + "Data loss detected while trying to add a conflux leg."); + valid = false; + + // TODO-329-ARTI: Instead of closing the set here, we could + // immediately send a SWITCH cell and re-send the missing data. + // To do this, though, we would need to constantly buffer at least + // a cwnd worth of sent data to retransmit. We're not going to try + // this in C-Tor, but arti could consider it. + } + validate_circ_has_no_streams(leg->circ); + } SMARTLIST_FOREACH_END(leg); + + /* Note that if no legs, it validates. */ + + return valid; +} + +/** Add up a new leg to the given conflux object. */ +static void +cfx_add_leg(conflux_t *cfx, leg_t *leg) +{ + tor_assert(cfx); + tor_assert(leg); + tor_assert(leg->link); + + /* Big trouble if we add a leg to the wrong set. */ + tor_assert(tor_memeq(cfx->nonce, leg->link->nonce, sizeof(cfx->nonce))); + + if (BUG(CONFLUX_NUM_LEGS(cfx) > CONFLUX_MAX_CIRCS)) { + return; + } + + conflux_leg_t *cleg = tor_malloc_zero(sizeof(*cleg)); + cleg->circ = leg->circ; + // TODO-329-ARTI: Blindly copying the values from the cell. Is this correct? + // I think no... When adding new legs, switching to this leg is + // likely to break, unless the sender tracks what link cell it sent.. + // Is that the best option? Or should we use the max of our legs, here? + // (It seems the other side will have no idea what our current maxes + /// are, so this option seems better right now) + cleg->last_seq_recv = leg->link->last_seqno_sent; + cleg->last_seq_sent = leg->link->last_seqno_recv; + cleg->circ_rtts_usec = leg->rtt_usec; + cleg->linked_sent_usec = leg->link_sent_usec; + + cfx->params.alg = conflux_choose_algorithm(leg->link->desired_ux); + + /* Add leg to given conflux. */ + smartlist_add(cfx->legs, cleg); + + /* Ensure the new circuit has no streams. */ + validate_circ_has_no_streams(leg->circ); + + /* If this is not the first leg, get the first leg, and get + * the reference streams from it. */ + if (CONFLUX_NUM_LEGS(cfx) > 0) { + conflux_leg_t *first_leg = smartlist_get(cfx->legs, 0); + if (CIRCUIT_IS_ORIGIN(first_leg->circ)) { + origin_circuit_t *old_circ = TO_ORIGIN_CIRCUIT(first_leg->circ); + origin_circuit_t *new_circ = TO_ORIGIN_CIRCUIT(leg->circ); + + new_circ->p_streams = old_circ->p_streams; + new_circ->half_streams = old_circ->half_streams; + /* Sync all legs with the new stream(s). */ + conflux_sync_circ_fields(cfx, old_circ); + } else { + or_circuit_t *old_circ = TO_OR_CIRCUIT(first_leg->circ); + or_circuit_t *new_circ = TO_OR_CIRCUIT(leg->circ); + new_circ->n_streams = old_circ->n_streams; + new_circ->resolving_streams = old_circ->resolving_streams; + } + } + + if (CIRCUIT_IS_ORIGIN(cleg->circ)) { + tor_assert_nonfatal(cleg->circ->purpose == + CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + circuit_change_purpose(cleg->circ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + } + conflux_validate_stream_lists(cfx); +} + +/** + * Clean up a circuit from its conflux_t object. + * + * Return true if closing this circuit should tear down the entire set, + * false otherwise. + */ +static bool +cfx_del_leg(conflux_t *cfx, const circuit_t *circ) +{ + conflux_leg_t *leg; + bool full_teardown = false; + + tor_assert(cfx); + tor_assert(circ); + + leg = conflux_get_leg(cfx, circ); + if (!leg) { + goto end; + } + + // If the circuit still has inflight data, teardown + const struct congestion_control_t *cc = circuit_ccontrol(circ); + tor_assert(cc); + tor_assert(cc->sendme_inc); + if (cc->inflight >= cc->sendme_inc) { + full_teardown = true; + log_info(LD_CIRC, "Conflux current circuit has closed with " + "data in flight, tearing down entire set."); + } + + /* Remove it from the cfx. */ + smartlist_remove(cfx->legs, leg); + + /* After removal, if this leg had the highest sent (or recv) + * sequence number, it was in active use by us (or the other side). + * We need to tear down the entire set. */ + // TODO-329-ARTI: If we support resumption, we don't need this. + if (CONFLUX_NUM_LEGS(cfx) > 0) { + if (conflux_get_max_seq_sent(cfx) < leg->last_seq_sent || + conflux_get_max_seq_recv(cfx) < leg->last_seq_recv) { + full_teardown = true; + log_info(LD_CIRC, "Conflux sequence number check failed, " + "tearing down entire set."); + } + } + + /* Cleanup any reference to leg. */ + if (cfx->curr_leg == leg) { + cfx->curr_leg = NULL; + full_teardown = true; + log_info(LD_CIRC, "Conflux current circuit has closed, " + "tearing down entire set."); + } + if (cfx->prev_leg == leg) { + cfx->prev_leg = NULL; + } + + tor_free(leg); + + end: + return full_teardown; +} + +/** Close the circuit of each legs of the given unlinked object. */ +static void +unlinked_close_all_legs(unlinked_circuits_t *unlinked) +{ + smartlist_t *circ_to_close = NULL; + + tor_assert(unlinked); + + /* Small optimization here, avoid this work if no legs. */ + if (smartlist_len(unlinked->legs) == 0) { + return; + } + + /* We will iterate over all legs and put the circuit in its own list and then + * mark them for close. The unlinked object gets freed opportunistically once + * there is no more legs attached to it and so we can't hold a reference + * while closing circuits. */ + circ_to_close = smartlist_new(); + + SMARTLIST_FOREACH(unlinked->legs, leg_t *, leg, + smartlist_add(circ_to_close, leg->circ)); + unlinked = NULL; + + /* The leg gets cleaned up in the circuit close. */ + SMARTLIST_FOREACH_BEGIN(circ_to_close, circuit_t *, circ) { + if (CIRCUIT_IS_ORIGIN(circ)) { + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + } + if (!circ->marked_for_close) { + circuit_mark_for_close(circ, END_CIRC_REASON_INTERNAL); + } + } SMARTLIST_FOREACH_END(circ); + + /* Drop the list and ignore its content, we don't have ownership. */ + smartlist_free(circ_to_close); +} + +/** Either closee all legs of the given unlinked set or delete it from the pool + * and free its memory. + * + * Important: The unlinked object is freed opportunistically when legs are + * removed until the point none remains. And so, it is only safe to free the + * object if no more legs exist. + */ +static void +unlinked_close_or_free(unlinked_circuits_t *unlinked) +{ + if (!unlinked) { + return; + } + + /* If we have legs, the circuit close will trigger the unlinked object to be + * opportunistically freed. Else, we do it explicitly. */ + if (smartlist_len(unlinked->legs) > 0) { + unlinked_close_all_legs(unlinked); + } else { + unlinked_pool_del_and_free(unlinked, unlinked->is_client); + } + /* Either the unlinked object has been freed or the last leg close will free + * it so from this point on, nullify for safety reasons. */ + unlinked = NULL; +} + +/** Upon an error condition or a close of an in-use circuit, we must close all + * linked and unlinked circuits associated with a set. When the last leg of + * each set is closed, the set is removed from the pool. */ +static void +conflux_mark_all_for_close(const uint8_t *nonce, bool is_client, int reason) +{ + /* It is possible that for a nonce we have both an unlinked set and a linked + * set. This happens if there is a recovery leg launched for an existing + * linked set. */ + + /* Close the unlinked set. */ + unlinked_circuits_t *unlinked = unlinked_pool_get(nonce, is_client); + if (unlinked) { + unlinked_close_or_free(unlinked); + } + /* In case it gets freed, be safe here. */ + unlinked = NULL; + + /* Close the linked set. It will free itself upon the close of + * the last leg. */ + conflux_t *linked = linked_pool_get(nonce, is_client); + if (linked) { + if (linked->in_full_teardown) { + return; + } + linked->in_full_teardown = true; + + smartlist_t *circ_to_close = smartlist_new(); + + SMARTLIST_FOREACH(linked->legs, conflux_leg_t *, leg, + smartlist_add(circ_to_close, leg->circ)); + + SMARTLIST_FOREACH(circ_to_close, circuit_t *, circ, + circuit_mark_for_close(circ, reason)); + + /* Drop the list and ignore its content, we don't have ownership. */ + smartlist_free(circ_to_close); + } +} + +/** Helper: Free function taking a void pointer for the digest256map_free. */ +static inline void +free_unlinked_void_(void *ptr) +{ + unlinked_circuits_t *unlinked = ptr; + unlinked_pool_del_and_free(unlinked, unlinked->is_client); +} + +/** Attempt to finalize the unlinked set to become a linked set and be put in + * the linked pool. + * + * If this finalized successfully, the given unlinked object is freed. */ +static link_circ_err_t +try_finalize_set(unlinked_circuits_t *unlinked) +{ + link_circ_err_t err = ERR_LINK_CIRC_OK; + bool is_client; + + tor_assert(unlinked); + + /* Without legs, this is not ready to become a linked set. */ + if (BUG(smartlist_len(unlinked->legs) == 0)) { + err = ERR_LINK_CIRC_MISSING_LEG; + goto end; + } + + /* Validate that all legs are coherent and parameters match. On failure, we + * teardown the whole unlinked set because this means we either have a code + * flow problem or the Exit is trying to trick us. */ + if (!validate_unlinked_legs(unlinked)) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Conflux unlinked set legs are not validating. Tearing it down."); + conflux_mark_all_for_close(unlinked->cfx->nonce, unlinked->is_client, + END_CIRC_REASON_TORPROTOCOL); + err = ERR_LINK_CIRC_INVALID_LEG; + goto end; + } + + /* Check all linked status. All need to be true in order to finalize the set + * and move it to the linked pool. */ + SMARTLIST_FOREACH_BEGIN(unlinked->legs, const leg_t *, leg) { + /* We are still waiting on a leg. */ + if (!leg->linked) { + log_info(LD_CIRC, "Can't finalize conflux set, still waiting on at " + "least one leg to link up."); + + goto end; + } + } SMARTLIST_FOREACH_END(leg); + + /* Finalize the cfx object by adding all legs into it. */ + SMARTLIST_FOREACH_BEGIN(unlinked->legs, leg_t *, leg) { + /* Removing the leg from the list is important so we avoid ending up with a + * leg in the unlinked list that is set with LINKED purpose. */ + SMARTLIST_DEL_CURRENT(unlinked->legs, leg); + + /* We are ready to attach the leg to the cfx object now. */ + cfx_add_leg(unlinked->cfx, leg); + + /* Clean the pending nonce and set the conflux object in the circuit. */ + leg->circ->conflux = unlinked->cfx; + + /* We are done with this leg object. */ + leg_free(leg); + } SMARTLIST_FOREACH_END(leg); + + is_client = unlinked->is_client; + + /* Add the conflux object to the linked pool. For an existing linked cfx + * object, we'll simply replace it with itself. */ + linked_pool_add(unlinked->cfx, is_client); + + /* Remove it from the unlinked pool. */ + unlinked_pool_del(unlinked, is_client); + + /* We don't recover a leg when it is linked but if we would like to support + * session ressumption, this would be very important in order to allow new + * legs to be created/recovered. */ + unlinked->cfx->num_leg_launch = 0; + + /* Nullify because we are about to free the unlinked object and the cfx has + * moved to all circuits. */ + unlinked->cfx = NULL; + unlinked_free(unlinked); + + /* Now that this set is ready to use, try any pending streams again. */ + if (is_client) { + connection_ap_attach_pending(1); + } + + log_info(LD_CIRC, + "Successfully linked a conflux %s set which is now usable.", + is_client ? "client" : "relay"); + + end: + return err; +} + +/** Record the RTT for this client circuit. + * + * Return the RTT value. UINT64_MAX is returned if we couldn't find the initial + * measurement of when the cell was sent or if the leg is missing. */ +static uint64_t +record_rtt_client(const circuit_t *circ) +{ + tor_assert(circ); + tor_assert(circ->conflux_pending_nonce); + tor_assert(CIRCUIT_IS_ORIGIN(circ)); + + leg_t *leg = unlinked_leg_find(circ, true); + if (leg && leg->link_sent_usec > 0) { + leg->rtt_usec = monotime_absolute_usec() - leg->link_sent_usec; + return leg->rtt_usec; + } + return UINT64_MAX; +} + +/** Record the RTT for this Exit circuit. + * + * Return the RTT value. UINT64_MAX is returned if we couldn't find the initial + * measurement of when the cell was sent or if the leg is missing. */ + +static uint64_t +record_rtt_exit(const circuit_t *circ) +{ + tor_assert(circ); + tor_assert(circ->conflux); + tor_assert(CIRCUIT_IS_ORCIRC(circ)); + + conflux_leg_t *leg = conflux_get_leg(circ->conflux, circ); + if (leg && leg->linked_sent_usec > 0) { + leg->circ_rtts_usec = monotime_absolute_usec() - leg->linked_sent_usec; + return leg->circ_rtts_usec; + } + return UINT64_MAX; +} + +/** For the given circuit, record the RTT from when the LINK or LINKED cell was + * sent that is this function works for either client or Exit. + * + * Return false if the RTT is too high for our standard else true. */ +static bool +record_rtt(const circuit_t *circ, bool is_client) +{ + uint64_t rtt_usec; + + tor_assert(circ); + + if (is_client) { + rtt_usec = record_rtt_client(circ); + + if (rtt_usec >= get_circuit_build_timeout_ms()*1000) { + log_info(LD_CIRC, "Conflux leg RTT is above circuit build time out " + "currently at %f msec. Relaunching.", + get_circuit_build_timeout_ms()); + return false; + } + } else { + rtt_usec = record_rtt_exit(circ); + } + + return true; +} + +/** Link the given circuit within its unlinked set. This is called when either + * the LINKED or LINKED_ACK is received depending on which side of the circuit + * it is. + * + * It attempts to finalize the unlinked set as well which, if successful, puts + * it in the linked pool. */ +static link_circ_err_t +link_circuit(circuit_t *circ) +{ + link_circ_err_t err = ERR_LINK_CIRC_OK; + unlinked_circuits_t *unlinked = NULL; + bool is_client = false; + + tor_assert(circ); + if (CIRCUIT_IS_ORIGIN(circ)) { + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + is_client = true; + } + + unlinked = unlinked_pool_get(circ->conflux_pending_nonce, is_client); + if (BUG(!unlinked)) { + log_warn(LD_BUG, "Failed to find the unlinked set %s when linking. " + "Closing circuit.", + fmt_nonce(circ->conflux_pending_nonce)); + err = ERR_LINK_CIRC_MISSING_SET; + goto end; + } + + leg_t *leg = leg_find(unlinked, circ); + if (BUG(!leg)) { + /* Failure to find the leg when linking a circuit is an important problem + * so log loudly and error. */ + log_warn(LD_BUG, "Failed to find leg for the unlinked set %s when " + "linking. Closing circuit.", + fmt_nonce(unlinked->cfx->nonce)); + err = ERR_LINK_CIRC_MISSING_LEG; + goto end; + } + + /* Successful link. Attempt to finalize the set in case this was the last + * LINKED or LINKED_ACK cell to receive. */ + leg->linked = true; + err = try_finalize_set(unlinked); + + end: + return err; +} + +static unlinked_circuits_t * +unlinked_get_or_create(const uint8_t *nonce, bool is_client) +{ + unlinked_circuits_t *unlinked; + + tor_assert(nonce); + + unlinked = unlinked_pool_get(nonce, is_client); + if (!unlinked) { + unlinked = unlinked_new(nonce, is_client); + + /* If this is a leg of an existing linked set, use that conflux object + * instead so all legs point to the same. It is put in the leg's circuit + * once the link is confirmed. */ + conflux_t *cfx = linked_pool_get(nonce, is_client); + if (cfx) { + conflux_free(unlinked->cfx); + unlinked->cfx = cfx; + unlinked->is_for_linked_set = true; + } + /* Add this set to the unlinked pool. */ + unlinked_pool_add(unlinked, is_client); + } + + return unlinked; +} + +/** + * On the client side, we need to determine if there is already + * an exit in use for this set, and if so, use that. + * + * Otherwise, we return NULL and the exit is decided by the + * circuitbuild.c code. + */ +static extend_info_t * +get_exit_for_nonce(const uint8_t *nonce) +{ + extend_info_t *exit = NULL; + + tor_assert(nonce); + + // First, check the linked pool for the nonce + const conflux_t *cfx = linked_pool_get(nonce, true); + if (cfx) { + tor_assert(cfx->legs); + /* Get the exit from the first leg */ + conflux_leg_t *leg = smartlist_get(cfx->legs, 0); + tor_assert(leg); + tor_assert(leg->circ); + tor_assert(TO_ORIGIN_CIRCUIT(leg->circ)->cpath); + exit = TO_ORIGIN_CIRCUIT(leg->circ)->cpath->prev->extend_info; + tor_assert(exit); + } else { + unlinked_circuits_t *unlinked = NULL; + unlinked = unlinked_pool_get(nonce, true); + + if (unlinked) { + tor_assert(unlinked->legs); + if (smartlist_len(unlinked->legs) > 0) { + /* Get the exit from the first leg */ + leg_t *leg = smartlist_get(unlinked->legs, 0); + tor_assert(leg); + tor_assert(leg->circ); + tor_assert(TO_ORIGIN_CIRCUIT(leg->circ)->cpath); + exit = TO_ORIGIN_CIRCUIT(leg->circ)->cpath->prev->extend_info; + tor_assert(exit); + } + } + } + + return exit; +} + +/** Return true iff the given conflux object is allowed to launch a new leg. If + * the cfx object is NULL, then it is always allowed to launch a new leg. */ +static bool +launch_leg_is_allowed(const conflux_t *cfx) +{ + if (!cfx) { + goto allowed; + } + + /* The maximum number of retry is the minimum number of legs we are allowed + * per set plus the maximum amount of retries we are allowed to do. */ + unsigned int max_num_launch = + conflux_params_get_num_legs_set() + + conflux_params_get_max_unlinked_leg_retry(); + + /* Only log once per nonce if we've reached the maximum. */ + if (cfx->num_leg_launch == max_num_launch) { + log_info(LD_CIRC, "Maximum number of leg launch reached for nonce %s", + fmt_nonce(cfx->nonce)); + } + + if (cfx->num_leg_launch >= max_num_launch) { + return false; + } + + allowed: + return true; +} + +/* + * Public API. + */ + +/** Launch a new conflux leg for the given nonce. + * + * Return true on success else false which teardowns the entire unlinked set if + * any. */ +bool +conflux_launch_leg(const uint8_t *nonce) +{ + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_NEED_CAPACITY | + CIRCLAUNCH_NEED_CONFLUX; + unlinked_circuits_t *unlinked = NULL; + extend_info_t *exit = NULL; + + tor_assert(nonce); + + /* Get or create a new unlinked object for this leg. */ + unlinked = unlinked_get_or_create(nonce, true); + tor_assert(unlinked); + + /* If we have an existing linked set, validate the number of leg retries + * before attempting the launch. */ + if (!launch_leg_is_allowed(unlinked->cfx)) { + goto err; + } + + exit = get_exit_for_nonce(nonce); + + if (exit) { + log_info(LD_CIRC, "Launching conflux leg for nonce %s.", fmt_nonce(nonce)); + } else { + log_info(LD_CIRC, "Launching new conflux set for nonce %s.", + fmt_nonce(nonce)); + } + + origin_circuit_t *circ = + circuit_establish_circuit_conflux(nonce, CIRCUIT_PURPOSE_CONFLUX_UNLINKED, + exit, flags); + if (!circ) { + goto err; + } + tor_assert(TO_CIRCUIT(circ)->conflux_pending_nonce); + + /* At this point, the unlinked object has either a new conflux_t or the one + * used by a linked set so it is fine to use the cfx from the unlinked object + * from now on. */ + + /* Get the max_seq_sent and recv from the linked pool, if it exists, and pass + * to new link cell. */ + uint64_t last_seq_sent = conflux_get_max_seq_sent(unlinked->cfx); + uint64_t last_seq_recv = unlinked->cfx->last_seq_delivered; + + // TODO-329-ARTI: To support resumption/retransmit, the client should store + // the last_seq_sent now, so that it can know how much data to retransmit to + // the server after link. C-Tor will not be implementing this, but arti and + // arti-relay could (if resumption seems worthwhile; it may not be worth the + // memory storage there, either). + + /* We have a circuit, create the new leg and attach it to the set. */ + leg_t *leg = leg_new(TO_CIRCUIT(circ), + conflux_cell_new_link(nonce, + last_seq_sent, last_seq_recv, + true)); + + /* Increase the retry count for this conflux object as in this nonce. */ + unlinked->cfx->num_leg_launch++; + + unlinked_leg_add(unlinked, leg); + return true; + + err: + return false; +} + +/** The given circuit is conflux pending and has closed. This deletes the leg + * from the set, attempt to finalize it and relaunch a new leg. If the set is + * empty after removing this leg, it is deleted. */ +static void +unlinked_circuit_closed(circuit_t *circ) +{ + uint8_t nonce[DIGEST256_LEN]; + unlinked_circuits_t *unlinked = NULL; + bool is_client = false; + + tor_assert(circ); + tor_assert(circ->conflux_pending_nonce); + + if (CIRCUIT_IS_ORIGIN(circ)) { + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + is_client = true; + } + + unlinked = unlinked_pool_get(circ->conflux_pending_nonce, is_client); + + /* This circuit is part of set that has already been removed previously freed + * by another leg closing. */ + if (!unlinked) { + return; + } + + /* We keep the nonce here because we will try to recover if we can and the + * pending nonce will get nullified early. */ + memcpy(nonce, circ->conflux_pending_nonce, sizeof(nonce)); + + log_info(LD_CIRC, "Conflux unlinked circuit with nonce %s has closed", + fmt_nonce(nonce)); + + /* Remove leg from set. */ + unlinked_leg_del_and_free(unlinked, circ); + /* The circuit pending nonce has been nullified at this point. */ + + /* If no more legs, opportunistically free the unlinked set. */ + if (smartlist_len(unlinked->legs) == 0) { + unlinked_pool_del_and_free(unlinked, is_client); + } else if (!shutting_down) { + /* Launch a new leg for this set to recover. */ + if (CIRCUIT_IS_ORIGIN(circ)) { + conflux_launch_leg(nonce); + } + } + /* After this, it might have been freed. */ + unlinked = NULL; + + /* Unlinked circuits should not have attached streams, but check + * anyway, because The Maze. */ + validate_circ_has_no_streams(circ); +} + +/** Update all stream pointers to point to this circuit. + * This is used when a linked circuit is closed and we need to update the + * streams to point to the remaining circuit + */ +static void +linked_update_stream_backpointers(circuit_t *circ) +{ + tor_assert(circ); + tor_assert_nonfatal(circ->conflux); + + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED); + /* Iterate over stream list using next_stream pointer, until null */ + for (edge_connection_t *stream = ocirc->p_streams; stream; + stream = stream->next_stream) { + /* Update the circuit pointer of each stream */ + stream->on_circuit = circ; + stream->cpath_layer = ocirc->cpath->prev; + } + } else { + or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + /* Iterate over stream list using next_stream pointer, until null */ + for (edge_connection_t *stream = orcirc->n_streams; stream; + stream = stream->next_stream) { + /* Update the circuit pointer of each stream */ + stream->on_circuit = circ; + } + /* Iterate over stream list using next_stream pointer, until null */ + for (edge_connection_t *stream = orcirc->resolving_streams; stream; + stream = stream->next_stream) { + /* Update the circuit pointer of each stream */ + stream->on_circuit = circ; + } + } +} + +/** Nullify all streams of the given circuit. */ +static void +linked_nullify_streams(circuit_t *circ) +{ + tor_assert(circ); + + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + ocirc->p_streams = NULL; + ocirc->half_streams = NULL; + } else { + or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + orcirc->n_streams = NULL; + orcirc->resolving_streams = NULL; + } +} + +/** The given circuit is already linked to a set and has been closed. Remove it + * from the set and free the pool if no more legs. */ +static void +linked_circuit_closed(circuit_t *circ) +{ + bool is_client = false; + bool full_teardown = false; + uint8_t nonce[DIGEST256_LEN] = {0}; + + tor_assert(circ); + tor_assert(circ->conflux); + + if (CIRCUIT_IS_ORIGIN(circ)) { + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED); + is_client = true; + } + + /* Unlink circuit from its conflux object. */ + full_teardown = cfx_del_leg(circ->conflux, circ); + + if (CONFLUX_NUM_LEGS(circ->conflux) == 0) { + /* Last leg, remove conflux object from linked set. */ + linked_pool_del(circ->conflux->nonce, is_client); + } else { + /* If there are other circuits, update streams backpointers and + * nullify the stream lists. We do not free those streams in circuit_free_. + * (They only get freed when the last circuit is freed). */ + conflux_leg_t *leg = smartlist_get(circ->conflux->legs, 0); + linked_update_stream_backpointers(leg->circ); + linked_nullify_streams(circ); + } + + /* Keep the nonce so we can use it through out the rest of the function in + * case we nullify the conflux object before. Reason is that in the case of a + * full teardown, this function becomes basically recursive and so we must + * nullify the conflux object of this circuit now before the recursiveness + * starts leading to all legs being removed and thus not noticing if we are + * the last or the first. + * + * Not the prettiest but that is the price to pay to live in the C-tor maze + * and protected by ballrogs. */ + memcpy(nonce, circ->conflux->nonce, sizeof(nonce)); + + /* Nullify the conflux object from the circuit being closed iff we have more + * legs. Reason being that the last leg needs to have the conflux object + * attached to the circuit so it can be freed in conflux_circuit_free(). */ + if (CONFLUX_NUM_LEGS(circ->conflux) > 0) { + circ->conflux = NULL; + } + + /* If this was a teardown condition, we need to mark other circuits, + * including any potential unlinked circuits, for close. + * + * This call is recursive in the sense that linked_circuit_closed() will end + * up being called for all legs and so by the time we come back here, the + * linked is likely entirely gone. Thus why this is done last. */ + if (full_teardown) { + conflux_mark_all_for_close(nonce, is_client, END_CIRC_REASON_FINISHED); + } +} + +/** The given circuit is being freed and it is a linked leg. Clean up and free + * anything that has to do with this circuit. + * + * After this call, the circuit should NOT be referenced anymore anywhere. */ +static void +linked_circuit_free(circuit_t *circ, bool is_client) +{ + tor_assert(circ); + tor_assert(circ->conflux); + if (is_client) { + tor_assert(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED); + } + + /* Circuit can be freed without being closed and so we try to delete this leg + * so we can learn if this circuit is the last leg or not. */ + cfx_del_leg(circ->conflux, circ); + + if (CONFLUX_NUM_LEGS(circ->conflux) > 0) { + /* The last leg will free the streams but until then, we nullify to avoid + * use-after-free. */ + linked_nullify_streams(circ); + } else { + /* We are the last leg. */ + + /* Remove from pool in case it is still lingering there else we'll end up + * in a double free situation. */ + linked_pool_del(circ->conflux->nonce, is_client); + + /* If there is an unlinked circuit that was also created for this set, we + * need to look for it, and tell it is no longer part of a linked set + * anymore, so it can be freed properly, or can complete the link if it is + * able to. Effectively, the conflux_t object lifetime is longer than + * either the linked or unlinked sets by themselves. This is a situation we + * could cover with handles, but so far, it is not clear they are an + * obvious benefit for other cases than this one. */ + unlinked_circuits_t *unlinked = + unlinked_pool_get(circ->conflux->nonce, is_client); + if (unlinked) { + tor_assert(unlinked->is_for_linked_set); + unlinked->is_for_linked_set = false; + } else { + /* We are the last one, clear the conflux object. If an unlinked object + * has a reference to it, it won't get freed due to is_for_linked_set + * flag. */ + conflux_free(circ->conflux); + } + } +} + +/** The given circuit is being freed and it is an unlinked leg. Clean up and + * free anything that has to do with this circuit. + * + * After this call, the circuit should NOT be referenced anymore anywhere. */ +static void +unlinked_circuit_free(circuit_t *circ, bool is_client) +{ + tor_assert(circ); + tor_assert(circ->conflux_pending_nonce); + if (is_client) { + tor_assert(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + } + + /* Cleanup circuit reference if a leg exists. This is possible if the circuit + * was not marked for close before being freed. */ + leg_t *leg = unlinked_leg_find(circ, is_client); + if (leg) { + leg->circ = NULL; + } + + /* Null pointers are safe here. */ + tor_free(circ->conflux_pending_nonce); +} + +/** Circuit has been marked for close. */ +void +conflux_circuit_has_closed(circuit_t *circ) +{ + /* The unlinked case. If an unlinked set exists, we delete the leg and then + * attempt to finalize it. After that, we'll launch a new leg to recover. */ + if (circ->conflux_pending_nonce) { + unlinked_circuit_closed(circ); + } else if (circ->conflux) { + linked_circuit_closed(circ); + } +} + +/** Circuit with conflux purpose just opened. */ +void +conflux_circuit_has_opened(origin_circuit_t *orig_circ) +{ + circuit_t *circ = NULL; + leg_t *leg = NULL; + + tor_assert(orig_circ); + + circ = TO_CIRCUIT(orig_circ); + + /* Extra safety layer so we never let a circuit opens if conflux is not + * enabled. */ + if (!conflux_is_enabled(circ)) { + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + static ratelim_t conflux_ratelim = RATELIM_INIT(600); + log_fn_ratelim(&conflux_ratelim, LOG_NOTICE, LD_CIRC, + "Conflux circuit opened without negotiating " + "congestion control"); + return; + } + + /* Unrelated to conflux. */ + if (circ->conflux_pending_nonce == NULL) { + goto end; + } + + log_info(LD_CIRC, "Conflux circuit has opened with nonce %s", + fmt_nonce(circ->conflux_pending_nonce)); + + leg = unlinked_leg_find(circ, true); + if (BUG(!leg)) { + log_warn(LD_CIRC, "Unable to find conflux leg in unlinked set."); + goto end; + } + + /* On failure here, the circuit is closed and thus the leg and unlinked set + * will be cleaned up. */ + if (!conflux_cell_send_link(leg->link, orig_circ)) { + goto end; + } + + /* Mark the leg on when the LINK cell is sent. Used to timeout the circuit + * for a minimum RTT when getting the LINKED. */ + leg->link_sent_usec = monotime_absolute_usec(); + + end: + validate_circ_has_no_streams(circ); + return; +} + +/** Process a CONFLUX_LINK cell which arrived on the given circuit. */ +void +conflux_process_link(circuit_t *circ, const cell_t *cell, + const uint16_t cell_len) +{ + unlinked_circuits_t *unlinked = NULL; + conflux_cell_link_t *link = NULL; + + tor_assert(circ); + tor_assert(cell); + + if (!conflux_is_enabled(circ)) { + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + /* This cell can't be received on an origin circuit because only the endpoint + * creating the circuit sends it. */ + if (CIRCUIT_IS_ORIGIN(circ)) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Got a CONFLUX_LINK cell on an origin circuit. Closing circuit."); + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + if (!conflux_validate_source_hop(circ, NULL)) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Got a CONFLUX_LINK with further hops. Closing circuit."); + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + if (circ->conflux_pending_nonce) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Got a CONFLUX_LINK on a circuit with a pending nonce. " + "Closing circuit."); + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + if (circ->conflux) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Got a CONFLUX_LINK on an already linked circuit " + "Closing circuit."); + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + /* On errors, logging is emitted in this parsing function. */ + link = conflux_cell_parse_link(cell, cell_len); + if (!link) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, "Unable to parse " + "CONFLUX_LINK cell. Closing circuit."); + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + log_info(LD_CIRC, "Processing a CONFLUX_LINK for set %s", + fmt_nonce(link->nonce)); + + /* Consider this circuit a new leg. We'll now attempt to attach it to an + * existing set or unlinked one. */ + leg_t *leg = leg_new(circ, link); + unlinked = unlinked_get_or_create(link->nonce, false); + tor_assert(unlinked); + + /* Attach leg to the unlinked set. */ + unlinked_leg_add(unlinked, leg); + + /* Set the circuit in a pending conflux state for the LINKED_ACK. */ + circ->conflux_pending_nonce = tor_memdup(leg->link->nonce, + sizeof(leg->link->nonce)); + + /* Mark when we send the LINKED. */ + leg->link_sent_usec = monotime_absolute_usec(); + + /* Send LINKED. */ + uint64_t last_seq_sent = conflux_get_max_seq_sent(unlinked->cfx); + uint64_t last_seq_recv = unlinked->cfx->last_seq_delivered; + + // TODO-329-ARTI: To support resumption/retransmit, the server should + // store the last_seq_sent now, so that it can know how much data + // to retransmit to the server after link. C-Tor will not be implementing + // this, but arti and arti-relay could (if resumption seems worthwhile; + // it may not be worth the memory storage there, either). + + uint8_t nonce[DIGEST256_LEN]; + memcpy(nonce, circ->conflux_pending_nonce, sizeof(nonce)); + + /* Link the circuit to the a conflux set immediately before the LINKED is + * sent. Reason is that once the client sends the LINKED_ACK, there is a race + * with the BEGIN cell that can be sent immediately after and arrive first. + * And so, we need to sync the streams before that happens that is before we + * receive the LINKED_ACK. */ + if (link_circuit(circ) != ERR_LINK_CIRC_OK) { + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + conflux_cell_link_t *linked = conflux_cell_new_link(nonce, last_seq_sent, + last_seq_recv, false); + + conflux_cell_send_linked(linked, TO_OR_CIRCUIT(circ)); + tor_free(linked); + + end: + return; +} + +/** Process a CONFLUX_LINKED cell which arrived on the given circuit. */ +void +conflux_process_linked(circuit_t *circ, crypt_path_t *layer_hint, + const cell_t *cell, + const uint16_t cell_len) +{ + conflux_cell_link_t *link = NULL; + + tor_assert(circ); + + /* + * There several ways a malicious exit could create problems when sending + * back this LINKED cell. + * + * 1. Using a different nonce that it knows about from another set. Accepting + * it would mean a confirmation attack of linking sets to the same client. + * To address that, the cell nonce MUST be matched with the circuit nonce. + * + * 2. Re-Sending a LINKED cell on an already linked circuit could create side + * channel attacks or unpredictable issues. Circuit is closed. + * + * 3. Receiving a LINKED cell on a circuit that was not expecting it. Again, + * as (2), can create side channel(s). Circuit is closed. + * + * 4. Receiving a LINKED cell from the another hop other than the last one + * (exit). Same as (2) and (3) in terms of issues. Circuit is closed. + */ + + if (!conflux_is_enabled(circ)) { + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + /* LINKED cell are in response to a LINK cell which are only sent on an + * origin circuit and thus received on such.*/ + if (!CIRCUIT_IS_ORIGIN(circ)) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Received CONFLUX_LINKED cell on a non origin circuit."); + goto close; + } + + if (!circ->conflux_pending_nonce) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Received a CONFLUX_LINKED cell without having sent a " + "CONFLUX_LINK cell. Closing circuit."); + goto close; + } + + if (circ->conflux) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Received a CONFLUX_LINKED cell on a circuit that is already " + "linked. Closing circuit."); + goto close; + } + + if (!conflux_validate_source_hop(circ, layer_hint)) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Got a CONFLUX_LINKED from wrong hop on circuit. Closing circuit."); + goto close; + } + + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + + /* On errors, logging is emitted in this parsing function. */ + link = conflux_cell_parse_link(cell, cell_len); + if (!link) { + goto close; + } + + log_info(LD_CIRC, "Processing a CONFLUX_LINKED for set %s", + fmt_nonce(link->nonce)); + + /* Make sure the cell nonce matches the one on the circuit that was + * previously set by the CONFLUX_LINK cell. */ + if (tor_memneq(link->nonce, circ->conflux_pending_nonce, + sizeof(*link->nonce))) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Received CONFLUX_LINKED but circuit nonce doesn't match " + "cell nonce. Closing circuit."); + goto close; + } + + /* Find the leg from the associated unlinked set. */ + leg_t *leg = unlinked_leg_find(circ, true); + if (BUG(!leg)) { + log_warn(LD_CIRC, "Received CONFLUX_LINKED but can't find " + "associated leg. Closing circuit."); + goto close; + } + + log_info(LD_CIRC, "Successfully processed a CONFLUX_LINKED cell."); + + /* Free the old link, and store the new one. We need to validate + * the one we get during finalize, not the one we sent. */ + tor_free(leg->link); + leg->link = link; + + /* Record the RTT for this circuit. On failure, it means the RTT was too + * high, we relaunch to recover. */ + if (!record_rtt(circ, true)) { + goto close; + } + + /* The following will link the circuit with its set and attempt to finalize + * the set if all expected legs have linked. On error, we close the circuit + * because it means the unlinked set needs to be teardowned. */ + link_circ_err_t err = link_circuit(circ); + switch (err) { + case ERR_LINK_CIRC_OK: + /* Successfully linked. */ + break; + case ERR_LINK_CIRC_INVALID_LEG: + case ERR_LINK_CIRC_MISSING_SET: + /* No relaunch if the leg is invalid or the set is not found as in the + * nonce is unknown. */ + break; + case ERR_LINK_CIRC_BAD_RTT: + case ERR_LINK_CIRC_MISSING_LEG: + goto close; + } + + /* We can send the ack only if we finalize. This will not cause issues, + * because LINKED_ACK is exempted from multiplexing in + * conflux_should_multiplex(). */ + if (!conflux_cell_send_linked_ack(TO_ORIGIN_CIRCUIT(circ))) { + /* On failure, the circuit is closed by the underlying function(s). */ + goto end; + } + + goto end; + + close: + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + + end: + return; +} + +/** Process a CONFLUX_LINKED_ACK cell which arrived on the given circuit. */ +void +conflux_process_linked_ack(circuit_t *circ) +{ + tor_assert(circ); + + if (!conflux_is_enabled(circ)) { + goto close; + } + + if (CIRCUIT_IS_ORIGIN(circ)) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Received CONFLUX_LINKED_ACK cell on an origin circuit. Closing."); + goto close; + } + + if (!conflux_validate_source_hop(circ, NULL)) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Got a CONFLUX_LINKED_ACK with further hops. Closing circuit."); + goto close; + } + + if (BUG(!circ->conflux)) { + log_fn(LOG_PROTOCOL_WARN, LD_CIRC, + "Received a CONFLUX_LINKED_ACK cell on a circuit that is not" + "linked. Closing circuit."); + goto close; + } + + log_info(LD_CIRC, "Processing a CONFLUX_LINKED_ACK for set %s", + fmt_nonce(circ->conflux->nonce)); + + /* Record the RTT for this circuit. This should not fail */ + if (BUG(!record_rtt(circ, false))) { + goto close; + } + + return; + + close: + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); +} + +/** Called when a circuit is freed. + * + * It is possible a conflux circuit gets freed without being closed (for + * instance SIGTERM) and so this callback is needed in order to finalize the + * cleanup. */ +void +conflux_circuit_about_to_free(circuit_t *circ) +{ + tor_assert(circ); + + bool is_client = CIRCUIT_IS_ORIGIN(circ); + + if (circ->conflux) { + linked_circuit_free(circ, is_client); + } else if (circ->conflux_pending_nonce) { + unlinked_circuit_free(circ, is_client); + } + + /* Whatever happens, nullify all conflux related pointers. */ + circ->conflux = NULL; + circ->conflux_pending_nonce = NULL; +} + +/** Initialize the conflux pool subsystem. This is called by the subsys + * manager. */ +void +conflux_pool_init(void) +{ + if (!client_linked_pool) { + client_linked_pool = digest256map_new(); + } + if (!client_unlinked_pool) { + client_unlinked_pool = digest256map_new(); + } + if (!server_linked_pool) { + server_linked_pool = digest256map_new(); + } + if (!server_unlinked_pool) { + server_unlinked_pool = digest256map_new(); + } +} + +/** Free and clean up the conflux pool subsystem. This is called by the subsys + * manager AFTER all circuits have been freed which implies that all objects in + * the pools aren't referenced anymore. */ +void +conflux_pool_free_all(void) +{ + shutting_down = true; + + digest256map_free(client_linked_pool, free_conflux_void_); + digest256map_free(server_linked_pool, free_conflux_void_); + digest256map_free(client_unlinked_pool, free_unlinked_void_); + digest256map_free(server_unlinked_pool, free_unlinked_void_); +} diff --git a/src/core/or/conflux_pool.h b/src/core/or/conflux_pool.h new file mode 100644 index 0000000000..4276049485 --- /dev/null +++ b/src/core/or/conflux_pool.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_pool.h + * \brief Header file for conflux_pool.c. + **/ + +#ifndef TOR_CONFLUX_POOL_H +#define TOR_CONFLUX_POOL_H + +#include "core/or/or.h" + +void conflux_pool_init(void); +void conflux_pool_free_all(void); + +origin_circuit_t *conflux_get_circ_for_conn(const entry_connection_t *conn, + time_t now); + +void conflux_predict_new(time_t now); + +bool conflux_launch_leg(const uint8_t *nonce); + +void conflux_circuit_has_closed(circuit_t *circ); +void conflux_circuit_has_opened(origin_circuit_t *orig_circ); +void conflux_circuit_about_to_free(circuit_t *circ); + +void conflux_process_link(circuit_t *circ, const cell_t *cell, + const uint16_t cell_len); +void conflux_process_linked(circuit_t *circ, crypt_path_t *layer_hint, + const cell_t *cell, const uint16_t cell_len); +void conflux_process_linked_ack(circuit_t *circ); + +#ifdef TOR_UNIT_TESTS +bool launch_new_set(int num_legs); +digest256map_t *get_linked_pool(bool is_client); +digest256map_t *get_unlinked_pool(bool is_client); +#endif /* defined(UNIT_TESTS) */ + +#endif /* TOR_CONFLUX_POOL_H */ + diff --git a/src/core/or/conflux_sys.c b/src/core/or/conflux_sys.c new file mode 100644 index 0000000000..7429b08b8f --- /dev/null +++ b/src/core/or/conflux_sys.c @@ -0,0 +1,37 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_sys.c + * \brief Register the conflux pool for early initialization. + **/ + +#include "core/or/conflux_pool.h" +#include "core/or/conflux_sys.h" + +#include "lib/subsys/subsys.h" + +static int +subsys_conflux_initialize(void) +{ + conflux_pool_init(); + return 0; +} + +static void +subsys_conflux_shutdown(void) +{ + /* The conflux pool free all must be called before the circuit free all and + * so we are not calling it from subsys shutdown. */ +} + +const subsys_fns_t sys_conflux = { + SUBSYS_DECLARE_LOCATION(), + + .name = "conflux", + .supported = true, + .level = CONFLUX_SUBSYS_LEVEL, + + .initialize = subsys_conflux_initialize, + .shutdown = subsys_conflux_shutdown, +}; diff --git a/src/core/or/conflux_sys.h b/src/core/or/conflux_sys.h new file mode 100644 index 0000000000..f6fb85e007 --- /dev/null +++ b/src/core/or/conflux_sys.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conflux_sys.h + * \brief Header file for conflux_sys.c. + **/ + +#ifndef TOR_CONFLUX_SYS_H +#define TOR_CONFLUX_SYS_H + +extern const struct subsys_fns_t sys_conflux; + +/** + * Subsystem level. + * + * Defined here so that it can be shared between the real and stub + * definitions. + **/ +#define CONFLUX_SUBSYS_LEVEL (10) + +#endif /* TOR_CONFLUX_SYS_H */ + diff --git a/src/core/or/cpath_build_state_st.h b/src/core/or/cpath_build_state_st.h index 2dc682de81..d4fba552ae 100644 --- a/src/core/or/cpath_build_state_st.h +++ b/src/core/or/cpath_build_state_st.h @@ -30,6 +30,8 @@ struct cpath_build_state_t { * These are for encrypted dir conns that exit to this router, not * for arbitrary exits from the circuit. */ unsigned int onehop_tunnel : 1; + /** Indicating the exit needs to support Conflux. */ + unsigned int need_conflux: 1; /** How many times has building a circuit for this task failed? */ int failure_count; /** At what time should we give up on this task? */ diff --git a/src/core/or/or.h b/src/core/or/or.h index ece2475613..088c45342b 100644 --- a/src/core/or/or.h +++ b/src/core/or/or.h @@ -185,18 +185,27 @@ struct curve25519_public_key_t; #define RELAY_COMMAND_DATA 2 #define RELAY_COMMAND_END 3 #define RELAY_COMMAND_CONNECTED 4 + #define RELAY_COMMAND_SENDME 5 #define RELAY_COMMAND_EXTEND 6 #define RELAY_COMMAND_EXTENDED 7 #define RELAY_COMMAND_TRUNCATE 8 #define RELAY_COMMAND_TRUNCATED 9 #define RELAY_COMMAND_DROP 10 + #define RELAY_COMMAND_RESOLVE 11 #define RELAY_COMMAND_RESOLVED 12 + #define RELAY_COMMAND_BEGIN_DIR 13 #define RELAY_COMMAND_EXTEND2 14 #define RELAY_COMMAND_EXTENDED2 15
+/* Conflux */ +#define RELAY_COMMAND_CONFLUX_LINK 19 +#define RELAY_COMMAND_CONFLUX_LINKED 20 +#define RELAY_COMMAND_CONFLUX_LINKED_ACK 21 +#define RELAY_COMMAND_CONFLUX_SWITCH 22 + #define RELAY_COMMAND_ESTABLISH_INTRO 32 #define RELAY_COMMAND_ESTABLISH_RENDEZVOUS 33 #define RELAY_COMMAND_INTRODUCE1 34 diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 58e48df902..131432b7c1 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -100,6 +100,8 @@ #include "core/or/congestion_control_common.h" #include "core/or/congestion_control_flow.h" #include "core/or/conflux.h" +#include "core/or/conflux_util.h" +#include "core/or/conflux_pool.h"
static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, @@ -450,7 +452,6 @@ relay_lookup_conn(circuit_t *circ, cell_t *cell, /* IN or OUT cells could have come from either direction, now * that we allow rendezvous *to* an OP. */ - if (CIRCUIT_IS_ORIGIN(circ)) { for (tmpconn = TO_ORIGIN_CIRCUIT(circ)->p_streams; tmpconn; tmpconn=tmpconn->next_stream) { @@ -544,6 +545,10 @@ relay_command_to_string(uint8_t command) case RELAY_COMMAND_EXTENDED2: return "EXTENDED2"; case RELAY_COMMAND_PADDING_NEGOTIATE: return "PADDING_NEGOTIATE"; case RELAY_COMMAND_PADDING_NEGOTIATED: return "PADDING_NEGOTIATED"; + case RELAY_COMMAND_CONFLUX_LINK: return "CONFLUX_LINK"; + case RELAY_COMMAND_CONFLUX_LINKED: return "CONFLUX_LINKED"; + case RELAY_COMMAND_CONFLUX_LINKED_ACK: return "CONFLUX_LINKED_ACK"; + case RELAY_COMMAND_CONFLUX_SWITCH: return "CONFLUX_SWITCH"; default: tor_snprintf(buf, sizeof(buf), "Unrecognized relay command %u", (unsigned)command); @@ -782,6 +787,7 @@ connection_edge_send_command(edge_connection_t *fromconn, circuit_t *circ; crypt_path_t *cpath_layer = fromconn->cpath_layer; tor_assert(fromconn); + circ = fromconn->on_circuit;
if (fromconn->base_.marked_for_close) { @@ -1653,6 +1659,15 @@ handle_relay_cell_command(cell_t *cell, circuit_t *circ,
/* Now handle all the other commands */ switch (rh->command) { + case RELAY_COMMAND_CONFLUX_LINK: + conflux_process_link(circ, cell, rh->length); + return 0; + case RELAY_COMMAND_CONFLUX_LINKED: + conflux_process_linked(circ, layer_hint, cell, rh->length); + return 0; + case RELAY_COMMAND_CONFLUX_LINKED_ACK: + conflux_process_linked_ack(circ); + return 0; case RELAY_COMMAND_CONFLUX_SWITCH: return conflux_process_switch_command(circ, layer_hint, cell, rh); case RELAY_COMMAND_BEGIN: @@ -1713,7 +1728,6 @@ handle_relay_cell_command(cell_t *cell, circuit_t *circ, /* 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."); diff --git a/src/feature/nodelist/node_select.h b/src/feature/nodelist/node_select.h index 18a14ff0cb..cc2e656e70 100644 --- a/src/feature/nodelist/node_select.h +++ b/src/feature/nodelist/node_select.h @@ -34,6 +34,8 @@ typedef enum router_crn_flags_t { CRN_RENDEZVOUS_V3 = 1<<6, /* On clients, only provide nodes that can initiate IPv6 extends. */ CRN_INITIATE_IPV6_EXTEND = 1<<7, + /* On clients, only provide nodes that support Conflux (Relay=5). */ + CRN_CONFLUX = 1<<8, } router_crn_flags_t;
/** Possible ways to weight routers when choosing one randomly. See diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c index f3875670f0..63de68dda7 100644 --- a/src/feature/nodelist/routerlist.c +++ b/src/feature/nodelist/routerlist.c @@ -558,6 +558,7 @@ router_can_choose_node(const node_t *node, int flags) const bool direct_conn = (flags & CRN_DIRECT_CONN) != 0; const bool rendezvous_v3 = (flags & CRN_RENDEZVOUS_V3) != 0; const bool initiate_ipv6_extend = (flags & CRN_INITIATE_IPV6_EXTEND) != 0; + const bool need_conflux = (flags & CRN_CONFLUX) != 0;
const or_options_t *options = get_options(); const bool check_reach = @@ -592,6 +593,10 @@ router_can_choose_node(const node_t *node, int flags) if (rendezvous_v3 && !node_supports_v3_rendezvous_point(node)) return false; + /* Exclude relay that don't do conflux if requested. */ + if (need_conflux && !node_supports_conflux(node)) { + return false; + } /* Choose a node with an OR address that matches the firewall rules */ if (direct_conn && check_reach && !reachable_addr_allows_node(node,
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 46e473f43ee6aa920a779d37f7d2a28da64df383 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Fri Feb 3 02:11:10 2023 +0000
Prop#329 Pool: Avoid sharing Guards and Middles between circuits.
Conflux must not use the same Guard for each leg; nor the same middle for each leg. --- src/core/or/circuitbuild.c | 24 +++++--- src/core/or/circuitbuild.h | 3 +- src/core/or/conflux_pool.c | 123 ++++++++++++++++++++++++++++++++++++++++ src/core/or/conflux_pool.h | 5 ++ src/feature/client/entrynodes.c | 42 +++++++++++--- src/feature/client/entrynodes.h | 14 ++++- 6 files changed, 193 insertions(+), 18 deletions(-)
diff --git a/src/core/or/circuitbuild.c b/src/core/or/circuitbuild.c index 743d67acde..d6e022e7fa 100644 --- a/src/core/or/circuitbuild.c +++ b/src/core/or/circuitbuild.c @@ -45,6 +45,7 @@ #include "core/or/command.h" #include "core/or/connection_edge.h" #include "core/or/connection_or.h" +#include "core/or/conflux_pool.h" #include "core/or/extendinfo.h" #include "core/or/onion.h" #include "core/or/ocirc_event.h" @@ -89,7 +90,8 @@ static int circuit_send_first_onion_skin(origin_circuit_t *circ); static int circuit_build_no_more_hops(origin_circuit_t *circ); static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ, crypt_path_t *hop); -static const node_t *choose_good_middle_server(uint8_t purpose, +static const node_t *choose_good_middle_server(const origin_circuit_t *, + uint8_t purpose, cpath_build_state_t *state, crypt_path_t *head, int cur_len); @@ -2313,7 +2315,8 @@ build_vanguard_middle_exclude_list(uint8_t purpose, * hop, based on already chosen nodes. */ static smartlist_t * -build_middle_exclude_list(uint8_t purpose, +build_middle_exclude_list(const origin_circuit_t *circ, + uint8_t purpose, cpath_build_state_t *state, crypt_path_t *head, int cur_len) @@ -2330,6 +2333,9 @@ build_middle_exclude_list(uint8_t purpose,
excluded = smartlist_new();
+ // Exclude other middles on pending and built conflux circs + conflux_add_middles_to_exclude_list(circ, excluded); + /* For non-vanguard circuits, add the exit and its family to the exclude list * (note that the exit/last hop is always chosen first in * circuit_establish_circuit()). */ @@ -2423,7 +2429,8 @@ pick_vanguard_middle_node(const or_options_t *options, * family, and make sure we don't duplicate any previous nodes or their * families. */ static const node_t * -choose_good_middle_server(uint8_t purpose, +choose_good_middle_server(const origin_circuit_t * circ, + uint8_t purpose, cpath_build_state_t *state, crypt_path_t *head, int cur_len) @@ -2438,7 +2445,7 @@ choose_good_middle_server(uint8_t purpose, log_debug(LD_CIRC, "Contemplating intermediate hop #%d: random choice.", cur_len+1);
- excluded = build_middle_exclude_list(purpose, state, head, cur_len); + excluded = build_middle_exclude_list(circ, purpose, state, head, cur_len);
flags |= cpath_build_state_to_crn_flags(state); flags |= cpath_build_state_to_crn_ipv6_extend_flag(state, cur_len); @@ -2483,7 +2490,8 @@ choose_good_middle_server(uint8_t purpose, * guard worked or not. */ const node_t * -choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state, +choose_good_entry_server(const origin_circuit_t *circ, + uint8_t purpose, cpath_build_state_t *state, circuit_guard_state_t **guard_state_out) { const node_t *choice; @@ -2505,7 +2513,7 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state, /* This request is for an entry server to use for a regular circuit, * and we use entry guard nodes. Just return one of the guard nodes. */ tor_assert(guard_state_out); - return guards_choose_guard(state, purpose, guard_state_out); + return guards_choose_guard(circ, state, purpose, guard_state_out); }
excluded = smartlist_new(); @@ -2551,7 +2559,7 @@ onion_extend_cpath(origin_circuit_t *circ) if (cur_len == state->desired_path_len - 1) { /* Picking last node */ info = extend_info_dup(state->chosen_exit); } else if (cur_len == 0) { /* picking first node */ - const node_t *r = choose_good_entry_server(purpose, state, + const node_t *r = choose_good_entry_server(circ, purpose, state, &circ->guard_state); if (r) { /* If we're a client, use the preferred address rather than the @@ -2564,7 +2572,7 @@ onion_extend_cpath(origin_circuit_t *circ) } } else { const node_t *r = - choose_good_middle_server(purpose, state, circ->cpath, cur_len); + choose_good_middle_server(circ, purpose, state, circ->cpath, cur_len); if (r) { info = extend_info_from_node(r, 0, false); } diff --git a/src/core/or/circuitbuild.h b/src/core/or/circuitbuild.h index 4235ee96b2..c76259fc29 100644 --- a/src/core/or/circuitbuild.h +++ b/src/core/or/circuitbuild.h @@ -57,7 +57,8 @@ const char *build_state_get_exit_nickname(cpath_build_state_t *state);
struct circuit_guard_state_t;
-const node_t *choose_good_entry_server(uint8_t purpose, +const node_t *choose_good_entry_server(const origin_circuit_t *circ, + uint8_t purpose, cpath_build_state_t *state, struct circuit_guard_state_t **guard_state_out); void circuit_upgrade_circuits_from_guard_wait(void); diff --git a/src/core/or/conflux_pool.c b/src/core/or/conflux_pool.c index 66a405d4c1..c1b522fa67 100644 --- a/src/core/or/conflux_pool.c +++ b/src/core/or/conflux_pool.c @@ -31,6 +31,8 @@ #include "core/or/crypt_path_st.h" #include "core/or/or_circuit_st.h" #include "core/or/origin_circuit_st.h" +#include "core/or/extend_info_st.h" +#include "core/or/conflux_st.h"
#include "feature/nodelist/nodelist.h"
@@ -1092,6 +1094,127 @@ conflux_launch_leg(const uint8_t *nonce) return false; }
+/** + * Add the identity digest of the guard nodes of all legs of the conflux + * circuit. + * + * This function checks both pending and linked conflux circuits. + */ +void +conflux_add_guards_to_exclude_list(const origin_circuit_t *orig_circ, + smartlist_t *excluded) +{ + tor_assert(orig_circ); + tor_assert(excluded); + + /* Ease our lives. */ + const circuit_t *circ = TO_CIRCUIT(orig_circ); + + /* Ignore if this is not conflux related. */ + if (!CIRCUIT_IS_CONFLUX(circ)) { + return; + } + + /* When building a circuit, we should not have a conflux object + * ourselves (though one may exist elsewhere). */ + tor_assert(!circ->conflux); + + /* Getting here without a nonce is a code flow issue. */ + if (BUG(!circ->conflux_pending_nonce)) { + return; + } + + /* A linked set exists, use it. */ + const conflux_t *cfx = linked_pool_get(circ->conflux_pending_nonce, true); + if (cfx) { + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) { + const origin_circuit_t *ocirc = CONST_TO_ORIGIN_CIRCUIT(leg->circ); + smartlist_add(excluded, + tor_memdup(ocirc->cpath->extend_info->identity_digest, + DIGEST_LEN)); + } CONFLUX_FOR_EACH_LEG_END(leg); + } + + /* An unlinked set might exist for this nonce, if so, add the second hop of + * the existing legs to the exclusion list. */ + unlinked_circuits_t *unlinked = + unlinked_pool_get(circ->conflux_pending_nonce, true); + if (unlinked) { + tor_assert(unlinked->is_client); + SMARTLIST_FOREACH_BEGIN(unlinked->legs, leg_t *, leg) { + /* Convert to origin circ and get cpath */ + const origin_circuit_t *ocirc = CONST_TO_ORIGIN_CIRCUIT(leg->circ); + smartlist_add(excluded, + tor_memdup(ocirc->cpath->extend_info->identity_digest, + DIGEST_LEN)); + } SMARTLIST_FOREACH_END(leg); + } +} + +/** + * Add the identity digest of the middle nodes of all legs of the conflux + * circuit. + * + * This function checks both pending and linked conflux circuits. + * + * XXX: The add guard and middle could be merged since it is the exact same + * code except for the cpath position and the identity digest vs node_t in + * the list. We could use an extra param indicating guard or middle. */ +void +conflux_add_middles_to_exclude_list(const origin_circuit_t *orig_circ, + smartlist_t *excluded) +{ + tor_assert(orig_circ); + tor_assert(excluded); + + /* Ease our lives. */ + const circuit_t *circ = TO_CIRCUIT(orig_circ); + + /* Ignore if this is not conflux related. */ + if (!CIRCUIT_IS_CONFLUX(circ)) { + return; + } + + /* When building a circuit, we should not have a conflux object + * ourselves (though one may exist elsewhere). */ + tor_assert(!circ->conflux); + + /* Getting here without a nonce is a code flow issue. */ + if (BUG(!circ->conflux_pending_nonce)) { + return; + } + + /* A linked set exists, use it. */ + const conflux_t *cfx = linked_pool_get(circ->conflux_pending_nonce, true); + if (cfx) { + CONFLUX_FOR_EACH_LEG_BEGIN(cfx, leg) { + const origin_circuit_t *ocirc = CONST_TO_ORIGIN_CIRCUIT(leg->circ); + node_t *node = node_get_mutable_by_id( + ocirc->cpath->next->extend_info->identity_digest); + if (node) { + smartlist_add(excluded, node); + } + } CONFLUX_FOR_EACH_LEG_END(leg); + } + + /* An unlinked set might exist for this nonce, if so, add the second hop of + * the existing legs to the exclusion list. */ + unlinked_circuits_t *unlinked = + unlinked_pool_get(circ->conflux_pending_nonce, true); + if (unlinked) { + tor_assert(unlinked->is_client); + SMARTLIST_FOREACH_BEGIN(unlinked->legs, leg_t *, leg) { + /* Convert to origin circ and get cpath */ + const origin_circuit_t *ocirc = CONST_TO_ORIGIN_CIRCUIT(leg->circ); + node_t *node = node_get_mutable_by_id( + ocirc->cpath->next->extend_info->identity_digest); + if (node) { + smartlist_add(excluded, node); + } + } SMARTLIST_FOREACH_END(leg); + } +} + /** The given circuit is conflux pending and has closed. This deletes the leg * from the set, attempt to finalize it and relaunch a new leg. If the set is * empty after removing this leg, it is deleted. */ diff --git a/src/core/or/conflux_pool.h b/src/core/or/conflux_pool.h index 4276049485..547b4d3974 100644 --- a/src/core/or/conflux_pool.h +++ b/src/core/or/conflux_pool.h @@ -21,6 +21,11 @@ void conflux_predict_new(time_t now);
bool conflux_launch_leg(const uint8_t *nonce);
+void conflux_add_guards_to_exclude_list(const origin_circuit_t *circ, + smartlist_t *excluded); +void conflux_add_middles_to_exclude_list(const origin_circuit_t *circ, + smartlist_t *excluded); + void conflux_circuit_has_closed(circuit_t *circ); void conflux_circuit_has_opened(origin_circuit_t *orig_circ); void conflux_circuit_about_to_free(circuit_t *circ); diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c index b078382e76..4783faf9dd 100644 --- a/src/feature/client/entrynodes.c +++ b/src/feature/client/entrynodes.c @@ -126,6 +126,7 @@ #include "core/or/circuitlist.h" #include "core/or/circuitstats.h" #include "core/or/circuituse.h" +#include "core/or/conflux_pool.h" #include "core/or/policies.h" #include "feature/client/bridges.h" #include "feature/client/circpathbias.h" @@ -151,6 +152,8 @@ #include "core/or/origin_circuit_st.h" #include "app/config/or_state_st.h"
+#include "core/or/conflux_util.h" + /** A list of existing guard selection contexts. */ static smartlist_t *guard_contexts = NULL; /** The currently enabled guard selection context. */ @@ -1588,6 +1591,19 @@ guard_create_exit_restriction(const uint8_t *exit_id) return rst; }
+/* Allocate and return a new exit guard restriction that excludes all current + * and pending conflux guards */ +STATIC entry_guard_restriction_t * +guard_create_conflux_restriction(const origin_circuit_t *circ) +{ + entry_guard_restriction_t *rst = NULL; + rst = tor_malloc_zero(sizeof(entry_guard_restriction_t)); + rst->type = RST_EXCL_LIST; + rst->excluded = smartlist_new(); + conflux_add_guards_to_exclude_list(circ, rst->excluded); + return rst; +} + /** If we have fewer than this many possible usable guards, don't set * MD-availability-based restrictions: we might denylist all of them. */ #define MIN_GUARDS_FOR_MD_RESTRICTION 10 @@ -1680,6 +1696,8 @@ entry_guard_obeys_restriction(const entry_guard_t *guard, return guard_obeys_exit_restriction(guard, rst); } else if (rst->type == RST_OUTDATED_MD_DIRSERVER) { return guard_obeys_md_dirserver_restriction(guard); + } else if (rst->type == RST_EXCL_LIST) { + return !smartlist_contains_digest(rst->excluded, guard->identity); }
tor_assert_nonfatal_unreached(); @@ -2427,6 +2445,11 @@ entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b) STATIC void entry_guard_restriction_free_(entry_guard_restriction_t *rst) { + if (rst && rst->excluded) { + SMARTLIST_FOREACH(rst->excluded, void *, g, + tor_free(g)); + smartlist_free(rst->excluded); + } tor_free(rst); }
@@ -3780,7 +3803,8 @@ guards_update_all(void) /** Helper: pick a guard for a circuit, with whatever algorithm is used. */ const node_t * -guards_choose_guard(cpath_build_state_t *state, +guards_choose_guard(const origin_circuit_t *circ, + cpath_build_state_t *state, uint8_t purpose, circuit_guard_state_t **guard_state_out) { @@ -3788,14 +3812,18 @@ guards_choose_guard(cpath_build_state_t *state, const uint8_t *exit_id = NULL; entry_guard_restriction_t *rst = NULL;
- /* Only apply restrictions if we have a specific exit node in mind, and only - * if we are not doing vanguard circuits: we don't want to apply guard - * restrictions to vanguard circuits. */ - if (state && !circuit_should_use_vanguards(purpose) && + /* If we this is a conflux circuit, build an exclusion list for it. */ + if (CIRCUIT_IS_CONFLUX(TO_CIRCUIT(circ))) { + rst = guard_create_conflux_restriction(circ); + /* Don't allow connecting back to the exit if there is one */ + if (state && (exit_id = build_state_get_exit_rsa_id(state))) { + /* add the exit_id to the excluded list */ + smartlist_add(rst->excluded, tor_memdup(exit_id, DIGEST_LEN)); + } + } else if (state && !circuit_should_use_vanguards(purpose) && (exit_id = build_state_get_exit_rsa_id(state))) { /* We're building to a targeted exit node, so that node can't be - * chosen as our guard for this circuit. Remember that fact in a - * restriction. */ + * chosen as our guard for this circuit, unless we're vanguards. */ rst = guard_create_exit_restriction(exit_id); tor_assert(rst); } diff --git a/src/feature/client/entrynodes.h b/src/feature/client/entrynodes.h index 08fd7cf745..2a94775430 100644 --- a/src/feature/client/entrynodes.h +++ b/src/feature/client/entrynodes.h @@ -294,7 +294,9 @@ typedef enum guard_restriction_type_t { /* Don't pick the same guard node as our exit node (or its family) */ RST_EXIT_NODE = 0, /* Don't pick dirguards that have previously shown to be outdated */ - RST_OUTDATED_MD_DIRSERVER = 1 + RST_OUTDATED_MD_DIRSERVER = 1, + /* Don't pick guards if they are in the exclusion list */ + RST_EXCL_LIST = 2, } guard_restriction_type_t;
/** @@ -312,6 +314,10 @@ struct entry_guard_restriction_t { * digest must not equal this; and it must not be in the same family as any * node with this digest. */ uint8_t exclude_id[DIGEST_LEN]; + + /* In the case of RST_EXCL_LIST, any identity digests in this list + * must not be used. */ + smartlist_t *excluded; };
/** @@ -337,7 +343,8 @@ struct circuit_guard_state_t {
/* Common entry points for old and new guard code */ int guards_update_all(void); -const node_t *guards_choose_guard(cpath_build_state_t *state, +const node_t *guards_choose_guard(const origin_circuit_t *circ, + cpath_build_state_t *state, uint8_t purpose, circuit_guard_state_t **guard_state_out); const node_t *guards_choose_dirguard(uint8_t dir_purpose, @@ -597,6 +604,9 @@ STATIC entry_guard_restriction_t *guard_create_exit_restriction(
STATIC entry_guard_restriction_t *guard_create_dirserver_md_restriction(void);
+STATIC entry_guard_restriction_t * guard_create_conflux_restriction( + const origin_circuit_t *circ); + STATIC void entry_guard_restriction_free_(entry_guard_restriction_t *rst); #define entry_guard_restriction_free(rst) \ FREE_AND_NULL(entry_guard_restriction_t, \
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 39c2927d6fffc391bcd724bfddf8b90ac765e145 Author: David Goulet dgoulet@torproject.org AuthorDate: Thu Mar 30 19:42:27 2023 +0000
Prop#329 Pool: Handle pre-building and using conflux sets.
Signed-off-by: David Goulet dgoulet@torproject.org --- src/core/or/circuituse.c | 17 +++++- src/core/or/circuituse.h | 6 +++ src/core/or/conflux_pool.c | 131 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 2 deletions(-)
diff --git a/src/core/or/circuituse.c b/src/core/or/circuituse.c index 7ea9cddbd9..b10c140253 100644 --- a/src/core/or/circuituse.c +++ b/src/core/or/circuituse.c @@ -103,7 +103,7 @@ circuit_matches_with_rend_stream(const edge_connection_t *edge_conn, /** Return 1 if <b>circ</b> could be returned by circuit_get_best(). * Else return 0. */ -static int +int circuit_is_acceptable(const origin_circuit_t *origin_circ, const entry_connection_t *conn, int must_be_open, uint8_t purpose, @@ -338,6 +338,7 @@ circuit_get_best(const entry_connection_t *conn, { origin_circuit_t *best=NULL; struct timeval now; + time_t now_sec;
tor_assert(conn);
@@ -349,6 +350,14 @@ circuit_get_best(const entry_connection_t *conn, purpose == CIRCUIT_PURPOSE_C_REND_JOINED);
tor_gettimeofday(&now); + now_sec = now.tv_sec; + + // Prefer pre-built conflux circuits here, if available but only for general + // purposes. We don't have onion service conflux support at the moment. + if (purpose == CIRCUIT_PURPOSE_C_GENERAL && + (best = conflux_get_circ_for_conn(conn, now_sec))) { + return best; + }
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { origin_circuit_t *origin_circ; @@ -357,7 +366,7 @@ circuit_get_best(const entry_connection_t *conn, origin_circ = TO_ORIGIN_CIRCUIT(circ);
if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose, - need_uptime,need_internal, (time_t)now.tv_sec)) + need_uptime,need_internal, now_sec)) continue;
/* now this is an acceptable circ to hand back. but that doesn't @@ -1192,6 +1201,10 @@ circuit_predict_and_launch_new(void) time_t now = time(NULL); int flags = 0;
+ /* Attempt to launch predicted conflux circuits. This is outside the HS or + * Exit preemptive circuit set. */ + conflux_predict_new(now); + /* Count how many of each type of circuit we currently have. */ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!circuit_is_available_for_use(circ)) diff --git a/src/core/or/circuituse.h b/src/core/or/circuituse.h index cda6d7ee2e..aa572f3212 100644 --- a/src/core/or/circuituse.h +++ b/src/core/or/circuituse.h @@ -79,6 +79,12 @@ bool circuit_purpose_is_hs_service(const uint8_t purpose); bool circuit_purpose_is_hs_vanguards(const uint8_t purpose);
bool circuit_is_hs_v3(const circuit_t *circ); +int circuit_is_acceptable(const origin_circuit_t *origin_circ, + const entry_connection_t *conn, + int must_be_open, uint8_t purpose, + int need_uptime, int need_internal, + time_t now); + int circuit_should_use_vanguards(uint8_t); void circuit_sent_valid_data(origin_circuit_t *circ, uint16_t relay_body_len); void circuit_read_valid_data(origin_circuit_t *circ, uint16_t relay_body_len); diff --git a/src/core/or/conflux_pool.c b/src/core/or/conflux_pool.c index c1b522fa67..b9da1fe75a 100644 --- a/src/core/or/conflux_pool.c +++ b/src/core/or/conflux_pool.c @@ -916,6 +916,32 @@ link_circuit(circuit_t *circ) return err; }
+/** Launch a brand new set. + * + * Return true if all legs successfully launched or false if one failed. */ +STATIC bool +launch_new_set(int num_legs) +{ + uint8_t nonce[DIGEST256_LEN]; + + /* Brand new nonce for this set. */ + crypto_rand((char *) nonce, sizeof(nonce)); + + /* Launch all legs. */ + for (int i = 0; i < num_legs; i++) { + if (!conflux_launch_leg(nonce)) { + /* This function cleans up entirely the unlinked set if a leg is unable + * to be launched. The recovery would be complex here. */ + goto err; + } + } + + return true; + + err: + return false; +} + static unlinked_circuits_t * unlinked_get_or_create(const uint8_t *nonce, bool is_client) { @@ -1215,6 +1241,111 @@ conflux_add_middles_to_exclude_list(const origin_circuit_t *orig_circ, } }
+/** Return the number of unused client linked set. */ +static int +count_client_usable_sets(void) +{ + int count = 0; + + DIGEST256MAP_FOREACH(client_linked_pool, key, conflux_t *, cfx) { + conflux_leg_t *leg = smartlist_get(cfx->legs, 0); + if (BUG(!leg->circ)) { + log_warn(LD_BUG, "Client conflux linked set leg without a circuit"); + continue; + } + if (!CONST_TO_ORIGIN_CIRCUIT(leg->circ)->unusable_for_new_conns) { + count++; + } + } DIGEST256MAP_FOREACH_END; + + return count; +} + +/** Determine if we need to launch new conflux circuits for our preemptive + * pool. + * + * This is called once a second from the mainloop from + * circuit_predict_and_launch_new(). */ +void +conflux_predict_new(time_t now) +{ + (void) now; + + if (!conflux_is_enabled(NULL)) { + return; + } + + /* Don't attempt to build a new set if we are above our allowed maximum of + * linked sets. */ + if (digest256map_size(client_linked_pool) >= + conflux_params_get_max_linked_set()) { + return; + } + + /* Count the linked and unlinked to get the total number of sets we have + * (will have). */ + int num_linked = count_client_usable_sets(); + int num_unlinked = digest256map_size(client_unlinked_pool); + int num_set = num_unlinked + num_linked; + int max_prebuilt = conflux_params_get_max_prebuilt(); + + if (num_set >= max_prebuilt) { + return; + } + + log_info(LD_CIRC, "Preemptively launching new conflux circuit set(s). " + "We have %d linked and %d unlinked.", + num_linked, num_unlinked); + + for (int i = 0; i < (max_prebuilt - num_set); i++) { + if (!launch_new_set(conflux_params_get_num_legs_set())) { + /* Failing once likely means we'll fail next attempt so stop for now and + * we'll try later. */ + break; + } + } +} + +/** Return the first circuit from the linked pool that will work with the conn. + * If no such circuit exists, return NULL. */ +origin_circuit_t * +conflux_get_circ_for_conn(const entry_connection_t *conn, time_t now) +{ + /* Use conn to check the exit policy of the first circuit + * of each set in the linked pool. */ + tor_assert(conn); + + DIGEST256MAP_FOREACH(client_linked_pool, key, conflux_t *, cfx) { + /* Get the first circuit of the set. */ + conflux_leg_t *leg = smartlist_get(cfx->legs, 0); + tor_assert(leg); + tor_assert(leg->circ); + + /* Bug on these but we can recover. */ + if (BUG(leg->circ->purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED)) { + continue; + } + if (BUG(!CIRCUIT_IS_ORIGIN(leg->circ))) { + continue; + } + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(leg->circ); + + /* Make sure the connection conforms with the exit policy and the isolation + * flags also allows it. */ + if (!circuit_is_acceptable(ocirc, conn, 1 /* Must be open */, + CIRCUIT_PURPOSE_CONFLUX_LINKED, + 1 /* Need uptime */, + 0 /* No need for internal */, now)) { + continue; + } + + /* Found a circuit that works. */ + return ocirc; + } DIGEST256MAP_FOREACH_END; + + return NULL; +} + /** The given circuit is conflux pending and has closed. This deletes the leg * from the set, attempt to finalize it and relaunch a new leg. If the set is * empty after removing this leg, it is deleted. */
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 8d4781e730b3e6c543add3e9acf4eff118bffa70 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Wed Feb 15 19:47:52 2023 +0000
Prop#329 Tests: Add tests for the conflux pool --- src/test/fakecircs.c | 6 + src/test/include.am | 1 + src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_conflux_pool.c | 1338 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1347 insertions(+)
diff --git a/src/test/fakecircs.c b/src/test/fakecircs.c index caeacd84ef..e52df022d0 100644 --- a/src/test/fakecircs.c +++ b/src/test/fakecircs.c @@ -17,6 +17,9 @@ #include "core/or/circuitbuild.h" #include "core/or/circuitlist.h" #include "core/or/circuitpadding.h" +#include "core/or/congestion_control_common.h" +#include "core/or/conflux_pool.h" +#include "core/or/conflux.h" #include "core/or/crypt_path.h" #include "core/or/relay.h" #include "core/or/relay_crypto_st.h" @@ -87,5 +90,8 @@ free_fake_orcirc(or_circuit_t *orcirc) circuitmux_detach_circuit(circ->n_chan->cmux, circ); }
+ conflux_circuit_about_to_free(circ); + congestion_control_free(circ->ccontrol); + tor_free_(circ); } diff --git a/src/test/include.am b/src/test/include.am index 1c2929c3ca..deff450490 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -148,6 +148,7 @@ src_test_test_SOURCES += \ src/test/test_compat_libevent.c \ src/test/test_config.c \ src/test/test_conflux_cell.c \ + src/test/test_conflux_pool.c \ src/test/test_confmgr.c \ src/test/test_confparse.c \ src/test/test_connection.c \ diff --git a/src/test/test.c b/src/test/test.c index e1252eda66..170dafe0c6 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -779,6 +779,7 @@ struct testgroup_t testgroups[] = { { "config/mgr/", confmgr_tests }, { "config/parse/", confparse_tests }, { "conflux/cell/", conflux_cell_tests }, + { "conflux/pool/", conflux_pool_tests }, { "connection/", connection_tests }, { "conscache/", conscache_tests }, { "consdiff/", consdiff_tests }, diff --git a/src/test/test.h b/src/test/test.h index fb0ea98f93..d6c06c658f 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -108,6 +108,7 @@ extern struct testcase_t circuituse_tests[]; extern struct testcase_t compat_libevent_tests[]; extern struct testcase_t config_tests[]; extern struct testcase_t conflux_cell_tests[]; +extern struct testcase_t conflux_pool_tests[]; extern struct testcase_t confmgr_tests[]; extern struct testcase_t confparse_tests[]; extern struct testcase_t connection_tests[]; diff --git a/src/test/test_conflux_pool.c b/src/test/test_conflux_pool.c new file mode 100644 index 0000000000..b8053b7c0a --- /dev/null +++ b/src/test/test_conflux_pool.c @@ -0,0 +1,1338 @@ +#define CHANNEL_OBJECT_PRIVATE +#define TOR_TIMERS_PRIVATE +#define TOR_CONFLUX_PRIVATE +#define CIRCUITLIST_PRIVATE +#define NETWORKSTATUS_PRIVATE +#define CRYPT_PATH_PRIVATE +#define RELAY_PRIVATE +#define CONNECTION_PRIVATE +#define TOR_CONGESTION_CONTROL_PRIVATE + +#include "core/or/or.h" +#include "test/test.h" +#include "test/log_test_helpers.h" +#include "lib/testsupport/testsupport.h" +#include "core/or/connection_or.h" +#include "core/or/channel.h" +#include "core/or/channeltls.h" +#include "core/or/crypt_path.h" +#include <event.h> +#include "lib/evloop/compat_libevent.h" +#include "lib/time/compat_time.h" +#include "lib/defs/time.h" +#include "core/or/relay.h" +#include "core/or/circuitlist.h" +#include "core/or/circuitstats.h" +#include "core/or/circuitbuild.h" +#include "core/or/circuituse.h" +#include "core/or/congestion_control_common.h" +#include "core/or/congestion_control_st.h" +#include "core/or/extendinfo.h" +#include "core/mainloop/netstatus.h" +#include "core/crypto/relay_crypto.h" +#include "core/or/protover.h" +#include "feature/nodelist/nodelist.h" +#include "app/config/config.h" + +#include "feature/nodelist/routerstatus_st.h" +#include "feature/nodelist/networkstatus_st.h" +#include "feature/nodelist/node_st.h" +#include "core/or/cell_st.h" +#include "core/or/crypt_path_st.h" +#include "core/or/or_circuit_st.h" +#include "core/or/origin_circuit_st.h" + +#include "core/mainloop/connection.h" +#include "core/or/connection_edge.h" +#include "core/or/edge_connection_st.h" + +#include "test/fakecircs.h" +#include "test/rng_test_helpers.h" +#include "core/or/conflux_pool.h" +#include "core/or/conflux_util.h" +#include "core/or/conflux_params.h" +#include "core/or/conflux.h" +#include "core/or/conflux_st.h" +#include "lib/crypt_ops/crypto_rand.h" + +/* Start our monotime mocking at 1 second past whatever monotime_init() + * thought the actual wall clock time was, for platforms with bad resolution + * and weird timevalues during monotime_init() before mocking. */ +#define MONOTIME_MOCK_START (monotime_absolute_nsec()+\ + TOR_NSEC_PER_USEC*TOR_USEC_PER_SEC) + +extern smartlist_t *connection_array; +void circuit_expire_old_circuits_clientside(void); + +circid_t get_unique_circ_id_by_chan(channel_t *chan); + +channel_t *new_fake_channel(void); + +static void simulate_single_hop_extend(origin_circuit_t *client, int exit); +static void free_fake_origin_circuit(origin_circuit_t *circ); +static circuit_t * get_exit_circ(circuit_t *client_circ); +static circuit_t * get_client_circ(circuit_t *exit_circ); +static void simulate_circuit_build(circuit_t *client_circ); + +static int64_t curr_mocked_time; + +static channel_t dummy_channel; + +static void +timers_advance_and_run(int64_t msec_update) +{ + curr_mocked_time += msec_update*TOR_NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(curr_mocked_time); + monotime_set_mock_time_nsec(curr_mocked_time); +} + +/* These lists of circuit endpoints send to eachother via + * circuit_package_relay_cell_mocked */ +static smartlist_t *client_circs; +static smartlist_t *exit_circs; +static smartlist_t *client_streams; +static smartlist_t *exit_streams; + +typedef struct { + circuit_t *client; + circuit_t *exit; +} circ_pair_t; +static smartlist_t *circ_pairs; + +static void +simulate_circuit_built(circuit_t *client, circuit_t *exit) +{ + circ_pair_t *pair = tor_malloc_zero(sizeof(circ_pair_t)); + pair->client = client; + pair->exit = exit; + smartlist_add(circ_pairs, pair); +} + +static origin_circuit_t * +circuit_establish_circuit_conflux_mock(const uint8_t *conflux_nonce, + uint8_t purpose, extend_info_t *exit_ei, + int flags) +{ + (void)exit_ei; + (void)flags; + origin_circuit_t *circ = origin_circuit_init(purpose, flags); + circ->base_.conflux_pending_nonce = tor_memdup(conflux_nonce, DIGEST256_LEN); + circ->base_.purpose = CIRCUIT_PURPOSE_CONFLUX_UNLINKED; + smartlist_add(client_circs, circ); + + // This also moves the clock forward as if these hops were opened.. + // Not a problem, unless we want to accurately test buildtimeouts + simulate_single_hop_extend(circ, 0); + simulate_single_hop_extend(circ, 0); + simulate_single_hop_extend(circ, 1); + circ->cpath->prev->ccontrol = tor_malloc_zero(sizeof(congestion_control_t)); + circ->cpath->prev->ccontrol->sendme_arrival_timestamps = smartlist_new(); + circ->cpath->prev->ccontrol->sendme_pending_timestamps = smartlist_new(); + circ->cpath->prev->ccontrol->sendme_inc = 31; + + return circ; +} + +static void +free_fake_origin_circuit(origin_circuit_t *circ) +{ + circuit_clear_cpath(circ); + tor_free(circ); +} + +void dummy_nop_timer(void); + +static int +circuit_package_relay_cell_mock(cell_t *cell, circuit_t *circ, + cell_direction_t cell_direction, + crypt_path_t *layer_hint, streamid_t on_stream, + const char *filename, int lineno); + +static void +circuitmux_attach_circuit_mock(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction); + +static void +circuitmux_attach_circuit_mock(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction) +{ + (void)cmux; + (void)circ; + (void)direction; + + return; +} +/* For use in the mock_net smartlist queue: + * this struct contains a circuit and a cell to + * deliver on it. */ +typedef struct { + circuit_t *circ; + cell_t *cell; +} cell_delivery_t; + +static smartlist_t *mock_cell_delivery = NULL; + +static int +circuit_package_relay_cell_mock(cell_t *cell, circuit_t *circ, + cell_direction_t cell_direction, + crypt_path_t *layer_hint, streamid_t on_stream, + const char *filename, int lineno) +{ + (void)cell; (void)on_stream; (void)filename; (void)lineno; + (void)cell_direction; + circuit_t *dest_circ = NULL; + + // If we have a layer hint, we are sending to the exit. Look + // up the exit circ based on our circuit index in the smartlist + if (layer_hint) { + tor_assert(CIRCUIT_IS_ORIGIN(circ)); + tt_int_op(cell_direction, OP_EQ, CELL_DIRECTION_OUT); + + // Search the circ pairs list for the pair whose client is this circ, + // and set dest_circ to the exit circ in that pair + SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) { + if (pair->client == circ) { + dest_circ = pair->exit; + break; + } + } SMARTLIST_FOREACH_END(pair); + } else { + tt_int_op(cell_direction, OP_EQ, CELL_DIRECTION_IN); + + // Search the circ pairs list for the pair whose exit is this circ, + // and set dest_circ to the client circ in that pair + SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) { + if (pair->exit == circ) { + dest_circ = pair->client; + break; + } + } SMARTLIST_FOREACH_END(pair); + } + + cell_delivery_t *delivery = tor_malloc_zero(sizeof(cell_delivery_t)); + delivery->circ = dest_circ; + delivery->cell = tor_memdup(cell, sizeof(cell_t)); + smartlist_add(mock_cell_delivery, delivery); + done: + return 0; +} + +/** Pull the next cell from the mock delivery queue and deliver it. */ +static void +process_mock_cell_delivery(void) +{ + relay_header_t rh; + + cell_delivery_t *delivery = smartlist_pop_last(mock_cell_delivery); + tor_assert(delivery); + cell_t *cell = delivery->cell; + circuit_t *dest_circ = delivery->circ; + relay_header_unpack(&rh, cell->payload); + + timers_advance_and_run(1); + + switch (cell->payload[0]) { + case RELAY_COMMAND_CONFLUX_LINK: + tor_assert(!CIRCUIT_IS_ORIGIN(dest_circ)); + conflux_process_link(dest_circ, cell, rh.length); + break; + case RELAY_COMMAND_CONFLUX_LINKED: + tor_assert(CIRCUIT_IS_ORIGIN(dest_circ)); + conflux_process_linked(dest_circ, + TO_ORIGIN_CIRCUIT(dest_circ)->cpath->prev, + cell, rh.length); + break; + case RELAY_COMMAND_CONFLUX_LINKED_ACK: + tor_assert(!CIRCUIT_IS_ORIGIN(dest_circ)); + conflux_process_linked_ack(dest_circ); + break; + case RELAY_COMMAND_CONFLUX_SWITCH: + // We only test the case where the switch is initiated by the client. + // It is symmetric, so this should not matter. If we ever want to test + // the case where the switch is initiated by the exit, we will need to + // get the cpath layer hint for the client. + tor_assert(!CIRCUIT_IS_ORIGIN(dest_circ)); + conflux_process_switch_command(dest_circ, NULL, cell, &rh); + break; + } + + tor_free(delivery); + tor_free(cell); + return; +} + +static uint64_t +mock_monotime_absolute_usec(void) +{ + return 100; +} + +static int +channel_get_addr_if_possible_mock(const channel_t *chan, tor_addr_t *addr_out) +{ + (void)chan; + tt_int_op(AF_INET,OP_EQ, tor_addr_parse(addr_out, "18.0.0.1")); + return 1; + + done: + return 0; +} + +static void +circuit_mark_for_close_mock(circuit_t *circ, int reason, + int line, const char *file) +{ + (void)circ; + (void)reason; + (void)line; + (void)file; + + log_info(LD_CIRC, "Marking circuit for close at %s:%d", file, line); + + if (BUG(circ->marked_for_close)) { + log_warn(LD_BUG, + "Duplicate call to circuit_mark_for_close at %s:%d" + " (first at %s:%d)", file, line, + circ->marked_for_close_file, circ->marked_for_close); + return; + } + + circ->marked_for_close = line; + circ->marked_for_close_file = file; + circ->marked_for_close_reason = reason; + + if (CIRCUIT_IS_CONFLUX(circ)) { + conflux_circuit_has_closed(circ); + } + + // Mark the other side for close too. No idea if this even improves things; + // We might also want to make this go through the cell queue as a destroy + if (CIRCUIT_IS_ORIGIN(circ)) { + circuit_t *exit_circ = get_exit_circ(circ); + if (!exit_circ->marked_for_close) + circuit_mark_for_close_mock(get_exit_circ(circ), reason, line, file); + } else { + circuit_t *client_circ = get_client_circ(circ); + if (!client_circ->marked_for_close) + circuit_mark_for_close_mock(get_client_circ(circ), reason, line, file); + } + + // XXX: Should we do this? + //if (circuits_pending_close == NULL) + // circuits_pending_close = smartlist_new(); + //smartlist_add(circuits_pending_close, circ); +} + +static void +simulate_single_hop_extend(origin_circuit_t *client, int exit) +{ + char whatevs_key[CPATH_KEY_MATERIAL_LEN]; + char digest[DIGEST_LEN]; + tor_addr_t addr; + + // Advance time a tiny bit so we can calculate an RTT + curr_mocked_time += 10 * TOR_NSEC_PER_MSEC; + monotime_coarse_set_mock_time_nsec(curr_mocked_time); + monotime_set_mock_time_nsec(curr_mocked_time); + + // Add a hop to cpath + crypt_path_t *hop = tor_malloc_zero(sizeof(crypt_path_t)); + cpath_extend_linked_list(&client->cpath, hop); + + hop->magic = CRYPT_PATH_MAGIC; + hop->state = CPATH_STATE_OPEN; + + // add an extend info to indicate if this node supports padding or not. + // (set the first byte of the digest for our mocked node_get_by_id) + digest[0] = exit; + + hop->extend_info = extend_info_new( + exit ? "exit" : "non-exit", + digest, NULL, NULL, NULL, + &addr, exit, NULL, exit); + + cpath_init_circuit_crypto(hop, whatevs_key, sizeof(whatevs_key), 0, 0); + + hop->package_window = circuit_initial_package_window(); + hop->deliver_window = CIRCWINDOW_START; +} + +static void +test_setup(void) +{ + int64_t actual_mocked_monotime_start; + + MOCK(circuitmux_attach_circuit, circuitmux_attach_circuit_mock); + MOCK(channel_get_addr_if_possible, channel_get_addr_if_possible_mock); + MOCK(circuit_establish_circuit_conflux, + circuit_establish_circuit_conflux_mock); + MOCK(circuit_package_relay_cell, + circuit_package_relay_cell_mock); + MOCK(circuit_mark_for_close_, + circuit_mark_for_close_mock); + MOCK(monotime_absolute_usec, mock_monotime_absolute_usec); + testing_enable_reproducible_rng(); + + monotime_init(); + monotime_enable_test_mocking(); + actual_mocked_monotime_start = MONOTIME_MOCK_START; + monotime_set_mock_time_nsec(actual_mocked_monotime_start); + monotime_coarse_set_mock_time_nsec(actual_mocked_monotime_start); + curr_mocked_time = actual_mocked_monotime_start; + + client_circs = smartlist_new(); + exit_circs = smartlist_new(); + circ_pairs = smartlist_new(); + mock_cell_delivery = smartlist_new(); + dummy_channel.cmux = circuitmux_alloc(); + + get_circuit_build_times_mutable()->timeout_ms = 1000; + + congestion_control_set_cc_enabled(); + max_unlinked_leg_retry = UINT32_MAX; +} + +static void +test_clear_circs(void) +{ + SMARTLIST_FOREACH(circ_pairs, circ_pair_t *, circ_pair, { + tor_free(circ_pair); + }); + SMARTLIST_FOREACH(client_circs, circuit_t *, client_side, { + conflux_circuit_about_to_free(client_side); + circuit_free(client_side); + }); + SMARTLIST_FOREACH(exit_circs, or_circuit_t *, relay_side, { + free_fake_orcirc(relay_side); + }); + + smartlist_clear(circ_pairs); + smartlist_clear(client_circs); + smartlist_clear(exit_circs); + + if (client_streams) { + // Free each edge connection + SMARTLIST_FOREACH(client_streams, edge_connection_t *, edge_conn, { + connection_free_minimal(TO_CONN(edge_conn)); + }); + smartlist_free(client_streams); + } + + if (exit_streams) { + // Free each edge connection + SMARTLIST_FOREACH(exit_streams, edge_connection_t *, edge_conn, { + connection_free_minimal(TO_CONN(edge_conn)); + }); + smartlist_free(exit_streams); + } + + tor_assert(smartlist_len(mock_cell_delivery) == 0); + + (void)free_fake_origin_circuit; +} + +static void +test_teardown(void) +{ + conflux_pool_free_all(); + smartlist_free(client_circs); + smartlist_free(exit_circs); + smartlist_free(mock_cell_delivery); + circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); + circuitmux_free(dummy_channel.cmux); + testing_disable_reproducible_rng(); +} + +/* Test linking a conflux circuit */ +static void +test_conflux_link(void *arg) +{ + (void) arg; + test_setup(); + + launch_new_set(2); + + // For each circuit in the client_circs list, we need to create an + // exit side circuit and simulate two extends + SMARTLIST_FOREACH(client_circs, circuit_t *, client_side, { + simulate_circuit_build(client_side); + + /* Handle network activity*/ + while (smartlist_len(mock_cell_delivery) > 0) { + process_mock_cell_delivery(); + } + }); + + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 2); + + // Test that the cells have all been delivered + tt_int_op(smartlist_len(mock_cell_delivery), OP_EQ, 0); + + // Test that the client side circuits are linked + conflux_t *cfx = ((circuit_t*)smartlist_get(client_circs, 0))->conflux; + SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) { + tt_int_op(client_side->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + tt_ptr_op(client_side->conflux, OP_EQ, cfx); + tt_ptr_op(client_side->conflux_pending_nonce, OP_EQ, NULL); + } SMARTLIST_FOREACH_END(client_side); + + // Test circuit teardown + SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) { + circuit_mark_for_close(client_side, END_CIRC_REASON_FINISHED); + } SMARTLIST_FOREACH_END(client_side); + + done: + test_clear_circs(); + test_teardown(); +} + +static void +simulate_circuit_build(circuit_t *client_circ) +{ + // Create a relay circuit, and simulate the extend and open + circuit_t *relay_side = NULL; + + relay_side = (circuit_t*)new_fake_orcirc(&dummy_channel, &dummy_channel); + relay_side->purpose = CIRCUIT_PURPOSE_OR; + relay_side->n_chan = NULL; // No next hop + relay_side->ccontrol = tor_malloc_zero(sizeof(congestion_control_t)); + relay_side->ccontrol->sendme_arrival_timestamps = smartlist_new(); + relay_side->ccontrol->sendme_pending_timestamps = smartlist_new(); + relay_side->ccontrol->sendme_inc = 31; + smartlist_add(exit_circs, relay_side); + simulate_circuit_built(client_circ, relay_side); + conflux_circuit_has_opened(TO_ORIGIN_CIRCUIT(client_circ)); +} + +static circuit_t * +simulate_close_retry(circuit_t *close, bool manual_launch) +{ + // Find the dest pair for the circuit in the circ pair list, + // and close it too + circuit_t *dest = NULL; + uint8_t *nonce = NULL; + + if (manual_launch) { + nonce = tor_memdup(close->conflux->nonce, DIGEST256_LEN); + } + + SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) { + if (pair->client == close) { + dest = pair->exit; + SMARTLIST_DEL_CURRENT_KEEPORDER(circ_pairs, pair); + tor_free(pair); + } else if (pair->exit == close) { + // This function should not be called on the exit side.. + tor_assert(0); + } + } SMARTLIST_FOREACH_END(pair); + + tor_assert(dest); + log_info(LD_CIRC, "Simulating close of %p->%p, dest %p->%p", + close, close->conflux, dest, dest->conflux); + + // Free all pending cells related to this close in mock_cell_delivery + SMARTLIST_FOREACH(mock_cell_delivery, cell_delivery_t *, cd, { + if (cd->circ == close || cd->circ == dest) { + SMARTLIST_DEL_CURRENT_KEEPORDER(mock_cell_delivery, cd); + tor_free(cd->cell); + tor_free(cd); + } + }); + + // When a circuit closes, both ends get notification, + // and the client will launch a new circuit. We need to find + // that circuit at the end of the list, and then simulate + // building it, and creating a relay circuit for it. + conflux_circuit_has_closed(close); + conflux_circuit_has_closed(dest); + + //tor_assert(digest256map_size(get_unlinked_pool(true)) != 0); + + // Find these legs in our circuit lists, and free them + tor_assert(CIRCUIT_IS_ORIGIN(close)); + tor_assert(!CIRCUIT_IS_ORIGIN(dest)); + SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) { + if (client_side == close) { + SMARTLIST_DEL_CURRENT_KEEPORDER(client_circs, client_side); + conflux_circuit_about_to_free(client_side); + circuit_free(client_side); + } + } SMARTLIST_FOREACH_END(client_side); + SMARTLIST_FOREACH_BEGIN(exit_circs, or_circuit_t *, exit_side) { + if (exit_side == (or_circuit_t *)dest) { + SMARTLIST_DEL_CURRENT_KEEPORDER(exit_circs, exit_side); + free_fake_orcirc(exit_side); + } + } SMARTLIST_FOREACH_END(exit_side); + + if (manual_launch) { + // Launch a new leg for this nonce + tor_assert(nonce); + conflux_launch_leg(nonce); + tor_free(nonce); + } + + if (smartlist_len(client_circs) == 0) { + // No new circuit was launched + return NULL; + } + + // At this point, a new circuit will have launched on the client + // list. Get that circuit from the end of the list and return it + circuit_t * circ = smartlist_get(client_circs, + smartlist_len(client_circs) - 1); + + //tor_assert(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED); + + return circ; +} + +static void +test_retry(void) +{ + log_info(LD_CIRC, "==========NEW RUN ==========="); + launch_new_set(2); + + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + circuit_t *client1 = smartlist_get(client_circs, 0); + circuit_t *client2 = smartlist_get(client_circs, 1); + + get_circuit_build_times_mutable()->timeout_ms = 1000; + + // Dice roll on which leg builds first + if (crypto_rand_int(2) == 0) { + simulate_circuit_build(client1); + simulate_circuit_build(client2); + } else { + simulate_circuit_build(client2); + simulate_circuit_build(client1); + } + + while (smartlist_len(mock_cell_delivery) > 0) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 2); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2); + + if (crypto_rand_int(2) == 0) { + if (crypto_rand_int(2) == 0) { + if (client1->purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED) { + client1 = simulate_close_retry(client1, false); + simulate_circuit_build(client1); + } + } else { + if (client2->purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED) { + client2 = simulate_close_retry(client2, false); + simulate_circuit_build(client2); + } + } + } + + process_mock_cell_delivery(); + } + + // Test that the cells have all been delivered + tt_int_op(smartlist_len(mock_cell_delivery), OP_EQ, 0); + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 2); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2); + + conflux_t *cfx = ((circuit_t *)smartlist_get(client_circs, 0))->conflux; + SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) { + tt_int_op(client_side->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + tt_ptr_op(client_side->conflux, OP_EQ, cfx); + tt_ptr_op(client_side->conflux_pending_nonce, OP_EQ, NULL); + } SMARTLIST_FOREACH_END(client_side); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1); + + cfx = ((circuit_t *)smartlist_get(exit_circs, 0))->conflux; + SMARTLIST_FOREACH_BEGIN(exit_circs, circuit_t *, exit_side) { + tt_ptr_op(exit_side->conflux, OP_EQ, cfx); + tt_ptr_op(exit_side->conflux_pending_nonce, OP_EQ, NULL); + } SMARTLIST_FOREACH_END(exit_side); + + // Test circuit teardown + SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) { + tt_int_op(client_side->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + circuit_mark_for_close(client_side, END_CIRC_REASON_FINISHED); + } SMARTLIST_FOREACH_END(client_side); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 0); + + test_clear_circs(); + + done: + return; +} + +/* Test linking a conflux circuit with build failures */ +static void +test_conflux_link_retry(void *arg) +{ + (void) arg; + test_setup(); + + for (int i = 0; i < 500; i++) { + test_retry(); + } + + test_teardown(); +} + +#if 0 +/* Test closing both circuits in the set before the link handshake completes + * on either leg, by closing circuits before process_mock_cell_delivery. + * + * XXX: This test currently fails because conflux keeps relaunching closed + * circuits. We need to set a limit on the number of times we relaunch a + * circuit before we can fix this test. + */ +static void +test_conflux_link_fail(void *arg) +{ + (void) arg; + test_setup(); + + launch_new_set(2); + + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + circuit_t *client1 = smartlist_get(client_circs, 0); + circuit_t *client2 = smartlist_get(client_circs, 1); + + get_circuit_build_times_mutable()->timeout_ms = 1000; + + // Close both circuits before the link handshake completes + conflux_circuit_has_closed(client1); + conflux_circuit_has_closed(client2); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 0); + done: + test_clear_circs(); + test_teardown(); +} +#endif + +// - Relink test: +// - More than 2 legs +// - Close one linked leg; relink +// - Test mismatching sequence numbers for link and data +// - This should destroy the whole set +// - RTT timeout relinking test +// - Three circuits; close 1; retry and link +static void +test_conflux_link_relink(void *arg) +{ + (void) arg; + test_setup(); + + launch_new_set(3); + + tt_int_op(smartlist_len(client_circs), OP_EQ, 3); + circuit_t *client1 = smartlist_get(client_circs, 0); + circuit_t *client2 = smartlist_get(client_circs, 1); + circuit_t *client3 = smartlist_get(client_circs, 2); + + get_circuit_build_times_mutable()->timeout_ms = 1000; + + simulate_circuit_build(client1); + simulate_circuit_build(client2); + simulate_circuit_build(client3); + + while (smartlist_len(mock_cell_delivery) > 0) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 3); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 3); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 3); + + process_mock_cell_delivery(); + } + + // Now test closing and relinking the third leg + client3 = simulate_close_retry(client3, true); + simulate_circuit_build(client3); + while (smartlist_len(mock_cell_delivery) > 0) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 3); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 3); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 3); + + process_mock_cell_delivery(); + } + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1); + + // Now test closing all circuits and verify the conflux object is gone + simulate_close_retry(client1, false); + simulate_close_retry(client2, false); + simulate_close_retry(client3, false); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 0); + + done: + test_clear_circs(); + test_teardown(); +} + +#if 0 +static void +test_conflux_close(void *arg) +{ + (void) arg; + test_setup(); + + launch_new_set(2); + + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + circuit_t *client1 = smartlist_get(client_circs, 0); + circuit_t *client2 = smartlist_get(client_circs, 1); + + get_circuit_build_times_mutable()->timeout_ms = 1000; + + simulate_circuit_build(client1); + simulate_circuit_build(client2); + + while (smartlist_len(mock_cell_delivery) > 0) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 2); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2); + + process_mock_cell_delivery(); + } + + // There are actually 3 kinds of close: mark, mark+free, + // and purpose change. We need to test these in link_retry, but + // here our focus is on after the set is linked. + + // Additionally, we can close on an unlinked leg, or a non-critical linked + // leg, or a critical linked leg that causes teardown + // And we can close on linked legs when there are unlinked legs, or not. + + // And we can do this at the client, or the exit. + // And we can do this with a circuit that has streams, or not. + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 0); + done: + test_clear_circs(); + test_teardown(); +} +#endif + +// Test launching a new set and closing the first leg, but +// with mismatched sequence numbers (missing data) +// - Test this teardown with only linked circs, and with some +// unlinked circs +// Test mismatching sequence numbers for link and data: +// Test missing sent data from client (link cell mismatch): +// Test missing sent data from relay (linked cell mismatch): + +static circuit_t * +get_exit_circ(circuit_t *client_circ) +{ + circuit_t *exit_circ = NULL; + SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) { + if (pair->client == client_circ) { + exit_circ = pair->exit; + break; + } + } SMARTLIST_FOREACH_END(pair); + tor_assert(exit_circ); + return exit_circ; +} + +static circuit_t * +get_client_circ(circuit_t *exit_circ) +{ + circuit_t *client_circ = NULL; + SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) { + if (pair->exit == exit_circ) { + client_circ = pair->client; + break; + } + } SMARTLIST_FOREACH_END(pair); + tor_assert(client_circ); + return client_circ; +} + +static edge_connection_t * +new_client_stream(origin_circuit_t *on_circ) +{ + edge_connection_t *stream = edge_connection_new(CONN_TYPE_EXIT, AF_INET); + + stream->stream_id = get_unique_stream_id_by_circ(on_circ); + stream->on_circuit = TO_CIRCUIT(on_circ); + stream->cpath_layer = on_circ->cpath->prev; + + stream->next_stream = on_circ->p_streams; + on_circ->p_streams = stream; + conflux_update_p_streams(on_circ, stream); + + smartlist_add(client_streams, stream); + + return stream; +} + +static edge_connection_t * +new_exit_stream(circuit_t *on_circ, streamid_t stream_id) +{ + edge_connection_t *stream = edge_connection_new(CONN_TYPE_EXIT, AF_INET); + + stream->stream_id = stream_id; + stream->on_circuit = on_circ; + + stream->next_stream = TO_OR_CIRCUIT(on_circ)->n_streams; + conflux_update_n_streams(TO_OR_CIRCUIT(on_circ), stream); + + smartlist_add(exit_streams, stream); + + return stream; +} + +static void +validate_stream_counts(circuit_t *circ, int expected) +{ + int count = 0; + + conflux_validate_stream_lists(circ->conflux); + + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED); + /* Iterate over stream list using next_stream pointer, until null */ + for (edge_connection_t *stream = ocirc->p_streams; stream; + stream = stream->next_stream) { + count++; + } + } else { + or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + /* Iterate over stream list using next_stream pointer, until null */ + for (edge_connection_t *stream = orcirc->n_streams; stream; + stream = stream->next_stream) { + count++; + } + } + tt_int_op(count, OP_EQ, expected); + + done: + return; +} + +// - Streams test +// - Attach streams +// - Fail one leg, free it, attach new leg, new stream +// - Fail both legs +// - Shutdown +// - With streams attached +static void +test_conflux_link_streams(void *arg) +{ + (void) arg; + test_setup(); + + launch_new_set(2); + + client_streams = smartlist_new(); + exit_streams = smartlist_new(); + + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + circuit_t *client1 = smartlist_get(client_circs, 0); + circuit_t *client2 = smartlist_get(client_circs, 1); + + get_circuit_build_times_mutable()->timeout_ms = 1000; + + simulate_circuit_build(client1); + simulate_circuit_build(client2); + + while (smartlist_len(mock_cell_delivery) > 0) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 2); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2); + + process_mock_cell_delivery(); + } + + // Attach a stream to the client1 circuit + new_client_stream(TO_ORIGIN_CIRCUIT(client1)); + new_client_stream(TO_ORIGIN_CIRCUIT(client2)); + new_client_stream(TO_ORIGIN_CIRCUIT(client1)); + new_exit_stream(get_exit_circ(client2), 1); + new_exit_stream(get_exit_circ(client1), 1); + new_exit_stream(get_exit_circ(client1), 1); + + // Test that we can close the first leg, and attach a new one + // with a new stream + client1 = simulate_close_retry(client1, true); + simulate_circuit_build(client1); + + while (smartlist_len(mock_cell_delivery) > 0) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 2); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2); + + process_mock_cell_delivery(); + } + + tt_ptr_op(client1->conflux, OP_EQ, client2->conflux); + + new_client_stream(TO_ORIGIN_CIRCUIT(client1)); + new_exit_stream(get_exit_circ(client2), 1); + + // Use Ensure that there are four streams on each circuit + validate_stream_counts(client1, 4); + validate_stream_counts(client2, 4); + validate_stream_counts(get_exit_circ(client1), 4); + validate_stream_counts(get_exit_circ(client2), 4); + + // Test that we can close all streams on either circuit, + // in any order + circuit_detach_stream(get_exit_circ(client1), + TO_OR_CIRCUIT(get_exit_circ(client1))->n_streams); + validate_stream_counts(get_exit_circ(client2), 3); + circuit_detach_stream(get_exit_circ(client2), + TO_OR_CIRCUIT(get_exit_circ(client2))->n_streams->next_stream); + validate_stream_counts(get_exit_circ(client1), 2); + circuit_detach_stream(get_exit_circ(client1), + TO_OR_CIRCUIT(get_exit_circ(client1))->n_streams); + validate_stream_counts(get_exit_circ(client1), 1); + circuit_detach_stream(get_exit_circ(client1), + TO_OR_CIRCUIT(get_exit_circ(client1))->n_streams); + validate_stream_counts(get_exit_circ(client1), 0); + + circuit_detach_stream(client1, + TO_ORIGIN_CIRCUIT(client1)->p_streams->next_stream-> + next_stream->next_stream); + circuit_detach_stream(client2, + TO_ORIGIN_CIRCUIT(client2)->p_streams); + circuit_detach_stream(client2, + TO_ORIGIN_CIRCUIT(client2)->p_streams->next_stream); + circuit_detach_stream(client2, + TO_ORIGIN_CIRCUIT(client2)->p_streams); + validate_stream_counts(client1, 0); + validate_stream_counts(client2, 0); + + done: + test_clear_circs(); + test_teardown(); +} + +// Right now this does not involve congestion control.. But it could, +// if we actually build and send real RELAY_DATA cells (and also handle them +// and SENDME cells in the mocked cell delivery) +static void +send_fake_cell(circuit_t *client_circ) +{ + circuit_t *exit_circ = get_exit_circ(client_circ); + conflux_leg_t *exit_leg = conflux_get_leg(exit_circ->conflux, + exit_circ); + + TO_ORIGIN_CIRCUIT(client_circ)->cpath->prev->ccontrol->inflight++; + conflux_note_cell_sent(client_circ->conflux, client_circ, + RELAY_COMMAND_DATA); + + exit_leg->last_seq_recv++; + exit_circ->conflux->last_seq_delivered++; +} + +static circuit_t * +send_until_switch(circuit_t *client_circ) +{ + conflux_leg_t *client_leg = conflux_get_leg(client_circ->conflux, + client_circ); + circuit_t *exit_circ = get_exit_circ(client_circ); + conflux_leg_t *exit_leg = conflux_get_leg(exit_circ->conflux, + exit_circ); + circuit_t *next_circ = client_circ; + int i = 0; + + // XXX: This is a hack so the tests pass using cc->sendme_inc + // (There is another hack in circuit_ready_to_send() that causes + // us to block early below, and return NULL for next_circ) + TO_ORIGIN_CIRCUIT(client_circ)->cpath->prev->ccontrol->sendme_inc = 0; + + while (client_circ == next_circ) { + next_circ = conflux_decide_circ_for_send(client_circ->conflux, client_circ, + RELAY_COMMAND_DATA); + tor_assert(next_circ); + send_fake_cell(next_circ); + i++; + } + + // XXX: This too: + TO_ORIGIN_CIRCUIT(client_circ)->cpath->prev->ccontrol->sendme_inc = 31; + + log_info(LD_CIRC, "Sent %d cells on client circ", i-1); + process_mock_cell_delivery(); + + circuit_t *new_client = + (circuit_t*)conflux_decide_next_circ(client_circ->conflux); + tt_ptr_op(new_client, OP_NE, client_circ); + conflux_leg_t *new_client_leg = conflux_get_leg(new_client->conflux, + new_client); + circuit_t *new_exit = get_exit_circ(new_client); + conflux_leg_t *new_exit_leg = conflux_get_leg(new_exit->conflux, + new_exit); + + // Verify sequence numbers make sense + tt_int_op(new_client_leg->last_seq_sent, OP_EQ, client_leg->last_seq_sent+1); + tt_int_op(new_client_leg->last_seq_recv, OP_EQ, client_leg->last_seq_recv); + tt_int_op(exit_leg->last_seq_sent, OP_EQ, new_exit_leg->last_seq_sent); + tt_int_op(exit_leg->last_seq_recv+1, OP_EQ, new_exit_leg->last_seq_recv); + + tt_int_op(client_leg->last_seq_sent+1, OP_EQ, new_exit_leg->last_seq_recv); + tt_int_op(client_leg->last_seq_recv, OP_EQ, new_exit_leg->last_seq_sent); + + done: + return new_client; +} + +/** + * This tests switching as well as the UDP optimization that attaches + * a third circuit and closes the slowest one. (This optimization is not + * implemented in C-Tor but must be supported at exits, for arti). + */ +static void +test_conflux_switch(void *arg) +{ + (void) arg; + test_setup(); + + launch_new_set(2); + + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + circuit_t *client1 = smartlist_get(client_circs, 0); + circuit_t *client2 = smartlist_get(client_circs, 1); + get_circuit_build_times_mutable()->timeout_ms = 1000; + + simulate_circuit_build(client1); + simulate_circuit_build(client2); + + circuit_t *exit1 = get_exit_circ(client1); + circuit_t *exit2 = get_exit_circ(client2); + circuit_t *next_circ = client1; + + while (smartlist_len(mock_cell_delivery) > 0) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 2); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2); + + process_mock_cell_delivery(); + } + + // Check to make sure everything is linked`up + tt_ptr_op(client1->conflux, OP_EQ, client2->conflux); + tt_ptr_op(exit1->conflux, OP_EQ, exit2->conflux); + tt_ptr_op(client1->conflux, OP_NE, NULL); + tt_ptr_op(exit1->conflux, OP_NE, NULL); + tt_int_op(smartlist_len(client1->conflux->legs), OP_EQ, 2); + tt_int_op(smartlist_len(exit1->conflux->legs), OP_EQ, 2); + tt_int_op(client1->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + tt_int_op(client2->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1); + tt_ptr_op(get_exit_circ(client1), OP_EQ, exit1); + tt_ptr_op(get_exit_circ(client2), OP_EQ, exit2); + + // Give circuits approximately equal RTT: + conflux_update_rtt(client1->conflux, client1, 100); + conflux_update_rtt(client2->conflux, client2, 125); + + client1->conflux->params.alg = CONFLUX_ALG_LOWRTT; + get_exit_circ(client1)->conflux->params.alg = CONFLUX_ALG_LOWRTT; + TO_ORIGIN_CIRCUIT(client1)->cpath->prev->ccontrol->cwnd = 300; + TO_ORIGIN_CIRCUIT(client2)->cpath->prev->ccontrol->cwnd = 300; + + // Keep sending fake cells until we decide to switch four times + for (int i = 0; i < 4; i++) { + next_circ = send_until_switch(next_circ); + + // XXX: This can't be set to 0 or we will decide we can switch immediately, + // because the client1 has a lower RTT + TO_ORIGIN_CIRCUIT(client1)->cpath->prev->ccontrol->inflight = 1; + + // Check to make sure everything is linked`up + tt_ptr_op(client1->conflux, OP_EQ, client2->conflux); + tt_ptr_op(exit1->conflux, OP_EQ, exit2->conflux); + tt_ptr_op(client1->conflux, OP_NE, NULL); + tt_ptr_op(exit1->conflux, OP_NE, NULL); + tt_int_op(smartlist_len(client1->conflux->legs), OP_EQ, 2); + tt_int_op(smartlist_len(exit1->conflux->legs), OP_EQ, 2); + tt_int_op(client1->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + tt_int_op(client2->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1); + + tt_ptr_op(get_exit_circ(client1), OP_EQ, exit1); + tt_ptr_op(get_exit_circ(client2), OP_EQ, exit2); + tt_ptr_op(next_circ, OP_EQ, client2); + + next_circ = send_until_switch(next_circ); + + // Check to make sure everything is linked`up + tt_ptr_op(client1->conflux, OP_EQ, client2->conflux); + tt_ptr_op(exit1->conflux, OP_EQ, exit2->conflux); + tt_ptr_op(client1->conflux, OP_NE, NULL); + tt_ptr_op(exit1->conflux, OP_NE, NULL); + tt_int_op(smartlist_len(client1->conflux->legs), OP_EQ, 2); + tt_int_op(smartlist_len(exit1->conflux->legs), OP_EQ, 2); + tt_int_op(client1->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + tt_int_op(client2->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1); + + tt_ptr_op(get_exit_circ(client1), OP_EQ, exit1); + tt_ptr_op(get_exit_circ(client2), OP_EQ, exit2); + tt_ptr_op(next_circ, OP_EQ, client1); + + TO_ORIGIN_CIRCUIT(client2)->cpath->prev->ccontrol->inflight = 0; + } + + // Try the UDP minRTT reconnect optimization a few times + for (int i = 0; i < 500; i++) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + client1 = smartlist_get(client_circs, 0); + client2 = smartlist_get(client_circs, 1); + exit1 = get_exit_circ(client1); + exit2 = get_exit_circ(client2); + + // Attach a third leg + conflux_launch_leg(client1->conflux->nonce); + + // It should be added to the end of the local test list + circuit_t *client3 = smartlist_get(client_circs, + smartlist_len(client_circs)-1); + simulate_circuit_build(client3); + + while (smartlist_len(mock_cell_delivery) > 0) { + tt_int_op(smartlist_len(client_circs), OP_EQ, 3); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 3); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 3); + + process_mock_cell_delivery(); + } + + circuit_t *exit3 = get_exit_circ(client3); + + // Check to make sure everything is linked`up + tt_ptr_op(client3->conflux, OP_EQ, client2->conflux); + tt_ptr_op(exit3->conflux, OP_EQ, exit2->conflux); + tt_ptr_op(client3->conflux, OP_NE, NULL); + tt_ptr_op(exit3->conflux, OP_NE, NULL); + tt_int_op(smartlist_len(client1->conflux->legs), OP_EQ, 3); + tt_int_op(smartlist_len(exit1->conflux->legs), OP_EQ, 3); + tt_int_op(client3->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1); + + conflux_update_rtt(client3->conflux, client3, + crypto_rand_int_range(90, 200)); + TO_ORIGIN_CIRCUIT(client3)->cpath->prev->ccontrol->cwnd = 300; + + circuit_t *circ_close = NULL; + uint64_t max_rtt = 0; + // Pick the leg with the highest RTT and close it + tor_assert(client3); + tor_assert(client3->conflux); + tor_assert(client3->conflux->legs); + CONFLUX_FOR_EACH_LEG_BEGIN(client3->conflux, leg) { + if (client3->conflux->curr_leg == leg) + continue; + + if (leg->circ_rtts_usec > max_rtt) { + max_rtt = leg->circ_rtts_usec; + circ_close = leg->circ; + } + } CONFLUX_FOR_EACH_LEG_END(leg); + + // Let the second leg "send" all data and close it. + tor_assert(circ_close); + tor_assert(circ_close->conflux); + tor_assert(circ_close->conflux->legs); + CONFLUX_FOR_EACH_LEG_BEGIN(circ_close->conflux, leg) { + TO_ORIGIN_CIRCUIT(leg->circ)->cpath->prev->ccontrol->inflight = 0; + } CONFLUX_FOR_EACH_LEG_END(leg); + + // Close without manual launch (code will not relaunch for linked) + simulate_close_retry(circ_close, false); + + tt_int_op(smartlist_len(mock_cell_delivery), OP_EQ, 0); + tt_int_op(smartlist_len(client_circs), OP_EQ, 2); + tt_int_op(smartlist_len(exit_circs), OP_EQ, 2); + tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2); + + // Send until we switch to the third leg + next_circ = send_until_switch(next_circ); + + // Check to make sure everything is linked`up + tt_ptr_op(next_circ->conflux, OP_NE, NULL); + tt_int_op(smartlist_len(next_circ->conflux->legs), OP_EQ, 2); + tt_int_op(next_circ->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1); + + CONFLUX_FOR_EACH_LEG_BEGIN(next_circ->conflux, leg) { + TO_ORIGIN_CIRCUIT(leg->circ)->cpath->prev->ccontrol->inflight = 0; + } CONFLUX_FOR_EACH_LEG_END(leg); + + // send until we switch back to the first leg + next_circ = send_until_switch(next_circ); + + // Check to make sure everything is linked`up + tt_ptr_op(next_circ->conflux, OP_NE, NULL); + tt_int_op(smartlist_len(next_circ->conflux->legs), OP_EQ, 2); + tt_int_op(next_circ->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED); + + tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1); + tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0); + tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0); + tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1); + } + + done: + test_clear_circs(); + test_teardown(); + return; + } + +struct testcase_t conflux_pool_tests[] = { + { "link", test_conflux_link, TT_FORK, NULL, NULL }, + { "link_retry", test_conflux_link_retry, TT_FORK, NULL, NULL }, + { "link_relink", test_conflux_link_relink, TT_FORK, NULL, NULL }, + { "link_streams", test_conflux_link_streams, TT_FORK, NULL, NULL }, + { "switch", test_conflux_switch, TT_FORK, NULL, NULL }, + // XXX: These two currently fail, because they are not finished: + //{ "link_fail", test_conflux_link_fail, TT_FORK, NULL, NULL }, + //{ "close", test_conflux_close, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +};
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 731a50c8c490c0d838605c2664ced3f68bb3d488 Author: David Goulet dgoulet@torproject.org AuthorDate: Wed Feb 1 22:31:12 2023 +0000
Prop#329: Add conflux to build
Signed-off-by: David Goulet dgoulet@torproject.org --- src/core/or/include.am | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/src/core/or/include.am b/src/core/or/include.am index 9a4841e937..12456b98dd 100644 --- a/src/core/or/include.am +++ b/src/core/or/include.am @@ -39,6 +39,12 @@ LIBTOR_APP_A_SOURCES += \ src/core/or/congestion_control_nola.c \ src/core/or/congestion_control_westwood.c \ src/core/or/congestion_control_flow.c \ + src/core/or/conflux.c \ + src/core/or/conflux_cell.c \ + src/core/or/conflux_params.c \ + src/core/or/conflux_pool.c \ + src/core/or/conflux_sys.c \ + src/core/or/conflux_util.c \ src/core/or/status.c \ src/core/or/versions.c
@@ -109,7 +115,12 @@ noinst_HEADERS += \ src/core/or/congestion_control_nola.h \ src/core/or/congestion_control_westwood.h \ src/core/or/conflux.h \ + src/core/or/conflux_cell.h \ + src/core/or/conflux_params.h \ + src/core/or/conflux_pool.h \ src/core/or/conflux_st.h \ + src/core/or/conflux_sys.h \ + src/core/or/conflux_util.h \ src/core/or/server_port_cfg_st.h \ src/core/or/socks_request_st.h \ src/core/or/status.h \
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 7c70f713c31c0989a0008c7d0d92a1f12d498e32 Author: Mike Perry mikeperry-git@torproject.org AuthorDate: Thu Mar 23 20:52:13 2023 +0000
Avoid closing dirty circs with active half-edges
In https://gitlab.torproject.org/tpo/core/tor/-/issues/40623, we changed the DESTROY propogation to ensure memory was freed quickly at relays. This was a good move, but it exacerbates the condition where a stream is closed on a circuit, and then it is immediately closed because it is dirty. This creates a race between the DESTROY and the last data sent on the stream. This race is visible in shadow, and does happen.
This could be backported. A better solution to these kinds of problems is to create an ENDED cell, and not close any circuits until the ENDED comes back. But this will also require thinking, since this ENDED cell can also get lost, so some kind of timeout may be needed either way. The ENDED cell could just allow us to have much longer timeouts for this case. --- src/core/or/circuituse.c | 1 + src/core/or/connection_edge.c | 19 +++++++++++++++++++ src/core/or/connection_edge.h | 1 + 3 files changed, 21 insertions(+)
diff --git a/src/core/or/circuituse.c b/src/core/or/circuituse.c index b10c140253..6956cf9849 100644 --- a/src/core/or/circuituse.c +++ b/src/core/or/circuituse.c @@ -1458,6 +1458,7 @@ circuit_expire_old_circuits_clientside(void) if (circ->timestamp_dirty && circ->timestamp_dirty + get_options()->MaxCircuitDirtiness < now.tv_sec && + !connection_half_edges_waiting(TO_ORIGIN_CIRCUIT(circ)) && !TO_ORIGIN_CIRCUIT(circ)->p_streams /* nothing attached */ ) { log_debug(LD_CIRC, "Closing n_circ_id %u (dirty %ld sec ago, " "purpose %d)", diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c index ee6ab8596c..e1eeb2f64f 100644 --- a/src/core/or/connection_edge.c +++ b/src/core/or/connection_edge.c @@ -675,6 +675,25 @@ connection_half_edge_add(const edge_connection_t *conn, smartlist_insert(circ->half_streams, insert_at, half_conn); }
+/** + * Return true if the circuit has any half-closed connections + * that are still within the end_ack_expected_usec timestamp + * from now. + */ +bool +connection_half_edges_waiting(const origin_circuit_t *circ) +{ + if (!circ->half_streams) + return false; + + SMARTLIST_FOREACH_BEGIN(circ->half_streams, const half_edge_t *, half_conn) { + if (half_conn->end_ack_expected_usec > monotime_absolute_usec()) + return true; + } SMARTLIST_FOREACH_END(half_conn); + + return false; +} + /** Release space held by <b>he</b> */ void half_edge_free_(half_edge_t *he) diff --git a/src/core/or/connection_edge.h b/src/core/or/connection_edge.h index 1816f2a463..59fc17dea5 100644 --- a/src/core/or/connection_edge.h +++ b/src/core/or/connection_edge.h @@ -218,6 +218,7 @@ int connection_half_edge_is_valid_end(smartlist_t *half_conns, streamid_t stream_id); int connection_half_edge_is_valid_resolved(smartlist_t *half_conns, streamid_t stream_id); +bool connection_half_edges_waiting(const origin_circuit_t *circ);
size_t half_streams_get_total_allocation(void); struct half_edge_t;
This is an automated email from the git hooks/post-receive script.
ahf pushed a commit to branch main in repository tor.
commit 2bb8988629b6d7ddb5a15d5490154c9a92e0c866 Author: David Goulet dgoulet@torproject.org AuthorDate: Sun Apr 2 21:18:25 2023 +0000
Fix cases where edge connections can stall.
We discovered two cases where edge connections can stall during testing: 1. Due to final data sitting in the edge inbuf when it was resumed 2. Due to flag synchronization between the token bucket and XON/XOFF
The first issue has always existed in C-Tor, but we were able to tickle it in scp testing. If the last data from the protocol is able to fit in the inbuf, but not large enough to send, if an XOFF or connection block comes in at exactly that point, when the edge connection resumes, there will be no data to read from the socket, but the inbuf can just sit there, never draining.
We noticed the second issue along the way to finding the first. It seems wrong, but it didn't seem to affect anything in practice.
These are extremely rare in normal operation, but with conflux, XON/XOFF activity is more common, so we hit these.
Signed-off-by: David Goulet dgoulet@torproject.org --- src/core/mainloop/connection.c | 27 ++++++++++++++++++++------- src/core/mainloop/connection.h | 1 + src/core/mainloop/mainloop.c | 10 ++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-)
diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 7204b69e54..5a769f38be 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -187,7 +187,6 @@ static int connection_reached_eof(connection_t *conn); static int connection_buf_read_from_socket(connection_t *conn, ssize_t *max_to_read, int *socket_error); -static int connection_process_inbuf(connection_t *conn, int package_partial); static void client_check_address_changed(tor_socket_t sock); static void set_constrained_socket_buffers(tor_socket_t sock, int size);
@@ -3744,9 +3743,16 @@ void connection_read_bw_exhausted(connection_t *conn, bool is_global_bw) { (void)is_global_bw; - conn->read_blocked_on_bw = 1; - connection_stop_reading(conn); - reenable_blocked_connection_schedule(); + // Double-calls to stop-reading are correlated with stalling for + // ssh uploads. Might as well prevent this from happening, + // especially the read_blocked_on_bw flag. That was clearly getting + // set when it should not be, during an already-blocked XOFF + // condition. + if (!CONN_IS_EDGE(conn) || !TO_EDGE_CONN(conn)->xoff_received) { + conn->read_blocked_on_bw = 1; + connection_stop_reading(conn); + reenable_blocked_connection_schedule(); + } }
/** @@ -3923,10 +3929,17 @@ reenable_blocked_connections_cb(mainloop_event_t *ev, void *arg) (void)ev; (void)arg; SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { - if (conn->read_blocked_on_bw == 1) { + /* For conflux, we noticed logs of connection_start_reading() called + * multiple times while we were blocked from a previous XOFF, and this + * was log was correlated with stalls during ssh uploads. So we added + * this additional check, to avoid connection_start_reading() without + * getting an XON. The most important piece is always allowing + * the read_blocked_on_bw to get cleared, either way. */ + if (conn->read_blocked_on_bw == 1 && + (!CONN_IS_EDGE(conn) || !TO_EDGE_CONN(conn)->xoff_received)) { connection_start_reading(conn); - conn->read_blocked_on_bw = 0; } + conn->read_blocked_on_bw = 0; if (conn->write_blocked_on_bw == 1) { connection_start_writing(conn); conn->write_blocked_on_bw = 0; @@ -5198,7 +5211,7 @@ set_constrained_socket_buffers(tor_socket_t sock, int size) * connection_*_process_inbuf() function. It also passes in * package_partial if wanted. */ -static int +int connection_process_inbuf(connection_t *conn, int package_partial) { tor_assert(conn); diff --git a/src/core/mainloop/connection.h b/src/core/mainloop/connection.h index 8b378b15a4..88bf47a497 100644 --- a/src/core/mainloop/connection.h +++ b/src/core/mainloop/connection.h @@ -256,6 +256,7 @@ int connection_wants_to_flush(struct connection_t *conn); int connection_outbuf_too_full(struct connection_t *conn); int connection_handle_write(struct connection_t *conn, int force); int connection_flush(struct connection_t *conn); +int connection_process_inbuf(struct connection_t *conn, int package_partial);
MOCK_DECL(void, connection_write_to_buf_impl_, (const char *string, size_t len, struct connection_t *conn, diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index e1c9786b2e..3702f3a74e 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -645,6 +645,16 @@ connection_start_reading,(connection_t *conn)) "to watched: %s", (int)conn->s, tor_socket_strerror(tor_socket_errno(conn->s))); + + /* Process the inbuf if it is not empty because the only way to empty it is + * through a read event or a SENDME which might not come if the package + * window is proper or if the application has nothing more for us to read. + * + * If this is not done here, we risk having data lingering in the inbuf + * forever. */ + if (conn->inbuf && buf_datalen(conn->inbuf) > 0) { + connection_process_inbuf(conn, 1); + } } }
tor-commits@lists.torproject.org