#define DLSTATUS_PRIVATE
#include "core/or/or.h"
#include "app/config/config.h"
#include "feature/client/entrynodes.h"
#include "feature/dirclient/dlstatus.h"
#include "feature/nodelist/networkstatus.h"
#include "feature/relay/routermode.h"
#include "lib/crypt_ops/crypto_rand.h"
#include "feature/dirclient/download_status_st.h"
STATIC int
find_dl_min_delay(const download_status_t *dls, const or_options_t *options)
{
tor_assert(dls);
tor_assert(options);
switch (dls->schedule) {
case DL_SCHED_GENERIC:
if (dir_server_mode(options)) {
return options->TestingServerDownloadInitialDelay;
} else {
return options->TestingClientDownloadInitialDelay;
}
case DL_SCHED_CONSENSUS:
if (!networkstatus_consensus_can_use_multiple_directories(options)) {
return options->TestingServerConsensusDownloadInitialDelay;
} else {
if (networkstatus_consensus_is_bootstrapping(time(NULL))) {
if (!networkstatus_consensus_can_use_extra_fallbacks(options)) {
return options->
ClientBootstrapConsensusAuthorityOnlyDownloadInitialDelay;
} else if (dls->want_authority) {
return
options->ClientBootstrapConsensusAuthorityDownloadInitialDelay;
} else {
return
options->ClientBootstrapConsensusFallbackDownloadInitialDelay;
}
} else {
return options->TestingClientConsensusDownloadInitialDelay;
}
}
case DL_SCHED_BRIDGE:
if (options->UseBridges && num_bridges_usable(0) > 0) {
return options->TestingBridgeDownloadInitialDelay;
} else {
return options->TestingBridgeBootstrapDownloadInitialDelay;
}
default:
tor_assert(0);
}
return 0;
}
STATIC void
next_random_exponential_delay_range(int *low_bound_out,
int *high_bound_out,
int delay,
int base_delay)
{
const int delay_times_3 = delay < INT_MAX/3 ? delay * 3 : INT_MAX;
*low_bound_out = base_delay;
if (delay_times_3 > base_delay) {
*high_bound_out = delay_times_3;
} else {
*high_bound_out = base_delay+1;
}
}
STATIC int
next_random_exponential_delay(int delay,
int base_delay)
{
if (BUG(delay < 0))
delay = 0;
if (base_delay < 1)
base_delay = 1;
int low_bound=0, high_bound=INT_MAX;
next_random_exponential_delay_range(&low_bound, &high_bound,
delay, base_delay);
return crypto_rand_int_range(low_bound, high_bound);
}
STATIC int
download_status_schedule_get_delay(download_status_t *dls,
int min_delay,
time_t now)
{
tor_assert(dls);
tor_assert(min_delay >= 0);
int delay = INT_MAX;
uint8_t dls_schedule_position = (dls->increment_on
== DL_SCHED_INCREMENT_ATTEMPT
? dls->n_download_attempts
: dls->n_download_failures);
IF_BUG_ONCE(dls->last_backoff_position > dls_schedule_position) {
dls->last_backoff_position = 0;
dls->last_delay_used = 0;
}
if (dls_schedule_position > 0) {
delay = dls->last_delay_used;
while (dls->last_backoff_position < dls_schedule_position) {
delay = next_random_exponential_delay(delay, min_delay);
++(dls->last_backoff_position);
}
} else {
delay = min_delay;
}
if (min_delay >= 0 && delay < min_delay) delay = min_delay;
dls->last_backoff_position = dls_schedule_position;
dls->last_delay_used = delay;
tor_assert(delay >= 0);
if (delay < INT_MAX && now <= TIME_MAX - delay) {
dls->next_attempt_at = now+delay;
} else {
dls->next_attempt_at = TIME_MAX;
}
return delay;
}
static void
download_status_log_helper(const char *item, int was_schedule_incremented,
const char *increment_action,
const char *not_incremented_response,
uint8_t dls_n_download_increments, int increment,
time_t dls_next_attempt_at, time_t now)
{
if (item) {
if (!was_schedule_incremented)
log_debug(LD_DIR, "%s %s %d time(s); I'll try again %s.",
item, increment_action, (int)dls_n_download_increments,
not_incremented_response);
else if (increment == 0)
log_debug(LD_DIR, "%s %s %d time(s); I'll try again immediately.",
item, increment_action, (int)dls_n_download_increments);
else if (dls_next_attempt_at < TIME_MAX)
log_debug(LD_DIR, "%s %s %d time(s); I'll try again in %d seconds.",
item, increment_action, (int)dls_n_download_increments,
(int)(dls_next_attempt_at-now));
else
log_debug(LD_DIR, "%s %s %d time(s); Giving up for a while.",
item, increment_action, (int)dls_n_download_increments);
}
}
time_t
download_status_increment_failure(download_status_t *dls, int status_code,
const char *item, int server, time_t now)
{
(void) status_code; (void) server; int increment = -1;
int min_delay = 0;
tor_assert(dls);
if (dls->next_attempt_at == 0) {
download_status_reset(dls);
}
if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) {
++dls->n_download_failures;
}
if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) {
if (dls->n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD-1)
++dls->n_download_attempts;
min_delay = find_dl_min_delay(dls, get_options());
increment = download_status_schedule_get_delay(dls, min_delay, now);
}
download_status_log_helper(item, !dls->increment_on, "failed",
"concurrently", dls->n_download_failures,
increment,
download_status_get_next_attempt_at(dls),
now);
if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) {
return TIME_MAX;
} else {
return download_status_get_next_attempt_at(dls);
}
}
time_t
download_status_increment_attempt(download_status_t *dls, const char *item,
time_t now)
{
int delay = -1;
int min_delay = 0;
tor_assert(dls);
if (dls->next_attempt_at == 0) {
download_status_reset(dls);
}
if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) {
log_warn(LD_BUG, "Tried to launch an attempt-based connection on a "
"failure-based schedule.");
return TIME_MAX;
}
if (dls->n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD-1)
++dls->n_download_attempts;
min_delay = find_dl_min_delay(dls, get_options());
delay = download_status_schedule_get_delay(dls, min_delay, now);
download_status_log_helper(item, dls->increment_on, "attempted",
"on failure", dls->n_download_attempts,
delay, download_status_get_next_attempt_at(dls),
now);
return download_status_get_next_attempt_at(dls);
}
static time_t
download_status_get_initial_delay_from_now(const download_status_t *dls)
{
return time(NULL) + find_dl_min_delay(dls, get_options());
}
void
download_status_reset(download_status_t *dls)
{
if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD
|| dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD)
return;
dls->n_download_failures = 0;
dls->n_download_attempts = 0;
dls->next_attempt_at = download_status_get_initial_delay_from_now(dls);
dls->last_backoff_position = 0;
dls->last_delay_used = 0;
}
int
download_status_is_ready(download_status_t *dls, time_t now)
{
if (dls->next_attempt_at == 0) {
download_status_reset(dls);
}
return download_status_get_next_attempt_at(dls) <= now;
}
void
download_status_mark_impossible(download_status_t *dl)
{
dl->n_download_failures = IMPOSSIBLE_TO_DOWNLOAD;
dl->n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD;
}
int
download_status_get_n_failures(const download_status_t *dls)
{
return dls->n_download_failures;
}
int
download_status_get_n_attempts(const download_status_t *dls)
{
return dls->n_download_attempts;
}
time_t
download_status_get_next_attempt_at(const download_status_t *dls)
{
if (dls->next_attempt_at == 0) {
return download_status_get_initial_delay_from_now(dls);
}
return dls->next_attempt_at;
}