aboutsummaryrefslogtreecommitdiff
path: root/src/feature
diff options
context:
space:
mode:
Diffstat (limited to 'src/feature')
-rw-r--r--src/feature/api/tor_api.c4
-rw-r--r--src/feature/client/bridges.c22
-rw-r--r--src/feature/client/bridges.h3
-rw-r--r--src/feature/client/circpathbias.c11
-rw-r--r--src/feature/client/entrynodes.c299
-rw-r--r--src/feature/client/entrynodes.h5
-rw-r--r--src/feature/client/transports.c14
-rw-r--r--src/feature/control/control_cmd.c4
-rw-r--r--src/feature/dirauth/dirauth_options.inc7
-rw-r--r--src/feature/dirauth/dirvote.c55
-rw-r--r--src/feature/dirauth/dirvote.h6
-rw-r--r--src/feature/dirauth/process_descs.c23
-rw-r--r--src/feature/dirauth/process_descs.h3
-rw-r--r--src/feature/dirauth/voteflags.c11
-rw-r--r--src/feature/dirauth/voteflags.h3
-rw-r--r--src/feature/dircache/dircache.c2
-rw-r--r--src/feature/dirclient/dir_server_st.h6
-rw-r--r--src/feature/dirclient/dirclient.c38
-rw-r--r--src/feature/dirclient/dlstatus.c17
-rw-r--r--src/feature/dircommon/consdiff.c2
-rw-r--r--src/feature/dirparse/ns_parse.c2
-rw-r--r--src/feature/hs/hs_config.c23
-rw-r--r--src/feature/hs/hs_descriptor.c16
-rw-r--r--src/feature/hs/hs_descriptor.h19
-rw-r--r--src/feature/hs/hs_metrics.c18
-rw-r--r--src/feature/nodelist/dirlist.c144
-rw-r--r--src/feature/nodelist/dirlist.h35
-rw-r--r--src/feature/nodelist/fmt_routerstatus.c3
-rw-r--r--src/feature/nodelist/networkstatus.c8
-rw-r--r--src/feature/nodelist/node_st.h2
-rw-r--r--src/feature/nodelist/nodelist.c10
-rw-r--r--src/feature/nodelist/routerlist.c33
-rw-r--r--src/feature/nodelist/routerlist.h1
-rw-r--r--src/feature/nodelist/routerstatus_st.h2
-rw-r--r--src/feature/relay/dns.c15
-rw-r--r--src/feature/relay/include.am2
-rw-r--r--src/feature/relay/onion_queue.c1
-rw-r--r--src/feature/relay/relay_metrics.c401
-rw-r--r--src/feature/relay/relay_metrics.h55
-rw-r--r--src/feature/relay/relay_periodic.c2
-rw-r--r--src/feature/relay/relay_sys.c5
-rw-r--r--src/feature/rend/rendmid.c6
-rw-r--r--src/feature/stats/geoip_stats.c7
-rw-r--r--src/feature/stats/rephist.c351
-rw-r--r--src/feature/stats/rephist.h20
45 files changed, 1545 insertions, 171 deletions
diff --git a/src/feature/api/tor_api.c b/src/feature/api/tor_api.c
index 051be50b3a..88e91ebfd5 100644
--- a/src/feature/api/tor_api.c
+++ b/src/feature/api/tor_api.c
@@ -18,9 +18,9 @@
// Include this after the above headers, to insure that they don't
// depend on anything else.
#include "orconfig.h"
+#include "lib/cc/compat_compiler.h"
#include "lib/cc/torint.h"
#include "feature/api/tor_api_internal.h"
-#include "lib/cc/compat_compiler.h"
#include <stdio.h>
#include <stdlib.h>
@@ -39,7 +39,9 @@
#include "lib/net/socketpair.h"
#define raw_socketpair tor_ersatz_socketpair
#define raw_closesocket closesocket
+#if !defined(HAVE_SNPRINTF)
#define snprintf _snprintf
+#endif
#else /* !defined(_WIN32) */
#define raw_socketpair socketpair
#define raw_closesocket close
diff --git a/src/feature/client/bridges.c b/src/feature/client/bridges.c
index d40bcc6c8e..9e36d26929 100644
--- a/src/feature/client/bridges.c
+++ b/src/feature/client/bridges.c
@@ -943,9 +943,17 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node)
}
/** We just learned a descriptor for a bridge. See if that
- * digest is in our entry guard list, and add it if not. */
+ * digest is in our entry guard list, and add it if not. Schedule the
+ * next fetch for a long time from now, and initiate any follow-up
+ * activities like continuing to bootstrap.
+ *
+ * <b>from_cache</b> * tells us whether we fetched it from disk (else
+ * the network)
+ *
+ * <b>desc_is_new</b> tells us if we preferred it to the old version we
+ * had, if any. */
void
-learned_bridge_descriptor(routerinfo_t *ri, int from_cache)
+learned_bridge_descriptor(routerinfo_t *ri, int from_cache, int desc_is_new)
{
tor_assert(ri);
tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE);
@@ -961,12 +969,14 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache)
if (bridge) { /* if we actually want to use this one */
node_t *node;
- /* it's here; schedule its re-fetch for a long time from now. */
if (!from_cache) {
/* This schedules the re-fetch at a constant interval, which produces
* a pattern of bridge traffic. But it's better than trying all
* configured bridges several times in the first few minutes. */
download_status_reset(&bridge->fetch_status);
+ /* it's here; schedule its re-fetch for a long time from now. */
+ bridge->fetch_status.next_attempt_at +=
+ get_options()->TestingBridgeDownloadInitialDelay;
}
node = node_get_mutable_by_id(ri->cache_info.identity_digest);
@@ -982,8 +992,10 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache)
entry_guard_learned_bridge_identity(&bridge->addrport_configured,
(const uint8_t*)ri->cache_info.identity_digest);
- log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname,
- from_cache ? "cached" : "fresh", router_describe(ri));
+ if (desc_is_new)
+ log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s",
+ ri->nickname,
+ from_cache ? "cached" : "fresh", router_describe(ri));
/* If we didn't have a reachable bridge before this one, try directory
* documents again. */
if (first) {
diff --git a/src/feature/client/bridges.h b/src/feature/client/bridges.h
index a42363f683..dd3e498a0a 100644
--- a/src/feature/client/bridges.h
+++ b/src/feature/client/bridges.h
@@ -46,7 +46,8 @@ void learned_router_identity(const tor_addr_t *addr, uint16_t port,
void bridge_add_from_config(struct bridge_line_t *bridge_line);
void retry_bridge_descriptor_fetch_directly(const char *digest);
void fetch_bridge_descriptors(const or_options_t *options, time_t now);
-void learned_bridge_descriptor(routerinfo_t *ri, int from_cache);
+void learned_bridge_descriptor(routerinfo_t *ri,
+ int from_cache, int desc_is_new);
const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr,
uint16_t port);
diff --git a/src/feature/client/circpathbias.c b/src/feature/client/circpathbias.c
index 9c0ecc56ad..ff9e05a645 100644
--- a/src/feature/client/circpathbias.c
+++ b/src/feature/client/circpathbias.c
@@ -363,6 +363,17 @@ pathbias_should_count(origin_circuit_t *circ)
return 0;
}
+ /* Ignore circuits where the controller helped choose the path. When
+ * this happens, we can't be sure whether the path was chosen randomly
+ * or not. */
+ if (circ->any_hop_from_controller) {
+ /* (In this case, we _don't_ check to see if shouldcount is changing,
+ * since it's possible that an already-created circuit later gets extended
+ * by the controller. */
+ circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_IGNORED;
+ return 0;
+ }
+
/* Completely ignore one hop circuits */
if (circ->build_state->onehop_tunnel ||
circ->build_state->desired_path_len == 1) {
diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c
index 502cb99690..32ecb4f705 100644
--- a/src/feature/client/entrynodes.c
+++ b/src/feature/client/entrynodes.c
@@ -132,6 +132,7 @@
#include "feature/client/entrynodes.h"
#include "feature/client/transports.h"
#include "feature/control/control_events.h"
+#include "feature/dirclient/dlstatus.h"
#include "feature/dircommon/directory.h"
#include "feature/nodelist/describe.h"
#include "feature/nodelist/microdesc.h"
@@ -559,7 +560,7 @@ get_extreme_restriction_threshold(void)
int32_t pct = networkstatus_get_param(NULL,
"guard-extreme-restriction-percent",
DFLT_EXTREME_RESTRICTION_PERCENT,
- 1, INT32_MAX);
+ 1, 100);
return pct / 100.0;
}
@@ -576,6 +577,18 @@ mark_guard_maybe_reachable(entry_guard_t *guard)
guard->is_reachable = GUARD_REACHABLE_MAYBE;
if (guard->is_filtered_guard)
guard->is_usable_filtered_guard = 1;
+
+ /* Check if it is a bridge and we don't have its descriptor yet */
+ if (guard->bridge_addr && !guard_has_descriptor(guard)) {
+ /* Reset the descriptor fetch retry schedule, so it gives it another
+ * go soon. It's important to keep any "REACHABLE_MAYBE" bridges in
+ * sync with the descriptor fetch schedule, since we will refuse to
+ * use the network until our first primary bridges are either
+ * known-usable or known-unusable. See bug 40396. */
+ download_status_t *dl = get_bridge_dl_status_by_id(guard->identity);
+ if (dl)
+ download_status_reset(dl);
+ }
}
/**
@@ -2046,6 +2059,14 @@ entry_guard_consider_retry(entry_guard_t *guard)
get_retry_schedule(guard->failing_since, now, guard->is_primary);
const time_t last_attempt = guard->last_tried_to_connect;
+ /* Check if it is a bridge and we don't have its descriptor yet */
+ if (guard->bridge_addr && !guard_has_descriptor(guard)) {
+ /* We want to leave the retry schedule to fetch_bridge_descriptors(),
+ * so we don't have two retry schedules clobbering each other. See
+ * bugs 40396 and 40497 for details of why we need this exception. */
+ return;
+ }
+
if (BUG(last_attempt == 0) ||
now >= last_attempt + delay) {
/* We should mark this retriable. */
@@ -2271,6 +2292,13 @@ entry_guards_note_guard_failure(guard_selection_t *gs,
guard->is_primary?"primary ":"",
guard->confirmed_idx>=0?"confirmed ":"",
entry_guard_describe(guard));
+
+ /* Schedule a re-assessment of whether we have enough dir info to
+ * use the network. Counterintuitively, *losing* a bridge might actually
+ * be just what we need to *resume* using the network, if we had it in
+ * state GUARD_REACHABLE_MAYBE and we were stalling to learn this
+ * outcome. See bug 40396 for more details. */
+ router_dir_info_changed();
}
/**
@@ -2295,6 +2323,12 @@ entry_guards_note_guard_success(guard_selection_t *gs,
/* If guard was not already marked as reachable, send a GUARD UP signal */
if (guard->is_reachable != GUARD_REACHABLE_YES) {
control_event_guard(guard->nickname, guard->identity, "UP");
+
+ /* Schedule a re-assessment of whether we have enough dir info to
+ * use the network. One of our guards has just moved to
+ * GUARD_REACHABLE_YES, so maybe we can resume using the network
+ * now. */
+ router_dir_info_changed();
}
guard->is_reachable = GUARD_REACHABLE_YES;
@@ -3538,6 +3572,11 @@ entry_guards_changed_for_guard_selection(guard_selection_t *gs)
entry_guards_update_guards_in_state()
*/
or_state_mark_dirty(get_or_state(), when);
+
+ /* Schedule a re-assessment of whether we have enough dir info to
+ * use the network. When we add or remove or disable or enable a
+ * guard, the decision could shift. */
+ router_dir_info_changed();
}
/** Our list of entry guards has changed for the default guard selection
@@ -3930,6 +3969,253 @@ guard_selection_free_(guard_selection_t *gs)
tor_free(gs);
}
+/**********************************************************************/
+
+/** Layer2 guard subsystem (vanguards-lite) used for onion service circuits */
+
+/** A simple representation of a layer2 guard. We just need its identity so
+ * that we feed it into a routerset, and a sampled timestamp to do expiration
+ * checks. */
+typedef struct layer2_guard_t {
+ /** Identity of the guard */
+ char identity[DIGEST_LEN];
+ /** When does this guard expire? (randomized timestamp) */
+ time_t expire_on_date;
+} layer2_guard_t;
+
+#define layer2_guard_free(val) \
+ FREE_AND_NULL(layer2_guard_t, layer2_guard_free_, (val))
+
+/** Return true if the vanguards-lite subsystem is enabled */
+bool
+vanguards_lite_is_enabled(void)
+{
+ /* First check torrc option and then maybe also the consensus parameter. */
+ const or_options_t *options = get_options();
+
+ /* If the option is explicitly disabled, that's the final word here */
+ if (options->VanguardsLiteEnabled == 0) {
+ return false;
+ }
+
+ /* If the option is set to auto, then check the consensus parameter */
+ if (options->VanguardsLiteEnabled == -1) {
+ return networkstatus_get_param(NULL, "vanguards-lite-enabled",
+ 1, /* default to "on" */
+ 0, 1);
+ }
+
+ /* else it's enabled */
+ tor_assert_nonfatal(options->VanguardsLiteEnabled == 1);
+ return options->VanguardsLiteEnabled;
+}
+
+static void
+layer2_guard_free_(layer2_guard_t *l2)
+{
+ if (!l2) {
+ return;
+ }
+
+ tor_free(l2);
+}
+
+/** Global list and routerset of L2 guards. They are both synced and they get
+ * updated periodically. We need both the list and the routerset: we use the
+ * smartlist to keep track of expiration times and the routerset is what we
+ * return to the users of this subsystem. */
+static smartlist_t *layer2_guards = NULL;
+static routerset_t *layer2_routerset = NULL;
+
+/** Number of L2 guards */
+#define NUMBER_SECOND_GUARDS 4
+/** Make sure that the number of L2 guards is less than the number of
+ * MAX_SANE_RESTRICTED_NODES */
+CTASSERT(NUMBER_SECOND_GUARDS < 20);
+
+/** Lifetime of L2 guards:
+ * 1 to 12 days, for an average of a week using the max(x,x) distribution */
+#define MIN_SECOND_GUARD_LIFETIME (3600*24)
+#define MAX_SECOND_GUARD_LIFETIME (3600*24*12)
+
+/** Return the number of guards our L2 guardset should have */
+static int
+get_number_of_layer2_hs_guards(void)
+{
+ return (int) networkstatus_get_param(NULL,
+ "guard-hs-l2-number",
+ NUMBER_SECOND_GUARDS,
+ 1, 19);
+}
+
+/** Return the minimum lifetime of L2 guards */
+static int
+get_min_lifetime_of_layer2_hs_guards(void)
+{
+ return (int) networkstatus_get_param(NULL,
+ "guard-hs-l2-lifetime-min",
+ MIN_SECOND_GUARD_LIFETIME,
+ 1, INT32_MAX);
+}
+
+/** Return the maximum lifetime of L2 guards */
+static int
+get_max_lifetime_of_layer2_hs_guards(void)
+{
+ return (int) networkstatus_get_param(NULL,
+ "guard-hs-l2-lifetime-max",
+ MAX_SECOND_GUARD_LIFETIME,
+ 1, INT32_MAX);
+}
+
+/**
+ * Sample and return a lifetime for an L2 guard.
+ *
+ * Lifetime randomized uniformly between min and max consensus params.
+ */
+static int
+get_layer2_hs_guard_lifetime(void)
+{
+ int min = get_min_lifetime_of_layer2_hs_guards();
+ int max = get_max_lifetime_of_layer2_hs_guards();
+
+ if (BUG(min >= max)) {
+ return min;
+ }
+
+ return crypto_rand_int_range(min, max);
+}
+
+/** Maintain the L2 guard list. Make sure the list contains enough guards, do
+ * expirations as necessary, and keep all the data structures of this
+ * subsystem synchronized */
+void
+maintain_layer2_guards(void)
+{
+ if (!router_have_minimum_dir_info()) {
+ return;
+ }
+
+ /* Create the list if it doesn't exist */
+ if (!layer2_guards) {
+ layer2_guards = smartlist_new();
+ }
+
+ /* Go through the list and perform any needed expirations */
+ SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) {
+ /* Expire based on expiration date */
+ if (g->expire_on_date <= approx_time()) {
+ log_info(LD_GENERAL, "Removing expired Layer2 guard %s",
+ safe_str_client(hex_str(g->identity, DIGEST_LEN)));
+ // Nickname may be gone from consensus and doesn't matter anyway
+ control_event_guard("None", g->identity, "BAD_L2");
+ layer2_guard_free(g);
+ SMARTLIST_DEL_CURRENT_KEEPORDER(layer2_guards, g);
+ continue;
+ }
+
+ /* Expire if relay has left consensus */
+ if (router_get_consensus_status_by_id(g->identity) == NULL) {
+ log_info(LD_GENERAL, "Removing missing Layer2 guard %s",
+ safe_str_client(hex_str(g->identity, DIGEST_LEN)));
+ // Nickname may be gone from consensus and doesn't matter anyway
+ control_event_guard("None", g->identity, "BAD_L2");
+ layer2_guard_free(g);
+ SMARTLIST_DEL_CURRENT_KEEPORDER(layer2_guards, g);
+ continue;
+ }
+ } SMARTLIST_FOREACH_END(g);
+
+ /* Find out how many guards we need to add */
+ int new_guards_needed_n =
+ get_number_of_layer2_hs_guards() - smartlist_len(layer2_guards);
+ if (new_guards_needed_n <= 0) {
+ return;
+ }
+
+ log_info(LD_GENERAL, "Adding %d guards to Layer2 routerset",
+ new_guards_needed_n);
+
+ /* Add required guards to the list */
+ smartlist_t *excluded = smartlist_new();
+ for (int i = 0; i < new_guards_needed_n; i++) {
+ const node_t *choice = NULL;
+ const or_options_t *options = get_options();
+ /* Pick Stable nodes */
+ router_crn_flags_t flags = CRN_NEED_DESC|CRN_NEED_UPTIME;
+ choice = router_choose_random_node(excluded, options->ExcludeNodes, flags);
+ if (!choice) {
+ break;
+ }
+
+ /* We found our node: create an L2 guard out of it */
+ layer2_guard_t *layer2_guard = tor_malloc_zero(sizeof(layer2_guard_t));
+ memcpy(layer2_guard->identity, choice->identity, DIGEST_LEN);
+ layer2_guard->expire_on_date = approx_time() +
+ get_layer2_hs_guard_lifetime();
+ smartlist_add(layer2_guards, layer2_guard);
+ log_info(LD_GENERAL, "Adding Layer2 guard %s",
+ safe_str_client(hex_str(layer2_guard->identity, DIGEST_LEN)));
+ // Nickname can also be None here because it is looked up later
+ control_event_guard("None", layer2_guard->identity,
+ "GOOD_L2");
+ /* Exclude this node and its family so that we don't double-pick. */
+ nodelist_add_node_and_family(excluded, choice);
+ }
+
+ /* Some cleanup */
+ smartlist_free(excluded);
+
+ /* Now that the list is up to date, synchronize the routerset */
+ routerset_free(layer2_routerset);
+ layer2_routerset = routerset_new();
+
+ SMARTLIST_FOREACH_BEGIN (layer2_guards, layer2_guard_t *, g) {
+ routerset_parse(layer2_routerset,
+ hex_str(g->identity, DIGEST_LEN),
+ "l2 guards");
+ } SMARTLIST_FOREACH_END(g);
+}
+
+/**
+ * Reset vanguards-lite list(s).
+ *
+ * Used for SIGNAL NEWNYM.
+ */
+void
+purge_vanguards_lite(void)
+{
+ if (!layer2_guards)
+ return;
+
+ /* Go through the list and perform any needed expirations */
+ SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) {
+ layer2_guard_free(g);
+ } SMARTLIST_FOREACH_END(g);
+
+ smartlist_clear(layer2_guards);
+
+ /* Pick new l2 guards */
+ maintain_layer2_guards();
+}
+
+/** Return a routerset containing the L2 guards or NULL if it's not yet
+ * initialized. Callers must not free the routerset. Designed for use in
+ * pick_vanguard_middle_node() and should not be used anywhere else. Do not
+ * store this pointer -- any future calls to maintain_layer2_guards() and
+ * purge_vanguards_lite() can invalidate it. */
+const routerset_t *
+get_layer2_guards(void)
+{
+ if (!layer2_guards) {
+ maintain_layer2_guards();
+ }
+
+ return layer2_routerset;
+}
+
+/*****************************************************************************/
+
/** Release all storage held by the list of entry guards and related
* memory structs. */
void
@@ -3946,4 +4232,15 @@ entry_guards_free_all(void)
guard_contexts = NULL;
}
circuit_build_times_free_timeouts(get_circuit_build_times_mutable());
+
+ if (!layer2_guards) {
+ return;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) {
+ layer2_guard_free(g);
+ } SMARTLIST_FOREACH_END(g);
+
+ smartlist_free(layer2_guards);
+ routerset_free(layer2_routerset);
}
diff --git a/src/feature/client/entrynodes.h b/src/feature/client/entrynodes.h
index 88ed8f649e..08fd7cf745 100644
--- a/src/feature/client/entrynodes.h
+++ b/src/feature/client/entrynodes.h
@@ -651,4 +651,9 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
int orig_bandwidth,
uint32_t guardfraction_percentage);
+bool vanguards_lite_is_enabled(void);
+const routerset_t *get_layer2_guards(void);
+void maintain_layer2_guards(void);
+void purge_vanguards_lite(void);
+
#endif /* !defined(TOR_ENTRYNODES_H) */
diff --git a/src/feature/client/transports.c b/src/feature/client/transports.c
index 167beb96c6..80903ac9e5 100644
--- a/src/feature/client/transports.c
+++ b/src/feature/client/transports.c
@@ -843,7 +843,7 @@ handle_methods_done(const managed_proxy_t *mp)
tor_assert(mp->transports);
if (smartlist_len(mp->transports) == 0)
- log_notice(LD_GENERAL, "Managed proxy '%s' was spawned successfully, "
+ log_warn(LD_GENERAL, "Managed proxy '%s' was spawned successfully, "
"but it didn't launch any pluggable transport listeners!",
mp->argv[0]);
@@ -903,14 +903,22 @@ handle_proxy_line(const char *line, managed_proxy_t *mp)
if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS)
goto err;
+ /* Log the error but do not kill the managed proxy.
+ * A proxy may contain several transports and if one
+ * of them is misconfigured, we still want to use
+ * the other transports. A managed proxy with no usable
+ * transports will log a warning.
+ * See https://gitlab.torproject.org/tpo/core/tor/-/issues/7362
+ * */
parse_client_method_error(line);
- goto err;
+ return;
} else if (!strcmpstart(line, PROTO_SMETHOD_ERROR)) {
if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS)
goto err;
+ /* Log the error but do not kill the managed proxy */
parse_server_method_error(line);
- goto err;
+ return;
} else if (!strcmpstart(line, PROTO_CMETHOD)) {
if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS)
goto err;
diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c
index bd0d41d29e..b19935e456 100644
--- a/src/feature/control/control_cmd.c
+++ b/src/feature/control/control_cmd.c
@@ -817,6 +817,8 @@ handle_control_extendcircuit(control_connection_t *conn,
circ->first_hop_from_controller = 1;
}
+ circ->any_hop_from_controller = 1;
+
/* now circ refers to something that is ready to be extended */
first_node = zero_circ;
SMARTLIST_FOREACH(nodes, const node_t *, node,
@@ -1075,7 +1077,7 @@ static const control_cmd_syntax_t redirectstream_syntax = {
.max_args = UINT_MAX, // XXX should be 3.
};
-/** Called when we receive a REDIRECTSTERAM command. Try to change the target
+/** Called when we receive a REDIRECTSTREAM command. Try to change the target
* address of the named AP stream, and report success or failure. */
static int
handle_control_redirectstream(control_connection_t *conn,
diff --git a/src/feature/dirauth/dirauth_options.inc b/src/feature/dirauth/dirauth_options.inc
index 05726b8c2f..4fd07a8859 100644
--- a/src/feature/dirauth/dirauth_options.inc
+++ b/src/feature/dirauth/dirauth_options.inc
@@ -27,6 +27,10 @@ CONF_VAR(AuthDirHasIPv6Connectivity, BOOL, 0, "0")
* good. */
CONF_VAR(AuthDirListBadExits, BOOL, 0, "0")
+/** True iff we should list middle-only relays, and vote for all other
+ * relays as possibly suitable for other positions. */
+CONF_VAR(AuthDirListMiddleOnly, BOOL, 0, "0")
+
/** Do not permit more than this number of servers per IP address. */
CONF_VAR(AuthDirMaxServersPerAddr, POSINT, 0, "2")
@@ -109,4 +113,7 @@ CONF_VAR(VersioningAuthoritativeDirectory, BOOL, 0, "0")
* pressure or not. */
CONF_VAR(AuthDirRejectRequestsUnderLoad, BOOL, 0, "1")
+/** Boolean: Should we not give bandwidth weight measurements to dirauths? */
+CONF_VAR(AuthDirDontVoteOnDirAuthBandwidth, BOOL, 0, "1")
+
END_CONF_STRUCT(dirauth_options_t)
diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c
index fa906c0c3c..cdd2c132ef 100644
--- a/src/feature/dirauth/dirvote.c
+++ b/src/feature/dirauth/dirvote.c
@@ -1479,6 +1479,21 @@ compute_nth_protocol_set(int n, int n_voters, const smartlist_t *votes)
return result;
}
+/** Helper: Takes a smartlist of `const char *` flags, and a flag to remove.
+ *
+ * Removes that flag if it is present in the list. Doesn't free it.
+ */
+static void
+remove_flag(smartlist_t *sl, const char *flag)
+{
+ /* We can't use smartlist_string_remove() here, since that doesn't preserve
+ * order, and since it frees elements from the string. */
+
+ int idx = smartlist_string_pos(sl, flag);
+ if (idx >= 0)
+ smartlist_del_keeporder(sl, idx);
+}
+
/** Given a list of vote networkstatus_t in <b>votes</b>, our public
* authority <b>identity_key</b>, our private authority <b>signing_key</b>,
* and the number of <b>total_authorities</b> that we believe exist in our
@@ -1633,6 +1648,9 @@ networkstatus_compute_consensus(smartlist_t *votes,
tor_free(votesec_list);
tor_free(distsec_list);
}
+ // True if anybody is voting on the BadExit flag.
+ const bool badexit_flag_is_listed =
+ smartlist_contains_string(flags, "BadExit");
chunks = smartlist_new();
@@ -1924,7 +1942,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
const char *chosen_name = NULL;
int exitsummary_disagreement = 0;
int is_named = 0, is_unnamed = 0, is_running = 0, is_valid = 0;
- int is_guard = 0, is_exit = 0, is_bad_exit = 0;
+ int is_guard = 0, is_exit = 0, is_bad_exit = 0, is_middle_only = 0;
int naming_conflict = 0;
int n_listing = 0;
char microdesc_digest[DIGEST256_LEN];
@@ -2055,7 +2073,6 @@ networkstatus_compute_consensus(smartlist_t *votes,
}
/* Set the flags. */
- smartlist_add(chosen_flags, (char*)"s"); /* for the start of the line. */
SMARTLIST_FOREACH_BEGIN(flags, const char *, fl) {
if (!strcmp(fl, "Named")) {
if (is_named)
@@ -2077,6 +2094,8 @@ networkstatus_compute_consensus(smartlist_t *votes,
is_running = 1;
else if (!strcmp(fl, "BadExit"))
is_bad_exit = 1;
+ else if (!strcmp(fl, "MiddleOnly"))
+ is_middle_only = 1;
else if (!strcmp(fl, "Valid"))
is_valid = 1;
}
@@ -2093,6 +2112,22 @@ networkstatus_compute_consensus(smartlist_t *votes,
if (!is_valid)
continue;
+ /* Starting with consensus method 32, we handle the middle-only
+ * flag specially: when it is present, we clear some flags, and
+ * set others. */
+ if (is_middle_only && consensus_method >= MIN_METHOD_FOR_MIDDLEONLY) {
+ remove_flag(chosen_flags, "Exit");
+ remove_flag(chosen_flags, "V2Dir");
+ remove_flag(chosen_flags, "Guard");
+ remove_flag(chosen_flags, "HSDir");
+ is_exit = is_guard = 0;
+ if (! is_bad_exit && badexit_flag_is_listed) {
+ is_bad_exit = 1;
+ smartlist_add(chosen_flags, (char *)"BadExit");
+ smartlist_sort_strings(chosen_flags); // restore order.
+ }
+ }
+
/* Pick the version. */
if (smartlist_len(versions)) {
sort_version_list(versions, 0);
@@ -2253,6 +2288,8 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_add_asprintf(chunks, "m %s\n", m);
}
/* Next line is all flags. The "\n" is missing. */
+ smartlist_add_asprintf(chunks, "s%s",
+ smartlist_len(chosen_flags)?" ":"");
smartlist_add(chunks,
smartlist_join_strings(chosen_flags, " ", 0, NULL));
/* Now the version line. */
@@ -2265,7 +2302,8 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_add_asprintf(chunks, "pr %s\n", chosen_protocol_list);
}
/* Now the weight line. */
- if (rs_out.has_bandwidth) {
+ if (rs_out.has_bandwidth && (!rs_out.is_authority ||
+ !dirauth_get_options()->AuthDirDontVoteOnDirAuthBandwidth)) {
char *guardfraction_str = NULL;
int unmeasured = rs_out.bw_is_unmeasured;
@@ -4581,6 +4619,7 @@ const char DIRVOTE_UNIVERSAL_FLAGS[] =
* depending on our configuration. */
const char DIRVOTE_OPTIONAL_FLAGS[] =
"BadExit "
+ "MiddleOnly "
"Running";
/** Return a new networkstatus_t* containing our current opinion. (For v3
@@ -4598,7 +4637,8 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
smartlist_t *routers, *routerstatuses;
char identity_digest[DIGEST_LEN];
char signing_key_digest[DIGEST_LEN];
- const int listbadexits = d_options->AuthDirListBadExits;
+ const int list_bad_exits = d_options->AuthDirListBadExits;
+ const int list_middle_only = d_options->AuthDirListMiddleOnly;
routerlist_t *rl = router_get_routerlist();
time_t now = time(NULL);
time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
@@ -4703,7 +4743,8 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
rs = &vrs->status;
dirauth_set_routerstatus_from_routerinfo(rs, node, ri, now,
- listbadexits);
+ list_bad_exits,
+ list_middle_only);
if (ri->cache_info.signing_key_cert) {
memcpy(vrs->ed25519_id,
@@ -4825,8 +4866,10 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
if (vote_on_reachability)
smartlist_add_strdup(v3_out->known_flags, "Running");
- if (listbadexits)
+ if (list_bad_exits)
smartlist_add_strdup(v3_out->known_flags, "BadExit");
+ if (list_middle_only)
+ smartlist_add_strdup(v3_out->known_flags, "MiddleOnly");
smartlist_sort_strings(v3_out->known_flags);
if (d_options->ConsensusParams) {
diff --git a/src/feature/dirauth/dirvote.h b/src/feature/dirauth/dirvote.h
index 3420098315..64aaec116e 100644
--- a/src/feature/dirauth/dirvote.h
+++ b/src/feature/dirauth/dirvote.h
@@ -53,7 +53,7 @@
#define MIN_SUPPORTED_CONSENSUS_METHOD 28
/** The highest consensus method that we currently support. */
-#define MAX_SUPPORTED_CONSENSUS_METHOD 31
+#define MAX_SUPPORTED_CONSENSUS_METHOD 32
/**
* Lowest consensus method where microdescriptor lines are put in canonical
@@ -70,6 +70,10 @@
*/
#define MIN_METHOD_FOR_CORRECT_BWWEIGHTSCALE 31
+/** Lowest consensus method for which we handle the MiddleOnly flag specially.
+ */
+#define MIN_METHOD_FOR_MIDDLEONLY 32
+
/** Default bandwidth to clip unmeasured bandwidths to using method >=
* MIN_METHOD_TO_CLIP_UNMEASURED_BW. (This is not a consensus method; do not
* get confused with the above macros.) */
diff --git a/src/feature/dirauth/process_descs.c b/src/feature/dirauth/process_descs.c
index eca987b8b5..a75f516dca 100644
--- a/src/feature/dirauth/process_descs.c
+++ b/src/feature/dirauth/process_descs.c
@@ -226,6 +226,8 @@ dirserv_load_fingerprint_file(void)
add_status = RTR_BADEXIT;
} else if (!strcasecmp(nickname, "!invalid")) {
add_status = RTR_INVALID;
+ } else if (!strcasecmp(nickname, "!middleonly")) {
+ add_status = RTR_MIDDLEONLY;
}
/* Check if fingerprint is RSA or ed25519 by verifying it. */
@@ -412,11 +414,11 @@ dirserv_rejects_tor_version(const char *platform,
return true;
}
- /* Series between Tor 0.3.6 and 0.4.1 inclusive are unsupported. Reject
- * them. 0.3.6.0-alpha-dev only existed for a short time, before it was
- * renamed to 0.4.0.0-alpha-dev. */
+ /* Series between Tor 0.3.6.x and 0.4.5.5-rc inclusive are unsupported.
+ * Reject them. 0.3.6.0-alpha-dev only existed for a short time, before it
+ * was renamed to 0.4.0.0-alpha-dev. */
if (tor_version_as_new_as(platform,"0.3.6.0-alpha-dev") &&
- !tor_version_as_new_as(platform,"0.4.2.1-alpha")) {
+ !tor_version_as_new_as(platform,"0.4.5.6")) {
if (msg) {
*msg = please_upgrade_string;
}
@@ -496,6 +498,13 @@ dirserv_get_status_impl(const char *id_digest,
result |= RTR_BADEXIT;
}
+ if (authdir_policy_middleonly_address(ipv4_addr, ipv4_orport)) {
+ log_fn(severity, LD_DIRSERV,
+ "Marking '%s' as middle-only because of address '%s'",
+ nickname, fmt_addr(ipv4_addr));
+ result |= RTR_MIDDLEONLY;
+ }
+
if (!authdir_policy_permits_address(ipv4_addr, ipv4_orport)) {
log_fn(severity, LD_DIRSERV, "Rejecting '%s' because of address '%s'",
nickname, fmt_addr(ipv4_addr));
@@ -630,6 +639,7 @@ dirserv_set_node_flags_from_authoritative_status(node_t *node,
{
node->is_valid = (authstatus & RTR_INVALID) ? 0 : 1;
node->is_bad_exit = (authstatus & RTR_BADEXIT) ? 1 : 0;
+ node->is_middle_only = (authstatus & RTR_MIDDLEONLY) ? 1 : 0;
}
/** True iff <b>a</b> is more severe than <b>b</b>. */
@@ -963,6 +973,11 @@ directory_remove_invalid(void)
(r & RTR_BADEXIT) ? "bad" : "good");
node->is_bad_exit = (r&RTR_BADEXIT) ? 1: 0;
}
+ if (bool_neq((r & RTR_MIDDLEONLY), node->is_middle_only)) {
+ log_info(LD_DIRSERV, "Router '%s' is now %smiddle-only", description,
+ (r & RTR_MIDDLEONLY) ? "" : "not");
+ node->is_middle_only = (r&RTR_MIDDLEONLY) ? 1: 0;
+ }
} SMARTLIST_FOREACH_END(node);
routerlist_assert_ok(rl);
diff --git a/src/feature/dirauth/process_descs.h b/src/feature/dirauth/process_descs.h
index 6c056d11dd..a509eb1fbe 100644
--- a/src/feature/dirauth/process_descs.h
+++ b/src/feature/dirauth/process_descs.h
@@ -45,7 +45,8 @@ typedef struct authdir_config_t {
#define RTR_REJECT 4 /**< We will not publish this router. */
/* 8 Historically used to avoid using this as a dir. */
#define RTR_BADEXIT 16 /**< We'll tell clients not to use this as an exit. */
-/* 32 Historically used to indicade Unnamed */
+/** We'll vote to only use this router as a midpoint. */
+#define RTR_MIDDLEONLY 32
#endif /* defined(PROCESS_DESCS_PRIVATE) || defined(TOR_UNIT_TESTS) */
diff --git a/src/feature/dirauth/voteflags.c b/src/feature/dirauth/voteflags.c
index d755a270be..05c19ff501 100644
--- a/src/feature/dirauth/voteflags.c
+++ b/src/feature/dirauth/voteflags.c
@@ -565,7 +565,8 @@ dirauth_set_routerstatus_from_routerinfo(routerstatus_t *rs,
node_t *node,
const routerinfo_t *ri,
time_t now,
- int listbadexits)
+ int listbadexits,
+ int listmiddleonly)
{
const or_options_t *options = get_options();
uint32_t routerbw_kb = dirserv_get_credible_bandwidth_kb(ri);
@@ -597,6 +598,14 @@ dirauth_set_routerstatus_from_routerinfo(routerstatus_t *rs,
/* Override rs->is_bad_exit */
rs->is_bad_exit = listbadexits && node->is_bad_exit;
+ /* Override rs->is_middle_only and related flags. */
+ rs->is_middle_only = listmiddleonly && node->is_middle_only;
+ if (rs->is_middle_only) {
+ if (listbadexits)
+ rs->is_bad_exit = 1;
+ rs->is_exit = rs->is_possible_guard = rs->is_hs_dir = rs->is_v2_dir = 0;
+ }
+
/* Set rs->is_staledesc. */
rs->is_staledesc =
(ri->cache_info.published_on + DESC_IS_STALE_INTERVAL) < now;
diff --git a/src/feature/dirauth/voteflags.h b/src/feature/dirauth/voteflags.h
index 818a0bafd2..8371f1c315 100644
--- a/src/feature/dirauth/voteflags.h
+++ b/src/feature/dirauth/voteflags.h
@@ -22,7 +22,8 @@ void dirauth_set_routerstatus_from_routerinfo(routerstatus_t *rs,
node_t *node,
const routerinfo_t *ri,
time_t now,
- int listbadexits);
+ int listbadexits,
+ int listmiddleonly);
void dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil);
#endif /* defined(HAVE_MODULE_DIRAUTH) */
diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c
index 7fdb1bc70f..7319b96caf 100644
--- a/src/feature/dircache/dircache.c
+++ b/src/feature/dircache/dircache.c
@@ -1569,6 +1569,8 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers,
char *url = NULL;
const or_options_t *options = get_options();
+ (void) body_len;
+
log_debug(LD_DIRSERV,"Received POST command.");
conn->base_.state = DIR_CONN_STATE_SERVER_WRITING;
diff --git a/src/feature/dirclient/dir_server_st.h b/src/feature/dirclient/dir_server_st.h
index ed6b00647e..ac45f3787b 100644
--- a/src/feature/dirclient/dir_server_st.h
+++ b/src/feature/dirclient/dir_server_st.h
@@ -16,6 +16,8 @@
#include "core/or/or.h"
#include "feature/nodelist/routerstatus_st.h"
+struct smartlist_t;
+
/** Represents information about a single trusted or fallback directory
* server. */
struct dir_server_t {
@@ -48,6 +50,10 @@ struct dir_server_t {
time_t addr_current_at; /**< When was the document that we derived the
* address information from published? */
+ /** Authority only. Can be null. If present, a list of auth_dirport_t
+ * representing HTTP dirports for this authority. */
+ struct smartlist_t *auth_dirports;
+
routerstatus_t fake_status; /**< Used when we need to pass this trusted
* dir_server_t to
* directory_request_set_routerstatus.
diff --git a/src/feature/dirclient/dirclient.c b/src/feature/dirclient/dirclient.c
index 0b6a8101a5..4e9c8e2f45 100644
--- a/src/feature/dirclient/dirclient.c
+++ b/src/feature/dirclient/dirclient.c
@@ -1134,6 +1134,7 @@ directory_request_set_routerstatus(directory_request_t *req,
{
req->routerstatus = status;
}
+
/**
* Helper: update the addresses, ports, and identities in <b>req</b>
* from the routerstatus object in <b>req</b>. Return 0 on success.
@@ -1176,7 +1177,7 @@ directory_request_set_dir_from_routerstatus(directory_request_t *req)
return -1;
}
- /* At this point, if we are a client making a direct connection to a
+ /* At this point, if we are a client making a direct connection to a
* directory server, we have selected a server that has at least one address
* allowed by ClientUseIPv4/6 and Reachable{"",OR,Dir}Addresses. This
* selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if
@@ -1191,6 +1192,37 @@ directory_request_set_dir_from_routerstatus(directory_request_t *req)
return -1;
}
+ /* One last thing: If we're talking to an authority, we might want to use
+ * a special HTTP port for it based on our purpose.
+ */
+ if (req->indirection == DIRIND_DIRECT_CONN && status->is_authority) {
+ const dir_server_t *ds = router_get_trusteddirserver_by_digest(
+ status->identity_digest);
+ if (ds) {
+ const tor_addr_port_t *v4 = NULL;
+ if (authdir_mode_v3(get_options())) {
+ // An authority connecting to another authority should always
+ // prefer the VOTING usage, if one is specifically configured.
+ v4 = trusted_dir_server_get_dirport_exact(
+ ds, AUTH_USAGE_VOTING, AF_INET);
+ }
+ if (! v4) {
+ // Everybody else should prefer a usage dependent on their
+ // the dir_purpose.
+ auth_dirport_usage_t usage =
+ auth_dirport_usage_for_purpose(req->dir_purpose);
+ v4 = trusted_dir_server_get_dirport(ds, usage, AF_INET);
+ }
+ tor_assert_nonfatal(v4);
+ if (v4) {
+ // XXXX We could, if we wanted, also select a v6 address. But a v4
+ // address must exist here, and we as a relay are required to support
+ // ipv4. So we just that.
+ tor_addr_port_copy(&use_dir_ap, v4);
+ }
+ }
+ }
+
directory_request_set_or_addr_port(req, &use_or_ap);
directory_request_set_dir_addr_port(req, &use_dir_ap);
directory_request_set_directory_id_digest(req, status->identity_digest);
@@ -1209,7 +1241,7 @@ directory_initiate_request,(directory_request_t *request))
tor_assert_nonfatal(
! directory_request_dir_contact_info_specified(request));
if (directory_request_set_dir_from_routerstatus(request) < 0) {
- return;
+ return; // or here XXXX
}
}
@@ -1324,6 +1356,8 @@ directory_initiate_request,(directory_request_t *request))
entry_guard_cancel(&guard_state);
}
+ // XXXX This is the case where we replace.
+
switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr,
port, &socket_error)) {
case -1:
diff --git a/src/feature/dirclient/dlstatus.c b/src/feature/dirclient/dlstatus.c
index 8be2983a5d..c21dd113b4 100644
--- a/src/feature/dirclient/dlstatus.c
+++ b/src/feature/dirclient/dlstatus.c
@@ -73,15 +73,14 @@ find_dl_min_delay(const download_status_t *dls, const or_options_t *options)
}
}
case DL_SCHED_BRIDGE:
- if (options->UseBridges && num_bridges_usable(0) > 0) {
- /* A bridge client that is sure that one or more of its bridges are
- * running can afford to wait longer to update bridge descriptors. */
- return options->TestingBridgeDownloadInitialDelay;
- } else {
- /* A bridge client which might have no running bridges, must try to
- * get bridge descriptors straight away. */
- return options->TestingBridgeBootstrapDownloadInitialDelay;
- }
+ /* Be conservative here: always return the 'during bootstrap' delay
+ * value, so we never delay while trying to fetch descriptors
+ * for new bridges. Once we do succeed at fetching a descriptor
+ * for our bridge, we will adjust its next_attempt_at based on
+ * the longer "TestingBridgeDownloadInitialDelay" value. See
+ * learned_bridge_descriptor() for details.
+ */
+ return options->TestingBridgeBootstrapDownloadInitialDelay;
default:
tor_assert(0);
}
diff --git a/src/feature/dircommon/consdiff.c b/src/feature/dircommon/consdiff.c
index c877227adc..323f2bd576 100644
--- a/src/feature/dircommon/consdiff.c
+++ b/src/feature/dircommon/consdiff.c
@@ -1128,7 +1128,7 @@ consdiff_get_digests(const smartlist_t *diff,
{
const cdline_t *line2 = smartlist_get(diff, 1);
char *h = tor_memdup_nulterm(line2->s, line2->len);
- smartlist_split_string(hash_words, h, " ", 0, 0);
+ smartlist_split_string(hash_words, h, " ", 0, 4);
tor_free(h);
}
diff --git a/src/feature/dirparse/ns_parse.c b/src/feature/dirparse/ns_parse.c
index 947b3810a4..cd3e2731be 100644
--- a/src/feature/dirparse/ns_parse.c
+++ b/src/feature/dirparse/ns_parse.c
@@ -434,6 +434,8 @@ routerstatus_parse_entry_from_string(memarea_t *area,
rs->is_possible_guard = 1;
else if (!strcmp(tok->args[i], "BadExit"))
rs->is_bad_exit = 1;
+ else if (!strcmp(tok->args[i], "MiddleOnly"))
+ rs->is_middle_only = 1;
else if (!strcmp(tok->args[i], "Authority"))
rs->is_authority = 1;
else if (!strcmp(tok->args[i], "Unnamed") &&
diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c
index 73f9176186..a76893fe1a 100644
--- a/src/feature/hs/hs_config.c
+++ b/src/feature/hs/hs_config.c
@@ -548,15 +548,19 @@ config_service(config_line_t *line, const or_options_t *options,
tor_assert(service->config.version <= HS_VERSION_MAX);
- /* Check permission on service directory that was just parsed. And this must
- * be done regardless of the service version. Do not ask for the directory
- * to be created, this is done when the keys are loaded because we could be
- * in validation mode right now. */
- if (hs_check_service_private_dir(options->User,
- service->config.directory_path,
- service->config.dir_group_readable,
- 0) < 0) {
- goto err;
+ /* If we're running with TestingTorNetwork enabled, we relax the permissions
+ * check on the hs directory. */
+ if (!options->TestingTorNetwork) {
+ /* Check permission on service directory that was just parsed. And this
+ * must be done regardless of the service version. Do not ask for the
+ * directory to be created, this is done when the keys are loaded because
+ * we could be in validation mode right now. */
+ if (hs_check_service_private_dir(options->User,
+ service->config.directory_path,
+ service->config.dir_group_readable,
+ 0) < 0) {
+ goto err;
+ }
}
/* We'll try to learn the service version here by loading the key(s) if
@@ -640,6 +644,7 @@ hs_config_service_all(const or_options_t *options, int validate_only)
int rv = config_service(section, options, new_service_list);
config_free_lines(section);
if (rv < 0) {
+ config_free_lines(remaining);
goto err;
}
}
diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c
index 70ff4e9690..a37eab5b5d 100644
--- a/src/feature/hs/hs_descriptor.c
+++ b/src/feature/hs/hs_descriptor.c
@@ -1607,8 +1607,8 @@ decrypt_desc_layer,(const hs_descriptor_t *desc,
* put in decrypted_out which contains the superencrypted layer of the
* descriptor. Return the length of decrypted_out on success else 0 is
* returned and decrypted_out is set to NULL. */
-static size_t
-desc_decrypt_superencrypted(const hs_descriptor_t *desc, char **decrypted_out)
+MOCK_IMPL(STATIC size_t,
+desc_decrypt_superencrypted,(const hs_descriptor_t *desc,char **decrypted_out))
{
size_t superencrypted_len = 0;
char *superencrypted_plaintext = NULL;
@@ -1639,10 +1639,10 @@ desc_decrypt_superencrypted(const hs_descriptor_t *desc, char **decrypted_out)
* decrypted_out which contains the encrypted layer of the descriptor.
* Return the length of decrypted_out on success else 0 is returned and
* decrypted_out is set to NULL. */
-static size_t
-desc_decrypt_encrypted(const hs_descriptor_t *desc,
- const curve25519_secret_key_t *client_auth_sk,
- char **decrypted_out)
+MOCK_IMPL(STATIC size_t,
+desc_decrypt_encrypted,(const hs_descriptor_t *desc,
+ const curve25519_secret_key_t *client_auth_sk,
+ char **decrypted_out))
{
size_t encrypted_len = 0;
char *encrypted_plaintext = NULL;
@@ -2145,7 +2145,7 @@ desc_decode_plaintext_v3(smartlist_t *tokens,
/** Decode the version 3 superencrypted section of the given descriptor desc.
* The desc_superencrypted_out will be populated with the decoded data. */
-static hs_desc_decode_status_t
+STATIC hs_desc_decode_status_t
desc_decode_superencrypted_v3(const hs_descriptor_t *desc,
hs_desc_superencrypted_data_t *
desc_superencrypted_out)
@@ -2259,7 +2259,7 @@ desc_decode_superencrypted_v3(const hs_descriptor_t *desc,
/** Decode the version 3 encrypted section of the given descriptor desc. The
* desc_encrypted_out will be populated with the decoded data. */
-static hs_desc_decode_status_t
+STATIC hs_desc_decode_status_t
desc_decode_encrypted_v3(const hs_descriptor_t *desc,
const curve25519_secret_key_t *client_auth_sk,
hs_desc_encrypted_data_t *desc_encrypted_out)
diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h
index 7e437faeb8..d959431369 100644
--- a/src/feature/hs/hs_descriptor.h
+++ b/src/feature/hs/hs_descriptor.h
@@ -339,6 +339,25 @@ MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc,
bool is_superencrypted_layer,
char **decrypted_out));
+STATIC hs_desc_decode_status_t desc_decode_encrypted_v3(
+ const hs_descriptor_t *desc,
+ const curve25519_secret_key_t *client_auth_sk,
+ hs_desc_encrypted_data_t *desc_encrypted_out);
+
+STATIC hs_desc_decode_status_t
+desc_decode_superencrypted_v3(const hs_descriptor_t *desc,
+ hs_desc_superencrypted_data_t *
+ desc_superencrypted_out);
+
+MOCK_DECL(STATIC size_t, desc_decrypt_encrypted,(
+ const hs_descriptor_t *desc,
+ const curve25519_secret_key_t *client_auth_sk,
+ char **decrypted_out));
+
+MOCK_DECL(STATIC size_t, desc_decrypt_superencrypted,(
+ const hs_descriptor_t *desc,
+ char **decrypted_out));
+
#endif /* defined(HS_DESCRIPTOR_PRIVATE) */
#endif /* !defined(TOR_HS_DESCRIPTOR_H) */
diff --git a/src/feature/hs/hs_metrics.c b/src/feature/hs/hs_metrics.c
index e023eab90c..0f1824c51c 100644
--- a/src/feature/hs/hs_metrics.c
+++ b/src/feature/hs/hs_metrics.c
@@ -29,18 +29,6 @@ port_to_str(const uint16_t port)
return buf;
}
-/** Return a static buffer pointer that contains a formatted label on the form
- * of key=value.
- *
- * Subsequent call to this function invalidates the previous buffer. */
-static const char *
-format_label(const char *key, const char *value)
-{
- static char buf[128];
- tor_snprintf(buf, sizeof(buf), "%s=%s", key, value);
- return buf;
-}
-
/** Initialize a metrics store for the given service.
*
* Essentially, this goes over the base_metrics array and adds them all to the
@@ -61,12 +49,12 @@ init_store(hs_service_t *service)
/* Add labels to the entry. */
metrics_store_entry_add_label(entry,
- format_label("onion", service->onion_address));
+ metrics_format_label("onion", service->onion_address));
if (base_metrics[i].port_as_label && service->config.ports) {
SMARTLIST_FOREACH_BEGIN(service->config.ports,
const hs_port_config_t *, p) {
metrics_store_entry_add_label(entry,
- format_label("port", port_to_str(p->virtual_port)));
+ metrics_format_label("port", port_to_str(p->virtual_port)));
} SMARTLIST_FOREACH_END(p);
}
}
@@ -96,7 +84,7 @@ hs_metrics_update_by_service(const hs_metrics_key_t key,
SMARTLIST_FOREACH_BEGIN(entries, metrics_store_entry_t *, entry) {
if (port == 0 ||
metrics_store_entry_has_label(entry,
- format_label("port", port_to_str(port)))) {
+ metrics_format_label("port", port_to_str(port)))) {
metrics_store_entry_update(entry, n);
break;
}
diff --git a/src/feature/nodelist/dirlist.c b/src/feature/nodelist/dirlist.c
index 1f18bd71a2..1f1ac4d106 100644
--- a/src/feature/nodelist/dirlist.c
+++ b/src/feature/nodelist/dirlist.c
@@ -43,6 +43,14 @@
#include "feature/dirclient/dir_server_st.h"
#include "feature/nodelist/node_st.h"
+/** Information about an (HTTP) dirport for a directory authority. */
+struct auth_dirport_t {
+ /** What is the intended usage for this dirport? One of AUTH_USAGE_* */
+ auth_dirport_usage_t usage;
+ /** What is the correct address/port ? */
+ tor_addr_port_t dirport;
+};
+
/** Global list of a dir_server_t object for each directory
* authority. */
static smartlist_t *trusted_dir_servers = NULL;
@@ -66,6 +74,11 @@ add_trusted_dir_to_nodelist_addr_set(const dir_server_t *dir)
/* IPv6 DirPort is not a thing yet for authorities. */
nodelist_add_addr_to_address_set(&dir->ipv6_addr, dir->ipv6_orport, 0);
}
+ if (dir->auth_dirports) {
+ SMARTLIST_FOREACH_BEGIN(dir->auth_dirports, const auth_dirport_t *, p) {
+ nodelist_add_addr_to_address_set(&p->dirport.addr, 0, p->dirport.port);
+ } SMARTLIST_FOREACH_END(p);
+ }
}
/** Go over the trusted directory server list and add their address(es) to the
@@ -256,7 +269,10 @@ MOCK_IMPL(int, router_digest_is_trusted_dir_type,
/** Return true iff the given address matches a trusted directory that matches
* at least one bit of type.
*
- * If type is NO_DIRINFO or ALL_DIRINFO, any authority is matched. */
+ * If type is NO_DIRINFO or ALL_DIRINFO, any authority is matched.
+ *
+ * Only ORPorts' addresses are considered.
+ */
bool
router_addr_is_trusted_dir_type(const tor_addr_t *addr, dirinfo_type_t type)
{
@@ -281,6 +297,39 @@ router_addr_is_trusted_dir_type(const tor_addr_t *addr, dirinfo_type_t type)
return false;
}
+/** Return an appropriate usage value describing which authdir port to use
+ * for a given directory connection purpose.
+ */
+auth_dirport_usage_t
+auth_dirport_usage_for_purpose(int purpose)
+{
+ switch (purpose) {
+ case DIR_PURPOSE_FETCH_SERVERDESC:
+ case DIR_PURPOSE_FETCH_EXTRAINFO:
+ case DIR_PURPOSE_FETCH_CONSENSUS:
+ case DIR_PURPOSE_FETCH_CERTIFICATE:
+ case DIR_PURPOSE_FETCH_MICRODESC:
+ return AUTH_USAGE_DOWNLOAD;
+
+ case DIR_PURPOSE_UPLOAD_DIR:
+ return AUTH_USAGE_UPLOAD;
+
+ case DIR_PURPOSE_UPLOAD_VOTE:
+ case DIR_PURPOSE_UPLOAD_SIGNATURES:
+ case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES:
+ case DIR_PURPOSE_FETCH_STATUS_VOTE:
+ return AUTH_USAGE_VOTING;
+
+ case DIR_PURPOSE_SERVER:
+ case DIR_PURPOSE_UPLOAD_HSDESC:
+ case DIR_PURPOSE_FETCH_HSDESC:
+ case DIR_PURPOSE_HAS_FETCHED_HSDESC:
+ default:
+ tor_assert_nonfatal_unreached();
+ return AUTH_USAGE_LEGACY;
+ }
+}
+
/** Create a directory server at <b>address</b>:<b>port</b>, with OR identity
* key <b>digest</b> which has DIGEST_LEN bytes. If <b>address</b> is NULL,
* add ourself. If <b>is_authority</b>, this is a directory authority. Return
@@ -357,6 +406,7 @@ dir_server_new(int is_authority,
ent->fake_status.ipv4_dirport = ent->ipv4_dirport;
ent->fake_status.ipv4_orport = ent->ipv4_orport;
ent->fake_status.ipv6_orport = ent->ipv6_orport;
+ ent->fake_status.is_authority = !! is_authority;
return ent;
}
@@ -404,10 +454,98 @@ trusted_dir_server_new(const char *nickname, const char *address,
ipv6_addrport,
digest,
v3_auth_digest, type, weight);
+
+ if (ipv4_dirport) {
+ tor_addr_port_t p;
+ memset(&p, 0, sizeof(p));
+ tor_addr_copy(&p.addr, &ipv4_addr);
+ p.port = ipv4_dirport;
+ trusted_dir_server_add_dirport(result, AUTH_USAGE_LEGACY, &p);
+ }
tor_free(hostname);
return result;
}
+/**
+ * Add @a dirport as an HTTP DirPort contact point for the directory authority
+ * @a ds, for use when contacting that authority for the given @a usage.
+ *
+ * Multiple ports of the same usage are allowed; if present, then only
+ * the first one of each address family is currently used.
+ */
+void
+trusted_dir_server_add_dirport(dir_server_t *ds,
+ auth_dirport_usage_t usage,
+ const tor_addr_port_t *dirport)
+{
+ tor_assert(ds);
+ tor_assert(dirport);
+
+ if (BUG(! ds->is_authority)) {
+ return;
+ }
+
+ if (ds->auth_dirports == NULL) {
+ ds->auth_dirports = smartlist_new();
+ }
+
+ auth_dirport_t *port = tor_malloc_zero(sizeof(auth_dirport_t));
+ port->usage = usage;
+ tor_addr_port_copy(&port->dirport, dirport);
+ smartlist_add(ds->auth_dirports, port);
+}
+
+/**
+ * Helper for trusted_dir_server_get_dirport: only return the exact requested
+ * usage type.
+ */
+const tor_addr_port_t *
+trusted_dir_server_get_dirport_exact(const dir_server_t *ds,
+ auth_dirport_usage_t usage,
+ int addr_family)
+{
+ tor_assert(ds);
+ tor_assert_nonfatal(addr_family == AF_INET || addr_family == AF_INET6);
+ if (ds->auth_dirports == NULL)
+ return NULL;
+
+ SMARTLIST_FOREACH_BEGIN(ds->auth_dirports, const auth_dirport_t *, port) {
+ if (port->usage == usage &&
+ tor_addr_family(&port->dirport.addr) == addr_family) {
+ return &port->dirport;
+ }
+ } SMARTLIST_FOREACH_END(port);
+
+ return NULL;
+}
+
+/**
+ * Return the DirPort of the authority @a ds for with the usage type
+ * @a usage and address family @a addr_family. If none is found, try
+ * again with an AUTH_USAGE_LEGACY dirport, if there is one. Return NULL
+ * if no port can be found.
+ */
+const tor_addr_port_t *
+trusted_dir_server_get_dirport(const dir_server_t *ds,
+ auth_dirport_usage_t usage,
+ int addr_family)
+{
+ const tor_addr_port_t *port;
+
+ while (1) {
+ port = trusted_dir_server_get_dirport_exact(ds, usage, addr_family);
+ if (port)
+ return port;
+
+ // If we tried LEGACY, there is no fallback from this point.
+ if (usage == AUTH_USAGE_LEGACY)
+ return NULL;
+
+ // Try again with LEGACY.
+ usage = AUTH_USAGE_LEGACY;
+ }
+}
+
/** Return a new dir_server_t for a fallback directory server at
* <b>addr</b>:<b>or_port</b>/<b>dir_port</b>, with identity key digest
* <b>id_digest</b> */
@@ -447,6 +585,10 @@ dir_server_free_(dir_server_t *ds)
if (!ds)
return;
+ if (ds->auth_dirports) {
+ SMARTLIST_FOREACH(ds->auth_dirports, auth_dirport_t *, p, tor_free(p));
+ smartlist_free(ds->auth_dirports);
+ }
tor_free(ds->nickname);
tor_free(ds->description);
tor_free(ds->address);
diff --git a/src/feature/nodelist/dirlist.h b/src/feature/nodelist/dirlist.h
index f744fecf92..3b4faf07af 100644
--- a/src/feature/nodelist/dirlist.h
+++ b/src/feature/nodelist/dirlist.h
@@ -11,6 +11,28 @@
#ifndef TOR_DIRLIST_H
#define TOR_DIRLIST_H
+typedef struct auth_dirport_t auth_dirport_t;
+/**
+ * Different usages for an authority's HTTP directory port.
+ *
+ * Historically, only legacy ports existed; proposal 330 added multiple types
+ * of dirport to better enable authorities to offload work and resist DoS
+ * attacks.
+ **/
+typedef enum auth_dirport_usage_t {
+ /** Flag for an authority's dirport that is intended for misc/legacy
+ * usage. May be used when no other dirport is available. */
+ AUTH_USAGE_LEGACY,
+ /** Flag for an authority's dirport that is intended for descriptor uploads
+ * only. */
+ AUTH_USAGE_UPLOAD,
+ /** Flag for an authority's dirport that is intended for voting only */
+ AUTH_USAGE_VOTING,
+ /** Flag for an authority's dirport that is intended for relay downloads
+ * only. */
+ AUTH_USAGE_DOWNLOAD,
+} auth_dirport_usage_t;
+
int get_n_authorities(dirinfo_type_t type);
const smartlist_t *router_get_trusted_dir_servers(void);
const smartlist_t *router_get_fallback_dir_servers(void);
@@ -18,6 +40,8 @@ smartlist_t *router_get_trusted_dir_servers_mutable(void);
smartlist_t *router_get_fallback_dir_servers_mutable(void);
void mark_all_dirservers_up(smartlist_t *server_list);
+auth_dirport_usage_t auth_dirport_usage_for_purpose(int purpose);
+
dir_server_t *router_get_trusteddirserver_by_digest(const char *d);
dir_server_t *router_get_fallback_dirserver_by_digest(
const char *digest);
@@ -28,6 +52,14 @@ MOCK_DECL(dir_server_t *, trusteddirserver_get_by_v3_auth_digest,
MOCK_DECL(int, router_digest_is_trusted_dir_type,
(const char *digest, dirinfo_type_t type));
+const tor_addr_port_t *trusted_dir_server_get_dirport(const dir_server_t *ds,
+ auth_dirport_usage_t usage,
+ int addr_family);
+const tor_addr_port_t *trusted_dir_server_get_dirport_exact(
+ const dir_server_t *ds,
+ auth_dirport_usage_t usage,
+ int addr_family);
+
bool router_addr_is_trusted_dir_type(const tor_addr_t *addr,
dirinfo_type_t type);
#define router_addr_is_trusted_dir(d) \
@@ -41,6 +73,9 @@ dir_server_t *trusted_dir_server_new(const char *nickname, const char *address,
const tor_addr_port_t *addrport_ipv6,
const char *digest, const char *v3_auth_digest,
dirinfo_type_t type, double weight);
+void trusted_dir_server_add_dirport(dir_server_t *ds,
+ auth_dirport_usage_t usage,
+ const tor_addr_port_t *dirport);
dir_server_t *fallback_dir_server_new(const tor_addr_t *addr,
uint16_t dir_port, uint16_t or_port,
const tor_addr_port_t *addrport_ipv6,
diff --git a/src/feature/nodelist/fmt_routerstatus.c b/src/feature/nodelist/fmt_routerstatus.c
index 6db40c0b68..95379a7721 100644
--- a/src/feature/nodelist/fmt_routerstatus.c
+++ b/src/feature/nodelist/fmt_routerstatus.c
@@ -87,7 +87,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
goto done;
smartlist_add_asprintf(chunks,
- "s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+ "s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
/* These must stay in alphabetical order. */
rs->is_authority?" Authority":"",
rs->is_bad_exit?" BadExit":"",
@@ -95,6 +95,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
rs->is_fast?" Fast":"",
rs->is_possible_guard?" Guard":"",
rs->is_hs_dir?" HSDir":"",
+ rs->is_middle_only?" MiddleOnly":"",
rs->is_flagged_running?" Running":"",
rs->is_stable?" Stable":"",
rs->is_staledesc?" StaleDesc":"",
diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c
index af808a6ba7..77e2b547f5 100644
--- a/src/feature/nodelist/networkstatus.c
+++ b/src/feature/nodelist/networkstatus.c
@@ -45,6 +45,8 @@
#include "core/or/channel.h"
#include "core/or/channelpadding.h"
#include "core/or/circuitpadding.h"
+#include "core/or/congestion_control_common.h"
+#include "core/or/congestion_control_flow.h"
#include "core/or/circuitmux.h"
#include "core/or/circuitmux_ewma.h"
#include "core/or/circuitstats.h"
@@ -1664,7 +1666,6 @@ notify_before_networkstatus_changes(const networkstatus_t *old_c,
dos_consensus_has_changed(new_c);
relay_consensus_has_changed(new_c);
hs_dos_consensus_has_changed(new_c);
- rep_hist_consensus_has_changed(new_c);
}
/* Called after a new consensus has been put in the global state. It is safe
@@ -1701,6 +1702,11 @@ notify_after_networkstatus_changes(void)
channelpadding_new_consensus_params(c);
circpad_new_consensus_params(c);
router_new_consensus_params(c);
+ congestion_control_new_consensus_params(c);
+ flow_control_new_consensus_params(c);
+
+ /* Maintenance of our L2 guard list */
+ maintain_layer2_guards();
}
/** Copy all the ancillary information (like router download status and so on)
diff --git a/src/feature/nodelist/node_st.h b/src/feature/nodelist/node_st.h
index b15e7154c4..df67a47ada 100644
--- a/src/feature/nodelist/node_st.h
+++ b/src/feature/nodelist/node_st.h
@@ -70,6 +70,8 @@ struct node_t {
unsigned int is_exit:1; /**< Do we think this is an OK exit? */
unsigned int is_bad_exit:1; /**< Do we think this exit is censored, borked,
* or otherwise nasty? */
+ /** Is this unsuitable for use as anything besides a middle relay? */
+ unsigned int is_middle_only:1;
unsigned int is_hs_dir:1; /**< True iff this router is a hidden service
* directory according to the authorities. */
diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c
index 121dc8823a..c676e8dfb4 100644
--- a/src/feature/nodelist/nodelist.c
+++ b/src/feature/nodelist/nodelist.c
@@ -2820,6 +2820,7 @@ update_router_have_minimum_dir_info(void)
const networkstatus_t *consensus =
networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor());
int using_md;
+ static int be_loud_when_things_work_again = 0;
if (!consensus) {
if (!networkstatus_get_latest_consensus())
@@ -2875,8 +2876,9 @@ update_router_have_minimum_dir_info(void)
if (res && !have_min_dir_info) {
control_event_client_status(LOG_NOTICE, "ENOUGH_DIR_INFO");
control_event_boot_dir(BOOTSTRAP_STATUS_ENOUGH_DIRINFO, 0);
- log_info(LD_DIR,
- "We now have enough directory information to build circuits.");
+ tor_log(be_loud_when_things_work_again ? LOG_NOTICE : LOG_INFO, LD_DIR,
+ "We now have enough directory information to build circuits.");
+ be_loud_when_things_work_again = 0;
}
/* If paths have just become unavailable in this update. */
@@ -2885,6 +2887,10 @@ update_router_have_minimum_dir_info(void)
tor_log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR,
"Our directory information is no longer up-to-date "
"enough to build circuits: %s", dir_info_status);
+ if (!quiet) {
+ /* remember to do a notice-level log when things come back */
+ be_loud_when_things_work_again = 1;
+ }
/* a) make us log when we next complete a circuit, so we know when Tor
* is back up and usable, and b) disable some activities that Tor
diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c
index 565d4596d4..c00f7ffb26 100644
--- a/src/feature/nodelist/routerlist.c
+++ b/src/feature/nodelist/routerlist.c
@@ -1617,6 +1617,13 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg,
"descriptor for router %s",
router_describe(router));
} else {
+ if (router->purpose == ROUTER_PURPOSE_BRIDGE) {
+ /* Even if we're not going to keep this descriptor, we need to
+ * let the bridge descriptor fetch subsystem know that we
+ * succeeded at getting it -- so we can adjust the retry schedule
+ * to stop trying for a while. */
+ learned_bridge_descriptor(router, from_cache, 0);
+ }
log_info(LD_DIR,
"Dropping descriptor that we already have for router %s",
router_describe(router));
@@ -2012,6 +2019,30 @@ routerlist_remove_old_routers(void)
router_rebuild_store(RRS_DONT_REMOVE_OLD,&routerlist->extrainfo_store);
}
+/* Drop every bridge descriptor in our routerlist. Used by the external
+ * 'bridgestrap' tool to discard bridge descriptors so that it can then
+ * do a clean reachability test. */
+void
+routerlist_drop_bridge_descriptors(void)
+{
+ routerinfo_t *router;
+ int i;
+
+ if (!routerlist)
+ return;
+
+ for (i = 0; i < smartlist_len(routerlist->routers); ++i) {
+ router = smartlist_get(routerlist->routers, i);
+ if (router->purpose == ROUTER_PURPOSE_BRIDGE) {
+ log_notice(LD_DIR,
+ "Dropping existing bridge descriptor for %s",
+ router_describe(router));
+ routerlist_remove(routerlist, router, 0, time(NULL));
+ i--;
+ }
+ }
+}
+
/** We just added a new set of descriptors. Take whatever extra steps
* we need. */
void
@@ -2023,7 +2054,7 @@ routerlist_descriptors_added(smartlist_t *sl, int from_cache)
control_event_descriptors_changed(sl);
SMARTLIST_FOREACH_BEGIN(sl, routerinfo_t *, ri) {
if (ri->purpose == ROUTER_PURPOSE_BRIDGE)
- learned_bridge_descriptor(ri, from_cache);
+ learned_bridge_descriptor(ri, from_cache, 1);
if (ri->needs_retest_if_added) {
ri->needs_retest_if_added = 0;
dirserv_single_reachability_test(approx_time(), ri);
diff --git a/src/feature/nodelist/routerlist.h b/src/feature/nodelist/routerlist.h
index 7dc748c94b..7ba305baf6 100644
--- a/src/feature/nodelist/routerlist.h
+++ b/src/feature/nodelist/routerlist.h
@@ -145,6 +145,7 @@ was_router_added_t router_add_extrainfo_to_routerlist(
int from_cache, int from_fetch);
void routerlist_descriptors_added(smartlist_t *sl, int from_cache);
void routerlist_remove_old_routers(void);
+void routerlist_drop_bridge_descriptors(void);
int router_load_single_router(const char *s, uint8_t purpose, int cache,
const char **msg);
int router_load_routers_from_string(const char *s, const char *eos,
diff --git a/src/feature/nodelist/routerstatus_st.h b/src/feature/nodelist/routerstatus_st.h
index 46ff0bdeac..55b76de581 100644
--- a/src/feature/nodelist/routerstatus_st.h
+++ b/src/feature/nodelist/routerstatus_st.h
@@ -51,6 +51,8 @@ struct routerstatus_t {
* choice as an entry guard. */
unsigned int is_bad_exit:1; /**< True iff this node is a bad choice for
* an exit node. */
+ unsigned int is_middle_only:1; /**< True iff this node is marked as bad
+ * for anything besides middle positions. */
unsigned int is_hs_dir:1; /**< True iff this router is a v2-or-later hidden
* service directory. */
unsigned int is_v2_dir:1; /** True iff this router publishes an open DirPort
diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c
index 6a703f2ab3..c6e0439338 100644
--- a/src/feature/relay/dns.c
+++ b/src/feature/relay/dns.c
@@ -1539,6 +1539,16 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses,
tor_addr_make_unspec(&addr);
+ /* Note down any DNS errors to the statistics module */
+ if (result == DNS_ERR_TIMEOUT) {
+ /* libevent timed out while resolving a name. However, because libevent
+ * handles retries and timeouts internally, this means that all attempts of
+ * libevent timed out. If we wanted to get more granular information about
+ * individual libevent attempts, we would have to implement our own DNS
+ * timeout/retry logic */
+ rep_hist_note_overload(OVERLOAD_GENERAL);
+ }
+
/* Keep track of whether IPv6 is working */
if (type == DNS_IPv6_AAAA) {
if (result == DNS_ERR_TIMEOUT) {
@@ -1642,7 +1652,7 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses,
/* The result can be changed within this function thus why we note the result
* at the end. */
- rep_hist_note_dns_query(type, result);
+ rep_hist_note_dns_error(type, result);
tor_free(arg_);
}
@@ -1662,6 +1672,9 @@ launch_one_resolve(const char *address, uint8_t query_type,
addr[0] = (char) query_type;
memcpy(addr+1, address, addr_len + 1);
+ /* Note the query for our statistics. */
+ rep_hist_note_dns_request(query_type);
+
switch (query_type) {
case DNS_IPv4_A:
req = evdns_base_resolve_ipv4(the_evdns_base,
diff --git a/src/feature/relay/include.am b/src/feature/relay/include.am
index 84bb1ff35e..8a121cef01 100644
--- a/src/feature/relay/include.am
+++ b/src/feature/relay/include.am
@@ -15,6 +15,7 @@ MODULE_RELAY_SOURCES = \
src/feature/relay/routermode.c \
src/feature/relay/relay_config.c \
src/feature/relay/relay_handshake.c \
+ src/feature/relay/relay_metrics.c \
src/feature/relay/relay_periodic.c \
src/feature/relay/relay_sys.c \
src/feature/relay/routerkeys.c \
@@ -30,6 +31,7 @@ noinst_HEADERS += \
src/feature/relay/onion_queue.h \
src/feature/relay/relay_config.h \
src/feature/relay/relay_handshake.h \
+ src/feature/relay/relay_metrics.h \
src/feature/relay/relay_periodic.h \
src/feature/relay/relay_sys.h \
src/feature/relay/relay_find_addr.h \
diff --git a/src/feature/relay/onion_queue.c b/src/feature/relay/onion_queue.c
index 85ec0dc74a..c09f4d5b9b 100644
--- a/src/feature/relay/onion_queue.c
+++ b/src/feature/relay/onion_queue.c
@@ -164,6 +164,7 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin)
#define WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL (60)
static ratelim_t last_warned =
RATELIM_INIT(WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL);
+ rep_hist_note_circuit_handshake_dropped(onionskin->handshake_type);
if (onionskin->handshake_type == ONION_HANDSHAKE_TYPE_NTOR) {
char *m;
/* Note this ntor onionskin drop as an overload */
diff --git a/src/feature/relay/relay_metrics.c b/src/feature/relay/relay_metrics.c
new file mode 100644
index 0000000000..fc8eb10d1b
--- /dev/null
+++ b/src/feature/relay/relay_metrics.c
@@ -0,0 +1,401 @@
+/* Copyright (c) 2021, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * @file relay_metrics.c
+ * @brief Relay metrics exposed through the MetricsPort
+ **/
+
+#define RELAY_METRICS_ENTRY_PRIVATE
+
+#include "orconfig.h"
+
+#include "core/or/or.h"
+#include "core/or/relay.h"
+
+#include "lib/malloc/malloc.h"
+#include "lib/container/smartlist.h"
+#include "lib/metrics/metrics_store.h"
+#include "lib/log/util_bug.h"
+
+#include "feature/relay/relay_metrics.h"
+#include "feature/stats/rephist.h"
+
+#include <event2/dns.h>
+
+/** Declarations of each fill function for metrics defined in base_metrics. */
+static void fill_dns_error_values(void);
+static void fill_dns_query_values(void);
+static void fill_global_bw_limit_values(void);
+static void fill_socket_values(void);
+static void fill_onionskins_values(void);
+static void fill_oom_values(void);
+static void fill_tcp_exhaustion_values(void);
+
+/** The base metrics that is a static array of metrics added to the metrics
+ * store.
+ *
+ * The key member MUST be also the index of the entry in the array. */
+static const relay_metrics_entry_t base_metrics[] =
+{
+ {
+ .key = RELAY_METRICS_NUM_OOM_BYTES,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_load_oom_bytes_total),
+ .help = "Total number of bytes the OOM has freed by subsystem",
+ .fill_fn = fill_oom_values,
+ },
+ {
+ .key = RELAY_METRICS_NUM_ONIONSKINS,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_load_onionskins_total),
+ .help = "Total number of onionskins handled",
+ .fill_fn = fill_onionskins_values,
+ },
+ {
+ .key = RELAY_METRICS_NUM_SOCKETS,
+ .type = METRICS_TYPE_GAUGE,
+ .name = METRICS_NAME(relay_load_socket_total),
+ .help = "Total number of sockets",
+ .fill_fn = fill_socket_values,
+ },
+ {
+ .key = RELAY_METRICS_NUM_GLOBAL_RW_LIMIT,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_load_global_rate_limit_reached_total),
+ .help = "Total number of global connection bucket limit reached",
+ .fill_fn = fill_global_bw_limit_values,
+ },
+ {
+ .key = RELAY_METRICS_NUM_DNS,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_exit_dns_query_total),
+ .help = "Total number of DNS queries done by this relay",
+ .fill_fn = fill_dns_query_values,
+ },
+ {
+ .key = RELAY_METRICS_NUM_DNS_ERRORS,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_exit_dns_error_total),
+ .help = "Total number of DNS errors encountered by this relay",
+ .fill_fn = fill_dns_error_values,
+ },
+ {
+ .key = RELAY_METRICS_NUM_TCP_EXHAUSTION,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_load_tcp_exhaustion_total),
+ .help = "Total number of times we ran out of TCP ports",
+ .fill_fn = fill_tcp_exhaustion_values,
+ },
+};
+static const size_t num_base_metrics = ARRAY_LENGTH(base_metrics);
+
+/** The only and single store of all the relay metrics. */
+static metrics_store_t *the_store;
+
+/** Helper function to convert an handshake type into a string. */
+static inline const char *
+handshake_type_to_str(const uint16_t type)
+{
+ switch (type) {
+ case ONION_HANDSHAKE_TYPE_TAP:
+ return "tap";
+ case ONION_HANDSHAKE_TYPE_FAST:
+ return "fast";
+ case ONION_HANDSHAKE_TYPE_NTOR:
+ return "ntor";
+ default:
+ // LCOV_EXCL_START
+ tor_assert_unreached();
+ // LCOV_EXCL_STOP
+ }
+}
+
+/** Fill function for the RELAY_METRICS_NUM_DNS metrics. */
+static void
+fill_tcp_exhaustion_values(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_TCP_EXHAUSTION];
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_update(sentry, rep_hist_get_n_tcp_exhaustion());
+}
+
+/* NOTE: Disable the record type label until libevent is fixed. */
+#if 0
+/** Helper array containing mapping for the name of the different DNS records
+ * and their corresponding libevent values. */
+static struct dns_type {
+ const char *name;
+ uint8_t type;
+} dns_types[] = {
+ { .name = "A", .type = DNS_IPv4_A },
+ { .name = "PTR", .type = DNS_PTR },
+ { .name = "AAAA", .type = DNS_IPv6_AAAA },
+};
+static const size_t num_dns_types = ARRAY_LENGTH(dns_types);
+#endif
+
+/** Fill function for the RELAY_METRICS_NUM_DNS_ERRORS metrics. */
+static void
+fill_dns_error_values(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_DNS_ERRORS];
+
+ /* Helper array to map libeven DNS errors to their names and so we can
+ * iterate over this array to add all metrics. */
+ static struct dns_error {
+ const char *name;
+ uint8_t key;
+ } errors[] = {
+ { .name = "success", .key = DNS_ERR_NONE },
+ { .name = "format", .key = DNS_ERR_FORMAT },
+ { .name = "serverfailed", .key = DNS_ERR_SERVERFAILED },
+ { .name = "notexist", .key = DNS_ERR_NOTEXIST },
+ { .name = "notimpl", .key = DNS_ERR_NOTIMPL },
+ { .name = "refused", .key = DNS_ERR_REFUSED },
+ { .name = "truncated", .key = DNS_ERR_TRUNCATED },
+ { .name = "unknown", .key = DNS_ERR_UNKNOWN },
+ { .name = "tor_timeout", .key = DNS_ERR_TIMEOUT },
+ { .name = "shutdown", .key = DNS_ERR_SHUTDOWN },
+ { .name = "cancel", .key = DNS_ERR_CANCEL },
+ { .name = "nodata", .key = DNS_ERR_NODATA },
+ };
+ static const size_t num_errors = ARRAY_LENGTH(errors);
+
+ /* NOTE: Disable the record type label until libevent is fixed. */
+#if 0
+ for (size_t i = 0; i < num_dns_types; i++) {
+ /* Dup the label because metrics_format_label() returns a pointer to a
+ * string on the stack and we need that label for all metrics. */
+ char *record_label =
+ tor_strdup(metrics_format_label("record", dns_types[i].name));
+
+ for (size_t j = 0; j < num_errors; j++) {
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry, record_label);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("reason", errors[j].name));
+ metrics_store_entry_update(sentry,
+ rep_hist_get_n_dns_error(dns_types[i].type, errors[j].key));
+ }
+ tor_free(record_label);
+ }
+#endif
+
+ /* Put in the DNS errors, unfortunately not per-type for now. */
+ for (size_t j = 0; j < num_errors; j++) {
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("reason", errors[j].name));
+ metrics_store_entry_update(sentry,
+ rep_hist_get_n_dns_error(0, errors[j].key));
+ }
+}
+
+/** Fill function for the RELAY_METRICS_NUM_DNS metrics. */
+static void
+fill_dns_query_values(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_DNS];
+
+ /* NOTE: Disable the record type label until libevent is fixed (#40490). */
+#if 0
+ for (size_t i = 0; i < num_dns_types; i++) {
+ /* Dup the label because metrics_format_label() returns a pointer to a
+ * string on the stack and we need that label for all metrics. */
+ char *record_label =
+ tor_strdup(metrics_format_label("record", dns_types[i].name));
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry, record_label);
+ metrics_store_entry_update(sentry,
+ rep_hist_get_n_dns_request(dns_types[i].type));
+ tor_free(record_label);
+ }
+#endif
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_update(sentry, rep_hist_get_n_dns_request(0));
+}
+
+/** Fill function for the RELAY_METRICS_NUM_GLOBAL_RW_LIMIT metrics. */
+static void
+fill_global_bw_limit_values(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_GLOBAL_RW_LIMIT];
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("side", "read"));
+ metrics_store_entry_update(sentry, rep_hist_get_n_read_limit_reached());
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("side", "write"));
+ metrics_store_entry_update(sentry, rep_hist_get_n_write_limit_reached());
+}
+
+/** Fill function for the RELAY_METRICS_NUM_SOCKETS metrics. */
+static void
+fill_socket_values(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_SOCKETS];
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("state", "opened"));
+ metrics_store_entry_update(sentry, get_n_open_sockets());
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_update(sentry, get_max_sockets());
+}
+
+/** Fill function for the RELAY_METRICS_NUM_ONIONSKINS metrics. */
+static void
+fill_onionskins_values(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_ONIONSKINS];
+
+ for (uint16_t t = 0; t <= MAX_ONION_HANDSHAKE_TYPE; t++) {
+ /* Dup the label because metrics_format_label() returns a pointer to a
+ * string on the stack and we need that label for all metrics. */
+ char *type_label =
+ tor_strdup(metrics_format_label("type", handshake_type_to_str(t)));
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry, type_label);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("action", "processed"));
+ metrics_store_entry_update(sentry,
+ rep_hist_get_circuit_n_handshake_assigned(t));
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry, type_label);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("action", "dropped"));
+ metrics_store_entry_update(sentry,
+ rep_hist_get_circuit_n_handshake_dropped(t));
+ tor_free(type_label);
+ }
+}
+
+/** Fill function for the RELAY_METRICS_NUM_OOM_BYTES metrics. */
+static void
+fill_oom_values(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_OOM_BYTES];
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("subsys", "cell"));
+ metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_cell);
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("subsys", "dns"));
+ metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_dns);
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("subsys", "geoip"));
+ metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_geoip);
+
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help);
+ metrics_store_entry_add_label(sentry,
+ metrics_format_label("subsys", "hsdir"));
+ metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_hsdir);
+}
+
+/** Reset the global store and fill it with all the metrics from base_metrics
+ * and their associated values.
+ *
+ * To pull this off, every metrics has a "fill" function that is called and in
+ * charge of adding the metrics to the store, appropriate labels and finally
+ * updating the value to report. */
+static void
+fill_store(void)
+{
+ /* Reset the current store, we are about to fill it with all the things. */
+ metrics_store_reset(the_store);
+
+ /* Call the fill function for each metrics. */
+ for (size_t i = 0; i < num_base_metrics; i++) {
+ if (BUG(!base_metrics[i].fill_fn)) {
+ continue;
+ }
+ base_metrics[i].fill_fn();
+ }
+}
+
+/** Return a list of all the relay metrics stores. This is the
+ * function attached to the .get_metrics() member of the subsys_t. */
+const smartlist_t *
+relay_metrics_get_stores(void)
+{
+ /* We can't have the caller to free the returned list so keep it static,
+ * simply update it. */
+ static smartlist_t *stores_list = NULL;
+
+ /* We dynamically fill the store with all the metrics upon a request. The
+ * reason for this is because the exposed metrics of a relay are often
+ * internal counters in the fast path and thus we fetch the value when a
+ * metrics port request arrives instead of keeping a local metrics store of
+ * those values. */
+ fill_store();
+
+ if (!stores_list) {
+ stores_list = smartlist_new();
+ smartlist_add(stores_list, the_store);
+ }
+
+ return stores_list;
+}
+
+/** Initialize the relay metrics. */
+void
+relay_metrics_init(void)
+{
+ if (BUG(the_store)) {
+ return;
+ }
+ the_store = metrics_store_new();
+}
+
+/** Free the relay metrics. */
+void
+relay_metrics_free(void)
+{
+ if (!the_store) {
+ return;
+ }
+ /* NULL is set with this call. */
+ metrics_store_free(the_store);
+}
diff --git a/src/feature/relay/relay_metrics.h b/src/feature/relay/relay_metrics.h
new file mode 100644
index 0000000000..00dfeaa624
--- /dev/null
+++ b/src/feature/relay/relay_metrics.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2021, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * @file relay_metrics.h
+ * @brief Header for feature/relay/relay_metrics.c
+ **/
+
+#ifndef TOR_FEATURE_RELAY_RELAY_METRICS_H
+#define TOR_FEATURE_RELAY_RELAY_METRICS_H
+
+#include "lib/container/smartlist.h"
+#include "lib/metrics/metrics_common.h"
+
+/** Metrics key for each reported metrics. This key is also used as an index in
+ * the base_metrics array. */
+typedef enum {
+ /** Number of OOM invocation. */
+ RELAY_METRICS_NUM_OOM_BYTES = 0,
+ /** Number of onionskines handled. */
+ RELAY_METRICS_NUM_ONIONSKINS = 1,
+ /** Number of sockets. */
+ RELAY_METRICS_NUM_SOCKETS = 2,
+ /** Number of global connection rate limit. */
+ RELAY_METRICS_NUM_GLOBAL_RW_LIMIT = 3,
+ /** Number of DNS queries. */
+ RELAY_METRICS_NUM_DNS = 4,
+ /** Number of DNS query errors. */
+ RELAY_METRICS_NUM_DNS_ERRORS = 5,
+ /** Number of TCP exhaustion reached. */
+ RELAY_METRICS_NUM_TCP_EXHAUSTION = 6,
+} relay_metrics_key_t;
+
+/** The metadata of a relay metric. */
+typedef struct relay_metrics_entry_t {
+ /* Metric key used as a static array index. */
+ relay_metrics_key_t key;
+ /* Metric type. */
+ metrics_type_t type;
+ /* Metrics output name. */
+ const char *name;
+ /* Metrics output help comment. */
+ const char *help;
+ /* Update value function. */
+ void (*fill_fn)(void);
+} relay_metrics_entry_t;
+
+/* Init. */
+void relay_metrics_init(void);
+void relay_metrics_free(void);
+
+/* Accessors. */
+const smartlist_t *relay_metrics_get_stores(void);
+
+#endif /* !defined(TOR_FEATURE_RELAY_RELAY_METRICS_H) */
diff --git a/src/feature/relay/relay_periodic.c b/src/feature/relay/relay_periodic.c
index ee94590e01..dd9be4e36f 100644
--- a/src/feature/relay/relay_periodic.c
+++ b/src/feature/relay/relay_periodic.c
@@ -219,7 +219,7 @@ reachability_warnings_callback(time_t now, const or_options_t *options)
tor_asprintf(&where4, "%s:%d", address4, me->ipv4_orport);
if (!v6_ok)
tor_asprintf(&where6, "[%s]:%d", address6, me->ipv6_orport);
- const char *opt_and = (!v4_ok && !v6_ok) ? "and" : "";
+ const char *opt_and = (!v4_ok && !v6_ok) ? " and " : "";
/* IPv4 reachability test worked but not the IPv6. We will _not_
* publish the descriptor if our IPv6 was configured. We will if it
diff --git a/src/feature/relay/relay_sys.c b/src/feature/relay/relay_sys.c
index 25fc0bbd32..9c43734b84 100644
--- a/src/feature/relay/relay_sys.c
+++ b/src/feature/relay/relay_sys.c
@@ -14,6 +14,7 @@
#include "feature/relay/dns.h"
#include "feature/relay/ext_orport.h"
+#include "feature/relay/relay_metrics.h"
#include "feature/relay/onion_queue.h"
#include "feature/relay/relay_periodic.h"
#include "feature/relay/relay_sys.h"
@@ -25,6 +26,7 @@
static int
subsys_relay_initialize(void)
{
+ relay_metrics_init();
relay_register_periodic_events();
return 0;
}
@@ -37,6 +39,7 @@ subsys_relay_shutdown(void)
clear_pending_onions();
routerkeys_free_all();
router_free_all();
+ relay_metrics_free();
}
const struct subsys_fns_t sys_relay = {
@@ -46,4 +49,6 @@ const struct subsys_fns_t sys_relay = {
.level = RELAY_SUBSYS_LEVEL,
.initialize = subsys_relay_initialize,
.shutdown = subsys_relay_shutdown,
+
+ .get_metrics = relay_metrics_get_stores,
};
diff --git a/src/feature/rend/rendmid.c b/src/feature/rend/rendmid.c
index df838aa527..8f6a45dfef 100644
--- a/src/feature/rend/rendmid.c
+++ b/src/feature/rend/rendmid.c
@@ -131,7 +131,11 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
rend_circ = hs_circuitmap_get_rend_circ_relay_side(request);
if (!rend_circ) {
- log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ /* Once this was a LOG_PROTOCOL_WARN, but it can happen naturally if a
+ * client gives up on a rendezvous circuit after sending INTRODUCE1, but
+ * before the onion service sends the RENDEZVOUS1 cell.
+ */
+ log_fn(LOG_DEBUG, LD_PROTOCOL,
"Rejecting RENDEZVOUS1 cell with unrecognized rendezvous cookie %s.",
hexid);
reason = END_CIRC_REASON_TORPROTOCOL;
diff --git a/src/feature/stats/geoip_stats.c b/src/feature/stats/geoip_stats.c
index b4b107c3f7..a0fe8597c1 100644
--- a/src/feature/stats/geoip_stats.c
+++ b/src/feature/stats/geoip_stats.c
@@ -1206,11 +1206,11 @@ format_bridge_stats_controller(time_t now)
char *
format_client_stats_heartbeat(time_t now)
{
- const int n_hours = 6;
+ const int n_seconds = get_options()->HeartbeatPeriod;
char *out = NULL;
int n_clients = 0;
clientmap_entry_t **ent;
- unsigned cutoff = (unsigned)( (now-n_hours*3600)/60 );
+ unsigned cutoff = (unsigned)( (now-n_seconds)/60 );
if (!start_of_bridge_stats_interval)
return NULL; /* Not initialized. */
@@ -1226,8 +1226,7 @@ format_client_stats_heartbeat(time_t now)
}
tor_asprintf(&out, "Heartbeat: "
- "In the last %d hours, I have seen %d unique clients.",
- n_hours,
+ "Since last heartbeat message, I have seen %d unique clients.",
n_clients);
return out;
diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c
index 2bfa14d326..5ff4ef1d2e 100644
--- a/src/feature/stats/rephist.c
+++ b/src/feature/stats/rephist.c
@@ -206,18 +206,33 @@ typedef struct {
uint64_t overload_fd_exhausted;
} overload_stats_t;
+/** Current state of overload stats */
+static overload_stats_t overload_stats;
+
+/** Counters to count the number of times we've reached an overload for the
+ * global connection read/write limit. Reported on the MetricsPort. */
+static uint64_t stats_n_read_limit_reached = 0;
+static uint64_t stats_n_write_limit_reached = 0;
+
+/** Total number of times we've reached TCP port exhaustion. */
+static uint64_t stats_n_tcp_exhaustion = 0;
+
/***** DNS statistics *****/
-/** Represents the statistics of DNS queries seen if it is an Exit. */
+/** Overload DNS statistics. The information in this object is used to assess
+ * if, due to DNS errors, we should emit a general overload signal or not.
+ *
+ * NOTE: This structure is _not_ per DNS query type like the statistics below
+ * because of a libevent bug
+ * (https://github.com/libevent/libevent/issues/1219), on error, the type is
+ * not propagated up back to the user and so we need to keep our own stats for
+ * the overload signal. */
typedef struct {
/** Total number of DNS request seen at an Exit. They might not all end
* successfully or might even be lost by tor. This counter is incremented
* right before the DNS request is initiated. */
uint64_t stats_n_request;
- /** Total number of DNS timeout errors. */
- uint64_t stats_n_error_timeout;
-
/** When is the next assessment time of the general overload for DNS errors.
* Once this time is reached, all stats are reset and this time is set to the
* next assessment time. */
@@ -227,121 +242,230 @@ typedef struct {
/** Keep track of the DNS requests for the general overload state. */
static overload_dns_stats_t overload_dns_stats;
-/* We use a scale here so we can represent percentages with decimal points by
- * scaling the value by this factor and so 0.5% becomes a value of 500.
- * Default is 1% and thus min and max range is 0 to 100%. */
-#define OVERLOAD_DNS_TIMEOUT_PERCENT_SCALE 1000.0
-#define OVERLOAD_DNS_TIMEOUT_PERCENT_DEFAULT 1000
-#define OVERLOAD_DNS_TIMEOUT_PERCENT_MIN 0
-#define OVERLOAD_DNS_TIMEOUT_PERCENT_MAX 100000
-
-/** Consensus parameter: indicate what fraction of DNS timeout errors over the
- * total number of DNS requests must be reached before we trigger a general
- * overload signal .*/
-static double overload_dns_timeout_fraction =
- OVERLOAD_DNS_TIMEOUT_PERCENT_DEFAULT /
- OVERLOAD_DNS_TIMEOUT_PERCENT_SCALE / 100.0;
-
-/* Number of seconds for the assessment period. Default is 10 minutes (600) and
- * the min max range is within a 32bit value. */
-#define OVERLOAD_DNS_TIMEOUT_PERIOD_SECS_DEFAULT (10 * 60)
-#define OVERLOAD_DNS_TIMEOUT_PERIOD_SECS_MIN 0
-#define OVERLOAD_DNS_TIMEOUT_PERIOD_SECS_MAX INT32_MAX
-
-/** Consensus parameter: Period, in seconds, over which we count the number of
- * DNS requests and timeout errors. After that period, we assess if we trigger
- * an overload or not. */
-static int32_t overload_dns_timeout_period_secs =
- OVERLOAD_DNS_TIMEOUT_PERIOD_SECS_DEFAULT;
+/** Represents the statistics of DNS queries seen if it is an Exit. */
+typedef struct {
+ /* Total number of DNS errors found in RFC 1035 (from 0 to 5 code). */
+ uint64_t stats_n_error_none; /* 0 */
+ uint64_t stats_n_error_format; /* 1 */
+ uint64_t stats_n_error_serverfailed; /* 2 */
+ uint64_t stats_n_error_notexist; /* 3 */
+ uint64_t stats_n_error_notimpl; /* 4 */
+ uint64_t stats_n_error_refused; /* 5 */
+
+ /* Total number of DNS errors specific to libevent. */
+ uint64_t stats_n_error_truncated; /* 65 */
+ uint64_t stats_n_error_unknown; /* 66 */
+ uint64_t stats_n_error_tor_timeout; /* 67 */
+ uint64_t stats_n_error_shutdown; /* 68 */
+ uint64_t stats_n_error_cancel; /* 69 */
+ uint64_t stats_n_error_nodata; /* 70 */
+
+ /* Total number of DNS request seen at an Exit. They might not all end
+ * successfully or might even be lost by tor. This counter is incremented
+ * right before the DNS request is initiated. */
+ uint64_t stats_n_request;
+} dns_stats_t;
+
+/* This is disabled because of the libevent bug where on error we don't get the
+ * DNS query type back. Once it is fixed, we can re-enable this. */
+#if 0
+/** DNS statistics store for each DNS record type for which tor supports only
+ * three at the moment: A, PTR and AAAA. */
+static dns_stats_t dns_A_stats;
+static dns_stats_t dns_PTR_stats;
+static dns_stats_t dns_AAAA_stats;
+#endif
-/** Current state of overload stats */
-static overload_stats_t overload_stats;
+/** DNS query statistics store. It covers all type of queries. */
+static dns_stats_t dns_all_stats;
-/** Return true if this overload happened within the last `n_hours`. */
-static bool
-overload_happened_recently(time_t overload_time, int n_hours)
+/** Return the point to the DNS statistics store. Ignore the type for now
+ * because of a libevent problem. */
+static inline dns_stats_t *
+get_dns_stats_by_type(const int type)
{
- /* An overload is relevant if it happened in the last 72 hours */
- if (overload_time > approx_time() - 3600 * n_hours) {
- return true;
- }
- return false;
+ (void) type;
+ return &dns_all_stats;
}
-/** Assess the DNS timeout errors and if we have enough to trigger a general
- * overload. */
-static void
-overload_general_dns_assessment(void)
+#if 0
+/** From a libevent record type, return a pointer to the corresponding DNS
+ * statistics store. NULL is returned if the type is unhandled. */
+static inline dns_stats_t *
+get_dns_stats_by_type(const int type)
{
- /* Initialize the time. Should be done once. */
- if (overload_dns_stats.next_assessment_time == 0) {
- goto reset;
+ switch (type) {
+ case DNS_IPv4_A:
+ return &dns_A_stats;
+ case DNS_PTR:
+ return &dns_PTR_stats;
+ case DNS_IPv6_AAAA:
+ return &dns_AAAA_stats;
+ default:
+ return NULL;
}
+}
+#endif
- /* Not the time yet. */
- if (overload_dns_stats.next_assessment_time > approx_time()) {
- return;
+/** Return the DNS error count for the given libevent DNS type and error code.
+ * The possible types are: DNS_IPv4_A, DNS_PTR, DNS_IPv6_AAAA. */
+uint64_t
+rep_hist_get_n_dns_error(int type, uint8_t error)
+{
+ dns_stats_t *dns_stats = get_dns_stats_by_type(type);
+ if (BUG(!dns_stats)) {
+ return 0;
}
- reset:
- /* Reset counters for the next period. */
- overload_dns_stats.stats_n_error_timeout = 0;
- overload_dns_stats.stats_n_request = 0;
- overload_dns_stats.next_assessment_time =
- approx_time() + overload_dns_timeout_period_secs;
+ switch (error) {
+ case DNS_ERR_NONE:
+ return dns_stats->stats_n_error_none;
+ case DNS_ERR_FORMAT:
+ return dns_stats->stats_n_error_format;
+ case DNS_ERR_SERVERFAILED:
+ return dns_stats->stats_n_error_serverfailed;
+ case DNS_ERR_NOTEXIST:
+ return dns_stats->stats_n_error_notexist;
+ case DNS_ERR_NOTIMPL:
+ return dns_stats->stats_n_error_notimpl;
+ case DNS_ERR_REFUSED:
+ return dns_stats->stats_n_error_refused;
+ case DNS_ERR_TRUNCATED:
+ return dns_stats->stats_n_error_truncated;
+ case DNS_ERR_UNKNOWN:
+ return dns_stats->stats_n_error_unknown;
+ case DNS_ERR_TIMEOUT:
+ return dns_stats->stats_n_error_tor_timeout;
+ case DNS_ERR_SHUTDOWN:
+ return dns_stats->stats_n_error_shutdown;
+ case DNS_ERR_CANCEL:
+ return dns_stats->stats_n_error_cancel;
+ case DNS_ERR_NODATA:
+ return dns_stats->stats_n_error_nodata;
+ default:
+ /* Unhandled code sent back by libevent. */
+ return 0;
+ }
}
-/** Called just before the consensus will be replaced. Update the consensus
- * parameters in case they changed. */
-void
-rep_hist_consensus_has_changed(const networkstatus_t *ns)
+/** Return the total number of DNS request seen for the given libevent DNS
+ * record type. Possible types are: DNS_IPv4_A, DNS_PTR, DNS_IPv6_AAAA. */
+uint64_t
+rep_hist_get_n_dns_request(int type)
{
- overload_dns_timeout_fraction =
- networkstatus_get_param(ns, "overload_dns_timeout_scale_percent",
- OVERLOAD_DNS_TIMEOUT_PERCENT_DEFAULT,
- OVERLOAD_DNS_TIMEOUT_PERCENT_MIN,
- OVERLOAD_DNS_TIMEOUT_PERCENT_MAX) /
- OVERLOAD_DNS_TIMEOUT_PERCENT_SCALE / 100.0;
-
- overload_dns_timeout_period_secs =
- networkstatus_get_param(ns, "overload_dns_timeout_period_secs",
- OVERLOAD_DNS_TIMEOUT_PERIOD_SECS_DEFAULT,
- OVERLOAD_DNS_TIMEOUT_PERIOD_SECS_MIN,
- OVERLOAD_DNS_TIMEOUT_PERIOD_SECS_MAX);
+ dns_stats_t *dns_stats = get_dns_stats_by_type(type);
+ if (BUG(!dns_stats)) {
+ return 0;
+ }
+ return dns_stats->stats_n_request;
}
/** Note a DNS error for the given given libevent DNS record type and error
* code. Possible types are: DNS_IPv4_A, DNS_PTR, DNS_IPv6_AAAA.
*
- * IMPORTANT: Libevent is _not_ returning the type in case of an error and so
- * if error is anything but DNS_ERR_NONE, the type is not usable and set to 0.
+ * NOTE: Libevent is _not_ returning the type in case of an error and so if
+ * error is anything but DNS_ERR_NONE, the type is not usable and set to 0.
*
* See: https://gitlab.torproject.org/tpo/core/tor/-/issues/40490 */
void
-rep_hist_note_dns_query(int type, uint8_t error)
+rep_hist_note_dns_error(int type, uint8_t error)
{
- (void) type;
+ overload_dns_stats.stats_n_request++;
- /* Assess if we need to trigger a general overload with regards to the DNS
- * errors or not. */
- overload_general_dns_assessment();
+ /* Again, the libevent bug (see function comment), for an error that is
+ * anything but DNS_ERR_NONE, the type is always 0 which means that we don't
+ * have a DNS stat object for it so this code will do nothing until libevent
+ * is fixed. */
+ dns_stats_t *dns_stats = get_dns_stats_by_type(type);
+ /* Unsupported DNS query type. */
+ if (!dns_stats) {
+ return;
+ }
- /* We only care about timeouts for the moment. */
switch (error) {
+ case DNS_ERR_NONE:
+ dns_stats->stats_n_error_none++;
+ break;
+ case DNS_ERR_FORMAT:
+ dns_stats->stats_n_error_format++;
+ break;
+ case DNS_ERR_SERVERFAILED:
+ dns_stats->stats_n_error_serverfailed++;
+ break;
+ case DNS_ERR_NOTEXIST:
+ dns_stats->stats_n_error_notexist++;
+ break;
+ case DNS_ERR_NOTIMPL:
+ dns_stats->stats_n_error_notimpl++;
+ break;
+ case DNS_ERR_REFUSED:
+ dns_stats->stats_n_error_refused++;
+ break;
+ case DNS_ERR_TRUNCATED:
+ dns_stats->stats_n_error_truncated++;
+ break;
+ case DNS_ERR_UNKNOWN:
+ dns_stats->stats_n_error_unknown++;
+ break;
case DNS_ERR_TIMEOUT:
- overload_dns_stats.stats_n_error_timeout++;
+ dns_stats->stats_n_error_tor_timeout++;
+ break;
+ case DNS_ERR_SHUTDOWN:
+ dns_stats->stats_n_error_shutdown++;
+ break;
+ case DNS_ERR_CANCEL:
+ dns_stats->stats_n_error_cancel++;
+ break;
+ case DNS_ERR_NODATA:
+ dns_stats->stats_n_error_nodata++;
break;
default:
+ /* Unhandled code sent back by libevent. */
break;
}
+}
- /* Increment total number of requests. */
- overload_dns_stats.stats_n_request++;
+/** Note a DNS request for the given given libevent DNS record type. */
+void
+rep_hist_note_dns_request(int type)
+{
+ dns_stats_t *dns_stats = get_dns_stats_by_type(type);
+ if (BUG(!dns_stats)) {
+ return;
+ }
+ dns_stats->stats_n_request++;
+}
+
+/***** END of DNS statistics *****/
+
+/** Return true if this overload happened within the last `n_hours`. */
+static bool
+overload_happened_recently(time_t overload_time, int n_hours)
+{
+ /* An overload is relevant if it happened in the last 72 hours */
+ if (overload_time > approx_time() - 3600 * n_hours) {
+ return true;
+ }
+ return false;
}
/* The current version of the overload stats version */
#define OVERLOAD_STATS_VERSION 1
+/** Return the stats_n_read_limit_reached counter. */
+uint64_t
+rep_hist_get_n_read_limit_reached(void)
+{
+ return stats_n_read_limit_reached;
+}
+
+/** Return the stats_n_write_limit_reached counter. */
+uint64_t
+rep_hist_get_n_write_limit_reached(void)
+{
+ return stats_n_write_limit_reached;
+}
+
/** Returns an allocated string for server descriptor for publising information
* on whether we are overloaded or not. */
char *
@@ -420,6 +544,7 @@ rep_hist_note_overload(overload_type_t overload)
SET_TO_START_OF_HOUR(overload_stats.overload_general_time);
break;
case OVERLOAD_READ: {
+ stats_n_read_limit_reached++;
SET_TO_START_OF_HOUR(overload_stats.overload_ratelimits_time);
if (approx_time() >= last_read_counted + 60) { /* Count once a minute */
overload_stats.overload_read_count++;
@@ -428,6 +553,7 @@ rep_hist_note_overload(overload_type_t overload)
break;
}
case OVERLOAD_WRITE: {
+ stats_n_write_limit_reached++;
SET_TO_START_OF_HOUR(overload_stats.overload_ratelimits_time);
if (approx_time() >= last_write_counted + 60) { /* Count once a minute */
overload_stats.overload_write_count++;
@@ -442,6 +568,22 @@ rep_hist_note_overload(overload_type_t overload)
}
}
+/** Note down that we've reached a TCP port exhaustion. This triggers an
+ * overload general event. */
+void
+rep_hist_note_tcp_exhaustion(void)
+{
+ stats_n_tcp_exhaustion++;
+ rep_hist_note_overload(OVERLOAD_GENERAL);
+}
+
+/** Return the total number of TCP exhaustion times we've reached. */
+uint64_t
+rep_hist_get_n_tcp_exhaustion(void)
+{
+ return stats_n_tcp_exhaustion;
+}
+
/** Return the or_history_t for the OR with identity digest <b>id</b>,
* creating it if necessary. */
static or_history_t *
@@ -641,7 +783,7 @@ rep_hist_downrate_old_runs(time_t now)
return stability_last_downrated + STABILITY_INTERVAL;
/* Okay, we should downrate the data. By how much? */
- while (stability_last_downrated + STABILITY_INTERVAL < now) {
+ while (stability_last_downrated + STABILITY_INTERVAL <= now) {
stability_last_downrated += STABILITY_INTERVAL;
alpha *= STABILITY_ALPHA;
}
@@ -1908,11 +2050,18 @@ rep_hist_note_desc_served(const char * desc)
/** Internal statistics to track how many requests of each type of
* handshake we've received, and how many we've assigned to cpuworkers.
* Useful for seeing trends in cpu load.
+ *
+ * They are reset at every heartbeat.
* @{ */
STATIC int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1] = {0};
STATIC int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1] = {0};
/**@}*/
+/** Counters keeping the same stats as above but for the entire duration of the
+ * process (not reset). */
+static uint64_t stats_n_onionskin_assigned[MAX_ONION_HANDSHAKE_TYPE+1] = {0};
+static uint64_t stats_n_onionskin_dropped[MAX_ONION_HANDSHAKE_TYPE+1] = {0};
+
/** A new onionskin (using the <b>type</b> handshake) has arrived. */
void
rep_hist_note_circuit_handshake_requested(uint16_t type)
@@ -1926,8 +2075,20 @@ rep_hist_note_circuit_handshake_requested(uint16_t type)
void
rep_hist_note_circuit_handshake_assigned(uint16_t type)
{
- if (type <= MAX_ONION_HANDSHAKE_TYPE)
+ if (type <= MAX_ONION_HANDSHAKE_TYPE) {
onion_handshakes_assigned[type]++;
+ stats_n_onionskin_assigned[type]++;
+ }
+}
+
+/** We've just drop an onionskin (using the <b>type</b> handshake) due to being
+ * overloaded. */
+void
+rep_hist_note_circuit_handshake_dropped(uint16_t type)
+{
+ if (type <= MAX_ONION_HANDSHAKE_TYPE) {
+ stats_n_onionskin_dropped[type]++;
+ }
}
/** Get the circuit handshake value that is requested. */
@@ -1950,6 +2111,26 @@ rep_hist_get_circuit_handshake_assigned, (uint16_t type))
return onion_handshakes_assigned[type];
}
+/** Get the total number of circuit handshake value that is assigned. */
+MOCK_IMPL(uint64_t,
+rep_hist_get_circuit_n_handshake_assigned, (uint16_t type))
+{
+ if (BUG(type > MAX_ONION_HANDSHAKE_TYPE)) {
+ return 0;
+ }
+ return stats_n_onionskin_assigned[type];
+}
+
+/** Get the total number of circuit handshake value that is dropped. */
+MOCK_IMPL(uint64_t,
+rep_hist_get_circuit_n_handshake_dropped, (uint16_t type))
+{
+ if (BUG(type > MAX_ONION_HANDSHAKE_TYPE)) {
+ return 0;
+ }
+ return stats_n_onionskin_dropped[type];
+}
+
/** Log our onionskin statistics since the last time we were called. */
void
rep_hist_log_circuit_handshake_stats(time_t now)
diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h
index 749b4996a8..7f414de4c8 100644
--- a/src/feature/stats/rephist.h
+++ b/src/feature/stats/rephist.h
@@ -58,11 +58,17 @@ time_t rep_hist_desc_stats_write(time_t now);
void rep_hist_note_circuit_handshake_requested(uint16_t type);
void rep_hist_note_circuit_handshake_assigned(uint16_t type);
+void rep_hist_note_circuit_handshake_dropped(uint16_t type);
void rep_hist_log_circuit_handshake_stats(time_t now);
MOCK_DECL(int, rep_hist_get_circuit_handshake_requested, (uint16_t type));
MOCK_DECL(int, rep_hist_get_circuit_handshake_assigned, (uint16_t type));
+MOCK_DECL(uint64_t, rep_hist_get_circuit_n_handshake_assigned,
+ (uint16_t type));
+MOCK_DECL(uint64_t, rep_hist_get_circuit_n_handshake_dropped,
+ (uint16_t type));
+
void rep_hist_hs_stats_init(time_t now);
void rep_hist_hs_stats_term(void);
time_t rep_hist_hs_stats_write(time_t now, bool is_v3);
@@ -72,14 +78,16 @@ void rep_hist_seen_new_rp_cell(bool is_v2);
char *rep_hist_get_hs_v3_stats_string(void);
void rep_hist_hsdir_stored_maybe_new_v3_onion(const uint8_t *blinded_key);
-void rep_hist_note_dns_query(int type, uint8_t error);
-
void rep_hist_free_all(void);
void rep_hist_note_negotiated_link_proto(unsigned link_proto,
int started_here);
void rep_hist_log_link_protocol_counts(void);
-void rep_hist_consensus_has_changed(const networkstatus_t *ns);
+
+uint64_t rep_hist_get_n_dns_error(int type, uint8_t error);
+uint64_t rep_hist_get_n_dns_request(int type);
+void rep_hist_note_dns_request(int type);
+void rep_hist_note_dns_error(int type, uint8_t error);
extern uint64_t rephist_total_alloc;
extern uint32_t rephist_total_num;
@@ -162,6 +170,12 @@ void rep_hist_note_overload(overload_type_t overload);
char *rep_hist_get_overload_general_line(void);
char *rep_hist_get_overload_stats_lines(void);
+void rep_hist_note_tcp_exhaustion(void);
+uint64_t rep_hist_get_n_tcp_exhaustion(void);
+
+uint64_t rep_hist_get_n_read_limit_reached(void);
+uint64_t rep_hist_get_n_write_limit_reached(void);
+
#ifdef TOR_UNIT_TESTS
struct hs_v2_stats_t;
const struct hs_v2_stats_t *rep_hist_get_hs_v2_stats(void);