commit 988d4903a3fc23153896e0daf7738f87ade9bc4b Merge: a18230115 594140574 Author: Nick Mathewson nickm@torproject.org Date: Wed Oct 31 09:04:12 2018 -0400
Merge branch 'networkstatus_mmap' into networkstatus_mmap_merge
changes/feature27244 | 5 ++ src/feature/control/control.c | 6 ++- src/feature/dirauth/dirvote.c | 19 ++++--- src/feature/dircache/consdiffmgr.c | 83 +++++++++++++++++++++--------- src/feature/dircache/consdiffmgr.h | 11 +++- src/feature/dircache/dirserv.c | 5 +- src/feature/dircache/dirserv.h | 1 + src/feature/dirclient/dirclient.c | 22 +++++--- src/feature/dircommon/consdiff.c | 42 ++++++++------- src/feature/dircommon/consdiff.h | 15 +++--- src/feature/dirparse/authcert_parse.c | 16 +++--- src/feature/dirparse/authcert_parse.h | 1 + src/feature/dirparse/ns_parse.c | 55 ++++++++++++-------- src/feature/dirparse/ns_parse.h | 10 ++-- src/feature/nodelist/authcert.c | 3 +- src/feature/nodelist/networkstatus.c | 96 +++++++++++++++++++---------------- src/feature/nodelist/networkstatus.h | 4 +- src/feature/relay/router.c | 2 +- src/test/bench.c | 6 ++- src/test/fuzz/fuzz_consensus.c | 6 +-- src/test/fuzz/fuzz_diff.c | 32 +++++++----- src/test/fuzz/fuzz_diff_apply.c | 13 +++-- src/test/fuzz/fuzz_vrs.c | 16 +++--- src/test/test_consdiff.c | 94 +++++++++++++++++++++++----------- src/test/test_consdiffmgr.c | 41 +++++++++++---- src/test/test_dir.c | 44 ++++++++++++---- src/test/test_dir_common.c | 17 +++++-- src/test/test_dir_handle_get.c | 18 +++++-- src/test/test_routerlist.c | 19 +++++-- src/test/test_shared_random.c | 12 +++-- 30 files changed, 467 insertions(+), 247 deletions(-)
diff --cc src/feature/control/control.c index f0db97dc8,9e7d21308..3fa47747e --- a/src/feature/control/control.c +++ b/src/feature/control/control.c @@@ -2352,7 -2341,9 +2352,11 @@@ getinfo_helper_dir(control_connection_ *answer = tor_strdup(consensus->dir); } if (!*answer) { /* try loading it from disk */ - *answer = networkstatus_read_cached_consensus("ns"); - char *filename = get_cachedir_fname("cached-consensus"); - *answer = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); - tor_free(filename); ++ tor_mmap_t *mapped = networkstatus_map_cached_consensus("ns"); ++ if (mapped) { ++ *answer = tor_memdup_nulterm(mapped->data, mapped->size); ++ tor_munmap_file(mapped); ++ } if (!*answer) { /* generate an error */ *errmsg = "Could not open cached consensus. " "Make sure FetchUselessDescriptors is set to 1."; diff --cc src/feature/dircache/dirserv.c index 57178cd50,433d3f4ce..4366000e2 --- a/src/feature/dircache/dirserv.c +++ b/src/feature/dircache/dirserv.c @@@ -95,173 -1133,1591 +95,176 @@@ directory_fetches_from_authorities(cons me = router_get_my_routerinfo(); if (!me || (!me->supports_tunnelled_dir_requests && !refuseunknown)) return 0; /* if we don't service directory requests, return 0 too */ - return 1; -} - -/** Return 1 if we should fetch new networkstatuses, descriptors, etc - * on the "mirror" schedule rather than the "client" schedule. - */ -int -directory_fetches_dir_info_early(const or_options_t *options) -{ - return directory_fetches_from_authorities(options); -} - -/** Return 1 if we should fetch new networkstatuses, descriptors, etc - * on a very passive schedule -- waiting long enough for ordinary clients - * to probably have the info we want. These would include bridge users, - * and maybe others in the future e.g. if a Tor client uses another Tor - * client as a directory guard. - */ -int -directory_fetches_dir_info_later(const or_options_t *options) -{ - return options->UseBridges != 0; -} - -/** Return true iff we want to serve certificates for authorities - * that we don't acknowledge as authorities ourself. - * Use we_want_to_fetch_unknown_auth_certs to check if we want to fetch - * and keep these certificates. - */ -int -directory_caches_unknown_auth_certs(const or_options_t *options) -{ - return dir_server_mode(options) || options->BridgeRelay; -} - -/** Return 1 if we want to fetch and serve descriptors, networkstatuses, etc - * Else return 0. - * Check options->DirPort_set and directory_permits_begindir_requests() - * to see if we are willing to serve these directory documents to others via - * the DirPort and begindir-over-ORPort, respectively. - * - * To check if we should fetch documents, use we_want_to_fetch_flavor and - * we_want_to_fetch_unknown_auth_certs instead of this function. - */ -int -directory_caches_dir_info(const or_options_t *options) -{ - if (options->BridgeRelay || dir_server_mode(options)) - return 1; - if (!server_mode(options) || !advertised_server_mode()) - return 0; - /* We need an up-to-date view of network info if we're going to try to - * block exit attempts from unknown relays. */ - return ! router_my_exit_policy_is_reject_star() && - should_refuse_unknown_exits(options); -} - -/** Return 1 if we want to allow remote clients to ask us directory - * requests via the "begin_dir" interface, which doesn't require - * having any separate port open. */ -int -directory_permits_begindir_requests(const or_options_t *options) -{ - return options->BridgeRelay != 0 || dir_server_mode(options); -} - -/** Return 1 if we have no need to fetch new descriptors. This generally - * happens when we're not a dir cache and we haven't built any circuits - * lately. - */ -int -directory_too_idle_to_fetch_descriptors(const or_options_t *options, - time_t now) -{ - return !directory_caches_dir_info(options) && - !options->FetchUselessDescriptors && - rep_hist_circbuilding_dormant(now); -} - -/********************************************************************/ - -/** Map from flavor name to the cached_dir_t for the v3 consensuses that we're - * currently serving. */ -static strmap_t *cached_consensuses = NULL; - -/** Decrement the reference count on <b>d</b>, and free it if it no longer has - * any references. */ -void -cached_dir_decref(cached_dir_t *d) -{ - if (!d || --d->refcnt > 0) - return; - clear_cached_dir(d); - tor_free(d); -} - -/** Allocate and return a new cached_dir_t containing the string <b>s</b>, - * published at <b>published</b>. */ -cached_dir_t * -new_cached_dir(char *s, time_t published) -{ - cached_dir_t *d = tor_malloc_zero(sizeof(cached_dir_t)); - d->refcnt = 1; - d->dir = s; - d->dir_len = strlen(s); - d->published = published; - if (tor_compress(&(d->dir_compressed), &(d->dir_compressed_len), - d->dir, d->dir_len, ZLIB_METHOD)) { - log_warn(LD_BUG, "Error compressing directory"); - } - return d; -} - -/** Remove all storage held in <b>d</b>, but do not free <b>d</b> itself. */ -static void -clear_cached_dir(cached_dir_t *d) -{ - tor_free(d->dir); - tor_free(d->dir_compressed); - memset(d, 0, sizeof(cached_dir_t)); -} - -/** Free all storage held by the cached_dir_t in <b>d</b>. */ -static void -free_cached_dir_(void *_d) -{ - cached_dir_t *d; - if (!_d) - return; - - d = (cached_dir_t *)_d; - cached_dir_decref(d); -} - -/** Replace the v3 consensus networkstatus of type <b>flavor_name</b> that - * we're serving with <b>networkstatus</b>, published at <b>published</b>. No - * validation is performed. */ -void -dirserv_set_cached_consensus_networkstatus(const char *networkstatus, - size_t networkstatus_len, - const char *flavor_name, - const common_digests_t *digests, - const uint8_t *sha3_as_signed, - time_t published) -{ - cached_dir_t *new_networkstatus; - cached_dir_t *old_networkstatus; - if (!cached_consensuses) - cached_consensuses = strmap_new(); - - new_networkstatus = - new_cached_dir(tor_memdup_nulterm(networkstatus, networkstatus_len), - published); - memcpy(&new_networkstatus->digests, digests, sizeof(common_digests_t)); - memcpy(&new_networkstatus->digest_sha3_as_signed, sha3_as_signed, - DIGEST256_LEN); - old_networkstatus = strmap_set(cached_consensuses, flavor_name, - new_networkstatus); - if (old_networkstatus) - cached_dir_decref(old_networkstatus); -} - -/** Return the latest downloaded consensus networkstatus in encoded, signed, - * optionally compressed format, suitable for sending to clients. */ -cached_dir_t * -dirserv_get_consensus(const char *flavor_name) -{ - if (!cached_consensuses) - return NULL; - return strmap_get(cached_consensuses, flavor_name); -} - -/** If a router's uptime is at least this value, then it is always - * considered stable, regardless of the rest of the network. This - * way we resist attacks where an attacker doubles the size of the - * network using allegedly high-uptime nodes, displacing all the - * current guards. */ -#define UPTIME_TO_GUARANTEE_STABLE (3600*24*30) -/** If a router's MTBF is at least this value, then it is always stable. - * See above. (Corresponds to about 7 days for current decay rates.) */ -#define MTBF_TO_GUARANTEE_STABLE (60*60*24*5) -/** Similarly, every node with at least this much weighted time known can be - * considered familiar enough to be a guard. Corresponds to about 20 days for - * current decay rates. - */ -#define TIME_KNOWN_TO_GUARANTEE_FAMILIAR (8*24*60*60) -/** Similarly, every node with sufficient WFU is around enough to be a guard. - */ -#define WFU_TO_GUARANTEE_GUARD (0.98) - -/* Thresholds for server performance: set by - * dirserv_compute_performance_thresholds, and used by - * generate_v2_networkstatus */ - -/** Any router with an uptime of at least this value is stable. */ -static uint32_t stable_uptime = 0; /* start at a safe value */ -/** Any router with an mtbf of at least this value is stable. */ -static double stable_mtbf = 0.0; -/** If true, we have measured enough mtbf info to look at stable_mtbf rather - * than stable_uptime. */ -static int enough_mtbf_info = 0; -/** Any router with a weighted fractional uptime of at least this much might - * be good as a guard. */ -static double guard_wfu = 0.0; -/** Don't call a router a guard unless we've known about it for at least this - * many seconds. */ -static long guard_tk = 0; -/** Any router with a bandwidth at least this high is "Fast" */ -static uint32_t fast_bandwidth_kb = 0; -/** If exits can be guards, then all guards must have a bandwidth this - * high. */ -static uint32_t guard_bandwidth_including_exits_kb = 0; -/** If exits can't be guards, then all guards must have a bandwidth this - * high. */ -static uint32_t guard_bandwidth_excluding_exits_kb = 0; - -/** Helper: estimate the uptime of a router given its stated uptime and the - * amount of time since it last stated its stated uptime. */ -static inline long -real_uptime(const routerinfo_t *router, time_t now) -{ - if (now < router->cache_info.published_on) - return router->uptime; - else - return router->uptime + (now - router->cache_info.published_on); -} - -/** Return 1 if <b>router</b> is not suitable for these parameters, else 0. - * If <b>need_uptime</b> is non-zero, we require a minimum uptime. - * If <b>need_capacity</b> is non-zero, we require a minimum advertised - * bandwidth. - */ -static int -dirserv_thinks_router_is_unreliable(time_t now, - routerinfo_t *router, - int need_uptime, int need_capacity) -{ - if (need_uptime) { - if (!enough_mtbf_info) { - /* XXXX We should change the rule from - * "use uptime if we don't have mtbf data" to "don't advertise Stable on - * v3 if we don't have enough mtbf data." Or maybe not, since if we ever - * hit a point where we need to reset a lot of authorities at once, - * none of them would be in a position to declare Stable. - */ - long uptime = real_uptime(router, now); - if ((unsigned)uptime < stable_uptime && - (unsigned)uptime < UPTIME_TO_GUARANTEE_STABLE) - return 1; - } else { - double mtbf = - rep_hist_get_stability(router->cache_info.identity_digest, now); - if (mtbf < stable_mtbf && - mtbf < MTBF_TO_GUARANTEE_STABLE) - return 1; - } - } - if (need_capacity) { - uint32_t bw_kb = dirserv_get_credible_bandwidth_kb(router); - if (bw_kb < fast_bandwidth_kb) - return 1; - } - return 0; -} - -/** Return true iff <b>router</b> should be assigned the "HSDir" flag. - * - * Right now this means it advertises support for it, it has a high uptime, - * it's a directory cache, it has the Stable and Fast flags, and it's currently - * considered Running. - * - * This function needs to be called after router->is_running has - * been set. - */ -static int -dirserv_thinks_router_is_hs_dir(const routerinfo_t *router, - const node_t *node, time_t now) -{ - - long uptime; - - /* If we haven't been running for at least - * get_options()->MinUptimeHidServDirectoryV2 seconds, we can't - * have accurate data telling us a relay has been up for at least - * that long. We also want to allow a bit of slack: Reachability - * tests aren't instant. If we haven't been running long enough, - * trust the relay. */ - - if (get_uptime() > - get_options()->MinUptimeHidServDirectoryV2 * 1.1) - uptime = MIN(rep_hist_get_uptime(router->cache_info.identity_digest, now), - real_uptime(router, now)); - else - uptime = real_uptime(router, now); - - return (router->wants_to_be_hs_dir && - router->supports_tunnelled_dir_requests && - node->is_stable && node->is_fast && - uptime >= get_options()->MinUptimeHidServDirectoryV2 && - router_is_active(router, node, now)); -} - -/** Don't consider routers with less bandwidth than this when computing - * thresholds. */ -#define ABSOLUTE_MIN_BW_VALUE_TO_CONSIDER_KB 4 - -/** Helper for dirserv_compute_performance_thresholds(): Decide whether to - * include a router in our calculations, and return true iff we should; the - * require_mbw parameter is passed in by - * dirserv_compute_performance_thresholds() and controls whether we ever - * count routers with only advertised bandwidths */ -static int -router_counts_toward_thresholds(const node_t *node, time_t now, - const digestmap_t *omit_as_sybil, - int require_mbw) -{ - /* Have measured bw? */ - int have_mbw = - dirserv_has_measured_bw(node->identity); - uint64_t min_bw_kb = ABSOLUTE_MIN_BW_VALUE_TO_CONSIDER_KB; - const or_options_t *options = get_options(); - - if (options->TestingTorNetwork) { - min_bw_kb = (int64_t)options->TestingMinExitFlagThreshold / 1000; - } - - return node->ri && router_is_active(node->ri, node, now) && - !digestmap_get(omit_as_sybil, node->identity) && - (dirserv_get_credible_bandwidth_kb(node->ri) >= min_bw_kb) && - (have_mbw || !require_mbw); -} - -/** Look through the routerlist, and using the measured bandwidth cache count - * how many measured bandwidths we know. This is used to decide whether we - * ever trust advertised bandwidths for purposes of assigning flags. */ -void -dirserv_count_measured_bws(const smartlist_t *routers) -{ - /* Initialize this first */ - routers_with_measured_bw = 0; - - /* Iterate over the routerlist and count measured bandwidths */ - SMARTLIST_FOREACH_BEGIN(routers, const routerinfo_t *, ri) { - /* Check if we know a measured bandwidth for this one */ - if (dirserv_has_measured_bw(ri->cache_info.identity_digest)) { - ++routers_with_measured_bw; - } - } SMARTLIST_FOREACH_END(ri); -} - -/** Look through the routerlist, the Mean Time Between Failure history, and - * the Weighted Fractional Uptime history, and use them to set thresholds for - * the Stable, Fast, and Guard flags. Update the fields stable_uptime, - * stable_mtbf, enough_mtbf_info, guard_wfu, guard_tk, fast_bandwidth, - * guard_bandwidth_including_exits, and guard_bandwidth_excluding_exits. - * - * Also, set the is_exit flag of each router appropriately. */ -void -dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil) -{ - int n_active, n_active_nonexit, n_familiar; - uint32_t *uptimes, *bandwidths_kb, *bandwidths_excluding_exits_kb; - long *tks; - double *mtbfs, *wfus; - smartlist_t *nodelist; - time_t now = time(NULL); - const or_options_t *options = get_options(); - - /* Require mbw? */ - int require_mbw = - (routers_with_measured_bw > - options->MinMeasuredBWsForAuthToIgnoreAdvertised) ? 1 : 0; - - /* initialize these all here, in case there are no routers */ - stable_uptime = 0; - stable_mtbf = 0; - fast_bandwidth_kb = 0; - guard_bandwidth_including_exits_kb = 0; - guard_bandwidth_excluding_exits_kb = 0; - guard_tk = 0; - guard_wfu = 0; - - nodelist_assert_ok(); - nodelist = nodelist_get_list(); - - /* Initialize arrays that will hold values for each router. We'll - * sort them and use that to compute thresholds. */ - n_active = n_active_nonexit = 0; - /* Uptime for every active router. */ - uptimes = tor_calloc(smartlist_len(nodelist), sizeof(uint32_t)); - /* Bandwidth for every active router. */ - bandwidths_kb = tor_calloc(smartlist_len(nodelist), sizeof(uint32_t)); - /* Bandwidth for every active non-exit router. */ - bandwidths_excluding_exits_kb = - tor_calloc(smartlist_len(nodelist), sizeof(uint32_t)); - /* Weighted mean time between failure for each active router. */ - mtbfs = tor_calloc(smartlist_len(nodelist), sizeof(double)); - /* Time-known for each active router. */ - tks = tor_calloc(smartlist_len(nodelist), sizeof(long)); - /* Weighted fractional uptime for each active router. */ - wfus = tor_calloc(smartlist_len(nodelist), sizeof(double)); - - /* Now, fill in the arrays. */ - SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) { - if (options->BridgeAuthoritativeDir && - node->ri && - node->ri->purpose != ROUTER_PURPOSE_BRIDGE) - continue; - - routerinfo_t *ri = node->ri; - if (ri) { - node->is_exit = (!router_exit_policy_rejects_all(ri) && - exit_policy_is_general_exit(ri->exit_policy)); - } - - if (router_counts_toward_thresholds(node, now, omit_as_sybil, - require_mbw)) { - const char *id = node->identity; - uint32_t bw_kb; - - /* resolve spurious clang shallow analysis null pointer errors */ - tor_assert(ri); - - 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_kb[n_active] = bw_kb = dirserv_get_credible_bandwidth_kb(ri); - if (!node->is_exit || node->is_bad_exit) { - bandwidths_excluding_exits_kb[n_active_nonexit] = bw_kb; - ++n_active_nonexit; - } - ++n_active; - } - } SMARTLIST_FOREACH_END(node); - - /* Now, compute thresholds. */ - if (n_active) { - /* The median uptime is stable. */ - stable_uptime = median_uint32(uptimes, n_active); - /* The median mtbf is stable, if we have enough mtbf info */ - stable_mtbf = median_double(mtbfs, n_active); - /* The 12.5th percentile bandwidth is fast. */ - fast_bandwidth_kb = find_nth_uint32(bandwidths_kb, n_active, n_active/8); - /* (Now bandwidths is sorted.) */ - if (fast_bandwidth_kb < RELAY_REQUIRED_MIN_BANDWIDTH/(2 * 1000)) - fast_bandwidth_kb = bandwidths_kb[n_active/4]; - guard_bandwidth_including_exits_kb = - third_quartile_uint32(bandwidths_kb, n_active); - guard_tk = find_nth_long(tks, n_active, n_active/8); - } - - if (guard_tk > TIME_KNOWN_TO_GUARANTEE_FAMILIAR) - guard_tk = TIME_KNOWN_TO_GUARANTEE_FAMILIAR; - - { - /* We can vote on a parameter for the minimum and maximum. */ -#define ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG 4 - int32_t min_fast_kb, max_fast_kb, min_fast, max_fast; - min_fast = networkstatus_get_param(NULL, "FastFlagMinThreshold", - ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG, - ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG, - INT32_MAX); - if (options->TestingTorNetwork) { - min_fast = (int32_t)options->TestingMinFastFlagThreshold; - } - max_fast = networkstatus_get_param(NULL, "FastFlagMaxThreshold", - INT32_MAX, min_fast, INT32_MAX); - min_fast_kb = min_fast / 1000; - max_fast_kb = max_fast / 1000; - - if (fast_bandwidth_kb < (uint32_t)min_fast_kb) - fast_bandwidth_kb = min_fast_kb; - if (fast_bandwidth_kb > (uint32_t)max_fast_kb) - fast_bandwidth_kb = max_fast_kb; - } - /* Protect sufficiently fast nodes from being pushed out of the set - * of Fast nodes. */ - if (options->AuthDirFastGuarantee && - fast_bandwidth_kb > options->AuthDirFastGuarantee/1000) - fast_bandwidth_kb = (uint32_t)options->AuthDirFastGuarantee/1000; - - /* Now that we have a time-known that 7/8 routers are known longer than, - * fill wfus with the wfu of every such "familiar" router. */ - n_familiar = 0; - - SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) { - if (router_counts_toward_thresholds(node, now, - omit_as_sybil, require_mbw)) { - routerinfo_t *ri = node->ri; - const char *id = ri->cache_info.identity_digest; - long tk = rep_hist_get_weighted_time_known(id, now); - if (tk < guard_tk) - continue; - wfus[n_familiar++] = rep_hist_get_weighted_fractional_uptime(id, now); - } - } SMARTLIST_FOREACH_END(node); - if (n_familiar) - guard_wfu = median_double(wfus, n_familiar); - if (guard_wfu > WFU_TO_GUARANTEE_GUARD) - guard_wfu = WFU_TO_GUARANTEE_GUARD; - - enough_mtbf_info = rep_hist_have_measured_enough_stability(); - - if (n_active_nonexit) { - guard_bandwidth_excluding_exits_kb = - find_nth_uint32(bandwidths_excluding_exits_kb, - n_active_nonexit, n_active_nonexit*3/4); - } - - log_info(LD_DIRSERV, - "Cutoffs: For Stable, %lu sec uptime, %lu sec MTBF. " - "For Fast: %lu kilobytes/sec. " - "For Guard: WFU %.03f%%, time-known %lu sec, " - "and bandwidth %lu or %lu kilobytes/sec. " - "We%s have enough stability data.", - (unsigned long)stable_uptime, - (unsigned long)stable_mtbf, - (unsigned long)fast_bandwidth_kb, - guard_wfu*100, - (unsigned long)guard_tk, - (unsigned long)guard_bandwidth_including_exits_kb, - (unsigned long)guard_bandwidth_excluding_exits_kb, - enough_mtbf_info ? "" : " don't"); - - tor_free(uptimes); - tor_free(mtbfs); - tor_free(bandwidths_kb); - tor_free(bandwidths_excluding_exits_kb); - tor_free(tks); - tor_free(wfus); -} - -/* Use dirserv_compute_performance_thresholds() to compute the thresholds - * for the status flags, specifically for bridges. - * - * This is only called by a Bridge Authority from - * networkstatus_getinfo_by_purpose(). - */ -void -dirserv_compute_bridge_flag_thresholds(void) -{ - digestmap_t *omit_as_sybil = digestmap_new(); - dirserv_compute_performance_thresholds(omit_as_sybil); - digestmap_free(omit_as_sybil, NULL); -} - -/** Measured bandwidth cache entry */ -typedef struct mbw_cache_entry_s { - long mbw_kb; - 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(const 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_kb = parsed_line->bw_kb; - e->as_of = as_of; - } - } else { - /* We'll have to insert a new entry */ - e = tor_malloc(sizeof(*e)); - e->mbw_kb = parsed_line->bw_kb; - e->as_of = as_of; - digestmap_set(mbw_cache, parsed_line->node_id, e); - } -} - -/** Clear and free the measured bandwidth cache */ -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) -{ - - if (mbw_cache) { - /* Iterate through the cache and check each entry */ - DIGESTMAP_FOREACH_MODIFY(mbw_cache, k, mbw_cache_entry_t *, e) { - if (now > e->as_of + MAX_MEASUREMENT_AGE) { - tor_free(e); - MAP_DEL_CURRENT(k); - } - } DIGESTMAP_FOREACH_END; - - /* 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; - } - } -} - -/** Query the cache by identity digest, return value indicates whether - * we found it. The bw_out and as_of_out pointers receive the cached - * bandwidth value and the time it was cached if not NULL. */ -int -dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_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_kb_out) *bw_kb_out = v->mbw_kb; - if (as_of_out) *as_of_out = v->as_of; - } - } - - return rv; -} - -/** Predicate wrapper for dirserv_query_measured_bw_cache() */ -int -dirserv_has_measured_bw(const char *node_id) -{ - return dirserv_query_measured_bw_cache_kb(node_id, NULL, NULL); -} - -/** Get the current size of the measured bandwidth cache */ -int -dirserv_get_measured_bw_cache_size(void) -{ - if (mbw_cache) return digestmap_size(mbw_cache); - else return 0; -} - -/** Return the bandwidth we believe for assigning flags; prefer measured - * over advertised, and if we have above a threshold quantity of measured - * bandwidths, we don't want to ever give flags to unmeasured routers, so - * return 0. */ -static uint32_t -dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri) -{ - int threshold; - uint32_t bw_kb = 0; - long mbw_kb; - - tor_assert(ri); - /* Check if we have a measured bandwidth, and check the threshold if not */ - if (!(dirserv_query_measured_bw_cache_kb(ri->cache_info.identity_digest, - &mbw_kb, NULL))) { - threshold = get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised; - if (routers_with_measured_bw > threshold) { - /* Return zero for unmeasured bandwidth if we are above threshold */ - bw_kb = 0; - } else { - /* Return an advertised bandwidth otherwise */ - bw_kb = router_get_advertised_bandwidth_capped(ri) / 1000; - } - } else { - /* We have the measured bandwidth in mbw */ - bw_kb = (uint32_t)mbw_kb; - } - - return bw_kb; -} - -/** Give a statement of our current performance thresholds for inclusion - * in a vote document. */ -char * -dirserv_get_flag_thresholds_line(void) -{ - char *result=NULL; - const int measured_threshold = - get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised; - const int enough_measured_bw = routers_with_measured_bw > measured_threshold; - - tor_asprintf(&result, - "stable-uptime=%lu stable-mtbf=%lu " - "fast-speed=%lu " - "guard-wfu=%.03f%% guard-tk=%lu " - "guard-bw-inc-exits=%lu guard-bw-exc-exits=%lu " - "enough-mtbf=%d ignoring-advertised-bws=%d", - (unsigned long)stable_uptime, - (unsigned long)stable_mtbf, - (unsigned long)fast_bandwidth_kb*1000, - guard_wfu*100, - (unsigned long)guard_tk, - (unsigned long)guard_bandwidth_including_exits_kb*1000, - (unsigned long)guard_bandwidth_excluding_exits_kb*1000, - enough_mtbf_info ? 1 : 0, - enough_measured_bw ? 1 : 0); - - return result; -} - -/** Helper: write the router-status information in <b>rs</b> into a newly - * allocated character buffer. Use the same format as in network-status - * documents. If <b>version</b> is non-NULL, add a "v" line for the platform. - * - * consensus_method is the current consensus method when format is - * NS_V3_CONSENSUS or NS_V3_CONSENSUS_MICRODESC. It is ignored for other - * formats: pass ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD. - * - * Return 0 on success, -1 on failure. - * - * The format argument has one of the following values: - * NS_V2 - Output an entry suitable for a V2 NS opinion document - * NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry - * for consensus_method. - * NS_V3_CONSENSUS_MICRODESC - Output the first portion of a V3 microdesc - * consensus entry for consensus_method. - * NS_V3_VOTE - Output a complete V3 NS vote. If <b>vrs</b> is present, - * it contains additional information for the vote. - * NS_CONTROL_PORT - Output a NS document for the control port. - */ -char * -routerstatus_format_entry(const routerstatus_t *rs, const char *version, - const char *protocols, - routerstatus_format_type_t format, - int consensus_method, - const vote_routerstatus_t *vrs) -{ - char *summary; - char *result = NULL; - - char published[ISO_TIME_LEN+1]; - char identity64[BASE64_DIGEST_LEN+1]; - char digest64[BASE64_DIGEST_LEN+1]; - smartlist_t *chunks = smartlist_new(); - - format_iso_time(published, rs->published_on); - digest_to_base64(identity64, rs->identity_digest); - digest_to_base64(digest64, rs->descriptor_digest); - - smartlist_add_asprintf(chunks, - "r %s %s %s%s%s %s %d %d\n", - rs->nickname, - identity64, - (format==NS_V3_CONSENSUS_MICRODESC)?"":digest64, - (format==NS_V3_CONSENSUS_MICRODESC)?"":" ", - published, - fmt_addr32(rs->addr), - (int)rs->or_port, - (int)rs->dir_port); - - /* TODO: Maybe we want to pass in what we need to build the rest of - * this here, instead of in the caller. Then we could use the - * networkstatus_type_t values, with an additional control port value - * added -MP */ - - /* V3 microdesc consensuses only have "a" lines in later consensus methods - */ - if (format == NS_V3_CONSENSUS_MICRODESC && - consensus_method < MIN_METHOD_FOR_A_LINES_IN_MICRODESC_CONSENSUS) - goto done; - - /* Possible "a" line. At most one for now. */ - if (!tor_addr_is_null(&rs->ipv6_addr)) { - smartlist_add_asprintf(chunks, "a %s\n", - fmt_addrport(&rs->ipv6_addr, rs->ipv6_orport)); - } - - if (format == NS_V3_CONSENSUS || format == NS_V3_CONSENSUS_MICRODESC) - goto done; - - smartlist_add_asprintf(chunks, - "s%s%s%s%s%s%s%s%s%s%s\n", - /* These must stay in alphabetical order. */ - rs->is_authority?" Authority":"", - rs->is_bad_exit?" BadExit":"", - rs->is_exit?" Exit":"", - rs->is_fast?" Fast":"", - rs->is_possible_guard?" Guard":"", - rs->is_hs_dir?" HSDir":"", - rs->is_flagged_running?" Running":"", - rs->is_stable?" Stable":"", - rs->is_v2_dir?" V2Dir":"", - rs->is_valid?" Valid":""); - - /* length of "opt v \n" */ -#define V_LINE_OVERHEAD 7 - if (version && strlen(version) < MAX_V_LINE_LEN - V_LINE_OVERHEAD) { - smartlist_add_asprintf(chunks, "v %s\n", version); - } - if (protocols) { - smartlist_add_asprintf(chunks, "pr %s\n", protocols); - } - - if (format != NS_V2) { - const routerinfo_t* desc = router_get_by_id_digest(rs->identity_digest); - uint32_t bw_kb; - - if (format != NS_CONTROL_PORT) { - /* Blow up more or less nicely if we didn't get anything or not the - * thing we expected. - */ - if (!desc) { - char id[HEX_DIGEST_LEN+1]; - char dd[HEX_DIGEST_LEN+1]; - - base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN); - base16_encode(dd, sizeof(dd), rs->descriptor_digest, DIGEST_LEN); - log_warn(LD_BUG, "Cannot get any descriptor for %s " - "(wanted descriptor %s).", - id, dd); - goto err; - } - - /* This assert could fire for the control port, because - * it can request NS documents before all descriptors - * have been fetched. Therefore, we only do this test when - * format != NS_CONTROL_PORT. */ - if (tor_memneq(desc->cache_info.signed_descriptor_digest, - rs->descriptor_digest, - DIGEST_LEN)) { - char rl_d[HEX_DIGEST_LEN+1]; - char rs_d[HEX_DIGEST_LEN+1]; - char id[HEX_DIGEST_LEN+1]; - - base16_encode(rl_d, sizeof(rl_d), - desc->cache_info.signed_descriptor_digest, DIGEST_LEN); - base16_encode(rs_d, sizeof(rs_d), rs->descriptor_digest, DIGEST_LEN); - base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN); - log_err(LD_BUG, "descriptor digest in routerlist does not match " - "the one in routerstatus: %s vs %s " - "(router %s)\n", - rl_d, rs_d, id); - - tor_assert(tor_memeq(desc->cache_info.signed_descriptor_digest, - rs->descriptor_digest, - DIGEST_LEN)); - } - } - - if (format == NS_CONTROL_PORT && rs->has_bandwidth) { - bw_kb = rs->bandwidth_kb; - } else { - tor_assert(desc); - bw_kb = router_get_advertised_bandwidth_capped(desc) / 1000; - } - smartlist_add_asprintf(chunks, - "w Bandwidth=%d", bw_kb); - - if (format == NS_V3_VOTE && vrs && vrs->has_measured_bw) { - smartlist_add_asprintf(chunks, - " Measured=%d", vrs->measured_bw_kb); - } - /* Write down guardfraction information if we have it. */ - if (format == NS_V3_VOTE && vrs && vrs->status.has_guardfraction) { - smartlist_add_asprintf(chunks, - " GuardFraction=%d", - vrs->status.guardfraction_percentage); - } - - smartlist_add_strdup(chunks, "\n"); - - if (desc) { - summary = policy_summarize(desc->exit_policy, AF_INET); - smartlist_add_asprintf(chunks, "p %s\n", summary); - tor_free(summary); - } - - if (format == NS_V3_VOTE && vrs) { - if (tor_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) { - smartlist_add_strdup(chunks, "id ed25519 none\n"); - } else { - char ed_b64[BASE64_DIGEST256_LEN+1]; - digest256_to_base64(ed_b64, (const char*)vrs->ed25519_id); - smartlist_add_asprintf(chunks, "id ed25519 %s\n", ed_b64); - } - } - } - - done: - result = smartlist_join_strings(chunks, "", 0, NULL); - - err: - SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); - smartlist_free(chunks); - - return result; -} - -/** Extract status information from <b>ri</b> and from other authority - * functions and store it in <b>rs</b>. <b>rs</b> is zeroed out before it is - * set. - * - * We assume that ri->is_running has already been set, e.g. by - * dirserv_set_router_is_running(ri, now); - */ -void -set_routerstatus_from_routerinfo(routerstatus_t *rs, - node_t *node, - routerinfo_t *ri, - time_t now, - int listbadexits) -{ - const or_options_t *options = get_options(); - uint32_t routerbw_kb = dirserv_get_credible_bandwidth_kb(ri); - - memset(rs, 0, sizeof(routerstatus_t)); - - rs->is_authority = - router_digest_is_trusted_dir(ri->cache_info.identity_digest); - - /* Already set by compute_performance_thresholds. */ - rs->is_exit = node->is_exit; - rs->is_stable = node->is_stable = - !dirserv_thinks_router_is_unreliable(now, ri, 1, 0); - rs->is_fast = node->is_fast = - !dirserv_thinks_router_is_unreliable(now, ri, 0, 1); - rs->is_flagged_running = node->is_running; /* computed above */ - - rs->is_valid = node->is_valid; - - if (node->is_fast && node->is_stable && - ri->supports_tunnelled_dir_requests && - ((options->AuthDirGuardBWGuarantee && - routerbw_kb >= options->AuthDirGuardBWGuarantee/1000) || - routerbw_kb >= MIN(guard_bandwidth_including_exits_kb, - guard_bandwidth_excluding_exits_kb))) { - long tk = rep_hist_get_weighted_time_known( - node->identity, now); - double wfu = rep_hist_get_weighted_fractional_uptime( - node->identity, now); - rs->is_possible_guard = (wfu >= guard_wfu && tk >= guard_tk) ? 1 : 0; - } else { - rs->is_possible_guard = 0; - } - - rs->is_bad_exit = listbadexits && node->is_bad_exit; - rs->is_hs_dir = node->is_hs_dir = - dirserv_thinks_router_is_hs_dir(ri, node, now); - - rs->is_named = rs->is_unnamed = 0; - - rs->published_on = ri->cache_info.published_on; - memcpy(rs->identity_digest, node->identity, DIGEST_LEN); - memcpy(rs->descriptor_digest, ri->cache_info.signed_descriptor_digest, - DIGEST_LEN); - rs->addr = ri->addr; - strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname)); - rs->or_port = ri->or_port; - rs->dir_port = ri->dir_port; - rs->is_v2_dir = ri->supports_tunnelled_dir_requests; - if (options->AuthDirHasIPv6Connectivity == 1 && - !tor_addr_is_null(&ri->ipv6_addr) && - node->last_reachable6 >= now - REACHABLE_TIMEOUT) { - /* We're configured as having IPv6 connectivity. There's an IPv6 - OR port and it's reachable so copy it to the routerstatus. */ - tor_addr_copy(&rs->ipv6_addr, &ri->ipv6_addr); - rs->ipv6_orport = ri->ipv6_orport; - } else { - tor_addr_make_null(&rs->ipv6_addr, AF_INET6); - rs->ipv6_orport = 0; - } - - if (options->TestingTorNetwork) { - dirserv_set_routerstatus_testing(rs); - } -} - -/** Use TestingDirAuthVoteExit, TestingDirAuthVoteGuard, and - * TestingDirAuthVoteHSDir to give out the Exit, Guard, and HSDir flags, - * respectively. But don't set the corresponding node flags. - * Should only be called if TestingTorNetwork is set. */ -STATIC void -dirserv_set_routerstatus_testing(routerstatus_t *rs) -{ - const or_options_t *options = get_options(); - - tor_assert(options->TestingTorNetwork); - - if (routerset_contains_routerstatus(options->TestingDirAuthVoteExit, - rs, 0)) { - rs->is_exit = 1; - } else if (options->TestingDirAuthVoteExitIsStrict) { - rs->is_exit = 0; - } - - if (routerset_contains_routerstatus(options->TestingDirAuthVoteGuard, - rs, 0)) { - rs->is_possible_guard = 1; - } else if (options->TestingDirAuthVoteGuardIsStrict) { - rs->is_possible_guard = 0; - } - - if (routerset_contains_routerstatus(options->TestingDirAuthVoteHSDir, - rs, 0)) { - rs->is_hs_dir = 1; - } else if (options->TestingDirAuthVoteHSDirIsStrict) { - rs->is_hs_dir = 0; - } -} - -/** The guardfraction of the guard with identity fingerprint <b>guard_id</b> - * is <b>guardfraction_percentage</b>. See if we have a vote routerstatus for - * this guard in <b>vote_routerstatuses</b>, and if we do, register the - * information to it. - * - * Return 1 if we applied the information and 0 if we couldn't find a - * matching guard. - * - * Requires that <b>vote_routerstatuses</b> be sorted. - */ -static int -guardfraction_line_apply(const char *guard_id, - uint32_t guardfraction_percentage, - smartlist_t *vote_routerstatuses) -{ - vote_routerstatus_t *vrs = NULL; - - tor_assert(vote_routerstatuses); - - vrs = smartlist_bsearch(vote_routerstatuses, guard_id, - compare_digest_to_vote_routerstatus_entry); - - if (!vrs) { - return 0; - } - - vrs->status.has_guardfraction = 1; - vrs->status.guardfraction_percentage = guardfraction_percentage; - - return 1; -} - -/* Given a guard line from a guardfraction file, parse it and register - * its information to <b>vote_routerstatuses</b>. - * - * Return: - * * 1 if the line was proper and its information got registered. - * * 0 if the line was proper but no currently active guard was found - * to register the guardfraction information to. - * * -1 if the line could not be parsed and set <b>err_msg</b> to a - newly allocated string containing the error message. - */ -static int -guardfraction_file_parse_guard_line(const char *guard_line, - smartlist_t *vote_routerstatuses, - char **err_msg) -{ - char guard_id[DIGEST_LEN]; - uint32_t guardfraction; - char *inputs_tmp = NULL; - int num_ok = 1; - - smartlist_t *sl = smartlist_new(); - int retval = -1; - - tor_assert(err_msg); - - /* guard_line should contain something like this: - <hex digest> <guardfraction> <appearances> */ - smartlist_split_string(sl, guard_line, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3); - if (smartlist_len(sl) < 3) { - tor_asprintf(err_msg, "bad line '%s'", guard_line); - goto done; - } - - inputs_tmp = smartlist_get(sl, 0); - if (strlen(inputs_tmp) != HEX_DIGEST_LEN || - base16_decode(guard_id, DIGEST_LEN, - inputs_tmp, HEX_DIGEST_LEN) != DIGEST_LEN) { - tor_asprintf(err_msg, "bad digest '%s'", inputs_tmp); - goto done; - } - - inputs_tmp = smartlist_get(sl, 1); - /* Guardfraction is an integer in [0, 100]. */ - guardfraction = - (uint32_t) tor_parse_long(inputs_tmp, 10, 0, 100, &num_ok, NULL); - if (!num_ok) { - tor_asprintf(err_msg, "wrong percentage '%s'", inputs_tmp); - goto done; - } - - /* If routerstatuses were provided, apply this info to actual routers. */ - if (vote_routerstatuses) { - retval = guardfraction_line_apply(guard_id, guardfraction, - vote_routerstatuses); - } else { - retval = 0; /* If we got this far, line was correctly formatted. */ - } - - done: - - SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); - smartlist_free(sl); - - return retval; -} - -/** Given an inputs line from a guardfraction file, parse it and - * register its information to <b>total_consensuses</b> and - * <b>total_days</b>. - * - * Return 0 if it parsed well. Return -1 if there was an error, and - * set <b>err_msg</b> to a newly allocated string containing the - * error message. - */ -static int -guardfraction_file_parse_inputs_line(const char *inputs_line, - int *total_consensuses, - int *total_days, - char **err_msg) -{ - int retval = -1; - char *inputs_tmp = NULL; - int num_ok = 1; - smartlist_t *sl = smartlist_new(); - - tor_assert(err_msg); - - /* Second line is inputs information: - * n-inputs <total_consensuses> <total_days>. */ - smartlist_split_string(sl, inputs_line, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3); - if (smartlist_len(sl) < 2) { - tor_asprintf(err_msg, "incomplete line '%s'", inputs_line); - goto done; - } - - inputs_tmp = smartlist_get(sl, 0); - *total_consensuses = - (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL); - if (!num_ok) { - tor_asprintf(err_msg, "unparseable consensus '%s'", inputs_tmp); - goto done; - } - - inputs_tmp = smartlist_get(sl, 1); - *total_days = - (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL); - if (!num_ok) { - tor_asprintf(err_msg, "unparseable days '%s'", inputs_tmp); - goto done; - } - - retval = 0; - - done: - SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); - smartlist_free(sl); - - return retval; -} - -/* Maximum age of a guardfraction file that we are willing to accept. */ -#define MAX_GUARDFRACTION_FILE_AGE (7*24*60*60) /* approx a week */ - -/** Static strings of guardfraction files. */ -#define GUARDFRACTION_DATE_STR "written-at" -#define GUARDFRACTION_INPUTS "n-inputs" -#define GUARDFRACTION_GUARD "guard-seen" -#define GUARDFRACTION_VERSION "guardfraction-file-version" - -/** Given a guardfraction file in a string, parse it and register the - * guardfraction information to the provided vote routerstatuses. - * - * This is the rough format of the guardfraction file: - * - * guardfraction-file-version 1 - * written-at <date and time> - * n-inputs <number of consesuses parsed> <number of days considered> - * - * guard-seen <fpr 1> <guardfraction percentage> <consensus appearances> - * guard-seen <fpr 2> <guardfraction percentage> <consensus appearances> - * guard-seen <fpr 3> <guardfraction percentage> <consensus appearances> - * guard-seen <fpr 4> <guardfraction percentage> <consensus appearances> - * guard-seen <fpr 5> <guardfraction percentage> <consensus appearances> - * ... - * - * Return -1 if the parsing failed and 0 if it went smoothly. Parsing - * should tolerate errors in all lines but the written-at header. - */ -STATIC int -dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str, - smartlist_t *vote_routerstatuses) -{ - config_line_t *front=NULL, *line; - int ret_tmp; - int retval = -1; - int current_line_n = 0; /* line counter for better log messages */ - - /* Guardfraction info to be parsed */ - int total_consensuses = 0; - int total_days = 0; - - /* Stats */ - int guards_read_n = 0; - int guards_applied_n = 0; - - /* Parse file and split it in lines */ - ret_tmp = config_get_lines(guardfraction_file_str, &front, 0); - if (ret_tmp < 0) { - log_warn(LD_CONFIG, "Error reading from guardfraction file"); - goto done; - } - - /* Sort routerstatuses (needed later when applying guardfraction info) */ - if (vote_routerstatuses) - smartlist_sort(vote_routerstatuses, compare_vote_routerstatus_entries); - - for (line = front; line; line=line->next) { - current_line_n++; - - if (!strcmp(line->key, GUARDFRACTION_VERSION)) { - int num_ok = 1; - unsigned int version; - - version = - (unsigned int) tor_parse_long(line->value, - 10, 0, INT_MAX, &num_ok, NULL); - - if (!num_ok || version != 1) { - log_warn(LD_GENERAL, "Got unknown guardfraction version %d.", version); - goto done; - } - } else if (!strcmp(line->key, GUARDFRACTION_DATE_STR)) { - time_t file_written_at; - time_t now = time(NULL); - - /* First line is 'written-at <date>' */ - if (parse_iso_time(line->value, &file_written_at) < 0) { - log_warn(LD_CONFIG, "Guardfraction:%d: Bad date '%s'. Ignoring", - current_line_n, line->value); - goto done; /* don't tolerate failure here. */ - } - if (file_written_at < now - MAX_GUARDFRACTION_FILE_AGE) { - log_warn(LD_CONFIG, "Guardfraction:%d: was written very long ago '%s'", - current_line_n, line->value); - goto done; /* don't tolerate failure here. */ - } - } else if (!strcmp(line->key, GUARDFRACTION_INPUTS)) { - char *err_msg = NULL; - - if (guardfraction_file_parse_inputs_line(line->value, - &total_consensuses, - &total_days, - &err_msg) < 0) { - log_warn(LD_CONFIG, "Guardfraction:%d: %s", - current_line_n, err_msg); - tor_free(err_msg); - continue; - } - - } else if (!strcmp(line->key, GUARDFRACTION_GUARD)) { - char *err_msg = NULL; - - ret_tmp = guardfraction_file_parse_guard_line(line->value, - vote_routerstatuses, - &err_msg); - if (ret_tmp < 0) { /* failed while parsing the guard line */ - log_warn(LD_CONFIG, "Guardfraction:%d: %s", - current_line_n, err_msg); - tor_free(err_msg); - continue; - } - - /* Successfully parsed guard line. Check if it was applied properly. */ - guards_read_n++; - if (ret_tmp > 0) { - guards_applied_n++; - } - } else { - log_warn(LD_CONFIG, "Unknown guardfraction line %d (%s %s)", - current_line_n, line->key, line->value); - } - } - - retval = 0; - - log_info(LD_CONFIG, - "Successfully parsed guardfraction file with %d consensuses over " - "%d days. Parsed %d nodes and applied %d of them%s.", - total_consensuses, total_days, guards_read_n, guards_applied_n, - vote_routerstatuses ? "" : " (no routerstatus provided)" ); - - done: - config_free_lines(front); - - if (retval < 0) { - return retval; - } else { - return guards_read_n; - } + return 1; }
-/** Read a guardfraction file at <b>fname</b> and load all its - * information to <b>vote_routerstatuses</b>. */ +/** Return 1 if we should fetch new networkstatuses, descriptors, etc + * on the "mirror" schedule rather than the "client" schedule. + */ int -dirserv_read_guardfraction_file(const char *fname, - smartlist_t *vote_routerstatuses) +directory_fetches_dir_info_early(const or_options_t *options) { - char *guardfraction_file_str; - - /* Read file to a string */ - guardfraction_file_str = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL); - if (!guardfraction_file_str) { - log_warn(LD_FS, "Cannot open guardfraction file '%s'. Failing.", fname); - return -1; - } - - return dirserv_read_guardfraction_file_from_str(guardfraction_file_str, - vote_routerstatuses); + return directory_fetches_from_authorities(options); }
-/** - * Helper function to parse out a line in the measured bandwidth file - * into a measured_bw_line_t output structure. - * - * If <b>line_is_after_headers</b> is true, then if we encounter an incomplete - * bw line, return -1 and warn, since we are after the headers and we should - * only parse bw lines. Return 0 otherwise. - * - * If <b>line_is_after_headers</b> is false then it means that we are not past - * the header block yet. If we encounter an incomplete bw line, return -1 but - * don't warn since there could be additional header lines coming. If we - * encounter a proper bw line, return 0 (and we got past the headers). +/** Return 1 if we should fetch new networkstatuses, descriptors, etc + * on a very passive schedule -- waiting long enough for ordinary clients + * to probably have the info we want. These would include bridge users, + * and maybe others in the future e.g. if a Tor client uses another Tor + * client as a directory guard. */ -STATIC int -measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line, - int line_is_after_headers) +int +directory_fetches_dir_info_later(const or_options_t *options) { - char *line = tor_strdup(orig_line); - char *cp = line; - int got_bw = 0; - int got_node_id = 0; - char *strtok_state; /* lame sauce d'jour */ - - if (strlen(line) == 0) { - log_warn(LD_DIRSERV, "Empty line in bandwidth file"); - tor_free(line); - return -1; - } - - /* Remove end of line character, so that is not part of the token */ - if (line[strlen(line) - 1] == '\n') { - line[strlen(line) - 1] = '\0'; - } - - cp = tor_strtok_r(cp, " \t", &strtok_state); - - if (!cp) { - log_warn(LD_DIRSERV, "Invalid line in bandwidth file: %s", - escaped(orig_line)); - tor_free(line); - return -1; - } - - if (orig_line[strlen(orig_line)-1] != '\n') { - log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s", - escaped(orig_line)); - tor_free(line); - return -1; - } - - do { - if (strcmpstart(cp, "bw=") == 0) { - int parse_ok = 0; - char *endptr; - if (got_bw) { - log_warn(LD_DIRSERV, "Double bw= in bandwidth file line: %s", - escaped(orig_line)); - tor_free(line); - return -1; - } - cp+=strlen("bw="); - - out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr); - if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) { - log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s", - escaped(orig_line)); - tor_free(line); - return -1; - } - got_bw=1; - } else if (strcmpstart(cp, "node_id=$") == 0) { - if (got_node_id) { - log_warn(LD_DIRSERV, "Double node_id= in bandwidth file line: %s", - escaped(orig_line)); - tor_free(line); - return -1; - } - cp+=strlen("node_id=$"); - - if (strlen(cp) != HEX_DIGEST_LEN || - base16_decode(out->node_id, DIGEST_LEN, - cp, HEX_DIGEST_LEN) != DIGEST_LEN) { - log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s", - escaped(orig_line)); - tor_free(line); - return -1; - } - strlcpy(out->node_hex, cp, sizeof(out->node_hex)); - got_node_id=1; - } - } while ((cp = tor_strtok_r(NULL, " \t", &strtok_state))); + return options->UseBridges != 0; +}
- if (got_bw && got_node_id) { - tor_free(line); - return 0; - } else if (line_is_after_headers == 0) { - /* There could be additional header lines, therefore do not give warnings - * but returns -1 since it's not a complete bw line. */ - log_debug(LD_DIRSERV, "Missing bw or node_id in bandwidth file line: %s", - escaped(orig_line)); - tor_free(line); - return -1; - } else { - log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s", - escaped(orig_line)); - tor_free(line); - return -1; - } +/** Return true iff we want to serve certificates for authorities + * that we don't acknowledge as authorities ourself. + * Use we_want_to_fetch_unknown_auth_certs to check if we want to fetch + * and keep these certificates. + */ +int +directory_caches_unknown_auth_certs(const or_options_t *options) +{ + return dir_server_mode(options) || options->BridgeRelay; }
-/** - * Helper function to apply a parsed measurement line to a list - * of bandwidth statuses. Returns true if a line is found, - * false otherwise. +/** Return 1 if we want to fetch and serve descriptors, networkstatuses, etc + * Else return 0. + * Check options->DirPort_set and directory_permits_begindir_requests() + * to see if we are willing to serve these directory documents to others via + * the DirPort and begindir-over-ORPort, respectively. + * + * To check if we should fetch documents, use we_want_to_fetch_flavor and + * we_want_to_fetch_unknown_auth_certs instead of this function. */ -STATIC int -measured_bw_line_apply(measured_bw_line_t *parsed_line, - smartlist_t *routerstatuses) +int +directory_caches_dir_info(const or_options_t *options) { - vote_routerstatus_t *rs = NULL; - if (!routerstatuses) + if (options->BridgeRelay || dir_server_mode(options)) + return 1; + if (!server_mode(options) || !advertised_server_mode()) return 0; + /* We need an up-to-date view of network info if we're going to try to + * block exit attempts from unknown relays. */ + return ! router_my_exit_policy_is_reject_star() && + should_refuse_unknown_exits(options); +}
- rs = smartlist_bsearch(routerstatuses, parsed_line->node_id, - compare_digest_to_vote_routerstatus_entry); - - if (rs) { - rs->has_measured_bw = 1; - rs->measured_bw_kb = (uint32_t)parsed_line->bw_kb; - } else { - log_info(LD_DIRSERV, "Node ID %s not found in routerstatus list", - parsed_line->node_hex); - } - - return rs != NULL; +/** Return 1 if we want to allow remote clients to ask us directory + * requests via the "begin_dir" interface, which doesn't require + * having any separate port open. */ +int +directory_permits_begindir_requests(const or_options_t *options) +{ + return options->BridgeRelay != 0 || dir_server_mode(options); }
-/** - * Read the measured bandwidth list file, apply it to the list of - * vote_routerstatus_t and store all the headers in <b>bw_file_headers</b>. - * Returns -1 on error, 0 otherwise. +/** Return 1 if we have no need to fetch new descriptors. This generally + * happens when we're not a dir cache and we haven't built any circuits + * lately. */ int -dirserv_read_measured_bandwidths(const char *from_file, - smartlist_t *routerstatuses, - smartlist_t *bw_file_headers) +directory_too_idle_to_fetch_descriptors(const or_options_t *options, + time_t now) { - FILE *fp = tor_fopen_cloexec(from_file, "r"); - int applied_lines = 0; - time_t file_time, now; - int ok; - /* This flag will be 1 only when the first successful bw measurement line - * has been encountered, so that measured_bw_line_parse don't give warnings - * if there are additional header lines, as introduced in Bandwidth List spec - * version 1.1.0 */ - int line_is_after_headers = 0; - int rv = -1; - char *line = NULL; - size_t n = 0; - - /* Initialise line, so that we can't possibly run off the end. */ - - if (fp == NULL) { - log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s", - from_file); - goto err; - } + return !directory_caches_dir_info(options) && + !options->FetchUselessDescriptors && + rep_hist_circbuilding_dormant(now); +}
- /* If fgets fails, line is either unmodified, or indeterminate. */ - if (tor_getline(&line,&n,fp) <= 0) { - log_warn(LD_DIRSERV, "Empty bandwidth file"); - goto err; - } +/********************************************************************/
- if (!strlen(line) || line[strlen(line)-1] != '\n') { - log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s", - escaped(line)); - goto err; - } +/** Map from flavor name to the cached_dir_t for the v3 consensuses that we're + * currently serving. */ +static strmap_t *cached_consensuses = NULL;
- line[strlen(line)-1] = '\0'; - file_time = (time_t)tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL); - if (!ok) { - log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s", - escaped(line)); - goto err; - } +/** Decrement the reference count on <b>d</b>, and free it if it no longer has + * any references. */ +void +cached_dir_decref(cached_dir_t *d) +{ + if (!d || --d->refcnt > 0) + return; + clear_cached_dir(d); + tor_free(d); +}
- 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)); - goto err; +/** Allocate and return a new cached_dir_t containing the string <b>s</b>, + * published at <b>published</b>. */ +cached_dir_t * +new_cached_dir(char *s, time_t published) +{ + cached_dir_t *d = tor_malloc_zero(sizeof(cached_dir_t)); + d->refcnt = 1; + d->dir = s; + d->dir_len = strlen(s); + d->published = published; + if (tor_compress(&(d->dir_compressed), &(d->dir_compressed_len), + d->dir, d->dir_len, ZLIB_METHOD)) { + log_warn(LD_BUG, "Error compressing directory"); } + return d; +}
- /* If timestamp was correct and bw_file_headers is not NULL, - * add timestamp to bw_file_headers */ - if (bw_file_headers) - smartlist_add_asprintf(bw_file_headers, "timestamp=%lu", - (unsigned long)file_time); - - if (routerstatuses) - smartlist_sort(routerstatuses, compare_vote_routerstatus_entries); - - while (!feof(fp)) { - measured_bw_line_t parsed_line; - if (tor_getline(&line, &n, fp) >= 0) { - if (measured_bw_line_parse(&parsed_line, line, - line_is_after_headers) != -1) { - /* This condition will be true when the first complete valid bw line - * has been encountered, which means the end of the header lines. */ - line_is_after_headers = 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++; - /* if the terminator is found, it is the end of header lines, set the - * flag but do not store anything */ - } else if (strcmp(line, BW_FILE_HEADERS_TERMINATOR) == 0) { - line_is_after_headers = 1; - /* if the line was not a correct relay line nor the terminator and - * the end of the header lines has not been detected yet - * and it is key_value and bw_file_headers did not reach the maximum - * number of headers, - * then assume this line is a header and add it to bw_file_headers */ - } else if (bw_file_headers && - (line_is_after_headers == 0) && - string_is_key_value(LOG_DEBUG, line) && - !strchr(line, ' ') && - (smartlist_len(bw_file_headers) - < MAX_BW_FILE_HEADER_COUNT_IN_VOTE)) { - line[strlen(line)-1] = '\0'; - smartlist_add_strdup(bw_file_headers, line); - }; - } - } +/** Remove all storage held in <b>d</b>, but do not free <b>d</b> itself. */ +static void +clear_cached_dir(cached_dir_t *d) +{ + tor_free(d->dir); + tor_free(d->dir_compressed); + memset(d, 0, sizeof(cached_dir_t)); +}
- /* Now would be a nice time to clean the cache, too */ - dirserv_expire_measured_bw_cache(now); +/** Free all storage held by the cached_dir_t in <b>d</b>. */ +static void +free_cached_dir_(void *_d) +{ + cached_dir_t *d; + if (!_d) + return;
- log_info(LD_DIRSERV, - "Bandwidth measurement file successfully read. " - "Applied %d measurements.", applied_lines); - rv = 0; + d = (cached_dir_t *)_d; + cached_dir_decref(d); +}
- err: - if (line) { - // we need to raw_free this buffer because we got it from tor_getdelim() - raw_free(line); - } - if (fp) - fclose(fp); - return rv; +/** Replace the v3 consensus networkstatus of type <b>flavor_name</b> that + * we're serving with <b>networkstatus</b>, published at <b>published</b>. No + * validation is performed. */ +void +dirserv_set_cached_consensus_networkstatus(const char *networkstatus, ++ size_t networkstatus_len, + const char *flavor_name, + const common_digests_t *digests, + const uint8_t *sha3_as_signed, + time_t published) +{ + cached_dir_t *new_networkstatus; + cached_dir_t *old_networkstatus; + if (!cached_consensuses) + cached_consensuses = strmap_new(); + - new_networkstatus = new_cached_dir(tor_strdup(networkstatus), published); ++ new_networkstatus = ++ new_cached_dir(tor_memdup_nulterm(networkstatus, networkstatus_len), ++ published); + memcpy(&new_networkstatus->digests, digests, sizeof(common_digests_t)); + memcpy(&new_networkstatus->digest_sha3_as_signed, sha3_as_signed, + DIGEST256_LEN); + old_networkstatus = strmap_set(cached_consensuses, flavor_name, + new_networkstatus); + if (old_networkstatus) + cached_dir_decref(old_networkstatus); +} + +/** Return the latest downloaded consensus networkstatus in encoded, signed, + * optionally compressed format, suitable for sending to clients. */ +cached_dir_t * +dirserv_get_consensus(const char *flavor_name) +{ + if (!cached_consensuses) + return NULL; + return strmap_get(cached_consensuses, flavor_name); }
/** As dirserv_get_routerdescs(), but instead of getting signed_descriptor_t diff --cc src/feature/dirparse/authcert_parse.c index 2ba46bb8f,000000000..334baf8b1 mode 100644,000000..100644 --- a/src/feature/dirparse/authcert_parse.c +++ b/src/feature/dirparse/authcert_parse.c @@@ -1,207 -1,0 +1,209 @@@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "core/or/or.h" +#include "feature/dirparse/authcert_parse.h" +#include "feature/dirparse/parsecommon.h" +#include "feature/dirparse/sigcommon.h" +#include "feature/dirparse/unparseable.h" +#include "feature/nodelist/authcert.h" +#include "lib/memarea/memarea.h" + +#include "feature/nodelist/authority_cert_st.h" + +/** List of tokens recognized in V3 authority certificates. */ +static token_rule_t dir_key_certificate_table[] = { +#include "feature/dirparse/authcert_members.i" + T1("fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), + END_OF_TABLE +}; + +/** Parse a key certificate from <b>s</b>; point <b>end-of-string</b> to + * the first character after the certificate. */ +authority_cert_t * - authority_cert_parse_from_string(const char *s, const char **end_of_string) ++authority_cert_parse_from_string(const char *s, size_t maxlen, ++ const char **end_of_string) +{ + /** Reject any certificate at least this big; it is probably an overflow, an + * attack, a bug, or some other nonsense. */ +#define MAX_CERT_SIZE (128*1024) + + authority_cert_t *cert = NULL, *old_cert; + smartlist_t *tokens = NULL; + char digest[DIGEST_LEN]; + directory_token_t *tok; + char fp_declared[DIGEST_LEN]; - char *eos; ++ const char *eos; + size_t len; + int found; + memarea_t *area = NULL; ++ const char *end_of_s = s + maxlen; + const char *s_dup = s; + - s = eat_whitespace(s); - eos = strstr(s, "\ndir-key-certification"); ++ s = eat_whitespace_eos(s, end_of_s); ++ eos = tor_memstr(s, end_of_s - s, "\ndir-key-certification"); + if (! eos) { + log_warn(LD_DIR, "No signature found on key certificate"); + return NULL; + } - eos = strstr(eos, "\n-----END SIGNATURE-----\n"); ++ eos = tor_memstr(eos, end_of_s - eos, "\n-----END SIGNATURE-----\n"); + if (! eos) { + log_warn(LD_DIR, "No end-of-signature found on key certificate"); + return NULL; + } - eos = strchr(eos+2, '\n'); ++ eos = memchr(eos+2, '\n', end_of_s - (eos+2)); + tor_assert(eos); + ++eos; + len = eos - s; + + if (len > MAX_CERT_SIZE) { + log_warn(LD_DIR, "Certificate is far too big (at %lu bytes long); " + "rejecting", (unsigned long)len); + return NULL; + } + + tokens = smartlist_new(); + area = memarea_new(); + if (tokenize_string(area,s, eos, tokens, dir_key_certificate_table, 0) < 0) { + log_warn(LD_DIR, "Error tokenizing key certificate"); + goto err; + } - if (router_get_hash_impl(s, strlen(s), digest, "dir-key-certificate-version", ++ if (router_get_hash_impl(s, eos - s, digest, "dir-key-certificate-version", + "\ndir-key-certification", '\n', DIGEST_SHA1) < 0) + goto err; + tok = smartlist_get(tokens, 0); + if (tok->tp != K_DIR_KEY_CERTIFICATE_VERSION || strcmp(tok->args[0], "3")) { + log_warn(LD_DIR, + "Key certificate does not begin with a recognized version (3)."); + goto err; + } + + cert = tor_malloc_zero(sizeof(authority_cert_t)); + memcpy(cert->cache_info.signed_descriptor_digest, digest, DIGEST_LEN); + + tok = find_by_keyword(tokens, K_DIR_SIGNING_KEY); + tor_assert(tok->key); + cert->signing_key = tok->key; + tok->key = NULL; + if (crypto_pk_get_digest(cert->signing_key, cert->signing_key_digest)) + goto err; + + tok = find_by_keyword(tokens, K_DIR_IDENTITY_KEY); + tor_assert(tok->key); + cert->identity_key = tok->key; + tok->key = NULL; + + tok = find_by_keyword(tokens, K_FINGERPRINT); + tor_assert(tok->n_args); + if (base16_decode(fp_declared, DIGEST_LEN, tok->args[0], + strlen(tok->args[0])) != DIGEST_LEN) { + log_warn(LD_DIR, "Couldn't decode key certificate fingerprint %s", + escaped(tok->args[0])); + goto err; + } + + if (crypto_pk_get_digest(cert->identity_key, + cert->cache_info.identity_digest)) + goto err; + + if (tor_memneq(cert->cache_info.identity_digest, fp_declared, DIGEST_LEN)) { + log_warn(LD_DIR, "Digest of certificate key didn't match declared " + "fingerprint"); + goto err; + } + + tok = find_opt_by_keyword(tokens, K_DIR_ADDRESS); + if (tok) { + struct in_addr in; + char *address = NULL; + tor_assert(tok->n_args); + /* XXX++ use some tor_addr parse function below instead. -RD */ + if (tor_addr_port_split(LOG_WARN, tok->args[0], &address, + &cert->dir_port) < 0 || + tor_inet_aton(address, &in) == 0) { + log_warn(LD_DIR, "Couldn't parse dir-address in certificate"); + tor_free(address); + goto err; + } + cert->addr = ntohl(in.s_addr); + tor_free(address); + } + + tok = find_by_keyword(tokens, K_DIR_KEY_PUBLISHED); + if (parse_iso_time(tok->args[0], &cert->cache_info.published_on) < 0) { + goto err; + } + tok = find_by_keyword(tokens, K_DIR_KEY_EXPIRES); + if (parse_iso_time(tok->args[0], &cert->expires) < 0) { + goto err; + } + + tok = smartlist_get(tokens, smartlist_len(tokens)-1); + if (tok->tp != K_DIR_KEY_CERTIFICATION) { + log_warn(LD_DIR, "Certificate didn't end with dir-key-certification."); + goto err; + } + + /* If we already have this cert, don't bother checking the signature. */ + old_cert = authority_cert_get_by_digests( + cert->cache_info.identity_digest, + cert->signing_key_digest); + found = 0; + if (old_cert) { + /* XXXX We could just compare signed_descriptor_digest, but that wouldn't + * buy us much. */ + if (old_cert->cache_info.signed_descriptor_len == len && + old_cert->cache_info.signed_descriptor_body && + tor_memeq(s, old_cert->cache_info.signed_descriptor_body, len)) { + log_debug(LD_DIR, "We already checked the signature on this " + "certificate; no need to do so again."); + found = 1; + } + } + if (!found) { + if (check_signature_token(digest, DIGEST_LEN, tok, cert->identity_key, 0, + "key certificate")) { + goto err; + } + + tok = find_by_keyword(tokens, K_DIR_KEY_CROSSCERT); + if (check_signature_token(cert->cache_info.identity_digest, + DIGEST_LEN, + tok, + cert->signing_key, + CST_NO_CHECK_OBJTYPE, + "key cross-certification")) { + goto err; + } + } + + cert->cache_info.signed_descriptor_len = len; + cert->cache_info.signed_descriptor_body = tor_malloc(len+1); + memcpy(cert->cache_info.signed_descriptor_body, s, len); + cert->cache_info.signed_descriptor_body[len] = 0; + cert->cache_info.saved_location = SAVED_NOWHERE; + + if (end_of_string) { + *end_of_string = eat_whitespace(eos); + } + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(tokens); + if (area) { + DUMP_AREA(area, "authority cert"); + memarea_drop_all(area); + } + return cert; + err: + dump_desc(s_dup, "authority cert"); + authority_cert_free(cert); + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(tokens); + if (area) { + DUMP_AREA(area, "authority cert"); + memarea_drop_all(area); + } + return NULL; +} diff --cc src/feature/dirparse/authcert_parse.h index f63525e04,000000000..e4e9fec99 mode 100644,000000..100644 --- a/src/feature/dirparse/authcert_parse.h +++ b/src/feature/dirparse/authcert_parse.h @@@ -1,18 -1,0 +1,19 @@@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file authcert_parse.h + * \brief Header file for authcert_parse.c. + **/ + +#ifndef TOR_AUTHCERT_PARSE_H +#define TOR_AUTHCERT_PARSE_H + +authority_cert_t *authority_cert_parse_from_string(const char *s, ++ size_t maxlen, + const char **end_of_string); + +#endif /* !defined(TOR_AUTHCERT_PARSE_H) */ diff --cc src/feature/dirparse/ns_parse.c index 72299e807,000000000..3fccec154 mode 100644,000000..100644 --- a/src/feature/dirparse/ns_parse.c +++ b/src/feature/dirparse/ns_parse.c @@@ -1,1685 -1,0 +1,1696 @@@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file routerparse.c + * \brief Code to parse and validate consensus documents and votes. + */ + +#define NS_PARSE_PRIVATE + +#include "core/or/or.h" +#include "app/config/config.h" +#include "core/or/versions.h" +#include "feature/client/entrynodes.h" +#include "feature/dirauth/dirvote.h" +#include "feature/dirparse/authcert_parse.h" +#include "feature/dirparse/ns_parse.h" +#include "feature/dirparse/parsecommon.h" +#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/sigcommon.h" +#include "feature/dirparse/unparseable.h" +#include "feature/hs_common/shared_random_client.h" +#include "feature/nodelist/authcert.h" +#include "feature/nodelist/describe.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nickname.h" +#include "lib/crypt_ops/crypto_format.h" +#include "lib/memarea/memarea.h" + +#include "feature/dirauth/vote_microdesc_hash_st.h" +#include "feature/nodelist/authority_cert_st.h" +#include "feature/nodelist/document_signature_st.h" +#include "feature/nodelist/networkstatus_st.h" +#include "feature/nodelist/networkstatus_voter_info_st.h" +#include "feature/nodelist/vote_routerstatus_st.h" + +#undef log +#include <math.h> + +/** List of tokens recognized in the body part of v3 networkstatus + * documents. */ +static token_rule_t rtrstatus_token_table[] = { + T01("p", K_P, CONCAT_ARGS, NO_OBJ ), + T1( "r", K_R, GE(7), NO_OBJ ), + T0N("a", K_A, GE(1), NO_OBJ ), + T1( "s", K_S, ARGS, NO_OBJ ), + T01("v", K_V, CONCAT_ARGS, NO_OBJ ), + T01("w", K_W, ARGS, NO_OBJ ), + T0N("m", K_M, CONCAT_ARGS, NO_OBJ ), + T0N("id", K_ID, GE(2), NO_OBJ ), + T01("pr", K_PROTO, CONCAT_ARGS, NO_OBJ ), + T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), + END_OF_TABLE +}; + +/** List of tokens recognized in V3 networkstatus votes. */ +static token_rule_t networkstatus_token_table[] = { + T1_START("network-status-version", K_NETWORK_STATUS_VERSION, + GE(1), NO_OBJ ), + T1("vote-status", K_VOTE_STATUS, GE(1), NO_OBJ ), + T1("published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ), + T1("valid-after", K_VALID_AFTER, CONCAT_ARGS, NO_OBJ ), + T1("fresh-until", K_FRESH_UNTIL, CONCAT_ARGS, NO_OBJ ), + T1("valid-until", K_VALID_UNTIL, CONCAT_ARGS, NO_OBJ ), + T1("voting-delay", K_VOTING_DELAY, GE(2), NO_OBJ ), + T1("known-flags", K_KNOWN_FLAGS, ARGS, NO_OBJ ), + T01("params", K_PARAMS, ARGS, NO_OBJ ), + T( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), + T01("signing-ed25519", K_SIGNING_CERT_ED, NO_ARGS , NEED_OBJ ), + T01("shared-rand-participate",K_SR_FLAG, NO_ARGS, NO_OBJ ), + T0N("shared-rand-commit", K_COMMIT, GE(3), NO_OBJ ), + T01("shared-rand-previous-value", K_PREVIOUS_SRV,EQ(2), NO_OBJ ), + T01("shared-rand-current-value", K_CURRENT_SRV, EQ(2), NO_OBJ ), + T0N("package", K_PACKAGE, CONCAT_ARGS, NO_OBJ ), + T01("recommended-client-protocols", K_RECOMMENDED_CLIENT_PROTOCOLS, + CONCAT_ARGS, NO_OBJ ), + T01("recommended-relay-protocols", K_RECOMMENDED_RELAY_PROTOCOLS, + CONCAT_ARGS, NO_OBJ ), + T01("required-client-protocols", K_REQUIRED_CLIENT_PROTOCOLS, + CONCAT_ARGS, NO_OBJ ), + T01("required-relay-protocols", K_REQUIRED_RELAY_PROTOCOLS, + CONCAT_ARGS, NO_OBJ ), + +#include "feature/dirparse/authcert_members.i" + + T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), + T1( "contact", K_CONTACT, CONCAT_ARGS, NO_OBJ ), + T1( "dir-source", K_DIR_SOURCE, GE(6), NO_OBJ ), + T01("legacy-dir-key", K_LEGACY_DIR_KEY, GE(1), NO_OBJ ), + T1( "known-flags", K_KNOWN_FLAGS, CONCAT_ARGS, NO_OBJ ), + T01("client-versions", K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ), + T01("server-versions", K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ), + T1( "consensus-methods", K_CONSENSUS_METHODS, GE(1), NO_OBJ ), + + END_OF_TABLE +}; + +/** List of tokens recognized in V3 networkstatus consensuses. */ +static token_rule_t networkstatus_consensus_token_table[] = { + T1_START("network-status-version", K_NETWORK_STATUS_VERSION, + GE(1), NO_OBJ ), + T1("vote-status", K_VOTE_STATUS, GE(1), NO_OBJ ), + T1("valid-after", K_VALID_AFTER, CONCAT_ARGS, NO_OBJ ), + T1("fresh-until", K_FRESH_UNTIL, CONCAT_ARGS, NO_OBJ ), + T1("valid-until", K_VALID_UNTIL, CONCAT_ARGS, NO_OBJ ), + T1("voting-delay", K_VOTING_DELAY, GE(2), NO_OBJ ), + + T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), + + T1N("dir-source", K_DIR_SOURCE, GE(6), NO_OBJ ), + T1N("contact", K_CONTACT, CONCAT_ARGS, NO_OBJ ), + T1N("vote-digest", K_VOTE_DIGEST, GE(1), NO_OBJ ), + + T1( "known-flags", K_KNOWN_FLAGS, CONCAT_ARGS, NO_OBJ ), + + T01("client-versions", K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ), + T01("server-versions", K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ), + T01("consensus-method", K_CONSENSUS_METHOD, EQ(1), NO_OBJ), + T01("params", K_PARAMS, ARGS, NO_OBJ ), + + T01("shared-rand-previous-value", K_PREVIOUS_SRV, EQ(2), NO_OBJ ), + T01("shared-rand-current-value", K_CURRENT_SRV, EQ(2), NO_OBJ ), + + T01("recommended-client-protocols", K_RECOMMENDED_CLIENT_PROTOCOLS, + CONCAT_ARGS, NO_OBJ ), + T01("recommended-relay-protocols", K_RECOMMENDED_RELAY_PROTOCOLS, + CONCAT_ARGS, NO_OBJ ), + T01("required-client-protocols", K_REQUIRED_CLIENT_PROTOCOLS, + CONCAT_ARGS, NO_OBJ ), + T01("required-relay-protocols", K_REQUIRED_RELAY_PROTOCOLS, + CONCAT_ARGS, NO_OBJ ), + + END_OF_TABLE +}; + +/** List of tokens recognized in the footer of v1 directory footers. */ +static token_rule_t networkstatus_vote_footer_token_table[] = { + T01("directory-footer", K_DIRECTORY_FOOTER, NO_ARGS, NO_OBJ ), + T01("bandwidth-weights", K_BW_WEIGHTS, ARGS, NO_OBJ ), + T( "directory-signature", K_DIRECTORY_SIGNATURE, GE(2), NEED_OBJ ), + END_OF_TABLE +}; + +/** Try to find the start and end of the signed portion of a networkstatus + * document in <b>s</b>. On success, set <b>start_out</b> to the first + * character of the document, and <b>end_out</b> to a position one after the + * final character of the signed document, and return 0. On failure, return + * -1. */ +int +router_get_networkstatus_v3_signed_boundaries(const char *s, ++ size_t len, + const char **start_out, + const char **end_out) +{ - return router_get_hash_impl_helper(s, strlen(s), ++ return router_get_hash_impl_helper(s, len, + "network-status-version", + "\ndirectory-signature", + ' ', LOG_INFO, + start_out, end_out); +} + +/** Set <b>digest_out</b> to the SHA3-256 digest of the signed portion of the + * networkstatus vote in <b>s</b> -- or of the entirety of <b>s</b> if no + * signed portion can be identified. Return 0 on success, -1 on failure. */ +int +router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, - const char *s) ++ const char *s, size_t len) +{ + const char *start, *end; - if (router_get_networkstatus_v3_signed_boundaries(s, &start, &end) < 0) { ++ if (router_get_networkstatus_v3_signed_boundaries(s, len, ++ &start, &end) < 0) { + start = s; - end = s + strlen(s); ++ end = s + len; + } + tor_assert(start); + tor_assert(end); + return crypto_digest256((char*)digest_out, start, end-start, + DIGEST_SHA3_256); +} + +/** Set <b>digests</b> to all the digests of the consensus document in + * <b>s</b> */ +int - router_get_networkstatus_v3_hashes(const char *s, common_digests_t *digests) ++router_get_networkstatus_v3_hashes(const char *s, size_t len, ++ common_digests_t *digests) +{ - return router_get_hashes_impl(s,strlen(s),digests, ++ return router_get_hashes_impl(s, len, digests, + "network-status-version", + "\ndirectory-signature", + ' '); +} + +/** Helper: given a string <b>s</b>, return the start of the next router-status + * object (starting with "r " at the start of a line). If none is found, + * return the start of the directory footer, or the next directory signature. + * If none is found, return the end of the string. */ +static inline const char * - find_start_of_next_routerstatus(const char *s) ++find_start_of_next_routerstatus(const char *s, const char *s_eos) +{ + const char *eos, *footer, *sig; - if ((eos = strstr(s, "\nr "))) ++ if ((eos = tor_memstr(s, s_eos - s, "\nr "))) + ++eos; + else - eos = s + strlen(s); ++ eos = s_eos; + + footer = tor_memstr(s, eos-s, "\ndirectory-footer"); + sig = tor_memstr(s, eos-s, "\ndirectory-signature"); + + if (footer && sig) + return MIN(footer, sig) + 1; + else if (footer) + return footer+1; + else if (sig) + return sig+1; + else + return eos; +} + +/** Parse the GuardFraction string from a consensus or vote. + * + * If <b>vote</b> or <b>vote_rs</b> are set the document getting + * parsed is a vote routerstatus. Otherwise it's a consensus. This is + * the same semantic as in routerstatus_parse_entry_from_string(). */ +STATIC int +routerstatus_parse_guardfraction(const char *guardfraction_str, + networkstatus_t *vote, + vote_routerstatus_t *vote_rs, + routerstatus_t *rs) +{ + int ok; + const char *end_of_header = NULL; + int is_consensus = !vote_rs; + uint32_t guardfraction; + + tor_assert(bool_eq(vote, vote_rs)); + + /* If this info comes from a consensus, but we should't apply + guardfraction, just exit. */ + if (is_consensus && !should_apply_guardfraction(NULL)) { + return 0; + } + + end_of_header = strchr(guardfraction_str, '='); + if (!end_of_header) { + return -1; + } + + guardfraction = (uint32_t)tor_parse_ulong(end_of_header+1, + 10, 0, 100, &ok, NULL); + if (!ok) { + log_warn(LD_DIR, "Invalid GuardFraction %s", escaped(guardfraction_str)); + return -1; + } + + log_debug(LD_GENERAL, "[*] Parsed %s guardfraction '%s' for '%s'.", + is_consensus ? "consensus" : "vote", + guardfraction_str, rs->nickname); + + if (!is_consensus) { /* We are parsing a vote */ + vote_rs->status.guardfraction_percentage = guardfraction; + vote_rs->status.has_guardfraction = 1; + } else { + /* We are parsing a consensus. Only apply guardfraction to guards. */ + if (rs->is_possible_guard) { + rs->guardfraction_percentage = guardfraction; + rs->has_guardfraction = 1; + } else { + log_warn(LD_BUG, "Got GuardFraction for non-guard %s. " + "This is not supposed to happen. Not applying. ", rs->nickname); + } + } + + return 0; +} + +/** Given a string at *<b>s</b>, containing a routerstatus object, and an + * empty smartlist at <b>tokens</b>, parse and return the first router status + * object in the string, and advance *<b>s</b> to just after the end of the + * router status. Return NULL and advance *<b>s</b> on error. + * + * If <b>vote</b> and <b>vote_rs</b> are provided, don't allocate a fresh + * routerstatus but use <b>vote_rs</b> instead. + * + * If <b>consensus_method</b> is nonzero, this routerstatus is part of a + * consensus, and we should parse it according to the method used to + * make that consensus. + * + * Parse according to the syntax used by the consensus flavor <b>flav</b>. + **/ +STATIC routerstatus_t * +routerstatus_parse_entry_from_string(memarea_t *area, - const char **s, smartlist_t *tokens, ++ const char **s, const char *s_eos, ++ smartlist_t *tokens, + networkstatus_t *vote, + vote_routerstatus_t *vote_rs, + int consensus_method, + consensus_flavor_t flav) +{ + const char *eos, *s_dup = *s; + routerstatus_t *rs = NULL; + directory_token_t *tok; + char timebuf[ISO_TIME_LEN+1]; + struct in_addr in; + int offset = 0; + tor_assert(tokens); + tor_assert(bool_eq(vote, vote_rs)); + + if (!consensus_method) + flav = FLAV_NS; + tor_assert(flav == FLAV_NS || flav == FLAV_MICRODESC); + - eos = find_start_of_next_routerstatus(*s); ++ eos = find_start_of_next_routerstatus(*s, s_eos); + + if (tokenize_string(area,*s, eos, tokens, rtrstatus_token_table,0)) { + log_warn(LD_DIR, "Error tokenizing router status"); + goto err; + } + if (smartlist_len(tokens) < 1) { + log_warn(LD_DIR, "Impossibly short router status"); + goto err; + } + tok = find_by_keyword(tokens, K_R); + tor_assert(tok->n_args >= 7); /* guaranteed by GE(7) in K_R setup */ + if (flav == FLAV_NS) { + if (tok->n_args < 8) { + log_warn(LD_DIR, "Too few arguments to r"); + goto err; + } + } else if (flav == FLAV_MICRODESC) { + offset = -1; /* There is no descriptor digest in an md consensus r line */ + } + + if (vote_rs) { + rs = &vote_rs->status; + } else { + rs = tor_malloc_zero(sizeof(routerstatus_t)); + } + + if (!is_legal_nickname(tok->args[0])) { + log_warn(LD_DIR, + "Invalid nickname %s in router status; skipping.", + escaped(tok->args[0])); + goto err; + } + strlcpy(rs->nickname, tok->args[0], sizeof(rs->nickname)); + + if (digest_from_base64(rs->identity_digest, tok->args[1])) { + log_warn(LD_DIR, "Error decoding identity digest %s", + escaped(tok->args[1])); + goto err; + } + + if (flav == FLAV_NS) { + if (digest_from_base64(rs->descriptor_digest, tok->args[2])) { + log_warn(LD_DIR, "Error decoding descriptor digest %s", + escaped(tok->args[2])); + goto err; + } + } + + if (tor_snprintf(timebuf, sizeof(timebuf), "%s %s", + tok->args[3+offset], tok->args[4+offset]) < 0 || + parse_iso_time(timebuf, &rs->published_on)<0) { + log_warn(LD_DIR, "Error parsing time '%s %s' [%d %d]", + tok->args[3+offset], tok->args[4+offset], + offset, (int)flav); + goto err; + } + + if (tor_inet_aton(tok->args[5+offset], &in) == 0) { + log_warn(LD_DIR, "Error parsing router address in network-status %s", + escaped(tok->args[5+offset])); + goto err; + } + rs->addr = ntohl(in.s_addr); + + rs->or_port = (uint16_t) tor_parse_long(tok->args[6+offset], + 10,0,65535,NULL,NULL); + rs->dir_port = (uint16_t) tor_parse_long(tok->args[7+offset], + 10,0,65535,NULL,NULL); + + { + smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); + if (a_lines) { + find_single_ipv6_orport(a_lines, &rs->ipv6_addr, &rs->ipv6_orport); + smartlist_free(a_lines); + } + } + + tok = find_opt_by_keyword(tokens, K_S); + if (tok && vote) { + int i; + vote_rs->flags = 0; + for (i=0; i < tok->n_args; ++i) { + int p = smartlist_string_pos(vote->known_flags, tok->args[i]); + if (p >= 0) { + vote_rs->flags |= (UINT64_C(1)<<p); + } else { + log_warn(LD_DIR, "Flags line had a flag %s not listed in known_flags.", + escaped(tok->args[i])); + goto err; + } + } + } else if (tok) { + /* This is a consensus, not a vote. */ + int i; + for (i=0; i < tok->n_args; ++i) { + if (!strcmp(tok->args[i], "Exit")) + rs->is_exit = 1; + else if (!strcmp(tok->args[i], "Stable")) + rs->is_stable = 1; + else if (!strcmp(tok->args[i], "Fast")) + rs->is_fast = 1; + else if (!strcmp(tok->args[i], "Running")) + rs->is_flagged_running = 1; + else if (!strcmp(tok->args[i], "Named")) + rs->is_named = 1; + else if (!strcmp(tok->args[i], "Valid")) + rs->is_valid = 1; + else if (!strcmp(tok->args[i], "Guard")) + rs->is_possible_guard = 1; + else if (!strcmp(tok->args[i], "BadExit")) + rs->is_bad_exit = 1; + else if (!strcmp(tok->args[i], "Authority")) + rs->is_authority = 1; + else if (!strcmp(tok->args[i], "Unnamed") && + consensus_method >= 2) { + /* Unnamed is computed right by consensus method 2 and later. */ + rs->is_unnamed = 1; + } else if (!strcmp(tok->args[i], "HSDir")) { + rs->is_hs_dir = 1; + } else if (!strcmp(tok->args[i], "V2Dir")) { + rs->is_v2_dir = 1; + } + } + /* These are implied true by having been included in a consensus made + * with a given method */ + rs->is_flagged_running = 1; /* Starting with consensus method 4. */ + rs->is_valid = 1; /* Starting with consensus method 24. */ + } + { + const char *protocols = NULL, *version = NULL; + if ((tok = find_opt_by_keyword(tokens, K_PROTO))) { + tor_assert(tok->n_args == 1); + protocols = tok->args[0]; + } + if ((tok = find_opt_by_keyword(tokens, K_V))) { + tor_assert(tok->n_args == 1); + version = tok->args[0]; + if (vote_rs) { + vote_rs->version = tor_strdup(tok->args[0]); + } + } + + summarize_protover_flags(&rs->pv, protocols, version); + } + + /* handle weighting/bandwidth info */ + if ((tok = find_opt_by_keyword(tokens, K_W))) { + int i; + for (i=0; i < tok->n_args; ++i) { + if (!strcmpstart(tok->args[i], "Bandwidth=")) { + int ok; + rs->bandwidth_kb = + (uint32_t)tor_parse_ulong(strchr(tok->args[i], '=')+1, + 10, 0, UINT32_MAX, + &ok, NULL); + if (!ok) { + log_warn(LD_DIR, "Invalid Bandwidth %s", escaped(tok->args[i])); + goto err; + } + rs->has_bandwidth = 1; + } else if (!strcmpstart(tok->args[i], "Measured=") && vote_rs) { + int ok; + vote_rs->measured_bw_kb = + (uint32_t)tor_parse_ulong(strchr(tok->args[i], '=')+1, + 10, 0, UINT32_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_DIR, "Invalid Measured Bandwidth %s", + escaped(tok->args[i])); + goto err; + } + vote_rs->has_measured_bw = 1; + vote->has_measured_bws = 1; + } else if (!strcmpstart(tok->args[i], "Unmeasured=1")) { + rs->bw_is_unmeasured = 1; + } else if (!strcmpstart(tok->args[i], "GuardFraction=")) { + if (routerstatus_parse_guardfraction(tok->args[i], + vote, vote_rs, rs) < 0) { + goto err; + } + } + } + } + + /* parse exit policy summaries */ + if ((tok = find_opt_by_keyword(tokens, K_P))) { + tor_assert(tok->n_args == 1); + if (strcmpstart(tok->args[0], "accept ") && + strcmpstart(tok->args[0], "reject ")) { + log_warn(LD_DIR, "Unknown exit policy summary type %s.", + escaped(tok->args[0])); + goto err; + } + /* XXX weasel: parse this into ports and represent them somehow smart, + * maybe not here but somewhere on if we need it for the client. + * we should still parse it here to check it's valid tho. + */ + rs->exitsummary = tor_strdup(tok->args[0]); + rs->has_exitsummary = 1; + } + + if (vote_rs) { + SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, t) { + if (t->tp == K_M && t->n_args) { + vote_microdesc_hash_t *line = + tor_malloc(sizeof(vote_microdesc_hash_t)); + line->next = vote_rs->microdesc; + line->microdesc_hash_line = tor_strdup(t->args[0]); + vote_rs->microdesc = line; + } + if (t->tp == K_ID) { + tor_assert(t->n_args >= 2); + if (!strcmp(t->args[0], "ed25519")) { + vote_rs->has_ed25519_listing = 1; + if (strcmp(t->args[1], "none") && + digest256_from_base64((char*)vote_rs->ed25519_id, + t->args[1])<0) { + log_warn(LD_DIR, "Bogus ed25519 key in networkstatus vote"); + goto err; + } + } + } + if (t->tp == K_PROTO) { + tor_assert(t->n_args == 1); + vote_rs->protocols = tor_strdup(t->args[0]); + } + } SMARTLIST_FOREACH_END(t); + } else if (flav == FLAV_MICRODESC) { + tok = find_opt_by_keyword(tokens, K_M); + if (tok) { + tor_assert(tok->n_args); + if (digest256_from_base64(rs->descriptor_digest, tok->args[0])) { + log_warn(LD_DIR, "Error decoding microdescriptor digest %s", + escaped(tok->args[0])); + goto err; + } + } else { + log_info(LD_BUG, "Found an entry in networkstatus with no " + "microdescriptor digest. (Router %s ($%s) at %s:%d.)", + rs->nickname, hex_str(rs->identity_digest, DIGEST_LEN), + fmt_addr32(rs->addr), rs->or_port); + } + } + + if (!strcasecmp(rs->nickname, UNNAMED_ROUTER_NICKNAME)) + rs->is_named = 0; + + goto done; + err: + dump_desc(s_dup, "routerstatus entry"); + if (rs && !vote_rs) + routerstatus_free(rs); + rs = NULL; + done: + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_clear(tokens); + if (area) { + DUMP_AREA(area, "routerstatus entry"); + memarea_clear(area); + } + *s = eos; + + return rs; +} + +int +compare_vote_routerstatus_entries(const void **_a, const void **_b) +{ + const vote_routerstatus_t *a = *_a, *b = *_b; + return fast_memcmp(a->status.identity_digest, b->status.identity_digest, + DIGEST_LEN); +} + +/** Verify the bandwidth weights of a network status document */ +int +networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) +{ + int64_t G=0, M=0, E=0, D=0, T=0; + double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed; + double Gtotal=0, Mtotal=0, Etotal=0; + const char *casename = NULL; + int valid = 1; + (void) consensus_method; + + const int64_t weight_scale = networkstatus_get_weight_scale_param(ns); + tor_assert(weight_scale >= 1); + Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1); + Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1); + Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1); + Wmg = networkstatus_get_bw_weight(ns, "Wmg", -1); + Wmm = networkstatus_get_bw_weight(ns, "Wmm", -1); + Wme = networkstatus_get_bw_weight(ns, "Wme", -1); + Wmd = networkstatus_get_bw_weight(ns, "Wmd", -1); + Weg = networkstatus_get_bw_weight(ns, "Weg", -1); + Wem = networkstatus_get_bw_weight(ns, "Wem", -1); + Wee = networkstatus_get_bw_weight(ns, "Wee", -1); + Wed = networkstatus_get_bw_weight(ns, "Wed", -1); + + if (Wgg<0 || Wgm<0 || Wgd<0 || Wmg<0 || Wmm<0 || Wme<0 || Wmd<0 || Weg<0 + || Wem<0 || Wee<0 || Wed<0) { + log_warn(LD_BUG, "No bandwidth weights produced in consensus!"); + return 0; + } + + // First, sanity check basic summing properties that hold for all cases + // We use > 1 as the check for these because they are computed as integers. + // Sometimes there are rounding errors. + if (fabs(Wmm - weight_scale) > 1) { + log_warn(LD_BUG, "Wmm=%f != %"PRId64, + Wmm, (weight_scale)); + valid = 0; + } + + if (fabs(Wem - Wee) > 1) { + log_warn(LD_BUG, "Wem=%f != Wee=%f", Wem, Wee); + valid = 0; + } + + if (fabs(Wgm - Wgg) > 1) { + log_warn(LD_BUG, "Wgm=%f != Wgg=%f", Wgm, Wgg); + valid = 0; + } + + if (fabs(Weg - Wed) > 1) { + log_warn(LD_BUG, "Wed=%f != Weg=%f", Wed, Weg); + valid = 0; + } + + if (fabs(Wgg + Wmg - weight_scale) > 0.001*weight_scale) { + log_warn(LD_BUG, "Wgg=%f != %"PRId64" - Wmg=%f", Wgg, + (weight_scale), Wmg); + valid = 0; + } + + if (fabs(Wee + Wme - weight_scale) > 0.001*weight_scale) { + log_warn(LD_BUG, "Wee=%f != %"PRId64" - Wme=%f", Wee, + (weight_scale), Wme); + valid = 0; + } + + if (fabs(Wgd + Wmd + Wed - weight_scale) > 0.001*weight_scale) { + log_warn(LD_BUG, "Wgd=%f + Wmd=%f + Wed=%f != %"PRId64, + Wgd, Wmd, Wed, (weight_scale)); + valid = 0; + } + + Wgg /= weight_scale; + Wgm /= weight_scale; (void) Wgm; // unused from here on. + Wgd /= weight_scale; + + Wmg /= weight_scale; + Wmm /= weight_scale; + Wme /= weight_scale; + Wmd /= weight_scale; + + Weg /= weight_scale; (void) Weg; // unused from here on. + Wem /= weight_scale; (void) Wem; // unused from here on. + Wee /= weight_scale; + Wed /= weight_scale; + + // Then, gather G, M, E, D, T to determine case + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + int is_exit = 0; + /* Bug #2203: Don't count bad exits as exits for balancing */ + is_exit = rs->is_exit && !rs->is_bad_exit; + if (rs->has_bandwidth) { + T += rs->bandwidth_kb; + if (is_exit && rs->is_possible_guard) { + D += rs->bandwidth_kb; + Gtotal += Wgd*rs->bandwidth_kb; + Mtotal += Wmd*rs->bandwidth_kb; + Etotal += Wed*rs->bandwidth_kb; + } else if (is_exit) { + E += rs->bandwidth_kb; + Mtotal += Wme*rs->bandwidth_kb; + Etotal += Wee*rs->bandwidth_kb; + } else if (rs->is_possible_guard) { + G += rs->bandwidth_kb; + Gtotal += Wgg*rs->bandwidth_kb; + Mtotal += Wmg*rs->bandwidth_kb; + } else { + M += rs->bandwidth_kb; + Mtotal += Wmm*rs->bandwidth_kb; + } + } else { + log_warn(LD_BUG, "Missing consensus bandwidth for router %s", + routerstatus_describe(rs)); + } + } SMARTLIST_FOREACH_END(rs); + + // Finally, check equality conditions depending upon case 1, 2 or 3 + // Full equality cases: 1, 3b + // Partial equality cases: 2b (E=G), 3a (M=E) + // Fully unknown: 2a + if (3*E >= T && 3*G >= T) { + // Case 1: Neither are scarce + casename = "Case 1"; + if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Etotal %f != Mtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Etotal, Mtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Etotal %f != Gtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Etotal, Gtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Mtotal %f != Gtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Mtotal, Gtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + } else if (3*E < T && 3*G < T) { + int64_t R = MIN(E, G); + int64_t S = MAX(E, G); + /* + * Case 2: Both Guards and Exits are scarce + * Balance D between E and G, depending upon + * D capacity and scarcity. Devote no extra + * bandwidth to middle nodes. + */ + if (R+D < S) { // Subcase a + double Rtotal, Stotal; + if (E < G) { + Rtotal = Etotal; + Stotal = Gtotal; + } else { + Rtotal = Gtotal; + Stotal = Etotal; + } + casename = "Case 2a"; + // Rtotal < Stotal + if (Rtotal > Stotal) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Rtotal %f > Stotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Rtotal, Stotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + // Rtotal < T/3 + if (3*Rtotal > T) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: 3*Rtotal %f > T " + "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64 + " D=%"PRId64" T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Rtotal*3, (T), + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + // Stotal < T/3 + if (3*Stotal > T) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: 3*Stotal %f > T " + "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64 + " D=%"PRId64" T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Stotal*3, (T), + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + // Mtotal > T/3 + if (3*Mtotal < T) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: 3*Mtotal %f < T " + "%"PRId64". " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Mtotal*3, (T), + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + } else { // Subcase b: R+D > S + casename = "Case 2b"; + + /* Check the rare-M redirect case. */ + if (D != 0 && 3*M < T) { + casename = "Case 2b (balanced)"; + if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Etotal %f != Mtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Etotal, Mtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Etotal %f != Gtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Etotal, Gtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Mtotal %f != Gtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Mtotal, Gtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + } else { + if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Etotal %f != Gtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Etotal, Gtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + } + } + } else { // if (E < T/3 || G < T/3) { + int64_t S = MIN(E, G); + int64_t NS = MAX(E, G); + if (3*(S+D) < T) { // Subcase a: + double Stotal; + double NStotal; + if (G < E) { + casename = "Case 3a (G scarce)"; + Stotal = Gtotal; + NStotal = Etotal; + } else { // if (G >= E) { + casename = "Case 3a (E scarce)"; + NStotal = Gtotal; + Stotal = Etotal; + } + // Stotal < T/3 + if (3*Stotal > T) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: 3*Stotal %f > T " + "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64 + " D=%"PRId64" T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Stotal*3, (T), + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + if (NS >= M) { + if (fabs(NStotal-Mtotal) > 0.01*MAX(NStotal,Mtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: NStotal %f != Mtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, NStotal, Mtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + } else { + // if NS < M, NStotal > T/3 because only one of G or E is scarce + if (3*NStotal < T) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: 3*NStotal %f < T " + "%"PRId64". G=%"PRId64" M=%"PRId64 + " E=%"PRId64" D=%"PRId64" T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, NStotal*3, (T), + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + } + } else { // Subcase b: S+D >= T/3 + casename = "Case 3b"; + if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Etotal %f != Mtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Etotal, Mtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Etotal %f != Gtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Etotal, Gtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) { + log_warn(LD_DIR, + "Bw Weight Failure for %s: Mtotal %f != Gtotal %f. " + "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64 + " T=%"PRId64". " + "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f", + casename, Mtotal, Gtotal, + (G), (M), (E), + (D), (T), + Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed); + valid = 0; + } + } + } + + if (valid) + log_notice(LD_DIR, "Bandwidth-weight %s is verified and valid.", + casename); + + return valid; +} + +/** Check if a shared random value of type <b>srv_type</b> is in + * <b>tokens</b>. If there is, parse it and set it to <b>srv_out</b>. Return + * -1 on failure, 0 on success. The resulting srv is allocated on the heap and + * it's the responsibility of the caller to free it. */ +static int +extract_one_srv(smartlist_t *tokens, directory_keyword srv_type, + sr_srv_t **srv_out) +{ + int ret = -1; + directory_token_t *tok; + sr_srv_t *srv = NULL; + smartlist_t *chunks; + + tor_assert(tokens); + + chunks = smartlist_new(); + tok = find_opt_by_keyword(tokens, srv_type); + if (!tok) { + /* That's fine, no SRV is allowed. */ + ret = 0; + goto end; + } + for (int i = 0; i < tok->n_args; i++) { + smartlist_add(chunks, tok->args[i]); + } + srv = sr_parse_srv(chunks); + if (srv == NULL) { + log_warn(LD_DIR, "SR: Unparseable SRV %s", escaped(tok->object_body)); + goto end; + } + /* All is good. */ + *srv_out = srv; + ret = 0; + end: + smartlist_free(chunks); + return ret; +} + +/** Extract any shared random values found in <b>tokens</b> and place them in + * the networkstatus <b>ns</b>. */ +static void +extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens) +{ + const char *voter_identity; + networkstatus_voter_info_t *voter; + + tor_assert(ns); + tor_assert(tokens); + /* Can be only one of them else code flow. */ + tor_assert(ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS); + + if (ns->type == NS_TYPE_VOTE) { + voter = smartlist_get(ns->voters, 0); + tor_assert(voter); + voter_identity = hex_str(voter->identity_digest, + sizeof(voter->identity_digest)); + } else { + /* Consensus has multiple voters so no specific voter. */ + voter_identity = "consensus"; + } + + /* We extract both, and on error everything is stopped because it means + * the vote is malformed for the shared random value(s). */ + if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) { + log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s", + voter_identity); + /* Maybe we have a chance with the current SRV so let's try it anyway. */ + } + if (extract_one_srv(tokens, K_CURRENT_SRV, &ns->sr_info.current_srv) < 0) { + log_warn(LD_DIR, "SR: Unable to parse current SRV from %s", + voter_identity); + } +} + +/** Parse a v3 networkstatus vote, opinion, or consensus (depending on + * ns_type), from <b>s</b>, and return the result. Return NULL on failure. */ +networkstatus_t * - networkstatus_parse_vote_from_string(const char *s, const char **eos_out, ++networkstatus_parse_vote_from_string(const char *s, ++ size_t s_len, ++ const char **eos_out, + networkstatus_type_t ns_type) +{ + smartlist_t *tokens = smartlist_new(); + smartlist_t *rs_tokens = NULL, *footer_tokens = NULL; + networkstatus_voter_info_t *voter = NULL; + networkstatus_t *ns = NULL; + common_digests_t ns_digests; + uint8_t sha3_as_signed[DIGEST256_LEN]; + const char *cert, *end_of_header, *end_of_footer, *s_dup = s; + directory_token_t *tok; + struct in_addr in; + int i, inorder, n_signatures = 0; + memarea_t *area = NULL, *rs_area = NULL; + consensus_flavor_t flav = FLAV_NS; + char *last_kwd=NULL; ++ const char *eos = s + s_len; + + tor_assert(s); + + if (eos_out) + *eos_out = NULL; + - if (router_get_networkstatus_v3_hashes(s, &ns_digests) || - router_get_networkstatus_v3_sha3_as_signed(sha3_as_signed, s)<0) { ++ if (router_get_networkstatus_v3_hashes(s, s_len, &ns_digests) || ++ router_get_networkstatus_v3_sha3_as_signed(sha3_as_signed, ++ s, s_len)<0) { + log_warn(LD_DIR, "Unable to compute digest of network-status"); + goto err; + } + + area = memarea_new(); - end_of_header = find_start_of_next_routerstatus(s); ++ end_of_header = find_start_of_next_routerstatus(s, eos); + if (tokenize_string(area, s, end_of_header, tokens, + (ns_type == NS_TYPE_CONSENSUS) ? + networkstatus_consensus_token_table : + networkstatus_token_table, 0)) { + log_warn(LD_DIR, "Error tokenizing network-status header"); + goto err; + } + + ns = tor_malloc_zero(sizeof(networkstatus_t)); + memcpy(&ns->digests, &ns_digests, sizeof(ns_digests)); + memcpy(&ns->digest_sha3_as_signed, sha3_as_signed, sizeof(sha3_as_signed)); + + tok = find_by_keyword(tokens, K_NETWORK_STATUS_VERSION); + tor_assert(tok); + if (tok->n_args > 1) { + int flavor = networkstatus_parse_flavor_name(tok->args[1]); + if (flavor < 0) { + log_warn(LD_DIR, "Can't parse document with unknown flavor %s", + escaped(tok->args[1])); + goto err; + } + ns->flavor = flav = flavor; + } + if (flav != FLAV_NS && ns_type != NS_TYPE_CONSENSUS) { + log_warn(LD_DIR, "Flavor found on non-consensus networkstatus."); + goto err; + } + + if (ns_type != NS_TYPE_CONSENSUS) { + const char *end_of_cert = NULL; - if (!(cert = strstr(s, "\ndir-key-certificate-version"))) ++ if (!(cert = tor_memstr(s, end_of_header - s, ++ "\ndir-key-certificate-version"))) + goto err; + ++cert; - ns->cert = authority_cert_parse_from_string(cert, &end_of_cert); ++ ns->cert = authority_cert_parse_from_string(cert, end_of_header - cert, ++ &end_of_cert); + if (!ns->cert || !end_of_cert || end_of_cert > end_of_header) + goto err; + } + + tok = find_by_keyword(tokens, K_VOTE_STATUS); + tor_assert(tok->n_args); + if (!strcmp(tok->args[0], "vote")) { + ns->type = NS_TYPE_VOTE; + } else if (!strcmp(tok->args[0], "consensus")) { + ns->type = NS_TYPE_CONSENSUS; + } else if (!strcmp(tok->args[0], "opinion")) { + ns->type = NS_TYPE_OPINION; + } else { + log_warn(LD_DIR, "Unrecognized vote status %s in network-status", + escaped(tok->args[0])); + goto err; + } + if (ns_type != ns->type) { + log_warn(LD_DIR, "Got the wrong kind of v3 networkstatus."); + goto err; + } + + if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_OPINION) { + tok = find_by_keyword(tokens, K_PUBLISHED); + if (parse_iso_time(tok->args[0], &ns->published)) + goto err; + + ns->supported_methods = smartlist_new(); + tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHODS); + if (tok) { + for (i=0; i < tok->n_args; ++i) + smartlist_add_strdup(ns->supported_methods, tok->args[i]); + } else { + smartlist_add_strdup(ns->supported_methods, "1"); + } + } else { + tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHOD); + if (tok) { + int num_ok; + ns->consensus_method = (int)tor_parse_long(tok->args[0], 10, 1, INT_MAX, + &num_ok, NULL); + if (!num_ok) + goto err; + } else { + ns->consensus_method = 1; + } + } + + if ((tok = find_opt_by_keyword(tokens, K_RECOMMENDED_CLIENT_PROTOCOLS))) + ns->recommended_client_protocols = tor_strdup(tok->args[0]); + if ((tok = find_opt_by_keyword(tokens, K_RECOMMENDED_RELAY_PROTOCOLS))) + ns->recommended_relay_protocols = tor_strdup(tok->args[0]); + if ((tok = find_opt_by_keyword(tokens, K_REQUIRED_CLIENT_PROTOCOLS))) + ns->required_client_protocols = tor_strdup(tok->args[0]); + if ((tok = find_opt_by_keyword(tokens, K_REQUIRED_RELAY_PROTOCOLS))) + ns->required_relay_protocols = tor_strdup(tok->args[0]); + + tok = find_by_keyword(tokens, K_VALID_AFTER); + if (parse_iso_time(tok->args[0], &ns->valid_after)) + goto err; + + tok = find_by_keyword(tokens, K_FRESH_UNTIL); + if (parse_iso_time(tok->args[0], &ns->fresh_until)) + goto err; + + tok = find_by_keyword(tokens, K_VALID_UNTIL); + if (parse_iso_time(tok->args[0], &ns->valid_until)) + goto err; + + tok = find_by_keyword(tokens, K_VOTING_DELAY); + tor_assert(tok->n_args >= 2); + { + int ok; + ns->vote_seconds = + (int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &ok, NULL); + if (!ok) + goto err; + ns->dist_seconds = + (int) tor_parse_long(tok->args[1], 10, 0, INT_MAX, &ok, NULL); + if (!ok) + goto err; + } + if (ns->valid_after + + (get_options()->TestingTorNetwork ? + MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) > ns->fresh_until) { + log_warn(LD_DIR, "Vote/consensus freshness interval is too short"); + goto err; + } + if (ns->valid_after + + (get_options()->TestingTorNetwork ? + MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL)*2 > ns->valid_until) { + log_warn(LD_DIR, "Vote/consensus liveness interval is too short"); + goto err; + } + if (ns->vote_seconds < MIN_VOTE_SECONDS) { + log_warn(LD_DIR, "Vote seconds is too short"); + goto err; + } + if (ns->dist_seconds < MIN_DIST_SECONDS) { + log_warn(LD_DIR, "Dist seconds is too short"); + goto err; + } + + if ((tok = find_opt_by_keyword(tokens, K_CLIENT_VERSIONS))) { + ns->client_versions = tor_strdup(tok->args[0]); + } + if ((tok = find_opt_by_keyword(tokens, K_SERVER_VERSIONS))) { + ns->server_versions = tor_strdup(tok->args[0]); + } + + { + smartlist_t *package_lst = find_all_by_keyword(tokens, K_PACKAGE); + ns->package_lines = smartlist_new(); + if (package_lst) { + SMARTLIST_FOREACH(package_lst, directory_token_t *, t, + smartlist_add_strdup(ns->package_lines, t->args[0])); + } + smartlist_free(package_lst); + } + + tok = find_by_keyword(tokens, K_KNOWN_FLAGS); + ns->known_flags = smartlist_new(); + inorder = 1; + for (i = 0; i < tok->n_args; ++i) { + smartlist_add_strdup(ns->known_flags, tok->args[i]); + if (i>0 && strcmp(tok->args[i-1], tok->args[i])>= 0) { + log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]); + inorder = 0; + } + } + if (!inorder) { + log_warn(LD_DIR, "known-flags not in order"); + goto err; + } + if (ns->type != NS_TYPE_CONSENSUS && + smartlist_len(ns->known_flags) > MAX_KNOWN_FLAGS_IN_VOTE) { + /* If we allowed more than 64 flags in votes, then parsing them would make + * us invoke undefined behavior whenever we used 1<<flagnum to do a + * bit-shift. This is only for votes and opinions: consensus users don't + * care about flags they don't recognize, and so don't build a bitfield + * for them. */ + log_warn(LD_DIR, "Too many known-flags in consensus vote or opinion"); + goto err; + } + + tok = find_opt_by_keyword(tokens, K_PARAMS); + if (tok) { + int any_dups = 0; + inorder = 1; + ns->net_params = smartlist_new(); + for (i = 0; i < tok->n_args; ++i) { + int ok=0; + char *eq = strchr(tok->args[i], '='); + size_t eq_pos; + if (!eq) { + log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i])); + goto err; + } + eq_pos = eq-tok->args[i]; + tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i])); + goto err; + } + if (i > 0 && strcmp(tok->args[i-1], tok->args[i]) >= 0) { + log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]); + inorder = 0; + } + if (last_kwd && eq_pos == strlen(last_kwd) && + fast_memeq(last_kwd, tok->args[i], eq_pos)) { + log_warn(LD_DIR, "Duplicate value for %s parameter", + escaped(tok->args[i])); + any_dups = 1; + } + tor_free(last_kwd); + last_kwd = tor_strndup(tok->args[i], eq_pos); + smartlist_add_strdup(ns->net_params, tok->args[i]); + } + if (!inorder) { + log_warn(LD_DIR, "params not in order"); + goto err; + } + if (any_dups) { + log_warn(LD_DIR, "Duplicate in parameters"); + goto err; + } + } + + ns->voters = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) { + tok = _tok; + if (tok->tp == K_DIR_SOURCE) { + tor_assert(tok->n_args >= 6); + + if (voter) + smartlist_add(ns->voters, voter); + voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t)); + voter->sigs = smartlist_new(); + if (ns->type != NS_TYPE_CONSENSUS) + memcpy(voter->vote_digest, ns_digests.d[DIGEST_SHA1], DIGEST_LEN); + + voter->nickname = tor_strdup(tok->args[0]); + if (strlen(tok->args[1]) != HEX_DIGEST_LEN || + base16_decode(voter->identity_digest, sizeof(voter->identity_digest), + tok->args[1], HEX_DIGEST_LEN) + != sizeof(voter->identity_digest)) { + log_warn(LD_DIR, "Error decoding identity digest %s in " + "network-status document.", escaped(tok->args[1])); + goto err; + } + if (ns->type != NS_TYPE_CONSENSUS && + tor_memneq(ns->cert->cache_info.identity_digest, + voter->identity_digest, DIGEST_LEN)) { + log_warn(LD_DIR,"Mismatch between identities in certificate and vote"); + goto err; + } + if (ns->type != NS_TYPE_CONSENSUS) { + if (authority_cert_is_blacklisted(ns->cert)) { + log_warn(LD_DIR, "Rejecting vote signature made with blacklisted " + "signing key %s", + hex_str(ns->cert->signing_key_digest, DIGEST_LEN)); + goto err; + } + } + voter->address = tor_strdup(tok->args[2]); + if (!tor_inet_aton(tok->args[3], &in)) { + log_warn(LD_DIR, "Error decoding IP address %s in network-status.", + escaped(tok->args[3])); + goto err; + } + voter->addr = ntohl(in.s_addr); + int ok; + voter->dir_port = (uint16_t) + tor_parse_long(tok->args[4], 10, 0, 65535, &ok, NULL); + if (!ok) + goto err; + voter->or_port = (uint16_t) + tor_parse_long(tok->args[5], 10, 0, 65535, &ok, NULL); + if (!ok) + goto err; + } else if (tok->tp == K_CONTACT) { + if (!voter || voter->contact) { + log_warn(LD_DIR, "contact element is out of place."); + goto err; + } + voter->contact = tor_strdup(tok->args[0]); + } else if (tok->tp == K_VOTE_DIGEST) { + tor_assert(ns->type == NS_TYPE_CONSENSUS); + tor_assert(tok->n_args >= 1); + if (!voter || ! tor_digest_is_zero(voter->vote_digest)) { + log_warn(LD_DIR, "vote-digest element is out of place."); + goto err; + } + if (strlen(tok->args[0]) != HEX_DIGEST_LEN || + base16_decode(voter->vote_digest, sizeof(voter->vote_digest), + tok->args[0], HEX_DIGEST_LEN) + != sizeof(voter->vote_digest)) { + log_warn(LD_DIR, "Error decoding vote digest %s in " + "network-status consensus.", escaped(tok->args[0])); + goto err; + } + } + } SMARTLIST_FOREACH_END(_tok); + if (voter) { + smartlist_add(ns->voters, voter); + voter = NULL; + } + if (smartlist_len(ns->voters) == 0) { + log_warn(LD_DIR, "Missing dir-source elements in a networkstatus."); + goto err; + } else if (ns->type != NS_TYPE_CONSENSUS && smartlist_len(ns->voters) != 1) { + log_warn(LD_DIR, "Too many dir-source elements in a vote networkstatus."); + goto err; + } + + if (ns->type != NS_TYPE_CONSENSUS && + (tok = find_opt_by_keyword(tokens, K_LEGACY_DIR_KEY))) { + int bad = 1; + if (strlen(tok->args[0]) == HEX_DIGEST_LEN) { + networkstatus_voter_info_t *voter_0 = smartlist_get(ns->voters, 0); + if (base16_decode(voter_0->legacy_id_digest, DIGEST_LEN, + tok->args[0], HEX_DIGEST_LEN) != DIGEST_LEN) + bad = 1; + else + bad = 0; + } + if (bad) { + log_warn(LD_DIR, "Invalid legacy key digest %s on vote.", + escaped(tok->args[0])); + } + } + + /* If this is a vote document, check if information about the shared + randomness protocol is included, and extract it. */ + if (ns->type == NS_TYPE_VOTE) { + dirvote_parse_sr_commits(ns, tokens); + } + /* For both a vote and consensus, extract the shared random values. */ + if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS) { + extract_shared_random_srvs(ns, tokens); + } + + /* Parse routerstatus lines. */ + rs_tokens = smartlist_new(); + rs_area = memarea_new(); + s = end_of_header; + ns->routerstatus_list = smartlist_new(); + - while (!strcmpstart(s, "r ")) { ++ while (eos - s >= 2 && fast_memeq(s, "r ", 2)) { + if (ns->type != NS_TYPE_CONSENSUS) { + vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - if (routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ns, ++ if (routerstatus_parse_entry_from_string(rs_area, &s, eos, rs_tokens, ns, + rs, 0, 0)) { + smartlist_add(ns->routerstatus_list, rs); + } else { + vote_routerstatus_free(rs); + } + } else { + routerstatus_t *rs; - if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ++ if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, eos, ++ rs_tokens, + NULL, NULL, + ns->consensus_method, + flav))) { + /* Use exponential-backoff scheduling when downloading microdescs */ + smartlist_add(ns->routerstatus_list, rs); + } + } + } + for (i = 1; i < smartlist_len(ns->routerstatus_list); ++i) { + routerstatus_t *rs1, *rs2; + if (ns->type != NS_TYPE_CONSENSUS) { + vote_routerstatus_t *a = smartlist_get(ns->routerstatus_list, i-1); + vote_routerstatus_t *b = smartlist_get(ns->routerstatus_list, i); + rs1 = &a->status; rs2 = &b->status; + } else { + rs1 = smartlist_get(ns->routerstatus_list, i-1); + rs2 = smartlist_get(ns->routerstatus_list, i); + } + if (fast_memcmp(rs1->identity_digest, rs2->identity_digest, DIGEST_LEN) + >= 0) { + log_warn(LD_DIR, "Networkstatus entries not sorted by identity digest"); + goto err; + } + } + if (ns_type != NS_TYPE_CONSENSUS) { + digest256map_t *ed_id_map = digest256map_new(); + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, vote_routerstatus_t *, + vrs) { + if (! vrs->has_ed25519_listing || + tor_mem_is_zero((const char *)vrs->ed25519_id, DIGEST256_LEN)) + continue; + if (digest256map_get(ed_id_map, vrs->ed25519_id) != NULL) { + log_warn(LD_DIR, "Vote networkstatus ed25519 identities were not " + "unique"); + digest256map_free(ed_id_map, NULL); + goto err; + } + digest256map_set(ed_id_map, vrs->ed25519_id, (void*)1); + } SMARTLIST_FOREACH_END(vrs); + digest256map_free(ed_id_map, NULL); + } + + /* Parse footer; check signature. */ + footer_tokens = smartlist_new(); - if ((end_of_footer = strstr(s, "\nnetwork-status-version "))) ++ if ((end_of_footer = tor_memstr(s, eos-s, "\nnetwork-status-version "))) + ++end_of_footer; + else - end_of_footer = s + strlen(s); ++ end_of_footer = eos; + if (tokenize_string(area,s, end_of_footer, footer_tokens, + networkstatus_vote_footer_token_table, 0)) { + log_warn(LD_DIR, "Error tokenizing network-status vote footer."); + goto err; + } + + { + int found_sig = 0; + SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) { + tok = _tok; + if (tok->tp == K_DIRECTORY_SIGNATURE) + found_sig = 1; + else if (found_sig) { + log_warn(LD_DIR, "Extraneous token after first directory-signature"); + goto err; + } + } SMARTLIST_FOREACH_END(_tok); + } + + if ((tok = find_opt_by_keyword(footer_tokens, K_DIRECTORY_FOOTER))) { + if (tok != smartlist_get(footer_tokens, 0)) { + log_warn(LD_DIR, "Misplaced directory-footer token"); + goto err; + } + } + + tok = find_opt_by_keyword(footer_tokens, K_BW_WEIGHTS); + if (tok) { + ns->weight_params = smartlist_new(); + for (i = 0; i < tok->n_args; ++i) { + int ok=0; + char *eq = strchr(tok->args[i], '='); + if (!eq) { + log_warn(LD_DIR, "Bad element '%s' in weight params", + escaped(tok->args[i])); + goto err; + } + tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i])); + goto err; + } + smartlist_add_strdup(ns->weight_params, tok->args[i]); + } + } + + SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) { + char declared_identity[DIGEST_LEN]; + networkstatus_voter_info_t *v; + document_signature_t *sig; + const char *id_hexdigest = NULL; + const char *sk_hexdigest = NULL; + digest_algorithm_t alg = DIGEST_SHA1; + tok = _tok; + if (tok->tp != K_DIRECTORY_SIGNATURE) + continue; + tor_assert(tok->n_args >= 2); + if (tok->n_args == 2) { + id_hexdigest = tok->args[0]; + sk_hexdigest = tok->args[1]; + } else { + const char *algname = tok->args[0]; + int a; + id_hexdigest = tok->args[1]; + sk_hexdigest = tok->args[2]; + a = crypto_digest_algorithm_parse_name(algname); + if (a<0) { + log_warn(LD_DIR, "Unknown digest algorithm %s; skipping", + escaped(algname)); + continue; + } + alg = a; + } + + if (!tok->object_type || + strcmp(tok->object_type, "SIGNATURE") || + tok->object_size < 128 || tok->object_size > 512) { + log_warn(LD_DIR, "Bad object type or length on directory-signature"); + goto err; + } + + if (strlen(id_hexdigest) != HEX_DIGEST_LEN || + base16_decode(declared_identity, sizeof(declared_identity), + id_hexdigest, HEX_DIGEST_LEN) + != sizeof(declared_identity)) { + log_warn(LD_DIR, "Error decoding declared identity %s in " + "network-status document.", escaped(id_hexdigest)); + goto err; + } + if (!(v = networkstatus_get_voter_by_id(ns, declared_identity))) { + log_warn(LD_DIR, "ID on signature on network-status document does " + "not match any declared directory source."); + goto err; + } + sig = tor_malloc_zero(sizeof(document_signature_t)); + memcpy(sig->identity_digest, v->identity_digest, DIGEST_LEN); + sig->alg = alg; + if (strlen(sk_hexdigest) != HEX_DIGEST_LEN || + base16_decode(sig->signing_key_digest, sizeof(sig->signing_key_digest), + sk_hexdigest, HEX_DIGEST_LEN) + != sizeof(sig->signing_key_digest)) { + log_warn(LD_DIR, "Error decoding declared signing key digest %s in " + "network-status document.", escaped(sk_hexdigest)); + tor_free(sig); + goto err; + } + + if (ns->type != NS_TYPE_CONSENSUS) { + if (tor_memneq(declared_identity, ns->cert->cache_info.identity_digest, + DIGEST_LEN)) { + log_warn(LD_DIR, "Digest mismatch between declared and actual on " + "network-status vote."); + tor_free(sig); + goto err; + } + } + + if (networkstatus_get_voter_sig_by_alg(v, sig->alg)) { + /* We already parsed a vote with this algorithm from this voter. Use the + first one. */ + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus " + "that contains two signatures from the same voter with the same " + "algorithm. Ignoring the second signature."); + tor_free(sig); + continue; + } + + if (ns->type != NS_TYPE_CONSENSUS) { + if (check_signature_token(ns_digests.d[DIGEST_SHA1], DIGEST_LEN, + tok, ns->cert->signing_key, 0, + "network-status document")) { + tor_free(sig); + goto err; + } + sig->good_signature = 1; + } else { + if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING) { + tor_free(sig); + goto err; + } + sig->signature = tor_memdup(tok->object_body, tok->object_size); + sig->signature_len = (int) tok->object_size; + } + smartlist_add(v->sigs, sig); + + ++n_signatures; + } SMARTLIST_FOREACH_END(_tok); + + if (! n_signatures) { + log_warn(LD_DIR, "No signatures on networkstatus document."); + goto err; + } else if (ns->type == NS_TYPE_VOTE && n_signatures != 1) { + log_warn(LD_DIR, "Received more than one signature on a " + "network-status vote."); + goto err; + } + + if (eos_out) + *eos_out = end_of_footer; + + goto done; + err: + dump_desc(s_dup, "v3 networkstatus"); + networkstatus_vote_free(ns); + ns = NULL; + done: + if (tokens) { + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(tokens); + } + if (voter) { + if (voter->sigs) { + SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig, + document_signature_free(sig)); + smartlist_free(voter->sigs); + } + tor_free(voter->nickname); + tor_free(voter->address); + tor_free(voter->contact); + tor_free(voter); + } + if (rs_tokens) { + SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(rs_tokens); + } + if (footer_tokens) { + SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(footer_tokens); + } + if (area) { + DUMP_AREA(area, "v3 networkstatus"); + memarea_drop_all(area); + } + if (rs_area) + memarea_drop_all(rs_area); + tor_free(last_kwd); + + return ns; +} diff --cc src/feature/dirparse/ns_parse.h index 22438d73a,000000000..85d9ded68 mode 100644,000000..100644 --- a/src/feature/dirparse/ns_parse.h +++ b/src/feature/dirparse/ns_parse.h @@@ -1,45 -1,0 +1,47 @@@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file ns_parse.h + * \brief Header file for ns_parse.c. + **/ + +#ifndef TOR_NS_PARSE_H +#define TOR_NS_PARSE_H + - int router_get_networkstatus_v3_hashes(const char *s, ++int router_get_networkstatus_v3_hashes(const char *s, size_t len, + common_digests_t *digests); - int router_get_networkstatus_v3_signed_boundaries(const char *s, ++int router_get_networkstatus_v3_signed_boundaries(const char *s, size_t len, + const char **start_out, + const char **end_out); +int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, - const char *s); ++ const char *s, size_t len); +int compare_vote_routerstatus_entries(const void **_a, const void **_b); + +int networkstatus_verify_bw_weights(networkstatus_t *ns, int); +enum networkstatus_type_t; +networkstatus_t *networkstatus_parse_vote_from_string(const char *s, ++ size_t len, + const char **eos_out, + enum networkstatus_type_t ns_type); + +#ifdef NS_PARSE_PRIVATE +STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str, + networkstatus_t *vote, + vote_routerstatus_t *vote_rs, + routerstatus_t *rs); +struct memarea_t; +STATIC routerstatus_t *routerstatus_parse_entry_from_string( + struct memarea_t *area, - const char **s, smartlist_t *tokens, ++ const char **s, const char *eos, ++ smartlist_t *tokens, + networkstatus_t *vote, + vote_routerstatus_t *vote_rs, + int consensus_method, + consensus_flavor_t flav); +#endif + +#endif diff --cc src/feature/nodelist/authcert.c index b111422d0,000000000..2c4915e91 mode 100644,000000..100644 --- a/src/feature/nodelist/authcert.c +++ b/src/feature/nodelist/authcert.c @@@ -1,1208 -1,0 +1,1209 @@@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file authcert.c + * \brief Code to maintain directory authorities' certificates. + * + * Authority certificates are signed with authority identity keys; they + * are used to authenticate shorter-term authority signing keys. We + * fetch them when we find a consensus or a vote that has been signed + * with a signing key we don't recognize. We cache them on disk and + * load them on startup. Authority operators generate them with the + * "tor-gencert" utility. + */ + +#include "core/or/or.h" + +#include "app/config/config.h" +#include "core/mainloop/connection.h" +#include "core/mainloop/mainloop.h" +#include "core/or/policies.h" +#include "feature/client/bridges.h" +#include "feature/dirauth/authmode.h" +#include "feature/dirclient/dirclient.h" +#include "feature/dirclient/dlstatus.h" +#include "feature/dircommon/directory.h" +#include "feature/dircommon/fp_pair.h" +#include "feature/dirparse/authcert_parse.h" +#include "feature/nodelist/authcert.h" +#include "feature/nodelist/dirlist.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/node_select.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerlist.h" +#include "feature/relay/routermode.h" + +#include "core/or/connection_st.h" +#include "feature/dirclient/dir_server_st.h" +#include "feature/dircommon/dir_connection_st.h" +#include "feature/nodelist/authority_cert_st.h" +#include "feature/nodelist/document_signature_st.h" +#include "feature/nodelist/networkstatus_st.h" +#include "feature/nodelist/networkstatus_voter_info_st.h" +#include "feature/nodelist/node_st.h" + +DECLARE_TYPED_DIGESTMAP_FNS(dsmap_, digest_ds_map_t, download_status_t) +#define DSMAP_FOREACH(map, keyvar, valvar) \ + DIGESTMAP_FOREACH(dsmap_to_digestmap(map), keyvar, download_status_t *, \ + valvar) +#define dsmap_free(map, fn) MAP_FREE_AND_NULL(dsmap, (map), (fn)) + +/* Forward declaration for cert_list_t */ +typedef struct cert_list_t cert_list_t; + +static void download_status_reset_by_sk_in_cl(cert_list_t *cl, + const char *digest); +static int download_status_is_ready_by_sk_in_cl(cert_list_t *cl, + const char *digest, + time_t now); +static void list_pending_fpsk_downloads(fp_pair_map_t *result); + +/** List of certificates for a single authority, and download status for + * latest certificate. + */ +struct cert_list_t { + /* + * The keys of download status map are cert->signing_key_digest for pending + * downloads by (identity digest/signing key digest) pair; functions such + * as authority_cert_get_by_digest() already assume these are unique. + */ + struct digest_ds_map_t *dl_status_map; + /* There is also a dlstatus for the download by identity key only */ + download_status_t dl_status_by_id; + smartlist_t *certs; +}; +/** Map from v3 identity key digest to cert_list_t. */ +static digestmap_t *trusted_dir_certs = NULL; + +/** True iff any key certificate in at least one member of + * <b>trusted_dir_certs</b> has changed since we last flushed the + * certificates to disk. */ +static int trusted_dir_servers_certs_changed = 0; + +/** Initialise schedule, want_authority, and increment_on in the download + * status dlstatus, then call download_status_reset() on it. + * It is safe to call this function or download_status_reset() multiple times + * on a new dlstatus. But it should *not* be called after a dlstatus has been + * used to count download attempts or failures. */ +static void +download_status_cert_init(download_status_t *dlstatus) +{ + dlstatus->schedule = DL_SCHED_CONSENSUS; + dlstatus->want_authority = DL_WANT_ANY_DIRSERVER; + dlstatus->increment_on = DL_SCHED_INCREMENT_FAILURE; + dlstatus->last_backoff_position = 0; + dlstatus->last_delay_used = 0; + + /* Use the new schedule to set next_attempt_at */ + download_status_reset(dlstatus); +} + +/** Reset the download status of a specified element in a dsmap */ +static void +download_status_reset_by_sk_in_cl(cert_list_t *cl, const char *digest) +{ + download_status_t *dlstatus = NULL; + + tor_assert(cl); + tor_assert(digest); + + /* Make sure we have a dsmap */ + if (!(cl->dl_status_map)) { + cl->dl_status_map = dsmap_new(); + } + /* Look for a download_status_t in the map with this digest */ + dlstatus = dsmap_get(cl->dl_status_map, digest); + /* Got one? */ + if (!dlstatus) { + /* Insert before we reset */ + dlstatus = tor_malloc_zero(sizeof(*dlstatus)); + dsmap_set(cl->dl_status_map, digest, dlstatus); + download_status_cert_init(dlstatus); + } + tor_assert(dlstatus); + /* Go ahead and reset it */ + download_status_reset(dlstatus); +} + +/** + * Return true if the download for this signing key digest in cl is ready + * to be re-attempted. + */ +static int +download_status_is_ready_by_sk_in_cl(cert_list_t *cl, + const char *digest, + time_t now) +{ + int rv = 0; + download_status_t *dlstatus = NULL; + + tor_assert(cl); + tor_assert(digest); + + /* Make sure we have a dsmap */ + if (!(cl->dl_status_map)) { + cl->dl_status_map = dsmap_new(); + } + /* Look for a download_status_t in the map with this digest */ + dlstatus = dsmap_get(cl->dl_status_map, digest); + /* Got one? */ + if (dlstatus) { + /* Use download_status_is_ready() */ + rv = download_status_is_ready(dlstatus, now); + } else { + /* + * If we don't know anything about it, return 1, since we haven't + * tried this one before. We need to create a new entry here, + * too. + */ + dlstatus = tor_malloc_zero(sizeof(*dlstatus)); + download_status_cert_init(dlstatus); + dsmap_set(cl->dl_status_map, digest, dlstatus); + rv = 1; + } + + return rv; +} + +/** Helper: Return the cert_list_t for an authority whose authority ID is + * <b>id_digest</b>, allocating a new list if necessary. */ +static cert_list_t * +get_cert_list(const char *id_digest) +{ + cert_list_t *cl; + if (!trusted_dir_certs) + trusted_dir_certs = digestmap_new(); + cl = digestmap_get(trusted_dir_certs, id_digest); + if (!cl) { + cl = tor_malloc_zero(sizeof(cert_list_t)); + download_status_cert_init(&cl->dl_status_by_id); + cl->certs = smartlist_new(); + cl->dl_status_map = dsmap_new(); + digestmap_set(trusted_dir_certs, id_digest, cl); + } + return cl; +} + +/** Return a list of authority ID digests with potentially enumerable lists + * of download_status_t objects; used by controller GETINFO queries. + */ + +MOCK_IMPL(smartlist_t *, +list_authority_ids_with_downloads, (void)) +{ + smartlist_t *ids = smartlist_new(); + digestmap_iter_t *i; + const char *digest; + char *tmp; + void *cl; + + if (trusted_dir_certs) { + for (i = digestmap_iter_init(trusted_dir_certs); + !(digestmap_iter_done(i)); + i = digestmap_iter_next(trusted_dir_certs, i)) { + /* + * We always have at least dl_status_by_id to query, so no need to + * probe deeper than the existence of a cert_list_t. + */ + digestmap_iter_get(i, &digest, &cl); + tmp = tor_malloc(DIGEST_LEN); + memcpy(tmp, digest, DIGEST_LEN); + smartlist_add(ids, tmp); + } + } + /* else definitely no downloads going since nothing even has a cert list */ + + return ids; +} + +/** Given an authority ID digest, return a pointer to the default download + * status, or NULL if there is no such entry in trusted_dir_certs */ + +MOCK_IMPL(download_status_t *, +id_only_download_status_for_authority_id, (const char *digest)) +{ + download_status_t *dl = NULL; + cert_list_t *cl; + + if (trusted_dir_certs) { + cl = digestmap_get(trusted_dir_certs, digest); + if (cl) { + dl = &(cl->dl_status_by_id); + } + } + + return dl; +} + +/** Given an authority ID digest, return a smartlist of signing key digests + * for which download_status_t is potentially queryable, or NULL if no such + * authority ID digest is known. */ + +MOCK_IMPL(smartlist_t *, +list_sk_digests_for_authority_id, (const char *digest)) +{ + smartlist_t *sks = NULL; + cert_list_t *cl; + dsmap_iter_t *i; + const char *sk_digest; + char *tmp; + download_status_t *dl; + + if (trusted_dir_certs) { + cl = digestmap_get(trusted_dir_certs, digest); + if (cl) { + sks = smartlist_new(); + if (cl->dl_status_map) { + for (i = dsmap_iter_init(cl->dl_status_map); + !(dsmap_iter_done(i)); + i = dsmap_iter_next(cl->dl_status_map, i)) { + /* Pull the digest out and add it to the list */ + dsmap_iter_get(i, &sk_digest, &dl); + tmp = tor_malloc(DIGEST_LEN); + memcpy(tmp, sk_digest, DIGEST_LEN); + smartlist_add(sks, tmp); + } + } + } + } + + return sks; +} + +/** Given an authority ID digest and a signing key digest, return the + * download_status_t or NULL if none exists. */ + +MOCK_IMPL(download_status_t *, +download_status_for_authority_id_and_sk,(const char *id_digest, + const char *sk_digest)) +{ + download_status_t *dl = NULL; + cert_list_t *cl = NULL; + + if (trusted_dir_certs) { + cl = digestmap_get(trusted_dir_certs, id_digest); + if (cl && cl->dl_status_map) { + dl = dsmap_get(cl->dl_status_map, sk_digest); + } + } + + return dl; +} + +#define cert_list_free(val) \ + FREE_AND_NULL(cert_list_t, cert_list_free_, (val)) + +/** Release all space held by a cert_list_t */ +static void +cert_list_free_(cert_list_t *cl) +{ + if (!cl) + return; + + SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, + authority_cert_free(cert)); + smartlist_free(cl->certs); + dsmap_free(cl->dl_status_map, tor_free_); + tor_free(cl); +} + +/** Wrapper for cert_list_free so we can pass it to digestmap_free */ +static void +cert_list_free_void(void *cl) +{ + cert_list_free_(cl); +} + +/** Reload the cached v3 key certificates from the cached-certs file in + * the data directory. Return 0 on success, -1 on failure. */ +int +trusted_dirs_reload_certs(void) +{ + char *filename; + char *contents; + int r; + + filename = get_cachedir_fname("cached-certs"); + contents = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); + tor_free(filename); + if (!contents) + return 0; + r = trusted_dirs_load_certs_from_string( + contents, + TRUSTED_DIRS_CERTS_SRC_FROM_STORE, 1, NULL); + tor_free(contents); + return r; +} + +/** Helper: return true iff we already have loaded the exact cert + * <b>cert</b>. */ +static inline int +already_have_cert(authority_cert_t *cert) +{ + cert_list_t *cl = get_cert_list(cert->cache_info.identity_digest); + + SMARTLIST_FOREACH(cl->certs, authority_cert_t *, c, + { + if (tor_memeq(c->cache_info.signed_descriptor_digest, + cert->cache_info.signed_descriptor_digest, + DIGEST_LEN)) + return 1; + }); + return 0; +} + +/** Load a bunch of new key certificates from the string <b>contents</b>. If + * <b>source</b> is TRUSTED_DIRS_CERTS_SRC_FROM_STORE, the certificates are + * from the cache, and we don't need to flush them to disk. If we are a + * dirauth loading our own cert, source is TRUSTED_DIRS_CERTS_SRC_SELF. + * Otherwise, source is download type: TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST + * or TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_SK_DIGEST. If <b>flush</b> is true, we + * need to flush any changed certificates to disk now. Return 0 on success, + * -1 if any certs fail to parse. + * + * If source_dir is non-NULL, it's the identity digest for a directory that + * we've just successfully retrieved certificates from, so try it first to + * fetch any missing certificates. + */ +int +trusted_dirs_load_certs_from_string(const char *contents, int source, + int flush, const char *source_dir) +{ + dir_server_t *ds; + const char *s, *eos; + int failure_code = 0; + int from_store = (source == TRUSTED_DIRS_CERTS_SRC_FROM_STORE); + int added_trusted_cert = 0; + + for (s = contents; *s; s = eos) { - authority_cert_t *cert = authority_cert_parse_from_string(s, &eos); ++ authority_cert_t *cert = authority_cert_parse_from_string(s, strlen(s), ++ &eos); + cert_list_t *cl; + if (!cert) { + failure_code = -1; + break; + } + ds = trusteddirserver_get_by_v3_auth_digest( + cert->cache_info.identity_digest); + log_debug(LD_DIR, "Parsed certificate for %s", + ds ? ds->nickname : "unknown authority"); + + if (already_have_cert(cert)) { + /* we already have this one. continue. */ + log_info(LD_DIR, "Skipping %s certificate for %s that we " + "already have.", + from_store ? "cached" : "downloaded", + ds ? ds->nickname : "an old or new authority"); + + /* + * A duplicate on download should be treated as a failure, so we call + * authority_cert_dl_failed() to reset the download status to make sure + * we can't try again. Since we've implemented the fp-sk mechanism + * to download certs by signing key, this should be much rarer than it + * was and is perhaps cause for concern. + */ + if (!from_store) { + if (authdir_mode(get_options())) { + log_warn(LD_DIR, + "Got a certificate for %s, but we already have it. " + "Maybe they haven't updated it. Waiting for a while.", + ds ? ds->nickname : "an old or new authority"); + } else { + log_info(LD_DIR, + "Got a certificate for %s, but we already have it. " + "Maybe they haven't updated it. Waiting for a while.", + ds ? ds->nickname : "an old or new authority"); + } + + /* + * This is where we care about the source; authority_cert_dl_failed() + * needs to know whether the download was by fp or (fp,sk) pair to + * twiddle the right bit in the download map. + */ + if (source == TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST) { + authority_cert_dl_failed(cert->cache_info.identity_digest, + NULL, 404); + } else if (source == TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_SK_DIGEST) { + authority_cert_dl_failed(cert->cache_info.identity_digest, + cert->signing_key_digest, 404); + } + } + + authority_cert_free(cert); + continue; + } + + if (ds) { + added_trusted_cert = 1; + log_info(LD_DIR, "Adding %s certificate for directory authority %s with " + "signing key %s", from_store ? "cached" : "downloaded", + ds->nickname, hex_str(cert->signing_key_digest,DIGEST_LEN)); + } else { + int adding = we_want_to_fetch_unknown_auth_certs(get_options()); + log_info(LD_DIR, "%s %s certificate for unrecognized directory " + "authority with signing key %s", + adding ? "Adding" : "Not adding", + from_store ? "cached" : "downloaded", + hex_str(cert->signing_key_digest,DIGEST_LEN)); + if (!adding) { + authority_cert_free(cert); + continue; + } + } + + cl = get_cert_list(cert->cache_info.identity_digest); + smartlist_add(cl->certs, cert); + if (ds && cert->cache_info.published_on > ds->addr_current_at) { + /* Check to see whether we should update our view of the authority's + * address. */ + if (cert->addr && cert->dir_port && + (ds->addr != cert->addr || + ds->dir_port != cert->dir_port)) { + char *a = tor_dup_ip(cert->addr); + log_notice(LD_DIR, "Updating address for directory authority %s " + "from %s:%d to %s:%d based on certificate.", + ds->nickname, ds->address, (int)ds->dir_port, + a, cert->dir_port); + tor_free(a); + ds->addr = cert->addr; + ds->dir_port = cert->dir_port; + } + ds->addr_current_at = cert->cache_info.published_on; + } + + if (!from_store) + trusted_dir_servers_certs_changed = 1; + } + + if (flush) + trusted_dirs_flush_certs_to_disk(); + + /* call this even if failure_code is <0, since some certs might have + * succeeded, but only pass source_dir if there were no failures, + * and at least one more authority certificate was added to the store. + * This avoids retrying a directory that's serving bad or entirely duplicate + * certificates. */ + if (failure_code == 0 && added_trusted_cert) { + networkstatus_note_certs_arrived(source_dir); + } else { + networkstatus_note_certs_arrived(NULL); + } + + return failure_code; +} + +/** Save all v3 key certificates to the cached-certs file. */ +void +trusted_dirs_flush_certs_to_disk(void) +{ + char *filename; + smartlist_t *chunks; + + if (!trusted_dir_servers_certs_changed || !trusted_dir_certs) + return; + + chunks = smartlist_new(); + DIGESTMAP_FOREACH(trusted_dir_certs, key, cert_list_t *, cl) { + SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, + { + sized_chunk_t *c = tor_malloc(sizeof(sized_chunk_t)); + c->bytes = cert->cache_info.signed_descriptor_body; + c->len = cert->cache_info.signed_descriptor_len; + smartlist_add(chunks, c); + }); + } DIGESTMAP_FOREACH_END; + + filename = get_cachedir_fname("cached-certs"); + if (write_chunks_to_file(filename, chunks, 0, 0)) { + log_warn(LD_FS, "Error writing certificates to disk."); + } + tor_free(filename); + SMARTLIST_FOREACH(chunks, sized_chunk_t *, c, tor_free(c)); + smartlist_free(chunks); + + trusted_dir_servers_certs_changed = 0; +} + +static int +compare_certs_by_pubdates(const void **_a, const void **_b) +{ + const authority_cert_t *cert1 = *_a, *cert2=*_b; + + if (cert1->cache_info.published_on < cert2->cache_info.published_on) + return -1; + else if (cert1->cache_info.published_on > cert2->cache_info.published_on) + return 1; + else + return 0; +} + +/** Remove all expired v3 authority certificates that have been superseded for + * more than 48 hours or, if not expired, that were published more than 7 days + * before being superseded. (If the most recent cert was published more than 48 + * hours ago, then we aren't going to get any consensuses signed with older + * keys.) */ +void +trusted_dirs_remove_old_certs(void) +{ + time_t now = time(NULL); +#define DEAD_CERT_LIFETIME (2*24*60*60) +#define SUPERSEDED_CERT_LIFETIME (2*24*60*60) + if (!trusted_dir_certs) + return; + + DIGESTMAP_FOREACH(trusted_dir_certs, key, cert_list_t *, cl) { + /* Sort the list from first-published to last-published */ + smartlist_sort(cl->certs, compare_certs_by_pubdates); + + SMARTLIST_FOREACH_BEGIN(cl->certs, authority_cert_t *, cert) { + if (cert_sl_idx == smartlist_len(cl->certs) - 1) { + /* This is the most recently published cert. Keep it. */ + continue; + } + authority_cert_t *next_cert = smartlist_get(cl->certs, cert_sl_idx+1); + const time_t next_cert_published = next_cert->cache_info.published_on; + if (next_cert_published > now) { + /* All later certs are published in the future. Keep everything + * we didn't discard. */ + break; + } + int should_remove = 0; + if (cert->expires + DEAD_CERT_LIFETIME < now) { + /* Certificate has been expired for at least DEAD_CERT_LIFETIME. + * Remove it. */ + should_remove = 1; + } else if (next_cert_published + SUPERSEDED_CERT_LIFETIME < now) { + /* Certificate has been superseded for OLD_CERT_LIFETIME. + * Remove it. + */ + should_remove = 1; + } + if (should_remove) { + SMARTLIST_DEL_CURRENT_KEEPORDER(cl->certs, cert); + authority_cert_free(cert); + trusted_dir_servers_certs_changed = 1; + } + } SMARTLIST_FOREACH_END(cert); + + } DIGESTMAP_FOREACH_END; +#undef DEAD_CERT_LIFETIME +#undef OLD_CERT_LIFETIME + + trusted_dirs_flush_certs_to_disk(); +} + +/** Return the newest v3 authority certificate whose v3 authority identity key + * has digest <b>id_digest</b>. Return NULL if no such authority is known, + * or it has no certificate. */ +authority_cert_t * +authority_cert_get_newest_by_id(const char *id_digest) +{ + cert_list_t *cl; + authority_cert_t *best = NULL; + if (!trusted_dir_certs || + !(cl = digestmap_get(trusted_dir_certs, id_digest))) + return NULL; + + SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, + { + if (!best || cert->cache_info.published_on > best->cache_info.published_on) + best = cert; + }); + return best; +} + +/** Return the newest v3 authority certificate whose directory signing key has + * digest <b>sk_digest</b>. Return NULL if no such certificate is known. + */ +authority_cert_t * +authority_cert_get_by_sk_digest(const char *sk_digest) +{ + authority_cert_t *c; + if (!trusted_dir_certs) + return NULL; + + if ((c = get_my_v3_authority_cert()) && + tor_memeq(c->signing_key_digest, sk_digest, DIGEST_LEN)) + return c; + if ((c = get_my_v3_legacy_cert()) && + tor_memeq(c->signing_key_digest, sk_digest, DIGEST_LEN)) + return c; + + DIGESTMAP_FOREACH(trusted_dir_certs, key, cert_list_t *, cl) { + SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, + { + if (tor_memeq(cert->signing_key_digest, sk_digest, DIGEST_LEN)) + return cert; + }); + } DIGESTMAP_FOREACH_END; + return NULL; +} + +/** Return the v3 authority certificate with signing key matching + * <b>sk_digest</b>, for the authority with identity digest <b>id_digest</b>. + * Return NULL if no such authority is known. */ +authority_cert_t * +authority_cert_get_by_digests(const char *id_digest, + const char *sk_digest) +{ + cert_list_t *cl; + if (!trusted_dir_certs || + !(cl = digestmap_get(trusted_dir_certs, id_digest))) + return NULL; + SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, + if (tor_memeq(cert->signing_key_digest, sk_digest, DIGEST_LEN)) + return cert; ); + + return NULL; +} + +/** Add every known authority_cert_t to <b>certs_out</b>. */ +void +authority_cert_get_all(smartlist_t *certs_out) +{ + tor_assert(certs_out); + if (!trusted_dir_certs) + return; + + DIGESTMAP_FOREACH(trusted_dir_certs, key, cert_list_t *, cl) { + SMARTLIST_FOREACH(cl->certs, authority_cert_t *, c, + smartlist_add(certs_out, c)); + } DIGESTMAP_FOREACH_END; +} + +/** Called when an attempt to download a certificate with the authority with + * ID <b>id_digest</b> and, if not NULL, signed with key signing_key_digest + * fails with HTTP response code <b>status</b>: remember the failure, so we + * don't try again immediately. */ +void +authority_cert_dl_failed(const char *id_digest, + const char *signing_key_digest, int status) +{ + cert_list_t *cl; + download_status_t *dlstatus = NULL; + char id_digest_str[2*DIGEST_LEN+1]; + char sk_digest_str[2*DIGEST_LEN+1]; + + if (!trusted_dir_certs || + !(cl = digestmap_get(trusted_dir_certs, id_digest))) + return; + + /* + * Are we noting a failed download of the latest cert for the id digest, + * or of a download by (id, signing key) digest pair? + */ + if (!signing_key_digest) { + /* Just by id digest */ + download_status_failed(&cl->dl_status_by_id, status); + } else { + /* Reset by (id, signing key) digest pair + * + * Look for a download_status_t in the map with this digest + */ + dlstatus = dsmap_get(cl->dl_status_map, signing_key_digest); + /* Got one? */ + if (dlstatus) { + download_status_failed(dlstatus, status); + } else { + /* + * Do this rather than hex_str(), since hex_str clobbers + * old results and we call twice in the param list. + */ + base16_encode(id_digest_str, sizeof(id_digest_str), + id_digest, DIGEST_LEN); + base16_encode(sk_digest_str, sizeof(sk_digest_str), + signing_key_digest, DIGEST_LEN); + log_warn(LD_BUG, + "Got failure for cert fetch with (fp,sk) = (%s,%s), with " + "status %d, but knew nothing about the download.", + id_digest_str, sk_digest_str, status); + } + } +} + +static const char *BAD_SIGNING_KEYS[] = { + "09CD84F751FD6E955E0F8ADB497D5401470D697E", // Expires 2015-01-11 16:26:31 + "0E7E9C07F0969D0468AD741E172A6109DC289F3C", // Expires 2014-08-12 10:18:26 + "57B85409891D3FB32137F642FDEDF8B7F8CDFDCD", // Expires 2015-02-11 17:19:09 + "87326329007AF781F587AF5B594E540B2B6C7630", // Expires 2014-07-17 11:10:09 + "98CC82342DE8D298CF99D3F1A396475901E0D38E", // Expires 2014-11-10 13:18:56 + "9904B52336713A5ADCB13E4FB14DC919E0D45571", // Expires 2014-04-20 20:01:01 + "9DCD8E3F1DD1597E2AD476BBA28A1A89F3095227", // Expires 2015-01-16 03:52:30 + "A61682F34B9BB9694AC98491FE1ABBFE61923941", // Expires 2014-06-11 09:25:09 + "B59F6E99C575113650C99F1C425BA7B20A8C071D", // Expires 2014-07-31 13:22:10 + "D27178388FA75B96D37FA36E0B015227DDDBDA51", // Expires 2014-08-04 04:01:57 + NULL, +}; + +/** Return true iff <b>cert</b> authenticates some atuhority signing key + * which, because of the old openssl heartbleed vulnerability, should + * never be trusted. */ +int +authority_cert_is_blacklisted(const authority_cert_t *cert) +{ + char hex_digest[HEX_DIGEST_LEN+1]; + int i; + base16_encode(hex_digest, sizeof(hex_digest), + cert->signing_key_digest, sizeof(cert->signing_key_digest)); + + for (i = 0; BAD_SIGNING_KEYS[i]; ++i) { + if (!strcasecmp(hex_digest, BAD_SIGNING_KEYS[i])) { + return 1; + } + } + return 0; +} + +/** Return true iff when we've been getting enough failures when trying to + * download the certificate with ID digest <b>id_digest</b> that we're willing + * to start bugging the user about it. */ +int +authority_cert_dl_looks_uncertain(const char *id_digest) +{ +#define N_AUTH_CERT_DL_FAILURES_TO_BUG_USER 2 + cert_list_t *cl; + int n_failures; + if (!trusted_dir_certs || + !(cl = digestmap_get(trusted_dir_certs, id_digest))) + return 0; + + n_failures = download_status_get_n_failures(&cl->dl_status_by_id); + return n_failures >= N_AUTH_CERT_DL_FAILURES_TO_BUG_USER; +} + +/* Fetch the authority certificates specified in resource. + * If we are a bridge client, and node is a configured bridge, fetch from node + * using dir_hint as the fingerprint. Otherwise, if rs is not NULL, fetch from + * rs. Otherwise, fetch from a random directory mirror. */ +static void +authority_certs_fetch_resource_impl(const char *resource, + const char *dir_hint, + const node_t *node, + const routerstatus_t *rs) +{ + const or_options_t *options = get_options(); + int get_via_tor = purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0, + resource); + + /* Make sure bridge clients never connect to anything but a bridge */ + if (options->UseBridges) { + if (node && !node_is_a_configured_bridge(node)) { + /* If we're using bridges, and node is not a bridge, use a 3-hop path. */ + get_via_tor = 1; + } else if (!node) { + /* If we're using bridges, and there's no node, use a 3-hop path. */ + get_via_tor = 1; + } + } + + const dir_indirection_t indirection = get_via_tor ? DIRIND_ANONYMOUS + : DIRIND_ONEHOP; + + directory_request_t *req = NULL; + /* If we've just downloaded a consensus from a bridge, re-use that + * bridge */ + if (options->UseBridges && node && node->ri && !get_via_tor) { + /* clients always make OR connections to bridges */ + tor_addr_port_t or_ap; + /* we are willing to use a non-preferred address if we need to */ + fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0, + &or_ap); + + req = directory_request_new(DIR_PURPOSE_FETCH_CERTIFICATE); + directory_request_set_or_addr_port(req, &or_ap); + if (dir_hint) + directory_request_set_directory_id_digest(req, dir_hint); + } else if (rs) { + /* And if we've just downloaded a consensus from a directory, re-use that + * directory */ + req = directory_request_new(DIR_PURPOSE_FETCH_CERTIFICATE); + directory_request_set_routerstatus(req, rs); + } + + if (req) { + /* We've set up a request object -- fill in the other request fields, and + * send the request. */ + directory_request_set_indirection(req, indirection); + directory_request_set_resource(req, resource); + directory_initiate_request(req); + directory_request_free(req); + return; + } + + /* Otherwise, we want certs from a random fallback or directory + * mirror, because they will almost always succeed. */ + directory_get_from_dirserver(DIR_PURPOSE_FETCH_CERTIFICATE, 0, + resource, PDS_RETRY_IF_NO_SERVERS, + DL_WANT_ANY_DIRSERVER); +} + +/** Try to download any v3 authority certificates that we may be missing. If + * <b>status</b> is provided, try to get all the ones that were used to sign + * <b>status</b>. Additionally, try to have a non-expired certificate for + * every V3 authority in trusted_dir_servers. Don't fetch certificates we + * already have. + * + * If dir_hint is non-NULL, it's the identity digest for a directory that + * we've just successfully retrieved a consensus or certificates from, so try + * it first to fetch any missing certificates. + **/ +void +authority_certs_fetch_missing(networkstatus_t *status, time_t now, + const char *dir_hint) +{ + /* + * The pending_id digestmap tracks pending certificate downloads by + * identity digest; the pending_cert digestmap tracks pending downloads + * by (identity digest, signing key digest) pairs. + */ + digestmap_t *pending_id; + fp_pair_map_t *pending_cert; + /* + * The missing_id_digests smartlist will hold a list of id digests + * we want to fetch the newest cert for; the missing_cert_digests + * smartlist will hold a list of fp_pair_t with an identity and + * signing key digest. + */ + smartlist_t *missing_cert_digests, *missing_id_digests; + char *resource = NULL; + cert_list_t *cl; + const or_options_t *options = get_options(); + const int keep_unknown = we_want_to_fetch_unknown_auth_certs(options); + fp_pair_t *fp_tmp = NULL; + char id_digest_str[2*DIGEST_LEN+1]; + char sk_digest_str[2*DIGEST_LEN+1]; + + if (should_delay_dir_fetches(options, NULL)) + return; + + pending_cert = fp_pair_map_new(); + pending_id = digestmap_new(); + missing_cert_digests = smartlist_new(); + missing_id_digests = smartlist_new(); + + /* + * First, we get the lists of already pending downloads so we don't + * duplicate effort. + */ + list_pending_downloads(pending_id, NULL, + DIR_PURPOSE_FETCH_CERTIFICATE, "fp/"); + list_pending_fpsk_downloads(pending_cert); + + /* + * Now, we download any trusted authority certs we don't have by + * identity digest only. This gets the latest cert for that authority. + */ + SMARTLIST_FOREACH_BEGIN(router_get_trusted_dir_servers(), + dir_server_t *, ds) { + int found = 0; + if (!(ds->type & V3_DIRINFO)) + continue; + if (smartlist_contains_digest(missing_id_digests, + ds->v3_identity_digest)) + continue; + cl = get_cert_list(ds->v3_identity_digest); + SMARTLIST_FOREACH_BEGIN(cl->certs, authority_cert_t *, cert) { + if (now < cert->expires) { + /* It's not expired, and we weren't looking for something to + * verify a consensus with. Call it done. */ + download_status_reset(&(cl->dl_status_by_id)); + /* No sense trying to download it specifically by signing key hash */ + download_status_reset_by_sk_in_cl(cl, cert->signing_key_digest); + found = 1; + break; + } + } SMARTLIST_FOREACH_END(cert); + if (!found && + download_status_is_ready(&(cl->dl_status_by_id), now) && + !digestmap_get(pending_id, ds->v3_identity_digest)) { + log_info(LD_DIR, + "No current certificate known for authority %s " + "(ID digest %s); launching request.", + ds->nickname, hex_str(ds->v3_identity_digest, DIGEST_LEN)); + smartlist_add(missing_id_digests, ds->v3_identity_digest); + } + } SMARTLIST_FOREACH_END(ds); + + /* + * Next, if we have a consensus, scan through it and look for anything + * signed with a key from a cert we don't have. Those get downloaded + * by (fp,sk) pair, but if we don't know any certs at all for the fp + * (identity digest), and it's one of the trusted dir server certs + * we started off above or a pending download in pending_id, don't + * try to get it yet. Most likely, the one we'll get for that will + * have the right signing key too, and we'd just be downloading + * redundantly. + */ + if (status) { + SMARTLIST_FOREACH_BEGIN(status->voters, networkstatus_voter_info_t *, + voter) { + if (!smartlist_len(voter->sigs)) + continue; /* This authority never signed this consensus, so don't + * go looking for a cert with key digest 0000000000. */ + if (!keep_unknown && + !trusteddirserver_get_by_v3_auth_digest(voter->identity_digest)) + continue; /* We don't want unknown certs, and we don't know this + * authority.*/ + + /* + * If we don't know *any* cert for this authority, and a download by ID + * is pending or we added it to missing_id_digests above, skip this + * one for now to avoid duplicate downloads. + */ + cl = get_cert_list(voter->identity_digest); + if (smartlist_len(cl->certs) == 0) { + /* We have no certs at all for this one */ + + /* Do we have a download of one pending? */ + if (digestmap_get(pending_id, voter->identity_digest)) + continue; + + /* + * Are we about to launch a download of one due to the trusted + * dir server check above? + */ + if (smartlist_contains_digest(missing_id_digests, + voter->identity_digest)) + continue; + } + + SMARTLIST_FOREACH_BEGIN(voter->sigs, document_signature_t *, sig) { + authority_cert_t *cert = + authority_cert_get_by_digests(voter->identity_digest, + sig->signing_key_digest); + if (cert) { + if (now < cert->expires) + download_status_reset_by_sk_in_cl(cl, sig->signing_key_digest); + continue; + } + if (download_status_is_ready_by_sk_in_cl( + cl, sig->signing_key_digest, now) && + !fp_pair_map_get_by_digests(pending_cert, + voter->identity_digest, + sig->signing_key_digest)) { + /* + * Do this rather than hex_str(), since hex_str clobbers + * old results and we call twice in the param list. + */ + base16_encode(id_digest_str, sizeof(id_digest_str), + voter->identity_digest, DIGEST_LEN); + base16_encode(sk_digest_str, sizeof(sk_digest_str), + sig->signing_key_digest, DIGEST_LEN); + + if (voter->nickname) { + log_info(LD_DIR, + "We're missing a certificate from authority %s " + "(ID digest %s) with signing key %s: " + "launching request.", + voter->nickname, id_digest_str, sk_digest_str); + } else { + log_info(LD_DIR, + "We're missing a certificate from authority ID digest " + "%s with signing key %s: launching request.", + id_digest_str, sk_digest_str); + } + + /* Allocate a new fp_pair_t to append */ + fp_tmp = tor_malloc(sizeof(*fp_tmp)); + memcpy(fp_tmp->first, voter->identity_digest, sizeof(fp_tmp->first)); + memcpy(fp_tmp->second, sig->signing_key_digest, + sizeof(fp_tmp->second)); + smartlist_add(missing_cert_digests, fp_tmp); + } + } SMARTLIST_FOREACH_END(sig); + } SMARTLIST_FOREACH_END(voter); + } + + /* Bridge clients look up the node for the dir_hint */ + const node_t *node = NULL; + /* All clients, including bridge clients, look up the routerstatus for the + * dir_hint */ + const routerstatus_t *rs = NULL; + + /* If we still need certificates, try the directory that just successfully + * served us a consensus or certificates. + * As soon as the directory fails to provide additional certificates, we try + * another, randomly selected directory. This avoids continual retries. + * (We only ever have one outstanding request per certificate.) + */ + if (dir_hint) { + if (options->UseBridges) { + /* Bridge clients try the nodelist. If the dir_hint is from an authority, + * or something else fetched over tor, we won't find the node here, but + * we will find the rs. */ + node = node_get_by_id(dir_hint); + } + + /* All clients try the consensus routerstatus, then the fallback + * routerstatus */ + rs = router_get_consensus_status_by_id(dir_hint); + if (!rs) { + /* This will also find authorities */ + const dir_server_t *ds = router_get_fallback_dirserver_by_digest( + dir_hint); + if (ds) { + rs = &ds->fake_status; + } + } + + if (!node && !rs) { + log_warn(LD_BUG, "Directory %s delivered a consensus, but %s" + "no routerstatus could be found for it.", + options->UseBridges ? "no node and " : "", + hex_str(dir_hint, DIGEST_LEN)); + } + } + + /* Do downloads by identity digest */ + if (smartlist_len(missing_id_digests) > 0) { + int need_plus = 0; + smartlist_t *fps = smartlist_new(); + + smartlist_add_strdup(fps, "fp/"); + + SMARTLIST_FOREACH_BEGIN(missing_id_digests, const char *, d) { + char *fp = NULL; + + if (digestmap_get(pending_id, d)) + continue; + + base16_encode(id_digest_str, sizeof(id_digest_str), + d, DIGEST_LEN); + + if (need_plus) { + tor_asprintf(&fp, "+%s", id_digest_str); + } else { + /* No need for tor_asprintf() in this case; first one gets no '+' */ + fp = tor_strdup(id_digest_str); + need_plus = 1; + } + + smartlist_add(fps, fp); + } SMARTLIST_FOREACH_END(d); + + if (smartlist_len(fps) > 1) { + resource = smartlist_join_strings(fps, "", 0, NULL); + /* node and rs are directories that just gave us a consensus or + * certificates */ + authority_certs_fetch_resource_impl(resource, dir_hint, node, rs); + tor_free(resource); + } + /* else we didn't add any: they were all pending */ + + SMARTLIST_FOREACH(fps, char *, cp, tor_free(cp)); + smartlist_free(fps); + } + + /* Do downloads by identity digest/signing key pair */ + if (smartlist_len(missing_cert_digests) > 0) { + int need_plus = 0; + smartlist_t *fp_pairs = smartlist_new(); + + smartlist_add_strdup(fp_pairs, "fp-sk/"); + + SMARTLIST_FOREACH_BEGIN(missing_cert_digests, const fp_pair_t *, d) { + char *fp_pair = NULL; + + if (fp_pair_map_get(pending_cert, d)) + continue; + + /* Construct string encodings of the digests */ + base16_encode(id_digest_str, sizeof(id_digest_str), + d->first, DIGEST_LEN); + base16_encode(sk_digest_str, sizeof(sk_digest_str), + d->second, DIGEST_LEN); + + /* Now tor_asprintf() */ + if (need_plus) { + tor_asprintf(&fp_pair, "+%s-%s", id_digest_str, sk_digest_str); + } else { + /* First one in the list doesn't get a '+' */ + tor_asprintf(&fp_pair, "%s-%s", id_digest_str, sk_digest_str); + need_plus = 1; + } + + /* Add it to the list of pairs to request */ + smartlist_add(fp_pairs, fp_pair); + } SMARTLIST_FOREACH_END(d); + + if (smartlist_len(fp_pairs) > 1) { + resource = smartlist_join_strings(fp_pairs, "", 0, NULL); + /* node and rs are directories that just gave us a consensus or + * certificates */ + authority_certs_fetch_resource_impl(resource, dir_hint, node, rs); + tor_free(resource); + } + /* else they were all pending */ + + SMARTLIST_FOREACH(fp_pairs, char *, p, tor_free(p)); + smartlist_free(fp_pairs); + } + + smartlist_free(missing_id_digests); + SMARTLIST_FOREACH(missing_cert_digests, fp_pair_t *, p, tor_free(p)); + smartlist_free(missing_cert_digests); + digestmap_free(pending_id, NULL); + fp_pair_map_free(pending_cert, NULL); +} + +void +authcert_free_all(void) +{ + if (trusted_dir_certs) { + digestmap_free(trusted_dir_certs, cert_list_free_void); + trusted_dir_certs = NULL; + } +} + +/** Free storage held in <b>cert</b>. */ +void +authority_cert_free_(authority_cert_t *cert) +{ + if (!cert) + return; + + tor_free(cert->cache_info.signed_descriptor_body); + crypto_pk_free(cert->signing_key); + crypto_pk_free(cert->identity_key); + + tor_free(cert); +} + +/** For every certificate we are currently downloading by (identity digest, + * signing key digest) pair, set result[fp_pair] to (void *1). + */ +static void +list_pending_fpsk_downloads(fp_pair_map_t *result) +{ + const char *pfx = "fp-sk/"; + smartlist_t *tmp; + smartlist_t *conns; + const char *resource; + + tor_assert(result); + + tmp = smartlist_new(); + conns = get_connection_array(); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + if (conn->type == CONN_TYPE_DIR && + conn->purpose == DIR_PURPOSE_FETCH_CERTIFICATE && + !conn->marked_for_close) { + resource = TO_DIR_CONN(conn)->requested_resource; + if (!strcmpstart(resource, pfx)) + dir_split_resource_into_fingerprint_pairs(resource + strlen(pfx), + tmp); + } + } SMARTLIST_FOREACH_END(conn); + + SMARTLIST_FOREACH_BEGIN(tmp, fp_pair_t *, fp) { + fp_pair_map_set(result, fp, (void*)1); + tor_free(fp); + } SMARTLIST_FOREACH_END(fp); + + smartlist_free(tmp); +} diff --cc src/feature/nodelist/networkstatus.c index de2451b79,97a8779be..ec1a69b9e --- a/src/feature/nodelist/networkstatus.c +++ b/src/feature/nodelist/networkstatus.c @@@ -2109,14 -2106,14 +2117,15 @@@ networkstatus_set_current_consensus(con }
if (we_want_to_fetch_flavor(options, flav)) { - dirserv_set_cached_consensus_networkstatus(consensus, - consensus_len, - flavor, - &c->digests, - c->digest_sha3_as_signed, - c->valid_after); if (dir_server_mode(get_options())) { + dirserv_set_cached_consensus_networkstatus(consensus, ++ consensus_len, + flavor, + &c->digests, + c->digest_sha3_as_signed, + c->valid_after); + - consdiffmgr_add_consensus(consensus, c); + consdiffmgr_add_consensus(consensus, consensus_len, c); } }
tor-commits@lists.torproject.org