diff --git a/src/or/consdiff.c b/src/or/consdiff.c index 3c2140b642..1baa11897c 100644 --- a/src/or/consdiff.c +++ b/src/or/consdiff.c @@ -1401,3 +1401,12 @@ consensus_diff_apply(const char *consensus, return result; } +/** Return true iff, based on its header, document is likely + * to be a consensus diff. */ +int +looks_like_a_consensus_diff(const char *document, size_t len) +{ + return (len >= strlen(ns_diff_version) && + fast_memeq(document, ns_diff_version, strlen(ns_diff_version))); +} + diff --git a/src/or/consdiff.h b/src/or/consdiff.h index 284a576525..d05df74b75 100644 --- a/src/or/consdiff.h +++ b/src/or/consdiff.h @@ -12,6 +12,8 @@ char *consensus_diff_generate(const char *cons1, char *consensus_diff_apply(const char *consensus, const char *diff); +int looks_like_a_consensus_diff(const char *document, size_t len); + #ifdef CONSDIFF_PRIVATE struct memarea_t; diff --git a/src/or/directory.c b/src/or/directory.c index 76fc121909..bf963f1187 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -13,6 +13,7 @@ #include "config.h" #include "connection.h" #include "connection_edge.h" +#include "consdiff.h" #include "consdiffmgr.h" #include "control.h" #include "compat.h" @@ -2417,6 +2418,10 @@ handle_response_fetch_consensus(dir_connection_t *conn, const char *reason = args->reason; const time_t now = approx_time(); + const char *consensus; + char *new_consensus = NULL; + const char *sourcename; + int r; const char *flavname = conn->requested_resource; if (status_code != 200) { @@ -2429,15 +2434,57 @@ handle_response_fetch_consensus(dir_connection_t *conn, networkstatus_consensus_download_failed(status_code, flavname); return -1; } - log_info(LD_DIR,"Received consensus directory (body size %d) from server " - "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); - if ((r=networkstatus_set_current_consensus(body, flavname, 0, + + if (looks_like_a_consensus_diff(body, body_len)) { + /* First find our previous consensus. Maybe it's in ram, maybe not. */ + cached_dir_t *cd = dirserv_get_consensus(flavname); + const char *consensus_body; + char *owned_consensus = NULL; + if (cd) { + consensus_body = cd->dir; + } else { + owned_consensus = networkstatus_read_cached_consensus(flavname); + consensus_body = owned_consensus; + } + if (!consensus_body) { + log_warn(LD_DIR, "Received a consensus diff, but we can't find " + "any %s-flavored consensus in our current cache.",flavname); + networkstatus_consensus_download_failed(0, flavname); + // XXXX if this happens too much, see below + return -1; + } + + new_consensus = consensus_diff_apply(consensus_body, body); + tor_free(owned_consensus); + if (new_consensus == NULL) { + log_warn(LD_DIR, "Could not apply consensus diff received from server " + "'%s:%d'", conn->base_.address, conn->base_.port); + // XXXX If this happens too many times, we should maybe not use + // XXXX this directory for diffs any more? + networkstatus_consensus_download_failed(0, flavname); + return -1; + } + log_info(LD_DIR, "Applied consensus diff (body size %d) from server " + "'%s:%d' resulted in a new consensus document (size %d).", + (int)body_len, conn->base_.address, conn->base_.port, + (int)strlen(new_consensus)); + consensus = new_consensus; + sourcename = "generated based on a diff"; + } else { + log_info(LD_DIR,"Received consensus directory (body size %d) from server " + "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); + consensus = body; + sourcename = "downloaded"; + } + + if ((r=networkstatus_set_current_consensus(consensus, flavname, 0, conn->identity_digest))<0) { log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR, - "Unable to load %s consensus directory downloaded from " + "Unable to load %s consensus directory %s from " "server '%s:%d'. I'll try again soon.", - flavname, conn->base_.address, conn->base_.port); + flavname, sourcename, conn->base_.address, conn->base_.port); networkstatus_consensus_download_failed(0, flavname); + tor_free(new_consensus); return -1; } @@ -2455,6 +2502,7 @@ handle_response_fetch_consensus(dir_connection_t *conn, } log_info(LD_DIR, "Successfully loaded consensus."); + tor_free(new_consensus); return 0; } diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 8a5cdaf863..18537a3f7b 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -179,53 +179,74 @@ networkstatus_reset_download_failures(void) download_status_reset(&consensus_bootstrap_dl_status[i]); } +/** + * Read and and return the cached consensus of type flavorname. If + * unverified is false, get the one we haven't verified. Return NULL if + * the file isn't there. */ +static char * +networkstatus_read_cached_consensus_impl(int flav, + const char *flavorname, + int unverified_consensus) +{ + char buf[128]; + const char *prefix; + if (unverified_consensus) { + prefix = "unverified"; + } else { + prefix = "cached"; + } + if (flav == FLAV_NS) { + tor_snprintf(buf, sizeof(buf), "%s-consensus", prefix); + } else { + tor_snprintf(buf, sizeof(buf), "%s-%s-consensus", prefix, flavorname); + } + + char *filename = get_datadir_fname(buf); + char *result = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); + tor_free(filename); + return result; +} + +/** Return a new string containing the current cached consensus of flavor + * flavorname. */ +char * +networkstatus_read_cached_consensus(const char *flavorname) + { + int flav = networkstatus_parse_flavor_name(flavorname); + if (flav < 0) + return NULL; + return networkstatus_read_cached_consensus_impl(flav, flavorname, 0); +} + /** Read every cached v3 consensus networkstatus from the disk. */ int router_reload_consensus_networkstatus(void) { - char *filename; - char *s; const unsigned int flags = NSSET_FROM_CACHE | NSSET_DONT_DOWNLOAD_CERTS; int flav; /* FFFF Suppress warnings if cached consensus is bad? */ for (flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) { - char buf[128]; const char *flavor = networkstatus_get_flavor_name(flav); - if (flav == FLAV_NS) { - filename = get_datadir_fname("cached-consensus"); - } else { - tor_snprintf(buf, sizeof(buf), "cached-%s-consensus", flavor); - filename = get_datadir_fname(buf); - } - s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); + char *s = networkstatus_read_cached_consensus_impl(flav, flavor, 0); if (s) { if (networkstatus_set_current_consensus(s, flavor, flags, NULL) < -1) { - log_warn(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"", - flavor, filename); + log_warn(LD_FS, "Couldn't load consensus %s networkstatus from cache", + flavor); } tor_free(s); } - tor_free(filename); - if (flav == FLAV_NS) { - filename = get_datadir_fname("unverified-consensus"); - } else { - tor_snprintf(buf, sizeof(buf), "unverified-%s-consensus", flavor); - filename = get_datadir_fname(buf); - } - - s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); + s = networkstatus_read_cached_consensus_impl(flav, flavor, 1); if (s) { if (networkstatus_set_current_consensus(s, flavor, flags|NSSET_WAS_WAITING_FOR_CERTS, NULL)) { - log_info(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"", - flavor, filename); - } + log_info(LD_FS, "Couldn't load unverified consensus %s networkstatus " + "from cache", flavor); + } tor_free(s); } - tor_free(filename); } if (!networkstatus_get_latest_consensus()) { diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 8a9f5f0b09..6a90d706c5 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -16,6 +16,7 @@ void networkstatus_reset_warnings(void); void networkstatus_reset_download_failures(void); +char *networkstatus_read_cached_consensus(const char *flavorname); int router_reload_consensus_networkstatus(void); void routerstatus_free(routerstatus_t *rs); void networkstatus_vote_free(networkstatus_t *ns);