/* 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/relay.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_pool.h" #include "core/or/conflux_st.h" #include "core/or/conflux_cell.h" #include "lib/time/compat_time.h" #include "app/config/config.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); /* 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. * * 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; tor_assert(cfx); tor_assert(cfx->legs); // 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; } /** * 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; } /** 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. */ 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->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) { /* Ignore circuits with no RTT measurement */ if (leg->circ_rtts_usec && 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; } /* Ignore circuits with no RTT */ if (leg->circ_rtts_usec && 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; } /** * 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; } /** * 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); uint64_t cwnd_adjusted = cwnd_available(on_circ); 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 cwnd_adjusted; } if (cc->in_slow_start) { return cwnd_adjusted; } else { /* 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. */ uint64_t sendable = cwnd_adjusted*in_usec/our_usec; return MIN(cc->cwnd, sendable); } } /** * 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. */ // TODO-329-TUNING: Should we try to switch if the prev_leg is // ready to send, instead of this? if (ccontrol->inflight < ccontrol->sendme_inc || 100*ccontrol->inflight <= conflux_params_get_drain_pct()*ccontrol->cwnd) { return true; } 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) { /* Ignore circuits with invalid RTT */ if (l->circ_rtts_usec && 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; } 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 (l->circ_rtts_usec && cwnd_sendable(l->circ, min_rtt, l->circ_rtts_usec) > 0) { 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; } /** * 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. No BUG() here because the only caller BUG()s. */ if (!cfx->curr_leg) { log_warn(LD_BUG, "No current leg for conflux with relay command %d", relay_command); return NULL; } 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) { // TODO-329-TUNING: This is one mechanism to rate limit switching, // which should reduce the OOQ mem. However, we're not going to do that // until we get some data on if the memory usage is high cfx->cells_until_switch = 0; //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 bool 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 (!min_leg) { // Get the 0th leg; if it does not exist, log the set. // Bug 40827 managed to hit this, so let's dump the sets // in case it happens again. if (BUG(smartlist_len(cfx->legs) <= 0)) { // Since we have no legs, we have no idea if this is really a client // or server set. Try to find any that match: log_warn(LD_BUG, "Matching client sets:"); conflux_log_set(LOG_WARN, cfx, true); log_warn(LD_BUG, "Matching server sets:"); conflux_log_set(LOG_WARN, cfx, false); log_warn(LD_BUG, "End conflux set dump"); return false; } min_leg = smartlist_get(cfx->legs, 0); tor_assert(min_leg); if (BUG(min_leg->linked_sent_usec == 0)) { log_warn(LD_BUG, "Conflux has no legs with non-zero RTT. " "Using first leg."); conflux_log_set(LOG_WARN, cfx, CIRCUIT_IS_ORIGIN(min_leg->circ)); } } // TODO-329-TUNING: We may want to initialize this to a cwnd, to // minimize early switching? //cfx->cells_until_switch = circuit_ccontrol(min_leg->circ)->cwnd; cfx->cells_until_switch = 0; cfx->curr_leg = min_leg; return true; } /** * 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 the conflux set is tearing down and has no current leg, * bail and give up */ if (cfx->in_full_teardown) { return NULL; } /* 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) { if (!conflux_pick_first_leg(cfx)) return NULL; } /* 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); 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. * * 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; } // 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(). * * 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; } }