diff options
-rw-r--r-- | src/or/circuitbuild.c | 551 | ||||
-rw-r--r-- | src/or/circuitbuild.h | 3 | ||||
-rw-r--r-- | src/or/circuituse.c | 11 | ||||
-rw-r--r-- | src/or/config.c | 7 | ||||
-rw-r--r-- | src/or/connection_edge.c | 9 | ||||
-rw-r--r-- | src/or/entrynodes.c | 48 | ||||
-rw-r--r-- | src/or/entrynodes.h | 7 | ||||
-rw-r--r-- | src/or/or.h | 32 | ||||
-rw-r--r-- | src/or/relay.c | 2 | ||||
-rw-r--r-- | src/or/rendclient.c | 9 | ||||
-rw-r--r-- | src/or/rendservice.c | 13 |
11 files changed, 588 insertions, 104 deletions
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index b986243867..435ccd9a27 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -67,7 +67,9 @@ static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard); static void pathbias_count_build_success(origin_circuit_t *circ); static void pathbias_count_successful_close(origin_circuit_t *circ); static void pathbias_count_collapse(origin_circuit_t *circ); -static void pathbias_count_unusable(origin_circuit_t *circ); +static void pathbias_count_use_failed(origin_circuit_t *circ); +static int pathbias_check_use_rate(entry_guard_t *guard); +static int pathbias_check_close_rate(entry_guard_t *guard); /** This function tries to get a channel to the specified endpoint, * and then calls command_setup_channel() to give it the right @@ -1210,19 +1212,65 @@ pathbias_get_mult_factor(const or_options_t *options) pathbias_get_scale_factor(options)); } +/** The minimum number of circuit usage attempts before we start + * thinking about warning about path use bias and dropping guards */ +static int +pathbias_get_min_use(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_MIN_USE 20 + if (options->PathBiasUseThreshold >= 3) + return options->PathBiasUseThreshold; + else + return networkstatus_get_param(NULL, "pb_minuse", + DFLT_PATH_BIAS_MIN_USE, + 3, INT32_MAX); +} + +/** The circuit use success rate below which we issue a notice */ +static double +pathbias_get_notice_use_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_NOTICE_USE_PCT 90 + if (options->PathBiasNoticeUseRate >= 0.0) + return options->PathBiasNoticeUseRate; + else + return networkstatus_get_param(NULL, "pb_noticeusepct", + DFLT_PATH_BIAS_NOTICE_USE_PCT, + 0, 100)/100.0; +} + /** - * If this parameter is set to a true value (default), we use the - * successful_circuits_closed. Otherwise, we use the success_count. + * The extreme use rate is the rate at which we would drop the guard, + * if pb_dropguard is also set. Otherwise we just warn. + */ +double +pathbias_get_extreme_use_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_EXTREME_USE_PCT 70 + if (options->PathBiasExtremeUseRate >= 0.0) + return options->PathBiasExtremeUseRate; + else + return networkstatus_get_param(NULL, "pb_extremeusepct", + DFLT_PATH_BIAS_EXTREME_USE_PCT, + 0, 100)/100.0; +} + +/** + * This is the number of circuits at which we scale our + * use counts by mult_factor/scale_factor. Note, this count is + * not exact, as we only perform the scaling in the event + * of no integer truncation. */ static int -pathbias_use_close_counts(const or_options_t *options) +pathbias_get_scale_use_threshold(const or_options_t *options) { -#define DFLT_PATH_BIAS_USE_CLOSE_COUNTS 1 - if (options->PathBiasUseCloseCounts >= 0) - return options->PathBiasUseCloseCounts; +#define DFLT_PATH_BIAS_SCALE_USE_THRESHOLD 100 + if (options->PathBiasScaleUseThreshold >= 10) + return options->PathBiasScaleUseThreshold; else - return networkstatus_get_param(NULL, "pb_useclosecounts", - DFLT_PATH_BIAS_USE_CLOSE_COUNTS, 0, 1); + return networkstatus_get_param(NULL, "pb_scaleuse", + DFLT_PATH_BIAS_SCALE_USE_THRESHOLD, + 10, INT32_MAX); } /** @@ -1238,10 +1286,14 @@ pathbias_state_to_string(path_state_t state) return "build attempted"; case PATH_STATE_BUILD_SUCCEEDED: return "build succeeded"; + case PATH_STATE_USE_ATTEMPTED: + return "use attempted"; case PATH_STATE_USE_SUCCEEDED: return "use succeeded"; case PATH_STATE_USE_FAILED: return "use failed"; + case PATH_STATE_ALREADY_COUNTED: + return "already counted"; } return "unknown"; @@ -1338,7 +1390,7 @@ pathbias_should_count(origin_circuit_t *circ) * Also check for several potential error cases for bug #6475. */ static int -pathbias_count_circ_attempt(origin_circuit_t *circ) +pathbias_count_build_attempt(origin_circuit_t *circ) { #define CIRC_ATTEMPT_NOTICE_INTERVAL (600) static ratelim_t circ_attempt_notice_limit = @@ -1505,6 +1557,126 @@ pathbias_count_build_success(origin_circuit_t *circ) } /** + * Record an attempt to use a circuit. Changes the circuit's + * path state and update its guard's usage counter. + * + * Used for path bias usage accounting. + */ +void +pathbias_count_use_attempt(origin_circuit_t *circ) +{ + entry_guard_t *guard; + + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { + log_notice(LD_BUG, + "Used circuit is in strange path state %s. " + "Circuit is a %s currently %s.", + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } else if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + if (guard) { + pathbias_check_use_rate(guard); + guard->use_attempts++; + + log_debug(LD_CIRC, "Marked circuit %d as used for guard %s=%s.", + circ->global_identifier, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + } + + circ->path_state = PATH_STATE_USE_ATTEMPTED; + } else { + /* Harmless but educational log message */ + log_info(LD_CIRC, + "Used circuit %d is already in path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } + + return; +} + +/** + * Check the circuit's path stat is appropriate and it as successfully + * used. + * + * We don't actually increment the guard's counters until + * pathbias_check_close(). + * + * Used for path bias usage accounting. + */ +void +pathbias_mark_use_success(origin_circuit_t *circ) +{ + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state < PATH_STATE_USE_ATTEMPTED) { + log_notice(LD_BUG, + "Used circuit %d is in strange path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + + pathbias_count_use_attempt(circ); + } + + /* We don't do any accounting at the guard until actual circuit close */ + circ->path_state = PATH_STATE_USE_SUCCEEDED; + + return; +} + +/** + * Actually count a circuit success towards a guard's usage counters + * if the path state is appropriate. + */ +static void +pathbias_count_use_success(origin_circuit_t *circ) +{ + entry_guard_t *guard; + + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->path_state != PATH_STATE_USE_SUCCEEDED) { + log_notice(LD_BUG, + "Successfully used circuit %d is in strange path state %s. " + "Circuit is a %s currently %s.", + circ->global_identifier, + pathbias_state_to_string(circ->path_state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } else { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + if (guard) { + guard->use_successes++; + + log_debug(LD_CIRC, + "Marked circuit %d as used successfully for guard %s=%s.", + circ->global_identifier, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + } + } + + return; +} + +/** * 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 @@ -1648,7 +1820,7 @@ pathbias_check_probe_response(circuit_t *circ, const cell_t *cell) /* Check nonce */ if (ipv4_host == ocirc->pathbias_probe_nonce) { - ocirc->path_state = PATH_STATE_USE_SUCCEEDED; + pathbias_mark_use_success(ocirc); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); log_info(LD_CIRC, "Got valid path bias probe back for circ %d, stream %d.", @@ -1691,24 +1863,11 @@ pathbias_check_close(origin_circuit_t *ocirc, int reason) 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, - "Circuit %d closed without successful use for reason %d. " - "Circuit purpose %d currently %d,%s. Len %d.", - ocirc->global_identifier, - reason, circ->purpose, ocirc->has_opened, - circuit_state_to_string(circ->state), - ocirc->build_state->desired_path_len); - - } else { + switch (ocirc->path_state) { + /* If the circuit was closed after building, but before use, we need + * to ensure we were the ones who tried to close it (and not a remote + * actor). */ + case PATH_STATE_BUILD_SUCCEEDED: if (reason & END_CIRC_REASON_FLAG_REMOTE) { /* Remote circ close reasons on an unused circuit all could be bias */ log_info(LD_CIRC, @@ -1739,11 +1898,41 @@ pathbias_check_close(origin_circuit_t *ocirc, int reason) } else { pathbias_count_successful_close(ocirc); } - } - } else if (ocirc->path_state == PATH_STATE_USE_SUCCEEDED) { - pathbias_count_successful_close(ocirc); + break; + + /* If we tried to use a circuit but failed, we should probe it to ensure + * it has not been tampered with. */ + case PATH_STATE_USE_ATTEMPTED: + /* XXX: Only probe and/or count failure if the network is live? + * What about clock jumps/suspends? */ + if (pathbias_send_usable_probe(circ) == 0) + return -1; + else + pathbias_count_use_failed(ocirc); + + /* Any circuit where there were attempted streams but no successful + * streams could be bias */ + log_info(LD_CIRC, + "Circuit %d closed without successful use for reason %d. " + "Circuit purpose %d currently %d,%s. Len %d.", + ocirc->global_identifier, + reason, circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + break; + + case PATH_STATE_USE_SUCCEEDED: + pathbias_count_successful_close(ocirc); + pathbias_count_use_success(ocirc); + break; + + default: + // Other states are uninteresting. No stats to count. + break; } + ocirc->path_state = PATH_STATE_ALREADY_COUNTED; + return 0; } @@ -1792,6 +1981,7 @@ static void pathbias_count_collapse(origin_circuit_t *circ) { entry_guard_t *guard = NULL; + if (!pathbias_should_count(circ)) { return; } @@ -1816,8 +2006,13 @@ pathbias_count_collapse(origin_circuit_t *circ) } } +/** + * Count a known failed circuit (because we could not probe it). + * + * This counter is informational. + */ static void -pathbias_count_unusable(origin_circuit_t *circ) +pathbias_count_use_failed(origin_circuit_t *circ) { entry_guard_t *guard = NULL; if (!pathbias_should_count(circ)) { @@ -1886,7 +2081,7 @@ pathbias_count_timeout(origin_circuit_t *circ) * of the doubt. */ double -pathbias_get_closed_count(entry_guard_t *guard) +pathbias_get_close_success_count(entry_guard_t *guard) { circuit_t *circ; int open_circuits = 0; @@ -1903,7 +2098,12 @@ pathbias_get_closed_count(entry_guard_t *guard) if (!ocirc->cpath || !ocirc->cpath->extend_info) continue; + /* Don't count known failed or already counted circuits */ + if (ocirc->path_state >= PATH_STATE_USE_FAILED) + continue; + if (ocirc->path_state >= PATH_STATE_BUILD_SUCCEEDED && + pathbias_should_count(ocirc) && fast_memeq(guard->identity, ocirc->cpath->extend_info->identity_digest, DIGEST_LEN)) { @@ -1915,51 +2115,201 @@ pathbias_get_closed_count(entry_guard_t *guard) } /** - * This function checks the consensus parameters to decide - * if it should return guard->circ_successes or - * guard->successful_circuits_closed. + * Return the number of circuits counted as successfully used + * this guard. + * + * Also add in the currently open circuits that we are attempting + * to use to give them the benefit of the doubt. */ double -pathbias_get_success_count(entry_guard_t *guard) +pathbias_get_use_success_count(entry_guard_t *guard) { - if (pathbias_use_close_counts(get_options())) { - return pathbias_get_closed_count(guard); - } else { - return guard->circ_successes; + circuit_t *circ = global_circuitlist; + int open_circuits = 0; + + /* Count currently open circuits. Give them the benefit of the doubt */ + for ( ; circ; circ = circ->next) { + origin_circuit_t *ocirc = NULL; + if (!CIRCUIT_IS_ORIGIN(circ) || /* didn't originate here */ + circ->marked_for_close) /* already counted */ + continue; + + ocirc = TO_ORIGIN_CIRCUIT(circ); + + if (!ocirc->cpath || !ocirc->cpath->extend_info) + continue; + + /* Don't count known failed or already counted circuits */ + if (ocirc->path_state >= PATH_STATE_USE_FAILED) + continue; + + if (ocirc->path_state >= PATH_STATE_USE_ATTEMPTED && + pathbias_should_count(ocirc) && + fast_memeq(guard->identity, + ocirc->cpath->extend_info->identity_digest, + DIGEST_LEN)) { + open_circuits++; + } } + + return guard->use_successes + open_circuits; } -/** Increment the number of times we successfully extended a circuit to - * <b>guard</b>, first checking if the failure rate is high enough that - * we should eliminate the guard. Return -1 if the guard looks no good; - * return 0 if the guard looks fine. +/** + * Check the path bias use rate against our consensus parameter limits. + * + * Emits a log message if the use success rates are too low. + * + * If pathbias_get_dropguards() is set, we also disable the use of + * very failure prone guards. + * + * Returns -1 if we decided to disable the guard, 0 otherwise. */ static int -entry_guard_inc_circ_attempt_count(entry_guard_t *guard) +pathbias_check_use_rate(entry_guard_t *guard) { const or_options_t *options = get_options(); - entry_guards_changed(); + if (guard->use_attempts > pathbias_get_min_use(options)) { + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_use_success_count(guard)/guard->use_attempts + < pathbias_get_extreme_use_rate(options)) { + /* Dropping is currently disabled by default. */ + if (pathbias_get_dropguards(options)) { + if (!guard->path_bias_disabled) { + log_warn(LD_CIRC, + "Your Guard %s=%s is failing to carry an extremely large " + "amount of stream on its circuits. " + "To avoid potential route manipluation attacks, Tor has " + "disabled use of this guard. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(circ_times.close_ms/1000)); + guard->path_bias_disabled = 1; + guard->bad_since = approx_time(); + return -1; + } + } else if (!guard->path_bias_extreme) { + guard->path_bias_extreme = 1; + log_warn(LD_CIRC, + "Your Guard %s=%s is failing to carry an extremely large " + "amount of streams on its circuits. " + "This could indicate a route manipulation attack, network " + "overload, bad local network connectivity, or a bug. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(circ_times.close_ms/1000)); + } + } else if (pathbias_get_use_success_count(guard)/guard->use_attempts + < pathbias_get_notice_use_rate(options)) { + if (!guard->path_bias_noticed) { + guard->path_bias_noticed = 1; + log_notice(LD_CIRC, + "Your Guard %s=%s is failing to carry more streams on its " + "circuits than usual. " + "Most likely this means the Tor network is overloaded " + "or your network connection is poor. " + "Use counts are %ld/%ld. Success counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(circ_times.close_ms/1000)); + } + } + } + + /* If we get a ton of circuits, just scale everything down */ + if (guard->use_attempts > pathbias_get_scale_use_threshold(options)) { + const int scale_factor = pathbias_get_scale_factor(options); + const int mult_factor = pathbias_get_mult_factor(options); + log_info(LD_CIRC, + "Scaling pathbias use counts to (%f/%f)*(%d/%d) for guard %s=%s", + guard->use_successes, guard->use_attempts, + mult_factor, scale_factor, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + + guard->use_attempts *= mult_factor; + guard->use_successes *= mult_factor; + + guard->use_attempts /= scale_factor; + guard->use_successes /= scale_factor; + } + + return 0; +} + +/** + * Check the path bias circuit close status rates against our consensus + * parameter limits. + * + * Emits a log message if the use success rates are too low. + * + * If pathbias_get_dropguards() is set, we also disable the use of + * very failure prone guards. + * + * Returns -1 if we decided to disable the guard, 0 otherwise. + */ +static int +pathbias_check_close_rate(entry_guard_t *guard) +{ + const or_options_t *options = get_options(); if (guard->circ_attempts > pathbias_get_min_circs(options)) { /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_success_count(guard)/guard->circ_attempts + if (pathbias_get_close_success_count(guard)/guard->circ_attempts < pathbias_get_extreme_rate(options)) { /* Dropping is currently disabled by default. */ if (pathbias_get_dropguards(options)) { if (!guard->path_bias_disabled) { log_warn(LD_CIRC, - "Your Guard %s=%s is failing an extremely large amount of " - "circuits. To avoid potential route manipulation attacks, " - "Tor has disabled use of this guard. " - "Success counts are %ld/%ld. %ld circuits completed, %ld " - "were unusable, %ld collapsed, and %ld timed out. For " - "reference, your timeout cutoff is %ld seconds.", + "Your Guard %s=%s is failing an extremely large " + "amount of circuits. " + "To avoid potential route manipluation attacks, Tor has " + "disabled use of this guard. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_closed_count(guard)), + tor_lround(pathbias_get_close_success_count(guard)), tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), tor_lround(guard->circ_successes), tor_lround(guard->unusable_circuits), tor_lround(guard->collapsed_circuits), @@ -1972,60 +2322,72 @@ entry_guard_inc_circ_attempt_count(entry_guard_t *guard) } else if (!guard->path_bias_extreme) { guard->path_bias_extreme = 1; log_warn(LD_CIRC, - "Your Guard %s=%s is failing an extremely large amount of " - "circuits. This could indicate a route manipulation attack, " + "Your Guard %s=%s is failing an extremely large " + "amount of circuits. " + "This could indicate a route manipulation attack, " "extreme network overload, or a bug. " - "Success counts are %ld/%ld. %ld circuits completed, %ld " - "were unusable, %ld collapsed, and %ld timed out. For " - "reference, your timeout cutoff is %ld seconds.", + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_closed_count(guard)), + tor_lround(pathbias_get_close_success_count(guard)), tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), tor_lround(guard->circ_successes), tor_lround(guard->unusable_circuits), tor_lround(guard->collapsed_circuits), tor_lround(guard->timeouts), tor_lround(circ_times.close_ms/1000)); } - } else if (pathbias_get_success_count(guard)/((double)guard->circ_attempts) - < pathbias_get_warn_rate(options)) { + } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts + < pathbias_get_warn_rate(options)) { if (!guard->path_bias_warned) { guard->path_bias_warned = 1; log_warn(LD_CIRC, - "Your Guard %s=%s is failing a very large amount of " - "circuits. Most likely this means the Tor network is " + "Your Guard %s=%s is failing a very large " + "amount of circuits. " + "Most likely this means the Tor network is " "overloaded, but it could also mean an attack against " - "you or potentially the guard itself. " - "Success counts are %ld/%ld. %ld circuits completed, %ld " - "were unusable, %ld collapsed, and %ld timed out. For " - "reference, your timeout cutoff is %ld seconds.", + "you or the potentially the guard itself. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_closed_count(guard)), + tor_lround(pathbias_get_close_success_count(guard)), tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), tor_lround(guard->circ_successes), tor_lround(guard->unusable_circuits), tor_lround(guard->collapsed_circuits), tor_lround(guard->timeouts), tor_lround(circ_times.close_ms/1000)); } - } else if (pathbias_get_success_count(guard)/((double)guard->circ_attempts) + } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts < pathbias_get_notice_rate(options)) { if (!guard->path_bias_noticed) { guard->path_bias_noticed = 1; log_notice(LD_CIRC, - "Your Guard %s=%s is failing more circuits than usual. " - "Most likely this means the Tor network is overloaded. " - "Success counts are %ld/%ld. %ld circuits completed, %ld " - "were unusable, %ld collapsed, and %ld timed out. For " - "reference, your timeout cutoff is %ld seconds.", - guard->nickname, hex_str(guard->identity, DIGEST_LEN), - tor_lround(pathbias_get_closed_count(guard)), - tor_lround(guard->circ_attempts), - tor_lround(guard->circ_successes), - tor_lround(guard->unusable_circuits), - tor_lround(guard->collapsed_circuits), - tor_lround(guard->timeouts), - tor_lround(circ_times.close_ms/1000)); + "Your Guard %s=%s is failing more circuits than " + "usual. " + "Most likely this means the Tor network is overloaded. " + "Success counts are %ld/%ld. Use counts are %ld/%ld. " + "%ld circuits completed, %ld were unusable, %ld collapsed, " + "and %ld timed out. " + "For reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_close_success_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(pathbias_get_use_success_count(guard)), + tor_lround(guard->use_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(circ_times.close_ms/1000)); } } } @@ -2054,7 +2416,24 @@ entry_guard_inc_circ_attempt_count(entry_guard_t *guard) guard->collapsed_circuits /= scale_factor; guard->unusable_circuits /= scale_factor; } + + return 0; +} + +/** Increment the number of times we successfully extended a circuit to + * 'guard', first checking if the failure rate is high enough that we should + * eliminate the guard. Return -1 if the guard looks no good; return 0 if the + * guard looks fine. */ +static int +entry_guard_inc_circ_attempt_count(entry_guard_t *guard) +{ + entry_guards_changed(); + + if (pathbias_check_close_rate(guard) < 0) + return -1; + guard->circ_attempts++; + log_info(LD_CIRC, "Got success count %f/%f for guard %s=%s", guard->circ_successes, guard->circ_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); @@ -2078,7 +2457,7 @@ circuit_finish_handshake(origin_circuit_t *circ, crypt_path_t *hop; int rv; - if ((rv = pathbias_count_circ_attempt(circ)) < 0) + if ((rv = pathbias_count_build_attempt(circ)) < 0) return rv; if (circ->cpath->state == CPATH_STATE_AWAITING_KEYS) { diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index e6cf802f2b..d03a7c5323 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -58,10 +58,13 @@ const char *build_state_get_exit_nickname(cpath_build_state_t *state); const node_t *choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state); double pathbias_get_extreme_rate(const or_options_t *options); +double pathbias_get_extreme_use_rate(const or_options_t *options); int pathbias_get_dropguards(const or_options_t *options); void pathbias_count_timeout(origin_circuit_t *circ); int pathbias_check_close(origin_circuit_t *circ, int reason); int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell); +void pathbias_count_use_attempt(origin_circuit_t *circ); +void pathbias_mark_use_success(origin_circuit_t *circ); #endif diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 83734c9d6d..d0b1b49465 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -2037,6 +2037,8 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn, if (!circ->base_.timestamp_dirty) circ->base_.timestamp_dirty = time(NULL); + pathbias_count_use_attempt(circ); + link_apconn_to_circ(conn, circ, cpath); tor_assert(conn->socks_request); if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) { @@ -2163,6 +2165,11 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) * feasibility, at this point. */ rendcirc->base_.timestamp_dirty = time(NULL); + + /* We've also attempted to use them. If they fail, we need to + * probe them for path bias */ + pathbias_count_use_attempt(rendcirc); + link_apconn_to_circ(conn, rendcirc, NULL); if (connection_ap_handshake_send_begin(conn) < 0) return 0; /* already marked, let them fade away */ @@ -2214,6 +2221,10 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) case 0: /* success */ rendcirc->base_.timestamp_dirty = time(NULL); introcirc->base_.timestamp_dirty = time(NULL); + + pathbias_count_use_attempt(introcirc); + pathbias_count_use_attempt(rendcirc); + assert_circuit_ok(TO_CIRCUIT(rendcirc)); assert_circuit_ok(TO_CIRCUIT(introcirc)); return 0; diff --git a/src/or/config.c b/src/or/config.c index 4349b670b0..42f070f93b 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -323,7 +323,12 @@ static config_var_t option_vars_[] = { V(PathBiasScaleFactor, INT, "-1"), V(PathBiasMultFactor, INT, "-1"), V(PathBiasDropGuards, AUTOBOOL, "0"), - V(PathBiasUseCloseCounts, AUTOBOOL, "1"), + OBSOLETE("PathBiasUseCloseCounts"), + + V(PathBiasUseThreshold, INT, "-1"), + V(PathBiasNoticeUseRate, DOUBLE, "-1"), + V(PathBiasExtremeUseRate, DOUBLE, "-1"), + V(PathBiasScaleUseThreshold, INT, "-1"), OBSOLETE("PathlenCoinWeight"), V(PerConnBWBurst, MEMUNIT, "0"), diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 870ded98c9..41795acb6c 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -37,6 +37,7 @@ #include "router.h" #include "routerlist.h" #include "routerset.h" +#include "circuitbuild.h" #ifdef HAVE_LINUX_TYPES_H #include <linux/types.h> @@ -2205,8 +2206,10 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, U64_PRINTF_ARG(ENTRY_TO_CONN(conn)->global_identifier), endreason); } else { - TO_ORIGIN_CIRCUIT(conn->edge_.on_circuit)->path_state - = PATH_STATE_USE_SUCCEEDED; + // XXX: Hrmm. It looks like optimistic data can't go through this + // codepath, but someone should probably test it and make sure. + // We don't want to mark optimistically opened streams as successful. + pathbias_mark_use_success(TO_ORIGIN_CIRCUIT(conn->edge_.on_circuit)); } } @@ -2480,7 +2483,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) connection_exit_connect(n_stream); /* For path bias: This circuit was used successfully */ - origin_circ->path_state = PATH_STATE_USE_SUCCEEDED; + pathbias_mark_use_success(origin_circ); tor_free(address); return 0; diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 3e58371643..4ca56cbacf 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1098,6 +1098,40 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) continue; } digestmap_set(added_by, d, tor_strdup(line->value+HEX_DIGEST_LEN+1)); + } else if (!strcasecmp(line->key, "EntryGuardPathUseBias")) { + const or_options_t *options = get_options(); + double use_cnt, success_cnt; + + if (!node) { + *msg = tor_strdup("Unable to parse entry nodes: " + "EntryGuardPathUseBias without EntryGuard"); + break; + } + + if (tor_sscanf(line->value, "%lf %lf", + &use_cnt, &success_cnt) != 2) { + log_info(LD_GENERAL, "Malformed path use bias line for node %s", + node->nickname); + continue; + } + + node->use_attempts = use_cnt; + node->use_successes = success_cnt; + + log_info(LD_GENERAL, "Read %f/%f path use bias for node %s", + node->use_successes, node->use_attempts, node->nickname); + + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_use_success_count(node)/node->use_attempts + < pathbias_get_extreme_use_rate(options) && + pathbias_get_dropguards(options)) { + node->path_bias_disabled = 1; + log_info(LD_GENERAL, + "Path use bias is too high (%f/%f); disabling node %s", + node->circ_successes, node->circ_attempts, node->nickname); + } } else if (!strcasecmp(line->key, "EntryGuardPathBias")) { const or_options_t *options = get_options(); double hop_cnt, success_cnt, timeouts, collapsed, successful_closed, @@ -1144,7 +1178,7 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (pathbias_get_success_count(node)/node->circ_attempts + if (pathbias_get_close_success_count(node)/node->circ_attempts < pathbias_get_extreme_rate(options) && pathbias_get_dropguards(options)) { node->path_bias_disabled = 1; @@ -1282,10 +1316,20 @@ entry_guards_update_state(or_state_t *state) * unusable_circuits */ tor_asprintf(&line->value, "%f %f %f %f %f %f", e->circ_attempts, e->circ_successes, - pathbias_get_closed_count(e), e->collapsed_circuits, + pathbias_get_close_success_count(e), + e->collapsed_circuits, e->unusable_circuits, e->timeouts); next = &(line->next); } + if (e->use_attempts > 0) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("EntryGuardPathUseBias"); + + tor_asprintf(&line->value, "%f %f", + e->use_attempts, + pathbias_get_use_success_count(e)); + next = &(line->next); + } } SMARTLIST_FOREACH_END(e); if (!get_options()->AvoidDiskWrites) diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 1e52ba28ea..e6c973c95a 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -61,6 +61,9 @@ typedef struct entry_guard_t { * attempted, but none succeeded. */ double timeouts; /**< Number of 'right-censored' circuit timeouts for this * guard. */ + double use_attempts; /**< Number of circuits we tried to use with streams */ + double use_successes; /**< Number of successfully used circuits using + * this guard as first hop. */ } entry_guard_t; entry_guard_t *entry_guard_get_by_id_digest(const char *digest); @@ -113,8 +116,8 @@ int find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, int validate_pluggable_transports_config(void); -double pathbias_get_closed_count(entry_guard_t *gaurd); -double pathbias_get_success_count(entry_guard_t *guard); +double pathbias_get_close_success_count(entry_guard_t *guard); +double pathbias_get_use_success_count(entry_guard_t *guard); #endif diff --git a/src/or/or.h b/src/or/or.h index a6f3d3e88a..409a603875 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2838,6 +2838,15 @@ typedef enum { PATH_STATE_BUILD_ATTEMPTED = 1, /** This circuit has been completely built */ PATH_STATE_BUILD_SUCCEEDED = 2, + /** Did we try to attach any SOCKS streams or hidserv introductions to + * this circuit? + * + * Note: If we ever implement end-to-end stream timing through test + * stream probes (#5707), we must *not* set this for those probes + * (or any other automatic streams) because the adversary could + * just tag at a later point. + */ + PATH_STATE_USE_ATTEMPTED = 3, /** Did any SOCKS streams or hidserv introductions actually succeed on * this circuit? * @@ -2846,13 +2855,20 @@ typedef enum { * (or any other automatic streams) because the adversary could * just tag at a later point. */ - PATH_STATE_USE_SUCCEEDED = 3, + PATH_STATE_USE_SUCCEEDED = 4, /** * This is a special state to indicate that we got a corrupted * relay cell on a circuit and we don't intend to probe it. */ - PATH_STATE_USE_FAILED = 4, + PATH_STATE_USE_FAILED = 5, + + /** + * This is a special state to indicate that we already counted + * the circuit. Used to guard against potential state machine + * violations. + */ + PATH_STATE_ALREADY_COUNTED = 6, } path_state_t; /** An origin_circuit_t holds data necessary to build and use a circuit. @@ -2997,7 +3013,6 @@ typedef struct origin_circuit_t { * ISO_STREAM. */ uint64_t associated_isolated_stream_global_id; /**@}*/ - } origin_circuit_t; /** An or_circuit_t holds information needed to implement a circuit at an @@ -3909,7 +3924,16 @@ typedef struct { int PathBiasScaleThreshold; int PathBiasScaleFactor; int PathBiasMultFactor; - int PathBiasUseCloseCounts; + /** @} */ + + /** + * Parameters for path-bias use detection + * @{ + */ + int PathBiasUseThreshold; + double PathBiasNoticeUseRate; + double PathBiasExtremeUseRate; + int PathBiasScaleUseThreshold; /** @} */ int IPv6Exit; /**< Do we support exiting to IPv6 addresses? */ diff --git a/src/or/relay.c b/src/or/relay.c index bb3a835442..f711eae92c 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -730,7 +730,7 @@ connection_ap_process_end_not_open( * We rely on recognized+digest being strong enough to make * tags unlikely to allow us to get tagged, yet 'recognized' * reason codes here. */ - circ->path_state = PATH_STATE_USE_SUCCEEDED; + pathbias_mark_use_success(circ); } } diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 32623c3f40..61e3b917e3 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -71,6 +71,9 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ) * and the rend cookie also means we've used the circ. */ circ->base_.timestamp_dirty = time(NULL); + /* We've attempted to use this circuit. Probe it if we fail */ + pathbias_count_use_attempt(circ); + if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), RELAY_COMMAND_ESTABLISH_RENDEZVOUS, circ->rend_data->rend_cookie, @@ -316,6 +319,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc, * state. */ introcirc->base_.timestamp_dirty = time(NULL); + pathbias_count_use_attempt(introcirc); + goto cleanup; perm_err: @@ -395,7 +400,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, /* For path bias: This circuit was used successfully. Valid * nacks and acks count. */ - circ->path_state = PATH_STATE_USE_SUCCEEDED; + pathbias_mark_use_success(circ); if (request_len == 0) { /* It's an ACK; the introduction point relayed our introduction request. */ @@ -902,7 +907,7 @@ rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, * Waiting any longer opens us up to attacks from Bob. He could induce * Alice to attempt to connect to his hidden service and never reply * to her rend requests */ - circ->path_state = PATH_STATE_USE_SUCCEEDED; + pathbias_mark_use_success(circ); /* XXXX This is a pretty brute-force approach. It'd be better to * attach only the connections that are waiting on this circuit, rather diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 6ffa4f8f95..3b2eeb4ad0 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -1384,9 +1384,6 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, goto err; memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN); - /* For path bias: This intro circuit was used successfully */ - circuit->path_state = PATH_STATE_USE_SUCCEEDED; - goto done; log_error: @@ -2511,6 +2508,9 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) goto err; } + /* We've attempted to use this circuit */ + pathbias_count_use_attempt(circuit); + goto done; err: @@ -2558,6 +2558,10 @@ rend_service_intro_established(origin_circuit_t *circuit, "Received INTRO_ESTABLISHED cell on circuit %d for service %s", circuit->base_.n_circ_id, serviceid); + /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully + * used the circ */ + pathbias_mark_use_success(circuit); + return 0; err: circuit_mark_for_close(TO_CIRCUIT(circuit), END_CIRC_REASON_TORPROTOCOL); @@ -2589,6 +2593,9 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) if (!circuit->base_.timestamp_dirty) circuit->base_.timestamp_dirty = time(NULL); + /* This may be redundant */ + pathbias_count_use_attempt(circuit); + hop = circuit->build_state->service_pending_final_cpath_ref->cpath; base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4); |