From b5a164bde4f33fdd33c83a8425c6464c0f5ef60d Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 27 Feb 2013 19:43:50 -0800 Subject: [PATCH] Prefer measured bandwidths over advertised when computing things for votes on a dirauth --- changes/bug8273 | 3 + src/or/dirserv.c | 206 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 changes/bug8273 diff --git a/changes/bug8273 b/changes/bug8273 new file mode 100644 index 000000000..257f57e7a --- /dev/null +++ b/changes/bug8273 @@ -0,0 +1,3 @@ + o Critical bugfixes: + - When dirserv.c computes flags and thresholds, use measured bandwidths + in preference to advertised ones. diff --git a/src/or/dirserv.c b/src/or/dirserv.c index badacd683..1c78d2d3b 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -85,6 +85,14 @@ static const signed_descriptor_t *get_signed_descriptor_by_fp( time_t publish_cutoff); static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei, const char **msg); +static void dirserv_cache_measured_bw(measured_bw_line_t *parsed_line, + time_t as_of); +static void dirserv_clear_measured_bw_cache(void); +static void dirserv_expire_measured_bw_cache(time_t now); +static int dirserv_get_measured_bw_cache_size(void); +static int dirserv_query_measured_bw_cache(char *node_id, long *bw_out, + time_t *as_of_out); +static uint32_t dirserv_get_bandwidth_for_router(routerinfo_t *ri); /************** Measured Bandwidth parsing code ******/ #define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */ @@ -1824,7 +1832,7 @@ dirserv_thinks_router_is_unreliable(time_t now, } } if (need_capacity) { - uint32_t bw = router_get_advertised_bandwidth(router); + uint32_t bw = dirserv_get_bandwidth_for_router(router); if (bw < fast_bandwidth) return 1; } @@ -1883,7 +1891,7 @@ router_counts_toward_thresholds(const node_t *node, time_t now, { return node->ri && router_is_active(node->ri, node, now) && !digestmap_get(omit_as_sybil, node->ri->cache_info.identity_digest) && - (router_get_advertised_bandwidth(node->ri) >= + (dirserv_get_bandwidth_for_router(node->ri) >= ABSOLUTE_MIN_BW_VALUE_TO_CONSIDER); } @@ -1947,7 +1955,7 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, uptimes[n_active] = (uint32_t)real_uptime(ri, now); mtbfs[n_active] = rep_hist_get_stability(id, now); tks [n_active] = rep_hist_get_weighted_time_known(id, now); - bandwidths[n_active] = bw = router_get_advertised_bandwidth(ri); + bandwidths[n_active] = bw = dirserv_get_bandwidth_for_router(ri); total_bandwidth += bw; if (node->is_exit && !node->is_bad_exit) { total_exit_bandwidth += bw; @@ -2046,6 +2054,165 @@ dirserv_compute_performance_thresholds(routerlist_t *rl, tor_free(wfus); } +/** Measured bandwidth cache entry */ +typedef struct mbw_cache_entry_s { + long mbw; + time_t as_of; +} mbw_cache_entry_t; + +/** Measured bandwidth cache - keys are identity_digests, values are + * mbw_cache_entry_t *. */ +static digestmap_t *mbw_cache = NULL; + +/** Store a measured bandwidth cache entry when reading the measured + * bandwidths file. */ +static void +dirserv_cache_measured_bw(measured_bw_line_t *parsed_line, time_t as_of) +{ + mbw_cache_entry_t *e = NULL; + + tor_assert(parsed_line); + + /* Allocate a cache if we need */ + if (!mbw_cache) mbw_cache = digestmap_new(); + + /* Check if we have an existing entry */ + e = digestmap_get(mbw_cache, parsed_line->node_id); + /* If we do, we can re-use it */ + if (e) { + /* Check that we really are newer, and update */ + if (as_of > e->as_of) { + e->mbw = parsed_line->bw; + e->as_of = as_of; + } + } else { + /* We'll have to insert a new entry */ + e = tor_malloc(sizeof(*e)); + e->mbw = parsed_line->bw; + e->as_of = as_of; + digestmap_set(mbw_cache, parsed_line->node_id, e); + } +} + +/** Clear and free the measured bandwidth cache */ +static void +dirserv_clear_measured_bw_cache(void) +{ + if (mbw_cache) { + /* Free the map and all entries */ + digestmap_free(mbw_cache, tor_free_); + mbw_cache = NULL; + } +} + +/** Scan the measured bandwidth cache and remove expired entries */ +static void +dirserv_expire_measured_bw_cache(time_t now) +{ + digestmap_iter_t *itr = NULL; + const char *k = NULL; + void *e_v = NULL; + mbw_cache_entry_t *e = NULL; + int rmv; + + if (mbw_cache) { + /* Start iterating */ + itr = digestmap_iter_init(mbw_cache); + while (!digestmap_iter_done(itr)) { + rmv = 0; + digestmap_iter_get(itr, &k, &e_v); + e = (mbw_cache_entry_t *)e_v; + if (e) { + /* Check for expiration and remove if so */ + if (now - e->as_of > MAX_MEASUREMENT_AGE) { + tor_free(e); + rmv = 1; + } + } else { + /* Weird; remove it and complain */ + log_warn(LD_BUG, "Saw NULL entry in measured bandwidth cache"); + rmv = 1; + } + + /* Advance, or remove and advance */ + if (rmv) { + itr = digestmap_iter_next_rmv(mbw_cache, itr); + } else { + itr = digestmap_iter_next(mbw_cache, itr); + } + } + + /* Check if we cleared the whole thing and free if so */ + if (digestmap_size(mbw_cache) == 0) { + digestmap_free(mbw_cache, tor_free_); + mbw_cache = 0; + } + } +} + +/** Get the current size of the measured bandwidth cache */ +static int +dirserv_get_measured_bw_cache_size(void) +{ + if (mbw_cache) return digestmap_size(mbw_cache); + else return 0; +} + +/** Query the cache by identity digest, return value indicates whether + * we found it. */ +static int +dirserv_query_measured_bw_cache(char *node_id, long *bw_out, + time_t *as_of_out) +{ + mbw_cache_entry_t *v = NULL; + int rv = 0; + + if (mbw_cache && node_id) { + v = digestmap_get(mbw_cache, node_id); + if (v) { + /* Found something */ + rv = 1; + if (bw_out) *bw_out = v->mbw; + if (as_of_out) *as_of_out = v->as_of; + } + } + + return rv; +} + +/** Get the best estimate of a router's bandwidth for dirauth purposes, + * preferring measured to advertised values if available. */ + +static uint32_t +dirserv_get_bandwidth_for_router(routerinfo_t *ri) +{ + uint32_t bw = 0; + /* + * Yeah, measured bandwidths in measured_bw_line_t are (implicitly + * signed) longs and the ones router_get_advertised_bandwidth() returns + * are uint32_t. + */ + long mbw = 0; + + if (ri) { + /* + * * First try to see if we have a measured bandwidth; don't bother with + * as_of_out here, on the theory that a stale measured bandwidth is still + * better to trust than an advertised one. + */ + if (dirserv_query_measured_bw_cache(ri->cache_info.identity_digest, + &mbw, NULL)) { + /* Got one! */ + bw = (uint32_t)mbw; + } else { + /* If not, fall back to advertised */ + bw = router_get_advertised_bandwidth(ri); + } + } + + return bw; +} + /** Give a statement of our current performance thresholds for inclusion * in a vote document. */ char * @@ -2327,8 +2494,8 @@ compare_routerinfo_by_ip_and_bw_(const void **a, const void **b) else if (!first_is_running && second_is_running) return 1; - bw_first = router_get_advertised_bandwidth(first); - bw_second = router_get_advertised_bandwidth(second); + bw_first = dirserv_get_bandwidth_for_router(first); + bw_second = dirserv_get_bandwidth_for_router(second); if (bw_first > bw_second) return -1; @@ -2468,7 +2635,7 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, int listbaddirs, int vote_on_hsdirs) { const or_options_t *options = get_options(); - uint32_t routerbw = router_get_advertised_bandwidth(ri); + uint32_t routerbw = dirserv_get_bandwidth_for_router(ri); memset(rs, 0, sizeof(routerstatus_t)); @@ -2670,8 +2837,9 @@ dirserv_read_measured_bandwidths(const char *from_file, char line[256]; FILE *fp = tor_fopen_cloexec(from_file, "r"); int applied_lines = 0; - time_t file_time; + time_t file_time, now; int ok; + if (fp == NULL) { log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s", from_file); @@ -2695,7 +2863,8 @@ dirserv_read_measured_bandwidths(const char *from_file, return -1; } - if ((time(NULL) - file_time) > MAX_MEASUREMENT_AGE) { + now = time(NULL); + if ((now - file_time) > MAX_MEASUREMENT_AGE) { log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u", (unsigned)(time(NULL) - file_time)); fclose(fp); @@ -2709,12 +2878,17 @@ dirserv_read_measured_bandwidths(const char *from_file, measured_bw_line_t parsed_line; if (fgets(line, sizeof(line), fp) && strlen(line)) { if (measured_bw_line_parse(&parsed_line, line) != -1) { + /* Also cache the line for dirserv_get_bandwidth_for_router() */ + dirserv_cache_measured_bw(&parsed_line, file_time); if (measured_bw_line_apply(&parsed_line, routerstatuses) > 0) applied_lines++; } } } + /* Now would be a nice time to clean the cache, too */ + dirserv_expire_measured_bw_cache(now); + fclose(fp); log_info(LD_DIRSERV, "Bandwidth measurement file successfully read. " @@ -2778,6 +2952,14 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, if (!contact) contact = "(none)"; + /* + * Do this so dirserv_compute_performance_thresholds() and + * set_routerstatus_from_routerinfo() see up-to-date bandwidth info. + */ + if (options->V3BandwidthsFile) { + dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL); + } + /* precompute this part, since we need it to decide what "stable" * means. */ SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, { @@ -2841,6 +3023,14 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, if (options->V3BandwidthsFile) { dirserv_read_measured_bandwidths(options->V3BandwidthsFile, routerstatuses); + } else { + /* + * No bandwidths file; clear the measured bandwidth cache in case we had + * one last time around. + */ + if (dirserv_get_measured_bw_cache_size() > 0) { + dirserv_clear_measured_bw_cache(); + } } v3_out = tor_malloc_zero(sizeof(networkstatus_t));