diff options
Diffstat (limited to 'src/feature')
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(ô->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 |