#define BWAUTH_PRIVATE
#include "core/or/or.h"
#include "feature/dirauth/bwauth.h"
#include "app/config/config.h"
#include "feature/dirauth/dirauth_sys.h"
#include "feature/nodelist/networkstatus.h"
#include "feature/nodelist/routerlist.h"
#include "feature/dirparse/ns_parse.h"
#include "feature/dirauth/dirauth_options_st.h"
#include "feature/nodelist/routerinfo_st.h"
#include "feature/nodelist/vote_routerstatus_st.h"
#include "lib/crypt_ops/crypto_format.h"
#include "lib/encoding/keyval.h"
static int routers_with_measured_bw = 0;
void
dirserv_count_measured_bws(const smartlist_t *routers)
{
routers_with_measured_bw = 0;
SMARTLIST_FOREACH_BEGIN(routers, const routerinfo_t *, ri) {
if (dirserv_has_measured_bw(ri->cache_info.identity_digest)) {
++routers_with_measured_bw;
}
} SMARTLIST_FOREACH_END(ri);
}
int
dirserv_get_last_n_measured_bws(void)
{
return routers_with_measured_bw;
}
typedef struct mbw_cache_entry_t {
long mbw_kb;
time_t as_of;
} mbw_cache_entry_t;
static digestmap_t *mbw_cache = NULL;
STATIC void
dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line,
time_t as_of)
{
mbw_cache_entry_t *e = NULL;
tor_assert(parsed_line);
if (!mbw_cache) mbw_cache = digestmap_new();
e = digestmap_get(mbw_cache, parsed_line->node_id);
if (e) {
if (as_of > e->as_of) {
e->mbw_kb = parsed_line->bw_kb;
e->as_of = as_of;
}
} else {
e = tor_malloc(sizeof(*e));
e->mbw_kb = parsed_line->bw_kb;
e->as_of = as_of;
digestmap_set(mbw_cache, parsed_line->node_id, e);
}
}
void
dirserv_clear_measured_bw_cache(void)
{
if (mbw_cache) {
digestmap_free(mbw_cache, tor_free_);
mbw_cache = NULL;
}
}
STATIC void
dirserv_expire_measured_bw_cache(time_t now)
{
if (mbw_cache) {
DIGESTMAP_FOREACH_MODIFY(mbw_cache, k, mbw_cache_entry_t *, e) {
if (now > e->as_of + MAX_MEASUREMENT_AGE) {
tor_free(e);
MAP_DEL_CURRENT(k);
}
} DIGESTMAP_FOREACH_END;
if (digestmap_size(mbw_cache) == 0) {
digestmap_free(mbw_cache, tor_free_);
mbw_cache = 0;
}
}
}
int
dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_kb_out,
time_t *as_of_out)
{
mbw_cache_entry_t *v = NULL;
int rv = 0;
if (mbw_cache && node_id) {
v = digestmap_get(mbw_cache, node_id);
if (v) {
rv = 1;
if (bw_kb_out) *bw_kb_out = v->mbw_kb;
if (as_of_out) *as_of_out = v->as_of;
}
}
return rv;
}
int
dirserv_has_measured_bw(const char *node_id)
{
return dirserv_query_measured_bw_cache_kb(node_id, NULL, NULL);
}
int
dirserv_get_measured_bw_cache_size(void)
{
if (mbw_cache) return digestmap_size(mbw_cache);
else return 0;
}
uint32_t
dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri)
{
int threshold;
uint32_t bw_kb = 0;
long mbw_kb;
tor_assert(ri);
if (!(dirserv_query_measured_bw_cache_kb(ri->cache_info.identity_digest,
&mbw_kb, NULL))) {
threshold = dirauth_get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised;
if (routers_with_measured_bw > threshold) {
bw_kb = 0;
} else {
bw_kb = router_get_advertised_bandwidth_capped(ri) / 1000;
}
} else {
bw_kb = (uint32_t)mbw_kb;
}
return bw_kb;
}
int
dirserv_read_measured_bandwidths(const char *from_file,
smartlist_t *routerstatuses,
smartlist_t *bw_file_headers,
uint8_t *digest_out)
{
FILE *fp = tor_fopen_cloexec(from_file, "r");
int applied_lines = 0;
time_t file_time, now;
int ok;
int line_is_after_headers = 0;
int rv = -1;
char *line = NULL;
size_t n = 0;
crypto_digest_t *digest = crypto_digest256_new(DIGEST_SHA256);
if (fp == NULL) {
log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s",
from_file);
goto err;
}
if (tor_getline(&line,&n,fp) <= 0) {
log_warn(LD_DIRSERV, "Empty bandwidth file");
goto err;
}
crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
if (!strlen(line) || line[strlen(line)-1] != '\n') {
log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s",
escaped(line));
goto continue_digest;
}
line[strlen(line)-1] = '\0';
file_time = (time_t)tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL);
if (!ok) {
log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s",
escaped(line));
goto continue_digest;
}
now = approx_time();
if ((now - file_time) > MAX_MEASUREMENT_AGE) {
log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u",
(unsigned)(time(NULL) - file_time));
goto continue_digest;
}
if (bw_file_headers)
smartlist_add_asprintf(bw_file_headers, "timestamp=%lu",
(unsigned long)file_time);
if (routerstatuses)
smartlist_sort(routerstatuses, compare_vote_routerstatus_entries);
while (!feof(fp)) {
measured_bw_line_t parsed_line;
if (tor_getline(&line, &n, fp) >= 0) {
crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
if (measured_bw_line_parse(&parsed_line, line,
line_is_after_headers) != -1) {
line_is_after_headers = 1;
dirserv_cache_measured_bw(&parsed_line, file_time);
if (measured_bw_line_apply(&parsed_line, routerstatuses) > 0)
applied_lines++;
} else if (strcmp(line, BW_FILE_HEADERS_TERMINATOR) == 0) {
line_is_after_headers = 1;
} else if (bw_file_headers &&
(line_is_after_headers == 0) &&
string_is_key_value(LOG_DEBUG, line) &&
!strchr(line, ' ') &&
(smartlist_len(bw_file_headers)
< MAX_BW_FILE_HEADER_COUNT_IN_VOTE)) {
line[strlen(line)-1] = '\0';
smartlist_add_strdup(bw_file_headers, line);
};
}
}
dirserv_expire_measured_bw_cache(now);
log_info(LD_DIRSERV,
"Bandwidth measurement file successfully read. "
"Applied %d measurements.", applied_lines);
rv = 0;
continue_digest:
while (!feof(fp)) {
if (tor_getline(&line, &n, fp) >= 0) {
crypto_digest_add_bytes(digest, (const char *) line, strlen(line));
}
}
err:
if (line) {
raw_free(line);
}
if (fp)
fclose(fp);
if (digest_out)
crypto_digest_get_digest(digest, (char *) digest_out, DIGEST256_LEN);
crypto_digest_free(digest);
return rv;
}
STATIC int
measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line,
int line_is_after_headers)
{
char *line = tor_strdup(orig_line);
char *cp = line;
int got_bw = 0;
int got_node_id = 0;
char *strtok_state;
if (strlen(line) == 0) {
log_warn(LD_DIRSERV, "Empty line in bandwidth file");
tor_free(line);
return -1;
}
if (line[strlen(line) - 1] == '\n') {
line[strlen(line) - 1] = '\0';
}
cp = tor_strtok_r(cp, " \t", &strtok_state);
if (!cp) {
log_warn(LD_DIRSERV, "Invalid line in bandwidth file: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
if (orig_line[strlen(orig_line)-1] != '\n') {
log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
do {
if (strcmpstart(cp, "vote=0") == 0) {
log_debug(LD_DIRSERV, "Ignoring bandwidth file line that contains "
"vote=0: %s",escaped(orig_line));
tor_free(line);
return -1;
} else if (strcmpstart(cp, "bw=") == 0) {
int parse_ok = 0;
char *endptr;
if (got_bw) {
log_warn(LD_DIRSERV, "Double bw= in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
cp+=strlen("bw=");
out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr);
if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) {
log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
got_bw=1;
} else if (strcmpstart(cp, "node_id=$") == 0) {
if (got_node_id) {
log_warn(LD_DIRSERV, "Double node_id= in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
cp+=strlen("node_id=$");
if (strlen(cp) != HEX_DIGEST_LEN ||
base16_decode(out->node_id, DIGEST_LEN,
cp, HEX_DIGEST_LEN) != DIGEST_LEN) {
log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
strlcpy(out->node_hex, cp, sizeof(out->node_hex));
got_node_id=1;
}
} while ((cp = tor_strtok_r(NULL, " \t", &strtok_state)));
if (got_bw && got_node_id) {
tor_free(line);
return 0;
} else if (line_is_after_headers == 0) {
log_debug(LD_DIRSERV, "Missing bw or node_id in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
return -1;
} else {
log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
escaped(orig_line));
tor_free(line);
return -1;
}
}
STATIC int
measured_bw_line_apply(measured_bw_line_t *parsed_line,
smartlist_t *routerstatuses)
{
vote_routerstatus_t *rs = NULL;
if (!routerstatuses)
return 0;
rs = smartlist_bsearch(routerstatuses, parsed_line->node_id,
compare_digest_to_vote_routerstatus_entry);
if (rs) {
rs->has_measured_bw = 1;
rs->measured_bw_kb = (uint32_t)parsed_line->bw_kb;
} else {
log_info(LD_DIRSERV, "Node ID %s not found in routerstatus list",
parsed_line->node_hex);
}
return rs != NULL;
}