📄 networkstatus.c
字号:
CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES))
return; /* We failed downloading a consensus too recently. */
if (connection_get_by_type_purpose(CONN_TYPE_DIR,
DIR_PURPOSE_FETCH_CONSENSUS))
return; /* There's an in-progress download.*/
if (consensus_waiting_for_certs) {
/* XXXX020 make sure this doesn't delay sane downloads. */
if (consensus_waiting_for_certs_set_at + DELAY_WHILE_FETCHING_CERTS > now)
return; /* We're still getting certs for this one. */
else {
if (!consensus_waiting_for_certs_dl_failed) {
download_status_failed(&consensus_dl_status, 0);
consensus_waiting_for_certs_dl_failed=1;
}
}
}
log_info(LD_DIR, "Launching networkstatus consensus download.");
directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS,
ROUTER_PURPOSE_GENERAL, NULL, 1);
}
/** Called when an attempt to download a consensus fails: note that the
* failure occurred, and possibly retry. */
void
networkstatus_consensus_download_failed(int status_code)
{
download_status_failed(&consensus_dl_status, status_code);
/* Retry immediately, if appropriate. */
update_consensus_networkstatus_downloads(time(NULL));
}
/** How long do we (as a cache) wait after a consensus becomes non-fresh
* before trying to fetch another? */
#define CONSENSUS_MIN_SECONDS_BEFORE_CACHING 120
/** Update the time at which we'll consider replacing the current
* consensus. */
void
update_consensus_networkstatus_fetch_time(time_t now)
{
or_options_t *options = get_options();
networkstatus_t *c = networkstatus_get_live_consensus(now);
if (c) {
long dl_interval;
long interval = c->fresh_until - c->valid_after;
time_t start;
if (directory_fetches_dir_info_early(options)) {
/* We want to cache the next one at some point after this one
* is no longer fresh... */
start = c->fresh_until + CONSENSUS_MIN_SECONDS_BEFORE_CACHING;
/* But only in the first half-interval after that. */
dl_interval = interval/2;
} else {
/* We're an ordinary client or a bridge. Give all the caches enough
* time to download the consensus. */
start = c->fresh_until + (interval*3)/4;
/* But download the next one well before this one is expired. */
dl_interval = ((c->valid_until - start) * 7 )/ 8;
/* If we're a bridge user, make use of the numbers we just computed
* to choose the rest of the interval *after* them. */
if (directory_fetches_dir_info_later(options)) {
/* Give all the *clients* enough time to download the consensus. */
start = start + dl_interval + CONSENSUS_MIN_SECONDS_BEFORE_CACHING;
/* But try to get it before ours actually expires. */
dl_interval = (c->valid_until - start) -
CONSENSUS_MIN_SECONDS_BEFORE_CACHING;
}
}
if (dl_interval < 1)
dl_interval = 1;
/* We must not try to replace c while it's still the most valid: */
tor_assert(c->fresh_until < start);
/* We must download the next one before c is invalid: */
tor_assert(start+dl_interval < c->valid_until);
time_to_download_next_consensus = start +crypto_rand_int((int)dl_interval);
{
char tbuf1[ISO_TIME_LEN+1];
char tbuf2[ISO_TIME_LEN+1];
char tbuf3[ISO_TIME_LEN+1];
format_local_iso_time(tbuf1, c->fresh_until);
format_local_iso_time(tbuf2, c->valid_until);
format_local_iso_time(tbuf3, time_to_download_next_consensus);
log_info(LD_DIR, "Live consensus %s the most recent until %s and will "
"expire at %s; fetching the next one at %s.",
(c->fresh_until > now) ? "will be" : "was",
tbuf1, tbuf2, tbuf3);
}
} else {
time_to_download_next_consensus = now;
log_info(LD_DIR, "No live consensus; we should fetch one immediately.");
}
}
/** Return 1 if there's a reason we shouldn't try any directory
* fetches yet (e.g. we demand bridges and none are yet known).
* Else return 0. */
int
should_delay_dir_fetches(or_options_t *options)
{
if (options->UseBridges && !any_bridge_descriptors_known()) {
log_info(LD_DIR, "delaying dir fetches (no running bridges known)");
return 1;
}
return 0;
}
/** Launch requests for networkstatus documents and authority certificates as
* appropriate. */
void
update_networkstatus_downloads(time_t now)
{
or_options_t *options = get_options();
if (should_delay_dir_fetches(options))
return;
if (directory_fetches_dir_info_early(options))
update_v2_networkstatus_cache_downloads(now);
update_consensus_networkstatus_downloads(now);
update_certificate_downloads(now);
}
/** Launch requests as appropriate for missing directory authority
* certificates. */
void
update_certificate_downloads(time_t now)
{
if (consensus_waiting_for_certs)
authority_certs_fetch_missing(consensus_waiting_for_certs, now);
else
authority_certs_fetch_missing(current_consensus, now);
}
/** Return the network status with a given identity digest. */
networkstatus_v2_t *
networkstatus_v2_get_by_digest(const char *digest)
{
SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
{
if (!memcmp(ns->identity_digest, digest, DIGEST_LEN))
return ns;
});
return NULL;
}
/** Return the most recent consensus that we have downloaded, or NULL if we
* don't have one. */
networkstatus_t *
networkstatus_get_latest_consensus(void)
{
return current_consensus;
}
/** Return the most recent consensus that we have downloaded, or NULL if it is
* no longer live. */
networkstatus_t *
networkstatus_get_live_consensus(time_t now)
{
if (current_consensus &&
current_consensus->valid_after <= now &&
now <= current_consensus->valid_until)
return current_consensus;
else
return NULL;
}
/* XXXX020 remove this in favor of get_live_consensus. But actually,
* leave something like it for bridge users, who need to not totally
* lose if they spend a while fetching a new consensus. */
/** As networkstatus_get_live_consensus(), but is way more tolerant of expired
* consensuses. */
networkstatus_t *
networkstatus_get_reasonably_live_consensus(time_t now)
{
#define REASONABLY_LIVE_TIME (24*60*60)
if (current_consensus &&
current_consensus->valid_after <= now &&
now <= current_consensus->valid_until+REASONABLY_LIVE_TIME)
return current_consensus;
else
return NULL;
}
/** Given two router status entries for the same router identity, return 1 if
* if the contents have changed between them. Otherwise, return 0. */
static int
routerstatus_has_changed(const routerstatus_t *a, const routerstatus_t *b)
{
tor_assert(!memcmp(a->identity_digest, b->identity_digest, DIGEST_LEN));
return strcmp(a->nickname, b->nickname) ||
memcmp(a->descriptor_digest, b->descriptor_digest, DIGEST_LEN) ||
a->addr != b->addr ||
a->or_port != b->or_port ||
a->dir_port != b->dir_port ||
a->is_authority != b->is_authority ||
a->is_exit != b->is_exit ||
a->is_stable != b->is_stable ||
a->is_fast != b->is_fast ||
a->is_running != b->is_running ||
a->is_named != b->is_named ||
a->is_unnamed != b->is_unnamed ||
a->is_valid != b->is_valid ||
a->is_v2_dir != b->is_v2_dir ||
a->is_possible_guard != b->is_possible_guard ||
a->is_bad_exit != b->is_bad_exit ||
a->is_bad_directory != b->is_bad_directory ||
a->is_hs_dir != b->is_hs_dir ||
a->version_known != b->version_known ||
a->version_supports_begindir != b->version_supports_begindir ||
a->version_supports_extrainfo_upload !=
b->version_supports_extrainfo_upload ||
a->version_supports_conditional_consensus !=
b->version_supports_conditional_consensus ||
a->version_supports_v3_dir != b->version_supports_v3_dir;
}
/** Notify controllers of any router status entries that changed between
* <b>old_c</b> and <b>new_c</b>. */
static void
notify_control_networkstatus_changed(const networkstatus_t *old_c,
const networkstatus_t *new_c)
{
smartlist_t *changed;
if (old_c == new_c)
return;
if (!old_c) {
control_event_networkstatus_changed(new_c->routerstatus_list);
return;
}
changed = smartlist_create();
SMARTLIST_FOREACH_JOIN(old_c->routerstatus_list, routerstatus_t *, rs_old,
new_c->routerstatus_list, routerstatus_t *, rs_new,
memcmp(rs_old->identity_digest,
rs_new->identity_digest, DIGEST_LEN),
smartlist_add(changed, rs_new)) {
if (routerstatus_has_changed(rs_old, rs_new))
smartlist_add(changed, rs_new);
} SMARTLIST_FOREACH_JOIN_END(rs_old, rs_new);
control_event_networkstatus_changed(changed);
smartlist_free(changed);
}
/** Copy all the ancillary information (like router download status and so on)
* from <b>old_c</b> to <b>new_c</b>. */
static void
networkstatus_copy_old_consensus_info(networkstatus_t *new_c,
const networkstatus_t *old_c)
{
if (old_c == new_c)
return;
if (!old_c || !smartlist_len(old_c->routerstatus_list))
return;
SMARTLIST_FOREACH_JOIN(old_c->routerstatus_list, routerstatus_t *, rs_old,
new_c->routerstatus_list, routerstatus_t *, rs_new,
memcmp(rs_old->identity_digest,
rs_new->identity_digest, DIGEST_LEN),
STMT_NIL) {
/* Okay, so we're looking at the same identity. */
rs_new->name_lookup_warned = rs_old->name_lookup_warned;
rs_new->last_dir_503_at = rs_old->last_dir_503_at;
if (!memcmp(rs_old->descriptor_digest, rs_new->descriptor_digest,
DIGEST_LEN)) {
/* And the same descriptor too! */
memcpy(&rs_new->dl_status, &rs_old->dl_status,sizeof(download_status_t));
}
} SMARTLIST_FOREACH_JOIN_END(rs_old, rs_new);
}
/** Try to replace the current cached v3 networkstatus with the one in
* <b>consensus</b>. If we don't have enough certificates to validate it,
* store it in consensus_waiting_for_certs and launch a certificate fetch.
*
* Return 0 on success, <0 on failure. On failure, caller should increment
* the failure count as appropriate.
*
* We return -1 for mild failures that don't need to be reported to the
* user, and -2 for more serious problems.
*/
int
networkstatus_set_current_consensus(const char *consensus, unsigned flags)
{
networkstatus_t *c;
int r, result = -1;
time_t now = time(NULL);
char *unverified_fname = NULL, *consensus_fname = NULL;
const unsigned from_cache = flags & NSSET_FROM_CACHE;
const unsigned was_waiting_for_certs = flags & NSSET_WAS_WAITING_FOR_CERTS;
const unsigned dl_certs = !(flags & NSSET_DONT_DOWNLOAD_CERTS);
/* Make sure it's parseable. */
c = networkstatus_parse_vote_from_string(consensus, NULL, 0);
if (!c) {
log_warn(LD_DIR, "Unable to parse networkstatus consensus");
result = -2;
goto done;
}
if (current_consensus &&
!memcmp(c->networkstatus_digest, current_consensus->networkstatus_digest,
DIGEST_LEN)) {
/* We already have this one. That's a failure. */
log_info(LD_DIR, "Got a consensus we already have");
goto done;
}
if (current_consensus && c->valid_after <= current_consensus->valid_after) {
/* We have a newer one. There's no point in accepting this one,
* even if it's great. */
log_info(LD_DIR, "Got a consensus at least as old as the one we have");
goto done;
}
consensus_fname = get_datadir_fname("cached-consensus");
unverified_fname = get_datadir_fname("unverified-consensus");
/* Make sure it's signed enough. */
if ((r=networkstatus_check_consensus_signature(c, 1))<0) {
if (r == -1) {
/* Okay, so it _might_ be signed enough if we get more certificates. */
if (!was_waiting_for_certs) {
log_info(LD_DIR,
"Not enough certificates to check networkstatus consensus");
}
if (!current_consensus ||
c->valid_after > current_consensus->valid_after) {
if (consensus_waiting_for_certs)
networkstatus_vote_free(consensus_waiting_for_certs);
tor_free(consensus_waiting_for_certs_body);
consensus_waiting_for_certs = c;
c = NULL; /* Prevent free. */
consensus_waiting_for_certs_body = tor_strdup(consensus);
consensus_waiting_for_certs_set_at = now;
consensus_waiting_for_certs_dl_failed = 0;
if (!from_cache) {
write_str_to_file(unverified_fname, consensus, 0);
}
if (dl_certs)
authority_certs_fetch_missing(c, now);
/* This case is not a success or a failure until we get the certs
* or fail to get the certs. */
result = 0;
} else {
/* Even if we had enough signatures, we'd never use this as the
* latest consensus. */
if (was_waiting_for_certs && from_cache)
unlink(unverified_fname);
}
goto done;
} else {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -