tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

bwauth.c (16542B)


      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 bwauth.c
      8 * \brief Code to read and apply bandwidth authority data.
      9 **/
     10 
     11 #define BWAUTH_PRIVATE
     12 #include "core/or/or.h"
     13 #include "feature/dirauth/bwauth.h"
     14 
     15 #include "app/config/config.h"
     16 #include "feature/dirauth/dirauth_sys.h"
     17 #include "feature/nodelist/networkstatus.h"
     18 #include "feature/nodelist/routerlist.h"
     19 #include "feature/dirparse/ns_parse.h"
     20 
     21 #include "feature/dirauth/dirauth_options_st.h"
     22 #include "feature/nodelist/routerinfo_st.h"
     23 #include "feature/nodelist/vote_routerstatus_st.h"
     24 
     25 #include "lib/crypt_ops/crypto_format.h"
     26 #include "lib/encoding/keyval.h"
     27 
     28 /** Total number of routers with measured bandwidth; this is set by
     29 * dirserv_count_measured_bs() before the loop in
     30 * dirserv_generate_networkstatus_vote_obj() and checked by
     31 * dirserv_get_credible_bandwidth() and
     32 * dirserv_compute_performance_thresholds() */
     33 static int routers_with_measured_bw = 0;
     34 
     35 /** Look through the routerlist, and using the measured bandwidth cache count
     36 * how many measured bandwidths we know.  This is used to decide whether we
     37 * ever trust advertised bandwidths for purposes of assigning flags. */
     38 void
     39 dirserv_count_measured_bws(const smartlist_t *routers)
     40 {
     41  /* Initialize this first */
     42  routers_with_measured_bw = 0;
     43 
     44  /* Iterate over the routerlist and count measured bandwidths */
     45  SMARTLIST_FOREACH_BEGIN(routers, const routerinfo_t *, ri) {
     46    /* Check if we know a measured bandwidth for this one */
     47    if (dirserv_has_measured_bw(ri->cache_info.identity_digest)) {
     48      ++routers_with_measured_bw;
     49    }
     50  } SMARTLIST_FOREACH_END(ri);
     51 }
     52 
     53 /** Return the last-computed result from dirserv_count_mesured_bws(). */
     54 int
     55 dirserv_get_last_n_measured_bws(void)
     56 {
     57  return routers_with_measured_bw;
     58 }
     59 
     60 /** Measured bandwidth cache entry */
     61 typedef struct mbw_cache_entry_t {
     62  long mbw_kb;
     63  time_t as_of;
     64 } mbw_cache_entry_t;
     65 
     66 /** Measured bandwidth cache - keys are identity_digests, values are
     67 * mbw_cache_entry_t *. */
     68 static digestmap_t *mbw_cache = NULL;
     69 
     70 /** Store a measured bandwidth cache entry when reading the measured
     71 * bandwidths file. */
     72 STATIC void
     73 dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line,
     74                          time_t as_of)
     75 {
     76  mbw_cache_entry_t *e = NULL;
     77 
     78  tor_assert(parsed_line);
     79 
     80  /* Allocate a cache if we need */
     81  if (!mbw_cache) mbw_cache = digestmap_new();
     82 
     83  /* Check if we have an existing entry */
     84  e = digestmap_get(mbw_cache, parsed_line->node_id);
     85  /* If we do, we can re-use it */
     86  if (e) {
     87    /* Check that we really are newer, and update */
     88    if (as_of > e->as_of) {
     89      e->mbw_kb = parsed_line->bw_kb;
     90      e->as_of = as_of;
     91    }
     92  } else {
     93    /* We'll have to insert a new entry */
     94    e = tor_malloc(sizeof(*e));
     95    e->mbw_kb = parsed_line->bw_kb;
     96    e->as_of = as_of;
     97    digestmap_set(mbw_cache, parsed_line->node_id, e);
     98  }
     99 }
    100 
    101 /** Clear and free the measured bandwidth cache */
    102 void
    103 dirserv_clear_measured_bw_cache(void)
    104 {
    105  if (mbw_cache) {
    106    /* Free the map and all entries */
    107    digestmap_free(mbw_cache, tor_free_);
    108    mbw_cache = NULL;
    109  }
    110 }
    111 
    112 /** Scan the measured bandwidth cache and remove expired entries */
    113 STATIC void
    114 dirserv_expire_measured_bw_cache(time_t now)
    115 {
    116 
    117  if (mbw_cache) {
    118    /* Iterate through the cache and check each entry */
    119    DIGESTMAP_FOREACH_MODIFY(mbw_cache, k, mbw_cache_entry_t *, e) {
    120      if (now > e->as_of + MAX_MEASUREMENT_AGE) {
    121        tor_free(e);
    122        MAP_DEL_CURRENT(k);
    123      }
    124    } DIGESTMAP_FOREACH_END;
    125 
    126    /* Check if we cleared the whole thing and free if so */
    127    if (digestmap_size(mbw_cache) == 0) {
    128      digestmap_free(mbw_cache, tor_free_);
    129      mbw_cache = 0;
    130    }
    131  }
    132 }
    133 
    134 /** Query the cache by identity digest, return value indicates whether
    135 * we found it. The bw_out and as_of_out pointers receive the cached
    136 * bandwidth value and the time it was cached if not NULL. */
    137 int
    138 dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out,
    139                                   time_t *as_of_out)
    140 {
    141  mbw_cache_entry_t *v = NULL;
    142  int rv = 0;
    143 
    144  if (mbw_cache && node_id) {
    145    v = digestmap_get(mbw_cache, node_id);
    146    if (v) {
    147      /* Found something */
    148      rv = 1;
    149      if (bw_kb_out) *bw_kb_out = v->mbw_kb;
    150      if (as_of_out) *as_of_out = v->as_of;
    151    }
    152  }
    153 
    154  return rv;
    155 }
    156 
    157 /** Predicate wrapper for dirserv_query_measured_bw_cache() */
    158 int
    159 dirserv_has_measured_bw(const char *node_id)
    160 {
    161  return dirserv_query_measured_bw_cache_kb(node_id, NULL, NULL);
    162 }
    163 
    164 /** Get the current size of the measured bandwidth cache */
    165 int
    166 dirserv_get_measured_bw_cache_size(void)
    167 {
    168  if (mbw_cache) return digestmap_size(mbw_cache);
    169  else return 0;
    170 }
    171 
    172 /** Return the bandwidth we believe for assigning flags; prefer measured
    173 * over advertised, and if we have above a threshold quantity of measured
    174 * bandwidths, we don't want to ever give flags to unmeasured routers, so
    175 * return 0. */
    176 uint32_t
    177 dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri)
    178 {
    179  int threshold;
    180  uint32_t bw_kb = 0;
    181  long mbw_kb;
    182 
    183  tor_assert(ri);
    184  /* Check if we have a measured bandwidth, and check the threshold if not */
    185  if (!(dirserv_query_measured_bw_cache_kb(ri->cache_info.identity_digest,
    186                                       &mbw_kb, NULL))) {
    187    threshold = dirauth_get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised;
    188    if (routers_with_measured_bw > threshold) {
    189      /* Return zero for unmeasured bandwidth if we are above threshold */
    190      bw_kb = 0;
    191    } else {
    192      /* Return an advertised bandwidth otherwise */
    193      bw_kb = router_get_advertised_bandwidth_capped(ri) / 1000;
    194    }
    195  } else {
    196    /* We have the measured bandwidth in mbw */
    197    bw_kb = (uint32_t)mbw_kb;
    198  }
    199 
    200  return bw_kb;
    201 }
    202 
    203 /**
    204 * Read the measured bandwidth list <b>from_file</b>:
    205 * - store all the headers in <b>bw_file_headers</b>,
    206 * - apply bandwidth lines to the list of vote_routerstatus_t in
    207 *   <b>routerstatuses</b>,
    208 * - cache bandwidth lines for dirserv_get_bandwidth_for_router(),
    209 * - expire old entries in the measured bandwidth cache, and
    210 * - store the DIGEST_SHA256 of the contents of the file in <b>digest_out</b>.
    211 *
    212 * Returns -1 on error, 0 otherwise.
    213 *
    214 * If the file can't be read, or is empty:
    215 * - <b>bw_file_headers</b> is empty,
    216 * - <b>routerstatuses</b> is not modified,
    217 * - the measured bandwidth cache is not modified, and
    218 * - <b>digest_out</b> is the zero-byte digest.
    219 *
    220 * Otherwise, if there is an error later in the file:
    221 * - <b>bw_file_headers</b> contains all the headers up to the error,
    222 * - <b>routerstatuses</b> is updated with all the relay lines up to the error,
    223 * - the measured bandwidth cache is updated with all the relay lines up to
    224 *   the error,
    225 * - if the timestamp is valid and recent, old entries in the  measured
    226 *   bandwidth cache are expired, and
    227 * - <b>digest_out</b> is the digest up to the first read error (if any).
    228 *   The digest is taken over all the readable file contents, even if the
    229 *   file is outdated or unparseable.
    230 */
    231 int
    232 dirserv_read_measured_bandwidths(const char *from_file,
    233                                 smartlist_t *routerstatuses,
    234                                 smartlist_t *bw_file_headers,
    235                                 uint8_t *digest_out)
    236 {
    237  FILE *fp = tor_fopen_cloexec(from_file, "r");
    238  int applied_lines = 0;
    239  time_t file_time, now;
    240  int ok;
    241   /* This flag will be 1 only when the first successful bw measurement line
    242   * has been encountered, so that measured_bw_line_parse don't give warnings
    243   * if there are additional header lines, as introduced in Bandwidth List spec
    244   * version 1.1.0 */
    245  int line_is_after_headers = 0;
    246  int rv = -1;
    247  char *line = NULL;
    248  size_t n = 0;
    249  crypto_digest_t *digest = crypto_digest256_new(DIGEST_SHA256);
    250 
    251  if (fp == NULL) {
    252    log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s",
    253             from_file);
    254    goto err;
    255  }
    256 
    257  if (tor_getline(&line,&n,fp) <= 0) {
    258    log_warn(LD_DIRSERV, "Empty bandwidth file");
    259    goto err;
    260  }
    261  /* If the line could be gotten, add it to the digest */
    262  crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
    263 
    264  if (!strlen(line) || line[strlen(line)-1] != '\n') {
    265    log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s",
    266             escaped(line));
    267    /* Continue adding lines to the digest. */
    268    goto continue_digest;
    269  }
    270 
    271  line[strlen(line)-1] = '\0';
    272  file_time = (time_t)tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL);
    273  if (!ok) {
    274    log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s",
    275             escaped(line));
    276    goto continue_digest;
    277  }
    278 
    279  now = approx_time();
    280  if ((now - file_time) > MAX_MEASUREMENT_AGE) {
    281    log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u",
    282             (unsigned)(time(NULL) - file_time));
    283    goto continue_digest;
    284  }
    285 
    286  /* If timestamp was correct and bw_file_headers is not NULL,
    287   * add timestamp to bw_file_headers */
    288  if (bw_file_headers)
    289    smartlist_add_asprintf(bw_file_headers, "timestamp=%lu",
    290                           (unsigned long)file_time);
    291 
    292  if (routerstatuses)
    293    smartlist_sort(routerstatuses, compare_vote_routerstatus_entries);
    294 
    295  while (!feof(fp)) {
    296    measured_bw_line_t parsed_line;
    297    if (tor_getline(&line, &n, fp) >= 0) {
    298      crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
    299      if (measured_bw_line_parse(&parsed_line, line,
    300                                 line_is_after_headers) != -1) {
    301        /* This condition will be true when the first complete valid bw line
    302         * has been encountered, which means the end of the header lines. */
    303        line_is_after_headers = 1;
    304        /* Also cache the line for dirserv_get_bandwidth_for_router() */
    305        dirserv_cache_measured_bw(&parsed_line, file_time);
    306        if (measured_bw_line_apply(&parsed_line, routerstatuses) > 0)
    307          applied_lines++;
    308      /* if the terminator is found, it is the end of header lines, set the
    309       * flag but do not store anything */
    310      } else if (strcmp(line, BW_FILE_HEADERS_TERMINATOR) == 0) {
    311        line_is_after_headers = 1;
    312      /* if the line was not a correct relay line nor the terminator and
    313       * the end of the header lines has not been detected yet
    314       * and it is key_value and bw_file_headers did not reach the maximum
    315       * number of headers,
    316       * then assume this line is a header and add it to bw_file_headers */
    317      } else if (bw_file_headers &&
    318              (line_is_after_headers == 0) &&
    319              string_is_key_value(LOG_DEBUG, line) &&
    320              !strchr(line, ' ') &&
    321              (smartlist_len(bw_file_headers)
    322               < MAX_BW_FILE_HEADER_COUNT_IN_VOTE)) {
    323        line[strlen(line)-1] = '\0';
    324        smartlist_add_strdup(bw_file_headers, line);
    325      };
    326    }
    327  }
    328 
    329  /* Now would be a nice time to clean the cache, too */
    330  dirserv_expire_measured_bw_cache(now);
    331 
    332  log_info(LD_DIRSERV,
    333           "Bandwidth measurement file successfully read. "
    334           "Applied %d measurements.", applied_lines);
    335  rv = 0;
    336 
    337 continue_digest:
    338  /* Continue parsing lines to return the digest of the Bandwidth File. */
    339  while (!feof(fp)) {
    340    if (tor_getline(&line, &n, fp) >= 0) {
    341      crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
    342    }
    343  }
    344 
    345 err:
    346  if (line) {
    347    // we need to raw_free this buffer because we got it from tor_getdelim()
    348    raw_free(line);
    349  }
    350  if (fp)
    351    fclose(fp);
    352  if (digest_out)
    353    crypto_digest_get_digest(digest, (char *) digest_out, DIGEST256_LEN);
    354  crypto_digest_free(digest);
    355  return rv;
    356 }
    357 
    358 /**
    359 * Helper function to parse out a line in the measured bandwidth file
    360 * into a measured_bw_line_t output structure.
    361 *
    362 * If <b>line_is_after_headers</b> is true, then if we encounter an incomplete
    363 * bw line, return -1 and warn, since we are after the headers and we should
    364 * only parse bw lines. Return 0 otherwise.
    365 *
    366 * If <b>line_is_after_headers</b> is false then it means that we are not past
    367 * the header block yet. If we encounter an incomplete bw line, return -1 but
    368 * don't warn since there could be additional header lines coming. If we
    369 * encounter a proper bw line, return 0 (and we got past the headers).
    370 *
    371 * If the line contains "vote=0", stop parsing it, and return -1, so that the
    372 * line is ignored during voting.
    373 */
    374 STATIC int
    375 measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line,
    376                       int line_is_after_headers)
    377 {
    378  char *line = tor_strdup(orig_line);
    379  char *cp = line;
    380  int got_bw = 0;
    381  int got_node_id = 0;
    382  char *strtok_state; /* lame sauce d'jour */
    383 
    384  if (strlen(line) == 0) {
    385    log_warn(LD_DIRSERV, "Empty line in bandwidth file");
    386    tor_free(line);
    387    return -1;
    388  }
    389 
    390  /* Remove end of line character, so that is not part of the token */
    391  if (line[strlen(line) - 1] == '\n') {
    392    line[strlen(line) - 1] = '\0';
    393  }
    394 
    395  cp = tor_strtok_r(cp, " \t", &strtok_state);
    396 
    397  if (!cp) {
    398    log_warn(LD_DIRSERV, "Invalid line in bandwidth file: %s",
    399             escaped(orig_line));
    400    tor_free(line);
    401    return -1;
    402  }
    403 
    404  if (orig_line[strlen(orig_line)-1] != '\n') {
    405    log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
    406             escaped(orig_line));
    407    tor_free(line);
    408    return -1;
    409  }
    410 
    411  do {
    412    // If the line contains vote=0, ignore it.
    413    if (strcmpstart(cp, "vote=0") == 0) {
    414      log_debug(LD_DIRSERV, "Ignoring bandwidth file line that contains "
    415                "vote=0: %s",escaped(orig_line));
    416      tor_free(line);
    417      return -1;
    418    } else if (strcmpstart(cp, "bw=") == 0) {
    419      int parse_ok = 0;
    420      char *endptr;
    421      if (got_bw) {
    422        log_warn(LD_DIRSERV, "Double bw= in bandwidth file line: %s",
    423                 escaped(orig_line));
    424        tor_free(line);
    425        return -1;
    426      }
    427      cp+=strlen("bw=");
    428 
    429      out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr);
    430      if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) {
    431        log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s",
    432                 escaped(orig_line));
    433        tor_free(line);
    434        return -1;
    435      }
    436      got_bw=1;
    437    // Allow node_id to start with or without the dollar sign.
    438    } else if (strcmpstart(cp, "node_id=") == 0) {
    439      if (got_node_id) {
    440        log_warn(LD_DIRSERV, "Double node_id= in bandwidth file line: %s",
    441                 escaped(orig_line));
    442        tor_free(line);
    443        return -1;
    444      }
    445      if (strcmpstart(cp, "node_id=$") == 0) {
    446        cp+=strlen("node_id=$");
    447      } else if (strcmpstart(cp, "node_id=") == 0) {
    448        cp+=strlen("node_id=");
    449      }
    450      if (strlen(cp) != HEX_DIGEST_LEN ||
    451          base16_decode(out->node_id, DIGEST_LEN,
    452                        cp, HEX_DIGEST_LEN) != DIGEST_LEN) {
    453        log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s",
    454                 escaped(orig_line));
    455        tor_free(line);
    456        return -1;
    457      }
    458      strlcpy(out->node_hex, cp, sizeof(out->node_hex));
    459      got_node_id=1;
    460    }
    461  } while ((cp = tor_strtok_r(NULL, " \t", &strtok_state)));
    462 
    463  if (got_bw && got_node_id) {
    464    tor_free(line);
    465    return 0;
    466  } else if (line_is_after_headers == 0) {
    467    /* There could be additional header lines, therefore do not give warnings
    468     * but returns -1 since it's not a complete bw line. */
    469    log_debug(LD_DIRSERV, "Missing bw or node_id in bandwidth file line: %s",
    470             escaped(orig_line));
    471    tor_free(line);
    472    return -1;
    473  } else {
    474    log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
    475             escaped(orig_line));
    476    tor_free(line);
    477    return -1;
    478  }
    479 }
    480 
    481 /**
    482 * Helper function to apply a parsed measurement line to a list
    483 * of bandwidth statuses. Returns true if a line is found,
    484 * false otherwise.
    485 */
    486 STATIC int
    487 measured_bw_line_apply(measured_bw_line_t *parsed_line,
    488                       smartlist_t *routerstatuses)
    489 {
    490  vote_routerstatus_t *rs = NULL;
    491  if (!routerstatuses)
    492    return 0;
    493 
    494  rs = smartlist_bsearch(routerstatuses, parsed_line->node_id,
    495                         compare_digest_to_vote_routerstatus_entry);
    496 
    497  if (rs) {
    498    rs->has_measured_bw = 1;
    499    rs->measured_bw_kb = (uint32_t)parsed_line->bw_kb;
    500  } else {
    501    log_info(LD_DIRSERV, "Node ID %s not found in routerstatus list",
    502             parsed_line->node_hex);
    503  }
    504 
    505  return rs != NULL;
    506 }