/* 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 router descriptors, consenus directories, * and similar objects. * * The objects parsed by this module use a common text-based metaformat, * documented in dir-spec.txt in torspec.git. This module is itself divided * into two major kinds of function: code to handle the metaformat, and code * to convert from particular instances of the metaformat into the * objects that Tor uses. * * The generic parsing code works by calling a table-based tokenizer on the * input string. Each token corresponds to a single line with a token, plus * optional arguments on that line, plus an optional base-64 encoded object * after that line. Each token has a definition in a table of token_rule_t * entries that describes how many arguments it can take, whether it takes an * object, how many times it may appear, whether it must appear first, and so * on. * * The tokenizer function tokenize_string() converts its string input into a * smartlist full of instances of directory_token_t, according to a provided * table of token_rule_t. * * The generic parts of this module additionally include functions for * finding the start and end of signed information inside a signed object, and * computing the digest that will be signed. * * There are also functions for saving objects to disk that have caused * parsing to fail. * * The specific parts of this module describe conversions between * particular lists of directory_token_t and particular objects. The * kinds of objects that can be parsed here are: *
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<