diff --git a/src/core/include.am b/src/core/include.am index 6815f327fe..1cecd8ba77 100644 --- a/src/core/include.am +++ b/src/core/include.am @@ -75,6 +75,7 @@ LIBTOR_APP_A_SOURCES = \ src/feature/dircommon/voting_schedule.c \ src/feature/dirparse/authcert_parse.c \ src/feature/dirparse/microdesc_parse.c \ + src/feature/dirparse/ns_parse.c \ src/feature/dirparse/parsecommon.c \ src/feature/dirparse/routerparse.c \ src/feature/dirparse/sigcommon.c \ @@ -298,6 +299,7 @@ noinst_HEADERS += \ src/feature/dirparse/authcert_members.i \ src/feature/dirparse/authcert_parse.h \ src/feature/dirparse/microdesc_parse.h \ + src/feature/dirparse/ns_parse.h \ src/feature/dirparse/parsecommon.h \ src/feature/dirparse/routerparse.h \ src/feature/dirparse/sigcommon.h \ diff --git a/src/feature/dirauth/bwauth.c b/src/feature/dirauth/bwauth.c index ec23f629c5..29e0c90962 100644 --- a/src/feature/dirauth/bwauth.c +++ b/src/feature/dirauth/bwauth.c @@ -15,7 +15,7 @@ #include "app/config/config.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/routerlist.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/vote_routerstatus_st.h" diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c index 0322c9cae7..80a5b54737 100644 --- a/src/feature/dirauth/dirvote.c +++ b/src/feature/dirauth/dirvote.c @@ -6,32 +6,33 @@ #define DIRVOTE_PRIVATE #include "core/or/or.h" #include "app/config/config.h" -#include "feature/dirauth/dircollate.h" -#include "feature/dirauth/dsigs_parse.h" -#include "feature/dirauth/recommend_pkg.h" -#include "feature/dirauth/voteflags.h" -#include "feature/dircommon/directory.h" -#include "feature/dirclient/dirclient.h" -#include "feature/dirauth/bwauth.h" -#include "feature/dircache/dirserv.h" -#include "feature/dirauth/guardfraction.h" -#include "feature/nodelist/microdesc.h" -#include "feature/nodelist/networkstatus.h" -#include "feature/nodelist/nodelist.h" -#include "feature/dirparse/parsecommon.h" #include "core/or/policies.h" #include "core/or/protover.h" #include "core/or/tor_version_st.h" -#include "feature/stats/rephist.h" -#include "feature/relay/router.h" -#include "feature/relay/routerkeys.h" +#include "core/or/versions.h" +#include "feature/dirauth/bwauth.h" +#include "feature/dirauth/dircollate.h" +#include "feature/dirauth/dsigs_parse.h" +#include "feature/dirauth/guardfraction.h" +#include "feature/dirauth/recommend_pkg.h" +#include "feature/dirauth/voteflags.h" +#include "feature/dircache/dirserv.h" +#include "feature/dirclient/dirclient.h" +#include "feature/dircommon/directory.h" +#include "feature/dirparse/microdesc_parse.h" +#include "feature/dirparse/ns_parse.h" +#include "feature/dirparse/parsecommon.h" +#include "feature/dirparse/signing.h" #include "feature/nodelist/authcert.h" #include "feature/nodelist/dirlist.h" -#include "feature/nodelist/routerlist.h" -#include "feature/dirparse/routerparse.h" -#include "feature/dirparse/microdesc_parse.h" -#include "feature/dirparse/signing.h" #include "feature/nodelist/fmt_routerstatus.h" +#include "feature/nodelist/microdesc.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerlist.h" +#include "feature/relay/router.h" +#include "feature/relay/routerkeys.h" +#include "feature/stats/rephist.h" #include "feature/client/entrynodes.h" /* needed for guardfraction methods */ #include "feature/nodelist/torcert.h" #include "feature/dircommon/voting_schedule.h" diff --git a/src/feature/dirauth/guardfraction.c b/src/feature/dirauth/guardfraction.c index 812cb7df2c..1734d0a13c 100644 --- a/src/feature/dirauth/guardfraction.c +++ b/src/feature/dirauth/guardfraction.c @@ -12,7 +12,7 @@ #include "core/or/or.h" #include "feature/dirauth/guardfraction.h" #include "feature/nodelist/networkstatus.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "feature/nodelist/vote_routerstatus_st.h" diff --git a/src/feature/dircache/consdiffmgr.c b/src/feature/dircache/consdiffmgr.c index 7dc56f4f14..e79aad6efb 100644 --- a/src/feature/dircache/consdiffmgr.c +++ b/src/feature/dircache/consdiffmgr.c @@ -20,7 +20,7 @@ #include "feature/dircache/consdiffmgr.h" #include "core/mainloop/cpuworker.h" #include "feature/nodelist/networkstatus.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "lib/evloop/compat_libevent.h" #include "lib/evloop/workqueue.h" #include "lib/compress/compress.h" diff --git a/src/feature/dircommon/consdiff.c b/src/feature/dircommon/consdiff.c index 944ddd552b..f8ced8503f 100644 --- a/src/feature/dircommon/consdiff.c +++ b/src/feature/dircommon/consdiff.c @@ -41,7 +41,7 @@ #include "core/or/or.h" #include "feature/dircommon/consdiff.h" #include "lib/memarea/memarea.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" static const char* ns_diff_version = "network-status-diff-version 1"; static const char* hash_token = "hash"; diff --git a/src/feature/dirparse/ns_parse.c b/src/feature/dirparse/ns_parse.c new file mode 100644 index 0000000000..e356c0c11f --- /dev/null +++ b/src/feature/dirparse/ns_parse.c @@ -0,0 +1,1684 @@ +/* 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 "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 + +/** 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 s. On success, set start_out to the first + * character of the document, and end_out 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, + const char **start_out, + const char **end_out) +{ + return router_get_hash_impl_helper(s, strlen(s), + "network-status-version", + "\ndirectory-signature", + ' ', LOG_INFO, + start_out, end_out); +} + +/** Set digest_out to the SHA3-256 digest of the signed portion of the + * networkstatus vote in s -- or of the entirety of s 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 *start, *end; + if (router_get_networkstatus_v3_signed_boundaries(s, &start, &end) < 0) { + start = s; + end = s + strlen(s); + } + tor_assert(start); + tor_assert(end); + return crypto_digest256((char*)digest_out, start, end-start, + DIGEST_SHA3_256); +} + +/** Set digests to all the digests of the consensus document in + * s */ +int +router_get_networkstatus_v3_hashes(const char *s, common_digests_t *digests) +{ + return router_get_hashes_impl(s,strlen(s),digests, + "network-status-version", + "\ndirectory-signature", + ' '); +} + +/** Helper: given a string s, 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) +{ + const char *eos, *footer, *sig; + if ((eos = strstr(s, "\nr "))) + ++eos; + else + eos = s + strlen(s); + + 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 vote or vote_rs 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 *s, containing a routerstatus object, and an + * empty smartlist at tokens, parse and return the first router status + * object in the string, and advance *s to just after the end of the + * router status. Return NULL and advance *s on error. + * + * If vote and vote_rs are provided, don't allocate a fresh + * routerstatus but use vote_rs instead. + * + * If consensus_method 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 flav. + **/ +STATIC routerstatus_t * +routerstatus_parse_entry_from_string(memarea_t *area, + const char **s, 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); + + 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)<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 srv_type is in + * tokens. If there is, parse it and set it to srv_out. 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 tokens and place them in + * the networkstatus ns. */ +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 s, and return the result. Return NULL on failure. */ +networkstatus_t * +networkstatus_parse_vote_from_string(const char *s, 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; + + 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) { + 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); + 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"))) + goto err; + ++cert; + ns->cert = authority_cert_parse_from_string(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<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 ")) { + 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, + 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, + 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 "))) + ++end_of_footer; + else + end_of_footer = s + strlen(s); + 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 --git a/src/feature/dirparse/ns_parse.h b/src/feature/dirparse/ns_parse.h new file mode 100644 index 0000000000..22438d73a7 --- /dev/null +++ b/src/feature/dirparse/ns_parse.h @@ -0,0 +1,45 @@ +/* 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, + common_digests_t *digests); +int router_get_networkstatus_v3_signed_boundaries(const char *s, + const char **start_out, + const char **end_out); +int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, + const char *s); +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, + 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, + networkstatus_t *vote, + vote_routerstatus_t *vote_rs, + int consensus_method, + consensus_flavor_t flav); +#endif + +#endif diff --git a/src/feature/dirparse/routerparse.c b/src/feature/dirparse/routerparse.c index daeb67c917..956e8f3eae 100644 --- a/src/feature/dirparse/routerparse.c +++ b/src/feature/dirparse/routerparse.c @@ -193,110 +193,6 @@ static token_rule_t extrainfo_token_table[] = { END_OF_TABLE }; -/** 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 -}; - #undef T /* static function prototypes */ @@ -318,52 +214,6 @@ router_get_router_hash(const char *s, size_t s_len, char *digest) DIGEST_SHA1); } -/** Try to find the start and end of the signed portion of a networkstatus - * document in s. On success, set start_out to the first - * character of the document, and end_out 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, - const char **start_out, - const char **end_out) -{ - return router_get_hash_impl_helper(s, strlen(s), - "network-status-version", - "\ndirectory-signature", - ' ', LOG_INFO, - start_out, end_out); -} - -/** Set digest_out to the SHA3-256 digest of the signed portion of the - * networkstatus vote in s -- or of the entirety of s 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 *start, *end; - if (router_get_networkstatus_v3_signed_boundaries(s, &start, &end) < 0) { - start = s; - end = s + strlen(s); - } - tor_assert(start); - tor_assert(end); - return crypto_digest256((char*)digest_out, start, end-start, - DIGEST_SHA3_256); -} - -/** Set digests to all the digests of the consensus document in - * s */ -int -router_get_networkstatus_v3_hashes(const char *s, common_digests_t *digests) -{ - return router_get_hashes_impl(s,strlen(s),digests, - "network-status-version", - "\ndirectory-signature", - ' '); -} - /** Set digest to the SHA-1 digest of the hash of the s_len-byte * extrainfo string at s. Return 0 on success, -1 on failure. */ int @@ -1351,93 +1201,10 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, return extrainfo; } -/** Helper: given a string s, 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) -{ - const char *eos, *footer, *sig; - if ((eos = strstr(s, "\nr "))) - ++eos; - else - eos = s + strlen(s); - - 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 vote or vote_rs 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; -} - /** Summarize the protocols listed in protocols into out, * falling back or correcting them based on version as appropriate. */ -STATIC void +void summarize_protover_flags(protover_summary_flags_t *out, const char *protocols, const char *version) @@ -1478,1417 +1245,6 @@ summarize_protover_flags(protover_summary_flags_t *out, } } -/** Given a string at *s, containing a routerstatus object, and an - * empty smartlist at tokens, parse and return the first router status - * object in the string, and advance *s to just after the end of the - * router status. Return NULL and advance *s on error. - * - * If vote and vote_rs are provided, don't allocate a fresh - * routerstatus but use vote_rs instead. - * - * If consensus_method 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 flav. - **/ -STATIC routerstatus_t * -routerstatus_parse_entry_from_string(memarea_t *area, - const char **s, 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); - - 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)<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 srv_type is in - * tokens. If there is, parse it and set it to srv_out. 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 tokens and place them in - * the networkstatus ns. */ -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 s, and return the result. Return NULL on failure. */ -networkstatus_t * -networkstatus_parse_vote_from_string(const char *s, 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; - - 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) { - 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); - 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"))) - goto err; - ++cert; - ns->cert = authority_cert_parse_from_string(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<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 ")) { - 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, - 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, - 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 "))) - ++end_of_footer; - else - end_of_footer = s + strlen(s); - 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; -} - /** Parse the addr policy in the string s and return it. If * assume_action is nonnegative, then insert its action (ADDR_POLICY_ACCEPT or * ADDR_POLICY_REJECT) for items that specify no action. diff --git a/src/feature/dirparse/routerparse.h b/src/feature/dirparse/routerparse.h index eb0ca880f3..80c974e060 100644 --- a/src/feature/dirparse/routerparse.h +++ b/src/feature/dirparse/routerparse.h @@ -14,16 +14,7 @@ #include "core/or/versions.h" -enum networkstatus_type_t; - int router_get_router_hash(const char *s, size_t s_len, char *digest); -int router_get_networkstatus_v3_hashes(const char *s, - common_digests_t *digests); -int router_get_networkstatus_v3_signed_boundaries(const char *s, - const char **start_out, - const char **end_out); -int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, - const char *s); int router_get_extrainfo_hash(const char *s, size_t s_len, char *digest); int router_parse_list_from_string(const char **s, const char *eos, @@ -46,12 +37,6 @@ extrainfo_t *extrainfo_parse_entry_from_string(const char *s, const char *end, MOCK_DECL(addr_policy_t *, router_parse_addr_policy_item_from_string, (const char *s, int assume_action, int *malformed_list)); -int compare_vote_routerstatus_entries(const void **_a, const void **_b); -int networkstatus_verify_bw_weights(networkstatus_t *ns, int); -networkstatus_t *networkstatus_parse_vote_from_string(const char *s, - const char **eos_out, - enum networkstatus_type_t ns_type); - int find_single_ipv6_orport(const smartlist_t *list, tor_addr_t *addr_out, uint16_t *port_out); @@ -59,23 +44,9 @@ int find_single_ipv6_orport(const smartlist_t *list, void routerparse_init(void); void routerparse_free_all(void); -#ifdef ROUTERPARSE_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, - networkstatus_t *vote, - vote_routerstatus_t *vote_rs, - int consensus_method, - consensus_flavor_t flav); -STATIC void summarize_protover_flags(protover_summary_flags_t *out, - const char *protocols, +void summarize_protover_flags(protover_summary_flags_t *out, + const char *protocols, const char *version); -#endif /* defined(ROUTERPARSE_PRIVATE) */ #define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1" diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c index d214e6470d..de2451b79c 100644 --- a/src/feature/nodelist/networkstatus.c +++ b/src/feature/nodelist/networkstatus.c @@ -53,6 +53,7 @@ #include "core/or/protover.h" #include "core/or/relay.h" #include "core/or/scheduler.h" +#include "core/or/versions.h" #include "feature/client/bridges.h" #include "feature/client/entrynodes.h" #include "feature/client/transports.h" @@ -64,7 +65,7 @@ #include "feature/dirclient/dlstatus.h" #include "feature/dircommon/directory.h" #include "feature/dircommon/voting_schedule.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "feature/hibernate/hibernate.h" #include "feature/nodelist/authcert.h" #include "feature/nodelist/dirlist.h" diff --git a/src/test/fuzz/fuzz_consensus.c b/src/test/fuzz/fuzz_consensus.c index 64507c67ea..b56702a650 100644 --- a/src/test/fuzz/fuzz_consensus.c +++ b/src/test/fuzz/fuzz_consensus.c @@ -2,7 +2,7 @@ /* See LICENSE for licensing information */ #define SIGCOMMON_PRIVATE #include "core/or/or.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "feature/dirparse/sigcommon.h" #include "feature/dirparse/unparseable.h" #include "feature/nodelist/networkstatus.h" diff --git a/src/test/fuzz/fuzz_vrs.c b/src/test/fuzz/fuzz_vrs.c index 8fb6e6ef24..0b869aa5c0 100644 --- a/src/test/fuzz/fuzz_vrs.c +++ b/src/test/fuzz/fuzz_vrs.c @@ -1,9 +1,9 @@ /* Copyright (c) 2016-2018, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#define ROUTERPARSE_PRIVATE +#define NS_PARSE_PRIVATE #define NETWORKSTATUS_PRIVATE #include "core/or/or.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "feature/dirparse/unparseable.h" #include "lib/memarea/memarea.h" #include "feature/nodelist/microdesc.h" diff --git a/src/test/test_consdiffmgr.c b/src/test/test_consdiffmgr.c index dc05c351bf..6c451da685 100644 --- a/src/test/test_consdiffmgr.c +++ b/src/test/test_consdiffmgr.c @@ -11,7 +11,7 @@ #include "core/mainloop/cpuworker.h" #include "lib/crypt_ops/crypto_rand.h" #include "feature/nodelist/networkstatus.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "lib/evloop/workqueue.h" #include "lib/compress/compress.h" #include "lib/encoding/confline.h" diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 59464022b4..3b5d6dc3d6 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -16,6 +16,7 @@ #define DLSTATUS_PRIVATE #define HIBERNATE_PRIVATE #define NETWORKSTATUS_PRIVATE +#define NS_PARSE_PRIVATE #define NODE_SELECT_PRIVATE #define RELAY_PRIVATE #define ROUTERLIST_PRIVATE @@ -54,6 +55,7 @@ #include "feature/nodelist/node_select.h" #include "feature/nodelist/routerlist.h" #include "feature/dirparse/authcert_parse.h" +#include "feature/dirparse/ns_parse.h" #include "feature/dirparse/routerparse.h" #include "feature/dirparse/unparseable.h" #include "feature/nodelist/routerset.h" diff --git a/src/test/test_dir_common.c b/src/test/test_dir_common.c index 5a9659ad4f..86d2838944 100644 --- a/src/test/test_dir_common.c +++ b/src/test/test_dir_common.c @@ -11,6 +11,7 @@ #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" #include "feature/dirparse/authcert_parse.h" +#include "feature/dirparse/ns_parse.h" #include "test/test_dir_common.h" #include "feature/dircommon/voting_schedule.h" diff --git a/src/test/test_guardfraction.c b/src/test/test_guardfraction.c index c7266be66f..d6f4cd63f2 100644 --- a/src/test/test_guardfraction.c +++ b/src/test/test_guardfraction.c @@ -2,15 +2,15 @@ /* See LICENSE for licensing information */ #define GUARDFRACTION_PRIVATE -#define ROUTERPARSE_PRIVATE #define NETWORKSTATUS_PRIVATE +#define NS_PARSE_PRIVATE #include "orconfig.h" #include "core/or/or.h" #include "app/config/config.h" #include "feature/dirauth/guardfraction.h" #include "feature/client/entrynodes.h" -#include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/networkstatus_st.h" diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index d4606c227d..b386ad5298 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -35,6 +35,7 @@ #include "feature/nodelist/routerset.h" #include "feature/dirparse/authcert_parse.h" #include "feature/dirparse/routerparse.h" +#include "feature/dirparse/ns_parse.h" #include "feature/dirauth/shared_random.h" #include "app/config/statefile.h"