diff options
author | Nick Mathewson <nickm@torproject.org> | 2015-12-15 12:57:57 -0500 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2015-12-15 12:57:57 -0500 |
commit | a7d44731d9ac831cd68f34ac640b50cdde3a60af (patch) | |
tree | 88a149ac07f30c0af5be47cc7d8f9ed9d57db244 | |
parent | 54433993c7c84dc9af878ebaf8dd1deae8c595e3 (diff) | |
parent | d72af1085a1d9ed6f2c9bdb5e9c85eba991db842 (diff) | |
download | tor-a7d44731d9ac831cd68f34ac640b50cdde3a60af.tar.gz tor-a7d44731d9ac831cd68f34ac640b50cdde3a60af.zip |
Merge remote-tracking branch 'teor/feature4483-v10-squashed'
-rw-r--r-- | changes/bug4483-multiple-consensus-downloads | 9 | ||||
-rw-r--r-- | doc/tor.1.txt | 61 | ||||
-rw-r--r-- | src/common/torint.h | 26 | ||||
-rw-r--r-- | src/or/config.c | 76 | ||||
-rw-r--r-- | src/or/connection.c | 230 | ||||
-rw-r--r-- | src/or/connection.h | 59 | ||||
-rw-r--r-- | src/or/directory.c | 479 | ||||
-rw-r--r-- | src/or/directory.h | 27 | ||||
-rw-r--r-- | src/or/entrynodes.c | 2 | ||||
-rw-r--r-- | src/or/main.c | 24 | ||||
-rw-r--r-- | src/or/networkstatus.c | 358 | ||||
-rw-r--r-- | src/or/networkstatus.h | 8 | ||||
-rw-r--r-- | src/or/or.h | 120 | ||||
-rw-r--r-- | src/or/routerlist.c | 37 | ||||
-rw-r--r-- | src/or/routerlist.h | 1 | ||||
-rw-r--r-- | src/test/Makefile.nmake | 3 | ||||
-rw-r--r-- | src/test/include.am | 1 | ||||
-rw-r--r-- | src/test/test.c | 2 | ||||
-rw-r--r-- | src/test/test_config.c | 99 | ||||
-rw-r--r-- | src/test/test_connection.c | 757 | ||||
-rw-r--r-- | src/test/test_dir.c | 431 | ||||
-rw-r--r-- | src/test/test_routerlist.c | 4 |
22 files changed, 2621 insertions, 193 deletions
diff --git a/changes/bug4483-multiple-consensus-downloads b/changes/bug4483-multiple-consensus-downloads new file mode 100644 index 0000000000..23d22a89c4 --- /dev/null +++ b/changes/bug4483-multiple-consensus-downloads @@ -0,0 +1,9 @@ + o Major features (consensus downloads): + - Schedule multiple in-progress consensus downloads during client + bootstrap. Use the first one that starts downloading, close the + rest. This reduces failures when authorities are slow or down. + With #15775, it reduces failures due to fallback churn. + Implements #4483 (reduce failures when authorities are down). + Patch by "teor". + Implements IPv4 portions of proposal #210 by "mikeperry" and + "teor". diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 7f8d9b6cbf..d8802bf104 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -360,7 +360,11 @@ GENERAL OPTIONS [[FallbackDir]] **FallbackDir** __address__:__port__ orport=__port__ id=__fingerprint__ [weight=__num__]:: When we're unable to connect to any directory cache for directory info - (usually because we don't know about any yet) we try a FallbackDir. + (usually because we don't know about any yet) we try a directory authority. + Clients also simultaneously try a FallbackDir, to avoid hangs on client + startup if a directory authority is down. Clients retry FallbackDirs more + often than directory authorities, to reduce the load on the directory + authorities. By default, the directory authorities are also FallbackDirs. Specifying a FallbackDir replaces Tor's default hard-coded FallbackDirs (if any). @@ -2287,10 +2291,18 @@ The following options are used for running a testing Tor network. TestingClientDownloadSchedule 0, 0, 5, 10, 15, 20, 30, 60 TestingServerConsensusDownloadSchedule 0, 0, 5, 10, 15, 20, 30, 60 TestingClientConsensusDownloadSchedule 0, 0, 5, 10, 15, 20, 30, 60 + TestingClientBootstrapConsensusAuthorityDownloadSchedule 0, 2, + 4 (for 40 seconds), 8, 16, 32, 60 + TestingClientBootstrapConsensusFallbackDownloadSchedule 0, 1, + 4 (for 40 seconds), 8, 16, 32, 60 + TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule 0, 1, + 4 (for 40 seconds), 8, 16, 32, 60 TestingBridgeDownloadSchedule 60, 30, 30, 60 TestingClientMaxIntervalWithoutRequest 5 seconds TestingDirConnectionMaxStall 30 seconds TestingConsensusMaxDownloadTries 80 + TestingClientBootstrapConsensusMaxDownloadTries 80 + TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries 80 TestingDescriptorMaxDownloadTries 80 TestingMicrodescMaxDownloadTries 80 TestingCertMaxDownloadTries 80 @@ -2351,6 +2363,36 @@ The following options are used for running a testing Tor network. requires that **TestingTorNetwork** is set. (Default: 0, 0, 60, 300, 600, 1800, 3600, 3600, 3600, 10800, 21600, 43200) +[[TestingClientBootstrapConsensusAuthorityDownloadSchedule]] **TestingClientBootstrapConsensusAuthorityDownloadSchedule** __N__,__N__,__...__:: + Schedule for when clients should download consensuses from authorities if + they are bootstrapping (that is, they don't have a usable, reasonably live + consensus). Only used by clients fetching from a list of fallback + directory mirrors. This schedule is advanced by (potentially concurrent) + connection attempts, unlike other schedules, which are advanced by + connection failures. Changing this schedule requires that + **TestingTorNetwork** is set. (Default: 10, 11, 3600, 10800, 25200, 54000, + 111600, 262800) + +[[TestingClientBootstrapConsensusFallbackDownloadSchedule]] **TestingClientBootstrapConsensusFallbackDownloadSchedule** __N__,__N__,__...__:: + Schedule for when clients should download consensuses from fallback + directory mirrors if they are bootstrapping (that is, they don't have a + usable, reasonably live consensus). Only used by clients fetching from a + list of fallback directory mirrors. This schedule is advanced by + (potentially concurrent) connection attempts, unlike other schedules, which + are advanced by connection failures. Changing this schedule requires that + **TestingTorNetwork** is set. (Default: 0, 1, 4, 11, 3600, 10800, 25200, + 54000, 111600, 262800) + +[[TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule]] **TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule** __N__,__N__,__...__:: + Schedule for when clients should download consensuses from authorities if + they are bootstrapping (that is, they don't have a usable, reasonably live + consensus). Only used by clients which don't have or won't fetch from a + list of fallback directory mirrors. This schedule is advanced by + (potentially concurrent) connection attempts, unlike other schedules, + which are advanced by connection failures. Changing this schedule requires + that **TestingTorNetwork** is set. (Default: 0, 3, 7, 3600, 10800, 25200, + 54000, 111600, 262800) + [[TestingBridgeDownloadSchedule]] **TestingBridgeDownloadSchedule** __N__,__N__,__...__:: Schedule for when clients should download bridge descriptors. Changing this requires that **TestingTorNetwork** is set. (Default: 3600, 900, 900, 3600) @@ -2367,9 +2409,24 @@ The following options are used for running a testing Tor network. 5 minutes) [[TestingConsensusMaxDownloadTries]] **TestingConsensusMaxDownloadTries** __NUM__:: - Try this often to download a consensus before giving up. Changing + Try this many times to download a consensus before giving up. Changing this requires that **TestingTorNetwork** is set. (Default: 8) +[[TestingClientBootstrapConsensusMaxDownloadTries]] **TestingClientBootstrapConsensusMaxDownloadTries** __NUM__:: + Try this many times to download a consensus while bootstrapping using + fallback directory mirrors before giving up. Changing this requires that + **TestingTorNetwork** is set. (Default: 7) + +[[TestingClientBootstrapConsensusMaxDownloadTries]] **TestingClientBootstrapConsensusMaxDownloadTries** __NUM__:: + Try this many times to download a consensus while bootstrapping using + only authorities before giving up. Changing this requires that + **TestingTorNetwork** is set. (Default: 4) + +[[TestingClientBootstrapConsensusMaxInProgressTries]] **TestingClientBootstrapConsensusMaxInProgressTries** __NUM__:: + Try this many simultaneous connections to download a consensus before + waiting for one to complete, timeout, or error out. Changing this + requires that **TestingTorNetwork** is set. (Default: 4) + [[TestingDescriptorMaxDownloadTries]] **TestingDescriptorMaxDownloadTries** __NUM__:: Try this often to download a server descriptor before giving up. Changing this requires that **TestingTorNetwork** is set. (Default: 8) diff --git a/src/common/torint.h b/src/common/torint.h index 6171700898..418fe0fabf 100644 --- a/src/common/torint.h +++ b/src/common/torint.h @@ -336,6 +336,32 @@ typedef uint32_t uintptr_t; #endif /* time_t_is_signed */ #endif /* ifndef(TIME_MAX) */ +#ifndef TIME_MIN + +#ifdef TIME_T_IS_SIGNED + +#if (SIZEOF_TIME_T == SIZEOF_INT) +#define TIME_MIN ((time_t)INT_MIN) +#elif (SIZEOF_TIME_T == SIZEOF_LONG) +#define TIME_MIN ((time_t)LONG_MIN) +#elif (SIZEOF_TIME_T == 8) +#define TIME_MIN ((time_t)INT64_MIN) +#else +#error "Can't define (signed) TIME_MIN" +#endif + +#else +/* Unsigned case */ +#if (SIZEOF_TIME_T == 4) +#define TIME_MIN ((time_t)UINT32_MIN) +#elif (SIZEOF_TIME_T == 8) +#define TIME_MIN ((time_t)UINT64_MIN) +#else +#error "Can't define (unsigned) TIME_MIN" +#endif +#endif /* time_t_is_signed */ +#endif /* ifndef(TIME_MIN) */ + #ifndef SIZE_MAX #if (SIZEOF_SIZE_T == 4) #define SIZE_MAX UINT32_MAX diff --git a/src/or/config.c b/src/or/config.c index 04f01dc8c6..86c3125cbb 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -476,10 +476,40 @@ static config_var_t option_vars_[] = { V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 60, " "300, 600, 1800, 3600, 3600, 3600, " "10800, 21600, 43200"), + /* With the TestingClientBootstrapConsensus*Download* below: + * Clients with only authorities will try: + * - 3 authorities over 10 seconds, then wait 60 minutes. + * Clients with authorities and fallbacks will try: + * - 2 authorities and 4 fallbacks over 21 seconds, then wait 60 minutes. + * Clients will also retry when an application request arrives. + * After a number of failed reqests, clients retry every 3 days + 1 hour. + * + * Clients used to try 2 authorities over 10 seconds, then wait for + * 60 minutes or an application request. + * + * When clients have authorities and fallbacks available, they use these + * schedules: (we stagger the times to avoid thundering herds) */ + V(TestingClientBootstrapConsensusAuthorityDownloadSchedule, CSV_INTERVAL, + "10, 11, 3600, 10800, 25200, 54000, 111600, 262800" /* 3 days + 1 hour */), + V(TestingClientBootstrapConsensusFallbackDownloadSchedule, CSV_INTERVAL, + "0, 1, 4, 11, 3600, 10800, 25200, 54000, 111600, 262800"), + /* When clients only have authorities available, they use this schedule: */ + V(TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule, CSV_INTERVAL, + "0, 3, 7, 3600, 10800, 25200, 54000, 111600, 262800"), + /* We don't want to overwhelm slow networks (or mirrors whose replies are + * blocked), but we also don't want to fail if only some mirrors are + * blackholed. Clients will try 3 directories simultaneously. + * (Relays never use simultaneous connections.) */ + V(TestingClientBootstrapConsensusMaxInProgressTries, UINT, "3"), V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "3600, 900, 900, 3600"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"), V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"), V(TestingConsensusMaxDownloadTries, UINT, "8"), + /* Since we try connections rapidly and simultaneously, we can afford + * to give up earlier. (This protects against overloading directories.) */ + V(TestingClientBootstrapConsensusMaxDownloadTries, UINT, "7"), + /* We want to give up much earlier if we're only using authorities. */ + V(TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries, UINT, "4"), V(TestingDescriptorMaxDownloadTries, UINT, "8"), V(TestingMicrodescMaxDownloadTries, UINT, "8"), V(TestingCertMaxDownloadTries, UINT, "8"), @@ -526,10 +556,18 @@ static const config_var_t testing_tor_network_defaults[] = { "15, 20, 30, 60"), V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " "15, 20, 30, 60"), + V(TestingClientBootstrapConsensusAuthorityDownloadSchedule, CSV_INTERVAL, + "0, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), + V(TestingClientBootstrapConsensusFallbackDownloadSchedule, CSV_INTERVAL, + "0, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), + V(TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule, CSV_INTERVAL, + "0, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "60, 30, 30, 60"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "5 seconds"), V(TestingDirConnectionMaxStall, INTERVAL, "30 seconds"), V(TestingConsensusMaxDownloadTries, UINT, "80"), + V(TestingClientBootstrapConsensusMaxDownloadTries, UINT, "80"), + V(TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries, UINT, "80"), V(TestingDescriptorMaxDownloadTries, UINT, "80"), V(TestingMicrodescMaxDownloadTries, UINT, "80"), V(TestingCertMaxDownloadTries, UINT, "80"), @@ -3758,10 +3796,16 @@ options_validate(or_options_t *old_options, or_options_t *options, CHECK_DEFAULT(TestingClientDownloadSchedule); CHECK_DEFAULT(TestingServerConsensusDownloadSchedule); CHECK_DEFAULT(TestingClientConsensusDownloadSchedule); + CHECK_DEFAULT(TestingClientBootstrapConsensusAuthorityDownloadSchedule); + CHECK_DEFAULT(TestingClientBootstrapConsensusFallbackDownloadSchedule); + CHECK_DEFAULT(TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule); CHECK_DEFAULT(TestingBridgeDownloadSchedule); CHECK_DEFAULT(TestingClientMaxIntervalWithoutRequest); CHECK_DEFAULT(TestingDirConnectionMaxStall); CHECK_DEFAULT(TestingConsensusMaxDownloadTries); + CHECK_DEFAULT(TestingClientBootstrapConsensusMaxDownloadTries); + CHECK_DEFAULT(TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries); + CHECK_DEFAULT(TestingClientBootstrapConsensusMaxInProgressTries); CHECK_DEFAULT(TestingDescriptorMaxDownloadTries); CHECK_DEFAULT(TestingMicrodescMaxDownloadTries); CHECK_DEFAULT(TestingCertMaxDownloadTries); @@ -3836,11 +3880,41 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->TestingConsensusMaxDownloadTries < 2) { - REJECT("TestingConsensusMaxDownloadTries must be greater than 1."); + REJECT("TestingConsensusMaxDownloadTries must be greater than 2."); } else if (options->TestingConsensusMaxDownloadTries > 800) { COMPLAIN("TestingConsensusMaxDownloadTries is insanely high."); } + if (options->TestingClientBootstrapConsensusMaxDownloadTries < 2) { + REJECT("TestingClientBootstrapConsensusMaxDownloadTries must be greater " + "than 2." + ); + } else if (options->TestingClientBootstrapConsensusMaxDownloadTries > 800) { + COMPLAIN("TestingClientBootstrapConsensusMaxDownloadTries is insanely " + "high."); + } + + if (options->TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries + < 2) { + REJECT("TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries must " + "be greater than 2." + ); + } else if ( + options->TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries + > 800) { + COMPLAIN("TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries is " + "insanely high."); + } + + if (options->TestingClientBootstrapConsensusMaxInProgressTries < 1) { + REJECT("TestingClientBootstrapConsensusMaxInProgressTries must be greater " + "than 0."); + } else if (options->TestingClientBootstrapConsensusMaxInProgressTries + > 100) { + COMPLAIN("TestingClientBootstrapConsensusMaxInProgressTries is insanely " + "high."); + } + if (options->TestingDescriptorMaxDownloadTries < 2) { REJECT("TestingDescriptorMaxDownloadTries must be greater than 1."); } else if (options->TestingDescriptorMaxDownloadTries > 800) { diff --git a/src/or/connection.c b/src/or/connection.c index bff994d385..7df02b538c 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1618,13 +1618,18 @@ connection_init_accepted_conn(connection_t *conn, return 0; } -static int -connection_connect_sockaddr(connection_t *conn, +/** Take conn, make a nonblocking socket; try to connect to + * sa, binding to bindaddr if sa is not localhost. If fail, return -1 and if + * applicable put your best guess about errno into *<b>socket_error</b>. + * If connected return 1, if EAGAIN return 0. + */ +MOCK_IMPL(STATIC int, +connection_connect_sockaddr,(connection_t *conn, const struct sockaddr *sa, socklen_t sa_len, const struct sockaddr *bindaddr, socklen_t bindaddr_len, - int *socket_error) + int *socket_error)) { tor_socket_t s; int inprogress = 0; @@ -4222,6 +4227,19 @@ connection_write_to_buf_impl_,(const char *string, size_t len, } } +/** Return a connection_t * from get_connection_array() that satisfies test on + * var, and that is not marked for close. */ +#define CONN_GET_TEMPLATE(var, test) \ + STMT_BEGIN \ + smartlist_t *conns = get_connection_array(); \ + SMARTLIST_FOREACH(conns, connection_t *, var, \ + { \ + if (var && (test) && !var->marked_for_close) \ + return var; \ + }); \ + return NULL; \ + STMT_END + /** Return a connection with given type, address, port, and purpose; * or NULL if no such connection exists (or if all such connections are marked * for close). */ @@ -4230,17 +4248,11 @@ connection_get_by_type_addr_port_purpose(int type, const tor_addr_t *addr, uint16_t port, int purpose) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type == type && + CONN_GET_TEMPLATE(conn, + (conn->type == type && tor_addr_eq(&conn->addr, addr) && conn->port == port && - conn->purpose == purpose && - !conn->marked_for_close) - return conn; - }); - return NULL; + conn->purpose == purpose)); } /** Return the stream with id <b>id</b> if it is not already marked for @@ -4249,13 +4261,7 @@ connection_get_by_type_addr_port_purpose(int type, connection_t * connection_get_by_global_id(uint64_t id) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->global_identifier == id) - return conn; - }); - return NULL; + CONN_GET_TEMPLATE(conn, conn->global_identifier == id); } /** Return a connection of type <b>type</b> that is not marked for close. @@ -4263,13 +4269,7 @@ connection_get_by_global_id(uint64_t id) connection_t * connection_get_by_type(int type) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type == type && !conn->marked_for_close) - return conn; - }); - return NULL; + CONN_GET_TEMPLATE(conn, conn->type == type); } /** Return a connection of type <b>type</b> that is in state <b>state</b>, @@ -4278,13 +4278,7 @@ connection_get_by_type(int type) connection_t * connection_get_by_type_state(int type, int state) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type == type && conn->state == state && !conn->marked_for_close) - return conn; - }); - return NULL; + CONN_GET_TEMPLATE(conn, conn->type == type && conn->state == state); } /** Return a connection of type <b>type</b> that has rendquery equal @@ -4295,55 +4289,142 @@ connection_t * connection_get_by_type_state_rendquery(int type, int state, const char *rendquery) { - smartlist_t *conns = get_connection_array(); - tor_assert(type == CONN_TYPE_DIR || type == CONN_TYPE_AP || type == CONN_TYPE_EXIT); tor_assert(rendquery); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - if (conn->type == type && - !conn->marked_for_close && - (!state || state == conn->state)) { - if (type == CONN_TYPE_DIR && + CONN_GET_TEMPLATE(conn, + (conn->type == type && + (!state || state == conn->state)) && + ( + (type == CONN_TYPE_DIR && TO_DIR_CONN(conn)->rend_data && !rend_cmp_service_ids(rendquery, TO_DIR_CONN(conn)->rend_data->onion_address)) - return conn; - else if (CONN_IS_EDGE(conn) && + || + (CONN_IS_EDGE(conn) && TO_EDGE_CONN(conn)->rend_data && !rend_cmp_service_ids(rendquery, TO_EDGE_CONN(conn)->rend_data->onion_address)) - return conn; - } - } SMARTLIST_FOREACH_END(conn); - return NULL; + )); } +#define CONN_FIRST_AND_FREE_TEMPLATE(sl) \ + STMT_BEGIN \ + if (smartlist_len(sl) > 0) { \ + void *first_item = smartlist_get(sl, 0); \ + smartlist_free(sl); \ + return first_item; \ + } else { \ + smartlist_free(sl); \ + return NULL; \ + } \ + STMT_END + + /** Return a directory connection (if any one exists) that is fetching - * the item described by <b>state</b>/<b>resource</b> */ + * the item described by <b>purpose</b>/<b>resource</b>, otherwise return NULL. + */ dir_connection_t * -connection_dir_get_by_purpose_and_resource(int purpose, +connection_dir_get_by_purpose_and_resource( + int purpose, const char *resource) { - smartlist_t *conns = get_connection_array(); + smartlist_t *conns = connection_dir_list_by_purpose_and_resource( + purpose, + resource); + CONN_FIRST_AND_FREE_TEMPLATE(conns); +} - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - dir_connection_t *dirconn; - if (conn->type != CONN_TYPE_DIR || conn->marked_for_close || - conn->purpose != purpose) - continue; - dirconn = TO_DIR_CONN(conn); - if (dirconn->requested_resource == NULL) { - if (resource == NULL) - return dirconn; - } else if (resource) { - if (0 == strcmp(resource, dirconn->requested_resource)) - return dirconn; - } - } SMARTLIST_FOREACH_END(conn); +/** Return a new smartlist of dir_connection_t * from get_connection_array() + * that satisfy conn_test on connection_t *conn_var, and dirconn_test on + * dir_connection_t *dirconn_var. conn_var must be of CONN_TYPE_DIR and not + * marked for close to be included in the list. */ +#define DIR_CONN_LIST_TEMPLATE(conn_var, conn_test, \ + dirconn_var, dirconn_test) \ + STMT_BEGIN \ + smartlist_t *conns = get_connection_array(); \ + smartlist_t *dir_conns = smartlist_new(); \ + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn_var) { \ + if (conn_var && (conn_test) \ + && conn_var->type == CONN_TYPE_DIR \ + && !conn_var->marked_for_close) { \ + dir_connection_t *dirconn_var = TO_DIR_CONN(conn_var); \ + if (dirconn_var && (dirconn_test)) { \ + smartlist_add(dir_conns, dirconn_var); \ + } \ + } \ + } SMARTLIST_FOREACH_END(conn_var); \ + return dir_conns; \ + STMT_END + +/** Return a list of directory connections that are fetching the item + * described by <b>purpose</b>/<b>resource</b>. If there are none, + * return an empty list. This list must be freed using smartlist_free, + * but the pointers in it must not be freed. + * Note that this list should not be cached, as the pointers in it can be + * freed if their connections close. */ +smartlist_t * +connection_dir_list_by_purpose_and_resource( + int purpose, + const char *resource) +{ + DIR_CONN_LIST_TEMPLATE(conn, + conn->purpose == purpose, + dirconn, + 0 == strcmp_opt(resource, + dirconn->requested_resource)); +} - return NULL; +/** Return a directory connection (if any one exists) that is fetching + * the item described by <b>purpose</b>/<b>resource</b>/<b>state</b>, + * otherwise return NULL. */ +dir_connection_t * +connection_dir_get_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state) +{ + smartlist_t *conns = + connection_dir_list_by_purpose_resource_and_state( + purpose, + resource, + state); + CONN_FIRST_AND_FREE_TEMPLATE(conns); +} + +#undef CONN_FIRST_AND_FREE_TEMPLATE + +/** Return a list of directory connections that are fetching the item + * described by <b>purpose</b>/<b>resource</b>/<b>state</b>. If there are + * none, return an empty list. This list must be freed using smartlist_free, + * but the pointers in it must not be freed. + * Note that this list should not be cached, as the pointers in it can be + * freed if their connections close. */ +smartlist_t * +connection_dir_list_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state) +{ + DIR_CONN_LIST_TEMPLATE(conn, + conn->purpose == purpose && conn->state == state, + dirconn, + 0 == strcmp_opt(resource, + dirconn->requested_resource)); +} + +#undef DIR_CONN_LIST_TEMPLATE + +/** Return an arbitrary active OR connection that isn't <b>this_conn</b>. + * + * We use this to guess if we should tell the controller that we + * didn't manage to connect to any of our bridges. */ +static connection_t * +connection_get_another_active_or_conn(const or_connection_t *this_conn) +{ + CONN_GET_TEMPLATE(conn, + conn != TO_CONN(this_conn) && conn->type == CONN_TYPE_OR); } /** Return 1 if there are any active OR connections apart from @@ -4354,23 +4435,18 @@ connection_dir_get_by_purpose_and_resource(int purpose, int any_other_active_or_conns(const or_connection_t *this_conn) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - if (conn == TO_CONN(this_conn)) { /* don't consider this conn */ - continue; - } - - if (conn->type == CONN_TYPE_OR && - !conn->marked_for_close) { - log_debug(LD_DIR, "%s: Found an OR connection: %s", - __func__, conn->address); - return 1; - } - } SMARTLIST_FOREACH_END(conn); + connection_t *conn = connection_get_another_active_or_conn(this_conn); + if (conn != NULL) { + log_debug(LD_DIR, "%s: Found an OR connection: %s", + __func__, conn->address); + return 1; + } return 0; } +#undef CONN_GET_TEMPLATE + /** Return 1 if <b>conn</b> is a listener conn, else return 0. */ int connection_is_listener(connection_t *conn) diff --git a/src/or/connection.h b/src/or/connection.h index d416962c0a..7479faa509 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -193,7 +193,57 @@ connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); dir_connection_t *connection_dir_get_by_purpose_and_resource( - int state, const char *resource); + int purpose, + const char *resource); +dir_connection_t *connection_dir_get_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state); +smartlist_t *connection_dir_list_by_purpose_and_resource( + int purpose, + const char *resource); +smartlist_t *connection_dir_list_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state); + +#define CONN_LEN_AND_FREE_TEMPLATE(sl) \ + STMT_BEGIN \ + int len = smartlist_len(sl); \ + smartlist_free(sl); \ + return len; \ + STMT_END + +/** Return a count of directory connections that are fetching the item + * described by <b>purpose</b>/<b>resource</b>. */ +static INLINE int +connection_dir_count_by_purpose_and_resource( + int purpose, + const char *resource) +{ + smartlist_t *conns = connection_dir_list_by_purpose_and_resource( + purpose, + resource); + CONN_LEN_AND_FREE_TEMPLATE(conns); +} + +/** Return a count of directory connections that are fetching the item + * described by <b>purpose</b>/<b>resource</b>/<b>state</b>. */ +static INLINE int +connection_dir_count_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state) +{ + smartlist_t *conns = + connection_dir_list_by_purpose_resource_and_state( + purpose, + resource, + state); + CONN_LEN_AND_FREE_TEMPLATE(conns); +} + +#undef CONN_LEN_AND_FREE_TEMPLATE int any_other_active_or_conns(const or_connection_t *this_conn); @@ -239,6 +289,13 @@ void connection_buckets_note_empty_ts(uint32_t *timestamp_var, int tokens_before, size_t tokens_removed, const struct timeval *tvnow); +MOCK_DECL(STATIC int,connection_connect_sockaddr, + (connection_t *conn, + const struct sockaddr *sa, + socklen_t sa_len, + const struct sockaddr *bindaddr, + socklen_t bindaddr_len, + int *socket_error)); #endif #endif diff --git a/src/or/directory.c b/src/or/directory.c index 4e5644b854..63bbdafd3f 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -425,14 +425,17 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, * Use <b>pds_flags</b> as arguments to router_pick_directory_server() * or router_pick_trusteddirserver(). */ -MOCK_IMPL(void, directory_get_from_dirserver, (uint8_t dir_purpose, - uint8_t router_purpose, - const char *resource, - int pds_flags)) +MOCK_IMPL(void, directory_get_from_dirserver, ( + uint8_t dir_purpose, + uint8_t router_purpose, + const char *resource, + int pds_flags, + download_want_authority_t want_authority)) { const routerstatus_t *rs = NULL; const or_options_t *options = get_options(); - int prefer_authority = directory_fetches_from_authorities(options); + int prefer_authority = (directory_fetches_from_authorities(options) + || want_authority == DL_WANT_AUTHORITY); int require_authority = 0; int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose); dirinfo_type_t type = dir_fetch_type(dir_purpose, router_purpose, resource); @@ -958,6 +961,12 @@ directory_initiate_command_rend(const tor_addr_t *_addr, return; } + /* ensure we don't make excess connections when we're already downloading + * a consensus during bootstrap */ + if (connection_dir_avoid_extra_connection_for_purpose(dir_purpose)) { + return; + } + conn = dir_connection_new(tor_addr_family(&addr)); /* set up conn so it's got all the data we need to remember */ @@ -998,6 +1007,9 @@ directory_initiate_command_rend(const tor_addr_t *_addr, conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* fall through */ case 0: + if (connection_dir_close_consensus_conn_if_extra(conn)) { + return; + } /* queue the command on the outbuf */ directory_send_command(conn, dir_purpose, 1, resource, payload, payload_len, @@ -1041,6 +1053,9 @@ directory_initiate_command_rend(const tor_addr_t *_addr, connection_mark_for_close(TO_CONN(conn)); return; } + if (connection_dir_close_consensus_conn_if_extra(conn)) { + return; + } conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* queue the command on the outbuf */ directory_send_command(conn, dir_purpose, 0, resource, @@ -3423,8 +3438,205 @@ connection_dir_finished_flushing(dir_connection_t *conn) return 0; } +/* A helper function for connection_dir_close_consensus_conn_if_extra() + * and connection_dir_close_extra_consensus_conns() that returns 0 if + * we can't have, or don't want to close, excess consensus connections. */ +int +connection_dir_would_close_consensus_conn_helper(void) +{ + const or_options_t *options = get_options(); + + /* we're only interested in closing excess connections if we could + * have created any in the first place */ + if (!networkstatus_consensus_can_use_multiple_directories(options)) { + return 0; + } + + /* We want to close excess connections downloading a consensus. + * If there aren't any excess, we don't have anything to close. */ + if (!networkstatus_consensus_has_excess_connections()) { + return 0; + } + + /* If we have excess connections, but none of them are downloading a + * consensus, and we are still bootstrapping (that is, we have no usable + * consensus), we don't want to close any until one starts downloading. */ + if (!networkstatus_consensus_is_downloading_usable_flavor() + && networkstatus_consensus_is_boostrapping(time(NULL))) { + return 0; + } + + /* If we have just stopped bootstrapping (that is, just parsed a consensus), + * we might still have some excess connections hanging around. So we still + * have to check if we want to close any, even if we've stopped + * bootstrapping. */ + return 1; +} + +/* Check if we would close excess consensus connections. If we would, any + * new consensus connection would become excess immediately, so return 1. + * Otherwise, return 0. */ +int +connection_dir_avoid_extra_connection_for_purpose(unsigned int purpose) +{ + const or_options_t *options = get_options(); + + /* We're not interested in connections that aren't fetching a consensus. */ + if (purpose != DIR_PURPOSE_FETCH_CONSENSUS) { + return 0; + } + + /* we're only interested in avoiding excess connections if we could + * have created any in the first place */ + if (!networkstatus_consensus_can_use_multiple_directories(options)) { + return 0; + } + + /* If there are connections downloading a consensus, and we are still + * bootstrapping (that is, we have no usable consensus), we can be sure that + * any further connections would be excess. */ + if (networkstatus_consensus_is_downloading_usable_flavor() + && networkstatus_consensus_is_boostrapping(time(NULL))) { + return 1; + } + + return 0; +} + +/* Check if we have excess consensus download connection attempts, and close + * conn: + * - if we don't have a consensus, and we're downloading a consensus, and conn + * is not downloading a consensus yet, close it; + * - if we do have a consensus, conn is excess, close it. */ +int +connection_dir_close_consensus_conn_if_extra(dir_connection_t *conn) +{ + tor_assert(conn); + tor_assert(conn->base_.type == CONN_TYPE_DIR); + + /* We're not interested in connections that aren't fetching a consensus. */ + if (conn->base_.purpose != DIR_PURPOSE_FETCH_CONSENSUS) { + return 0; + } + + /* The connection has already been closed */ + if (conn->base_.marked_for_close) { + return 0; + } + + if (!connection_dir_would_close_consensus_conn_helper()) { + return 0; + } + + const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + time(NULL)); + + /* We don't want to check other connections to see if they are downloading, + * as this is prone to race-conditions. So leave it for + * connection_dir_consider_close_extra_consensus_conns() to clean up. + * + * But if conn has just started connecting, or we have a consensus already, + * we can be sure it's not needed any more. */ + if (!we_are_bootstrapping + || conn->base_.state == DIR_CONN_STATE_CONNECTING) { + connection_close_immediate(&conn->base_); + connection_mark_for_close(&conn->base_); + return -1; + } + + return 0; +} + +/* Check if we have excess consensus download connection attempts, and close + * them: + * - if we don't have a consensus, and we're downloading a consensus, keep an + * earlier connection, or a connection to a fallback directory, and close + * all other connections; + * - if we do have a consensus, close all connections: they are all excess. */ +void +connection_dir_close_extra_consensus_conns(void) +{ + if (!connection_dir_would_close_consensus_conn_helper()) { + return; + } + + int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + time(NULL)); + + const char *usable_resource = networkstatus_get_flavor_name( + usable_consensus_flavor()); + smartlist_t *consens_usable_conns = + connection_dir_list_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource); + + /* If we want to keep a connection that's downloading, find a connection to + * keep, favouring: + * - connections opened earlier (they are likely to have progressed further) + * - connections to fallbacks (to reduce the load on authorities) */ + dir_connection_t *kept_download_conn = NULL; + int kept_is_authority = 0; + if (we_are_bootstrapping) { + SMARTLIST_FOREACH_BEGIN(consens_usable_conns, + dir_connection_t *, d) { + tor_assert(d); + int d_is_authority = router_digest_is_trusted_dir(d->identity_digest); + /* keep the first connection that is past the connecting state, but + * prefer fallbacks. */ + if (d->base_.state != DIR_CONN_STATE_CONNECTING) { + if (!kept_download_conn || (kept_is_authority && !d_is_authority)) { + kept_download_conn = d; + kept_is_authority = d_is_authority; + /* we've found the earliest fallback, and want to keep it regardless + * of any other connections */ + if (!kept_is_authority) + break; + } + } + } SMARTLIST_FOREACH_END(d); + } + + SMARTLIST_FOREACH_BEGIN(consens_usable_conns, + dir_connection_t *, d) { + tor_assert(d); + /* don't close this connection if it's the one we want to keep */ + if (kept_download_conn && d == kept_download_conn) + continue; + /* mark all other connections for close */ + if (!d->base_.marked_for_close) { + connection_close_immediate(&d->base_); + connection_mark_for_close(&d->base_); + } + } SMARTLIST_FOREACH_END(d); + + smartlist_free(consens_usable_conns); + consens_usable_conns = NULL; + + /* make sure we've closed all excess connections */ + const int final_connecting_conn_count = + connection_dir_count_by_purpose_resource_and_state( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource, + DIR_CONN_STATE_CONNECTING); + if (final_connecting_conn_count > 0) { + log_warn(LD_BUG, "Expected 0 consensus connections connecting after " + "cleanup, got %d.", final_connecting_conn_count); + } + const int expected_final_conn_count = (we_are_bootstrapping ? 1 : 0); + const int final_conn_count = + connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource); + if (final_conn_count > expected_final_conn_count) { + log_warn(LD_BUG, "Expected %d consensus connections after cleanup, got " + "%d.", expected_final_conn_count, final_connecting_conn_count); + } +} + /** Connected handler for directory connections: begin sending data to the - * server */ + * server, and return 0, or, if the connection is an excess bootstrap + * connection, close all excess bootstrap connections. + * Only used when connections don't immediately connect. */ int connection_dir_finished_connecting(dir_connection_t *conn) { @@ -3435,31 +3647,64 @@ connection_dir_finished_connecting(dir_connection_t *conn) log_debug(LD_HTTP,"Dir connection to router %s:%u established.", conn->base_.address,conn->base_.port); - conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */ + if (connection_dir_close_consensus_conn_if_extra(conn)) { + return -1; + } + + /* start flushing conn */ + conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; return 0; } /** Decide which download schedule we want to use based on descriptor type - * in <b>dls</b> and whether we are acting as directory <b>server</b>, and - * then return a list of int pointers defining download delays in seconds. - * Helper function for download_status_increment_failure() and - * download_status_reset(). */ + * in <b>dls</b> and <b>options</b>. + * Then return a list of int pointers defining download delays in seconds. + * Helper function for download_status_increment_failure(), + * download_status_reset(), and download_status_increment_attempt(). */ static const smartlist_t * -find_dl_schedule_and_len(download_status_t *dls, int server) +find_dl_schedule(download_status_t *dls, const or_options_t *options) { + /* XX/teor Replace with dir_server_mode from #12538 */ + const int dir_server = options->DirPort_set; + const int multi_d = networkstatus_consensus_can_use_multiple_directories( + options); + const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + time(NULL)); + const int use_fallbacks = networkstatus_consensus_can_use_extra_fallbacks( + options); switch (dls->schedule) { case DL_SCHED_GENERIC: - if (server) - return get_options()->TestingServerDownloadSchedule; - else - return get_options()->TestingClientDownloadSchedule; + if (dir_server) { + return options->TestingServerDownloadSchedule; + } else { + return options->TestingClientDownloadSchedule; + } case DL_SCHED_CONSENSUS: - if (server) - return get_options()->TestingServerConsensusDownloadSchedule; - else - return get_options()->TestingClientConsensusDownloadSchedule; + if (!multi_d) { + return options->TestingServerConsensusDownloadSchedule; + } else { + if (we_are_bootstrapping) { + if (!use_fallbacks) { + /* A bootstrapping client without extra fallback directories */ + return + options->TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule; + } else if (dls->want_authority) { + /* A bootstrapping client with extra fallback directories, but + * connecting to an authority */ + return + options->TestingClientBootstrapConsensusAuthorityDownloadSchedule; + } else { + /* A bootstrapping client connecting to extra fallback directories + */ + return + options->TestingClientBootstrapConsensusFallbackDownloadSchedule; + } + } else { + return options->TestingClientConsensusDownloadSchedule; + } + } case DL_SCHED_BRIDGE: - return get_options()->TestingBridgeDownloadSchedule; + return options->TestingBridgeDownloadSchedule; default: tor_assert(0); } @@ -3468,54 +3713,168 @@ find_dl_schedule_and_len(download_status_t *dls, int server) return NULL; } -/** Called when an attempt to download <b>dls</b> has failed with HTTP status +/* Find the current delay for dls based on schedule. + * Set dls->next_attempt_at based on now, and return the delay. + * Helper for download_status_increment_failure and + * download_status_increment_attempt. */ +STATIC int +download_status_schedule_get_delay(download_status_t *dls, + const smartlist_t *schedule, + time_t now) +{ + tor_assert(dls); + tor_assert(schedule); + + int delay = INT_MAX; + uint8_t dls_schedule_position = (dls->increment_on + == DL_SCHED_INCREMENT_ATTEMPT + ? dls->n_download_attempts + : dls->n_download_failures); + + if (dls_schedule_position < smartlist_len(schedule)) + delay = *(int *)smartlist_get(schedule, dls_schedule_position); + else if (dls_schedule_position == IMPOSSIBLE_TO_DOWNLOAD) + delay = INT_MAX; + else + delay = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1); + + /* A negative delay makes no sense. Knowing that delay is + * non-negative allows us to safely do the wrapping check below. */ + tor_assert(delay >= 0); + + /* Avoid now+delay overflowing INT_MAX, by comparing with a subtraction + * that won't overflow (since delay is non-negative). */ + if (delay < INT_MAX && now <= INT_MAX - delay) { + dls->next_attempt_at = now+delay; + } else { + dls->next_attempt_at = TIME_MAX; + } + + return delay; +} + +/* Log a debug message about item, which increments on increment_action, has + * incremented dls_n_download_increments times. The message varies based on + * was_schedule_incremented (if not, not_incremented_response is logged), and + * the values of increment, dls_next_attempt_at, and now. + * Helper for download_status_increment_failure and + * download_status_increment_attempt. */ +static void +download_status_log_helper(const char *item, int was_schedule_incremented, + const char *increment_action, + const char *not_incremented_response, + uint8_t dls_n_download_increments, int increment, + time_t dls_next_attempt_at, time_t now) +{ + if (item) { + if (!was_schedule_incremented) + log_debug(LD_DIR, "%s %s %d time(s); I'll try again %s.", + item, increment_action, (int)dls_n_download_increments, + not_incremented_response); + else if (increment == 0) + log_debug(LD_DIR, "%s %s %d time(s); I'll try again immediately.", + item, increment_action, (int)dls_n_download_increments); + else if (dls_next_attempt_at < TIME_MAX) + log_debug(LD_DIR, "%s %s %d time(s); I'll try again in %d seconds.", + item, increment_action, (int)dls_n_download_increments, + (int)(dls_next_attempt_at-now)); + else + log_debug(LD_DIR, "%s %s %d time(s); Giving up for a while.", + item, increment_action, (int)dls_n_download_increments); + } +} + +/** Determine when a failed download attempt should be retried. + * Called when an attempt to download <b>dls</b> has failed with HTTP status * <b>status_code</b>. Increment the failure count (if the code indicates a - * real failure) and set <b>dls</b>-\>next_attempt_at to an appropriate time - * in the future. */ + * real failure, or if we're a server) and set <b>dls</b>-\>next_attempt_at to + * an appropriate time in the future and return it. + * If <b>dls->increment_on</b> is DL_SCHED_INCREMENT_ATTEMPT, increment the + * failure count, and return a time in the far future for the next attempt (to + * avoid an immediate retry). */ time_t download_status_increment_failure(download_status_t *dls, int status_code, const char *item, int server, time_t now) { - const smartlist_t *schedule; - int increment; + int increment = -1; tor_assert(dls); + + /* only count the failure if it's permanent, or we're a server */ if (status_code != 503 || server) { if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) ++dls->n_download_failures; } - schedule = find_dl_schedule_and_len(dls, server); + if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { + /* We don't find out that a failure-based schedule has attempted a + * connection until that connection fails. + * We'll never find out about successful connections, but this doesn't + * matter, because schedules are reset after a successful download. + */ + if (dls->n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD-1) + ++dls->n_download_attempts; - if (dls->n_download_failures < smartlist_len(schedule)) - increment = *(int *)smartlist_get(schedule, dls->n_download_failures); - else if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD) - increment = INT_MAX; - else - increment = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1); + /* only return a failure retry time if this schedule increments on failures + */ + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); + increment = download_status_schedule_get_delay(dls, schedule, now); + } - if (increment < INT_MAX) - dls->next_attempt_at = now+increment; - else - dls->next_attempt_at = TIME_MAX; + download_status_log_helper(item, !dls->increment_on, "failed", + "concurrently", dls->n_download_failures, + increment, dls->next_attempt_at, now); - if (item) { - if (increment == 0) - log_debug(LD_DIR, "%s failed %d time(s); I'll try again immediately.", - item, (int)dls->n_download_failures); - else if (dls->next_attempt_at < TIME_MAX) - log_debug(LD_DIR, "%s failed %d time(s); I'll try again in %d seconds.", - item, (int)dls->n_download_failures, - (int)(dls->next_attempt_at-now)); - else - log_debug(LD_DIR, "%s failed %d time(s); Giving up for a while.", - item, (int)dls->n_download_failures); + if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) { + /* stop this schedule retrying on failure, it will launch concurrent + * connections instead */ + return TIME_MAX; + } else { + return dls->next_attempt_at; + } +} + +/** Determine when the next download attempt should be made when using an + * attempt-based (potentially concurrent) download schedule. + * Called when an attempt to download <b>dls</b> is being initiated. + * Increment the attempt count and set <b>dls</b>-\>next_attempt_at to an + * appropriate time in the future and return it. + * If <b>dls->increment_on</b> is DL_SCHED_INCREMENT_FAILURE, don't increment + * the attempts, and return a time in the far future (to avoid launching a + * concurrent attempt). */ +time_t +download_status_increment_attempt(download_status_t *dls, const char *item, + time_t now) +{ + int delay = -1; + tor_assert(dls); + + if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { + /* this schedule should retry on failure, and not launch any concurrent + attempts */ + log_info(LD_BUG, "Tried to launch an attempt-based connection on a " + "failure-based schedule."); + return TIME_MAX; } + + if (dls->n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD-1) + ++dls->n_download_attempts; + + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); + delay = download_status_schedule_get_delay(dls, schedule, now); + + download_status_log_helper(item, dls->increment_on, "attempted", + "on failure", dls->n_download_attempts, + delay, dls->next_attempt_at, now); + return dls->next_attempt_at; } /** Reset <b>dls</b> so that it will be considered downloadable * immediately, and/or to show that we don't need it anymore. * + * Must be called to initialise a download schedule, otherwise the zeroth item + * in the schedule will never be used. + * * (We find the zeroth element of the download schedule, and set * next_attempt_at to be the appropriate offset from 'now'. In most * cases this means setting it to 'now', so the item will be immediately @@ -3524,14 +3883,16 @@ download_status_increment_failure(download_status_t *dls, int status_code, void download_status_reset(download_status_t *dls) { - if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD) + if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD + || dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD) return; /* Don't reset this. */ - const smartlist_t *schedule = find_dl_schedule_and_len( - dls, get_options()->DirPort_set); + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); dls->n_download_failures = 0; + dls->n_download_attempts = 0; dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0); + /* Don't reset dls->want_authority or dls->increment_on */ } /** Return the number of failures on <b>dls</b> since the last success (if @@ -3542,6 +3903,22 @@ download_status_get_n_failures(const download_status_t *dls) return dls->n_download_failures; } +/** Return the number of attempts to download <b>dls</b> since the last success + * (if any). This can differ from download_status_get_n_failures() due to + * outstanding concurrent attempts. */ +int +download_status_get_n_attempts(const download_status_t *dls) +{ + return dls->n_download_attempts; +} + +/** Return the next time to attempt to download <b>dls</b>. */ +time_t +download_status_get_next_attempt_at(const download_status_t *dls) +{ + return dls->next_attempt_at; +} + /** Called when one or more routerdesc (or extrainfo, if <b>was_extrainfo</b>) * fetches have failed (with uppercase fingerprints listed in <b>failed</b>, * either as descriptor digests or as identity digests based on diff --git a/src/or/directory.h b/src/or/directory.h index 274227f412..22d7b66212 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -16,10 +16,12 @@ int directories_have_accepted_server_descriptor(void); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, dirinfo_type_t type, const char *payload, size_t payload_len, size_t extrainfo_len); -MOCK_DECL(void, directory_get_from_dirserver, (uint8_t dir_purpose, - uint8_t router_purpose, - const char *resource, - int pds_flags)); +MOCK_DECL(void, directory_get_from_dirserver, ( + uint8_t dir_purpose, + uint8_t router_purpose, + const char *resource, + int pds_flags, + download_want_authority_t want_authority)); void directory_get_from_all_authorities(uint8_t dir_purpose, uint8_t router_purpose, const char *resource); @@ -72,6 +74,9 @@ void directory_initiate_command(const tor_addr_t *addr, const char *resource, const char *payload, size_t payload_len, time_t if_modified_since); +int connection_dir_avoid_extra_connection_for_purpose(unsigned int purpose); +int connection_dir_close_consensus_conn_if_extra(dir_connection_t *conn); +void connection_dir_close_extra_consensus_conns(void); #define DSR_HEX (1<<0) #define DSR_BASE64 (1<<1) @@ -90,6 +95,8 @@ int router_supports_extrainfo(const char *identity_digest, int is_authority); time_t download_status_increment_failure(download_status_t *dls, int status_code, const char *item, int server, time_t now); +time_t download_status_increment_attempt(download_status_t *dls, + const char *item, time_t now); /** Increment the failure count of the download_status_t <b>dls</b>, with * the optional status code <b>sc</b>. */ #define download_status_failed(dls, sc) \ @@ -105,8 +112,9 @@ static inline int download_status_is_ready(download_status_t *dls, time_t now, int max_failures) { - return (dls->n_download_failures <= max_failures - && dls->next_attempt_at <= now); + int under_failure_limit = (dls->n_download_failures <= max_failures + && dls->n_download_attempts <= max_failures); + return (under_failure_limit && dls->next_attempt_at <= now); } static void download_status_mark_impossible(download_status_t *dl); @@ -115,9 +123,12 @@ static inline void download_status_mark_impossible(download_status_t *dl) { dl->n_download_failures = IMPOSSIBLE_TO_DOWNLOAD; + dl->n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD; } int download_status_get_n_failures(const download_status_t *dls); +int download_status_get_n_attempts(const download_status_t *dls); +time_t download_status_get_next_attempt_at(const download_status_t *dls); #ifdef TOR_UNIT_TESTS /* Used only by directory.c and test_dir.c */ @@ -131,6 +142,10 @@ STATIC int directory_handle_command_get(dir_connection_t *conn, const char *headers, const char *req_body, size_t req_body_len); +int connection_dir_would_close_consensus_conn_helper(void); +STATIC int download_status_schedule_get_delay(download_status_t *dls, + const smartlist_t *schedule, + time_t now); #endif #endif diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index ebf675166b..bf71fc30c0 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -2205,7 +2205,7 @@ fetch_bridge_descriptors(const or_options_t *options, time_t now) log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.", resource); directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, - ROUTER_PURPOSE_BRIDGE, resource, 0); + ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY); } } SMARTLIST_FOREACH_END(bridge); diff --git a/src/or/main.c b/src/or/main.c index 527e2b1ffe..455cba4513 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1460,6 +1460,11 @@ run_scheduled_events(time_t now) dirvote_act(options, now); } + /* 2d. Cleanup excess consensus bootstrap connections every second. + * connection_dir_close_consensus_conn_if_extra() will close connections + * that are clearly excess, but this check is more thorough. */ + connection_dir_close_extra_consensus_conns(); + /* 3a. Every second, we examine pending circuits and prune the * ones which have been pending for more than a few seconds. * We do this before step 4, so it can try building more if @@ -1876,18 +1881,29 @@ check_for_reachability_bw_callback(time_t now, const or_options_t *options) static int fetch_networkstatus_callback(time_t now, const or_options_t *options) { - /* 2c. Every minute (or every second if TestingTorNetwork), check - * whether we want to download any networkstatus documents. */ + /* 2c. Every minute (or every second if TestingTorNetwork, or during + * client bootstrap), check whether we want to download any networkstatus + * documents. */ /* How often do we check whether we should download network status * documents? */ -#define networkstatus_dl_check_interval(o) ((o)->TestingTorNetwork ? 1 : 60) + const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + now); + const int prefer_mirrors = !directory_fetches_from_authorities( + get_options()); + int networkstatus_dl_check_interval = 60; + /* check more often when testing, or when bootstrapping from mirrors + * (connection limits prevent too many connections being made) */ + if (options->TestingTorNetwork + || (we_are_bootstrapping && prefer_mirrors)) { + networkstatus_dl_check_interval = 1; + } if (should_delay_dir_fetches(options, NULL)) return PERIODIC_EVENT_NO_UPDATE; update_networkstatus_downloads(now); - return networkstatus_dl_check_interval(options); + return networkstatus_dl_check_interval; } static int diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 71a2c0f121..173c109d60 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -85,8 +85,30 @@ static time_t time_to_download_next_consensus[N_CONSENSUS_FLAVORS]; /** Download status for the current consensus networkstatus. */ static download_status_t consensus_dl_status[N_CONSENSUS_FLAVORS] = { - { 0, 0, DL_SCHED_CONSENSUS }, - { 0, 0, DL_SCHED_CONSENSUS }, + { 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_FAILURE }, + { 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_FAILURE }, + }; + +#define N_CONSENSUS_BOOTSTRAP_SCHEDULES 2 +#define CONSENSUS_BOOTSTRAP_SOURCE_AUTHORITY 0 +#define CONSENSUS_BOOTSTRAP_SOURCE_ANY_DIRSERVER 1 + +/* Using DL_SCHED_INCREMENT_ATTEMPT on these schedules means that + * download_status_increment_failure won't increment these entries. + * However, any bootstrap connection failures that occur after we have + * a valid consensus will count against the failure counts on the non-bootstrap + * schedules. There should only be one of these, as all the others will have + * been cancelled. (This doesn't seem to be a significant issue.) */ +static download_status_t + consensus_bootstrap_dl_status[N_CONSENSUS_BOOTSTRAP_SCHEDULES] = + { + { 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_AUTHORITY, + DL_SCHED_INCREMENT_ATTEMPT }, + /* During bootstrap, DL_WANT_ANY_DIRSERVER means "use fallbacks". */ + { 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_ATTEMPT }, }; /** True iff we have logged a warning about this OR's version being older than @@ -97,6 +119,10 @@ static int have_warned_about_old_version = 0; static int have_warned_about_new_version = 0; static void routerstatus_list_update_named_server_map(void); +static void update_consensus_bootstrap_multiple_downloads( + time_t now, + const or_options_t *options, + int we_are_bootstrapping); /** Forget that we've warned about anything networkstatus-related, so we will * give fresh warnings if the same behavior happens again. */ @@ -122,6 +148,9 @@ networkstatus_reset_download_failures(void) for (i=0; i < N_CONSENSUS_FLAVORS; ++i) download_status_reset(&consensus_dl_status[i]); + + for (i=0; i < N_CONSENSUS_BOOTSTRAP_SCHEDULES; ++i) + download_status_reset(&consensus_bootstrap_dl_status[i]); } /** Read every cached v3 consensus networkstatus from the disk. */ @@ -734,6 +763,55 @@ we_want_to_fetch_flavor(const or_options_t *options, int flavor) * fetching certs before we check whether there is a better one? */ #define DELAY_WHILE_FETCHING_CERTS (20*60) +/* Check if a downloaded consensus flavor should still wait for certificates + * to download now. + * If so, return 1. If not, fail dls and return 0. */ +static int +check_consensus_waiting_for_certs(int flavor, time_t now, + download_status_t *dls) +{ + consensus_waiting_for_certs_t *waiting; + + /* We should always have a known flavor, because we_want_to_fetch_flavor() + * filters out unknown flavors. */ + tor_assert(flavor >= 0 && flavor < N_CONSENSUS_FLAVORS); + + waiting = &consensus_waiting_for_certs[flavor]; + if (waiting->consensus) { + /* XXXX make sure this doesn't delay sane downloads. */ + if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) { + return 1; + } else { + if (!waiting->dl_failed) { + download_status_failed(dls, 0); + waiting->dl_failed=1; + } + } + } + + return 0; +} + +/* Return the maximum download tries for a consensus, based on options and + * whether we_are_bootstrapping. */ +static int +consensus_max_download_tries(const or_options_t *options, + int we_are_bootstrapping) +{ + int use_fallbacks = networkstatus_consensus_can_use_extra_fallbacks(options); + + if (we_are_bootstrapping) { + if (use_fallbacks) { + return options->TestingClientBootstrapConsensusMaxDownloadTries; + } else { + return + options->TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries; + } + } + + return options->TestingConsensusMaxDownloadTries; +} + /** If we want to download a fresh consensus, launch a new download as * appropriate. */ static void @@ -741,12 +819,19 @@ update_consensus_networkstatus_downloads(time_t now) { int i; const or_options_t *options = get_options(); + const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + now); + const int use_multi_conn = + networkstatus_consensus_can_use_multiple_directories(options); + + if (should_delay_dir_fetches(options, NULL)) + return; for (i=0; i < N_CONSENSUS_FLAVORS; ++i) { /* XXXX need some way to download unknown flavors if we are caching. */ const char *resource; - consensus_waiting_for_certs_t *waiting; networkstatus_t *c; + int max_in_progress_conns = 1; if (! we_want_to_fetch_flavor(options, i)) continue; @@ -762,35 +847,166 @@ update_consensus_networkstatus_downloads(time_t now) resource = networkstatus_get_flavor_name(i); - /* Let's make sure we remembered to update consensus_dl_status */ - tor_assert(consensus_dl_status[i].schedule == DL_SCHED_CONSENSUS); + /* Check if we already have enough connections in progress */ + if (we_are_bootstrapping) { + max_in_progress_conns = + options->TestingClientBootstrapConsensusMaxInProgressTries; + } + if (connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + resource) + >= max_in_progress_conns) { + continue; + } - if (!download_status_is_ready(&consensus_dl_status[i], now, - options->TestingConsensusMaxDownloadTries)) - continue; /* We failed downloading a consensus too recently. */ - if (connection_dir_get_by_purpose_and_resource( - DIR_PURPOSE_FETCH_CONSENSUS, resource)) - continue; /* There's an in-progress download.*/ + /* Check if we want to launch another download for a usable consensus. + * Only used during bootstrap. */ + if (we_are_bootstrapping && use_multi_conn + && i == usable_consensus_flavor()) { + + /* Check if we're already downloading a usable consensus */ + int consens_conn_count = + connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + resource); + int connect_consens_conn_count = + connection_dir_count_by_purpose_resource_and_state( + DIR_PURPOSE_FETCH_CONSENSUS, + resource, + DIR_CONN_STATE_CONNECTING); + + if (i == usable_consensus_flavor() + && connect_consens_conn_count < consens_conn_count) { + continue; + } - waiting = &consensus_waiting_for_certs[i]; - if (waiting->consensus) { - /* XXXX make sure this doesn't delay sane downloads. */ - if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) { - continue; /* We're still getting certs for this one. */ - } else { - if (!waiting->dl_failed) { - download_status_failed(&consensus_dl_status[i], 0); - waiting->dl_failed=1; - } + /* Make multiple connections for a bootstrap consensus download */ + update_consensus_bootstrap_multiple_downloads(now, options, + we_are_bootstrapping); + } else { + /* Check if we failed downloading a consensus too recently */ + int max_dl_tries = consensus_max_download_tries(options, + we_are_bootstrapping); + + /* Let's make sure we remembered to update consensus_dl_status */ + tor_assert(consensus_dl_status[i].schedule == DL_SCHED_CONSENSUS); + + if (!download_status_is_ready(&consensus_dl_status[i], + now, + max_dl_tries)) { + continue; } + + /* Check if we're waiting for certificates to download */ + if (check_consensus_waiting_for_certs(i, now, &consensus_dl_status[i])) + continue; + + /* Try the requested attempt */ + log_info(LD_DIR, "Launching %s standard networkstatus consensus " + "download.", networkstatus_get_flavor_name(i)); + directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS, + ROUTER_PURPOSE_GENERAL, resource, + PDS_RETRY_IF_NO_SERVERS, + consensus_dl_status[i].want_authority); } + } +} - log_info(LD_DIR, "Launching %s networkstatus consensus download.", - networkstatus_get_flavor_name(i)); +/** When we're bootstrapping, launch one or more consensus download + * connections, if schedule indicates connection(s) should be made after now. + * If is_authority, connect to an authority, otherwise, use a fallback + * directory mirror. + */ +static void +update_consensus_bootstrap_attempt_downloads( + time_t now, + const or_options_t *options, + int we_are_bootstrapping, + download_status_t *dls, + download_want_authority_t want_authority) +{ + int max_dl_tries = consensus_max_download_tries(options, + we_are_bootstrapping); + const char *resource = networkstatus_get_flavor_name( + usable_consensus_flavor()); + + /* Let's make sure we remembered to update schedule */ + tor_assert(dls->schedule == DL_SCHED_CONSENSUS); + + /* Allow for multiple connections in the same second, if the schedule value + * is 0. */ + while (download_status_is_ready(dls, now, max_dl_tries)) { + log_info(LD_DIR, "Launching %s bootstrap %s networkstatus consensus " + "download.", resource, (want_authority == DL_WANT_AUTHORITY + ? "authority" + : "mirror")); directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS, ROUTER_PURPOSE_GENERAL, resource, - PDS_RETRY_IF_NO_SERVERS); + PDS_RETRY_IF_NO_SERVERS, want_authority); + /* schedule the next attempt */ + download_status_increment_attempt(dls, resource, now); + } +} + +/** If we're bootstrapping, check the connection schedules and see if we want + * to make additional, potentially concurrent, consensus download + * connections. + * Only call when bootstrapping, and when we want to make additional + * connections. Only nodes that satisfy + * networkstatus_consensus_can_use_multiple_directories make additonal + * connections. + */ +static void +update_consensus_bootstrap_multiple_downloads(time_t now, + const or_options_t *options, + int we_are_bootstrapping) +{ + const int usable_flavor = usable_consensus_flavor(); + + /* make sure we can use multiple connections */ + if (!networkstatus_consensus_can_use_multiple_directories(options)) { + return; + } + + /* If we've managed to validate a usable consensus, don't make additonal + * connections. */ + if (!we_are_bootstrapping) { + return; + } + + /* Launch concurrent consensus download attempt(s) based on the mirror and + * authority schedules. Try the mirror first - this makes it slightly more + * likely that we'll connect to the fallback first, and then end the + * authority connection attempt. */ + + /* If a consensus download fails because it's waiting for certificates, + * we'll fail both the authority and fallback schedules. This is better than + * failing only one of the schedules, and having the other continue + * unchecked. + */ + + /* If we don't have or can't use extra fallbacks, don't try them. */ + if (networkstatus_consensus_can_use_extra_fallbacks(options)) { + download_status_t *dls_f = + &consensus_bootstrap_dl_status[CONSENSUS_BOOTSTRAP_SOURCE_ANY_DIRSERVER]; + + if (!check_consensus_waiting_for_certs(usable_flavor, now, dls_f)) { + /* During bootstrap, DL_WANT_ANY_DIRSERVER means "use fallbacks". */ + update_consensus_bootstrap_attempt_downloads(now, options, + we_are_bootstrapping, dls_f, + DL_WANT_ANY_DIRSERVER); + } + } + + /* Now try an authority. */ + download_status_t *dls_a = + &consensus_bootstrap_dl_status[CONSENSUS_BOOTSTRAP_SOURCE_AUTHORITY]; + + if (!check_consensus_waiting_for_certs(usable_flavor, now, dls_a)) { + update_consensus_bootstrap_attempt_downloads(now, options, + we_are_bootstrapping, dls_a, + DL_WANT_AUTHORITY); } } @@ -1057,6 +1273,100 @@ networkstatus_get_reasonably_live_consensus(time_t now, int flavor) return NULL; } +/** Check if we're bootstrapping a consensus download. This means that we are + * only using the authorities and fallback directory mirrors to download the + * consensus flavour we'll use. */ +int +networkstatus_consensus_is_boostrapping(time_t now) +{ + /* If we don't have a consensus, we must still be bootstrapping */ + return !networkstatus_get_reasonably_live_consensus( + now, + usable_consensus_flavor()); +} + +/** Check if we can use multiple directories for a consensus download. + * Only clients (including bridges, but excluding bridge clients) benefit + * from multiple simultaneous consensus downloads. */ +int +networkstatus_consensus_can_use_multiple_directories( + const or_options_t *options) +{ + /* If we are a client, bridge, bridge client, or hidden service */ + return (!directory_fetches_from_authorities(options)); +} + +/** Check if we can use fallback directory mirrors for a consensus download. + * Only clients that have a list of additional fallbacks can use fallbacks. */ +int +networkstatus_consensus_can_use_extra_fallbacks(const or_options_t *options) +{ + /* If we are a client, and we have additional mirrors, we can use them. + * The list length comparisons are a quick way to check if we have any + * non-authority fallback directories. If we ever have any authorities that + * aren't fallback directories, we will need to change this code. */ + return (!directory_fetches_from_authorities(options) + && (smartlist_len(router_get_fallback_dir_servers()) + > smartlist_len(router_get_trusted_dir_servers()))); +} + +/* Check if there is more than 1 consensus connection retrieving the usable + * consensus flavor. If so, return 1, if not, return 0. + * + * During normal operation, Tor only makes one consensus download + * connection. But clients can make multiple simultaneous consensus + * connections to improve bootstrap speed and reliability. + * + * If there is more than one connection, we must have connections left + * over from bootstrapping. However, some of the connections may have + * completed and been cleaned up, so it is not sufficient to check the + * return value of this function to see if a client could make multiple + * bootstrap connections. Use + * networkstatus_consensus_can_use_multiple_directories() + * and networkstatus_consensus_is_boostrapping(). */ +int +networkstatus_consensus_has_excess_connections(void) +{ + const char *usable_resource = networkstatus_get_flavor_name( + usable_consensus_flavor()); + const int consens_conn_usable_count = + connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource); + /* The maximum number of connections we want downloading a usable consensus + * Always 1, whether bootstrapping or not. */ + const int max_expected_consens_conn_usable_count = 1; + + if (consens_conn_usable_count > max_expected_consens_conn_usable_count) { + return 1; + } + + return 0; +} + +/* Is tor currently downloading a consensus of the usable flavor? */ +int +networkstatus_consensus_is_downloading_usable_flavor(void) +{ + const char *usable_resource = networkstatus_get_flavor_name( + usable_consensus_flavor()); + const int consens_conn_usable_count = + connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource); + + const int connect_consens_conn_usable_count = + connection_dir_count_by_purpose_resource_and_state( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource, + DIR_CONN_STATE_CONNECTING); + if (connect_consens_conn_usable_count < consens_conn_usable_count) { + return 1; + } + + return 0; +} + /** Given two router status entries for the same router identity, return 1 if * if the contents have changed between them. Otherwise, return 0. */ static int diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index d6e9e37013..4cb33c3fc0 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -70,6 +70,14 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor, networkstatus_t *networkstatus_get_live_consensus(time_t now); networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now, int flavor); +int networkstatus_consensus_is_boostrapping(time_t now); +int networkstatus_consensus_can_use_multiple_directories( + const or_options_t *options); +int networkstatus_consensus_can_use_extra_fallbacks( + const or_options_t *options); +int networkstatus_consensus_has_excess_connections(void); +int networkstatus_consensus_is_downloading_usable_flavor(void); + #define NSSET_FROM_CACHE 1 #define NSSET_WAS_WAITING_FOR_CERTS 2 #define NSSET_DONT_DOWNLOAD_CERTS 4 diff --git a/src/or/or.h b/src/or/or.h index 1a94d99f96..2944eaf330 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1946,8 +1946,8 @@ typedef enum { } saved_location_t; #define saved_location_bitfield_t ENUM_BF(saved_location_t) -/** Enumeration: what kind of download schedule are we using for a given - * object? */ +/** Enumeration: what directory object is being downloaded? + * This determines which schedule is selected to perform the download. */ typedef enum { DL_SCHED_GENERIC = 0, DL_SCHED_CONSENSUS = 1, @@ -1955,15 +1955,74 @@ typedef enum { } download_schedule_t; #define download_schedule_bitfield_t ENUM_BF(download_schedule_t) +/** Enumeration: is the download schedule for downloading from an authority, + * or from any available directory mirror? + * During bootstrap, "any" means a fallback (or an authority, if there + * are no fallbacks). + * When we have a valid consensus, "any" means any directory server. */ +typedef enum { + DL_WANT_ANY_DIRSERVER = 0, + DL_WANT_AUTHORITY = 1, +} download_want_authority_t; +#define download_want_authority_bitfield_t \ + ENUM_BF(download_want_authority_t) + +/** Enumeration: do we want to increment the schedule position each time a + * connection is attempted (these attempts can be concurrent), or do we want + * to increment the schedule position after a connection fails? */ +typedef enum { + DL_SCHED_INCREMENT_FAILURE = 0, + DL_SCHED_INCREMENT_ATTEMPT = 1, +} download_schedule_increment_t; +#define download_schedule_increment_bitfield_t \ + ENUM_BF(download_schedule_increment_t) + /** Information about our plans for retrying downloads for a downloadable - * object. */ + * directory object. + * Each type of downloadable directory object has a corresponding retry + * <b>schedule</b>, which can be different depending on whether the object is + * being downloaded from an authority or a mirror (<b>want_authority</b>). + * <b>next_attempt_at</b> contains the next time we will attempt to download + * the object. + * For schedules that <b>increment_on</b> failure, <b>n_download_failures</b> + * is used to determine the position in the schedule. (Each schedule is a + * smartlist of integer delays, parsed from a CSV option.) Every time a + * connection attempt fails, <b>n_download_failures</b> is incremented, + * the new delay value is looked up from the schedule, and + * <b>next_attempt_at</b> is set delay seconds from the time the previous + * connection failed. Therefore, at most one failure-based connection can be + * in progress for each download_status_t. + * For schedules that <b>increment_on</b> attempt, <b>n_download_attempts</b> + * is used to determine the position in the schedule. Every time a + * connection attempt is made, <b>n_download_attempts</b> is incremented, + * the new delay value is looked up from the schedule, and + * <b>next_attempt_at</b> is set delay seconds from the time the previous + * connection was attempted. Therefore, multiple concurrent attempted-based + * connections can be in progress for each download_status_t. + * After an object is successfully downloaded, any other concurrent connections + * are terminated. A new schedule which starts at position 0 is used for + * subsequent downloads of the same object. + */ typedef struct download_status_t { - time_t next_attempt_at; /**< When should we try downloading this descriptor + time_t next_attempt_at; /**< When should we try downloading this object * again? */ - uint8_t n_download_failures; /**< Number of failures trying to download the - * most recent descriptor. */ - download_schedule_bitfield_t schedule : 8; - + uint8_t n_download_failures; /**< Number of failed downloads of the most + * recent object, since the last success. */ + uint8_t n_download_attempts; /**< Number of (potentially concurrent) attempts + * to download the most recent object, since + * the last success. */ + download_schedule_bitfield_t schedule : 8; /**< What kind of object is being + * downloaded? This determines the + * schedule used for the download. + */ + download_want_authority_bitfield_t want_authority : 1; /**< Is the download + * happening from an authority + * or a mirror? This determines + * the schedule used for the + * download. */ + download_schedule_increment_bitfield_t increment_on : 1; /**< does this + * schedule increment on each attempt, + * or after each failure? */ } download_status_t; /** If n_download_failures is this high, the download can never happen. */ @@ -4071,6 +4130,36 @@ typedef struct { * on testing networks. */ smartlist_t *TestingClientConsensusDownloadSchedule; + /** Schedule for when clients should download consensuses from authorities + * if they are bootstrapping (that is, they don't have a usable, reasonably + * live consensus). Only used by clients fetching from a list of fallback + * directory mirrors. + * + * This schedule is incremented by (potentially concurrent) connection + * attempts, unlike other schedules, which are incremented by connection + * failures. Only altered on testing networks. */ + smartlist_t *TestingClientBootstrapConsensusAuthorityDownloadSchedule; + + /** Schedule for when clients should download consensuses from fallback + * directory mirrors if they are bootstrapping (that is, they don't have a + * usable, reasonably live consensus). Only used by clients fetching from a + * list of fallback directory mirrors. + * + * This schedule is incremented by (potentially concurrent) connection + * attempts, unlike other schedules, which are incremented by connection + * failures. Only altered on testing networks. */ + smartlist_t *TestingClientBootstrapConsensusFallbackDownloadSchedule; + + /** Schedule for when clients should download consensuses from authorities + * if they are bootstrapping (that is, they don't have a usable, reasonably + * live consensus). Only used by clients which don't have or won't fetch + * from a list of fallback directory mirrors. + * + * This schedule is incremented by (potentially concurrent) connection + * attempts, unlike other schedules, which are incremented by connection + * failures. Only altered on testing networks. */ + smartlist_t *TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule; + /** Schedule for when clients should download bridge descriptors. Only * altered on testing networks. */ smartlist_t *TestingBridgeDownloadSchedule; @@ -4088,6 +4177,21 @@ typedef struct { * up? Only altered on testing networks. */ int TestingConsensusMaxDownloadTries; + /** How many times will a client try to fetch a consensus while + * bootstrapping using a list of fallback directories, before it gives up? + * Only altered on testing networks. */ + int TestingClientBootstrapConsensusMaxDownloadTries; + + /** How many times will a client try to fetch a consensus while + * bootstrapping using only a list of authorities, before it gives up? + * Only altered on testing networks. */ + int TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries; + + /** How many simultaneous in-progress connections will we make when trying + * to fetch a consensus before we wait for one to complete, timeout, or + * error out? Only altered on testing networks. */ + int TestingClientBootstrapConsensusMaxInProgressTries; + /** How many times will we try to download a router's descriptor before * giving up? Only altered on testing networks. */ int TestingDescriptorMaxDownloadTries; diff --git a/src/or/routerlist.c b/src/or/routerlist.c index ef548d8df5..1d4cfcd1e7 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -897,8 +897,10 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) if (smartlist_len(fps) > 1) { resource = smartlist_join_strings(fps, "", 0, NULL); + /* XXX - do we want certs from authorities or mirrors? - teor */ directory_get_from_dirserver(DIR_PURPOSE_FETCH_CERTIFICATE, 0, - resource, PDS_RETRY_IF_NO_SERVERS); + resource, PDS_RETRY_IF_NO_SERVERS, + DL_WANT_ANY_DIRSERVER); tor_free(resource); } /* else we didn't add any: they were all pending */ @@ -941,8 +943,10 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) if (smartlist_len(fp_pairs) > 1) { resource = smartlist_join_strings(fp_pairs, "", 0, NULL); + /* XXX - do we want certs from authorities or mirrors? - teor */ directory_get_from_dirserver(DIR_PURPOSE_FETCH_CERTIFICATE, 0, - resource, PDS_RETRY_IF_NO_SERVERS); + resource, PDS_RETRY_IF_NO_SERVERS, + DL_WANT_ANY_DIRSERVER); tor_free(resource); } /* else they were all pending */ @@ -1358,7 +1362,9 @@ router_get_trusteddirserver_by_digest(const char *digest) } /** Return the dir_server_t for the fallback dirserver whose identity - * key hashes to <b>digest</b>, or NULL if no such authority is known. + * key hashes to <b>digest</b>, or NULL if no such fallback is in the list of + * fallback_dir_servers. (fallback_dir_servers is affected by the FallbackDir + * and UseDefaultFallbackDirs torrc options.) */ dir_server_t * router_get_fallback_dirserver_by_digest(const char *digest) @@ -1366,6 +1372,9 @@ router_get_fallback_dirserver_by_digest(const char *digest) if (!fallback_dir_servers) return NULL; + if (!digest) + return NULL; + SMARTLIST_FOREACH(fallback_dir_servers, dir_server_t *, ds, { if (tor_memeq(ds->digest, digest, DIGEST_LEN)) @@ -1375,6 +1384,17 @@ router_get_fallback_dirserver_by_digest(const char *digest) return NULL; } +/** Return 1 if any fallback dirserver's identity key hashes to <b>digest</b>, + * or 0 if no such fallback is in the list of fallback_dir_servers. + * (fallback_dir_servers is affected by the FallbackDir and + * UseDefaultFallbackDirs torrc options.) + */ +int +router_digest_is_fallback_dir(const char *digest) +{ + return (router_get_fallback_dirserver_by_digest(digest) != NULL); +} + /** Return the dir_server_t for the directory authority whose * v3 identity key hashes to <b>digest</b>, or NULL if no such authority * is known. @@ -4376,14 +4396,14 @@ MOCK_IMPL(STATIC void, initiate_descriptor_downloads, tor_free(cp); if (source) { - /* We know which authority we want. */ + /* We know which authority or directory mirror we want. */ directory_initiate_command_routerstatus(source, purpose, ROUTER_PURPOSE_GENERAL, DIRIND_ONEHOP, resource, NULL, 0, 0); } else { directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource, - pds_flags); + pds_flags, DL_WANT_ANY_DIRSERVER); } tor_free(resource); } @@ -4665,9 +4685,14 @@ launch_dummy_descriptor_download_as_needed(time_t now, last_descriptor_download_attempted + DUMMY_DOWNLOAD_INTERVAL < now && last_dummy_download + DUMMY_DOWNLOAD_INTERVAL < now) { last_dummy_download = now; + /* XX/teor - do we want an authority here, because they are less likely + * to give us the wrong address? (See #17782) + * I'm leaving the previous behaviour intact, because I don't like + * the idea of some relays contacting an authority every 20 minutes. */ directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_GENERAL, "authority.z", - PDS_RETRY_IF_NO_SERVERS); + PDS_RETRY_IF_NO_SERVERS, + DL_WANT_ANY_DIRSERVER); } } diff --git a/src/or/routerlist.h b/src/or/routerlist.h index b9bab2604d..0ccd3f4dca 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -50,6 +50,7 @@ const routerstatus_t *router_pick_directory_server(dirinfo_type_t type, dir_server_t *router_get_trusteddirserver_by_digest(const char *d); dir_server_t *router_get_fallback_dirserver_by_digest( const char *digest); +int router_digest_is_fallback_dir(const char *digest); dir_server_t *trusteddirserver_get_by_v3_auth_digest(const char *d); const routerstatus_t *router_pick_trusteddirserver(dirinfo_type_t type, int flags); diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index 0435617683..0ba56d7036 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -14,7 +14,8 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ TEST_OBJECTS = test.obj test_addr.obj test_channel.obj test_channeltls.obj \ test_containers.obj \ test_controller_events.obj test_crypto.obj test_data.obj test_dir.obj \ - test_checkdir.obj test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ + test_checkdir.obj test_microdesc.obj test_pt.obj test_util.obj \ + test_config.obj test_connection.obj \ test_cell_formats.obj test_relay.obj test_replay.obj \ test_scheduler.obj test_introduce.obj test_hs.obj tinytest.obj diff --git a/src/test/include.am b/src/test/include.am index 051f0e77d6..f593782709 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -69,6 +69,7 @@ src_test_test_SOURCES = \ src/test/test_circuitmux.c \ src/test/test_compat_libevent.c \ src/test/test_config.c \ + src/test/test_connection.c \ src/test/test_containers.c \ src/test/test_controller.c \ src/test/test_controller_events.c \ diff --git a/src/test/test.c b/src/test/test.c index 1c4c2921db..f12ae21ff0 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1141,6 +1141,7 @@ extern struct testcase_t circuitlist_tests[]; extern struct testcase_t circuitmux_tests[]; extern struct testcase_t compat_libevent_tests[]; extern struct testcase_t config_tests[]; +extern struct testcase_t connection_tests[]; extern struct testcase_t container_tests[]; extern struct testcase_t controller_tests[]; extern struct testcase_t controller_event_tests[]; @@ -1196,6 +1197,7 @@ struct testgroup_t testgroups[] = { { "circuitmux/", circuitmux_tests }, { "compat/libevent/", compat_libevent_tests }, { "config/", config_tests }, + { "connection/", connection_tests }, { "container/", container_tests }, { "control/", controller_tests }, { "control/event/", controller_event_tests }, diff --git a/src/test/test_config.c b/src/test/test_config.c index 4ecd5148e0..1d25f8693f 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -18,6 +18,7 @@ #include "entrynodes.h" #include "transports.h" #include "routerlist.h" +#include "networkstatus.h" static void test_config_addressmap(void *arg) @@ -1478,7 +1479,7 @@ test_config_adding_dir_servers(void *arg) (void)arg; /* allocate options */ - or_options_t *options = tor_malloc(sizeof(or_options_t)); + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); /* Allocate and populate configuration lines: * @@ -1487,8 +1488,7 @@ test_config_adding_dir_servers(void *arg) * Zeroing the structure has the same effect as initialising to: * { NULL, NULL, NULL, CONFIG_LINE_NORMAL, 0}; */ - config_line_t *test_dir_authority = tor_malloc(sizeof(config_line_t)); - memset(test_dir_authority, 0, sizeof(config_line_t)); + config_line_t *test_dir_authority = tor_malloc_zero(sizeof(config_line_t)); test_dir_authority->key = tor_strdup("DirAuthority"); test_dir_authority->value = tor_strdup( "D0 orport=9000 " @@ -1496,16 +1496,16 @@ test_config_adding_dir_servers(void *arg) "127.0.0.1:60090 0123 4567 8901 2345 6789 0123 4567 8901 2345 6789" ); - config_line_t *test_alt_bridge_authority = tor_malloc(sizeof(config_line_t)); - memset(test_alt_bridge_authority, 0, sizeof(config_line_t)); + config_line_t *test_alt_bridge_authority = tor_malloc_zero( + sizeof(config_line_t)); test_alt_bridge_authority->key = tor_strdup("AlternateBridgeAuthority"); test_alt_bridge_authority->value = tor_strdup( "B1 orport=9001 bridge " "127.0.0.1:60091 1123 4567 8901 2345 6789 0123 4567 8901 2345 6789" ); - config_line_t *test_alt_dir_authority = tor_malloc(sizeof(config_line_t)); - memset(test_alt_dir_authority, 0, sizeof(config_line_t)); + config_line_t *test_alt_dir_authority = tor_malloc_zero( + sizeof(config_line_t)); test_alt_dir_authority->key = tor_strdup("AlternateDirAuthority"); test_alt_dir_authority->value = tor_strdup( "A2 orport=9002 " @@ -1514,8 +1514,8 @@ test_config_adding_dir_servers(void *arg) ); /* Use the format specified in the manual page */ - config_line_t *test_fallback_directory = tor_malloc(sizeof(config_line_t)); - memset(test_fallback_directory, 0, sizeof(config_line_t)); + config_line_t *test_fallback_directory = tor_malloc_zero( + sizeof(config_line_t)); test_fallback_directory->key = tor_strdup("FallbackDir"); test_fallback_directory->value = tor_strdup( "127.0.0.1:60093 orport=9003 id=0323456789012345678901234567890123456789" @@ -1623,6 +1623,9 @@ test_config_adding_dir_servers(void *arg) /* we must have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* fallback_dir_servers */ const smartlist_t *fallback_servers = router_get_fallback_dir_servers(); @@ -1655,7 +1658,10 @@ test_config_adding_dir_servers(void *arg) n_default_fallback_dir = (smartlist_len(fallback_servers) - n_default_alt_bridge_authority - n_default_alt_dir_authority); - /* If we have a negative count, something has gone really wrong */ + /* If we have a negative count, something has gone really wrong, + * or some authorities aren't being added as fallback directories. + * (networkstatus_consensus_can_use_extra_fallbacks depends on all + * authorities being fallback directories.) */ tt_assert(n_default_fallback_dir >= 0); } } @@ -1699,6 +1705,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -1837,6 +1846,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we just have the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -1975,6 +1987,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2114,6 +2129,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2263,6 +2281,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2414,6 +2435,9 @@ test_config_adding_dir_servers(void *arg) /* we must have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2574,6 +2598,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2728,6 +2755,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we just have the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2891,6 +2921,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -3051,6 +3084,9 @@ test_config_adding_dir_servers(void *arg) /* we must have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -3244,6 +3280,48 @@ test_config_default_dir_servers(void *arg) or_options_free(opts); } +static void +test_config_use_multiple_directories(void *arg) +{ + (void)arg; + + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + + /* Clients can use multiple directory mirrors for bootstrap */ + memset(options, 0, sizeof(or_options_t)); + options->ClientOnly = 1; + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 1); + + /* Bridge Clients can use multiple directory mirrors for bootstrap */ + memset(options, 0, sizeof(or_options_t)); + options->UseBridges = 1; + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 1); + + /* Bridge Relays (Bridges) must act like clients, and use multiple + * directory mirrors for bootstrap */ + memset(options, 0, sizeof(or_options_t)); + options->BridgeRelay = 1; + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 1); + + /* Clients set to FetchDirInfoEarly must fetch it from the authorities */ + memset(options, 0, sizeof(or_options_t)); + options->FetchDirInfoEarly = 1; + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + /* OR servers must fetch the consensus from the authorities */ + memset(options, 0, sizeof(or_options_t)); + options->ORPort_set = 1; + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + done: + tor_free(options); +} + #define CONFIG_TEST(name, flags) \ { #name, test_config_ ## name, flags, NULL, NULL } @@ -3258,6 +3336,7 @@ struct testcase_t config_tests[] = { CONFIG_TEST(check_or_create_data_subdir, TT_FORK), CONFIG_TEST(write_to_data_subdir, TT_FORK), CONFIG_TEST(fix_my_family, 0), + CONFIG_TEST(use_multiple_directories, 0), END_OF_TESTCASES }; diff --git a/src/test/test_connection.c b/src/test/test_connection.c new file mode 100644 index 0000000000..1067b5fa1f --- /dev/null +++ b/src/test/test_connection.c @@ -0,0 +1,757 @@ +/* Copyright (c) 2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#define CONNECTION_PRIVATE +#define MAIN_PRIVATE + +#include "or.h" +#include "test.h" + +#include "connection.h" +#include "main.h" +#include "networkstatus.h" +#include "rendcache.h" +#include "directory.h" + +static void test_conn_lookup_addr_helper(const char *address, + int family, + tor_addr_t *addr); + +static void * test_conn_get_basic_setup(const struct testcase_t *tc); +static int test_conn_get_basic_teardown(const struct testcase_t *tc, + void *arg); + +static void * test_conn_get_rend_setup(const struct testcase_t *tc); +static int test_conn_get_rend_teardown(const struct testcase_t *tc, + void *arg); + +static void * test_conn_get_rsrc_setup(const struct testcase_t *tc); +static int test_conn_get_rsrc_teardown(const struct testcase_t *tc, + void *arg); + +/* Arbitrary choice - IPv4 Directory Connection to localhost */ +#define TEST_CONN_TYPE (CONN_TYPE_DIR) +/* We assume every machine has IPv4 localhost, is that ok? */ +#define TEST_CONN_ADDRESS "127.0.0.1" +#define TEST_CONN_PORT (12345) +#define TEST_CONN_ADDRESS_PORT "127.0.0.1:12345" +#define TEST_CONN_FAMILY (AF_INET) +#define TEST_CONN_STATE (DIR_CONN_STATE_MIN_) +#define TEST_CONN_ADDRESS_2 "127.0.0.2" + +#define TEST_CONN_BASIC_PURPOSE (DIR_PURPOSE_MIN_) + +#define TEST_CONN_REND_ADDR "cfs3rltphxxvabci" +#define TEST_CONN_REND_PURPOSE (DIR_PURPOSE_FETCH_RENDDESC_V2) +#define TEST_CONN_REND_PURPOSE_SUCCESSFUL (DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) +#define TEST_CONN_REND_TYPE_2 (CONN_TYPE_AP) +#define TEST_CONN_REND_ADDR_2 "icbavxxhptlr3sfc" + +#define TEST_CONN_RSRC (networkstatus_get_flavor_name(FLAV_MICRODESC)) +#define TEST_CONN_RSRC_PURPOSE (DIR_PURPOSE_FETCH_CONSENSUS) +#define TEST_CONN_RSRC_STATE_SUCCESSFUL (DIR_CONN_STATE_CLIENT_FINISHED) +#define TEST_CONN_RSRC_2 (networkstatus_get_flavor_name(FLAV_NS)) + +#define TEST_CONN_DL_STATE (DIR_CONN_STATE_CLIENT_SENDING) + +#define TEST_CONN_FD_INIT 50 +static int mock_connection_connect_sockaddr_called = 0; +static int fake_socket_number = TEST_CONN_FD_INIT; + +static int +mock_connection_connect_sockaddr(connection_t *conn, + const struct sockaddr *sa, + socklen_t sa_len, + const struct sockaddr *bindaddr, + socklen_t bindaddr_len, + int *socket_error) +{ + (void)sa_len; + (void)bindaddr; + (void)bindaddr_len; + + tor_assert(conn); + tor_assert(sa); + tor_assert(socket_error); + + mock_connection_connect_sockaddr_called++; + + conn->s = fake_socket_number++; + tt_assert(SOCKET_OK(conn->s)); + /* We really should call tor_libevent_initialize() here. Because we don't, + * we are relying on other parts of the code not checking if the_event_base + * (and therefore event->ev_base) is NULL. */ + tt_assert(connection_add_connecting(conn) == 0); + + done: + /* Fake "connected" status */ + return 1; +} + +static void +test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr) +{ + int rv = 0; + + tt_assert(addr); + + rv = tor_addr_lookup(address, family, addr); + /* XXXX - should we retry on transient failure? */ + tt_assert(rv == 0); + tt_assert(tor_addr_is_loopback(addr)); + tt_assert(tor_addr_is_v4(addr)); + + return; + + done: + tor_addr_make_null(addr, TEST_CONN_FAMILY); +} + +static void * +test_conn_get_basic_setup(const struct testcase_t *tc) +{ + connection_t *conn = NULL; + tor_addr_t addr; + int socket_err = 0; + int in_progress = 0; + (void)tc; + + MOCK(connection_connect_sockaddr, + mock_connection_connect_sockaddr); + + init_connection_lists(); + + conn = connection_new(TEST_CONN_TYPE, TEST_CONN_FAMILY); + tt_assert(conn); + + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr); + tt_assert(!tor_addr_is_null(&addr)); + + /* XXXX - connection_connect doesn't set these, should it? */ + tor_addr_copy_tight(&conn->addr, &addr); + conn->port = TEST_CONN_PORT; + mock_connection_connect_sockaddr_called = 0; + in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr, + TEST_CONN_PORT, &socket_err); + tt_assert(mock_connection_connect_sockaddr_called == 1); + tt_assert(!socket_err); + tt_assert(in_progress == 0 || in_progress == 1); + + /* fake some of the attributes so the connection looks OK */ + conn->state = TEST_CONN_STATE; + conn->purpose = TEST_CONN_BASIC_PURPOSE; + assert_connection_ok(conn, time(NULL)); + + UNMOCK(connection_connect_sockaddr); + + return conn; + + /* On failure */ + done: + UNMOCK(connection_connect_sockaddr); + test_conn_get_basic_teardown(tc, conn); + + /* Returning NULL causes the unit test to fail */ + return NULL; +} + +static int +test_conn_get_basic_teardown(const struct testcase_t *tc, void *arg) +{ + (void)tc; + connection_t *conn = arg; + + tt_assert(conn); + assert_connection_ok(conn, time(NULL)); + + /* teardown the connection as fast as possible */ + if (conn->linked_conn) { + assert_connection_ok(conn->linked_conn, time(NULL)); + + /* We didn't call tor_libevent_initialize(), so event_base was NULL, + * so we can't rely on connection_unregister_events() use of event_del(). + */ + if (conn->linked_conn->read_event) { + tor_free(conn->linked_conn->read_event); + conn->linked_conn->read_event = NULL; + } + if (conn->linked_conn->write_event) { + tor_free(conn->linked_conn->write_event); + conn->linked_conn->write_event = NULL; + } + + connection_free(conn->linked_conn); + conn->linked_conn = NULL; + + conn->linked_conn->linked_conn = NULL; + if (!conn->linked_conn->marked_for_close) { + connection_close_immediate(conn->linked_conn); + connection_mark_for_close(conn->linked_conn); + } + } + + /* We didn't set the events up properly, so we can't use event_del() in + * close_closeable_connections() > connection_free() + * > connection_unregister_events() */ + if (conn->read_event) { + tor_free(conn->read_event); + conn->read_event = NULL; + } + if (conn->write_event) { + tor_free(conn->write_event); + conn->write_event = NULL; + } + + if (!conn->marked_for_close) { + connection_close_immediate(conn); + connection_mark_for_close(conn); + } + + close_closeable_connections(); + + /* The unit test will fail if we return 0 */ + return 1; + + /* When conn == NULL, we can't cleanup anything */ + done: + return 0; +} + +static void * +test_conn_get_rend_setup(const struct testcase_t *tc) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, + test_conn_get_basic_setup(tc)); + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + rend_cache_init(); + + /* TODO: use directory_initiate_command_rend() to do this - maybe? */ + conn->rend_data = tor_malloc_zero(sizeof(rend_data_t)); + memcpy(conn->rend_data->onion_address, + TEST_CONN_REND_ADDR, + REND_SERVICE_ADDRESS_LEN+1); + conn->rend_data->hsdirs_fp = smartlist_new(); + conn->base_.purpose = TEST_CONN_REND_PURPOSE; + + assert_connection_ok(&conn->base_, time(NULL)); + return conn; + + /* On failure */ + done: + test_conn_get_rend_teardown(tc, conn); + /* Returning NULL causes the unit test to fail */ + return NULL; +} + +static int +test_conn_get_rend_teardown(const struct testcase_t *tc, void *arg) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, arg); + int rv = 0; + + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + /* avoid a last-ditch attempt to refetch the descriptor */ + conn->base_.purpose = TEST_CONN_REND_PURPOSE_SUCCESSFUL; + + /* connection_free_() cleans up rend_data */ + rv = test_conn_get_basic_teardown(tc, arg); + done: + rend_cache_free_all(); + return rv; +} + +static void * +test_conn_get_rsrc_setup(const struct testcase_t *tc) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, + test_conn_get_basic_setup(tc)); + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + /* TODO: use the canonical function to do this - maybe? */ + conn->requested_resource = tor_strdup(TEST_CONN_RSRC); + conn->base_.purpose = TEST_CONN_RSRC_PURPOSE; + + assert_connection_ok(&conn->base_, time(NULL)); + return conn; + + /* On failure */ + done: + test_conn_get_rend_teardown(tc, conn); + /* Returning NULL causes the unit test to fail */ + return NULL; +} + +static int +test_conn_get_rsrc_teardown(const struct testcase_t *tc, void *arg) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, arg); + int rv = 0; + + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + /* avoid a last-ditch attempt to refetch the consensus */ + conn->base_.state = TEST_CONN_RSRC_STATE_SUCCESSFUL; + + /* connection_free_() cleans up requested_resource */ + rv = test_conn_get_basic_teardown(tc, arg); + done: + return rv; +} + +static void * +test_conn_download_status_setup(const struct testcase_t *tc) +{ + (void)tc; + + /* Don't return NULL, that causes the test to fail */ + return "ok"; +} + +static int +test_conn_download_status_teardown(const struct testcase_t *tc, void *arg) +{ + (void)arg; + int rv = 0; + + /* Ignore arg, and just loop through the connection array */ + SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { + if (conn) { + assert_connection_ok(conn, time(NULL)); + + /* connection_free_() cleans up requested_resource */ + rv = test_conn_get_rsrc_teardown(tc, conn); + tt_assert(rv == 1); + } + } SMARTLIST_FOREACH_END(conn); + + done: + return rv; +} + +static dir_connection_t * +test_conn_download_status_add_a_connection(void) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, + test_conn_get_rsrc_setup(NULL)); + + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + return conn; + + done: + test_conn_download_status_teardown(NULL, NULL); + return NULL; +} + +static struct testcase_setup_t test_conn_get_basic_st = { + test_conn_get_basic_setup, test_conn_get_basic_teardown +}; + +static struct testcase_setup_t test_conn_get_rend_st = { + test_conn_get_rend_setup, test_conn_get_rend_teardown +}; + +static struct testcase_setup_t test_conn_get_rsrc_st = { + test_conn_get_rsrc_setup, test_conn_get_rsrc_teardown +}; + +static struct testcase_setup_t test_conn_download_status_st = { + test_conn_download_status_setup, test_conn_download_status_teardown +}; + +static void +test_conn_get_basic(void *arg) +{ + connection_t *conn = (connection_t*)arg; + tor_addr_t addr, addr2; + + tt_assert(conn); + assert_connection_ok(conn, time(NULL)); + + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr); + tt_assert(!tor_addr_is_null(&addr)); + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS_2, TEST_CONN_FAMILY, &addr2); + tt_assert(!tor_addr_is_null(&addr2)); + + /* Check that we get this connection back when we search for it by + * its attributes, but get NULL when we supply a different value. */ + + tt_assert(connection_get_by_global_id(conn->global_identifier) == conn); + tt_assert(connection_get_by_global_id(!conn->global_identifier) == NULL); + + tt_assert(connection_get_by_type(conn->type) == conn); + tt_assert(connection_get_by_type(TEST_CONN_TYPE) == conn); + tt_assert(connection_get_by_type(!conn->type) == NULL); + tt_assert(connection_get_by_type(!TEST_CONN_TYPE) == NULL); + + tt_assert(connection_get_by_type_state(conn->type, conn->state) + == conn); + tt_assert(connection_get_by_type_state(TEST_CONN_TYPE, TEST_CONN_STATE) + == conn); + tt_assert(connection_get_by_type_state(!conn->type, !conn->state) + == NULL); + tt_assert(connection_get_by_type_state(!TEST_CONN_TYPE, !TEST_CONN_STATE) + == NULL); + + /* Match on the connection fields themselves */ + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + conn->port, + conn->purpose) + == conn); + /* Match on the original inputs to the connection */ + tt_assert(connection_get_by_type_addr_port_purpose(TEST_CONN_TYPE, + &conn->addr, + conn->port, + conn->purpose) + == conn); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &addr, + conn->port, + conn->purpose) + == conn); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + TEST_CONN_PORT, + conn->purpose) + == conn); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + conn->port, + TEST_CONN_BASIC_PURPOSE) + == conn); + tt_assert(connection_get_by_type_addr_port_purpose(TEST_CONN_TYPE, + &addr, + TEST_CONN_PORT, + TEST_CONN_BASIC_PURPOSE) + == conn); + /* Then try each of the not-matching combinations */ + tt_assert(connection_get_by_type_addr_port_purpose(!conn->type, + &conn->addr, + conn->port, + conn->purpose) + == NULL); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &addr2, + conn->port, + conn->purpose) + == NULL); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + !conn->port, + conn->purpose) + == NULL); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + conn->port, + !conn->purpose) + == NULL); + /* Then try everything not-matching */ + tt_assert(connection_get_by_type_addr_port_purpose(!conn->type, + &addr2, + !conn->port, + !conn->purpose) + == NULL); + tt_assert(connection_get_by_type_addr_port_purpose(!TEST_CONN_TYPE, + &addr2, + !TEST_CONN_PORT, + !TEST_CONN_BASIC_PURPOSE) + == NULL); + + done: + ; +} + +static void +test_conn_get_rend(void *arg) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, arg); + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + tt_assert(connection_get_by_type_state_rendquery( + conn->base_.type, + conn->base_.state, + conn->rend_data->onion_address) + == TO_CONN(conn)); + tt_assert(connection_get_by_type_state_rendquery( + TEST_CONN_TYPE, + TEST_CONN_STATE, + TEST_CONN_REND_ADDR) + == TO_CONN(conn)); + tt_assert(connection_get_by_type_state_rendquery(TEST_CONN_REND_TYPE_2, + !conn->base_.state, + "") + == NULL); + tt_assert(connection_get_by_type_state_rendquery(TEST_CONN_REND_TYPE_2, + !TEST_CONN_STATE, + TEST_CONN_REND_ADDR_2) + == NULL); + + done: + ; +} + +#define sl_is_conn_assert(sl, conn) \ + do { \ + tt_assert(smartlist_len((sl)) == 1); \ + tt_assert(smartlist_get((sl), 0) == (conn)); \ + } while (0) + +#define sl_no_conn_assert(sl) \ + do { \ + tt_assert(smartlist_len((sl)) == 0); \ + } while (0) + +static void +test_conn_get_rsrc(void *arg) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, arg); + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + tt_assert(connection_dir_get_by_purpose_and_resource( + conn->base_.purpose, + conn->requested_resource) + == conn); + tt_assert(connection_dir_get_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) + == conn); + tt_assert(connection_dir_get_by_purpose_and_resource( + !conn->base_.purpose, + "") + == NULL); + tt_assert(connection_dir_get_by_purpose_and_resource( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2) + == NULL); + + tt_assert(connection_dir_get_by_purpose_resource_and_state( + conn->base_.purpose, + conn->requested_resource, + conn->base_.state) + == conn); + tt_assert(connection_dir_get_by_purpose_resource_and_state( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC, + TEST_CONN_STATE) + == conn); + tt_assert(connection_dir_get_by_purpose_resource_and_state( + !conn->base_.purpose, + "", + !conn->base_.state) + == NULL); + tt_assert(connection_dir_get_by_purpose_resource_and_state( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2, + !TEST_CONN_STATE) + == NULL); + + sl_is_conn_assert(connection_dir_list_by_purpose_and_resource( + conn->base_.purpose, + conn->requested_resource), + conn); + sl_is_conn_assert(connection_dir_list_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC), + conn); + sl_no_conn_assert(connection_dir_list_by_purpose_and_resource( + !conn->base_.purpose, + "")); + sl_no_conn_assert(connection_dir_list_by_purpose_and_resource( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2)); + + sl_is_conn_assert(connection_dir_list_by_purpose_resource_and_state( + conn->base_.purpose, + conn->requested_resource, + conn->base_.state), + conn); + sl_is_conn_assert(connection_dir_list_by_purpose_resource_and_state( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC, + TEST_CONN_STATE), + conn); + sl_no_conn_assert(connection_dir_list_by_purpose_resource_and_state( + !conn->base_.purpose, + "", + !conn->base_.state)); + sl_no_conn_assert(connection_dir_list_by_purpose_resource_and_state( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2, + !TEST_CONN_STATE)); + + tt_assert(connection_dir_count_by_purpose_and_resource( + conn->base_.purpose, + conn->requested_resource) + == 1); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) + == 1); + tt_assert(connection_dir_count_by_purpose_and_resource( + !conn->base_.purpose, + "") + == 0); + tt_assert(connection_dir_count_by_purpose_and_resource( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2) + == 0); + + tt_assert(connection_dir_count_by_purpose_resource_and_state( + conn->base_.purpose, + conn->requested_resource, + conn->base_.state) + == 1); + tt_assert(connection_dir_count_by_purpose_resource_and_state( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC, + TEST_CONN_STATE) + == 1); + tt_assert(connection_dir_count_by_purpose_resource_and_state( + !conn->base_.purpose, + "", + !conn->base_.state) + == 0); + tt_assert(connection_dir_count_by_purpose_resource_and_state( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2, + !TEST_CONN_STATE) + == 0); + + done: + ; +} + +static void +test_conn_download_status(void *arg) +{ + (void)arg; + dir_connection_t *conn = NULL; + dir_connection_t *conn2 = NULL; + dir_connection_t *conn3 = NULL; + + /* no connections, no excess, not downloading */ + tt_assert(networkstatus_consensus_has_excess_connections() == 0); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 0); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 0); + + /* one connection, no excess, not downloading */ + conn = test_conn_download_status_add_a_connection(); + tt_assert(networkstatus_consensus_has_excess_connections() == 0); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 0); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 0); + + /* one connection, no excess, but downloading */ + conn->base_.state = TEST_CONN_DL_STATE; + tt_assert(networkstatus_consensus_has_excess_connections() == 0); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + conn->base_.state = TEST_CONN_STATE; + + /* two connections, excess, but not downloading */ + conn2 = test_conn_download_status_add_a_connection(); + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 0); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 0); + + /* two connections, excess, downloading */ + conn2->base_.state = TEST_CONN_DL_STATE; + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + conn2->base_.state = TEST_CONN_STATE; + + /* more connections, excess, but not downloading */ + conn3 = test_conn_download_status_add_a_connection(); + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 0); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 0); + + /* more connections, excess, downloading */ + conn3->base_.state = TEST_CONN_DL_STATE; + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* more connections, more downloading */ + conn2->base_.state = TEST_CONN_DL_STATE; + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* now try closing the one that isn't downloading: + * these tests won't work unless tor thinks it is bootstrapping */ + tt_assert(networkstatus_consensus_is_boostrapping(time(NULL))); + + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 3); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + tt_assert(connection_dir_close_consensus_conn_if_extra(conn) == -1); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 2); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* now try closing one that is already closed - nothing happens */ + tt_assert(connection_dir_close_consensus_conn_if_extra(conn) == 0); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 2); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + + /* now try closing one that is downloading - it stays open */ + tt_assert(connection_dir_close_consensus_conn_if_extra(conn2) == 0); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 2); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* now try closing all excess connections */ + connection_dir_close_extra_consensus_conns(); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + done: + /* the teardown function removes all the connections */; +} + +#define CONNECTION_TESTCASE(name, fork, setup) \ + { #name, test_conn_##name, fork, &setup, NULL } + +struct testcase_t connection_tests[] = { + CONNECTION_TESTCASE(get_basic, TT_FORK, test_conn_get_basic_st), + CONNECTION_TESTCASE(get_rend, TT_FORK, test_conn_get_rend_st), + CONNECTION_TESTCASE(get_rsrc, TT_FORK, test_conn_get_rsrc_st), + CONNECTION_TESTCASE(download_status, TT_FORK, test_conn_download_status_st), +//CONNECTION_TESTCASE(func_suffix, TT_FORK, setup_func_pair), + END_OF_TESTCASES +}; + diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 855746e749..ce639b644f 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -3494,6 +3494,435 @@ test_dir_packages(void *arg) tor_free(res); } +static void +test_dir_download_status_schedule(void *arg) +{ + (void)arg; + download_status_t dls_failure = { 0, 0, 0, DL_SCHED_GENERIC, + DL_WANT_AUTHORITY, + DL_SCHED_INCREMENT_FAILURE }; + download_status_t dls_attempt = { 0, 0, 0, DL_SCHED_CONSENSUS, + DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_ATTEMPT}; + download_status_t dls_bridge = { 0, 0, 0, DL_SCHED_BRIDGE, + DL_WANT_AUTHORITY, + DL_SCHED_INCREMENT_FAILURE}; + int increment = -1; + int expected_increment = -1; + time_t current_time = time(NULL); + int delay1 = -1; + int delay2 = -1; + smartlist_t *schedule = smartlist_new(); + + /* Make a dummy schedule */ + smartlist_add(schedule, (void *)&delay1); + smartlist_add(schedule, (void *)&delay2); + + /* check a range of values */ + delay1 = 1000; + increment = download_status_schedule_get_delay(&dls_failure, + schedule, + TIME_MIN); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_failure.next_attempt_at == TIME_MIN + expected_increment); + +#if TIME_T_IS_SIGNED + delay1 = INT_MAX; + increment = download_status_schedule_get_delay(&dls_failure, + schedule, + -1); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_failure.next_attempt_at == TIME_MAX); +#endif + + delay1 = 0; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + 0); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == 0 + expected_increment); + + delay1 = 1000; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + 1); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == 1 + expected_increment); + + delay1 = INT_MAX; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + current_time); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == TIME_MAX); + + delay1 = 1; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + TIME_MAX); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == TIME_MAX); + + /* see what happens when we reach the end */ + dls_attempt.n_download_attempts++; + dls_bridge.n_download_failures++; + + delay2 = 100; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + current_time); + expected_increment = delay2; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == current_time + delay2); + + delay2 = 1; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + current_time); + expected_increment = delay2; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == current_time + delay2); + + /* see what happens when we try to go off the end */ + dls_attempt.n_download_attempts++; + dls_bridge.n_download_failures++; + + delay2 = 5; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + current_time); + expected_increment = delay2; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == current_time + delay2); + + delay2 = 17; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + current_time); + expected_increment = delay2; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == current_time + delay2); + + /* see what happens when we reach IMPOSSIBLE_TO_DOWNLOAD */ + dls_attempt.n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD; + dls_bridge.n_download_failures = IMPOSSIBLE_TO_DOWNLOAD; + + delay2 = 35; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + current_time); + expected_increment = INT_MAX; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == TIME_MAX); + + delay2 = 99; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + current_time); + expected_increment = INT_MAX; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == TIME_MAX); + + done: + /* the pointers in schedule are allocated on the stack */ + smartlist_free(schedule); +} + +static void +test_dir_download_status_increment(void *arg) +{ + (void)arg; + download_status_t dls_failure = { 0, 0, 0, DL_SCHED_GENERIC, + DL_WANT_AUTHORITY, + DL_SCHED_INCREMENT_FAILURE }; + download_status_t dls_attempt = { 0, 0, 0, DL_SCHED_BRIDGE, + DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_ATTEMPT}; + int delay0 = -1; + int delay1 = -1; + int delay2 = -1; + smartlist_t *schedule = smartlist_new(); + or_options_t test_options; + time_t next_at = TIME_MAX; + time_t current_time = time(NULL); + + /* Provide some values for the schedule */ + delay0 = 10; + delay1 = 99; + delay2 = 20; + + /* Make the schedule */ + smartlist_add(schedule, (void *)&delay0); + smartlist_add(schedule, (void *)&delay1); + smartlist_add(schedule, (void *)&delay2); + + /* Put it in the options */ + mock_options = &test_options; + reset_options(mock_options, &mock_get_options_calls); + mock_options->TestingClientDownloadSchedule = schedule; + mock_options->TestingBridgeDownloadSchedule = schedule; + + MOCK(get_options, mock_get_options); + + /* Check that a failure reset works */ + mock_get_options_calls = 0; + download_status_reset(&dls_failure); + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_failure) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_failure) + != TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) == 0); + tt_assert(download_status_get_n_attempts(&dls_failure) == 0); + tt_assert(mock_get_options_calls >= 1); + + /* avoid timing inconsistencies */ + dls_failure.next_attempt_at = current_time + delay0; + + /* check that a reset schedule becomes ready at the right time */ + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay0 - 1, + 1) == 0); + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay0, + 1) == 1); + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay0 + 1, + 1) == 1); + + /* Check that a failure increment works */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, + current_time); + tt_assert(next_at == current_time + delay1); + tt_assert(download_status_get_n_failures(&dls_failure) == 1); + tt_assert(download_status_get_n_attempts(&dls_failure) == 1); + tt_assert(mock_get_options_calls >= 1); + + /* check that an incremented schedule becomes ready at the right time */ + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay1 - 1, + 1) == 0); + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay1, + 1) == 1); + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay1 + 1, + 1) == 1); + + /* check that a schedule isn't ready if it's had too many failures */ + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay1 + 10, + 0) == 0); + + /* Check that failure increments don't happen on 503 for clients, but that + * attempt increments do. */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_failure, 503, "test", 0, + current_time); + tt_assert(next_at == current_time + delay1); + tt_assert(download_status_get_n_failures(&dls_failure) == 1); + tt_assert(download_status_get_n_attempts(&dls_failure) == 2); + tt_assert(mock_get_options_calls >= 1); + + /* Check that failure increments do happen on 503 for servers */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_failure, 503, "test", 1, + current_time); + tt_assert(next_at == current_time + delay2); + tt_assert(download_status_get_n_failures(&dls_failure) == 2); + tt_assert(download_status_get_n_attempts(&dls_failure) == 3); + tt_assert(mock_get_options_calls >= 1); + + /* Check what happens when we run off the end of the schedule */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, + current_time); + tt_assert(next_at == current_time + delay2); + tt_assert(download_status_get_n_failures(&dls_failure) == 3); + tt_assert(download_status_get_n_attempts(&dls_failure) == 4); + tt_assert(mock_get_options_calls >= 1); + + /* Check what happens when we hit the failure limit */ + mock_get_options_calls = 0; + download_status_mark_impossible(&dls_failure); + next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, + current_time); + tt_assert(next_at == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(download_status_get_n_attempts(&dls_failure) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(mock_get_options_calls >= 1); + + /* Check that a failure reset doesn't reset at the limit */ + mock_get_options_calls = 0; + download_status_reset(&dls_failure); + tt_assert(download_status_get_next_attempt_at(&dls_failure) + == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(download_status_get_n_attempts(&dls_failure) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(mock_get_options_calls == 0); + + /* Check that a failure reset resets just before the limit */ + mock_get_options_calls = 0; + dls_failure.n_download_failures = IMPOSSIBLE_TO_DOWNLOAD - 1; + dls_failure.n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD - 1; + download_status_reset(&dls_failure); + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_failure) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_failure) + != TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) == 0); + tt_assert(download_status_get_n_attempts(&dls_failure) == 0); + tt_assert(mock_get_options_calls >= 1); + + /* Check that failure increments do happen on attempt-based schedules, + * but that the retry is set at the end of time */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_attempt, 404, "test", 0, + current_time); + tt_assert(next_at == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) == 1); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); + tt_assert(mock_get_options_calls == 0); + + /* Check that an attempt reset works */ + mock_get_options_calls = 0; + download_status_reset(&dls_attempt); + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + != TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); + tt_assert(mock_get_options_calls >= 1); + + /* avoid timing inconsistencies */ + dls_attempt.next_attempt_at = current_time + delay0; + + /* check that a reset schedule becomes ready at the right time */ + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay0 - 1, + 1) == 0); + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay0, + 1) == 1); + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay0 + 1, + 1) == 1); + + /* Check that an attempt increment works */ + mock_get_options_calls = 0; + next_at = download_status_increment_attempt(&dls_attempt, "test", + current_time); + tt_assert(next_at == current_time + delay1); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 1); + tt_assert(mock_get_options_calls >= 1); + + /* check that an incremented schedule becomes ready at the right time */ + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay1 - 1, + 1) == 0); + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay1, + 1) == 1); + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay1 + 1, + 1) == 1); + + /* check that a schedule isn't ready if it's had too many attempts */ + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay1 + 10, + 0) == 0); + + /* Check what happens when we reach then run off the end of the schedule */ + mock_get_options_calls = 0; + next_at = download_status_increment_attempt(&dls_attempt, "test", + current_time); + tt_assert(next_at == current_time + delay2); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 2); + tt_assert(mock_get_options_calls >= 1); + + mock_get_options_calls = 0; + next_at = download_status_increment_attempt(&dls_attempt, "test", + current_time); + tt_assert(next_at == current_time + delay2); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 3); + tt_assert(mock_get_options_calls >= 1); + + /* Check what happens when we hit the attempt limit */ + mock_get_options_calls = 0; + download_status_mark_impossible(&dls_attempt); + next_at = download_status_increment_attempt(&dls_attempt, "test", + current_time); + tt_assert(next_at == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(download_status_get_n_attempts(&dls_attempt) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(mock_get_options_calls >= 1); + + /* Check that an attempt reset doesn't reset at the limit */ + mock_get_options_calls = 0; + download_status_reset(&dls_attempt); + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(download_status_get_n_attempts(&dls_attempt) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(mock_get_options_calls == 0); + + /* Check that an attempt reset resets just before the limit */ + mock_get_options_calls = 0; + dls_attempt.n_download_failures = IMPOSSIBLE_TO_DOWNLOAD - 1; + dls_attempt.n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD - 1; + download_status_reset(&dls_attempt); + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + != TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); + tt_assert(mock_get_options_calls >= 1); + + /* Check that attempt increments don't happen on failure-based schedules, + * and that the attempt is set at the end of time */ + mock_get_options_calls = 0; + next_at = download_status_increment_attempt(&dls_failure, "test", + current_time); + tt_assert(next_at == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) == 0); + tt_assert(download_status_get_n_attempts(&dls_failure) == 0); + tt_assert(mock_get_options_calls == 0); + + done: + /* the pointers in schedule are allocated on the stack */ + smartlist_free(schedule); + UNMOCK(get_options); + mock_options = NULL; + mock_get_options_calls = 0; +} + #define DIR_LEGACY(name) \ { #name, test_dir_ ## name , TT_FORK, NULL, NULL } @@ -3525,6 +3954,8 @@ struct testcase_t dir_tests[] = { DIR(purpose_needs_anonymity, 0), DIR(fetch_type, 0), DIR(packages, 0), + DIR(download_status_schedule, 0), + DIR(download_status_increment, 0), END_OF_TESTCASES }; diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index 381a592c5b..1bc5e4bb16 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -12,11 +12,13 @@ static char output[4*BASE64_DIGEST256_LEN+3+2+2+1]; static void mock_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, - const char *resource, int pds_flags) + const char *resource, int pds_flags, + download_want_authority_t want_authority) { (void)dir_purpose; (void)router_purpose; (void)pds_flags; + (void)want_authority; tt_assert(resource); strlcpy(output, resource, sizeof(output)); done: |