diff --git a/src/or/config.c b/src/or/config.c index d1a229976b..fd9c2822fb 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -191,6 +191,8 @@ static config_var_t _option_vars[] = { VAR("UseHelperNodes", BOOL, UseHelperNodes, "0"), VAR("User", STRING, User, NULL), VAR("V1AuthoritativeDirectory",BOOL, V1AuthoritativeDir, "0"), + /* XXXX 011 change this default on 0.1.1.x */ + VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "1"), VAR("__LeaveStreamsUnattached", BOOL,LeaveStreamsUnattached, "0"), { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL, NULL } }; diff --git a/src/or/dirserv.c b/src/or/dirserv.c index db79102d88..9dd537c56a 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -1149,6 +1149,7 @@ generate_v2_networkstatus(void) smartlist_t *descriptor_list = get_descriptor_list(); time_t now = time(NULL); int naming = options->NamingAuthoritativeDir; + int versioning = options->VersioningAuthoritativeDir; const char *contact; if (!descriptor_list) { @@ -1193,7 +1194,7 @@ generate_v2_networkstatus(void) "fingerprint %s\n" "contact %s\n" "published %s\n" - "dir-options%s\n" + "dir-options%s%s\n" "client-versions %s\n" "server-versions %s\n" "dir-signing-key\n%s\n", @@ -1202,6 +1203,7 @@ generate_v2_networkstatus(void) contact, published, naming ? " Names" : "", + versioning ? " Versions" : "", client_versions, server_versions, identity_pkey); diff --git a/src/or/or.h b/src/or/or.h index 258941c3cc..f5d4b41229 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -816,7 +816,8 @@ typedef struct networkstatus_t { /** What was the digest of the document? */ char networkstatus_digest[DIGEST_LEN]; - int is_recent; /** Is this recent enough to influence running status? */ + unsigned int is_recent; /**< Is this recent enough to influence running + * status? */ /* These fields come from the actual network-status document.*/ time_t published_on; /**< Declared publication date. */ @@ -833,7 +834,10 @@ typedef struct networkstatus_t { char *server_versions; /**< comma-separated list of recommended server * versions. */ - int binds_names:1; /**< True iff this directory server binds names. */ + unsigned int binds_names:1; /**< True iff this directory server binds names. */ + unsigned int recommends_versions:1; /**< True iff this directory server + * recommends client and server software + * versions. */ smartlist_t *entries; /**< List of router_status_t*. This list is kept * sorted by identity_digest. */ @@ -1175,6 +1179,8 @@ typedef struct { * for version 1 directories? */ int NamingAuthoritativeDir; /**< Boolean: is this an authoritative directory * that's willing to bind names? */ + int VersioningAuthoritativeDir; /**< Boolean: is this an authoritative directory + * that's willing to recommend versions? */ int ClientOnly; /**< Boolean: should we never evolve into a server role? */ int NoPublish; /**< Boolean: should we never publish a descriptor? */ int ConnLimit; /**< Requested maximum number of simultaneous connections. */ @@ -2143,6 +2149,15 @@ typedef struct tor_version_t { char status_tag[MAX_STATUS_TAG_LEN]; } tor_version_t; +typedef enum version_status_t { + VS_RECOMMENDED=0, /**< This version is listed as recommended. */ + VS_OLD=1, /**< This version is older than any recommended version. */ + VS_NEW=2, /**< This version is newer than any recommended version. */ + VS_NEW_IN_SERIES=3, /**< This version is newer than any recommended version + * in its series, and such recommended versions exist. */ + VS_UNRECOMMENDED=4 /**< This version is not recommended (general case) */ +} version_status_t; + int router_get_router_hash(const char *s, char *digest); int router_get_dir_hash(const char *s, char *digest); int router_get_runningrouters_hash(const char *s, char *digest); @@ -2162,6 +2177,9 @@ routerinfo_t *router_parse_entry_from_string(const char *s, const char *end); int router_add_exit_policy_from_string(routerinfo_t *router, const char *s); addr_policy_t *router_parse_addr_policy_from_string(const char *s, int assume_action); +version_status_t tor_version_is_obsolete(const char *myversion, + const char *versionlist); +version_status_t version_status_join(version_status_t a, version_status_t b); int tor_version_parse(const char *s, tor_version_t *out); int tor_version_as_new_as(const char *platform, const char *cutoff); int tor_version_compare(tor_version_t *a, tor_version_t *b); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 49445135c0..2e65c35523 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1983,7 +1983,10 @@ networkstatus_get_by_digest(const char *digest) void routers_update_all_from_networkstatus(void) { +#define SELF_OPINION_INTERVAL 90*60 static int have_warned_about_unverified_status = 0; + static int have_warned_about_old_version = 0; + static int have_warned_about_new_version = 0; routerinfo_t *me; time_t now; if (!routerlist || !networkstatus_list || @@ -1997,18 +2000,33 @@ routers_update_all_from_networkstatus(void) routers_update_status_from_networkstatus(routerlist->routers, 0); me = router_get_my_routerinfo(); - if (me) { - /* We could be more sophisticated about this whole business. How many - * dirservers list us as named, valid, etc. */ - smartlist_t *lst = smartlist_create(); - smartlist_add(lst, me); - routers_update_status_from_networkstatus(lst, 1); - if (me->is_verified == 0) { - log_fn(LOG_WARN, "Many directory servers list us as unverified. Please consider sending your identity fingerprint to the tor-ops."); - have_warned_about_unverified_status = 1; - } else if (me->is_named == 0) { - log_fn(LOG_WARN, "Many directory servers list us as unnamed. Please consider sending your identity fingerprint to the tor-ops."); - have_warned_about_unverified_status = 1; + if (me && !have_warned_about_unverified_status) { + int n_recent = 0, n_listing = 0, n_valid = 0, n_named = 0; + routerstatus_t *rs; + SMARTLIST_FOREACH(networkstatus_list, networkstatus_t *, ns, + { + if (ns->received_on + SELF_OPINION_INTERVAL < now) + continue; + ++n_recent; + if (!(rs = networkstatus_find_entry(ns, me->identity_digest))) + continue; + ++n_listing; + if (rs->is_valid) + ++n_valid; + if (rs->is_named) + ++n_named; + }); + + if (n_recent >= 2 && n_listing >= 2) { + if (n_valid <= n_recent/2) { + log_fn(LOG_WARN, "%d/%d recent directory servers list us as invalid. Please consider sending your identity fingerprint to the tor-ops.", + n_recent-n_valid, n_recent); + have_warned_about_unverified_status = 1; + } else if (n_named <= n_recent/2) { + log_fn(LOG_WARN, "%d/%d recent directory servers list us as unnamed. Please consider sending your identity fingerprint to the tor-ops.", + n_recent-n_valid, n_recent); + have_warned_about_unverified_status = 1; + } } } @@ -2016,6 +2034,47 @@ routers_update_all_from_networkstatus(void) update_router_descriptor_downloads(time(NULL)); + if (!have_warned_about_old_version) { + int n_recent = 0; + int n_recommended = 0; + int is_server = server_mode(get_options()); + version_status_t consensus = VS_RECOMMENDED; + SMARTLIST_FOREACH(networkstatus_list, networkstatus_t *, ns, + { + version_status_t vs; + if (ns->received_on + SELF_OPINION_INTERVAL < now ) + // XXXX NM enable this! || !ns->recommends_versions) + continue; + vs = tor_version_is_obsolete( + VERSION, is_server ? ns->server_versions : ns->client_versions); + if (vs == VS_RECOMMENDED) + ++n_recommended; + if (n_recent++ == 0) { + consensus = vs; + } else if (consensus != vs) { + consensus = version_status_join(consensus, vs); + } + }); + if (n_recent > 2 && n_recommended < n_recent/2) { + if (consensus == VS_NEW || consensus == VS_NEW_IN_SERIES) { + if (!have_warned_about_new_version) { + log_fn(LOG_NOTICE, "This version of Tor (%s) is newer than any recommended version%s, according to %d/%d recent network statuses.", + VERSION, consensus == VS_NEW_IN_SERIES ? " in its series" : "", + n_recent-n_recommended, n_recent); + have_warned_about_new_version = 1; + } + } else { + log_fn(LOG_NOTICE, "This version of Tor (%s) is %s, according to %d/%d recent network statuses.", + VERSION, consensus == VS_OLD ? "obsolete" : "not recommended", + n_recent-n_recommended, n_recent); + have_warned_about_old_version = 1; + } + } else { + log_fn(LOG_NOTICE, "%d/%d recent directories think my version is ok.", + n_recommended, n_recent); + } + } + routerstatus_list_has_changed = 0; } diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 9c9196abf0..fe52930126 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -166,8 +166,6 @@ static int check_directory_signature(const char *digest, crypto_pk_env_t *declared_key, int check_authority); static crypto_pk_env_t *find_dir_signing_key(const char *str); -/* static */ int is_obsolete_version(const char *myversion, - const char *versionlist); static int tor_version_same_series(tor_version_t *a, tor_version_t *b); /** Set digest to the SHA-1 digest of the hash of the directory in @@ -258,14 +256,17 @@ router_append_dirobj_signature(char *buf, size_t buf_len, const char *digest, * Otherwise return 0. * (versionlist is a comma-separated list of version strings, * optionally prefixed with "Tor". Versions that can't be parsed are - * ignored.) */ -/* static */ int -is_obsolete_version(const char *myversion, const char *versionlist) { + * ignored.) + * + * DOCDOC interface changed */ +version_status_t +tor_version_is_obsolete(const char *myversion, const char *versionlist) +{ const char *vl; tor_version_t mine, other; - int found_newer = 0, found_newer_in_series = 0, found_any_in_series = 0, - r, ret, same; - static int warned_too_new=0; + int found_newer = 0, found_older = 0, found_newer_in_series = 0, + found_any_in_series = 0, r, same; + version_status_t ret = VS_UNRECOMMENDED; smartlist_t *version_sl; vl = versionlist; @@ -292,54 +293,28 @@ is_obsolete_version(const char *myversion, const char *versionlist) { found_any_in_series = 1; r = tor_version_compare(&mine, &other); if (r==0) { - ret = 0; + ret = VS_RECOMMENDED; goto done; } else if (r<0) { found_newer = 1; if (same) found_newer_in_series = 1; + } else if (r>0) { + found_older = 1; } } }); /* We didn't find the listed version. Is it new or old? */ - - if (found_any_in_series) { - if (!found_newer_in_series) { - /* We belong to a series with recommended members, and we are newer than - * any recommended member. We're probably okay. */ - if (!warned_too_new) { - log(LOG_WARN, "This version of Tor (%s) is newer than any in the same series on the recommended list (%s).", - myversion, versionlist); - warned_too_new = 1; - } - ret = 0; - } else { - /* We found a newer one in the same series; we're obsolete. */ - ret = 1; - } + if (found_any_in_series && !found_newer_in_series) { + ret = VS_NEW_IN_SERIES; + } else if (found_newer && !found_older) { + ret = VS_OLD; + } else if (found_older && !found_newer) { + ret = VS_NEW; } else { - if (found_newer) { - /* We belong to a series with no recommended members, and - * a newer series is recommended. We're obsolete. */ - ret = 1; - } else { - /* We belong to a series with no recommended members, and it's - * newer than any recommended series. We're probably okay. */ - if (!warned_too_new) { - log(LOG_WARN, "This version of Tor (%s) is newer than any on the recommended list (%s).", - myversion, versionlist); - warned_too_new = 1; - } - ret = 0; - } + ret = VS_UNRECOMMENDED; } - /* - log_fn(LOG_DEBUG, - "Decided that %s is %sobsolete relative to %s: %d, %d, %d\n", - myversion, ret?"":"not ", versionlist, found_newer, - found_any_in_series, found_newer_in_series); - */ done: SMARTLIST_FOREACH(version_sl, char *, version, tor_free(version)); @@ -347,29 +322,25 @@ is_obsolete_version(const char *myversion, const char *versionlist) { return ret; } -#if 0 -/* Return 0 if myversion is supported; else warn and return -1. */ -int -check_software_version_against_directory(const char *directory) +/** DOCDOC */ +version_status_t +version_status_join(version_status_t a, version_status_t b) { - char *v; - v = get_recommended_software_from_directory(directory); - if (!v) { - log_fn(LOG_WARN, "No recommended-versions string found in directory"); - return -1; - } - if (!is_obsolete_version(VERSION, v)) { - tor_free(v); - return 0; - } - log(LOG_WARN, - "You are running Tor version %s, which will not work with this network.\n" - "Please use %s%s.", - VERSION, strchr(v,',') ? "one of " : "", v); - tor_free(v); - return -1; + if (a == b) + return a; + else if (a == VS_UNRECOMMENDED || b == VS_UNRECOMMENDED) + return VS_UNRECOMMENDED; + else if (a == VS_RECOMMENDED) + return b; + else if (b == VS_RECOMMENDED) + return a; + /* Okay. Neither is 'recommended' or 'unrecommended', and they differ. */ + else if (a == VS_OLD || b == VS_OLD) + return VS_UNRECOMMENDED; + /* One is VS_NEW, the other is VS_NEW_IN_SERIES */ + else + return VS_NEW_IN_SERIES; } -#endif /** Read a signed directory from str. If it's well-formed, return 0. * Otherwise, return -1. If we're a directory cache, cache it. @@ -1164,25 +1135,31 @@ networkstatus_parse_from_string(const char *s) goto err; } - if (!(tok = find_first_by_keyword(tokens, K_CLIENT_VERSIONS)) || tok->n_args<1) { - log_fn(LOG_WARN, "Missing client-versions"); - goto err; + if ((tok = find_first_by_keyword(tokens, K_DIR_OPTIONS))) { + for (i=0; i < tok->n_args; ++i) { + if (!strcmp(tok->args[i], "Names")) + ns->binds_names = 1; + if (!strcmp(tok->args[i], "Versions")) + ns->recommends_versions = 1; + } } - ns->client_versions = tok->args[0]; - if (!(tok = find_first_by_keyword(tokens, K_CLIENT_VERSIONS)) || tok->n_args<1) { - log_fn(LOG_WARN, "Missing client-versions"); - goto err; - } - ns->client_versions = tok->args[0]; - tok->args[0] = NULL; + if (ns->recommends_versions || 1) { //XXXX NM re-enable conditional. + if (!(tok = find_first_by_keyword(tokens, K_CLIENT_VERSIONS)) || + tok->n_args<1) { + log_fn(LOG_WARN, "Missing client-versions"); + } + ns->client_versions = tok->args[0]; + tok->args[0] = NULL; - if (!(tok = find_first_by_keyword(tokens, K_SERVER_VERSIONS)) || tok->n_args<1) { - log_fn(LOG_WARN, "Missing server-versions"); - goto err; + if (!(tok = find_first_by_keyword(tokens, K_SERVER_VERSIONS)) || + tok->n_args<1) { + log_fn(LOG_WARN, "Missing server-versions on versioning directory"); + goto err; + } + ns->server_versions = tok->args[0]; + tok->args[0] = NULL; } - ns->server_versions = tok->args[0]; - tok->args[0] = NULL; if (!(tok = find_first_by_keyword(tokens, K_PUBLISHED))) { log_fn(LOG_WARN, "Missing published time on directory."); @@ -1193,13 +1170,6 @@ networkstatus_parse_from_string(const char *s) goto err; } - if ((tok = find_first_by_keyword(tokens, K_DIR_OPTIONS))) { - for (i=0; i < tok->n_args; ++i) { - if (!strcmp(tok->args[i], "Names")) - ns->binds_names = 1; - } - } - ns->entries = smartlist_create(); s = eos; SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t)); diff --git a/src/or/test.c b/src/or/test.c index 5007589eba..560bf6ed08 100644 --- a/src/or/test.c +++ b/src/or/test.c @@ -32,7 +32,6 @@ int have_failed = 0; void add_fingerprint_to_dir(const char *nickname, const char *fp, smartlist_t *list); void get_platform_str(char *platform, size_t len); -int is_obsolete_version(const char *myversion, const char *start); size_t read_escaped_data(const char *data, size_t len, int translate_newlines, char **out); or_options_t *options_new(void); @@ -1368,28 +1367,35 @@ test_dir_format(void) test_eq(IS_NOT_CVS, ver1.cvs); test_streq("", ver1.status_tag); - /* make sure is_obsolete_version() works */ - test_eq(1, is_obsolete_version("0.0.1", "Tor 0.0.2")); - test_eq(1, is_obsolete_version("0.0.1", "0.0.2, Tor 0.0.3")); - test_eq(1, is_obsolete_version("0.0.1", "0.0.2,Tor 0.0.3")); - test_eq(1, is_obsolete_version("0.0.1", "0.0.3,BetterTor 0.0.1")); - test_eq(0, is_obsolete_version("0.0.2", "Tor 0.0.2,Tor 0.0.3")); - test_eq(0, is_obsolete_version("0.0.2", "Tor 0.0.2pre1,Tor 0.0.3")); - test_eq(1, is_obsolete_version("0.0.2", "Tor 0.0.2.1,Tor 0.0.3")); - test_eq(0, is_obsolete_version("0.1.0", "Tor 0.0.2,Tor 0.0.3")); - test_eq(0, is_obsolete_version("0.0.7rc2", "0.0.7,Tor 0.0.7rc2,Tor 0.0.8")); - test_eq(0, is_obsolete_version("0.0.5", "0.0.5-cvs")); - test_eq(0, is_obsolete_version("0.0.5.1-cvs", "0.0.5")); + /* make sure tor_version_is_obsolete() works */ + test_eq(VS_OLD, tor_version_is_obsolete("0.0.1", "Tor 0.0.2")); + test_eq(VS_OLD, tor_version_is_obsolete("0.0.1", "0.0.2, Tor 0.0.3")); + test_eq(VS_OLD, tor_version_is_obsolete("0.0.1", "0.0.2,Tor 0.0.3")); + test_eq(VS_OLD, tor_version_is_obsolete("0.0.1", "0.0.3,BetterTor 0.0.1")); + test_eq(VS_RECOMMENDED,tor_version_is_obsolete("0.0.2", "Tor 0.0.2,Tor 0.0.3")); + test_eq(VS_NEW_IN_SERIES, + tor_version_is_obsolete("0.0.2", "Tor 0.0.2pre1,Tor 0.0.3")); + test_eq(VS_OLD, tor_version_is_obsolete("0.0.2", "Tor 0.0.2.1,Tor 0.0.3")); + test_eq(VS_NEW, tor_version_is_obsolete("0.1.0", "Tor 0.0.2,Tor 0.0.3")); + test_eq(VS_RECOMMENDED, + tor_version_is_obsolete("0.0.7rc2", "0.0.7,Tor 0.0.7rc2,Tor 0.0.8")); + test_eq(VS_OLD, tor_version_is_obsolete("0.0.5.0", "0.0.5.1-cvs")); + test_eq(VS_NEW_IN_SERIES, tor_version_is_obsolete("0.0.5.1-cvs", "0.0.5")); /* Not on list, but newer than any in same series. */ - test_eq(0, is_obsolete_version("0.1.0.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); + test_eq(VS_NEW_IN_SERIES, + tor_version_is_obsolete("0.1.0.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); /* Series newer than any on list. */ - test_eq(0, is_obsolete_version("0.1.1.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); + test_eq(VS_NEW, + tor_version_is_obsolete("0.1.2.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); /* Series older than any on list. */ - test_eq(1, is_obsolete_version("0.0.1.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); + test_eq(VS_OLD, + tor_version_is_obsolete("0.0.1.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); /* Not on list, not newer than any on same series. */ - test_eq(1, is_obsolete_version("0.1.0.1", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); + test_eq(VS_UNRECOMMENDED, + tor_version_is_obsolete("0.1.0.1", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); /* On list, not newer than any on same series. */ - test_eq(1, is_obsolete_version("0.1.0.1", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); + test_eq(VS_UNRECOMMENDED, + tor_version_is_obsolete("0.1.0.1", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0")); test_eq(0, tor_version_as_new_as("Tor 0.0.5", "0.0.9pre1-cvs")); test_eq(1, tor_version_as_new_as(