Commit 6fbdf635 authored by Mike Perry's avatar Mike Perry
Browse files

Implement measured bw parsing + unit tests.

parent 9da7b223
......@@ -337,6 +337,7 @@ static config_var_t _option_vars[] = {
V(V3AuthDistDelay, INTERVAL, "5 minutes"),
V(V3AuthNIntervalsValid, UINT, "3"),
V(V3AuthUseLegacyKey, BOOL, "0"),
V(V3BandwidthsFile, FILENAME, NULL),
VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"),
V(VirtualAddrNetwork, STRING, "127.192.0.0/10"),
V(WarnPlaintextPorts, CSV, "23,109,110,143"),
......
......@@ -63,6 +63,9 @@ static signed_descriptor_t *get_signed_descriptor_by_fp(const char *fp,
time_t publish_cutoff);
static int dirserv_add_extrainfo(extrainfo_t *ei, const char **msg);
/************** Measured Bandwidth parsing code ******/
#define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */
/************** Fingerprint handling code ************/
#define FP_NAMED 1 /**< Listed in fingerprint file. */
......@@ -1854,16 +1857,18 @@ version_from_platform(const char *platform)
* which has at least <b>buf_len</b> free characters. Do NUL-termination.
* Use the same format as in network-status documents. If <b>version</b> is
* non-NULL, add a "v" line for the platform. Return 0 on success, -1 on
* failure. If <b>first_line_only</b> is true, don't include any flags
* or version line.
* failure.
*
* The format argument has three possible values:
* NS_V2 - Output an entry suitable for a V2 NS opinion document
* NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry
* NS_V3_VOTE - Output a complete V3 NS vote
* NS_CONTROL_PORT - Output a NS docunent for the control port
*/
int
routerstatus_format_entry(char *buf, size_t buf_len,
routerstatus_t *rs, const char *version,
int first_line_only, int v2_format)
/* XXX: first_line_only and v2_format should probably be be both
* replaced by a single purpose parameter.
*/
routerstatus_format_type_t format)
{
int r;
struct in_addr in;
......@@ -1894,7 +1899,12 @@ routerstatus_format_entry(char *buf, size_t buf_len,
log_warn(LD_BUG, "Not enough space in buffer.");
return -1;
}
if (first_line_only)
/* TODO: Maybe we want to pass in what we need to build the rest of
* this here, instead of in the caller. Then we could use the
* networkstatus_type_t values, with an additional control port value
* added -MP */
if (format == NS_V3_CONSENSUS)
return 0;
cp = buf + strlen(buf);
......@@ -1931,62 +1941,81 @@ routerstatus_format_entry(char *buf, size_t buf_len,
cp += strlen(cp);
}
if (!v2_format) {
if (format != NS_V2) {
routerinfo_t* desc = router_get_by_digest(rs->identity_digest);
/* Blow up more or less nicely if we didn't get anything or not the
* thing we expected.
*/
if (!desc) {
char id[HEX_DIGEST_LEN+1];
char dd[HEX_DIGEST_LEN+1];
base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
base16_encode(dd, sizeof(dd), rs->descriptor_digest, DIGEST_LEN);
log_warn(LD_BUG, "Cannot get any descriptor for %s "
"(wanted descriptor %s).",
id, dd);
return -1;
};
if (memcmp(desc->cache_info.signed_descriptor_digest,
rs->descriptor_digest,
DIGEST_LEN)) {
char rl_d[HEX_DIGEST_LEN+1];
char rs_d[HEX_DIGEST_LEN+1];
char id[HEX_DIGEST_LEN+1];
base16_encode(rl_d, sizeof(rl_d),
desc->cache_info.signed_descriptor_digest, DIGEST_LEN);
base16_encode(rs_d, sizeof(rs_d), rs->descriptor_digest, DIGEST_LEN);
base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
log_err(LD_BUG, "descriptor digest in routerlist does not match "
"the one in routerstatus: %s vs %s "
"(router %s)\n",
rl_d, rs_d, id);
tor_assert(!memcmp(desc->cache_info.signed_descriptor_digest,
rs->descriptor_digest,
DIGEST_LEN));
};
if (format != NS_CONTROL_PORT) {
/* Blow up more or less nicely if we didn't get anything or not the
* thing we expected.
*/
if (!desc) {
char id[HEX_DIGEST_LEN+1];
char dd[HEX_DIGEST_LEN+1];
base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
base16_encode(dd, sizeof(dd), rs->descriptor_digest, DIGEST_LEN);
log_warn(LD_BUG, "Cannot get any descriptor for %s "
"(wanted descriptor %s).",
id, dd);
return -1;
};
/* This assert can fire for the control port, because
* it can request NS documents before all descriptors
* have been fetched. */
if (memcmp(desc->cache_info.signed_descriptor_digest,
rs->descriptor_digest,
DIGEST_LEN)) {
char rl_d[HEX_DIGEST_LEN+1];
char rs_d[HEX_DIGEST_LEN+1];
char id[HEX_DIGEST_LEN+1];
base16_encode(rl_d, sizeof(rl_d),
desc->cache_info.signed_descriptor_digest, DIGEST_LEN);
base16_encode(rs_d, sizeof(rs_d), rs->descriptor_digest, DIGEST_LEN);
base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
log_err(LD_BUG, "descriptor digest in routerlist does not match "
"the one in routerstatus: %s vs %s "
"(router %s)\n",
rl_d, rs_d, id);
tor_assert(!memcmp(desc->cache_info.signed_descriptor_digest,
rs->descriptor_digest,
DIGEST_LEN));
};
}
r = tor_snprintf(cp, buf_len - (cp-buf),
"w Bandwidth=%d\n",
router_get_advertised_bandwidth_capped(desc) / 1024);
if (r<0) {
log_warn(LD_BUG, "Not enough space in buffer.");
return -1;
}
cp += strlen(cp);
if (format == NS_V3_VOTE && rs->has_measured_bw) {
*--cp = '\0'; /* Kill "\n" */
r = tor_snprintf(cp, buf_len - (cp-buf),
" Measured=%d\n", rs->measured_bw);
if (r<0) {
log_warn(LD_BUG, "Not enough space in buffer for weight line.");
return -1;
}
cp += strlen(cp);
}
summary = policy_summarize(desc->exit_policy);
r = tor_snprintf(cp, buf_len - (cp-buf), "p %s\n", summary);
if (r<0) {
log_warn(LD_BUG, "Not enough space in buffer.");
if (desc) {
summary = policy_summarize(desc->exit_policy);
r = tor_snprintf(cp, buf_len - (cp-buf), "p %s\n", summary);
if (r<0) {
log_warn(LD_BUG, "Not enough space in buffer.");
tor_free(summary);
return -1;
}
cp += strlen(cp);
tor_free(summary);
return -1;
}
cp += strlen(cp);
tor_free(summary);
}
return 0;
......@@ -2189,6 +2218,194 @@ router_clear_status_flags(routerinfo_t *router)
router->is_bad_exit = router->is_bad_directory = 0;
}
#ifndef HAVE_STRTOK_R
/*
* XXX-MP: If a system lacks strtok_r and we use a non-reentrant strtok,
* we may introduce odd bugs if we call a codepath that also uses strtok
* and resets its internal state. Do we want to abandon use of strtok
* entirely for this reason? Roger mentioned smartlist_split and
* eat_whitespace() as alternatives.
*/
static char *
strtok_r(char *s, const char *delim, char **state)
{
(void)state;
return strtok(s, delim);
}
#endif
/**
* Helper function to parse out a line in the measured bandwidth file
* into a measured_bw_line_t output structure. Returns -1 on failure
* or 0 on success.
*/
int
measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line)
{
char *line = tor_strdup(orig_line);
char *cp = line;
int got_bw = 0;
int got_node_id = 0;
char *strtok_state; /* lame sauce d'jour */
cp = strtok_r(cp, " \t", &strtok_state);
if (!cp) {
log_warn(LD_DIRSERV, "Invalid line in bandwidth file: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
if (orig_line[strlen(orig_line)-1] != '\n') {
log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
do {
if (strncasecmp(cp, "bw=", strlen("bw=")) == 0) {
char *endptr;
if (got_bw) {
log_warn(LD_DIRSERV, "Double bw= in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
cp+=strlen("bw=");
errno=0;
out->bw = strtol(cp, &endptr, 0);
if (errno || endptr == cp || (*endptr && !TOR_ISSPACE(*endptr)) ||
out->bw < 0) {
log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
got_bw=1;
} else if (strncasecmp(cp, "node_id=$", strlen("node_id=$")) == 0) {
if (got_node_id) {
log_warn(LD_DIRSERV, "Double node_id= in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
cp+=strlen("node_id=$");
if (strlen(cp) != HEX_DIGEST_LEN ||
base16_decode(out->node_id, DIGEST_LEN, cp, HEX_DIGEST_LEN)) {
log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
strncpy(out->node_hex, cp, sizeof(out->node_hex));
got_node_id=1;
}
} while ((cp = strtok_r(NULL, " \t", &strtok_state)));
if (got_bw && got_node_id) {
tor_free(line);
return 0;
} else {
log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
}
/**
* Helper function to apply a parsed measurement line to a list
* of bandwidth statuses. Returns true if a line is found,
* false otherwise.
*/
int
measured_bw_line_apply(measured_bw_line_t *parsed_line,
smartlist_t *routerstatuses)
{
routerstatus_t *rs = NULL;
if (!routerstatuses)
return 0;
rs = smartlist_bsearch(routerstatuses, parsed_line->node_id,
compare_digest_to_routerstatus_entry);
if (rs) {
rs->has_measured_bw = 1;
rs->measured_bw = parsed_line->bw;
} else {
log_info(LD_DIRSERV, "Node ID %s not found in routerstatus list",
parsed_line->node_hex);
}
return rs != NULL;
}
/**
* Read the measured bandwidth file and apply it to the list of
* routerstatuses. Returns -1 on error, 0 otherwise.
*/
int
dirserv_read_measured_bandwidths(const char *from_file,
smartlist_t *routerstatuses)
{
char line[256];
FILE *fp = fopen(from_file, "r");
int applied_lines = 0;
time_t file_time;
int ok;
if (fp == NULL) {
log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s",
from_file);
return -1;
}
fgets(line, sizeof(line), fp);
if (line[strlen(line)-1] != '\n') {
log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s",
escaped(line));
fclose(fp);
return -1;
}
line[strlen(line)-1] = '\0';
file_time = tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL);
if (!ok) {
log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s",
escaped(line));
fclose(fp);
return -1;
}
if ((time(NULL) - file_time) > MAX_MEASUREMENT_AGE) {
log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u",
(unsigned)(time(NULL) - file_time));
fclose(fp);
return -1;
}
if (routerstatuses)
smartlist_sort(routerstatuses, compare_routerstatus_entries);
while (!feof(fp)) {
measured_bw_line_t parsed_line;
fgets(line, sizeof(line), fp);
if (measured_bw_line_parse(&parsed_line, line) != -1) {
if (measured_bw_line_apply(&parsed_line, routerstatuses) > 0)
applied_lines++;
}
}
fclose(fp);
log_notice(LD_DIRSERV,
"Bandwidth measurement file successfully read. "
"Applied %d measurements.", applied_lines);
return 0;
}
/** Return a new networkstatus_t* containing our current opinion. (For v3
* authorities) */
networkstatus_t *
......@@ -2288,9 +2505,15 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_env_t *private_key,
smartlist_add(routerstatuses, vrs);
}
});
smartlist_free(routers);
digestmap_free(omit_as_sybil, NULL);
if (options->V3BandwidthsFile) {
dirserv_read_measured_bandwidths(options->V3BandwidthsFile,
routerstatuses);
}
v3_out = tor_malloc_zero(sizeof(networkstatus_t));
v3_out->type = NS_TYPE_VOTE;
......@@ -2494,7 +2717,7 @@ 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, 0, 1)) {
if (routerstatus_format_entry(outp, endp-outp, &rs, version, NS_V2)) {
log_warn(LD_BUG, "Unable to print router status.");
tor_free(version);
goto done;
......
......@@ -103,6 +103,7 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key,
tor_snprintf(status, len,
"network-status-version 3\n"
"vote-status %s\n"
/* TODO-160: add 6 when ready */
"consensus-methods 1 2 3 4 5\n"
"published %s\n"
"valid-after %s\n"
......@@ -142,7 +143,7 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key,
SMARTLIST_FOREACH(v3_ns->routerstatus_list, vote_routerstatus_t *, vrs,
{
if (routerstatus_format_entry(outp, endp-outp, &vrs->status,
vrs->version, 0, 0) < 0) {
vrs->version, NS_V3_VOTE) < 0) {
log_warn(LD_BUG, "Unable to print router status.");
goto err;
}
......@@ -701,7 +702,10 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_t *versions = smartlist_create();
smartlist_t *exitsummaries = smartlist_create();
uint32_t *bandwidths = tor_malloc(sizeof(uint32_t) * smartlist_len(votes));
uint32_t *measured_bws = tor_malloc(sizeof(uint32_t) *
smartlist_len(votes));
int num_bandwidths;
int num_mbws;
int *n_voter_flags; /* n_voter_flags[j] is the number of flags that
* votes[j] knows about. */
......@@ -835,6 +839,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_clear(chosen_flags);
smartlist_clear(versions);
num_bandwidths = 0;
num_mbws = 0;
/* Okay, go through all the entries for this digest. */
SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
......@@ -868,6 +873,9 @@ networkstatus_compute_consensus(smartlist_t *votes,
}
/* count bandwidths */
if (rs->status.has_measured_bw)
measured_bws[num_mbws++] = rs->status.measured_bw;
if (rs->status.has_bandwidth)
bandwidths[num_bandwidths++] = rs->status.bandwidth;
} SMARTLIST_FOREACH_END(v);
......@@ -945,7 +953,10 @@ networkstatus_compute_consensus(smartlist_t *votes,
}
/* Pick a bandwidth */
if (consensus_method >= 5 && num_bandwidths > 0) {
if (consensus_method >= 6 && num_mbws > 2) {
rs_out.has_bandwidth = 1;
rs_out.bandwidth = median_uint32(measured_bws, num_mbws);
} else if (consensus_method >= 5 && num_bandwidths > 0) {
rs_out.has_bandwidth = 1;
rs_out.bandwidth = median_uint32(bandwidths, num_bandwidths);
}
......@@ -1036,7 +1047,8 @@ 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, 1, 0);
routerstatus_format_entry(buf, sizeof(buf), &rs_out, NULL,
NS_V3_CONSENSUS);
smartlist_add(chunks, tor_strdup(buf));
/* Second line is all flags. The "\n" is missing. */
smartlist_add(chunks,
......@@ -1055,8 +1067,10 @@ networkstatus_compute_consensus(smartlist_t *votes,
log_warn(LD_BUG, "Not enough space in buffer for weight line.");
*buf = '\0';
}
smartlist_add(chunks, tor_strdup(buf));
};
/* Now the exitpolicy summary line. */
if (rs_out.has_exitsummary) {
char buf[MAX_POLICY_LINE_LEN+1];
......
......@@ -780,8 +780,8 @@ networkstatus_v2_list_clean(time_t now)
/** Helper for bsearching a list of routerstatus_t pointers: compare a
* digest in the key to the identity digest of a routerstatus_t. */
static int
_compare_digest_to_routerstatus_entry(const void *_key, const void **_member)
int
compare_digest_to_routerstatus_entry(const void *_key, const void **_member)
{
const char *key = _key;
const routerstatus_t *rs = *_member;
......@@ -794,7 +794,7 @@ routerstatus_t *
networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest)
{
return smartlist_bsearch(ns->entries, digest,
_compare_digest_to_routerstatus_entry);
compare_digest_to_routerstatus_entry);
}
/** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or
......@@ -803,7 +803,7 @@ routerstatus_t *
networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest)
{
return smartlist_bsearch(ns->routerstatus_list, digest,
_compare_digest_to_routerstatus_entry);
compare_digest_to_routerstatus_entry);
}
/*XXXX make this static once functions are moved into this file. */
......@@ -815,7 +815,7 @@ networkstatus_vote_find_entry_idx(networkstatus_t *ns,
const char *digest, int *found_out)
{
return smartlist_bsearch_idx(ns->routerstatus_list, digest,
_compare_digest_to_routerstatus_entry,
compare_digest_to_routerstatus_entry,
found_out);
}
......@@ -868,7 +868,7 @@ router_get_consensus_status_by_id(const char *digest)
if (!current_consensus)
return NULL;
return smartlist_bsearch(current_consensus->routerstatus_list, digest,
_compare_digest_to_routerstatus_entry);
compare_digest_to_routerstatus_entry);
}
/** Given a nickname (possibly verbose, possibly a hexadecimal digest), return
......@@ -1827,7 +1827,7 @@ char *
networkstatus_getinfo_helper_single(routerstatus_t *rs)
{
char buf[RS_ENTRY_LEN+1];
routerstatus_format_entry(buf, sizeof(buf), rs, NULL, 0, 1);
routerstatus_format_entry(buf, sizeof(buf), rs, NULL, NS_CONTROL_PORT);
return tor_strdup(buf);
}
......
......@@ -1509,6 +1509,9 @@ 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 */
uint32_t bandwidth; /**< Bandwidth (capacity) of the router as reported in
* the vote/consensus, in kilobytes/sec. */
......@@ -2539,6 +2542,9 @@ typedef struct {
* migration purposes? */
int V3AuthUseLegacyKey;
/** Location of bandwidth measurement file */
char *V3BandwidthsFile;
/** The length of time that we think an initial consensus should be fresh.
* Only altered on testing networks. */
int TestingV3AuthInitialVotingInterval;
......@@ -3437,8 +3443,8 @@ download_status_mark_impossible(download_status_t *dl)
* Running Stable Unnamed V2Dir Valid\n". */
#define MAX_FLAG_LINE_LEN 96
/** Length of "w" line for weighting. Currently at most
* "w Bandwidth=<uint32t>\n" */
#define MAX_WEIGHT_LINE_LEN (13+10)
* "w Bandwidth=<uint32t> Measured=<uint32t>\n" */
#define MAX_WEIGHT_LINE_LEN (12+10+10+10+1)
/** Maximum length of an exit policy summary line. */
#define MAX_POLICY_LINE_LEN (3+MAX_EXITPOLICY_SUMMARY_LEN)
/** Amount of space to allocate for each entry: r, s, and v lines. */
......@@ -3521,13 +3527,34 @@ int dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff);
int dirserv_have_any_serverdesc(smartlist_t *fps, int spool_src);
size_t dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs,
int compressed);
typedef enum {
NS_V2, NS_V3_CONSENSUS, NS_V3_VOTE, NS_CONTROL_PORT
} routerstatus_format_type_t;
int routerstatus_format_entry(char *buf, size_t buf_len,
routerstatus_t *rs, const char *platform,
int first_line_only, int v2_format);
routerstatus_format_type_t format);
void dirserv_free_all(void);
void cached_dir_decref(cached_dir_t *d);
cached_dir_t *new_cached_dir(char *s, time_t published);
#ifdef DIRSERV_PRIVATE
typedef struct measured_bw_line_t {
char node_id[DIGEST_LEN];
char node_hex[MAX_HEX_NICKNAME_LEN+1];
long int bw;
} measured_bw_line_t;
int
measured_bw_line_parse(measured_bw_line_t *out, const char *line);
int
measured_bw_line_apply(measured_bw_line_t *parsed_line,
smartlist_t *routerstatuses);
#endif
int dirserv_read_measured_bandwidths(const char *from_file,
smartlist_t *routerstatuses);
/********************************* dirvote.c ************************/
/** Lowest allowable value for VoteSeconds. */
......@@ -3841,6 +3868,8 @@ int router_set_networkstatus_v2(const char *s, time_t arrived_at,
v2_networkstatus_source_t source,
smartlist_t *requested_fingerprints);
void networkstatus_v2_list_clean(time_t now);
int compare_digest_to_routerstatus_entry(const void *_key,
const void **_member);
routerstatus_t *networkstatus_v2_find_entry(networkstatus_v2_t *ns,
const char *digest);
routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns,
......@@ -4705,6 +4734,7 @@ void sort_version_list(smartlist_t *lst, int remove_duplicates);
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);
networkstatus_v2_t *networkstatus_v2_parse_from_string(const char *s);
networkstatus_t *networkstatus_parse_vote_from_string(const char *s,
const char **eos_out,
......
......@@ -1924,6 +1924,16 @@ routerstatus_parse_entry_from_string(memarea_t *area,
goto err;
}
rs->has_bandwidth = 1;
} else if (!strcmpstart(tok->args[i], "Measured=")) {
int ok;
rs->measured_bw = 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;
}
rs->has_measured_bw = 1;