Numerous changes to move towards client-side v2 directories.

connection.c:
- Add some more connection accessor functions to make directory
  download redundancy checking work.

directory.c, or.h, router.c, routerlist.c:
- Start on logic to note when networkstatus downloads fail.

dirserv.c, routerlist.c, routerparse.c:
- Start maintaining an is_named field in routerstatus_t.  Don't
  actually look at it yet.

dirserv.c, routerlist.c:
- Remove expired networkstatus objects.

or.h:
- Make some booleans into bitfields
- Add prototypes

routerlist.c:
- Sort networkstatus list by publication time
- Function to remove old (older than 10 days) networkstatus objects.
- Function to set a list of routerinfo_ts' status info from the
  current set of networkstatus objects.
- Function to tell which routerinfos we need to download based no the
  current set of networkstatus objects.
- Do not launch a networkstatus download if a redundant one is in progress.

routerparse.c:
- Keep router entries in networkstatus sorted by digest.


svn:r5012
This commit is contained in:
Nick Mathewson
2005-09-12 06:56:42 +00:00
parent e4272f1978
commit 3dc5e77b58
7 changed files with 361 additions and 33 deletions
+43
View File
@@ -1479,6 +1479,29 @@ connection_or_exact_get_by_addr_port(uint32_t addr, uint16_t port)
return best;
}
/** Return a connection with give type, address, port, and purpose or NULL if
* no such connection exists. */
connection_t *
connection_get_by_type_addr_port_purpose(int type, uint32_t addr, uint16_t port,
int purpose)
{
int i, n;
connection_t *conn;
connection_t **carray;
get_connection_array(&carray,&n);
for (i=0;i<n;i++) {
conn = carray[i];
if (conn->type == type &&
conn->addr == addr &&
conn->port == port &&
conn->purpose == purpose &&
!conn->marked_for_close)
return conn;
}
return NULL;
}
connection_t *
connection_get_by_identity_digest(const char *digest, int type)
{
@@ -1603,6 +1626,26 @@ connection_get_by_type_state_rendquery(int type, int state, const char *rendquer
return NULL;
}
/** Return an open, non-marked connection of a given type and purpose, or NULL
* if no such connection exists. */
connection_t *
connection_get_by_type_purpose(int type, int purpose)
{
int i, n;
connection_t *conn;
connection_t **carray;
get_connection_array(&carray,&n);
for (i=0;i<n;i++) {
conn = carray[i];
if (conn->type == type &&
!conn->marked_for_close &&
(purpose == conn->purpose))
return conn;
}
return NULL;
}
/** Return 1 if <b>conn</b> is a listener conn, else return 0. */
int
connection_is_listener(connection_t *conn)
+48
View File
@@ -51,6 +51,8 @@ static int body_is_plausible(const char *body, size_t body_len, int purpose);
static int purpose_is_private(uint8_t purpose);
static char *http_get_header(const char *headers, const char *which);
static char *http_get_origin(const char *headers, connection_t *conn);
static void connection_dir_download_networkstatus_failed(connection_t *conn);
static void dir_networkstatus_download_failed(smartlist_t *failed);
/********* START VARIABLES **********/
@@ -274,9 +276,36 @@ connection_dir_connect_failed(connection_t *conn)
conn->address);
directory_get_from_dirserver(conn->purpose, NULL,
0 /* don't retry_if_no_servers */);
} else if (conn->purpose == DIR_PURPOSE_FETCH_NETWORKSTATUS) {
log_fn(LOG_INFO, "Giving up on directory server at '%s'; retrying",
conn->address);
connection_dir_download_networkstatus_failed(conn);
}
}
/** DOCDOC */
static void
connection_dir_download_networkstatus_failed(connection_t *conn)
{
if (!strcmpstart(conn->requested_resource, "all")) {
directory_get_from_dirserver(conn->purpose, "all.z",
0 /* don't retry_if_no_servers */);
} else if (!strcmpstart(conn->requested_resource, "fp/")) {
smartlist_t *failed = smartlist_create();
smartlist_split_string(failed, conn->requested_resource+3, "+", 0, 0);
if (smartlist_len(failed)) {
char *last = smartlist_get(failed,smartlist_len(failed)-1);
size_t last_len = strlen(last);
if (!strcmp(last+last_len-2, ".z"))
last[last_len-2] = '\0';
dir_networkstatus_download_failed(failed);
SMARTLIST_FOREACH(failed, char *, cp, tor_free(cp));
}
smartlist_free(failed);
}
}
/** Helper for directory_initiate_command_(router|trusted_dir): send the
* command to a server whose address is <b>address</b>, whose IP is
* <b>addr</b>, whose directory port is <b>dir_port</b>, whose tor version is
@@ -896,6 +925,7 @@ connection_dir_client_reached_eof(connection_t *conn)
status_code, reason, conn->address, conn->port,
conn->requested_resource);
tor_free(body); tor_free(headers); tor_free(reason);
connection_dir_download_networkstatus_failed(conn);
return -1;
}
if (conn->requested_resource &&
@@ -922,6 +952,9 @@ connection_dir_client_reached_eof(connection_t *conn)
break;
}
if (which) {
if (smartlist_len(which)) {
dir_networkstatus_download_failed(which);
}
SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
smartlist_free(which);
}
@@ -1451,3 +1484,18 @@ connection_dir_finished_connecting(connection_t *conn)
return 0;
}
/** DOCDOC */
static void
dir_networkstatus_download_failed(smartlist_t *failed)
{
SMARTLIST_FOREACH(failed, const char *, fp,
{
char digest[DIGEST_LEN];
trusted_dir_server_t *dir;
base16_decode(digest, DIGEST_LEN, fp, strlen(fp));
dir = router_get_trusteddirserver_by_digest(digest);
++dir->n_networkstatus_failures;
});
}
+12 -3
View File
@@ -388,11 +388,11 @@ directory_remove_invalid(void)
changed = 1;
} else if (r>0 && !ent->is_verified) {
log(LOG_INFO, "Router '%s' is now approved.", ent->nickname);
ent->is_verified = 1;
ent->is_verified = ent->is_named = 1;
changed = 1;
} else if (r==0 && ent->is_verified) {
log(LOG_INFO, "Router '%s' is no longer approved.", ent->nickname);
ent->is_verified = 0;
ent->is_verified = ent->is_named = 0;
changed = 1;
}
}
@@ -752,6 +752,8 @@ dirserv_set_cached_directory(const char *directory, time_t published,
/** We've just received a v2 network-status for an authoritative directory
* with fingerprint <b>fp</b> (hex digest, no spaces), published at
* <b>published</b>. Store it so we can serve it to others.
*
* DOCDOC directory==NULL, published==0
*/
void
dirserv_set_cached_networkstatus_v2(const char *directory, const char *fp,
@@ -764,12 +766,19 @@ dirserv_set_cached_networkstatus_v2(const char *directory, const char *fp,
tor_assert(strlen(fp) == HEX_DIGEST_LEN);
if (!(d = strmap_get(cached_v2_networkstatus, fp))) {
if (!directory)
return;
d = tor_malloc_zero(sizeof(cached_dir_t));
strmap_set(cached_v2_networkstatus, fp, d);
}
tor_assert(d);
set_cached_dir(d, tor_strdup(directory), published);
if (directory) {
set_cached_dir(d, tor_strdup(directory), published);
} else {
free_cached_dir(d);
strmap_remove(cached_v2_networkstatus, fp);
}
}
static cached_dir_t *
+18 -7
View File
@@ -755,17 +755,22 @@ typedef struct {
addr_policy_t *exit_policy; /**< What streams will this OR permit
* to exit? */
long uptime; /**< How many seconds the router claims to have been up */
uint8_t is_hibernating; /**< Whether the router claims to be hibernating */
smartlist_t *declared_family; /**< Nicknames of router which this router
* claims are its family. */
char *contact_info; /**< Declared contact info for this router. */
unsigned int is_hibernating:1; /**< Whether the router claims to be
* hibernating */
/* local info */
int is_running; /**< As far as we know, is this OR currently running? */
unsigned int is_running:1; /**< As far as we know, is this OR currently
* running? */
unsigned int is_verified:1; /**< Has a trusted dirserver validated this OR?
* (For Authdir: Have we validated this OR?)
*/
/*XXXX Make this get used once we think we do naming right. NM */
unsigned int is_named:1; /* Do we believe the nickname that this OR gives us? */
time_t status_set_at; /**< When did we last update is_running? */
int is_verified; /**< Has a trusted dirserver validated this OR?
* (For Authdir: Have we validated this OR?)
*/
/* The below items are used only by authdirservers for
* reachability testing. */
@@ -1504,6 +1509,9 @@ connection_t *connection_get_by_identity_digest(const char *digest, int type);
connection_t *connection_get_by_global_id(uint32_t id);
connection_t *connection_get_by_type(int type);
connection_t *connection_get_by_type_purpose(int type, int purpose);
connection_t *connection_get_by_type_addr_port_purpose(int type, uint32_t addr,
uint16_t port, int purpose);
connection_t *connection_get_by_type_state(int type, int state);
connection_t *connection_get_by_type_state_lastwritten(int type, int state);
connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery);
@@ -2015,8 +2023,9 @@ typedef struct trusted_dir_server_t {
uint32_t addr;
uint16_t dir_port;
char digest[DIGEST_LEN];
int is_running;
int supports_v1_protocol;
unsigned int is_running:1;
unsigned int supports_v1_protocol:1;
int n_networkstatus_failures;
} trusted_dir_server_t;
int router_reload_router_list(void);
@@ -2097,6 +2106,8 @@ void clear_trusted_dir_servers(void);
networkstatus_t *networkstatus_get_by_digest(const char *digest);
void update_networkstatus_cache_downloads(time_t now);
void update_networkstatus_client_downloads(time_t now);
void routers_update_status_from_networkstatus(smartlist_t *routers);
smartlist_t *router_list_superseded(void);
/********************************* routerparse.c ************************/
+5 -2
View File
@@ -764,10 +764,13 @@ router_rebuild_descriptor(int force)
config_parse_addr_policy(get_options()->ExitPolicy, &ri->exit_policy, -1);
options_append_default_exit_policy(&ri->exit_policy);
if (desc_routerinfo) /* inherit values */
if (desc_routerinfo) { /* inherit values */
ri->is_verified = desc_routerinfo->is_verified;
ri->is_running = desc_routerinfo->is_running;
ri->is_named = desc_routerinfo->is_named;
}
if (authdir_mode(options))
ri->is_verified = 1; /* believe in yourself */
ri->is_verified = ri->is_named = 1; /* believe in yourself */
if (options->MyFamily) {
ri->declared_family = smartlist_create();
smartlist_split_string(ri->declared_family, options->MyFamily, ",",
+225 -20
View File
@@ -980,6 +980,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg)
return -2;
}
router->is_verified = authdir_verified;
router->is_named = router->is_verified; /*XXXX NM not right. */
if (tor_version_as_new_as(router->platform,"0.1.0.2-rc"))
router->is_verified = 1;
}
@@ -996,6 +997,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg)
if (authdir) {
/* Update the is_verified status based on our lookup. */
old_router->is_verified = router->is_verified;
old_router->is_named = router->is_named;
} else {
/* Update the is_running status to whatever we were told. */
old_router->is_running = router->is_running;
@@ -1198,10 +1200,37 @@ router_load_routerlist_from_directory(const char *s,
return 0;
}
/**DOCDOC */
static char *
networkstatus_get_cache_filename(const networkstatus_t *ns)
{
const char *datadir = get_options()->DataDirectory;
size_t len = strlen(datadir)+64;
char fp[HEX_DIGEST_LEN+1];
char *fn = tor_malloc(len+1);
base16_encode(fp, HEX_DIGEST_LEN+1, ns->identity_digest, DIGEST_LEN);
tor_snprintf(fn, len, "%s/cached-status/%s",datadir,fp);
return fn;
}
/** DOCDOC */
static int
_compare_networkstatus_published_on(const void **_a, const void **_b)
{
const networkstatus_t *a = *_a, *b = *_b;
if (a->published_on < b->published_on)
return -1;
else if (a->published_on > b->published_on)
return 1;
else
return 0;
}
/** How far in the future do we allow a network-status to get? (seconds) */
#define NETWORKSTATUS_ALLOW_SKEW (48*60*60)
/** DOCDOC returns 0 on no problems, -1 on problems.
* requested fingerprints must be upcased.
* removes and frees items from requested_fingerpritns
*/
int
router_set_networkstatus(const char *s, time_t arrived_at,
@@ -1210,15 +1239,16 @@ router_set_networkstatus(const char *s, time_t arrived_at,
networkstatus_t *ns;
int i, found;
time_t now;
char fp[HEX_DIGEST_LEN+1];
int skewed = 0;
trusted_dir_server_t *trusted_dir;
char fp[HEX_DIGEST_LEN+1];
ns = networkstatus_parse_from_string(s);
if (!ns) {
log_fn(LOG_WARN, "Couldn't parse network status.");
return -1;
}
if (!router_digest_is_trusted_dir(ns->identity_digest)) {
if (!(trusted_dir=router_get_trusteddirserver_by_digest(ns->identity_digest))) {
log_fn(LOG_INFO, "Network status was signed, but not by an authoritative directory we recognize.");
networkstatus_free(ns);
return -1;
@@ -1238,21 +1268,27 @@ router_set_networkstatus(const char *s, time_t arrived_at,
networkstatus_list = smartlist_create();
if (source == NS_FROM_DIR && router_digest_is_me(ns->identity_digest)) {
/* Drop our own networkstatus when we get it from somebody else. */
/* Don't replace our own networkstatus when we get it from somebody else. */
networkstatus_free(ns);
return 0;
}
base16_encode(fp, HEX_DIGEST_LEN+1, ns->identity_digest, DIGEST_LEN);
if (requested_fingerprints &&
!smartlist_string_isin(requested_fingerprints, fp)) {
char *requested = smartlist_join_strings(requested_fingerprints," ",0,NULL);
log_fn(LOG_WARN, "We received a network status with a fingerprint (%s) that we never requested. (%s) Dropping.", fp, requested);
tor_free(requested);
return 0;
if (requested_fingerprints) {
if (smartlist_string_isin(requested_fingerprints, fp)) {
smartlist_string_remove(requested_fingerprints, fp);
} else {
char *requested = smartlist_join_strings(requested_fingerprints," ",0,NULL);
log_fn(LOG_WARN, "We received a network status with a fingerprint (%s) that we never requested. (%s) Dropping.", fp, requested);
tor_free(requested);
return 0;
}
}
if (source != NS_FROM_CACHE)
trusted_dir->n_networkstatus_failures = 0;
found = 0;
for (i=0; i < smartlist_len(networkstatus_list); ++i) {
networkstatus_t *old_ns = smartlist_get(networkstatus_list, i);
@@ -1281,11 +1317,10 @@ router_set_networkstatus(const char *s, time_t arrived_at,
if (!found)
smartlist_add(networkstatus_list, ns);
smartlist_sort(networkstatus_list, _compare_networkstatus_published_on);
if (source != NS_FROM_CACHE && !skewed) {
const char *datadir = get_options()->DataDirectory;
size_t len = strlen(datadir)+64;
char *fn = tor_malloc(len+1);
tor_snprintf(fn, len, "%s/cached-status/%s",datadir,fp);
char *fn = networkstatus_get_cache_filename(ns);
if (write_str_to_file(fn, s, 0)<0) {
log_fn(LOG_NOTICE, "Couldn't write cached network status to \"%s\"", fn);
}
@@ -1298,6 +1333,56 @@ router_set_networkstatus(const char *s, time_t arrived_at,
return 0;
}
#define MAX_NETWORKSTATUS_AGE (10*24*60*60)
/** DOCDOC */
void
networkstatus_list_clean(time_t now)
{
int i;
if (!networkstatus_list)
return;
for (i = 0; i < smartlist_len(networkstatus_list); ++i) {
networkstatus_t *ns = smartlist_get(networkstatus_list, i);
char *fname = NULL;;
if (ns->published_on + MAX_NETWORKSTATUS_AGE > now)
continue;
/* Okay, this one is too old. Remove it from the list, and delete it
* from the cache. */
smartlist_del(networkstatus_list, i--);
fname = networkstatus_get_cache_filename(ns);
if (file_status(fname) == FN_FILE) {
log_fn(LOG_INFO, "Removing too-old networkstatus in %s", fname);
unlink(fname);
}
tor_free(fname);
if (get_options()->DirPort) {
char fp[HEX_DIGEST_LEN+1];
base16_encode(fp, sizeof(fp), ns->identity_digest, DIGEST_LEN);
dirserv_set_cached_networkstatus_v2(NULL, fp, 0);
}
networkstatus_free(ns);
}
}
/** Helper for bsearching a list of routerstatus_t pointers.*/
static int
_compare_digest_to_routerstatus_entry(const void *_key, const void **_member)
{
const char *key = _key;
const routerstatus_t *rs = *_member;
return memcmp(key, rs->identity_digest, DIGEST_LEN);
}
/** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or
* NULL if none was found. */
routerstatus_t *
networkstatus_find_entry(networkstatus_t *ns, const char *digest)
{
return smartlist_bsearch(ns->entries, digest,
_compare_digest_to_routerstatus_entry);
}
/* XXXX These should be configurable, perhaps? NM */
#define AUTHORITY_NS_CACHE_INTERVAL 10*60
#define NONAUTHORITY_NS_CACHE_INTERVAL 15*60
@@ -1324,6 +1409,10 @@ update_networkstatus_cache_downloads(time_t now)
char resource[HEX_DIGEST_LEN+6];
if (router_digest_is_me(ds->digest))
continue;
if (connection_get_by_type_addr_port_purpose(
CONN_TYPE_DIR, ds->addr, ds->dir_port,
DIR_PURPOSE_FETCH_NETWORKSTATUS))
continue;
strlcpy(resource, "fp/", sizeof(resource));
base16_encode(resource+3, sizeof(resource)-3, ds->digest, DIGEST_LEN);
strlcat(resource, ".z", sizeof(resource));
@@ -1331,7 +1420,9 @@ update_networkstatus_cache_downloads(time_t now)
});
} else {
/* A non-authority cache launches one connection to a random authority. */
directory_get_from_dirserver(DIR_PURPOSE_FETCH_NETWORKSTATUS,"all.z",1);
if (!connection_get_by_type_purpose(CONN_TYPE_DIR,
DIR_PURPOSE_FETCH_NETWORKSTATUS))
directory_get_from_dirserver(DIR_PURPOSE_FETCH_NETWORKSTATUS,"all.z",1);
}
}
@@ -1353,10 +1444,15 @@ update_networkstatus_client_downloads(time_t now)
char *resource, *cp;
size_t resource_len;
if (connection_get_by_type_purpose(CONN_TYPE_DIR,
DIR_PURPOSE_FETCH_NETWORKSTATUS))
return;
/* This is a little tricky. We want to download enough network-status
* objects so that we have at least half of them under NETWORKSTATUS_MAX_VALIDITY
* publication time. We want to download a new *one* if the most recent
* one's publication time is under NETWORKSTATUS_CLIENT_DL_INTERVAL.
* objects so that we have at least half of them under
* NETWORKSTATUS_MAX_VALIDITY publication time. We want to download a new
* *one* if the most recent one's publication time is under
* NETWORKSTATUS_CLIENT_DL_INTERVAL.
*/
if (!trusted_dir_servers || !smartlist_len(trusted_dir_servers))
return;
@@ -1707,7 +1803,7 @@ routers_update_status_from_entry(smartlist_t *routers,
{
int authdir = get_options()->AuthoritativeDir;
int is_running = 1;
int is_verified = 0;
int is_verified = 0, is_named = 0;
int hex_digest_set = 0;
char nickname[MAX_NICKNAME_LEN+1];
char hexdigest[HEX_DIGEST_LEN+1];
@@ -1726,6 +1822,7 @@ routers_update_status_from_entry(smartlist_t *routers,
* entry will either extend to a NUL (old running-routers format) or to an
* equals sign (new router-status format). */
is_verified = 1;
is_named = 1;
end = strchr(cp, '=');
if (!end)
end = strchr(cp,'\0');
@@ -1781,9 +1878,9 @@ routers_update_status_from_entry(smartlist_t *routers,
/* If we're not an authoritative directory, update verified status.
*/
if (nickname_matches && digest_matches)
r->is_verified = 1;
r->is_verified = r->is_named = 1;
else if (digest_matches)
r->is_verified = 0;
r->is_verified = r->is_named = 0;
}
if (digest_matches)
if (r->status_set_at < list_time) {
@@ -1878,3 +1975,111 @@ networkstatus_get_by_digest(const char *digest)
return NULL;
}
#define DEFAULT_RUNNING_INTERVAL 60*60
#define MIN_TO_INFLUENCE_RUNNING 3
void
routers_update_status_from_networkstatus(smartlist_t *routers)
{
int n_trusted, n_statuses, n_valid, n_naming, n_named, n_running, n_listing,
n_recent;
int i;
time_t now = time(NULL);
if (authdir_mode(get_options())) {
/* An authoritative directory should never believer someone else about
* a server's status. */
return;
}
if (!networkstatus_list)
networkstatus_list = smartlist_create();
if (!trusted_dir_servers)
trusted_dir_servers = smartlist_create();
n_trusted = smartlist_len(trusted_dir_servers);
n_statuses = smartlist_len(networkstatus_list);
if (n_statuses < (n_trusted/2)+1) {
/* Not enough statuses to adjust status. */
return;
}
if (n_statuses < MIN_TO_INFLUENCE_RUNNING) {
n_recent = n_statuses;
} else {
n_recent = 0;
for (i=smartlist_len(networkstatus_list)-1; i >= 0; --i) {
networkstatus_t *ns = smartlist_get(networkstatus_list, i);
if (ns->published_on + DEFAULT_RUNNING_INTERVAL < now)
break;
++n_recent;
}
if (n_recent < MIN_TO_INFLUENCE_RUNNING) {
n_recent = MIN_TO_INFLUENCE_RUNNING;
}
}
SMARTLIST_FOREACH(routers, routerinfo_t *, router,
{
n_listing = n_valid = n_naming = n_named = n_running = 0;
SMARTLIST_FOREACH(networkstatus_list, networkstatus_t *, ns,
{
routerstatus_t *rs = networkstatus_find_entry(ns, router->identity_digest);
if (ns->binds_names)
++n_naming;
if (!rs)
continue;
++n_listing;
if (rs->is_named && !strcasecmp(rs->nickname, router->nickname))
++n_named;
if (rs->is_valid)
++n_valid;
if (ns_sl_idx >= smartlist_len(networkstatus_list)-n_recent) {
if (rs->is_running)
++n_running;
}
});
log_fn(LOG_DEBUG, "Router '%s' at %s:%d is listed by %d/%d directories, "
"named by %d/%d, validated by %d/%d, and %d/%d recent directories "
"think it's running.",
router->nickname, router->address, router->or_port,
n_listing, n_statuses, n_named, n_naming, n_valid, n_statuses,
n_running, n_recent);
router->is_named = (n_named > n_naming/2);
router->is_verified = (n_valid > n_statuses/2);
router->is_running = (n_running > n_recent/2);
});
}
/** Return new list of ID digests for superseded routers.
* A router is superseded if any network-status has a router with a different
* digest published more recently.
*/
smartlist_t *
router_list_superseded(void)
{
smartlist_t *superseded = smartlist_create();
if (!routerlist || !networkstatus_list)
return superseded;
SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, ri,
{
SMARTLIST_FOREACH(networkstatus_list, networkstatus_t *, ns,
{
routerstatus_t *rs = networkstatus_find_entry(ns, ri->identity_digest);
if (memcmp(ri->signed_descriptor_digest,rs->descriptor_digest,DIGEST_LEN)
&& rs->published_on > ri->published_on) {
char *d = tor_malloc(DIGEST_LEN);
memcpy(d, ri->identity_digest, DIGEST_LEN);
smartlist_add(superseded, d);
break;
}
});
});
return superseded;
}
+10 -1
View File
@@ -849,7 +849,7 @@ router_parse_list_from_string(const char **s, routerlist_t **dest,
if (!good_nickname_list) {
router->is_running = 1; /* start out assuming all dirservers are up */
router->is_verified = 1;
router->is_verified = router->is_named = 1;
router->status_set_at = time(NULL);
}
smartlist_add(routers, router);
@@ -1245,6 +1245,14 @@ routerstatus_parse_entry_from_string(const char **s, smartlist_t *tokens)
return rs;
}
/** Helper to sort a smartlist of pointers to routerstatus_t */
static int
_compare_routerstatus_entries(const void **_a, const void **_b)
{
const routerstatus_t *a = *_a, *b = *_b;
return memcmp(a->identity_digest, b->identity_digest, DIGEST_LEN);
}
/** Given a versioned (v2 or later) network-status object in <b>s</b>, try to
* parse it and return the result. Return NULL on failure. Check the
* signature of the network status, but do not (yet) check the signing key for
@@ -1387,6 +1395,7 @@ networkstatus_parse_from_string(const char *s)
if ((rs = routerstatus_parse_entry_from_string(&s, tokens)))
smartlist_add(ns->entries, rs);
}
smartlist_sort(ns->entries, _compare_routerstatus_entries);
if (tokenize_string(s, NULL, tokens, NETSTATUS)) {
log_fn(LOG_WARN, "Error tokenizing network-status footer.");