Loading changes/feature9574 0 → 100644 +7 −0 Original line number Diff line number Diff line o Major features: - Relays now process the new "NTor" circuit-level handshake requests with higher priority than the old "TAP" circuit-level handshake requests. We still process some TAP requests to not totally starve 0.2.3 clients when NTor becomes popular. A new consensus parameter "NumNTorsPerTAP" lets us tune the balance later if we need to. Implements ticket 9574. src/or/onion.c +186 −45 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ #include "circuitlist.h" #include "config.h" #include "cpuworker.h" #include "networkstatus.h" #include "onion.h" #include "onion_fast.h" #include "onion_ntor.h" Loading @@ -27,6 +28,7 @@ typedef struct onion_queue_t { TOR_TAILQ_ENTRY(onion_queue_t) next; or_circuit_t *circ; uint16_t handshake_type; create_cell_t *onionskin; time_t when_added; } onion_queue_t; Loading @@ -34,13 +36,19 @@ typedef struct onion_queue_t { /** 5 seconds on the onion queue til we just send back a destroy */ #define ONIONQUEUE_WAIT_CUTOFF 5 /** Queue of circuits waiting for CPU workers, or NULL if the list is empty.*/ TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t) ol_list = TOR_TAILQ_HEAD_INITIALIZER(ol_list); /** Array of queues of circuits waiting for CPU workers. An element is NULL * if that queue is empty.*/ TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t) ol_list[MAX_ONION_HANDSHAKE_TYPE+1] = { TOR_TAILQ_HEAD_INITIALIZER(ol_list[0]), /* tap */ TOR_TAILQ_HEAD_INITIALIZER(ol_list[1]), /* fast */ TOR_TAILQ_HEAD_INITIALIZER(ol_list[2]), /* ntor */ }; /** Number of entries of each type currently in ol_list. */ /** Number of entries of each type currently in each element of ol_list[]. */ static int ol_entries[MAX_ONION_HANDSHAKE_TYPE+1]; static int num_ntors_per_tap(void); static void onion_queue_entry_remove(onion_queue_t *victim); /* XXXX024 Check lengths vs MAX_ONIONSKIN_{CHALLENGE,REPLY}_LEN. Loading @@ -58,23 +66,51 @@ have_room_for_onionskin(uint16_t type) const or_options_t *options = get_options(); int num_cpus; uint64_t tap_usec, ntor_usec; uint64_t ntor_during_tap_usec, tap_during_ntor_usec; /* If we've got fewer than 50 entries, we always have room for one more. */ if (ol_entries[ONION_HANDSHAKE_TYPE_TAP] + ol_entries[ONION_HANDSHAKE_TYPE_NTOR] < 50) if (ol_entries[type] < 50) return 1; num_cpus = get_num_cpus(options); /* Compute how many microseconds we'd expect to need to clear all * onionskins in the current queue. */ * onionskins in various combinations of the queues. */ /* How long would it take to process all the TAP cells in the queue? */ tap_usec = estimated_usec_for_onionskins( ol_entries[ONION_HANDSHAKE_TYPE_TAP], ONION_HANDSHAKE_TYPE_TAP) / num_cpus; /* How long would it take to process all the NTor cells in the queue? */ ntor_usec = estimated_usec_for_onionskins( ol_entries[ONION_HANDSHAKE_TYPE_NTOR], ONION_HANDSHAKE_TYPE_NTOR) / num_cpus; /* How long would it take to process the tap cells that we expect to * process while draining the ntor queue? */ tap_during_ntor_usec = estimated_usec_for_onionskins( MIN(ol_entries[ONION_HANDSHAKE_TYPE_TAP], ol_entries[ONION_HANDSHAKE_TYPE_NTOR] / num_ntors_per_tap()), ONION_HANDSHAKE_TYPE_TAP) / num_cpus; /* How long would it take to process the ntor cells that we expect to * process while draining the tap queue? */ ntor_during_tap_usec = estimated_usec_for_onionskins( MIN(ol_entries[ONION_HANDSHAKE_TYPE_NTOR], ol_entries[ONION_HANDSHAKE_TYPE_TAP] * num_ntors_per_tap()), ONION_HANDSHAKE_TYPE_NTOR) / num_cpus; /* See whether that exceeds MaxOnionQueueDelay. If so, we can't queue * this. */ if ((tap_usec + ntor_usec) / 1000 > (uint64_t)options->MaxOnionQueueDelay) if (type == ONION_HANDSHAKE_TYPE_NTOR && (ntor_usec + tap_during_ntor_usec) / 1000 > (uint64_t)options->MaxOnionQueueDelay) return 0; if (type == ONION_HANDSHAKE_TYPE_TAP && (tap_usec + ntor_during_tap_usec) / 1000 > (uint64_t)options->MaxOnionQueueDelay) return 0; #ifdef CURVE25519_ENABLED /* If we support the ntor handshake, then don't let TAP handshakes use * more than 2/3 of the space on the queue. */ Loading @@ -97,8 +133,15 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) onion_queue_t *tmp; time_t now = time(NULL); if (onionskin->handshake_type > MAX_ONION_HANDSHAKE_TYPE) { log_warn(LD_BUG, "Handshake %d out of range! Dropping.", onionskin->handshake_type); return -1; } tmp = tor_malloc_zero(sizeof(onion_queue_t)); tmp->circ = circ; tmp->handshake_type = onionskin->handshake_type; tmp->onionskin = onionskin; tmp->when_added = now; Loading @@ -107,7 +150,8 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) static ratelim_t last_warned = RATELIM_INIT(WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL); char *m; if ((m = rate_limit_log(&last_warned, approx_time()))) { if (onionskin->handshake_type == ONION_HANDSHAKE_TYPE_NTOR && (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 " Loading @@ -120,12 +164,17 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) } ++ol_entries[onionskin->handshake_type]; log_info(LD_OR, "New create (%s). Queues now ntor=%d and tap=%d.", onionskin->handshake_type == 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, tmp, next); TOR_TAILQ_INSERT_TAIL(&ol_list[onionskin->handshake_type], tmp, next); /* cull elderly requests. */ while (1) { onion_queue_t *head = TOR_TAILQ_FIRST(&ol_list); onion_queue_t *head = TOR_TAILQ_FIRST(&ol_list[onionskin->handshake_type]); if (now - head->when_added < (time_t)ONIONQUEUE_WAIT_CUTOFF) break; Loading @@ -139,24 +188,87 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) return 0; } /** Remove the first item from ol_list and return it, or return * NULL if the list is empty. /** Return a fairness parameter, to prefer processing NTOR style * handshakes but still slowly drain the TAP queue so we don't starve * it entirely. */ static int num_ntors_per_tap(void) { #define DEFAULT_NUM_NTORS_PER_TAP 10 #define MIN_NUM_NTORS_PER_TAP 1 #define MAX_NUM_NTORS_PER_TAP 100000 return networkstatus_get_param(NULL, "NumNTorsPerTAP", DEFAULT_NUM_NTORS_PER_TAP, MIN_NUM_NTORS_PER_TAP, MAX_NUM_NTORS_PER_TAP); } /** Choose which onion queue we'll pull from next. If one is empty choose * the other; if they both have elements, load balance across them but * favoring NTOR. */ static uint16_t decide_next_handshake_type(void) { /* The number of times we've chosen ntor lately when both were available. */ static int recently_chosen_ntors = 0; if (!ol_entries[ONION_HANDSHAKE_TYPE_NTOR]) return ONION_HANDSHAKE_TYPE_TAP; /* no ntors? try tap */ if (!ol_entries[ONION_HANDSHAKE_TYPE_TAP]) { /* Nick wants us to prioritize new tap requests when there aren't * any in the queue and we've processed k ntor cells since the last * tap cell. This strategy is maybe a good idea, since it starves tap * less in the case where tap is rare, or maybe a poor idea, since it * makes the new tap cell unfairly jump in front of ntor cells that * got here first. In any case this edge case will only become relevant * once tap is rare. We should reevaluate whether we like this decision * once tap gets more rare. */ if (ol_entries[ONION_HANDSHAKE_TYPE_NTOR]) ++recently_chosen_ntors; return ONION_HANDSHAKE_TYPE_NTOR; /* no taps? try ntor */ } /* They both have something queued. Pick ntor if we haven't done that * too much lately. */ if (++recently_chosen_ntors <= num_ntors_per_tap()) { return ONION_HANDSHAKE_TYPE_NTOR; } /* Else, it's time to let tap have its turn. */ recently_chosen_ntors = 0; return ONION_HANDSHAKE_TYPE_TAP; } /** Remove the highest priority item from ol_list[] and return it, or * return NULL if the lists are empty. */ or_circuit_t * onion_next_task(create_cell_t **onionskin_out) { or_circuit_t *circ; onion_queue_t *head = TOR_TAILQ_FIRST(&ol_list); 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; /* no onions pending, we're done */ tor_assert(head->circ); tor_assert(head->circ->p_chan); /* make sure it's still valid */ tor_assert(head->handshake_type <= MAX_ONION_HANDSHAKE_TYPE); // tor_assert(head->circ->p_chan); /* make sure it's still valid */ /* XXX I only commented out the above line to make the unit tests * more manageable. That's probably not good long-term. -RD */ circ = head->circ; if (head->onionskin && head->onionskin->handshake_type <= MAX_ONION_HANDSHAKE_TYPE) --ol_entries[head->onionskin->handshake_type]; if (head->onionskin) --ol_entries[head->handshake_type]; log_info(LD_OR, "Processing create (%s). Queues now ntor=%d and tap=%d.", head->handshake_type == 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; /* prevent free. */ circ->onionqueue_entry = NULL; Loading @@ -164,6 +276,14 @@ onion_next_task(create_cell_t **onionskin_out) return circ; } /** Return the number of <b>handshake_type</b>-style create requests pending. */ int onion_num_pending(uint16_t handshake_type) { return ol_entries[handshake_type]; } /** Go through ol_list, find the onion_queue_t element which points to * circ, remove and free that element. Leave circ itself alone. */ Loading @@ -185,14 +305,20 @@ onion_pending_remove(or_circuit_t *circ) static void onion_queue_entry_remove(onion_queue_t *victim) { TOR_TAILQ_REMOVE(&ol_list, victim, next); if (victim->handshake_type > MAX_ONION_HANDSHAKE_TYPE) { log_warn(LD_BUG, "Handshake %d out of range! Dropping.", victim->handshake_type); /* XXX leaks */ return; } TOR_TAILQ_REMOVE(&ol_list[victim->handshake_type], victim, next); if (victim->circ) victim->circ->onionqueue_entry = NULL; if (victim->onionskin && victim->onionskin->handshake_type <= MAX_ONION_HANDSHAKE_TYPE) --ol_entries[victim->onionskin->handshake_type]; if (victim->onionskin) --ol_entries[victim->handshake_type]; tor_free(victim->onionskin); tor_free(victim); Loading @@ -203,9 +329,12 @@ void clear_pending_onions(void) { onion_queue_t *victim; while ((victim = TOR_TAILQ_FIRST(&ol_list))) { int i; for (i=0; i<=MAX_ONION_HANDSHAKE_TYPE; i++) { while ((victim = TOR_TAILQ_FIRST(&ol_list[i]))) { onion_queue_entry_remove(victim); } } memset(ol_entries, 0, sizeof(ol_entries)); } Loading Loading @@ -513,6 +642,22 @@ check_create_cell(const create_cell_t *cell, int unknown_ok) return 0; } /** Write the various parameters into the create cell. Separate from * create_cell_parse() to make unit testing easier. */ void create_cell_init(create_cell_t *cell_out, uint8_t cell_type, uint16_t handshake_type, uint16_t handshake_len, const uint8_t *onionskin) { memset(cell_out, 0, sizeof(*cell_out)); cell_out->cell_type = cell_type; cell_out->handshake_type = handshake_type; cell_out->handshake_len = handshake_len; memcpy(cell_out->onionskin, onionskin, handshake_len); } /** Helper: parse the CREATE2 payload at <b>p</b>, which could be up to * <b>p_len</b> bytes long, and use it to fill the fields of * <b>cell_out</b>. Return 0 on success and -1 on failure. Loading @@ -523,17 +668,21 @@ check_create_cell(const create_cell_t *cell, int unknown_ok) static int parse_create2_payload(create_cell_t *cell_out, const uint8_t *p, size_t p_len) { uint16_t handshake_type, handshake_len; if (p_len < 4) return -1; cell_out->cell_type = CELL_CREATE2; cell_out->handshake_type = ntohs(get_uint16(p)); cell_out->handshake_len = ntohs(get_uint16(p+2)); if (cell_out->handshake_len > CELL_PAYLOAD_SIZE - 4 || cell_out->handshake_len > p_len - 4) handshake_type = ntohs(get_uint16(p)); handshake_len = ntohs(get_uint16(p+2)); if (handshake_len > CELL_PAYLOAD_SIZE - 4 || handshake_len > p_len - 4) return -1; if (cell_out->handshake_type == ONION_HANDSHAKE_TYPE_FAST) if (handshake_type == ONION_HANDSHAKE_TYPE_FAST) return -1; memcpy(cell_out->onionskin, p+4, cell_out->handshake_len); create_cell_init(cell_out, CELL_CREATE2, handshake_type, handshake_len, p+4); return 0; } Loading @@ -551,27 +700,19 @@ parse_create2_payload(create_cell_t *cell_out, const uint8_t *p, size_t p_len) int create_cell_parse(create_cell_t *cell_out, const cell_t *cell_in) { memset(cell_out, 0, sizeof(*cell_out)); switch (cell_in->command) { case CELL_CREATE: cell_out->cell_type = CELL_CREATE; if (tor_memeq(cell_in->payload, NTOR_CREATE_MAGIC, 16)) { cell_out->handshake_type = ONION_HANDSHAKE_TYPE_NTOR; cell_out->handshake_len = NTOR_ONIONSKIN_LEN; memcpy(cell_out->onionskin, cell_in->payload+16, NTOR_ONIONSKIN_LEN); create_cell_init(cell_out, CELL_CREATE, ONION_HANDSHAKE_TYPE_NTOR, NTOR_ONIONSKIN_LEN, cell_in->payload+16); } else { cell_out->handshake_type = ONION_HANDSHAKE_TYPE_TAP; cell_out->handshake_len = TAP_ONIONSKIN_CHALLENGE_LEN; memcpy(cell_out->onionskin, cell_in->payload, TAP_ONIONSKIN_CHALLENGE_LEN); create_cell_init(cell_out, CELL_CREATE, ONION_HANDSHAKE_TYPE_TAP, TAP_ONIONSKIN_CHALLENGE_LEN, cell_in->payload); } break; case CELL_CREATE_FAST: cell_out->cell_type = CELL_CREATE_FAST; cell_out->handshake_type = ONION_HANDSHAKE_TYPE_FAST; cell_out->handshake_len = CREATE_FAST_LEN; memcpy(cell_out->onionskin, cell_in->payload, CREATE_FAST_LEN); create_cell_init(cell_out, CELL_CREATE_FAST, ONION_HANDSHAKE_TYPE_FAST, CREATE_FAST_LEN, cell_in->payload); break; case CELL_CREATE2: if (parse_create2_payload(cell_out, cell_in->payload, Loading src/or/onion.h +4 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ struct create_cell_t; int onion_pending_add(or_circuit_t *circ, struct create_cell_t *onionskin); or_circuit_t *onion_next_task(struct create_cell_t **onionskin_out); int onion_num_pending(uint16_t handshake_type); void onion_pending_remove(or_circuit_t *circ); void clear_pending_onions(void); Loading Loading @@ -99,6 +100,9 @@ typedef struct extended_cell_t { created_cell_t created_cell; } extended_cell_t; void create_cell_init(create_cell_t *cell_out, uint8_t cell_type, uint16_t handshake_type, uint16_t handshake_len, const uint8_t *onionskin); int create_cell_parse(create_cell_t *cell_out, const cell_t *cell_in); int created_cell_parse(created_cell_t *cell_out, const cell_t *cell_in); int extend_cell_parse(extend_cell_t *cell_out, const uint8_t command, Loading src/test/test.c +47 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,8 @@ long int lround(double x); double fabs(double x); #include "or.h" #include "buffers.h" #include "circuitlist.h" #include "circuitstats.h" #include "config.h" #include "connection_edge.h" Loading @@ -50,6 +52,7 @@ double fabs(double x); #include "torgzip.h" #include "mempool.h" #include "memarea.h" #include "onion.h" #include "onion_tap.h" #include "policies.h" #include "rephist.h" Loading Loading @@ -401,6 +404,49 @@ test_ntor_handshake(void *arg) } #endif /** Run unit tests for the onion queues. */ static void test_onion_queues(void) { uint8_t buf1[TAP_ONIONSKIN_CHALLENGE_LEN] = {0}; uint8_t buf2[NTOR_ONIONSKIN_LEN] = {0}; or_circuit_t *circ1 = or_circuit_new(0, NULL); or_circuit_t *circ2 = or_circuit_new(0, NULL); create_cell_t *onionskin = NULL; create_cell_t *create1 = tor_malloc_zero(sizeof(create_cell_t)); create_cell_t *create2 = tor_malloc_zero(sizeof(create_cell_t)); create_cell_init(create1, CELL_CREATE, ONION_HANDSHAKE_TYPE_TAP, TAP_ONIONSKIN_CHALLENGE_LEN, buf1); create_cell_init(create2, CELL_CREATE, ONION_HANDSHAKE_TYPE_NTOR, NTOR_ONIONSKIN_LEN, buf2); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_pending_add(circ1, create1)); test_eq(1, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); test_eq(0, onion_pending_add(circ2, create2)); test_eq(1, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); test_eq_ptr(circ2, onion_next_task(&onionskin)); test_eq(1, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); clear_pending_onions(); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); done: ; // circuit_free(circ1); // circuit_free(circ2); /* and free create1 and create2 */ /* XXX leaks everything here */ } static void test_circuit_timeout(void) { Loading Loading @@ -1541,6 +1587,7 @@ const struct testcase_setup_t legacy_setup = { static struct testcase_t test_array[] = { ENT(onion_handshake), { "bad_onion_handshake", test_bad_onion_handshake, 0, NULL, NULL }, ENT(onion_queues), #ifdef CURVE25519_ENABLED { "ntor_handshake", test_ntor_handshake, 0, NULL, NULL }, #endif Loading Loading
changes/feature9574 0 → 100644 +7 −0 Original line number Diff line number Diff line o Major features: - Relays now process the new "NTor" circuit-level handshake requests with higher priority than the old "TAP" circuit-level handshake requests. We still process some TAP requests to not totally starve 0.2.3 clients when NTor becomes popular. A new consensus parameter "NumNTorsPerTAP" lets us tune the balance later if we need to. Implements ticket 9574.
src/or/onion.c +186 −45 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ #include "circuitlist.h" #include "config.h" #include "cpuworker.h" #include "networkstatus.h" #include "onion.h" #include "onion_fast.h" #include "onion_ntor.h" Loading @@ -27,6 +28,7 @@ typedef struct onion_queue_t { TOR_TAILQ_ENTRY(onion_queue_t) next; or_circuit_t *circ; uint16_t handshake_type; create_cell_t *onionskin; time_t when_added; } onion_queue_t; Loading @@ -34,13 +36,19 @@ typedef struct onion_queue_t { /** 5 seconds on the onion queue til we just send back a destroy */ #define ONIONQUEUE_WAIT_CUTOFF 5 /** Queue of circuits waiting for CPU workers, or NULL if the list is empty.*/ TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t) ol_list = TOR_TAILQ_HEAD_INITIALIZER(ol_list); /** Array of queues of circuits waiting for CPU workers. An element is NULL * if that queue is empty.*/ TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t) ol_list[MAX_ONION_HANDSHAKE_TYPE+1] = { TOR_TAILQ_HEAD_INITIALIZER(ol_list[0]), /* tap */ TOR_TAILQ_HEAD_INITIALIZER(ol_list[1]), /* fast */ TOR_TAILQ_HEAD_INITIALIZER(ol_list[2]), /* ntor */ }; /** Number of entries of each type currently in ol_list. */ /** Number of entries of each type currently in each element of ol_list[]. */ static int ol_entries[MAX_ONION_HANDSHAKE_TYPE+1]; static int num_ntors_per_tap(void); static void onion_queue_entry_remove(onion_queue_t *victim); /* XXXX024 Check lengths vs MAX_ONIONSKIN_{CHALLENGE,REPLY}_LEN. Loading @@ -58,23 +66,51 @@ have_room_for_onionskin(uint16_t type) const or_options_t *options = get_options(); int num_cpus; uint64_t tap_usec, ntor_usec; uint64_t ntor_during_tap_usec, tap_during_ntor_usec; /* If we've got fewer than 50 entries, we always have room for one more. */ if (ol_entries[ONION_HANDSHAKE_TYPE_TAP] + ol_entries[ONION_HANDSHAKE_TYPE_NTOR] < 50) if (ol_entries[type] < 50) return 1; num_cpus = get_num_cpus(options); /* Compute how many microseconds we'd expect to need to clear all * onionskins in the current queue. */ * onionskins in various combinations of the queues. */ /* How long would it take to process all the TAP cells in the queue? */ tap_usec = estimated_usec_for_onionskins( ol_entries[ONION_HANDSHAKE_TYPE_TAP], ONION_HANDSHAKE_TYPE_TAP) / num_cpus; /* How long would it take to process all the NTor cells in the queue? */ ntor_usec = estimated_usec_for_onionskins( ol_entries[ONION_HANDSHAKE_TYPE_NTOR], ONION_HANDSHAKE_TYPE_NTOR) / num_cpus; /* How long would it take to process the tap cells that we expect to * process while draining the ntor queue? */ tap_during_ntor_usec = estimated_usec_for_onionskins( MIN(ol_entries[ONION_HANDSHAKE_TYPE_TAP], ol_entries[ONION_HANDSHAKE_TYPE_NTOR] / num_ntors_per_tap()), ONION_HANDSHAKE_TYPE_TAP) / num_cpus; /* How long would it take to process the ntor cells that we expect to * process while draining the tap queue? */ ntor_during_tap_usec = estimated_usec_for_onionskins( MIN(ol_entries[ONION_HANDSHAKE_TYPE_NTOR], ol_entries[ONION_HANDSHAKE_TYPE_TAP] * num_ntors_per_tap()), ONION_HANDSHAKE_TYPE_NTOR) / num_cpus; /* See whether that exceeds MaxOnionQueueDelay. If so, we can't queue * this. */ if ((tap_usec + ntor_usec) / 1000 > (uint64_t)options->MaxOnionQueueDelay) if (type == ONION_HANDSHAKE_TYPE_NTOR && (ntor_usec + tap_during_ntor_usec) / 1000 > (uint64_t)options->MaxOnionQueueDelay) return 0; if (type == ONION_HANDSHAKE_TYPE_TAP && (tap_usec + ntor_during_tap_usec) / 1000 > (uint64_t)options->MaxOnionQueueDelay) return 0; #ifdef CURVE25519_ENABLED /* If we support the ntor handshake, then don't let TAP handshakes use * more than 2/3 of the space on the queue. */ Loading @@ -97,8 +133,15 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) onion_queue_t *tmp; time_t now = time(NULL); if (onionskin->handshake_type > MAX_ONION_HANDSHAKE_TYPE) { log_warn(LD_BUG, "Handshake %d out of range! Dropping.", onionskin->handshake_type); return -1; } tmp = tor_malloc_zero(sizeof(onion_queue_t)); tmp->circ = circ; tmp->handshake_type = onionskin->handshake_type; tmp->onionskin = onionskin; tmp->when_added = now; Loading @@ -107,7 +150,8 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) static ratelim_t last_warned = RATELIM_INIT(WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL); char *m; if ((m = rate_limit_log(&last_warned, approx_time()))) { if (onionskin->handshake_type == ONION_HANDSHAKE_TYPE_NTOR && (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 " Loading @@ -120,12 +164,17 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) } ++ol_entries[onionskin->handshake_type]; log_info(LD_OR, "New create (%s). Queues now ntor=%d and tap=%d.", onionskin->handshake_type == 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, tmp, next); TOR_TAILQ_INSERT_TAIL(&ol_list[onionskin->handshake_type], tmp, next); /* cull elderly requests. */ while (1) { onion_queue_t *head = TOR_TAILQ_FIRST(&ol_list); onion_queue_t *head = TOR_TAILQ_FIRST(&ol_list[onionskin->handshake_type]); if (now - head->when_added < (time_t)ONIONQUEUE_WAIT_CUTOFF) break; Loading @@ -139,24 +188,87 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) return 0; } /** Remove the first item from ol_list and return it, or return * NULL if the list is empty. /** Return a fairness parameter, to prefer processing NTOR style * handshakes but still slowly drain the TAP queue so we don't starve * it entirely. */ static int num_ntors_per_tap(void) { #define DEFAULT_NUM_NTORS_PER_TAP 10 #define MIN_NUM_NTORS_PER_TAP 1 #define MAX_NUM_NTORS_PER_TAP 100000 return networkstatus_get_param(NULL, "NumNTorsPerTAP", DEFAULT_NUM_NTORS_PER_TAP, MIN_NUM_NTORS_PER_TAP, MAX_NUM_NTORS_PER_TAP); } /** Choose which onion queue we'll pull from next. If one is empty choose * the other; if they both have elements, load balance across them but * favoring NTOR. */ static uint16_t decide_next_handshake_type(void) { /* The number of times we've chosen ntor lately when both were available. */ static int recently_chosen_ntors = 0; if (!ol_entries[ONION_HANDSHAKE_TYPE_NTOR]) return ONION_HANDSHAKE_TYPE_TAP; /* no ntors? try tap */ if (!ol_entries[ONION_HANDSHAKE_TYPE_TAP]) { /* Nick wants us to prioritize new tap requests when there aren't * any in the queue and we've processed k ntor cells since the last * tap cell. This strategy is maybe a good idea, since it starves tap * less in the case where tap is rare, or maybe a poor idea, since it * makes the new tap cell unfairly jump in front of ntor cells that * got here first. In any case this edge case will only become relevant * once tap is rare. We should reevaluate whether we like this decision * once tap gets more rare. */ if (ol_entries[ONION_HANDSHAKE_TYPE_NTOR]) ++recently_chosen_ntors; return ONION_HANDSHAKE_TYPE_NTOR; /* no taps? try ntor */ } /* They both have something queued. Pick ntor if we haven't done that * too much lately. */ if (++recently_chosen_ntors <= num_ntors_per_tap()) { return ONION_HANDSHAKE_TYPE_NTOR; } /* Else, it's time to let tap have its turn. */ recently_chosen_ntors = 0; return ONION_HANDSHAKE_TYPE_TAP; } /** Remove the highest priority item from ol_list[] and return it, or * return NULL if the lists are empty. */ or_circuit_t * onion_next_task(create_cell_t **onionskin_out) { or_circuit_t *circ; onion_queue_t *head = TOR_TAILQ_FIRST(&ol_list); 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; /* no onions pending, we're done */ tor_assert(head->circ); tor_assert(head->circ->p_chan); /* make sure it's still valid */ tor_assert(head->handshake_type <= MAX_ONION_HANDSHAKE_TYPE); // tor_assert(head->circ->p_chan); /* make sure it's still valid */ /* XXX I only commented out the above line to make the unit tests * more manageable. That's probably not good long-term. -RD */ circ = head->circ; if (head->onionskin && head->onionskin->handshake_type <= MAX_ONION_HANDSHAKE_TYPE) --ol_entries[head->onionskin->handshake_type]; if (head->onionskin) --ol_entries[head->handshake_type]; log_info(LD_OR, "Processing create (%s). Queues now ntor=%d and tap=%d.", head->handshake_type == 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; /* prevent free. */ circ->onionqueue_entry = NULL; Loading @@ -164,6 +276,14 @@ onion_next_task(create_cell_t **onionskin_out) return circ; } /** Return the number of <b>handshake_type</b>-style create requests pending. */ int onion_num_pending(uint16_t handshake_type) { return ol_entries[handshake_type]; } /** Go through ol_list, find the onion_queue_t element which points to * circ, remove and free that element. Leave circ itself alone. */ Loading @@ -185,14 +305,20 @@ onion_pending_remove(or_circuit_t *circ) static void onion_queue_entry_remove(onion_queue_t *victim) { TOR_TAILQ_REMOVE(&ol_list, victim, next); if (victim->handshake_type > MAX_ONION_HANDSHAKE_TYPE) { log_warn(LD_BUG, "Handshake %d out of range! Dropping.", victim->handshake_type); /* XXX leaks */ return; } TOR_TAILQ_REMOVE(&ol_list[victim->handshake_type], victim, next); if (victim->circ) victim->circ->onionqueue_entry = NULL; if (victim->onionskin && victim->onionskin->handshake_type <= MAX_ONION_HANDSHAKE_TYPE) --ol_entries[victim->onionskin->handshake_type]; if (victim->onionskin) --ol_entries[victim->handshake_type]; tor_free(victim->onionskin); tor_free(victim); Loading @@ -203,9 +329,12 @@ void clear_pending_onions(void) { onion_queue_t *victim; while ((victim = TOR_TAILQ_FIRST(&ol_list))) { int i; for (i=0; i<=MAX_ONION_HANDSHAKE_TYPE; i++) { while ((victim = TOR_TAILQ_FIRST(&ol_list[i]))) { onion_queue_entry_remove(victim); } } memset(ol_entries, 0, sizeof(ol_entries)); } Loading Loading @@ -513,6 +642,22 @@ check_create_cell(const create_cell_t *cell, int unknown_ok) return 0; } /** Write the various parameters into the create cell. Separate from * create_cell_parse() to make unit testing easier. */ void create_cell_init(create_cell_t *cell_out, uint8_t cell_type, uint16_t handshake_type, uint16_t handshake_len, const uint8_t *onionskin) { memset(cell_out, 0, sizeof(*cell_out)); cell_out->cell_type = cell_type; cell_out->handshake_type = handshake_type; cell_out->handshake_len = handshake_len; memcpy(cell_out->onionskin, onionskin, handshake_len); } /** Helper: parse the CREATE2 payload at <b>p</b>, which could be up to * <b>p_len</b> bytes long, and use it to fill the fields of * <b>cell_out</b>. Return 0 on success and -1 on failure. Loading @@ -523,17 +668,21 @@ check_create_cell(const create_cell_t *cell, int unknown_ok) static int parse_create2_payload(create_cell_t *cell_out, const uint8_t *p, size_t p_len) { uint16_t handshake_type, handshake_len; if (p_len < 4) return -1; cell_out->cell_type = CELL_CREATE2; cell_out->handshake_type = ntohs(get_uint16(p)); cell_out->handshake_len = ntohs(get_uint16(p+2)); if (cell_out->handshake_len > CELL_PAYLOAD_SIZE - 4 || cell_out->handshake_len > p_len - 4) handshake_type = ntohs(get_uint16(p)); handshake_len = ntohs(get_uint16(p+2)); if (handshake_len > CELL_PAYLOAD_SIZE - 4 || handshake_len > p_len - 4) return -1; if (cell_out->handshake_type == ONION_HANDSHAKE_TYPE_FAST) if (handshake_type == ONION_HANDSHAKE_TYPE_FAST) return -1; memcpy(cell_out->onionskin, p+4, cell_out->handshake_len); create_cell_init(cell_out, CELL_CREATE2, handshake_type, handshake_len, p+4); return 0; } Loading @@ -551,27 +700,19 @@ parse_create2_payload(create_cell_t *cell_out, const uint8_t *p, size_t p_len) int create_cell_parse(create_cell_t *cell_out, const cell_t *cell_in) { memset(cell_out, 0, sizeof(*cell_out)); switch (cell_in->command) { case CELL_CREATE: cell_out->cell_type = CELL_CREATE; if (tor_memeq(cell_in->payload, NTOR_CREATE_MAGIC, 16)) { cell_out->handshake_type = ONION_HANDSHAKE_TYPE_NTOR; cell_out->handshake_len = NTOR_ONIONSKIN_LEN; memcpy(cell_out->onionskin, cell_in->payload+16, NTOR_ONIONSKIN_LEN); create_cell_init(cell_out, CELL_CREATE, ONION_HANDSHAKE_TYPE_NTOR, NTOR_ONIONSKIN_LEN, cell_in->payload+16); } else { cell_out->handshake_type = ONION_HANDSHAKE_TYPE_TAP; cell_out->handshake_len = TAP_ONIONSKIN_CHALLENGE_LEN; memcpy(cell_out->onionskin, cell_in->payload, TAP_ONIONSKIN_CHALLENGE_LEN); create_cell_init(cell_out, CELL_CREATE, ONION_HANDSHAKE_TYPE_TAP, TAP_ONIONSKIN_CHALLENGE_LEN, cell_in->payload); } break; case CELL_CREATE_FAST: cell_out->cell_type = CELL_CREATE_FAST; cell_out->handshake_type = ONION_HANDSHAKE_TYPE_FAST; cell_out->handshake_len = CREATE_FAST_LEN; memcpy(cell_out->onionskin, cell_in->payload, CREATE_FAST_LEN); create_cell_init(cell_out, CELL_CREATE_FAST, ONION_HANDSHAKE_TYPE_FAST, CREATE_FAST_LEN, cell_in->payload); break; case CELL_CREATE2: if (parse_create2_payload(cell_out, cell_in->payload, Loading
src/or/onion.h +4 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ struct create_cell_t; int onion_pending_add(or_circuit_t *circ, struct create_cell_t *onionskin); or_circuit_t *onion_next_task(struct create_cell_t **onionskin_out); int onion_num_pending(uint16_t handshake_type); void onion_pending_remove(or_circuit_t *circ); void clear_pending_onions(void); Loading Loading @@ -99,6 +100,9 @@ typedef struct extended_cell_t { created_cell_t created_cell; } extended_cell_t; void create_cell_init(create_cell_t *cell_out, uint8_t cell_type, uint16_t handshake_type, uint16_t handshake_len, const uint8_t *onionskin); int create_cell_parse(create_cell_t *cell_out, const cell_t *cell_in); int created_cell_parse(created_cell_t *cell_out, const cell_t *cell_in); int extend_cell_parse(extend_cell_t *cell_out, const uint8_t command, Loading
src/test/test.c +47 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,8 @@ long int lround(double x); double fabs(double x); #include "or.h" #include "buffers.h" #include "circuitlist.h" #include "circuitstats.h" #include "config.h" #include "connection_edge.h" Loading @@ -50,6 +52,7 @@ double fabs(double x); #include "torgzip.h" #include "mempool.h" #include "memarea.h" #include "onion.h" #include "onion_tap.h" #include "policies.h" #include "rephist.h" Loading Loading @@ -401,6 +404,49 @@ test_ntor_handshake(void *arg) } #endif /** Run unit tests for the onion queues. */ static void test_onion_queues(void) { uint8_t buf1[TAP_ONIONSKIN_CHALLENGE_LEN] = {0}; uint8_t buf2[NTOR_ONIONSKIN_LEN] = {0}; or_circuit_t *circ1 = or_circuit_new(0, NULL); or_circuit_t *circ2 = or_circuit_new(0, NULL); create_cell_t *onionskin = NULL; create_cell_t *create1 = tor_malloc_zero(sizeof(create_cell_t)); create_cell_t *create2 = tor_malloc_zero(sizeof(create_cell_t)); create_cell_init(create1, CELL_CREATE, ONION_HANDSHAKE_TYPE_TAP, TAP_ONIONSKIN_CHALLENGE_LEN, buf1); create_cell_init(create2, CELL_CREATE, ONION_HANDSHAKE_TYPE_NTOR, NTOR_ONIONSKIN_LEN, buf2); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_pending_add(circ1, create1)); test_eq(1, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); test_eq(0, onion_pending_add(circ2, create2)); test_eq(1, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); test_eq_ptr(circ2, onion_next_task(&onionskin)); test_eq(1, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); clear_pending_onions(); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_TAP)); test_eq(0, onion_num_pending(ONION_HANDSHAKE_TYPE_NTOR)); done: ; // circuit_free(circ1); // circuit_free(circ2); /* and free create1 and create2 */ /* XXX leaks everything here */ } static void test_circuit_timeout(void) { Loading Loading @@ -1541,6 +1587,7 @@ const struct testcase_setup_t legacy_setup = { static struct testcase_t test_array[] = { ENT(onion_handshake), { "bad_onion_handshake", test_bad_onion_handshake, 0, NULL, NULL }, ENT(onion_queues), #ifdef CURVE25519_ENABLED { "ntor_handshake", test_ntor_handshake, 0, NULL, NULL }, #endif Loading