diff --git a/src/or/directory.c b/src/or/directory.c index ea8f2046d3..c340e504b2 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -163,14 +163,25 @@ directory_get_from_dirserver(uint8_t purpose, const char *resource, trusted_dir_server_t *ds = NULL; int fascistfirewall = firewall_is_fascist(); int directconn = purpose == DIR_PURPOSE_FETCH_DIR || - purpose == DIR_PURPOSE_FETCH_RUNNING_LIST; + purpose == DIR_PURPOSE_FETCH_RUNNING_LIST || + purpose == DIR_PURPOSE_FETCH_NETWORKSTATUS || + purpose == DIR_PURPOSE_FETCH_SERVERDESC; int fetch_fresh_first = advertised_server_mode(); int priv = purpose_is_private(purpose); + int need_v1_support = purpose == DIR_PURPOSE_FETCH_DIR || + purpose == DIR_PURPOSE_FETCH_RUNNING_LIST; if (directconn) { - if (fetch_fresh_first) { + if (fetch_fresh_first && purpose == DIR_PURPOSE_FETCH_NETWORKSTATUS && + strlen(resource) == HEX_DIGEST_LEN) { + /* Try to ask the actual dirserver its opinion. */ + char digest[DIGEST_LEN]; + base16_decode(digest, DIGEST_LEN, resource, HEX_DIGEST_LEN); + ds = router_get_trusteddirserver_by_digest(digest); + } + if (!ds && fetch_fresh_first) { /* only ask authdirservers, and don't ask myself */ - ds = router_pick_trusteddirserver(1, 1, fascistfirewall, + ds = router_pick_trusteddirserver(need_v1_support, 1, fascistfirewall, retry_if_no_servers); } if (!ds) { @@ -178,9 +189,17 @@ directory_get_from_dirserver(uint8_t purpose, const char *resource, r = router_pick_directory_server(1, fascistfirewall, 0, retry_if_no_servers); if (!r) { - log_fn(LOG_INFO, "No router found for %s; falling back to dirserver list", - purpose == DIR_PURPOSE_FETCH_RUNNING_LIST - ? "status list" : "directory"); + const char *which; + if (purpose == DIR_PURPOSE_FETCH_DIR) + which = "directory"; + else if (purpose == DIR_PURPOSE_FETCH_RUNNING_LIST) + which = "status list"; + else if (purpose == DIR_PURPOSE_FETCH_NETWORKSTATUS) + which = "network status"; + else // if (purpose == DIR_PURPOSE_FETCH_NETWORKSTATUS) + which = "server descriptors"; + log_fn(LOG_INFO, + "No router found for %s; falling back to dirserver list",which); ds = router_pick_trusteddirserver(1, 1, fascistfirewall, retry_if_no_servers); } @@ -466,19 +485,21 @@ directory_send_command(connection_t *conn, const char *platform, httpcommand = "POST"; url = tor_strdup("/tor/rendezvous/publish"); break; + default: + tor_assert(0); + return; } tor_snprintf(request, sizeof(request), "%s %s", httpcommand, proxystring); connection_write_to_buf(request, strlen(request), conn); connection_write_to_buf(url, strlen(url), conn); tor_free(url); - tor_snprintf(request, len, " HTTP/1.0\r\nContent-Length: %lu\r\nHost: %s%s\r\n\r\n", + tor_snprintf(request, sizeof(request), " HTTP/1.0\r\nContent-Length: %lu\r\nHost: %s%s\r\n\r\n", payload ? (unsigned long)payload_len : 0, hoststring, proxyauthstring); connection_write_to_buf(request, strlen(request), conn); - if (payload) { /* then send the payload afterwards too */ connection_write_to_buf(payload, payload_len, conn); @@ -858,6 +879,34 @@ connection_dir_client_reached_eof(connection_t *conn) } } + if (conn->purpose == DIR_PURPOSE_FETCH_NETWORKSTATUS) { + /* XXXX NM We *must* make certain we get the one(s) we asked for or we + * could be partitioned. */ + log_fn(LOG_INFO,"Received networkstatus objects (size %d)",(int) body_len); + if (status_code != 200) { + log_fn(LOG_WARN,"Received http status code %d (\"%s\") from server '%s'. Failing.", + status_code, reason, conn->address); + tor_free(body); tor_free(headers); tor_free(reason); + return -1; + } + while (*body) { + char *next = strstr(body, "\nnetwork-status-version"); + if (next) + *next = '\0'; + if (router_set_networkstatus(body, time(NULL), 0)<0) + break; + if (next) + body = next+1; + else + break; + } + } + + if (conn->purpose == DIR_PURPOSE_FETCH_SERVERDESC) { + /* XXXX NM implement this. */ + log_fn(LOG_WARN, "Somehow, we requested some individual server descriptors. Skipping."); + } + if (conn->purpose == DIR_PURPOSE_UPLOAD_DIR) { switch (status_code) { case 200: diff --git a/src/or/main.c b/src/or/main.c index 8f0f329845..08f8d26a1d 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -731,6 +731,8 @@ run_scheduled_events(time_t now) stats_n_seconds_working < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT && !we_are_hibernating()) consider_testing_reachability(); + + update_networkstatus_downloads(); } /** 3a. Every second, we examine pending circuits and prune the diff --git a/src/or/or.h b/src/or/or.h index 33574e9181..8c715d11b4 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2027,6 +2027,8 @@ trusted_dir_server_t *router_pick_trusteddirserver(int need_v1_support, int requireother, int fascistfirewall, int retry_if_no_servers); +trusted_dir_server_t *router_get_trusteddirserver_by_digest( + const char *digest); int all_trusted_directory_servers_down(void); void routerlist_add_family(smartlist_t *sl, routerinfo_t *router); void add_nickname_list_to_smartlist(smartlist_t *sl, const char *list, int warn_if_down); @@ -2086,6 +2088,8 @@ int router_update_status_from_smartlist(routerinfo_t *r, void add_trusted_dir_server(const char *addr, uint16_t port, const char *digest, int supports_v1); void clear_trusted_dir_servers(void); +networkstatus_t *networkstatus_get_by_digest(const char *digest); +void update_networkstatus_downloads(void); /********************************* routerparse.c ************************/ diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 6ede4d2270..c572f3d2ac 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -176,6 +176,21 @@ router_pick_directory_server(int requireother, return choice; } +trusted_dir_server_t * +router_get_trusteddirserver_by_digest(const char *digest) +{ + if (!trusted_dir_servers) + return NULL; + + SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds, + { + if (!memcmp(ds->digest, digest, DIGEST_LEN)) + return ds; + }); + + return NULL; +} + /** Try to find a running trusted dirserver. If there are no running * trusted dirservers and retry_if_no_servers is non-zero, * set them all as running again, and try again. @@ -1219,7 +1234,10 @@ router_set_networkstatus(const char *s, time_t arrived_at, int is_cached) if (!memcmp(old_ns->identity_digest, ns->identity_digest, DIGEST_LEN)) { if (!memcmp(old_ns->networkstatus_digest, ns->networkstatus_digest, DIGEST_LEN)) { + /* Same one we had before. */ networkstatus_free(ns); + if (old_ns->received_on < arrived_at) + old_ns->received_on = arrived_at; return 0; } else if (old_ns->published_on >= ns->published_on) { log_fn(LOG_INFO, "Dropping network-status; we have a newer one for this authority."); @@ -1257,6 +1275,70 @@ router_set_networkstatus(const char *s, time_t arrived_at, int is_cached) return 0; } +/** DOCDOC */ +void +update_networkstatus_downloads(void) +{ + /* XXX Yes, these constants are supposed to be dumb, so we can choose better + * values. */ +#define ABOUT_TWO_DAYS (48*60*60) +#define ABOUT_HALF_AN_HOUR (30*60) + int n_live = 0, needed = 0, n_dirservers, i; + int most_recent_idx = -1; + trusted_dir_server_t *most_recent = NULL; + time_t most_recent_received = 0; + time_t now = time(NULL); + + /* This is a little tricky. We want to download enough network-status + * objects so that we have at least half of them under ABOUT_TWO_DAYS + * publication time. We want to download a new *one* if the most recent + * one's publication time is under ABOUT_HALF_AN_HOUR. + */ + + tor_assert(trusted_dir_servers); + n_dirservers = smartlist_len(trusted_dir_servers); + SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds, + { + networkstatus_t *ns = networkstatus_get_by_digest(ds->digest); + if (ns->published_on > now-ABOUT_TWO_DAYS) + ++n_live; + if (!most_recent || ns->received_on > most_recent_received) { + most_recent_idx = ds_sl_idx; /* magic variable from FOREACH*/ + most_recent = ds; + most_recent_received = ns->received_on; + } + }); + + /* Download enough so we have at least half live, but no more than all the + * trusted dirservers we know. + */ + if (n_live < (n_dirservers/2)+1) + needed = (n_dirservers/2)+1-n_live; + if (needed > n_dirservers) + needed = n_dirservers; + /* Also, download at least 1 every ABOUT_HALF_AN_HOUR. */ + if (most_recent_received < now-ABOUT_HALF_AN_HOUR && needed < 1) + needed = 1; + + /* If no networkstatus was found, choose a dirserver at random as "most + * recent". */ + if (most_recent_idx<0) + most_recent_idx = crypto_pseudo_rand_int(n_dirservers); + + /* XXXX NM This could compress multiple downloads into a single request. + * It could also be smarter on failures. */ + for (i = most_recent_idx+1; needed; ++i) { + char resource[HEX_DIGEST_LEN+1]; + trusted_dir_server_t *ds; + if (i > n_dirservers) + i = 0; + ds = smartlist_get(trusted_dir_servers, i); + base16_encode(resource, sizeof(resource), ds->digest, DIGEST_LEN); + directory_get_from_dirserver(DIR_PURPOSE_FETCH_NETWORKSTATUS, resource, 1); + --needed; + } +} + /** Ensure that our own routerinfo is at the front, and remove duplicates * of our routerinfo. */ @@ -1709,3 +1791,14 @@ clear_trusted_dir_servers(void) } } +networkstatus_t * +networkstatus_get_by_digest(const char *digest) +{ + SMARTLIST_FOREACH(networkstatus_list, networkstatus_t *, ns, + { + if (!memcmp(ns->identity_digest, digest, DIGEST_LEN)) + return ns; + }); + return NULL; +} +