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 */
8 * \brief Code to read and apply bandwidth authority data.
11 #define BWAUTH_PRIVATE
12 #include "core/or/or.h"
13 #include "feature/dirauth/bwauth.h"
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"
21 #include "feature/dirauth/dirauth_options_st.h"
22 #include "feature/nodelist/routerinfo_st.h"
23 #include "feature/nodelist/vote_routerstatus_st.h"
25 #include "lib/crypt_ops/crypto_format.h"
26 #include "lib/encoding/keyval.h"
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;
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. */
39 dirserv_count_measured_bws(const smartlist_t
*routers
)
41 /* Initialize this first */
42 routers_with_measured_bw
= 0;
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
;
50 } SMARTLIST_FOREACH_END(ri
);
53 /** Return the last-computed result from dirserv_count_mesured_bws(). */
55 dirserv_get_last_n_measured_bws(void)
57 return routers_with_measured_bw
;
60 /** Measured bandwidth cache entry */
61 typedef struct mbw_cache_entry_t
{
66 /** Measured bandwidth cache - keys are identity_digests, values are
67 * mbw_cache_entry_t *. */
68 static digestmap_t
*mbw_cache
= NULL
;
70 /** Store a measured bandwidth cache entry when reading the measured
73 dirserv_cache_measured_bw(const measured_bw_line_t
*parsed_line
,
76 mbw_cache_entry_t
*e
= NULL
;
78 tor_assert(parsed_line
);
80 /* Allocate a cache if we need */
81 if (!mbw_cache
) mbw_cache
= digestmap_new();
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 */
87 /* Check that we really are newer, and update */
88 if (as_of
> e
->as_of
) {
89 e
->mbw_kb
= parsed_line
->bw_kb
;
93 /* We'll have to insert a new entry */
94 e
= tor_malloc(sizeof(*e
));
95 e
->mbw_kb
= parsed_line
->bw_kb
;
97 digestmap_set(mbw_cache
, parsed_line
->node_id
, e
);
101 /** Clear and free the measured bandwidth cache */
103 dirserv_clear_measured_bw_cache(void)
106 /* Free the map and all entries */
107 digestmap_free(mbw_cache
, tor_free_
);
112 /** Scan the measured bandwidth cache and remove expired entries */
114 dirserv_expire_measured_bw_cache(time_t now
)
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
) {
124 } DIGESTMAP_FOREACH_END
;
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_
);
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. */
138 dirserv_query_measured_bw_cache_kb(const char *node_id
, long *bw_kb_out
,
141 mbw_cache_entry_t
*v
= NULL
;
144 if (mbw_cache
&& node_id
) {
145 v
= digestmap_get(mbw_cache
, node_id
);
147 /* Found something */
149 if (bw_kb_out
) *bw_kb_out
= v
->mbw_kb
;
150 if (as_of_out
) *as_of_out
= v
->as_of
;
157 /** Predicate wrapper for dirserv_query_measured_bw_cache() */
159 dirserv_has_measured_bw(const char *node_id
)
161 return dirserv_query_measured_bw_cache_kb(node_id
, NULL
, NULL
);
164 /** Get the current size of the measured bandwidth cache */
166 dirserv_get_measured_bw_cache_size(void)
168 if (mbw_cache
) return digestmap_size(mbw_cache
);
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
177 dirserv_get_credible_bandwidth_kb(const routerinfo_t
*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
,
187 threshold
= dirauth_get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised
;
188 if (routers_with_measured_bw
> threshold
) {
189 /* Return zero for unmeasured bandwidth if we are above threshold */
192 /* Return an advertised bandwidth otherwise */
193 bw_kb
= router_get_advertised_bandwidth_capped(ri
) / 1000;
196 /* We have the measured bandwidth in mbw */
197 bw_kb
= (uint32_t)mbw_kb
;
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>.
212 * Returns -1 on error, 0 otherwise.
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.
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
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.
232 dirserv_read_measured_bandwidths(const char *from_file
,
233 smartlist_t
*routerstatuses
,
234 smartlist_t
*bw_file_headers
,
237 FILE *fp
= tor_fopen_cloexec(from_file
, "r");
238 int applied_lines
= 0;
239 time_t file_time
, now
;
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
245 int line_is_after_headers
= 0;
249 crypto_digest_t
*digest
= crypto_digest256_new(DIGEST_SHA256
);
252 log_warn(LD_CONFIG
, "Can't open bandwidth file at configured location: %s",
257 if (tor_getline(&line
,&n
,fp
) <= 0) {
258 log_warn(LD_DIRSERV
, "Empty bandwidth file");
261 /* If the line could be gotten, add it to the digest */
262 crypto_digest_add_bytes(digest
, (const char *) line
, strlen(line
));
264 if (!strlen(line
) || line
[strlen(line
)-1] != '\n') {
265 log_warn(LD_DIRSERV
, "Long or truncated time in bandwidth file: %s",
267 /* Continue adding lines to the digest. */
268 goto continue_digest
;
271 line
[strlen(line
)-1] = '\0';
272 file_time
= (time_t)tor_parse_ulong(line
, 10, 0, ULONG_MAX
, &ok
, NULL
);
274 log_warn(LD_DIRSERV
, "Non-integer time in bandwidth file: %s",
276 goto continue_digest
;
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
;
286 /* If timestamp was correct and bw_file_headers is not NULL,
287 * add timestamp to bw_file_headers */
289 smartlist_add_asprintf(bw_file_headers
, "timestamp=%lu",
290 (unsigned long)file_time
);
293 smartlist_sort(routerstatuses
, compare_vote_routerstatus_entries
);
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)
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
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
);
329 /* Now would be a nice time to clean the cache, too */
330 dirserv_expire_measured_bw_cache(now
);
333 "Bandwidth measurement file successfully read. "
334 "Applied %d measurements.", applied_lines
);
338 /* Continue parsing lines to return the digest of the Bandwidth File. */
340 if (tor_getline(&line
, &n
, fp
) >= 0) {
341 crypto_digest_add_bytes(digest
, (const char *) line
, strlen(line
));
347 // we need to raw_free this buffer because we got it from tor_getdelim()
353 crypto_digest_get_digest(digest
, (char *) digest_out
, DIGEST256_LEN
);
354 crypto_digest_free(digest
);
359 * Helper function to parse out a line in the measured bandwidth file
360 * into a measured_bw_line_t output structure.
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.
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).
371 * If the line contains "vote=0", stop parsing it, and return -1, so that the
372 * line is ignored during voting.
375 measured_bw_line_parse(measured_bw_line_t
*out
, const char *orig_line
,
376 int line_is_after_headers
)
378 char *line
= tor_strdup(orig_line
);
382 char *strtok_state
; /* lame sauce d'jour */
384 if (strlen(line
) == 0) {
385 log_warn(LD_DIRSERV
, "Empty line in bandwidth file");
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';
395 cp
= tor_strtok_r(cp
, " \t", &strtok_state
);
398 log_warn(LD_DIRSERV
, "Invalid line in bandwidth file: %s",
404 if (orig_line
[strlen(orig_line
)-1] != '\n') {
405 log_warn(LD_DIRSERV
, "Incomplete line in bandwidth file: %s",
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
));
418 } else if (strcmpstart(cp
, "bw=") == 0) {
422 log_warn(LD_DIRSERV
, "Double bw= in bandwidth file line: %s",
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",
437 } else if (strcmpstart(cp
, "node_id=$") == 0) {
439 log_warn(LD_DIRSERV
, "Double node_id= in bandwidth file line: %s",
444 cp
+=strlen("node_id=$");
446 if (strlen(cp
) != HEX_DIGEST_LEN
||
447 base16_decode(out
->node_id
, DIGEST_LEN
,
448 cp
, HEX_DIGEST_LEN
) != DIGEST_LEN
) {
449 log_warn(LD_DIRSERV
, "Invalid node_id in bandwidth file line: %s",
454 strlcpy(out
->node_hex
, cp
, sizeof(out
->node_hex
));
457 } while ((cp
= tor_strtok_r(NULL
, " \t", &strtok_state
)));
459 if (got_bw
&& got_node_id
) {
462 } 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 log_debug(LD_DIRSERV
, "Missing bw or node_id in bandwidth file line: %s",
470 log_warn(LD_DIRSERV
, "Incomplete line in bandwidth file: %s",
478 * Helper function to apply a parsed measurement line to a list
479 * of bandwidth statuses. Returns true if a line is found,
483 measured_bw_line_apply(measured_bw_line_t
*parsed_line
,
484 smartlist_t
*routerstatuses
)
486 vote_routerstatus_t
*rs
= NULL
;
490 rs
= smartlist_bsearch(routerstatuses
, parsed_line
->node_id
,
491 compare_digest_to_vote_routerstatus_entry
);
494 rs
->has_measured_bw
= 1;
495 rs
->measured_bw_kb
= (uint32_t)parsed_line
->bw_kb
;
497 log_info(LD_DIRSERV
, "Node ID %s not found in routerstatus list",
498 parsed_line
->node_hex
);