LCOV - code coverage report
Current view: top level - feature/dirauth - bwauth.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 182 204 89.2 %
Date: 2021-11-24 03:28:48 Functions: 10 12 83.3 %

          Line data    Source code
       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           0 : dirserv_count_measured_bws(const smartlist_t *routers)
      40             : {
      41             :   /* Initialize this first */
      42           0 :   routers_with_measured_bw = 0;
      43             : 
      44             :   /* Iterate over the routerlist and count measured bandwidths */
      45           0 :   SMARTLIST_FOREACH_BEGIN(routers, const routerinfo_t *, ri) {
      46             :     /* Check if we know a measured bandwidth for this one */
      47           0 :     if (dirserv_has_measured_bw(ri->cache_info.identity_digest)) {
      48           0 :       ++routers_with_measured_bw;
      49             :     }
      50           0 :   } SMARTLIST_FOREACH_END(ri);
      51           0 : }
      52             : 
      53             : /** Return the last-computed result from dirserv_count_mesured_bws(). */
      54             : int
      55          27 : dirserv_get_last_n_measured_bws(void)
      56             : {
      57          27 :   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          19 : dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line,
      74             :                           time_t as_of)
      75             : {
      76          19 :   mbw_cache_entry_t *e = NULL;
      77             : 
      78          19 :   tor_assert(parsed_line);
      79             : 
      80             :   /* Allocate a cache if we need */
      81          19 :   if (!mbw_cache) mbw_cache = digestmap_new();
      82             : 
      83             :   /* Check if we have an existing entry */
      84          19 :   e = digestmap_get(mbw_cache, parsed_line->node_id);
      85             :   /* If we do, we can re-use it */
      86          19 :   if (e) {
      87             :     /* Check that we really are newer, and update */
      88          12 :     if (as_of > e->as_of) {
      89           0 :       e->mbw_kb = parsed_line->bw_kb;
      90           0 :       e->as_of = as_of;
      91             :     }
      92             :   } else {
      93             :     /* We'll have to insert a new entry */
      94           7 :     e = tor_malloc(sizeof(*e));
      95           7 :     e->mbw_kb = parsed_line->bw_kb;
      96           7 :     e->as_of = as_of;
      97           7 :     digestmap_set(mbw_cache, parsed_line->node_id, e);
      98             :   }
      99          19 : }
     100             : 
     101             : /** Clear and free the measured bandwidth cache */
     102             : void
     103         236 : dirserv_clear_measured_bw_cache(void)
     104             : {
     105         236 :   if (mbw_cache) {
     106             :     /* Free the map and all entries */
     107           0 :     digestmap_free(mbw_cache, tor_free_);
     108           0 :     mbw_cache = NULL;
     109             :   }
     110         236 : }
     111             : 
     112             : /** Scan the measured bandwidth cache and remove expired entries */
     113             : STATIC void
     114          22 : dirserv_expire_measured_bw_cache(time_t now)
     115             : {
     116             : 
     117          22 :   if (mbw_cache) {
     118             :     /* Iterate through the cache and check each entry */
     119          85 :     DIGESTMAP_FOREACH_MODIFY(mbw_cache, k, mbw_cache_entry_t *, e) {
     120          34 :       if (now > e->as_of + MAX_MEASUREMENT_AGE) {
     121           4 :         tor_free(e);
     122           4 :         MAP_DEL_CURRENT(k);
     123             :       }
     124          21 :     } DIGESTMAP_FOREACH_END;
     125             : 
     126             :     /* Check if we cleared the whole thing and free if so */
     127          21 :     if (digestmap_size(mbw_cache) == 0) {
     128           2 :       digestmap_free(mbw_cache, tor_free_);
     129           2 :       mbw_cache = 0;
     130             :     }
     131             :   }
     132          22 : }
     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          17 : dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out,
     139             :                                    time_t *as_of_out)
     140             : {
     141          17 :   mbw_cache_entry_t *v = NULL;
     142          17 :   int rv = 0;
     143             : 
     144          17 :   if (mbw_cache && node_id) {
     145          16 :     v = digestmap_get(mbw_cache, node_id);
     146          16 :     if (v) {
     147             :       /* Found something */
     148           4 :       rv = 1;
     149           4 :       if (bw_kb_out) *bw_kb_out = v->mbw_kb;
     150           4 :       if (as_of_out) *as_of_out = v->as_of;
     151             :     }
     152             :   }
     153             : 
     154          17 :   return rv;
     155             : }
     156             : 
     157             : /** Predicate wrapper for dirserv_query_measured_bw_cache() */
     158             : int
     159           0 : dirserv_has_measured_bw(const char *node_id)
     160             : {
     161           0 :   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          10 : dirserv_get_measured_bw_cache_size(void)
     167             : {
     168          10 :   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          12 : dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri)
     178             : {
     179          12 :   int threshold;
     180          12 :   uint32_t bw_kb = 0;
     181          12 :   long mbw_kb;
     182             : 
     183          12 :   tor_assert(ri);
     184             :   /* Check if we have a measured bandwidth, and check the threshold if not */
     185          12 :   if (!(dirserv_query_measured_bw_cache_kb(ri->cache_info.identity_digest,
     186             :                                        &mbw_kb, NULL))) {
     187          12 :     threshold = dirauth_get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised;
     188          12 :     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          12 :       bw_kb = router_get_advertised_bandwidth_capped(ri) / 1000;
     194             :     }
     195             :   } else {
     196             :     /* We have the measured bandwidth in mbw */
     197           0 :     bw_kb = (uint32_t)mbw_kb;
     198             :   }
     199             : 
     200          12 :   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          22 : 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          22 :   FILE *fp = tor_fopen_cloexec(from_file, "r");
     238          22 :   int applied_lines = 0;
     239          22 :   time_t file_time, now;
     240          22 :   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          22 :   int line_is_after_headers = 0;
     246          22 :   int rv = -1;
     247          22 :   char *line = NULL;
     248          22 :   size_t n = 0;
     249          22 :   crypto_digest_t *digest = crypto_digest256_new(DIGEST_SHA256);
     250             : 
     251          22 :   if (fp == NULL) {
     252           2 :     log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s",
     253             :              from_file);
     254           2 :     goto err;
     255             :   }
     256             : 
     257          20 :   if (tor_getline(&line,&n,fp) <= 0) {
     258           1 :     log_warn(LD_DIRSERV, "Empty bandwidth file");
     259           1 :     goto err;
     260             :   }
     261             :   /* If the line could be gotten, add it to the digest */
     262          19 :   crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
     263             : 
     264          19 :   if (!strlen(line) || line[strlen(line)-1] != '\n') {
     265           1 :     log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s",
     266             :              escaped(line));
     267             :     /* Continue adding lines to the digest. */
     268           1 :     goto continue_digest;
     269             :   }
     270             : 
     271          18 :   line[strlen(line)-1] = '\0';
     272          18 :   file_time = (time_t)tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL);
     273          18 :   if (!ok) {
     274           0 :     log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s",
     275             :              escaped(line));
     276           0 :     goto continue_digest;
     277             :   }
     278             : 
     279          18 :   now = approx_time();
     280          18 :   if ((now - file_time) > MAX_MEASUREMENT_AGE) {
     281           1 :     log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u",
     282             :              (unsigned)(time(NULL) - file_time));
     283           1 :     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          17 :   if (bw_file_headers)
     289          13 :     smartlist_add_asprintf(bw_file_headers, "timestamp=%lu",
     290             :                            (unsigned long)file_time);
     291             : 
     292          17 :   if (routerstatuses)
     293           0 :     smartlist_sort(routerstatuses, compare_vote_routerstatus_entries);
     294             : 
     295         242 :   while (!feof(fp)) {
     296         225 :     measured_bw_line_t parsed_line;
     297         225 :     if (tor_getline(&line, &n, fp) >= 0) {
     298         208 :       crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
     299         208 :       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          15 :         line_is_after_headers = 1;
     304             :         /* Also cache the line for dirserv_get_bandwidth_for_router() */
     305          15 :         dirserv_cache_measured_bw(&parsed_line, file_time);
     306          15 :         if (measured_bw_line_apply(&parsed_line, routerstatuses) > 0)
     307           0 :           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         193 :       } 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         186 :       } else if (bw_file_headers &&
     318         351 :               (line_is_after_headers == 0) &&
     319         165 :               string_is_key_value(LOG_DEBUG, line) &&
     320         164 :               !strchr(line, ' ') &&
     321         164 :               (smartlist_len(bw_file_headers)
     322             :                < MAX_BW_FILE_HEADER_COUNT_IN_VOTE)) {
     323         148 :         line[strlen(line)-1] = '\0';
     324         148 :         smartlist_add_strdup(bw_file_headers, line);
     325         225 :       };
     326             :     }
     327             :   }
     328             : 
     329             :   /* Now would be a nice time to clean the cache, too */
     330          17 :   dirserv_expire_measured_bw_cache(now);
     331             : 
     332          17 :   log_info(LD_DIRSERV,
     333             :            "Bandwidth measurement file successfully read. "
     334             :            "Applied %d measurements.", applied_lines);
     335          17 :   rv = 0;
     336             : 
     337          19 :  continue_digest:
     338             :   /* Continue parsing lines to return the digest of the Bandwidth File. */
     339          21 :   while (!feof(fp)) {
     340           2 :     if (tor_getline(&line, &n, fp) >= 0) {
     341           1 :       crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
     342             :     }
     343             :   }
     344             : 
     345          19 :  err:
     346          22 :   if (line) {
     347             :     // we need to raw_free this buffer because we got it from tor_getdelim()
     348          20 :     raw_free(line);
     349             :   }
     350          22 :   if (fp)
     351          20 :     fclose(fp);
     352          22 :   if (digest_out)
     353           3 :     crypto_digest_get_digest(digest, (char *) digest_out, DIGEST256_LEN);
     354          22 :   crypto_digest_free(digest);
     355          22 :   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         258 : measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line,
     376             :                        int line_is_after_headers)
     377             : {
     378         258 :   char *line = tor_strdup(orig_line);
     379         258 :   char *cp = line;
     380         258 :   int got_bw = 0;
     381         258 :   int got_node_id = 0;
     382         258 :   char *strtok_state; /* lame sauce d'jour */
     383             : 
     384         258 :   if (strlen(line) == 0) {
     385           1 :     log_warn(LD_DIRSERV, "Empty line in bandwidth file");
     386           1 :     tor_free(line);
     387           1 :     return -1;
     388             :   }
     389             : 
     390             :   /* Remove end of line character, so that is not part of the token */
     391         257 :   if (line[strlen(line) - 1] == '\n') {
     392         250 :     line[strlen(line) - 1] = '\0';
     393             :   }
     394             : 
     395         257 :   cp = tor_strtok_r(cp, " \t", &strtok_state);
     396             : 
     397         257 :   if (!cp) {
     398           1 :     log_warn(LD_DIRSERV, "Invalid line in bandwidth file: %s",
     399             :              escaped(orig_line));
     400           1 :     tor_free(line);
     401           1 :     return -1;
     402             :   }
     403             : 
     404         256 :   if (orig_line[strlen(orig_line)-1] != '\n') {
     405           7 :     log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
     406             :              escaped(orig_line));
     407           7 :     tor_free(line);
     408           7 :     return -1;
     409             :   }
     410             : 
     411         395 :   do {
     412             :     // If the line contains vote=0, ignore it.
     413         395 :     if (strcmpstart(cp, "vote=0") == 0) {
     414           5 :       log_debug(LD_DIRSERV, "Ignoring bandwidth file line that contains "
     415             :                 "vote=0: %s",escaped(orig_line));
     416           5 :       tor_free(line);
     417           5 :       return -1;
     418         390 :     } else if (strcmpstart(cp, "bw=") == 0) {
     419          51 :       int parse_ok = 0;
     420          51 :       char *endptr;
     421          51 :       if (got_bw) {
     422           2 :         log_warn(LD_DIRSERV, "Double bw= in bandwidth file line: %s",
     423             :                  escaped(orig_line));
     424           2 :         tor_free(line);
     425          10 :         return -1;
     426             :       }
     427          49 :       cp+=strlen("bw=");
     428             : 
     429          49 :       out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr);
     430          49 :       if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) {
     431           8 :         log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s",
     432             :                  escaped(orig_line));
     433           8 :         tor_free(line);
     434           8 :         return -1;
     435             :       }
     436          41 :       got_bw=1;
     437         339 :     } else if (strcmpstart(cp, "node_id=$") == 0) {
     438          51 :       if (got_node_id) {
     439           0 :         log_warn(LD_DIRSERV, "Double node_id= in bandwidth file line: %s",
     440             :                  escaped(orig_line));
     441           0 :         tor_free(line);
     442           0 :         return -1;
     443             :       }
     444          51 :       cp+=strlen("node_id=$");
     445             : 
     446          96 :       if (strlen(cp) != HEX_DIGEST_LEN ||
     447          45 :           base16_decode(out->node_id, DIGEST_LEN,
     448             :                         cp, HEX_DIGEST_LEN) != DIGEST_LEN) {
     449           6 :         log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s",
     450             :                  escaped(orig_line));
     451           6 :         tor_free(line);
     452           6 :         return -1;
     453             :       }
     454          45 :       strlcpy(out->node_hex, cp, sizeof(out->node_hex));
     455          45 :       got_node_id=1;
     456             :     }
     457         374 :   } while ((cp = tor_strtok_r(NULL, " \t", &strtok_state)));
     458             : 
     459         228 :   if (got_bw && got_node_id) {
     460          30 :     tor_free(line);
     461          30 :     return 0;
     462         198 :   } else if (line_is_after_headers == 0) {
     463             :     /* There could be additional header lines, therefore do not give warnings
     464             :      * but returns -1 since it's not a complete bw line. */
     465         171 :     log_debug(LD_DIRSERV, "Missing bw or node_id in bandwidth file line: %s",
     466             :              escaped(orig_line));
     467         171 :     tor_free(line);
     468         171 :     return -1;
     469             :   } else {
     470          27 :     log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
     471             :              escaped(orig_line));
     472          27 :     tor_free(line);
     473          27 :     return -1;
     474             :   }
     475             : }
     476             : 
     477             : /**
     478             :  * Helper function to apply a parsed measurement line to a list
     479             :  * of bandwidth statuses. Returns true if a line is found,
     480             :  * false otherwise.
     481             :  */
     482             : STATIC int
     483          16 : measured_bw_line_apply(measured_bw_line_t *parsed_line,
     484             :                        smartlist_t *routerstatuses)
     485             : {
     486          16 :   vote_routerstatus_t *rs = NULL;
     487          16 :   if (!routerstatuses)
     488             :     return 0;
     489             : 
     490           1 :   rs = smartlist_bsearch(routerstatuses, parsed_line->node_id,
     491             :                          compare_digest_to_vote_routerstatus_entry);
     492             : 
     493           1 :   if (rs) {
     494           1 :     rs->has_measured_bw = 1;
     495           1 :     rs->measured_bw_kb = (uint32_t)parsed_line->bw_kb;
     496             :   } else {
     497           0 :     log_info(LD_DIRSERV, "Node ID %s not found in routerstatus list",
     498             :              parsed_line->node_hex);
     499             :   }
     500             : 
     501           1 :   return rs != NULL;
     502             : }

Generated by: LCOV version 1.14