summaryrefslogtreecommitdiff
path: root/src/feature
diff options
context:
space:
mode:
Diffstat (limited to 'src/feature')
-rw-r--r--src/feature/client/bridges.c35
-rw-r--r--src/feature/client/bridges.h1
-rw-r--r--src/feature/client/circpathbias.c13
-rw-r--r--src/feature/client/entrynodes.c48
-rw-r--r--src/feature/client/entrynodes.h14
-rw-r--r--src/feature/client/transports.c99
-rw-r--r--src/feature/client/transports.h2
-rw-r--r--src/feature/control/control_fmt.c7
-rw-r--r--src/feature/control/getinfo_geoip.c5
-rw-r--r--src/feature/dirauth/dirauth_options.inc5
-rw-r--r--src/feature/dirauth/dirvote.c36
-rw-r--r--src/feature/dirauth/dirvote.h8
-rw-r--r--src/feature/dirauth/process_descs.c15
-rw-r--r--src/feature/dirclient/dirclient.c25
-rw-r--r--src/feature/dirparse/ns_parse.c5
-rw-r--r--src/feature/dirparse/parsecommon.h1
-rw-r--r--src/feature/hs/hs_cache.c9
-rw-r--r--src/feature/hs/hs_cell.c201
-rw-r--r--src/feature/hs/hs_cell.h35
-rw-r--r--src/feature/hs/hs_circuit.c410
-rw-r--r--src/feature/hs/hs_circuit.h31
-rw-r--r--src/feature/hs/hs_client.c224
-rw-r--r--src/feature/hs/hs_client.h12
-rw-r--r--src/feature/hs/hs_common.c4
-rw-r--r--src/feature/hs/hs_common.h7
-rw-r--r--src/feature/hs/hs_config.c29
-rw-r--r--src/feature/hs/hs_config.h3
-rw-r--r--src/feature/hs/hs_descriptor.c134
-rw-r--r--src/feature/hs/hs_descriptor.h11
-rw-r--r--src/feature/hs/hs_dos.c5
-rw-r--r--src/feature/hs/hs_intropoint.c21
-rw-r--r--src/feature/hs/hs_metrics.c148
-rw-r--r--src/feature/hs/hs_metrics.h78
-rw-r--r--src/feature/hs/hs_metrics_entry.c97
-rw-r--r--src/feature/hs/hs_metrics_entry.h56
-rw-r--r--src/feature/hs/hs_options.inc3
-rw-r--r--src/feature/hs/hs_pow.c569
-rw-r--r--src/feature/hs/hs_pow.h228
-rw-r--r--src/feature/hs/hs_service.c309
-rw-r--r--src/feature/hs/hs_service.h18
-rw-r--r--src/feature/hs/include.am9
-rw-r--r--src/feature/nodelist/fmt_routerstatus.c16
-rw-r--r--src/feature/nodelist/fmt_routerstatus.h3
-rw-r--r--src/feature/nodelist/microdesc.c2
-rw-r--r--src/feature/nodelist/networkstatus.c15
-rw-r--r--src/feature/nodelist/node_select.h2
-rw-r--r--src/feature/nodelist/nodelist.c11
-rw-r--r--src/feature/nodelist/nodelist.h1
-rw-r--r--src/feature/nodelist/routerlist.c41
-rw-r--r--src/feature/nodelist/routerstatus_st.h1
-rw-r--r--src/feature/nodelist/vote_routerstatus_st.h1
-rw-r--r--src/feature/relay/dns.c10
-rw-r--r--src/feature/relay/onion_queue.c2
-rw-r--r--src/feature/relay/relay_config.c21
-rw-r--r--src/feature/relay/relay_find_addr.c18
-rw-r--r--src/feature/relay/relay_metrics.c379
-rw-r--r--src/feature/relay/relay_metrics.h60
-rw-r--r--src/feature/relay/relay_periodic.c4
-rw-r--r--src/feature/relay/relay_stub.c25
-rw-r--r--src/feature/relay/router.c13
-rw-r--r--src/feature/relay/router.h2
-rw-r--r--src/feature/relay/routerkeys.h1
-rw-r--r--src/feature/rend/rendmid.c13
-rw-r--r--src/feature/stats/geoip_stats.h2
-rw-r--r--src/feature/stats/rephist.c4
65 files changed, 3221 insertions, 396 deletions
diff --git a/src/feature/client/bridges.c b/src/feature/client/bridges.c
index 9e36d26929..a0375828a7 100644
--- a/src/feature/client/bridges.c
+++ b/src/feature/client/bridges.c
@@ -140,6 +140,41 @@ bridge_list_get(void)
}
/**
+ * Returns true if there are enough bridges to make a conflux set
+ * without re-using the same bridge.
+ */
+bool
+conflux_can_exclude_used_bridges(void)
+{
+ if (smartlist_len(bridge_list_get()) == 1) {
+ static bool warned_once = false;
+ bridge_info_t *bridge = smartlist_get(bridge_list_get(), 0);
+ tor_assert(bridge);
+
+ /* Snowflake is a special case. With one snowflake bridge,
+ * you are load balanced among many back-end bridges.
+ * So we do not need to warn the user for it. */
+ if (bridge->transport_name &&
+ strcasecmp(bridge->transport_name, "snowflake") == 0) {
+ return false;
+ }
+
+ if (!warned_once) {
+ log_warn(LD_CIRC, "Only one bridge (transport: '%s') is configured. "
+ "You should have at least two for conflux, "
+ "for any transport that is not 'snowflake'.",
+ bridge->transport_name ?
+ bridge->transport_name : "vanilla");
+ warned_once = true;
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+/**
* Given a <b>bridge</b>, return a pointer to its RSA identity digest, or
* NULL if we don't know one for it.
*/
diff --git a/src/feature/client/bridges.h b/src/feature/client/bridges.h
index dd3e498a0a..2b514ba6c9 100644
--- a/src/feature/client/bridges.h
+++ b/src/feature/client/bridges.h
@@ -67,6 +67,7 @@ MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id,
(const char *digest));
void bridges_free_all(void);
+bool conflux_can_exclude_used_bridges(void);
#ifdef TOR_BRIDGES_PRIVATE
STATIC void clear_bridge_list(void);
diff --git a/src/feature/client/circpathbias.c b/src/feature/client/circpathbias.c
index ff9e05a645..144a53c972 100644
--- a/src/feature/client/circpathbias.c
+++ b/src/feature/client/circpathbias.c
@@ -334,12 +334,23 @@ pathbias_should_count(origin_circuit_t *circ)
* endpoint could be chosen maliciously.
* Similarly, we can't count client-side intro attempts,
* because clients can be manipulated into connecting to
- * malicious intro points. */
+ * malicious intro points.
+ *
+ * Finally, avoid counting conflux circuits for now, because
+ * a malicious exit could cause us to reconnect and blame
+ * our guard...
+ *
+ * TODO-329-PURPOSE: This is not quite right, we could
+ * instead avoid sending usable probes on conflux circs,
+ * and count only linked circs as failures, but it is
+ * not 100% clear that would result in accurate counts. */
if (get_options()->UseEntryGuards == 0 ||
circ->base_.purpose == CIRCUIT_PURPOSE_TESTING ||
circ->base_.purpose == CIRCUIT_PURPOSE_CONTROLLER ||
circ->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND ||
circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED ||
+ circ->base_.purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED ||
+ circ->base_.purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED ||
(circ->base_.purpose >= CIRCUIT_PURPOSE_C_INTRODUCING &&
circ->base_.purpose <= CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) {
diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c
index 5436b74b9c..e5c89645f6 100644
--- a/src/feature/client/entrynodes.c
+++ b/src/feature/client/entrynodes.c
@@ -126,6 +126,7 @@
#include "core/or/circuitlist.h"
#include "core/or/circuitstats.h"
#include "core/or/circuituse.h"
+#include "core/or/conflux_pool.h"
#include "core/or/policies.h"
#include "feature/client/bridges.h"
#include "feature/client/circpathbias.h"
@@ -152,6 +153,8 @@
#include "app/config/or_state_st.h"
#include "src/feature/nodelist/routerstatus_st.h"
+#include "core/or/conflux_util.h"
+
/** A list of existing guard selection contexts. */
static smartlist_t *guard_contexts = NULL;
/** The currently enabled guard selection context. */
@@ -611,7 +614,7 @@ mark_primary_guards_maybe_reachable(guard_selection_t *gs)
}
/* Called when we exhaust all guards in our sampled set: Marks all guards as
- maybe-reachable so that we 'll try them again. */
+ maybe-reachable so that we'll try them again. */
static void
mark_all_guards_maybe_reachable(guard_selection_t *gs)
{
@@ -1059,7 +1062,7 @@ get_max_sample_size(guard_selection_t *gs,
}
/**
- * Return a smartlist of the all the guards that are not currently
+ * Return a smartlist of all the guards that are not currently
* members of the sample (GUARDS - SAMPLED_GUARDS). The elements of
* this list are node_t pointers in the non-bridge case, and
* bridge_info_t pointers in the bridge case. Set *<b>n_guards_out</b>
@@ -1589,6 +1592,19 @@ guard_create_exit_restriction(const uint8_t *exit_id)
return rst;
}
+/* Allocate and return a new exit guard restriction that excludes all current
+ * and pending conflux guards */
+STATIC entry_guard_restriction_t *
+guard_create_conflux_restriction(const origin_circuit_t *circ)
+{
+ entry_guard_restriction_t *rst = NULL;
+ rst = tor_malloc_zero(sizeof(entry_guard_restriction_t));
+ rst->type = RST_EXCL_LIST;
+ rst->excluded = smartlist_new();
+ conflux_add_guards_to_exclude_list(circ, rst->excluded);
+ return rst;
+}
+
/** If we have fewer than this many possible usable guards, don't set
* MD-availability-based restrictions: we might denylist all of them. */
#define MIN_GUARDS_FOR_MD_RESTRICTION 10
@@ -1681,6 +1697,8 @@ entry_guard_obeys_restriction(const entry_guard_t *guard,
return guard_obeys_exit_restriction(guard, rst);
} else if (rst->type == RST_OUTDATED_MD_DIRSERVER) {
return guard_obeys_md_dirserver_restriction(guard);
+ } else if (rst->type == RST_EXCL_LIST) {
+ return !smartlist_contains_digest(rst->excluded, guard->identity);
}
tor_assert_nonfatal_unreached();
@@ -1896,7 +1914,7 @@ make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard)
guard->confirmed_idx = gs->next_confirmed_idx++;
smartlist_add(gs->confirmed_entry_guards, guard);
- /** The confirmation ordering might not be the sample ording. We need to
+ /** The confirmation ordering might not be the sample ordering. We need to
* reorder */
smartlist_sort(gs->confirmed_entry_guards, compare_guards_by_sampled_idx);
@@ -2428,6 +2446,11 @@ entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b)
STATIC void
entry_guard_restriction_free_(entry_guard_restriction_t *rst)
{
+ if (rst && rst->excluded) {
+ SMARTLIST_FOREACH(rst->excluded, void *, g,
+ tor_free(g));
+ smartlist_free(rst->excluded);
+ }
tor_free(rst);
}
@@ -3781,7 +3804,8 @@ guards_update_all(void)
/** Helper: pick a guard for a circuit, with whatever algorithm is
used. */
const node_t *
-guards_choose_guard(cpath_build_state_t *state,
+guards_choose_guard(const origin_circuit_t *circ,
+ cpath_build_state_t *state,
uint8_t purpose,
circuit_guard_state_t **guard_state_out)
{
@@ -3789,14 +3813,18 @@ guards_choose_guard(cpath_build_state_t *state,
const uint8_t *exit_id = NULL;
entry_guard_restriction_t *rst = NULL;
- /* Only apply restrictions if we have a specific exit node in mind, and only
- * if we are not doing vanguard circuits: we don't want to apply guard
- * restrictions to vanguard circuits. */
- if (state && !circuit_should_use_vanguards(purpose) &&
+ /* If we this is a conflux circuit, build an exclusion list for it. */
+ if (CIRCUIT_IS_CONFLUX(TO_CIRCUIT(circ))) {
+ rst = guard_create_conflux_restriction(circ);
+ /* Don't allow connecting back to the exit if there is one */
+ if (state && (exit_id = build_state_get_exit_rsa_id(state))) {
+ /* add the exit_id to the excluded list */
+ smartlist_add(rst->excluded, tor_memdup(exit_id, DIGEST_LEN));
+ }
+ } else if (state && !circuit_should_use_vanguards(purpose) &&
(exit_id = build_state_get_exit_rsa_id(state))) {
/* We're building to a targeted exit node, so that node can't be
- * chosen as our guard for this circuit. Remember that fact in a
- * restriction. */
+ * chosen as our guard for this circuit, unless we're vanguards. */
rst = guard_create_exit_restriction(exit_id);
tor_assert(rst);
}
diff --git a/src/feature/client/entrynodes.h b/src/feature/client/entrynodes.h
index 08fd7cf745..2a94775430 100644
--- a/src/feature/client/entrynodes.h
+++ b/src/feature/client/entrynodes.h
@@ -294,7 +294,9 @@ typedef enum guard_restriction_type_t {
/* Don't pick the same guard node as our exit node (or its family) */
RST_EXIT_NODE = 0,
/* Don't pick dirguards that have previously shown to be outdated */
- RST_OUTDATED_MD_DIRSERVER = 1
+ RST_OUTDATED_MD_DIRSERVER = 1,
+ /* Don't pick guards if they are in the exclusion list */
+ RST_EXCL_LIST = 2,
} guard_restriction_type_t;
/**
@@ -312,6 +314,10 @@ struct entry_guard_restriction_t {
* digest must not equal this; and it must not be in the same family as any
* node with this digest. */
uint8_t exclude_id[DIGEST_LEN];
+
+ /* In the case of RST_EXCL_LIST, any identity digests in this list
+ * must not be used. */
+ smartlist_t *excluded;
};
/**
@@ -337,7 +343,8 @@ struct circuit_guard_state_t {
/* Common entry points for old and new guard code */
int guards_update_all(void);
-const node_t *guards_choose_guard(cpath_build_state_t *state,
+const node_t *guards_choose_guard(const origin_circuit_t *circ,
+ cpath_build_state_t *state,
uint8_t purpose,
circuit_guard_state_t **guard_state_out);
const node_t *guards_choose_dirguard(uint8_t dir_purpose,
@@ -597,6 +604,9 @@ STATIC entry_guard_restriction_t *guard_create_exit_restriction(
STATIC entry_guard_restriction_t *guard_create_dirserver_md_restriction(void);
+STATIC entry_guard_restriction_t * guard_create_conflux_restriction(
+ const origin_circuit_t *circ);
+
STATIC void entry_guard_restriction_free_(entry_guard_restriction_t *rst);
#define entry_guard_restriction_free(rst) \
FREE_AND_NULL(entry_guard_restriction_t, \
diff --git a/src/feature/client/transports.c b/src/feature/client/transports.c
index 80903ac9e5..4d92a2a67a 100644
--- a/src/feature/client/transports.c
+++ b/src/feature/client/transports.c
@@ -519,8 +519,10 @@ proxy_prepare_for_restart(managed_proxy_t *mp)
tor_assert(mp->conf_state == PT_PROTO_COMPLETED);
/* destroy the process handle and terminate the process. */
- process_set_data(mp->process, NULL);
- process_terminate(mp->process);
+ if (mp->process) {
+ process_set_data(mp->process, NULL);
+ process_terminate(mp->process);
+ }
/* destroy all its registered transports, since we will no longer
use them. */
@@ -541,7 +543,7 @@ proxy_prepare_for_restart(managed_proxy_t *mp)
mp->proxy_supported = 0;
/* flag it as an infant proxy so that it gets launched on next tick */
- mp->conf_state = PT_PROTO_INFANT;
+ managed_proxy_set_state(mp, PT_PROTO_INFANT);
unconfigured_proxies_n++;
}
@@ -554,6 +556,8 @@ launch_managed_proxy(managed_proxy_t *mp)
smartlist_t *env = create_managed_proxy_environment(mp);
/* Configure our process. */
+ tor_assert(mp->process == NULL);
+ mp->process = process_new(mp->argv[0]);
process_set_data(mp->process, mp);
process_set_stdout_read_callback(mp->process, managed_proxy_stdout_callback);
process_set_stderr_read_callback(mp->process, managed_proxy_stderr_callback);
@@ -578,7 +582,7 @@ launch_managed_proxy(managed_proxy_t *mp)
log_info(LD_CONFIG,
"Managed proxy at '%s' has spawned with PID '%" PRIu64 "'.",
mp->argv[0], process_get_pid(mp->process));
- mp->conf_state = PT_PROTO_LAUNCHED;
+ managed_proxy_set_state(mp, PT_PROTO_LAUNCHED);
return 0;
}
@@ -648,7 +652,7 @@ configure_proxy(managed_proxy_t *mp)
/* if we haven't launched the proxy yet, do it now */
if (mp->conf_state == PT_PROTO_INFANT) {
if (launch_managed_proxy(mp) < 0) { /* launch fail */
- mp->conf_state = PT_PROTO_FAILED_LAUNCH;
+ managed_proxy_set_state(mp, PT_PROTO_FAILED_LAUNCH);
handle_finished_proxy(mp);
}
return 0;
@@ -810,8 +814,12 @@ handle_finished_proxy(managed_proxy_t *mp)
managed_proxy_destroy(mp, 1); /* annihilate it. */
break;
}
- register_proxy(mp); /* register its transports */
- mp->conf_state = PT_PROTO_COMPLETED; /* and mark it as completed. */
+
+ /* register its transports */
+ register_proxy(mp);
+
+ /* and mark it as completed. */
+ managed_proxy_set_state(mp, PT_PROTO_COMPLETED);
break;
case PT_PROTO_INFANT:
case PT_PROTO_LAUNCHED:
@@ -857,7 +865,7 @@ handle_methods_done(const managed_proxy_t *mp)
STATIC void
handle_proxy_line(const char *line, managed_proxy_t *mp)
{
- log_info(LD_GENERAL, "Got a line from managed proxy '%s': (%s)",
+ log_info(LD_PT, "Got a line from managed proxy '%s': (%s)",
mp->argv[0], line);
if (!strcmpstart(line, PROTO_ENV_ERROR)) {
@@ -881,7 +889,7 @@ handle_proxy_line(const char *line, managed_proxy_t *mp)
goto err;
tor_assert(mp->conf_protocol != 0);
- mp->conf_state = PT_PROTO_ACCEPTING_METHODS;
+ managed_proxy_set_state(mp, PT_PROTO_ACCEPTING_METHODS);
return;
} else if (!strcmpstart(line, PROTO_CMETHODS_DONE)) {
if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS)
@@ -889,7 +897,7 @@ handle_proxy_line(const char *line, managed_proxy_t *mp)
handle_methods_done(mp);
- mp->conf_state = PT_PROTO_CONFIGURED;
+ managed_proxy_set_state(mp, PT_PROTO_CONFIGURED);
return;
} else if (!strcmpstart(line, PROTO_SMETHODS_DONE)) {
if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS)
@@ -897,7 +905,7 @@ handle_proxy_line(const char *line, managed_proxy_t *mp)
handle_methods_done(mp);
- mp->conf_state = PT_PROTO_CONFIGURED;
+ managed_proxy_set_state(mp, PT_PROTO_CONFIGURED);
return;
} else if (!strcmpstart(line, PROTO_CMETHOD_ERROR)) {
if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS)
@@ -968,7 +976,7 @@ handle_proxy_line(const char *line, managed_proxy_t *mp)
return;
err:
- mp->conf_state = PT_PROTO_BROKEN;
+ managed_proxy_set_state(mp, PT_PROTO_BROKEN);
log_warn(LD_CONFIG, "Managed proxy at '%s' failed the configuration protocol"
" and will be destroyed.", mp->argv[0]);
}
@@ -1529,12 +1537,14 @@ managed_proxy_create(const smartlist_t *with_transport_list,
char **proxy_argv, int is_server)
{
managed_proxy_t *mp = tor_malloc_zero(sizeof(managed_proxy_t));
- mp->conf_state = PT_PROTO_INFANT;
+ managed_proxy_set_state(mp, PT_PROTO_INFANT);
mp->is_server = is_server;
mp->argv = proxy_argv;
mp->transports = smartlist_new();
mp->proxy_uri = get_pt_proxy_uri();
- mp->process = process_new(proxy_argv[0]);
+
+ /* Gets set in launch_managed_proxy(). */
+ mp->process = NULL;
mp->transports_to_launch = smartlist_new();
SMARTLIST_FOREACH(with_transport_list, const char *, transport,
@@ -1945,9 +1955,24 @@ managed_proxy_exit_callback(process_t *process, process_exit_code_t exit_code)
{
tor_assert(process);
+ managed_proxy_t *mp = process_get_data(process);
+ const char *name = mp ? mp->argv[0] : "N/A";
+
log_warn(LD_PT,
- "Pluggable Transport process terminated with status code %" PRIu64,
- exit_code);
+ "Managed proxy \"%s\" process terminated with status code %" PRIu64,
+ name, exit_code);
+
+ if (mp) {
+ /* We remove this process_t from the mp. */
+ tor_assert(mp->process == process);
+ mp->process = NULL;
+
+ /* Prepare the proxy for restart. */
+ proxy_prepare_for_restart(mp);
+
+ /* We have proxies we want to restart? */
+ pt_configure_remaining_proxies();
+ }
/* Returning true here means that the process subsystem will take care of
* calling process_free() on our process_t. */
@@ -2023,3 +2048,45 @@ managed_proxy_outbound_address(const or_options_t *options, sa_family_t family)
/* The user have not specified a preference for outgoing connections. */
return NULL;
}
+
+STATIC const char *
+managed_proxy_state_to_string(enum pt_proto_state state)
+{
+ switch (state) {
+ case PT_PROTO_INFANT:
+ return "Infant";
+ case PT_PROTO_LAUNCHED:
+ return "Launched";
+ case PT_PROTO_ACCEPTING_METHODS:
+ return "Accepting methods";
+ case PT_PROTO_CONFIGURED:
+ return "Configured";
+ case PT_PROTO_COMPLETED:
+ return "Completed";
+ case PT_PROTO_BROKEN:
+ return "Broken";
+ case PT_PROTO_FAILED_LAUNCH:
+ return "Failed to launch";
+ }
+
+ /* LCOV_EXCL_START */
+ tor_assert_unreached();
+ return NULL;
+ /* LCOV_EXCL_STOP */
+}
+
+/** Set the internal state of the given <b>mp</b> to the given <b>new_state</b>
+ * value. */
+STATIC void
+managed_proxy_set_state(managed_proxy_t *mp, enum pt_proto_state new_state)
+{
+ if (mp->conf_state == new_state)
+ return;
+
+ tor_log(LOG_INFO, LD_PT, "Managed proxy \"%s\" changed state: %s -> %s",
+ mp->argv[0],
+ managed_proxy_state_to_string(mp->conf_state),
+ managed_proxy_state_to_string(new_state));
+
+ mp->conf_state = new_state;
+}
diff --git a/src/feature/client/transports.h b/src/feature/client/transports.h
index 3f08beadba..535689537c 100644
--- a/src/feature/client/transports.h
+++ b/src/feature/client/transports.h
@@ -153,6 +153,8 @@ STATIC int managed_proxy_severity_parse(const char *);
STATIC const tor_addr_t *managed_proxy_outbound_address(const or_options_t *,
sa_family_t);
+STATIC const char *managed_proxy_state_to_string(enum pt_proto_state);
+STATIC void managed_proxy_set_state(managed_proxy_t *, enum pt_proto_state);
#endif /* defined(PT_PRIVATE) */
#endif /* !defined(TOR_TRANSPORTS_H) */
diff --git a/src/feature/control/control_fmt.c b/src/feature/control/control_fmt.c
index cc8686818a..b6efd18163 100644
--- a/src/feature/control/control_fmt.c
+++ b/src/feature/control/control_fmt.c
@@ -153,6 +153,13 @@ circuit_describe_status_for_controller(origin_circuit_t *circ)
tor_free(socks_password_escaped);
}
+ /* Attach the proof-of-work solution effort, if it's nonzero. Clients set
+ * this to the effort they've chosen, services set this to a value that
+ * was provided by the client and then verified by the service. */
+ if (circ->hs_pow_effort > 0) {
+ smartlist_add_asprintf(descparts, "HS_POW=v1,%u", circ->hs_pow_effort);
+ }
+
rv = smartlist_join_strings(descparts, " ", 0, NULL);
SMARTLIST_FOREACH(descparts, char *, cp, tor_free(cp));
diff --git a/src/feature/control/getinfo_geoip.c b/src/feature/control/getinfo_geoip.c
index be89c2c641..e2d277f256 100644
--- a/src/feature/control/getinfo_geoip.c
+++ b/src/feature/control/getinfo_geoip.c
@@ -44,10 +44,7 @@ getinfo_helper_geoip(control_connection_t *control_conn,
*errmsg = "GeoIP data not loaded";
return -1;
}
- if (family == AF_INET)
- c = geoip_get_country_by_ipv4(tor_addr_to_ipv4h(&addr));
- else /* AF_INET6 */
- c = geoip_get_country_by_ipv6(tor_addr_to_in6(&addr));
+ c = geoip_get_country_by_addr(&addr);
*answer = tor_strdup(geoip_get_country_name(c));
}
return 0;
diff --git a/src/feature/dirauth/dirauth_options.inc b/src/feature/dirauth/dirauth_options.inc
index a43ed285ce..e2056c9cc7 100644
--- a/src/feature/dirauth/dirauth_options.inc
+++ b/src/feature/dirauth/dirauth_options.inc
@@ -86,11 +86,12 @@ CONF_VAR(AuthDirVoteGuard, ROUTERSET, 0, NULL)
CONF_VAR(AuthDirVoteStableGuaranteeMinUptime, INTERVAL, 0, "30 days")
/** If a relay's MTBF is at least this value, then it is always stable. See
- * above. */
+ * above. (Corresponds to about 7 days for current decay rates.) */
CONF_VAR(AuthDirVoteStableGuaranteeMTBF, INTERVAL, 0, "5 days")
/** A relay with at least this much weighted time known can be considered
- * familiar enough to be a guard. */
+ * familiar enough to be a guard. (Corresponds to about 20 days for current
+ * decay rates.) */
CONF_VAR(AuthDirVoteGuardGuaranteeTimeKnown, INTERVAL, 0, "8 days")
/** A relay with sufficient WFU is around enough to be a guard. */
diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c
index 0591125d51..77148c8725 100644
--- a/src/feature/dirauth/dirvote.c
+++ b/src/feature/dirauth/dirvote.c
@@ -390,7 +390,8 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
rsf = routerstatus_format_entry(&vrs->status,
vrs->version, vrs->protocols,
NS_V3_VOTE,
- vrs);
+ vrs,
+ -1);
if (rsf)
smartlist_add(chunks, rsf);
@@ -618,8 +619,8 @@ compare_vote_rs(const vote_routerstatus_t *a, const vote_routerstatus_t *b)
* the descriptor digests matched, so somebody is making SHA1 collisions.
*/
#define CMP_FIELD(utype, itype, field) do { \
- utype aval = (utype) (itype) a->status.field; \
- utype bval = (utype) (itype) b->status.field; \
+ utype aval = (utype) (itype) a->field; \
+ utype bval = (utype) (itype) b->field; \
utype u = bval - aval; \
itype r2 = (itype) u; \
if (r2 < 0) { \
@@ -638,8 +639,8 @@ compare_vote_rs(const vote_routerstatus_t *a, const vote_routerstatus_t *b)
CMP_EXACT))) {
return r;
}
- CMP_FIELD(unsigned, int, ipv4_orport);
- CMP_FIELD(unsigned, int, ipv4_dirport);
+ CMP_FIELD(unsigned, int, status.ipv4_orport);
+ CMP_FIELD(unsigned, int, status.ipv4_dirport);
return 0;
}
@@ -692,10 +693,10 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method,
} else {
if (cur && (cur_n > most_n ||
(cur_n == most_n &&
- cur->status.published_on > most_published))) {
+ cur->published_on > most_published))) {
most = cur;
most_n = cur_n;
- most_published = cur->status.published_on;
+ most_published = cur->published_on;
}
cur_n = 1;
cur = rs;
@@ -703,7 +704,7 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method,
} SMARTLIST_FOREACH_END(rs);
if (cur_n > most_n ||
- (cur && cur_n == most_n && cur->status.published_on > most_published)) {
+ (cur && cur_n == most_n && cur->published_on > most_published)) {
most = cur;
// most_n = cur_n; // unused after this point.
// most_published = cur->status.published_on; // unused after this point.
@@ -2047,7 +2048,6 @@ networkstatus_compute_consensus(smartlist_t *votes,
memcpy(rs_out.descriptor_digest, rs->status.descriptor_digest,
DIGEST_LEN);
tor_addr_copy(&rs_out.ipv4_addr, &rs->status.ipv4_addr);
- rs_out.published_on = rs->status.published_on;
rs_out.ipv4_dirport = rs->status.ipv4_dirport;
rs_out.ipv4_orport = rs->status.ipv4_orport;
tor_addr_copy(&rs_out.ipv6_addr, &alt_orport.addr);
@@ -2055,6 +2055,21 @@ networkstatus_compute_consensus(smartlist_t *votes,
rs_out.has_bandwidth = 0;
rs_out.has_exitsummary = 0;
+ time_t published_on = rs->published_on;
+
+ /* Starting with this consensus method, we no longer include a
+ meaningful published_on time for microdescriptor consensuses. This
+ makes their diffs smaller and more compressible.
+
+ We need to keep including a meaningful published_on time for NS
+ consensuses, however, until 035 relays are all obsolete. (They use
+ it for a purpose similar to the current StaleDesc flag.)
+ */
+ if (consensus_method >= MIN_METHOD_TO_SUPPRESS_MD_PUBLISHED &&
+ flavor == FLAV_MICRODESC) {
+ published_on = -1;
+ }
+
if (chosen_name && !naming_conflict) {
strlcpy(rs_out.nickname, chosen_name, sizeof(rs_out.nickname));
} else {
@@ -2276,7 +2291,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
/* Okay!! Now we can write the descriptor... */
/* First line goes into "buf". */
buf = routerstatus_format_entry(&rs_out, NULL, NULL,
- rs_format, NULL);
+ rs_format, NULL, published_on);
if (buf)
smartlist_add(chunks, buf);
}
@@ -4744,6 +4759,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
dirauth_set_routerstatus_from_routerinfo(rs, node, ri, now,
list_bad_exits,
list_middle_only);
+ vrs->published_on = ri->cache_info.published_on;
if (ri->cache_info.signing_key_cert) {
memcpy(vrs->ed25519_id,
diff --git a/src/feature/dirauth/dirvote.h b/src/feature/dirauth/dirvote.h
index 64aaec116e..ae8d43a6f0 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 32
+#define MAX_SUPPORTED_CONSENSUS_METHOD 33
/**
* Lowest consensus method where microdescriptor lines are put in canonical
@@ -74,6 +74,12 @@
*/
#define MIN_METHOD_FOR_MIDDLEONLY 32
+/**
+ * Lowest consensus method for which we suppress the published time in
+ * microdescriptor consensuses.
+ */
+#define MIN_METHOD_TO_SUPPRESS_MD_PUBLISHED 33
+
/** 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 f1d4f49c46..7fd930e246 100644
--- a/src/feature/dirauth/process_descs.c
+++ b/src/feature/dirauth/process_descs.c
@@ -110,7 +110,7 @@ add_rsa_fingerprint_to_dir(const char *fp, authdir_config_t *list,
tor_strstrip(fingerprint, " ");
if (base16_decode(d, DIGEST_LEN,
fingerprint, strlen(fingerprint)) != DIGEST_LEN) {
- log_warn(LD_DIRSERV, "Couldn't decode fingerprint \"%s\"",
+ log_warn(LD_DIRSERV, "Couldn't decode fingerprint %s",
escaped(fp));
tor_free(fingerprint);
return -1;
@@ -404,17 +404,8 @@ dirserv_rejects_tor_version(const char *platform,
static const char please_upgrade_string[] =
"Tor version is insecure or unsupported. Please upgrade!";
- /* Anything before 0.4.5.6 is unsupported. Reject them. */
- if (!tor_version_as_new_as(platform,"0.4.5.6")) {
- if (msg) {
- *msg = please_upgrade_string;
- }
- return true;
- }
-
- /* Reject 0.4.6.x series. */
- if (tor_version_as_new_as(platform, "0.4.6.0") &&
- !tor_version_as_new_as(platform, "0.4.7.0-alpha-dev")) {
+ /* Anything before 0.4.7.0 is unsupported. Reject them. */
+ if (!tor_version_as_new_as(platform,"0.4.7.0-alpha-dev")) {
if (msg) {
*msg = please_upgrade_string;
}
diff --git a/src/feature/dirclient/dirclient.c b/src/feature/dirclient/dirclient.c
index 4e9c8e2f45..84eefdd90b 100644
--- a/src/feature/dirclient/dirclient.c
+++ b/src/feature/dirclient/dirclient.c
@@ -242,14 +242,21 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
* harmless, and we may as well err on the side of getting things uploaded.
*/
SMARTLIST_FOREACH_BEGIN(dirservers, dir_server_t *, ds) {
- routerstatus_t *rs = &(ds->fake_status);
+ const routerstatus_t *rs = router_get_consensus_status_by_id(ds->digest);
+ if (!rs) {
+ /* prefer to use the address in the consensus, but fall back to
+ * the hard-coded trusted_dir_server address if we don't have a
+ * consensus or this digest isn't in our consensus. */
+ rs = &ds->fake_status;
+ }
+
size_t upload_len = payload_len;
if ((type & ds->type) == 0)
continue;
if (exclude_self && router_digest_is_me(ds->digest)) {
- /* we don't upload to ourselves, but at least there's now at least
+ /* we don't upload to ourselves, but there's now at least
* one authority of this type that has what we wanted to upload. */
found = 1;
continue;
@@ -276,10 +283,8 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
}
if (purpose_needs_anonymity(dir_purpose, router_purpose, NULL)) {
indirection = DIRIND_ANONYMOUS;
- } else if (!reachable_addr_allows_dir_server(ds,
- FIREWALL_DIR_CONNECTION,
- 0)) {
- if (reachable_addr_allows_dir_server(ds, FIREWALL_OR_CONNECTION, 0))
+ } else if (!reachable_addr_allows_rs(rs, FIREWALL_DIR_CONNECTION, 0)) {
+ if (reachable_addr_allows_rs(rs, FIREWALL_OR_CONNECTION, 0))
indirection = DIRIND_ONEHOP;
else
indirection = DIRIND_ANONYMOUS;
@@ -590,7 +595,13 @@ directory_get_from_all_authorities(uint8_t dir_purpose,
continue;
if (!(ds->type & V3_DIRINFO))
continue;
- const routerstatus_t *rs = &ds->fake_status;
+ const routerstatus_t *rs = router_get_consensus_status_by_id(ds->digest);
+ if (!rs) {
+ /* prefer to use the address in the consensus, but fall back to
+ * the hard-coded trusted_dir_server address if we don't have a
+ * consensus or this digest isn't in our consensus. */
+ rs = &ds->fake_status;
+ }
directory_request_t *req = directory_request_new(dir_purpose);
directory_request_set_routerstatus(req, rs);
directory_request_set_router_purpose(req, router_purpose);
diff --git a/src/feature/dirparse/ns_parse.c b/src/feature/dirparse/ns_parse.c
index cd3e2731be..3e1f9a3bd3 100644
--- a/src/feature/dirparse/ns_parse.c
+++ b/src/feature/dirparse/ns_parse.c
@@ -371,14 +371,17 @@ routerstatus_parse_entry_from_string(memarea_t *area,
}
}
+ time_t published_on;
if (tor_snprintf(timebuf, sizeof(timebuf), "%s %s",
tok->args[3+offset], tok->args[4+offset]) < 0 ||
- parse_iso_time(timebuf, &rs->published_on)<0) {
+ parse_iso_time(timebuf, &published_on)<0) {
log_warn(LD_DIR, "Error parsing time '%s %s' [%d %d]",
tok->args[3+offset], tok->args[4+offset],
offset, (int)flav);
goto err;
}
+ if (vote_rs)
+ vote_rs->published_on = published_on;
if (tor_inet_aton(tok->args[5+offset], &in) == 0) {
log_warn(LD_DIR, "Error parsing router address in network-status %s",
diff --git a/src/feature/dirparse/parsecommon.h b/src/feature/dirparse/parsecommon.h
index 675c5f68d5..9333ec4b27 100644
--- a/src/feature/dirparse/parsecommon.h
+++ b/src/feature/dirparse/parsecommon.h
@@ -173,6 +173,7 @@ typedef enum {
R3_DESC_AUTH_CLIENT,
R3_ENCRYPTED,
R3_FLOW_CONTROL,
+ R3_POW_PARAMS,
R_IPO_IDENTIFIER,
R_IPO_IP_ADDRESS,
diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c
index cf8e377313..0cc7dfd031 100644
--- a/src/feature/hs/hs_cache.c
+++ b/src/feature/hs/hs_cache.c
@@ -581,6 +581,8 @@ cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk,
tor_assert(service_pk);
tor_assert(auth_key);
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(service_pk));
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(auth_key));
/* Lookup the intro state cache for this service key. */
cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
@@ -1081,7 +1083,7 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
*
* 1) Deallocate all entries from v3 cache that are older than K hours
* 2.1) If the amount of remove bytes has been reached, stop.
- * 2) Set K = K - RendPostPeriod and repeat process until K is < 0.
+ * 2) Set K = K - 1 hour and repeat process until K is < 0.
*
* This ends up being O(Kn).
*/
@@ -1104,8 +1106,9 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
if (bytes_removed < min_remove_bytes) {
/* We haven't remove enough bytes so clean v3 cache. */
bytes_removed += cache_clean_v3_as_dir(now, cutoff);
- /* Decrement K by a post period to shorten the cutoff. */
- k -= get_options()->RendPostPeriod;
+ /* Decrement K by a post period to shorten the cutoff, Two minutes
+ * if we are a testing network, or one hour otherwise. */
+ k -= get_options()->TestingTorNetwork ? 120 : 3600;
}
} while (bytes_removed < min_remove_bytes);
diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c
index 490f05e54f..0039825f3c 100644
--- a/src/feature/hs/hs_cell.c
+++ b/src/feature/hs/hs_cell.c
@@ -374,6 +374,80 @@ introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell,
tor_free(encrypted);
}
+/** Build the PoW cell extension and put it in the given extensions object.
+ * Return 0 on success, -1 on failure. */
+static int
+build_introduce_pow_extension(const hs_pow_solution_t *pow_solution,
+ trn_extension_t *extensions)
+{
+ ssize_t ret;
+ size_t pow_ext_encoded_len;
+ uint8_t *field_array;
+ trn_extension_field_t *field = NULL;
+ trn_cell_extension_pow_t *pow_ext = NULL;
+
+ tor_assert(pow_solution);
+ tor_assert(extensions);
+
+ /* We are creating a cell extension field of type PoW solution. */
+ field = trn_extension_field_new();
+ trn_extension_field_set_field_type(field, TRUNNEL_EXT_TYPE_POW);
+
+ /* Build PoW extension field. */
+ pow_ext = trn_cell_extension_pow_new();
+
+ /* Copy PoW solution values into PoW extension cell. */
+
+ /* Equi-X base scheme */
+ trn_cell_extension_pow_set_pow_version(pow_ext, TRUNNEL_POW_VERSION_EQUIX);
+
+ memcpy(trn_cell_extension_pow_getarray_pow_nonce(pow_ext),
+ &pow_solution->nonce, TRUNNEL_POW_NONCE_LEN);
+
+ trn_cell_extension_pow_set_pow_effort(pow_ext, pow_solution->effort);
+
+ memcpy(trn_cell_extension_pow_getarray_pow_seed(pow_ext),
+ pow_solution->seed_head, TRUNNEL_POW_SEED_HEAD_LEN);
+ memcpy(trn_cell_extension_pow_getarray_pow_solution(pow_ext),
+ pow_solution->equix_solution, TRUNNEL_POW_SOLUTION_LEN);
+
+ /* Set the field with the encoded PoW extension. */
+ ret = trn_cell_extension_pow_encoded_len(pow_ext);
+ if (BUG(ret <= 0)) {
+ goto err;
+ }
+ pow_ext_encoded_len = ret;
+
+ /* Set length field and the field array size length. */
+ trn_extension_field_set_field_len(field, pow_ext_encoded_len);
+ trn_extension_field_setlen_field(field, pow_ext_encoded_len);
+ /* Encode the PoW extension into the cell extension field. */
+ field_array = trn_extension_field_getarray_field(field);
+ ret = trn_cell_extension_pow_encode(field_array,
+ trn_extension_field_getlen_field(field), pow_ext);
+ if (BUG(ret <= 0)) {
+ goto err;
+ }
+ tor_assert(ret == (ssize_t)pow_ext_encoded_len);
+
+ /* Finally, encode field into the cell extension. */
+ trn_extension_add_fields(extensions, field);
+
+ /* We've just add an extension field to the cell extensions so increment the
+ * total number. */
+ trn_extension_set_num(extensions, trn_extension_get_num(extensions) + 1);
+
+ /* Cleanup. PoW extension has been encoded at this point. */
+ trn_cell_extension_pow_free(pow_ext);
+
+ return 0;
+
+err:
+ trn_extension_field_free(field);
+ trn_cell_extension_pow_free(pow_ext);
+ return -1;
+}
+
/** Build and set the INTRODUCE congestion control extension in the given
* extensions. */
static void
@@ -384,7 +458,7 @@ build_introduce_cc_extension(trn_extension_t *extensions)
/* Build CC request extension. */
field = trn_extension_field_new();
trn_extension_field_set_field_type(field,
- TRUNNEL_EXT_TYPE_CC_FIELD_REQUEST);
+ TRUNNEL_EXT_TYPE_CC_REQUEST);
/* No payload indicating a request to use congestion control. */
trn_extension_field_set_field_len(field, 0);
@@ -412,10 +486,14 @@ introduce1_set_encrypted(trn_cell_introduce1_t *cell,
/* Setup extension(s) if any. */
ext = trn_extension_new();
tor_assert(ext);
- /* Build congestion control extension is enabled. */
+ /* Build congestion control extension if enabled. */
if (data->cc_enabled) {
build_introduce_cc_extension(ext);
}
+ /* Build PoW extension if present. */
+ if (data->pow_solution) {
+ build_introduce_pow_extension(data->pow_solution, ext);
+ }
trn_cell_introduce_encrypted_set_extensions(enc_cell, ext);
/* Set the rendezvous cookie. */
@@ -716,6 +794,70 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
return ret;
}
+/** Parse the cell PoW solution extension. Return 0 on success and data
+ * structure is updated with the PoW effort. Return -1 on any kind of error
+ * including if PoW couldn't be verified. */
+static int
+handle_introduce2_encrypted_cell_pow_extension(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ const trn_extension_field_t *field,
+ hs_cell_introduce2_data_t *data)
+{
+ int ret = -1;
+ trn_cell_extension_pow_t *pow = NULL;
+ hs_pow_solution_t sol;
+
+ tor_assert(field);
+ tor_assert(ip);
+
+ if (!service->state.pow_state) {
+ log_info(LD_REND, "Unsolicited PoW solution in INTRODUCE2 request.");
+ goto end;
+ }
+
+ if (trn_cell_extension_pow_parse(&pow,
+ trn_extension_field_getconstarray_field(field),
+ trn_extension_field_getlen_field(field)) < 0) {
+ goto end;
+ }
+
+ /* There is only one version supported at the moment so validate we at least
+ * have that. */
+ if (trn_cell_extension_pow_get_pow_version(pow) !=
+ TRUNNEL_POW_VERSION_EQUIX) {
+ log_debug(LD_REND, "Unsupported PoW version. Malformed INTRODUCE2");
+ goto end;
+ }
+
+ /* Effort E */
+ sol.effort = trn_cell_extension_pow_get_pow_effort(pow);
+ /* Seed C */
+ memcpy(sol.seed_head, trn_cell_extension_pow_getconstarray_pow_seed(pow),
+ HS_POW_SEED_HEAD_LEN);
+ /* Nonce N */
+ memcpy(sol.nonce, trn_cell_extension_pow_getconstarray_pow_nonce(pow),
+ HS_POW_NONCE_LEN);
+ /* Solution S */
+ memcpy(sol.equix_solution,
+ trn_cell_extension_pow_getconstarray_pow_solution(pow),
+ HS_POW_EQX_SOL_LEN);
+
+ if (hs_pow_verify(&ip->blinded_id, service->state.pow_state, &sol)) {
+ log_info(LD_REND, "PoW INTRODUCE2 request failed to verify.");
+ goto end;
+ }
+
+ log_info(LD_REND, "PoW INTRODUCE2 request successfully verified.");
+ data->rdv_data.pow_effort = sol.effort;
+
+ /* Successfully parsed and verified the PoW solution */
+ ret = 0;
+
+ end:
+ trn_cell_extension_pow_free(pow);
+ return ret;
+}
+
/** For the encrypted INTRO2 cell in <b>encrypted_section</b>, use the crypto
* material in <b>data</b> to compute the right ntor keys. Also validate the
* INTRO2 MAC to ensure that the keys are the right ones.
@@ -735,7 +877,7 @@ get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data,
data->n_subcredentials,
data->subcredentials,
encrypted_section,
- &data->client_pk);
+ &data->rdv_data.client_pk);
if (intro_keys == NULL) {
log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
"compute key material");
@@ -785,28 +927,42 @@ get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data,
}
/** Parse the given INTRODUCE cell extension. Update the data object
- * accordingly depending on the extension. */
-static void
-parse_introduce_cell_extension(hs_cell_introduce2_data_t *data,
+ * accordingly depending on the extension. Return 0 if it validated
+ * correctly, or return -1 if it is malformed (for example because it
+ * includes a PoW that doesn't verify). */
+static int
+parse_introduce_cell_extension(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ hs_cell_introduce2_data_t *data,
const trn_extension_field_t *field)
{
+ int ret = 0;
trn_extension_field_cc_t *cc_field = NULL;
tor_assert(data);
tor_assert(field);
switch (trn_extension_field_get_field_type(field)) {
- case TRUNNEL_EXT_TYPE_CC_FIELD_REQUEST:
+ case TRUNNEL_EXT_TYPE_CC_REQUEST:
/* CC requests, enable it. */
- data->cc_enabled = 1;
+ data->rdv_data.cc_enabled = 1;
data->pv.protocols_known = 1;
- data->pv.supports_congestion_control = data->cc_enabled;
+ data->pv.supports_congestion_control = data->rdv_data.cc_enabled;
+ break;
+ case TRUNNEL_EXT_TYPE_POW:
+ /* PoW request. If successful, the effort is put in the data. */
+ if (handle_introduce2_encrypted_cell_pow_extension(service, ip,
+ field, data) < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_REND, "Invalid PoW cell extension.");
+ ret = -1;
+ }
break;
default:
break;
}
trn_extension_field_cc_free(cc_field);
+ return ret;
}
/** Parse the INTRODUCE2 cell using data which contains everything we need to
@@ -816,7 +972,8 @@ parse_introduce_cell_extension(hs_cell_introduce2_data_t *data,
ssize_t
hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
const origin_circuit_t *circ,
- const hs_service_t *service)
+ const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
{
int ret = -1;
time_t elapsed;
@@ -867,7 +1024,7 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
* guaranteed to exist because of the length check above). We are gonna use
* the client public key to compute the ntor keys and decrypt the payload:
*/
- memcpy(&data->client_pk.public_key, encrypted_section,
+ memcpy(&data->rdv_data.client_pk.public_key, encrypted_section,
CURVE25519_PUBKEY_LEN);
/* Get the right INTRODUCE2 ntor keys and verify the cell MAC */
@@ -883,12 +1040,13 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
{
/* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */
const uint8_t *encrypted_data =
- encrypted_section + sizeof(data->client_pk);
+ encrypted_section + sizeof(data->rdv_data.client_pk);
/* It's symmetric encryption so it's correct to use the ENCRYPTED length
* for decryption. Computes the length of ENCRYPTED_DATA meaning removing
* the CLIENT_PK and MAC length. */
size_t encrypted_data_len =
- encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN);
+ encrypted_section_len -
+ (sizeof(data->rdv_data.client_pk) + DIGEST256_LEN);
/* This decrypts the ENCRYPTED_DATA section of the cell. */
decrypted = decrypt_introduce2(intro_keys->enc_key,
@@ -915,12 +1073,12 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
/* Extract onion key and rendezvous cookie from the cell used for the
* rendezvous point circuit e2e encryption. */
- memcpy(data->onion_pk.public_key,
+ memcpy(data->rdv_data.onion_pk.public_key,
trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell),
CURVE25519_PUBKEY_LEN);
- memcpy(data->rendezvous_cookie,
+ memcpy(data->rdv_data.rendezvous_cookie,
trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell),
- sizeof(data->rendezvous_cookie));
+ sizeof(data->rdv_data.rendezvous_cookie));
/* Extract rendezvous link specifiers. */
for (size_t idx = 0;
@@ -934,7 +1092,7 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
if (BUG(!lspec_dup)) {
goto done;
}
- smartlist_add(data->link_specifiers, lspec_dup);
+ smartlist_add(data->rdv_data.link_specifiers, lspec_dup);
}
/* Extract any extensions. */
@@ -948,19 +1106,22 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
/* The number of extensions should match the number of fields. */
break;
}
- parse_introduce_cell_extension(data, field);
+ if (parse_introduce_cell_extension(service, ip, data, field) < 0) {
+ goto done;
+ }
}
}
/* If the client asked for congestion control, but we don't support it,
* that's a failure. It should not have asked, based on our descriptor. */
- if (data->cc_enabled && !congestion_control_enabled()) {
+ if (data->rdv_data.cc_enabled && !congestion_control_enabled()) {
goto done;
}
/* Success. */
ret = 0;
- log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit.");
+ log_info(LD_REND,
+ "Valid INTRODUCE2 cell. Willing to launch rendezvous circuit.");
done:
if (intro_keys) {
diff --git a/src/feature/hs/hs_cell.h b/src/feature/hs/hs_cell.h
index c76a0690a8..02631cf376 100644
--- a/src/feature/hs/hs_cell.h
+++ b/src/feature/hs/hs_cell.h
@@ -11,6 +11,7 @@
#include "core/or/or.h"
#include "feature/hs/hs_service.h"
+#include "feature/hs/hs_pow.h"
/** An INTRODUCE1 cell requires at least this amount of bytes (see section
* 3.2.2 of the specification). Below this value, the cell must be padded. */
@@ -42,8 +43,27 @@ typedef struct hs_cell_introduce1_data_t {
smartlist_t *link_specifiers;
/** Congestion control parameters. */
unsigned int cc_enabled : 1;
+ /** PoW solution (Can be NULL if disabled). */
+ const hs_pow_solution_t *pow_solution;
} hs_cell_introduce1_data_t;
+/** Introduction data needed to launch a rendezvous circuit. This is set after
+ * receiving an INTRODUCE2 valid cell. */
+typedef struct hs_cell_intro_rdv_data_t {
+ /** Onion public key computed using the INTRODUCE2 encrypted section. */
+ curve25519_public_key_t onion_pk;
+ /** Rendezvous cookie taken from the INTRODUCE2 encrypted section. */
+ uint8_t rendezvous_cookie[REND_COOKIE_LEN];
+ /** Client public key from the INTRODUCE2 encrypted section. */
+ curve25519_public_key_t client_pk;
+ /** Link specifiers of the rendezvous point. Contains link_specifier_t. */
+ smartlist_t *link_specifiers;
+ /** Congestion control parameters. */
+ unsigned int cc_enabled : 1;
+ /** PoW effort. */
+ uint32_t pow_effort;
+} hs_cell_intro_rdv_data_t;
+
/** This data structure contains data that we need to parse an INTRODUCE2 cell
* which is used by the INTRODUCE2 cell parsing function. On a successful
* parsing, the onion_pk and rendezvous_cookie will be populated with the
@@ -74,20 +94,12 @@ typedef struct hs_cell_introduce2_data_t {
/*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/
- /** Onion public key computed using the INTRODUCE2 encrypted section. */
- curve25519_public_key_t onion_pk;
- /** Rendezvous cookie taken from the INTRODUCE2 encrypted section. */
- uint8_t rendezvous_cookie[REND_COOKIE_LEN];
- /** Client public key from the INTRODUCE2 encrypted section. */
- curve25519_public_key_t client_pk;
- /** Link specifiers of the rendezvous point. Contains link_specifier_t. */
- smartlist_t *link_specifiers;
+ /** Data needed to launch a rendezvous circuit. */
+ hs_cell_intro_rdv_data_t rdv_data;
/** Replay cache of the introduction point. */
replaycache_t *replay_cache;
/** Flow control negotiation parameters. */
protover_summary_flags_t pv;
- /** Congestion control parameters. */
- unsigned int cc_enabled : 1;
} hs_cell_introduce2_data_t;
/* Build cell API. */
@@ -110,7 +122,8 @@ ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
size_t payload_len);
ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
const origin_circuit_t *circ,
- const hs_service_t *service);
+ const hs_service_t *service,
+ const hs_service_intro_point_t *ip);
int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len);
int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
uint8_t *handshake_info,
diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c
index 53855d40a9..4904f3ddf9 100644
--- a/src/feature/hs/hs_circuit.c
+++ b/src/feature/hs/hs_circuit.c
@@ -22,6 +22,7 @@
#include "feature/client/circpathbias.h"
#include "feature/hs/hs_cell.h"
#include "feature/hs/hs_circuit.h"
+#include "feature/hs/hs_common.h"
#include "feature/hs/hs_ob.h"
#include "feature/hs/hs_circuitmap.h"
#include "feature/hs/hs_client.h"
@@ -34,6 +35,7 @@
#include "lib/crypt_ops/crypto_dh.h"
#include "lib/crypt_ops/crypto_rand.h"
#include "lib/crypt_ops/crypto_util.h"
+#include "lib/time/compat_time.h"
/* Trunnel. */
#include "trunnel/ed25519_cert.h"
@@ -45,6 +47,18 @@
#include "feature/nodelist/node_st.h"
#include "core/or/origin_circuit_st.h"
+/** Helper: Free a pending rend object. */
+static inline void
+free_pending_rend(pending_rend_t *req)
+{
+ if (!req) {
+ return;
+ }
+ link_specifier_smartlist_free(req->rdv_data.link_specifiers);
+ memwipe(req, 0, sizeof(pending_rend_t));
+ tor_free(req);
+}
+
/** A circuit is about to become an e2e rendezvous circuit. Check
* <b>circ_purpose</b> and ensure that it's properly set. Return true iff
* circuit purpose is properly set, otherwise return false. */
@@ -238,6 +252,7 @@ create_intro_circuit_identifier(const hs_service_t *service,
ident = hs_ident_circuit_new(&service->keys.identity_pk);
ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey);
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(&ident->intro_auth_pk));
return ident;
}
@@ -310,24 +325,26 @@ get_service_anonymity_string(const hs_service_t *service)
* MAX_REND_FAILURES then it will give up. */
MOCK_IMPL(STATIC void,
launch_rendezvous_point_circuit,(const hs_service_t *service,
- const hs_service_intro_point_t *ip,
- const hs_cell_introduce2_data_t *data))
+ const ed25519_public_key_t *ip_auth_pubkey,
+ const curve25519_keypair_t *ip_enc_key_kp,
+ const hs_cell_intro_rdv_data_t *rdv_data,
+ time_t now))
{
int circ_needs_uptime;
- time_t now = time(NULL);
extend_info_t *info = NULL;
origin_circuit_t *circ;
tor_assert(service);
- tor_assert(ip);
- tor_assert(data);
+ tor_assert(ip_auth_pubkey);
+ tor_assert(ip_enc_key_kp);
+ tor_assert(rdv_data);
circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports);
/* Get the extend info data structure for the chosen rendezvous point
* specified by the given link specifiers. */
- info = hs_get_extend_info_from_lspecs(data->link_specifiers,
- &data->onion_pk,
+ info = hs_get_extend_info_from_lspecs(rdv_data->link_specifiers,
+ &rdv_data->onion_pk,
service->config.is_single_onion);
if (info == NULL) {
/* We are done here, we can't extend to the rendezvous point. */
@@ -374,7 +391,8 @@ launch_rendezvous_point_circuit,(const hs_service_t *service,
log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s "
"for %s service %s",
safe_str_client(extend_info_describe(info)),
- safe_str_client(hex_str((const char *) data->rendezvous_cookie,
+ safe_str_client(hex_str((const char *)
+ rdv_data->rendezvous_cookie,
REND_COOKIE_LEN)),
get_service_anonymity_string(service),
safe_str_client(service->onion_address));
@@ -391,9 +409,10 @@ launch_rendezvous_point_circuit,(const hs_service_t *service,
* key will be used for the RENDEZVOUS1 cell that will be sent on the
* circuit once opened. */
curve25519_keypair_generate(&ephemeral_kp, 0);
- if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey,
- &ip->enc_key_kp,
- &ephemeral_kp, &data->client_pk,
+ if (hs_ntor_service_get_rendezvous1_keys(ip_auth_pubkey,
+ ip_enc_key_kp,
+ &ephemeral_kp,
+ &rdv_data->client_pk,
&keys) < 0) {
/* This should not really happened but just in case, don't make tor
* freak out, close the circuit and move on. */
@@ -404,15 +423,22 @@ launch_rendezvous_point_circuit,(const hs_service_t *service,
goto end;
}
circ->hs_ident = create_rp_circuit_identifier(service,
- data->rendezvous_cookie,
- &ephemeral_kp.pubkey, &keys);
+ rdv_data->rendezvous_cookie,
+ &ephemeral_kp.pubkey, &keys);
memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp));
memwipe(&keys, 0, sizeof(keys));
tor_assert(circ->hs_ident);
}
+ /* Remember PoW state if this introduction included a valid proof of work
+ * client puzzle extension. */
+ if (rdv_data->pow_effort > 0) {
+ circ->hs_pow_effort = rdv_data->pow_effort;
+ circ->hs_with_pow_circ = 1;
+ }
+
/* Setup congestion control if asked by the client from the INTRO cell. */
- if (data->cc_enabled) {
+ if (rdv_data->cc_enabled) {
hs_circ_setup_congestion_control(circ, congestion_control_sendme_inc(),
service->config.is_single_onion);
}
@@ -494,6 +520,10 @@ retry_service_rendezvous_point(const origin_circuit_t *circ)
if (new_circ == NULL) {
log_warn(LD_REND, "Failed to launch rendezvous circuit to %s",
safe_str_client(extend_info_describe(bstate->chosen_exit)));
+
+ hs_metrics_failed_rdv(&circ->hs_ident->identity_pk,
+ HS_METRICS_ERR_RDV_RETRY);
+
goto done;
}
@@ -594,6 +624,298 @@ cleanup_on_free_client_circ(circuit_t *circ)
* Thus possible that this passes through. */
}
+/** Return less than 0 if a precedes b, 0 if a equals b and greater than 0 if
+ * b precedes a. Note that *higher* effort is *earlier* in the pqueue. */
+static int
+compare_rend_request_by_effort_(const void *_a, const void *_b)
+{
+ const pending_rend_t *a = _a, *b = _b;
+ if (a->rdv_data.pow_effort > b->rdv_data.pow_effort) {
+ return -1;
+ } else if (a->rdv_data.pow_effort == b->rdv_data.pow_effort) {
+ /* tie-breaker! use the time it was added to the queue. older better. */
+ if (a->enqueued_ts < b->enqueued_ts)
+ return -1;
+ if (a->enqueued_ts > b->enqueued_ts)
+ return 1;
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/** Return 1 if a request waiting in our service-side pqueue is old
+ * enough that we should just discard it rather than trying to respond,
+ * or 0 if we still like it. As a heuristic, choose half of the total
+ * permitted time interval (so we don't approve trying to respond to
+ * requests when we will then give up on them a moment later).
+ */
+static int
+queued_rend_request_is_too_old(pending_rend_t *req, time_t now)
+{
+ if ((req->enqueued_ts + MAX_REND_TIMEOUT/2) < now)
+ return 1;
+ return 0;
+}
+
+/** Our rendezvous request priority queue is too full; keep the first
+ * pqueue_high_level/2 entries and discard the rest.
+ */
+static void
+trim_rend_pqueue(hs_pow_service_state_t *pow_state, time_t now)
+{
+ smartlist_t *old_pqueue = pow_state->rend_request_pqueue;
+ smartlist_t *new_pqueue = pow_state->rend_request_pqueue = smartlist_new();
+
+ log_info(LD_REND, "Rendezvous request priority queue has "
+ "reached capacity (%d). Discarding the bottom half.",
+ smartlist_len(old_pqueue));
+
+ while (smartlist_len(old_pqueue) &&
+ smartlist_len(new_pqueue) < pow_state->pqueue_high_level/2) {
+ /* while there are still old ones, and the new one isn't full yet */
+ pending_rend_t *req =
+ smartlist_pqueue_pop(old_pqueue,
+ compare_rend_request_by_effort_,
+ offsetof(pending_rend_t, idx));
+ if (queued_rend_request_is_too_old(req, now)) {
+ log_info(LD_REND, "While trimming, rend request has been pending "
+ "for too long; discarding.");
+
+ pow_state->max_trimmed_effort = MAX(pow_state->max_trimmed_effort,
+ req->rdv_data.pow_effort);
+
+ free_pending_rend(req);
+ } else {
+ smartlist_pqueue_add(new_pqueue,
+ compare_rend_request_by_effort_,
+ offsetof(pending_rend_t, idx), req);
+ }
+ }
+
+ /* Ok, we have rescued all the entries we want to keep. The rest are
+ * all excess. */
+ SMARTLIST_FOREACH_BEGIN(old_pqueue, pending_rend_t *, req) {
+ pow_state->max_trimmed_effort = MAX(pow_state->max_trimmed_effort,
+ req->rdv_data.pow_effort);
+ free_pending_rend(req);
+ } SMARTLIST_FOREACH_END(req);
+ smartlist_free(old_pqueue);
+}
+
+/** Count up how many pending outgoing (CIRCUIT_PURPOSE_S_CONNECT_REND)
+ * circuits there are for this service. Used in the PoW rate limiting
+ * world to decide whether it's time to launch any new ones.
+ */
+static int
+count_service_rp_circuits_pending(hs_service_t *service)
+{
+ origin_circuit_t *ocirc = NULL;
+ int count = 0;
+ while ((ocirc = circuit_get_next_by_purpose(ocirc,
+ CIRCUIT_PURPOSE_S_CONNECT_REND))) {
+ /* Count up circuits that are v3 and for this service. */
+ if (ocirc->hs_ident != NULL &&
+ ed25519_pubkey_eq(&ocirc->hs_ident->identity_pk,
+ &service->keys.identity_pk)) {
+ count++;
+ }
+ }
+ return count;
+}
+
+/** Peek at the top entry on the pending rend pqueue, which must not be empty.
+ * If its level of effort is at least what we're suggesting for that service
+ * right now, return 1, else return 0.
+ */
+int
+top_of_rend_pqueue_is_worthwhile(hs_pow_service_state_t *pow_state)
+{
+ tor_assert(pow_state->rend_request_pqueue);
+ tor_assert(smartlist_len(pow_state->rend_request_pqueue));
+
+ pending_rend_t *req =
+ smartlist_get(pow_state->rend_request_pqueue, 0);
+
+ if (req->rdv_data.pow_effort >= pow_state->suggested_effort)
+ return 1;
+
+ return 0;
+}
+
+/** Abandon and free all pending rend requests, leaving the pqueue empty. */
+void
+rend_pqueue_clear(hs_pow_service_state_t *pow_state)
+{
+ tor_assert(pow_state->rend_request_pqueue);
+ while (smartlist_len(pow_state->rend_request_pqueue)) {
+ pending_rend_t *req = smartlist_pop_last(pow_state->rend_request_pqueue);
+ free_pending_rend(req);
+ }
+}
+
+/** What is the threshold of in-progress (CIRCUIT_PURPOSE_S_CONNECT_REND)
+ * rendezvous responses above which we won't launch new low-effort rendezvous
+ * responses? (Intro2 cells with suitable PoW effort are not affected
+ * by this threshold.) */
+#define MAX_CHEAP_REND_CIRCUITS_IN_PROGRESS 16
+
+static void
+handle_rend_pqueue_cb(mainloop_event_t *ev, void *arg)
+{
+ hs_service_t *service = arg;
+ hs_pow_service_state_t *pow_state = service->state.pow_state;
+ time_t now = time(NULL);
+ int in_flight = count_service_rp_circuits_pending(service);
+
+ (void) ev; /* Not using the returned event, make compiler happy. */
+
+ log_info(LD_REND, "Considering launching more rendezvous responses. "
+ "%d in-flight, %d pending.",
+ in_flight,
+ smartlist_len(pow_state->rend_request_pqueue));
+
+ /* Process only one rend request per callback, so that this work will not
+ * be prioritized over other event loop callbacks. We may need to retry
+ * in order to find one request that's still viable. */
+ while (smartlist_len(pow_state->rend_request_pqueue) > 0) {
+
+ /* first, peek at the top result to see if we want to pop it */
+ if (in_flight >= MAX_CHEAP_REND_CIRCUITS_IN_PROGRESS &&
+ !top_of_rend_pqueue_is_worthwhile(pow_state)) {
+ /* We have queued requests, but they are all low priority, and also
+ * we have too many in-progress rendezvous responses. Don't launch
+ * any more. Schedule ourselves to reassess in a bit. */
+ log_info(LD_REND, "Next request to launch is low priority, and "
+ "%d in-flight already. Waiting to launch more.", in_flight);
+ const struct timeval delay_tv = { 0, 100000 };
+ mainloop_event_schedule(pow_state->pop_pqueue_ev, &delay_tv);
+ return; /* done here! no cleanup needed. */
+ }
+
+ if (pow_state->using_pqueue_bucket) {
+ token_bucket_ctr_refill(&pow_state->pqueue_bucket,
+ (uint32_t) monotime_coarse_absolute_sec());
+
+ if (token_bucket_ctr_get(&pow_state->pqueue_bucket) > 0) {
+ token_bucket_ctr_dec(&pow_state->pqueue_bucket, 1);
+ } else {
+ /* Waiting for pqueue rate limit to refill, come back later */
+ const struct timeval delay_tv = { 0, 100000 };
+ mainloop_event_schedule(pow_state->pop_pqueue_ev, &delay_tv);
+ return;
+ }
+ }
+
+ /* Pop next request by effort. */
+ pending_rend_t *req =
+ smartlist_pqueue_pop(pow_state->rend_request_pqueue,
+ compare_rend_request_by_effort_,
+ offsetof(pending_rend_t, idx));
+
+ hs_metrics_pow_pqueue_rdv(service,
+ smartlist_len(pow_state->rend_request_pqueue));
+
+ log_info(LD_REND, "Dequeued pending rendezvous request with effort: %u. "
+ "Waited %d. "
+ "Remaining requests: %u",
+ req->rdv_data.pow_effort,
+ (int)(now - req->enqueued_ts),
+ smartlist_len(pow_state->rend_request_pqueue));
+
+ if (queued_rend_request_is_too_old(req, now)) {
+ log_info(LD_REND, "Top rend request has been pending for too long; "
+ "discarding and moving to the next one.");
+ free_pending_rend(req);
+ continue; /* do not increment count, this one's free */
+ }
+
+ /* Launch the rendezvous circuit. */
+ launch_rendezvous_point_circuit(service, &req->ip_auth_pubkey,
+ &req->ip_enc_key_kp, &req->rdv_data, now);
+ free_pending_rend(req);
+
+ ++pow_state->rend_handled;
+ ++in_flight;
+ break;
+ }
+
+ /* If there are still some pending rendezvous circuits in the pqueue then
+ * reschedule the event in order to continue handling them. */
+ if (smartlist_len(pow_state->rend_request_pqueue) > 0) {
+ mainloop_event_activate(pow_state->pop_pqueue_ev);
+
+ if (smartlist_len(pow_state->rend_request_pqueue) >=
+ pow_state->pqueue_low_level) {
+ pow_state->had_queue = 1;
+ }
+ }
+}
+
+/** Given the information needed to launch a rendezvous circuit and an
+ * effort value, enqueue the rendezvous request in the service's PoW priority
+ * queue with the effort being the priority.
+ *
+ * Return 0 if we successfully enqueued the request else -1. */
+static int
+enqueue_rend_request(const hs_service_t *service, hs_service_intro_point_t *ip,
+ hs_cell_introduce2_data_t *data, time_t now)
+{
+ hs_pow_service_state_t *pow_state = NULL;
+ pending_rend_t *req = NULL;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(data);
+
+ /* Ease our lives */
+ pow_state = service->state.pow_state;
+
+ req = tor_malloc_zero(sizeof(pending_rend_t));
+
+ /* Copy over the rendezvous request the needed data to launch a circuit. */
+ ed25519_pubkey_copy(&req->ip_auth_pubkey, &ip->auth_key_kp.pubkey);
+ memcpy(&req->ip_enc_key_kp, &ip->enc_key_kp, sizeof(req->ip_enc_key_kp));
+ memcpy(&req->rdv_data, &data->rdv_data, sizeof(req->rdv_data));
+ /* Invalidate the link specifier pointer in the introduce2 data so it
+ * doesn't get freed under us. */
+ data->rdv_data.link_specifiers = NULL;
+ req->idx = -1;
+ req->enqueued_ts = now;
+
+ /* Enqueue the rendezvous request. */
+ smartlist_pqueue_add(pow_state->rend_request_pqueue,
+ compare_rend_request_by_effort_,
+ offsetof(pending_rend_t, idx), req);
+
+ hs_metrics_pow_pqueue_rdv(service,
+ smartlist_len(pow_state->rend_request_pqueue));
+
+ log_info(LD_REND, "Enqueued rendezvous request with effort: %u. "
+ "Queued requests: %u",
+ req->rdv_data.pow_effort,
+ smartlist_len(pow_state->rend_request_pqueue));
+
+ /* Initialize the priority queue event if it hasn't been done so already. */
+ if (pow_state->pop_pqueue_ev == NULL) {
+ pow_state->pop_pqueue_ev =
+ mainloop_event_postloop_new(handle_rend_pqueue_cb, (void *)service);
+ }
+
+ /* Activate event, we just enqueued a rendezvous request. */
+ mainloop_event_activate(pow_state->pop_pqueue_ev);
+
+ /* See if there are so many cells queued that we need to cull. */
+ if (smartlist_len(pow_state->rend_request_pqueue) >=
+ pow_state->pqueue_high_level) {
+ trim_rend_pqueue(pow_state, now);
+ hs_metrics_pow_pqueue_rdv(service,
+ smartlist_len(pow_state->rend_request_pqueue));
+ }
+
+ return 0;
+}
+
/* ========== */
/* Public API */
/* ========== */
@@ -864,13 +1186,17 @@ hs_circ_service_rp_has_opened(const hs_service_t *service,
if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
RELAY_COMMAND_RENDEZVOUS1,
- (const char *) payload, payload_len,
+ (const char *) payload,
+ payload_len,
circ->cpath->prev) < 0) {
/* On error, circuit is closed. */
log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u "
"for service %s",
TO_CIRCUIT(circ)->n_circ_id,
safe_str_client(service->onion_address));
+
+ hs_metrics_failed_rdv(&service->keys.identity_pk,
+ HS_METRICS_ERR_RDV_RENDEZVOUS1);
goto done;
}
@@ -880,6 +1206,8 @@ hs_circ_service_rp_has_opened(const hs_service_t *service,
sizeof(circ->hs_ident->rendezvous_ntor_key_seed),
1) < 0) {
log_warn(LD_GENERAL, "Failed to setup circ");
+
+ hs_metrics_failed_rdv(&service->keys.identity_pk, HS_METRICS_ERR_RDV_E2E);
goto done;
}
@@ -980,6 +1308,7 @@ hs_circ_handle_introduce2(const hs_service_t *service,
int ret = -1;
time_t elapsed;
hs_cell_introduce2_data_t data;
+ time_t now = time(NULL);
tor_assert(service);
tor_assert(circ);
@@ -993,23 +1322,28 @@ hs_circ_handle_introduce2(const hs_service_t *service,
data.enc_kp = &ip->enc_key_kp;
data.payload = payload;
data.payload_len = payload_len;
- data.link_specifiers = smartlist_new();
data.replay_cache = ip->replay_cache;
- data.cc_enabled = 0;
-
- if (get_subcredential_for_handling_intro2_cell(service,
- &data, subcredential)) {
+ data.rdv_data.link_specifiers = smartlist_new();
+ data.rdv_data.cc_enabled = 0;
+ data.rdv_data.pow_effort = 0;
+
+ if (get_subcredential_for_handling_intro2_cell(service, &data,
+ subcredential)) {
+ hs_metrics_reject_intro_req(service,
+ HS_METRICS_ERR_INTRO_REQ_SUBCREDENTIAL);
goto done;
}
- if (hs_cell_parse_introduce2(&data, circ, service) < 0) {
+ if (hs_cell_parse_introduce2(&data, circ, service, ip) < 0) {
+ hs_metrics_reject_intro_req(service, HS_METRICS_ERR_INTRO_REQ_INTRODUCE2);
goto done;
}
/* Check whether we've seen this REND_COOKIE before to detect repeats. */
if (replaycache_add_test_and_elapsed(
service->state.replay_cache_rend_cookie,
- data.rendezvous_cookie, sizeof(data.rendezvous_cookie),
+ data.rdv_data.rendezvous_cookie,
+ sizeof(data.rdv_data.rendezvous_cookie),
&elapsed)) {
/* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE
* as its previous one if its intro circ times out while in state
@@ -1020,6 +1354,8 @@ hs_circ_handle_introduce2(const hs_service_t *service,
log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE "
"field %ld seconds ago. Dropping cell.",
(long int) elapsed);
+ hs_metrics_reject_intro_req(service,
+ HS_METRICS_ERR_INTRO_REQ_INTRODUCE2_REPLAY);
goto done;
}
@@ -1027,13 +1363,33 @@ hs_circ_handle_introduce2(const hs_service_t *service,
* so increment our counter that we've seen one on this intro point. */
ip->introduce2_count++;
+ /* Add the rendezvous request to the priority queue if PoW defenses are
+ * enabled, otherwise rendezvous as usual. */
+ if (have_module_pow() && service->config.has_pow_defenses_enabled) {
+ log_info(LD_REND,
+ "Adding introduction request to pqueue with effort: %u",
+ data.rdv_data.pow_effort);
+ if (enqueue_rend_request(service, ip, &data, now) < 0) {
+ goto done;
+ }
+
+ /* Track the total effort in valid requests received this period */
+ service->state.pow_state->total_effort += data.rdv_data.pow_effort;
+
+ /* Successfully added rend circuit to priority queue. */
+ ret = 0;
+ goto done;
+ }
+
/* Launch rendezvous circuit with the onion key and rend cookie. */
- launch_rendezvous_point_circuit(service, ip, &data);
+ launch_rendezvous_point_circuit(service, &ip->auth_key_kp.pubkey,
+ &ip->enc_key_kp, &data.rdv_data, now);
/* Success. */
ret = 0;
done:
- link_specifier_smartlist_free(data.link_specifiers);
+ /* Note that if PoW defenses are enabled, this is NULL. */
+ link_specifier_smartlist_free(data.rdv_data.link_specifiers);
memwipe(&data, 0, sizeof(data));
return ret;
}
@@ -1080,7 +1436,8 @@ int
hs_circ_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ,
const hs_desc_intro_point_t *ip,
- const hs_subcredential_t *subcredential)
+ const hs_subcredential_t *subcredential,
+ const hs_pow_solution_t *pow_solution)
{
int ret = -1;
ssize_t payload_len;
@@ -1114,6 +1471,9 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ,
goto close;
}
+ /* Set the PoW solution if any. */
+ intro1_data.pow_solution = pow_solution;
+
/* If the rend circ was set up for congestion control, add that to the
* intro data, to signal it in an extension */
if (TO_CIRCUIT(rend_circ)->ccontrol) {
diff --git a/src/feature/hs/hs_circuit.h b/src/feature/hs/hs_circuit.h
index afbff7b894..8b0692d76b 100644
--- a/src/feature/hs/hs_circuit.h
+++ b/src/feature/hs/hs_circuit.h
@@ -12,8 +12,29 @@
#include "core/or/or.h"
#include "lib/crypt_ops/crypto_ed25519.h"
+#include "feature/hs/hs_cell.h"
#include "feature/hs/hs_service.h"
+/** Pending rendezvous request. This is put in a service priority queue. */
+typedef struct pending_rend_t {
+ /* Intro point authentication pubkey. */
+ ed25519_public_key_t ip_auth_pubkey;
+ /* Intro point encryption keypair for the "ntor" type. */
+ curve25519_keypair_t ip_enc_key_kp;
+
+ /* Rendezvous data for the circuit. */
+ hs_cell_intro_rdv_data_t rdv_data;
+
+ /** Position of element in the heap */
+ int idx;
+
+ /** When was this request enqueued. */
+ time_t enqueued_ts;
+} pending_rend_t;
+
+int top_of_rend_pqueue_is_worthwhile(hs_pow_service_state_t *pow_state);
+void rend_pqueue_clear(hs_pow_service_state_t *pow_state);
+
/* Cleanup function when the circuit is closed or freed. */
void hs_circ_cleanup_on_close(circuit_t *circ);
void hs_circ_cleanup_on_free(circuit_t *circ);
@@ -55,7 +76,8 @@ int hs_circ_handle_introduce2(const hs_service_t *service,
int hs_circ_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ,
const hs_desc_intro_point_t *ip,
- const struct hs_subcredential_t *subcredential);
+ const struct hs_subcredential_t *subcredential,
+ const hs_pow_solution_t *pow_solution);
int hs_circ_send_establish_rendezvous(origin_circuit_t *circ);
/* e2e circuit API. */
@@ -83,11 +105,12 @@ create_rp_circuit_identifier(const hs_service_t *service,
const curve25519_public_key_t *server_pk,
const struct hs_ntor_rend_cell_keys_t *keys);
-struct hs_cell_introduce2_data_t;
MOCK_DECL(STATIC void,
launch_rendezvous_point_circuit,(const hs_service_t *service,
- const hs_service_intro_point_t *ip,
- const struct hs_cell_introduce2_data_t *data));
+ const ed25519_public_key_t *ip_auth_pubkey,
+ const curve25519_keypair_t *ip_enc_key_kp,
+ const hs_cell_intro_rdv_data_t *rdv_data,
+ time_t now));
#endif /* defined(HS_CIRCUIT_PRIVATE) */
diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c
index a50598d9f3..2bb59f078e 100644
--- a/src/feature/hs/hs_client.c
+++ b/src/feature/hs/hs_client.c
@@ -541,7 +541,7 @@ intro_circ_is_ok(const origin_circuit_t *circ)
/** Find a descriptor intro point object that matches the given ident in the
* given descriptor desc. Return NULL if not found. */
-static const hs_desc_intro_point_t *
+const hs_desc_intro_point_t *
find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident,
const hs_descriptor_t *desc)
{
@@ -549,6 +549,7 @@ find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident,
tor_assert(ident);
tor_assert(desc);
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(&ident->intro_auth_pk));
SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
const hs_desc_intro_point_t *, ip) {
@@ -600,14 +601,75 @@ find_desc_intro_point_by_legacy_id(const char *legacy_id,
return ret_ip;
}
+/** Phase two for client-side introducing:
+ * Send an INTRODUCE1 cell along the intro circuit and populate the rend
+ * circuit identifier with the needed key material for the e2e encryption.
+ */
+int
+send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ,
+ const hs_descriptor_t *desc,
+ hs_pow_solution_t *pow_solution,
+ const hs_desc_intro_point_t *ip)
+{
+ const ed25519_public_key_t *service_identity_pk =
+ &intro_circ->hs_ident->identity_pk;
+
+ /* Send the INTRODUCE1 cell. */
+ if (hs_circ_send_introduce1(intro_circ, rend_circ, ip,
+ &desc->subcredential, pow_solution) < 0) {
+ if (TO_CIRCUIT(intro_circ)->marked_for_close) {
+ /* If the introduction circuit was closed, we were unable to send the
+ * cell for some reasons. In any case, the intro circuit has to be
+ * closed by the above function. We'll return a transient error so tor
+ * can recover and pick a new intro point. To avoid picking that same
+ * intro point, we'll note down the intro point failure so it doesn't
+ * get reused. */
+ hs_cache_client_intro_state_note(service_identity_pk,
+ &intro_circ->hs_ident->intro_auth_pk,
+ INTRO_POINT_FAILURE_GENERIC);
+ }
+ /* It is also possible that the rendezvous circuit was closed due to being
+ * unable to use the rendezvous point node_t so in that case, we also want
+ * to recover and let tor pick a new one. */
+ return -1; /* transient failure */
+ }
+
+ /* Cell has been sent successfully.
+ * Now, we wait for an ACK or NAK on this circuit. */
+ circuit_change_purpose(TO_CIRCUIT(intro_circ),
+ CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT);
+ /* Set timestamp_dirty, because circuit_expire_building expects it to
+ * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */
+ TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL);
+ pathbias_count_use_attempt(intro_circ);
+
+ return 0; /* Success. */
+}
+
+/** Set a client-side cap on the highest effort of PoW we will try to
+ * tackle. If asked for higher, we solve it at this cap. */
+#define CLIENT_MAX_POW_EFFORT 10000
+
+/** Set a client-side minimum effort. If the client is choosing to increase
+ * effort on retry, it will always pick a value >= this lower limit. */
+#define CLIENT_MIN_RETRY_POW_EFFORT 8
+
+/** Client effort will double on every retry until this level is hit */
+#define CLIENT_POW_EFFORT_DOUBLE_UNTIL 1000
+
+/** After we reach DOUBLE_UNTIL, client effort is multiplied by this amount
+ * on every retry until we reach MAX_POW_EFFORT. */
+#define CLIENT_POW_RETRY_MULTIPLIER (1.5f)
+
/** Send an INTRODUCE1 cell along the intro circuit and populate the rend
* circuit identifier with the needed key material for the e2e encryption.
* Return 0 on success, -1 if there is a transient error such that an action
* has been taken to recover and -2 if there is a permanent error indicating
* that both circuits were closed. */
static int
-send_introduce1(origin_circuit_t *intro_circ,
- origin_circuit_t *rend_circ)
+consider_sending_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ)
{
int status;
char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
@@ -624,9 +686,15 @@ send_introduce1(origin_circuit_t *intro_circ,
* version number but for now there is none because it's all v3. */
hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address);
- log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u",
+ log_info(LD_REND, "Considering sending INTRODUCE1 cell to service %s "
+ "on circuit %u",
safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id);
+ /* if it's already waiting on the cpuworker farm, don't queue it again */
+ if (intro_circ->hs_currently_solving_pow) {
+ goto tran_err;
+ }
+
/* 1) Get descriptor from our cache. */
const hs_descriptor_t *desc =
hs_cache_lookup_as_client(service_identity_pk);
@@ -644,8 +712,8 @@ send_introduce1(origin_circuit_t *intro_circ,
goto tran_err;
}
- /* Check if the rendevous circuit was setup WITHOUT congestion control but if
- * it is enabled and the service supports it. This can happen, see
+ /* Check if the rendezvous circuit was setup WITHOUT congestion control,
+ * but if it is enabled and the service supports it. This can happen, see
* setup_rendezvous_circ_congestion_control() and so close rendezvous circuit
* so another one can be created. */
if (TO_CIRCUIT(rend_circ)->ccontrol == NULL && congestion_control_enabled()
@@ -668,41 +736,84 @@ send_introduce1(origin_circuit_t *intro_circ,
goto perm_err;
}
- /* Send the INTRODUCE1 cell. */
- if (hs_circ_send_introduce1(intro_circ, rend_circ, ip,
- &desc->subcredential) < 0) {
- if (TO_CIRCUIT(intro_circ)->marked_for_close) {
- /* If the introduction circuit was closed, we were unable to send the
- * cell for some reasons. In any case, the intro circuit has to be
- * closed by the above function. We'll return a transient error so tor
- * can recover and pick a new intro point. To avoid picking that same
- * intro point, we'll note down the intro point failure so it doesn't
- * get reused. */
- hs_cache_client_intro_state_note(service_identity_pk,
- &intro_circ->hs_ident->intro_auth_pk,
- INTRO_POINT_FAILURE_GENERIC);
- }
- /* It is also possible that the rendezvous circuit was closed due to being
- * unable to use the rendezvous point node_t so in that case, we also want
- * to recover and let tor pick a new one. */
- goto tran_err;
- }
-
- /* Cell has been sent successfully. Copy the introduction point
- * authentication and encryption key in the rendezvous circuit identifier so
- * we can compute the ntor keys when we receive the RENDEZVOUS2 cell. */
+ /* Copy the introduction point authentication and encryption key
+ * in the rendezvous circuit identifier so we can compute the ntor keys
+ * when we receive the RENDEZVOUS2 cell. */
memcpy(&rend_circ->hs_ident->intro_enc_pk, &ip->enc_key,
sizeof(rend_circ->hs_ident->intro_enc_pk));
- ed25519_pubkey_copy(&rend_circ->hs_ident->intro_auth_pk,
- &intro_circ->hs_ident->intro_auth_pk);
- /* Now, we wait for an ACK or NAK on this circuit. */
- circuit_change_purpose(TO_CIRCUIT(intro_circ),
- CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT);
- /* Set timestamp_dirty, because circuit_expire_building expects it to
- * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */
- TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL);
- pathbias_count_use_attempt(intro_circ);
+ /* Optionally choose to solve a client puzzle for this connection. This
+ * is only available if we have PoW support at compile time, and if the
+ * service has provided a PoW seed in its descriptor. The puzzle is enabled
+ * any time effort is nonzero, which can be recommended by the service or
+ * self-imposed as a result of previous timeouts.
+ */
+ if (have_module_pow() && desc->encrypted_data.pow_params) {
+ hs_pow_solver_inputs_t pow_inputs = {
+ .effort = desc->encrypted_data.pow_params->suggested_effort,
+ .CompiledProofOfWorkHash = get_options()->CompiledProofOfWorkHash
+ };
+ ed25519_pubkey_copy(&pow_inputs.service_blinded_id,
+ &desc->plaintext_data.blinded_pubkey);
+ memcpy(pow_inputs.seed, desc->encrypted_data.pow_params->seed,
+ sizeof pow_inputs.seed);
+ log_debug(LD_REND, "PoW params present in descriptor, suggested_effort=%u",
+ pow_inputs.effort);
+
+ if (pow_inputs.effort > CLIENT_MAX_POW_EFFORT) {
+ log_notice(LD_REND, "Onion service suggested effort %d which is "
+ "higher than we want to solve. Solving at %d instead.",
+ pow_inputs.effort, CLIENT_MAX_POW_EFFORT);
+ pow_inputs.effort = CLIENT_MAX_POW_EFFORT;
+ }
+
+ const hs_cache_intro_state_t *state =
+ hs_cache_client_intro_state_find(&intro_circ->hs_ident->identity_pk,
+ &intro_circ->hs_ident->intro_auth_pk);
+ uint32_t unreachable_count = state ? state->unreachable_count : 0;
+ if (state) {
+ log_debug(LD_REND, "hs_cache state during PoW consideration, "
+ "error=%d timed_out=%d unreachable_count=%u",
+ state->error, state->timed_out, state->unreachable_count);
+ }
+ uint64_t new_effort = pow_inputs.effort;
+ for (unsigned n_retry = 0; n_retry < unreachable_count; n_retry++) {
+ if (new_effort >= CLIENT_MAX_POW_EFFORT) {
+ break;
+ }
+ if (new_effort < CLIENT_POW_EFFORT_DOUBLE_UNTIL) {
+ new_effort <<= 1;
+ } else {
+ new_effort = (uint64_t) (CLIENT_POW_RETRY_MULTIPLIER * new_effort);
+ }
+ new_effort = MAX((uint64_t)CLIENT_MIN_RETRY_POW_EFFORT, new_effort);
+ new_effort = MIN((uint64_t)CLIENT_MAX_POW_EFFORT, new_effort);
+ }
+ if (pow_inputs.effort != (uint32_t)new_effort) {
+ log_info(LD_REND, "Increasing PoW effort from %d to %d after intro "
+ "point unreachable_count=%d",
+ pow_inputs.effort, (int)new_effort, unreachable_count);
+ pow_inputs.effort = (uint32_t)new_effort;
+ }
+
+ if (pow_inputs.effort > 0) {
+ /* send it to the client-side pow cpuworker for solving. */
+ intro_circ->hs_currently_solving_pow = 1;
+ if (hs_pow_queue_work(intro_circ->global_identifier,
+ rend_circ->hs_ident->rendezvous_cookie,
+ &pow_inputs) != 0) {
+ log_warn(LD_REND, "Failed to enqueue PoW request");
+ }
+
+ /* can't proceed with the intro1 cell yet, so yield back to the
+ * main loop */
+ goto tran_err;
+ }
+ }
+
+ /* move on to the next phase: actually try to send it */
+ if (send_introduce1(intro_circ, rend_circ, desc, NULL, ip) < 0)
+ goto tran_err;
/* Success. */
status = 0;
@@ -732,8 +843,8 @@ send_introduce1(origin_circuit_t *intro_circ,
*
* Return 0 if everything went well, otherwise return -1 in the case of errors.
*/
-static int
-setup_intro_circ_auth_key(origin_circuit_t *circ)
+int
+hs_client_setup_intro_circ_auth_key(origin_circuit_t *circ)
{
const hs_descriptor_t *desc;
const hs_desc_intro_point_t *ip;
@@ -779,13 +890,6 @@ client_intro_circ_has_opened(origin_circuit_t *circ)
log_info(LD_REND, "Introduction circuit %u has opened. Attaching streams.",
(unsigned int) TO_CIRCUIT(circ)->n_circ_id);
- /* This is an introduction circuit so we'll attach the correct
- * authentication key to the circuit identifier so it can be identified
- * properly later on. */
- if (setup_intro_circ_auth_key(circ) < 0) {
- return;
- }
-
connection_ap_attach_pending(1);
}
@@ -1390,9 +1494,20 @@ can_client_refetch_desc(const ed25519_public_key_t *identity_pk,
/* Check if fetching a desc for this HS is useful to us right now */
{
const hs_descriptor_t *cached_desc = NULL;
+ int has_usable_intro = false;
+ int has_expired_hs_pow = false;
+
cached_desc = hs_cache_lookup_as_client(identity_pk);
- if (cached_desc && hs_client_any_intro_points_usable(identity_pk,
- cached_desc)) {
+ if (cached_desc) {
+ has_usable_intro = hs_client_any_intro_points_usable(identity_pk,
+ cached_desc);
+ if (cached_desc->encrypted_data.pow_params) {
+ has_expired_hs_pow =
+ cached_desc->encrypted_data.pow_params->expiration_time <
+ approx_time();
+ }
+ }
+ if (has_usable_intro && !has_expired_hs_pow) {
log_info(LD_GENERAL, "We would fetch a v3 hidden service descriptor "
"but we already have a usable descriptor.");
status = HS_CLIENT_FETCH_HAVE_DESC;
@@ -1972,6 +2087,7 @@ hs_client_circuit_cleanup_on_free(const circuit_t *circ)
orig_circ = CONST_TO_ORIGIN_CIRCUIT(circ);
tor_assert(orig_circ->hs_ident);
+ const ed25519_public_key_t *intro_pk = &orig_circ->hs_ident->intro_auth_pk;
has_timed_out =
(circ->marked_for_close_orig_reason == END_CIRC_REASON_TIMEOUT);
@@ -1986,22 +2102,22 @@ hs_client_circuit_cleanup_on_free(const circuit_t *circ)
safe_str_client(ed25519_fmt(&orig_circ->hs_ident->identity_pk)),
safe_str_client(build_state_get_exit_nickname(orig_circ->build_state)),
failure);
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(intro_pk));
hs_cache_client_intro_state_note(&orig_circ->hs_ident->identity_pk,
- &orig_circ->hs_ident->intro_auth_pk,
- failure);
+ intro_pk, failure);
break;
case CIRCUIT_PURPOSE_C_INTRODUCING:
if (has_timed_out || !orig_circ->build_state) {
break;
}
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(intro_pk));
failure = INTRO_POINT_FAILURE_UNREACHABLE;
log_info(LD_REND, "Failed v3 intro circ for service %s to intro point %s "
"(while building circuit). Marking as unreachable.",
safe_str_client(ed25519_fmt(&orig_circ->hs_ident->identity_pk)),
safe_str_client(build_state_get_exit_nickname(orig_circ->build_state)));
hs_cache_client_intro_state_note(&orig_circ->hs_ident->identity_pk,
- &orig_circ->hs_ident->intro_auth_pk,
- failure);
+ intro_pk, failure);
break;
default:
break;
@@ -2143,7 +2259,7 @@ int
hs_client_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ)
{
- return send_introduce1(intro_circ, rend_circ);
+ return consider_sending_introduce1(intro_circ, rend_circ);
}
/** Called when the client circuit circ has been established. It can be either
diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h
index 2fe955605f..234306a3c3 100644
--- a/src/feature/hs/hs_client.h
+++ b/src/feature/hs/hs_client.h
@@ -78,6 +78,10 @@ typedef struct hs_client_service_authorization_t {
int flags;
} hs_client_service_authorization_t;
+const hs_desc_intro_point_t *
+find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident,
+ const hs_descriptor_t *desc);
+
hs_client_register_auth_status_t
hs_client_register_auth_credentials(hs_client_service_authorization_t *creds);
@@ -100,6 +104,12 @@ void hs_client_launch_v3_desc_fetch(
const ed25519_public_key_t *onion_identity_pk,
const smartlist_t *hsdirs);
+int send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ,
+ const hs_descriptor_t *desc,
+ hs_pow_solution_t *pow_solution,
+ const hs_desc_intro_point_t *ip);
+
hs_desc_decode_status_t hs_client_decode_descriptor(
const char *desc_str,
const ed25519_public_key_t *service_identity_pk,
@@ -109,6 +119,8 @@ int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk);
void hs_client_dir_info_changed(void);
+int hs_client_setup_intro_circ_auth_key(origin_circuit_t *circ);
+
int hs_client_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ);
diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c
index e326581dd1..cd7e4890d1 100644
--- a/src/feature/hs/hs_common.c
+++ b/src/feature/hs/hs_common.c
@@ -432,7 +432,7 @@ get_second_cached_disaster_srv(void)
* thus will be ignored for the param construction.
*
* The result is put in param_out. */
-static void
+STATIC void
build_blinded_key_param(const ed25519_public_key_t *pubkey,
const uint8_t *secret, size_t secret_len,
uint64_t period_num, uint64_t period_length,
@@ -1680,7 +1680,7 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
if (!extend_info_addr_is_allowed(&ap.addr)) {
log_fn(LOG_PROTOCOL_WARN, LD_REND,
"Requested address is private and we are not allowed to extend to "
- "it: %s:%u", fmt_addr(&ap.addr), ap.port);
+ "it: %s:%u", safe_str(fmt_addr(&ap.addr)), ap.port);
goto done;
}
diff --git a/src/feature/hs/hs_common.h b/src/feature/hs/hs_common.h
index a7a8f23a3c..48c112110c 100644
--- a/src/feature/hs/hs_common.h
+++ b/src/feature/hs/hs_common.h
@@ -256,7 +256,14 @@ link_specifier_t *link_specifier_dup(const link_specifier_t *src);
#ifdef HS_COMMON_PRIVATE
+struct ed25519_public_key_t;
+
STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out);
+STATIC void build_blinded_key_param(
+ const struct ed25519_public_key_t *pubkey,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t period_num, uint64_t period_length,
+ uint8_t *param_out);
/** The period for which a hidden service directory cannot be queried for
* the same descriptor ID again. */
diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c
index a76893fe1a..296941138b 100644
--- a/src/feature/hs/hs_config.c
+++ b/src/feature/hs/hs_config.c
@@ -320,6 +320,19 @@ config_validate_service(const hs_service_config_t *config)
config->intro_dos_burst_per_sec, config->intro_dos_rate_per_sec);
goto invalid;
}
+ if (config->has_pow_defenses_enabled &&
+ (config->pow_queue_burst < config->pow_queue_rate)) {
+ log_warn(LD_CONFIG, "Hidden service PoW queue burst (%" PRIu32 ") can "
+ "not be smaller than the rate value (%" PRIu32 ").",
+ config->pow_queue_burst, config->pow_queue_rate);
+ goto invalid;
+ }
+ if (config->has_pow_defenses_enabled && !have_module_pow()) {
+ log_warn(LD_CONFIG, "Hidden service proof-of-work defenses are enabled "
+ "in our configuration but this build of tor does not "
+ "include the required 'pow' module.");
+ goto invalid;
+ }
/* Valid. */
return 0;
@@ -351,7 +364,7 @@ config_service_v3(const hs_opts_t *hs_opts,
if (hs_opts->HiddenServiceExportCircuitID) {
int ok;
config->circuit_id_protocol =
- helper_parse_circuit_id_protocol("HiddenServcieExportCircuitID",
+ helper_parse_circuit_id_protocol("HiddenServiceExportCircuitID",
hs_opts->HiddenServiceExportCircuitID,
&ok);
if (!ok) {
@@ -392,6 +405,20 @@ config_service_v3(const hs_opts_t *hs_opts,
}
}
+ /* Are the PoW anti-DoS defenses enabled? */
+ config->has_pow_defenses_enabled = hs_opts->HiddenServicePoWDefensesEnabled;
+ config->pow_queue_rate = hs_opts->HiddenServicePoWQueueRate;
+ config->pow_queue_burst = hs_opts->HiddenServicePoWQueueBurst;
+
+ log_info(LD_REND, "Service PoW defenses are %s",
+ config->has_pow_defenses_enabled ? "enabled" : "disabled");
+ if (config->has_pow_defenses_enabled) {
+ log_info(LD_REND, "Service PoW queue rate set to: %" PRIu32,
+ config->pow_queue_rate);
+ log_info(LD_REND, "Service PoW queue burst set to: %" PRIu32,
+ config->pow_queue_burst);
+ }
+
/* We do not load the key material for the service at this stage. This is
* done later once tor can confirm that it is in a running state. */
diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h
index b250c62c8b..bde6da9612 100644
--- a/src/feature/hs/hs_config.h
+++ b/src/feature/hs/hs_config.h
@@ -25,6 +25,9 @@
#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN 0
#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX INT32_MAX
+/* Default values for the HS anti-DoS PoW defenses. */
+#define HS_CONFIG_V3_POW_DEFENSES_DEFAULT 0
+
/* API */
int hs_config_service_all(const or_options_t *options, int validate_only);
diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c
index d61ce237fa..da7bb662e1 100644
--- a/src/feature/hs/hs_descriptor.c
+++ b/src/feature/hs/hs_descriptor.c
@@ -68,6 +68,7 @@
#include "feature/dirparse/parsecommon.h"
#include "feature/hs/hs_cache.h"
#include "feature/hs/hs_config.h"
+#include "feature/hs/hs_pow.h"
#include "feature/nodelist/torcert.h" /* tor_cert_encode_ed22519() */
#include "lib/memarea/memarea.h"
#include "lib/crypt_ops/crypto_format.h"
@@ -96,6 +97,7 @@
#define str_ip_legacy_key_cert "legacy-key-cert"
#define str_intro_point_start "\n" str_intro_point " "
#define str_flow_control "flow-control"
+#define str_pow_params "pow-params"
/* Constant string value for the construction to encrypt the encrypted data
* section. */
#define str_enc_const_superencryption "hsdir-superencrypted-data"
@@ -117,6 +119,16 @@ static const struct {
{ 0, NULL }
};
+/** PoW supported types. */
+static const struct {
+ hs_pow_desc_type_t type;
+ const char *identifier;
+} pow_types[] = {
+ { HS_POW_DESC_V1, "v1"},
+ /* Indicate end of array. */
+ { 0, NULL }
+};
+
/** Descriptor ruleset. */
static token_rule_t hs_desc_v3_token_table[] = {
T1_START(str_hs_desc, R_HS_DESCRIPTOR, EQ(1), NO_OBJ),
@@ -143,6 +155,7 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = {
T01(str_intro_auth_required, R3_INTRO_AUTH_REQUIRED, GE(1), NO_OBJ),
T01(str_single_onion, R3_SINGLE_ONION_SERVICE, ARGS, NO_OBJ),
T01(str_flow_control, R3_FLOW_CONTROL, GE(2), NO_OBJ),
+ T01(str_pow_params, R3_POW_PARAMS, GE(4), NO_OBJ),
END_OF_TABLE
};
@@ -758,6 +771,13 @@ get_inner_encrypted_layer_plaintext(const hs_descriptor_t *desc)
smartlist_add_asprintf(lines, "%s %d\n", str_create2_formats,
ONION_HANDSHAKE_TYPE_NTOR);
+#ifdef TOR_UNIT_TESTS
+ if (desc->encrypted_data.test_extra_plaintext) {
+ smartlist_add(lines,
+ tor_strdup(desc->encrypted_data.test_extra_plaintext));
+ }
+#endif
+
if (desc->encrypted_data.intro_auth_types &&
smartlist_len(desc->encrypted_data.intro_auth_types)) {
/* Put the authentication-required line. */
@@ -777,6 +797,31 @@ get_inner_encrypted_layer_plaintext(const hs_descriptor_t *desc)
protover_get_supported(PRT_FLOWCTRL),
congestion_control_sendme_inc());
}
+
+ /* Add PoW parameters if present. */
+ if (desc->encrypted_data.pow_params) {
+ /* Base64 the seed */
+ size_t seed_b64_len = base64_encode_size(HS_POW_SEED_LEN, 0) + 1;
+ char *seed_b64 = tor_malloc_zero(seed_b64_len);
+ int ret = base64_encode(seed_b64, seed_b64_len,
+ (char *)desc->encrypted_data.pow_params->seed,
+ HS_POW_SEED_LEN, 0);
+ /* Return length doesn't count the NUL byte. */
+ tor_assert((size_t) ret == (seed_b64_len - 1));
+
+ /* Convert the expiration time to space-less ISO format. */
+ char time_buf[ISO_TIME_LEN + 1];
+ format_iso_time_nospace(time_buf,
+ desc->encrypted_data.pow_params->expiration_time);
+
+ /* Add "pow-params" line to descriptor encoding. */
+ smartlist_add_asprintf(lines, "%s %s %s %u %s\n", str_pow_params,
+ pow_types[desc->encrypted_data.pow_params->type].identifier,
+ seed_b64,
+ desc->encrypted_data.pow_params->suggested_effort,
+ time_buf);
+ tor_free(seed_b64);
+ }
}
/* Build the introduction point(s) section. */
@@ -2053,6 +2098,70 @@ desc_sig_is_valid(const char *b64_sig,
return ret;
}
+/** Given the token tok for PoW params, decode it as hs_pow_desc_params_t.
+ * tok->args MUST contain at least 4 elements Return 0 on success else -1 on
+ * failure. */
+static int
+decode_pow_params(const directory_token_t *tok,
+ hs_pow_desc_params_t *pow_params)
+{
+ int ret = -1;
+
+ tor_assert(tok);
+ tor_assert(tok->n_args >= 4);
+ tor_assert(pow_params);
+
+ /* Find the type of PoW system being used. */
+ int match = 0;
+ for (int idx = 0; pow_types[idx].identifier; idx++) {
+ if (!strncmp(tok->args[0], pow_types[idx].identifier,
+ strlen(pow_types[idx].identifier))) {
+ pow_params->type = pow_types[idx].type;
+ match = 1;
+ break;
+ }
+ }
+ if (!match) {
+ log_warn(LD_REND, "Unknown PoW type from descriptor.");
+ goto done;
+ }
+
+ if (base64_decode((char *)pow_params->seed, sizeof(pow_params->seed),
+ tok->args[1], strlen(tok->args[1])) !=
+ sizeof(pow_params->seed)) {
+ log_warn(LD_REND, "Unparseable seed %s in PoW params",
+ escaped(tok->args[1]));
+ goto done;
+ }
+
+ int ok;
+ unsigned long effort =
+ tor_parse_ulong(tok->args[2], 10, 0, UINT32_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Unparseable suggested effort %s in PoW params",
+ escaped(tok->args[2]));
+ goto done;
+ }
+ pow_params->suggested_effort = (uint32_t)effort;
+
+ /* Parse the expiration time of the PoW params. */
+ time_t expiration_time = 0;
+ if (parse_iso_time_nospace(tok->args[3], &expiration_time)) {
+ log_warn(LD_REND, "Unparseable expiration time %s in PoW params",
+ escaped(tok->args[3]));
+ goto done;
+ }
+ /* Validation of this time is done in client_desc_has_arrived() so we can
+ * trigger a fetch if expired. */
+ pow_params->expiration_time = expiration_time;
+
+ /* Success. */
+ ret = 0;
+
+ done:
+ return ret;
+}
+
/** Decode descriptor plaintext data for version 3. Given a list of tokens, an
* allocated plaintext object that will be populated and the encoded
* descriptor with its length. The last one is needed for signature
@@ -2364,6 +2473,18 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc,
desc_encrypted_out->sendme_inc = sendme_inc;
}
+ /* Get PoW if any. */
+ tok = find_opt_by_keyword(tokens, R3_POW_PARAMS);
+ if (tok) {
+ hs_pow_desc_params_t *pow_params =
+ tor_malloc_zero(sizeof(hs_pow_desc_params_t));
+ if (decode_pow_params(tok, pow_params)) {
+ tor_free(pow_params);
+ goto err;
+ }
+ desc_encrypted_out->pow_params = pow_params;
+ }
+
/* Initialize the descriptor's introduction point list before we start
* decoding. Having 0 intro point is valid. Then decode them all. */
desc_encrypted_out->intro_points = smartlist_new();
@@ -2704,9 +2825,15 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
}
/* Try to decode what we just encoded. Symmetry is nice!, but it is
- * symmetric only if the client auth is disabled. That is, the descriptor
- * cookie will be NULL. */
- if (!descriptor_cookie) {
+ * symmetric only if the client auth is disabled (That is, the descriptor
+ * cookie will be NULL) and the test-only mock plaintext isn't in use. */
+ bool do_round_trip_test = !descriptor_cookie;
+#ifdef TOR_UNIT_TESTS
+ if (desc->encrypted_data.test_extra_plaintext) {
+ do_round_trip_test = false;
+ }
+#endif
+ if (do_round_trip_test) {
ret = hs_desc_decode_descriptor(*encoded_out, &desc->subcredential,
NULL, NULL);
if (BUG(ret != HS_DESC_DECODE_OK)) {
@@ -2776,6 +2903,7 @@ hs_desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
smartlist_free(desc->intro_points);
}
tor_free(desc->flow_control_pv);
+ tor_free(desc->pow_params);
memwipe(desc, 0, sizeof(*desc));
}
diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h
index 8ae16825d2..22517470c1 100644
--- a/src/feature/hs/hs_descriptor.h
+++ b/src/feature/hs/hs_descriptor.h
@@ -15,6 +15,7 @@
#include "trunnel/ed25519_cert.h" /* needed for trunnel */
#include "feature/nodelist/torcert.h"
#include "core/crypto/hs_ntor.h" /* for hs_subcredential_t */
+#include "feature/hs/hs_pow.h"
/* Trunnel */
struct link_specifier_t;
@@ -171,8 +172,18 @@ typedef struct hs_desc_encrypted_data_t {
char *flow_control_pv;
uint8_t sendme_inc;
+ /** PoW parameters. If NULL, it is not present. */
+ hs_pow_desc_params_t *pow_params;
+
/** A list of intro points. Contains hs_desc_intro_point_t objects. */
smartlist_t *intro_points;
+
+#ifdef TOR_UNIT_TESTS
+ /** In unit tests only, we can include additional arbitrary plaintext.
+ * This is used to test parser validation by adding invalid inner data to
+ * descriptors that are otherwise correct and correctly encrypted. */
+ const char *test_extra_plaintext;
+#endif
} hs_desc_encrypted_data_t;
/** The superencrypted data section of a descriptor. Obviously the data in
diff --git a/src/feature/hs/hs_dos.c b/src/feature/hs/hs_dos.c
index 6323dbeeac..80ad3b1daa 100644
--- a/src/feature/hs/hs_dos.c
+++ b/src/feature/hs/hs_dos.c
@@ -28,6 +28,7 @@
#include "feature/relay/routermode.h"
#include "lib/evloop/token_bucket.h"
+#include "lib/time/compat_time.h"
#include "feature/hs/hs_dos.h"
@@ -143,7 +144,7 @@ hs_dos_setup_default_intro2_defenses(or_circuit_t *circ)
token_bucket_ctr_init(&circ->introduce2_bucket,
consensus_param_introduce_rate_per_sec,
consensus_param_introduce_burst_per_sec,
- (uint32_t) approx_time());
+ (uint32_t) monotime_coarse_absolute_sec());
}
/** Called when the consensus has changed. We might have new consensus
@@ -188,7 +189,7 @@ hs_dos_can_send_intro2(or_circuit_t *s_intro_circ)
/* Refill INTRODUCE2 bucket. */
token_bucket_ctr_refill(&s_intro_circ->introduce2_bucket,
- (uint32_t) approx_time());
+ (uint32_t) monotime_coarse_absolute_sec());
/* Decrement the bucket for this valid INTRODUCE1 cell we just got. Don't
* underflow else we end up with a too big of a bucket. */
diff --git a/src/feature/hs/hs_intropoint.c b/src/feature/hs/hs_intropoint.c
index 0a656b78dd..02b5b4866b 100644
--- a/src/feature/hs/hs_intropoint.c
+++ b/src/feature/hs/hs_intropoint.c
@@ -15,8 +15,10 @@
#include "core/or/circuituse.h"
#include "core/or/relay.h"
#include "feature/rend/rendmid.h"
+#include "feature/relay/relay_metrics.h"
#include "feature/stats/rephist.h"
#include "lib/crypt_ops/crypto_format.h"
+#include "lib/time/compat_time.h"
/* Trunnel */
#include "trunnel/ed25519_cert.h"
@@ -316,7 +318,7 @@ handle_establish_intro_cell_dos_extension(
token_bucket_ctr_init(&circ->introduce2_bucket,
(uint32_t) intro2_rate_per_sec,
(uint32_t) intro2_burst_per_sec,
- (uint32_t) approx_time());
+ (uint32_t) monotime_coarse_absolute_sec());
log_info(LD_REND, "Intro point DoS defenses enabled. Rate is %" PRIu64
" and Burst is %" PRIu64,
intro2_rate_per_sec, intro2_burst_per_sec);
@@ -418,6 +420,7 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request,
/* Check that the circuit is in shape to become an intro point */
if (!hs_intro_circuit_is_suitable_for_establish_intro(circ)) {
+ relay_increment_est_intro_action(EST_INTRO_UNSUITABLE_CIRCUIT);
goto err;
}
@@ -425,6 +428,7 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request,
ssize_t parsing_result = trn_cell_establish_intro_parse(&parsed_cell,
request, request_len);
if (parsing_result < 0) {
+ relay_increment_est_intro_action(EST_INTRO_MALFORMED);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Rejecting %s ESTABLISH_INTRO cell.",
parsing_result == -1 ? "invalid" : "truncated");
@@ -435,6 +439,7 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request,
(uint8_t *) circ->rend_circ_nonce,
sizeof(circ->rend_circ_nonce));
if (cell_ok < 0) {
+ relay_increment_est_intro_action(EST_INTRO_MALFORMED);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Failed to verify ESTABLISH_INTRO cell.");
goto err;
@@ -443,9 +448,11 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request,
/* This cell is legit. Take the appropriate actions. */
cell_ok = handle_verified_establish_intro_cell(circ, parsed_cell);
if (cell_ok < 0) {
+ relay_increment_est_intro_action(EST_INTRO_CIRCUIT_DEAD);
goto err;
}
+ relay_increment_est_intro_action(EST_INTRO_SUCCESS);
/* We are done! */
retval = 0;
goto done;
@@ -504,6 +511,7 @@ hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request,
tor_assert(request);
if (request_len == 0) {
+ relay_increment_est_intro_action(EST_INTRO_MALFORMED);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Empty ESTABLISH_INTRO cell.");
goto err;
}
@@ -516,10 +524,12 @@ hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request,
case TRUNNEL_HS_INTRO_AUTH_KEY_TYPE_LEGACY1:
/* Likely version 2 onion service which is now obsolete. Avoid a
* protocol warning considering they still exists on the network. */
+ relay_increment_est_intro_action(EST_INTRO_MALFORMED);
goto err;
case TRUNNEL_HS_INTRO_AUTH_KEY_TYPE_ED25519:
return handle_establish_intro(circ, request, request_len);
default:
+ relay_increment_est_intro_action(EST_INTRO_MALFORMED);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Unrecognized AUTH_KEY_TYPE %u.", first_byte);
goto err;
@@ -643,6 +653,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
ssize_t cell_size = trn_cell_introduce1_parse(&parsed_cell, request,
request_len);
if (cell_size < 0) {
+ relay_increment_intro1_action(INTRO1_MALFORMED);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Rejecting %s INTRODUCE1 cell. Responding with NACK.",
cell_size == -1 ? "invalid" : "truncated");
@@ -653,6 +664,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
/* Once parsed validate the cell format. */
if (validate_introduce1_parsed_cell(parsed_cell) < 0) {
+ relay_increment_intro1_action(INTRO1_MALFORMED);
/* Inform client that the INTRODUCE1 has bad format. */
status = TRUNNEL_HS_INTRO_ACK_STATUS_BAD_FORMAT;
goto send_ack;
@@ -664,6 +676,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
get_auth_key_from_cell(&auth_key, RELAY_COMMAND_INTRODUCE1, parsed_cell);
service_circ = hs_circuitmap_get_intro_circ_v3_relay_side(&auth_key);
if (service_circ == NULL) {
+ relay_increment_intro1_action(INTRO1_UNKNOWN_SERVICE);
char b64_key[ED25519_BASE64_LEN + 1];
ed25519_public_to_base64(b64_key, &auth_key);
log_info(LD_REND, "No intro circuit found for INTRODUCE1 cell "
@@ -679,6 +692,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
/* Before sending, lets make sure this cell can be sent on the service
* circuit asking the DoS defenses. */
if (!hs_dos_can_send_intro2(service_circ)) {
+ relay_increment_intro1_action(INTRO1_RATE_LIMITED);
char *msg;
static ratelim_t rlimit = RATELIM_INIT(5 * 60);
if ((msg = rate_limit_log(&rlimit, approx_time()))) {
@@ -695,6 +709,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(service_circ),
RELAY_COMMAND_INTRODUCE2,
(char *) request, request_len, NULL)) {
+ relay_increment_intro1_action(INTRO1_CIRCUIT_DEAD);
log_warn(LD_PROTOCOL, "Unable to send INTRODUCE2 cell to the service.");
/* Inform the client that we can't relay the cell. Use the unknown ID
* status code since it means that we do not know the service. */
@@ -702,6 +717,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
goto send_ack;
}
+ relay_increment_intro1_action(INTRO1_SUCCESS);
/* Success! Send an INTRODUCE_ACK success status onto the client circuit. */
status = TRUNNEL_HS_INTRO_ACK_STATUS_SUCCESS;
ret = 0;
@@ -732,6 +748,7 @@ circuit_is_suitable_for_introduce1(const or_circuit_t *circ)
}
if (circ->already_received_introduce1) {
+ relay_increment_intro1_action(INTRO1_CIRCUIT_REUSED);
log_fn(LOG_PROTOCOL_WARN, LD_REND,
"Blocking multiple introductions on the same circuit. "
"Someone might be trying to attack a hidden service through "
@@ -741,6 +758,7 @@ circuit_is_suitable_for_introduce1(const or_circuit_t *circ)
/* Disallow single hop client circuit. */
if (circ->p_chan && channel_is_client(circ->p_chan)) {
+ relay_increment_intro1_action(INTRO1_SINGLE_HOP);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Single hop client was rejected while trying to introduce. "
"Closing circuit.");
@@ -762,6 +780,7 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
/* A cell that can't hold a DIGEST_LEN is invalid. */
if (request_len < DIGEST_LEN) {
+ relay_increment_intro1_action(INTRO1_MALFORMED);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Invalid INTRODUCE1 cell length.");
goto err;
}
diff --git a/src/feature/hs/hs_metrics.c b/src/feature/hs/hs_metrics.c
index e80d98c2dd..19a330a01e 100644
--- a/src/feature/hs/hs_metrics.c
+++ b/src/feature/hs/hs_metrics.c
@@ -13,6 +13,7 @@
#include "lib/malloc/malloc.h"
#include "lib/container/smartlist.h"
#include "lib/metrics/metrics_store.h"
+#include "lib/log/util_bug.h"
#include "feature/hs/hs_metrics.h"
#include "feature/hs/hs_metrics_entry.h"
@@ -29,51 +30,125 @@ port_to_str(const uint16_t port)
return buf;
}
-/** Initialize a metrics store for the given service.
+/** Add a new metric to the metrics store of the service.
*
- * Essentially, this goes over the base_metrics array and adds them all to the
- * store set with their label(s) if any. */
+ * <b>metric</b> is the index of the metric in the <b>base_metrics</b> array.
+ */
static void
-init_store(hs_service_t *service)
+add_metric_with_labels(hs_service_t *service, hs_metrics_key_t metric,
+ bool port_as_label, uint16_t port)
{
metrics_store_t *store;
+ const char **error_reasons = NULL;
+ size_t num_error_reasons = 0;
tor_assert(service);
+ if (BUG(metric >= base_metrics_size))
+ return;
+
store = service->metrics.store;
+ /* Check whether the current metric is an error metric, because error metrics
+ * require an additional `reason` label. */
+ switch (metric) {
+ case HS_METRICS_NUM_REJECTED_INTRO_REQ:
+ error_reasons = hs_metrics_intro_req_error_reasons;
+ num_error_reasons = hs_metrics_intro_req_error_reasons_size;
+ break;
+ case HS_METRICS_NUM_FAILED_RDV:
+ error_reasons = hs_metrics_rend_error_reasons;
+ num_error_reasons = hs_metrics_rend_error_reasons_size;
+ break;
+ /* Fall through for all other metrics, as they don't need a
+ * reason label. */
+ case HS_METRICS_NUM_INTRODUCTIONS: FALLTHROUGH;
+ case HS_METRICS_APP_WRITE_BYTES: FALLTHROUGH;
+ case HS_METRICS_APP_READ_BYTES: FALLTHROUGH;
+ case HS_METRICS_NUM_ESTABLISHED_RDV: FALLTHROUGH;
+ case HS_METRICS_NUM_RDV: FALLTHROUGH;
+ case HS_METRICS_NUM_ESTABLISHED_INTRO: FALLTHROUGH;
+ case HS_METRICS_POW_NUM_PQUEUE_RDV: FALLTHROUGH;
+ case HS_METRICS_POW_SUGGESTED_EFFORT: FALLTHROUGH;
+ case HS_METRICS_INTRO_CIRC_BUILD_TIME: FALLTHROUGH;
+ case HS_METRICS_REND_CIRC_BUILD_TIME: FALLTHROUGH;
+ default:
+ break;
+ }
+
+ /* We don't need a reason label for this metric */
+ if (!num_error_reasons) {
+ metrics_store_entry_t *entry = metrics_store_add(
+ store, base_metrics[metric].type, base_metrics[metric].name,
+ base_metrics[metric].help, base_metrics[metric].bucket_count,
+ base_metrics[metric].buckets);
+
+ metrics_store_entry_add_label(entry,
+ metrics_format_label("onion", service->onion_address));
+
+ if (port_as_label) {
+ metrics_store_entry_add_label(entry,
+ metrics_format_label("port", port_to_str(port)));
+ }
+
+ return;
+ }
+
+ tor_assert(error_reasons);
+
+ /* Add entries with reason as label. We need one metric line per
+ * reason. */
+ for (size_t i = 0; i < num_error_reasons; ++i) {
+ metrics_store_entry_t *entry =
+ metrics_store_add(store, base_metrics[metric].type,
+ base_metrics[metric].name,
+ base_metrics[metric].help,
+ base_metrics[metric].bucket_count,
+ base_metrics[metric].buckets);
+ /* Add labels to the entry. */
+ metrics_store_entry_add_label(entry,
+ metrics_format_label("onion", service->onion_address));
+ metrics_store_entry_add_label(entry,
+ metrics_format_label("reason", error_reasons[i]));
+
+ if (port_as_label) {
+ metrics_store_entry_add_label(entry,
+ metrics_format_label("port", port_to_str(port)));
+ }
+ }
+}
+
+/** Initialize a metrics store for the given service.
+ *
+ * Essentially, this goes over the base_metrics array and adds them all to the
+ * store set with their label(s) if any. */
+static void
+init_store(hs_service_t *service)
+{
+ tor_assert(service);
+
for (size_t i = 0; i < base_metrics_size; ++i) {
/* Add entries with port as label. We need one metric line per port. */
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_t *entry =
- metrics_store_add(store, base_metrics[i].type, base_metrics[i].name,
- base_metrics[i].help);
-
- /* Add labels to the entry. */
- metrics_store_entry_add_label(entry,
- metrics_format_label("onion", service->onion_address));
- metrics_store_entry_add_label(entry,
- metrics_format_label("port", port_to_str(p->virtual_port)));
+ add_metric_with_labels(service, base_metrics[i].key, true,
+ p->virtual_port);
} SMARTLIST_FOREACH_END(p);
} else {
- metrics_store_entry_t *entry =
- metrics_store_add(store, base_metrics[i].type, base_metrics[i].name,
- base_metrics[i].help);
- metrics_store_entry_add_label(entry,
- metrics_format_label("onion", service->onion_address));
+ add_metric_with_labels(service, base_metrics[i].key, false, 0);
}
}
}
/** Update the metrics key entry in the store in the given service. The port,
- * if non 0, is used to find the correct metrics entry. The value n is the
- * value used to update the entry. */
+ * if non 0, and the reason label, if non NULL, are used to find the correct
+ * metrics entry. The value n is the value used to update the entry. */
void
hs_metrics_update_by_service(const hs_metrics_key_t key,
- hs_service_t *service, const uint16_t port,
- int64_t n)
+ const hs_service_t *service,
+ uint16_t port, const char *reason,
+ int64_t n, int64_t obs, bool reset)
{
tor_assert(service);
@@ -89,25 +164,38 @@ hs_metrics_update_by_service(const hs_metrics_key_t key,
* XXX: This is not the most optimal due to the string format. Maybe at some
* point turn this into a kvline and a map in a metric entry? */
SMARTLIST_FOREACH_BEGIN(entries, metrics_store_entry_t *, entry) {
- if (port == 0 ||
- metrics_store_entry_has_label(entry,
- metrics_format_label("port", port_to_str(port)))) {
- metrics_store_entry_update(entry, n);
+ if ((port == 0 ||
+ metrics_store_entry_has_label(
+ entry, metrics_format_label("port", port_to_str(port)))) &&
+ ((!reason || metrics_store_entry_has_label(
+ entry, metrics_format_label("reason", reason))))) {
+ if (reset) {
+ metrics_store_entry_reset(entry);
+ }
+
+ if (metrics_store_entry_is_histogram(entry)) {
+ metrics_store_hist_entry_update(entry, n, obs);
+ } else {
+ metrics_store_entry_update(entry, n);
+ }
+
break;
}
} SMARTLIST_FOREACH_END(entry);
}
/** Update the metrics key entry in the store of a service identified by the
- * given identity public key. The port, if non 0, is used to find the correct
- * metrics entry. The value n is the value used to update the entry.
+ * given identity public key. The port, if non 0, and the reason label, if non
+ * NULL, are used to find the correct metrics entry. The value n is the value
+ * used to update the entry.
*
* This is used by callsite that have access to the key but not the service
* object so an extra lookup is done to find the service. */
void
hs_metrics_update_by_ident(const hs_metrics_key_t key,
const ed25519_public_key_t *ident_pk,
- const uint16_t port, int64_t n)
+ const uint16_t port, const char *reason,
+ int64_t n, int64_t obs, bool reset)
{
hs_service_t *service;
@@ -121,7 +209,7 @@ hs_metrics_update_by_ident(const hs_metrics_key_t key,
* service and thus the only way to know is to lookup the service. */
return;
}
- hs_metrics_update_by_service(key, service, port, n);
+ hs_metrics_update_by_service(key, service, port, reason, n, obs, reset);
}
/** Return a list of all the onion service metrics stores. This is the
diff --git a/src/feature/hs/hs_metrics.h b/src/feature/hs/hs_metrics.h
index 2e0fa5048d..f2e5dbd9ec 100644
--- a/src/feature/hs/hs_metrics.h
+++ b/src/feature/hs/hs_metrics.h
@@ -26,45 +26,87 @@ const smartlist_t *hs_metrics_get_stores(void);
/* Metrics Update. */
void hs_metrics_update_by_ident(const hs_metrics_key_t key,
const ed25519_public_key_t *ident_pk,
- const uint16_t port, int64_t n);
+ const uint16_t port, const char *reason,
+ int64_t n, int64_t obs, bool reset);
void hs_metrics_update_by_service(const hs_metrics_key_t key,
- hs_service_t *service, const uint16_t port,
- int64_t n);
+ const hs_service_t *service,
+ uint16_t port, const char *reason,
+ int64_t n, int64_t obs, bool reset);
/** New introducion request received. */
-#define hs_metrics_new_introduction(s) \
- hs_metrics_update_by_service(HS_METRICS_NUM_INTRODUCTIONS, (s), 0, 1)
+#define hs_metrics_new_introduction(s) \
+ hs_metrics_update_by_service(HS_METRICS_NUM_INTRODUCTIONS, (s), \
+ 0, NULL, 1, 0, false)
+
+/** Introducion request rejected. */
+#define hs_metrics_reject_intro_req(s, reason) \
+ hs_metrics_update_by_service(HS_METRICS_NUM_REJECTED_INTRO_REQ, (s), 0, \
+ (reason), 1, 0, false)
/** Number of bytes written to the application from the service. */
-#define hs_metrics_app_write_bytes(i, port, n) \
- hs_metrics_update_by_ident(HS_METRICS_APP_WRITE_BYTES, (i), (port), (n))
+#define hs_metrics_app_write_bytes(i, port, n) \
+ hs_metrics_update_by_ident(HS_METRICS_APP_WRITE_BYTES, (i), (port), NULL, \
+ (n), 0, false)
/** Number of bytes read from the application to the service. */
-#define hs_metrics_app_read_bytes(i, port, n) \
- hs_metrics_update_by_ident(HS_METRICS_APP_READ_BYTES, (i), (port), (n))
+#define hs_metrics_app_read_bytes(i, port, n) \
+ hs_metrics_update_by_ident(HS_METRICS_APP_READ_BYTES, (i), \
+ (port), NULL, (n), 0, false)
/** Newly established rendezvous. This is called as soon as the circuit purpose
* is REND_JOINED which is when the RENDEZVOUS2 cell is sent. */
-#define hs_metrics_new_established_rdv(s) \
- hs_metrics_update_by_service(HS_METRICS_NUM_ESTABLISHED_RDV, (s), 0, 1)
+#define hs_metrics_new_established_rdv(s) \
+ hs_metrics_update_by_service(HS_METRICS_NUM_ESTABLISHED_RDV, (s), \
+ 0, NULL, 1, 0, false)
+
+/** New rendezvous circuit failure. */
+#define hs_metrics_failed_rdv(i, reason) \
+ hs_metrics_update_by_ident(HS_METRICS_NUM_FAILED_RDV, (i), \
+ 0, (reason), 1, 0, false)
/** Established rendezvous closed. This is called when the circuit in
* REND_JOINED state is marked for close. */
-#define hs_metrics_close_established_rdv(i) \
- hs_metrics_update_by_ident(HS_METRICS_NUM_ESTABLISHED_RDV, (i), 0, -1)
+#define hs_metrics_close_established_rdv(i) \
+ hs_metrics_update_by_ident(HS_METRICS_NUM_ESTABLISHED_RDV, (i), \
+ 0, NULL, -1, 0, false)
/** New rendezvous circuit being launched. */
#define hs_metrics_new_rdv(i) \
- hs_metrics_update_by_ident(HS_METRICS_NUM_RDV, (i), 0, 1)
+ hs_metrics_update_by_ident(HS_METRICS_NUM_RDV, (i), 0, NULL, 1, 0, false)
+
+/** Update depth of rendezvous pqueue any time new work is enqueued. */
+#define hs_metrics_pow_pqueue_rdv(s, n) \
+ hs_metrics_update_by_service(HS_METRICS_POW_NUM_PQUEUE_RDV, (s), 0, \
+ NULL, (n), 0, true)
+
+/** Update the suggested effort we include in proof-of-work state */
+#define hs_metrics_pow_suggested_effort(s, n) \
+ hs_metrics_update_by_service(HS_METRICS_POW_SUGGESTED_EFFORT, (s), 0, \
+ NULL, (n), 0, true)
/** New introduction circuit has been established. This is called when the
* INTRO_ESTABLISHED has been received by the service. */
-#define hs_metrics_new_established_intro(s) \
- hs_metrics_update_by_service(HS_METRICS_NUM_ESTABLISHED_INTRO, (s), 0, 1)
+#define hs_metrics_new_established_intro(s) \
+ hs_metrics_update_by_service(HS_METRICS_NUM_ESTABLISHED_INTRO, (s), 0, \
+ NULL, 1, 0, false)
/** Established introduction circuit closes. This is called when
* INTRO_ESTABLISHED circuit is marked for close. */
-#define hs_metrics_close_established_intro(i) \
- hs_metrics_update_by_ident(HS_METRICS_NUM_ESTABLISHED_INTRO, (i), 0, -1)
+#define hs_metrics_close_established_intro(i) \
+ hs_metrics_update_by_ident(HS_METRICS_NUM_ESTABLISHED_INTRO, (i), 0, NULL, \
+ -1, 0, false)
+
+/** Record an introduction circuit build time duration. This is called
+ * when the INTRO_ESTABLISHED has been received by the service. */
+#define hs_metrics_intro_circ_build_time(s, obs) \
+ hs_metrics_update_by_service(HS_METRICS_INTRO_CIRC_BUILD_TIME, (s), 0, \
+ NULL, 1, obs, false)
+
+/** Record a rendezvous circuit build time duration. This is called as soon as
+ * the circuit purpose is REND_JOINED which is when the RENDEZVOUS2 cell is
+ * sent. */
+#define hs_metrics_rdv_circ_build_time(s, obs) \
+ hs_metrics_update_by_service(HS_METRICS_REND_CIRC_BUILD_TIME, (s), 0, NULL, \
+ 1, obs, false)
#endif /* !defined(TOR_FEATURE_HS_HS_METRICS_H) */
diff --git a/src/feature/hs/hs_metrics_entry.c b/src/feature/hs/hs_metrics_entry.c
index 46d2d88aca..d862d0adec 100644
--- a/src/feature/hs/hs_metrics_entry.c
+++ b/src/feature/hs/hs_metrics_entry.c
@@ -8,12 +8,37 @@
#define HS_METRICS_ENTRY_PRIVATE
+#include <stddef.h>
+
#include "orconfig.h"
#include "lib/cc/compat_compiler.h"
+#include "lib/log/log.h"
+#include "lib/log/util_bug.h"
#include "feature/hs/hs_metrics_entry.h"
+/* Histogram time buckets (in milliseconds). */
+static const int64_t hs_metrics_circ_build_time_buckets[] =
+{
+ 1000, /* 1s */
+ 5000, /* 5s */
+ 10000, /* 10s */
+ 30000, /* 30s */
+ 60000 /* 60s */
+};
+
+// TODO: Define a constant for ARRAY_LENGTH(hs_metrics_circ_build_time_buckets)
+// and use where it applicable.
+//
+// This is commented out because it doesn't compile with gcc versions < 8.1
+// or with MSVC ("initializer element is not constant").
+//
+// See ticket#40773 and https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69960#c18
+//
+/*static const size_t hs_metrics_circ_build_time_buckets_size =*/
+/*ARRAY_LENGTH(hs_metrics_circ_build_time_buckets);*/
+
/** The base metrics that is a static array of metrics that are added to every
* single new stores.
*
@@ -45,13 +70,19 @@ const hs_metrics_entry_t base_metrics[] =
.key = HS_METRICS_NUM_ESTABLISHED_RDV,
.type = METRICS_TYPE_GAUGE,
.name = METRICS_NAME(hs_rdv_established_count),
- .help = "Total number of established rendezvous circuit",
+ .help = "Total number of established rendezvous circuits",
},
{
.key = HS_METRICS_NUM_RDV,
.type = METRICS_TYPE_COUNTER,
.name = METRICS_NAME(hs_rdv_num_total),
- .help = "Total number of rendezvous circuit created",
+ .help = "Total number of rendezvous circuits created",
+ },
+ {
+ .key = HS_METRICS_NUM_FAILED_RDV,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(hs_rdv_error_count),
+ .help = "Total number of rendezvous circuit errors",
},
{
.key = HS_METRICS_NUM_ESTABLISHED_INTRO,
@@ -59,7 +90,69 @@ const hs_metrics_entry_t base_metrics[] =
.name = METRICS_NAME(hs_intro_established_count),
.help = "Total number of established introduction circuit",
},
+ {
+ .key = HS_METRICS_NUM_REJECTED_INTRO_REQ,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(hs_intro_rejected_intro_req_count),
+ .help = "Total number of rejected introduction circuits",
+ },
+ {
+ .key = HS_METRICS_INTRO_CIRC_BUILD_TIME,
+ .type = METRICS_TYPE_HISTOGRAM,
+ .name = METRICS_NAME(hs_intro_circ_build_time),
+ .buckets = hs_metrics_circ_build_time_buckets,
+ .bucket_count = ARRAY_LENGTH(hs_metrics_circ_build_time_buckets),
+ .help = "The introduction circuit build time in milliseconds",
+ },
+ {
+ .key = HS_METRICS_REND_CIRC_BUILD_TIME,
+ .type = METRICS_TYPE_HISTOGRAM,
+ .name = METRICS_NAME(hs_rend_circ_build_time),
+ .buckets = hs_metrics_circ_build_time_buckets,
+ .bucket_count = ARRAY_LENGTH(hs_metrics_circ_build_time_buckets),
+ .help = "The rendezvous circuit build time in milliseconds",
+ },
+ {
+ .key = HS_METRICS_POW_NUM_PQUEUE_RDV,
+ .type = METRICS_TYPE_GAUGE,
+ .name = METRICS_NAME(hs_rdv_pow_pqueue_count),
+ .help = "Number of requests waiting in the proof of work priority queue",
+ },
+ {
+ .key = HS_METRICS_POW_SUGGESTED_EFFORT,
+ .type = METRICS_TYPE_GAUGE,
+ .name = METRICS_NAME(hs_pow_suggested_effort),
+ .help = "Suggested effort for requests with a proof of work client puzzle",
+ },
};
/** Size of base_metrics array that is number of entries. */
const size_t base_metrics_size = ARRAY_LENGTH(base_metrics);
+
+/** Possible values for the reason label of the
+ * hs_intro_rejected_intro_req_count metric. */
+const char *hs_metrics_intro_req_error_reasons[] =
+{
+ HS_METRICS_ERR_INTRO_REQ_BAD_AUTH_KEY,
+ HS_METRICS_ERR_INTRO_REQ_INTRODUCE2,
+ HS_METRICS_ERR_INTRO_REQ_SUBCREDENTIAL,
+ HS_METRICS_ERR_INTRO_REQ_INTRODUCE2_REPLAY,
+};
+
+/** The number of entries in the hs_metrics_intro_req_error_reasons array. */
+const size_t hs_metrics_intro_req_error_reasons_size =
+ ARRAY_LENGTH(hs_metrics_intro_req_error_reasons);
+
+/** Possible values for the reason label of the hs_rdv_error_count metric. */
+const char *hs_metrics_rend_error_reasons[] =
+{
+ HS_METRICS_ERR_RDV_RP_CONN_FAILURE,
+ HS_METRICS_ERR_RDV_PATH,
+ HS_METRICS_ERR_RDV_RENDEZVOUS1,
+ HS_METRICS_ERR_RDV_E2E,
+ HS_METRICS_ERR_RDV_RETRY,
+};
+
+/** The number of entries in the hs_metrics_rend_error_reasons array. */
+const size_t hs_metrics_rend_error_reasons_size =
+ ARRAY_LENGTH(hs_metrics_rend_error_reasons);
diff --git a/src/feature/hs/hs_metrics_entry.h b/src/feature/hs/hs_metrics_entry.h
index b9786ac6f7..1a1bc701ec 100644
--- a/src/feature/hs/hs_metrics_entry.h
+++ b/src/feature/hs/hs_metrics_entry.h
@@ -13,6 +13,33 @@
#include "lib/metrics/metrics_common.h"
+/* Possible values for the reason label of the
+ * hs_intro_rejected_intro_req_count metric. */
+/** The hidden service received an unknown introduction auth key. */
+#define HS_METRICS_ERR_INTRO_REQ_BAD_AUTH_KEY "bad_auth_key"
+/** The hidden service received a malformed INTRODUCE2 cell. */
+#define HS_METRICS_ERR_INTRO_REQ_INTRODUCE2 "invalid_introduce2"
+/** The hidden service does not have the necessary subcredential. */
+#define HS_METRICS_ERR_INTRO_REQ_SUBCREDENTIAL "subcredential"
+/** The hidden service received an INTRODUCE2 replay. */
+#define HS_METRICS_ERR_INTRO_REQ_INTRODUCE2_REPLAY "replay"
+
+/* Possible values for the reason label of the hs_rdv_error_count metric. */
+/** The hidden service failed to connect to the rendezvous point. */
+#define HS_METRICS_ERR_RDV_RP_CONN_FAILURE "rp_conn_failure"
+/** The hidden service failed to build a circuit to the rendezvous point due
+ * to an invalid selected path. */
+#define HS_METRICS_ERR_RDV_PATH "invalid_path"
+/** The hidden service failed to send the RENDEZVOUS1 cell on rendezvous
+ * circuit. */
+#define HS_METRICS_ERR_RDV_RENDEZVOUS1 "rendezvous1"
+/** The hidden service failed to set up an end-to-end rendezvous circuit to
+ * the client. */
+#define HS_METRICS_ERR_RDV_E2E "e2e_circ"
+/** The hidden service reattempted to connect to the rendezvous point by
+ * launching a new circuit to it, but failed */
+#define HS_METRICS_ERR_RDV_RETRY "retry"
+
/** Metrics key which are used as an index in the main base metrics array. */
typedef enum {
/** Number of introduction requests. */
@@ -21,12 +48,24 @@ typedef enum {
HS_METRICS_APP_WRITE_BYTES = 1,
/** Number of bytes read from application to onion service. */
HS_METRICS_APP_READ_BYTES = 2,
- /** Number of established rendezsvous. */
+ /** Number of established rendezvous. */
HS_METRICS_NUM_ESTABLISHED_RDV = 3,
- /** Number of rendezsvous circuits created. */
+ /** Number of rendezvous circuits created. */
HS_METRICS_NUM_RDV = 4,
+ /** Number of failed rendezvous. */
+ HS_METRICS_NUM_FAILED_RDV = 5,
/** Number of established introducton points. */
- HS_METRICS_NUM_ESTABLISHED_INTRO = 5,
+ HS_METRICS_NUM_ESTABLISHED_INTRO = 6,
+ /** Number of rejected introducton requests. */
+ HS_METRICS_NUM_REJECTED_INTRO_REQ = 7,
+ /** Introduction circuit build time in milliseconds. */
+ HS_METRICS_INTRO_CIRC_BUILD_TIME = 8,
+ /** Rendezvous circuit build time in milliseconds. */
+ HS_METRICS_REND_CIRC_BUILD_TIME = 9,
+ /** Number of requests waiting in the proof of work priority queue. */
+ HS_METRICS_POW_NUM_PQUEUE_RDV = 10,
+ /** Suggested effort for requests with a proof of work client puzzle. */
+ HS_METRICS_POW_SUGGESTED_EFFORT = 11,
} hs_metrics_key_t;
/** The metadata of an HS metrics. */
@@ -39,6 +78,10 @@ typedef struct hs_metrics_entry_t {
const char *name;
/* Metrics output help comment. */
const char *help;
+ /* The buckets, if the metric type is METRICS_TYPE_HISTOGRAM. */
+ const int64_t *buckets;
+ /* The number of buckets, if the metric type is METRICS_TYPE_HISTOGRAM. */
+ size_t bucket_count;
/* True iff a port label should be added to the metrics entry. */
bool port_as_label;
} hs_metrics_entry_t;
@@ -46,6 +89,11 @@ typedef struct hs_metrics_entry_t {
extern const hs_metrics_entry_t base_metrics[];
extern const size_t base_metrics_size;
-#endif /* defined(HS_METRICS_ENTRY_PRIVATE) */
+extern const char *hs_metrics_intro_req_error_reasons[];
+extern const size_t hs_metrics_intro_req_error_reasons_size;
+extern const char *hs_metrics_rend_error_reasons[];
+extern const size_t hs_metrics_rend_error_reasons_size;
+
+#endif /* defined(HS_METRICS_ENTRY_PRIVATE) */
#endif /* !defined(TOR_FEATURE_HS_METRICS_ENTRY_H) */
diff --git a/src/feature/hs/hs_options.inc b/src/feature/hs/hs_options.inc
index d3ca688b46..4ec62d592b 100644
--- a/src/feature/hs/hs_options.inc
+++ b/src/feature/hs/hs_options.inc
@@ -31,5 +31,8 @@ CONF_VAR(HiddenServiceEnableIntroDoSDefense, BOOL, 0, "0")
CONF_VAR(HiddenServiceEnableIntroDoSRatePerSec, POSINT, 0, "25")
CONF_VAR(HiddenServiceEnableIntroDoSBurstPerSec, POSINT, 0, "200")
CONF_VAR(HiddenServiceOnionBalanceInstance, BOOL, 0, "0")
+CONF_VAR(HiddenServicePoWDefensesEnabled, BOOL, 0, "0")
+CONF_VAR(HiddenServicePoWQueueRate, POSINT, 0, "250")
+CONF_VAR(HiddenServicePoWQueueBurst, POSINT, 0, "2500")
END_CONF_STRUCT(hs_opts_t)
diff --git a/src/feature/hs/hs_pow.c b/src/feature/hs/hs_pow.c
new file mode 100644
index 0000000000..5cee3b00d7
--- /dev/null
+++ b/src/feature/hs/hs_pow.c
@@ -0,0 +1,569 @@
+/* Copyright (c) 2017-2020, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_pow.c
+ * \brief Contains code to handle proof-of-work computations
+ * when a hidden service is defending against DoS attacks.
+ **/
+
+#include <stdio.h>
+
+#include "core/or/or.h"
+#include "app/config/config.h"
+#include "ext/ht.h"
+#include "ext/compat_blake2.h"
+#include "core/or/circuitlist.h"
+#include "core/or/origin_circuit_st.h"
+#include "ext/equix/include/equix.h"
+#include "feature/hs/hs_cache.h"
+#include "feature/hs/hs_descriptor.h"
+#include "feature/hs/hs_circuitmap.h"
+#include "feature/hs/hs_client.h"
+#include "feature/hs/hs_pow.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "lib/crypt_ops/crypto_format.h"
+#include "lib/arch/bytes.h"
+#include "lib/cc/ctassert.h"
+#include "core/mainloop/cpuworker.h"
+#include "lib/evloop/workqueue.h"
+#include "lib/time/compat_time.h"
+
+/** Replay cache set up */
+/** Cache entry for (nonce, seed) replay protection. */
+typedef struct nonce_cache_entry_t {
+ HT_ENTRY(nonce_cache_entry_t) node;
+ struct {
+ uint8_t nonce[HS_POW_NONCE_LEN];
+ uint8_t seed_head[HS_POW_SEED_HEAD_LEN];
+ } bytes;
+} nonce_cache_entry_t;
+
+/** Return true if the two (nonce, seed) replay cache entries are the same */
+static inline int
+nonce_cache_entries_eq_(const struct nonce_cache_entry_t *entry1,
+ const struct nonce_cache_entry_t *entry2)
+{
+ return fast_memeq(&entry1->bytes, &entry2->bytes, sizeof entry1->bytes);
+}
+
+/** Hash function to hash the (nonce, seed) tuple entry. */
+static inline unsigned
+nonce_cache_entry_hash_(const struct nonce_cache_entry_t *ent)
+{
+ return (unsigned)siphash24g(&ent->bytes, sizeof ent->bytes);
+}
+
+static HT_HEAD(nonce_cache_table_ht, nonce_cache_entry_t)
+ nonce_cache_table = HT_INITIALIZER();
+
+HT_PROTOTYPE(nonce_cache_table_ht, nonce_cache_entry_t, node,
+ nonce_cache_entry_hash_, nonce_cache_entries_eq_);
+
+HT_GENERATE2(nonce_cache_table_ht, nonce_cache_entry_t, node,
+ nonce_cache_entry_hash_, nonce_cache_entries_eq_, 0.6,
+ tor_reallocarray_, tor_free_);
+
+/** This is a callback used to check replay cache entries against a provided
+ * seed head, or NULL to operate on the entire cache. Matching entries return
+ * 1 and their internal cache entry is freed, non-matching entries return 0. */
+static int
+nonce_cache_entry_match_seed_and_free(nonce_cache_entry_t *ent, void *data)
+{
+ if (data == NULL ||
+ fast_memeq(ent->bytes.seed_head, data, HS_POW_SEED_HEAD_LEN)) {
+ tor_free(ent);
+ return 1;
+ }
+ return 0;
+}
+
+/** Helper: Increment a given nonce and set it in the challenge at the right
+ * offset. Use by the solve function. */
+static inline void
+increment_and_set_nonce(uint8_t *nonce, uint8_t *challenge)
+{
+ for (unsigned i = 0; i < HS_POW_NONCE_LEN; i++) {
+ uint8_t prev = nonce[i];
+ if (++nonce[i] > prev) {
+ break;
+ }
+ }
+ memcpy(challenge + HS_POW_NONCE_OFFSET, nonce, HS_POW_NONCE_LEN);
+}
+
+/* Helper: Build EquiX challenge (P || ID || C || N || INT_32(E)) and return
+ * a newly allocated buffer containing it. */
+static uint8_t *
+build_equix_challenge(const ed25519_public_key_t *blinded_id,
+ const uint8_t *seed, const uint8_t *nonce,
+ const uint32_t effort)
+{
+ size_t offset = 0;
+ uint8_t *challenge = tor_malloc_zero(HS_POW_CHALLENGE_LEN);
+
+ CTASSERT(HS_POW_ID_LEN == sizeof *blinded_id);
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(blinded_id));
+
+ log_debug(LD_REND,
+ "Constructing EquiX challenge with "
+ "blinded service id %s, effort: %d",
+ safe_str_client(ed25519_fmt(blinded_id)),
+ effort);
+
+ memcpy(challenge + offset, HS_POW_PSTRING, HS_POW_PSTRING_LEN);
+ offset += HS_POW_PSTRING_LEN;
+ memcpy(challenge + offset, blinded_id, HS_POW_ID_LEN);
+ offset += HS_POW_ID_LEN;
+ memcpy(challenge + offset, seed, HS_POW_SEED_LEN);
+ offset += HS_POW_SEED_LEN;
+ tor_assert(HS_POW_NONCE_OFFSET == offset);
+ memcpy(challenge + offset, nonce, HS_POW_NONCE_LEN);
+ offset += HS_POW_NONCE_LEN;
+ set_uint32(challenge + offset, tor_htonl(effort));
+ offset += HS_POW_EFFORT_LEN;
+ tor_assert(HS_POW_CHALLENGE_LEN == offset);
+
+ return challenge;
+}
+
+/** Helper: Return true iff the given challenge and solution for the given
+ * effort do validate as in: R * E <= UINT32_MAX. */
+static bool
+validate_equix_challenge(const uint8_t *challenge,
+ const uint8_t *solution_bytes,
+ const uint32_t effort)
+{
+ /* Fail if R * E > UINT32_MAX. */
+ uint8_t hash_result[HS_POW_HASH_LEN];
+ blake2b_state b2_state;
+
+ if (BUG(blake2b_init(&b2_state, HS_POW_HASH_LEN) < 0)) {
+ return false;
+ }
+
+ /* Construct: blake2b(C || N || E || S) */
+ blake2b_update(&b2_state, challenge, HS_POW_CHALLENGE_LEN);
+ blake2b_update(&b2_state, solution_bytes, HS_POW_EQX_SOL_LEN);
+ blake2b_final(&b2_state, hash_result, HS_POW_HASH_LEN);
+
+ /* Scale to 64 bit so we can avoid 32 bit overflow. */
+ uint64_t RE = tor_htonl(get_uint32(hash_result)) * (uint64_t) effort;
+
+ return RE <= UINT32_MAX;
+}
+
+/** Helper: Convert equix_solution to a byte array in little-endian order */
+static void
+pack_equix_solution(const equix_solution *sol_in,
+ uint8_t *bytes_out)
+{
+ for (unsigned i = 0; i < EQUIX_NUM_IDX; i++) {
+ bytes_out[i*2+0] = (uint8_t)sol_in->idx[i];
+ bytes_out[i*2+1] = (uint8_t)(sol_in->idx[i] >> 8);
+ }
+}
+
+/** Helper: Build an equix_solution from its corresponding byte array. */
+static void
+unpack_equix_solution(const uint8_t *bytes_in,
+ equix_solution *sol_out)
+{
+ for (unsigned i = 0; i < EQUIX_NUM_IDX; i++) {
+ sol_out->idx[i] = (uint16_t)bytes_in[i*2+0] |
+ (uint16_t)bytes_in[i*2+1] << 8;
+ }
+}
+
+/** Helper: Map the CompiledProofOfWorkHash configuration option to its
+ * corresponding equix_ctx_flags bit. */
+static equix_ctx_flags
+hs_pow_equix_option_flags(int CompiledProofOfWorkHash)
+{
+ if (CompiledProofOfWorkHash == 0) {
+ return 0;
+ } else if (CompiledProofOfWorkHash == 1) {
+ return EQUIX_CTX_MUST_COMPILE;
+ } else {
+ tor_assert_nonfatal(CompiledProofOfWorkHash == -1);
+ return EQUIX_CTX_TRY_COMPILE;
+ }
+}
+
+/** Solve the EquiX/blake2b PoW scheme using the parameters in pow_params, and
+ * store the solution in pow_solution_out. Returns 0 on success and -1
+ * otherwise. Called by a client, from a cpuworker thread. */
+int
+hs_pow_solve(const hs_pow_solver_inputs_t *pow_inputs,
+ hs_pow_solution_t *pow_solution_out)
+{
+ int ret = -1;
+ uint8_t nonce[HS_POW_NONCE_LEN];
+ uint8_t *challenge = NULL;
+ equix_ctx *ctx = NULL;
+
+ tor_assert(pow_inputs);
+ tor_assert(pow_solution_out);
+ const uint32_t effort = pow_inputs->effort;
+
+ /* Generate a random nonce N. */
+ crypto_rand((char *)nonce, sizeof nonce);
+
+ /* Build EquiX challenge string */
+ challenge = build_equix_challenge(&pow_inputs->service_blinded_id,
+ pow_inputs->seed, nonce, effort);
+
+ /* This runs on a cpuworker, let's not access global get_options().
+ * Instead, the particular options we need are captured in pow_inputs. */
+ ctx = equix_alloc(EQUIX_CTX_SOLVE |
+ hs_pow_equix_option_flags(pow_inputs->CompiledProofOfWorkHash));
+ if (!ctx) {
+ goto end;
+ }
+
+ uint8_t sol_bytes[HS_POW_EQX_SOL_LEN];
+ monotime_t start_time;
+ monotime_get(&start_time);
+ log_info(LD_REND, "Solving proof of work (effort %u)", effort);
+
+ for (;;) {
+ /* Calculate solutions to S = equix_solve(C || N || E), */
+ equix_solutions_buffer buffer;
+ equix_result result;
+ result = equix_solve(ctx, challenge, HS_POW_CHALLENGE_LEN, &buffer);
+ switch (result) {
+
+ case EQUIX_OK:
+ for (unsigned i = 0; i < buffer.count; i++) {
+ pack_equix_solution(&buffer.sols[i], sol_bytes);
+
+ /* Check an Equi-X solution against the effort threshold */
+ if (validate_equix_challenge(challenge, sol_bytes, effort)) {
+ /* Store the nonce N. */
+ memcpy(pow_solution_out->nonce, nonce, HS_POW_NONCE_LEN);
+ /* Store the effort E. */
+ pow_solution_out->effort = effort;
+ /* We only store the first 4 bytes of the seed C. */
+ memcpy(pow_solution_out->seed_head, pow_inputs->seed,
+ sizeof(pow_solution_out->seed_head));
+ /* Store the solution S */
+ memcpy(&pow_solution_out->equix_solution,
+ sol_bytes, sizeof sol_bytes);
+
+ monotime_t end_time;
+ monotime_get(&end_time);
+ int64_t duration_usec = monotime_diff_usec(&start_time, &end_time);
+ log_info(LD_REND, "Proof of work solution (effort %u) found "
+ "using %s implementation in %u.%06u seconds",
+ effort,
+ (EQUIX_SOLVER_DID_USE_COMPILER & buffer.flags)
+ ? "compiled" : "interpreted",
+ (unsigned)(duration_usec / 1000000),
+ (unsigned)(duration_usec % 1000000));
+
+ /* Indicate success and we are done. */
+ ret = 0;
+ goto end;
+ }
+ }
+ break;
+
+ case EQUIX_FAIL_CHALLENGE:
+ /* This happens occasionally due to HashX rejecting some program
+ * configurations. For our purposes here it's the same as count==0.
+ * Increment the nonce and try again. */
+ break;
+
+ case EQUIX_FAIL_COMPILE:
+ /* The interpreter is disabled and the compiler failed */
+ log_warn(LD_REND, "Proof of work solver failed, "
+ "compile error with no fallback enabled.");
+ goto end;
+
+ /* These failures are not applicable to equix_solve, but included for
+ * completeness and to satisfy exhaustive enum warnings. */
+ case EQUIX_FAIL_ORDER:
+ case EQUIX_FAIL_PARTIAL_SUM:
+ case EQUIX_FAIL_FINAL_SUM:
+ /* And these really should not happen, and indicate
+ * programming errors if they do. */
+ case EQUIX_FAIL_NO_SOLVER:
+ case EQUIX_FAIL_INTERNAL:
+ default:
+ tor_assert_nonfatal_unreached();
+ goto end;
+ }
+
+ /* No solutions for this nonce and/or none that passed the effort
+ * threshold, increment and try again. */
+ increment_and_set_nonce(nonce, challenge);
+ }
+
+ end:
+ tor_free(challenge);
+ equix_free(ctx);
+ return ret;
+}
+
+/** Verify the solution in pow_solution using the service's current PoW
+ * parameters found in pow_state. Returns 0 on success and -1 otherwise. Called
+ * by the service. */
+int
+hs_pow_verify(const ed25519_public_key_t *service_blinded_id,
+ const hs_pow_service_state_t *pow_state,
+ const hs_pow_solution_t *pow_solution)
+{
+ int ret = -1;
+ uint8_t *challenge = NULL;
+ nonce_cache_entry_t search, *entry = NULL;
+ equix_ctx *ctx = NULL;
+ const uint8_t *seed = NULL;
+
+ tor_assert(pow_state);
+ tor_assert(pow_solution);
+ tor_assert(service_blinded_id);
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(service_blinded_id));
+
+ /* Find a valid seed C that starts with the seed head. Fail if no such seed
+ * exists. */
+ if (fast_memeq(pow_state->seed_current, pow_solution->seed_head,
+ HS_POW_SEED_HEAD_LEN)) {
+ seed = pow_state->seed_current;
+ } else if (fast_memeq(pow_state->seed_previous, pow_solution->seed_head,
+ HS_POW_SEED_HEAD_LEN)) {
+ seed = pow_state->seed_previous;
+ } else {
+ log_warn(LD_REND, "Seed head didn't match either seed.");
+ goto done;
+ }
+
+ /* Fail if N = POW_NONCE is present in the replay cache. */
+ memcpy(search.bytes.nonce, pow_solution->nonce, HS_POW_NONCE_LEN);
+ memcpy(search.bytes.seed_head, pow_solution->seed_head,
+ HS_POW_SEED_HEAD_LEN);
+ entry = HT_FIND(nonce_cache_table_ht, &nonce_cache_table, &search);
+ if (entry) {
+ log_warn(LD_REND, "Found (nonce, seed) tuple in the replay cache.");
+ goto done;
+ }
+
+ /* Build the challenge with the params we have. */
+ challenge = build_equix_challenge(service_blinded_id, seed,
+ pow_solution->nonce, pow_solution->effort);
+
+ if (!validate_equix_challenge(challenge, pow_solution->equix_solution,
+ pow_solution->effort)) {
+ log_warn(LD_REND, "Verification of challenge effort in PoW failed.");
+ goto done;
+ }
+
+ ctx = equix_alloc(EQUIX_CTX_VERIFY |
+ hs_pow_equix_option_flags(get_options()->CompiledProofOfWorkHash));
+ if (!ctx) {
+ goto done;
+ }
+
+ /* Fail if equix_verify() != EQUIX_OK */
+ equix_solution equix_sol;
+ unpack_equix_solution(pow_solution->equix_solution, &equix_sol);
+ equix_result result = equix_verify(ctx, challenge, HS_POW_CHALLENGE_LEN,
+ &equix_sol);
+ if (result != EQUIX_OK) {
+ log_warn(LD_REND, "Verification of EquiX solution in PoW failed.");
+ goto done;
+ }
+
+ /* PoW verified successfully. */
+ ret = 0;
+
+ /* Add the (nonce, seed) tuple to the replay cache. */
+ entry = tor_malloc_zero(sizeof(nonce_cache_entry_t));
+ memcpy(entry->bytes.nonce, pow_solution->nonce, HS_POW_NONCE_LEN);
+ memcpy(entry->bytes.seed_head, pow_solution->seed_head,
+ HS_POW_SEED_HEAD_LEN);
+ HT_INSERT(nonce_cache_table_ht, &nonce_cache_table, entry);
+
+ done:
+ tor_free(challenge);
+ equix_free(ctx);
+ return ret;
+}
+
+/** Remove entries from the (nonce, seed) replay cache which are for the seed
+ * beginning with seed_head. If seed_head is NULL, remove all cache entries. */
+void
+hs_pow_remove_seed_from_cache(const uint8_t *seed_head)
+{
+ /* If nonce_cache_entry_has_seed returns 1, the entry is removed. */
+ HT_FOREACH_FN(nonce_cache_table_ht, &nonce_cache_table,
+ nonce_cache_entry_match_seed_and_free, (void*)seed_head);
+}
+
+/** Free a given PoW service state. */
+void
+hs_pow_free_service_state(hs_pow_service_state_t *state)
+{
+ if (state == NULL) {
+ return;
+ }
+ rend_pqueue_clear(state);
+ tor_assert(smartlist_len(state->rend_request_pqueue) == 0);
+ smartlist_free(state->rend_request_pqueue);
+ mainloop_event_free(state->pop_pqueue_ev);
+ tor_free(state);
+}
+
+/* =====
+ Thread workers
+ =====*/
+
+/**
+ * An object passed to a worker thread that will try to solve the pow.
+ */
+typedef struct pow_worker_job_t {
+
+ /** Inputs for the PoW solver (seed, chosen effort) */
+ hs_pow_solver_inputs_t pow_inputs;
+
+ /** State: we'll look these up to figure out how to proceed after. */
+ uint32_t intro_circ_identifier;
+ uint8_t rend_circ_cookie[HS_REND_COOKIE_LEN];
+
+ /** Output: The worker thread will malloc and write its answer here,
+ * or set it to NULL if it produced no useful answer. */
+ hs_pow_solution_t *pow_solution_out;
+
+} pow_worker_job_t;
+
+/**
+ * Worker function. This function runs inside a worker thread and receives
+ * a pow_worker_job_t as its input.
+ */
+static workqueue_reply_t
+pow_worker_threadfn(void *state_, void *work_)
+{
+ (void)state_;
+ pow_worker_job_t *job = work_;
+ job->pow_solution_out = tor_malloc_zero(sizeof(hs_pow_solution_t));
+
+ if (hs_pow_solve(&job->pow_inputs, job->pow_solution_out)) {
+ tor_free(job->pow_solution_out);
+ job->pow_solution_out = NULL; /* how we signal that we came up empty */
+ }
+ return WQ_RPL_REPLY;
+}
+
+/**
+ * Helper: release all storage held in <b>job</b>.
+ */
+static void
+pow_worker_job_free(pow_worker_job_t *job)
+{
+ if (!job)
+ return;
+ tor_free(job->pow_solution_out);
+ tor_free(job);
+}
+
+/**
+ * Worker function: This function runs in the main thread, and receives
+ * a pow_worker_job_t that the worker thread has already processed.
+ */
+static void
+pow_worker_replyfn(void *work_)
+{
+ tor_assert(in_main_thread());
+ tor_assert(work_);
+
+ pow_worker_job_t *job = work_;
+
+ /* Look up the circuits that we're going to use this pow in.
+ * There's room for improvement here. We already had a fast mapping to
+ * rend circuits from some kind of identifier that we can keep in a
+ * pow_worker_job_t, but we don't have that index for intro circs at this
+ * time. If the linear search in circuit_get_by_global_id() is ever a
+ * noticeable bottleneck we should add another map.
+ */
+ origin_circuit_t *intro_circ =
+ circuit_get_by_global_id(job->intro_circ_identifier);
+ origin_circuit_t *rend_circ =
+ hs_circuitmap_get_established_rend_circ_client_side(job->rend_circ_cookie);
+
+ /* try to re-create desc and ip */
+ const ed25519_public_key_t *service_identity_pk = NULL;
+ const hs_descriptor_t *desc = NULL;
+ const hs_desc_intro_point_t *ip = NULL;
+ if (intro_circ)
+ service_identity_pk = &intro_circ->hs_ident->identity_pk;
+ if (service_identity_pk)
+ desc = hs_cache_lookup_as_client(service_identity_pk);
+ if (desc)
+ ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc);
+
+ if (intro_circ && rend_circ && service_identity_pk && desc && ip &&
+ job->pow_solution_out) {
+
+ /* successful pow solve, and circs still here */
+ log_info(LD_REND, "Got a PoW solution we like! Shipping it!");
+
+ /* Set flag to reflect that the HS we are attempting to rendezvous has PoW
+ * defenses enabled, and as such we will need to be more lenient with
+ * timing out while waiting for the service-side circuit to be built. */
+ rend_circ->hs_with_pow_circ = 1;
+
+ /* Remember the PoW effort we chose, for client-side rend circuits. */
+ rend_circ->hs_pow_effort = job->pow_inputs.effort;
+
+ // and then send that intro cell
+ if (send_introduce1(intro_circ, rend_circ,
+ desc, job->pow_solution_out, ip) < 0) {
+ /* if it failed, mark the intro point as ready to start over */
+ intro_circ->hs_currently_solving_pow = 0;
+ }
+
+ } else {
+ if (!job->pow_solution_out) {
+ log_warn(LD_REND, "PoW cpuworker returned with no solution");
+ } else {
+ log_info(LD_REND, "PoW solution completed but we can "
+ "no longer locate its circuit");
+ }
+ if (intro_circ) {
+ intro_circ->hs_currently_solving_pow = 0;
+ }
+ }
+
+ pow_worker_job_free(job);
+}
+
+/**
+ * Queue the job of solving the pow in a worker thread.
+ */
+int
+hs_pow_queue_work(uint32_t intro_circ_identifier,
+ const uint8_t *rend_circ_cookie,
+ const hs_pow_solver_inputs_t *pow_inputs)
+{
+ tor_assert(in_main_thread());
+ tor_assert(rend_circ_cookie);
+ tor_assert(pow_inputs);
+ tor_assert_nonfatal(
+ !ed25519_public_key_is_zero(&pow_inputs->service_blinded_id));
+
+ pow_worker_job_t *job = tor_malloc_zero(sizeof(*job));
+ job->intro_circ_identifier = intro_circ_identifier;
+ memcpy(&job->rend_circ_cookie, rend_circ_cookie,
+ sizeof job->rend_circ_cookie);
+ memcpy(&job->pow_inputs, pow_inputs, sizeof job->pow_inputs);
+
+ workqueue_entry_t *work;
+ work = cpuworker_queue_work(WQ_PRI_LOW,
+ pow_worker_threadfn,
+ pow_worker_replyfn,
+ job);
+ if (!work) {
+ pow_worker_job_free(job);
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/feature/hs/hs_pow.h b/src/feature/hs/hs_pow.h
new file mode 100644
index 0000000000..d47eba82ab
--- /dev/null
+++ b/src/feature/hs/hs_pow.h
@@ -0,0 +1,228 @@
+/* Copyright (c) 2019-2020, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_pow.h
+ * \brief Header file containing PoW denial of service defenses for the HS
+ * subsystem for all versions.
+ **/
+
+#ifndef TOR_HS_POW_H
+#define TOR_HS_POW_H
+
+#include "lib/evloop/compat_libevent.h"
+#include "lib/evloop/token_bucket.h"
+#include "lib/smartlist_core/smartlist_core.h"
+#include "lib/crypt_ops/crypto_ed25519.h"
+
+/* Service updates the suggested effort every HS_UPDATE_PERIOD seconds.
+ * This parameter controls how often we can change hs descriptor data to
+ * update suggested_effort, but it also controls the frequency of our
+ * opportunities to increase or decrease effort. Lower values react to
+ * attacks faster, higher values may be more stable.
+ * Can this move to torrc? (Or the consensus?) The hs_cache timings are
+ * related, and they're also hardcoded.
+*/
+#define HS_UPDATE_PERIOD 300
+
+/** Length of random nonce (N) used in the PoW scheme. */
+#define HS_POW_NONCE_LEN 16
+/** Length of an E-quiX solution (S) in bytes. */
+#define HS_POW_EQX_SOL_LEN 16
+/** Length of blake2b hash result (R) used in the PoW scheme. */
+#define HS_POW_HASH_LEN 4
+/** Length of algorithm personalization string (P) used in the PoW scheme */
+#define HS_POW_PSTRING_LEN 16
+/** Algorithm personalization string (P) */
+#define HS_POW_PSTRING "Tor hs intro v1\0"
+/** Length of the blinded public ID for the onion service (ID) */
+#define HS_POW_ID_LEN 32
+/** Length of random seed used in the PoW scheme. */
+#define HS_POW_SEED_LEN 32
+/** Length of seed identification heading in the PoW scheme. */
+#define HS_POW_SEED_HEAD_LEN 4
+/** Length of an effort value */
+#define HS_POW_EFFORT_LEN sizeof(uint32_t)
+/** Offset of the nonce value within the challenge string */
+#define HS_POW_NONCE_OFFSET \
+ (HS_POW_PSTRING_LEN + HS_POW_ID_LEN + HS_POW_SEED_LEN)
+/** Length of a PoW challenge. Construction as per prop327 is:
+ * (P || ID || C || N || INT_32(E))
+ */
+#define HS_POW_CHALLENGE_LEN \
+ (HS_POW_PSTRING_LEN + HS_POW_ID_LEN + \
+ HS_POW_SEED_LEN + HS_POW_NONCE_LEN + HS_POW_EFFORT_LEN)
+
+/** Type of PoW in the descriptor. */
+typedef enum {
+ HS_POW_DESC_V1 = 1,
+} hs_pow_desc_type_t;
+
+/** Proof-of-Work parameters for DoS defense located in a descriptor. */
+typedef struct hs_pow_desc_params_t {
+ /** Type of PoW system being used. */
+ hs_pow_desc_type_t type;
+
+ /** Random 32-byte seed used as input the the PoW hash function */
+ uint8_t seed[HS_POW_SEED_LEN];
+
+ /** Specifies effort value that clients should aim for when contacting the
+ * service. */
+ uint32_t suggested_effort;
+
+ /** Timestamp after which the above seed expires. */
+ time_t expiration_time;
+} hs_pow_desc_params_t;
+
+/** The inputs to the PoW solver, derived from the descriptor data and the
+ * client's per-connection effort choices. */
+typedef struct hs_pow_solver_inputs_t {
+ /** Seed value from a current descriptor */
+ uint8_t seed[HS_POW_SEED_LEN];
+ /** Blinded public ID for the onion service this puzzle is bound to */
+ ed25519_public_key_t service_blinded_id;
+ /** Effort chosen by the client. May be higher or lower than
+ * suggested_effort in the descriptor. */
+ uint32_t effort;
+ /** Configuration option, choice of hash implementation. AUTOBOOL. */
+ int CompiledProofOfWorkHash;
+} hs_pow_solver_inputs_t;
+
+/** State and parameters of PoW defenses, stored in the service state. */
+typedef struct hs_pow_service_state_t {
+ /* If PoW defenses are enabled this is a priority queue containing acceptable
+ * requests that are awaiting rendezvous circuits to built, where priority is
+ * based on the amount of effort that was exerted in the PoW. */
+ smartlist_t *rend_request_pqueue;
+
+ /* Low level mark for pqueue size. Below this length it's considered to be
+ * effectively empty when calculating effort adjustments. */
+ int pqueue_low_level;
+
+ /* High level mark for pqueue size. When the queue is this length we will
+ * trim it down to pqueue_high_level/2. */
+ int pqueue_high_level;
+
+ /* Event callback for dequeueing rend requests, paused when the queue is
+ * empty or rate limited. */
+ mainloop_event_t *pop_pqueue_ev;
+
+ /* Token bucket for rate limiting the priority queue */
+ token_bucket_ctr_t pqueue_bucket;
+
+ /* The current seed being used in the PoW defenses. */
+ uint8_t seed_current[HS_POW_SEED_LEN];
+
+ /* The previous seed that was used in the PoW defenses. We accept solutions
+ * for both the current and previous seed. */
+ uint8_t seed_previous[HS_POW_SEED_LEN];
+
+ /* The time at which the current seed expires and rotates for a new one. */
+ time_t expiration_time;
+
+ /* The suggested effort that clients should use in order for their request to
+ * be serviced in a timely manner. */
+ uint32_t suggested_effort;
+
+ /* The maximum effort of a request we've had to trim, this update period */
+ uint32_t max_trimmed_effort;
+
+ /* The following values are used when calculating and updating the suggested
+ * effort every HS_UPDATE_PERIOD seconds. */
+
+ /* Number of intro requests the service handled since last update. */
+ uint32_t rend_handled;
+ /* The next time at which to update the suggested effort. */
+ time_t next_effort_update;
+ /* Sum of effort of all valid requests received since the last update. */
+ uint64_t total_effort;
+
+ /* Did we have elements waiting in the queue during this period? */
+ bool had_queue;
+ /* Are we using pqueue_bucket to rate limit the pqueue? */
+ bool using_pqueue_bucket;
+
+} hs_pow_service_state_t;
+
+/* Struct to store a solution to the PoW challenge. */
+typedef struct hs_pow_solution_t {
+ /* The nonce chosen to satisfy the PoW challenge's conditions. */
+ uint8_t nonce[HS_POW_NONCE_LEN];
+
+ /* The effort used in this solution. */
+ uint32_t effort;
+
+ /* A prefix of the seed used in this solution, so it can be identified. */
+ uint8_t seed_head[HS_POW_SEED_HEAD_LEN];
+
+ /* The Equi-X solution used in this PoW solution. */
+ uint8_t equix_solution[HS_POW_EQX_SOL_LEN];
+} hs_pow_solution_t;
+
+#ifdef HAVE_MODULE_POW
+#define have_module_pow() (1)
+
+/* API */
+int hs_pow_solve(const hs_pow_solver_inputs_t *pow_inputs,
+ hs_pow_solution_t *pow_solution_out);
+
+int hs_pow_verify(const ed25519_public_key_t *service_blinded_id,
+ const hs_pow_service_state_t *pow_state,
+ const hs_pow_solution_t *pow_solution);
+
+void hs_pow_remove_seed_from_cache(const uint8_t *seed_head);
+void hs_pow_free_service_state(hs_pow_service_state_t *state);
+
+int hs_pow_queue_work(uint32_t intro_circ_identifier,
+ const uint8_t *rend_circ_cookie,
+ const hs_pow_solver_inputs_t *pow_inputs);
+
+#else /* !defined(HAVE_MODULE_POW) */
+#define have_module_pow() (0)
+
+static inline int
+hs_pow_solve(const hs_pow_solver_inputs_t *pow_inputs,
+ hs_pow_solution_t *pow_solution_out)
+{
+ (void)pow_inputs;
+ (void)pow_solution_out;
+ return -1;
+}
+
+static inline int
+hs_pow_verify(const ed25519_public_key_t *service_blinded_id,
+ const hs_pow_service_state_t *pow_state,
+ const hs_pow_solution_t *pow_solution)
+{
+ (void)service_blinded_id;
+ (void)pow_state;
+ (void)pow_solution;
+ return -1;
+}
+
+static inline void
+hs_pow_remove_seed_from_cache(const uint8_t *seed_head)
+{
+ (void)seed_head;
+}
+
+static inline void
+hs_pow_free_service_state(hs_pow_service_state_t *state)
+{
+ (void)state;
+}
+
+static inline int
+hs_pow_queue_work(uint32_t intro_circ_identifier,
+ const uint8_t *rend_circ_cookie,
+ const hs_pow_solver_inputs_t *pow_inputs)
+{
+ (void)intro_circ_identifier;
+ (void)rend_circ_cookie;
+ (void)pow_inputs;
+ return -1;
+}
+
+#endif /* defined(HAVE_MODULE_POW) */
+
+#endif /* !defined(TOR_HS_POW_H) */
diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c
index 7d9fbe546a..3cc8c23e0b 100644
--- a/src/feature/hs/hs_service.c
+++ b/src/feature/hs/hs_service.c
@@ -33,6 +33,8 @@
#include "lib/crypt_ops/crypto_ope.h"
#include "lib/crypt_ops/crypto_rand.h"
#include "lib/crypt_ops/crypto_util.h"
+#include "lib/time/tvdiff.h"
+#include "lib/time/compat_time.h"
#include "feature/hs/hs_circuit.h"
#include "feature/hs/hs_common.h"
@@ -42,6 +44,7 @@
#include "feature/hs/hs_ident.h"
#include "feature/hs/hs_intropoint.h"
#include "feature/hs/hs_metrics.h"
+#include "feature/hs/hs_metrics_entry.h"
#include "feature/hs/hs_service.h"
#include "feature/hs/hs_stats.h"
#include "feature/hs/hs_ob.h"
@@ -144,7 +147,7 @@ hs_service_ht_hash(const hs_service_t *service)
sizeof(service->keys.identity_pk.pubkey));
}
-/** This is _the_ global hash map of hidden services which indexed the service
+/** This is _the_ global hash map of hidden services which indexes the services
* contained in it by master public identity key which is roughly the onion
* address of the service. */
static struct hs_service_ht *hs_service_map;
@@ -170,9 +173,7 @@ is_client_auth_enabled(const hs_service_t *service)
}
/** Query the given service map with a public key and return a service object
- * if found else NULL. It is also possible to set a directory path in the
- * search query. If pk is NULL, then it will be set to zero indicating the
- * hash table to compare the directory path instead. */
+ * if found else NULL. */
STATIC hs_service_t *
find_service(hs_service_ht *map, const ed25519_public_key_t *pk)
{
@@ -262,6 +263,61 @@ set_service_default_config(hs_service_config_t *c,
c->has_dos_defense_enabled = HS_CONFIG_V3_DOS_DEFENSE_DEFAULT;
c->intro_dos_rate_per_sec = HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT;
c->intro_dos_burst_per_sec = HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT;
+ /* PoW default options. */
+ c->has_dos_defense_enabled = HS_CONFIG_V3_POW_DEFENSES_DEFAULT;
+}
+
+/** Initialize PoW defenses */
+static void
+initialize_pow_defenses(hs_service_t *service)
+{
+ service->state.pow_state = tor_malloc_zero(sizeof(hs_pow_service_state_t));
+
+ /* Make life easier */
+ hs_pow_service_state_t *pow_state = service->state.pow_state;
+
+ pow_state->rend_request_pqueue = smartlist_new();
+ pow_state->pop_pqueue_ev = NULL;
+
+ /* If we are using the pqueue rate limiter, calculate min and max queue
+ * levels based on those programmed rates. If not, we have generic
+ * defaults */
+ pow_state->pqueue_low_level = 16;
+ pow_state->pqueue_high_level = 16384;
+
+ if (service->config.pow_queue_rate > 0 &&
+ service->config.pow_queue_burst >= service->config.pow_queue_rate) {
+ pow_state->using_pqueue_bucket = 1;
+ token_bucket_ctr_init(&pow_state->pqueue_bucket,
+ service->config.pow_queue_rate,
+ service->config.pow_queue_burst,
+ (uint32_t) monotime_coarse_absolute_sec());
+
+ pow_state->pqueue_low_level = MAX(8, service->config.pow_queue_rate / 4);
+ pow_state->pqueue_high_level =
+ service->config.pow_queue_burst +
+ service->config.pow_queue_rate * MAX_REND_TIMEOUT * 2;
+ }
+
+ /* We recalculate and update the suggested effort every HS_UPDATE_PERIOD
+ * seconds. */
+ pow_state->suggested_effort = 0;
+ pow_state->rend_handled = 0;
+ pow_state->total_effort = 0;
+ pow_state->next_effort_update = (time(NULL) + HS_UPDATE_PERIOD);
+
+ /* Generate the random seeds. We generate both as we don't want the previous
+ * seed to be predictable even if it doesn't really exist yet, and it needs
+ * to be different to the current nonce for the replay cache scrubbing to
+ * function correctly. */
+ log_info(LD_REND, "Generating both PoW seeds...");
+ crypto_rand((char *)&pow_state->seed_current, HS_POW_SEED_LEN);
+ crypto_rand((char *)&pow_state->seed_previous, HS_POW_SEED_LEN);
+
+ pow_state->expiration_time =
+ (time(NULL) +
+ crypto_rand_int_range(HS_SERVICE_POW_SEED_ROTATE_TIME_MIN,
+ HS_SERVICE_POW_SEED_ROTATE_TIME_MAX));
}
/** From a service configuration object config, clear everything from it
@@ -2250,6 +2306,14 @@ pick_needed_intro_points(hs_service_t *service,
safe_str_client(service->onion_address));
goto done;
}
+
+ /* Save a copy of the specific version of the blinded ID that we
+ * use to reach this intro point. Needed to validate proof-of-work
+ * solutions that are bound to this specific service. */
+ tor_assert(desc->desc);
+ ed25519_pubkey_copy(&ip->blinded_id,
+ &desc->desc->plaintext_data.blinded_pubkey);
+
/* Valid intro point object, add it to the descriptor current map. */
service_intro_point_add(desc->intro_points.map, ip);
}
@@ -2367,6 +2431,88 @@ update_all_descriptors_intro_points(time_t now)
} FOR_EACH_SERVICE_END;
}
+/** Update or initialise PoW parameters in the descriptors if they do not
+ * reflect the current state of the PoW defenses. If the defenses have been
+ * disabled then remove the PoW parameters from the descriptors. */
+static void
+update_all_descriptors_pow_params(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ int descs_updated = 0;
+ hs_pow_service_state_t *pow_state = service->state.pow_state;
+ hs_desc_encrypted_data_t *encrypted;
+ uint32_t previous_effort;
+
+ /* If PoW defenses have been disabled after previously being enabled, i.e
+ * via config change and SIGHUP, we need to remove the PoW parameters from
+ * the descriptors so clients stop attempting to solve the puzzle. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ if (!service->config.has_pow_defenses_enabled &&
+ desc->desc->encrypted_data.pow_params) {
+ log_info(LD_REND, "PoW defenses have been disabled, clearing "
+ "pow_params from a descriptor.");
+ tor_free(desc->desc->encrypted_data.pow_params);
+ /* Schedule for upload here as we can skip the following checks as PoW
+ * defenses are disabled. */
+ service_desc_schedule_upload(desc, now, 1);
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+
+ /* Skip remaining checks if this service does not have PoW defenses
+ * enabled. */
+ if (!service->config.has_pow_defenses_enabled) {
+ continue;
+ }
+
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ encrypted = &desc->desc->encrypted_data;
+ /* If this is a new service or PoW defenses were just enabled we need to
+ * initialise pow_params in the descriptors. If this runs the next if
+ * statement will run and set the correct values. */
+ if (!encrypted->pow_params) {
+ log_info(LD_REND, "Initializing pow_params in descriptor...");
+ encrypted->pow_params = tor_malloc_zero(sizeof(hs_pow_desc_params_t));
+ }
+
+ /* Update the descriptor any time the seed rotates, using expiration
+ * time as a proxy for parameters not including the suggested_effort,
+ * which gets special treatment below. */
+ if (encrypted->pow_params->expiration_time !=
+ pow_state->expiration_time) {
+ encrypted->pow_params->type = 0; /* use first version in the list */
+ memcpy(encrypted->pow_params->seed, &pow_state->seed_current,
+ HS_POW_SEED_LEN);
+ encrypted->pow_params->suggested_effort = pow_state->suggested_effort;
+ encrypted->pow_params->expiration_time = pow_state->expiration_time;
+ descs_updated = 1;
+ }
+
+ /* Services SHOULD NOT upload a new descriptor if the suggested
+ * effort value changes by less than 15 percent. */
+ previous_effort = encrypted->pow_params->suggested_effort;
+ if (pow_state->suggested_effort < previous_effort * 0.85 ||
+ previous_effort * 1.15 < pow_state->suggested_effort) {
+ log_info(LD_REND, "Suggested effort changed significantly, "
+ "updating descriptors...");
+ encrypted->pow_params->suggested_effort = pow_state->suggested_effort;
+ descs_updated = 1;
+ } else if (previous_effort != pow_state->suggested_effort) {
+ /* The change in suggested effort was not significant enough to
+ * warrant updating the descriptors, return 0 to reflect they are
+ * unchanged. */
+ log_info(LD_REND, "Change in suggested effort didn't warrant "
+ "updating descriptors.");
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+
+ if (descs_updated) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ service_desc_schedule_upload(desc, now, 1);
+ } FOR_EACH_DESCRIPTOR_END;
+ }
+ } FOR_EACH_SERVICE_END;
+}
+
/** Return true iff the given intro point has expired that is it has been used
* for too long or we've reached our max seen INTRODUCE2 cell. */
STATIC int
@@ -2420,7 +2566,7 @@ should_remove_intro_point(hs_service_intro_point_t *ip, time_t now)
goto end;
}
- /* Pass this point, even though we might be over the retry limit, we check
+ /* Past this point, even though we might be over the retry limit, we check
* if a circuit (established or pending) exists. In that case, we should not
* remove it because it might simply be valid and opened at the previous
* scheduled event for the last retry. */
@@ -2508,6 +2654,131 @@ cleanup_intro_points(hs_service_t *service, time_t now)
smartlist_free(ips_to_free);
}
+/** Rotate the seeds used in the proof-of-work defenses. */
+static void
+rotate_pow_seeds(hs_service_t *service, time_t now)
+{
+ /* Make life easier */
+ hs_pow_service_state_t *pow_state = service->state.pow_state;
+
+ log_info(LD_REND,
+ "Current seed expired. Scrubbing replay cache, rotating PoW "
+ "seeds, generating new seed and updating descriptors.");
+
+ /* Before we overwrite the previous seed lets scrub entries corresponding
+ * to it in the nonce replay cache. */
+ hs_pow_remove_seed_from_cache(pow_state->seed_previous);
+
+ /* Keep track of the current seed that we are now rotating. */
+ memcpy(pow_state->seed_previous, pow_state->seed_current, HS_POW_SEED_LEN);
+
+ /* Generate a new random seed to use from now on. Make sure the seed head
+ * is different to that of the previous seed. The following while loop
+ * will run at least once as the seeds will initially be equal. */
+ while (fast_memeq(pow_state->seed_previous, pow_state->seed_current,
+ HS_POW_SEED_HEAD_LEN)) {
+ crypto_rand((char *)pow_state->seed_current, HS_POW_SEED_LEN);
+ }
+
+ /* Update the expiration time for the new seed. */
+ pow_state->expiration_time =
+ (now +
+ crypto_rand_int_range(HS_SERVICE_POW_SEED_ROTATE_TIME_MIN,
+ HS_SERVICE_POW_SEED_ROTATE_TIME_MAX));
+
+ {
+ char fmt_next_time[ISO_TIME_LEN + 1];
+ format_local_iso_time(fmt_next_time, pow_state->expiration_time);
+ log_debug(LD_REND, "PoW state expiration time set to: %s", fmt_next_time);
+ }
+}
+
+/** Every HS_UPDATE_PERIOD seconds, and while PoW defenses are enabled, the
+ * service updates its suggested effort for PoW solutions as SUGGESTED_EFFORT =
+ * TOTAL_EFFORT / (SVC_BOTTOM_CAPACITY * HS_UPDATE_PERIOD) where TOTAL_EFFORT
+ * is the sum of the effort of all valid requests that have been received since
+ * the suggested_effort was last updated. */
+static void
+update_suggested_effort(hs_service_t *service, time_t now)
+{
+ /* Make life easier */
+ hs_pow_service_state_t *pow_state = service->state.pow_state;
+
+ /* Calculate the new suggested effort, using an additive-increase
+ * multiplicative-decrease estimation scheme. */
+ enum {
+ NONE,
+ INCREASE,
+ DECREASE
+ } aimd_event = NONE;
+
+ if (pow_state->max_trimmed_effort > pow_state->suggested_effort) {
+ /* Increase when we notice that high-effort requests are trimmed */
+ aimd_event = INCREASE;
+ } else if (pow_state->had_queue) {
+ if (smartlist_len(pow_state->rend_request_pqueue) > 0 &&
+ top_of_rend_pqueue_is_worthwhile(pow_state)) {
+ /* Increase when the top of queue is high-effort */
+ aimd_event = INCREASE;
+ }
+ } else if (smartlist_len(pow_state->rend_request_pqueue) <
+ pow_state->pqueue_low_level) {
+ /* Dec when the queue is empty now and had_queue wasn't set this period */
+ aimd_event = DECREASE;
+ }
+
+ switch (aimd_event) {
+ case INCREASE:
+ if (pow_state->suggested_effort < UINT32_MAX) {
+ pow_state->suggested_effort = MAX(pow_state->suggested_effort + 1,
+ (uint32_t)(pow_state->total_effort /
+ pow_state->rend_handled));
+ }
+ break;
+ case DECREASE:
+ pow_state->suggested_effort = 2*pow_state->suggested_effort/3;
+ break;
+ case NONE:
+ break;
+ }
+
+ hs_metrics_pow_suggested_effort(service, pow_state->suggested_effort);
+
+ log_debug(LD_REND, "Recalculated suggested effort: %u",
+ pow_state->suggested_effort);
+
+ /* Reset the total effort sum and number of rends for this update period. */
+ pow_state->total_effort = 0;
+ pow_state->rend_handled = 0;
+ pow_state->max_trimmed_effort = 0;
+ pow_state->had_queue = 0;
+ pow_state->next_effort_update = now + HS_UPDATE_PERIOD;
+}
+
+/** Run PoW defenses housekeeping. This MUST be called if the defenses are
+ * actually enabled for the given service. */
+static void
+pow_housekeeping(hs_service_t *service, time_t now)
+{
+ /* If the service is starting off or just been reset we need to
+ * initialize the state of the defenses. */
+ if (!service->state.pow_state) {
+ initialize_pow_defenses(service);
+ }
+
+ /* If the current PoW seed has expired then generate a new current
+ * seed, storing the old one in seed_previous. */
+ if (now >= service->state.pow_state->expiration_time) {
+ rotate_pow_seeds(service, now);
+ }
+
+ /* Update the suggested effort if HS_UPDATE_PERIOD seconds have passed
+ * since we last did so. */
+ if (now >= service->state.pow_state->next_effort_update) {
+ update_suggested_effort(service, now);
+ }
+}
+
/** Set the next rotation time of the descriptors for the given service for the
* time now. */
static void
@@ -2652,6 +2923,12 @@ run_housekeeping_event(time_t now)
set_rotation_time(service);
}
+ /* Check if we need to initialize or update PoW parameters, if the
+ * defenses are enabled. */
+ if (have_module_pow() && service->config.has_pow_defenses_enabled) {
+ pow_housekeeping(service, now);
+ }
+
/* Cleanup invalid intro points from the service descriptor. */
cleanup_intro_points(service, now);
@@ -2685,6 +2962,11 @@ run_build_descriptor_event(time_t now)
* points. Missing introduction points will be picked in this function which
* is useful for newly built descriptors. */
update_all_descriptors_intro_points(now);
+
+ if (have_module_pow()) {
+ /* Update the PoW params if needed. */
+ update_all_descriptors_pow_params(now);
+ }
}
/** For the given service, launch any intro point circuits that could be
@@ -3414,6 +3696,11 @@ service_rendezvous_circ_has_opened(origin_circuit_t *circ)
* will even out the metric. */
if (TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
hs_metrics_new_established_rdv(service);
+
+ struct timeval now;
+ tor_gettimeofday(&now);
+ int64_t duration = tv_mdiff(&TO_CIRCUIT(circ)->timestamp_began, &now);
+ hs_metrics_rdv_circ_build_time(service, duration);
}
goto done;
@@ -3467,8 +3754,13 @@ service_handle_intro_established(origin_circuit_t *circ,
goto err;
}
+ struct timeval now;
+ tor_gettimeofday(&now);
+ int64_t duration = tv_mdiff(&TO_CIRCUIT(circ)->timestamp_began, &now);
+
/* Update metrics. */
hs_metrics_new_established_intro(service);
+ hs_metrics_intro_circ_build_time(service, duration);
log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell "
"on circuit %u for service %s",
@@ -3511,6 +3803,9 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload,
"an INTRODUCE2 cell on circuit %u for service %s",
TO_CIRCUIT(circ)->n_circ_id,
safe_str_client(service->onion_address));
+
+ hs_metrics_reject_intro_req(service,
+ HS_METRICS_ERR_INTRO_REQ_BAD_AUTH_KEY);
goto err;
}
/* If we have an IP object, we MUST have a descriptor object. */
@@ -3527,6 +3822,7 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload,
return 0;
err:
+
return -1;
}
@@ -4352,6 +4648,9 @@ hs_service_free_(hs_service_t *service)
service_descriptor_free(desc);
} FOR_EACH_DESCRIPTOR_END;
+ /* Free the state of the PoW defenses. */
+ hs_pow_free_service_state(service->state.pow_state);
+
/* Free service configuration. */
service_clear_config(&service->config);
diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h
index 95461289ce..36d67719ca 100644
--- a/src/feature/hs/hs_service.h
+++ b/src/feature/hs/hs_service.h
@@ -35,6 +35,11 @@
/** Maximum interval for uploading next descriptor (in seconds). */
#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60)
+/** PoW seed expiration time is set to RAND_TIME(now+7200, 900)
+ * seconds. */
+#define HS_SERVICE_POW_SEED_ROTATE_TIME_MIN (7200 - 900)
+#define HS_SERVICE_POW_SEED_ROTATE_TIME_MAX (7200)
+
/** Collected metrics for a specific service. */
typedef struct hs_service_metrics_t {
/** Store containing the metrics values. */
@@ -57,6 +62,10 @@ typedef struct hs_service_intro_point_t {
/** Encryption keypair for the "ntor" type. */
curve25519_keypair_t enc_key_kp;
+ /** Blinded public ID for this service, from this intro point's
+ * active time period. */
+ ed25519_public_key_t blinded_id;
+
/** Legacy key if that intro point doesn't support v3. This should be used if
* the base object legacy flag is set. */
crypto_pk_t *legacy_key;
@@ -257,6 +266,11 @@ typedef struct hs_service_config_t {
uint32_t intro_dos_rate_per_sec;
uint32_t intro_dos_burst_per_sec;
+ /** True iff PoW anti-DoS defenses are enabled. */
+ unsigned int has_pow_defenses_enabled : 1;
+ uint32_t pow_queue_rate;
+ uint32_t pow_queue_burst;
+
/** If set, contains the Onion Balance master ed25519 public key (taken from
* an .onion addresses) that this tor instance serves as backend. */
smartlist_t *ob_master_pubkeys;
@@ -291,6 +305,10 @@ typedef struct hs_service_state_t {
hs_subcredential_t *ob_subcreds;
/* Number of OB subcredentials */
size_t n_ob_subcreds;
+
+ /** State of the PoW defenses, which may be enabled dynamically. NULL if not
+ * defined for this service. */
+ hs_pow_service_state_t *pow_state;
} hs_service_state_t;
/** Representation of a service running on this tor instance. */
diff --git a/src/feature/hs/include.am b/src/feature/hs/include.am
index c55abd3d47..b64ab1b41c 100644
--- a/src/feature/hs/include.am
+++ b/src/feature/hs/include.am
@@ -20,6 +20,14 @@ LIBTOR_APP_A_SOURCES += \
src/feature/hs/hs_sys.c \
src/feature/hs/hs_metrics_entry.c
+# Proof of Work module
+MODULE_POW_SOURCES = \
+ src/feature/hs/hs_pow.c
+
+if BUILD_MODULE_POW
+LIBTOR_APP_A_SOURCES += $(MODULE_POW_SOURCES)
+endif
+
# ADD_C_FILE: INSERT HEADERS HERE.
noinst_HEADERS += \
src/feature/hs/hs_cache.h \
@@ -38,6 +46,7 @@ noinst_HEADERS += \
src/feature/hs/hs_ob.h \
src/feature/hs/hs_opts_st.h \
src/feature/hs/hs_options.inc \
+ src/feature/hs/hs_pow.h \
src/feature/hs/hs_service.h \
src/feature/hs/hs_stats.h \
src/feature/hs/hsdir_index_st.h \
diff --git a/src/feature/nodelist/fmt_routerstatus.c b/src/feature/nodelist/fmt_routerstatus.c
index 8c02a302af..4be2ec2a18 100644
--- a/src/feature/nodelist/fmt_routerstatus.c
+++ b/src/feature/nodelist/fmt_routerstatus.c
@@ -26,6 +26,9 @@
/** Helper: write the router-status information in <b>rs</b> into a newly
* allocated character buffer. Use the same format as in network-status
* documents. If <b>version</b> is non-NULL, add a "v" line for the platform.
+ * If <b>declared_publish_time</b> is nonnegative, we declare it as the
+ * publication time. Otherwise we look for a publication time in <b>vrs</b>,
+ * and fall back to a default (not useful) publication time.
*
* Return 0 on success, -1 on failure.
*
@@ -38,12 +41,14 @@
* NS_V3_VOTE - Output a complete V3 NS vote. If <b>vrs</b> is present,
* it contains additional information for the vote.
* NS_CONTROL_PORT - Output a NS document for the control port.
+ *
*/
char *
routerstatus_format_entry(const routerstatus_t *rs, const char *version,
const char *protocols,
routerstatus_format_type_t format,
- const vote_routerstatus_t *vrs)
+ const vote_routerstatus_t *vrs,
+ time_t declared_publish_time)
{
char *summary;
char *result = NULL;
@@ -53,11 +58,18 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
char digest64[BASE64_DIGEST_LEN+1];
smartlist_t *chunks = smartlist_new();
+ if (declared_publish_time >= 0) {
+ format_iso_time(published, declared_publish_time);
+ } else if (vrs) {
+ format_iso_time(published, vrs->published_on);
+ } else {
+ strlcpy(published, "2038-01-01 00:00:00", sizeof(published));
+ }
+
const char *ip_str = fmt_addr(&rs->ipv4_addr);
if (ip_str[0] == '\0')
goto err;
- format_iso_time(published, rs->published_on);
digest_to_base64(identity64, rs->identity_digest);
digest_to_base64(digest64, rs->descriptor_digest);
diff --git a/src/feature/nodelist/fmt_routerstatus.h b/src/feature/nodelist/fmt_routerstatus.h
index 7482f373e1..740ea51dd9 100644
--- a/src/feature/nodelist/fmt_routerstatus.h
+++ b/src/feature/nodelist/fmt_routerstatus.h
@@ -35,6 +35,7 @@ char *routerstatus_format_entry(
const char *version,
const char *protocols,
routerstatus_format_type_t format,
- const vote_routerstatus_t *vrs);
+ const vote_routerstatus_t *vrs,
+ time_t declared_publish_time);
#endif /* !defined(TOR_FMT_ROUTERSTATUS_H) */
diff --git a/src/feature/nodelist/microdesc.c b/src/feature/nodelist/microdesc.c
index a95d535dc0..9e5f0bb9a4 100644
--- a/src/feature/nodelist/microdesc.c
+++ b/src/feature/nodelist/microdesc.c
@@ -626,7 +626,7 @@ microdesc_cache_clean(microdesc_cache_t *cache, time_t cutoff, int force)
(*mdp)->digest, DIGEST256_LEN)) {
rs_match = "Microdesc digest in RS matches";
} else {
- rs_match = "Microdesc digest in RS does match";
+ rs_match = "Microdesc digest in RS does not match";
}
if (ns) {
/* This should be impossible, but let's see! */
diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c
index af3bde83a5..61eef5bfa5 100644
--- a/src/feature/nodelist/networkstatus.c
+++ b/src/feature/nodelist/networkstatus.c
@@ -51,6 +51,7 @@
#include "core/or/circuitmux.h"
#include "core/or/circuitmux_ewma.h"
#include "core/or/circuitstats.h"
+#include "core/or/conflux_params.h"
#include "core/or/connection_edge.h"
#include "core/or/connection_or.h"
#include "core/or/dos.h"
@@ -1616,7 +1617,6 @@ routerstatus_has_visibly_changed(const routerstatus_t *a,
a->is_hs_dir != b->is_hs_dir ||
a->is_staledesc != b->is_staledesc ||
a->has_bandwidth != b->has_bandwidth ||
- a->published_on != b->published_on ||
a->ipv6_orport != b->ipv6_orport ||
a->is_v2_dir != b->is_v2_dir ||
a->bandwidth_kb != b->bandwidth_kb ||
@@ -1712,6 +1712,7 @@ notify_after_networkstatus_changes(void)
flow_control_new_consensus_params(c);
hs_service_new_consensus_params(c);
dns_new_consensus_params(c);
+ conflux_params_new_consensus(c);
/* Maintenance of our L2 guard list */
maintain_layer2_guards();
@@ -2372,7 +2373,7 @@ char *
networkstatus_getinfo_helper_single(const routerstatus_t *rs)
{
return routerstatus_format_entry(rs, NULL, NULL, NS_CONTROL_PORT,
- NULL);
+ NULL, -1);
}
/**
@@ -2404,7 +2405,6 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs,
rs->is_hs_dir = node->is_hs_dir;
rs->is_named = rs->is_unnamed = 0;
- rs->published_on = ri->cache_info.published_on;
memcpy(rs->identity_digest, node->identity, DIGEST_LEN);
memcpy(rs->descriptor_digest, ri->cache_info.signed_descriptor_digest,
DIGEST_LEN);
@@ -2451,7 +2451,9 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now)
if (ri->purpose != purpose)
continue;
set_routerstatus_from_routerinfo(&rs, node, ri);
- smartlist_add(statuses, networkstatus_getinfo_helper_single(&rs));
+ char *text = routerstatus_format_entry(
+ &rs, NULL, NULL, NS_CONTROL_PORT, NULL, ri->cache_info.published_on);
+ smartlist_add(statuses, text);
} SMARTLIST_FOREACH_END(ri);
answer = smartlist_join_strings(statuses, "", 0, NULL);
@@ -2622,15 +2624,12 @@ networkstatus_parse_flavor_name(const char *flavname)
int
client_would_use_router(const routerstatus_t *rs, time_t now)
{
+ (void) now;
if (!rs->is_flagged_running) {
/* If we had this router descriptor, we wouldn't even bother using it.
* (Fetching and storing depends on by we_want_to_fetch_flavor().) */
return 0;
}
- if (rs->published_on + OLD_ROUTER_DESC_MAX_AGE < now) {
- /* We'd drop it immediately for being too old. */
- return 0;
- }
if (!routerstatus_version_supports_extend2_cells(rs, 1)) {
/* We'd ignore it because it doesn't support EXTEND2 cells.
* If we don't know the version, download the descriptor so we can
diff --git a/src/feature/nodelist/node_select.h b/src/feature/nodelist/node_select.h
index 18a14ff0cb..cc2e656e70 100644
--- a/src/feature/nodelist/node_select.h
+++ b/src/feature/nodelist/node_select.h
@@ -34,6 +34,8 @@ typedef enum router_crn_flags_t {
CRN_RENDEZVOUS_V3 = 1<<6,
/* On clients, only provide nodes that can initiate IPv6 extends. */
CRN_INITIATE_IPV6_EXTEND = 1<<7,
+ /* On clients, only provide nodes that support Conflux (Relay=5). */
+ CRN_CONFLUX = 1<<8,
} router_crn_flags_t;
/** Possible ways to weight routers when choosing one randomly. See
diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c
index b895a2c7f8..bbaa51a407 100644
--- a/src/feature/nodelist/nodelist.c
+++ b/src/feature/nodelist/nodelist.c
@@ -1205,7 +1205,7 @@ node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id)
/** Dummy object that should be unreturnable. Used to ensure that
* node_get_protover_summary_flags() always returns non-NULL. */
static const protover_summary_flags_t zero_protover_flags = {
- 0,0,0,0,0,0,0,0,0,0,0,0,0
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
/** Return the protover_summary_flags for a given node. */
@@ -1341,6 +1341,15 @@ node_supports_accepting_ipv6_extends(const node_t *node,
}
}
+/** Return true iff the given node supports conflux (Relay=5) */
+bool
+node_supports_conflux(const node_t *node)
+{
+ tor_assert(node);
+
+ return node_get_protover_summary_flags(node)->supports_conflux;
+}
+
/** Return the RSA ID key's SHA1 digest for the provided node. */
const uint8_t *
node_get_rsa_id_digest(const node_t *node)
diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h
index 5a45490dbb..3d5ad9c0ea 100644
--- a/src/feature/nodelist/nodelist.h
+++ b/src/feature/nodelist/nodelist.h
@@ -84,6 +84,7 @@ bool node_supports_establish_intro_dos_extension(const node_t *node);
bool node_supports_initiating_ipv6_extends(const node_t *node);
bool node_supports_accepting_ipv6_extends(const node_t *node,
bool need_canonical_ipv6_conn);
+bool node_supports_conflux(const node_t *node);
const uint8_t *node_get_rsa_id_digest(const node_t *node);
MOCK_DECL(smartlist_t *,node_get_link_specifier_smartlist,(const node_t *node,
diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c
index c00f7ffb26..63de68dda7 100644
--- a/src/feature/nodelist/routerlist.c
+++ b/src/feature/nodelist/routerlist.c
@@ -243,7 +243,6 @@ router_rebuild_store(int flags, desc_store_t *store)
int r = -1;
off_t offset = 0;
smartlist_t *signed_descriptors = NULL;
- int nocache=0;
size_t total_expected_len = 0;
int had_any;
int force = flags & RRS_FORCE;
@@ -304,7 +303,6 @@ router_rebuild_store(int flags, desc_store_t *store)
goto done;
}
if (sd->do_not_cache) {
- ++nocache;
continue;
}
c = tor_malloc(sizeof(sized_chunk_t));
@@ -560,6 +558,7 @@ router_can_choose_node(const node_t *node, int flags)
const bool direct_conn = (flags & CRN_DIRECT_CONN) != 0;
const bool rendezvous_v3 = (flags & CRN_RENDEZVOUS_V3) != 0;
const bool initiate_ipv6_extend = (flags & CRN_INITIATE_IPV6_EXTEND) != 0;
+ const bool need_conflux = (flags & CRN_CONFLUX) != 0;
const or_options_t *options = get_options();
const bool check_reach =
@@ -594,6 +593,10 @@ router_can_choose_node(const node_t *node, int flags)
if (rendezvous_v3 &&
!node_supports_v3_rendezvous_point(node))
return false;
+ /* Exclude relay that don't do conflux if requested. */
+ if (need_conflux && !node_supports_conflux(node)) {
+ return false;
+ }
/* Choose a node with an OR address that matches the firewall rules */
if (direct_conn && check_reach &&
!reachable_addr_allows_node(node,
@@ -1924,11 +1927,9 @@ routerlist_remove_old_routers(void)
retain = digestset_new(n_max_retain);
}
- cutoff = now - OLD_ROUTER_DESC_MAX_AGE;
/* Retain anything listed in the consensus. */
if (consensus) {
SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs,
- if (rs->published_on >= cutoff)
digestset_add(retain, rs->descriptor_digest));
}
@@ -2653,7 +2654,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
digestmap_t *map = NULL;
smartlist_t *no_longer_old = smartlist_new();
smartlist_t *downloadable = smartlist_new();
- routerstatus_t *source = NULL;
+ const routerstatus_t *source = NULL;
int authdir = authdir_mode(options);
int n_delayed=0, n_have=0, n_would_reject=0, n_wouldnt_use=0,
n_inprogress=0, n_in_oldrouters=0;
@@ -2669,10 +2670,17 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
networkstatus_voter_info_t *voter = smartlist_get(consensus->voters, 0);
tor_assert(voter);
ds = trusteddirserver_get_by_v3_auth_digest(voter->identity_digest);
- if (ds)
- source = &(ds->fake_status);
- else
+ if (ds) {
+ source = router_get_consensus_status_by_id(ds->digest);
+ if (!source) {
+ /* prefer to use the address in the consensus, but fall back to
+ * the hard-coded trusted_dir_server address if we don't have a
+ * consensus or this digest isn't in our consensus. */
+ source = &ds->fake_status;
+ }
+ } else {
log_warn(LD_DIR, "couldn't lookup source from vote?");
+ }
}
map = digestmap_new();
@@ -2721,17 +2729,20 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
continue; /* We would never use it ourself. */
}
if (is_vote && source) {
- char time_bufnew[ISO_TIME_LEN+1];
- char time_bufold[ISO_TIME_LEN+1];
+ char old_digest_buf[HEX_DIGEST_LEN+1];
+ const char *old_digest = "none";
const routerinfo_t *oldrouter;
oldrouter = router_get_by_id_digest(rs->identity_digest);
- format_iso_time(time_bufnew, rs->published_on);
- if (oldrouter)
- format_iso_time(time_bufold, oldrouter->cache_info.published_on);
+ if (oldrouter) {
+ base16_encode(old_digest_buf, sizeof(old_digest_buf),
+ oldrouter->cache_info.signed_descriptor_digest,
+ DIGEST_LEN);
+ old_digest = old_digest_buf;
+ }
log_info(LD_DIR, "Learned about %s (%s vs %s) from %s's vote (%s)",
routerstatus_describe(rs),
- time_bufnew,
- oldrouter ? time_bufold : "none",
+ hex_str(rs->descriptor_digest, DIGEST_LEN),
+ old_digest,
source->nickname, oldrouter ? "known" : "unknown");
}
smartlist_add(downloadable, rs->descriptor_digest);
diff --git a/src/feature/nodelist/routerstatus_st.h b/src/feature/nodelist/routerstatus_st.h
index 55b76de581..a36c80917c 100644
--- a/src/feature/nodelist/routerstatus_st.h
+++ b/src/feature/nodelist/routerstatus_st.h
@@ -21,7 +21,6 @@ struct routerstatus_t {
* routerstatus_has_visibly_changed and the printing function
* routerstatus_format_entry in NS_CONTROL_PORT mode.
*/
- time_t published_on; /**< When was this router published? */
char nickname[MAX_NICKNAME_LEN+1]; /**< The nickname this router says it
* has. */
char identity_digest[DIGEST_LEN]; /**< Digest of the router's identity
diff --git a/src/feature/nodelist/vote_routerstatus_st.h b/src/feature/nodelist/vote_routerstatus_st.h
index 6b2f7b92a9..41d465db8f 100644
--- a/src/feature/nodelist/vote_routerstatus_st.h
+++ b/src/feature/nodelist/vote_routerstatus_st.h
@@ -18,6 +18,7 @@
struct vote_routerstatus_t {
routerstatus_t status; /**< Underlying 'status' object for this router.
* Flags are redundant. */
+ time_t published_on; /**< When was this router published? */
/** How many known-flags are allowed in a vote? This is the width of
* the flags field of vote_routerstatus_t */
#define MAX_KNOWN_FLAGS_IN_VOTE 64
diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c
index a38bf5cf5a..f6a020d061 100644
--- a/src/feature/relay/dns.c
+++ b/src/feature/relay/dns.c
@@ -71,6 +71,7 @@
#include "core/or/edge_connection_st.h"
#include "core/or/or_circuit_st.h"
+#include "core/or/conflux_util.h"
#include "ht.h"
@@ -650,6 +651,7 @@ dns_resolve(edge_connection_t *exitconn)
* connected cell. */
exitconn->next_stream = oncirc->n_streams;
oncirc->n_streams = exitconn;
+ conflux_update_n_streams(oncirc, exitconn);
}
break;
case 0:
@@ -658,6 +660,7 @@ dns_resolve(edge_connection_t *exitconn)
exitconn->base_.state = EXIT_CONN_STATE_RESOLVING;
exitconn->next_stream = oncirc->resolving_streams;
oncirc->resolving_streams = exitconn;
+ conflux_update_resolving_streams(oncirc, exitconn);
break;
case -2:
case -1:
@@ -768,11 +771,11 @@ dns_resolve_impl,(edge_connection_t *exitconn, int is_resolve,
if (!is_reverse || !is_resolve) {
if (!is_reverse)
- log_info(LD_EXIT, "Bad .in-addr.arpa address \"%s\"; sending error.",
+ log_info(LD_EXIT, "Bad .in-addr.arpa address %s; sending error.",
escaped_safe_str(exitconn->base_.address));
else if (!is_resolve)
log_info(LD_EXIT,
- "Attempt to connect to a .in-addr.arpa address \"%s\"; "
+ "Attempt to connect to a .in-addr.arpa address %s; "
"sending error.",
escaped_safe_str(exitconn->base_.address));
@@ -1234,6 +1237,7 @@ inform_pending_connections(cached_resolve_t *resolve)
pend->conn->next_stream = TO_OR_CIRCUIT(circ)->n_streams;
pend->conn->on_circuit = circ;
TO_OR_CIRCUIT(circ)->n_streams = pend->conn;
+ conflux_update_n_streams(TO_OR_CIRCUIT(circ), pend->conn);
connection_exit_connect(pend->conn);
} else {
@@ -1459,7 +1463,7 @@ configure_libevent_options(void)
* the query itself timed out in transit. */
SET("timeout:", get_consensus_param_exit_dns_timeout());
- /* This tells libevent to attemps up to X times a DNS query if the previous
+ /* This tells libevent to attempt up to X times a DNS query if the previous
* one failed to complete within N second. We believe that this should be
* enough to catch temporary hiccups on the first query. But after that, it
* should signal us that it won't be able to resolve it. */
diff --git a/src/feature/relay/onion_queue.c b/src/feature/relay/onion_queue.c
index b844aefcd1..f3f4b169f4 100644
--- a/src/feature/relay/onion_queue.c
+++ b/src/feature/relay/onion_queue.c
@@ -115,7 +115,7 @@ get_onion_queue_max_delay(const or_options_t *options)
/**
* We combine ntorv3 and ntor into the same queue, so we must
- * use this function to covert the cell type to a queue index.
+ * use this function to convert the cell type to a queue index.
*/
static inline uint16_t
onionskin_type_to_queue(uint16_t type)
diff --git a/src/feature/relay/relay_config.c b/src/feature/relay/relay_config.c
index 85ccfc18a7..0b02461318 100644
--- a/src/feature/relay/relay_config.c
+++ b/src/feature/relay/relay_config.c
@@ -30,9 +30,11 @@
#include "core/mainloop/cpuworker.h"
#include "core/mainloop/mainloop.h"
#include "core/or/connection_or.h"
+#include "core/or/policies.h"
#include "core/or/port_cfg_st.h"
#include "feature/hibernate/hibernate.h"
+#include "feature/hs/hs_service.h"
#include "feature/nodelist/nickname.h"
#include "feature/stats/geoip_stats.h"
#include "feature/stats/predict_ports.h"
@@ -942,7 +944,8 @@ options_validate_relay_accounting(const or_options_t *old_options,
if (accounting_parse_options(options, 1)<0)
REJECT("Failed to parse accounting options. See logs for details.");
- if (options->AccountingMax) {
+ if (options->AccountingMax &&
+ !hs_service_non_anonymous_mode_enabled(options)) {
if (options->RendConfigLines && server_mode(options)) {
log_warn(LD_CONFIG, "Using accounting with a hidden service and an "
"ORPort is risky: your hidden service(s) and your public "
@@ -1118,7 +1121,8 @@ options_validate_relay_mode(const or_options_t *old_options,
if (BUG(!msg))
return -1;
- if (server_mode(options) && options->RendConfigLines)
+ if (server_mode(options) && options->RendConfigLines &&
+ !hs_service_non_anonymous_mode_enabled(options))
log_warn(LD_CONFIG,
"Tor is currently configured as a relay and a hidden service. "
"That's not very secure: you should probably run your hidden service "
@@ -1147,6 +1151,13 @@ options_validate_relay_mode(const or_options_t *old_options,
REJECT("BridgeRelay is 1, ORPort is not set. This is an invalid "
"combination.");
+ if (options->BridgeRelay == 1 && (options->ExitRelay == 1 ||
+ !policy_using_default_exit_options(options))) {
+ log_warn(LD_CONFIG, "BridgeRelay is 1, but ExitRelay is 1 or an "
+ "ExitPolicy is configured. Tor will start, but it will not "
+ "function as an exit relay.");
+ }
+
if (server_mode(options)) {
char *dircache_msg = NULL;
if (have_enough_mem_for_dircache(options, 0, &dircache_msg)) {
@@ -1324,12 +1335,6 @@ options_act_relay(const or_options_t *old_options)
"Worker-related options changed. Rotating workers.");
const int server_mode_turned_on =
server_mode(options) && !server_mode(old_options);
- const int dir_server_mode_turned_on =
- dir_server_mode(options) && !dir_server_mode(old_options);
-
- if (server_mode_turned_on || dir_server_mode_turned_on) {
- cpu_init();
- }
if (server_mode_turned_on) {
ip_address_changed(0);
diff --git a/src/feature/relay/relay_find_addr.c b/src/feature/relay/relay_find_addr.c
index f4f9d40823..106117b236 100644
--- a/src/feature/relay/relay_find_addr.c
+++ b/src/feature/relay/relay_find_addr.c
@@ -78,7 +78,7 @@ relay_address_new_suggestion(const tor_addr_t *suggested_addr,
/* Do not believe anyone who says our address is their address. */
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"A relay endpoint %s is telling us that their address is ours.",
- fmt_addr(peer_addr));
+ safe_str(fmt_addr(peer_addr)));
return;
}
@@ -212,17 +212,19 @@ relay_addr_learn_from_dirauth(void)
return;
}
const node_t *node = node_get_by_id(rs->identity_digest);
- if (!node) {
+ extend_info_t *ei = NULL;
+ if (node) {
+ ei = extend_info_from_node(node, 1, false);
+ }
+ if (!node || !ei) {
/* This can happen if we are still in the early starting stage where no
* descriptors we actually fetched and thus we have the routerstatus_t
* for the authority but not its descriptor which is needed to build a
* circuit and thus learn our address. */
- log_info(LD_GENERAL, "Can't build a circuit to an authority. Unable to "
- "learn for now our address from them.");
- return;
- }
- extend_info_t *ei = extend_info_from_node(node, 1, false);
- if (BUG(!ei)) {
+ log_info(LD_GENERAL,
+ "Trying to learn our IP address by connecting to an "
+ "authority, but can't build a circuit to one yet. Will try "
+ "again soon.");
return;
}
diff --git a/src/feature/relay/relay_metrics.c b/src/feature/relay/relay_metrics.c
index cdf34a3404..8f3b82bd96 100644
--- a/src/feature/relay/relay_metrics.c
+++ b/src/feature/relay/relay_metrics.c
@@ -32,8 +32,10 @@
#include "feature/nodelist/nodelist.h"
#include "feature/nodelist/node_st.h"
#include "feature/nodelist/routerstatus_st.h"
+#include "feature/nodelist/torcert.h"
#include "feature/relay/relay_metrics.h"
#include "feature/relay/router.h"
+#include "feature/relay/routerkeys.h"
#include "feature/stats/rephist.h"
#include <event2/dns.h>
@@ -55,6 +57,12 @@ static void fill_streams_values(void);
static void fill_relay_flags(void);
static void fill_tcp_exhaustion_values(void);
static void fill_traffic_values(void);
+static void fill_signing_cert_expiry(void);
+
+static void fill_est_intro_cells(void);
+static void fill_est_rend_cells(void);
+static void fill_intro1_cells(void);
+static void fill_rend1_cells(void);
/** The base metrics that is a static array of metrics added to the metrics
* store.
@@ -174,6 +182,41 @@ static const relay_metrics_entry_t base_metrics[] =
.help = "Total number of circuits",
.fill_fn = fill_circuits_values,
},
+ {
+ .key = RELAY_METRICS_SIGNING_CERT_EXPIRY,
+ .type = METRICS_TYPE_GAUGE,
+ .name = METRICS_NAME(relay_signing_cert_expiry_timestamp),
+ .help = "Timestamp at which the current online keys will expire",
+ .fill_fn = fill_signing_cert_expiry,
+ },
+ {
+ .key = RELAY_METRICS_NUM_EST_REND,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_est_rend_total),
+ .help = "Total number of EST_REND cells we received",
+ .fill_fn = fill_est_rend_cells,
+ },
+ {
+ .key = RELAY_METRICS_NUM_EST_INTRO,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_est_intro_total),
+ .help = "Total number of EST_INTRO cells we received",
+ .fill_fn = fill_est_intro_cells,
+ },
+ {
+ .key = RELAY_METRICS_NUM_INTRO1_CELLS,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_intro1_total),
+ .help = "Total number of INTRO1 cells we received",
+ .fill_fn = fill_intro1_cells,
+ },
+ {
+ .key = RELAY_METRICS_NUM_REND1_CELLS,
+ .type = METRICS_TYPE_COUNTER,
+ .name = METRICS_NAME(relay_rend1_total),
+ .help = "Total number of REND1 cells we received",
+ .fill_fn = fill_rend1_cells,
+ },
};
static const size_t num_base_metrics = ARRAY_LENGTH(base_metrics);
@@ -222,8 +265,8 @@ fill_circuits_values(void)
{
const relay_metrics_entry_t *rentry =
&base_metrics[RELAY_METRICS_NUM_CIRCUITS];
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "opened"));
@@ -255,57 +298,57 @@ fill_relay_flags(void)
const relay_metrics_entry_t *rentry =
&base_metrics[RELAY_METRICS_RELAY_FLAGS];
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "Fast"));
metrics_store_entry_update(sentry, is_fast);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "Exit"));
metrics_store_entry_update(sentry, is_exit);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "Authority"));
metrics_store_entry_update(sentry, is_authority);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "Stable"));
metrics_store_entry_update(sentry, is_stable);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "HSDir"));
metrics_store_entry_update(sentry, is_hs_dir);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "Running"));
metrics_store_entry_update(sentry, is_running);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "V2Dir"));
metrics_store_entry_update(sentry, is_v2_dir);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "Sybil"));
metrics_store_entry_update(sentry, is_sybil);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "Guard"));
metrics_store_entry_update(sentry, is_guard);
@@ -317,15 +360,15 @@ fill_traffic_values(void)
{
const relay_metrics_entry_t *rentry =
&base_metrics[RELAY_METRICS_NUM_TRAFFIC];
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("direction", "read"));
metrics_store_entry_update(sentry, get_bytes_read());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("direction", "written"));
metrics_store_entry_update(sentry, get_bytes_written());
@@ -336,57 +379,57 @@ static void
fill_dos_values(void)
{
const relay_metrics_entry_t *rentry = &base_metrics[RELAY_METRICS_NUM_DOS];
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "circuit_rejected"));
metrics_store_entry_update(sentry, dos_get_num_cc_rejected());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "circuit_killed_max_cell"));
metrics_store_entry_update(sentry, stats_n_circ_max_cell_reached);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "circuit_killed_max_cell_outq"));
metrics_store_entry_update(sentry, stats_n_circ_max_cell_outq_reached);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "marked_address"));
metrics_store_entry_update(sentry, dos_get_num_cc_marked_addr());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "marked_address_maxq"));
metrics_store_entry_update(sentry, dos_get_num_cc_marked_addr_maxq());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "conn_rejected"));
metrics_store_entry_update(sentry, dos_get_num_conn_addr_connect_rejected());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "concurrent_conn_rejected"));
metrics_store_entry_update(sentry, dos_get_num_conn_addr_rejected());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "single_hop_refused"));
metrics_store_entry_update(sentry, dos_get_num_single_hop_refused());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("type", "introduce2_rejected"));
metrics_store_entry_update(sentry, hs_dos_get_intro2_rejected_count());
@@ -399,8 +442,8 @@ fill_cc_counters_values(void)
const relay_metrics_entry_t *rentry =
&base_metrics[RELAY_METRICS_CC_COUNTERS];
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "starvation"));
metrics_store_entry_add_label(sentry,
@@ -408,7 +451,7 @@ fill_cc_counters_values(void)
metrics_store_entry_update(sentry, congestion_control_get_num_rtt_reset());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "clock_stalls"));
metrics_store_entry_add_label(sentry,
@@ -417,7 +460,7 @@ fill_cc_counters_values(void)
congestion_control_get_num_clock_stalls());
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "flow_control"));
metrics_store_entry_add_label(sentry,
@@ -426,7 +469,7 @@ fill_cc_counters_values(void)
cc_stats_flow_num_xoff_sent);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "flow_control"));
metrics_store_entry_add_label(sentry,
@@ -435,7 +478,7 @@ fill_cc_counters_values(void)
cc_stats_flow_num_xon_sent);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_limits"));
metrics_store_entry_add_label(sentry,
@@ -443,7 +486,7 @@ fill_cc_counters_values(void)
metrics_store_entry_update(sentry, cc_stats_vegas_above_delta);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_limits"));
metrics_store_entry_add_label(sentry,
@@ -451,7 +494,7 @@ fill_cc_counters_values(void)
metrics_store_entry_update(sentry, cc_stats_vegas_above_ss_cwnd_max);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_limits"));
metrics_store_entry_add_label(sentry,
@@ -459,7 +502,7 @@ fill_cc_counters_values(void)
metrics_store_entry_update(sentry, cc_stats_vegas_below_ss_inc_floor);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_circuits"));
metrics_store_entry_add_label(sentry,
@@ -467,7 +510,7 @@ fill_cc_counters_values(void)
metrics_store_entry_update(sentry, cc_stats_circs_created);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_circuits"));
metrics_store_entry_add_label(sentry,
@@ -475,7 +518,7 @@ fill_cc_counters_values(void)
metrics_store_entry_update(sentry, cc_stats_circs_closed);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_circuits"));
metrics_store_entry_add_label(sentry,
@@ -490,8 +533,8 @@ fill_cc_gauges_values(void)
const relay_metrics_entry_t *rentry =
&base_metrics[RELAY_METRICS_CC_GAUGES];
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "slow_start_exit"));
metrics_store_entry_add_label(sentry,
@@ -500,7 +543,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_exit_ss_cwnd_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "slow_start_exit"));
metrics_store_entry_add_label(sentry,
@@ -509,7 +552,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_exit_ss_bdp_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "slow_start_exit"));
metrics_store_entry_add_label(sentry,
@@ -518,7 +561,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_exit_ss_inc_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "on_circ_close"));
metrics_store_entry_add_label(sentry,
@@ -527,7 +570,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_circ_close_cwnd_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "on_circ_close"));
metrics_store_entry_add_label(sentry,
@@ -536,7 +579,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_circ_close_ss_cwnd_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "buffers"));
metrics_store_entry_add_label(sentry,
@@ -545,7 +588,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_flow_xon_outbuf_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "buffers"));
metrics_store_entry_add_label(sentry,
@@ -554,7 +597,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_flow_xoff_outbuf_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_backoff"));
metrics_store_entry_add_label(sentry,
@@ -563,7 +606,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_csig_blocked_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_backoff"));
metrics_store_entry_add_label(sentry,
@@ -572,7 +615,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_gamma_drop_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_backoff"));
metrics_store_entry_add_label(sentry,
@@ -581,7 +624,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_delta_drop_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_backoff"));
metrics_store_entry_add_label(sentry,
@@ -590,7 +633,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_ss_csig_blocked_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_cwnd_update"));
metrics_store_entry_add_label(sentry,
@@ -599,7 +642,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_csig_alpha_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_cwnd_update"));
metrics_store_entry_add_label(sentry,
@@ -608,7 +651,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_csig_beta_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_cwnd_update"));
metrics_store_entry_add_label(sentry,
@@ -617,7 +660,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_csig_delta_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_estimates"));
metrics_store_entry_add_label(sentry,
@@ -626,7 +669,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_ss_queue_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_estimates"));
metrics_store_entry_add_label(sentry,
@@ -635,7 +678,7 @@ fill_cc_gauges_values(void)
tor_llround(cc_stats_vegas_queue_ma));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("state", "cc_estimates"));
metrics_store_entry_add_label(sentry,
@@ -659,16 +702,16 @@ fill_streams_values(void)
{
const relay_metrics_entry_t *rentry =
&base_metrics[RELAY_METRICS_NUM_STREAMS];
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
fill_single_stream_value(sentry, RELAY_COMMAND_BEGIN);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_stream_value(sentry, RELAY_COMMAND_BEGIN_DIR);
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_stream_value(sentry, RELAY_COMMAND_RESOLVE);
}
@@ -704,31 +747,31 @@ fill_conn_counter_values(void)
if (i == 10) {
continue;
}
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "initiated", "created", AF_INET,
rep_hist_get_conn_created(false, i, AF_INET));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "initiated", "created", AF_INET6,
rep_hist_get_conn_created(false, i,
AF_INET6));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "received", "created", AF_INET,
rep_hist_get_conn_created(true, i, AF_INET));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "received", "created", AF_INET6,
rep_hist_get_conn_created(true, i, AF_INET6));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "received", "rejected", AF_INET,
rep_hist_get_conn_rejected(i, AF_INET));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "received", "rejected", AF_INET6,
rep_hist_get_conn_rejected(i, AF_INET6));
@@ -748,21 +791,21 @@ fill_conn_gauge_values(void)
if (i == 10) {
continue;
}
- metrics_store_entry_t *sentry =
- metrics_store_add(the_store, rentry->type, rentry->name, rentry->help);
+ metrics_store_entry_t *sentry = metrics_store_add(
+ the_store, rentry->type, rentry->name, rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "initiated", "opened", AF_INET,
rep_hist_get_conn_opened(false, i, AF_INET));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "initiated", "opened", AF_INET6,
rep_hist_get_conn_opened(false, i, AF_INET6));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "received", "opened", AF_INET,
rep_hist_get_conn_opened(true, i, AF_INET));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
fill_single_connection_value(sentry, i, "received", "opened", AF_INET6,
rep_hist_get_conn_opened(true, i, AF_INET6));
}
@@ -777,7 +820,7 @@ fill_tcp_exhaustion_values(void)
&base_metrics[RELAY_METRICS_NUM_TCP_EXHAUSTION];
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_update(sentry, rep_hist_get_n_tcp_exhaustion());
}
@@ -835,7 +878,7 @@ fill_dns_error_values(void)
for (size_t j = 0; j < num_errors; j++) {
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry, record_label);
metrics_store_entry_add_label(sentry,
metrics_format_label("reason", errors[j].name));
@@ -849,7 +892,7 @@ fill_dns_error_values(void)
/* 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);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("reason", errors[j].name));
metrics_store_entry_update(sentry,
@@ -873,7 +916,7 @@ fill_dns_query_values(void)
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);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry, record_label);
metrics_store_entry_update(sentry,
rep_hist_get_n_dns_request(dns_types[i].type));
@@ -882,7 +925,7 @@ fill_dns_query_values(void)
#endif
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_update(sentry, rep_hist_get_n_dns_request(0));
}
@@ -895,13 +938,13 @@ fill_global_bw_limit_values(void)
&base_metrics[RELAY_METRICS_NUM_GLOBAL_RW_LIMIT];
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
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);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("side", "write"));
metrics_store_entry_update(sentry, rep_hist_get_n_write_limit_reached());
@@ -916,13 +959,13 @@ fill_socket_values(void)
&base_metrics[RELAY_METRICS_NUM_SOCKETS];
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
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);
+ rentry->help, 0, NULL);
metrics_store_entry_update(sentry, get_max_sockets());
}
@@ -940,7 +983,7 @@ fill_onionskins_values(void)
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);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry, type_label);
metrics_store_entry_add_label(sentry,
metrics_format_label("action", "processed"));
@@ -948,7 +991,7 @@ fill_onionskins_values(void)
rep_hist_get_circuit_n_handshake_assigned(t));
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry, type_label);
metrics_store_entry_add_label(sentry,
metrics_format_label("action", "dropped"));
@@ -967,30 +1010,196 @@ fill_oom_values(void)
&base_metrics[RELAY_METRICS_NUM_OOM_BYTES];
sentry = metrics_store_add(the_store, rentry->type, rentry->name,
- rentry->help);
+ rentry->help, 0, NULL);
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);
+ rentry->help, 0, NULL);
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);
+ rentry->help, 0, NULL);
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);
+ rentry->help, 0, NULL);
metrics_store_entry_add_label(sentry,
metrics_format_label("subsys", "hsdir"));
metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_hsdir);
}
+/** Fill function for the RELAY_METRICS_SIGNING_CERT_EXPIRY metrics. */
+static void
+fill_signing_cert_expiry(void)
+{
+ metrics_store_entry_t *sentry;
+ const tor_cert_t *signing_key;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_SIGNING_CERT_EXPIRY];
+
+ if (get_options()->OfflineMasterKey) {
+ signing_key = get_master_signing_key_cert();
+ if (signing_key) {
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help, 0, NULL);
+ metrics_store_entry_update(sentry, signing_key->valid_until);
+ }
+ }
+}
+
+static uint64_t est_intro_actions[EST_INTRO_ACTION_COUNT] = {0};
+
+void
+relay_increment_est_intro_action(est_intro_action_t action)
+{
+ est_intro_actions[action]++;
+}
+
+static void
+fill_est_intro_cells(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_EST_INTRO];
+
+ static struct {
+ const char *name;
+ est_intro_action_t key;
+ } actions[] = {
+ {.name = "success", .key = EST_INTRO_SUCCESS},
+ {.name = "malformed", .key = EST_INTRO_MALFORMED},
+ {.name = "unsuitable_circuit", .key = EST_INTRO_UNSUITABLE_CIRCUIT},
+ {.name = "circuit_dead", .key = EST_INTRO_CIRCUIT_DEAD},
+ };
+ static const size_t num_actions = ARRAY_LENGTH(actions);
+
+ for (size_t i = 0; i < num_actions; ++i) {
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help, 0, NULL);
+ metrics_store_entry_add_label(
+ sentry, metrics_format_label("action", actions[i].name));
+ metrics_store_entry_update(sentry,
+ (long)est_intro_actions[actions[i].key]);
+ }
+}
+
+static uint64_t est_rend_actions[EST_REND_ACTION_COUNT] = {0};
+
+void
+relay_increment_est_rend_action(est_rend_action_t action)
+{
+ est_rend_actions[action]++;
+}
+
+static void
+fill_est_rend_cells(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_EST_REND];
+
+ static struct {
+ const char *name;
+ est_rend_action_t key;
+ } actions[] = {
+ {.name = "success", .key = EST_REND_SUCCESS},
+ {.name = "unsuitable_circuit", .key = EST_REND_UNSUITABLE_CIRCUIT},
+ {.name = "single_hop", .key = EST_REND_SINGLE_HOP},
+ {.name = "malformed", .key = EST_REND_MALFORMED},
+ {.name = "duplicate_cookie", .key = EST_REND_DUPLICATE_COOKIE},
+ {.name = "circuit_dead", .key = EST_REND_CIRCUIT_DEAD},
+ };
+ static const size_t num_actions = ARRAY_LENGTH(actions);
+
+ for (size_t i = 0; i < num_actions; ++i) {
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help, 0, NULL);
+ metrics_store_entry_add_label(
+ sentry, metrics_format_label("action", actions[i].name));
+ metrics_store_entry_update(sentry, (long)est_rend_actions[actions[i].key]);
+ }
+}
+
+static uint64_t intro1_actions[INTRO1_ACTION_COUNT] = {0};
+
+void
+relay_increment_intro1_action(intro1_action_t action)
+{
+ intro1_actions[action]++;
+}
+
+static void
+fill_intro1_cells(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_INTRO1_CELLS];
+
+ static struct {
+ const char *name;
+ intro1_action_t key;
+ } actions[] = {
+ {.name = "success", .key = INTRO1_SUCCESS},
+ {.name = "circuit_dead", .key = INTRO1_CIRCUIT_DEAD},
+ {.name = "malformed", .key = INTRO1_MALFORMED},
+ {.name = "unknown_service", .key = INTRO1_UNKNOWN_SERVICE},
+ {.name = "rate_limited", .key = INTRO1_RATE_LIMITED},
+ {.name = "circuit_reused", .key = INTRO1_CIRCUIT_REUSED},
+ {.name = "single_hop", .key = INTRO1_SINGLE_HOP},
+ };
+ static const size_t num_actions = ARRAY_LENGTH(actions);
+
+ for (size_t i = 0; i < num_actions; ++i) {
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help, 0, NULL);
+ metrics_store_entry_add_label(
+ sentry, metrics_format_label("action", actions[i].name));
+ metrics_store_entry_update(sentry, (long)intro1_actions[actions[i].key]);
+ }
+}
+
+static uint64_t rend1_actions[REND1_ACTION_COUNT] = {0};
+
+void
+relay_increment_rend1_action(rend1_action_t action)
+{
+ rend1_actions[action]++;
+}
+
+static void
+fill_rend1_cells(void)
+{
+ metrics_store_entry_t *sentry;
+ const relay_metrics_entry_t *rentry =
+ &base_metrics[RELAY_METRICS_NUM_REND1_CELLS];
+
+ static struct {
+ const char *name;
+ rend1_action_t key;
+ } actions[] = {
+ {.name = "success", .key = REND1_SUCCESS},
+ {.name = "unsuitable_circuit", .key = REND1_UNSUITABLE_CIRCUIT},
+ {.name = "malformed", .key = REND1_MALFORMED},
+ {.name = "unknown_cookie", .key = REND1_UNKNOWN_COOKIE},
+ {.name = "circuit_dead", .key = REND1_CIRCUIT_DEAD},
+ };
+ static const size_t num_actions = ARRAY_LENGTH(actions);
+
+ for (size_t i = 0; i < num_actions; ++i) {
+ sentry = metrics_store_add(the_store, rentry->type, rentry->name,
+ rentry->help, 0, NULL);
+ metrics_store_entry_add_label(
+ sentry, metrics_format_label("action", actions[i].name));
+ metrics_store_entry_update(sentry, (long)rend1_actions[actions[i].key]);
+ }
+}
+
/** Reset the global store and fill it with all the metrics from base_metrics
* and their associated values.
*
diff --git a/src/feature/relay/relay_metrics.h b/src/feature/relay/relay_metrics.h
index 1d2d649d8a..cf9dddf955 100644
--- a/src/feature/relay/relay_metrics.h
+++ b/src/feature/relay/relay_metrics.h
@@ -47,6 +47,16 @@ typedef enum {
RELAY_METRICS_RELAY_FLAGS,
/** Numer of circuits. */
RELAY_METRICS_NUM_CIRCUITS,
+ /** Timestamp at which the current online keys will expire. */
+ RELAY_METRICS_SIGNING_CERT_EXPIRY,
+ /** Number of times we received an EST_REND cell */
+ RELAY_METRICS_NUM_EST_REND,
+ /** Number of times we received an EST_INTRO cell */
+ RELAY_METRICS_NUM_EST_INTRO,
+ /** Number of times we received an INTRO1 cell */
+ RELAY_METRICS_NUM_INTRO1_CELLS,
+ /** Number of times we received a REND1 cell */
+ RELAY_METRICS_NUM_REND1_CELLS,
} relay_metrics_key_t;
/** The metadata of a relay metric. */
@@ -70,4 +80,54 @@ void relay_metrics_free(void);
/* Accessors. */
const smartlist_t *relay_metrics_get_stores(void);
+typedef enum {
+ EST_INTRO_SUCCESS,
+ EST_INTRO_MALFORMED,
+ EST_INTRO_UNSUITABLE_CIRCUIT,
+ EST_INTRO_CIRCUIT_DEAD,
+
+ EST_INTRO_ACTION_COUNT
+} est_intro_action_t;
+
+void relay_increment_est_intro_action(est_intro_action_t);
+
+typedef enum {
+ EST_REND_SUCCESS,
+ EST_REND_UNSUITABLE_CIRCUIT,
+ EST_REND_SINGLE_HOP,
+ EST_REND_MALFORMED,
+ EST_REND_DUPLICATE_COOKIE,
+ EST_REND_CIRCUIT_DEAD,
+
+ EST_REND_ACTION_COUNT
+} est_rend_action_t;
+
+void relay_increment_est_rend_action(est_rend_action_t);
+
+typedef enum {
+ INTRO1_SUCCESS,
+ INTRO1_CIRCUIT_DEAD,
+ INTRO1_MALFORMED,
+ INTRO1_UNKNOWN_SERVICE,
+ INTRO1_RATE_LIMITED,
+ INTRO1_CIRCUIT_REUSED,
+ INTRO1_SINGLE_HOP,
+
+ INTRO1_ACTION_COUNT
+} intro1_action_t;
+
+void relay_increment_intro1_action(intro1_action_t);
+
+typedef enum {
+ REND1_SUCCESS,
+ REND1_UNSUITABLE_CIRCUIT,
+ REND1_MALFORMED,
+ REND1_UNKNOWN_COOKIE,
+ REND1_CIRCUIT_DEAD,
+
+ REND1_ACTION_COUNT
+} rend1_action_t;
+
+void relay_increment_rend1_action(rend1_action_t);
+
#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 dd9be4e36f..7661d00afc 100644
--- a/src/feature/relay/relay_periodic.c
+++ b/src/feature/relay/relay_periodic.c
@@ -102,7 +102,9 @@ rotate_onion_key_callback(time_t now, const or_options_t *options)
}
log_info(LD_GENERAL,"Rotating onion key.");
- rotate_onion_key();
+ if (!rotate_onion_key()) {
+ return ONION_KEY_CONSENSUS_CHECK_INTERVAL;
+ }
cpuworkers_rotate_keyinfo();
if (!router_rebuild_descriptor(1)) {
log_info(LD_CONFIG, "Couldn't rebuild router descriptor");
diff --git a/src/feature/relay/relay_stub.c b/src/feature/relay/relay_stub.c
index c7ac9093fa..4531096316 100644
--- a/src/feature/relay/relay_stub.c
+++ b/src/feature/relay/relay_stub.c
@@ -12,6 +12,7 @@
#include "orconfig.h"
#include "feature/relay/relay_sys.h"
#include "lib/subsys/subsys.h"
+#include "feature/relay/relay_metrics.h"
const struct subsys_fns_t sys_relay = {
.name = "relay",
@@ -19,3 +20,27 @@ const struct subsys_fns_t sys_relay = {
.supported = false,
.level = RELAY_SUBSYS_LEVEL,
};
+
+void
+relay_increment_est_intro_action(est_intro_action_t action)
+{
+ (void)action;
+}
+
+void
+relay_increment_est_rend_action(est_rend_action_t action)
+{
+ (void)action;
+}
+
+void
+relay_increment_intro1_action(intro1_action_t action)
+{
+ (void)action;
+}
+
+void
+relay_increment_rend1_action(rend1_action_t action)
+{
+ (void)action;
+}
diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c
index bc98fd985c..1ed9630e09 100644
--- a/src/feature/relay/router.c
+++ b/src/feature/relay/router.c
@@ -482,8 +482,10 @@ get_my_v3_legacy_signing_key(void)
* - schedule all previous cpuworkers to shut down _after_ processing
* pending work. (This will cause fresh cpuworkers to be generated.)
* - generate and upload a fresh routerinfo.
+ *
+ * Return true on success, else false on error.
*/
-void
+bool
rotate_onion_key(void)
{
char *fname, *fname_prev;
@@ -491,6 +493,7 @@ rotate_onion_key(void)
or_state_t *state = get_or_state();
curve25519_keypair_t new_curve25519_keypair;
time_t now;
+ bool result = false;
fname = get_keydir_fname("secret_onion_key");
fname_prev = get_keydir_fname("secret_onion_key.old");
/* There isn't much point replacing an old key with an empty file */
@@ -540,6 +543,7 @@ rotate_onion_key(void)
tor_mutex_release(key_lock);
mark_my_descriptor_dirty("rotated onion key");
or_state_mark_dirty(state, get_options()->AvoidDiskWrites ? now+3600 : 0);
+ result = true;
goto done;
error:
log_warn(LD_GENERAL, "Couldn't rotate onion key.");
@@ -549,6 +553,7 @@ rotate_onion_key(void)
memwipe(&new_curve25519_keypair, 0, sizeof(new_curve25519_keypair));
tor_free(fname);
tor_free(fname_prev);
+ return result;
}
/** Log greeting message that points to new relay lifecycle document the
@@ -911,9 +916,9 @@ router_write_fingerprint(int hashed, int ed25519_identity)
goto done;
}
- log_notice(LD_GENERAL, "Your Tor %s identity key %s fingerprint is '%s %s'",
+ log_notice(LD_GENERAL, "Your Tor %s identity key %sfingerprint is '%s %s'",
hashed ? "bridge's hashed" : "server's",
- ed25519_identity ? "ed25519" : "",
+ ed25519_identity ? "ed25519 " : "",
options->Nickname, fingerprint);
result = 0;
@@ -2554,8 +2559,6 @@ mark_my_descriptor_dirty_if_too_old(time_t now)
rs = networkstatus_vote_find_entry(ns, server_identitykey_digest);
if (rs == NULL)
retry_fast_reason = "not listed in consensus";
- else if (rs->published_on < slow_cutoff)
- retry_fast_reason = "version listed in consensus is quite old";
else if (rs->is_staledesc && ns->valid_after > desc_clean_since)
retry_fast_reason = "listed as stale in consensus";
}
diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h
index b5b5a1fffa..f201fdbd63 100644
--- a/src/feature/relay/router.h
+++ b/src/feature/relay/router.h
@@ -45,7 +45,7 @@ authority_cert_t *get_my_v3_legacy_cert(void);
crypto_pk_t *get_my_v3_legacy_signing_key(void);
void dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last);
void expire_old_onion_keys(void);
-void rotate_onion_key(void);
+bool rotate_onion_key(void);
void v3_authority_check_key_expiry(void);
int get_onion_key_lifetime(void);
int get_onion_key_grace_period(void);
diff --git a/src/feature/relay/routerkeys.h b/src/feature/relay/routerkeys.h
index 7b6d80773c..b97615a9c9 100644
--- a/src/feature/relay/routerkeys.h
+++ b/src/feature/relay/routerkeys.h
@@ -53,7 +53,6 @@ void routerkeys_free_all(void);
static inline void *
relay_key_is_unavailable_(void)
{
- tor_assert_nonfatal_unreached();
return NULL;
}
#define relay_key_is_unavailable(type) \
diff --git a/src/feature/rend/rendmid.c b/src/feature/rend/rendmid.c
index 8f6a45dfef..dee91629ec 100644
--- a/src/feature/rend/rendmid.c
+++ b/src/feature/rend/rendmid.c
@@ -19,6 +19,7 @@
#include "feature/hs/hs_circuitmap.h"
#include "feature/hs/hs_dos.h"
#include "feature/hs/hs_intropoint.h"
+#include "feature/relay/relay_metrics.h"
#include "core/or/or_circuit_st.h"
@@ -36,6 +37,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
(unsigned)circ->p_circ_id);
if (circ->base_.purpose != CIRCUIT_PURPOSE_OR) {
+ relay_increment_est_rend_action(EST_REND_UNSUITABLE_CIRCUIT);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Tried to establish rendezvous on non-OR circuit with purpose %s",
circuit_purpose_to_string(circ->base_.purpose));
@@ -46,6 +48,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
* attempt to establish rendezvous points directly to us. */
if (channel_is_client(circ->p_chan) &&
dos_should_refuse_single_hop_client()) {
+ relay_increment_est_rend_action(EST_REND_SINGLE_HOP);
/* Note it down for the heartbeat log purposes. */
dos_note_refuse_single_hop_client();
/* Silent drop so the client has to time out before moving on. */
@@ -53,18 +56,21 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
}
if (circ->base_.n_chan) {
+ relay_increment_est_rend_action(EST_REND_UNSUITABLE_CIRCUIT);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Tried to establish rendezvous on non-edge circuit");
goto err;
}
if (request_len != REND_COOKIE_LEN) {
+ relay_increment_est_rend_action(EST_REND_MALFORMED);
log_fn(LOG_PROTOCOL_WARN,
LD_PROTOCOL, "Invalid length on ESTABLISH_RENDEZVOUS.");
goto err;
}
if (hs_circuitmap_get_rend_circ_relay_side(request)) {
+ relay_increment_est_rend_action(EST_REND_DUPLICATE_COOKIE);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Duplicate rendezvous cookie in ESTABLISH_RENDEZVOUS.");
goto err;
@@ -74,11 +80,13 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
if (relay_send_command_from_edge(0,TO_CIRCUIT(circ),
RELAY_COMMAND_RENDEZVOUS_ESTABLISHED,
"", 0, NULL)<0) {
+ relay_increment_est_rend_action(EST_REND_CIRCUIT_DEAD);
log_warn(LD_PROTOCOL, "Couldn't send RENDEZVOUS_ESTABLISHED cell.");
/* Stop right now, the circuit has been closed. */
return -1;
}
+ relay_increment_est_rend_action(EST_REND_SUCCESS);
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_POINT_WAITING);
hs_circuitmap_register_rend_circ_relay_side(circ, request);
@@ -108,6 +116,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
int reason = END_CIRC_REASON_INTERNAL;
if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) {
+ relay_increment_rend1_action(REND1_UNSUITABLE_CIRCUIT);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Tried to complete rendezvous on non-OR or non-edge circuit %u.",
(unsigned)circ->p_circ_id);
@@ -116,6 +125,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
}
if (request_len < REND_COOKIE_LEN) {
+ relay_increment_rend1_action(REND1_MALFORMED);
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Rejecting RENDEZVOUS1 cell with bad length (%d) on circuit %u.",
(int)request_len, (unsigned)circ->p_circ_id);
@@ -135,6 +145,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
* client gives up on a rendezvous circuit after sending INTRODUCE1, but
* before the onion service sends the RENDEZVOUS1 cell.
*/
+ relay_increment_rend1_action(REND1_UNKNOWN_COOKIE);
log_fn(LOG_DEBUG, LD_PROTOCOL,
"Rejecting RENDEZVOUS1 cell with unrecognized rendezvous cookie %s.",
hexid);
@@ -155,6 +166,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
RELAY_COMMAND_RENDEZVOUS2,
(char*)(request+REND_COOKIE_LEN),
request_len-REND_COOKIE_LEN, NULL)) {
+ relay_increment_rend1_action(REND1_CIRCUIT_DEAD);
log_warn(LD_GENERAL,
"Unable to send RENDEZVOUS2 cell to client on circuit %u.",
(unsigned)rend_circ->p_circ_id);
@@ -162,6 +174,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
return -1;
}
+ relay_increment_rend1_action(REND1_SUCCESS);
/* Join the circuits. */
log_info(LD_REND,
"Completing rendezvous: circuit %u joins circuit %u (cookie %s)",
diff --git a/src/feature/stats/geoip_stats.h b/src/feature/stats/geoip_stats.h
index b54304337a..3a455b2705 100644
--- a/src/feature/stats/geoip_stats.h
+++ b/src/feature/stats/geoip_stats.h
@@ -20,7 +20,7 @@
* the others, we're not.
*/
typedef enum {
- /** We've noticed a connection as a bridge relay or entry guard. */
+ /** An incoming ORPort connection */
GEOIP_CLIENT_CONNECT = 0,
/** We've served a networkstatus consensus as a directory server. */
GEOIP_CLIENT_NETWORKSTATUS = 1,
diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c
index d1ccc5edf5..8f4f33151a 100644
--- a/src/feature/stats/rephist.c
+++ b/src/feature/stats/rephist.c
@@ -2292,7 +2292,7 @@ static overload_onionskin_assessment_t overload_onionskin_assessment;
/**
* We combine ntorv3 and ntor into the same stat, so we must
- * use this function to covert the cell type to a stat index.
+ * use this function to convert the cell type to a stat index.
*/
static inline uint16_t
onionskin_type_to_stat(uint16_t type)
@@ -2315,7 +2315,7 @@ onionskin_type_to_stat(uint16_t type)
* the stats are reset back to 0 and the assessment time period updated.
*
* This is called when a ntor handshake is _requested_ because we want to avoid
- * to have an assymetric situation where requested counter is reset to 0 but
+ * to have an asymmetric situation where requested counter is reset to 0 but
* then a drop happens leading to the drop counter being incremented while the
* requested counter is 0. */
static void