dlstatus.c (15486B)
1 /* Copyright (c) 2001-2004, Roger Dingledine. 2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. 3 * Copyright (c) 2007-2021, The Tor Project, Inc. */ 4 /* See LICENSE for licensing information */ 5 6 /** 7 * @file dlstatus.c 8 * @brief Track status and retry schedule of a downloadable object. 9 **/ 10 11 #define DLSTATUS_PRIVATE 12 13 #include "core/or/or.h" 14 15 #include "app/config/config.h" 16 #include "feature/client/entrynodes.h" 17 #include "feature/dirclient/dlstatus.h" 18 #include "feature/nodelist/networkstatus.h" 19 #include "feature/relay/routermode.h" 20 #include "lib/crypt_ops/crypto_rand.h" 21 22 #include "feature/dirclient/download_status_st.h" 23 24 /** Decide which download schedule we want to use based on descriptor type 25 * in <b>dls</b> and <b>options</b>. 26 * 27 * Then, return the initial delay for that download schedule, in seconds. 28 * 29 * Helper function for download_status_increment_failure(), 30 * download_status_reset(), and download_status_increment_attempt(). */ 31 STATIC int 32 find_dl_min_delay(const download_status_t *dls, const or_options_t *options) 33 { 34 tor_assert(dls); 35 tor_assert(options); 36 37 switch (dls->schedule) { 38 case DL_SCHED_GENERIC: 39 /* Any other directory document */ 40 if (dir_server_mode(options)) { 41 /* A directory authority or directory mirror */ 42 return options->TestingServerDownloadInitialDelay; 43 } else { 44 return options->TestingClientDownloadInitialDelay; 45 } 46 case DL_SCHED_CONSENSUS: 47 if (!networkstatus_consensus_can_use_multiple_directories(options)) { 48 /* A public relay */ 49 return options->TestingServerConsensusDownloadInitialDelay; 50 } else { 51 /* A client or bridge */ 52 if (networkstatus_consensus_is_bootstrapping(time(NULL))) { 53 /* During bootstrapping */ 54 if (!networkstatus_consensus_can_use_extra_fallbacks(options)) { 55 /* A bootstrapping client without extra fallback directories */ 56 return options-> 57 ClientBootstrapConsensusAuthorityOnlyDownloadInitialDelay; 58 } else if (dls->want_authority) { 59 /* A bootstrapping client with extra fallback directories, but 60 * connecting to an authority */ 61 return 62 options->ClientBootstrapConsensusAuthorityDownloadInitialDelay; 63 } else { 64 /* A bootstrapping client connecting to extra fallback directories 65 */ 66 return 67 options->ClientBootstrapConsensusFallbackDownloadInitialDelay; 68 } 69 } else { 70 /* A client with a reasonably live consensus, with or without 71 * certificates */ 72 return options->TestingClientConsensusDownloadInitialDelay; 73 } 74 } 75 case DL_SCHED_BRIDGE: 76 /* Be conservative here: always return the 'during bootstrap' delay 77 * value, so we never delay while trying to fetch descriptors 78 * for new bridges. Once we do succeed at fetching a descriptor 79 * for our bridge, we will adjust its next_attempt_at based on 80 * the longer "TestingBridgeDownloadInitialDelay" value. See 81 * learned_bridge_descriptor() for details. 82 */ 83 return options->TestingBridgeBootstrapDownloadInitialDelay; 84 default: 85 tor_assert(0); 86 } 87 88 /* Impossible, but gcc will fail with -Werror without a `return`. */ 89 return 0; 90 } 91 92 /** As next_random_exponential_delay() below, but does not compute a random 93 * value. Instead, compute the range of values that 94 * next_random_exponential_delay() should use when computing its random value. 95 * Store the low bound into *<b>low_bound_out</b>, and the high bound into 96 * *<b>high_bound_out</b>. Guarantees that the low bound is strictly less 97 * than the high bound. */ 98 STATIC void 99 next_random_exponential_delay_range(int *low_bound_out, 100 int *high_bound_out, 101 int delay, 102 int base_delay) 103 { 104 // This is the "decorrelated jitter" approach, from 105 // https://www.awsarchitectureblog.com/2015/03/backoff.html 106 // The formula is 107 // sleep = min(cap, random_between(base, sleep * 3)) 108 109 const int delay_times_3 = delay < INT_MAX/3 ? delay * 3 : INT_MAX; 110 *low_bound_out = base_delay; 111 if (delay_times_3 > base_delay) { 112 *high_bound_out = delay_times_3; 113 } else { 114 *high_bound_out = base_delay+1; 115 } 116 } 117 118 /** Advance one delay step. The algorithm will generate a random delay, 119 * such that each failure is possibly (random) longer than the ones before. 120 * 121 * We then clamp that value to be no larger than max_delay, and return it. 122 * 123 * The <b>base_delay</b> parameter is lowest possible delay time (can't be 124 * zero); the <b>backoff_position</b> parameter is the number of times we've 125 * generated a delay; and the <b>delay</b> argument is the most recently used 126 * delay. 127 */ 128 STATIC int 129 next_random_exponential_delay(int delay, 130 int base_delay) 131 { 132 /* Check preconditions */ 133 if (BUG(delay < 0)) 134 delay = 0; 135 136 if (base_delay < 1) 137 base_delay = 1; 138 139 int low_bound=0, high_bound=INT_MAX; 140 141 next_random_exponential_delay_range(&low_bound, &high_bound, 142 delay, base_delay); 143 144 return crypto_rand_int_range(low_bound, high_bound); 145 } 146 147 /** Find the current delay for dls based on min_delay. 148 * 149 * This function sets dls->next_attempt_at based on now, and returns the delay. 150 * Helper for download_status_increment_failure and 151 * download_status_increment_attempt. */ 152 STATIC int 153 download_status_schedule_get_delay(download_status_t *dls, 154 int min_delay, 155 time_t now) 156 { 157 tor_assert(dls); 158 /* If we're using random exponential backoff, we do need min/max delay */ 159 tor_assert(min_delay >= 0); 160 161 int delay = INT_MAX; 162 uint8_t dls_schedule_position = (dls->increment_on 163 == DL_SCHED_INCREMENT_ATTEMPT 164 ? dls->n_download_attempts 165 : dls->n_download_failures); 166 167 /* Check if we missed a reset somehow */ 168 IF_BUG_ONCE(dls->last_backoff_position > dls_schedule_position) { 169 dls->last_backoff_position = 0; 170 dls->last_delay_used = 0; 171 } 172 173 if (dls_schedule_position > 0) { 174 delay = dls->last_delay_used; 175 176 while (dls->last_backoff_position < dls_schedule_position) { 177 /* Do one increment step */ 178 delay = next_random_exponential_delay(delay, min_delay); 179 /* Update our position */ 180 ++(dls->last_backoff_position); 181 } 182 } else { 183 /* If we're just starting out, use the minimum delay */ 184 delay = min_delay; 185 } 186 187 /* Clamp it within min/max if we have them */ 188 if (min_delay >= 0 && delay < min_delay) delay = min_delay; 189 190 /* Store it for next time */ 191 dls->last_backoff_position = dls_schedule_position; 192 dls->last_delay_used = delay; 193 194 /* A negative delay makes no sense. Knowing that delay is 195 * non-negative allows us to safely do the wrapping check below. */ 196 tor_assert(delay >= 0); 197 198 /* Avoid now+delay overflowing TIME_MAX, by comparing with a subtraction 199 * that won't overflow (since delay is non-negative). */ 200 if (delay < INT_MAX && now <= TIME_MAX - delay) { 201 dls->next_attempt_at = now+delay; 202 } else { 203 dls->next_attempt_at = TIME_MAX; 204 } 205 206 return delay; 207 } 208 209 /* Log a debug message about item, which increments on increment_action, has 210 * incremented dls_n_download_increments times. The message varies based on 211 * was_schedule_incremented (if not, not_incremented_response is logged), and 212 * the values of increment, dls_next_attempt_at, and now. 213 * Helper for download_status_increment_failure and 214 * download_status_increment_attempt. */ 215 static void 216 download_status_log_helper(const char *item, int was_schedule_incremented, 217 const char *increment_action, 218 const char *not_incremented_response, 219 uint8_t dls_n_download_increments, int increment, 220 time_t dls_next_attempt_at, time_t now) 221 { 222 if (item) { 223 if (!was_schedule_incremented) 224 log_debug(LD_DIR, "%s %s %d time(s); I'll try again %s.", 225 item, increment_action, (int)dls_n_download_increments, 226 not_incremented_response); 227 else if (increment == 0) 228 log_debug(LD_DIR, "%s %s %d time(s); I'll try again immediately.", 229 item, increment_action, (int)dls_n_download_increments); 230 else if (dls_next_attempt_at < TIME_MAX) 231 log_debug(LD_DIR, "%s %s %d time(s); I'll try again in %d seconds.", 232 item, increment_action, (int)dls_n_download_increments, 233 (int)(dls_next_attempt_at-now)); 234 else 235 log_debug(LD_DIR, "%s %s %d time(s); Giving up for a while.", 236 item, increment_action, (int)dls_n_download_increments); 237 } 238 } 239 240 /** Determine when a failed download attempt should be retried. 241 * Called when an attempt to download <b>dls</b> has failed with HTTP status 242 * <b>status_code</b>. Increment the failure count (if the code indicates a 243 * real failure, or if we're a server) and set <b>dls</b>-\>next_attempt_at to 244 * an appropriate time in the future and return it. 245 * If <b>dls->increment_on</b> is DL_SCHED_INCREMENT_ATTEMPT, increment the 246 * failure count, and return a time in the far future for the next attempt (to 247 * avoid an immediate retry). */ 248 time_t 249 download_status_increment_failure(download_status_t *dls, int status_code, 250 const char *item, int server, time_t now) 251 { 252 (void) status_code; // XXXX no longer used. 253 (void) server; // XXXX no longer used. 254 int increment = -1; 255 int min_delay = 0; 256 257 tor_assert(dls); 258 259 /* dls wasn't reset before it was used */ 260 if (dls->next_attempt_at == 0) { 261 download_status_reset(dls); 262 } 263 264 /* count the failure */ 265 if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) { 266 ++dls->n_download_failures; 267 } 268 269 if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { 270 /* We don't find out that a failure-based schedule has attempted a 271 * connection until that connection fails. 272 * We'll never find out about successful connections, but this doesn't 273 * matter, because schedules are reset after a successful download. 274 */ 275 if (dls->n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD-1) 276 ++dls->n_download_attempts; 277 278 /* only return a failure retry time if this schedule increments on failures 279 */ 280 min_delay = find_dl_min_delay(dls, get_options()); 281 increment = download_status_schedule_get_delay(dls, min_delay, now); 282 } 283 284 download_status_log_helper(item, !dls->increment_on, "failed", 285 "concurrently", dls->n_download_failures, 286 increment, 287 download_status_get_next_attempt_at(dls), 288 now); 289 290 if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) { 291 /* stop this schedule retrying on failure, it will launch concurrent 292 * connections instead */ 293 return TIME_MAX; 294 } else { 295 return download_status_get_next_attempt_at(dls); 296 } 297 } 298 299 /** Determine when the next download attempt should be made when using an 300 * attempt-based (potentially concurrent) download schedule. 301 * Called when an attempt to download <b>dls</b> is being initiated. 302 * Increment the attempt count and set <b>dls</b>-\>next_attempt_at to an 303 * appropriate time in the future and return it. 304 * If <b>dls->increment_on</b> is DL_SCHED_INCREMENT_FAILURE, don't increment 305 * the attempts, and return a time in the far future (to avoid launching a 306 * concurrent attempt). */ 307 time_t 308 download_status_increment_attempt(download_status_t *dls, const char *item, 309 time_t now) 310 { 311 int delay = -1; 312 int min_delay = 0; 313 314 tor_assert(dls); 315 316 /* dls wasn't reset before it was used */ 317 if (dls->next_attempt_at == 0) { 318 download_status_reset(dls); 319 } 320 321 if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { 322 /* this schedule should retry on failure, and not launch any concurrent 323 attempts */ 324 log_warn(LD_BUG, "Tried to launch an attempt-based connection on a " 325 "failure-based schedule."); 326 return TIME_MAX; 327 } 328 329 if (dls->n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD-1) 330 ++dls->n_download_attempts; 331 332 min_delay = find_dl_min_delay(dls, get_options()); 333 delay = download_status_schedule_get_delay(dls, min_delay, now); 334 335 download_status_log_helper(item, dls->increment_on, "attempted", 336 "on failure", dls->n_download_attempts, 337 delay, download_status_get_next_attempt_at(dls), 338 now); 339 340 return download_status_get_next_attempt_at(dls); 341 } 342 343 static time_t 344 download_status_get_initial_delay_from_now(const download_status_t *dls) 345 { 346 /* We use constant initial delays, even in exponential backoff 347 * schedules. */ 348 return time(NULL) + find_dl_min_delay(dls, get_options()); 349 } 350 351 /** Reset <b>dls</b> so that it will be considered downloadable 352 * immediately, and/or to show that we don't need it anymore. 353 * 354 * Must be called to initialise a download schedule, otherwise the zeroth item 355 * in the schedule will never be used. 356 * 357 * (We find the zeroth element of the download schedule, and set 358 * next_attempt_at to be the appropriate offset from 'now'. In most 359 * cases this means setting it to 'now', so the item will be immediately 360 * downloadable; when using authorities with fallbacks, there is a few seconds' 361 * delay.) */ 362 void 363 download_status_reset(download_status_t *dls) 364 { 365 if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD 366 || dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD) 367 return; /* Don't reset this. */ 368 369 dls->n_download_failures = 0; 370 dls->n_download_attempts = 0; 371 dls->next_attempt_at = download_status_get_initial_delay_from_now(dls); 372 dls->last_backoff_position = 0; 373 dls->last_delay_used = 0; 374 /* Don't reset dls->want_authority or dls->increment_on */ 375 } 376 377 /** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is 378 * ready to get its download reattempted. */ 379 int 380 download_status_is_ready(download_status_t *dls, time_t now) 381 { 382 /* dls wasn't reset before it was used */ 383 if (dls->next_attempt_at == 0) { 384 download_status_reset(dls); 385 } 386 387 return download_status_get_next_attempt_at(dls) <= now; 388 } 389 390 /** Mark <b>dl</b> as never downloadable. */ 391 void 392 download_status_mark_impossible(download_status_t *dl) 393 { 394 dl->n_download_failures = IMPOSSIBLE_TO_DOWNLOAD; 395 dl->n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD; 396 } 397 398 /** Return the number of failures on <b>dls</b> since the last success (if 399 * any). */ 400 int 401 download_status_get_n_failures(const download_status_t *dls) 402 { 403 return dls->n_download_failures; 404 } 405 406 /** Return the number of attempts to download <b>dls</b> since the last success 407 * (if any). This can differ from download_status_get_n_failures() due to 408 * outstanding concurrent attempts. */ 409 int 410 download_status_get_n_attempts(const download_status_t *dls) 411 { 412 return dls->n_download_attempts; 413 } 414 415 /** Return the next time to attempt to download <b>dls</b>. */ 416 time_t 417 download_status_get_next_attempt_at(const download_status_t *dls) 418 { 419 /* dls wasn't reset before it was used */ 420 if (dls->next_attempt_at == 0) { 421 /* so give the answer we would have given if it had been */ 422 return download_status_get_initial_delay_from_now(dls); 423 } 424 425 return dls->next_attempt_at; 426 }