aboutsummaryrefslogtreecommitdiff
path: root/src/feature
diff options
context:
space:
mode:
Diffstat (limited to 'src/feature')
-rw-r--r--src/feature/client/addressmap.c3
-rw-r--r--src/feature/client/bridges.c3
-rw-r--r--src/feature/client/entrynodes.c2
-rw-r--r--src/feature/client/transports.c6
-rw-r--r--src/feature/control/btrack.c2
-rw-r--r--src/feature/control/btrack_orconn_maps.c9
-rw-r--r--src/feature/control/control_bootstrap.c6
-rw-r--r--src/feature/control/control_cmd.c4
-rw-r--r--src/feature/control/control_events.h7
-rw-r--r--src/feature/dirauth/dirauth_config.c4
-rw-r--r--src/feature/dirauth/dircollate.c5
-rw-r--r--src/feature/dirauth/dirvote.c100
-rw-r--r--src/feature/dirauth/dirvote.h37
-rw-r--r--src/feature/dirauth/include.am6
-rw-r--r--src/feature/dirauth/keypin.c8
-rw-r--r--src/feature/dirauth/shared_random.c4
-rw-r--r--src/feature/dirauth/shared_random_state.c8
-rw-r--r--src/feature/dirauth/voting_schedule.c (renamed from src/feature/dircommon/voting_schedule.c)113
-rw-r--r--src/feature/dirauth/voting_schedule.h (renamed from src/feature/dircommon/voting_schedule.h)39
-rw-r--r--src/feature/dircache/conscache.c15
-rw-r--r--src/feature/dircache/consdiffmgr.c4
-rw-r--r--src/feature/dircache/dircache.c2
-rw-r--r--src/feature/dirclient/dirclient.c56
-rw-r--r--src/feature/dirclient/dirclient.h2
-rw-r--r--src/feature/dircommon/fp_pair.c5
-rw-r--r--src/feature/dircommon/include.am6
-rw-r--r--src/feature/dirparse/authcert_members.h2
-rw-r--r--src/feature/dirparse/authcert_parse.c2
-rw-r--r--src/feature/dirparse/microdesc_parse.c2
-rw-r--r--src/feature/dirparse/ns_parse.c8
-rw-r--r--src/feature/dirparse/routerparse.c4
-rw-r--r--src/feature/hs/hs_cache.c64
-rw-r--r--src/feature/hs/hs_cell.c138
-rw-r--r--src/feature/hs/hs_cell.h15
-rw-r--r--src/feature/hs/hs_circuit.c57
-rw-r--r--src/feature/hs/hs_circuit.h11
-rw-r--r--src/feature/hs/hs_circuitmap.c4
-rw-r--r--src/feature/hs/hs_client.c11
-rw-r--r--src/feature/hs/hs_common.c50
-rw-r--r--src/feature/hs/hs_common.h7
-rw-r--r--src/feature/hs/hs_config.c25
-rw-r--r--src/feature/hs/hs_descriptor.c26
-rw-r--r--src/feature/hs/hs_descriptor.h7
-rw-r--r--src/feature/hs/hs_ob.c408
-rw-r--r--src/feature/hs/hs_ob.h40
-rw-r--r--src/feature/hs/hs_service.c130
-rw-r--r--src/feature/hs/hs_service.h18
-rw-r--r--src/feature/hs/include.am2
-rw-r--r--src/feature/hs_common/shared_random_client.c82
-rw-r--r--src/feature/hs_common/shared_random_client.h2
-rw-r--r--src/feature/nodelist/authcert.c2
-rw-r--r--src/feature/nodelist/microdesc.c4
-rw-r--r--src/feature/nodelist/networkstatus.c48
-rw-r--r--src/feature/nodelist/networkstatus.h3
-rw-r--r--src/feature/nodelist/nodefamily.c4
-rw-r--r--src/feature/nodelist/nodelist.c12
-rw-r--r--src/feature/nodelist/nodelist.h4
-rw-r--r--src/feature/nodelist/routerlist.c6
-rw-r--r--src/feature/relay/dns.c53
-rw-r--r--src/feature/relay/dns.h61
-rw-r--r--src/feature/relay/ext_orport.c71
-rw-r--r--src/feature/relay/ext_orport.h50
-rw-r--r--src/feature/relay/include.am12
-rw-r--r--src/feature/relay/onion_queue.c6
-rw-r--r--src/feature/relay/relay_handshake.c565
-rw-r--r--src/feature/relay/relay_handshake.h90
-rw-r--r--src/feature/relay/router.h1
-rw-r--r--src/feature/relay/routerkeys.h82
-rw-r--r--src/feature/relay/selftest.h33
-rw-r--r--src/feature/stats/geoip_stats.c8
-rw-r--r--src/feature/stats/rephist.c4
71 files changed, 2286 insertions, 404 deletions
diff --git a/src/feature/client/addressmap.c b/src/feature/client/addressmap.c
index 1a6958d38c..cc97166f36 100644
--- a/src/feature/client/addressmap.c
+++ b/src/feature/client/addressmap.c
@@ -23,7 +23,6 @@
#include "app/config/config.h"
#include "core/or/connection_edge.h"
#include "feature/control/control_events.h"
-#include "feature/relay/dns.h"
#include "feature/nodelist/nodelist.h"
#include "feature/nodelist/routerset.h"
@@ -689,7 +688,7 @@ client_dns_set_addressmap_impl(entry_connection_t *for_conn,
if (ttl<0)
ttl = DEFAULT_DNS_TTL;
else
- ttl = dns_clip_ttl(ttl);
+ ttl = clip_dns_ttl(ttl);
if (exitname) {
/* XXXX fails to ever get attempts to get an exit address of
diff --git a/src/feature/client/bridges.c b/src/feature/client/bridges.c
index 2b52a1173d..66b04f3bc2 100644
--- a/src/feature/client/bridges.c
+++ b/src/feature/client/bridges.c
@@ -844,8 +844,7 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node)
}
}
- if (options->ClientPreferIPv6ORPort == -1 ||
- options->ClientAutoIPv6ORPort == 0) {
+ if (options->ClientPreferIPv6ORPort == -1) {
/* Mark which address to use based on which bridge_t we got. */
node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 &&
!tor_addr_is_null(&node->ri->ipv6_addr));
diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c
index 8962f65006..2843558e93 100644
--- a/src/feature/client/entrynodes.c
+++ b/src/feature/client/entrynodes.c
@@ -1974,10 +1974,12 @@ get_retry_schedule(time_t failing_since, time_t now,
const struct {
time_t maximum; int primary_delay; int nonprimary_delay;
} delays[] = {
+ // clang-format off
{ SIX_HOURS, 10*60, 1*60*60 },
{ FOUR_DAYS, 90*60, 4*60*60 },
{ SEVEN_DAYS, 4*60*60, 18*60*60 },
{ TIME_MAX, 9*60*60, 36*60*60 }
+ // clang-format on
};
unsigned i;
diff --git a/src/feature/client/transports.c b/src/feature/client/transports.c
index a8ea9781a4..55069bb60a 100644
--- a/src/feature/client/transports.c
+++ b/src/feature/client/transports.c
@@ -1420,8 +1420,10 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
smartlist_add_asprintf(envs, "TOR_PT_EXTENDED_SERVER_PORT=%s",
ext_or_addrport_tmp);
}
- smartlist_add_asprintf(envs, "TOR_PT_AUTH_COOKIE_FILE=%s",
- cookie_file_loc);
+ if (cookie_file_loc) {
+ smartlist_add_asprintf(envs, "TOR_PT_AUTH_COOKIE_FILE=%s",
+ cookie_file_loc);
+ }
tor_free(ext_or_addrport_tmp);
tor_free(cookie_file_loc);
diff --git a/src/feature/control/btrack.c b/src/feature/control/btrack.c
index 874150ee13..3595af0fcc 100644
--- a/src/feature/control/btrack.c
+++ b/src/feature/control/btrack.c
@@ -57,7 +57,7 @@ btrack_add_pubsub(pubsub_connector_t *connector)
const subsys_fns_t sys_btrack = {
.name = "btrack",
.supported = true,
- .level = -30,
+ .level = 55,
.initialize = btrack_init,
.shutdown = btrack_fini,
.add_pubsub = btrack_add_pubsub,
diff --git a/src/feature/control/btrack_orconn_maps.c b/src/feature/control/btrack_orconn_maps.c
index 0ef54237a8..a60dffb8c4 100644
--- a/src/feature/control/btrack_orconn_maps.c
+++ b/src/feature/control/btrack_orconn_maps.c
@@ -47,17 +47,18 @@ bto_chan_eq_(bt_orconn_t *a, bt_orconn_t *b)
}
HT_HEAD(bto_gid_ht, bt_orconn_t);
-HT_PROTOTYPE(bto_gid_ht, bt_orconn_t, node, bto_gid_hash_, bto_gid_eq_)
+HT_PROTOTYPE(bto_gid_ht, bt_orconn_t, node, bto_gid_hash_, bto_gid_eq_);
HT_GENERATE2(bto_gid_ht, bt_orconn_t, node,
bto_gid_hash_, bto_gid_eq_, 0.6,
- tor_reallocarray_, tor_free_)
+ tor_reallocarray_, tor_free_);
static struct bto_gid_ht *bto_gid_map;
HT_HEAD(bto_chan_ht, bt_orconn_t);
-HT_PROTOTYPE(bto_chan_ht, bt_orconn_t, chan_node, bto_chan_hash_, bto_chan_eq_)
+HT_PROTOTYPE(bto_chan_ht, bt_orconn_t, chan_node, bto_chan_hash_,
+ bto_chan_eq_);
HT_GENERATE2(bto_chan_ht, bt_orconn_t, chan_node,
bto_chan_hash_, bto_chan_eq_, 0.6,
- tor_reallocarray_, tor_free_)
+ tor_reallocarray_, tor_free_);
static struct bto_chan_ht *bto_chan_map;
/** Clear the GID hash map, freeing any bt_orconn_t objects that become
diff --git a/src/feature/control/control_bootstrap.c b/src/feature/control/control_bootstrap.c
index 2e78fad690..fee7612ba2 100644
--- a/src/feature/control/control_bootstrap.c
+++ b/src/feature/control/control_bootstrap.c
@@ -171,6 +171,12 @@ control_event_bootstrap_core(int loglevel, bootstrap_status_t status,
control_event_client_status(LOG_NOTICE, "%s", buf);
}
+int
+control_get_bootstrap_percent(void)
+{
+ return bootstrap_percent;
+}
+
/** Called when Tor has made progress at bootstrapping its directory
* information and initial circuits.
*
diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c
index c2d23243e5..cdefef97e1 100644
--- a/src/feature/control/control_cmd.c
+++ b/src/feature/control/control_cmd.c
@@ -2272,7 +2272,7 @@ typedef struct control_cmd_def_t {
**/
#define ONE_LINE(name, flags) \
{ \
- #name, \
+ (#name), \
handle_control_ ##name, \
flags, \
&name##_syntax, \
@@ -2283,7 +2283,7 @@ typedef struct control_cmd_def_t {
* flags.
**/
#define MULTLINE(name, flags) \
- { "+"#name, \
+ { ("+"#name), \
handle_control_ ##name, \
flags, \
&name##_syntax \
diff --git a/src/feature/control/control_events.h b/src/feature/control/control_events.h
index 74bbc0047d..4a5492b510 100644
--- a/src/feature/control/control_events.h
+++ b/src/feature/control/control_events.h
@@ -12,6 +12,7 @@
#ifndef TOR_CONTROL_EVENTS_H
#define TOR_CONTROL_EVENTS_H
+#include "lib/cc/ctassert.h"
#include "core/or/ocirc_event.h"
#include "core/or/orconn_event.h"
@@ -164,6 +165,7 @@ int control_event_buildtimeout_set(buildtimeout_set_event_t type,
int control_event_signal(uintptr_t signal);
void control_event_bootstrap(bootstrap_status_t status, int progress);
+int control_get_bootstrap_percent(void);
MOCK_DECL(void, control_event_bootstrap_prob_or,(const char *warn,
int reason,
or_connection_t *or_conn));
@@ -287,10 +289,7 @@ typedef uint64_t event_mask_t;
/* If EVENT_MAX_ ever hits 0x0040, we need to make the mask into a
* different structure, as it can only handle a maximum left shift of 1<<63. */
-
-#if EVENT_MAX_ >= EVENT_CAPACITY_
-#error control_connection_t.event_mask has an event greater than its capacity
-#endif
+CTASSERT(EVENT_MAX_ < EVENT_CAPACITY_);
#define EVENT_MASK_(e) (((uint64_t)1)<<(e))
diff --git a/src/feature/dirauth/dirauth_config.c b/src/feature/dirauth/dirauth_config.c
index ca16dc8424..38d2a8bc5a 100644
--- a/src/feature/dirauth/dirauth_config.c
+++ b/src/feature/dirauth/dirauth_config.c
@@ -21,7 +21,7 @@
#include "core/or/or.h"
#include "app/config/config.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/stats/rephist.h"
#include "feature/dirauth/authmode.h"
@@ -305,7 +305,7 @@ options_act_dirauth(const or_options_t *old_options)
/* We may need to reschedule some dirauth stuff if our status changed. */
if (old_options) {
if (options_transition_affects_dirauth_timing(old_options, options)) {
- voting_schedule_recalculate_timing(options, time(NULL));
+ dirauth_sched_recalculate_timing(options, time(NULL));
reschedule_dirvote(options);
}
}
diff --git a/src/feature/dirauth/dircollate.c b/src/feature/dirauth/dircollate.c
index b35cb021ff..2657f53853 100644
--- a/src/feature/dirauth/dircollate.c
+++ b/src/feature/dirauth/dircollate.c
@@ -90,9 +90,9 @@ ddmap_entry_set_digests(ddmap_entry_t *ent,
}
HT_PROTOTYPE(double_digest_map, ddmap_entry_t, node, ddmap_entry_hash,
- ddmap_entry_eq)
+ ddmap_entry_eq);
HT_GENERATE2(double_digest_map, ddmap_entry_t, node, ddmap_entry_hash,
- ddmap_entry_eq, 0.6, tor_reallocarray, tor_free_)
+ ddmap_entry_eq, 0.6, tor_reallocarray, tor_free_);
/** Helper: add a single vote_routerstatus_t <b>vrs</b> to the collator
* <b>dc</b>, indexing it by its RSA key digest, and by the 2-tuple of its RSA
@@ -324,4 +324,3 @@ dircollator_get_votes_for_router(dircollator_t *dc, int idx)
return digestmap_get(dc->by_collated_rsa_sha1,
smartlist_get(dc->all_rsa_sha1_lst, idx));
}
-
diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c
index e230815ca3..7eb2b720a6 100644
--- a/src/feature/dirauth/dirvote.c
+++ b/src/feature/dirauth/dirvote.c
@@ -36,7 +36,7 @@
#include "feature/stats/rephist.h"
#include "feature/client/entrynodes.h" /* needed for guardfraction methods */
#include "feature/nodelist/torcert.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/dirauth/dirvote.h"
#include "feature/dirauth/authmode.h"
@@ -886,7 +886,7 @@ dirvote_get_intermediate_param_value(const smartlist_t *param_list,
int ok;
value = (int32_t)
tor_parse_long(integer_str, 10, INT32_MIN, INT32_MAX, &ok, NULL);
- if (BUG(! ok))
+ if (BUG(!ok))
return default_val;
++n_found;
}
@@ -2853,7 +2853,7 @@ dirvote_act(const or_options_t *options, time_t now)
"Mine is %s.",
keys, hex_str(c->cache_info.identity_digest, DIGEST_LEN));
tor_free(keys);
- voting_schedule_recalculate_timing(options, now);
+ dirauth_sched_recalculate_timing(options, now);
}
#define IF_TIME_FOR_NEXT_ACTION(when_field, done_field) \
@@ -2899,7 +2899,7 @@ dirvote_act(const or_options_t *options, time_t now)
networkstatus_get_latest_consensus_by_flavor(FLAV_NS));
/* XXXX We will want to try again later if we haven't got enough
* signatures yet. Implement this if it turns out to ever happen. */
- voting_schedule_recalculate_timing(options, now);
+ dirauth_sched_recalculate_timing(options, now);
return voting_schedule.voting_starts;
} ENDIF
@@ -2966,7 +2966,7 @@ dirvote_perform_vote(void)
if (!contents)
return -1;
- pending_vote = dirvote_add_vote(contents, &msg, &status);
+ pending_vote = dirvote_add_vote(contents, 0, &msg, &status);
tor_free(contents);
if (!pending_vote) {
log_warn(LD_DIR, "Couldn't store my own vote! (I told myself, '%s'.)",
@@ -3122,13 +3122,45 @@ list_v3_auth_ids(void)
return keys;
}
+/* Check the voter information <b>vi</b>, and assert that at least one
+ * signature is good. Asserts on failure. */
+static void
+assert_any_sig_good(const networkstatus_voter_info_t *vi)
+{
+ int any_sig_good = 0;
+ SMARTLIST_FOREACH(vi->sigs, document_signature_t *, sig,
+ if (sig->good_signature)
+ any_sig_good = 1);
+ tor_assert(any_sig_good);
+}
+
+/* Add <b>cert</b> to our list of known authority certificates. */
+static void
+add_new_cert_if_needed(const struct authority_cert_t *cert)
+{
+ tor_assert(cert);
+ if (!authority_cert_get_by_digests(cert->cache_info.identity_digest,
+ cert->signing_key_digest)) {
+ /* Hey, it's a new cert! */
+ trusted_dirs_load_certs_from_string(
+ cert->cache_info.signed_descriptor_body,
+ TRUSTED_DIRS_CERTS_SRC_FROM_VOTE, 1 /*flush*/,
+ NULL);
+ if (!authority_cert_get_by_digests(cert->cache_info.identity_digest,
+ cert->signing_key_digest)) {
+ log_warn(LD_BUG, "We added a cert, but still couldn't find it.");
+ }
+ }
+}
+
/** Called when we have received a networkstatus vote in <b>vote_body</b>.
* Parse and validate it, and on success store it as a pending vote (which we
* then return). Return NULL on failure. Sets *<b>msg_out</b> and
* *<b>status_out</b> to an HTTP response and status code. (V3 authority
* only) */
pending_vote_t *
-dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
+dirvote_add_vote(const char *vote_body, time_t time_posted,
+ const char **msg_out, int *status_out)
{
networkstatus_t *vote;
networkstatus_voter_info_t *vi;
@@ -3159,13 +3191,7 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
}
tor_assert(smartlist_len(vote->voters) == 1);
vi = get_voter(vote);
- {
- int any_sig_good = 0;
- SMARTLIST_FOREACH(vi->sigs, document_signature_t *, sig,
- if (sig->good_signature)
- any_sig_good = 1);
- tor_assert(any_sig_good);
- }
+ assert_any_sig_good(vi);
ds = trusteddirserver_get_by_v3_auth_digest(vi->identity_digest);
if (!ds) {
char *keys = list_v3_auth_ids();
@@ -3178,19 +3204,7 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
*msg_out = "Vote not from a recognized v3 authority";
goto err;
}
- tor_assert(vote->cert);
- if (!authority_cert_get_by_digests(vote->cert->cache_info.identity_digest,
- vote->cert->signing_key_digest)) {
- /* Hey, it's a new cert! */
- trusted_dirs_load_certs_from_string(
- vote->cert->cache_info.signed_descriptor_body,
- TRUSTED_DIRS_CERTS_SRC_FROM_VOTE, 1 /*flush*/,
- NULL);
- if (!authority_cert_get_by_digests(vote->cert->cache_info.identity_digest,
- vote->cert->signing_key_digest)) {
- log_warn(LD_BUG, "We added a cert, but still couldn't find it.");
- }
- }
+ add_new_cert_if_needed(vote->cert);
/* Is it for the right period? */
if (vote->valid_after != voting_schedule.interval_starts) {
@@ -3203,6 +3217,23 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
goto err;
}
+ /* Check if we received it, as a post, after the cutoff when we
+ * start asking other dir auths for it. If we do, the best plan
+ * is to discard it, because using it greatly increases the chances
+ * of a split vote for this round (some dir auths got it in time,
+ * some didn't). */
+ if (time_posted && time_posted > voting_schedule.fetch_missing_votes) {
+ char tbuf1[ISO_TIME_LEN+1], tbuf2[ISO_TIME_LEN+1];
+ format_iso_time(tbuf1, time_posted);
+ format_iso_time(tbuf2, voting_schedule.fetch_missing_votes);
+ log_warn(LD_DIR, "Rejecting posted vote from %s received at %s; "
+ "our cutoff for received votes is %s. Check your clock, "
+ "CPU load, and network load. Also check the authority that "
+ "posted the vote.", vi->address, tbuf1, tbuf2);
+ *msg_out = "Posted vote received too late, would be dangerous to count it";
+ goto err;
+ }
+
/* Fetch any new router descriptors we just learned about */
update_consensus_router_descriptor_downloads(time(NULL), 1, vote);
@@ -4613,7 +4644,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
else
last_consensus_interval = options->TestingV3AuthInitialVotingInterval;
v3_out->valid_after =
- voting_schedule_get_start_of_next_interval(now,
+ voting_sched_get_start_of_interval_after(now,
(int)last_consensus_interval,
options->TestingV3AuthVotingStartOffset);
format_iso_time(tbuf, v3_out->valid_after);
@@ -4635,17 +4666,14 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
/* These are hardwired, to avoid disaster. */
v3_out->recommended_relay_protocols =
- tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
- "Link=4 Microdesc=1-2 Relay=2");
+ tor_strdup(DIRVOTE_RECCOMEND_RELAY_PROTO);
v3_out->recommended_client_protocols =
- tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
- "Link=4 Microdesc=1-2 Relay=2");
- v3_out->required_client_protocols =
- tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
- "Link=4 Microdesc=1-2 Relay=2");
+ tor_strdup(DIRVOTE_RECCOMEND_CLIENT_PROTO);
+
v3_out->required_relay_protocols =
- tor_strdup("Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
- "Link=3-4 Microdesc=1 Relay=1-2");
+ tor_strdup(DIRVOTE_REQUIRE_RELAY_PROTO);
+ v3_out->required_client_protocols =
+ tor_strdup(DIRVOTE_REQUIRE_CLIENT_PROTO);
/* We are not allowed to vote to require anything we don't have. */
tor_assert(protover_all_supported(v3_out->required_relay_protocols, NULL));
diff --git a/src/feature/dirauth/dirvote.h b/src/feature/dirauth/dirvote.h
index 675f4ee148..fa7b1da4ab 100644
--- a/src/feature/dirauth/dirvote.h
+++ b/src/feature/dirauth/dirvote.h
@@ -94,6 +94,7 @@ void dirvote_dirreq_get_status_vote(const char *url, smartlist_t *items,
/* Storing signatures and votes functions */
struct pending_vote_t * dirvote_add_vote(const char *vote_body,
+ time_t time_posted,
const char **msg_out,
int *status_out);
int dirvote_add_signatures(const char *detached_signatures_body,
@@ -142,9 +143,13 @@ dirvote_dirreq_get_status_vote(const char *url, smartlist_t *items,
}
static inline struct pending_vote_t *
-dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
+dirvote_add_vote(const char *vote_body,
+ time_t time_posted,
+ const char **msg_out,
+ int *status_out)
{
(void) vote_body;
+ (void) time_posted;
/* If the dirauth module is disabled, this should NEVER be called else we
* failed to safeguard the dirauth module. */
tor_assert_nonfatal_unreached();
@@ -230,6 +235,36 @@ char *networkstatus_get_detached_signatures(smartlist_t *consensuses);
STATIC microdesc_t *dirvote_create_microdescriptor(const routerinfo_t *ri,
int consensus_method);
+/** The recommended relay protocols for this authority's votes.
+ * Recommending a new protocol causes old tor versions to log a warning.
+ */
+#define DIRVOTE_RECCOMEND_RELAY_PROTO \
+ "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " \
+ "Link=4 Microdesc=1-2 Relay=2"
+/** The recommended client protocols for this authority's votes.
+ * Recommending a new protocol causes old tor versions to log a warning.
+ */
+#define DIRVOTE_RECCOMEND_CLIENT_PROTO \
+ "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " \
+ "Link=4 Microdesc=1-2 Relay=2"
+
+/** The required relay protocols for this authority's votes.
+ * WARNING: Requiring a new protocol causes old tor versions to shut down.
+ * Requiring the wrong protocols can break the tor network.
+ * See Proposal 303: When and how to remove support for protocol versions.
+ */
+#define DIRVOTE_REQUIRE_RELAY_PROTO \
+ "Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " \
+ "Link=3-4 Microdesc=1 Relay=1-2"
+/** The required relay protocols for this authority's votes.
+ * WARNING: Requiring a new protocol causes old tor versions to shut down.
+ * Requiring the wrong protocols can break the tor network.
+ * See Proposal 303: When and how to remove support for protocol versions.
+ */
+#define DIRVOTE_REQUIRE_CLIENT_PROTO \
+ "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " \
+ "Link=4 Microdesc=1-2 Relay=2"
+
#endif /* defined(DIRVOTE_PRIVATE) */
#endif /* !defined(TOR_DIRVOTE_H) */
diff --git a/src/feature/dirauth/include.am b/src/feature/dirauth/include.am
index 2ef629ae35..e26f120d4e 100644
--- a/src/feature/dirauth/include.am
+++ b/src/feature/dirauth/include.am
@@ -19,7 +19,8 @@ MODULE_DIRAUTH_SOURCES = \
src/feature/dirauth/recommend_pkg.c \
src/feature/dirauth/shared_random.c \
src/feature/dirauth/shared_random_state.c \
- src/feature/dirauth/voteflags.c
+ src/feature/dirauth/voteflags.c \
+ src/feature/dirauth/voting_schedule.c
# ADD_C_FILE: INSERT HEADERS HERE.
noinst_HEADERS += \
@@ -43,7 +44,8 @@ noinst_HEADERS += \
src/feature/dirauth/shared_random.h \
src/feature/dirauth/shared_random_state.h \
src/feature/dirauth/vote_microdesc_hash_st.h \
- src/feature/dirauth/voteflags.h
+ src/feature/dirauth/voteflags.h \
+ src/feature/dirauth/voting_schedule.h
if BUILD_MODULE_DIRAUTH
LIBTOR_APP_A_SOURCES += $(MODULE_DIRAUTH_SOURCES)
diff --git a/src/feature/dirauth/keypin.c b/src/feature/dirauth/keypin.c
index 6f6cfc01f1..98584a7d42 100644
--- a/src/feature/dirauth/keypin.c
+++ b/src/feature/dirauth/keypin.c
@@ -118,14 +118,14 @@ return (unsigned) siphash24g(a->ed25519_key, sizeof(a->ed25519_key));
}
HT_PROTOTYPE(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa,
- keypin_ents_eq_rsa)
+ keypin_ents_eq_rsa);
HT_GENERATE2(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa,
- keypin_ents_eq_rsa, 0.6, tor_reallocarray, tor_free_)
+ keypin_ents_eq_rsa, 0.6, tor_reallocarray, tor_free_);
HT_PROTOTYPE(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed,
- keypin_ents_eq_ed)
+ keypin_ents_eq_ed);
HT_GENERATE2(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed,
- keypin_ents_eq_ed, 0.6, tor_reallocarray, tor_free_)
+ keypin_ents_eq_ed, 0.6, tor_reallocarray, tor_free_);
/**
* Check whether we already have an entry in the key pinning table for a
diff --git a/src/feature/dirauth/shared_random.c b/src/feature/dirauth/shared_random.c
index 48e2147ea6..fd55008242 100644
--- a/src/feature/dirauth/shared_random.c
+++ b/src/feature/dirauth/shared_random.c
@@ -99,7 +99,7 @@
#include "feature/nodelist/dirlist.h"
#include "feature/hs_common/shared_random_client.h"
#include "feature/dirauth/shared_random_state.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/dirauth/dirvote.h"
#include "feature/dirauth/authmode.h"
@@ -1261,7 +1261,7 @@ sr_act_post_consensus(const networkstatus_t *consensus)
}
/* Prepare our state so that it's ready for the next voting period. */
- sr_state_update(voting_schedule_get_next_valid_after_time());
+ sr_state_update(dirauth_sched_get_next_valid_after_time());
}
/** Initialize shared random subsystem. This MUST be called early in the boot
diff --git a/src/feature/dirauth/shared_random_state.c b/src/feature/dirauth/shared_random_state.c
index 1792d540c6..598d781557 100644
--- a/src/feature/dirauth/shared_random_state.c
+++ b/src/feature/dirauth/shared_random_state.c
@@ -20,7 +20,7 @@
#include "feature/dirauth/shared_random.h"
#include "feature/hs_common/shared_random_client.h"
#include "feature/dirauth/shared_random_state.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "lib/encoding/confline.h"
#include "lib/version/torversion.h"
@@ -60,6 +60,7 @@ DUMMY_TYPECHECK_INSTANCE(sr_disk_state_t);
#define SR_DISK_STATE_MAGIC 0x98AB1254
/** Array of variables that are saved to disk as a persistent state. */
+// clang-format off
static const config_var_t state_vars[] = {
V(Version, POSINT, "0"),
V(TorVersion, STRING, NULL),
@@ -73,6 +74,7 @@ static const config_var_t state_vars[] = {
VAR("SharedRandCurrentValue", LINELIST_S, SharedRandValues, NULL),
END_OF_CONFIG_VARS
};
+// clang-format on
/** "Extra" variable in the state that receives lines we can't parse. This
* lets us preserve options from versions of Tor newer than us. */
@@ -139,7 +141,7 @@ get_state_valid_until_time(time_t now)
voting_interval = get_voting_interval();
/* Find the time the current round started. */
- beginning_of_current_round = get_start_time_of_current_round();
+ beginning_of_current_round = dirauth_sched_get_cur_valid_after_time();
/* Find how many rounds are left till the end of the protocol run */
current_round = (now / voting_interval) % total_rounds;
@@ -1330,7 +1332,7 @@ sr_state_init(int save_to_disk, int read_from_disk)
/* We have a state in memory, let's make sure it's updated for the current
* and next voting round. */
{
- time_t valid_after = voting_schedule_get_next_valid_after_time();
+ time_t valid_after = dirauth_sched_get_next_valid_after_time();
sr_state_update(valid_after);
}
return 0;
diff --git a/src/feature/dircommon/voting_schedule.c b/src/feature/dirauth/voting_schedule.c
index 389f7f6b5d..efc4a0b316 100644
--- a/src/feature/dircommon/voting_schedule.c
+++ b/src/feature/dirauth/voting_schedule.c
@@ -3,12 +3,11 @@
/**
* \file voting_schedule.c
- * \brief This file contains functions that are from the directory authority
- * subsystem related to voting specifically but used by many part of
- * tor. The full feature is built as part of the dirauth module.
+ * \brief Compute information about our voting schedule as a directory
+ * authority.
**/
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "core/or/or.h"
#include "app/config/config.h"
@@ -20,55 +19,11 @@
* Vote scheduling
* ===== */
-/** Return the start of the next interval of size <b>interval</b> (in
- * seconds) after <b>now</b>, plus <b>offset</b>. Midnight always
- * starts a fresh interval, and if the last interval of a day would be
- * truncated to less than half its size, it is rolled into the
- * previous interval. */
-time_t
-voting_schedule_get_start_of_next_interval(time_t now, int interval,
- int offset)
-{
- struct tm tm;
- time_t midnight_today=0;
- time_t midnight_tomorrow;
- time_t next;
-
- tor_gmtime_r(&now, &tm);
- tm.tm_hour = 0;
- tm.tm_min = 0;
- tm.tm_sec = 0;
-
- if (tor_timegm(&tm, &midnight_today) < 0) {
- // LCOV_EXCL_START
- log_warn(LD_BUG, "Ran into an invalid time when trying to find midnight.");
- // LCOV_EXCL_STOP
- }
- midnight_tomorrow = midnight_today + (24*60*60);
-
- next = midnight_today + ((now-midnight_today)/interval + 1)*interval;
-
- /* Intervals never cross midnight. */
- if (next > midnight_tomorrow)
- next = midnight_tomorrow;
-
- /* If the interval would only last half as long as it's supposed to, then
- * skip over to the next day. */
- if (next + interval/2 > midnight_tomorrow)
- next = midnight_tomorrow;
-
- next += offset;
- if (next - interval > now)
- next -= interval;
-
- return next;
-}
-
/* Populate and return a new voting_schedule_t that can be used to schedule
* voting. The object is allocated on the heap and it's the responsibility of
* the caller to free it. Can't fail. */
static voting_schedule_t *
-get_voting_schedule(const or_options_t *options, time_t now, int severity)
+create_voting_schedule(const or_options_t *options, time_t now, int severity)
{
int interval, vote_delay, dist_delay;
time_t start;
@@ -95,14 +50,15 @@ get_voting_schedule(const or_options_t *options, time_t now, int severity)
}
tor_assert(interval > 0);
+ new_voting_schedule->interval = interval;
if (vote_delay + dist_delay > interval/2)
vote_delay = dist_delay = interval / 4;
start = new_voting_schedule->interval_starts =
- voting_schedule_get_start_of_next_interval(now,interval,
+ voting_sched_get_start_of_interval_after(now,interval,
options->TestingV3AuthVotingStartOffset);
- end = voting_schedule_get_start_of_next_interval(start+1, interval,
+ end = voting_sched_get_start_of_interval_after(start+1, interval,
options->TestingV3AuthVotingStartOffset);
tor_assert(end > start);
@@ -139,9 +95,13 @@ voting_schedule_free_(voting_schedule_t *voting_schedule_to_free)
voting_schedule_t voting_schedule;
-/* Using the time <b>now</b>, return the next voting valid-after time. */
-time_t
-voting_schedule_get_next_valid_after_time(void)
+/**
+ * Return the current voting schedule, recreating it if necessary.
+ *
+ * Dirauth only.
+ **/
+static const voting_schedule_t *
+dirauth_get_voting_schedule(void)
{
time_t now = approx_time();
bool need_to_recalculate_voting_schedule = false;
@@ -167,27 +127,62 @@ voting_schedule_get_next_valid_after_time(void)
done:
if (need_to_recalculate_voting_schedule) {
- voting_schedule_recalculate_timing(get_options(), approx_time());
+ dirauth_sched_recalculate_timing(get_options(), approx_time());
voting_schedule.created_on_demand = 1;
}
- return voting_schedule.interval_starts;
+ return &voting_schedule;
+}
+
+/** Return the next voting valid-after time.
+ *
+ * Dirauth only. */
+time_t
+dirauth_sched_get_next_valid_after_time(void)
+{
+ return dirauth_get_voting_schedule()->interval_starts;
+}
+
+/**
+ * Return our best idea of what the valid-after time for the _current_
+ * consensus, whether we have one or not.
+ *
+ * Dirauth only.
+ **/
+time_t
+dirauth_sched_get_cur_valid_after_time(void)
+{
+ const voting_schedule_t *sched = dirauth_get_voting_schedule();
+ time_t next_start = sched->interval_starts;
+ int interval = sched->interval;
+ int offset = get_options()->TestingV3AuthVotingStartOffset;
+ return voting_sched_get_start_of_interval_after(next_start - interval - 1,
+ interval,
+ offset);
+}
+
+/** Return the voting interval that we are configured to use.
+ *
+ * Dirauth only. */
+int
+dirauth_sched_get_configured_interval(void)
+{
+ return get_options()->V3AuthVotingInterval;
}
/** Set voting_schedule to hold the timing for the next vote we should be
* doing. All type of tor do that because HS subsystem needs the timing as
* well to function properly. */
void
-voting_schedule_recalculate_timing(const or_options_t *options, time_t now)
+dirauth_sched_recalculate_timing(const or_options_t *options, time_t now)
{
voting_schedule_t *new_voting_schedule;
/* get the new voting schedule */
- new_voting_schedule = get_voting_schedule(options, now, LOG_INFO);
+ new_voting_schedule = create_voting_schedule(options, now, LOG_INFO);
tor_assert(new_voting_schedule);
/* Fill in the global static struct now */
memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule));
voting_schedule_free(new_voting_schedule);
}
-
diff --git a/src/feature/dircommon/voting_schedule.h b/src/feature/dirauth/voting_schedule.h
index e4c6210087..9e2ac29c75 100644
--- a/src/feature/dircommon/voting_schedule.h
+++ b/src/feature/dirauth/voting_schedule.h
@@ -11,6 +11,8 @@
#include "core/or/or.h"
+#ifdef HAVE_MODULE_DIRAUTH
+
/** Scheduling information for a voting interval. */
typedef struct {
/** When do we generate and distribute our vote for this interval? */
@@ -26,6 +28,9 @@ typedef struct {
/** When do we publish the consensus? */
time_t interval_starts;
+ /** Our computed dirauth interval */
+ int interval;
+
/** True iff we have generated and distributed our vote. */
int have_voted;
/** True iff we've requested missing votes. */
@@ -53,12 +58,36 @@ typedef struct {
extern voting_schedule_t voting_schedule;
-void voting_schedule_recalculate_timing(const or_options_t *options,
+void dirauth_sched_recalculate_timing(const or_options_t *options,
time_t now);
-time_t voting_schedule_get_start_of_next_interval(time_t now,
- int interval,
- int offset);
-time_t voting_schedule_get_next_valid_after_time(void);
+time_t dirauth_sched_get_next_valid_after_time(void);
+time_t dirauth_sched_get_cur_valid_after_time(void);
+int dirauth_sched_get_configured_interval(void);
+
+#else /* !defined(HAVE_MODULE_DIRAUTH) */
+
+#define dirauth_sched_recalculate_timing(opt,now) \
+ ((void)(opt), (void)(now))
+
+static inline time_t
+dirauth_sched_get_next_valid_after_time(void)
+{
+ tor_assert_unreached();
+ return 0;
+}
+static inline time_t
+dirauth_sched_get_cur_valid_after_time(void)
+{
+ tor_assert_unreached();
+ return 0;
+}
+static inline int
+dirauth_sched_get_configured_interval(void)
+{
+ tor_assert_unreached();
+ return 1;
+}
+#endif /* defined(HAVE_MODULE_DIRAUTH) */
#endif /* !defined(TOR_VOTING_SCHEDULE_H) */
diff --git a/src/feature/dircache/conscache.c b/src/feature/dircache/conscache.c
index ceba410a5f..d9aaccddc1 100644
--- a/src/feature/dircache/conscache.c
+++ b/src/feature/dircache/conscache.c
@@ -132,6 +132,15 @@ consensus_cache_may_overallocate(consensus_cache_t *cache)
#endif
}
+// HACK: GCC on Appveyor hates that we may assert before returning. Work around
+// the error.
+#ifdef _WIN32
+#ifndef COCCI
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn"
+#endif
+#endif
+
/**
* Tell the sandbox (if any) configured by <b>cfg</b> to allow the
* operations that <b>cache</b> will need.
@@ -156,6 +165,12 @@ consensus_cache_register_with_sandbox(consensus_cache_t *cache,
return storage_dir_register_with_sandbox(cache->dir, cfg);
}
+#ifdef _WIN32
+#ifndef COCCI
+#pragma GCC diagnostic pop
+#endif
+#endif
+
/**
* Helper: clear all entries from <b>cache</b> (but do not delete
* any that aren't marked for removal
diff --git a/src/feature/dircache/consdiffmgr.c b/src/feature/dircache/consdiffmgr.c
index 8445b8f986..10590cd6d2 100644
--- a/src/feature/dircache/consdiffmgr.c
+++ b/src/feature/dircache/consdiffmgr.c
@@ -218,9 +218,9 @@ cdm_diff_eq(const cdm_diff_t *diff1, const cdm_diff_t *diff2)
diff1->compress_method == diff2->compress_method;
}
-HT_PROTOTYPE(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq)
+HT_PROTOTYPE(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq);
HT_GENERATE2(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq,
- 0.6, tor_reallocarray, tor_free_)
+ 0.6, tor_reallocarray, tor_free_);
#define cdm_diff_free(diff) \
FREE_AND_NULL(cdm_diff_t, cdm_diff_free_, (diff))
diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c
index 3b8775968a..4f7f209207 100644
--- a/src/feature/dircache/dircache.c
+++ b/src/feature/dircache/dircache.c
@@ -1695,7 +1695,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers,
!strcmp(url,"/tor/post/vote")) { /* v3 networkstatus vote */
const char *msg = "OK";
int status;
- if (dirvote_add_vote(body, &msg, &status)) {
+ if (dirvote_add_vote(body, approx_time(), &msg, &status)) {
write_short_http_response(conn, status, "Vote stored");
} else {
tor_assert(msg);
diff --git a/src/feature/dirclient/dirclient.c b/src/feature/dirclient/dirclient.c
index 1b6eed12f0..e7bec89cad 100644
--- a/src/feature/dirclient/dirclient.c
+++ b/src/feature/dirclient/dirclient.c
@@ -21,6 +21,7 @@
#include "feature/client/entrynodes.h"
#include "feature/control/control_events.h"
#include "feature/dirauth/authmode.h"
+#include "feature/dirclient/dirclient.h"
#include "feature/dirauth/dirvote.h"
#include "feature/dirauth/shared_random.h"
#include "feature/dircache/dirserv.h"
@@ -51,6 +52,7 @@
#include "feature/rend/rendservice.h"
#include "feature/stats/predict_ports.h"
+#include "lib/cc/ctassert.h"
#include "lib/compress/compress.h"
#include "lib/crypt_ops/crypto_format.h"
#include "lib/crypt_ops/crypto_util.h"
@@ -1443,9 +1445,7 @@ compare_strs_(const void **a, const void **b)
}
#define CONDITIONAL_CONSENSUS_FPR_LEN 3
-#if (CONDITIONAL_CONSENSUS_FPR_LEN > DIGEST_LEN)
-#error "conditional consensus fingerprint length is larger than digest length"
-#endif
+CTASSERT(CONDITIONAL_CONSENSUS_FPR_LEN <= DIGEST_LEN);
/** Return the URL we should use for a consensus download.
*
@@ -1964,6 +1964,44 @@ dir_client_decompress_response_body(char **bodyp, size_t *bodylenp,
return rv;
}
+/**
+ * Total number of bytes downloaded of each directory purpose, when
+ * bootstrapped, and when not bootstrapped.
+ *
+ * (For example, the number of bytes downloaded of purpose p while
+ * not fully bootstrapped is total_dl[p][false].)
+ **/
+static uint64_t total_dl[DIR_PURPOSE_MAX_][2];
+
+/**
+ * Heartbeat: dump a summary of how many bytes of which purpose we've
+ * downloaded, when bootstrapping and when not bootstrapping.
+ **/
+void
+dirclient_dump_total_dls(void)
+{
+ const or_options_t *options = get_options();
+ for (int bootstrapped = 0; bootstrapped < 2; ++bootstrapped) {
+ bool first_time = true;
+ for (int i=0; i < DIR_PURPOSE_MAX_; ++i) {
+ uint64_t n = total_dl[i][0];
+ if (n == 0)
+ continue;
+ if (options->SafeLogging_ != SAFELOG_SCRUB_NONE &&
+ purpose_needs_anonymity(i, ROUTER_PURPOSE_GENERAL, NULL))
+ continue;
+ if (first_time) {
+ log_notice(LD_NET,
+ "While %sbootstrapping, fetched this many bytes: ",
+ bootstrapped?"not ":"");
+ first_time = false;
+ }
+ log_notice(LD_NET, " %"PRIu64" (%s)",
+ n, dir_conn_purpose_to_string(i));
+ }
+ }
+}
+
/** We are a client, and we've finished reading the server's
* response. Parse it and act appropriately.
*
@@ -1997,6 +2035,16 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
received_bytes = connection_get_inbuf_len(TO_CONN(conn));
+ log_debug(LD_DIR, "Downloaded %"TOR_PRIuSZ" bytes on connection of purpose "
+ "%s; bootstrap %d%%",
+ received_bytes,
+ dir_conn_purpose_to_string(conn->base_.purpose),
+ control_get_bootstrap_percent());
+ {
+ bool bootstrapped = control_get_bootstrap_percent() == 100;
+ total_dl[conn->base_.purpose][bootstrapped] += received_bytes;
+ }
+
switch (connection_fetch_from_buf_http(TO_CONN(conn),
&headers, MAX_HEADERS_SIZE,
&body, &body_len, MAX_DIR_DL_SIZE,
@@ -2364,7 +2412,7 @@ handle_response_fetch_status_vote(dir_connection_t *conn,
conn->base_.port, conn->requested_resource);
return -1;
}
- dirvote_add_vote(body, &msg, &st);
+ dirvote_add_vote(body, 0, &msg, &st);
if (st > 299) {
log_warn(LD_DIR, "Error adding retrieved vote: %s", msg);
} else {
diff --git a/src/feature/dirclient/dirclient.h b/src/feature/dirclient/dirclient.h
index 08209721bb..096b197526 100644
--- a/src/feature/dirclient/dirclient.h
+++ b/src/feature/dirclient/dirclient.h
@@ -14,6 +14,8 @@
#include "feature/hs/hs_ident.h"
+void dirclient_dump_total_dls(void);
+
int directories_have_accepted_server_descriptor(void);
void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
dirinfo_type_t type, const char *payload,
diff --git a/src/feature/dircommon/fp_pair.c b/src/feature/dircommon/fp_pair.c
index 8b55896ba8..87e1c253bd 100644
--- a/src/feature/dircommon/fp_pair.c
+++ b/src/feature/dircommon/fp_pair.c
@@ -57,10 +57,10 @@ fp_pair_map_entry_hash(const fp_pair_map_entry_t *a)
*/
HT_PROTOTYPE(fp_pair_map_impl, fp_pair_map_entry_t, node,
- fp_pair_map_entry_hash, fp_pair_map_entries_eq)
+ fp_pair_map_entry_hash, fp_pair_map_entries_eq);
HT_GENERATE2(fp_pair_map_impl, fp_pair_map_entry_t, node,
fp_pair_map_entry_hash, fp_pair_map_entries_eq,
- 0.6, tor_reallocarray_, tor_free_)
+ 0.6, tor_reallocarray_, tor_free_);
/** Constructor to create a new empty map from fp_pair_t to void *
*/
@@ -312,4 +312,3 @@ fp_pair_map_assert_ok(const fp_pair_map_t *map)
{
tor_assert(!fp_pair_map_impl_HT_REP_IS_BAD_(&(map->head)));
}
-
diff --git a/src/feature/dircommon/include.am b/src/feature/dircommon/include.am
index f0f0323d12..87850ce183 100644
--- a/src/feature/dircommon/include.am
+++ b/src/feature/dircommon/include.am
@@ -3,8 +3,7 @@
LIBTOR_APP_A_SOURCES += \
src/feature/dircommon/consdiff.c \
src/feature/dircommon/directory.c \
- src/feature/dircommon/fp_pair.c \
- src/feature/dircommon/voting_schedule.c
+ src/feature/dircommon/fp_pair.c
# ADD_C_FILE: INSERT HEADERS HERE.
noinst_HEADERS += \
@@ -12,5 +11,4 @@ noinst_HEADERS += \
src/feature/dircommon/dir_connection_st.h \
src/feature/dircommon/directory.h \
src/feature/dircommon/fp_pair.h \
- src/feature/dircommon/vote_timing_st.h \
- src/feature/dircommon/voting_schedule.h
+ src/feature/dircommon/vote_timing_st.h
diff --git a/src/feature/dirparse/authcert_members.h b/src/feature/dirparse/authcert_members.h
index c6755bb629..53eab175d6 100644
--- a/src/feature/dirparse/authcert_members.h
+++ b/src/feature/dirparse/authcert_members.h
@@ -14,6 +14,7 @@
#ifndef TOR_AUTHCERT_MEMBERS_H
#define TOR_AUTHCERT_MEMBERS_H
+// clang-format off
#define AUTHCERT_MEMBERS \
T1("dir-key-certificate-version", K_DIR_KEY_CERTIFICATE_VERSION, \
GE(1), NO_OBJ ), \
@@ -25,5 +26,6 @@
T1("dir-key-certification", K_DIR_KEY_CERTIFICATION,\
NO_ARGS, NEED_OBJ),\
T01("dir-address", K_DIR_ADDRESS, GE(1), NO_OBJ)
+// clang-format on
#endif /* !defined(TOR_AUTHCERT_MEMBERS_H) */
diff --git a/src/feature/dirparse/authcert_parse.c b/src/feature/dirparse/authcert_parse.c
index 3d42119b94..deb45c12de 100644
--- a/src/feature/dirparse/authcert_parse.c
+++ b/src/feature/dirparse/authcert_parse.c
@@ -21,11 +21,13 @@
#include "feature/dirparse/authcert_members.h"
/** List of tokens recognized in V3 authority certificates. */
+// clang-format off
static token_rule_t dir_key_certificate_table[] = {
AUTHCERT_MEMBERS,
T1("fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ),
END_OF_TABLE
};
+// clang-format on
/** Parse a key certificate from <b>s</b>; point <b>end-of-string</b> to
* the first character after the certificate. */
diff --git a/src/feature/dirparse/microdesc_parse.c b/src/feature/dirparse/microdesc_parse.c
index c2eabeb404..9231080aaa 100644
--- a/src/feature/dirparse/microdesc_parse.c
+++ b/src/feature/dirparse/microdesc_parse.c
@@ -28,6 +28,7 @@
#include "feature/nodelist/microdesc_st.h"
/** List of tokens recognized in microdescriptors */
+// clang-format off
static token_rule_t microdesc_token_table[] = {
T1_START("onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024),
T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ),
@@ -39,6 +40,7 @@ static token_rule_t microdesc_token_table[] = {
A01("@last-listed", A_LAST_LISTED, CONCAT_ARGS, NO_OBJ ),
END_OF_TABLE
};
+// clang-format on
/** Assuming that s starts with a microdesc, return the start of the
* *NEXT* one. Return NULL on "not found." */
diff --git a/src/feature/dirparse/ns_parse.c b/src/feature/dirparse/ns_parse.c
index 4d9b6e6e73..ac9325a608 100644
--- a/src/feature/dirparse/ns_parse.c
+++ b/src/feature/dirparse/ns_parse.c
@@ -43,6 +43,7 @@
/** List of tokens recognized in the body part of v3 networkstatus
* documents. */
+// clang-format off
static token_rule_t rtrstatus_token_table[] = {
T01("p", K_P, CONCAT_ARGS, NO_OBJ ),
T1( "r", K_R, GE(7), NO_OBJ ),
@@ -56,8 +57,10 @@ static token_rule_t rtrstatus_token_table[] = {
T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ),
END_OF_TABLE
};
+// clang-format on
/** List of tokens recognized in V3 networkstatus votes. */
+// clang-format off
static token_rule_t networkstatus_token_table[] = {
T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
GE(1), NO_OBJ ),
@@ -98,8 +101,10 @@ static token_rule_t networkstatus_token_table[] = {
END_OF_TABLE
};
+// clang-format on
/** List of tokens recognized in V3 networkstatus consensuses. */
+// clang-format off
static token_rule_t networkstatus_consensus_token_table[] = {
T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
GE(1), NO_OBJ ),
@@ -136,14 +141,17 @@ static token_rule_t networkstatus_consensus_token_table[] = {
END_OF_TABLE
};
+// clang-format on
/** List of tokens recognized in the footer of v1 directory footers. */
+// clang-format off
static token_rule_t networkstatus_vote_footer_token_table[] = {
T01("directory-footer", K_DIRECTORY_FOOTER, NO_ARGS, NO_OBJ ),
T01("bandwidth-weights", K_BW_WEIGHTS, ARGS, NO_OBJ ),
T( "directory-signature", K_DIRECTORY_SIGNATURE, GE(2), NEED_OBJ ),
END_OF_TABLE
};
+// clang-format on
/** Try to find the start and end of the signed portion of a networkstatus
* document in <b>s</b>. On success, set <b>start_out</b> to the first
diff --git a/src/feature/dirparse/routerparse.c b/src/feature/dirparse/routerparse.c
index f476beec66..8828a0f97a 100644
--- a/src/feature/dirparse/routerparse.c
+++ b/src/feature/dirparse/routerparse.c
@@ -81,6 +81,7 @@
/****************************************************************************/
/** List of tokens recognized in router descriptors */
+// clang-format off
const token_rule_t routerdesc_token_table[] = {
T0N("reject", K_REJECT, ARGS, NO_OBJ ),
T0N("accept", K_ACCEPT, ARGS, NO_OBJ ),
@@ -123,8 +124,10 @@ const token_rule_t routerdesc_token_table[] = {
END_OF_TABLE
};
+// clang-format on
/** List of tokens recognized in extra-info documents. */
+// clang-format off
static token_rule_t extrainfo_token_table[] = {
T1_END( "router-signature", K_ROUTER_SIGNATURE, NO_ARGS, NEED_OBJ ),
T1( "published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ),
@@ -162,6 +165,7 @@ static token_rule_t extrainfo_token_table[] = {
END_OF_TABLE
};
+// clang-format on
#undef T
diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c
index 9cf408ca3e..44cd2505fd 100644
--- a/src/feature/hs/hs_cache.c
+++ b/src/feature/hs/hs_cache.c
@@ -27,6 +27,21 @@
static int cached_client_descriptor_has_expired(time_t now,
const hs_cache_client_descriptor_t *cached_desc);
+/** Helper function: Return true iff the cache entry has a decrypted
+ * descriptor.
+ *
+ * A NULL desc object in the entry means that we were not able to decrypt the
+ * descriptor because we are likely lacking client authorization. It is still
+ * a valid entry but some operations can't be done without the decrypted
+ * descriptor thus this function MUST be used to safe guard access to the
+ * decrypted desc object. */
+static inline bool
+entry_has_decrypted_descriptor(const hs_cache_client_descriptor_t *entry)
+{
+ tor_assert(entry);
+ return (entry->desc != NULL);
+}
+
/********************** Directory HS cache ******************/
/** Directory descriptor cache. Map indexed by blinded key. */
@@ -341,8 +356,23 @@ static digest256map_t *hs_cache_client_intro_state;
static size_t
cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry)
{
- return sizeof(*entry) +
- strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc);
+ size_t size = 0;
+
+ if (entry == NULL) {
+ goto end;
+ }
+ size += sizeof(*entry);
+
+ if (entry->encoded_desc) {
+ size += strlen(entry->encoded_desc);
+ }
+
+ if (entry_has_decrypted_descriptor(entry)) {
+ size += hs_desc_obj_size(entry->desc);
+ }
+
+ end:
+ return size;
}
/** Remove a given descriptor from our cache. */
@@ -659,14 +689,20 @@ cache_store_as_client(hs_cache_client_descriptor_t *client_desc)
* client authorization. */
cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey);
if (cache_entry != NULL) {
- /* Signalling an undecrypted descriptor. We'll always replace the one we
- * have with the new one just fetched. */
- if (cache_entry->desc == NULL) {
+ /* If the current or the new cache entry don't have a decrypted descriptor
+ * (missing client authorization), we always replace the current one with
+ * the new one. Reason is that we can't inspect the revision counter
+ * within the plaintext data so we blindly replace. */
+ if (!entry_has_decrypted_descriptor(cache_entry) ||
+ !entry_has_decrypted_descriptor(client_desc)) {
remove_v3_desc_as_client(cache_entry);
cache_client_desc_free(cache_entry);
goto store;
}
+ /* From this point on, we know that the decrypted descriptor is in the
+ * current entry and new object thus safe to access. */
+
/* If we have an entry in our cache that has a revision counter greater
* than the one we just fetched, discard the one we fetched. */
if (cache_entry->desc->plaintext_data.revision_counter >
@@ -740,11 +776,15 @@ cache_clean_v3_as_client(time_t now)
MAP_DEL_CURRENT(key);
entry_size = cache_get_client_entry_size(entry);
bytes_removed += entry_size;
+
/* We just removed an old descriptor. We need to close all intro circuits
- * so we don't have leftovers that can be selected while lacking a
- * descriptor. We leave the rendezvous circuits opened because they could
- * be in use. */
- hs_client_close_intro_circuits_from_desc(entry->desc);
+ * if the descriptor is decrypted so we don't have leftovers that can be
+ * selected while lacking a descriptor. Circuits are selected by intro
+ * authentication key thus we need the descriptor. We leave the rendezvous
+ * circuits opened because they could be in use. */
+ if (entry_has_decrypted_descriptor(entry)) {
+ hs_client_close_intro_circuits_from_desc(entry->desc);
+ }
/* Entry is not in the cache anymore, destroy it. */
cache_client_desc_free(entry);
/* Update our OOM. We didn't use the remove() function because we are in
@@ -793,7 +833,7 @@ hs_cache_lookup_as_client(const ed25519_public_key_t *key)
tor_assert(key);
cached_desc = lookup_v3_desc_as_client(key->pubkey);
- if (cached_desc && cached_desc->desc) {
+ if (cached_desc && entry_has_decrypted_descriptor(cached_desc)) {
return cached_desc->desc;
}
@@ -866,7 +906,7 @@ hs_cache_remove_as_client(const ed25519_public_key_t *key)
/* If we have a decrypted/decoded descriptor, attempt to close its
* introduction circuit(s). We shouldn't have circuit(s) without a
* descriptor else it will lead to a failure. */
- if (cached_desc->desc) {
+ if (entry_has_decrypted_descriptor(cached_desc)) {
hs_client_close_intro_circuits_from_desc(cached_desc->desc);
}
/* Remove and free. */
@@ -995,7 +1035,7 @@ hs_cache_client_new_auth_parse(const ed25519_public_key_t *service_pk)
}
cached_desc = lookup_v3_desc_as_client(service_pk->pubkey);
- if (cached_desc == NULL || cached_desc->desc != NULL) {
+ if (cached_desc == NULL || entry_has_decrypted_descriptor(cached_desc)) {
/* No entry for that service or the descriptor is already decoded. */
goto end;
}
diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c
index 52bd663200..fc9f4a2654 100644
--- a/src/feature/hs/hs_cell.c
+++ b/src/feature/hs/hs_cell.c
@@ -13,6 +13,7 @@
#include "feature/hs_common/replaycache.h"
#include "feature/hs/hs_cell.h"
+#include "feature/hs/hs_ob.h"
#include "core/crypto/hs_ntor.h"
#include "core/or/origin_circuit_st.h"
@@ -67,14 +68,17 @@ compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len,
memwipe(mac_msg, 0, sizeof(mac_msg));
}
-/** From a set of keys, subcredential and the ENCRYPTED section of an
- * INTRODUCE2 cell, return a newly allocated intro cell keys structure.
- * Finally, the client public key is copied in client_pk. On error, return
- * NULL. */
+/**
+ * From a set of keys, a list of subcredentials, and the ENCRYPTED section of
+ * an INTRODUCE2 cell, return an array of newly allocated intro cell keys
+ * structures. Finally, the client public key is copied in client_pk. On
+ * error, return NULL.
+ **/
static hs_ntor_intro_cell_keys_t *
get_introduce2_key_material(const ed25519_public_key_t *auth_key,
const curve25519_keypair_t *enc_key,
- const uint8_t *subcredential,
+ size_t n_subcredentials,
+ const hs_subcredential_t *subcredentials,
const uint8_t *encrypted_section,
curve25519_public_key_t *client_pk)
{
@@ -82,17 +86,19 @@ get_introduce2_key_material(const ed25519_public_key_t *auth_key,
tor_assert(auth_key);
tor_assert(enc_key);
- tor_assert(subcredential);
+ tor_assert(n_subcredentials > 0);
+ tor_assert(subcredentials);
tor_assert(encrypted_section);
tor_assert(client_pk);
- keys = tor_malloc_zero(sizeof(*keys));
+ keys = tor_calloc(n_subcredentials, sizeof(hs_ntor_intro_cell_keys_t));
/* First bytes of the ENCRYPTED section are the client public key. */
memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN);
- if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk,
- subcredential, keys) < 0) {
+ if (hs_ntor_service_get_introduce1_keys_multi(auth_key, enc_key, client_pk,
+ n_subcredentials,
+ subcredentials, keys) < 0) {
/* Don't rely on the caller to wipe this on error. */
memwipe(client_pk, 0, sizeof(curve25519_public_key_t));
tor_free(keys);
@@ -747,6 +753,74 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
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.
+ *
+ * Return NULL on failure to either produce the key material or on MAC
+ * validation. Else return a newly allocated intro keys object. */
+static hs_ntor_intro_cell_keys_t *
+get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data,
+ const uint8_t *encrypted_section,
+ size_t encrypted_section_len)
+{
+ hs_ntor_intro_cell_keys_t *intro_keys = NULL;
+ hs_ntor_intro_cell_keys_t *intro_keys_result = NULL;
+
+ /* Build the key material out of the key material found in the cell. */
+ intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp,
+ data->n_subcredentials,
+ data->subcredentials,
+ encrypted_section,
+ &data->client_pk);
+ if (intro_keys == NULL) {
+ log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
+ "compute key material");
+ return NULL;
+ }
+
+ /* Make sure we are not about to underflow. */
+ if (BUG(encrypted_section_len < DIGEST256_LEN)) {
+ return NULL;
+ }
+
+ /* Validate MAC from the cell and our computed key material. The MAC field
+ * in the cell is at the end of the encrypted section. */
+ intro_keys_result = tor_malloc_zero(sizeof(*intro_keys_result));
+ for (unsigned i = 0; i < data->n_subcredentials; ++i) {
+ uint8_t mac[DIGEST256_LEN];
+
+ /* The MAC field is at the very end of the ENCRYPTED section. */
+ size_t mac_offset = encrypted_section_len - sizeof(mac);
+ /* Compute the MAC. Use the entire encoded payload with a length up to the
+ * ENCRYPTED section. */
+ compute_introduce_mac(data->payload,
+ data->payload_len - encrypted_section_len,
+ encrypted_section, encrypted_section_len,
+ intro_keys[i].mac_key,
+ sizeof(intro_keys[i].mac_key),
+ mac, sizeof(mac));
+ /* Time-invariant conditional copy: if the MAC is what we expected, then
+ * set intro_keys_result to intro_keys[i]. Otherwise, don't: but don't
+ * leak which one it was! */
+ bool equal = tor_memeq(mac, encrypted_section + mac_offset, sizeof(mac));
+ memcpy_if_true_timei(equal, intro_keys_result, &intro_keys[i],
+ sizeof(*intro_keys_result));
+ }
+
+ /* We no longer need intro_keys. */
+ memwipe(intro_keys, 0,
+ sizeof(hs_ntor_intro_cell_keys_t) * data->n_subcredentials);
+ tor_free(intro_keys);
+
+ if (safe_mem_is_zero(intro_keys_result, sizeof(*intro_keys_result))) {
+ log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell");
+ tor_free(intro_keys_result); /* sets intro_keys_result to NULL */
+ }
+
+ return intro_keys_result;
+}
+
/** Parse the INTRODUCE2 cell using data which contains everything we need to
* do so and contains the destination buffers of information we extract and
* compute from the cell. Return 0 on success else a negative value. The
@@ -795,47 +869,29 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
/* Check our replay cache for this introduction point. */
if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section,
encrypted_section_len, &elapsed)) {
- log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the"
+ log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the "
"same ENCRYPTED section was seen %ld seconds ago. "
"Dropping cell.", (long int) elapsed);
goto done;
}
- /* Build the key material out of the key material found in the cell. */
- intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp,
- data->subcredential,
- encrypted_section,
- &data->client_pk);
- if (intro_keys == NULL) {
- log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
- "compute key material on circuit %u for service %s",
- TO_CIRCUIT(circ)->n_circ_id,
+ /* First bytes of the ENCRYPTED section are the client public key (they are
+ * 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,
+ CURVE25519_PUBKEY_LEN);
+
+ /* Get the right INTRODUCE2 ntor keys and verify the cell MAC */
+ intro_keys = get_introduce2_keys_and_verify_mac(data, encrypted_section,
+ encrypted_section_len);
+ if (!intro_keys) {
+ log_warn(LD_REND, "Could not get valid INTRO2 keys on circuit %u "
+ "for service %s", TO_CIRCUIT(circ)->n_circ_id,
safe_str_client(service->onion_address));
goto done;
}
- /* Validate MAC from the cell and our computed key material. The MAC field
- * in the cell is at the end of the encrypted section. */
- {
- uint8_t mac[DIGEST256_LEN];
- /* The MAC field is at the very end of the ENCRYPTED section. */
- size_t mac_offset = encrypted_section_len - sizeof(mac);
- /* Compute the MAC. Use the entire encoded payload with a length up to the
- * ENCRYPTED section. */
- compute_introduce_mac(data->payload,
- data->payload_len - encrypted_section_len,
- encrypted_section, encrypted_section_len,
- intro_keys->mac_key, sizeof(intro_keys->mac_key),
- mac, sizeof(mac));
- if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) {
- log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on "
- "circuit %u for service %s",
- TO_CIRCUIT(circ)->n_circ_id,
- safe_str_client(service->onion_address));
- goto done;
- }
- }
-
{
/* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */
const uint8_t *encrypted_data =
diff --git a/src/feature/hs/hs_cell.h b/src/feature/hs/hs_cell.h
index 80f37057d2..2b28c44c50 100644
--- a/src/feature/hs/hs_cell.h
+++ b/src/feature/hs/hs_cell.h
@@ -16,6 +16,8 @@
* 3.2.2 of the specification). Below this value, the cell must be padded. */
#define HS_CELL_INTRODUCE1_MIN_SIZE 246
+struct hs_subcredential_t;
+
/** This data structure contains data that we need to build an INTRODUCE1 cell
* used by the INTRODUCE1 build function. */
typedef struct hs_cell_introduce1_data_t {
@@ -29,7 +31,7 @@ typedef struct hs_cell_introduce1_data_t {
/** Introduction point encryption public key. */
const curve25519_public_key_t *enc_pk;
/** Subcredentials of the service. */
- const uint8_t *subcredential;
+ const struct hs_subcredential_t *subcredential;
/** Onion public key for the ntor handshake. */
const curve25519_public_key_t *onion_pk;
/** Rendezvous cookie. */
@@ -55,9 +57,14 @@ typedef struct hs_cell_introduce2_data_t {
owned by the introduction point object through which we received the
INTRO2 cell*/
const curve25519_keypair_t *enc_kp;
- /** Subcredentials of the service. Pointer owned by the descriptor that owns
- the introduction point through which we received the INTRO2 cell. */
- const uint8_t *subcredential;
+ /**
+ * Length of the subcredentials array below.
+ **/
+ size_t n_subcredentials;
+ /** Array of <b>n_subcredentials</b> subcredentials for the service. Pointer
+ * owned by the descriptor that owns the introduction point through which we
+ * received the INTRO2 cell. */
+ const struct hs_subcredential_t *subcredentials;
/** Payload of the received encoded cell. */
const uint8_t *payload;
/** Size of the payload of the received encoded cell. */
diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c
index 90805a98b7..fdd226ba79 100644
--- a/src/feature/hs/hs_circuit.c
+++ b/src/feature/hs/hs_circuit.c
@@ -19,6 +19,7 @@
#include "feature/client/circpathbias.h"
#include "feature/hs/hs_cell.h"
#include "feature/hs/hs_circuit.h"
+#include "feature/hs/hs_ob.h"
#include "feature/hs/hs_circuitmap.h"
#include "feature/hs/hs_client.h"
#include "feature/hs/hs_ident.h"
@@ -367,10 +368,10 @@ get_service_anonymity_string(const hs_service_t *service)
* success, a circuit identifier is attached to the circuit with the needed
* data. This function will try to open a circuit for a maximum value of
* MAX_REND_FAILURES then it will give up. */
-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)
+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))
{
int circ_needs_uptime;
time_t now = time(NULL);
@@ -578,7 +579,7 @@ retry_service_rendezvous_point(const origin_circuit_t *circ)
static int
setup_introduce1_data(const hs_desc_intro_point_t *ip,
const node_t *rp_node,
- const uint8_t *subcredential,
+ const hs_subcredential_t *subcredential,
hs_cell_introduce1_data_t *intro1_data)
{
int ret = -1;
@@ -958,6 +959,42 @@ hs_circ_handle_intro_established(const hs_service_t *service,
return ret;
}
+/**
+ * Go into <b>data</b> and add the right subcredential to be able to handle
+ * this incoming cell.
+ *
+ * <b>desc_subcred</b> is the subcredential of the descriptor that corresponds
+ * to the intro point that received this intro request. This subcredential
+ * should be used if we are not an onionbalance instance.
+ *
+ * Return 0 if everything went well, or -1 in case of internal error.
+ */
+static int
+get_subcredential_for_handling_intro2_cell(const hs_service_t *service,
+ hs_cell_introduce2_data_t *data,
+ const hs_subcredential_t *desc_subcred)
+{
+ /* Handle the simple case first: We are not an onionbalance instance and we
+ * should just use the regular descriptor subcredential */
+ if (!hs_ob_service_is_instance(service)) {
+ data->n_subcredentials = 1;
+ data->subcredentials = desc_subcred;
+ return 0;
+ }
+
+ /* This should not happen since we should have made onionbalance
+ * subcredentials when we created our descriptors. */
+ if (BUG(!service->ob_subcreds)) {
+ return -1;
+ }
+
+ /* We are an onionbalance instance: */
+ data->n_subcredentials = service->n_ob_subcreds;
+ data->subcredentials = service->ob_subcreds;
+
+ return 0;
+}
+
/** We just received an INTRODUCE2 cell on the established introduction circuit
* circ. Handle the INTRODUCE2 payload of size payload_len for the given
* circuit and service. This cell is associated with the intro point object ip
@@ -966,7 +1003,7 @@ int
hs_circ_handle_introduce2(const hs_service_t *service,
const origin_circuit_t *circ,
hs_service_intro_point_t *ip,
- const uint8_t *subcredential,
+ const hs_subcredential_t *subcredential,
const uint8_t *payload, size_t payload_len)
{
int ret = -1;
@@ -983,12 +1020,16 @@ hs_circ_handle_introduce2(const hs_service_t *service,
* parsed, decrypted and key material computed correctly. */
data.auth_pk = &ip->auth_key_kp.pubkey;
data.enc_kp = &ip->enc_key_kp;
- data.subcredential = subcredential;
data.payload = payload;
data.payload_len = payload_len;
data.link_specifiers = smartlist_new();
data.replay_cache = ip->replay_cache;
+ if (get_subcredential_for_handling_intro2_cell(service,
+ &data, subcredential)) {
+ goto done;
+ }
+
if (hs_cell_parse_introduce2(&data, circ, service) < 0) {
goto done;
}
@@ -1092,7 +1133,7 @@ int
hs_circ_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ,
const hs_desc_intro_point_t *ip,
- const uint8_t *subcredential)
+ const hs_subcredential_t *subcredential)
{
int ret = -1;
ssize_t payload_len;
diff --git a/src/feature/hs/hs_circuit.h b/src/feature/hs/hs_circuit.h
index 92231369c6..22e936e685 100644
--- a/src/feature/hs/hs_circuit.h
+++ b/src/feature/hs/hs_circuit.h
@@ -46,15 +46,16 @@ int hs_circ_handle_intro_established(const hs_service_t *service,
origin_circuit_t *circ,
const uint8_t *payload,
size_t payload_len);
+struct hs_subcredential_t;
int hs_circ_handle_introduce2(const hs_service_t *service,
const origin_circuit_t *circ,
hs_service_intro_point_t *ip,
- const uint8_t *subcredential,
+ const struct hs_subcredential_t *subcredential,
const uint8_t *payload, size_t payload_len);
int hs_circ_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ,
const hs_desc_intro_point_t *ip,
- const uint8_t *subcredential);
+ const struct hs_subcredential_t *subcredential);
int hs_circ_send_establish_rendezvous(origin_circuit_t *circ);
/* e2e circuit API. */
@@ -78,6 +79,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));
+
#endif /* defined(HS_CIRCUIT_PRIVATE) */
#endif /* !defined(TOR_HS_CIRCUIT_H) */
diff --git a/src/feature/hs/hs_circuitmap.c b/src/feature/hs/hs_circuitmap.c
index 2343d729dd..466a02de39 100644
--- a/src/feature/hs/hs_circuitmap.c
+++ b/src/feature/hs/hs_circuitmap.c
@@ -76,11 +76,11 @@ hs_circuit_hash_token(const circuit_t *circuit)
HT_PROTOTYPE(hs_circuitmap_ht, // The name of the hashtable struct
circuit_t, // The name of the element struct,
hs_circuitmap_node, // The name of HT_ENTRY member
- hs_circuit_hash_token, hs_circuits_have_same_token)
+ hs_circuit_hash_token, hs_circuits_have_same_token);
HT_GENERATE2(hs_circuitmap_ht, circuit_t, hs_circuitmap_node,
hs_circuit_hash_token, hs_circuits_have_same_token,
- 0.6, tor_reallocarray, tor_free_)
+ 0.6, tor_reallocarray, tor_free_);
#ifdef TOR_UNIT_TESTS
diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c
index af8cb0b410..cc1b01d2ef 100644
--- a/src/feature/hs/hs_client.c
+++ b/src/feature/hs/hs_client.c
@@ -646,7 +646,7 @@ send_introduce1(origin_circuit_t *intro_circ,
/* Send the INTRODUCE1 cell. */
if (hs_circ_send_introduce1(intro_circ, rend_circ, ip,
- desc->subcredential) < 0) {
+ &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
@@ -1845,7 +1845,7 @@ hs_client_decode_descriptor(const char *desc_str,
hs_descriptor_t **desc)
{
hs_desc_decode_status_t ret;
- uint8_t subcredential[DIGEST256_LEN];
+ hs_subcredential_t subcredential;
ed25519_public_key_t blinded_pubkey;
hs_client_service_authorization_t *client_auth = NULL;
curve25519_secret_key_t *client_auth_sk = NULL;
@@ -1865,13 +1865,13 @@ hs_client_decode_descriptor(const char *desc_str,
uint64_t current_time_period = hs_get_time_period_num(0);
hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period,
&blinded_pubkey);
- hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential);
+ hs_get_subcredential(service_identity_pk, &blinded_pubkey, &subcredential);
}
/* Parse descriptor */
- ret = hs_desc_decode_descriptor(desc_str, subcredential,
+ ret = hs_desc_decode_descriptor(desc_str, &subcredential,
client_auth_sk, desc);
- memwipe(subcredential, 0, sizeof(subcredential));
+ memwipe(&subcredential, 0, sizeof(subcredential));
if (ret != HS_DESC_DECODE_OK) {
goto err;
}
@@ -2486,4 +2486,3 @@ set_hs_client_auths_map(digest256map_t *map)
}
#endif /* defined(TOR_UNIT_TESTS) */
-
diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c
index f8b031cc26..4639cdb68a 100644
--- a/src/feature/hs/hs_common.c
+++ b/src/feature/hs/hs_common.c
@@ -22,6 +22,7 @@
#include "feature/hs/hs_client.h"
#include "feature/hs/hs_common.h"
#include "feature/hs/hs_dos.h"
+#include "feature/hs/hs_ob.h"
#include "feature/hs/hs_ident.h"
#include "feature/hs/hs_service.h"
#include "feature/hs_common/shared_random_client.h"
@@ -808,12 +809,12 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out,
}
/** Using the given identity public key and a blinded public key, compute the
- * subcredential and put it in subcred_out (must be of size DIGEST256_LEN).
+ * subcredential and put it in subcred_out.
* This can't fail. */
void
hs_get_subcredential(const ed25519_public_key_t *identity_pk,
const ed25519_public_key_t *blinded_pk,
- uint8_t *subcred_out)
+ hs_subcredential_t *subcred_out)
{
uint8_t credential[DIGEST256_LEN];
crypto_digest_t *digest;
@@ -841,7 +842,8 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk,
sizeof(credential));
crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
ED25519_PUBKEY_LEN);
- crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN);
+ crypto_digest_get_digest(digest, (char *) subcred_out->subcred,
+ SUBCRED_LEN);
crypto_digest_free(digest);
memwipe(credential, 0, sizeof(credential));
@@ -909,30 +911,35 @@ hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn)
* case the caller would want only one field. checksum_out MUST at least be 2
* bytes long.
*
- * Return 0 if parsing went well; return -1 in case of error. */
+ * Return 0 if parsing went well; return -1 in case of error and if errmsg is
+ * non NULL, a human readable string message is set. */
int
-hs_parse_address(const char *address, ed25519_public_key_t *key_out,
- uint8_t *checksum_out, uint8_t *version_out)
+hs_parse_address_no_log(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out,
+ const char **errmsg)
{
char decoded[HS_SERVICE_ADDR_LEN];
tor_assert(address);
+ if (errmsg) {
+ *errmsg = NULL;
+ }
+
/* Obvious length check. */
if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) {
- log_warn(LD_REND, "Service address %s has an invalid length. "
- "Expected %lu but got %lu.",
- escaped_safe_str(address),
- (unsigned long) HS_SERVICE_ADDR_LEN_BASE32,
- (unsigned long) strlen(address));
+ if (errmsg) {
+ *errmsg = "Invalid length";
+ }
goto invalid;
}
/* Decode address so we can extract needed fields. */
if (base32_decode(decoded, sizeof(decoded), address, strlen(address))
!= sizeof(decoded)) {
- log_warn(LD_REND, "Service address %s can't be decoded.",
- escaped_safe_str(address));
+ if (errmsg) {
+ *errmsg = "Unable to base32 decode";
+ }
goto invalid;
}
@@ -944,6 +951,22 @@ hs_parse_address(const char *address, ed25519_public_key_t *key_out,
return -1;
}
+/** Same has hs_parse_address_no_log() but emits a log warning on parsing
+ * failure. */
+int
+hs_parse_address(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ const char *errmsg = NULL;
+ int ret = hs_parse_address_no_log(address, key_out, checksum_out,
+ version_out, &errmsg);
+ if (ret < 0) {
+ log_warn(LD_REND, "Service address %s failed to be parsed: %s",
+ escaped_safe_str(address), errmsg);
+ }
+ return ret;
+}
+
/** Validate a given onion address. The length, the base32 decoding, and
* checksum are validated. Return 1 if valid else 0. */
int
@@ -1807,6 +1830,7 @@ hs_free_all(void)
hs_service_free_all();
hs_cache_free_all();
hs_client_free_all();
+ hs_ob_free_all();
}
/** For the given origin circuit circ, decrement the number of rendezvous
diff --git a/src/feature/hs/hs_common.h b/src/feature/hs/hs_common.h
index 8f743d4d37..997b7298a6 100644
--- a/src/feature/hs/hs_common.h
+++ b/src/feature/hs/hs_common.h
@@ -179,6 +179,10 @@ void hs_build_address(const struct ed25519_public_key_t *key, uint8_t version,
int hs_address_is_valid(const char *address);
int hs_parse_address(const char *address, struct ed25519_public_key_t *key_out,
uint8_t *checksum_out, uint8_t *version_out);
+int hs_parse_address_no_log(const char *address,
+ struct ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out,
+ const char **errmsg);
void hs_build_blinded_pubkey(const struct ed25519_public_key_t *pubkey,
const uint8_t *secret, size_t secret_len,
@@ -210,9 +214,10 @@ const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32);
+struct hs_subcredential_t;
void hs_get_subcredential(const struct ed25519_public_key_t *identity_pk,
const struct ed25519_public_key_t *blinded_pk,
- uint8_t *subcred_out);
+ struct hs_subcredential_t *subcred_out);
uint64_t hs_get_previous_time_period_num(time_t now);
uint64_t hs_get_time_period_num(time_t now);
diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c
index 64656b1935..684f7bc975 100644
--- a/src/feature/hs/hs_config.c
+++ b/src/feature/hs/hs_config.c
@@ -26,6 +26,7 @@
#include "feature/hs/hs_common.h"
#include "feature/hs/hs_config.h"
#include "feature/hs/hs_client.h"
+#include "feature/hs/hs_ob.h"
#include "feature/hs/hs_service.h"
#include "feature/rend/rendclient.h"
#include "feature/rend/rendservice.h"
@@ -219,6 +220,7 @@ config_has_invalid_options(const config_line_t *line_,
"HiddenServiceEnableIntroDoSDefense",
"HiddenServiceEnableIntroDoSRatePerSec",
"HiddenServiceEnableIntroDoSBurstPerSec",
+ "HiddenServiceOnionBalanceInstance",
NULL /* End marker. */
};
@@ -317,7 +319,7 @@ config_service_v3(const config_line_t *line_,
int have_num_ip = 0;
bool export_circuit_id = false; /* just to detect duplicate options */
bool dos_enabled = false, dos_rate_per_sec = false;
- bool dos_burst_per_sec = false;
+ bool dos_burst_per_sec = false, ob_instance = false;
const char *dup_opt_seen = NULL;
const config_line_t *line;
@@ -402,6 +404,27 @@ config_service_v3(const config_line_t *line_,
config->intro_dos_burst_per_sec);
continue;
}
+ if (!strcasecmp(line->key, "HiddenServiceOnionBalanceInstance")) {
+ bool enabled = !!helper_parse_uint64(line->key, line->value,
+ 0, 1, &ok);
+ if (!ok || ob_instance) {
+ if (ob_instance) {
+ dup_opt_seen = line->key;
+ }
+ goto err;
+ }
+ ob_instance = true;
+ if (!enabled) {
+ /* Skip if this is disabled. */
+ continue;
+ }
+ /* Option is enabled, parse config file. */
+ ok = hs_ob_parse_config_file(config);
+ if (!ok) {
+ goto err;
+ }
+ continue;
+ }
}
/* We do not load the key material for the service at this stage. This is
diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c
index 65d6c7a581..c274ed7581 100644
--- a/src/feature/hs/hs_descriptor.c
+++ b/src/feature/hs/hs_descriptor.c
@@ -211,7 +211,7 @@ build_secret_input(const hs_descriptor_t *desc,
memcpy(secret_input, secret_data, secret_data_len);
offset += secret_data_len;
/* Copy subcredential. */
- memcpy(secret_input + offset, desc->subcredential, DIGEST256_LEN);
+ memcpy(secret_input + offset, desc->subcredential.subcred, DIGEST256_LEN);
offset += DIGEST256_LEN;
/* Copy revision counter value. */
set_uint64(secret_input + offset,
@@ -1018,10 +1018,6 @@ desc_encode_v3(const hs_descriptor_t *desc,
tor_assert(encoded_out);
tor_assert(desc->plaintext_data.version == 3);
- if (BUG(desc->subcredential == NULL)) {
- goto err;
- }
-
/* Build the non-encrypted values. */
{
char *encoded_cert;
@@ -1366,8 +1362,7 @@ encrypted_data_length_is_valid(size_t len)
* and return the buffer's length. The caller should wipe and free its content
* once done with it. This function can't fail. */
static size_t
-build_descriptor_cookie_keys(const uint8_t *subcredential,
- size_t subcredential_len,
+build_descriptor_cookie_keys(const hs_subcredential_t *subcredential,
const curve25519_secret_key_t *sk,
const curve25519_public_key_t *pk,
uint8_t **keys_out)
@@ -1389,7 +1384,7 @@ build_descriptor_cookie_keys(const uint8_t *subcredential,
/* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */
xof = crypto_xof_new();
- crypto_xof_add_bytes(xof, subcredential, subcredential_len);
+ crypto_xof_add_bytes(xof, subcredential->subcred, SUBCRED_LEN);
crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed));
crypto_xof_squeeze_bytes(xof, keystream, keystream_len);
crypto_xof_free(xof);
@@ -1426,11 +1421,12 @@ decrypt_descriptor_cookie(const hs_descriptor_t *desc,
sizeof(desc->superencrypted_data.auth_ephemeral_pubkey)));
tor_assert(!fast_mem_is_zero((char *) client_auth_sk,
sizeof(*client_auth_sk)));
- tor_assert(!fast_mem_is_zero((char *) desc->subcredential, DIGEST256_LEN));
+ tor_assert(!fast_mem_is_zero((char *) desc->subcredential.subcred,
+ DIGEST256_LEN));
/* Get the KEYS component to derive the CLIENT-ID and COOKIE-KEY. */
keystream_length =
- build_descriptor_cookie_keys(desc->subcredential, DIGEST256_LEN,
+ build_descriptor_cookie_keys(&desc->subcredential,
client_auth_sk,
&desc->superencrypted_data.auth_ephemeral_pubkey,
&keystream);
@@ -2558,7 +2554,7 @@ hs_desc_decode_plaintext(const char *encoded,
* set to NULL. */
hs_desc_decode_status_t
hs_desc_decode_descriptor(const char *encoded,
- const uint8_t *subcredential,
+ const hs_subcredential_t *subcredential,
const curve25519_secret_key_t *client_auth_sk,
hs_descriptor_t **desc_out)
{
@@ -2576,7 +2572,7 @@ hs_desc_decode_descriptor(const char *encoded,
goto err;
}
- memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
+ memcpy(&desc->subcredential, subcredential, sizeof(desc->subcredential));
ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data);
if (ret != HS_DESC_DECODE_OK) {
@@ -2666,7 +2662,7 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
* symmetric only if the client auth is disabled. That is, the descriptor
* cookie will be NULL. */
if (!descriptor_cookie) {
- ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential,
+ ret = hs_desc_decode_descriptor(*encoded_out, &desc->subcredential,
NULL, NULL);
if (BUG(ret != HS_DESC_DECODE_OK)) {
ret = -1;
@@ -2870,7 +2866,7 @@ hs_desc_build_fake_authorized_client(void)
* key, and descriptor cookie, build the auth client so we can then encode the
* descriptor for publication. client_out must be already allocated. */
void
-hs_desc_build_authorized_client(const uint8_t *subcredential,
+hs_desc_build_authorized_client(const hs_subcredential_t *subcredential,
const curve25519_public_key_t *client_auth_pk,
const curve25519_secret_key_t *
auth_ephemeral_sk,
@@ -2898,7 +2894,7 @@ hs_desc_build_authorized_client(const uint8_t *subcredential,
/* Get the KEYS part so we can derive the CLIENT-ID and COOKIE-KEY. */
keystream_length =
- build_descriptor_cookie_keys(subcredential, DIGEST256_LEN,
+ build_descriptor_cookie_keys(subcredential,
auth_ephemeral_sk, client_auth_pk,
&keystream);
tor_assert(keystream_length > 0);
diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h
index 639dd31c8f..08daa904b6 100644
--- a/src/feature/hs/hs_descriptor.h
+++ b/src/feature/hs/hs_descriptor.h
@@ -14,6 +14,7 @@
#include "core/or/or.h"
#include "trunnel/ed25519_cert.h" /* needed for trunnel */
#include "feature/nodelist/torcert.h"
+#include "core/crypto/hs_ntor.h" /* for hs_subcredential_t */
/* Trunnel */
struct link_specifier_t;
@@ -238,7 +239,7 @@ typedef struct hs_descriptor_t {
/** Subcredentials of a service, used by the client and service to decrypt
* the encrypted data. */
- uint8_t subcredential[DIGEST256_LEN];
+ hs_subcredential_t subcredential;
} hs_descriptor_t;
/** Return true iff the given descriptor format version is supported. */
@@ -277,7 +278,7 @@ MOCK_DECL(int,
char **encoded_out));
int hs_desc_decode_descriptor(const char *encoded,
- const uint8_t *subcredential,
+ const hs_subcredential_t *subcredential,
const curve25519_secret_key_t *client_auth_sk,
hs_descriptor_t **desc_out);
int hs_desc_decode_plaintext(const char *encoded,
@@ -302,7 +303,7 @@ void hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client);
hs_desc_authorized_client_t *hs_desc_build_fake_authorized_client(void);
-void hs_desc_build_authorized_client(const uint8_t *subcredential,
+void hs_desc_build_authorized_client(const hs_subcredential_t *subcredential,
const curve25519_public_key_t *
client_auth_pk,
const curve25519_secret_key_t *
diff --git a/src/feature/hs/hs_ob.c b/src/feature/hs/hs_ob.c
new file mode 100644
index 0000000000..f135ecd3f4
--- /dev/null
+++ b/src/feature/hs/hs_ob.c
@@ -0,0 +1,408 @@
+/* Copyright (c) 2017-2020, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ob.c
+ * \brief Implement Onion Balance specific code.
+ **/
+
+#define HS_OB_PRIVATE
+
+#include "feature/hs/hs_service.h"
+
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/networkstatus_st.h"
+
+#include "lib/confmgt/confmgt.h"
+#include "lib/encoding/confline.h"
+
+#include "feature/hs/hs_ob.h"
+
+/* Options config magic number. */
+#define OB_OPTIONS_MAGIC 0x631DE7EA
+
+/* Helper macros. */
+#define VAR(varname, conftype, member, initvalue) \
+ CONFIG_VAR_ETYPE(ob_options_t, varname, conftype, member, 0, initvalue)
+#define V(member,conftype,initvalue) \
+ VAR(#member, conftype, member, initvalue)
+
+/* Dummy instance of ob_options_t, used for type-checking its members with
+ * CONF_CHECK_VAR_TYPE. */
+DUMMY_TYPECHECK_INSTANCE(ob_options_t);
+
+/* Array of variables for the config file options. */
+static const config_var_t config_vars[] = {
+ V(MasterOnionAddress, LINELIST, NULL),
+
+ END_OF_CONFIG_VARS
+};
+
+/* "Extra" variable in the state that receives lines we can't parse. This
+ * lets us preserve options from versions of Tor newer than us. */
+static const struct_member_t config_extra_vars = {
+ .name = "__extra",
+ .type = CONFIG_TYPE_LINELIST,
+ .offset = offsetof(ob_options_t, ExtraLines),
+};
+
+/* Configuration format of ob_options_t. */
+static const config_format_t config_format = {
+ .size = sizeof(ob_options_t),
+ .magic = {
+ "ob_options_t",
+ OB_OPTIONS_MAGIC,
+ offsetof(ob_options_t, magic_),
+ },
+ .vars = config_vars,
+ .extra = &config_extra_vars,
+};
+
+/* Global configuration manager for the config file. */
+static config_mgr_t *config_options_mgr = NULL;
+
+/* Return the configuration manager for the config file. */
+static const config_mgr_t *
+get_config_options_mgr(void)
+{
+ if (PREDICT_UNLIKELY(config_options_mgr == NULL)) {
+ config_options_mgr = config_mgr_new(&config_format);
+ config_mgr_freeze(config_options_mgr);
+ }
+ return config_options_mgr;
+}
+
+#define ob_option_free(val) \
+ FREE_AND_NULL(ob_options_t, ob_option_free_, (val))
+
+/** Helper: Free a config options object. */
+static void
+ob_option_free_(ob_options_t *opts)
+{
+ if (opts == NULL) {
+ return;
+ }
+ config_free(get_config_options_mgr(), opts);
+}
+
+/** Return an allocated config options object. */
+static ob_options_t *
+ob_option_new(void)
+{
+ ob_options_t *opts = config_new(get_config_options_mgr());
+ config_init(get_config_options_mgr(), opts);
+ return opts;
+}
+
+/** Helper function: From the configuration line value which is an onion
+ * address with the ".onion" extension, find the public key and put it in
+ * pkey_out.
+ *
+ * On success, true is returned. Else, false and pkey is untouched. */
+static bool
+get_onion_public_key(const char *value, ed25519_public_key_t *pkey_out)
+{
+ char address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+
+ tor_assert(value);
+ tor_assert(pkey_out);
+
+ if (strcmpend(value, ".onion")) {
+ /* Not a .onion extension, bad format. */
+ return false;
+ }
+
+ /* Length validation. The -1 is because sizeof() counts the NUL byte. */
+ if (strlen(value) >
+ (HS_SERVICE_ADDR_LEN_BASE32 + sizeof(".onion") - 1)) {
+ /* Too long, bad format. */
+ return false;
+ }
+
+ /* We don't want the .onion so we add 2 because size - 1 is copied with
+ * strlcpy() in order to accomodate the NUL byte and sizeof() counts the NUL
+ * byte so we need to remove them from the equation. */
+ strlcpy(address, value, strlen(value) - sizeof(".onion") + 2);
+
+ if (hs_parse_address_no_log(address, pkey_out, NULL, NULL, NULL) < 0) {
+ return false;
+ }
+
+ /* Success. */
+ return true;
+}
+
+/** Parse the given ob options in opts and set the service config object
+ * accordingly.
+ *
+ * Return 1 on success else 0. */
+static int
+ob_option_parse(hs_service_config_t *config, const ob_options_t *opts)
+{
+ int ret = 0;
+ config_line_t *line;
+
+ tor_assert(config);
+ tor_assert(opts);
+
+ for (line = opts->MasterOnionAddress; line; line = line->next) {
+ /* Allocate config list if need be. */
+ if (!config->ob_master_pubkeys) {
+ config->ob_master_pubkeys = smartlist_new();
+ }
+ ed25519_public_key_t *pubkey = tor_malloc_zero(sizeof(*pubkey));
+
+ if (!get_onion_public_key(line->value, pubkey)) {
+ log_warn(LD_REND, "OnionBalance: MasterOnionAddress %s is invalid",
+ line->value);
+ tor_free(pubkey);
+ goto end;
+ }
+ smartlist_add(config->ob_master_pubkeys, pubkey);
+ log_info(LD_REND, "OnionBalance: MasterOnionAddress %s registered",
+ line->value);
+ }
+ /* Success. */
+ ret = 1;
+
+ end:
+ /* No keys added, we free the list since no list means no onion balance
+ * support for this tor instance. */
+ if (smartlist_len(config->ob_master_pubkeys) == 0) {
+ smartlist_free(config->ob_master_pubkeys);
+ }
+ return ret;
+}
+
+/** For the given master public key and time period, compute the subcredential
+ * and put them into subcredential. The subcredential parameter needs to be at
+ * least DIGEST256_LEN in size. */
+static void
+build_subcredential(const ed25519_public_key_t *pkey, uint64_t tp,
+ hs_subcredential_t *subcredential)
+{
+ ed25519_public_key_t blinded_pubkey;
+
+ tor_assert(pkey);
+ tor_assert(subcredential);
+
+ hs_build_blinded_pubkey(pkey, NULL, 0, tp, &blinded_pubkey);
+ hs_get_subcredential(pkey, &blinded_pubkey, subcredential);
+}
+
+/*
+ * Public API.
+ */
+
+/** Return true iff the given service is configured as an onion balance
+ * instance. To satisfy that condition, there must at least be one master
+ * ed25519 public key configured. */
+bool
+hs_ob_service_is_instance(const hs_service_t *service)
+{
+ if (BUG(service == NULL)) {
+ return false;
+ }
+
+ /* No list, we are not an instance. */
+ if (!service->config.ob_master_pubkeys) {
+ return false;
+ }
+
+ return smartlist_len(service->config.ob_master_pubkeys) > 0;
+}
+
+/** Read and parse the config file at fname on disk. The service config object
+ * is populated with the options if any.
+ *
+ * Return 1 on success else 0. This is to follow the "ok" convention in
+ * hs_config.c. */
+int
+hs_ob_parse_config_file(hs_service_config_t *config)
+{
+ static const char *fname = "ob_config";
+ int ret = 0;
+ char *content = NULL, *errmsg = NULL, *config_file_path = NULL;
+ ob_options_t *options = NULL;
+ config_line_t *lines = NULL;
+
+ tor_assert(config);
+
+ /* Read file from disk. */
+ config_file_path = hs_path_from_filename(config->directory_path, fname);
+ content = read_file_to_str(config_file_path, 0, NULL);
+ if (!content) {
+ log_warn(LD_FS, "OnionBalance: Unable to read config file %s",
+ escaped(config_file_path));
+ goto end;
+ }
+
+ /* Parse lines. */
+ if (config_get_lines(content, &lines, 0) < 0) {
+ goto end;
+ }
+
+ options = ob_option_new();
+ config_assign(get_config_options_mgr(), options, lines, 0, &errmsg);
+ if (errmsg) {
+ log_warn(LD_REND, "OnionBalance: Unable to parse config file: %s",
+ errmsg);
+ tor_free(errmsg);
+ goto end;
+ }
+
+ /* Parse the options and set the service config object with the details. */
+ ret = ob_option_parse(config, options);
+
+ end:
+ config_free_lines(lines);
+ ob_option_free(options);
+ tor_free(content);
+ tor_free(config_file_path);
+ return ret;
+}
+
+/** Compute all possible subcredentials for every onion master key in the given
+ * service config object. subcredentials_out is allocated and set as an
+ * continous array containing all possible values.
+ *
+ * On success, return the number of subcredential put in the array which will
+ * correspond to an arry of size: n * DIGEST256_LEN where DIGEST256_LEN is the
+ * length of a single subcredential.
+ *
+ * If the given configuration object has no OB master keys configured, 0 is
+ * returned and subcredentials_out is set to NULL.
+ *
+ * Otherwise, this can't fail. */
+STATIC size_t
+compute_subcredentials(const hs_service_t *service,
+ hs_subcredential_t **subcredentials_out)
+{
+ unsigned int num_pkeys, idx = 0;
+ hs_subcredential_t *subcreds = NULL;
+ const int steps[3] = {0, -1, 1};
+ const unsigned int num_steps = ARRAY_LENGTH(steps);
+ const uint64_t tp = hs_get_time_period_num(0);
+
+ tor_assert(service);
+ tor_assert(subcredentials_out);
+ /* Our caller has checked these too */
+ tor_assert(service->desc_current);
+ tor_assert(service->desc_next);
+
+ /* Make sure we are an OB instance, or bail out. */
+ num_pkeys = smartlist_len(service->config.ob_master_pubkeys);
+ if (!num_pkeys) {
+ *subcredentials_out = NULL;
+ return 0;
+ }
+
+ /* Time to build all the subcredentials for each time period: two for each
+ * instance descriptor plus three for the onionbalance frontend service: the
+ * previous one (-1), the current one (0) and the next one (1) for each
+ * configured key in order to accomodate client and service consensus skew.
+ *
+ * If the client consensus after_time is at 23:00 but the service one is at
+ * 01:00, the client will be using the previous time period where the
+ * service will think it is the client next time period. Thus why we have
+ * to try them all.
+ *
+ * The normal use case works because the service gets the descriptor object
+ * that corresponds to the intro point's request, and because each
+ * descriptor corresponds to a specific subcredential, we get the right
+ * subcredential out of it, and use that to do the decryption.
+ *
+ * As a slight optimization, statistically, the current time period (0) will
+ * be the one to work first so we'll put them first in the array to maximize
+ * our chance of success. */
+
+ /* We use a flat array, not a smartlist_t, in order to minimize memory
+ * allocation.
+ *
+ * Size of array is: length of a single subcredential multiplied by the
+ * number of time period we need to compute and finally multiplied by the
+ * total number of keys we are about to process. In other words, for each
+ * key, we allocate 3 subcredential slots. Then in the end we also add two
+ * subcredentials for this instance's active descriptors. */
+ subcreds =
+ tor_calloc((num_steps * num_pkeys) + 2, sizeof(hs_subcredential_t));
+
+ /* For each master pubkey we add 3 subcredentials: */
+ for (unsigned int i = 0; i < num_steps; i++) {
+ SMARTLIST_FOREACH_BEGIN(service->config.ob_master_pubkeys,
+ const ed25519_public_key_t *, pkey) {
+ build_subcredential(pkey, tp + steps[i], &subcreds[idx]);
+ idx++;
+ } SMARTLIST_FOREACH_END(pkey);
+ }
+
+ /* And then in the end we add the two subcredentials of the current active
+ * instance descriptors */
+ memcpy(&subcreds[idx++], &service->desc_current->desc->subcredential,
+ sizeof(hs_subcredential_t));
+ memcpy(&subcreds[idx++], &service->desc_next->desc->subcredential,
+ sizeof(hs_subcredential_t));
+
+ log_info(LD_REND, "Refreshing %u onionbalance keys (TP #%d).",
+ idx, (int)tp);
+
+ *subcredentials_out = subcreds;
+ return idx;
+}
+
+/**
+ * If we are an Onionbalance instance, refresh our keys.
+ *
+ * If we are not an Onionbalance instance or we are not ready to do so, this
+ * is a NOP.
+ *
+ * This function is called everytime we build a new descriptor. That's because
+ * we want our Onionbalance keys to always use up-to-date subcredentials both
+ * for the instance (ourselves) and for the onionbalance frontend.
+ */
+void
+hs_ob_refresh_keys(hs_service_t *service)
+{
+ hs_subcredential_t *ob_subcreds = NULL;
+ size_t num_subcreds;
+
+ tor_assert(service);
+
+ /* Don't do any of this if we are not configured as an OB instance */
+ if (!hs_ob_service_is_instance(service)) {
+ return;
+ }
+
+ /* We need both service descriptors created to make onionbalance keys.
+ *
+ * That's because we fetch our own (the instance's) subcredentials from our
+ * own descriptors which should always include the latest subcredentials that
+ * clients would use.
+ *
+ * This function is called with each descriptor build, so we will be
+ * eventually be called when both descriptors are created. */
+ if (!service->desc_current || !service->desc_next) {
+ return;
+ }
+
+ /* Get a new set of subcreds */
+ num_subcreds = compute_subcredentials(service, &ob_subcreds);
+ if (BUG(!num_subcreds)) {
+ return;
+ }
+
+ /* Delete old subcredentials if any */
+ if (service->ob_subcreds) {
+ tor_free(service->ob_subcreds);
+ }
+
+ service->ob_subcreds = ob_subcreds;
+ service->n_ob_subcreds = num_subcreds;
+}
+
+/** Free any memory allocated by the onionblance subsystem. */
+void
+hs_ob_free_all(void)
+{
+ config_mgr_free(config_options_mgr);
+}
diff --git a/src/feature/hs/hs_ob.h b/src/feature/hs/hs_ob.h
new file mode 100644
index 0000000000..d6e6e73a84
--- /dev/null
+++ b/src/feature/hs/hs_ob.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ob.h
+ * \brief Header file for the specific code for onion balance.
+ **/
+
+#ifndef TOR_HS_OB_H
+#define TOR_HS_OB_H
+
+#include "feature/hs/hs_service.h"
+
+bool hs_ob_service_is_instance(const hs_service_t *service);
+
+int hs_ob_parse_config_file(hs_service_config_t *config);
+
+struct hs_subcredential_t;
+
+void hs_ob_free_all(void);
+
+void hs_ob_refresh_keys(hs_service_t *service);
+
+#ifdef HS_OB_PRIVATE
+
+STATIC size_t compute_subcredentials(const hs_service_t *service,
+ struct hs_subcredential_t **subcredentials);
+
+typedef struct ob_options_t {
+ /** Magic number to identify the structure in memory. */
+ uint32_t magic_;
+ /** Master Onion Address(es). */
+ struct config_line_t *MasterOnionAddress;
+ /** Extra Lines for configuration we might not know. */
+ struct config_line_t *ExtraLines;
+} ob_options_t;
+
+#endif /* defined(HS_OB_PRIVATE) */
+
+#endif /* !defined(TOR_HS_OB_H) */
diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c
index b366ce83d9..3a2beb766f 100644
--- a/src/feature/hs/hs_service.c
+++ b/src/feature/hs/hs_service.c
@@ -41,6 +41,7 @@
#include "feature/hs/hs_intropoint.h"
#include "feature/hs/hs_service.h"
#include "feature/hs/hs_stats.h"
+#include "feature/hs/hs_ob.h"
#include "feature/dircommon/dir_connection_st.h"
#include "core/or/edge_connection_st.h"
@@ -151,11 +152,11 @@ HT_PROTOTYPE(hs_service_ht, /* Name of hashtable. */
hs_service_t, /* Object contained in the map. */
hs_service_node, /* The name of the HT_ENTRY member. */
hs_service_ht_hash, /* Hashing function. */
- hs_service_ht_eq) /* Compare function for objects. */
+ hs_service_ht_eq); /* Compare function for objects. */
HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node,
hs_service_ht_hash, hs_service_ht_eq,
- 0.6, tor_reallocarray, tor_free_)
+ 0.6, tor_reallocarray, tor_free_);
/** 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
@@ -267,6 +268,11 @@ service_clear_config(hs_service_config_t *config)
service_authorized_client_free(p));
smartlist_free(config->clients);
}
+ if (config->ob_master_pubkeys) {
+ SMARTLIST_FOREACH(config->ob_master_pubkeys, ed25519_public_key_t *, k,
+ tor_free(k));
+ smartlist_free(config->ob_master_pubkeys);
+ }
memset(config, 0, sizeof(*config));
}
@@ -701,8 +707,8 @@ get_extend_info_from_intro_point(const hs_service_intro_point_t *ip,
/** Return the number of introduction points that are established for the
* given descriptor. */
-static unsigned int
-count_desc_circuit_established(const hs_service_descriptor_t *desc)
+MOCK_IMPL(STATIC unsigned int,
+count_desc_circuit_established, (const hs_service_descriptor_t *desc))
{
unsigned int count = 0;
@@ -1764,7 +1770,8 @@ build_service_desc_superencrypted(const hs_service_t *service,
sizeof(curve25519_public_key_t));
/* Test that subcred is not zero because we might use it below */
- if (BUG(fast_mem_is_zero((char*)desc->desc->subcredential, DIGEST256_LEN))) {
+ if (BUG(fast_mem_is_zero((char*)desc->desc->subcredential.subcred,
+ DIGEST256_LEN))) {
return -1;
}
@@ -1781,7 +1788,7 @@ build_service_desc_superencrypted(const hs_service_t *service,
/* Prepare the client for descriptor and then add to the list in the
* superencrypted part of the descriptor */
- hs_desc_build_authorized_client(desc->desc->subcredential,
+ hs_desc_build_authorized_client(&desc->desc->subcredential,
&client->client_pk,
&desc->auth_ephemeral_kp.seckey,
desc->descriptor_cookie, desc_client);
@@ -1837,7 +1844,7 @@ build_service_desc_plaintext(const hs_service_t *service,
/* Set the subcredential. */
hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey,
- desc->desc->subcredential);
+ &desc->desc->subcredential);
plaintext = &desc->desc->plaintext_data;
@@ -1980,9 +1987,15 @@ build_service_descriptor(hs_service_t *service, uint64_t time_period_num,
/* Assign newly built descriptor to the next slot. */
*desc_out = desc;
+
/* Fire a CREATED control port event. */
hs_control_desc_event_created(service->onion_address,
&desc->blinded_kp.pubkey);
+
+ /* If we are an onionbalance instance, we refresh our keys when we rotate
+ * descriptors. */
+ hs_ob_refresh_keys(service);
+
return;
err:
@@ -3042,13 +3055,85 @@ service_desc_hsdirs_changed(const hs_service_t *service,
return should_reupload;
}
+/** These are all the reasons why a descriptor upload can't occur. We use
+ * those to log the reason properly with the right rate limiting and for the
+ * right descriptor. */
+typedef enum {
+ LOG_DESC_UPLOAD_REASON_MISSING_IPS = 0,
+ LOG_DESC_UPLOAD_REASON_IP_NOT_ESTABLISHED = 1,
+ LOG_DESC_UPLOAD_REASON_NOT_TIME = 2,
+ LOG_DESC_UPLOAD_REASON_NO_LIVE_CONSENSUS = 3,
+ LOG_DESC_UPLOAD_REASON_NO_DIRINFO = 4,
+} log_desc_upload_reason_t;
+
+/** Maximum number of reasons. This is used to allocate the static array of
+ * all rate limiting objects. */
+#define LOG_DESC_UPLOAD_REASON_MAX LOG_DESC_UPLOAD_REASON_NO_DIRINFO
+
+/** Log the reason why we can't upload the given descriptor for the given
+ * service. This takes a message string (allocated by the caller) and a
+ * reason.
+ *
+ * Depending on the reason and descriptor, different rate limit applies. This
+ * is done because this function will basically be called every second. Each
+ * descriptor for each reason uses its own log rate limit object in order to
+ * avoid message suppression for different reasons and descriptors. */
+static void
+log_cant_upload_desc(const hs_service_t *service,
+ const hs_service_descriptor_t *desc, const char *msg,
+ const log_desc_upload_reason_t reason)
+{
+ /* Writing the log every minute shouldn't be too annoying for log rate limit
+ * since this can be emitted every second for each descriptor.
+ *
+ * However, for one specific case, we increase it to 10 minutes because it
+ * is hit constantly, as an expected behavior, which is the reason
+ * indicating that it is not the time to upload. */
+ static ratelim_t limits[2][LOG_DESC_UPLOAD_REASON_MAX + 1] =
+ { { RATELIM_INIT(60), RATELIM_INIT(60), RATELIM_INIT(60 * 10),
+ RATELIM_INIT(60), RATELIM_INIT(60) },
+ { RATELIM_INIT(60), RATELIM_INIT(60), RATELIM_INIT(60 * 10),
+ RATELIM_INIT(60), RATELIM_INIT(60) },
+ };
+ bool is_next_desc = false;
+ unsigned int rlim_pos = 0;
+ ratelim_t *rlim = NULL;
+
+ tor_assert(service);
+ tor_assert(desc);
+ tor_assert(msg);
+
+ /* Make sure the reason value is valid. It should never happen because we
+ * control that value in the code flow but will be apparent during
+ * development if a reason is added but LOG_DESC_UPLOAD_REASON_NUM_ is not
+ * updated. */
+ if (BUG(reason > LOG_DESC_UPLOAD_REASON_MAX || reason < 0)) {
+ return;
+ }
+
+ /* Ease our life. Flag that tells us if the descriptor is the next one. */
+ is_next_desc = (service->desc_next == desc);
+
+ /* Current descriptor is the first element in the ratelimit object array.
+ * The next descriptor is the second element. */
+ rlim_pos = (is_next_desc ? 1 : 0);
+ /* Get the ratelimit object for the reason _and_ right descriptor. */
+ rlim = &limits[rlim_pos][reason];
+
+ log_fn_ratelim(rlim, LOG_INFO, LD_REND,
+ "Service %s can't upload its %s descriptor: %s",
+ safe_str_client(service->onion_address),
+ (is_next_desc) ? "next" : "current", msg);
+}
+
/** Return 1 if the given descriptor from the given service can be uploaded
* else return 0 if it can not. */
static int
should_service_upload_descriptor(const hs_service_t *service,
const hs_service_descriptor_t *desc, time_t now)
{
- unsigned int num_intro_points;
+ char *msg = NULL;
+ unsigned int num_intro_points, count_ip_established;
tor_assert(service);
tor_assert(desc);
@@ -3068,34 +3153,54 @@ should_service_upload_descriptor(const hs_service_t *service,
* upload descriptor in this case. We need at least one for the service to
* be reachable. */
if (desc->missing_intro_points && num_intro_points == 0) {
+ msg = tor_strdup("Missing intro points");
+ log_cant_upload_desc(service, desc, msg,
+ LOG_DESC_UPLOAD_REASON_MISSING_IPS);
goto cannot;
}
/* Check if all our introduction circuit have been established for all the
* intro points we have selected. */
- if (count_desc_circuit_established(desc) != num_intro_points) {
+ count_ip_established = count_desc_circuit_established(desc);
+ if (count_ip_established != num_intro_points) {
+ tor_asprintf(&msg, "Intro circuits aren't yet all established (%d/%d).",
+ count_ip_established, num_intro_points);
+ log_cant_upload_desc(service, desc, msg,
+ LOG_DESC_UPLOAD_REASON_IP_NOT_ESTABLISHED);
goto cannot;
}
/* Is it the right time to upload? */
if (desc->next_upload_time > now) {
+ tor_asprintf(&msg, "Next upload time is %ld, it is now %ld.",
+ (long int) desc->next_upload_time, (long int) now);
+ log_cant_upload_desc(service, desc, msg,
+ LOG_DESC_UPLOAD_REASON_NOT_TIME);
goto cannot;
}
/* Don't upload desc if we don't have a live consensus */
if (!networkstatus_get_live_consensus(now)) {
+ msg = tor_strdup("No live consensus");
+ log_cant_upload_desc(service, desc, msg,
+ LOG_DESC_UPLOAD_REASON_NO_LIVE_CONSENSUS);
goto cannot;
}
/* Do we know enough router descriptors to have adequate vision of the HSDir
hash ring? */
if (!router_have_minimum_dir_info()) {
+ msg = tor_strdup("Not enough directory information");
+ log_cant_upload_desc(service, desc, msg,
+ LOG_DESC_UPLOAD_REASON_NO_DIRINFO);
goto cannot;
}
/* Can upload! */
return 1;
+
cannot:
+ tor_free(msg);
return 0;
}
@@ -3369,7 +3474,7 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload,
/* The following will parse, decode and launch the rendezvous point circuit.
* Both current and legacy cells are handled. */
- if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential,
+ if (hs_circ_handle_introduce2(service, circ, ip, &desc->desc->subcredential,
payload, payload_len) < 0) {
goto err;
}
@@ -4048,6 +4153,11 @@ hs_service_free_(hs_service_t *service)
replaycache_free(service->state.replay_cache_rend_cookie);
}
+ /* Free onionbalance subcredentials (if any) */
+ if (service->ob_subcreds) {
+ tor_free(service->ob_subcreds);
+ }
+
/* Wipe service keys. */
memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk));
diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h
index 8809411e01..3fe14878ed 100644
--- a/src/feature/hs/hs_service.h
+++ b/src/feature/hs/hs_service.h
@@ -248,10 +248,14 @@ typedef struct hs_service_config_t {
/** Does this service export the circuit ID of its clients? */
hs_circuit_id_protocol_t circuit_id_protocol;
- /* DoS defenses. For the ESTABLISH_INTRO cell extension. */
+ /** DoS defenses. For the ESTABLISH_INTRO cell extension. */
unsigned int has_dos_defense_enabled : 1;
uint32_t intro_dos_rate_per_sec;
uint32_t intro_dos_burst_per_sec;
+
+ /** 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;
} hs_service_config_t;
/** Service state. */
@@ -301,8 +305,13 @@ typedef struct hs_service_t {
/** Next descriptor. */
hs_service_descriptor_t *desc_next;
- /* XXX: Credential (client auth.) #20700. */
-
+ /* If this is an onionbalance instance, this is an array of subcredentials
+ * that should be used when decrypting an INTRO2 cell. If this is not an
+ * onionbalance instance, this is NULL.
+ * See [ONIONBALANCE] section in rend-spec-v3.txt for more details . */
+ hs_subcredential_t *ob_subcreds;
+ /* Number of OB subcredentials */
+ size_t n_ob_subcreds;
} hs_service_t;
/** For the service global hash map, we define a specific type for it which
@@ -375,6 +384,9 @@ STATIC hs_service_t *get_first_service(void);
STATIC hs_service_intro_point_t *service_intro_point_find_by_ident(
const hs_service_t *service,
const hs_ident_circuit_t *ident);
+
+MOCK_DECL(STATIC unsigned int, count_desc_circuit_established,
+ (const hs_service_descriptor_t *desc));
#endif /* defined(TOR_UNIT_TESTS) */
/* Service accessors. */
diff --git a/src/feature/hs/include.am b/src/feature/hs/include.am
index 5e69607e59..f83907c76b 100644
--- a/src/feature/hs/include.am
+++ b/src/feature/hs/include.am
@@ -13,6 +13,7 @@ LIBTOR_APP_A_SOURCES += \
src/feature/hs/hs_dos.c \
src/feature/hs/hs_ident.c \
src/feature/hs/hs_intropoint.c \
+ src/feature/hs/hs_ob.c \
src/feature/hs/hs_service.c \
src/feature/hs/hs_stats.c
@@ -30,6 +31,7 @@ noinst_HEADERS += \
src/feature/hs/hs_dos.h \
src/feature/hs/hs_ident.h \
src/feature/hs/hs_intropoint.h \
+ src/feature/hs/hs_ob.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/hs_common/shared_random_client.c b/src/feature/hs_common/shared_random_client.c
index a46666ab50..3f46321be4 100644
--- a/src/feature/hs_common/shared_random_client.c
+++ b/src/feature/hs_common/shared_random_client.c
@@ -11,7 +11,8 @@
#include "feature/hs_common/shared_random_client.h"
#include "app/config/config.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/authmode.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/nodelist/networkstatus.h"
#include "lib/encoding/binascii.h"
@@ -31,6 +32,24 @@ srv_to_control_string(const sr_srv_t *srv)
return srv_str;
}
+/**
+ * If we have no consensus and we are not an authority, assume that this is
+ * the voting interval. We should never actually use this: only authorities
+ * should be trying to figure out the schedule when they don't have a
+ * consensus.
+ **/
+#define DEFAULT_NETWORK_VOTING_INTERVAL (3600)
+
+/* This is an unpleasing workaround for tests. Our unit tests assume that we
+ * are scheduling all of our shared random stuff as if we were a directory
+ * authority, but they do not always set V3AuthoritativeDir.
+ */
+#ifdef TOR_UNIT_TESTS
+#define ASSUME_AUTHORITY_SCHEDULING 1
+#else
+#define ASSUME_AUTHORITY_SCHEDULING 0
+#endif
+
/** Return the voting interval of the tor vote subsystem. */
int
get_voting_interval(void)
@@ -39,40 +58,27 @@ get_voting_interval(void)
networkstatus_t *consensus = networkstatus_get_live_consensus(time(NULL));
if (consensus) {
+ /* Ideally we have a live consensus and we can just use that. */
+ interval = (int)(consensus->fresh_until - consensus->valid_after);
+ } else if (authdir_mode(get_options()) || ASSUME_AUTHORITY_SCHEDULING) {
+ /* If we don't have a live consensus and we're an authority,
+ * we should believe our own view of what the schedule ought to be. */
+ interval = dirauth_sched_get_configured_interval();
+ } else if ((consensus = networkstatus_get_latest_consensus())) {
+ /* If we're a client, then maybe a latest consensus is good enough?
+ * It's better than falling back to the non-consensus case. */
interval = (int)(consensus->fresh_until - consensus->valid_after);
} else {
- /* Same for both a testing and real network. We voluntarily ignore the
- * InitialVotingInterval since it complexifies things and it doesn't
- * affect the SR protocol. */
- interval = get_options()->V3AuthVotingInterval;
+ /* We should never be reaching this point, since a client should never
+ * call this code unless they have some kind of a consensus. All we can
+ * do is hope that this network is using the default voting interval. */
+ tor_assert_nonfatal_unreached_once();
+ interval = DEFAULT_NETWORK_VOTING_INTERVAL;
}
tor_assert(interval > 0);
return interval;
}
-/** Given the current consensus, return the start time of the current round of
- * the SR protocol. For example, if it's 23:47:08, the current round thus
- * started at 23:47:00 for a voting interval of 10 seconds.
- *
- * This function uses the consensus voting schedule to derive its results,
- * instead of the actual consensus we are currently using, so it should be used
- * for voting purposes. */
-time_t
-get_start_time_of_current_round(void)
-{
- const or_options_t *options = get_options();
- int voting_interval = get_voting_interval();
- /* First, get the start time of the next round */
- time_t next_start = voting_schedule_get_next_valid_after_time();
- /* Now roll back next_start by a voting interval to find the start time of
- the current round. */
- time_t curr_start = voting_schedule_get_start_of_next_interval(
- next_start - voting_interval - 1,
- voting_interval,
- options->TestingV3AuthVotingStartOffset);
- return curr_start;
-}
-
/*
* Public API
*/
@@ -237,13 +243,27 @@ sr_state_get_start_time_of_current_protocol_run(void)
time_t beginning_of_curr_round;
/* This function is not used for voting purposes, so if we have a live
- consensus, use its valid-after as the beginning of the current round,
- otherwise resort to the voting schedule which should always exist. */
+ consensus, use its valid-after as the beginning of the current round.
+ If we have no consensus but we're an authority, use our own
+ schedule. Otherwise, try using our view of the voting interval
+ to figure out when the current round _should_ be starting.
+ */
networkstatus_t *ns = networkstatus_get_live_consensus(approx_time());
if (ns) {
beginning_of_curr_round = ns->valid_after;
+ } else if (authdir_mode(get_options()) || ASSUME_AUTHORITY_SCHEDULING) {
+ beginning_of_curr_round = dirauth_sched_get_cur_valid_after_time();
} else {
- beginning_of_curr_round = get_start_time_of_current_round();
+ /* voting_interval comes from get_voting_interval(), so if we're in
+ * this case as a client, we already tried to get the voting interval
+ * from the latest_consensus and gave a bug warning if we couldn't.
+ *
+ * We wouldn't want to look at the latest consensus's valid_after time,
+ * since that would be out of date. */
+ beginning_of_curr_round = voting_sched_get_start_of_interval_after(
+ approx_time() - voting_interval,
+ voting_interval,
+ 0);
}
/* Get current SR protocol round */
diff --git a/src/feature/hs_common/shared_random_client.h b/src/feature/hs_common/shared_random_client.h
index 3031a2bb9a..37a086d590 100644
--- a/src/feature/hs_common/shared_random_client.h
+++ b/src/feature/hs_common/shared_random_client.h
@@ -38,11 +38,9 @@ time_t sr_state_get_start_time_of_current_protocol_run(void);
time_t sr_state_get_start_time_of_previous_protocol_run(void);
unsigned int sr_state_get_phase_duration(void);
unsigned int sr_state_get_protocol_run_duration(void);
-time_t get_start_time_of_current_round(void);
#ifdef TOR_UNIT_TESTS
#endif /* TOR_UNIT_TESTS */
#endif /* !defined(TOR_SHARED_RANDOM_CLIENT_H) */
-
diff --git a/src/feature/nodelist/authcert.c b/src/feature/nodelist/authcert.c
index 7bdfabaeab..9c7525b6e2 100644
--- a/src/feature/nodelist/authcert.c
+++ b/src/feature/nodelist/authcert.c
@@ -46,7 +46,7 @@
#include "feature/nodelist/networkstatus_voter_info_st.h"
#include "feature/nodelist/node_st.h"
-DECLARE_TYPED_DIGESTMAP_FNS(dsmap_, digest_ds_map_t, download_status_t)
+DECLARE_TYPED_DIGESTMAP_FNS(dsmap, digest_ds_map_t, download_status_t)
#define DSMAP_FOREACH(map, keyvar, valvar) \
DIGESTMAP_FOREACH(dsmap_to_digestmap(map), keyvar, download_status_t *, \
valvar)
diff --git a/src/feature/nodelist/microdesc.c b/src/feature/nodelist/microdesc.c
index d32a4ea61e..cf7732b8dc 100644
--- a/src/feature/nodelist/microdesc.c
+++ b/src/feature/nodelist/microdesc.c
@@ -90,10 +90,10 @@ microdesc_eq_(microdesc_t *a, microdesc_t *b)
}
HT_PROTOTYPE(microdesc_map, microdesc_t, node,
- microdesc_hash_, microdesc_eq_)
+ microdesc_hash_, microdesc_eq_);
HT_GENERATE2(microdesc_map, microdesc_t, node,
microdesc_hash_, microdesc_eq_, 0.6,
- tor_reallocarray_, tor_free_)
+ tor_reallocarray_, tor_free_);
/************************* md fetch fail cache *****************************/
diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c
index cc4b8e1c34..2188e47177 100644
--- a/src/feature/nodelist/networkstatus.c
+++ b/src/feature/nodelist/networkstatus.c
@@ -66,7 +66,7 @@
#include "feature/dirclient/dirclient_modes.h"
#include "feature/dirclient/dlstatus.h"
#include "feature/dircommon/directory.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/dirparse/ns_parse.h"
#include "feature/hibernate/hibernate.h"
#include "feature/hs/hs_dos.h"
@@ -2120,7 +2120,7 @@ networkstatus_set_current_consensus(const char *consensus,
* the first thing we need to do is recalculate the voting schedule static
* object so we can use the timings in there needed by some subsystems
* such as hidden service and shared random. */
- voting_schedule_recalculate_timing(options, now);
+ dirauth_sched_recalculate_timing(options, now);
reschedule_dirvote(options);
nodelist_set_consensus(c);
@@ -2765,3 +2765,47 @@ networkstatus_free_all(void)
}
}
}
+
+/** Return the start of the next interval of size <b>interval</b> (in
+ * seconds) after <b>now</b>, plus <b>offset</b>. Midnight always
+ * starts a fresh interval, and if the last interval of a day would be
+ * truncated to less than half its size, it is rolled into the
+ * previous interval. */
+time_t
+voting_sched_get_start_of_interval_after(time_t now, int interval,
+ int offset)
+{
+ struct tm tm;
+ time_t midnight_today=0;
+ time_t midnight_tomorrow;
+ time_t next;
+
+ tor_gmtime_r(&now, &tm);
+ tm.tm_hour = 0;
+ tm.tm_min = 0;
+ tm.tm_sec = 0;
+
+ if (tor_timegm(&tm, &midnight_today) < 0) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG, "Ran into an invalid time when trying to find midnight.");
+ // LCOV_EXCL_STOP
+ }
+ midnight_tomorrow = midnight_today + (24*60*60);
+
+ next = midnight_today + ((now-midnight_today)/interval + 1)*interval;
+
+ /* Intervals never cross midnight. */
+ if (next > midnight_tomorrow)
+ next = midnight_tomorrow;
+
+ /* If the interval would only last half as long as it's supposed to, then
+ * skip over to the next day. */
+ if (next + interval/2 > midnight_tomorrow)
+ next = midnight_tomorrow;
+
+ next += offset;
+ if (next - interval > now)
+ next -= interval;
+
+ return next;
+}
diff --git a/src/feature/nodelist/networkstatus.h b/src/feature/nodelist/networkstatus.h
index 5e8c8a9e57..ce050aeadc 100644
--- a/src/feature/nodelist/networkstatus.h
+++ b/src/feature/nodelist/networkstatus.h
@@ -153,6 +153,9 @@ void vote_routerstatus_free_(vote_routerstatus_t *rs);
void set_routerstatus_from_routerinfo(routerstatus_t *rs,
const node_t *node,
const routerinfo_t *ri);
+time_t voting_sched_get_start_of_interval_after(time_t now,
+ int interval,
+ int offset);
#ifdef NETWORKSTATUS_PRIVATE
#ifdef TOR_UNIT_TESTS
diff --git a/src/feature/nodelist/nodefamily.c b/src/feature/nodelist/nodefamily.c
index 7ae8620749..feaa3730dc 100644
--- a/src/feature/nodelist/nodefamily.c
+++ b/src/feature/nodelist/nodefamily.c
@@ -69,9 +69,9 @@ static HT_HEAD(nodefamily_map, nodefamily_t) the_node_families
= HT_INITIALIZER();
HT_PROTOTYPE(nodefamily_map, nodefamily_t, ht_ent, nodefamily_hash,
- nodefamily_eq)
+ nodefamily_eq);
HT_GENERATE2(nodefamily_map, nodefamily_t, ht_ent, nodefamily_hash,
- node_family_eq, 0.6, tor_reallocarray_, tor_free_)
+ node_family_eq, 0.6, tor_reallocarray_, tor_free_);
/**
* Parse the family declaration in <b>s</b>, returning the canonical
diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c
index b7c7552561..fcf27b1a3a 100644
--- a/src/feature/nodelist/nodelist.c
+++ b/src/feature/nodelist/nodelist.c
@@ -153,9 +153,9 @@ node_id_eq(const node_t *node1, const node_t *node2)
return tor_memeq(node1->identity, node2->identity, DIGEST_LEN);
}
-HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq)
+HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq);
HT_GENERATE2(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq,
- 0.6, tor_reallocarray_, tor_free_)
+ 0.6, tor_reallocarray_, tor_free_);
static inline unsigned int
node_ed_id_hash(const node_t *node)
@@ -170,9 +170,9 @@ node_ed_id_eq(const node_t *node1, const node_t *node2)
}
HT_PROTOTYPE(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash,
- node_ed_id_eq)
+ node_ed_id_eq);
HT_GENERATE2(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash,
- node_ed_id_eq, 0.6, tor_reallocarray_, tor_free_)
+ node_ed_id_eq, 0.6, tor_reallocarray_, tor_free_);
/** The global nodelist. */
static nodelist_t *the_nodelist=NULL;
@@ -1240,8 +1240,8 @@ node_get_rsa_id_digest(const node_t *node)
* If node is NULL, returns an empty smartlist.
*
* The smartlist must be freed using link_specifier_smartlist_free(). */
-smartlist_t *
-node_get_link_specifier_smartlist(const node_t *node, bool direct_conn)
+MOCK_IMPL(smartlist_t *,
+node_get_link_specifier_smartlist,(const node_t *node, bool direct_conn))
{
link_specifier_t *ls;
tor_addr_port_t ap;
diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h
index 87020b81eb..6e854ec879 100644
--- a/src/feature/nodelist/nodelist.h
+++ b/src/feature/nodelist/nodelist.h
@@ -80,8 +80,8 @@ int node_supports_ed25519_hs_intro(const node_t *node);
int node_supports_v3_rendezvous_point(const node_t *node);
int node_supports_establish_intro_dos_extension(const node_t *node);
const uint8_t *node_get_rsa_id_digest(const node_t *node);
-smartlist_t *node_get_link_specifier_smartlist(const node_t *node,
- bool direct_conn);
+MOCK_DECL(smartlist_t *,node_get_link_specifier_smartlist,(const node_t *node,
+ bool direct_conn));
void link_specifier_smartlist_free_(smartlist_t *ls_list);
#define link_specifier_smartlist_free(ls_list) \
FREE_AND_NULL(smartlist_t, link_specifier_smartlist_free_, (ls_list))
diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c
index 42ce6f4c4e..f4e1215a40 100644
--- a/src/feature/nodelist/routerlist.c
+++ b/src/feature/nodelist/routerlist.c
@@ -117,9 +117,9 @@
/* Typed wrappers for different digestmap types; used to avoid type
* confusion. */
-DECLARE_TYPED_DIGESTMAP_FNS(sdmap_, digest_sd_map_t, signed_descriptor_t)
-DECLARE_TYPED_DIGESTMAP_FNS(rimap_, digest_ri_map_t, routerinfo_t)
-DECLARE_TYPED_DIGESTMAP_FNS(eimap_, digest_ei_map_t, extrainfo_t)
+DECLARE_TYPED_DIGESTMAP_FNS(sdmap, digest_sd_map_t, signed_descriptor_t)
+DECLARE_TYPED_DIGESTMAP_FNS(rimap, digest_ri_map_t, routerinfo_t)
+DECLARE_TYPED_DIGESTMAP_FNS(eimap, digest_ei_map_t, extrainfo_t)
#define SDMAP_FOREACH(map, keyvar, valvar) \
DIGESTMAP_FOREACH(sdmap_to_digestmap(map), keyvar, signed_descriptor_t *, \
valvar)
diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c
index da0cbb1df4..5f868a9020 100644
--- a/src/feature/relay/dns.c
+++ b/src/feature/relay/dns.c
@@ -146,9 +146,9 @@ cached_resolve_hash(cached_resolve_t *a)
}
HT_PROTOTYPE(cache_map, cached_resolve_t, node, cached_resolve_hash,
- cached_resolves_eq)
+ cached_resolves_eq);
HT_GENERATE2(cache_map, cached_resolve_t, node, cached_resolve_hash,
- cached_resolves_eq, 0.6, tor_reallocarray_, tor_free_)
+ cached_resolves_eq, 0.6, tor_reallocarray_, tor_free_);
/** Initialize the DNS cache. */
static void
@@ -268,22 +268,6 @@ has_dns_init_failed(void)
return nameserver_config_failed;
}
-/** Helper: Given a TTL from a DNS response, determine what TTL to give the
- * OP that asked us to resolve it, and how long to cache that record
- * ourselves. */
-uint32_t
-dns_clip_ttl(uint32_t ttl)
-{
- /* This logic is a defense against "DefectTor" DNS-based traffic
- * confirmation attacks, as in https://nymity.ch/tor-dns/tor-dns.pdf .
- * We only give two values: a "low" value and a "high" value.
- */
- if (ttl < MIN_DNS_TTL_AT_EXIT)
- return MIN_DNS_TTL_AT_EXIT;
- else
- return MAX_DNS_TTL_AT_EXIT;
-}
-
/** Helper: free storage held by an entry in the DNS cache. */
static void
free_cached_resolve_(cached_resolve_t *r)
@@ -521,7 +505,7 @@ send_resolved_cell,(edge_connection_t *conn, uint8_t answer_type,
uint32_t ttl;
buf[0] = answer_type;
- ttl = dns_clip_ttl(conn->address_ttl);
+ ttl = clip_dns_ttl(conn->address_ttl);
switch (answer_type)
{
@@ -593,7 +577,7 @@ send_resolved_hostname_cell,(edge_connection_t *conn,
size_t namelen = strlen(hostname);
tor_assert(namelen < 256);
- ttl = dns_clip_ttl(conn->address_ttl);
+ ttl = clip_dns_ttl(conn->address_ttl);
buf[0] = RESOLVED_TYPE_HOSTNAME;
buf[1] = (uint8_t)namelen;
@@ -987,25 +971,6 @@ assert_connection_edge_not_dns_pending(edge_connection_t *conn)
#endif /* 1 */
}
-/** Log an error and abort if any connection waiting for a DNS resolve is
- * corrupted. */
-void
-assert_all_pending_dns_resolves_ok(void)
-{
- pending_connection_t *pend;
- cached_resolve_t **resolve;
-
- HT_FOREACH(resolve, cache_map, &cache_root) {
- for (pend = (*resolve)->pending_connections;
- pend;
- pend = pend->next) {
- assert_connection_ok(TO_CONN(pend->conn), 0);
- tor_assert(!SOCKET_OK(pend->conn->base_.s));
- tor_assert(!connection_in_array(TO_CONN(pend->conn)));
- }
- }
-}
-
/** Remove <b>conn</b> from the list of connections waiting for conn-\>address.
*/
void
@@ -1063,7 +1028,7 @@ connection_dns_remove(edge_connection_t *conn)
* the resolve for <b>address</b> itself, and remove any cached results for
* <b>address</b> from the cache.
*/
-MOCK_IMPL(void,
+MOCK_IMPL(STATIC void,
dns_cancel_pending_resolve,(const char *address))
{
pending_connection_t *pend;
@@ -1338,7 +1303,7 @@ make_pending_resolve_cached(cached_resolve_t *resolve)
resolve->ttl_hostname < ttl)
ttl = resolve->ttl_hostname;
- set_expiry(new_resolve, time(NULL) + dns_clip_ttl(ttl));
+ set_expiry(new_resolve, time(NULL) + clip_dns_ttl(ttl));
}
assert_cache_ok();
@@ -2051,12 +2016,12 @@ dns_launch_correctness_checks(void)
/* Wait a while before launching requests for test addresses, so we can
* get the results from checking for wildcarding. */
- if (! launch_event)
+ if (!launch_event)
launch_event = tor_evtimer_new(tor_libevent_get_base(),
launch_test_addresses, NULL);
timeout.tv_sec = 30;
timeout.tv_usec = 0;
- if (evtimer_add(launch_event, &timeout)<0) {
+ if (evtimer_add(launch_event, &timeout) < 0) {
log_warn(LD_BUG, "Couldn't add timer for checking for dns hijacking");
}
}
@@ -2188,7 +2153,7 @@ dns_cache_handle_oom(time_t now, size_t min_remove_bytes)
total_bytes_removed += bytes_removed;
/* Increase time_inc by a reasonable fraction. */
- time_inc += (MAX_DNS_TTL_AT_EXIT / 4);
+ time_inc += (MAX_DNS_TTL / 4);
} while (total_bytes_removed < min_remove_bytes);
return total_bytes_removed;
diff --git a/src/feature/relay/dns.h b/src/feature/relay/dns.h
index 2b1da8d126..120b75bf8d 100644
--- a/src/feature/relay/dns.h
+++ b/src/feature/relay/dns.h
@@ -12,29 +12,14 @@
#ifndef TOR_DNS_H
#define TOR_DNS_H
-/** Lowest value for DNS ttl that a server will give. */
-#define MIN_DNS_TTL_AT_EXIT (5*60)
-/** Highest value for DNS ttl that a server will give. */
-#define MAX_DNS_TTL_AT_EXIT (60*60)
-
-/** How long do we keep DNS cache entries before purging them (regardless of
- * their TTL)? */
-#define MAX_DNS_ENTRY_AGE (3*60*60)
-/** How long do we cache/tell clients to cache DNS records when no TTL is
- * known? */
-#define DEFAULT_DNS_TTL (30*60)
+#ifdef HAVE_MODULE_RELAY
int dns_init(void);
int has_dns_init_failed(void);
-void dns_free_all(void);
-uint32_t dns_clip_ttl(uint32_t ttl);
int dns_reset(void);
void connection_dns_remove(edge_connection_t *conn);
void assert_connection_edge_not_dns_pending(edge_connection_t *conn);
-void assert_all_pending_dns_resolves_ok(void);
-MOCK_DECL(void,dns_cancel_pending_resolve,(const char *question));
int dns_resolve(edge_connection_t *exitconn);
-void dns_launch_correctness_checks(void);
int dns_seems_to_be_broken(void);
int dns_seems_to_be_broken_for_ipv6(void);
void dns_reset_correctness_checks(void);
@@ -42,6 +27,48 @@ size_t dns_cache_total_allocation(void);
void dump_dns_mem_usage(int severity);
size_t dns_cache_handle_oom(time_t now, size_t min_remove_bytes);
+/* These functions are only used within the feature/relay module, and don't
+ * need stubs. */
+void dns_free_all(void);
+void dns_launch_correctness_checks(void);
+
+#else /* !defined(HAVE_MODULE_RELAY) */
+
+#define dns_init() (0)
+#define dns_seems_to_be_broken() (0)
+#define has_dns_init_failed() (0)
+#define dns_cache_total_allocation() (0)
+
+#define dns_reset_correctness_checks() STMT_NIL
+
+#define assert_connection_edge_not_dns_pending(conn) \
+ ((void)(conn))
+#define dump_dns_mem_usage(severity)\
+ ((void)(severity))
+#define dns_cache_handle_oom(now, bytes) \
+ ((void)(now), (void)(bytes), 0)
+
+#define connection_dns_remove(conn) \
+ STMT_BEGIN \
+ (void)(conn); \
+ tor_assert_nonfatal_unreached(); \
+ STMT_END
+
+static inline int
+dns_reset(void)
+{
+ return 0;
+}
+static inline int
+dns_resolve(edge_connection_t *exitconn)
+{
+ (void)exitconn;
+ tor_assert_nonfatal_unreached();
+ return -1;
+}
+
+#endif /* defined(HAVE_MODULE_RELAY) */
+
#ifdef DNS_PRIVATE
#include "feature/relay/dns_structs.h"
@@ -50,6 +77,7 @@ size_t number_of_configured_nameservers(void);
tor_addr_t *configured_nameserver_address(const size_t idx);
#endif
+MOCK_DECL(STATIC void,dns_cancel_pending_resolve,(const char *question));
MOCK_DECL(STATIC int,dns_resolve_impl,(edge_connection_t *exitconn,
int is_resolve,or_circuit_t *oncirc, char **hostname_out,
int *made_connection_pending_out, cached_resolve_t **resolve_out));
@@ -74,4 +102,3 @@ launch_resolve,(cached_resolve_t *resolve));
#endif /* defined(DNS_PRIVATE) */
#endif /* !defined(TOR_DNS_H) */
-
diff --git a/src/feature/relay/ext_orport.c b/src/feature/relay/ext_orport.c
index ce4e043dd7..5568dacf1a 100644
--- a/src/feature/relay/ext_orport.c
+++ b/src/feature/relay/ext_orport.c
@@ -652,6 +652,77 @@ connection_ext_or_start_auth(or_connection_t *or_conn)
return 0;
}
+/** Global map between Extended ORPort identifiers and OR
+ * connections. */
+static digestmap_t *orconn_ext_or_id_map = NULL;
+
+/** Remove the Extended ORPort identifier of <b>conn</b> from the
+ * global identifier list. Also, clear the identifier from the
+ * connection itself. */
+void
+connection_or_remove_from_ext_or_id_map(or_connection_t *conn)
+{
+ or_connection_t *tmp;
+ if (!orconn_ext_or_id_map)
+ return;
+ if (!conn->ext_or_conn_id)
+ return;
+
+ tmp = digestmap_remove(orconn_ext_or_id_map, conn->ext_or_conn_id);
+ if (!tor_digest_is_zero(conn->ext_or_conn_id))
+ tor_assert(tmp == conn);
+
+ memset(conn->ext_or_conn_id, 0, EXT_OR_CONN_ID_LEN);
+}
+
+#ifdef TOR_UNIT_TESTS
+/** Return the connection whose ext_or_id is <b>id</b>. Return NULL if no such
+ * connection is found. */
+or_connection_t *
+connection_or_get_by_ext_or_id(const char *id)
+{
+ if (!orconn_ext_or_id_map)
+ return NULL;
+ return digestmap_get(orconn_ext_or_id_map, id);
+}
+#endif /* defined(TOR_UNIT_TESTS) */
+
+/** Deallocate the global Extended ORPort identifier list */
+void
+connection_or_clear_ext_or_id_map(void)
+{
+ digestmap_free(orconn_ext_or_id_map, NULL);
+ orconn_ext_or_id_map = NULL;
+}
+
+/** Creates an Extended ORPort identifier for <b>conn</b> and deposits
+ * it into the global list of identifiers. */
+void
+connection_or_set_ext_or_identifier(or_connection_t *conn)
+{
+ char random_id[EXT_OR_CONN_ID_LEN];
+ or_connection_t *tmp;
+
+ if (!orconn_ext_or_id_map)
+ orconn_ext_or_id_map = digestmap_new();
+
+ /* Remove any previous identifiers: */
+ if (conn->ext_or_conn_id && !tor_digest_is_zero(conn->ext_or_conn_id))
+ connection_or_remove_from_ext_or_id_map(conn);
+
+ do {
+ crypto_rand(random_id, sizeof(random_id));
+ } while (digestmap_get(orconn_ext_or_id_map, random_id));
+
+ if (!conn->ext_or_conn_id)
+ conn->ext_or_conn_id = tor_malloc_zero(EXT_OR_CONN_ID_LEN);
+
+ memcpy(conn->ext_or_conn_id, random_id, EXT_OR_CONN_ID_LEN);
+
+ tmp = digestmap_set(orconn_ext_or_id_map, random_id, conn);
+ tor_assert(!tmp);
+}
+
/** Free any leftover allocated memory of the ext_orport.c subsystem. */
void
ext_orport_free_all(void)
diff --git a/src/feature/relay/ext_orport.h b/src/feature/relay/ext_orport.h
index dbe89fce18..416c358397 100644
--- a/src/feature/relay/ext_orport.h
+++ b/src/feature/relay/ext_orport.h
@@ -31,26 +31,56 @@
#define EXT_OR_CONN_STATE_FLUSHING 5
#define EXT_OR_CONN_STATE_MAX_ 5
-int connection_ext_or_start_auth(or_connection_t *or_conn);
-
-ext_or_cmd_t *ext_or_cmd_new(uint16_t len);
+#ifdef HAVE_MODULE_RELAY
-#define ext_or_cmd_free(cmd) \
- FREE_AND_NULL(ext_or_cmd_t, ext_or_cmd_free_, (cmd))
+int connection_ext_or_start_auth(or_connection_t *or_conn);
-void ext_or_cmd_free_(ext_or_cmd_t *cmd);
void connection_or_set_ext_or_identifier(or_connection_t *conn);
void connection_or_remove_from_ext_or_id_map(or_connection_t *conn);
void connection_or_clear_ext_or_id_map(void);
-or_connection_t *connection_or_get_by_ext_or_id(const char *id);
-
int connection_ext_or_finished_flushing(or_connection_t *conn);
int connection_ext_or_process_inbuf(or_connection_t *or_conn);
+char *get_ext_or_auth_cookie_file_name(void);
+/* (No stub needed for these: they are only called within feature/relay.) */
int init_ext_or_cookie_authentication(int is_enabled);
-char *get_ext_or_auth_cookie_file_name(void);
void ext_orport_free_all(void);
+#else /* !defined(HAVE_MODULE_RELAY) */
+
+static inline int
+connection_ext_or_start_auth(or_connection_t *conn)
+{
+ (void)conn;
+ tor_assert_nonfatal_unreached();
+ return -1;
+}
+static inline int
+connection_ext_or_finished_flushing(or_connection_t *conn)
+{
+ (void)conn;
+ tor_assert_nonfatal_unreached();
+ return -1;
+}
+static inline int
+connection_ext_or_process_inbuf(or_connection_t *conn)
+{
+ (void)conn;
+ tor_assert_nonfatal_unreached();
+ return -1;
+}
+#define connection_or_set_ext_or_identifier(conn) \
+ ((void)(conn))
+#define connection_or_remove_from_ext_or_id_map(conn) \
+ ((void)(conn))
+#define connection_or_clear_ext_or_id_map() \
+ STMT_NIL
+
+#define get_ext_or_auth_cookie_file_name() \
+ (NULL)
+
+#endif /* defined(HAVE_MODULE_RELAY) */
+
#ifdef EXT_ORPORT_PRIVATE
STATIC int connection_write_ext_or_command(connection_t *conn,
uint16_t command,
@@ -60,9 +90,11 @@ STATIC int handle_client_auth_nonce(const char *client_nonce,
size_t client_nonce_len,
char **client_hash_out,
char **reply_out, size_t *reply_len_out);
+
#ifdef TOR_UNIT_TESTS
extern uint8_t *ext_or_auth_cookie;
extern int ext_or_auth_cookie_is_set;
+or_connection_t *connection_or_get_by_ext_or_id(const char *id);
#endif
#endif /* defined(EXT_ORPORT_PRIVATE) */
diff --git a/src/feature/relay/include.am b/src/feature/relay/include.am
index a4c025ae12..813ddb8fb1 100644
--- a/src/feature/relay/include.am
+++ b/src/feature/relay/include.am
@@ -1,21 +1,22 @@
# Legacy shared relay code: migrate to the relay module over time
LIBTOR_APP_A_SOURCES += \
- src/feature/relay/dns.c \
- src/feature/relay/ext_orport.c \
src/feature/relay/onion_queue.c \
- src/feature/relay/router.c \
- src/feature/relay/routerkeys.c \
- src/feature/relay/selftest.c
+ src/feature/relay/router.c
# The Relay module.
# ADD_C_FILE: INSERT SOURCES HERE.
MODULE_RELAY_SOURCES = \
+ src/feature/relay/dns.c \
+ src/feature/relay/ext_orport.c \
src/feature/relay/routermode.c \
src/feature/relay/relay_config.c \
+ src/feature/relay/relay_handshake.c \
src/feature/relay/relay_periodic.c \
src/feature/relay/relay_sys.c \
+ src/feature/relay/routerkeys.c \
+ src/feature/relay/selftest.c \
src/feature/relay/transport_config.c
# ADD_C_FILE: INSERT HEADERS HERE.
@@ -25,6 +26,7 @@ noinst_HEADERS += \
src/feature/relay/ext_orport.h \
src/feature/relay/onion_queue.h \
src/feature/relay/relay_config.h \
+ src/feature/relay/relay_handshake.h \
src/feature/relay/relay_periodic.h \
src/feature/relay/relay_sys.h \
src/feature/relay/router.h \
diff --git a/src/feature/relay/onion_queue.c b/src/feature/relay/onion_queue.c
index ce2d41b7e1..3cbaa65d28 100644
--- a/src/feature/relay/onion_queue.c
+++ b/src/feature/relay/onion_queue.c
@@ -49,10 +49,12 @@ typedef struct onion_queue_t {
/** 5 seconds on the onion queue til we just send back a destroy */
#define ONIONQUEUE_WAIT_CUTOFF 5
+TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t);
+typedef struct onion_queue_head_t onion_queue_head_t;
+
/** Array of queues of circuits waiting for CPU workers. An element is NULL
* if that queue is empty.*/
-static TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t)
- ol_list[MAX_ONION_HANDSHAKE_TYPE+1] =
+static onion_queue_head_t ol_list[MAX_ONION_HANDSHAKE_TYPE+1] =
{ TOR_TAILQ_HEAD_INITIALIZER(ol_list[0]), /* tap */
TOR_TAILQ_HEAD_INITIALIZER(ol_list[1]), /* fast */
TOR_TAILQ_HEAD_INITIALIZER(ol_list[2]), /* ntor */
diff --git a/src/feature/relay/relay_handshake.c b/src/feature/relay/relay_handshake.c
new file mode 100644
index 0000000000..030dc94956
--- /dev/null
+++ b/src/feature/relay/relay_handshake.c
@@ -0,0 +1,565 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2020, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * @file relay_handshake.c
+ * @brief Functions to implement the relay-only parts of our
+ * connection handshake.
+ *
+ * Some parts of our TLS link handshake are only done by relays (including
+ * bridges). Specifically, only relays need to send CERTS cells; only
+ * relays need to send or receive AUTHCHALLENGE cells, and only relays need to
+ * send or receive AUTHENTICATE cells.
+ **/
+
+#include "orconfig.h"
+#include "core/or/or.h"
+#include "feature/relay/relay_handshake.h"
+
+#include "app/config/config.h"
+#include "core/or/connection_or.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "trunnel/link_handshake.h"
+#include "feature/relay/routerkeys.h"
+#include "feature/nodelist/torcert.h"
+
+#include "core/or/or_connection_st.h"
+#include "core/or/or_handshake_certs_st.h"
+#include "core/or/or_handshake_state_st.h"
+#include "core/or/var_cell_st.h"
+
+#include "lib/tls/tortls.h"
+#include "lib/tls/x509.h"
+
+/** Helper used to add an encoded certs to a cert cell */
+static void
+add_certs_cell_cert_helper(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const uint8_t *cert_encoded,
+ size_t cert_len)
+{
+ tor_assert(cert_len <= UINT16_MAX);
+ certs_cell_cert_t *ccc = certs_cell_cert_new();
+ ccc->cert_type = cert_type;
+ ccc->cert_len = cert_len;
+ certs_cell_cert_setlen_body(ccc, cert_len);
+ memcpy(certs_cell_cert_getarray_body(ccc), cert_encoded, cert_len);
+
+ certs_cell_add_certs(certs_cell, ccc);
+}
+
+/** Add an encoded X509 cert (stored as <b>cert_len</b> bytes at
+ * <b>cert_encoded</b>) to the trunnel certs_cell_t object that we are
+ * building in <b>certs_cell</b>. Set its type field to <b>cert_type</b>.
+ * (If <b>cert</b> is NULL, take no action.) */
+static void
+add_x509_cert(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const tor_x509_cert_t *cert)
+{
+ if (NULL == cert)
+ return;
+
+ const uint8_t *cert_encoded = NULL;
+ size_t cert_len;
+ tor_x509_cert_get_der(cert, &cert_encoded, &cert_len);
+
+ add_certs_cell_cert_helper(certs_cell, cert_type, cert_encoded, cert_len);
+}
+
+/** Add an Ed25519 cert from <b>cert</b> to the trunnel certs_cell_t object
+ * that we are building in <b>certs_cell</b>. Set its type field to
+ * <b>cert_type</b>. (If <b>cert</b> is NULL, take no action.) */
+static void
+add_ed25519_cert(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const tor_cert_t *cert)
+{
+ if (NULL == cert)
+ return;
+
+ add_certs_cell_cert_helper(certs_cell, cert_type,
+ cert->encoded, cert->encoded_len);
+}
+
+#ifdef TOR_UNIT_TESTS
+int certs_cell_ed25519_disabled_for_testing = 0;
+#else
+#define certs_cell_ed25519_disabled_for_testing 0
+#endif
+
+/** Send a CERTS cell on the connection <b>conn</b>. Return 0 on success, -1
+ * on failure. */
+int
+connection_or_send_certs_cell(or_connection_t *conn)
+{
+ const tor_x509_cert_t *global_link_cert = NULL, *id_cert = NULL;
+ tor_x509_cert_t *own_link_cert = NULL;
+ var_cell_t *cell;
+
+ certs_cell_t *certs_cell = NULL;
+
+ tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3);
+
+ if (! conn->handshake_state)
+ return -1;
+
+ const int conn_in_server_mode = ! conn->handshake_state->started_here;
+
+ /* Get the encoded values of the X509 certificates */
+ if (tor_tls_get_my_certs(conn_in_server_mode,
+ &global_link_cert, &id_cert) < 0)
+ return -1;
+
+ if (conn_in_server_mode) {
+ own_link_cert = tor_tls_get_own_cert(conn->tls);
+ }
+ tor_assert(id_cert);
+
+ certs_cell = certs_cell_new();
+
+ /* Start adding certs. First the link cert or auth1024 cert. */
+ if (conn_in_server_mode) {
+ tor_assert_nonfatal(own_link_cert);
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_TLS_LINK, own_link_cert);
+ } else {
+ tor_assert(global_link_cert);
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_AUTH_1024, global_link_cert);
+ }
+
+ /* Next the RSA->RSA ID cert */
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_ID_1024, id_cert);
+
+ /* Next the Ed25519 certs */
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_ID_SIGN,
+ get_master_signing_key_cert());
+ if (conn_in_server_mode) {
+ tor_assert_nonfatal(conn->handshake_state->own_link_cert ||
+ certs_cell_ed25519_disabled_for_testing);
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_SIGN_LINK,
+ conn->handshake_state->own_link_cert);
+ } else {
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_SIGN_AUTH,
+ get_current_auth_key_cert());
+ }
+
+ /* And finally the crosscert. */
+ {
+ const uint8_t *crosscert=NULL;
+ size_t crosscert_len;
+ get_master_rsa_crosscert(&crosscert, &crosscert_len);
+ if (crosscert) {
+ add_certs_cell_cert_helper(certs_cell,
+ CERTTYPE_RSA1024_ID_EDID,
+ crosscert, crosscert_len);
+ }
+ }
+
+ /* We've added all the certs; make the cell. */
+ certs_cell->n_certs = certs_cell_getlen_certs(certs_cell);
+
+ ssize_t alloc_len = certs_cell_encoded_len(certs_cell);
+ tor_assert(alloc_len >= 0 && alloc_len <= UINT16_MAX);
+ cell = var_cell_new(alloc_len);
+ cell->command = CELL_CERTS;
+ ssize_t enc_len = certs_cell_encode(cell->payload, alloc_len, certs_cell);
+ tor_assert(enc_len > 0 && enc_len <= alloc_len);
+ cell->payload_len = enc_len;
+
+ connection_or_write_var_cell_to_buf(cell, conn);
+ var_cell_free(cell);
+ certs_cell_free(certs_cell);
+ tor_x509_cert_free(own_link_cert);
+
+ return 0;
+}
+
+#ifdef TOR_UNIT_TESTS
+int testing__connection_or_pretend_TLSSECRET_is_supported = 0;
+#else
+#define testing__connection_or_pretend_TLSSECRET_is_supported 0
+#endif
+
+/** Return true iff <b>challenge_type</b> is an AUTHCHALLENGE type that
+ * we can send and receive. */
+int
+authchallenge_type_is_supported(uint16_t challenge_type)
+{
+ switch (challenge_type) {
+ case AUTHTYPE_RSA_SHA256_TLSSECRET:
+#ifdef HAVE_WORKING_TOR_TLS_GET_TLSSECRETS
+ return 1;
+#else
+ return testing__connection_or_pretend_TLSSECRET_is_supported;
+#endif
+ case AUTHTYPE_ED25519_SHA256_RFC5705:
+ return 1;
+ case AUTHTYPE_RSA_SHA256_RFC5705:
+ default:
+ return 0;
+ }
+}
+
+/** Return true iff <b>challenge_type_a</b> is one that we would rather
+ * use than <b>challenge_type_b</b>. */
+int
+authchallenge_type_is_better(uint16_t challenge_type_a,
+ uint16_t challenge_type_b)
+{
+ /* Any supported type is better than an unsupported one;
+ * all unsupported types are equally bad. */
+ if (!authchallenge_type_is_supported(challenge_type_a))
+ return 0;
+ if (!authchallenge_type_is_supported(challenge_type_b))
+ return 1;
+ /* It happens that types are superior in numerically ascending order.
+ * If that ever changes, this must change too. */
+ return (challenge_type_a > challenge_type_b);
+}
+
+/** Send an AUTH_CHALLENGE cell on the connection <b>conn</b>. Return 0
+ * on success, -1 on failure. */
+int
+connection_or_send_auth_challenge_cell(or_connection_t *conn)
+{
+ var_cell_t *cell = NULL;
+ int r = -1;
+ tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3);
+
+ if (! conn->handshake_state)
+ return -1;
+
+ auth_challenge_cell_t *ac = auth_challenge_cell_new();
+
+ tor_assert(sizeof(ac->challenge) == 32);
+ crypto_rand((char*)ac->challenge, sizeof(ac->challenge));
+
+ if (authchallenge_type_is_supported(AUTHTYPE_RSA_SHA256_TLSSECRET))
+ auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_TLSSECRET);
+ /* Disabled, because everything that supports this method also supports
+ * the much-superior ED25519_SHA256_RFC5705 */
+ /* auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_RFC5705); */
+ if (authchallenge_type_is_supported(AUTHTYPE_ED25519_SHA256_RFC5705))
+ auth_challenge_cell_add_methods(ac, AUTHTYPE_ED25519_SHA256_RFC5705);
+ auth_challenge_cell_set_n_methods(ac,
+ auth_challenge_cell_getlen_methods(ac));
+
+ cell = var_cell_new(auth_challenge_cell_encoded_len(ac));
+ ssize_t len = auth_challenge_cell_encode(cell->payload, cell->payload_len,
+ ac);
+ if (len != cell->payload_len) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Encoded auth challenge cell length not as expected");
+ goto done;
+ /* LCOV_EXCL_STOP */
+ }
+ cell->command = CELL_AUTH_CHALLENGE;
+
+ connection_or_write_var_cell_to_buf(cell, conn);
+ r = 0;
+
+ done:
+ var_cell_free(cell);
+ auth_challenge_cell_free(ac);
+
+ return r;
+}
+
+/** Compute the main body of an AUTHENTICATE cell that a client can use
+ * to authenticate itself on a v3 handshake for <b>conn</b>. Return it
+ * in a var_cell_t.
+ *
+ * If <b>server</b> is true, only calculate the first
+ * V3_AUTH_FIXED_PART_LEN bytes -- the part of the authenticator that's
+ * determined by the rest of the handshake, and which match the provided value
+ * exactly.
+ *
+ * If <b>server</b> is false and <b>signing_key</b> is NULL, calculate the
+ * first V3_AUTH_BODY_LEN bytes of the authenticator (that is, everything
+ * that should be signed), but don't actually sign it.
+ *
+ * If <b>server</b> is false and <b>signing_key</b> is provided, calculate the
+ * entire authenticator, signed with <b>signing_key</b>.
+ *
+ * Return the length of the cell body on success, and -1 on failure.
+ */
+var_cell_t *
+connection_or_compute_authenticate_cell_body(or_connection_t *conn,
+ const int authtype,
+ crypto_pk_t *signing_key,
+ const ed25519_keypair_t *ed_signing_key,
+ int server)
+{
+ auth1_t *auth = NULL;
+ auth_ctx_t *ctx = auth_ctx_new();
+ var_cell_t *result = NULL;
+ int old_tlssecrets_algorithm = 0;
+ const char *authtype_str = NULL;
+
+ int is_ed = 0;
+
+ /* assert state is reasonable XXXX */
+ switch (authtype) {
+ case AUTHTYPE_RSA_SHA256_TLSSECRET:
+ authtype_str = "AUTH0001";
+ old_tlssecrets_algorithm = 1;
+ break;
+ case AUTHTYPE_RSA_SHA256_RFC5705:
+ authtype_str = "AUTH0002";
+ break;
+ case AUTHTYPE_ED25519_SHA256_RFC5705:
+ authtype_str = "AUTH0003";
+ is_ed = 1;
+ break;
+ default:
+ tor_assert(0);
+ break;
+ }
+
+ auth = auth1_new();
+ ctx->is_ed = is_ed;
+
+ /* Type: 8 bytes. */
+ memcpy(auth1_getarray_type(auth), authtype_str, 8);
+
+ {
+ const tor_x509_cert_t *id_cert=NULL;
+ const common_digests_t *my_digests, *their_digests;
+ const uint8_t *my_id, *their_id, *client_id, *server_id;
+ if (tor_tls_get_my_certs(server, NULL, &id_cert))
+ goto err;
+ my_digests = tor_x509_cert_get_id_digests(id_cert);
+ their_digests =
+ tor_x509_cert_get_id_digests(conn->handshake_state->certs->id_cert);
+ tor_assert(my_digests);
+ tor_assert(their_digests);
+ my_id = (uint8_t*)my_digests->d[DIGEST_SHA256];
+ their_id = (uint8_t*)their_digests->d[DIGEST_SHA256];
+
+ client_id = server ? their_id : my_id;
+ server_id = server ? my_id : their_id;
+
+ /* Client ID digest: 32 octets. */
+ memcpy(auth->cid, client_id, 32);
+
+ /* Server ID digest: 32 octets. */
+ memcpy(auth->sid, server_id, 32);
+ }
+
+ if (is_ed) {
+ const ed25519_public_key_t *my_ed_id, *their_ed_id;
+ if (!conn->handshake_state->certs->ed_id_sign) {
+ log_warn(LD_OR, "Ed authenticate without Ed ID cert from peer.");
+ goto err;
+ }
+ my_ed_id = get_master_identity_key();
+ their_ed_id = &conn->handshake_state->certs->ed_id_sign->signing_key;
+
+ const uint8_t *cid_ed = (server ? their_ed_id : my_ed_id)->pubkey;
+ const uint8_t *sid_ed = (server ? my_ed_id : their_ed_id)->pubkey;
+
+ memcpy(auth->u1_cid_ed, cid_ed, ED25519_PUBKEY_LEN);
+ memcpy(auth->u1_sid_ed, sid_ed, ED25519_PUBKEY_LEN);
+ }
+
+ {
+ crypto_digest_t *server_d, *client_d;
+ if (server) {
+ server_d = conn->handshake_state->digest_sent;
+ client_d = conn->handshake_state->digest_received;
+ } else {
+ client_d = conn->handshake_state->digest_sent;
+ server_d = conn->handshake_state->digest_received;
+ }
+
+ /* Server log digest : 32 octets */
+ crypto_digest_get_digest(server_d, (char*)auth->slog, 32);
+
+ /* Client log digest : 32 octets */
+ crypto_digest_get_digest(client_d, (char*)auth->clog, 32);
+ }
+
+ {
+ /* Digest of cert used on TLS link : 32 octets. */
+ tor_x509_cert_t *cert = NULL;
+ if (server) {
+ cert = tor_tls_get_own_cert(conn->tls);
+ } else {
+ cert = tor_tls_get_peer_cert(conn->tls);
+ }
+ if (!cert) {
+ log_warn(LD_OR, "Unable to find cert when making %s data.",
+ authtype_str);
+ goto err;
+ }
+
+ memcpy(auth->scert,
+ tor_x509_cert_get_cert_digests(cert)->d[DIGEST_SHA256], 32);
+
+ tor_x509_cert_free(cert);
+ }
+
+ /* HMAC of clientrandom and serverrandom using master key : 32 octets */
+ if (old_tlssecrets_algorithm) {
+ if (tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets) < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_OR, "Somebody asked us for an older TLS "
+ "authentication method (AUTHTYPE_RSA_SHA256_TLSSECRET) "
+ "which we don't support.");
+ }
+ } else {
+ char label[128];
+ tor_snprintf(label, sizeof(label),
+ "EXPORTER FOR TOR TLS CLIENT BINDING %s", authtype_str);
+ int r = tor_tls_export_key_material(conn->tls, auth->tlssecrets,
+ auth->cid, sizeof(auth->cid),
+ label);
+ if (r < 0) {
+ if (r != -2)
+ log_warn(LD_BUG, "TLS key export failed for unknown reason.");
+ // If r == -2, this was openssl bug 7712.
+ goto err;
+ }
+ }
+
+ /* 8 octets were reserved for the current time, but we're trying to get out
+ * of the habit of sending time around willynilly. Fortunately, nothing
+ * checks it. That's followed by 16 bytes of nonce. */
+ crypto_rand((char*)auth->rand, 24);
+
+ ssize_t maxlen = auth1_encoded_len(auth, ctx);
+ if (ed_signing_key && is_ed) {
+ maxlen += ED25519_SIG_LEN;
+ } else if (signing_key && !is_ed) {
+ maxlen += crypto_pk_keysize(signing_key);
+ }
+
+ const int AUTH_CELL_HEADER_LEN = 4; /* 2 bytes of type, 2 bytes of length */
+ result = var_cell_new(AUTH_CELL_HEADER_LEN + maxlen);
+ uint8_t *const out = result->payload + AUTH_CELL_HEADER_LEN;
+ const size_t outlen = maxlen;
+ ssize_t len;
+
+ result->command = CELL_AUTHENTICATE;
+ set_uint16(result->payload, htons(authtype));
+
+ if ((len = auth1_encode(out, outlen, auth, ctx)) < 0) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to encode signed part of AUTH1 data.");
+ goto err;
+ /* LCOV_EXCL_STOP */
+ }
+
+ if (server) {
+ auth1_t *tmp = NULL;
+ ssize_t len2 = auth1_parse(&tmp, out, len, ctx);
+ if (!tmp) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to parse signed part of AUTH1 data that "
+ "we just encoded");
+ goto err;
+ /* LCOV_EXCL_STOP */
+ }
+ result->payload_len = (tmp->end_of_signed - result->payload);
+
+ auth1_free(tmp);
+ if (len2 != len) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Mismatched length when re-parsing AUTH1 data.");
+ goto err;
+ /* LCOV_EXCL_STOP */
+ }
+ goto done;
+ }
+
+ if (ed_signing_key && is_ed) {
+ ed25519_signature_t sig;
+ if (ed25519_sign(&sig, out, len, ed_signing_key) < 0) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to sign ed25519 authentication data");
+ goto err;
+ /* LCOV_EXCL_STOP */
+ }
+ auth1_setlen_sig(auth, ED25519_SIG_LEN);
+ memcpy(auth1_getarray_sig(auth), sig.sig, ED25519_SIG_LEN);
+
+ } else if (signing_key && !is_ed) {
+ auth1_setlen_sig(auth, crypto_pk_keysize(signing_key));
+
+ char d[32];
+ crypto_digest256(d, (char*)out, len, DIGEST_SHA256);
+ int siglen = crypto_pk_private_sign(signing_key,
+ (char*)auth1_getarray_sig(auth),
+ auth1_getlen_sig(auth),
+ d, 32);
+ if (siglen < 0) {
+ log_warn(LD_OR, "Unable to sign AUTH1 data.");
+ goto err;
+ }
+
+ auth1_setlen_sig(auth, siglen);
+ }
+
+ len = auth1_encode(out, outlen, auth, ctx);
+ if (len < 0) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to encode signed AUTH1 data.");
+ goto err;
+ /* LCOV_EXCL_STOP */
+ }
+ tor_assert(len + AUTH_CELL_HEADER_LEN <= result->payload_len);
+ result->payload_len = len + AUTH_CELL_HEADER_LEN;
+ set_uint16(result->payload+2, htons(len));
+
+ goto done;
+
+ err:
+ var_cell_free(result);
+ result = NULL;
+ done:
+ auth1_free(auth);
+ auth_ctx_free(ctx);
+ return result;
+}
+
+/** Send an AUTHENTICATE cell on the connection <b>conn</b>. Return 0 on
+ * success, -1 on failure */
+MOCK_IMPL(int,
+connection_or_send_authenticate_cell,(or_connection_t *conn, int authtype))
+{
+ var_cell_t *cell;
+ crypto_pk_t *pk = tor_tls_get_my_client_auth_key();
+ /* XXXX make sure we're actually supposed to send this! */
+
+ if (!pk) {
+ log_warn(LD_BUG, "Can't compute authenticate cell: no client auth key");
+ return -1;
+ }
+ if (! authchallenge_type_is_supported(authtype)) {
+ log_warn(LD_BUG, "Tried to send authenticate cell with unknown "
+ "authentication type %d", authtype);
+ return -1;
+ }
+
+ cell = connection_or_compute_authenticate_cell_body(conn,
+ authtype,
+ pk,
+ get_current_auth_keypair(),
+ 0 /* not server */);
+ if (! cell) {
+ log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unable to compute authenticate cell!");
+ return -1;
+ }
+ connection_or_write_var_cell_to_buf(cell, conn);
+ var_cell_free(cell);
+
+ return 0;
+}
diff --git a/src/feature/relay/relay_handshake.h b/src/feature/relay/relay_handshake.h
new file mode 100644
index 0000000000..99a658cbcc
--- /dev/null
+++ b/src/feature/relay/relay_handshake.h
@@ -0,0 +1,90 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2020, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * @file relay_handshake.h
+ * @brief Header for feature/relay/relay_handshake.c
+ **/
+
+#ifndef TOR_CORE_OR_RELAY_HANDSHAKE_H
+#define TOR_CORE_OR_RELAY_HANDSHAKE_H
+
+#ifdef HAVE_MODULE_RELAY
+struct ed25519_keypair_t;
+
+int connection_or_send_certs_cell(or_connection_t *conn);
+int connection_or_send_auth_challenge_cell(or_connection_t *conn);
+
+var_cell_t *connection_or_compute_authenticate_cell_body(
+ or_connection_t *conn,
+ const int authtype,
+ crypto_pk_t *signing_key,
+ const struct ed25519_keypair_t *ed_signing_key,
+ int server);
+
+int authchallenge_type_is_supported(uint16_t challenge_type);
+int authchallenge_type_is_better(uint16_t challenge_type_a,
+ uint16_t challenge_type_b);
+
+MOCK_DECL(int,connection_or_send_authenticate_cell,
+ (or_connection_t *conn, int type));
+
+#ifdef TOR_UNIT_TESTS
+extern int certs_cell_ed25519_disabled_for_testing;
+#endif
+#else /* !defined(HAVE_MODULE_RELAY) */
+
+static inline int
+connection_or_send_certs_cell(or_connection_t *conn)
+{
+ (void)conn;
+ tor_assert_nonfatal_unreached();
+ return -1;
+}
+static inline int
+connection_or_send_auth_challenge_cell(or_connection_t *conn)
+{
+ (void)conn;
+ tor_assert_nonfatal_unreached();
+ return -1;
+}
+
+static inline var_cell_t *
+connection_or_compute_authenticate_cell_body(
+ or_connection_t *conn,
+ const int authtype,
+ crypto_pk_t *signing_key,
+ const struct ed25519_keypair_t *ed_signing_key,
+ int server)
+{
+ (void)conn;
+ (void)authtype;
+ (void)signing_key;
+ (void)ed_signing_key;
+ (void)server;
+ tor_assert_nonfatal_unreached();
+ return NULL;
+}
+
+#define authchallenge_type_is_supported(t) (0)
+#define authchallenge_type_is_better(a, b) (0)
+
+static inline int
+connection_or_send_authenticate_cell(or_connection_t *conn, int type)
+{
+ (void)conn;
+ (void)type;
+ tor_assert_nonfatal_unreached();
+ return -1;
+}
+
+#ifdef TOR_UNIT_TESTS
+extern int certs_cell_ed25519_disabled_for_testing;
+#endif
+
+#endif /* defined(HAVE_MODULE_RELAY) */
+
+#endif /* !defined(TOR_CORE_OR_RELAY_HANDSHAKE_H) */
diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h
index 782609d8ab..2e07df2e88 100644
--- a/src/feature/relay/router.h
+++ b/src/feature/relay/router.h
@@ -117,7 +117,6 @@ const char *routerinfo_err_to_string(int err);
int routerinfo_err_is_transient(int err);
void router_reset_warnings(void);
-void router_reset_reachability(void);
void router_free_all(void);
#ifdef ROUTER_PRIVATE
diff --git a/src/feature/relay/routerkeys.h b/src/feature/relay/routerkeys.h
index c2475f195f..2b5f03a2a3 100644
--- a/src/feature/relay/routerkeys.h
+++ b/src/feature/relay/routerkeys.h
@@ -11,6 +11,8 @@
#include "lib/crypt_ops/crypto_ed25519.h"
+#ifdef HAVE_MODULE_RELAY
+
const ed25519_public_key_t *get_master_identity_key(void);
MOCK_DECL(const ed25519_keypair_t *, get_master_signing_keypair,(void));
MOCK_DECL(const struct tor_cert_st *, get_master_signing_key_cert,(void));
@@ -24,6 +26,7 @@ void get_master_rsa_crosscert(const uint8_t **cert_out,
int router_ed25519_id_is_me(const ed25519_public_key_t *id);
+/* These are only used by router.c */
struct tor_cert_st *make_ntor_onion_key_crosscert(
const curve25519_keypair_t *onion_key,
const ed25519_public_key_t *master_id_key,
@@ -42,6 +45,85 @@ int generate_ed_link_cert(const or_options_t *options, time_t now, int force);
void routerkeys_free_all(void);
+#else /* !defined(HAVE_MODULE_RELAY) */
+
+#define router_ed25519_id_is_me(id) \
+ ((void)(id), 0)
+
+static inline void *
+relay_key_is_unavailable_(void)
+{
+ tor_assert_nonfatal_unreached();
+ return NULL;
+}
+#define relay_key_is_unavailable(type) \
+ ((type)(relay_key_is_unavailable_()))
+
+// Many of these can be removed once relay_handshake.c is relay-only.
+#define get_current_auth_keypair() \
+ relay_key_is_unavailable(const ed25519_keypair_t *)
+#define get_master_signing_keypair() \
+ relay_key_is_unavailable(const ed25519_keypair_t *)
+#define get_current_link_cert_cert() \
+ relay_key_is_unavailable(const struct tor_cert_st *)
+#define get_current_auth_key_cert() \
+ relay_key_is_unavailable(const struct tor_cert_st *)
+#define get_master_signing_key_cert() \
+ relay_key_is_unavailable(const struct tor_cert_st *)
+#define get_master_rsa_crosscert(cert_out, size_out) \
+ STMT_BEGIN \
+ tor_assert_nonfatal_unreached(); \
+ *(cert_out) = NULL; \
+ *(size_out) = 0; \
+ STMT_END
+#define get_master_identity_key() \
+ relay_key_is_unavailable(const ed25519_public_key_t *)
+
+#define generate_ed_link_cert(options, now, force) \
+ ((void)(options), (void)(now), (void)(force), 0)
+#define should_make_new_ed_keys(options, now) \
+ ((void)(options), (void)(now), 0)
+
+// These can get removed once router.c becomes relay-only.
+static inline struct tor_cert_st *
+make_ntor_onion_key_crosscert(const curve25519_keypair_t *onion_key,
+ const ed25519_public_key_t *master_id_key,
+ time_t now, time_t lifetime,
+ int *sign_out)
+{
+ (void)onion_key;
+ (void)master_id_key;
+ (void)now;
+ (void)lifetime;
+ (void)sign_out;
+ tor_assert_nonfatal_unreached();
+ return NULL;
+}
+static inline uint8_t *
+make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
+ const ed25519_public_key_t *master_id_key,
+ const crypto_pk_t *rsa_id_key,
+ int *len_out)
+{
+ (void)onion_key;
+ (void)master_id_key;
+ (void)rsa_id_key;
+ (void)len_out;
+ tor_assert_nonfatal_unreached();
+ return NULL;
+}
+
+/* This calls is used outside of relay mode, but only to implement
+ * CMD_KEY_EXPIRATION */
+#define log_cert_expiration() \
+ (puts("Not available: Tor has been compiled without relay support"), 0)
+/* This calls is used outside of relay mode, but only to implement
+ * CMD_KEYGEN. */
+#define load_ed_keys(x,y) \
+ (puts("Not available: Tor has been compiled without relay support"), 0)
+
+#endif /* defined(HAVE_MODULE_RELAY) */
+
#ifdef TOR_UNIT_TESTS
const ed25519_keypair_t *get_master_identity_keypair(void);
void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key);
diff --git a/src/feature/relay/selftest.h b/src/feature/relay/selftest.h
index 94f305f203..f3dd698bb7 100644
--- a/src/feature/relay/selftest.h
+++ b/src/feature/relay/selftest.h
@@ -12,6 +12,7 @@
#ifndef TOR_SELFTEST_H
#define TOR_SELFTEST_H
+#ifdef HAVE_MODULE_RELAY
struct or_options_t;
int check_whether_orport_reachable(const struct or_options_t *options);
int check_whether_dirport_reachable(const struct or_options_t *options);
@@ -20,5 +21,37 @@ void router_do_reachability_checks(int test_or, int test_dir);
void router_orport_found_reachable(void);
void router_dirport_found_reachable(void);
void router_perform_bandwidth_test(int num_circs, time_t now);
+void router_reset_reachability(void);
+
+#else /* !defined(HAVE_MODULE_RELAY) */
+
+#define check_whether_orport_reachable(opts) \
+ ((void)(opts), 0)
+#define check_whether_dirport_reachable(opts) \
+ ((void)(opts), 0)
+
+#define router_orport_found_reachable() \
+ STMT_NIL
+#define router_dirport_found_reachable() \
+ STMT_NIL
+#define router_reset_reachability() \
+ STMT_NIL
+
+static inline void
+router_do_reachability_checks(int test_or, int test_dir)
+{
+ (void)test_or;
+ (void)test_dir;
+ tor_assert_nonfatal_unreached();
+}
+static inline void
+router_perform_bandwidth_test(int num_circs, time_t now)
+{
+ (void)num_circs;
+ (void)now;
+ tor_assert_nonfatal_unreached();
+}
+
+#endif /* defined(HAVE_MODULE_RELAY) */
#endif /* !defined(TOR_SELFTEST_H) */
diff --git a/src/feature/stats/geoip_stats.c b/src/feature/stats/geoip_stats.c
index 3228b18973..f9a2f19d2e 100644
--- a/src/feature/stats/geoip_stats.c
+++ b/src/feature/stats/geoip_stats.c
@@ -146,9 +146,9 @@ clientmap_entries_eq(const clientmap_entry_t *a, const clientmap_entry_t *b)
}
HT_PROTOTYPE(clientmap, clientmap_entry_t, node, clientmap_entry_hash,
- clientmap_entries_eq)
+ clientmap_entries_eq);
HT_GENERATE2(clientmap, clientmap_entry_t, node, clientmap_entry_hash,
- clientmap_entries_eq, 0.6, tor_reallocarray_, tor_free_)
+ clientmap_entries_eq, 0.6, tor_reallocarray_, tor_free_);
#define clientmap_entry_free(ent) \
FREE_AND_NULL(clientmap_entry_t, clientmap_entry_free_, ent)
@@ -484,9 +484,9 @@ dirreq_map_ent_hash(const dirreq_map_entry_t *entry)
}
HT_PROTOTYPE(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash,
- dirreq_map_ent_eq)
+ dirreq_map_ent_eq);
HT_GENERATE2(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash,
- dirreq_map_ent_eq, 0.6, tor_reallocarray_, tor_free_)
+ dirreq_map_ent_eq, 0.6, tor_reallocarray_, tor_free_);
/** Helper: Put <b>entry</b> into map of directory requests using
* <b>type</b> and <b>dirreq_id</b> as key parts. If there is
diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c
index d229c755b4..71e2e00086 100644
--- a/src/feature/stats/rephist.c
+++ b/src/feature/stats/rephist.c
@@ -2285,9 +2285,9 @@ bidi_map_ent_hash(const bidi_map_entry_t *entry)
}
HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
- bidi_map_ent_eq)
+ bidi_map_ent_eq);
HT_GENERATE2(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
- bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_)
+ bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_);
/* DOCDOC bidi_map_free */
static void