Loading changes/bug7341 0 → 100644 +7 −0 Original line number Diff line number Diff line o Minor features: - Improve circuit build timeout handling for hidden services. In particular: adjust build timeouts more accurately depending upon the number of hop-RTTs that a particular circuit type undergoes. Additionally, launch intro circuits in parallel if they timeout, and take the first one to reply as valid. src/or/circuitbuild.c +182 −3 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ #include "routerparse.h" #include "routerset.h" #include "crypto.h" #include "connection_edge.h" #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) Loading Loading @@ -1503,6 +1504,171 @@ pathbias_count_build_success(origin_circuit_t *circ) } } /** * Send a probe down a circuit that the client attempted to use, * but for which the stream timed out/failed. The probe is a * RELAY_BEGIN cell with a 0.a.b.c destination address, which * the exit will reject and reply back, echoing that address. * * The reason for such probes is because it is possible to bias * a user's paths simply by causing timeouts, and these timeouts * are not possible to differentiate from unresponsive servers. * * The probe is sent at the end of the circuit lifetime for two * reasons: to prevent cyptographic taggers from being able to * drop cells to cause timeouts, and to prevent easy recognition * of probes before any real client traffic happens. * * Returns -1 if we couldn't probe, 0 otherwise. */ static int pathbias_send_usable_probe(circuit_t *circ) { /* Based on connection_ap_handshake_send_begin() */ char payload[CELL_PAYLOAD_SIZE]; int payload_len; origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); crypt_path_t *cpath_layer = NULL; char *probe_nonce = NULL; tor_assert(ocirc); cpath_layer = ocirc->cpath->prev; if (cpath_layer->state != CPATH_STATE_OPEN) { /* This can happen for cannibalized circuits. Their * last hop isn't yet open */ log_info(LD_CIRC, "Got pathbias probe request for unopened circuit %d. " "Opened %d, len %d", ocirc->global_identifier, ocirc->has_opened, ocirc->build_state->desired_path_len); return -1; } /* We already went down this road. */ if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING && ocirc->pathbias_probe_id) { log_info(LD_CIRC, "Got pathbias probe request for circuit %d with " "outstanding probe", ocirc->global_identifier); return -1; } circuit_change_purpose(circ, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); /* Update timestamp for circuit_expire_building to kill us */ tor_gettimeofday(&circ->timestamp_began); /* Generate a random address for the nonce */ crypto_rand((char*)ô->pathbias_probe_nonce, sizeof(ocirc->pathbias_probe_nonce)); ocirc->pathbias_probe_nonce &= 0x00ffffff; probe_nonce = tor_dup_ip(ocirc->pathbias_probe_nonce); tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce); payload_len = (int)strlen(payload)+1; // XXX: need this? Can we assume ipv4 will always be supported? // If not, how do we tell? //if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) { // set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags)); // payload_len += 4; //} /* Generate+Store stream id, make sure it's non-zero */ ocirc->pathbias_probe_id = get_unique_stream_id_by_circ(ocirc); if (ocirc->pathbias_probe_id==0) { log_warn(LD_CIRC, "Ran out of stream IDs on circuit %u during " "pathbias probe attempt.", ocirc->global_identifier); tor_free(probe_nonce); return -1; } log_info(LD_CIRC, "Sending pathbias testing cell to %s:25 on stream %d for circ %d.", probe_nonce, ocirc->pathbias_probe_id, ocirc->global_identifier); tor_free(probe_nonce); /* Send a test relay cell */ if (relay_send_command_from_edge(ocirc->pathbias_probe_id, circ, RELAY_COMMAND_BEGIN, payload, payload_len, cpath_layer) < 0) { log_notice(LD_CIRC, "Failed to send pathbias probe cell on circuit %d.", ocirc->global_identifier); return -1; } /* Mark it freshly dirty so it doesn't get expired in the meantime */ circ->timestamp_dirty = time(NULL); return 0; } /** * Check the response to a pathbias probe, to ensure the * cell is recognized and the nonce and other probe * characteristics are as expected. * * If the response is valid, return 0. Otherwise return < 0. */ int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell) { /* Based on connection_edge_process_relay_cell() */ relay_header_t rh; int reason; uint32_t ipv4_host; origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); tor_assert(cell); tor_assert(ocirc); tor_assert(circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING); relay_header_unpack(&rh, cell->payload); reason = rh.length > 0 ? get_uint8(cell->payload+RELAY_HEADER_SIZE) : END_STREAM_REASON_MISC; if (rh.command == RELAY_COMMAND_END && reason == END_STREAM_REASON_EXITPOLICY && ocirc->pathbias_probe_id == rh.stream_id) { /* Check length+extract host: It is in network order after the reason code. * See connection_edge_end(). */ if (rh.length < 9) { /* reason+ipv4+dns_ttl */ log_notice(LD_PROTOCOL, "Short path bias probe response length field (%d).", rh.length); return - END_CIRC_REASON_TORPROTOCOL; } ipv4_host = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); /* Check nonce */ if (ipv4_host == ocirc->pathbias_probe_nonce) { ocirc->path_state = PATH_STATE_USE_SUCCEEDED; circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); log_info(LD_CIRC, "Got valid path bias probe back for circ %d, stream %d.", ocirc->global_identifier, ocirc->pathbias_probe_id); return 0; } else { log_notice(LD_CIRC, "Got strange probe value 0x%x vs 0x%x back for circ %d, " "stream %d.", ipv4_host, ocirc->pathbias_probe_nonce, ocirc->global_identifier, ocirc->pathbias_probe_id); return -1; } } log_info(LD_CIRC, "Got another cell back back on pathbias probe circuit %d: " "Command: %d, Reason: %d, Stream-id: %d", ocirc->global_identifier, rh.command, reason, rh.stream_id); return -1; } /** * Check if a circuit was used and/or closed successfully. * Loading @@ -1512,18 +1678,26 @@ pathbias_count_build_success(origin_circuit_t *circ) * * If we *have* successfully used the circuit, or it appears to * have been closed by us locally, count it as a success. * * Returns 0 if we're done making decisions with the circ, * or -1 if we want to probe it first. */ void int pathbias_check_close(origin_circuit_t *ocirc, int reason) { circuit_t *circ = ô->base_; if (!pathbias_should_count(ocirc)) { return; return 0; } if (ocirc->path_state == PATH_STATE_BUILD_SUCCEEDED) { if (circ->timestamp_dirty) { if (pathbias_send_usable_probe(circ) == 0) return -1; else pathbias_count_unusable(ocirc); /* Any circuit where there were attempted streams but no successful * streams could be bias */ log_info(LD_CIRC, Loading @@ -1533,7 +1707,7 @@ pathbias_check_close(origin_circuit_t *ocirc, int reason) reason, circ->purpose, ocirc->has_opened, circuit_state_to_string(circ->state), ocirc->build_state->desired_path_len); pathbias_count_unusable(ocirc); } else { if (reason & END_CIRC_REASON_FLAG_REMOTE) { /* Unused remote circ close reasons all could be bias */ Loading Loading @@ -1569,6 +1743,8 @@ pathbias_check_close(origin_circuit_t *ocirc, int reason) } else if (ocirc->path_state == PATH_STATE_USE_SUCCEEDED) { pathbias_count_successful_close(ocirc); } return 0; } /** Loading Loading @@ -2567,6 +2743,9 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit) { int err_reason = 0; warn_if_last_router_excluded(circ, exit); tor_gettimeofday(&circ->base_.timestamp_began); circuit_append_new_exit(circ, exit); circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); if ((err_reason = circuit_send_next_onion_skin(circ))<0) { Loading src/or/circuitbuild.h +2 −1 Original line number Diff line number Diff line Loading @@ -60,7 +60,8 @@ const node_t *choose_good_entry_server(uint8_t purpose, double pathbias_get_extreme_rate(const or_options_t *options); int pathbias_get_dropguards(const or_options_t *options); void pathbias_count_timeout(origin_circuit_t *circ); void pathbias_check_close(origin_circuit_t *circ, int reason); int pathbias_check_close(origin_circuit_t *circ, int reason); int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell); #endif src/or/circuitlist.c +7 −1 Original line number Diff line number Diff line Loading @@ -414,6 +414,8 @@ circuit_purpose_to_controller_string(uint8_t purpose) return "MEASURE_TIMEOUT"; case CIRCUIT_PURPOSE_CONTROLLER: return "CONTROLLER"; case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: return "PATH_BIAS_TESTING"; default: tor_snprintf(buf, sizeof(buf), "UNKNOWN_%d", (int)purpose); Loading Loading @@ -441,6 +443,7 @@ circuit_purpose_to_controller_hs_state_string(uint8_t purpose) case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT: case CIRCUIT_PURPOSE_TESTING: case CIRCUIT_PURPOSE_CONTROLLER: case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: return NULL; case CIRCUIT_PURPOSE_INTRO_POINT: Loading Loading @@ -1356,7 +1359,10 @@ circuit_mark_for_close_(circuit_t *circ, int reason, int line, } if (CIRCUIT_IS_ORIGIN(circ)) { pathbias_check_close(TO_ORIGIN_CIRCUIT(circ), reason); if (pathbias_check_close(TO_ORIGIN_CIRCUIT(circ), reason) == -1) { /* Don't close it yet, we need to test it first */ return; } /* We don't send reasons when closing circuits at the origin. */ reason = END_CIRC_REASON_NONE; Loading src/or/circuituse.c +118 −51 Original line number Diff line number Diff line Loading @@ -280,17 +280,19 @@ circuit_get_best(const entry_connection_t *conn, if (!CIRCUIT_IS_ORIGIN(circ)) continue; origin_circ = TO_ORIGIN_CIRCUIT(circ); if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose, need_uptime,need_internal,now.tv_sec)) continue; /* Log an info message if we're going to launch a new intro circ in * parallel */ if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && !must_be_open && circ->state != CIRCUIT_STATE_OPEN && tv_mdiff(&now, &circ->timestamp_began) > circ_times.timeout_ms) { !must_be_open && origin_circ->hs_circ_has_timed_out) { intro_going_on_but_too_old = 1; continue; } if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose, need_uptime,need_internal,now.tv_sec)) continue; /* now this is an acceptable circ to hand back. but that doesn't * mean it's the *best* circ to hand back. try to decide. */ Loading Loading @@ -367,8 +369,8 @@ circuit_expire_building(void) * circuit_build_times_get_initial_timeout() if we haven't computed * custom timeouts yet */ struct timeval general_cutoff, begindir_cutoff, fourhop_cutoff, cannibalize_cutoff, close_cutoff, extremely_old_cutoff, hs_extremely_old_cutoff; close_cutoff, extremely_old_cutoff, hs_extremely_old_cutoff, cannibalized_cutoff, c_intro_cutoff, s_intro_cutoff, stream_cutoff; const or_options_t *options = get_options(); struct timeval now; cpath_build_state_t *build_state; Loading Loading @@ -407,10 +409,60 @@ circuit_expire_building(void) timersub(&now, &diff, &target); \ } while (0) /** * Because circuit build timeout is calculated only based on 3 hop * general purpose circuit construction, we need to scale the timeout * to make it properly apply to longer circuits, and circuits of * certain usage types. The following diagram illustrates how we * derive the scaling below. In short, we calculate the number * of times our telescoping-based circuit construction causes cells * to traverse each link for the circuit purpose types in question, * and then assume each link is equivalent. * * OP --a--> A --b--> B --c--> C * OP --a--> A --b--> B --c--> C --d--> D * * Let h = a = b = c = d * * Three hops (general_cutoff) * RTTs = 3a + 2b + c * RTTs = 6h * Cannibalized: * RTTs = a+b+c+d * RTTs = 4h * Four hops: * RTTs = 4a + 3b + 2c + d * RTTs = 10h * Client INTRODUCE1+ACK: // XXX: correct? * RTTs = 5a + 4b + 3c + 2d * RTTs = 14h * Server intro: * RTTs = 4a + 3b + 2c * RTTs = 9h */ SET_CUTOFF(general_cutoff, circ_times.timeout_ms); SET_CUTOFF(begindir_cutoff, circ_times.timeout_ms); SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (4/3.0)); SET_CUTOFF(cannibalize_cutoff, circ_times.timeout_ms / 2.0); /* > 3hop circs seem to have a 1.0 second delay on their cannibalized * 4th hop. */ SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (10/6.0) + 1000); /* CIRCUIT_PURPOSE_C_ESTABLISH_REND behaves more like a RELAY cell. * Use the stream cutoff (more or less). */ SET_CUTOFF(stream_cutoff, MAX(options->CircuitStreamTimeout,15)*1000 + 1000); /* Be lenient with cannibalized circs. They already survived the official * CBT, and they're usually not perf-critical. */ SET_CUTOFF(cannibalized_cutoff, MAX(circ_times.close_ms*(4/6.0), options->CircuitStreamTimeout * 1000) + 1000); // Intro circs have an extra round trip (and are also 4 hops long) SET_CUTOFF(c_intro_cutoff, circ_times.timeout_ms * (14/6.0) + 1000); // Server intro circs have an extra round trip SET_CUTOFF(s_intro_cutoff, circ_times.timeout_ms * (9/6.0) + 1000); SET_CUTOFF(close_cutoff, circ_times.close_ms); SET_CUTOFF(extremely_old_cutoff, circ_times.close_ms*2 + 1000); Loading Loading @@ -441,13 +493,22 @@ circuit_expire_building(void) build_state = TO_ORIGIN_CIRCUIT(victim)->build_state; if (build_state && build_state->onehop_tunnel) cutoff = begindir_cutoff; else if (build_state && build_state->desired_path_len == 4 && !TO_ORIGIN_CIRCUIT(victim)->has_opened) cutoff = fourhop_cutoff; else if (TO_ORIGIN_CIRCUIT(victim)->has_opened) cutoff = cannibalize_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) cutoff = close_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) cutoff = c_intro_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) cutoff = s_intro_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND) cutoff = stream_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING) cutoff = close_cutoff; else if (TO_ORIGIN_CIRCUIT(victim)->has_opened && victim->state != CIRCUIT_STATE_OPEN) cutoff = cannibalized_cutoff; else if (build_state && build_state->desired_path_len >= 4) cutoff = fourhop_cutoff; else cutoff = general_cutoff; Loading Loading @@ -520,8 +581,6 @@ circuit_expire_building(void) default: /* most open circuits can be left alone. */ continue; /* yes, continue inside a switch refers to the nearest * enclosing loop. C is smart. */ case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_INTRODUCING: case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: break; /* too old, need to die */ case CIRCUIT_PURPOSE_C_REND_READY: Loading @@ -533,6 +592,19 @@ circuit_expire_building(void) victim->timestamp_dirty > cutoff.tv_sec) continue; break; case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: /* Open path bias testing circuits are given a long * time to complete the test, but not forever */ TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED; break; case CIRCUIT_PURPOSE_C_INTRODUCING: /* We keep old introducing circuits around for * a while in parallel, and they can end up "opened". * We decide below if we're going to mark them timed * out and eventually close them. */ break; case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: /* rend and intro circs become dirty each time they Loading Loading @@ -596,6 +668,18 @@ circuit_expire_building(void) circuit_build_times_set_timeout(&circ_times); } } if (TO_ORIGIN_CIRCUIT(victim)->has_opened && victim->purpose != CIRCUIT_PURPOSE_PATH_BIAS_TESTING) { /* For path bias: we want to let these guys live for a while * so we get a chance to test them. */ log_info(LD_CIRC, "Allowing cannibalized circuit %d time to finish building " "as a pathbias testing circ.", TO_ORIGIN_CIRCUIT(victim)->global_identifier); circuit_change_purpose(victim, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); continue; /* It now should have a longer timeout next time */ } } /* If this is a hidden service client circuit which is far enough Loading @@ -621,6 +705,9 @@ circuit_expire_building(void) if (TO_ORIGIN_CIRCUIT(victim)->build_state->pending_final_cpath == NULL) break; /* fallthrough! */ case CIRCUIT_PURPOSE_C_INTRODUCING: /* connection_ap_handshake_attach_circuit() will relaunch for us */ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: /* If we have reached this line, we want to spare the circ for now. */ Loading Loading @@ -653,15 +740,23 @@ circuit_expire_building(void) } if (victim->n_chan) log_info(LD_CIRC,"Abandoning circ %s:%d (state %d:%s, purpose %d)", log_info(LD_CIRC, "Abandoning circ %u %s:%d (state %d,%d:%s, purpose %d, " "len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, channel_get_canonical_remote_descr(victim->n_chan), victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened, victim->state, circuit_state_to_string(victim->state), victim->purpose); victim->purpose, TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); else log_info(LD_CIRC,"Abandoning circ %d (state %d:%s, purpose %d)", victim->n_circ_id, victim->state, circuit_state_to_string(victim->state), victim->purpose); log_info(LD_CIRC, "Abandoning circ %u %d (state %d,%d:%s, purpose %d, len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened, victim->state, circuit_state_to_string(victim->state), victim->purpose, TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); circuit_log_path(LOG_INFO,LD_CIRC,TO_ORIGIN_CIRCUIT(victim)); if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) Loading Loading @@ -1165,18 +1260,6 @@ circuit_has_opened(origin_circuit_t *circ) { control_event_circuit_status(circ, CIRC_EVENT_BUILT, 0); /* Cannibalized circuits count as used for path bias. * (PURPOSE_GENERAL circs especially, since they are * marked dirty and often go unused after preemptive * building). */ // XXX: Cannibalized now use RELAY_EARLY, which is visible // to taggers end-to-end! We really need to probe these instead. // Don't forget to remove this check once that's done! if (circ->has_opened && circ->build_state->desired_path_len > DEFAULT_ROUTE_LEN) { circ->path_state = PATH_STATE_USE_SUCCEEDED; } /* Remember that this circuit has finished building. Now if we start * it building again later (e.g. by extending it), we will know not * to consider its build time. */ Loading Loading @@ -2101,28 +2184,12 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (retval > 0) { /* one has already sent the intro. keep waiting. */ circuit_t *c = NULL; tor_assert(introcirc); log_info(LD_REND, "Intro circ %d present and awaiting ack (rend %d). " "Stalling. (stream %d sec old)", introcirc->base_.n_circ_id, rendcirc ? rendcirc->base_.n_circ_id : 0, conn_age); /* abort parallel intro circs, if any */ for (c = global_circuitlist; c; c = c->next) { if (c->purpose == CIRCUIT_PURPOSE_C_INTRODUCING && !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c); if (oc->rend_data && !rend_cmp_service_ids( ENTRY_TO_EDGE_CONN(conn)->rend_data->onion_address, oc->rend_data->onion_address)) { log_info(LD_REND|LD_CIRC, "Closing introduction circuit that we " "built in parallel."); circuit_mark_for_close(c, END_CIRC_REASON_TIMEOUT); } } } return 0; } Loading Loading
changes/bug7341 0 → 100644 +7 −0 Original line number Diff line number Diff line o Minor features: - Improve circuit build timeout handling for hidden services. In particular: adjust build timeouts more accurately depending upon the number of hop-RTTs that a particular circuit type undergoes. Additionally, launch intro circuits in parallel if they timeout, and take the first one to reply as valid.
src/or/circuitbuild.c +182 −3 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ #include "routerparse.h" #include "routerset.h" #include "crypto.h" #include "connection_edge.h" #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) Loading Loading @@ -1503,6 +1504,171 @@ pathbias_count_build_success(origin_circuit_t *circ) } } /** * Send a probe down a circuit that the client attempted to use, * but for which the stream timed out/failed. The probe is a * RELAY_BEGIN cell with a 0.a.b.c destination address, which * the exit will reject and reply back, echoing that address. * * The reason for such probes is because it is possible to bias * a user's paths simply by causing timeouts, and these timeouts * are not possible to differentiate from unresponsive servers. * * The probe is sent at the end of the circuit lifetime for two * reasons: to prevent cyptographic taggers from being able to * drop cells to cause timeouts, and to prevent easy recognition * of probes before any real client traffic happens. * * Returns -1 if we couldn't probe, 0 otherwise. */ static int pathbias_send_usable_probe(circuit_t *circ) { /* Based on connection_ap_handshake_send_begin() */ char payload[CELL_PAYLOAD_SIZE]; int payload_len; origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); crypt_path_t *cpath_layer = NULL; char *probe_nonce = NULL; tor_assert(ocirc); cpath_layer = ocirc->cpath->prev; if (cpath_layer->state != CPATH_STATE_OPEN) { /* This can happen for cannibalized circuits. Their * last hop isn't yet open */ log_info(LD_CIRC, "Got pathbias probe request for unopened circuit %d. " "Opened %d, len %d", ocirc->global_identifier, ocirc->has_opened, ocirc->build_state->desired_path_len); return -1; } /* We already went down this road. */ if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING && ocirc->pathbias_probe_id) { log_info(LD_CIRC, "Got pathbias probe request for circuit %d with " "outstanding probe", ocirc->global_identifier); return -1; } circuit_change_purpose(circ, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); /* Update timestamp for circuit_expire_building to kill us */ tor_gettimeofday(&circ->timestamp_began); /* Generate a random address for the nonce */ crypto_rand((char*)ô->pathbias_probe_nonce, sizeof(ocirc->pathbias_probe_nonce)); ocirc->pathbias_probe_nonce &= 0x00ffffff; probe_nonce = tor_dup_ip(ocirc->pathbias_probe_nonce); tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce); payload_len = (int)strlen(payload)+1; // XXX: need this? Can we assume ipv4 will always be supported? // If not, how do we tell? //if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) { // set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags)); // payload_len += 4; //} /* Generate+Store stream id, make sure it's non-zero */ ocirc->pathbias_probe_id = get_unique_stream_id_by_circ(ocirc); if (ocirc->pathbias_probe_id==0) { log_warn(LD_CIRC, "Ran out of stream IDs on circuit %u during " "pathbias probe attempt.", ocirc->global_identifier); tor_free(probe_nonce); return -1; } log_info(LD_CIRC, "Sending pathbias testing cell to %s:25 on stream %d for circ %d.", probe_nonce, ocirc->pathbias_probe_id, ocirc->global_identifier); tor_free(probe_nonce); /* Send a test relay cell */ if (relay_send_command_from_edge(ocirc->pathbias_probe_id, circ, RELAY_COMMAND_BEGIN, payload, payload_len, cpath_layer) < 0) { log_notice(LD_CIRC, "Failed to send pathbias probe cell on circuit %d.", ocirc->global_identifier); return -1; } /* Mark it freshly dirty so it doesn't get expired in the meantime */ circ->timestamp_dirty = time(NULL); return 0; } /** * Check the response to a pathbias probe, to ensure the * cell is recognized and the nonce and other probe * characteristics are as expected. * * If the response is valid, return 0. Otherwise return < 0. */ int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell) { /* Based on connection_edge_process_relay_cell() */ relay_header_t rh; int reason; uint32_t ipv4_host; origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); tor_assert(cell); tor_assert(ocirc); tor_assert(circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING); relay_header_unpack(&rh, cell->payload); reason = rh.length > 0 ? get_uint8(cell->payload+RELAY_HEADER_SIZE) : END_STREAM_REASON_MISC; if (rh.command == RELAY_COMMAND_END && reason == END_STREAM_REASON_EXITPOLICY && ocirc->pathbias_probe_id == rh.stream_id) { /* Check length+extract host: It is in network order after the reason code. * See connection_edge_end(). */ if (rh.length < 9) { /* reason+ipv4+dns_ttl */ log_notice(LD_PROTOCOL, "Short path bias probe response length field (%d).", rh.length); return - END_CIRC_REASON_TORPROTOCOL; } ipv4_host = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); /* Check nonce */ if (ipv4_host == ocirc->pathbias_probe_nonce) { ocirc->path_state = PATH_STATE_USE_SUCCEEDED; circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); log_info(LD_CIRC, "Got valid path bias probe back for circ %d, stream %d.", ocirc->global_identifier, ocirc->pathbias_probe_id); return 0; } else { log_notice(LD_CIRC, "Got strange probe value 0x%x vs 0x%x back for circ %d, " "stream %d.", ipv4_host, ocirc->pathbias_probe_nonce, ocirc->global_identifier, ocirc->pathbias_probe_id); return -1; } } log_info(LD_CIRC, "Got another cell back back on pathbias probe circuit %d: " "Command: %d, Reason: %d, Stream-id: %d", ocirc->global_identifier, rh.command, reason, rh.stream_id); return -1; } /** * Check if a circuit was used and/or closed successfully. * Loading @@ -1512,18 +1678,26 @@ pathbias_count_build_success(origin_circuit_t *circ) * * If we *have* successfully used the circuit, or it appears to * have been closed by us locally, count it as a success. * * Returns 0 if we're done making decisions with the circ, * or -1 if we want to probe it first. */ void int pathbias_check_close(origin_circuit_t *ocirc, int reason) { circuit_t *circ = ô->base_; if (!pathbias_should_count(ocirc)) { return; return 0; } if (ocirc->path_state == PATH_STATE_BUILD_SUCCEEDED) { if (circ->timestamp_dirty) { if (pathbias_send_usable_probe(circ) == 0) return -1; else pathbias_count_unusable(ocirc); /* Any circuit where there were attempted streams but no successful * streams could be bias */ log_info(LD_CIRC, Loading @@ -1533,7 +1707,7 @@ pathbias_check_close(origin_circuit_t *ocirc, int reason) reason, circ->purpose, ocirc->has_opened, circuit_state_to_string(circ->state), ocirc->build_state->desired_path_len); pathbias_count_unusable(ocirc); } else { if (reason & END_CIRC_REASON_FLAG_REMOTE) { /* Unused remote circ close reasons all could be bias */ Loading Loading @@ -1569,6 +1743,8 @@ pathbias_check_close(origin_circuit_t *ocirc, int reason) } else if (ocirc->path_state == PATH_STATE_USE_SUCCEEDED) { pathbias_count_successful_close(ocirc); } return 0; } /** Loading Loading @@ -2567,6 +2743,9 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit) { int err_reason = 0; warn_if_last_router_excluded(circ, exit); tor_gettimeofday(&circ->base_.timestamp_began); circuit_append_new_exit(circ, exit); circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); if ((err_reason = circuit_send_next_onion_skin(circ))<0) { Loading
src/or/circuitbuild.h +2 −1 Original line number Diff line number Diff line Loading @@ -60,7 +60,8 @@ const node_t *choose_good_entry_server(uint8_t purpose, double pathbias_get_extreme_rate(const or_options_t *options); int pathbias_get_dropguards(const or_options_t *options); void pathbias_count_timeout(origin_circuit_t *circ); void pathbias_check_close(origin_circuit_t *circ, int reason); int pathbias_check_close(origin_circuit_t *circ, int reason); int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell); #endif
src/or/circuitlist.c +7 −1 Original line number Diff line number Diff line Loading @@ -414,6 +414,8 @@ circuit_purpose_to_controller_string(uint8_t purpose) return "MEASURE_TIMEOUT"; case CIRCUIT_PURPOSE_CONTROLLER: return "CONTROLLER"; case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: return "PATH_BIAS_TESTING"; default: tor_snprintf(buf, sizeof(buf), "UNKNOWN_%d", (int)purpose); Loading Loading @@ -441,6 +443,7 @@ circuit_purpose_to_controller_hs_state_string(uint8_t purpose) case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT: case CIRCUIT_PURPOSE_TESTING: case CIRCUIT_PURPOSE_CONTROLLER: case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: return NULL; case CIRCUIT_PURPOSE_INTRO_POINT: Loading Loading @@ -1356,7 +1359,10 @@ circuit_mark_for_close_(circuit_t *circ, int reason, int line, } if (CIRCUIT_IS_ORIGIN(circ)) { pathbias_check_close(TO_ORIGIN_CIRCUIT(circ), reason); if (pathbias_check_close(TO_ORIGIN_CIRCUIT(circ), reason) == -1) { /* Don't close it yet, we need to test it first */ return; } /* We don't send reasons when closing circuits at the origin. */ reason = END_CIRC_REASON_NONE; Loading
src/or/circuituse.c +118 −51 Original line number Diff line number Diff line Loading @@ -280,17 +280,19 @@ circuit_get_best(const entry_connection_t *conn, if (!CIRCUIT_IS_ORIGIN(circ)) continue; origin_circ = TO_ORIGIN_CIRCUIT(circ); if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose, need_uptime,need_internal,now.tv_sec)) continue; /* Log an info message if we're going to launch a new intro circ in * parallel */ if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && !must_be_open && circ->state != CIRCUIT_STATE_OPEN && tv_mdiff(&now, &circ->timestamp_began) > circ_times.timeout_ms) { !must_be_open && origin_circ->hs_circ_has_timed_out) { intro_going_on_but_too_old = 1; continue; } if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose, need_uptime,need_internal,now.tv_sec)) continue; /* now this is an acceptable circ to hand back. but that doesn't * mean it's the *best* circ to hand back. try to decide. */ Loading Loading @@ -367,8 +369,8 @@ circuit_expire_building(void) * circuit_build_times_get_initial_timeout() if we haven't computed * custom timeouts yet */ struct timeval general_cutoff, begindir_cutoff, fourhop_cutoff, cannibalize_cutoff, close_cutoff, extremely_old_cutoff, hs_extremely_old_cutoff; close_cutoff, extremely_old_cutoff, hs_extremely_old_cutoff, cannibalized_cutoff, c_intro_cutoff, s_intro_cutoff, stream_cutoff; const or_options_t *options = get_options(); struct timeval now; cpath_build_state_t *build_state; Loading Loading @@ -407,10 +409,60 @@ circuit_expire_building(void) timersub(&now, &diff, &target); \ } while (0) /** * Because circuit build timeout is calculated only based on 3 hop * general purpose circuit construction, we need to scale the timeout * to make it properly apply to longer circuits, and circuits of * certain usage types. The following diagram illustrates how we * derive the scaling below. In short, we calculate the number * of times our telescoping-based circuit construction causes cells * to traverse each link for the circuit purpose types in question, * and then assume each link is equivalent. * * OP --a--> A --b--> B --c--> C * OP --a--> A --b--> B --c--> C --d--> D * * Let h = a = b = c = d * * Three hops (general_cutoff) * RTTs = 3a + 2b + c * RTTs = 6h * Cannibalized: * RTTs = a+b+c+d * RTTs = 4h * Four hops: * RTTs = 4a + 3b + 2c + d * RTTs = 10h * Client INTRODUCE1+ACK: // XXX: correct? * RTTs = 5a + 4b + 3c + 2d * RTTs = 14h * Server intro: * RTTs = 4a + 3b + 2c * RTTs = 9h */ SET_CUTOFF(general_cutoff, circ_times.timeout_ms); SET_CUTOFF(begindir_cutoff, circ_times.timeout_ms); SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (4/3.0)); SET_CUTOFF(cannibalize_cutoff, circ_times.timeout_ms / 2.0); /* > 3hop circs seem to have a 1.0 second delay on their cannibalized * 4th hop. */ SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (10/6.0) + 1000); /* CIRCUIT_PURPOSE_C_ESTABLISH_REND behaves more like a RELAY cell. * Use the stream cutoff (more or less). */ SET_CUTOFF(stream_cutoff, MAX(options->CircuitStreamTimeout,15)*1000 + 1000); /* Be lenient with cannibalized circs. They already survived the official * CBT, and they're usually not perf-critical. */ SET_CUTOFF(cannibalized_cutoff, MAX(circ_times.close_ms*(4/6.0), options->CircuitStreamTimeout * 1000) + 1000); // Intro circs have an extra round trip (and are also 4 hops long) SET_CUTOFF(c_intro_cutoff, circ_times.timeout_ms * (14/6.0) + 1000); // Server intro circs have an extra round trip SET_CUTOFF(s_intro_cutoff, circ_times.timeout_ms * (9/6.0) + 1000); SET_CUTOFF(close_cutoff, circ_times.close_ms); SET_CUTOFF(extremely_old_cutoff, circ_times.close_ms*2 + 1000); Loading Loading @@ -441,13 +493,22 @@ circuit_expire_building(void) build_state = TO_ORIGIN_CIRCUIT(victim)->build_state; if (build_state && build_state->onehop_tunnel) cutoff = begindir_cutoff; else if (build_state && build_state->desired_path_len == 4 && !TO_ORIGIN_CIRCUIT(victim)->has_opened) cutoff = fourhop_cutoff; else if (TO_ORIGIN_CIRCUIT(victim)->has_opened) cutoff = cannibalize_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) cutoff = close_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) cutoff = c_intro_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) cutoff = s_intro_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND) cutoff = stream_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING) cutoff = close_cutoff; else if (TO_ORIGIN_CIRCUIT(victim)->has_opened && victim->state != CIRCUIT_STATE_OPEN) cutoff = cannibalized_cutoff; else if (build_state && build_state->desired_path_len >= 4) cutoff = fourhop_cutoff; else cutoff = general_cutoff; Loading Loading @@ -520,8 +581,6 @@ circuit_expire_building(void) default: /* most open circuits can be left alone. */ continue; /* yes, continue inside a switch refers to the nearest * enclosing loop. C is smart. */ case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_INTRODUCING: case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: break; /* too old, need to die */ case CIRCUIT_PURPOSE_C_REND_READY: Loading @@ -533,6 +592,19 @@ circuit_expire_building(void) victim->timestamp_dirty > cutoff.tv_sec) continue; break; case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: /* Open path bias testing circuits are given a long * time to complete the test, but not forever */ TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED; break; case CIRCUIT_PURPOSE_C_INTRODUCING: /* We keep old introducing circuits around for * a while in parallel, and they can end up "opened". * We decide below if we're going to mark them timed * out and eventually close them. */ break; case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: /* rend and intro circs become dirty each time they Loading Loading @@ -596,6 +668,18 @@ circuit_expire_building(void) circuit_build_times_set_timeout(&circ_times); } } if (TO_ORIGIN_CIRCUIT(victim)->has_opened && victim->purpose != CIRCUIT_PURPOSE_PATH_BIAS_TESTING) { /* For path bias: we want to let these guys live for a while * so we get a chance to test them. */ log_info(LD_CIRC, "Allowing cannibalized circuit %d time to finish building " "as a pathbias testing circ.", TO_ORIGIN_CIRCUIT(victim)->global_identifier); circuit_change_purpose(victim, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); continue; /* It now should have a longer timeout next time */ } } /* If this is a hidden service client circuit which is far enough Loading @@ -621,6 +705,9 @@ circuit_expire_building(void) if (TO_ORIGIN_CIRCUIT(victim)->build_state->pending_final_cpath == NULL) break; /* fallthrough! */ case CIRCUIT_PURPOSE_C_INTRODUCING: /* connection_ap_handshake_attach_circuit() will relaunch for us */ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: /* If we have reached this line, we want to spare the circ for now. */ Loading Loading @@ -653,15 +740,23 @@ circuit_expire_building(void) } if (victim->n_chan) log_info(LD_CIRC,"Abandoning circ %s:%d (state %d:%s, purpose %d)", log_info(LD_CIRC, "Abandoning circ %u %s:%d (state %d,%d:%s, purpose %d, " "len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, channel_get_canonical_remote_descr(victim->n_chan), victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened, victim->state, circuit_state_to_string(victim->state), victim->purpose); victim->purpose, TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); else log_info(LD_CIRC,"Abandoning circ %d (state %d:%s, purpose %d)", victim->n_circ_id, victim->state, circuit_state_to_string(victim->state), victim->purpose); log_info(LD_CIRC, "Abandoning circ %u %d (state %d,%d:%s, purpose %d, len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened, victim->state, circuit_state_to_string(victim->state), victim->purpose, TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); circuit_log_path(LOG_INFO,LD_CIRC,TO_ORIGIN_CIRCUIT(victim)); if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) Loading Loading @@ -1165,18 +1260,6 @@ circuit_has_opened(origin_circuit_t *circ) { control_event_circuit_status(circ, CIRC_EVENT_BUILT, 0); /* Cannibalized circuits count as used for path bias. * (PURPOSE_GENERAL circs especially, since they are * marked dirty and often go unused after preemptive * building). */ // XXX: Cannibalized now use RELAY_EARLY, which is visible // to taggers end-to-end! We really need to probe these instead. // Don't forget to remove this check once that's done! if (circ->has_opened && circ->build_state->desired_path_len > DEFAULT_ROUTE_LEN) { circ->path_state = PATH_STATE_USE_SUCCEEDED; } /* Remember that this circuit has finished building. Now if we start * it building again later (e.g. by extending it), we will know not * to consider its build time. */ Loading Loading @@ -2101,28 +2184,12 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (retval > 0) { /* one has already sent the intro. keep waiting. */ circuit_t *c = NULL; tor_assert(introcirc); log_info(LD_REND, "Intro circ %d present and awaiting ack (rend %d). " "Stalling. (stream %d sec old)", introcirc->base_.n_circ_id, rendcirc ? rendcirc->base_.n_circ_id : 0, conn_age); /* abort parallel intro circs, if any */ for (c = global_circuitlist; c; c = c->next) { if (c->purpose == CIRCUIT_PURPOSE_C_INTRODUCING && !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c); if (oc->rend_data && !rend_cmp_service_ids( ENTRY_TO_EDGE_CONN(conn)->rend_data->onion_address, oc->rend_data->onion_address)) { log_info(LD_REND|LD_CIRC, "Closing introduction circuit that we " "built in parallel."); circuit_mark_for_close(c, END_CIRC_REASON_TIMEOUT); } } } return 0; } Loading