#include "core/or/or.h"
#include "feature/relay/onion_queue.h"
#include "app/config/config.h"
#include "core/mainloop/cpuworker.h"
#include "core/or/circuitlist.h"
#include "core/or/onion.h"
#include "feature/nodelist/networkstatus.h"
#include "feature/stats/rephist.h"
#include "core/or/or_circuit_st.h"
#include "core/or/channel.h"
#define ONION_QUEUE_WAIT_CUTOFF_DEFAULT 5
#define ONION_QUEUE_WAIT_CUTOFF_MIN 0
#define ONION_QUEUE_WAIT_CUTOFF_MAX INT32_MAX
#define ONION_QUEUE_MAX_DELAY_DEFAULT 1750
#define ONION_QUEUE_MAX_DELAY_MIN 1
#define ONION_QUEUE_MAX_DELAY_MAX INT32_MAX
#define NUM_NTORS_PER_TAP_DEFAULT 10
#define NUM_NTORS_PER_TAP_MIN 1
#define NUM_NTORS_PER_TAP_MAX 100000
typedef struct onion_queue_t {
TOR_TAILQ_ENTRY(onion_queue_t) next;
or_circuit_t *circ;
uint16_t queue_idx;
create_cell_t *onionskin;
time_t when_added;
} onion_queue_t;
TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t);
typedef struct onion_queue_head_t onion_queue_head_t;
#define MAX_QUEUE_IDX ONION_HANDSHAKE_TYPE_NTOR
static onion_queue_head_t ol_list[MAX_QUEUE_IDX+1] =
{ TOR_TAILQ_HEAD_INITIALIZER(ol_list[0]),
TOR_TAILQ_HEAD_INITIALIZER(ol_list[1]),
TOR_TAILQ_HEAD_INITIALIZER(ol_list[2]),
};
static int ol_entries[MAX_QUEUE_IDX+1];
static void onion_queue_entry_remove(onion_queue_t *victim);
static int32_t ns_num_ntors_per_tap = NUM_NTORS_PER_TAP_DEFAULT;
static time_t ns_onion_queue_wait_cutoff = ONION_QUEUE_WAIT_CUTOFF_DEFAULT;
static uint32_t ns_onion_queue_max_delay = ONION_QUEUE_MAX_DELAY_DEFAULT;
static inline int32_t
get_num_ntors_per_tap(void)
{
return ns_num_ntors_per_tap;
}
static inline time_t
get_onion_queue_wait_cutoff(void)
{
return ns_onion_queue_wait_cutoff;
}
static inline uint32_t
get_onion_queue_max_delay(const or_options_t *options)
{
if (options && options->MaxOnionQueueDelay > 0) {
return options->MaxOnionQueueDelay;
}
return ns_onion_queue_max_delay;
}
static inline uint16_t
onionskin_type_to_queue(uint16_t type)
{
if (type == ONION_HANDSHAKE_TYPE_NTOR_V3) {
return ONION_HANDSHAKE_TYPE_NTOR;
}
if (BUG(type > MAX_QUEUE_IDX)) {
return MAX_QUEUE_IDX; }
return type;
}
static int
have_room_for_onionskin(uint16_t type)
{
const or_options_t *options = get_options();
int num_cpus;
uint64_t max_onion_queue_delay;
uint64_t tap_usec, ntor_usec;
uint64_t ntor_during_tap_usec, tap_during_ntor_usec;
if (ol_entries[type] < 50)
return 1;
num_cpus = cpuworker_get_n_threads();
tor_assert(num_cpus > 0);
max_onion_queue_delay = get_onion_queue_max_delay(options);
tap_usec = estimated_usec_for_onionskins(
ol_entries[ONION_HANDSHAKE_TYPE_TAP],
ONION_HANDSHAKE_TYPE_TAP) / num_cpus;
ntor_usec = estimated_usec_for_onionskins(
ol_entries[ONION_HANDSHAKE_TYPE_NTOR],
ONION_HANDSHAKE_TYPE_NTOR) / num_cpus;
tap_during_ntor_usec = estimated_usec_for_onionskins(
MIN(ol_entries[ONION_HANDSHAKE_TYPE_TAP],
ol_entries[ONION_HANDSHAKE_TYPE_NTOR] / get_num_ntors_per_tap()),
ONION_HANDSHAKE_TYPE_TAP) / num_cpus;
ntor_during_tap_usec = estimated_usec_for_onionskins(
MIN(ol_entries[ONION_HANDSHAKE_TYPE_NTOR],
ol_entries[ONION_HANDSHAKE_TYPE_TAP] * get_num_ntors_per_tap()),
ONION_HANDSHAKE_TYPE_NTOR) / num_cpus;
if (type == ONION_HANDSHAKE_TYPE_NTOR &&
(ntor_usec + tap_during_ntor_usec) / 1000 > max_onion_queue_delay)
return 0;
if (type == ONION_HANDSHAKE_TYPE_TAP &&
(tap_usec + ntor_during_tap_usec) / 1000 > max_onion_queue_delay)
return 0;
if (type == ONION_HANDSHAKE_TYPE_TAP &&
tap_usec / 1000 > max_onion_queue_delay * 2 / 3)
return 0;
return 1;
}
int
onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin)
{
onion_queue_t *tmp;
time_t now = time(NULL);
uint16_t queue_idx = 0;
if (onionskin->handshake_type > MAX_ONION_HANDSHAKE_TYPE) {
log_warn(LD_BUG, "Handshake %d out of range! Dropping.",
onionskin->handshake_type);
return -1;
}
queue_idx = onionskin_type_to_queue(onionskin->handshake_type);
tmp = tor_malloc_zero(sizeof(onion_queue_t));
tmp->circ = circ;
tmp->queue_idx = queue_idx;
tmp->onionskin = onionskin;
tmp->when_added = now;
if (!have_room_for_onionskin(queue_idx)) {
#define WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL (60)
static ratelim_t last_warned =
RATELIM_INIT(WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL);
if (!channel_is_client(circ->p_chan)) {
rep_hist_note_circuit_handshake_dropped(queue_idx);
}
if (queue_idx == ONION_HANDSHAKE_TYPE_NTOR) {
char *m;
if ((m = rate_limit_log(&last_warned, approx_time()))) {
log_warn(LD_GENERAL,
"Your computer is too slow to handle this many circuit "
"creation requests! Please consider using the "
"MaxAdvertisedBandwidth config option or choosing a more "
"restricted exit policy.%s",
m);
tor_free(m);
}
}
tor_free(tmp);
return -1;
}
++ol_entries[queue_idx];
log_info(LD_OR, "New create (%s). Queues now ntor=%d and tap=%d.",
queue_idx == ONION_HANDSHAKE_TYPE_NTOR ? "ntor" : "tap",
ol_entries[ONION_HANDSHAKE_TYPE_NTOR],
ol_entries[ONION_HANDSHAKE_TYPE_TAP]);
circ->onionqueue_entry = tmp;
TOR_TAILQ_INSERT_TAIL(&ol_list[queue_idx], tmp, next);
while (1) {
onion_queue_t *head = TOR_TAILQ_FIRST(&ol_list[queue_idx]);
if (now - head->when_added < get_onion_queue_wait_cutoff())
break;
circ = head->circ;
circ->onionqueue_entry = NULL;
onion_queue_entry_remove(head);
log_info(LD_CIRC,
"Circuit create request is too old; canceling due to overload.");
if (! TO_CIRCUIT(circ)->marked_for_close) {
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_RESOURCELIMIT);
}
}
return 0;
}
static uint16_t
decide_next_handshake_type(void)
{
static int recently_chosen_ntors = 0;
if (!ol_entries[ONION_HANDSHAKE_TYPE_NTOR])
return ONION_HANDSHAKE_TYPE_TAP;
if (!ol_entries[ONION_HANDSHAKE_TYPE_TAP]) {
if (ol_entries[ONION_HANDSHAKE_TYPE_NTOR] &&
recently_chosen_ntors <= get_num_ntors_per_tap())
++recently_chosen_ntors;
return ONION_HANDSHAKE_TYPE_NTOR;
}
if (++recently_chosen_ntors <= get_num_ntors_per_tap()) {
return ONION_HANDSHAKE_TYPE_NTOR;
}
recently_chosen_ntors = 0;
return ONION_HANDSHAKE_TYPE_TAP;
}
or_circuit_t *
onion_next_task(create_cell_t **onionskin_out)
{
or_circuit_t *circ;
uint16_t handshake_to_choose = decide_next_handshake_type();
onion_queue_t *head = TOR_TAILQ_FIRST(&ol_list[handshake_to_choose]);
if (!head)
return NULL;
tor_assert(head->circ);
tor_assert(head->queue_idx <= MAX_QUEUE_IDX);
circ = head->circ;
if (head->onionskin)
--ol_entries[head->queue_idx];
log_info(LD_OR, "Processing create (%s). Queues now ntor=%d and tap=%d.",
head->queue_idx == ONION_HANDSHAKE_TYPE_NTOR ? "ntor" : "tap",
ol_entries[ONION_HANDSHAKE_TYPE_NTOR],
ol_entries[ONION_HANDSHAKE_TYPE_TAP]);
*onionskin_out = head->onionskin;
head->onionskin = NULL;
circ->onionqueue_entry = NULL;
onion_queue_entry_remove(head);
return circ;
}
int
onion_num_pending(uint16_t handshake_type)
{
return ol_entries[onionskin_type_to_queue(handshake_type)];
}
void
onion_pending_remove(or_circuit_t *circ)
{
onion_queue_t *victim;
if (!circ)
return;
victim = circ->onionqueue_entry;
if (victim)
onion_queue_entry_remove(victim);
cpuworker_cancel_circ_handshake(circ);
}
static void
onion_queue_entry_remove(onion_queue_t *victim)
{
if (victim->queue_idx > MAX_QUEUE_IDX) {
log_warn(LD_BUG, "Handshake %d out of range! Dropping.",
victim->queue_idx);
return;
}
TOR_TAILQ_REMOVE(&ol_list[victim->queue_idx], victim, next);
if (victim->circ)
victim->circ->onionqueue_entry = NULL;
if (victim->onionskin)
--ol_entries[victim->queue_idx];
tor_free(victim->onionskin);
tor_free(victim);
}
void
clear_pending_onions(void)
{
onion_queue_t *victim, *next;
int i;
for (i=0; i<=MAX_QUEUE_IDX; i++) {
for (victim = TOR_TAILQ_FIRST(&ol_list[i]); victim; victim = next) {
next = TOR_TAILQ_NEXT(victim,next);
onion_queue_entry_remove(victim);
}
tor_assert(TOR_TAILQ_EMPTY(&ol_list[i]));
}
memset(ol_entries, 0, sizeof(ol_entries));
}
void
onion_consensus_has_changed(const networkstatus_t *ns)
{
tor_assert(ns);
ns_onion_queue_max_delay =
networkstatus_get_param(ns, "MaxOnionQueueDelay",
ONION_QUEUE_MAX_DELAY_DEFAULT,
ONION_QUEUE_MAX_DELAY_MIN,
ONION_QUEUE_MAX_DELAY_MAX);
ns_onion_queue_wait_cutoff =
networkstatus_get_param(ns, "onion_queue_wait_cutoff",
ONION_QUEUE_WAIT_CUTOFF_DEFAULT,
ONION_QUEUE_WAIT_CUTOFF_MIN,
ONION_QUEUE_WAIT_CUTOFF_MAX);
ns_num_ntors_per_tap =
networkstatus_get_param(ns, "NumNTorsPerTAP",
NUM_NTORS_PER_TAP_DEFAULT,
NUM_NTORS_PER_TAP_MIN,
NUM_NTORS_PER_TAP_MAX);
}