From ee55823a11b711fc4af6c3c906c341c057a414f3 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 22 Jan 2020 11:06:11 -0500 Subject: [PATCH 1/9] mainloop: Rename global_write_bucket_low() That function is only used to test the global bucket write limit for a directory connection. It should _not_ be used for anything else since that function looks to see if we are a directory authority. Rename it to something more meaningful. No change in behavior at this commit, only renaming. Part of #33029 Signed-off-by: David Goulet --- src/core/mainloop/connection.c | 7 ++++--- src/core/mainloop/connection.h | 5 +++-- src/feature/dircache/dircache.c | 10 +++++----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 6094f33e4d..2c075ba6be 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -3186,9 +3186,9 @@ connection_bucket_write_limit(connection_t *conn, time_t now) global_bucket_val, conn_bucket); } -/** Return 1 if the global write buckets are low enough that we +/** Return true iff the global write buckets are low enough that we * shouldn't send attempt bytes of low-priority directory stuff - * out to conn. Else return 0. + * out to conn. * Priority was 1 for v1 requests (directories and running-routers), * and 2 for v2 requests and later (statuses and descriptors). @@ -3206,7 +3206,8 @@ connection_bucket_write_limit(connection_t *conn, time_t now) * that's harder to quantify and harder to keep track of. */ int -global_write_bucket_low(connection_t *conn, size_t attempt, int priority) +connection_dir_is_global_write_low(connection_t *conn, size_t attempt, + int priority) { size_t smaller_bucket = MIN(token_bucket_rw_get_write(&global_bucket), diff --git a/src/core/mainloop/connection.h b/src/core/mainloop/connection.h index c93f1ef8e8..4f15c1dd6f 100644 --- a/src/core/mainloop/connection.h +++ b/src/core/mainloop/connection.h @@ -195,8 +195,9 @@ int retry_all_listeners(smartlist_t *new_conns, void connection_mark_all_noncontrol_listeners(void); void connection_mark_all_noncontrol_connections(void); -ssize_t connection_bucket_write_limit(connection_t *conn, time_t now); -int global_write_bucket_low(connection_t *conn, size_t attempt, int priority); +ssize_t connection_bucket_write_limit(struct connection_t *conn, time_t now); +int connection_dir_is_global_write_low(struct connection_t *conn, + size_t attempt, int priority); void connection_bucket_init(void); void connection_bucket_adjust(const or_options_t *options); void connection_bucket_refill_all(time_t now, diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c index d4d0ad9939..266729cdd3 100644 --- a/src/feature/dircache/dircache.c +++ b/src/feature/dircache/dircache.c @@ -951,7 +951,7 @@ handle_get_current_consensus(dir_connection_t *conn, goto done; } - if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess, 2)) { log_debug(LD_DIRSERV, "Client asked for network status lists, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1060,7 +1060,7 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) } }); - if (global_write_bucket_low(TO_CONN(conn), estimated_len, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), estimated_len, 2)) { write_short_http_response(conn, 503, "Directory busy, try again later"); goto vote_done; } @@ -1119,7 +1119,7 @@ handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args) write_short_http_response(conn, 404, "Not found"); goto done; } - if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1217,7 +1217,7 @@ handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args) msg = "Not found"; write_short_http_response(conn, 404, msg); } else { - if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1313,7 +1313,7 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) SMARTLIST_FOREACH(certs, authority_cert_t *, c, len += c->cache_info.signed_descriptor_len); - if (global_write_bucket_low(TO_CONN(conn), + if (connection_dir_is_global_write_low(TO_CONN(conn), compress_method != NO_METHOD ? len/2 : len, 2)) { write_short_http_response(conn, 503, "Directory busy, try again later"); From c1e0ac63b8bf570d16e2ccea456caa450510fbd6 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 28 Jan 2020 08:50:46 -0500 Subject: [PATCH 2/9] mainloop: Remove unused parameter from connection_dir_is_global_write_low() Signed-off-by: David Goulet --- src/core/mainloop/connection.c | 22 +++------------------- src/core/mainloop/connection.h | 2 +- src/feature/dircache/dircache.c | 11 +++++------ 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 2c075ba6be..9a07a62c29 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -3189,9 +3189,6 @@ connection_bucket_write_limit(connection_t *conn, time_t now) /** Return true iff the global write buckets are low enough that we * shouldn't send attempt bytes of low-priority directory stuff * out to conn. - - * Priority was 1 for v1 requests (directories and running-routers), - * and 2 for v2 requests and later (statuses and descriptors). * * There are a lot of parameters we could use here: * - global_relayed_write_bucket. Low is bad. @@ -3206,38 +3203,25 @@ connection_bucket_write_limit(connection_t *conn, time_t now) * that's harder to quantify and harder to keep track of. */ int -connection_dir_is_global_write_low(connection_t *conn, size_t attempt, - int priority) +connection_dir_is_global_write_low(connection_t *conn, size_t attempt) { size_t smaller_bucket = MIN(token_bucket_rw_get_write(&global_bucket), token_bucket_rw_get_write(&global_relayed_bucket)); - if (authdir_mode(get_options()) && priority>1) + if (authdir_mode(get_options())) return 0; /* there's always room to answer v2 if we're an auth dir */ if (!connection_is_rate_limited(conn)) return 0; /* local conns don't get limited */ if (smaller_bucket < attempt) - return 1; /* not enough space no matter the priority */ + return 1; /* not enough space. */ { const time_t diff = approx_time() - write_buckets_last_empty_at; if (diff <= 1) return 1; /* we're already hitting our limits, no more please */ } - - if (priority == 1) { /* old-style v1 query */ - /* Could we handle *two* of these requests within the next two seconds? */ - const or_options_t *options = get_options(); - size_t can_write = (size_t) (smaller_bucket - + 2*(options->RelayBandwidthRate ? options->RelayBandwidthRate : - options->BandwidthRate)); - if (can_write < 2*attempt) - return 1; - } else { /* v2 query */ - /* no further constraints yet */ - } return 0; } diff --git a/src/core/mainloop/connection.h b/src/core/mainloop/connection.h index 4f15c1dd6f..30cf65e4e6 100644 --- a/src/core/mainloop/connection.h +++ b/src/core/mainloop/connection.h @@ -197,7 +197,7 @@ void connection_mark_all_noncontrol_connections(void); ssize_t connection_bucket_write_limit(struct connection_t *conn, time_t now); int connection_dir_is_global_write_low(struct connection_t *conn, - size_t attempt, int priority); + size_t attempt); void connection_bucket_init(void); void connection_bucket_adjust(const or_options_t *options); void connection_bucket_refill_all(time_t now, diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c index 266729cdd3..59cdcc5e02 100644 --- a/src/feature/dircache/dircache.c +++ b/src/feature/dircache/dircache.c @@ -951,7 +951,7 @@ handle_get_current_consensus(dir_connection_t *conn, goto done; } - if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) { log_debug(LD_DIRSERV, "Client asked for network status lists, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1060,7 +1060,7 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) } }); - if (connection_dir_is_global_write_low(TO_CONN(conn), estimated_len, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), estimated_len)) { write_short_http_response(conn, 503, "Directory busy, try again later"); goto vote_done; } @@ -1119,7 +1119,7 @@ handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args) write_short_http_response(conn, 404, "Not found"); goto done; } - if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1217,7 +1217,7 @@ handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args) msg = "Not found"; write_short_http_response(conn, 404, msg); } else { - if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1314,8 +1314,7 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) len += c->cache_info.signed_descriptor_len); if (connection_dir_is_global_write_low(TO_CONN(conn), - compress_method != NO_METHOD ? len/2 : len, - 2)) { + compress_method != NO_METHOD ? len/2 : len)) { write_short_http_response(conn, 503, "Directory busy, try again later"); goto keys_done; } From 872f95ca0603ac96d77b886ff67aba63941547a9 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 28 Jan 2020 08:55:20 -0500 Subject: [PATCH 3/9] mainloop: Modernize a bit connection_dir_is_global_write_low() Signed-off-by: David Goulet --- src/core/mainloop/connection.c | 21 ++++++++++++--------- src/core/mainloop/connection.h | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 9a07a62c29..a157c0f3fb 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -3051,7 +3051,7 @@ connection_mark_all_noncontrol_connections(void) * uses pluggable transports, since we should then limit it even if it * comes from an internal IP address. */ static int -connection_is_rate_limited(connection_t *conn) +connection_is_rate_limited(const connection_t *conn) { const or_options_t *options = get_options(); if (conn->linked) @@ -3190,7 +3190,10 @@ connection_bucket_write_limit(connection_t *conn, time_t now) * shouldn't send attempt bytes of low-priority directory stuff * out to conn. * - * There are a lot of parameters we could use here: + * If we are a directory authority, always answer dir requests thus true is + * always returned. + * + * Note: There are a lot of parameters we could use here: * - global_relayed_write_bucket. Low is bad. * - global_write_bucket. Low is bad. * - bandwidthrate. Low is bad. @@ -3202,27 +3205,27 @@ connection_bucket_write_limit(connection_t *conn, time_t now) * mean is "total directory bytes added to outbufs recently", but * that's harder to quantify and harder to keep track of. */ -int -connection_dir_is_global_write_low(connection_t *conn, size_t attempt) +bool +connection_dir_is_global_write_low(const connection_t *conn, size_t attempt) { size_t smaller_bucket = MIN(token_bucket_rw_get_write(&global_bucket), token_bucket_rw_get_write(&global_relayed_bucket)); if (authdir_mode(get_options())) - return 0; /* there's always room to answer v2 if we're an auth dir */ + return false; /* there's always room to answer v2 if we're an auth dir */ if (!connection_is_rate_limited(conn)) - return 0; /* local conns don't get limited */ + return false; /* local conns don't get limited */ if (smaller_bucket < attempt) - return 1; /* not enough space. */ + return true; /* not enough space. */ { const time_t diff = approx_time() - write_buckets_last_empty_at; if (diff <= 1) - return 1; /* we're already hitting our limits, no more please */ + return true; /* we're already hitting our limits, no more please */ } - return 0; + return false; } /** When did we last tell the accounting subsystem about transmitted diff --git a/src/core/mainloop/connection.h b/src/core/mainloop/connection.h index 30cf65e4e6..668c740042 100644 --- a/src/core/mainloop/connection.h +++ b/src/core/mainloop/connection.h @@ -196,8 +196,8 @@ void connection_mark_all_noncontrol_listeners(void); void connection_mark_all_noncontrol_connections(void); ssize_t connection_bucket_write_limit(struct connection_t *conn, time_t now); -int connection_dir_is_global_write_low(struct connection_t *conn, - size_t attempt); +bool connection_dir_is_global_write_low(const struct connection_t *conn, + size_t attempt); void connection_bucket_init(void); void connection_bucket_adjust(const or_options_t *options); void connection_bucket_refill_all(time_t now, From 4152c349b4b9f7b2336a9d97356a28471e8d8f27 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 28 Jan 2020 09:17:34 -0500 Subject: [PATCH 4/9] nodelist: Helper to add an address to the nodelist address set We separate v4 and v6 because we often use an IPv4 address represented with a uint32_t instead of a tor_addr_t. This will be used to also add the trusted directory addresses taken from the configuration. The trusted directories from the consensus are already added to the address set from their descriptor. Signed-off-by: David Goulet --- src/feature/nodelist/nodelist.c | 31 ++++++++++++++++++++++++++----- src/feature/nodelist/nodelist.h | 2 ++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index bd80fc1b4b..90c655d12c 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -455,22 +455,43 @@ node_add_to_address_set(const node_t *node) if (node->rs) { if (node->rs->addr) - address_set_add_ipv4h(the_nodelist->node_addrs, node->rs->addr); + nodelist_add_addr4_to_address_set(node->rs->addr); if (!tor_addr_is_null(&node->rs->ipv6_addr)) - address_set_add(the_nodelist->node_addrs, &node->rs->ipv6_addr); + nodelist_add_addr6_to_address_set(&node->rs->ipv6_addr); } if (node->ri) { if (node->ri->addr) - address_set_add_ipv4h(the_nodelist->node_addrs, node->ri->addr); + nodelist_add_addr4_to_address_set(node->ri->addr); if (!tor_addr_is_null(&node->ri->ipv6_addr)) - address_set_add(the_nodelist->node_addrs, &node->ri->ipv6_addr); + nodelist_add_addr6_to_address_set(&node->ri->ipv6_addr); } if (node->md) { if (!tor_addr_is_null(&node->md->ipv6_addr)) - address_set_add(the_nodelist->node_addrs, &node->md->ipv6_addr); + nodelist_add_addr6_to_address_set(&node->md->ipv6_addr); } } +/** Add the given v4 address into the nodelist address set. */ +void +nodelist_add_addr4_to_address_set(const uint32_t addr) +{ + if (!the_nodelist || !the_nodelist->node_addrs || addr == 0) { + return; + } + address_set_add_ipv4h(the_nodelist->node_addrs, addr); +} + +/** Add the given v6 address into the nodelist address set. */ +void +nodelist_add_addr6_to_address_set(const tor_addr_t *addr) +{ + if (BUG(!addr) || tor_addr_is_null(addr) || tor_addr_is_v4(addr) || + !the_nodelist || !the_nodelist->node_addrs) { + return; + } + address_set_add(the_nodelist->node_addrs, addr); +} + /** Return true if addr is the address of some node in the nodelist. * If not, probably return false. */ int diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index af144c197f..87cfa48e25 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -35,6 +35,8 @@ node_t *nodelist_add_microdesc(microdesc_t *md); void nodelist_set_consensus(networkstatus_t *ns); void nodelist_ensure_freshness(networkstatus_t *ns); int nodelist_probably_contains_address(const tor_addr_t *addr); +void nodelist_add_addr4_to_address_set(const uint32_t addr); +void nodelist_add_addr6_to_address_set(const tor_addr_t *addr); void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md); void nodelist_remove_routerinfo(routerinfo_t *ri); From bd4f4cb5f0f98f224f0f707c6dd00d2fd8e55a7a Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 28 Jan 2020 09:26:28 -0500 Subject: [PATCH 5/9] dirlist: Add configured trusted dir to the nodelist address set The configured, within the torrc or hardcoded, directory authorities addresses are now added to the nodelist address set. Signed-off-by: David Goulet --- src/feature/nodelist/dirlist.c | 31 +++++++++++++++++++++++++++++++ src/feature/nodelist/dirlist.h | 2 ++ src/feature/nodelist/nodelist.c | 12 +++++++++--- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/feature/nodelist/dirlist.c b/src/feature/nodelist/dirlist.c index e2a1d6a9fa..df347889bf 100644 --- a/src/feature/nodelist/dirlist.c +++ b/src/feature/nodelist/dirlist.c @@ -49,6 +49,37 @@ static smartlist_t *trusted_dir_servers = NULL; * and all fallback directory servers. */ static smartlist_t *fallback_dir_servers = NULL; +/** Helper: From a given trusted directory entry, add the v4 or/and v6 address + * to the nodelist address set. */ +static void +add_trusted_dir_to_nodelist_addr_set(const dir_server_t *dir) +{ + tor_assert(dir); + tor_assert(dir->is_authority); + + /* Add IPv4 and then IPv6 if applicable. */ + nodelist_add_addr4_to_address_set(dir->addr); + if (!tor_addr_is_null(&dir->ipv6_addr)) { + nodelist_add_addr6_to_address_set(&dir->ipv6_addr); + } +} + +/** Go over the trusted directory server list and add their address(es) to the + * nodelist address set. This is called everytime a new consensus is set. */ +void +dirlist_add_trusted_addresses(void) +{ + if (!trusted_dir_servers) { + return; + } + + SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, const dir_server_t *, ent) { + if (ent->is_authority) { + add_trusted_dir_to_nodelist_addr_set(ent); + } + } SMARTLIST_FOREACH_END(ent); +} + /** Return the number of directory authorities whose type matches some bit set * in type */ int diff --git a/src/feature/nodelist/dirlist.h b/src/feature/nodelist/dirlist.h index b6dda32d85..d302ff5f6c 100644 --- a/src/feature/nodelist/dirlist.h +++ b/src/feature/nodelist/dirlist.h @@ -44,4 +44,6 @@ void dir_server_add(dir_server_t *ent); void clear_dir_servers(void); void dirlist_free_all(void); +void dirlist_add_trusted_addresses(void); + #endif /* !defined(TOR_DIRLIST_H) */ diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index 90c655d12c..34214880d9 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -633,9 +633,12 @@ nodelist_set_consensus(networkstatus_t *ns) SMARTLIST_FOREACH(the_nodelist->nodes, node_t *, node, node->rs = NULL); - /* Conservatively estimate that every node will have 2 addresses. */ - const int estimated_addresses = smartlist_len(ns->routerstatus_list) * - get_estimated_address_per_node(); + /* Conservatively estimate that every node will have 2 addresses (v4 and + * v6). Then we add the number of configured trusted authorities we have. */ + int estimated_addresses = smartlist_len(ns->routerstatus_list) * + get_estimated_address_per_node(); + estimated_addresses += (get_n_authorities(V3_DIRINFO & BRIDGE_DIRINFO) * + get_estimated_address_per_node()); address_set_free(the_nodelist->node_addrs); the_nodelist->node_addrs = address_set_new(estimated_addresses); @@ -686,6 +689,9 @@ nodelist_set_consensus(networkstatus_t *ns) SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { node_add_to_address_set(node); } SMARTLIST_FOREACH_END(node); + /* Then, add all trusted configured directories. Some might not be in the + * consensus so make sure we know them. */ + dirlist_add_trusted_addresses(); if (! authdir) { SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { From 735aa208b1592e166d03ec96e90422293d26b98a Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 11 Feb 2020 09:56:44 -0500 Subject: [PATCH 6/9] dirauth: Add option AuthDirRejectRequestsUnderLoad This controls the previous feature added that makes dirauth send back a 503 error code on non relay connections if under bandwidth pressure. Signed-off-by: David Goulet --- doc/tor.1.txt | 7 +++++++ src/app/config/config.c | 1 + src/app/config/or_options_st.h | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 1504223b89..c7c41e7841 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -2925,6 +2925,13 @@ on the public Tor network. before it will treat advertised bandwidths as wholly unreliable. (Default: 500) +[[AuthDirRejectRequestsUnderLoad]] **AuthDirRejectRequestsUnderLoad** **0**|**1**:: + If set, the directory authority will start rejecting directory requests + from non relay connections by sending a 503 error code if it is under + bandwidth pressure (reaching the configured limit if any). Relays will + always tried to be answered even if this is on. (Default: 1) + + HIDDEN SERVICE OPTIONS ---------------------- diff --git a/src/app/config/config.c b/src/app/config/config.c index deda2448b6..89ec26f05c 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -671,6 +671,7 @@ static const config_var_t option_vars_[] = { OBSOLETE("UseNTorHandshake"), V(User, STRING, NULL), OBSOLETE("UserspaceIOCPBuffers"), + V(AuthDirRejectRequestsUnderLoad, BOOL, "1"), V(AuthDirSharedRandomness, BOOL, "1"), V(AuthDirTestEd25519LinkKeys, BOOL, "1"), OBSOLETE("V1AuthoritativeDirectory"), diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index 32dcd9fb18..e6be797017 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -1008,6 +1008,13 @@ struct or_options_t { */ uint64_t MaxUnparseableDescSizeToLog; + /** Bool (default: 1): Under bandwidth pressure, if set to 1, the authority + * will always answer directory requests from relays but will start sending + * 503 error code for the other connections. If set to 0, all connections + * are considered the same and the authority will try to answer them all + * regardless of bandwidth pressure or not. */ + int AuthDirRejectRequestsUnderLoad; + /** Bool (default: 1): Switch for the shared random protocol. Only * relevant to a directory authority. If off, the authority won't * participate in the protocol. If on (default), a flag is added to the From 6d9113d2f65b6e3142efdaa91a5b4761cd197be8 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 28 Jan 2020 09:39:09 -0500 Subject: [PATCH 7/9] dirauth: Resume sending 503 directory error code Authorities were never sending back 503 error code because by design they should be able to always answer directory requests regardless of bandwidth capacity. However, that recently backfired because of a large number of requests from unknown source using the DirPort that are _not_ getting their 503 code which overloaded the DirPort leading to the authority to be unable to answer to its fellow authorities. This is not a complete solution to the problem but it will help ease off the load on the authority side by sending back 503 codes *unless* the connection is from a known relay or an authority. Fixes #33029 Signed-off-by: David Goulet --- changes/ticket33029 | 5 +++++ src/core/mainloop/connection.c | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 changes/ticket33029 diff --git a/changes/ticket33029 b/changes/ticket33029 new file mode 100644 index 0000000000..c32ee4ad84 --- /dev/null +++ b/changes/ticket33029 @@ -0,0 +1,5 @@ + o Major bugfixes (directory authority): + - Directory authorities will now send a 503 (not enough bandwidth) code to + clients when under bandwidth pressure. Known relays and other authorities + will always be answered regardless of the bandwidth situation. Fixes bug + 33029; bugfix on 0.1.2.5-alpha. diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index a157c0f3fb..50cd3810a4 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -3211,8 +3211,21 @@ connection_dir_is_global_write_low(const connection_t *conn, size_t attempt) size_t smaller_bucket = MIN(token_bucket_rw_get_write(&global_bucket), token_bucket_rw_get_write(&global_relayed_bucket)); - if (authdir_mode(get_options())) - return false; /* there's always room to answer v2 if we're an auth dir */ + + /* Special case for authorities (directory only). */ + if (authdir_mode_v3(get_options())) { + /* Are we configured to possibly reject requests under load? */ + if (!get_options()->AuthDirRejectRequestsUnderLoad) { + /* Answer request no matter what. */ + return false; + } + /* Always answer requests from a known relay which includes the other + * authorities. The following looks up the addresses for relays that we + * have their descriptor _and_ any configured trusted directories. */ + if (nodelist_probably_contains_address(&conn->addr)) { + return false; + } + } if (!connection_is_rate_limited(conn)) return false; /* local conns don't get limited */ From 33414e5494b56f3f677d022bfb4bb1ed99191032 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 11 Feb 2020 10:15:04 -0500 Subject: [PATCH 8/9] test: Add unit test for connection_dir_is_global_write_low() Part of #33029 Signed-off-by: David Goulet --- src/feature/nodelist/dirlist.c | 4 +- src/feature/nodelist/dirlist.h | 2 +- src/test/test_address_set.c | 14 ++- src/test/test_bwmgt.c | 209 ++++++++++++++++++++++++++++++++- 4 files changed, 223 insertions(+), 6 deletions(-) diff --git a/src/feature/nodelist/dirlist.c b/src/feature/nodelist/dirlist.c index df347889bf..749ff06a57 100644 --- a/src/feature/nodelist/dirlist.c +++ b/src/feature/nodelist/dirlist.c @@ -66,8 +66,8 @@ add_trusted_dir_to_nodelist_addr_set(const dir_server_t *dir) /** Go over the trusted directory server list and add their address(es) to the * nodelist address set. This is called everytime a new consensus is set. */ -void -dirlist_add_trusted_addresses(void) +MOCK_IMPL(void, +dirlist_add_trusted_addresses, (void)) { if (!trusted_dir_servers) { return; diff --git a/src/feature/nodelist/dirlist.h b/src/feature/nodelist/dirlist.h index d302ff5f6c..d4bdb4cedc 100644 --- a/src/feature/nodelist/dirlist.h +++ b/src/feature/nodelist/dirlist.h @@ -44,6 +44,6 @@ void dir_server_add(dir_server_t *ent); void clear_dir_servers(void); void dirlist_free_all(void); -void dirlist_add_trusted_addresses(void); +MOCK_DECL(void, dirlist_add_trusted_addresses, (void)); #endif /* !defined(TOR_DIRLIST_H) */ diff --git a/src/test/test_address_set.c b/src/test/test_address_set.c index fb8408b3c3..81dc6dff52 100644 --- a/src/test/test_address_set.c +++ b/src/test/test_address_set.c @@ -4,6 +4,7 @@ #include "core/or/or.h" #include "lib/crypt_ops/crypto_rand.h" #include "core/or/address_set.h" +#include "feature/nodelist/dirlist.h" #include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nodelist.h" @@ -31,6 +32,12 @@ mock_networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f) return dummy_ns; } +static void +mock_dirlist_add_trusted_addresses(void) +{ + return; +} + /* Number of address a single node_t can have. Default to the production * value. This is to control the size of the bloom filter. */ static int addr_per_node = 2; @@ -98,6 +105,8 @@ test_nodelist(void *arg) mock_networkstatus_get_latest_consensus_by_flavor); MOCK(get_estimated_address_per_node, mock_get_estimated_address_per_node); + MOCK(dirlist_add_trusted_addresses, + mock_dirlist_add_trusted_addresses); dummy_ns = tor_malloc_zero(sizeof(*dummy_ns)); dummy_ns->flavor = FLAV_MICRODESC; @@ -113,7 +122,9 @@ test_nodelist(void *arg) * (the_nodelist->node_addrs) so we will fail the contain test rarely. */ addr_per_node = 1024; - /* No node no nothing. The lookups should be empty. */ + /* No node no nothing. The lookups should be empty. We've mocked the + * dirlist_add_trusted_addresses in order for _no_ authorities to be added + * to the filter else it makes this test to trigger many false positive. */ nodelist_set_consensus(dummy_ns); /* The address set should be empty. */ @@ -167,6 +178,7 @@ test_nodelist(void *arg) UNMOCK(networkstatus_get_latest_consensus); UNMOCK(networkstatus_get_latest_consensus_by_flavor); UNMOCK(get_estimated_address_per_node); + UNMOCK(dirlist_add_trusted_addresses); } struct testcase_t address_set_tests[] = { diff --git a/src/test/test_bwmgt.c b/src/test/test_bwmgt.c index 5a013aa268..e6f028ed74 100644 --- a/src/test/test_bwmgt.c +++ b/src/test/test_bwmgt.c @@ -6,18 +6,67 @@ * \brief tests for bandwidth management / token bucket functions */ +#define CONFIG_PRIVATE +#define CONNECTION_PRIVATE #define TOKEN_BUCKET_PRIVATE #include "core/or/or.h" -#include "test/test.h" +#include "app/config/config.h" +#include "core/mainloop/connection.h" +#include "feature/dircommon/directory.h" +#include "feature/nodelist/microdesc.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerlist.h" +#include "lib/crypt_ops/crypto_rand.h" #include "lib/evloop/token_bucket.h" +#include "test/test.h" +#include "test/test_helpers.h" + +#include "app/config/or_options_st.h" +#include "core/or/connection_st.h" +#include "feature/nodelist/microdesc_st.h" +#include "feature/nodelist/networkstatus_st.h" +#include "feature/nodelist/routerinfo_st.h" +#include "feature/nodelist/routerstatus_st.h" // an imaginary time, in timestamp units. Chosen so it will roll over. static const uint32_t START_TS = UINT32_MAX-10; static const int32_t KB = 1024; static const uint32_t GB = (UINT64_C(1) << 30); +static or_options_t mock_options; + +static const or_options_t * +mock_get_options(void) +{ + return &mock_options; +} + +static networkstatus_t *dummy_ns = NULL; +static networkstatus_t * +mock_networkstatus_get_latest_consensus(void) +{ + return dummy_ns; +} + +static networkstatus_t * +mock_networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f) +{ + tor_assert(f == FLAV_MICRODESC); + return dummy_ns; +} + +/* Number of address a single node_t can have. Default to the production + * value. This is to control the size of the bloom filter. */ +static int addr_per_node = 2; +static int +mock_get_estimated_address_per_node(void) +{ + return addr_per_node; +} + static void test_bwmgt_token_buf_init(void *arg) { @@ -220,8 +269,162 @@ test_bwmgt_token_buf_helpers(void *arg) ; } +static void +test_bwmgt_dir_conn_global_write_low(void *arg) +{ + bool ret; + int addr_family; + connection_t *conn = NULL; + routerstatus_t *rs = NULL; microdesc_t *md = NULL; routerinfo_t *ri = NULL; + tor_addr_t relay_addr; + + (void) arg; + + memset(&mock_options, 0, sizeof(or_options_t)); + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus); + MOCK(networkstatus_get_latest_consensus_by_flavor, + mock_networkstatus_get_latest_consensus_by_flavor); + MOCK(get_estimated_address_per_node, + mock_get_estimated_address_per_node); + + /* + * The following is rather complex but that is what it takes to add a dummy + * consensus with a valid routerlist which will populate our node address + * set that we need to lookup to test the known relay code path. + * + * We MUST do that before we MOCK(get_options) else it is another world of + * complexity. + */ + + /* This will be the address of our relay. */ + tor_addr_parse(&relay_addr, "1.2.3.4"); + + /* We'll now add a relay into our routerlist and see if we let it. */ + dummy_ns = tor_malloc_zero(sizeof(*dummy_ns)); + dummy_ns->flavor = FLAV_MICRODESC; + dummy_ns->routerstatus_list = smartlist_new(); + + md = tor_malloc_zero(sizeof(*md)); + ri = tor_malloc_zero(sizeof(*ri)); + rs = tor_malloc_zero(sizeof(*rs)); + crypto_rand(rs->identity_digest, sizeof(rs->identity_digest)); + crypto_rand(md->digest, sizeof(md->digest)); + memcpy(rs->descriptor_digest, md->digest, DIGEST256_LEN); + + /* Set IP address. */ + rs->addr = tor_addr_to_ipv4h(&relay_addr); + ri->addr = rs->addr; + /* Add the rs to the consensus becoming a node_t. */ + smartlist_add(dummy_ns->routerstatus_list, rs); + + /* Add all configured authorities (hardcoded) before we set the consensus so + * the address set exists. */ + ret = consider_adding_dir_servers(&mock_options, &mock_options); + tt_int_op(ret, OP_EQ, 0); + + /* This will make the nodelist bloom filter very large + * (the_nodelist->node_addrs) so we will fail the contain test rarely. */ + addr_per_node = 1024; + + nodelist_set_consensus(dummy_ns); + + /* Ok, now time to control which options we use. */ + MOCK(get_options, mock_get_options); + + /* Set ourselves as an authoritative dir. */ + mock_options.AuthoritativeDir = 1; + mock_options.V3AuthoritativeDir = 1; + mock_options.UseDefaultFallbackDirs = 0; + + /* This will set our global bucket to 1 byte and thus we will hit the + * banwdith limit in our test. */ + mock_options.BandwidthRate = 1; + mock_options.BandwidthBurst = 1; + + /* Else an IPv4 address screams. */ + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 1; + + /* Initialize the global buckets. */ + connection_bucket_init(); + + /* The address "127.0.0.1" is set with this helper. */ + conn = test_conn_get_connection(DIR_CONN_STATE_MIN_, CONN_TYPE_DIR, + DIR_PURPOSE_MIN_); + tt_assert(conn); + + /* First try a non authority non relay IP thus a client but we are not + * configured to reject requests under load so we should get a false value + * that our limit is _not_ low. */ + addr_family = tor_addr_parse(&conn->addr, "1.1.1.1"); + tt_int_op(addr_family, OP_EQ, AF_INET); + ret = connection_dir_is_global_write_low(conn, INT_MAX); + tt_int_op(ret, OP_EQ, 0); + + /* Now, we will reject requests under load so try again a non authority non + * relay IP thus a client. We should get a warning that our limit is too + * low. */ + mock_options.AuthDirRejectRequestsUnderLoad = 1; + + addr_family = tor_addr_parse(&conn->addr, "1.1.1.1"); + tt_int_op(addr_family, OP_EQ, AF_INET); + ret = connection_dir_is_global_write_low(conn, INT_MAX); + tt_int_op(ret, OP_EQ, 1); + + /* Now, lets try with a connection address from moria1. It should always + * pass even though our limit is too low. */ + addr_family = tor_addr_parse(&conn->addr, "128.31.0.39"); + tt_int_op(addr_family, OP_EQ, AF_INET); + ret = connection_dir_is_global_write_low(conn, INT_MAX); + tt_int_op(ret, OP_EQ, 0); + + /* IPv6 testing of gabelmoo. */ + addr_family = tor_addr_parse(&conn->addr, "[2001:638:a000:4140::ffff:189]"); + tt_int_op(addr_family, OP_EQ, AF_INET6); + ret = connection_dir_is_global_write_low(conn, INT_MAX); + tt_int_op(ret, OP_EQ, 0); + + /* Lets retry with a known relay address. It should pass. Possible due to + * our consensus setting above. */ + memcpy(&conn->addr, &relay_addr, sizeof(tor_addr_t)); + ret = connection_dir_is_global_write_low(conn, INT_MAX); + tt_int_op(ret, OP_EQ, 0); + + /* Lets retry with a random IP that is not an authority nor a relay. */ + addr_family = tor_addr_parse(&conn->addr, "1.2.3.4"); + tt_int_op(addr_family, OP_EQ, AF_INET); + ret = connection_dir_is_global_write_low(conn, INT_MAX); + tt_int_op(ret, OP_EQ, 0); + + /* Finally, just make sure it still denies an IP if we are _not_ a v3 + * directory authority. */ + mock_options.V3AuthoritativeDir = 0; + addr_family = tor_addr_parse(&conn->addr, "1.2.3.4"); + tt_int_op(addr_family, OP_EQ, AF_INET); + ret = connection_dir_is_global_write_low(conn, INT_MAX); + tt_int_op(ret, OP_EQ, 1); + + /* Random IPv6 should not be allowed. */ + addr_family = tor_addr_parse(&conn->addr, "[CAFE::ACAB]"); + tt_int_op(addr_family, OP_EQ, AF_INET6); + ret = connection_dir_is_global_write_low(conn, INT_MAX); + tt_int_op(ret, OP_EQ, 1); + + done: + connection_free_minimal(conn); + routerstatus_free(rs); routerinfo_free(ri); microdesc_free(md); + smartlist_clear(dummy_ns->routerstatus_list); + networkstatus_vote_free(dummy_ns); + + UNMOCK(get_estimated_address_per_node); + UNMOCK(networkstatus_get_latest_consensus); + UNMOCK(networkstatus_get_latest_consensus_by_flavor); + UNMOCK(get_options); +} + #define BWMGT(name) \ - { #name, test_bwmgt_ ## name , 0, NULL, NULL } + { #name, test_bwmgt_ ## name , TT_FORK, NULL, NULL } struct testcase_t bwmgt_tests[] = { BWMGT(token_buf_init), @@ -229,5 +432,7 @@ struct testcase_t bwmgt_tests[] = { BWMGT(token_buf_dec), BWMGT(token_buf_refill), BWMGT(token_buf_helpers), + + BWMGT(dir_conn_global_write_low), END_OF_TESTCASES }; From 7b4d9fabefa97e1121482ace6bc712cd2b00ff0f Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 6 Feb 2020 09:49:00 -0500 Subject: [PATCH 9/9] dirauth: Rename function for better clarity Signed-off-by: David Goulet --- src/feature/nodelist/dirlist.c | 2 +- src/feature/nodelist/dirlist.h | 2 +- src/feature/nodelist/nodelist.c | 2 +- src/test/test_address_set.c | 13 +++++++------ 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/feature/nodelist/dirlist.c b/src/feature/nodelist/dirlist.c index 749ff06a57..ccbb378513 100644 --- a/src/feature/nodelist/dirlist.c +++ b/src/feature/nodelist/dirlist.c @@ -67,7 +67,7 @@ add_trusted_dir_to_nodelist_addr_set(const dir_server_t *dir) /** Go over the trusted directory server list and add their address(es) to the * nodelist address set. This is called everytime a new consensus is set. */ MOCK_IMPL(void, -dirlist_add_trusted_addresses, (void)) +dirlist_add_trusted_dir_addresses, (void)) { if (!trusted_dir_servers) { return; diff --git a/src/feature/nodelist/dirlist.h b/src/feature/nodelist/dirlist.h index d4bdb4cedc..c49162f1e9 100644 --- a/src/feature/nodelist/dirlist.h +++ b/src/feature/nodelist/dirlist.h @@ -44,6 +44,6 @@ void dir_server_add(dir_server_t *ent); void clear_dir_servers(void); void dirlist_free_all(void); -MOCK_DECL(void, dirlist_add_trusted_addresses, (void)); +MOCK_DECL(void, dirlist_add_trusted_dir_addresses, (void)); #endif /* !defined(TOR_DIRLIST_H) */ diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index 34214880d9..9191173c3b 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -691,7 +691,7 @@ nodelist_set_consensus(networkstatus_t *ns) } SMARTLIST_FOREACH_END(node); /* Then, add all trusted configured directories. Some might not be in the * consensus so make sure we know them. */ - dirlist_add_trusted_addresses(); + dirlist_add_trusted_dir_addresses(); if (! authdir) { SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { diff --git a/src/test/test_address_set.c b/src/test/test_address_set.c index 81dc6dff52..6e299d779e 100644 --- a/src/test/test_address_set.c +++ b/src/test/test_address_set.c @@ -33,7 +33,7 @@ mock_networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f) } static void -mock_dirlist_add_trusted_addresses(void) +mock_dirlist_add_trusted_dir_addresses(void) { return; } @@ -105,8 +105,8 @@ test_nodelist(void *arg) mock_networkstatus_get_latest_consensus_by_flavor); MOCK(get_estimated_address_per_node, mock_get_estimated_address_per_node); - MOCK(dirlist_add_trusted_addresses, - mock_dirlist_add_trusted_addresses); + MOCK(dirlist_add_trusted_dir_addresses, + mock_dirlist_add_trusted_dir_addresses); dummy_ns = tor_malloc_zero(sizeof(*dummy_ns)); dummy_ns->flavor = FLAV_MICRODESC; @@ -123,8 +123,9 @@ test_nodelist(void *arg) addr_per_node = 1024; /* No node no nothing. The lookups should be empty. We've mocked the - * dirlist_add_trusted_addresses in order for _no_ authorities to be added - * to the filter else it makes this test to trigger many false positive. */ + * dirlist_add_trusted_dir_addresses in order for _no_ authorities to be + * added to the filter else it makes this test to trigger many false + * positive. */ nodelist_set_consensus(dummy_ns); /* The address set should be empty. */ @@ -178,7 +179,7 @@ test_nodelist(void *arg) UNMOCK(networkstatus_get_latest_consensus); UNMOCK(networkstatus_get_latest_consensus_by_flavor); UNMOCK(get_estimated_address_per_node); - UNMOCK(dirlist_add_trusted_addresses); + UNMOCK(dirlist_add_trusted_dir_addresses); } struct testcase_t address_set_tests[] = {