diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..a535741e9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: c +branches: + only: + - prop259 +compiler: + - clang + - gcc +install: ./autogen.sh +script: + - ./configure --enable-prop259 --enable-coverage --disable-asciidoc --enable-gcc-warnings + - make && make check + - make check-spaces diff --git a/README b/README.md similarity index 87% rename from README rename to README.md index d246a6930..651023fea 100644 --- a/README +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/twstrike/tor_for_patching.svg?branch=master)](https://travis-ci.org/twstrike/tor_for_patching) + Tor protects your privacy on the internet by hiding the connection between your Internet address and the services you use. We believe Tor is reasonably secure, but please ensure you read the instructions and diff --git a/configure.ac b/configure.ac index df4b9cdc0..bcd7907d7 100644 --- a/configure.ac +++ b/configure.ac @@ -106,6 +106,13 @@ AC_ARG_ENABLE(systemd, esac], [systemd=auto]) +AC_ARG_ENABLE(prop259, + AS_HELP_STRING(--enable-prop259, [enable proposal 259]), + [case "${enableval}" in + "yes") prop259=true ;; + "no") prop259=false ;; + * ) AC_MSG_ERROR(bad value for --enable-prop259) ;; + esac], [prop259=auto]) # systemd support if test "x$enable_systemd" = "xno"; then @@ -1101,6 +1108,10 @@ if test "x$transparent" = "xtrue"; then fi fi +if test "x$prop259" = "xtrue"; then + AC_DEFINE(USE_PROP_259, 1, "Define to enable prop259 support") +fi + AC_CHECK_MEMBERS([struct timeval.tv_sec], , , [#ifdef HAVE_SYS_TYPES_H #include diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index a15f4c28b..647c97390 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -46,6 +46,7 @@ #include "routerparse.h" #include "routerset.h" #include "crypto.h" +#include "prop259.h" #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) @@ -2420,7 +2421,8 @@ build_state_get_exit_nickname(cpath_build_state_t *state) } /** Return true iff the given address can be used to extend to. */ -int extend_info_addr_is_allowed(const tor_addr_t *addr) +int +extend_info_addr_is_allowed(const tor_addr_t *addr) { tor_assert(addr); @@ -2434,3 +2436,4 @@ int extend_info_addr_is_allowed(const tor_addr_t *addr) disallow: return 0; } + diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 64fcc3005..8e85fd6a4 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -31,6 +31,7 @@ #include "rephist.h" #include "router.h" #include "routerlist.h" +#include "prop259.h" static void circuit_expire_old_circuits_clientside(void); static void circuit_increment_failure_count(void); diff --git a/src/or/config.c b/src/or/config.c index 4f4b9dfbf..d284d1016 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -49,6 +49,7 @@ #include "transports.h" #include "ext_orport.h" #include "torgzip.h" +#include "prop259.h" #ifdef _WIN32 #include #endif @@ -1759,6 +1760,7 @@ options_act(const or_options_t *old_options) "Changed to using entry guards or bridges, or changed " "preferred or excluded node lists. " "Abandoning previous circuits."); + guard_selection_fill_in_from_entrynodes(options); circuit_mark_all_unused_circs(); circuit_mark_all_dirty_circs_as_unusable(); revise_trackexithosts = 1; diff --git a/src/or/directory.c b/src/or/directory.c index e4feda44f..2a3d2240e 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -405,14 +405,17 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, if (should_use_directory_guards(options)) { const node_t *node = choose_random_dirguard(type); - if (node) + if (node) { + log_warn(LD_DIR, + "We got a guard for dir and will use it to connect dirserver."); rs = node->rs; + } } else { /* anybody with a non-zero dirport will do */ rs = router_pick_directory_server(type, pds_flags); } if (!rs) { - log_info(LD_DIR, "No router found for %s; falling back to " + log_warn(LD_DIR, "No router found for %s; falling back to " "dirserver list.", dir_conn_purpose_to_string(dir_purpose)); rs = router_pick_fallback_dirserver(type, pds_flags); } diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 3287fcd58..5ace309e2 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -36,6 +36,7 @@ #include "routerset.h" #include "transports.h" #include "statefile.h" +#include "prop259.h" /** Information about a configured bridge. Currently this just matches the * ones in the torrc file, but one day we may be able to learn about new @@ -212,7 +213,7 @@ entry_is_time_to_retry(const entry_guard_t *e, time_t now) * If need_descriptor is true, only return the node if we currently have * a descriptor (routerinfo or microdesc) for it. */ -STATIC const node_t * +const node_t * entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags, const char **msg) { @@ -306,11 +307,11 @@ num_live_entry_guards(int for_directory) entry_guard_t * entry_guard_get_by_id_digest(const char *digest) { - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, - if (tor_memeq(digest, entry->identity, DIGEST_LEN)) - return entry; - ); - return NULL; +#ifndef USE_PROP_259 + return guard_get_by_digest(digest, entry_guards); +#else + return used_guard_get_by_digest(digest); +#endif } /** Dump a description of our list of entry guards to the log at level @@ -376,7 +377,46 @@ control_event_guard_deferred(void) } /** Largest amount that we'll backdate chosen_on_date */ -#define CHOSEN_ON_DATE_SLOP (30*86400) +#define CHOSEN_ON_DATE_SLOP (3600*24*30) + +static time_t +entry_guard_chosen_on_date(const time_t now) +{ + /* Choose expiry time smudged over the past month. The goal here + * is to a) spread out when Tor clients rotate their guards, so they + * don't all select them on the same day, and b) avoid leaving a + * precise timestamp in the state file about when we first picked + * this guard. For details, see the Jan 2010 or-dev thread. */ + return crypto_rand_time_range(now - CHOSEN_ON_DATE_SLOP, now); +} + +entry_guard_t* +entry_guard_new(const node_t *node) +{ + entry_guard_t *entry = tor_malloc_zero(sizeof(entry_guard_t)); + strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); + memcpy(entry->identity, node->identity, DIGEST_LEN); + + entry->is_dir_cache = node_is_dir(node); + if (get_options()->UseBridges && node_is_a_configured_bridge(node)) + entry->is_dir_cache = 1; + + time_t now = time(NULL); + entry->chosen_on_date = entry_guard_chosen_on_date(now); + entry->chosen_by_version = tor_strdup(VERSION); + + return entry; +} + +/** Release all storage held by e. */ +void +entry_guard_free(entry_guard_t *e) +{ + if (!e) + return; + tor_free(e->chosen_by_version); + tor_free(e); +} /** Add a new (preferably stable and fast) router to our * entry_guards list. Return a pointer to the router if we succeed, @@ -429,23 +469,12 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, ((node_t*) node)->using_as_guard = 1; return NULL; } - entry = tor_malloc_zero(sizeof(entry_guard_t)); + + entry = entry_guard_new(node); + ((node_t*)node)->using_as_guard = 1; + log_info(LD_CIRC, "Chose %s as new entry guard.", node_describe(node)); - strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); - memcpy(entry->identity, node->identity, DIGEST_LEN); - entry->is_dir_cache = node_is_dir(node); - if (get_options()->UseBridges && node_is_a_configured_bridge(node)) - entry->is_dir_cache = 1; - - /* Choose expiry time smudged over the past month. The goal here - * is to a) spread out when Tor clients rotate their guards, so they - * don't all select them on the same day, and b) avoid leaving a - * precise timestamp in the state file about when we first picked - * this guard. For details, see the Jan 2010 or-dev thread. */ - time_t now = time(NULL); - entry->chosen_on_date = crypto_rand_time_range(now - 3600*24*30, now); - entry->chosen_by_version = tor_strdup(VERSION); /* Are we picking this guard because all of our current guards are * down so we need another one (for_discovery is 1), or because we @@ -458,11 +487,14 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, if (!for_discovery) entry->made_contact = 1; - ((node_t*)node)->using_as_guard = 1; + if (!entry_guards) + entry_guards = smartlist_new(); + if (prepend) smartlist_insert(entry_guards, 0, entry); else smartlist_add(entry_guards, entry); + control_event_guard(entry->nickname, entry->identity, "NEW"); control_event_guard_deferred(); log_entry_guards(LOG_INFO); @@ -472,7 +504,7 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, /** Choose how many entry guards or directory guards we'll use. If * for_directory is true, we return how many directory guards to * use; else we return how many entry guards to use. */ -STATIC int +int decide_num_guards(const or_options_t *options, int for_directory) { if (for_directory) { @@ -510,46 +542,6 @@ pick_entry_guards(const or_options_t *options, int for_directory) entry_guards_changed(); } -/** How long (in seconds) do we allow an entry guard to be nonfunctional, - * unlisted, excluded, or otherwise nonusable before we give up on it? */ -#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) - -/** Release all storage held by e. */ -static void -entry_guard_free(entry_guard_t *e) -{ - if (!e) - return; - tor_free(e->chosen_by_version); - tor_free(e); -} - -/** - * Return the minimum lifetime of working entry guard, in seconds, - * as given in the consensus networkstatus. (Plus CHOSEN_ON_DATE_SLOP, - * so that we can do the chosen_on_date randomization while achieving the - * desired minimum lifetime.) - */ -static int32_t -guards_get_lifetime(void) -{ - const or_options_t *options = get_options(); -#define DFLT_GUARD_LIFETIME (86400 * 60) /* Two months. */ -#define MIN_GUARD_LIFETIME (86400 * 30) /* One months. */ -#define MAX_GUARD_LIFETIME (86400 * 1826) /* Five years. */ - - if (options->GuardLifetime >= 1) { - return CLAMP(MIN_GUARD_LIFETIME, - options->GuardLifetime, - MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; - } - - return networkstatus_get_param(NULL, "GuardLifetime", - DFLT_GUARD_LIFETIME, - MIN_GUARD_LIFETIME, - MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; -} - /** Remove any entry guard which was selected by an unknown version of Tor, * or which was selected by a version of Tor that's known to select * entry guards badly, or which was selected more 2 months ago. */ @@ -558,44 +550,7 @@ guards_get_lifetime(void) static int remove_obsolete_entry_guards(time_t now) { - int changed = 0, i; - int32_t guard_lifetime = guards_get_lifetime(); - - for (i = 0; i < smartlist_len(entry_guards); ++i) { - entry_guard_t *entry = smartlist_get(entry_guards, i); - const char *ver = entry->chosen_by_version; - const char *msg = NULL; - tor_version_t v; - int version_is_bad = 0, date_is_bad = 0; - if (!ver) { - msg = "does not say what version of Tor it was selected by"; - version_is_bad = 1; - } else if (tor_version_parse(ver, &v)) { - msg = "does not seem to be from any recognized version of Tor"; - version_is_bad = 1; - } - if (!version_is_bad && entry->chosen_on_date + guard_lifetime < now) { - /* It's been too long since the date listed in our state file. */ - msg = "was selected several months ago"; - date_is_bad = 1; - } - - if (version_is_bad || date_is_bad) { /* we need to drop it */ - char dbuf[HEX_DIGEST_LEN+1]; - tor_assert(msg); - base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); - log_fn(version_is_bad ? LOG_NOTICE : LOG_INFO, LD_CIRC, - "Entry guard '%s' (%s) %s. (Version=%s.) Replacing it.", - entry->nickname, dbuf, msg, ver?escaped(ver):"none"); - control_event_guard(entry->nickname, entry->identity, "DROPPED"); - entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, i--); - log_entry_guards(LOG_INFO); - changed = 1; - } - } - - return changed ? 1 : 0; + return remove_obsolete_guards(now, entry_guards); } /** Remove all entry guards that have been down or unlisted for so @@ -604,31 +559,7 @@ remove_obsolete_entry_guards(time_t now) static int remove_dead_entry_guards(time_t now) { - char dbuf[HEX_DIGEST_LEN+1]; - char tbuf[ISO_TIME_LEN+1]; - int i; - int changed = 0; - - for (i = 0; i < smartlist_len(entry_guards); ) { - entry_guard_t *entry = smartlist_get(entry_guards, i); - if (entry->bad_since && - ! entry->path_bias_disabled && - entry->bad_since + ENTRY_GUARD_REMOVE_AFTER < now) { - - base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); - format_local_iso_time(tbuf, entry->bad_since); - log_info(LD_CIRC, "Entry guard '%s' (%s) has been down or unlisted " - "since %s local time; removing.", - entry->nickname, dbuf, tbuf); - control_event_guard(entry->nickname, entry->identity, "DROPPED"); - entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, i); - log_entry_guards(LOG_INFO); - changed = 1; - } else - ++i; - } - return changed ? 1 : 0; + return remove_dead_guards(now, entry_guards); } /** Remove all currently listed entry guards. So new ones will be chosen. */ @@ -729,6 +660,11 @@ int entry_guard_register_connect_status(const char *digest, int succeeded, int mark_relay_status, time_t now) { +#ifdef USE_PROP_259 + return guard_selection_register_connect_status(digest, succeeded, + mark_relay_status, now); +#endif + int changed = 0; int refuse_conn = 0; int first_contact = 0; @@ -751,53 +687,28 @@ entry_guard_register_connect_status(const char *digest, int succeeded, if (!entry) return 0; - base16_encode(buf, sizeof(buf), entry->identity, DIGEST_LEN); - - if (succeeded) { - if (entry->unreachable_since) { - log_info(LD_CIRC, "Entry guard '%s' (%s) is now reachable again. Good.", - entry->nickname, buf); - entry->can_retry = 0; - entry->unreachable_since = 0; - entry->last_attempted = now; - control_event_guard(entry->nickname, entry->identity, "UP"); - changed = 1; - } - if (!entry->made_contact) { - entry->made_contact = 1; - first_contact = changed = 1; - } - } else { /* ! succeeded */ - if (!entry->made_contact) { - /* We've never connected to this one. */ - log_info(LD_CIRC, - "Connection to never-contacted entry guard '%s' (%s) failed. " - "Removing from the list. %d/%d entry guards usable/new.", - entry->nickname, buf, - num_live_entry_guards(0)-1, smartlist_len(entry_guards)-1); - control_event_guard(entry->nickname, entry->identity, "DROPPED"); - entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, idx); - log_entry_guards(LOG_INFO); - changed = 1; - } else if (!entry->unreachable_since) { - log_info(LD_CIRC, "Unable to connect to entry guard '%s' (%s). " - "Marking as unreachable.", entry->nickname, buf); - entry->unreachable_since = entry->last_attempted = now; - control_event_guard(entry->nickname, entry->identity, "DOWN"); - changed = 1; - entry->can_retry = 0; /* We gave it an early chance; no good. */ - } else { - char tbuf[ISO_TIME_LEN+1]; - format_iso_time(tbuf, entry->unreachable_since); - log_debug(LD_CIRC, "Failed to connect to unreachable entry guard " - "'%s' (%s). It has been unreachable since %s.", - entry->nickname, buf, tbuf); - entry->last_attempted = now; - entry->can_retry = 0; /* We gave it an early chance; no good. */ - } + //Specific to the old behavior + if (!entry->made_contact) { + if (succeeded) { + first_contact = 1; + } else { + /* We've never connected to this one. */ + log_info(LD_CIRC, + "Connection to never-contacted entry guard '%s' (%s) failed. " + "Removing from the list. %d/%d entry guards usable/new.", + entry->nickname, buf, + num_live_entry_guards(0)-1, smartlist_len(entry_guards)-1); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del_keeporder(entry_guards, idx); + log_entry_guards(LOG_INFO); + changed = 1; + } } + changed = update_entry_guards_connection_status(entry, succeeded, now) + || changed; + /* if the caller asked us to, also update the is_running flags for this * relay */ if (mark_relay_status) @@ -835,6 +746,7 @@ entry_guard_register_connect_status(const char *digest, int succeeded, if (changed) entry_guards_changed(); + return refuse_conn ? -1 : 0; } @@ -986,7 +898,11 @@ entry_list_is_constrained(const or_options_t *options) const node_t * choose_random_entry(cpath_build_state_t *state) { +#ifdef USE_PROP_259 + return choose_random_entry_prop259(state, NULL); +#else return choose_random_entry_impl(state, 0, NO_DIRINFO, NULL); +#endif } /** Pick a live (up and listed) directory guard from entry_guards for @@ -994,7 +910,12 @@ choose_random_entry(cpath_build_state_t *state) const node_t * choose_random_dirguard(dirinfo_type_t type) { +#ifdef USE_PROP_259 + (void) type; + return choose_random_entry_prop259(NULL, NULL); +#else return choose_random_entry_impl(NULL, 1, type, NULL); +#endif } /** Filter all_entry_guards for usable entry guards and put them @@ -1445,7 +1366,7 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) } else { if (state_version) { time_t now = time(NULL); - e->chosen_on_date = crypto_rand_time_range(now - 3600*24*30, now); + e->chosen_on_date = entry_guard_chosen_on_date(now); e->chosen_by_version = tor_strdup(state_version); } } @@ -2348,7 +2269,12 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) fmt_and_decorate_addr(&bridge->addr), (int) bridge->port); } + //XXX maybe not useful after prop259 +#ifdef USE_PROP_259 + add_an_entry_bridge(node); +#else add_an_entry_guard(node, 1, 1, 0, 0); +#endif log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, from_cache ? "cached" : "fresh", router_describe(ri)); @@ -2373,7 +2299,11 @@ int any_bridge_descriptors_known(void) { tor_assert(get_options()->UseBridges); +#ifndef USE_PROP_259 return choose_random_entry(NULL) != NULL; +#else + return known_entry_bridge(); +#endif } /** Return the number of bridges that have descriptors that are marked with diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 59147d19b..994e9adcd 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -74,9 +74,26 @@ entry_guard_t *entry_guard_get_by_id_digest(const char *digest); void entry_guards_changed(void); const smartlist_t *get_entry_guards(void); int num_live_entry_guards(int for_directory); - +int decide_num_guards(const or_options_t *options, int for_directory); #endif +entry_guard_t* entry_guard_new(const node_t *node); + +void entry_guard_free(entry_guard_t *e); + +/** Flags to be passed to entry_is_live() to indicate what kind of + * entry nodes we are looking for. */ +typedef enum { + ENTRY_NEED_UPTIME = 1<<0, + ENTRY_NEED_CAPACITY = 1<<1, + ENTRY_ASSUME_REACHABLE = 1<<2, + ENTRY_NEED_DESCRIPTOR = 1<<3, +} entry_is_live_flags_t; + +const node_t *entry_is_live(const entry_guard_t *e, + entry_is_live_flags_t flags, + const char **msg); + #ifdef ENTRYNODES_PRIVATE STATIC const node_t *add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, @@ -88,23 +105,9 @@ STATIC int populate_live_entry_guards(smartlist_t *live_entry_guards, dirinfo_type_t dirinfo_type, int for_directory, int need_uptime, int need_capacity); -STATIC int decide_num_guards(const or_options_t *options, int for_directory); STATIC void entry_guards_set_from_config(const or_options_t *options); -/** Flags to be passed to entry_is_live() to indicate what kind of - * entry nodes we are looking for. */ -typedef enum { - ENTRY_NEED_UPTIME = 1<<0, - ENTRY_NEED_CAPACITY = 1<<1, - ENTRY_ASSUME_REACHABLE = 1<<2, - ENTRY_NEED_DESCRIPTOR = 1<<3, -} entry_is_live_flags_t; - -STATIC const node_t *entry_is_live(const entry_guard_t *e, - entry_is_live_flags_t flags, - const char **msg); - STATIC int entry_is_time_to_retry(const entry_guard_t *e, time_t now); #endif diff --git a/src/or/include.am b/src/or/include.am index 712ae1840..9a46bd148 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -52,6 +52,7 @@ LIBTOR_A_SOURCES = \ src/or/fp_pair.c \ src/or/geoip.c \ src/or/entrynodes.c \ + src/or/prop259.c \ src/or/ext_orport.c \ src/or/hibernate.c \ src/or/keypin.c \ @@ -160,6 +161,7 @@ ORHEADERS = \ src/or/fp_pair.h \ src/or/geoip.h \ src/or/entrynodes.h \ + src/or/prop259.h \ src/or/hibernate.h \ src/or/keypin.h \ src/or/main.h \ diff --git a/src/or/main.c b/src/or/main.c index d84e36512..623786ebc 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -67,6 +67,7 @@ #endif #include "memarea.h" #include "sandbox.h" +#include "prop259.h" #ifdef HAVE_EVENT2_EVENT_H #include @@ -1021,7 +1022,12 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs) /* if we have enough dir info, then update our guard status with * whatever we just learned. */ +#ifndef USE_PROP_259 entry_guards_compute_status(options, now); +#else + entry_guards_update_profiles(options, now); +#endif + /* Don't even bother trying to get extrainfo until the rest of our * directory info is up-to-date */ if (options->DownloadExtraInfo) diff --git a/src/or/or.h b/src/or/or.h index 4d145e45f..21fd3c120 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3087,6 +3087,7 @@ typedef struct origin_circuit_t { * length, the chosen exit router, rendezvous information, etc. */ cpath_build_state_t *build_state; + /** The doubly-linked list of crypt_path_t entries, one per hop, * for this circuit. This includes ciphers for each hop, * integrity-checking digests for each hop, and package/delivery @@ -4475,6 +4476,14 @@ typedef struct { /** Autobool: Do we try to retain capabilities if we can? */ int KeepBindCapabilities; + + /** How long (minutes) do we wanna to wait before to try to connect to a + * primary guard again? */ + int PrimaryGuardsRetryInterval; + + /** How long (minutes) do we wanna to wait before to try another guard to + * build circuit? */ + int InternetLikelyDownInterval; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ @@ -4500,6 +4509,8 @@ typedef struct { /** A list of Entry Guard-related configuration lines. */ config_line_t *EntryGuards; + config_line_t *UsedGuards; + config_line_t *SampledGuards; config_line_t *TransportProxies; diff --git a/src/or/prop259.c b/src/or/prop259.c new file mode 100644 index 000000000..befee5c2b --- /dev/null +++ b/src/or/prop259.c @@ -0,0 +1,2163 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file prop259.c + * \brief Implementation for proposal 259: New Guard Selection Behaviour. + * + * Entry nodes are selected as guards in such a way they require as less input + * from the user as possible and potentially exposing an OR to the smaller + * amount of said nodes. + * + * \details + * Each managed guard is represented by a entry_guard_t. + * Each managed guard list is constructed by a guardlist_t. + * + * ::entry_guard_selection guard_selection_t manage all states this tor + * instance knows about guards and selection algorithm. + * + * In every run_scheduled_event() tick, we attempt to select a guard + * using choose_random_entry to prepare entryguard to build new circuit. + * choose_random_entry_prop259() is just doing this according to different + * state. + * + * When entry_guard_selection is in STATE_INIT state, we init the algo with + * choose_entry_guard_algo_start(). + * When entry_guard_selection is other state, we do a selection with + * choose_entry_guard_algo_next() and dispatch two ways of selection: + * if entry_guard_selection is in STATE_PRIMARY_GUARDS state, choose from + * primary_guards. + * if entry_guard_selection is in STATE_TRY_REMAINING state, choose from + * used_guards, then remaining_guards in turn. + * + * In a little more detail: + * + * In choose_entry_guard_algo_start() we would initialize the algorithm + * by setting state as STATE_PRIMARY_GUARDS, and filling the primary_guards + * and remaining_guards with a filter function filter_sampled(). + * + * In filter_sampled function, the remaining_guard list will be filtered + * according to different options specified, if the list size gets too small + * smaller than MINIMUM_FILTERED_SAMPLE_SIZE after filter, it can be extended + * by next_node_by_bandwidth() until reaches MAXIMUM_SAMPLE_SIZE_THRESHOLD. + * + * When new consensus desc comes, sampled_guards and used_guards will be + * updated in entry_guards_update_profiles(). The number of sampled_guards + * is based on SAMPLE_SET_THRESHOLD, and these two lists will be persisted + * in state file during running. + **/ + +#define PROP259_PRIVATE + +//XXX Remove the global entry_guards. To do so, we need to replace: +//- get_entry_guards() +//- num_live_entry_guards() +//- getinfo_helper_entry_guards() +//- entries_retry_helper() +//- any_bridge_supports_microdescriptors() +//- update_node_guard_status (OK - called from *set_from_config and +// *parse_state. This is an optimization to set the using_as_guard flag.) +//- entry_guards_compute_status (OK - when a new consensus arrives) +//- log_entry_guards (OK - wont be used) +//- add_an_entry_guard (OK - wont be used) +//- entry_guard_register_connect_status (OK - wont be used) +//- entry_guards_set_from_config (OK - wont be used) +//- entry_guards_parse_state and entry_guards_update_state (OK - wont be used) +//- choose_random_entry_impl (OK - wont be used) + +//XXX Think about it from a modularity perspective and reduce +//the number of dependencies. +#include "prop259.h" +#include "nodelist.h" +#include "routerlist.h" +#include "config.h" +#include "circuitbuild.h" +#include "networkstatus.h" +#include "policies.h" +#include "router.h" +#include "routerset.h" +#include "confparse.h" +#include "statefile.h" +#include "entrynodes.h" +#include "routerparse.h" +#include "circpathbias.h" +#include "control.h" + +//XXX Find the appropriate place for this global state + +/** Entry guard selection algorithm **/ +static guard_selection_t *entry_guard_selection = NULL; + +static smartlist_t *bridges = NULL; + +/** used_guards_dirty is marked for saving used_guards into state file **/ +static int used_guards_dirty = 0; +/** Flag to mark sampled_guards to be saved into state file **/ +static int sampled_guards_dirty = 0; + +/** The maximum rate of all guards filled into sampled_guards **/ +const double SAMPLE_SET_THRESHOLD = 0.02; +/** The number of minutes that we wait until we retry using primary guards **/ +const int INTERNET_LIKELY_DOWN_INTERNAL= 5; +/** We restrict the number of guards that is going to be used more often **/ +const int PRIMARY_GUARDS_SIZE = 3; +/** When user has EntryNodes or Bridges configured + * (entry_list_is_constrained()) then we are in constrained mode **/ +const int PRIMARY_GUARDS_SIZE_CONSTRAINED = 1; +const int PRIMARY_GUARDS_RETRY_INTERVAL = 3; +/** We restrict the sample set length that is going to be used **/ +const int MINIMUM_FILTERED_SAMPLE_SIZE = 20; +const double MAXIMUM_SAMPLE_SIZE_THRESHOLD = 1.03; + +/** Largest amount that we'll backdate entry_guard_chosen_on_date */ +const int CHOSEN_ON_DATE_SLOP = (3600*24*30); + +/** XXX + * How long will we let a change in our guard nodes stay un-saved + * when we are trying to avoid disk writes? */ +const int SLOW_GUARD_STATE_FLUSH_TIME = 600; +/** XXX + * How long will we let a change in our guard nodes stay un-saved + * when we are not trying to avoid disk writes? */ +const int FAST_GUARD_STATE_FLUSH_TIME = 30; + +/** Returns new guard instance **/ +guardlist_t* +guardlist_new(void) +{ + //XXX We could keep a hashmap to make lookups faster + guardlist_t *gl = tor_malloc_zero(sizeof(guardlist_t)); + gl->list = smartlist_new(); + return gl; +} + +/** Gets an entry guard from guards lists by its digest **/ +static entry_guard_t* +get_guard_by_digest(const smartlist_t *guards, const char *digest) +{ + //XXX This could benefit from a hashmap + SMARTLIST_FOREACH(guards, entry_guard_t *, entry, + if (tor_memeq(digest, entry->identity, DIGEST_LEN)) + return entry; + ); + + return NULL; +} + +/** Return guard's index from smartlist of guards by its digest. + * If guard not found, returns -1.**/ +int +get_guard_index_by_digest(const smartlist_t *guards, const char *digest) +{ + SMARTLIST_FOREACH(guards, entry_guard_t *, entry, + if (tor_memeq(digest, entry->identity, DIGEST_LEN)) + return entry_sl_idx; + ); + + return -1; +} + +/** Returns the length of a guards list gl. **/ +int +guardlist_len(const guardlist_t *gl) +{ + if (!gl) + return 0; + + return smartlist_len(gl->list); +} + +/** Adds an entry guard e to guard list gl. **/ +void +guardlist_add(guardlist_t *gl, entry_guard_t *e) +{ + smartlist_add(gl->list, e); +} + +/** Adds all items of a smartlist sl into guard list gl. **/ +static void +guardlist_add_all_smarlist(guardlist_t *gl, const smartlist_t *sl) +{ + smartlist_add_all(gl->list, sl); +} + +/** Free a guard list gl. **/ +void +guardlist_free(guardlist_t *gl) +{ + if (!gl) + return; + + smartlist_free(gl->list); + tor_free(gl); +} + +/** Mark the state file to be saved according to user options **/ +static void +guards_mark_dirty(void) +{ + time_t when; + + if (get_options()->AvoidDiskWrites) + when = time(NULL) + SLOW_GUARD_STATE_FLUSH_TIME; + else + when = time(NULL) + FAST_GUARD_STATE_FLUSH_TIME; + + /* or_state_save() will call guard_selection_update_state(). */ + or_state_mark_dirty(get_or_state(), when); +} + +/** Mark the used guards to be saved in state file **/ +static void +used_guards_changed(void) +{ + used_guards_dirty = 1; + guards_mark_dirty(); +} + +/** Mark the sampled guards to be saved in state file **/ +static void +sampled_guards_changed(void) +{ + sampled_guards_dirty = 1; + guards_mark_dirty(); +} + +/** Converts an entry guard guard into a node. + * Using guard's identity, look through the nodes lists and returns the found + * node. + * XXX review if this is the right way of doing this **/ +static const node_t* +guard_to_node(const entry_guard_t *guard) +{ + return node_get_by_id(guard->identity); +} + +/** Returns 1 if node node is related with a node chosen_exit, + * otherwise returns 0. **/ +static int +is_related_to_choosen_exit(const node_t *node, const node_t *chosen_exit) +{ + int retval = 0; + smartlist_t *exit_family = smartlist_new(); + + if (chosen_exit) { + nodelist_add_node_and_family(exit_family, chosen_exit); + } + + if (node == chosen_exit) + retval = 1; + + if (smartlist_contains(exit_family, node)) + retval = 1; + + smartlist_free(exit_family); + return retval; +} + +// Note. need_uptime and need_capacity was ignored. See node_is_unreliable() +/* Note. conditions involving UseBridges were ignored because they are + * probably there to fit the existing code to this option. + * If we completely skip our guard selection when UseBridges is set, we + * also need to only choose live guards that also satisfy the ignored + * conditions + */ +MOCK_IMPL(STATIC int, +is_live,(const entry_guard_t *guard)) +{ + const char *msg = NULL; + entry_is_live_flags_t entry_flags = 0; + entry_flags |= ENTRY_NEED_UPTIME; + entry_flags |= ENTRY_NEED_CAPACITY; + + //All Guards are Fast. + //About 95% of Guards are Stable (this will become 100% with #18624) + return entry_is_live(guard, entry_flags, &msg) == NULL ? 0 : 1; +} + +/* + * Note. This is not Tor definition. bad_since is related to + * becoming unusable according to the directory or the user configuration, as + * mentioned in the C Appendix. entry_is_live() verifies part of this "now" as + * opposed to waiting until entry_guard_set_status() changes bad_since - which + * happens only when a new consensus arrives. + * + * For us, a not bad guard meets the condition of what is_suitable. + * Look to Appendix C. +*/ +MOCK_IMPL(STATIC int, +is_bad,(const entry_guard_t *guard)) +{ + return (node_get_by_id(guard->identity) == NULL); +} + +/** XXX Now we have a clear vision of when a guard is "bad" vs "unlisted", we + * should revisit this. + * + * Check if an entry guard guard should be tried according to + * can_retry or if its live AND not bad. **/ +static int +should_try(const entry_guard_t* guard) +{ + if (guard->can_retry) + return 1; + + return (is_live(guard) && !is_bad(guard)); +} + +/** Check if an entry_guard_t is_suitable according to + * !is_live or (for_directory && !is_dir_cache) **/ +static int +is_suitable(const entry_guard_t *entry, int for_directory) +{ + if (!is_live(entry)) + return 0; + + if (for_directory && !entry->is_dir_cache) + return 0; + + return 1; +} + +/** Check if an entry_guard_t should_ignore according to + * !is_suitable **/ +static int +should_ignore(const entry_guard_t *guard, int for_directory) +{ + return !is_suitable(guard, for_directory); +} + +/** Check if an entry_guard_t should_ignore according to + * should_try && !should_ignore **/ +static int +is_eligible(const entry_guard_t* guard, int for_directory) +{ + return should_try(guard) && + !should_ignore(guard, for_directory); +} + +/** Mark all items of guards list as can retry. **/ +static void +mark_for_retry(const smartlist_t *guards) +{ + SMARTLIST_FOREACH(guards, entry_guard_t *, e, { + e->can_retry = 1; + }); +} + +/** Mark all primary guards of guard_selection as can retry and + * transit the guard_selection state to STATE_PRIMARY_GUARDS. **/ +static void +retry_primary_guards(guard_selection_t *guard_selection) +{ + mark_for_retry(guard_selection->primary_guards); + transition_to(guard_selection, STATE_PRIMARY_GUARDS); +} + +/** Mark all used guards which are not in primary guards of + * guard_selection as can retry. **/ +static void +mark_remaining_used_for_retry(guard_selection_t *guard_selection) +{ + log_warn(LD_CIRC, "Will retry remaining used guards."); + + GUARDLIST_FOREACH_BEGIN(guard_selection->used_guards, entry_guard_t *, e) { + if (smartlist_contains(guard_selection->primary_guards, e)) { + continue; + } + + e->can_retry = 1; + } GUARDLIST_FOREACH_END(e); +} + +/** Transit the guard_selection to previous state unless it is + * STATE_INVALID or STATE_INIT, otherwise transit the guard_selection + * to STATE_TRY_REMAINING state **/ +static void +transition_to_previous_state_or_try_remaining( + guard_selection_t *guard_selection) +{ + if (guard_selection->previous_state != STATE_INVALID && + guard_selection->previous_state != STATE_INIT) { + log_warn(LD_CIRC, "Going back to previous state"); + transition_to(guard_selection, guard_selection->previous_state); + } else { + mark_remaining_used_for_retry(guard_selection); + transition_to(guard_selection, STATE_TRY_REMAINING); + } +} + +/** Called when guard_selection is in the state PRIMARY_GUARD and needs + * a guard. + * + * Returns the first item from primary guards in guard_selection if the + * guard is eligible. + * + * Returns NULL when do not have eligible guard and transit + * guard_selection's state **/ +static entry_guard_t* +state_PRIMARY_GUARDS_next(guard_selection_t *guard_selection) +{ + char buf[HEX_DIGEST_LEN+1]; + smartlist_t *guards = guard_selection->primary_guards; + + log_warn(LD_CIRC, "There are %d candidates", smartlist_len(guards)); + + SMARTLIST_FOREACH_BEGIN(guards, entry_guard_t *, e) { + base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN); + log_warn(LD_CIRC, "Evaluating '%s' (%s)", e->nickname, buf); + + if (is_eligible(e, guard_selection->for_directory)) + return e; + } SMARTLIST_FOREACH_END(e); + + log_warn(LD_CIRC, "No PRIMARY_GUARDS is live."); + + transition_to_previous_state_or_try_remaining(guard_selection); + return NULL; +} + +/** Transits guard_selection's state to a state when it's valid*/ +STATIC void +transition_to(guard_selection_t *guard_selection, + guard_selection_state_t state) +{ + switch (state) { + case STATE_INVALID: + log_warn(LD_CIRC, "Transitioned to INVALID_STATE."); + return; + case STATE_INIT: + log_warn(LD_CIRC, "Transitioned to INIT_STATE."); + return; + case STATE_PRIMARY_GUARDS: + log_warn(LD_CIRC, "Transitioned to STATE_PRIMARY_GUARDS."); + break; + case STATE_TRY_REMAINING: + log_warn(LD_CIRC, "Transitioned to STATE_TRY_REMAINING."); + break; + } + + guard_selection->state = state; +} + +/** Called when we are checking if we need to try primary guards first instead + * of trying the remaining guards. + * + * Saves the current guard_selection state and marks primary guards to + * be tried. **/ +static void +save_state_and_retry_primary_guards(guard_selection_t *guard_selection) +{ + guard_selection->previous_state = guard_selection->state; + retry_primary_guards(guard_selection); +} + +/** Converts all guards into nodes.**/ +static void +guards_to_nodes(smartlist_t *nodes, const smartlist_t *guards) +{ + SMARTLIST_FOREACH_BEGIN(guards, entry_guard_t *, e) { + const node_t *node = guard_to_node(e); + if (!node) + continue; + + smartlist_add(nodes, (node_t*) node); + } SMARTLIST_FOREACH_END(e); +} + +/** Returns the first node from nodes list based on + * WEIGHT_FOR_GUARD. + * If node is found, remove it from nodes. **/ +STATIC const node_t* +next_node_by_bandwidth(smartlist_t *nodes) +{ + const node_t *node = node_sl_choose_by_bandwidth(nodes, WEIGHT_FOR_GUARD); + if (node) + smartlist_remove(nodes, node); //otherwise it may return duplicates + + return node; +} + +/** Called when guard_selection is in TRY_REMAINING state. + * Returns the first guard in guard_selection used guards but not in + * primary guards. + * If no guards are found returns NULL **/ +static entry_guard_t* +each_used_guard_not_in_primary_guards(guard_selection_t *guard_selection) +{ + GUARDLIST_FOREACH_BEGIN(guard_selection->used_guards, entry_guard_t *, e) { + if (smartlist_contains(guard_selection->primary_guards, e)) { + continue; + } + return e; + } GUARDLIST_FOREACH_END(e); + + return NULL; +} + +/** Marks a node node that was used as a guard**/ +static void +choose_as_new_entry_guard(node_t *node) +{ + node->using_as_guard = 1; + log_info(LD_CIRC, "Chose %s as new entry guard.", node_describe(node)); +} + +/** Called when guard_selection is in TRY_REMAINING state. + * Returns the first live eligible entry guard that is in + * guard_selection remaining guards. + * If none are found returns NULL. + * + * If guard is not live, removes it from remaining guards. **/ +static entry_guard_t* +next_eligible_remaining_guard(guard_selection_t* guard_selection) +{ + char buf[HEX_DIGEST_LEN+1]; + entry_guard_t *guard = NULL; + + log_warn(LD_CIRC, "There are %d candidates", + smartlist_len(guard_selection->remaining_guards)); + + SMARTLIST_FOREACH_BEGIN(guard_selection->remaining_guards, + entry_guard_t *, g) { + base16_encode(buf, sizeof(buf), g->identity, DIGEST_LEN); + log_warn(LD_CIRC, "Evaluating '%s' (%s)", g->nickname, buf); + + if (!is_live(g)) { + log_warn(LD_CIRC, " Removing (not live)."); + SMARTLIST_DEL_CURRENT(guard_selection->remaining_guards, g); + continue; + } + + if (!is_eligible(g, guard_selection->for_directory)) { + log_warn(LD_CIRC, " Ignoring (not eligible)."); + continue; + } + + guard = g; + break; + } SMARTLIST_FOREACH_END(g); + + return guard; +} + +/** Called when an entry guard is needed and guard_selection is in + * TRY_REMAINING state. + * Returns the first entry guard from used guards that is not in primary + * guards. + * If none are found, try to pick the first eligible from remaining guards. + * If still none, then transition guard_selection to + * STATE_PRIMARY_GUARDS and returns NULL.**/ +static entry_guard_t* +state_TRY_REMAINING_next(guard_selection_t *guard_selection) +{ + log_warn(LD_CIRC, "Will try USED_GUARDS not in PRIMARY_GUARDS."); + + entry_guard_t *guard = each_used_guard_not_in_primary_guards( + guard_selection); + + if (guard) + return guard; + + log_warn(LD_CIRC, "Will try REMAINING_REMAINING_GUARDS."); + + guard = next_eligible_remaining_guard(guard_selection); + if (guard) { + return guard; + } + + transition_to(guard_selection, STATE_PRIMARY_GUARDS); + + return NULL; +} + +/** Returns 1 if any guard in guards list has been tried before, + * when last_attempted is smaller or equal than the time. + * Otherwise returns 0.**/ +static int +has_any_been_tried_before(const smartlist_t *guards, time_t time) +{ + SMARTLIST_FOREACH(guards, entry_guard_t *, e, { + //last_attempted is probably better because it is updated + //on subsequent failures. But keep in mind it is only updated + //if we have made contact before. + if (e->last_attempted && e->last_attempted <= time) + return 1; + }); + + return 0; +} + +/** Called when a circuit needs to choose an entry guard, to check if + * guard_selection should retry primary guards first. + * We will retry primary guards if any of these guards were tried before N + * interval. + * + * If need to retry, save current state and transit guard_selection to + * PRIMARY_GUARDS state. + * XXX Add tests**/ +static void +check_primary_guards_retry_interval(guard_selection_t *guard_selection, + const or_options_t *options, time_t now) +{ + if (guard_selection->state == STATE_PRIMARY_GUARDS) + return; + + int retry_interval = options->PrimaryGuardsRetryInterval ? + options->PrimaryGuardsRetryInterval : PRIMARY_GUARDS_RETRY_INTERVAL; + time_t primary_retry_time = now - retry_interval * 60; + + const smartlist_t *guards = guard_selection->primary_guards; + if (has_any_been_tried_before(guards, primary_retry_time)) { + log_warn(LD_CIRC, + "Some PRIMARY_GUARDS have been tried more than %d " + "minutes ago. Will retry PRIMARY_GUARDS.", + retry_interval); + + save_state_and_retry_primary_guards(guard_selection); + } +} + +/** Called when guard_selection needs an entry guard. + * First it checks if needs to retry primary guards, then calls next() for + * guard_selection state. **/ +STATIC entry_guard_t * +choose_entry_guard_algo_next(guard_selection_t *guard_selection, + const or_options_t *options, time_t now) +{ + check_primary_guards_retry_interval(guard_selection, options, now); + + switch (guard_selection->state) { + case STATE_INVALID: + case STATE_INIT: + log_err(LD_BUG, "Invalid state for guard selection."); + tor_assert(0); + return NULL; + case STATE_PRIMARY_GUARDS: + return state_PRIMARY_GUARDS_next(guard_selection); + case STATE_TRY_REMAINING: + return state_TRY_REMAINING_next(guard_selection); + } + + return NULL; +} + +/** Returns filtered live guards from sampled_guards set. + * + * If this set doesn't have the minimum min_filtered_sample_size + * length required, then expand it, filling with the next guard by bandwidth, + * from the consensus (all_guards). + * + * If we expand the filtered set to max_sample_size_threshold, + * warn the user and returns NULL.**/ +STATIC smartlist_t * +filter_set(const guardlist_t *sampled_guards, smartlist_t *all_guards, + int min_filtered_sample_size, int max_sample_size_threshold) +{ + smartlist_t *filtered = smartlist_new(); + + GUARDLIST_FOREACH(sampled_guards, entry_guard_t *, guard, { + if (is_live(guard)) + smartlist_add(filtered, guard); + }); + + if (smartlist_len(filtered) < min_filtered_sample_size) { + log_warn(LD_CIRC, + "size of sampled_guards %d\n", + guardlist_len(sampled_guards)); + if (guardlist_len(sampled_guards) >= max_sample_size_threshold) { + log_err(LD_GENERAL, + "Size of the set to be filtered is bigger than %d ", + max_sample_size_threshold); + return NULL; + } + + //We wanna to provide a sampled set with a rasonable minimum of guards + //that can be used as entry. + //If sampled_guards don't have the minimun required and also didn't reaches + //the maximum threshold, so filter out all guards, removing the ones that + //are in sampled_guards then pick the next one by bandwidth, adds it to + //sampled_guards and call again filter_set() with this sampled set and + //all_guards + { + smartlist_t *all_nodes = smartlist_new(); + smartlist_subtract(all_guards, sampled_guards->list); + guards_to_nodes(all_nodes, all_guards); + + const node_t * node = next_node_by_bandwidth(all_nodes); + entry_guard_t *ng = find_guard_by_node(all_guards, node); + smartlist_add(sampled_guards->list, ng); + smartlist_free(all_nodes); + + return filter_set(sampled_guards, + all_guards, + min_filtered_sample_size, + max_sample_size_threshold); + } + } + + return filtered; +} + +/** Fills and returns smartlist sampled_guards. + * Its length should be the minimum length configured in guard_selection + * (default value is 20), and not bigger than the configured + * max_sample_size_threshold. Default value is 1.03 **/ +static smartlist_t* +filter_sampled(guard_selection_t *guard_selection, + const guardlist_t *sampled_guards) +{ + int min_filtered_guards = guard_selection->min_filtered_sample_size > 0 + ? guard_selection->min_filtered_sample_size + : MINIMUM_FILTERED_SAMPLE_SIZE; + double sampled_guards_ratio = guard_selection->max_sample_size_threshold > 0 + ? guard_selection->max_sample_size_threshold + : MAXIMUM_SAMPLE_SIZE_THRESHOLD; + + int sampled_len = guardlist_len(sampled_guards); + //XXX What happen if sampled_guards_threshold is 0? + double sampled_guards_threshold = sampled_guards_ratio * sampled_len; + + return filter_set(sampled_guards, + get_all_guards(guard_selection->for_directory), + min_filtered_guards, + sampled_guards_threshold); +} + +/** Called when is starting guard_selection, to initialize remaining + * guards. + * If we are in a constrained mode (entry_list_is_constrained() is true) then + * remaining guards is going to be sampled_guards. + * Otherwise remaining guards is going to be the sampled_guards, + * excluing the used guards. **/ +STATIC void +fill_in_remaining_guards(guard_selection_t *guard_selection, + const guardlist_t *sampled_guards) +{ + if (entry_list_is_constrained(get_options())) { + smartlist_add_all(guard_selection->remaining_guards, + sampled_guards->list); + } else { + smartlist_t *filtered = filter_sampled(guard_selection, sampled_guards); + smartlist_subtract(filtered, guard_selection->used_guards->list); + if (filtered) + smartlist_add_all(guard_selection->remaining_guards, filtered); + } +} + +/** Called when is starting guard_selection, to initialize primary + * guards. + * + * Adds a guard from next_primary_guard while primary guards set is less than + * the minimum length required. + * + * This minimum length normally is 3, when we are in constrained mode the + * length is 1. **/ +STATIC void +fill_in_primary_guards(guard_selection_t *guard_selection) +{ + smartlist_t *primary = guard_selection->primary_guards; + while (smartlist_len(primary) < guard_selection->num_primary_guards) { + entry_guard_t *guard = next_primary_guard(guard_selection); + if (!guard) + break; + + smartlist_add(primary, guard); + } +} + +/** Called when guard_selection should be finished. + * This frees primary and remaining guards. Also sets the current and previous + * guard_selection state to init. **/ +STATIC void +guard_selection_free(guard_selection_t *guard_selection) +{ + if (guard_selection->primary_guards) + smartlist_free(guard_selection->primary_guards); + + if (guard_selection->remaining_guards) + smartlist_free(guard_selection->remaining_guards); + + guard_selection->state = STATE_INIT; + guard_selection->previous_state = STATE_INIT; +} + +/** Called when guard_selection should be initilized. + * It is going to set the limit (n_primary_guards) of primary guards + * that can be exposed. + * All the guards that we are going to use should be directory cache. + * Initilizes remaining and primary guards. **/ +STATIC void +choose_entry_guard_algo_start(guard_selection_t *guard_selection, + int n_primary_guards) +{ + guard_selection->state = STATE_PRIMARY_GUARDS; + guard_selection->num_primary_guards = n_primary_guards; + + fill_in_remaining_guards(guard_selection, guard_selection->sampled_guards); + fill_in_primary_guards(guard_selection); + + log_warn(LD_CIRC, + "Initializing guard_selection:\n" + "- used: %p,\n" + "- sampled_guards: %p,\n" + "- n_primary_guards: %d,\n", + guard_selection->used_guards, + guard_selection->sampled_guards, + n_primary_guards); +} + +/** Filters out the used and primary guards from remaining guards of + * guard_selection and adds the result to guards set.*/ +static void +remaining_guards_for_next_primary(guard_selection_t *guard_selection, + smartlist_t *guards) +{ + smartlist_add_all(guards, guard_selection->remaining_guards); + smartlist_subtract(guards, guard_selection->used_guards->list); + smartlist_subtract(guards, guard_selection->primary_guards); +} + +/** Called when is setting up primary guards, to initialize + * guard_selection. + * Returns first guard in used guards that is not in primary guards and was + * listed in the last consensus. + * + * If we dont have more candidates in used guards, pick the next node by + * bandwidth from remaining guards, excluing the used and the primary ones. + * + * Returns NULL if guard is not found. **/ +STATIC entry_guard_t* +next_primary_guard(guard_selection_t *guard_selection) +{ + const node_t *node = NULL; + const guardlist_t *used = guard_selection->used_guards; + const smartlist_t *primary = guard_selection->primary_guards; + + GUARDLIST_FOREACH_BEGIN(used, entry_guard_t *, e) { + if (!smartlist_contains(primary, e) && !is_bad(e)) + return e; + } GUARDLIST_FOREACH_END(e); + + { /** Get next remaining guard by bandwidth **/ + smartlist_t *remaining_guards = smartlist_new(); + smartlist_t *remaining_nodes = smartlist_new(); + + remaining_guards_for_next_primary(guard_selection, remaining_guards); + guards_to_nodes(remaining_nodes, remaining_guards); + node = next_node_by_bandwidth(remaining_nodes); + + tor_free(remaining_nodes); + tor_free(remaining_guards); + } + + if (!node) + return NULL; + + choose_as_new_entry_guard((node_t*) node); + + /** Remove from remaining **/ + entry_guard_t *guard = find_guard_by_node(guard_selection->remaining_guards, + node); + smartlist_remove(guard_selection->remaining_guards, guard); + + return guard; +} + +/** Returns the guard associated with the node node, from the list of + * guards.**/ +MOCK_IMPL(STATIC entry_guard_t*, +find_guard_by_node,(smartlist_t *guards, const node_t *node)) +{ + entry_guard_t *guard = NULL; + SMARTLIST_FOREACH(guards, entry_guard_t *, e, + if (fast_memeq(e->identity, node->identity, DIGEST_LEN)) { + guard = e; + break; + }); + return guard; +} + +/** Called when we receive a consensus, to fill sampled set sample with + * nodes. + * Picks each node by bandwidth and adds it to the sampled set until it reaches + * the expected size. **/ +STATIC void +fill_in_sampled_guard_set(guardlist_t *sample, const smartlist_t *nodes, + const int size) +{ + smartlist_t *remaining = smartlist_new(); + + smartlist_add_all(remaining, nodes); + while (guardlist_len(sample) < size && smartlist_len(remaining) > 0) { + const node_t *node = next_node_by_bandwidth(remaining); + if (!node) + break; + + guardlist_add(sample, entry_guard_new(node)); + } + smartlist_free(remaining); +} + +/** Called to finish the guard_selection. + * Checks if the used entry guard guard is currently in the used guards + * set. + * If the guard is not in the set, add it and call used_guards_changed() to + * update the state file. + * + * XXX Add tests **/ +STATIC void +choose_entry_guard_algo_end(guard_selection_t *guard_selection, + const entry_guard_t *guard) +{ + log_warn(LD_CIRC, "Finishing guard selection algorithm"); + + guardlist_t *used = guard_selection->used_guards; + smartlist_t *fps = smartlist_new(); + GUARDLIST_FOREACH(used, entry_guard_t *, e, + smartlist_add(fps, (void*)e->identity) + ); + + if (!smartlist_contains_digest(fps, guard->identity)) { + guardlist_add(used, (entry_guard_t*) guard); + used_guards_changed(); + } + + smartlist_free(fps); +} + +/** Called when parse state file. + * Returns the time to be set up in the node. **/ +static time_t +entry_guard_chosen_on_date(const time_t now) +{ + /* Choose expiry time smudged over the past month. The goal here + * is to a) spread out when Tor clients rotate their guards, so they + * don't all select them on the same day, and b) avoid leaving a + * precise timestamp in the state file about when we first picked + * this guard. For details, see the Jan 2010 tor-dev thread. */ + return crypto_rand_time_range(now - CHOSEN_ON_DATE_SLOP, now); +} + +/** Called when needs to parse guards from state file into set guards. + * It is going to look for guards of type config_name inside of the file + * lines line. + * If some of parsed guard was save in a different Tor version + * state_version, notify the user. + * + * Uses msg to store error message. + * + * Returns normally 1 if all guards was successful parsed, otherwhise returns + * -1 if find an error or 0 if none guard is found. + * + * This is basically a copy of entry_guards_parse_state() **/ +static int +guards_parse_state(config_line_t *line, const char *state_version, + const char* config_name, smartlist_t *guards, + char **msg) +{ + entry_guard_t *node = NULL; + smartlist_t *new_entry_guards = smartlist_new(); + int changed = 0; + time_t now = time(NULL); + digestmap_t *added_by = digestmap_new(); + + char *down_since_config_name = NULL; + char *unlisted_since_config_name = NULL; + char *added_by_config_name = NULL; + char *path_use_bias_config_name = NULL; + char *path_bias_config_name = NULL; + + tor_asprintf(&down_since_config_name, "%sDownSince", config_name); + tor_asprintf(&unlisted_since_config_name, "%sUnlistedSince", config_name); + tor_asprintf(&added_by_config_name, "%sAddedBy", config_name); + tor_asprintf(&path_use_bias_config_name, "%sPathUseBias", config_name); + tor_asprintf(&path_bias_config_name, "%sPathBias", config_name); + + *msg = NULL; + for (; line; line = line->next) { + if (!strcasecmp(line->key, config_name)) { + smartlist_t *args = smartlist_new(); + node = tor_malloc_zero(sizeof(entry_guard_t)); + + smartlist_add(new_entry_guards, node); + smartlist_split_string(args, line->value, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + + /* Validates nickname and fingerprint */ + if (smartlist_len(args)<2) { + tor_asprintf(msg, "Unable to parse entry nodes: " + "Too few arguments to %s", config_name); + } else if (!is_legal_nickname(smartlist_get(args,0))) { + tor_asprintf(msg, "Unable to parse entry nodes: " + "Bad nickname for %s", config_name); + } else { + char *nickname = smartlist_get(args, 0); + char *digest = smartlist_get(args, 1); + + strlcpy(node->nickname, nickname, MAX_NICKNAME_LEN+1); + if (base16_decode(node->identity, DIGEST_LEN, digest, + strlen(digest))<0) { + tor_asprintf(msg, "Unable to parse entry nodes: " + "Bad hex digest for %s", config_name); + } + } + + /* Parses DirCache / NoDirCache */ + if (smartlist_len(args) >= 3) { + const char *is_cache = smartlist_get(args, 2); + if (!strcasecmp(is_cache, "DirCache")) { + node->is_dir_cache = 1; + } else if (!strcasecmp(is_cache, "NoDirCache")) { + node->is_dir_cache = 0; + } else { + log_warn(LD_CONFIG, + "Bogus third argument to %s line: %s", + config_name, escaped(is_cache)); + } + } + + SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); + smartlist_free(args); + + /* Abort on error */ + if (*msg) + break; + + } else if (!strcasecmp(line->key, down_since_config_name) || + !strcasecmp(line->key, unlisted_since_config_name)) { + time_t when; + time_t last_try = 0; + + if (!node) { + tor_asprintf(msg, "Unable to parse used guard: " + "%sDownSince/UnlistedSince without %s", + config_name, + config_name); + break; + } + + if (parse_iso_time_(line->value, &when, 0)<0) { + tor_asprintf(msg, "Unable to parse used guard: " + "Bad time in %sDownSince/UnlistedSince", + config_name); + break; + } + + if (when > now) { + /* It's a bad idea to believe info in the future: you can wind + * up with timeouts that aren't allowed to happen for years. */ + continue; + } + + /* Parse optional last_attempt */ + if (strlen(line->value) >= ISO_TIME_LEN+ISO_TIME_LEN+1) { + /* ignore failure */ + (void) parse_iso_time(line->value+ISO_TIME_LEN+1, &last_try); + } + + if (!strcasecmp(line->key, down_since_config_name)) { + node->unreachable_since = when; + node->last_attempted = last_try; + } else { + node->bad_since = when; + } + + } else if (!strcasecmp(line->key, added_by_config_name)) { + char d[DIGEST_LEN]; + /* format is digest version date */ + if (strlen(line->value) < HEX_DIGEST_LEN+1+1+1+ISO_TIME_LEN) { + log_warn(LD_BUG, "%s line is not long enough.", + added_by_config_name); + continue; + } + + if (base16_decode(d, sizeof(d), line->value, HEX_DIGEST_LEN)<0 || + line->value[HEX_DIGEST_LEN] != ' ') { + log_warn(LD_BUG, "%s line %s does not begin with " + "hex digest", added_by_config_name, escaped(line->value)); + continue; + } + + digestmap_set(added_by, d, tor_strdup(line->value+HEX_DIGEST_LEN+1)); + } else if (!strcasecmp(line->key, path_use_bias_config_name)) { + const or_options_t *options = get_options(); + double use_cnt, success_cnt; + + if (!node) { + tor_asprintf(msg, "Unable to parse entry nodes: " + "%s without %s", path_use_bias_config_name, config_name); + break; + } + + if (tor_sscanf(line->value, "%lf %lf", + &use_cnt, &success_cnt) != 2) { + log_info(LD_GENERAL, "Malformed path use bias line for node" + " %s", node->nickname); + continue; + } + + if (use_cnt < success_cnt) { + int severity = LOG_INFO; + /* If this state file was written by a Tor that would have + * already fixed it, then the overcounting bug is still + * there.. */ + if (tor_version_as_new_as(state_version, "0.2.4.13-alpha")) { + severity = LOG_NOTICE; + } + log_fn(severity, LD_BUG, + "State file contains unexpectedly high usage success " + "counts %lf/%lf for Guard %s ($%s)", + success_cnt, use_cnt, + node->nickname, hex_str(node->identity, DIGEST_LEN)); + success_cnt = use_cnt; + } + + node->use_attempts = use_cnt; + node->use_successes = success_cnt; + + log_info(LD_GENERAL, "Read %f/%f path use bias for node %s", + node->use_successes, node->use_attempts, node->nickname); + + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_use_success_count(node)/node->use_attempts + < pathbias_get_extreme_use_rate(options) && + pathbias_get_dropguards(options)) { + node->path_bias_disabled = 1; + log_info(LD_GENERAL, + "Path use bias is too high (%f/%f); disabling node %s", + node->circ_successes, node->circ_attempts, node->nickname); + } + } else if (!strcasecmp(line->key, path_bias_config_name)) { + const or_options_t *options = get_options(); + double hop_cnt, success_cnt, timeouts, collapsed, + successful_closed, unusable; + + if (!node) { + tor_asprintf(msg, "Unable to parse entry nodes: " + "%s without %s", path_bias_config_name, config_name); + break; + } + + /* First try 3 params, then 2. */ + /* In the long run: circuit_success ~= successful_circuit_close + + * collapsed_circuits + + * unusable_circuits */ + if (tor_sscanf(line->value, "%lf %lf %lf %lf %lf %lf", + &hop_cnt, &success_cnt, &successful_closed, + &collapsed, &unusable, &timeouts) != 6) { + int old_success, old_hops; + if (tor_sscanf(line->value, "%u %u", &old_success, + &old_hops) != 2) { + continue; + } + log_info(LD_GENERAL, "Reading old-style %s %s", + path_bias_config_name, escaped(line->value)); + + success_cnt = old_success; + successful_closed = old_success; + hop_cnt = old_hops; + timeouts = 0; + collapsed = 0; + unusable = 0; + } + + if (hop_cnt < success_cnt) { + int severity = LOG_INFO; + /* If this state file was written by a Tor that would have + * already fixed it, then the overcounting bug is still + * there.. */ + if (tor_version_as_new_as(state_version, "0.2.4.13-alpha")) { + severity = LOG_NOTICE; + } + log_fn(severity, LD_BUG, + "State file contains unexpectedly high success counts " + "%lf/%lf for Guard %s ($%s)", + success_cnt, hop_cnt, + node->nickname, hex_str(node->identity, DIGEST_LEN)); + success_cnt = hop_cnt; + } + + node->circ_attempts = hop_cnt; + node->circ_successes = success_cnt; + + node->successful_circuits_closed = successful_closed; + node->timeouts = timeouts; + node->collapsed_circuits = collapsed; + node->unusable_circuits = unusable; + + log_info(LD_GENERAL, "Read %f/%f path bias for node %s", + node->circ_successes, node->circ_attempts, node->nickname); + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_close_success_count(node)/node->circ_attempts + < pathbias_get_extreme_rate(options) && + pathbias_get_dropguards(options)) { + node->path_bias_disabled = 1; + log_info(LD_GENERAL, + "Path bias is too high (%f/%f); disabling node %s", + node->circ_successes, node->circ_attempts, node->nickname); + } + } else { + log_warn(LD_BUG, "Unexpected key %s", line->key); + } + } + + SMARTLIST_FOREACH_BEGIN(new_entry_guards, entry_guard_t *, e) { + char *sp; + char *val = digestmap_get(added_by, e->identity); + if (val && (sp = strchr(val, ' '))) { + time_t when; + *sp++ = '\0'; + if (parse_iso_time(sp, &when)<0) { + log_warn(LD_BUG, "Can't read time %s in %s", sp, + added_by_config_name); + } else { + e->chosen_by_version = tor_strdup(val); + e->chosen_on_date = when; + } + } else { + if (state_version) { + time_t now = time(NULL); + e->chosen_on_date = entry_guard_chosen_on_date(now); + e->chosen_by_version = tor_strdup(state_version); + } + } + + if (e->path_bias_disabled && !e->bad_since) + e->bad_since = time(NULL); + } + SMARTLIST_FOREACH_END(e); + + if (*msg || !guards) { + SMARTLIST_FOREACH(new_entry_guards, entry_guard_t *, e, + entry_guard_free(e)); + smartlist_free(new_entry_guards); + } else { + /* Free used guards and replace by guards in state, on success */ + SMARTLIST_FOREACH(guards, entry_guard_t *, e, + entry_guard_free(e)); + smartlist_clear(guards); + smartlist_add_all(guards, new_entry_guards); + + remove_obsolete_guards(now, new_entry_guards); + if (smartlist_len(new_entry_guards)) { + changed = 1; + + log_warn(LD_CIRC, "GUARDS loaded:"); + log_guards(LOG_WARN, guards); + } + } + + tor_free(down_since_config_name); + tor_free(unlisted_since_config_name); + tor_free(added_by_config_name); + tor_free(path_use_bias_config_name); + tor_free(path_bias_config_name); + + return *msg ? -1 : changed; +} + +/** Parses SampledGuards from state file state into the set + * sample. + * If it gots some error, is going to be saved in msg. **/ +static int +sampled_guards_parse_state(const or_state_t *state, smartlist_t *sample, + char **msg) +{ + return guards_parse_state(state->SampledGuards, state->TorVersion, + "SampledGuard", sample, msg); +} + +/** Parses UseedGuards from state file state into the set + * used_guards. + * If it gots some error, is going to be saved in msg. **/ +STATIC int +used_guards_parse_state(const or_state_t *state, smartlist_t *used_guards, + char **msg) +{ + return guards_parse_state(state->UsedGuards, state->TorVersion, + "UsedGuard", used_guards, msg); +} + +/** Parses EntryGuards from state file state into the set + * entry_guards. + * In the case of been succefull parsed, trigers used_guards_changed() to + * sinalize that stored used guards should be updated. + * If it gots some error, is going to be saved in msg. **/ +STATIC int +entry_guards_parse_state_backward(const or_state_t *state, + smartlist_t *entry_guards, char **msg) +{ + int ret = guards_parse_state(state->EntryGuards, state->TorVersion, + "EntryGuard", entry_guards, msg); + + if (ret == 1) + used_guards_changed(); + + return ret; +} + +/** Called when we are notified that a guards list was changed and + * should save each item into the each line of the configuration file, + * based on guards type (config_name). **/ +static void +guards_update_state(config_line_t **next, const guardlist_t *guards, + const char* config_name) +{ + log_warn(LD_CIRC, "Will store %s", config_name); + + config_line_t *line = NULL; + char *down_since_config_name = NULL; + char *unlisted_since_config_name = NULL; + char *added_by_config_name = NULL; + char *path_use_bias_config_name = NULL; + char *path_bias_config_name = NULL; + + tor_asprintf(&down_since_config_name, "%sDownSince", config_name); + tor_asprintf(&unlisted_since_config_name, "%sUnlistedSince", config_name); + tor_asprintf(&added_by_config_name, "%sAddedBy", config_name); + tor_asprintf(&path_use_bias_config_name, "%sPathUseBias", config_name); + tor_asprintf(&path_bias_config_name, "%sPathBias", config_name); + + GUARDLIST_FOREACH_BEGIN(guards, entry_guard_t *, e) { + char dbuf[HEX_DIGEST_LEN+1]; + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup(config_name); + + base16_encode(dbuf, sizeof(dbuf), e->identity, DIGEST_LEN); + tor_asprintf(&line->value, "%s %s %sDirCache", e->nickname, dbuf, + e->is_dir_cache ? "" : "No"); + + next = &(line->next); + if (e->unreachable_since) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup(down_since_config_name); + line->value = tor_malloc(ISO_TIME_LEN+1+ISO_TIME_LEN+1); + format_iso_time(line->value, e->unreachable_since); + if (e->last_attempted) { + line->value[ISO_TIME_LEN] = ' '; + format_iso_time(line->value+ISO_TIME_LEN+1, e->last_attempted); + } + next = &(line->next); + } + + if (e->bad_since) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup(unlisted_since_config_name); + line->value = tor_malloc(ISO_TIME_LEN+1); + format_iso_time(line->value, e->bad_since); + next = &(line->next); + } + + if (e->chosen_on_date && e->chosen_by_version && + !strchr(e->chosen_by_version, ' ')) { + char d[HEX_DIGEST_LEN+1]; + char t[ISO_TIME_LEN+1]; + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup(added_by_config_name); + base16_encode(d, sizeof(d), e->identity, DIGEST_LEN); + format_iso_time(t, e->chosen_on_date); + tor_asprintf(&line->value, "%s %s %s", + d, e->chosen_by_version, t); + next = &(line->next); + } + + if (e->circ_attempts > 0) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup(path_bias_config_name); + /* In the long run: circuit_success ~= successful_circuit_close + + * collapsed_circuits + + * unusable_circuits */ + tor_asprintf(&line->value, "%f %f %f %f %f %f", + e->circ_attempts, e->circ_successes, + pathbias_get_close_success_count(e), + e->collapsed_circuits, + e->unusable_circuits, e->timeouts); + next = &(line->next); + } + + if (e->use_attempts > 0) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup(path_use_bias_config_name); + + tor_asprintf(&line->value, "%f %f", + e->use_attempts, + pathbias_get_use_success_count(e)); + next = &(line->next); + } + + } GUARDLIST_FOREACH_END(e); + + tor_free(down_since_config_name); + tor_free(unlisted_since_config_name); + tor_free(added_by_config_name); + tor_free(path_use_bias_config_name); + tor_free(path_bias_config_name); +} + +/** Called to update used_guards list, stored in state file. + * Also frees stored entry guards to be replaced by used guards. + * XXX Add test **/ +STATIC void +used_guards_update_state(or_state_t *state, guardlist_t *used_guards) +{ + config_line_t **next = NULL; + + //EntryGuards is replaced by UsedGuards + config_free_lines(state->EntryGuards); + next = &state->EntryGuards; + *next = NULL; + + config_free_lines(state->UsedGuards); + next = &state->UsedGuards; + *next = NULL; + + guards_update_state(next, used_guards, "UsedGuard"); +} + +/** Called to update sampled_guards list, stored in state file. + * XXX Add test **/ +static void +sampled_guards_update_state(or_state_t *state, guardlist_t *sampled_guards) +{ + config_line_t **next = NULL; + + config_free_lines(state->SampledGuards); + next = &state->SampledGuards; + *next = NULL; + + guards_update_state(next, sampled_guards, "SampledGuard"); +} + +/** Called when the guard_selection is notified that an entry guard + * connection was estabilished (succeeded==1) or has failed + * (succeeded==0) in the order to check if we should keep looking for a + * new entry guard or not. + * + * Return 0 always when succeeded==1 unless this connection was the + * first one estabilished after INTERNET_LIKELY_DOWN_INTERVAL minutes + * (5 as default). In that case we also set the state to STATE_PRIMARY_GUARDS + * and return 1 */ +int +choose_entry_guard_algo_should_continue(guard_selection_t *guard_selection, + int succeeded, time_t now) +{ + if (!succeeded) { + log_warn(LD_CIRC, "Did not succeeded."); + return 1; + } + + int should_continue = 0; + time_t last_success = guard_selection->last_success; + + const or_options_t *options = get_options(); + int internet_likely_down_interval = options->InternetLikelyDownInterval + ? options->InternetLikelyDownInterval : INTERNET_LIKELY_DOWN_INTERNAL; + + if (last_success && + now - last_success > internet_likely_down_interval * 60) { + log_warn(LD_CIRC, "Discarding circuit after %d minutes without " + "success. The network may have been down and now is up again," + "so we retry the used guards.", internet_likely_down_interval); + + retry_primary_guards(guard_selection); + should_continue = 1; + } + + guard_selection->last_success = now; + return should_continue; +} + +/** Called to certify guard_selection initialization. + * XXX Add tests **/ +STATIC void +guard_selection_ensure(guard_selection_t **guard_selection) +{ + //XXX we can init other list here so that start is only a function for + //filling something + if (!*guard_selection) { + guard_selection_t *new_guard_selection = tor_malloc_zero( + sizeof(guard_selection_t)); + + new_guard_selection->state = STATE_INIT; + new_guard_selection->previous_state = STATE_INIT; + + new_guard_selection->primary_guards = smartlist_new(); + new_guard_selection->remaining_guards = smartlist_new(); + new_guard_selection->used_guards = guardlist_new(); + new_guard_selection->sampled_guards = guardlist_new(); + + //We are using only directory guards + //About 80% of Guards are V2Dir/dirguards + //(this will become 100% with #12538) + new_guard_selection->for_directory = 1; + + *guard_selection = new_guard_selection; + } +} + +/** Public interface to start guard selection. + * + * Called by choose_random_entry() to choose an entry guard to build a circuit. + * Its receives user state file to look for stored guards; + * Returns normally an entry guard, in the case of consensus is not available + * yet, returns NULL. + * + * XXX Add tests **/ +const node_t * +choose_random_entry_prop259(cpath_build_state_t *state, int *n_options_out) +{ + guard_selection_ensure(&entry_guard_selection); + + const or_options_t *options = get_options(); + + //entry guard selection context should be the same for this batch of + //circuits. The same entry guard will be used for all the circuits in this + //batch until it fails. + if (entry_guard_selection->state == STATE_INIT) { + const int is_constrained = entry_list_is_constrained(options); + const int num_needed = is_constrained ? PRIMARY_GUARDS_SIZE_CONSTRAINED + : PRIMARY_GUARDS_SIZE; + + if (!router_have_minimum_dir_info() && !is_constrained) { + log_warn(LD_CIRC, "Cant initialize without a consensus."); + return NULL; + } + + char *err = NULL; + if (guard_selection_parse_used_guards_state(get_or_state(), 1, &err) < 0) { + log_err(LD_GENERAL,"%s",err); + tor_free(err); + } + + int result = guard_selection_parse_sampled_guards_state(get_or_state(), + 1, &err); + if (result < 0) { + log_err(LD_GENERAL,"%s",err); + tor_free(err); + } + choose_entry_guard_algo_start(entry_guard_selection, num_needed); + } + + //XXX choose_good_entry_server() ignores: + // - routers in the same family as the exit node + // - routers in the same family of the guards you have chosen + //Our proposal does not care. + + log_warn(LD_CIRC, "Using proposal 259 to choose entry guards."); + + const node_t *node = NULL; + const entry_guard_t* guard = NULL; + time_t now = time(NULL); + + if (n_options_out) + *n_options_out = 0; + + //XXX see entry_guards_set_from_config(options); + + const node_t *chosen_exit = + state ? build_state_get_exit_node(state) : NULL; + + retry: + guard = choose_entry_guard_algo_next(entry_guard_selection, options, now); + + // This only exists because NEXT() can return NULL when transitioning + // between states + if (!guard) + goto retry; + + // Guard is not in the consensus anymore. Not sure if this is possible + node = guard_to_node(guard); + tor_assert(node); + + // This is another part of IS_SUITABLE. It's here to avoid + // passing the exit node to the guard_selection_t + if (is_related_to_choosen_exit(node, chosen_exit)) + goto retry; + + log_warn(LD_CIRC, "Chose %s as entry guard for this circuit.", + node_describe(node)); + + //XXX check entry_guards_changed(); + + //XXX What is n_options_out in our case? + if (n_options_out) + *n_options_out = 1; + + return node; +} + +/** Called when consensus arrives and we are in constrained mode + * (entry_list_is_constrained() returns true). + * + * It will fills guard_selection sampled guards with configuraded + * EntryNodes from users configuration (options), following this + * priority: + * + * 1- Ones that are in both, used_guards and entry_nodes + * 2- Scrambled entry_nodes exluding worse_entry_node (where its node is not + * a possible guard + * 3- Scranbled worse_entry_nodes + * XXX Add tests */ +static void +fill_sampled_guards_from_entrynodes(guard_selection_t *guard_selection, + const or_options_t *options) +{ + tor_assert(guard_selection->sampled_guards); + + smartlist_t *entry_nodes, *worse_entry_nodes, *entry_fps; + smartlist_t *old_entry_guards_on_list, *old_entry_guards_not_on_list; + // old entry guards in the primary + old_entry_guards_on_list = smartlist_new(); + // old entry guards not in the primary + old_entry_guards_not_on_list = smartlist_new(); + + // EntryNodes in options + entry_nodes = smartlist_new(); + // EntryNodes in options but not is_possible_gurad + worse_entry_nodes = smartlist_new(); + + // convert EntryNodes to entry_nodes excluding ExcludeNodes + routerset_get_all_nodes(entry_nodes, options->EntryNodes, + options->ExcludeNodes, 0); + + //add fingerprints from entry_nodes + entry_fps = smartlist_new(); + SMARTLIST_FOREACH(entry_nodes, const node_t *,node, + smartlist_add(entry_fps, (void*)node->identity)); + + //XXX split up old guards into two list according to + //USED_GUARDS (sure ?) list + if (guard_selection && guard_selection->used_guards) { + SMARTLIST_FOREACH(guard_selection->used_guards->list, + entry_guard_t *, e, { + if (smartlist_contains_digest(entry_fps, e->identity)) + smartlist_add(old_entry_guards_on_list, e); + else + smartlist_add(old_entry_guards_not_on_list, e); + }); + } + + /* Remove all currently configured guard nodes, excluded nodes, unreachable + * nodes, or non-Guard nodes from entry_nodes. */ + SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) { + if (entry_guard_get_by_id_digest(node->identity)) { + SMARTLIST_DEL_CURRENT(entry_nodes, node); + continue; + } else if (! node->is_possible_guard) { + smartlist_add(worse_entry_nodes, (node_t*)node); + SMARTLIST_DEL_CURRENT(entry_nodes, node); + } + } SMARTLIST_FOREACH_END(node); + + smartlist_t *sample = smartlist_new(); + + /* First, the previously configured guards that are in EntryNodes. */ + smartlist_add_all(sample, old_entry_guards_on_list); + /* Next, scramble the rest of EntryNodes, putting the guards first. */ + smartlist_shuffle(entry_nodes); + smartlist_shuffle(worse_entry_nodes); + smartlist_add_all(entry_nodes, worse_entry_nodes); + /* Next, the rest of EntryNodes */ + smartlist_add_all(sample, entry_nodes); + + /* Finally, free the remaining previously configured guards that are not in + * EntryNodes. */ + SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e, + entry_guard_free(e)); + + /** Fill in ignoring sample size **/ + fill_in_sampled_guard_set(guard_selection->sampled_guards, sample, + smartlist_len(sample)); + + sampled_guards_changed(); + + log_warn(LD_CIRC, "We sampled %d from %d EntryNodes", + guardlist_len(guard_selection->sampled_guards), smartlist_len(sample)); + + smartlist_free(old_entry_guards_on_list); + smartlist_free(old_entry_guards_not_on_list); + smartlist_free(entry_nodes); + smartlist_free(worse_entry_nodes); + smartlist_free(entry_fps); + smartlist_free(sample); +} + +/** Fills sampled set dest with loaded bridges. + * If we are in bridge mode, our sampled set will be only these bridges and we + * are not going to expand this set with guards from consensus. **/ +static void +fill_in_from_bridges(guardlist_t *dest) +{ + tor_assert(dest); + smartlist_t *sample = smartlist_new(); + if (bridges) { + smartlist_add_all(sample, bridges); + } + + /** Fill in ignoring sample size **/ + fill_in_sampled_guard_set(dest, sample, + smartlist_len(sample)); + + //XXX do this only when it changed + sampled_guards_changed(); + + log_warn(LD_CIRC, "We sampled %d from %d EntryNodes", + guardlist_len(dest), smartlist_len(sample)); + + smartlist_free(sample); +} + +/** Called when receives bridge descriptor to add node to our bridges + * list if is not there also updates sampled set with the bridge list. **/ +void +add_an_entry_bridge(node_t *node) +{ + if (!bridges) bridges = smartlist_new(); + + if (!smartlist_contains(bridges, node->identity)) + smartlist_add(bridges, node); + fill_in_from_bridges(entry_guard_selection->sampled_guards); +} + +/** Returns 1 if we have bridges to be used or 0 if not.**/ +int +known_entry_bridge(void) +{ + if (bridges && smartlist_len(bridges)) return 1; + + return 0; +} + +/** Called when consensus arrives and we are in constrained mode. + * + * It will fills sampled guards with configuraded EntryNodes from users + * configuration (options). **/ +void +guard_selection_fill_in_from_entrynodes(const or_options_t *options) +{ + fill_sampled_guards_from_entrynodes(entry_guard_selection, options); +} + +/** Removes dead and obsoletes guards from a guards list gl, having in + * consideration the current time now. + * XXX Add tests **/ +static void +prune_guardlist(const time_t now, guardlist_t *gl) +{ + int changed = 0; + log_warn(LD_CIRC, "Prunning a list of guards"); + + changed = remove_dead_guards(now, gl->list); + if (changed) + log_warn(LD_CIRC, "Removed some dead guards"); + + changed = remove_obsolete_guards(now, gl->list); + if (changed) + log_warn(LD_CIRC, "Removed some obsolete guards"); +} + +//XXX Add tests +//This matches entry_guards_compute_status +void +entry_guards_update_profiles(const or_options_t *options, const time_t now) +{ + guard_selection_ensure(&entry_guard_selection); + log_warn(LD_CIRC, "Received a new consensus"); + if (entry_list_is_constrained(options)) { + //We mabe have new info about EntryNodes, refill it if possible + if (options->EntryNodes) + guard_selection_fill_in_from_entrynodes(options); + } else { + if (entry_guard_selection->used_guards) + prune_guardlist(now, entry_guard_selection->used_guards); + + if (entry_guard_selection->sampled_guards) + prune_guardlist(now, entry_guard_selection->sampled_guards); + + //We recreate the sample sets without restricting to directory + //guards, because most of the entry guards will be directory in + //the near ideal future. + smartlist_t *all_guards = get_all_guards( + entry_guard_selection->for_directory); + fill_in_sampled_guard_set(entry_guard_selection->sampled_guards, + all_guards, + SAMPLE_SET_THRESHOLD * smartlist_len(all_guards)); + log_warn(LD_CIRC, "We sampled %d from %d utopic guards", + guardlist_len(entry_guard_selection->sampled_guards), + smartlist_len(all_guards)); + sampled_guards_changed(); + + smartlist_free(all_guards); + } +} + +/** Called when we are notified about the connection result of an picked + * entry to updates when its was tried (now) also if was a + * successful (succeeded=1) or not (succeeded=0). + * Returns 1 when guard state changed or 0 when not.**/ +int +update_entry_guards_connection_status(entry_guard_t *entry, + const int succeeded, const time_t now) +{ + int changed = 0; + char buf[HEX_DIGEST_LEN+1]; + base16_encode(buf, sizeof(buf), entry->identity, DIGEST_LEN); + + if (succeeded) { + if (entry->unreachable_since) { + log_info(LD_CIRC, "Entry guard '%s' (%s) is now reachable again." + " Good.", entry->nickname, buf); + + entry->can_retry = 0; + entry->unreachable_since = 0; + entry->last_attempted = now; + control_event_guard(entry->nickname, entry->identity, "UP"); + changed = 1; + } + if (!entry->made_contact) { + entry->made_contact = 1; + changed = 1; + } + } else { /* ! succeeded */ + if (entry->made_contact && !entry->unreachable_since) { + log_info(LD_CIRC, "Unable to connect to entry guard '%s' (%s). " + "Marking as unreachable.", entry->nickname, buf); + entry->unreachable_since = entry->last_attempted = now; + control_event_guard(entry->nickname, entry->identity, "DOWN"); + changed = 1; + entry->can_retry = 0; /* We gave it an early chance; no good. */ + } else { + char tbuf[ISO_TIME_LEN+1]; + format_iso_time(tbuf, entry->unreachable_since); + log_debug(LD_CIRC, "Failed to connect to unreachable entry guard " + "'%s' (%s). It has been unreachable since %s.", + entry->nickname, buf, tbuf); + entry->last_attempted = now; + entry->can_retry = 0; /* We gave it an early chance; no good. */ + } + } + + return changed; +} + +/** Called when a connection to an entry guard with the identity digest + * digest is estabilished (succeeded==1) or has failed + * (succeeded==0). + * + * If mark_relay_status, also call router_set_status() on this relay. + * + * Changes entry's up/down status. + * + * Return 0 normally, or -1 if we want to try find out a new guard + */ +int +guard_selection_register_connect_status(const char *digest, int succeeded, + int mark_relay_status, time_t now) +{ + int changed = 0; + int should_continue = 0; + entry_guard_t *entry = NULL; + int idx = -1; + char buf[HEX_DIGEST_LEN+1]; + + guard_selection_ensure(&entry_guard_selection); + + /* Find the guard by digest */ + entry = get_guard_by_digest(entry_guard_selection->sampled_guards->list, + digest); + if (!entry) + entry = get_guard_by_digest(entry_guard_selection->used_guards->list, + digest); + + if (!entry || !guard_to_node(entry)) return 0; + + log_warn(LD_CIRC, "Guard %s has succeeded = %d. Processing...", + node_describe(guard_to_node(entry)), succeeded); + + /* if the caller asked us to, also update the is_running flags for this + * relay */ + if (mark_relay_status) + router_set_status(digest, succeeded); + + /* Should do it keep looking for a new entry guard? + * It checks if needs to end or not the algorithm */ + should_continue = choose_entry_guard_algo_should_continue( + entry_guard_selection, succeeded, now); + log_warn(LD_CIRC, "Should continue? %d", should_continue); + + if (!should_continue) { + choose_entry_guard_algo_end(entry_guard_selection, entry); + guard_selection_free(entry_guard_selection); + } else { + //XXX What is missing from entry_guard_register_connect_status() + + if (!succeeded && !entry->made_contact) { + /* We've never connected to this one. */ + log_info(LD_CIRC, + "Connection to never-contacted entry guard '%s' (%s) failed. " + "Removing from the list.", entry->nickname, buf); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + + idx = get_guard_index_by_digest( + entry_guard_selection->sampled_guards->list, digest); + smartlist_del_keeporder(entry_guard_selection->sampled_guards->list, + idx); + + if (idx == -1) { + idx = get_guard_index_by_digest( + entry_guard_selection->used_guards->list, digest); + smartlist_del_keeporder(entry_guard_selection->sampled_guards->list, + idx); + } + + changed = 1; + } + } + + changed = update_entry_guards_connection_status(entry, succeeded, now) + || changed; + + if (changed) + entry_guards_changed(); + + return should_continue ? -1 : 0; +} + +/** Called by or_state_save() to save used and sampled guards into state + * file based on the user configuration (options) **/ +void +guard_selection_update_state(or_state_t *state, const or_options_t *options) +{ + if (!used_guards_dirty && !sampled_guards_dirty) + return; + + if (used_guards_dirty) + used_guards_update_state(state, entry_guard_selection->used_guards); + + if (sampled_guards_dirty) + sampled_guards_update_state(state, entry_guard_selection->sampled_guards); + + if (!options->AvoidDiskWrites) + or_state_mark_dirty(state, 0); + + used_guards_dirty = 0; + sampled_guards_dirty = 0; +} + +/** Called to print out guards' information **/ +void +log_guards(int severity, const smartlist_t *guards) +{ + smartlist_t *elements = smartlist_new(); + char *s; + + SMARTLIST_FOREACH_BEGIN(guards, entry_guard_t *, e) { + if (is_live(e)) + smartlist_add_asprintf(elements, "%s [%s] (up %s)", + e->nickname, + hex_str(e->identity, DIGEST_LEN), + e->made_contact ? "made-contact" : "never-contacted"); + else + smartlist_add_asprintf(elements, "%s [%s] (NOT LIVE, %s)", + e->nickname, + hex_str(e->identity, DIGEST_LEN), + e->made_contact ? "made-contact" : "never-contacted"); + } SMARTLIST_FOREACH_END(e); + + s = smartlist_join_strings(elements, ",", 0, NULL); + SMARTLIST_FOREACH(elements, char*, cp, tor_free(cp)); + smartlist_free(elements); + log_fn(severity,LD_CIRC,"%s",s); + tor_free(s); +} + +/** Parses stored sampled guards from state file. + * Sets the parsed guards into guard selection sampled set when it receives + * set. + * If any error occur, it will be saved in msg. **/ +int +guard_selection_parse_sampled_guards_state(const or_state_t *state, int set, + char **msg) +{ + log_warn(LD_CIRC, "Will load sample set from state file."); + + smartlist_t *guards = set ? smartlist_new() : NULL; + int ret = sampled_guards_parse_state(state, guards, msg); + + if (set && ret == 1) { + guard_selection_ensure(&entry_guard_selection); + //XXX Should we mark them as made_contact if they are also in used? + guardlist_t *sampled_guards = entry_guard_selection->sampled_guards; + guardlist_add_all_smarlist(sampled_guards, guards); + } + + smartlist_free(guards); + return ret; +} + +/** Parses stored used guards from state file. + * Sets the parsed guards into guard selection sampled set when it receives + * set. + * If any error occur, it will be saved in msg. **/ +int +guard_selection_parse_used_guards_state(const or_state_t *state, int set, + char **msg) +{ + log_warn(LD_CIRC, "Will load used guards from state file."); + + smartlist_t *guards = set ? smartlist_new() : NULL; + int ret = entry_guards_parse_state_backward(state, guards, msg); + if (ret == 0) { + ret = used_guards_parse_state(state, guards, msg); + } + + if (set && ret == 1) { + guard_selection_ensure(&entry_guard_selection); + guardlist_t *used_guards = entry_guard_selection->used_guards; + guardlist_add_all_smarlist(used_guards, guards); + /* We have made contact to all USED_GUARDS */ + GUARDLIST_FOREACH(used_guards, entry_guard_t *, entry, + entry->made_contact = 1; + ); + } + //XXX Parse sampled set + //Should we update their + + smartlist_free(guards); + return ret; +} + +/** + * Return the minimum lifetime of working entry guard, in seconds, + * as given in the consensus networkstatus. (Plus CHOSEN_ON_DATE_SLOP, + * so that we can do the chosen_on_date randomization while achieving the + * desired minimum lifetime.) + */ +static int32_t +guards_get_lifetime(void) +{ + const or_options_t *options = get_options(); +#define DFLT_GUARD_LIFETIME (86400 * 60) /* Two months. */ +#define MIN_GUARD_LIFETIME (86400 * 30) /* One months. */ +#define MAX_GUARD_LIFETIME (86400 * 1826) /* Five years. */ + + if (options->GuardLifetime >= 1) { + return CLAMP(MIN_GUARD_LIFETIME, + options->GuardLifetime, + MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; + } + + return networkstatus_get_param(NULL, "GuardLifetime", + DFLT_GUARD_LIFETIME, + MIN_GUARD_LIFETIME, + MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; +} + +/** Remove any entry guard which was selected by an unknown version of Tor, + * or which was selected by a version of Tor that's known to select + * entry guards badly, or which was selected more 2 months ago. */ +/* XXXX The "obsolete guards" and "chosen long ago guards" things should + * probably be different functions. */ +int +remove_obsolete_guards(time_t now, smartlist_t *guards) +{ + log_warn(LD_CIRC, "Will remove OBSOLETE guards"); + + int changed = 0, i; + int32_t guard_lifetime = guards_get_lifetime(); + + for (i = 0; i < smartlist_len(guards); ++i) { + entry_guard_t *entry = smartlist_get(guards, i); + const char *ver = entry->chosen_by_version; + const char *msg = NULL; + tor_version_t v; + int version_is_bad = 0, date_is_bad = 0; + if (!ver) { + msg = "does not say what version of Tor it was selected by"; + version_is_bad = 1; + } else if (tor_version_parse(ver, &v)) { + msg = "does not seem to be from any recognized version of Tor"; + version_is_bad = 1; + } + if (!version_is_bad && entry->chosen_on_date + guard_lifetime < now) { + /* It's been too long since the date listed in our state file. */ + msg = "was selected several months ago"; + date_is_bad = 1; + } + + if (version_is_bad || date_is_bad) { /* we need to drop it */ + char dbuf[HEX_DIGEST_LEN+1]; + tor_assert(msg); + base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); + log_fn(version_is_bad ? LOG_NOTICE : LOG_INFO, LD_CIRC, + "Entry guard '%s' (%s) %s. (Version=%s.) Replacing it.", + entry->nickname, dbuf, msg, ver?escaped(ver):"none"); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del_keeporder(guards, i--); + log_guards(LOG_INFO, guards); + changed = 1; + } + } + + return changed ? 1 : 0; +} + +/** How long (in seconds) do we allow an entry guard to be nonfunctional, + * unlisted, excluded, or otherwise nonusable before we give up on it? */ +#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) + +/** Remove all entry guards that have been down or unlisted for so + * long that we don't think they'll come up again. Return 1 if we + * removed any, or 0 if we did nothing. */ +int +remove_dead_guards(time_t now, smartlist_t* guards) +{ + log_warn(LD_CIRC, "Will remove DEAD guards"); + + char dbuf[HEX_DIGEST_LEN+1]; + char tbuf[ISO_TIME_LEN+1]; + int i; + int changed = 0; + + for (i = 0; i < smartlist_len(guards); ) { + entry_guard_t *entry = smartlist_get(guards, i); + if (entry->bad_since && + ! entry->path_bias_disabled && + entry->bad_since + ENTRY_GUARD_REMOVE_AFTER < now) { + + base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); + format_local_iso_time(tbuf, entry->bad_since); + log_info(LD_CIRC, "Entry guard '%s' (%s) has been down or unlisted" + " since %s local time; removing.", + entry->nickname, dbuf, tbuf); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del_keeporder(guards, i); + log_guards(LOG_INFO, guards); + changed = 1; + } else + ++i; + } + return changed ? 1 : 0; +} + +/** Get an entry guard by its digest. **/ +entry_guard_t * +used_guard_get_by_digest(const char *digest) +{ + if (!entry_guard_selection) + return NULL; + + return get_guard_by_digest(entry_guard_selection->used_guards->list, digest); +} + diff --git a/src/or/prop259.h b/src/or/prop259.h new file mode 100644 index 000000000..f5d24a2d9 --- /dev/null +++ b/src/or/prop259.h @@ -0,0 +1,189 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "entrynodes.h" + +#ifndef TOR_GUARD_STATE_H +#define TOR_GUARD_STATE_H +typedef enum { + STATE_INVALID = -1, + STATE_INIT, + STATE_PRIMARY_GUARDS, + STATE_TRY_REMAINING, +} guard_selection_state_t; + +typedef struct { + smartlist_t *list; +} guardlist_t; + +typedef struct { + guard_selection_state_t state; + guard_selection_state_t previous_state; + + int started; + int for_directory; + int num_primary_guards; + time_t last_success; + + //The minimum size of the sampled set after filtering. + int min_filtered_sample_size; + //Fraction of GUARDS used as an upper bound when expanding SAMPLED_GUARDS. + int max_sample_size_threshold; + + //List of entry_guard_t + smartlist_t *remaining_guards; + + //They should be lists of entry_guard_t because they have been used as + //guards + smartlist_t *primary_guards; + guardlist_t *used_guards; + guardlist_t *sampled_guards; +} guard_selection_t; + +guardlist_t* guardlist_new(void); + +void +guardlist_add(guardlist_t *gl, entry_guard_t *e); + +int +guardlist_len(const guardlist_t *gl); + +void guardlist_free(guardlist_t*); + +#define GUARDLIST_FOREACH(a, b, c, d) \ + SMARTLIST_FOREACH(a->list, b, c, d) + +#define GUARDLIST_FOREACH_BEGIN(a, b, c) \ + SMARTLIST_FOREACH_BEGIN(a->list, b, c) + +#define GUARDLIST_FOREACH_END(a) SMARTLIST_FOREACH_END(a) + +const node_t * +choose_random_entry_prop259(cpath_build_state_t *state, int *n_options_out); + +void +entry_guards_update_profiles(const or_options_t *options, const time_t now); + +int +guard_selection_register_connect_status(const char *digest, int succeeded, + int mark_relay_status, time_t now); + +int +update_entry_guards_connection_status(entry_guard_t *entry, + const int succeeded, const time_t now); + +int +choose_entry_guard_algo_should_continue(guard_selection_t *guard_selection, + int succeeded, time_t now); + +int +guard_selection_parse_used_guards_state(const or_state_t *state, int set, + char **msg); + +int +guard_selection_parse_sampled_guards_state(const or_state_t *state, int set, + char **msg); + +void +guard_selection_update_state(or_state_t *state, const or_options_t *options); + +entry_guard_t* +guard_get_by_digest(const char *digest, const smartlist_t *guards); + +int +get_guard_index_by_digest(const smartlist_t *guards, const char *digest); + +entry_guard_t * +used_guard_get_by_digest(const char *digest); + +int +remove_dead_guards(time_t now, smartlist_t* guards); + +int +remove_obsolete_guards(time_t now, smartlist_t* guards); + +void +add_an_entry_bridge(node_t *node); + +int +known_entry_bridge(void); + +void +log_guards(int severity, const smartlist_t *guards); + +void +guard_selection_fill_in_from_entrynodes(const or_options_t *options); + +#ifdef PROP259_PRIVATE + +STATIC void +choose_entry_guard_algo_start(guard_selection_t *guard_selection, + int n_primary_guards); + +MOCK_DECL(STATIC int, +is_bad,(const entry_guard_t *guard)); + +MOCK_DECL(STATIC int, +is_live,(const entry_guard_t *guard)); + +STATIC entry_guard_t * +choose_entry_guard_algo_next(guard_selection_t *guard_selection, + const or_options_t *options, time_t now); + +STATIC smartlist_t * +filter_set(const guardlist_t *guards, smartlist_t *all_guards, +int min_filtered_sample_size, int max_sample_size_threshold); + +STATIC void +guard_selection_ensure(guard_selection_t **guard_selection); + +STATIC void +guard_selection_free(guard_selection_t *guard_selection); + +STATIC void +transition_to(guard_selection_t *algo, guard_selection_state_t state); + +STATIC entry_guard_t* +next_primary_guard(guard_selection_t *guard_selection); + +STATIC const node_t* +next_node_by_bandwidth(smartlist_t *nodes); + +STATIC void +fill_in_primary_guards(guard_selection_t *guard_selection); + +STATIC void +fill_in_sampled_guard_set(guardlist_t *sample, const smartlist_t *set, + const int size); + +STATIC void +fill_in_remaining_guards(guard_selection_t *guard_selection, + const guardlist_t *sampled_utopic); + +STATIC void +choose_entry_guard_algo_end(guard_selection_t *guard_selection, + const entry_guard_t *guard); + +STATIC int +used_guards_parse_state(const or_state_t *state, smartlist_t *used_guards, + char **msg); + +STATIC int +entry_guards_parse_state_backward(const or_state_t *state, + smartlist_t *entry_guards, char **msg); + +STATIC void +used_guards_update_state(or_state_t *state, guardlist_t *used_guards); + +MOCK_DECL( +STATIC entry_guard_t*, +find_guard_by_node,(smartlist_t *guards, const node_t *node)); + +#endif + +#endif + diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 79509cbe2..603e4f0dd 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -1009,3 +1009,4 @@ MOCK_IMPL(int, hid_serv_responsible_for_desc_id, smartlist_free(responsible); return result; } + diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index fe4529959..7d8197601 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -58,7 +58,7 @@ void rend_get_descriptor_id_bytes(char *descriptor_id_out, const char *service_id, const char *secret_id_part); int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, - const char *id); + const char *id); int hid_serv_acting_as_directory(void); MOCK_DECL(int, hid_serv_responsible_for_desc_id, (const char *id)); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 7f20aa9c3..dcaef5d51 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -2453,9 +2453,9 @@ frac_nodes_with_descriptors(const smartlist_t *sl, /** Choose a random element of status list sl, weighted by * the advertised bandwidth of each node */ -const node_t * -node_sl_choose_by_bandwidth(const smartlist_t *sl, - bandwidth_weight_rule_t rule) +MOCK_IMPL(const node_t *, +node_sl_choose_by_bandwidth,(const smartlist_t *sl, + bandwidth_weight_rule_t rule)) { /*XXXX MOVE */ return smartlist_choose_node_by_bandwidth_weights(sl, rule); } @@ -5395,3 +5395,53 @@ refresh_all_country_info(void) nodelist_refresh_countries(); } + +//XXX Review where to put this. It is for proposal 259 +smartlist_t* +get_all_guards(int for_directory) +{ + const or_options_t *options = get_options(); + + //XXX Ignoring for_directory for now. + (void) for_directory; + + //XXX This comes from router_choose_random_node + const int need_guard = 1; + const int need_desc = 1; + const int pref_addr = 1; + const routerset_t *excludedset = options->ExcludeNodes; + + smartlist_t *sl=smartlist_new(), + *excludednodes=smartlist_new(); + const routerinfo_t *r; + + //XXX review if ExcludeSingleHopRelays is needed + + // exclude ourself + if ((r = routerlist_find_my_routerinfo())) + routerlist_add_node_and_family(excludednodes, r); + + router_add_running_nodes_to_smartlist(sl, 0, 0, 0, + need_guard, need_desc, pref_addr); + + log_debug(LD_CIRC, + "We found %d running nodes.", + smartlist_len(sl)); + + smartlist_subtract(sl, excludednodes); + log_debug(LD_CIRC, + "We removed %d excludednodes, leaving %d nodes.", + smartlist_len(excludednodes), + smartlist_len(sl)); + + if (excludedset) { + routerset_subtract_nodes(sl, excludedset); + log_debug(LD_CIRC, + "We removed excludedset, leaving %d nodes.", + smartlist_len(sl)); + } + + smartlist_free(excludednodes); + return sl; +} + diff --git a/src/or/routerlist.h b/src/or/routerlist.h index bc48c2087..9009db957 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -68,8 +68,10 @@ const routerinfo_t *routerlist_find_my_routerinfo(void); uint32_t router_get_advertised_bandwidth(const routerinfo_t *router); uint32_t router_get_advertised_bandwidth_capped(const routerinfo_t *router); -const node_t *node_sl_choose_by_bandwidth(const smartlist_t *sl, - bandwidth_weight_rule_t rule); +MOCK_DECL(const node_t*, +node_sl_choose_by_bandwidth,(const smartlist_t *sl, + bandwidth_weight_rule_t rule)); + double frac_nodes_with_descriptors(const smartlist_t *sl, bandwidth_weight_rule_t rule); @@ -216,6 +218,9 @@ int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, const char *nickname, int is_named); +smartlist_t* +get_all_guards(int for_directory); + #ifdef ROUTERLIST_PRIVATE /** Helper type for choosing routers by bandwidth: contains a union of * double and uint64_t. Before we call scale_array_elements_to_u64, it holds diff --git a/src/or/statefile.c b/src/or/statefile.c index 9594d9cec..42b42af16 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -23,6 +23,7 @@ #include "router.h" #include "sandbox.h" #include "statefile.h" +#include "prop259.h" /** A list of state-file "abbreviations," for compatibility. */ static config_abbrev_t state_abbrevs_[] = { @@ -65,6 +66,22 @@ static config_var_t state_vars_[] = { VAR("EntryGuardPathUseBias", LINELIST_S, EntryGuards, NULL), V(EntryGuards, LINELIST_V, NULL), + VAR("UsedGuard", LINELIST_S, UsedGuards, NULL), + VAR("UsedGuardDownSince", LINELIST_S, UsedGuards, NULL), + VAR("UsedGuardUnlistedSince", LINELIST_S, UsedGuards, NULL), + VAR("UsedGuardAddedBy", LINELIST_S, UsedGuards, NULL), + VAR("UsedGuardPathBias", LINELIST_S, UsedGuards, NULL), + VAR("UsedGuardPathUseBias", LINELIST_S, UsedGuards, NULL), + V(UsedGuards, LINELIST_V, NULL), + + VAR("SampledGuard", LINELIST_S, SampledGuards, NULL), + VAR("SampledGuardDownSince", LINELIST_S, SampledGuards, NULL), + VAR("SampledGuardUnlistedSince", LINELIST_S, SampledGuards, NULL), + VAR("SampledGuardAddedBy", LINELIST_S, SampledGuards, NULL), + VAR("SampledGuardPathBias", LINELIST_S, SampledGuards, NULL), + VAR("SampledGuardPathUseBias", LINELIST_S, SampledGuards, NULL), + V(SampledGuards, LINELIST_V, NULL), + VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), V(TransportProxies, LINELIST_V, NULL), @@ -228,8 +245,17 @@ or_state_validate_cb(void *old_state, void *state, void *default_state, static int or_state_validate(or_state_t *state, char **msg) { + +#ifndef USE_PROP_259 if (entry_guards_parse_state(state, 0, msg)<0) return -1; +#else + if (guard_selection_parse_used_guards_state(state, 0, msg)<0) + return -1; + + if (guard_selection_parse_sampled_guards_state(state, 0, msg)<0) + return -1; +#endif if (validate_transports_in_state(state)<0) return -1; @@ -246,11 +272,27 @@ or_state_set(or_state_t *new_state) tor_assert(new_state); config_free(&state_format, global_state); global_state = new_state; + +#ifndef USE_PROP_259 if (entry_guards_parse_state(global_state, 1, &err)<0) { log_warn(LD_GENERAL,"%s",err); tor_free(err); ret = -1; } +#else + if (guard_selection_parse_used_guards_state(global_state, 1, &err)<0) { + log_warn(LD_GENERAL,"%s",err); + tor_free(err); + ret = -1; + } + + if (guard_selection_parse_sampled_guards_state(global_state, 1, &err)<0) { + log_warn(LD_GENERAL,"%s",err); + tor_free(err); + ret = -1; + } +#endif + if (rep_hist_load_state(global_state, &err)<0) { log_warn(LD_GENERAL,"Unparseable bandwidth history state: %s",err); tor_free(err); @@ -443,7 +485,12 @@ or_state_save(time_t now) /* Call everything else that might dirty the state even more, in order * to avoid redundant writes. */ +#ifndef USE_PROP_259 entry_guards_update_state(global_state); +#else + guard_selection_update_state(global_state, get_options()); +#endif + rep_hist_update_state(global_state); circuit_build_times_update_state(get_circuit_build_times(), global_state); if (accounting_is_enabled(get_options())) diff --git a/src/test/include.am b/src/test/include.am index 7d80fdf15..04967a6bb 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -83,6 +83,7 @@ src_test_test_SOURCES = \ src/test/test_dir_handle_get.c \ src/test/test_entryconn.c \ src/test/test_entrynodes.c \ + src/test/test_prop259.c \ src/test/test_guardfraction.c \ src/test/test_extorport.c \ src/test/test_hs.c \ diff --git a/src/test/test.c b/src/test/test.c index ed167a3e6..4d2b4c403 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1146,6 +1146,7 @@ extern struct testcase_t dir_tests[]; extern struct testcase_t dir_handle_get_tests[]; extern struct testcase_t entryconn_tests[]; extern struct testcase_t entrynodes_tests[]; +extern struct testcase_t entrynodes_new_tests[]; extern struct testcase_t guardfraction_tests[]; extern struct testcase_t extorport_tests[]; extern struct testcase_t hs_tests[]; @@ -1203,6 +1204,7 @@ struct testgroup_t testgroups[] = { { "dir/md/", microdesc_tests }, { "entryconn/", entryconn_tests }, { "entrynodes/", entrynodes_tests }, + { "entrynodes_new/", entrynodes_new_tests }, { "guardfraction/", guardfraction_tests }, { "extorport/", extorport_tests }, { "hs/", hs_tests }, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index b1c3accfa..5ac0bcb6a 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -841,14 +841,20 @@ static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; +#ifdef USE_PROP_259 +#define SHOULD_SKIP TT_SKIP +#else +#define SHOULD_SKIP 0 +#endif + struct testcase_t entrynodes_tests[] = { { "entry_is_time_to_retry", test_entry_is_time_to_retry, TT_FORK, NULL, NULL }, { "choose_random_entry_no_guards", test_choose_random_entry_no_guards, - TT_FORK, &fake_network, NULL }, + TT_FORK | SHOULD_SKIP , &fake_network, NULL }, { "choose_random_entry_one_possibleguard", test_choose_random_entry_one_possible_guard, - TT_FORK, &fake_network, NULL }, + TT_FORK | SHOULD_SKIP, &fake_network, NULL }, { "populate_live_entry_guards_1guard", test_populate_live_entry_guards_1guard, TT_FORK, &fake_network, NULL }, @@ -863,7 +869,7 @@ struct testcase_t entrynodes_tests[] = { TT_FORK, &fake_network, NULL }, { "entry_guards_set_from_config", test_entry_guards_set_from_config, - TT_FORK, &fake_network, NULL }, + TT_FORK | SHOULD_SKIP, &fake_network, NULL }, { "entry_is_live", test_entry_is_live, TT_FORK, &fake_network, NULL }, diff --git a/src/test/test_prop259.c b/src/test/test_prop259.c new file mode 100644 index 000000000..5c5717177 --- /dev/null +++ b/src/test/test_prop259.c @@ -0,0 +1,1133 @@ +/* Copyright (c) 2014-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#define STATEFILE_PRIVATE +#define ENTRYNODES_PRIVATE +#define ROUTERLIST_PRIVATE +#define PROP259_PRIVATE + +#include "or.h" +#include "test.h" + +#include "config.h" +#include "entrynodes.h" +#include "prop259.h" +#include "nodelist.h" +#include "policies.h" +#include "routerlist.h" +#include "routerparse.h" +#include "routerset.h" +#include "statefile.h" +#include "util.h" + +#include "test_helpers.h" + +/** Dummy Tor state used in unittests. */ +static or_state_t *dummy_state = NULL; +static or_state_t * +get_or_state_replacement(void) +{ + return dummy_state; +} + +/* Unittest setup function: Setup a fake network. */ +static void * +fake_network_setup(const struct testcase_t *testcase) +{ + (void) testcase; + + /* Setup fake state */ + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + MOCK(get_or_state, + get_or_state_replacement); + + /* Setup fake routerlist. */ + helper_setup_fake_routerlist(); + + /* Return anything but NULL (it's interpreted as test fail) */ + return dummy_state; +} + +/* Unittest cleanup function: Cleanup the fake network. */ +static int +fake_network_cleanup(const struct testcase_t *testcase, void *ptr) +{ + (void) testcase; + (void) ptr; + + routerlist_free_all(); + nodelist_free_all(); + entry_guards_free_all(); + or_state_free(dummy_state); + + return 1; /* NOP */ +} + +static const struct testcase_setup_t fake_network = { + fake_network_setup, fake_network_cleanup +}; + +static const node_t* +node_sl_choose_by_bandwidth_mock(const smartlist_t *sl, + bandwidth_weight_rule_t rule) +{ + (void) rule; + + return smartlist_get(sl, 0); +} + +static int +is_bad_mock(const entry_guard_t *guard) +{ + return guard->bad_since != 0; +} + +static int +is_live_mock(const entry_guard_t *guard) +{ + return guard->unreachable_since == 0; +} + +static entry_guard_t* +find_guard_by_node_mock(smartlist_t *guards, const node_t *node) +{ + (void) guards; + (void) node; + + return tor_malloc_zero(sizeof(entry_guard_t)); +} + +/* TODO: + * choose_entry_guard_algo_next() test with state machine. + * + */ + +static void +test_STATE_PRIMARY_GUARD_is_initial_state(void *arg) +{ + guard_selection_t *guard_selection = NULL; + (void) arg; + + MOCK(find_guard_by_node, find_guard_by_node_mock); + MOCK(is_live, is_live_mock); + + int n_primary_guards = 0; + guard_selection_ensure(&guard_selection); + choose_entry_guard_algo_start( + guard_selection, + n_primary_guards); + + tt_int_op(guard_selection->state, OP_EQ, STATE_PRIMARY_GUARDS); + + done: + UNMOCK(find_guard_by_node); + UNMOCK(is_live); + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_next_primary_guard(void *arg) +{ + guard_selection_t *guard_selection = tor_malloc_zero( + sizeof(guard_selection_t)); + guardlist_t *used_guards = guardlist_new(); + smartlist_t *primary_guards = smartlist_new(); + smartlist_t *remaining_guards = smartlist_new(); + + entry_guard_t *g1 = NULL; + entry_guard_t *g2 = NULL; + entry_guard_t *g3 = NULL; + entry_guard_t *chosen = NULL; + (void) arg; + + MOCK(node_sl_choose_by_bandwidth, node_sl_choose_by_bandwidth_mock); + MOCK(is_bad, is_bad_mock); + + tt_int_op(smartlist_len(get_entry_guards()), OP_EQ, 0); + + smartlist_t *our_nodelist = nodelist_get_list(); + + g1 = entry_guard_new(smartlist_get(our_nodelist, 0)); + g2 = entry_guard_new(smartlist_get(our_nodelist, 1)); + g2->bad_since = 1; + g3 = entry_guard_new(smartlist_get(our_nodelist, 2)); + + guardlist_add(used_guards, g1); + guardlist_add(used_guards, g2); + smartlist_add(remaining_guards, g3); + + guard_selection->used_guards = used_guards; + guard_selection->primary_guards = primary_guards; + guard_selection->remaining_guards = remaining_guards; + + /** Gets first used that is not bad **/ + chosen = next_primary_guard(guard_selection); + tt_ptr_op(chosen, OP_EQ, g1); + smartlist_add(primary_guards, chosen); + + /** No more used, gets first remaining **/ + chosen = next_primary_guard(guard_selection); + tt_ptr_op(chosen, OP_EQ, g3); + smartlist_add(primary_guards, chosen); + + done: + UNMOCK(node_sl_choose_by_bandwidth); + UNMOCK(is_bad); + tor_free(g1); + tor_free(g2); + tor_free(g3); + guardlist_free(used_guards); + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_fill_in_primary_guards(void *arg) +{ + guard_selection_t *guard_selection = tor_malloc_zero( + sizeof(guard_selection_t)); + guardlist_t *used_guards = guardlist_new(); + entry_guard_t *g1 = NULL; + entry_guard_t *g2 = NULL; + entry_guard_t *g3 = NULL; + entry_guard_t *g4 = NULL; + (void) arg; + + MOCK(is_bad, is_bad_mock); + + g1 = tor_malloc_zero(sizeof(entry_guard_t)); + g2 = tor_malloc_zero(sizeof(entry_guard_t)); + g3 = tor_malloc_zero(sizeof(entry_guard_t)); + g4 = tor_malloc_zero(sizeof(entry_guard_t)); + + g1->bad_since = 1; //not listed + + guardlist_add(used_guards, g1); + guardlist_add(used_guards, g2); + guardlist_add(used_guards, g3); + guardlist_add(used_guards, g4); + + guard_selection->used_guards = used_guards; + guard_selection->num_primary_guards = 2; + guard_selection->primary_guards = smartlist_new(); + + fill_in_primary_guards(guard_selection); + + smartlist_t *primary_guards = guard_selection->primary_guards; + tt_int_op(2, OP_EQ, smartlist_len(primary_guards)); + tt_ptr_op(g2, OP_EQ, smartlist_get(primary_guards, 0)); + tt_ptr_op(g3, OP_EQ, smartlist_get(primary_guards, 1)); + + done: + UNMOCK(is_bad); + tor_free(g1); + tor_free(g2); + tor_free(g3); + tor_free(g4); + guardlist_free(used_guards); + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_fill_in_sampled_set(void *arg) +{ + guardlist_t *sample = guardlist_new(); + smartlist_t *set = smartlist_new(); + (void) arg; + + MOCK(node_sl_choose_by_bandwidth, node_sl_choose_by_bandwidth_mock); + + smartlist_t *our_nodelist = nodelist_get_list(); + smartlist_add(set, smartlist_get(our_nodelist, 0)); + smartlist_add(set, smartlist_get(our_nodelist, 1)); + smartlist_add(set, smartlist_get(our_nodelist, 2)); + + fill_in_sampled_guard_set(sample, set, 2); + tt_int_op(guardlist_len(sample), OP_EQ, 2); + + done: + UNMOCK(node_sl_choose_by_bandwidth); + smartlist_free(set); + guardlist_free(sample); +} + +static void +test_fill_in_remaining_guards(void *arg) +{ + guard_selection_t *guard_selection = tor_malloc_zero( + sizeof(guard_selection_t)); + guardlist_t *used = guardlist_new(); + guardlist_t *sampled = guardlist_new(); + entry_guard_t *g2 = NULL; + (void) arg; + + smartlist_t *our_nodelist = nodelist_get_list(); + node_t *node1 = smartlist_get(our_nodelist, 0); + node_t *node2 = smartlist_get(our_nodelist, 1); + node_t *node3 = smartlist_get(our_nodelist, 2); + + MOCK(find_guard_by_node, find_guard_by_node_mock); + MOCK(is_live, is_live_mock); + + g2 = entry_guard_new(node2); + guardlist_add(sampled, entry_guard_new(node1)); + guardlist_add(sampled, g2); + guardlist_add(sampled, entry_guard_new(node3)); + guardlist_add(used, g2); + + guard_selection->used_guards = used; + guard_selection->remaining_guards = smartlist_new(); + guard_selection->min_filtered_sample_size = 3; + fill_in_remaining_guards(guard_selection, sampled); + + tt_int_op(smartlist_len(guard_selection->remaining_guards), + OP_EQ, 2); + + entry_guard_t* guard = smartlist_get(guard_selection->remaining_guards, 0); + tt_int_op(strcmp_len(guard->identity, node1->identity, DIGEST_LEN), + OP_EQ, 0); + + guard = smartlist_get(guard_selection->remaining_guards, 1); + tt_int_op(strcmp_len(guard->identity, node3->identity, DIGEST_LEN), + OP_EQ, 0); + + done: + UNMOCK(is_live); + UNMOCK(find_guard_by_node); + tor_free(g2); + guardlist_free(used); + guardlist_free(sampled); + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_state_machine_should_use_new_state_as_current_state(void *arg) +{ + (void) arg; + + guard_selection_t *guard_selection = NULL; + int n_primary_guards = 0; + + MOCK(find_guard_by_node, find_guard_by_node_mock); + MOCK(is_live, is_live_mock); + + guard_selection_ensure(&guard_selection); + choose_entry_guard_algo_start(guard_selection, n_primary_guards); + + tt_int_op(guard_selection->state, OP_EQ, STATE_PRIMARY_GUARDS); + transition_to(guard_selection, STATE_TRY_REMAINING); + tt_int_op(guard_selection->state, OP_EQ, STATE_TRY_REMAINING); + + done: + UNMOCK(is_live); + UNMOCK(find_guard_by_node); + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_NEXT_transitions_to_PRIMARY_GUARDS_and_saves_previous_state(void *arg) +{ + guard_selection_t *guard_selection = tor_malloc_zero( + sizeof(guard_selection_t)); + smartlist_t *primary_guards = smartlist_new(); + guardlist_t *used_guards = guardlist_new(); + smartlist_t *remaining_guards = smartlist_new(); + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + + entry_guard_t *chosen = NULL; + entry_guard_t *g1 = NULL, *g2 = NULL, *g3 = NULL; + (void) arg; + + MOCK(is_live, is_live_mock); + MOCK(is_bad, is_bad_mock); + MOCK(node_sl_choose_by_bandwidth, node_sl_choose_by_bandwidth_mock); + + time_t now = 100; + options->PrimaryGuardsRetryInterval = 3; + + g1 = tor_malloc_zero(sizeof(entry_guard_t)); + g2 = tor_malloc_zero(sizeof(entry_guard_t)); + g3 = tor_malloc_zero(sizeof(entry_guard_t)); + + smartlist_add(primary_guards, g1); + guardlist_add(used_guards, g1); + guardlist_add(used_guards, g3); //used not in primary + + g1->last_attempted = now - 3*60; + g2->last_attempted = now - 10; + + guard_selection->state = STATE_TRY_REMAINING; + guard_selection->used_guards = used_guards; + guard_selection->primary_guards = primary_guards; + guard_selection->remaining_guards = remaining_guards; + + chosen = choose_entry_guard_algo_next(guard_selection, options, now-1); + tt_ptr_op(chosen, OP_EQ, g3); + tt_int_op(guard_selection->state, OP_EQ, STATE_TRY_REMAINING); + + chosen = choose_entry_guard_algo_next(guard_selection, options, now); + tt_int_op(guard_selection->state, OP_EQ, STATE_PRIMARY_GUARDS); + + done: + UNMOCK(node_sl_choose_by_bandwidth); + UNMOCK(is_bad); + UNMOCK(is_live); + tor_free(g1); + tor_free(g2); + tor_free(g3); + guardlist_free(used_guards); + guard_selection_free(guard_selection); + tor_free(guard_selection); + tor_free(options); +} + +static void +test_PRIMARY_GUARDS_returns_PRIMARY_GUARDS_in_order(void *arg) +{ + guard_selection_t *guard_selection = NULL; + entry_guard_t *entry1 = NULL, *entry2 = NULL; + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + (void) arg; + + MOCK(find_guard_by_node, find_guard_by_node_mock); + MOCK(is_bad, is_bad_mock); + MOCK(is_live, is_live_mock); + + int n_primary_guards = 0; + + guard_selection_ensure(&guard_selection); + choose_entry_guard_algo_start(guard_selection, n_primary_guards); + + entry1 = tor_malloc_zero(sizeof(entry_guard_t)); + entry1->is_dir_cache = 1; + entry2 = tor_malloc_zero(sizeof(entry_guard_t)); + entry2->is_dir_cache = 1; + smartlist_add(guard_selection->primary_guards, entry1); + smartlist_add(guard_selection->primary_guards, entry2); + + entry_guard_t *chosen = choose_entry_guard_algo_next( + guard_selection, options, 0); + tt_ptr_op(entry1, OP_EQ, chosen); + chosen = choose_entry_guard_algo_next(guard_selection, options, 0); + tt_ptr_op(entry1, OP_EQ, chosen); + + entry1->unreachable_since = 1; + chosen = choose_entry_guard_algo_next(guard_selection, options, 0); + tt_ptr_op(entry2, OP_EQ, chosen); + + entry1->unreachable_since = 0; + chosen = choose_entry_guard_algo_next(guard_selection, options, 0); + + done: + UNMOCK(is_live); + UNMOCK(is_bad); + UNMOCK(find_guard_by_node); + tor_free(entry1); + tor_free(entry2); + tor_free(options); + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_transitions_to_TRY_REMAINING_when_theres_not_previous_state(void *arg) +{ + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + (void) arg; + + guard_selection_t *guard_selection = NULL; + int n_primary_guards = 0; + + MOCK(is_live, is_live_mock); + MOCK(find_guard_by_node, find_guard_by_node_mock); + + guard_selection_ensure(&guard_selection); + choose_entry_guard_algo_start(guard_selection, n_primary_guards); + + tt_int_op(guard_selection->state, OP_EQ, STATE_PRIMARY_GUARDS); + choose_entry_guard_algo_next(guard_selection, options, 0); + tt_int_op(guard_selection->state, OP_EQ, STATE_TRY_REMAINING); + + done: + UNMOCK(find_guard_by_node); + UNMOCK(is_live); + guard_selection_free(guard_selection); + tor_free(guard_selection); + tor_free(options); +} + +static void +test_PRIMARY_GUARDS_transitions_to_previous_state_when_theres_one(void *arg) +{ + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + (void) arg; + + guard_selection_t *guard_selection = NULL; + int n_primary_guards = 0; + + MOCK(find_guard_by_node, find_guard_by_node_mock); + MOCK(is_live, is_live_mock); + + guard_selection_ensure(&guard_selection); + choose_entry_guard_algo_start(guard_selection, n_primary_guards); + + tt_int_op(guard_selection->state, OP_EQ, STATE_PRIMARY_GUARDS); + guard_selection->previous_state = STATE_TRY_REMAINING; + choose_entry_guard_algo_next(guard_selection, options, 0); + tt_int_op(guard_selection->state, OP_EQ, STATE_TRY_REMAINING); + + done: + UNMOCK(find_guard_by_node); + UNMOCK(is_live); + guard_selection_free(guard_selection); + tor_free(guard_selection); + tor_free(options); +}static void +test_TRY_REMAINING_returns_each_USED_GUARDS_not_in_PRIMARY_GUARDS(void *arg) +{ + entry_guard_t* guard = NULL; + guardlist_t *used_guards = guardlist_new(); + smartlist_t *primary_guards = NULL; + entry_guard_t *g1 = NULL, *g2 = NULL, *g3 = NULL; + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + guard_selection_t *guard_selection = NULL; + (void) arg; + + MOCK(is_bad, is_bad_mock); + MOCK(is_live, is_live_mock); + MOCK(find_guard_by_node, find_guard_by_node_mock); + + g1 = tor_malloc_zero(sizeof(entry_guard_t)); + g2 = tor_malloc_zero(sizeof(entry_guard_t)); + g3 = tor_malloc_zero(sizeof(entry_guard_t)); + + primary_guards = smartlist_new(); + smartlist_add(primary_guards, g1); + + guardlist_add(used_guards, g1); + guardlist_add(used_guards, g2); + guardlist_add(used_guards, g3); + + tt_int_op(is_live(g1), OP_EQ, 1); + tt_int_op(is_live(g2), OP_EQ, 1); + tt_int_op(is_live(g3), OP_EQ, 1); + + guard_selection = tor_malloc_zero(sizeof(guard_selection_t)); + guard_selection->state = STATE_TRY_REMAINING; + guard_selection->used_guards = used_guards; + guard_selection->primary_guards = primary_guards; + + guard = choose_entry_guard_algo_next(guard_selection, options, 0); + tt_ptr_op(guard, OP_EQ, g2); + + done: + UNMOCK(is_live); + UNMOCK(is_bad); + UNMOCK(find_guard_by_node); + tor_free(g1); + tor_free(g2); + tor_free(g3); + guardlist_free(used_guards); + guard_selection_free(guard_selection); + tor_free(guard_selection); + tor_free(options); +} + +static void +test_TRY_REMAINING_returns_each_REMAINING_GUARD_by_bandwidth_weights(void *arg) +{ + guard_selection_t *guard_selection = NULL; + smartlist_t *primary_guards = smartlist_new(); + guardlist_t *used_guards = guardlist_new(); + smartlist_t *remaining_guards = smartlist_new(); + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + entry_guard_t *g1 = NULL; + entry_guard_t *g2 = NULL; + entry_guard_t *g3 = NULL; + (void) arg; + + MOCK(node_sl_choose_by_bandwidth, node_sl_choose_by_bandwidth_mock); + MOCK(is_live, is_live_mock); + + tt_int_op(smartlist_len(get_entry_guards()), OP_EQ, 0); + + smartlist_t *our_nodelist = nodelist_get_list(); + node_t *n1 = smartlist_get(our_nodelist, 0); + node_t *n2 = smartlist_get(our_nodelist, 1); + node_t *n3 = smartlist_get(our_nodelist, 2); + + //entry_guard_get_by_id_digest(node->identity); + g1 = entry_guard_new(n1); + smartlist_add(primary_guards, g1); + guardlist_add(used_guards, g1); + + g2 = entry_guard_new(n2); + g3 = entry_guard_new(n3); + smartlist_add(remaining_guards, g2); + smartlist_add(remaining_guards, g3); + + guard_selection = tor_malloc_zero(sizeof(guard_selection_t)); + guard_selection->state = STATE_TRY_REMAINING; + guard_selection->used_guards = used_guards; + guard_selection->primary_guards = primary_guards; + guard_selection->remaining_guards = remaining_guards; + + entry_guard_t* chosen = choose_entry_guard_algo_next( + guard_selection, options, 0); + tt_ptr_op(chosen, OP_EQ, g2); + + chosen->unreachable_since = 1; + chosen = choose_entry_guard_algo_next(guard_selection, options, 0); + tt_ptr_op(chosen, OP_EQ, g3); + tt_assert(!smartlist_contains(guard_selection->remaining_guards, + g2)); + + chosen->unreachable_since = 1; + chosen = choose_entry_guard_algo_next(guard_selection, options, 0); + tt_ptr_op(chosen, OP_EQ, NULL); + tt_assert(!smartlist_contains(guard_selection->remaining_guards, + g3)); + + done: + UNMOCK(is_live); + UNMOCK(node_sl_choose_by_bandwidth); + tor_free(g1); + tor_free(g2); + tor_free(g3); + guardlist_free(used_guards); + guard_selection_free(guard_selection); + tor_free(guard_selection); + tor_free(options); + remove_all_entry_guards(); +} + +static void +test_TRY_REMAINING_transitions_to_PRIMARY_GUARDS(void *arg) +{ + entry_guard_t* guard = NULL; + guard_selection_t *guard_selection = NULL; + smartlist_t *primary_guards = smartlist_new();; + guardlist_t *used_guards = guardlist_new(); + smartlist_t *remaining_guards = smartlist_new(); + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + (void) arg; + + MOCK(is_live, is_live_mock); + + guard_selection = tor_malloc_zero(sizeof(guard_selection_t)); + guard_selection->state = STATE_TRY_REMAINING; + guard_selection->used_guards = used_guards; + guard_selection->primary_guards = primary_guards; + guard_selection->remaining_guards = remaining_guards; + + guard = choose_entry_guard_algo_next(guard_selection, options, 0); + tt_ptr_op(guard, OP_EQ, NULL); + tt_int_op(guard_selection->state, OP_EQ, STATE_PRIMARY_GUARDS); + + done: + UNMOCK(is_live); + guardlist_free(used_guards); + guard_selection_free(guard_selection); + tor_free(guard_selection); + tor_free(options); +} + +static void +test_choose_entry_guard_algo_should_continue_when_circuit_fails(void *arg) +{ + guard_selection_t *guard_selection = + tor_malloc_zero(sizeof(guard_selection_t)); + int succeeded = 0; + time_t now = time(NULL); + (void) arg; + + int should_continue = choose_entry_guard_algo_should_continue + (guard_selection, succeeded, now); + + tt_int_op(should_continue, OP_EQ, 1); + + done: + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_should_not_continue_when_circuit_succeeds_for_first_time(void *arg) +{ + guard_selection_t *guard_selection = + tor_malloc_zero(sizeof(guard_selection_t)); + int succeeded = 1; + time_t now = time(NULL); + (void) arg; + + int should_continue = choose_entry_guard_algo_should_continue + (guard_selection, succeeded, now); + + tt_int_op(should_continue, OP_EQ, 0); + + done: + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_should_continue_when_last_sucess_was_before_likely_down_interval( + void *arg) +{ + guard_selection_t *guard_selection = + tor_malloc_zero(sizeof(guard_selection_t)); + int succeeded = 1; + time_t now = time(NULL); + (void) arg; + + guard_selection->primary_guards = smartlist_new(); + guard_selection->last_success = now - 301; + int should_continue = choose_entry_guard_algo_should_continue + (guard_selection, succeeded, now); + + tt_int_op(should_continue, OP_EQ, 1); + + done: + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +test_should_not_continue_when_last_success_was_after_likely_down_interval( + void *arg) +{ + guard_selection_t *guard_selection = + tor_malloc_zero(sizeof(guard_selection_t)); + int succeeded = 1; + time_t now = time(NULL); + (void) arg; + + guard_selection->used_guards = guardlist_new(); + guard_selection->last_success = now - 60; + int should_continue = choose_entry_guard_algo_should_continue + (guard_selection, succeeded, now); + + tt_int_op(should_continue, OP_EQ, 0); + + done: + guardlist_free(guard_selection->used_guards); + guard_selection_free(guard_selection); + tor_free(guard_selection); +} + +static void +state_insert_line_helper(config_line_t **next, + smartlist_t *lines) +{ + config_line_t *line; + *next = NULL; + + /* Loop over all the state lines in the smartlist */ + SMARTLIST_FOREACH_BEGIN(lines, const smartlist_t *,state_lines) { + /* Get key and value for each line */ + const char *state_key = smartlist_get(state_lines, 0); + const char *state_value = smartlist_get(state_lines, 1); + + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup(state_key); + tor_asprintf(&line->value, "%s", state_value); + next = &(line->next); + } SMARTLIST_FOREACH_END(state_lines); +} + +static void +state_lines_free(smartlist_t *lines) +{ + SMARTLIST_FOREACH_BEGIN(lines, smartlist_t *, state_lines) { + char *state_key = smartlist_get(state_lines, 0); + char *state_value = smartlist_get(state_lines, 1); + + tor_free(state_key); + tor_free(state_value); + smartlist_free(state_lines); + } SMARTLIST_FOREACH_END(state_lines); + + smartlist_free(lines); +} + +static void +test_used_guards_parse_state(void *arg) +{ + or_state_t *state = or_state_new(); + smartlist_t *used_state_lines = smartlist_new(); + smartlist_t *used_guards = smartlist_new(); + char *msg = NULL; + int retval; + + /* Details of our fake guard node */ + const char *nickname = "hagbard"; + const char *fpr = "B29D536DD1752D542E1FBB3C9CE4449D51298212"; + const char *unlisted_since = "2014-06-08 16:16:50"; + const char *down_since = "2014-06-07 16:16:40"; + const char *tor_version = "0.2.5.3-alpha-dev"; + const char *added_at = get_yesterday_date_str(); + + /* Path bias details of the fake guard */ + const double circ_attempts = 9; + const double circ_successes = 8; + const double successful_closed = 4; + const double collapsed = 2; + const double unusable = 0; + const double timeouts = 1; + + (void) arg; + + { /* Prepare the state entry */ + + /* Prepare the smartlist to hold the key/value of each line */ + smartlist_t *state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "UsedGuard"); + smartlist_add_asprintf(state_line, "%s %s %s", nickname, fpr, "DirCache"); + smartlist_add(used_state_lines, state_line); + + state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "UsedGuardDownSince"); + smartlist_add_asprintf(state_line, "%s", down_since); + smartlist_add(used_state_lines, state_line); + + state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "UsedGuardUnlistedSince"); + smartlist_add_asprintf(state_line, "%s", unlisted_since); + smartlist_add(used_state_lines, state_line); + + state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "UsedGuardAddedBy"); + smartlist_add_asprintf(state_line, "%s %s %s", fpr, tor_version, added_at); + smartlist_add(used_state_lines, state_line); + + state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "USedGuardPathBias"); + smartlist_add_asprintf(state_line, "%f %f %f %f %f %f", + circ_attempts, circ_successes, successful_closed, + collapsed, unusable, timeouts); + smartlist_add(used_state_lines, state_line); + } + + /* Inject our lines in the state */ + state_insert_line_helper(&state->UsedGuards, used_state_lines); + + /* Parse state */ + retval = used_guards_parse_state(state, used_guards, &msg); + tt_int_op(retval, OP_GE, 0); + + tt_int_op(smartlist_len(used_guards), OP_EQ, 1); + + { /* Test the entry guard structure */ + char hex_digest[1024]; + char str_time[1024]; + + const entry_guard_t *e = smartlist_get(used_guards, 0); + tt_str_op(e->nickname, OP_EQ, nickname); /* Verify nickname */ + + base16_encode(hex_digest, sizeof(hex_digest), + e->identity, DIGEST_LEN); + tt_str_op(hex_digest, OP_EQ, fpr); /* Verify fingerprint */ + + tt_assert(e->is_dir_cache); /* Verify dirness */ + + tt_str_op(e->chosen_by_version, OP_EQ, tor_version); /* Verify version */ + + tt_assert(e->bad_since); /* Verify bad_since timestamp */ + format_iso_time(str_time, e->bad_since); + tt_str_op(str_time, OP_EQ, unlisted_since); + + tt_assert(e->unreachable_since); /* Verify unreachable_since timestamp */ + format_iso_time(str_time, e->unreachable_since); + tt_str_op(str_time, OP_EQ, down_since); + + /* XXX tt_double_op doesn't support equality. Cast to int for now. */ + tt_int_op((int)e->circ_attempts, OP_EQ, (int)circ_attempts); + tt_int_op((int)e->circ_successes, OP_EQ, (int)circ_successes); + tt_int_op((int)e->successful_circuits_closed, OP_EQ, + (int)successful_closed); + tt_int_op((int)e->timeouts, OP_EQ, (int)timeouts); + tt_int_op((int)e->collapsed_circuits, OP_EQ, (int)collapsed); + tt_int_op((int)e->unusable_circuits, OP_EQ, (int)unusable); + + /* The rest should be unset */ + tt_assert(!e->made_contact); + tt_assert(!e->can_retry); + tt_assert(!e->path_bias_noticed); + tt_assert(!e->path_bias_warned); + tt_assert(!e->path_bias_extreme); + tt_assert(!e->path_bias_disabled); + tt_assert(!e->path_bias_use_noticed); + tt_assert(!e->path_bias_use_extreme); + tt_assert(!e->last_attempted); + } + + done: + state_lines_free(used_state_lines); + SMARTLIST_FOREACH(used_guards, entry_guard_t *, e, entry_guard_free(e)); + smartlist_free(used_guards); + or_state_free(state); + tor_free(msg); +} + +static void +test_used_guards_parse_state_backward_compatible(void *arg) +{ + or_state_t *state = or_state_new(); + smartlist_t *entry_state_lines = smartlist_new(); + smartlist_t *used_guards = smartlist_new(); + char *msg = NULL; + int retval; + + /* Details of our fake guard node */ + const char *nickname = "hagbard"; + const char *fpr = "B29D536DD1752D542E1FBB3C9CE4449D51298212"; + const char *tor_version = "0.2.5.3-alpha-dev"; + const char *added_at = get_yesterday_date_str(); + const char *unlisted_since = "2014-06-08 16:16:50"; + const char *down_since = "2014-06-07 16:16:40"; + + (void) arg; + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + MOCK(get_or_state, get_or_state_replacement); + + { /* Prepare backwards compatible state entry */ + smartlist_t *state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "EntryGuard"); + smartlist_add_asprintf(state_line, "%s %s %s", nickname, fpr, "DirCache"); + smartlist_add(entry_state_lines, state_line); + + state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "EntryGuardDownSince"); + smartlist_add_asprintf(state_line, "%s", down_since); + smartlist_add(entry_state_lines, state_line); + + state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "EntryGuardUnlistedSince"); + smartlist_add_asprintf(state_line, "%s", unlisted_since); + smartlist_add(entry_state_lines, state_line); + + state_line = smartlist_new(); + smartlist_add_asprintf(state_line, "EntryGuardAddedBy"); + smartlist_add_asprintf(state_line, "%s %s %s", fpr, tor_version, added_at); + smartlist_add(entry_state_lines, state_line); + } + + /* Inject our lines in the state */ + state_insert_line_helper(&state->EntryGuards, entry_state_lines); + + /* Parse state */ + retval = entry_guards_parse_state_backward(state, used_guards, &msg); + tt_int_op(retval, OP_GE, 0); + + tt_int_op(smartlist_len(used_guards), OP_EQ, 1); + + { /* Test the entry guard structure */ + char hex_digest[1024]; + char str_time[1024]; + + const entry_guard_t *e = smartlist_get(used_guards, 0); + tt_str_op(e->nickname, OP_EQ, nickname); /* Verify nickname */ + + base16_encode(hex_digest, sizeof(hex_digest), + e->identity, DIGEST_LEN); + tt_str_op(hex_digest, OP_EQ, fpr); /* Verify fingerprint */ + + tt_assert(e->is_dir_cache); /* Verify dirness */ + + tt_str_op(e->chosen_by_version, OP_EQ, tor_version); /* Verify version */ + + tt_assert(e->bad_since); /* Verify bad_since timestamp */ + format_iso_time(str_time, e->bad_since); + tt_str_op(str_time, OP_EQ, unlisted_since); + + tt_assert(e->unreachable_since); /* Verify unreachable_since timestamp */ + format_iso_time(str_time, e->unreachable_since); + tt_str_op(str_time, OP_EQ, down_since); + + /* The rest should be unset */ + tt_assert(!e->made_contact); + tt_assert(!e->can_retry); + tt_assert(!e->path_bias_noticed); + tt_assert(!e->path_bias_warned); + tt_assert(!e->path_bias_extreme); + tt_assert(!e->path_bias_disabled); + tt_assert(!e->path_bias_use_noticed); + tt_assert(!e->path_bias_use_extreme); + tt_assert(!e->last_attempted); + } + + done: + UNMOCK(get_or_state); + tor_free(dummy_state); + state_lines_free(entry_state_lines); + SMARTLIST_FOREACH(used_guards, entry_guard_t *, e, entry_guard_free(e)); + smartlist_free(used_guards); + or_state_free(state); + tor_free(msg); +} + +static void +test_should_filter_out_not_is_live_guard(void *arg) +{ + guardlist_t *guards = guardlist_new(); + entry_guard_t *g1 = tor_malloc_zero(sizeof(entry_guard_t)); + entry_guard_t *g2 = tor_malloc_zero(sizeof(entry_guard_t)); + (void) arg; + + g1->unreachable_since = 1; + guardlist_add(guards, g1); + guardlist_add(guards, g2); + + MOCK(find_guard_by_node, find_guard_by_node_mock); + MOCK(is_live, is_live_mock); + + smartlist_t *filteredset = filter_set(guards, smartlist_new(), 1, 1); + tt_int_op(smartlist_len(filteredset), OP_EQ, 1); + + done: + UNMOCK(is_live); + UNMOCK(find_guard_by_node); + tor_free(guards); + tor_free(filteredset); +} + +static void +test_should_expand_when_filtered_guards_lower_than_min(void *arg) +{ + guardlist_t *sampled_guards = guardlist_new(); + entry_guard_t *g1 = tor_malloc_zero(sizeof(entry_guard_t)); + entry_guard_t *g2 = tor_malloc_zero(sizeof(entry_guard_t)); + smartlist_t *all_guards = smartlist_new(); + (void) arg; + + MOCK(find_guard_by_node, find_guard_by_node_mock); + MOCK(is_live, is_live_mock); + + guardlist_add(sampled_guards, g1); + guardlist_add(sampled_guards, g2); + + int min_sample_size = 3; + + smartlist_t *filtered = filter_set(sampled_guards, all_guards, + min_sample_size, 2 * guardlist_len(sampled_guards)); + + tt_int_op(smartlist_len(filtered), OP_EQ, 3); + + done: + UNMOCK(is_live); + UNMOCK(find_guard_by_node); + tor_free(sampled_guards); + smartlist_free(filtered); + smartlist_free(all_guards); +} + +static void +test_should_not_expand_when_sampled_guards_reaches_max(void *arg) +{ + guardlist_t *guards = guardlist_new(); + entry_guard_t *g1 = tor_malloc_zero(sizeof(entry_guard_t)); + entry_guard_t *g2 = tor_malloc_zero(sizeof(entry_guard_t)); + smartlist_t *all_guards = smartlist_new(); + (void) arg; + + MOCK(find_guard_by_node, find_guard_by_node_mock); + MOCK(is_live, is_live_mock); + + guardlist_add(guards, g1); + guardlist_add(guards, g2); + smartlist_add(all_guards, tor_malloc_zero(sizeof(entry_guard_t))); + + int min_sample_size = 3; + int max_sample_size = 1; + + smartlist_t *filtered = filter_set + (guards, all_guards, min_sample_size, max_sample_size); + + tt_assert(filtered == NULL); + + done: + UNMOCK(is_live); + UNMOCK(find_guard_by_node); + tor_free(guards); + smartlist_free(filtered); + smartlist_free(all_guards); +} + +struct testcase_t entrynodes_new_tests[] = { + { "used_guards_parse_state", + test_used_guards_parse_state, + 0, NULL, NULL }, + { "used_guards_parse_state_backward_compatible", + test_used_guards_parse_state_backward_compatible, + 0, NULL, NULL }, + { "state_machine_init", + test_STATE_PRIMARY_GUARD_is_initial_state, + 0, NULL, NULL }, + { "next_primary_guard", + test_next_primary_guard, + TT_FORK, &fake_network, NULL }, + { "fill_in_primary_guards", + test_fill_in_primary_guards, + TT_FORK, &fake_network, NULL }, + { "fill_in_sampled_set", + test_fill_in_sampled_set, + TT_FORK, &fake_network, NULL }, + { "fill_in_remaining_guards", + test_fill_in_remaining_guards, + TT_FORK, &fake_network, NULL }, + { "state_machine_transitions_to", + test_state_machine_should_use_new_state_as_current_state, + 0, NULL, NULL }, + { "NEXT_transitions_to_STATE_PRIMARY_GUARDS_and_saves_previous_state", + test_NEXT_transitions_to_PRIMARY_GUARDS_and_saves_previous_state, + 0, NULL, NULL }, + { "STATE_PRIMARY_GUARDS_returns_PRIMARY_GUARDS_in_order", + test_PRIMARY_GUARDS_returns_PRIMARY_GUARDS_in_order, + 0, NULL, NULL }, + { "STATE_PRIMARY_GUARDS_transitions_to_previous_state", + test_PRIMARY_GUARDS_transitions_to_previous_state_when_theres_one, + 0, NULL, NULL }, + { "transitions_to_TRY_REMAINING_when_theres_not_previous_state", + test_transitions_to_TRY_REMAINING_when_theres_not_previous_state, + 0, NULL, NULL }, + { "STATE_TRY_REMAINING_returns_USED_NOT_PRIMARY", + test_TRY_REMAINING_returns_each_USED_GUARDS_not_in_PRIMARY_GUARDS, + 0, NULL, NULL }, + { "STATE_TRY_REMAINING_returns_REMAINING_GUARD", + test_TRY_REMAINING_returns_each_REMAINING_GUARD_by_bandwidth_weights, + TT_FORK, &fake_network, NULL }, + { "STATE_TRY_REMAINING_transitions_to_STATE_PRIMARY_GUARDS", + test_TRY_REMAINING_transitions_to_PRIMARY_GUARDS, + 0, NULL, NULL }, + { "should_continue_when_circuit_fails", + test_choose_entry_guard_algo_should_continue_when_circuit_fails, + 0, NULL, NULL }, + { "should_not_continue_when_circuit_succeeds_for_first_time", + test_should_not_continue_when_circuit_succeeds_for_first_time, + 0, NULL, NULL }, + { "should_continue_when_last_sucess_was_before_likely_down_interval", + test_should_continue_when_last_sucess_was_before_likely_down_interval, + 0, NULL, NULL }, + { "should_not_continue_when_last_success_was_after_likely_down_interval", + test_should_not_continue_when_last_success_was_after_likely_down_interval, + 0, NULL, NULL }, + { "should_filter_out_not_is_live_guard", + test_should_filter_out_not_is_live_guard, + 0, NULL, NULL }, + { "should_expand_when_filtered_guards_lower_than_min", + test_should_expand_when_filtered_guards_lower_than_min, + 0, NULL, NULL }, + { "test_should_not_expand_when_sampled_guards_reaches_max", + test_should_not_expand_when_sampled_guards_reaches_max, + 0, NULL, NULL }, + + END_OF_TESTCASES +}; +