diff options
-rw-r--r-- | changes/bug2286 | 5 | ||||
-rw-r--r-- | src/or/dirserv.c | 21 | ||||
-rw-r--r-- | src/or/dirserv.h | 3 | ||||
-rw-r--r-- | src/or/dirvote.c | 57 | ||||
-rw-r--r-- | src/or/dirvote.h | 10 | ||||
-rw-r--r-- | src/or/networkstatus.c | 13 | ||||
-rw-r--r-- | src/or/networkstatus.h | 2 | ||||
-rw-r--r-- | src/or/or.h | 10 | ||||
-rw-r--r-- | src/or/routerparse.c | 17 | ||||
-rw-r--r-- | src/or/routerparse.h | 1 | ||||
-rw-r--r-- | src/test/test_dir.c | 982 |
11 files changed, 923 insertions, 198 deletions
diff --git a/changes/bug2286 b/changes/bug2286 new file mode 100644 index 0000000000..4f8dfbbf68 --- /dev/null +++ b/changes/bug2286 @@ -0,0 +1,5 @@ + o Major features (directory authority): + - Directory authorities now support a new consensus method (17) + where they cap the published bandwidth of servers for which + insufficient bandwidth measurements exist. Fixes part of bug + 2286. diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 0819d4bd24..badacd683d 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -2101,13 +2101,15 @@ version_from_platform(const char *platform) * NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry * NS_V3_CONSENSUS_MICRODESC - Output the first portion of a V3 microdesc * consensus entry. - * NS_V3_VOTE - Output a complete V3 NS vote + * NS_V3_VOTE - Output a complete V3 NS vote. If <b>vrs</b> is present, + * it contains additional information for the vote. * NS_CONTROL_PORT - Output a NS document for the control port */ int routerstatus_format_entry(char *buf, size_t buf_len, const routerstatus_t *rs, const char *version, - routerstatus_format_type_t format) + routerstatus_format_type_t format, + const vote_routerstatus_t *vrs) { int r; char *cp; @@ -2253,10 +2255,10 @@ routerstatus_format_entry(char *buf, size_t buf_len, return -1; } cp += strlen(cp); - if (format == NS_V3_VOTE && rs->has_measured_bw) { + if (format == NS_V3_VOTE && vrs && vrs->has_measured_bw) { *--cp = '\0'; /* Kill "\n" */ r = tor_snprintf(cp, buf_len - (cp-buf), - " Measured=%d\n", rs->measured_bw); + " Measured=%d\n", vrs->measured_bw); if (r<0) { log_warn(LD_BUG, "Not enough space in buffer for weight line."); return -1; @@ -2639,12 +2641,12 @@ int measured_bw_line_apply(measured_bw_line_t *parsed_line, smartlist_t *routerstatuses) { - routerstatus_t *rs = NULL; + vote_routerstatus_t *rs = NULL; if (!routerstatuses) return 0; rs = smartlist_bsearch(routerstatuses, parsed_line->node_id, - compare_digest_to_routerstatus_entry); + compare_digest_to_vote_routerstatus_entry); if (rs) { rs->has_measured_bw = 1; @@ -2659,7 +2661,7 @@ measured_bw_line_apply(measured_bw_line_t *parsed_line, /** * Read the measured bandwidth file and apply it to the list of - * routerstatuses. Returns -1 on error, 0 otherwise. + * vote_routerstatus_t. Returns -1 on error, 0 otherwise. */ int dirserv_read_measured_bandwidths(const char *from_file, @@ -2701,7 +2703,7 @@ dirserv_read_measured_bandwidths(const char *from_file, } if (routerstatuses) - smartlist_sort(routerstatuses, compare_routerstatus_entries); + smartlist_sort(routerstatuses, compare_vote_routerstatus_entries); while (!feof(fp)) { measured_bw_line_t parsed_line; @@ -3052,7 +3054,8 @@ generate_v2_networkstatus_opinion(void) if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest)) clear_status_flags_on_sybil(&rs); - if (routerstatus_format_entry(outp, endp-outp, &rs, version, NS_V2)) { + if (routerstatus_format_entry(outp, endp-outp, &rs, version, NS_V2, + NULL)) { log_warn(LD_BUG, "Unable to print router status."); tor_free(version); goto done; diff --git a/src/or/dirserv.h b/src/or/dirserv.h index add09f44a3..0caf55f830 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -131,7 +131,8 @@ size_t dirserv_estimate_microdesc_size(const smartlist_t *fps, int compressed); int routerstatus_format_entry(char *buf, size_t buf_len, const routerstatus_t *rs, const char *platform, - routerstatus_format_type_t format); + routerstatus_format_type_t format, + const vote_routerstatus_t *vrs); void dirserv_free_all(void); void cached_dir_decref(cached_dir_t *d); cached_dir_t *new_cached_dir(char *s, time_t published); diff --git a/src/or/dirvote.c b/src/or/dirvote.c index 358708b6c5..bcfe2b0698 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -212,7 +212,7 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, vrs) { vote_microdesc_hash_t *h; if (routerstatus_format_entry(outp, endp-outp, &vrs->status, - vrs->version, NS_V3_VOTE) < 0) { + vrs->version, NS_V3_VOTE, vrs) < 0) { log_warn(LD_BUG, "Unable to print router status."); goto err; } @@ -1388,6 +1388,7 @@ networkstatus_compute_consensus(smartlist_t *votes, char *client_versions = NULL, *server_versions = NULL; smartlist_t *flags; const char *flavor_name; + uint32_t max_unmeasured_bw = DEFAULT_MAX_UNMEASURED_BW; int64_t G=0, M=0, E=0, D=0, T=0; /* For bandwidth weights */ const routerstatus_format_type_t rs_format = flavor == FLAV_NS ? NS_V3_CONSENSUS : NS_V3_CONSENSUS_MICRODESC; @@ -1586,6 +1587,30 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_free(dir_sources); } + if (consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW) { + char *max_unmeasured_param = NULL; + /* XXXX Extract this code into a common function */ + if (params) { + if (strcmpstart(params, "maxunmeasuredbw=") == 0) + max_unmeasured_param = params; + else + max_unmeasured_param = strstr(params, " maxunmeasuredbw="); + } + if (max_unmeasured_param) { + int ok = 0; + char *eq = strchr(max_unmeasured_param, '='); + if (eq) { + max_unmeasured_bw = (uint32_t) + tor_parse_ulong(eq+1, 10, 1, UINT32_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_DIR, "Bad element '%s' in max unmeasured bw param", + escaped(max_unmeasured_param)); + max_unmeasured_bw = DEFAULT_MAX_UNMEASURED_BW; + } + } + } + } + /* Add the actual router entries. */ { int *index; /* index[j] is the current index into votes[j]. */ @@ -1612,6 +1637,7 @@ networkstatus_compute_consensus(smartlist_t *votes, int *named_flag; /* Index of the flag "Named" for votes[j] */ int *unnamed_flag; /* Index of the flag "Unnamed" for votes[j] */ int chosen_named_idx; + int n_authorities_measuring_bandwidth; strmap_t *name_to_id_map = strmap_new(); char conflict[DIGEST_LEN]; @@ -1700,6 +1726,14 @@ networkstatus_compute_consensus(smartlist_t *votes, } SMARTLIST_FOREACH_END(v); } + /* We need to know how many votes measure bandwidth. */ + n_authorities_measuring_bandwidth = 0; + SMARTLIST_FOREACH(votes, networkstatus_t *, v, + if (v->has_measured_bws) { + ++n_authorities_measuring_bandwidth; + } + ); + /* Now go through all the votes */ flag_counts = tor_malloc(sizeof(int) * smartlist_len(flags)); while (1) { @@ -1769,8 +1803,8 @@ networkstatus_compute_consensus(smartlist_t *votes, } /* count bandwidths */ - if (rs->status.has_measured_bw) - measured_bws[num_mbws++] = rs->status.measured_bw; + if (rs->has_measured_bw) + measured_bws[num_mbws++] = rs->measured_bw; if (rs->status.has_bandwidth) bandwidths[num_bandwidths++] = rs->status.bandwidth; @@ -1863,10 +1897,19 @@ networkstatus_compute_consensus(smartlist_t *votes, /* Pick a bandwidth */ if (consensus_method >= 6 && num_mbws > 2) { rs_out.has_bandwidth = 1; + rs_out.bw_is_unmeasured = 0; rs_out.bandwidth = median_uint32(measured_bws, num_mbws); } else if (consensus_method >= 5 && num_bandwidths > 0) { rs_out.has_bandwidth = 1; + rs_out.bw_is_unmeasured = 1; rs_out.bandwidth = median_uint32(bandwidths, num_bandwidths); + if (consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW && + n_authorities_measuring_bandwidth > 2) { + /* Cap non-measured bandwidths. */ + if (rs_out.bandwidth > max_unmeasured_bw) { + rs_out.bandwidth = max_unmeasured_bw; + } + } } /* Fix bug 2203: Do not count BadExit nodes as Exits for bw weights */ @@ -1987,7 +2030,7 @@ networkstatus_compute_consensus(smartlist_t *votes, /* Okay!! Now we can write the descriptor... */ /* First line goes into "buf". */ routerstatus_format_entry(buf, sizeof(buf), &rs_out, NULL, - rs_format); + rs_format, NULL); smartlist_add(chunks, tor_strdup(buf)); } /* Now an m line, if applicable. */ @@ -2008,7 +2051,10 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_add(chunks, tor_strdup("\n")); /* Now the weight line. */ if (rs_out.has_bandwidth) { - smartlist_add_asprintf(chunks, "w Bandwidth=%d\n", rs_out.bandwidth); + int unmeasured = rs_out.bw_is_unmeasured && + consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW; + smartlist_add_asprintf(chunks, "w Bandwidth=%d%s\n", rs_out.bandwidth, + unmeasured?" Unmeasured=1":""); } /* Now the exitpolicy summary line. */ @@ -2051,6 +2097,7 @@ networkstatus_compute_consensus(smartlist_t *votes, // Parse params, extract BW_WEIGHT_SCALE if present // DO NOT use consensus_param_bw_weight_scale() in this code! // The consensus is not formed yet! + /* XXXX Extract this code into a common function */ if (params) { if (strcmpstart(params, "bwweightscale=") == 0) bw_weight_param = params; diff --git a/src/or/dirvote.h b/src/or/dirvote.h index 366b7cf037..fbb61b652f 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -20,7 +20,7 @@ #define MIN_VOTE_INTERVAL 300 /** The highest consensus method that we currently support. */ -#define MAX_SUPPORTED_CONSENSUS_METHOD 16 +#define MAX_SUPPORTED_CONSENSUS_METHOD 17 /** Lowest consensus method that contains a 'directory-footer' marker */ #define MIN_METHOD_FOR_FOOTER 9 @@ -52,6 +52,14 @@ * line */ #define MIN_METHOD_FOR_NTOR_KEY 16 +/** Lowest consensus method that ensures that authorities output an + * Unmeasured=1 flag for unmeasured bandwidths */ +#define MIN_METHOD_TO_CLIP_UNMEASURED_BW 17 + +/** Default bandwidth to clip unmeasured bandwidths to using method >= + * MIN_METHOD_TO_CLIP_UNMEASURED_BW */ +#define DEFAULT_MAX_UNMEASURED_BW 20 + void dirvote_free_all(void); /* vote manipulation */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 71ac054f88..c63c76fccd 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -937,6 +937,17 @@ compare_digest_to_routerstatus_entry(const void *_key, const void **_member) return tor_memcmp(key, rs->identity_digest, DIGEST_LEN); } +/** Helper for bsearching a list of routerstatus_t pointers: compare a + * digest in the key to the identity digest of a routerstatus_t. */ +int +compare_digest_to_vote_routerstatus_entry(const void *_key, + const void **_member) +{ + const char *key = _key; + const vote_routerstatus_t *vrs = *_member; + return tor_memcmp(key, vrs->status.identity_digest, DIGEST_LEN); +} + /** As networkstatus_v2_find_entry, but do not return a const pointer */ routerstatus_t * networkstatus_v2_find_mutable_entry(networkstatus_v2_t *ns, const char *digest) @@ -2117,7 +2128,7 @@ char * networkstatus_getinfo_helper_single(const routerstatus_t *rs) { char buf[RS_ENTRY_LEN+1]; - routerstatus_format_entry(buf, sizeof(buf), rs, NULL, NS_CONTROL_PORT); + routerstatus_format_entry(buf, sizeof(buf), rs, NULL, NS_CONTROL_PORT, NULL); return tor_strdup(buf); } diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index b437c5ec2f..b64e4b8e1a 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -38,6 +38,8 @@ int router_set_networkstatus_v2(const char *s, time_t arrived_at, void networkstatus_v2_list_clean(time_t now); int compare_digest_to_routerstatus_entry(const void *_key, const void **_member); +int compare_digest_to_vote_routerstatus_entry(const void *_key, + const void **_member); const routerstatus_t *networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest); const routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns, diff --git a/src/or/or.h b/src/or/or.h index d9da49d32d..45eb4673ce 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2091,9 +2091,8 @@ typedef struct routerstatus_t { unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ - unsigned int has_measured_bw:1; /**< The vote/consensus had a measured bw */ - - uint32_t measured_bw; /**< Measured bandwidth (capacity) of the router */ + unsigned int bw_is_unmeasured:1; /**< This is a consensus entry, with + * the Unmeasured flag set. */ uint32_t bandwidth; /**< Bandwidth (capacity) of the router as reported in * the vote/consensus, in kilobytes/sec. */ @@ -2337,6 +2336,8 @@ typedef struct vote_routerstatus_t { * networkstatus_t.known_flags. */ char *version; /**< The version that the authority says this router is * running. */ + unsigned int has_measured_bw:1; /**< The vote had a measured bw */ + uint32_t measured_bw; /**< Measured bandwidth (capacity) of the router */ /** The hash or hashes that the authority claims this microdesc has. */ vote_microdesc_hash_t *microdesc; } vote_routerstatus_t; @@ -2402,6 +2403,9 @@ typedef enum { typedef struct networkstatus_t { ENUM_BF(networkstatus_type_t) type : 8; /**< Vote, consensus, or opinion? */ ENUM_BF(consensus_flavor_t) flavor : 8; /**< If a consensus, what kind? */ + unsigned int has_measured_bws : 1;/**< True iff this networkstatus contains + * measured= bandwidth values. */ + time_t published; /**< Vote only: Time when vote was written. */ time_t valid_after; /**< Time after which this vote or consensus applies. */ time_t fresh_until; /**< Time before which this is the most recent vote or diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 2a3de12c35..ce2cd5c513 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -1974,9 +1974,9 @@ routerstatus_parse_entry_from_string(memarea_t *area, goto err; } rs->has_bandwidth = 1; - } else if (!strcmpstart(tok->args[i], "Measured=")) { + } else if (!strcmpstart(tok->args[i], "Measured=") && vote_rs) { int ok; - rs->measured_bw = + vote_rs->measured_bw = (uint32_t)tor_parse_ulong(strchr(tok->args[i], '=')+1, 10, 0, UINT32_MAX, &ok, NULL); if (!ok) { @@ -1984,7 +1984,10 @@ routerstatus_parse_entry_from_string(memarea_t *area, escaped(tok->args[i])); goto err; } - rs->has_measured_bw = 1; + vote_rs->has_measured_bw = 1; + vote->has_measured_bws = 1; + } else if (!strcmpstart(tok->args[i], "Unmeasured=1")) { + rs->bw_is_unmeasured = 1; } } } @@ -2062,6 +2065,14 @@ compare_routerstatus_entries(const void **_a, const void **_b) return fast_memcmp(a->identity_digest, b->identity_digest, DIGEST_LEN); } +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); +} + /** Helper: used in call to _smartlist_uniq to clear out duplicate entries. */ static void free_duplicate_routerstatus_entry_(void *e) diff --git a/src/or/routerparse.h b/src/or/routerparse.h index 4cc9bfd3ae..859a691e2a 100644 --- a/src/or/routerparse.h +++ b/src/or/routerparse.h @@ -52,6 +52,7 @@ void assert_addr_policy_ok(smartlist_t *t); void dump_distinct_digest_count(int severity); int compare_routerstatus_entries(const void **_a, const void **_b); +int compare_vote_routerstatus_entries(const void **_a, const void **_b); networkstatus_v2_t *networkstatus_v2_parse_from_string(const char *s); int networkstatus_verify_bw_weights(networkstatus_t *ns); networkstatus_t *networkstatus_parse_vote_from_string(const char *s, diff --git a/src/test/test_dir.c b/src/test/test_dir.c index f1c745ef78..fbd49b7106 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -746,6 +746,17 @@ generate_ri_from_rs(const vote_routerstatus_t *vrs) strlen(r->cache_info.signed_descriptor_body); r->exit_policy = smartlist_new(); r->cache_info.published_on = ++published + time(NULL); + if (rs->has_bandwidth) { + /* + * Multiply by 1000 because the routerinfo_t and the routerstatus_t + * seem to use different units (*sigh*) and because we seem stuck on + * icky and perverse decimal kilobytes (*double sigh*) - see + * router_get_advertised_bandwidth_capped() of routerlist.c and + * routerstatus_format_entry() of dirserv.c. + */ + r->bandwidthrate = rs->bandwidth * 1000; + r->bandwidthcapacity = rs->bandwidth * 1000; + } return r; } @@ -766,15 +777,321 @@ get_detached_sigs(networkstatus_t *ns, networkstatus_t *ns2) return r; } -/** Run unit tests for generating and parsing V3 consensus networkstatus - * documents. */ +/** + * Generate a routerstatus for v3_networkstatus test + */ +static vote_routerstatus_t * +gen_routerstatus_for_v3ns(int idx, time_t now) +{ + vote_routerstatus_t *vrs=NULL; + routerstatus_t *rs; + tor_addr_t addr_ipv6; + + switch (idx) { + case 0: + /* Generate the first routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.2.14"); + rs->published_on = now-1500; + strlcpy(rs->nickname, "router2", sizeof(rs->nickname)); + memset(rs->identity_digest, 3, DIGEST_LEN); + memset(rs->descriptor_digest, 78, DIGEST_LEN); + rs->addr = 0x99008801; + rs->or_port = 443; + rs->dir_port = 8000; + /* all flags but running cleared */ + rs->is_flagged_running = 1; + break; + case 1: + /* Generate the second routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.2.0.5"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router1", sizeof(rs->nickname)); + memset(rs->identity_digest, 5, DIGEST_LEN); + memset(rs->descriptor_digest, 77, DIGEST_LEN); + rs->addr = 0x99009901; + rs->or_port = 443; + rs->dir_port = 0; + tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); + tor_addr_copy(&rs->ipv6_addr, &addr_ipv6); + rs->ipv6_orport = 4711; + rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running = + rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1; + break; + case 2: + /* Generate the third routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.0.3"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router3", sizeof(rs->nickname)); + memset(rs->identity_digest, 33, DIGEST_LEN); + memset(rs->descriptor_digest, 79, DIGEST_LEN); + rs->addr = 0xAA009901; + rs->or_port = 400; + rs->dir_port = 9999; + rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = + rs->is_flagged_running = rs->is_valid = rs->is_v2_dir = + rs->is_possible_guard = 1; + break; + case 3: + /* Generate a fourth routerstatus that is not running. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.6.3"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router4", sizeof(rs->nickname)); + memset(rs->identity_digest, 34, DIGEST_LEN); + memset(rs->descriptor_digest, 47, DIGEST_LEN); + rs->addr = 0xC0000203; + rs->or_port = 500; + rs->dir_port = 1999; + /* Running flag (and others) cleared */ + break; + case 4: + /* No more for this test; return NULL */ + vrs = NULL; + break; + default: + /* Shouldn't happen */ + test_assert(0); + } + + done: + return vrs; +} + +/** Apply tweaks to the vote list for each voter */ +static int +vote_tweaks_for_v3ns(networkstatus_t *v, int voter, time_t now) +{ + vote_routerstatus_t *vrs; + const char *msg = NULL; + + test_assert(v); + (void)now; + + if (voter == 1) { + measured_bw_line_t mbw; + memset(mbw.node_id, 33, sizeof(mbw.node_id)); + mbw.bw = 1024; + test_assert(measured_bw_line_apply(&mbw, + v->routerstatus_list) == 1); + } else if (voter == 2 || voter == 3) { + /* Monkey around with the list a bit */ + vrs = smartlist_get(v->routerstatus_list, 2); + smartlist_del_keeporder(v->routerstatus_list, 2); + tor_free(vrs->version); + tor_free(vrs); + vrs = smartlist_get(v->routerstatus_list, 0); + vrs->status.is_fast = 1; + + if (voter == 3) { + vrs = smartlist_get(v->routerstatus_list, 0); + smartlist_del_keeporder(v->routerstatus_list, 0); + tor_free(vrs->version); + tor_free(vrs); + vrs = smartlist_get(v->routerstatus_list, 0); + memset(vrs->status.descriptor_digest, (int)'Z', DIGEST_LEN); + test_assert(router_add_to_routerlist( + generate_ri_from_rs(vrs), &msg,0,0) >= 0); + } + } + + done: + return 0; +} + +/** + * Test a parsed vote_routerstatus_t for v3_networkstatus test + */ static void -test_dir_v3_networkstatus(void) +test_vrs_for_v3ns(vote_routerstatus_t *vrs, int voter, time_t now) +{ + routerstatus_t *rs; + tor_addr_t addr_ipv6; + + test_assert(vrs); + rs = &(vrs->status); + test_assert(rs); + + /* Split out by digests to test */ + if (tor_memeq(rs->identity_digest, + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3" + "\x3\x3\x3\x3", + DIGEST_LEN) && + (voter == 1)) { + /* Check the first routerstatus. */ + test_streq(vrs->version, "0.1.2.14"); + test_eq(rs->published_on, now-1500); + test_streq(rs->nickname, "router2"); + test_memeq(rs->identity_digest, + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3" + "\x3\x3\x3\x3", + DIGEST_LEN); + test_memeq(rs->descriptor_digest, "NNNNNNNNNNNNNNNNNNNN", DIGEST_LEN); + test_eq(rs->addr, 0x99008801); + test_eq(rs->or_port, 443); + test_eq(rs->dir_port, 8000); + test_eq(vrs->flags, U64_LITERAL(16)); // no flags except "running" + } else if (tor_memeq(rs->identity_digest, + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5" + "\x5\x5\x5\x5", + DIGEST_LEN) && + (voter == 1 || voter == 2)) { + test_memeq(rs->identity_digest, + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5" + "\x5\x5\x5\x5", + DIGEST_LEN); + + if (voter == 1) { + /* Check the second routerstatus. */ + test_streq(vrs->version, "0.2.0.5"); + test_eq(rs->published_on, now-1000); + test_streq(rs->nickname, "router1"); + } + test_memeq(rs->descriptor_digest, "MMMMMMMMMMMMMMMMMMMM", DIGEST_LEN); + test_eq(rs->addr, 0x99009901); + test_eq(rs->or_port, 443); + test_eq(rs->dir_port, 0); + tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); + test_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6)); + test_eq(rs->ipv6_orport, 4711); + if (voter == 1) { + test_eq(vrs->flags, U64_LITERAL(254)); // all flags except "authority." + } else { + /* 1023 - authority(1) - madeofcheese(16) - madeoftin(32) */ + test_eq(vrs->flags, U64_LITERAL(974)); + } + } else if (tor_memeq(rs->identity_digest, + "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33" + "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33", + DIGEST_LEN) && + (voter == 1 || voter == 2)) { + /* Check the measured bandwidth bits */ + test_assert(vrs->has_measured_bw && + vrs->measured_bw == 1024); + } else { + /* + * Didn't expect this, but the old unit test only checked some of them, + * so don't assert. + */ + /* test_assert(0); */ + } + + done: + return; +} + +/** + * Test a consensus for v3_networkstatus_test + */ +static void +test_consensus_for_v3ns(networkstatus_t *con, time_t now) +{ + (void)now; + + test_assert(con); + test_assert(!con->cert); + test_eq(2, smartlist_len(con->routerstatus_list)); + /* There should be two listed routers: one with identity 3, one with + * identity 5. */ + + done: + return; +} + +/** + * Test a router list entry for v3_networkstatus test + */ +static void +test_routerstatus_for_v3ns(routerstatus_t *rs, time_t now) +{ + tor_addr_t addr_ipv6; + + test_assert(rs); + + /* There should be two listed routers: one with identity 3, one with + * identity 5. */ + /* This one showed up in 2 digests. */ + if (tor_memeq(rs->identity_digest, + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3" + "\x3\x3", + DIGEST_LEN)) { + test_memeq(rs->identity_digest, + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", + DIGEST_LEN); + test_memeq(rs->descriptor_digest, "NNNNNNNNNNNNNNNNNNNN", DIGEST_LEN); + test_assert(!rs->is_authority); + test_assert(!rs->is_exit); + test_assert(!rs->is_fast); + test_assert(!rs->is_possible_guard); + test_assert(!rs->is_stable); + /* (If it wasn't running it wouldn't be here) */ + test_assert(rs->is_flagged_running); + test_assert(!rs->is_v2_dir); + test_assert(!rs->is_valid); + test_assert(!rs->is_named); + /* XXXX check version */ + } else if (tor_memeq(rs->identity_digest, + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5" + "\x5\x5\x5\x5", + DIGEST_LEN)) { + /* This one showed up in 3 digests. Twice with ID 'M', once with 'Z'. */ + test_memeq(rs->identity_digest, + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5", + DIGEST_LEN); + test_streq(rs->nickname, "router1"); + test_memeq(rs->descriptor_digest, "MMMMMMMMMMMMMMMMMMMM", DIGEST_LEN); + test_eq(rs->published_on, now-1000); + test_eq(rs->addr, 0x99009901); + test_eq(rs->or_port, 443); + test_eq(rs->dir_port, 0); + tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); + test_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6)); + test_eq(rs->ipv6_orport, 4711); + test_assert(!rs->is_authority); + test_assert(rs->is_exit); + test_assert(rs->is_fast); + test_assert(rs->is_possible_guard); + test_assert(rs->is_stable); + test_assert(rs->is_flagged_running); + test_assert(rs->is_v2_dir); + test_assert(rs->is_valid); + test_assert(!rs->is_named); + /* XXXX check version */ + } else { + /* Weren't expecting this... */ + test_assert(0); + } + + done: + return; +} + +/** Run a unit tests for generating and parsing networkstatuses, with + * the supply test fns. */ +static void +test_a_networkstatus( + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + int (*vote_tweaks)(networkstatus_t *v, int voter, time_t now), + void (*vrs_test)(vote_routerstatus_t *vrs, int voter, time_t now), + void (*consensus_test)(networkstatus_t *con, time_t now), + void (*rs_test)(routerstatus_t *rs, time_t now)) { authority_cert_t *cert1=NULL, *cert2=NULL, *cert3=NULL; crypto_pk_t *sign_skey_1=NULL, *sign_skey_2=NULL, *sign_skey_3=NULL; crypto_pk_t *sign_skey_leg1=NULL; const char *msg=NULL; + /* + * Sum the non-zero returns from vote_tweaks() we've seen; if vote_tweaks() + * returns non-zero, it changed net_params and we should skip the tests for + * that later as they will fail. + */ + int params_tweaked = 0; time_t now = time(NULL); networkstatus_voter_info_t *voter; @@ -782,8 +1099,8 @@ test_dir_v3_networkstatus(void) networkstatus_t *vote=NULL, *v1=NULL, *v2=NULL, *v3=NULL, *con=NULL, *con_md=NULL; vote_routerstatus_t *vrs; - tor_addr_t addr_ipv6; routerstatus_t *rs; + int idx, n_rs, n_vrs; char *v1_text=NULL, *v2_text=NULL, *v3_text=NULL, *consensus_text=NULL, *cp; smartlist_t *votes = smartlist_new(); @@ -795,6 +1112,10 @@ test_dir_v3_networkstatus(void) networkstatus_t *con2=NULL, *con_md2=NULL, *con3=NULL, *con_md3=NULL; ns_detached_signatures_t *dsig1=NULL, *dsig2=NULL; + test_assert(vrs_gen); + test_assert(rs_test); + test_assert(vrs_test); + /* Parse certificates and keys. */ cert1 = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); test_assert(cert1); @@ -852,72 +1173,18 @@ test_dir_v3_networkstatus(void) smartlist_split_string(vote->net_params, "circuitwindow=101 foo=990", NULL, 0, 0); vote->routerstatus_list = smartlist_new(); - /* add the first routerstatus. */ - vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - rs = &vrs->status; - vrs->version = tor_strdup("0.1.2.14"); - rs->published_on = now-1500; - strlcpy(rs->nickname, "router2", sizeof(rs->nickname)); - memset(rs->identity_digest, 3, DIGEST_LEN); - memset(rs->descriptor_digest, 78, DIGEST_LEN); - rs->addr = 0x99008801; - rs->or_port = 443; - rs->dir_port = 8000; - /* all flags but running cleared */ - rs->is_flagged_running = 1; - smartlist_add(vote->routerstatus_list, vrs); - test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); - - /* add the second routerstatus. */ - vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - rs = &vrs->status; - vrs->version = tor_strdup("0.2.0.5"); - rs->published_on = now-1000; - strlcpy(rs->nickname, "router1", sizeof(rs->nickname)); - memset(rs->identity_digest, 5, DIGEST_LEN); - memset(rs->descriptor_digest, 77, DIGEST_LEN); - rs->addr = 0x99009901; - rs->or_port = 443; - rs->dir_port = 0; - tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); - tor_addr_copy(&rs->ipv6_addr, &addr_ipv6); - rs->ipv6_orport = 4711; - rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running = - rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1; - smartlist_add(vote->routerstatus_list, vrs); - test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); - - /* add the third routerstatus. */ - vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - rs = &vrs->status; - vrs->version = tor_strdup("0.1.0.3"); - rs->published_on = now-1000; - strlcpy(rs->nickname, "router3", sizeof(rs->nickname)); - memset(rs->identity_digest, 33, DIGEST_LEN); - memset(rs->descriptor_digest, 79, DIGEST_LEN); - rs->addr = 0xAA009901; - rs->or_port = 400; - rs->dir_port = 9999; - rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = - rs->is_flagged_running = rs->is_valid = rs->is_v2_dir = - rs->is_possible_guard = 1; - smartlist_add(vote->routerstatus_list, vrs); - test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); - - /* add a fourth routerstatus that is not running. */ - vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - rs = &vrs->status; - vrs->version = tor_strdup("0.1.6.3"); - rs->published_on = now-1000; - strlcpy(rs->nickname, "router4", sizeof(rs->nickname)); - memset(rs->identity_digest, 34, DIGEST_LEN); - memset(rs->descriptor_digest, 47, DIGEST_LEN); - rs->addr = 0xC0000203; - rs->or_port = 500; - rs->dir_port = 1999; - /* Running flag (and others) cleared */ - smartlist_add(vote->routerstatus_list, vrs); - test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); + /* add routerstatuses */ + idx = 0; + do { + vrs = vrs_gen(idx, now); + if (vrs) { + smartlist_add(vote->routerstatus_list, vrs); + test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), + &msg,0,0)>=0); + ++idx; + } + } while (vrs); + n_vrs = idx; /* dump the vote and try to parse it. */ v1_text = format_networkstatus_vote(sign_skey_1, vote); @@ -948,47 +1215,15 @@ test_dir_v3_networkstatus(void) cp = smartlist_join_strings(v1->known_flags, ":", 0, NULL); test_streq(cp, "Authority:Exit:Fast:Guard:Running:Stable:V2Dir:Valid"); tor_free(cp); - test_eq(smartlist_len(v1->routerstatus_list), 4); - /* Check the first routerstatus. */ - vrs = smartlist_get(v1->routerstatus_list, 0); - rs = &vrs->status; - test_streq(vrs->version, "0.1.2.14"); - test_eq(rs->published_on, now-1500); - test_streq(rs->nickname, "router2"); - test_memeq(rs->identity_digest, - "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", - DIGEST_LEN); - test_memeq(rs->descriptor_digest, "NNNNNNNNNNNNNNNNNNNN", DIGEST_LEN); - test_eq(rs->addr, 0x99008801); - test_eq(rs->or_port, 443); - test_eq(rs->dir_port, 8000); - test_eq(vrs->flags, U64_LITERAL(16)); // no flags except "running" - /* Check the second routerstatus. */ - vrs = smartlist_get(v1->routerstatus_list, 1); - rs = &vrs->status; - test_streq(vrs->version, "0.2.0.5"); - test_eq(rs->published_on, now-1000); - test_streq(rs->nickname, "router1"); - test_memeq(rs->identity_digest, - "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5", - DIGEST_LEN); - test_memeq(rs->descriptor_digest, "MMMMMMMMMMMMMMMMMMMM", DIGEST_LEN); - test_eq(rs->addr, 0x99009901); - test_eq(rs->or_port, 443); - test_eq(rs->dir_port, 0); - test_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6)); - test_eq(rs->ipv6_orport, 4711); - test_eq(vrs->flags, U64_LITERAL(254)); // all flags except "authority." + test_eq(smartlist_len(v1->routerstatus_list), n_vrs); - { - measured_bw_line_t mbw; - memset(mbw.node_id, 33, sizeof(mbw.node_id)); - mbw.bw = 1024; - test_assert(measured_bw_line_apply(&mbw, - v1->routerstatus_list) == 1); - vrs = smartlist_get(v1->routerstatus_list, 2); - test_assert(vrs->status.has_measured_bw && - vrs->status.measured_bw == 1024); + if (vote_tweaks) params_tweaked += vote_tweaks(v1, 1, now); + + /* Check the routerstatuses. */ + for (idx = 0; idx < n_vrs; ++idx) { + vrs = smartlist_get(v1->routerstatus_list, idx); + test_assert(vrs); + vrs_test(vrs, 1, now); } /* Generate second vote. It disagrees on some of the times, @@ -1013,25 +1248,28 @@ test_dir_v3_networkstatus(void) smartlist_add(vote->known_flags, tor_strdup("MadeOfCheese")); smartlist_add(vote->known_flags, tor_strdup("MadeOfTin")); smartlist_sort_strings(vote->known_flags); - vrs = smartlist_get(vote->routerstatus_list, 2); - smartlist_del_keeporder(vote->routerstatus_list, 2); - tor_free(vrs->version); - tor_free(vrs); - vrs = smartlist_get(vote->routerstatus_list, 0); - vrs->status.is_fast = 1; - /* generate and parse. */ + + /* generate and parse v2. */ v2_text = format_networkstatus_vote(sign_skey_2, vote); test_assert(v2_text); v2 = networkstatus_parse_vote_from_string(v2_text, NULL, NS_TYPE_VOTE); test_assert(v2); + + if (vote_tweaks) params_tweaked += vote_tweaks(v2, 2, now); + /* Check that flags come out right.*/ cp = smartlist_join_strings(v2->known_flags, ":", 0, NULL); test_streq(cp, "Authority:Exit:Fast:Guard:MadeOfCheese:MadeOfTin:" "Running:Stable:V2Dir:Valid"); tor_free(cp); - vrs = smartlist_get(v2->routerstatus_list, 1); - /* 1023 - authority(1) - madeofcheese(16) - madeoftin(32) */ - test_eq(vrs->flags, U64_LITERAL(974)); + + /* Check the routerstatuses. */ + n_vrs = smartlist_len(v2->routerstatus_list); + for (idx = 0; idx < n_vrs; ++idx) { + vrs = smartlist_get(v2->routerstatus_list, idx); + test_assert(vrs); + vrs_test(vrs, 2, now); + } /* Generate the third vote. */ vote->published = now; @@ -1054,13 +1292,6 @@ test_dir_v3_networkstatus(void) crypto_pk_get_digest(cert3->identity_key, voter->identity_digest); /* This one has a legacy id. */ memset(voter->legacy_id_digest, (int)'A', DIGEST_LEN); - vrs = smartlist_get(vote->routerstatus_list, 0); - smartlist_del_keeporder(vote->routerstatus_list, 0); - tor_free(vrs->version); - tor_free(vrs); - vrs = smartlist_get(vote->routerstatus_list, 0); - memset(vrs->status.descriptor_digest, (int)'Z', DIGEST_LEN); - test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); v3_text = format_networkstatus_vote(sign_skey_3, vote); test_assert(v3_text); @@ -1068,6 +1299,8 @@ test_dir_v3_networkstatus(void) v3 = networkstatus_parse_vote_from_string(v3_text, NULL, NS_TYPE_VOTE); test_assert(v3); + if (vote_tweaks) params_tweaked += vote_tweaks(v3, 3, now); + /* Compute a consensus as voter 3. */ smartlist_add(votes, v3); smartlist_add(votes, v1); @@ -1110,9 +1343,12 @@ test_dir_v3_networkstatus(void) test_streq(cp, "Authority:Exit:Fast:Guard:MadeOfCheese:MadeOfTin:" "Running:Stable:V2Dir:Valid"); tor_free(cp); - cp = smartlist_join_strings(con->net_params, ":", 0, NULL); - test_streq(cp, "circuitwindow=80:foo=660"); - tor_free(cp); + if (!params_tweaked) { + /* Skip this one if vote_tweaks() messed with the param lists */ + cp = smartlist_join_strings(con->net_params, ":", 0, NULL); + test_streq(cp, "circuitwindow=80:foo=660"); + tor_free(cp); + } test_eq(4, smartlist_len(con->voters)); /*3 voters, 1 legacy key.*/ /* The voter id digests should be in this order. */ @@ -1127,51 +1363,15 @@ test_dir_v3_networkstatus(void) test_same_voter(smartlist_get(con->voters, 3), smartlist_get(v3->voters, 0)); - test_assert(!con->cert); - test_eq(2, smartlist_len(con->routerstatus_list)); - /* There should be two listed routers: one with identity 3, one with - * identity 5. */ - /* This one showed up in 2 digests. */ - rs = smartlist_get(con->routerstatus_list, 0); - test_memeq(rs->identity_digest, - "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", - DIGEST_LEN); - test_memeq(rs->descriptor_digest, "NNNNNNNNNNNNNNNNNNNN", DIGEST_LEN); - test_assert(!rs->is_authority); - test_assert(!rs->is_exit); - test_assert(!rs->is_fast); - test_assert(!rs->is_possible_guard); - test_assert(!rs->is_stable); - /* (If it wasn't running it wouldn't be here) */ - test_assert(rs->is_flagged_running); - test_assert(!rs->is_v2_dir); - test_assert(!rs->is_valid); - test_assert(!rs->is_named); - /* XXXX check version */ - - rs = smartlist_get(con->routerstatus_list, 1); - /* This one showed up in 3 digests. Twice with ID 'M', once with 'Z'. */ - test_memeq(rs->identity_digest, - "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5", - DIGEST_LEN); - test_streq(rs->nickname, "router1"); - test_memeq(rs->descriptor_digest, "MMMMMMMMMMMMMMMMMMMM", DIGEST_LEN); - test_eq(rs->published_on, now-1000); - test_eq(rs->addr, 0x99009901); - test_eq(rs->or_port, 443); - test_eq(rs->dir_port, 0); - test_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6)); - test_eq(rs->ipv6_orport, 4711); - test_assert(!rs->is_authority); - test_assert(rs->is_exit); - test_assert(rs->is_fast); - test_assert(rs->is_possible_guard); - test_assert(rs->is_stable); - test_assert(rs->is_flagged_running); - test_assert(rs->is_v2_dir); - test_assert(rs->is_valid); - test_assert(!rs->is_named); - /* XXXX check version */ + consensus_test(con, now); + + /* Check the routerstatuses. */ + n_rs = smartlist_len(con->routerstatus_list); + for (idx = 0; idx < n_rs; ++idx) { + rs = smartlist_get(con->routerstatus_list, idx); + test_assert(rs); + rs_test(rs, now); + } /* Check signatures. the first voter is a pseudo-entry with a legacy key. * The second one hasn't signed. The fourth one has signed: validate it. */ @@ -1374,6 +1574,18 @@ test_dir_v3_networkstatus(void) ns_detached_signatures_free(dsig2); } +/** Run unit tests for generating and parsing V3 consensus networkstatus + * documents. */ +static void +test_dir_v3_networkstatus(void) +{ + test_a_networkstatus(gen_routerstatus_for_v3ns, + vote_tweaks_for_v3ns, + test_vrs_for_v3ns, + test_consensus_for_v3ns, + test_routerstatus_for_v3ns); +} + static void test_dir_scale_bw(void *testdata) { @@ -1492,6 +1704,424 @@ test_dir_random_weighted(void *testdata) ; } +/* Function pointers for test_dir_clip_unmeasured_bw() */ + +static uint32_t alternate_clip_bw = 0; + +/** + * Generate a routerstatus for clip_unmeasured_bw test; based on the + * v3_networkstatus ones. + */ +static vote_routerstatus_t * +gen_routerstatus_for_umbw(int idx, time_t now) +{ + vote_routerstatus_t *vrs = NULL; + routerstatus_t *rs; + tor_addr_t addr_ipv6; + uint32_t max_unmeasured_bw = (alternate_clip_bw > 0) ? + alternate_clip_bw : DEFAULT_MAX_UNMEASURED_BW; + + switch (idx) { + case 0: + /* Generate the first routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.2.14"); + rs->published_on = now-1500; + strlcpy(rs->nickname, "router2", sizeof(rs->nickname)); + memset(rs->identity_digest, 3, DIGEST_LEN); + memset(rs->descriptor_digest, 78, DIGEST_LEN); + rs->addr = 0x99008801; + rs->or_port = 443; + rs->dir_port = 8000; + /* all flags but running cleared */ + rs->is_flagged_running = 1; + /* + * This one has measured bandwidth below the clip cutoff, and + * so shouldn't be clipped; we'll have to test that it isn't + * later. + */ + vrs->has_measured_bw = 1; + rs->has_bandwidth = 1; + vrs->measured_bw = rs->bandwidth = max_unmeasured_bw / 2; + break; + case 1: + /* Generate the second routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.2.0.5"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router1", sizeof(rs->nickname)); + memset(rs->identity_digest, 5, DIGEST_LEN); + memset(rs->descriptor_digest, 77, DIGEST_LEN); + rs->addr = 0x99009901; + rs->or_port = 443; + rs->dir_port = 0; + tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); + tor_addr_copy(&rs->ipv6_addr, &addr_ipv6); + rs->ipv6_orport = 4711; + rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running = + rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1; + /* + * This one has measured bandwidth above the clip cutoff, and + * so shouldn't be clipped; we'll have to test that it isn't + * later. + */ + vrs->has_measured_bw = 1; + rs->has_bandwidth = 1; + vrs->measured_bw = rs->bandwidth = 2 * max_unmeasured_bw; + break; + case 2: + /* Generate the third routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.0.3"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router3", sizeof(rs->nickname)); + memset(rs->identity_digest, 0x33, DIGEST_LEN); + memset(rs->descriptor_digest, 79, DIGEST_LEN); + rs->addr = 0xAA009901; + rs->or_port = 400; + rs->dir_port = 9999; + rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = + rs->is_flagged_running = rs->is_valid = rs->is_v2_dir = + rs->is_possible_guard = 1; + /* + * This one has unmeasured bandwidth above the clip cutoff, and + * so should be clipped; we'll have to test that it isn't + * later. + */ + vrs->has_measured_bw = 0; + rs->has_bandwidth = 1; + vrs->measured_bw = 0; + rs->bandwidth = 2 * max_unmeasured_bw; + break; + case 3: + /* Generate a fourth routerstatus that is not running. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.6.3"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router4", sizeof(rs->nickname)); + memset(rs->identity_digest, 0x34, DIGEST_LEN); + memset(rs->descriptor_digest, 47, DIGEST_LEN); + rs->addr = 0xC0000203; + rs->or_port = 500; + rs->dir_port = 1999; + /* all flags but running cleared */ + rs->is_flagged_running = 1; + /* + * This one has unmeasured bandwidth below the clip cutoff, and + * so shouldn't be clipped; we'll have to test that it isn't + * later. + */ + vrs->has_measured_bw = 0; + rs->has_bandwidth = 1; + vrs->measured_bw = 0; + rs->bandwidth = max_unmeasured_bw / 2; + break; + case 4: + /* No more for this test; return NULL */ + vrs = NULL; + break; + default: + /* Shouldn't happen */ + test_assert(0); + } + + done: + return vrs; +} + +/** Apply tweaks to the vote list for each voter; for the umbw test this is + * just adding the right consensus methods to let clipping happen */ +static int +vote_tweaks_for_umbw(networkstatus_t *v, int voter, time_t now) +{ + char *maxbw_param = NULL; + int rv = 0; + + test_assert(v); + (void)voter; + (void)now; + + test_assert(v->supported_methods); + smartlist_clear(v->supported_methods); + /* Method 17 is MIN_METHOD_TO_CLIP_UNMEASURED_BW */ + smartlist_split_string(v->supported_methods, + "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17", + NULL, 0, -1); + /* If we're using a non-default clip bandwidth, add it to net_params */ + if (alternate_clip_bw > 0) { + tor_asprintf(&maxbw_param, "maxunmeasuredbw=%u", alternate_clip_bw); + test_assert(maxbw_param); + if (maxbw_param) { + smartlist_add(v->net_params, maxbw_param); + rv = 1; + } + } + + done: + return rv; +} + +/** + * Test a parsed vote_routerstatus_t for umbw test. + */ +static void +test_vrs_for_umbw(vote_routerstatus_t *vrs, int voter, time_t now) +{ + routerstatus_t *rs; + tor_addr_t addr_ipv6; + uint32_t max_unmeasured_bw = (alternate_clip_bw > 0) ? + alternate_clip_bw : DEFAULT_MAX_UNMEASURED_BW; + + (void)voter; + test_assert(vrs); + rs = &(vrs->status); + test_assert(rs); + + /* Split out by digests to test */ + if (tor_memeq(rs->identity_digest, + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3" + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", + DIGEST_LEN)) { + /* + * Check the first routerstatus - measured bandwidth below the clip + * cutoff. + */ + test_streq(vrs->version, "0.1.2.14"); + test_eq(rs->published_on, now-1500); + test_streq(rs->nickname, "router2"); + test_memeq(rs->identity_digest, + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3" + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", + DIGEST_LEN); + test_memeq(rs->descriptor_digest, "NNNNNNNNNNNNNNNNNNNN", DIGEST_LEN); + test_eq(rs->addr, 0x99008801); + test_eq(rs->or_port, 443); + test_eq(rs->dir_port, 8000); + test_assert(rs->has_bandwidth); + test_assert(vrs->has_measured_bw); + test_eq(rs->bandwidth, max_unmeasured_bw / 2); + test_eq(vrs->measured_bw, max_unmeasured_bw / 2); + } else if (tor_memeq(rs->identity_digest, + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5" + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5", + DIGEST_LEN)) { + + /* + * Check the second routerstatus - measured bandwidth above the clip + * cutoff. + */ + test_streq(vrs->version, "0.2.0.5"); + test_eq(rs->published_on, now-1000); + test_streq(rs->nickname, "router1"); + test_memeq(rs->identity_digest, + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5" + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5", + DIGEST_LEN); + test_memeq(rs->descriptor_digest, "MMMMMMMMMMMMMMMMMMMM", DIGEST_LEN); + test_eq(rs->addr, 0x99009901); + test_eq(rs->or_port, 443); + test_eq(rs->dir_port, 0); + tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); + test_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6)); + test_eq(rs->ipv6_orport, 4711); + test_assert(rs->has_bandwidth); + test_assert(vrs->has_measured_bw); + test_eq(rs->bandwidth, max_unmeasured_bw * 2); + test_eq(vrs->measured_bw, max_unmeasured_bw * 2); + } else if (tor_memeq(rs->identity_digest, + "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33" + "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33", + DIGEST_LEN)) { + /* + * Check the third routerstatus - unmeasured bandwidth above the clip + * cutoff; this one should be clipped later on in the consensus, but + * appears unclipped in the vote. + */ + test_assert(rs->has_bandwidth); + test_assert(!(vrs->has_measured_bw)); + test_eq(rs->bandwidth, max_unmeasured_bw * 2); + test_eq(vrs->measured_bw, 0); + } else if (tor_memeq(rs->identity_digest, + "\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34" + "\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34", + DIGEST_LEN)) { + /* + * Check the fourth routerstatus - unmeasured bandwidth below the clip + * cutoff; this one should not be clipped. + */ + test_assert(rs->has_bandwidth); + test_assert(!(vrs->has_measured_bw)); + test_eq(rs->bandwidth, max_unmeasured_bw / 2); + test_eq(vrs->measured_bw, 0); + } else { + test_assert(0); + } + + done: + return; +} + +/** + * Test a consensus for v3_networkstatus_test + */ +static void +test_consensus_for_umbw(networkstatus_t *con, time_t now) +{ + (void)now; + + test_assert(con); + test_assert(!con->cert); + /* test_assert(con->consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW); */ + test_assert(con->consensus_method >= 16); + test_eq(4, smartlist_len(con->routerstatus_list)); + /* There should be four listed routers; all voters saw the same in this */ + + done: + return; +} + +/** + * Test a router list entry for umbw test + */ +static void +test_routerstatus_for_umbw(routerstatus_t *rs, time_t now) +{ + tor_addr_t addr_ipv6; + uint32_t max_unmeasured_bw = (alternate_clip_bw > 0) ? + alternate_clip_bw : DEFAULT_MAX_UNMEASURED_BW; + + test_assert(rs); + + /* There should be four listed routers, as constructed above */ + if (tor_memeq(rs->identity_digest, + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3" + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", + DIGEST_LEN)) { + test_memeq(rs->identity_digest, + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3" + "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", + DIGEST_LEN); + test_memeq(rs->descriptor_digest, "NNNNNNNNNNNNNNNNNNNN", DIGEST_LEN); + test_assert(!rs->is_authority); + test_assert(!rs->is_exit); + test_assert(!rs->is_fast); + test_assert(!rs->is_possible_guard); + test_assert(!rs->is_stable); + /* (If it wasn't running it wouldn't be here) */ + test_assert(rs->is_flagged_running); + test_assert(!rs->is_v2_dir); + test_assert(!rs->is_valid); + test_assert(!rs->is_named); + /* This one should have measured bandwidth below the clip cutoff */ + test_assert(rs->has_bandwidth); + test_eq(rs->bandwidth, max_unmeasured_bw / 2); + test_assert(!(rs->bw_is_unmeasured)); + } else if (tor_memeq(rs->identity_digest, + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5" + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5", + DIGEST_LEN)) { + /* This one showed up in 3 digests. Twice with ID 'M', once with 'Z'. */ + test_memeq(rs->identity_digest, + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5" + "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5", + DIGEST_LEN); + test_streq(rs->nickname, "router1"); + test_memeq(rs->descriptor_digest, "MMMMMMMMMMMMMMMMMMMM", DIGEST_LEN); + test_eq(rs->published_on, now-1000); + test_eq(rs->addr, 0x99009901); + test_eq(rs->or_port, 443); + test_eq(rs->dir_port, 0); + tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); + test_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6)); + test_eq(rs->ipv6_orport, 4711); + test_assert(!rs->is_authority); + test_assert(rs->is_exit); + test_assert(rs->is_fast); + test_assert(rs->is_possible_guard); + test_assert(rs->is_stable); + test_assert(rs->is_flagged_running); + test_assert(rs->is_v2_dir); + test_assert(rs->is_valid); + test_assert(!rs->is_named); + /* This one should have measured bandwidth above the clip cutoff */ + test_assert(rs->has_bandwidth); + test_eq(rs->bandwidth, max_unmeasured_bw * 2); + test_assert(!(rs->bw_is_unmeasured)); + } else if (tor_memeq(rs->identity_digest, + "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33" + "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33", + DIGEST_LEN)) { + /* + * This one should have unmeasured bandwidth above the clip cutoff, + * and so should be clipped + */ + test_assert(rs->has_bandwidth); + test_eq(rs->bandwidth, max_unmeasured_bw); + test_assert(rs->bw_is_unmeasured); + } else if (tor_memeq(rs->identity_digest, + "\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34" + "\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34", + DIGEST_LEN)) { + /* + * This one should have unmeasured bandwidth below the clip cutoff, + * and so should not be clipped + */ + test_assert(rs->has_bandwidth); + test_eq(rs->bandwidth, max_unmeasured_bw / 2); + test_assert(rs->bw_is_unmeasured); + } else { + /* Weren't expecting this... */ + test_assert(0); + } + + done: + return; +} + +/** + * Compute a consensus involving clipping unmeasured bandwidth with consensus + * method 17; this uses the same test_a_networkstatus() function that the + * v3_networkstatus test uses. + */ + +static void +test_dir_clip_unmeasured_bw(void) +{ + /* Run the test with the default clip bandwidth */ + alternate_clip_bw = 0; + test_a_networkstatus(gen_routerstatus_for_umbw, + vote_tweaks_for_umbw, + test_vrs_for_umbw, + test_consensus_for_umbw, + test_routerstatus_for_umbw); +} + +/** + * This version of test_dir_clip_unmeasured_bw() uses a non-default choice of + * clip bandwidth. + */ + +static void +test_dir_clip_unmeasured_bw_alt(void) +{ + /* + * Try a different one; this value is chosen so that the below-the-cutoff + * unmeasured nodes the test uses, at alternate_clip_bw / 2, will be above + * DEFAULT_MAX_UNMEASURED_BW and if the consensus incorrectly uses that + * cutoff it will fail the test. + */ + alternate_clip_bw = 3 * DEFAULT_MAX_UNMEASURED_BW; + test_a_networkstatus(gen_routerstatus_for_umbw, + vote_tweaks_for_umbw, + test_vrs_for_umbw, + test_consensus_for_umbw, + test_routerstatus_for_umbw); +} + #define DIR_LEGACY(name) \ { #name, legacy_test_helper, TT_FORK, &legacy_setup, test_dir_ ## name } @@ -1509,6 +2139,8 @@ struct testcase_t dir_tests[] = { DIR_LEGACY(v3_networkstatus), DIR(random_weighted), DIR(scale_bw), + DIR_LEGACY(clip_unmeasured_bw), + DIR_LEGACY(clip_unmeasured_bw_alt), END_OF_TESTCASES }; |