Merge branch 'tor-gitlab/mr/182' into master

This commit is contained in:
David Goulet
2020-11-17 10:36:05 -05:00
14 changed files with 516 additions and 122 deletions
+4
View File
@@ -0,0 +1,4 @@
o Major features (statistics):
- Relays will now also publish statistics about the number of v3 onion
services and volume of v3 onion service traffic, in the same manner they
already do for v2 onions. Closes ticket 23126.
+2
View File
@@ -1080,6 +1080,7 @@ sandbox_init_filter(void)
OPEN_DATADIR2_SUFFIX("stats", "buffer-stats", ".tmp");
OPEN_DATADIR2_SUFFIX("stats", "conn-stats", ".tmp");
OPEN_DATADIR2_SUFFIX("stats", "hidserv-stats", ".tmp");
OPEN_DATADIR2_SUFFIX("stats", "hidserv-v3-stats", ".tmp");
OPEN_DATADIR("approved-routers");
OPEN_DATADIR_SUFFIX("fingerprint", ".tmp");
@@ -1105,6 +1106,7 @@ sandbox_init_filter(void)
RENAME_SUFFIX2("stats", "buffer-stats", ".tmp");
RENAME_SUFFIX2("stats", "conn-stats", ".tmp");
RENAME_SUFFIX2("stats", "hidserv-stats", ".tmp");
RENAME_SUFFIX2("stats", "hidserv-v3-stats", ".tmp");
RENAME_SUFFIX("hashed-fingerprint", ".tmp");
RENAME_SUFFIX("router-stability", ".tmp");
+5 -1
View File
@@ -1937,7 +1937,11 @@ write_stats_file_callback(time_t now, const or_options_t *options)
next_time_to_write_stats_files = next_write;
}
if (options->HiddenServiceStatistics) {
time_t next_write = rep_hist_hs_stats_write(now);
time_t next_write = rep_hist_hs_stats_write(now, false);
if (next_write && next_write < next_time_to_write_stats_files)
next_time_to_write_stats_files = next_write;
next_write = rep_hist_hs_stats_write(now, true);
if (next_write && next_write < next_time_to_write_stats_files)
next_time_to_write_stats_files = next_write;
}
+26 -3
View File
@@ -331,6 +331,13 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
return;
}
/* Mark whether this circuit used TAP in case we need to use this
* information for onion service statistics later on. */
if (create_cell->handshake_type == ONION_HANDSHAKE_TYPE_FAST ||
create_cell->handshake_type == ONION_HANDSHAKE_TYPE_TAP) {
circ->used_legacy_circuit_handshake = true;
}
if (!channel_is_client(chan)) {
/* remember create types we've seen, but don't remember them from
* clients, to be extra conservative about client statistics. */
@@ -587,11 +594,27 @@ command_process_relay_cell(cell_t *cell, channel_t *chan)
}
/* If this is a cell in an RP circuit, count it as part of the
hidden service stats */
onion service stats */
if (options->HiddenServiceStatistics &&
!CIRCUIT_IS_ORIGIN(circ) &&
TO_OR_CIRCUIT(circ)->circuit_carries_hs_traffic_stats) {
rep_hist_seen_new_rp_cell();
CONST_TO_OR_CIRCUIT(circ)->circuit_carries_hs_traffic_stats) {
/** We need to figure out of this is a v2 or v3 RP circuit to count it
* appropriately. v2 services always use the TAP legacy handshake to
* connect to the RP; we use this feature to distinguish between v2/v3. */
bool is_v2 = false;
if (CONST_TO_OR_CIRCUIT(circ)->used_legacy_circuit_handshake) {
is_v2 = true;
} else if (CONST_TO_OR_CIRCUIT(circ)->rend_splice) {
/* If this is a client->RP circuit we need to check the spliced circuit
* (which is the service->RP circuit) to see if it was using TAP and
* hence if it's a v2 circuit. That's because client->RP circuits can
* still use ntor even on v2; but service->RP will always use TAP. */
or_circuit_t *splice = CONST_TO_OR_CIRCUIT(circ)->rend_splice;
if (splice->used_legacy_circuit_handshake) {
is_v2 = true;
}
}
rep_hist_seen_new_rp_cell(is_v2);
}
}
+6
View File
@@ -63,6 +63,12 @@ struct or_circuit_t {
* statistics. */
unsigned int circuit_carries_hs_traffic_stats : 1;
/** True iff this circuit was made with a CREATE_FAST cell, or a CREATE[2]
* cell with a TAP handshake. If this is the case and this is a rend circuit,
* this is a v2 circuit, otherwise if this is a rend circuit it's a v3
* circuit. */
bool used_legacy_circuit_handshake;
/** Number of cells that were removed from circuit queue; reset every
* time when writing buffer stats to disk. */
uint32_t processed_cells;
+5 -1
View File
@@ -19,6 +19,7 @@
#include "feature/hs/hs_descriptor.h"
#include "feature/nodelist/networkstatus.h"
#include "feature/rend/rendcache.h"
#include "feature/stats/rephist.h"
#include "feature/hs/hs_cache.h"
@@ -174,7 +175,10 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
* old HS protocol cache subsystem for which we are tied with. */
rend_cache_increment_allocation(cache_get_dir_entry_size(desc));
/* XXX: Update HS statistics. We should have specific stats for v3. */
/* Update HSv3 statistics */
if (get_options()->HiddenServiceStatistics) {
rep_hist_hsdir_stored_maybe_new_v3_onion(desc->key);
}
return 0;
+11 -10
View File
@@ -33,12 +33,11 @@ srv_to_control_string(const sr_srv_t *srv)
}
/**
* 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.
**/
* If we have no consensus and we are not an authority, assume that this is the
* voting interval. This can be used while bootstrapping as a relay and we are
* asked to initialize HS stats (see rep_hist_hs_stats_init()) */
#define DEFAULT_NETWORK_VOTING_INTERVAL (3600)
#define TESTING_DEFAULT_NETWORK_VOTING_INTERVAL (20)
/* 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
@@ -69,11 +68,13 @@ get_voting_interval(void)
* It's better than falling back to the non-consensus case. */
interval = (int)(consensus->fresh_until - consensus->valid_after);
} else {
/* 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;
/* We can reach this as a relay when bootstrapping and we are asked to
* initialize HS stats (see rep_hist_hs_stats_init()). */
if (get_options()->TestingTorNetwork) {
interval = TESTING_DEFAULT_NETWORK_VOTING_INTERVAL;
} else {
interval = DEFAULT_NETWORK_VOTING_INTERVAL;
}
}
tor_assert(interval > 0);
return interval;
+5
View File
@@ -3306,6 +3306,11 @@ extrainfo_dump_to_string_stats_helper(smartlist_t *chunks,
"hidserv-stats-end", now, &contents) > 0) {
smartlist_add(chunks, contents);
}
if (options->HiddenServiceStatistics &&
load_stats_file("stats"PATH_SEPARATOR"hidserv-v3-stats",
"hidserv-v3-stats-end", now, &contents) > 0) {
smartlist_add(chunks, contents);
}
if (options->EntryStatistics &&
load_stats_file("stats"PATH_SEPARATOR"entry-stats",
"entry-stats-end", now, &contents) > 0) {
+1 -1
View File
@@ -718,7 +718,7 @@ rend_cache_store_v2_desc_as_dir(const char *desc)
safe_str(desc_id_base32), (int)encoded_size);
/* Statistics: Note down this potentially new HS. */
if (options->HiddenServiceStatistics) {
rep_hist_stored_maybe_new_hs(e->parsed->pk);
rep_hist_hsdir_stored_maybe_new_v2_onion(e->parsed->pk);
}
number_stored++;
+255 -97
View File
@@ -1710,103 +1710,63 @@ rep_hist_log_circuit_handshake_stats(time_t now)
/** Start of the current hidden service stats interval or 0 if we're
* not collecting hidden service statistics. */
static time_t start_of_hs_stats_interval;
static time_t start_of_hs_v2_stats_interval;
/** Carries the various hidden service statistics, and any other
* information needed. */
typedef struct hs_stats_t {
/** How many relay cells have we seen as rendezvous points? */
uint64_t rp_relay_cells_seen;
/** Our v2 statistics structure singleton. */
static hs_v2_stats_t *hs_v2_stats = NULL;
/** Set of unique public key digests we've seen this stat period
* (could also be implemented as sorted smartlist). */
digestmap_t *onions_seen_this_period;
} hs_stats_t;
/** HSv2 stats */
/** Our statistics structure singleton. */
static hs_stats_t *hs_stats = NULL;
/** Allocate, initialize and return an hs_stats_t structure. */
static hs_stats_t *
hs_stats_new(void)
/** Allocate, initialize and return an hs_v2_stats_t structure. */
static hs_v2_stats_t *
hs_v2_stats_new(void)
{
hs_stats_t *new_hs_stats = tor_malloc_zero(sizeof(hs_stats_t));
new_hs_stats->onions_seen_this_period = digestmap_new();
hs_v2_stats_t *new_hs_v2_stats = tor_malloc_zero(sizeof(hs_v2_stats_t));
new_hs_v2_stats->v2_onions_seen_this_period = digestmap_new();
return new_hs_stats;
return new_hs_v2_stats;
}
#define hs_stats_free(val) \
FREE_AND_NULL(hs_stats_t, hs_stats_free_, (val))
#define hs_v2_stats_free(val) \
FREE_AND_NULL(hs_v2_stats_t, hs_v2_stats_free_, (val))
/** Free an hs_stats_t structure. */
/** Free an hs_v2_stats_t structure. */
static void
hs_stats_free_(hs_stats_t *victim_hs_stats)
hs_v2_stats_free_(hs_v2_stats_t *victim_hs_v2_stats)
{
if (!victim_hs_stats) {
if (!victim_hs_v2_stats) {
return;
}
digestmap_free(victim_hs_stats->onions_seen_this_period, NULL);
tor_free(victim_hs_stats);
}
/** Initialize hidden service statistics. */
void
rep_hist_hs_stats_init(time_t now)
{
if (!hs_stats) {
hs_stats = hs_stats_new();
}
start_of_hs_stats_interval = now;
digestmap_free(victim_hs_v2_stats->v2_onions_seen_this_period, NULL);
tor_free(victim_hs_v2_stats);
}
/** Clear history of hidden service statistics and set the measurement
* interval start to <b>now</b>. */
static void
rep_hist_reset_hs_stats(time_t now)
rep_hist_reset_hs_v2_stats(time_t now)
{
if (!hs_stats) {
hs_stats = hs_stats_new();
if (!hs_v2_stats) {
hs_v2_stats = hs_v2_stats_new();
}
hs_stats->rp_relay_cells_seen = 0;
hs_v2_stats->rp_v2_relay_cells_seen = 0;
digestmap_free(hs_stats->onions_seen_this_period, NULL);
hs_stats->onions_seen_this_period = digestmap_new();
digestmap_free(hs_v2_stats->v2_onions_seen_this_period, NULL);
hs_v2_stats->v2_onions_seen_this_period = digestmap_new();
start_of_hs_stats_interval = now;
start_of_hs_v2_stats_interval = now;
}
/** Stop collecting hidden service stats in a way that we can re-start
* doing so in rep_hist_buffer_stats_init(). */
/** As HSDirs, we saw another v2 onion with public key <b>pubkey</b>. Check
* whether we have counted it before, if not count it now! */
void
rep_hist_hs_stats_term(void)
{
rep_hist_reset_hs_stats(0);
}
/** We saw a new HS relay cell, Count it! */
void
rep_hist_seen_new_rp_cell(void)
{
if (!hs_stats) {
return; // We're not collecting stats
}
hs_stats->rp_relay_cells_seen++;
}
/** As HSDirs, we saw another hidden service with public key
* <b>pubkey</b>. Check whether we have counted it before, if not
* count it now! */
void
rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey)
rep_hist_hsdir_stored_maybe_new_v2_onion(const crypto_pk_t *pubkey)
{
char pubkey_hash[DIGEST_LEN];
if (!hs_stats) {
if (!hs_v2_stats) {
return; // We're not collecting stats
}
@@ -1820,13 +1780,178 @@ rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey)
/* Check if this is the first time we've seen this hidden
service. If it is, count it as new. */
if (!digestmap_get(hs_stats->onions_seen_this_period,
if (!digestmap_get(hs_v2_stats->v2_onions_seen_this_period,
pubkey_hash)) {
digestmap_set(hs_stats->onions_seen_this_period,
digestmap_set(hs_v2_stats->v2_onions_seen_this_period,
pubkey_hash, (void*)(uintptr_t)1);
}
}
/*** HSv3 stats ******/
/** Start of the current hidden service stats interval or 0 if we're not
* collecting hidden service statistics.
*
* This is particularly important for v3 statistics since this variable
* controls the start time of initial v3 stats collection. It's initialized by
* rep_hist_hs_stats_init() to the next time period start (i.e. 12:00UTC), and
* should_collect_v3_stats() ensures that functions that collect v3 stats do
* not do so sooner than that.
*
* Collecting stats from 12:00UTC to 12:00UTC is extremely important for v3
* stats because rep_hist_hsdir_stored_maybe_new_v3_onion() uses the blinded
* key of each onion service as its double-counting index. Onion services
* rotate their descriptor at around 00:00UTC which means that their blinded
* key also changes around that time. However the precise time that onion
* services rotate their descriptors is actually when they fetch a new
* 00:00UTC consensus and that happens at a random time (e.g. it can even
* happen at 02:00UTC). This means that if we started keeping v3 stats at
* around 00:00UTC we wouldn't be able to tell when onion services change
* their blinded key and hence we would double count an unpredictable amount
* of them (for example, if an onion service fetches the 00:00UTC consensus at
* 01:00UTC it would upload to its old HSDir at 00:45UTC, and then to a
* different HSDir at 01:50UTC).
*
* For this reason, we start collecting statistics at 12:00UTC. This way we
* know that by the time we stop collecting statistics for that time period 24
* hours later, all the onion services have switched to their new blinded
* key. This way we can predict much better how much double counting has been
* performed.
*/
static time_t start_of_hs_v3_stats_interval;
/** Our v3 statistics structure singleton. */
static hs_v3_stats_t *hs_v3_stats = NULL;
/** Allocate, initialize and return an hs_v3_stats_t structure. */
static hs_v3_stats_t *
hs_v3_stats_new(void)
{
hs_v3_stats_t *new_hs_v3_stats = tor_malloc_zero(sizeof(hs_v3_stats_t));
new_hs_v3_stats->v3_onions_seen_this_period = digest256map_new();
return new_hs_v3_stats;
}
#define hs_v3_stats_free(val) \
FREE_AND_NULL(hs_v3_stats_t, hs_v3_stats_free_, (val))
/** Free an hs_v3_stats_t structure. */
static void
hs_v3_stats_free_(hs_v3_stats_t *victim_hs_v3_stats)
{
if (!victim_hs_v3_stats) {
return;
}
digest256map_free(victim_hs_v3_stats->v3_onions_seen_this_period, NULL);
tor_free(victim_hs_v3_stats);
}
/** Clear history of hidden service statistics and set the measurement
* interval start to <b>now</b>. */
static void
rep_hist_reset_hs_v3_stats(time_t now)
{
if (!hs_v3_stats) {
hs_v3_stats = hs_v3_stats_new();
}
digest256map_free(hs_v3_stats->v3_onions_seen_this_period, NULL);
hs_v3_stats->v3_onions_seen_this_period = digest256map_new();
hs_v3_stats->rp_v3_relay_cells_seen = 0;
start_of_hs_v3_stats_interval = now;
}
/** Return true if it's a good time to collect v3 stats.
*
* v3 stats have a strict stats collection period (from 12:00UTC to 12:00UTC
* on the real network). We don't want to collect statistics if (for example)
* we just booted and it's 03:00UTC; we will wait until 12:00UTC before we
* start collecting statistics to make sure that the final result represents
* the whole collection period. This behavior is controlled by
* rep_hist_hs_stats_init().
*/
MOCK_IMPL(STATIC bool,
should_collect_v3_stats,(void))
{
return start_of_hs_v3_stats_interval <= approx_time();
}
/** We just received a new descriptor with <b>blinded_key</b>. See if we've
* seen this blinded key before, and if not add it to the stats. */
void
rep_hist_hsdir_stored_maybe_new_v3_onion(const uint8_t *blinded_key)
{
/* Return early if we don't collect HSv3 stats, or if it's not yet the time
* to collect them. */
if (!hs_v3_stats || !should_collect_v3_stats()) {
return;
}
bool seen_before =
!!digest256map_get(hs_v3_stats->v3_onions_seen_this_period,
blinded_key);
log_info(LD_GENERAL, "Considering v3 descriptor with %s (%sseen before)",
safe_str(hex_str((char*)blinded_key, 32)),
seen_before ? "" : "not ");
/* Count it if we haven't seen it before. */
if (!seen_before) {
digest256map_set(hs_v3_stats->v3_onions_seen_this_period,
blinded_key, (void*)(uintptr_t)1);
}
}
/** We saw a new HS relay cell: count it!
* If <b>is_v2</b> is set then it's a v2 RP cell, otherwise it's a v3. */
void
rep_hist_seen_new_rp_cell(bool is_v2)
{
log_debug(LD_GENERAL, "New RP cell (%d)", is_v2);
if (is_v2 && hs_v2_stats) {
hs_v2_stats->rp_v2_relay_cells_seen++;
} else if (!is_v2 && hs_v3_stats && should_collect_v3_stats()) {
hs_v3_stats->rp_v3_relay_cells_seen++;
}
}
/** Generic HS stats code */
/** Initialize v2 and v3 hidden service statistics. */
void
rep_hist_hs_stats_init(time_t now)
{
if (!hs_v2_stats) {
hs_v2_stats = hs_v2_stats_new();
}
/* Start collecting v2 stats straight away */
start_of_hs_v2_stats_interval = now;
if (!hs_v3_stats) {
hs_v3_stats = hs_v3_stats_new();
}
/* Start collecting v3 stats at the next 12:00 UTC */
start_of_hs_v3_stats_interval = hs_get_start_time_of_next_time_period(now);
}
/** Stop collecting hidden service stats in a way that we can re-start
* doing so in rep_hist_buffer_stats_init(). */
void
rep_hist_hs_stats_term(void)
{
rep_hist_reset_hs_v2_stats(0);
rep_hist_reset_hs_v3_stats(0);
}
/** Stats reporting code */
/* The number of cells that are supposed to be hidden from the adversary
* by adding noise from the Laplace distribution. This value, divided by
* EPSILON, is Laplace parameter b. It must be greater than 0. */
@@ -1851,58 +1976,69 @@ rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey)
#define ONIONS_SEEN_BIN_SIZE 8
/** Allocate and return a string containing hidden service stats that
* are meant to be placed in the extra-info descriptor. */
static char *
rep_hist_format_hs_stats(time_t now)
* are meant to be placed in the extra-info descriptor.
*
* Function works for both v2 and v3 stats depending on <b>is_v3</b>. */
STATIC char *
rep_hist_format_hs_stats(time_t now, bool is_v3)
{
char t[ISO_TIME_LEN+1];
char *hs_stats_string;
int64_t obfuscated_cells_seen;
int64_t obfuscated_onions_seen;
int64_t obfuscated_onions_seen, obfuscated_cells_seen;
uint64_t rp_cells_seen = is_v3 ?
hs_v3_stats->rp_v3_relay_cells_seen : hs_v2_stats->rp_v2_relay_cells_seen;
size_t onions_seen = is_v3 ?
digest256map_size(hs_v3_stats->v3_onions_seen_this_period) :
digestmap_size(hs_v2_stats->v2_onions_seen_this_period);
time_t start_of_hs_stats_interval = is_v3 ?
start_of_hs_v3_stats_interval : start_of_hs_v2_stats_interval;
uint64_t rounded_cells_seen
= round_uint64_to_next_multiple_of(hs_stats->rp_relay_cells_seen,
REND_CELLS_BIN_SIZE);
= round_uint64_to_next_multiple_of(rp_cells_seen, REND_CELLS_BIN_SIZE);
rounded_cells_seen = MIN(rounded_cells_seen, INT64_MAX);
obfuscated_cells_seen = add_laplace_noise((int64_t)rounded_cells_seen,
crypto_rand_double(),
REND_CELLS_DELTA_F, REND_CELLS_EPSILON);
uint64_t rounded_onions_seen =
round_uint64_to_next_multiple_of((size_t)digestmap_size(
hs_stats->onions_seen_this_period),
ONIONS_SEEN_BIN_SIZE);
round_uint64_to_next_multiple_of(onions_seen, ONIONS_SEEN_BIN_SIZE);
rounded_onions_seen = MIN(rounded_onions_seen, INT64_MAX);
obfuscated_onions_seen = add_laplace_noise((int64_t)rounded_onions_seen,
crypto_rand_double(), ONIONS_SEEN_DELTA_F,
ONIONS_SEEN_EPSILON);
format_iso_time(t, now);
tor_asprintf(&hs_stats_string, "hidserv-stats-end %s (%d s)\n"
"hidserv-rend-relayed-cells %"PRId64" delta_f=%d "
"epsilon=%.2f bin_size=%d\n"
"hidserv-dir-onions-seen %"PRId64" delta_f=%d "
"epsilon=%.2f bin_size=%d\n",
t, (unsigned) (now - start_of_hs_stats_interval),
(obfuscated_cells_seen), REND_CELLS_DELTA_F,
tor_asprintf(&hs_stats_string, "%s %s (%ld s)\n"
"%s %"PRId64" delta_f=%d epsilon=%.2f bin_size=%d\n"
"%s %"PRId64" delta_f=%d epsilon=%.2f bin_size=%d\n",
is_v3 ? "hidserv-v3-stats-end" : "hidserv-stats-end",
t, now - start_of_hs_stats_interval,
is_v3 ?
"hidserv-rend-v3-relayed-cells" : "hidserv-rend-relayed-cells",
obfuscated_cells_seen, REND_CELLS_DELTA_F,
REND_CELLS_EPSILON, REND_CELLS_BIN_SIZE,
(obfuscated_onions_seen),
ONIONS_SEEN_DELTA_F,
is_v3 ? "hidserv-dir-v3-onions-seen" :"hidserv-dir-onions-seen",
obfuscated_onions_seen, ONIONS_SEEN_DELTA_F,
ONIONS_SEEN_EPSILON, ONIONS_SEEN_BIN_SIZE);
return hs_stats_string;
}
/** If 24 hours have passed since the beginning of the current HS
* stats period, write buffer stats to $DATADIR/stats/hidserv-stats
* stats period, write buffer stats to $DATADIR/stats/hidserv-v3-stats
* (possibly overwriting an existing file) and reset counters. Return
* when we would next want to write buffer stats or 0 if we never want to
* write. */
* write. Function works for both v2 and v3 stats depending on <b>is_v3</b>.
*/
time_t
rep_hist_hs_stats_write(time_t now)
rep_hist_hs_stats_write(time_t now, bool is_v3)
{
char *str = NULL;
time_t start_of_hs_stats_interval = is_v3 ?
start_of_hs_v3_stats_interval : start_of_hs_v2_stats_interval;
if (!start_of_hs_stats_interval) {
return 0; /* Not initialized. */
}
@@ -1912,15 +2048,20 @@ rep_hist_hs_stats_write(time_t now)
}
/* Generate history string. */
str = rep_hist_format_hs_stats(now);
str = rep_hist_format_hs_stats(now, is_v3);
/* Reset HS history. */
rep_hist_reset_hs_stats(now);
if (is_v3) {
rep_hist_reset_hs_v3_stats(now);
} else {
rep_hist_reset_hs_v2_stats(now);
}
/* Try to write to disk. */
if (!check_or_create_data_subdir("stats")) {
write_to_data_subdir("stats", "hidserv-stats", str,
"hidden service stats");
write_to_data_subdir("stats",
is_v3 ? "hidserv-v3-stats" : "hidserv-stats",
str, "hidden service stats");
}
done:
@@ -2134,7 +2275,8 @@ rep_hist_log_link_protocol_counts(void)
void
rep_hist_free_all(void)
{
hs_stats_free(hs_stats);
hs_v2_stats_free(hs_v2_stats);
hs_v3_stats_free(hs_v3_stats);
digestmap_free(history_map, free_or_history);
tor_free(exit_bytes_read);
@@ -2155,3 +2297,19 @@ rep_hist_free_all(void)
tor_assert_nonfatal(rephist_total_alloc == 0);
tor_assert_nonfatal_once(rephist_total_num == 0);
}
#ifdef TOR_UNIT_TESTS
/* only exists for unit tests: get HSv2 stats object */
const hs_v2_stats_t *
rep_hist_get_hs_v2_stats(void)
{
return hs_v2_stats;
}
/* only exists for unit tests: get HSv2 stats object */
const hs_v3_stats_t *
rep_hist_get_hs_v3_stats(void)
{
return hs_v3_stats;
}
#endif /* defined(TOR_UNIT_TESTS) */
+49 -4
View File
@@ -65,10 +65,14 @@ MOCK_DECL(int, rep_hist_get_circuit_handshake_assigned, (uint16_t type));
void rep_hist_hs_stats_init(time_t now);
void rep_hist_hs_stats_term(void);
time_t rep_hist_hs_stats_write(time_t now);
char *rep_hist_get_hs_stats_string(void);
void rep_hist_seen_new_rp_cell(void);
void rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey);
time_t rep_hist_hs_stats_write(time_t now, bool is_v3);
char *rep_hist_get_hs_v2_stats_string(void);
void rep_hist_seen_new_rp_cell(bool is_v2);
void rep_hist_hsdir_stored_maybe_new_v2_onion(const crypto_pk_t *pubkey);
char *rep_hist_get_hs_v3_stats_string(void);
void rep_hist_hsdir_stored_maybe_new_v3_onion(const uint8_t *blinded_key);
void rep_hist_free_all(void);
@@ -83,6 +87,40 @@ extern int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1];
extern int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1];
#endif
#ifdef REPHIST_PRIVATE
/** Carries the various hidden service statistics, and any other
* information needed. */
typedef struct hs_v2_stats_t {
/** How many v2 relay cells have we seen as rendezvous points? */
uint64_t rp_v2_relay_cells_seen;
/** Set of unique public key digests we've seen this stat period
* (could also be implemented as sorted smartlist). */
digestmap_t *v2_onions_seen_this_period;
} hs_v2_stats_t;
/** Structure that contains the various statistics we keep about v3
* services.
*
* Because of the time period logic of v3 services, v3 statistics are more
* sensitive to time than v2 stats. For this reason, we collect v3
* statistics strictly from 12:00UTC to 12:00UTC as dictated by
* 'start_of_hs_v3_stats_interval'.
**/
typedef struct hs_v3_stats_t {
/** How many v3 relay cells have we seen as a rendezvous point? */
uint64_t rp_v3_relay_cells_seen;
/* The number of unique v3 onion descriptors (actually, unique v3 blind keys)
* we've seen during the measurement period */
digest256map_t *v3_onions_seen_this_period;
} hs_v3_stats_t;
MOCK_DECL(STATIC bool, should_collect_v3_stats,(void));
STATIC char *rep_hist_format_hs_stats(time_t now, bool is_v3);
#endif /* defined(REPHIST_PRIVATE) */
/**
* Represents the type of a cell for padding accounting
*/
@@ -108,4 +146,11 @@ void rep_hist_reset_padding_counts(void);
void rep_hist_prep_published_padding_counts(time_t now);
void rep_hist_padding_count_timers(uint64_t num_timers);
#ifdef TOR_UNIT_TESTS
struct hs_v2_stats_t;
const struct hs_v2_stats_t *rep_hist_get_hs_v2_stats(void);
struct hs_v3_stats_t;
const struct hs_v3_stats_t *rep_hist_get_hs_v3_stats(void);
#endif
#endif /* !defined(TOR_REPHIST_H) */
+14 -5
View File
@@ -134,7 +134,8 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
* points are added. */
static hs_descriptor_t *
hs_helper_build_hs_desc_impl(unsigned int no_ip,
const ed25519_keypair_t *signing_kp)
const ed25519_keypair_t *signing_kp,
uint64_t rev_counter)
{
int ret;
int i;
@@ -161,7 +162,7 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
&signing_kp->pubkey, now, 3600,
CERT_FLAG_INCLUDE_SIGNING_KEY);
tt_assert(desc->plaintext_data.signing_key_cert);
desc->plaintext_data.revision_counter = 42;
desc->plaintext_data.revision_counter = rev_counter;
desc->plaintext_data.lifetime_sec = 3 * 60 * 60;
hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey,
@@ -226,18 +227,26 @@ hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp,
subcred_out);
}
/* Build a descriptor with a specific rev counter. */
hs_descriptor_t *
hs_helper_build_hs_desc_with_rev_counter(const ed25519_keypair_t *signing_kp,
uint64_t revision_counter)
{
return hs_helper_build_hs_desc_impl(0, signing_kp, revision_counter);
}
/* Build a descriptor with introduction points. */
hs_descriptor_t *
hs_helper_build_hs_desc_with_ip(const ed25519_keypair_t *signing_kp)
{
return hs_helper_build_hs_desc_impl(0, signing_kp);
return hs_helper_build_hs_desc_impl(0, signing_kp, 42);
}
/* Build a descriptor without any introduction points. */
hs_descriptor_t *
hs_helper_build_hs_desc_no_ip(const ed25519_keypair_t *signing_kp)
{
return hs_helper_build_hs_desc_impl(1, signing_kp);
return hs_helper_build_hs_desc_impl(1, signing_kp, 42);
}
hs_descriptor_t *
@@ -247,7 +256,7 @@ hs_helper_build_hs_desc_with_client_auth(
const ed25519_keypair_t *signing_kp)
{
curve25519_keypair_t auth_ephemeral_kp;
hs_descriptor_t *desc = hs_helper_build_hs_desc_impl(0, signing_kp);
hs_descriptor_t *desc = hs_helper_build_hs_desc_impl(0, signing_kp, 42);
hs_desc_authorized_client_t *desc_client;
/* The number of client authorized auth has tobe a multiple of
+4
View File
@@ -17,6 +17,10 @@ hs_descriptor_t *hs_helper_build_hs_desc_no_ip(
const ed25519_keypair_t *signing_kp);
hs_descriptor_t *hs_helper_build_hs_desc_with_ip(
const ed25519_keypair_t *signing_kp);
hs_descriptor_t *
hs_helper_build_hs_desc_with_rev_counter(const ed25519_keypair_t *signing_kp,
uint64_t revision_counter);
hs_descriptor_t *hs_helper_build_hs_desc_with_client_auth(
const uint8_t *descriptor_cookie,
const curve25519_public_key_t *client_pk,
+129
View File
@@ -12,6 +12,8 @@
#include "lib/crypt_ops/crypto_rand.h"
#include "app/config/or_state_st.h"
#include "test/rng_test_helpers.h"
#include "feature/hs/hs_cache.h"
#include "test/hs_test_helpers.h"
#include <stdio.h>
@@ -31,6 +33,7 @@
#define MAINLOOP_PRIVATE
#define STATEFILE_PRIVATE
#define BWHIST_PRIVATE
#define REPHIST_PRIVATE
#include "core/or/or.h"
#include "lib/err/backtrace.h"
@@ -493,6 +496,131 @@ test_get_bandwidth_lines(void *arg)
bwhist_free_all();
}
static bool
mock_should_collect_v3_stats(void)
{
return true;
}
/* Test v3 metrics */
static void
test_rephist_v3_onions(void *arg)
{
int ret;
char *stats_string = NULL;
char *desc1_str = NULL;
ed25519_keypair_t signing_kp1;
hs_descriptor_t *desc1 = NULL;
const hs_v3_stats_t *hs_v3_stats = NULL;
(void) arg;
MOCK(should_collect_v3_stats, mock_should_collect_v3_stats);
get_options_mutable()->HiddenServiceStatistics = 1;
/* Initialize the subsystems */
hs_cache_init();
rep_hist_hs_stats_init(0);
update_approx_time(10101010101);
/* HS stats should be zero here */
hs_v3_stats = rep_hist_get_hs_v3_stats();
tt_int_op(digest256map_size(hs_v3_stats->v3_onions_seen_this_period),
OP_EQ, 0);
/* Generate a valid descriptor */
ret = ed25519_keypair_generate(&signing_kp1, 0);
tt_int_op(ret, OP_EQ, 0);
desc1 = hs_helper_build_hs_desc_with_rev_counter(&signing_kp1, 42);
tt_assert(desc1);
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &desc1_str);
tt_int_op(ret, OP_EQ, 0);
/* Store descriptor and check that stats got updated */
ret = hs_cache_store_as_dir(desc1_str);
tt_int_op(ret, OP_EQ, 0);
hs_v3_stats = rep_hist_get_hs_v3_stats();
tt_int_op(digest256map_size(hs_v3_stats->v3_onions_seen_this_period),
OP_EQ, 1);
/* cleanup */
hs_descriptor_free(desc1);
tor_free(desc1_str);
/* Generate another valid descriptor */
ret = ed25519_keypair_generate(&signing_kp1, 0);
tt_int_op(ret, OP_EQ, 0);
desc1 = hs_helper_build_hs_desc_with_rev_counter(&signing_kp1, 42);
tt_assert(desc1);
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &desc1_str);
tt_int_op(ret, OP_EQ, 0);
/* Store descriptor and check that stats are updated */
ret = hs_cache_store_as_dir(desc1_str);
tt_int_op(ret, OP_EQ, 0);
hs_v3_stats = rep_hist_get_hs_v3_stats();
tt_int_op(digest256map_size(hs_v3_stats->v3_onions_seen_this_period),
OP_EQ, 2);
/* Check that storing the same descriptor twice does not work */
ret = hs_cache_store_as_dir(desc1_str);
tt_int_op(ret, OP_EQ, -1);
/* cleanup */
hs_descriptor_free(desc1);
tor_free(desc1_str);
/* Create a descriptor with the same identity key but diff rev counter and
same blinded key */
desc1 = hs_helper_build_hs_desc_with_rev_counter(&signing_kp1, 43);
tt_assert(desc1);
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &desc1_str);
tt_int_op(ret, OP_EQ, 0);
/* Store descriptor and check that stats are updated */
ret = hs_cache_store_as_dir(desc1_str);
tt_int_op(ret, OP_EQ, 0);
tt_int_op(digest256map_size(hs_v3_stats->v3_onions_seen_this_period),
OP_EQ, 2);
/* cleanup */
hs_descriptor_free(desc1);
tor_free(desc1_str);
/* Now let's skip to four days forward so that the blinded key rolls
forward */
update_approx_time(approx_time() + 345600);
/* Now create a descriptor with the same identity key but diff rev counter
and different blinded key */
desc1 = hs_helper_build_hs_desc_with_rev_counter(&signing_kp1, 44);
tt_assert(desc1);
ret = hs_desc_encode_descriptor(desc1, &signing_kp1, NULL, &desc1_str);
tt_int_op(ret, OP_EQ, 0);
/* Store descriptor and check that stats are updated */
ret = hs_cache_store_as_dir(desc1_str);
tt_int_op(ret, OP_EQ, 0);
tt_int_op(digest256map_size(hs_v3_stats->v3_onions_seen_this_period),
OP_EQ, 3);
/* cleanup */
hs_descriptor_free(desc1);
tor_free(desc1_str);
/* Because of differential privacy we can't actually check the stat value,
but let's just check that it's formatted correctly. */
stats_string = rep_hist_format_hs_stats(approx_time(), true);
tt_assert(strstr(stats_string, "hidserv-dir-v3-onions-seen"));
done:
UNMOCK(should_collect_v3_stats);
tor_free(stats_string);
}
#define ENT(name) \
{ #name, test_ ## name , 0, NULL, NULL }
#define FORK(name) \
@@ -506,6 +634,7 @@ struct testcase_t stats_tests[] = {
FORK(add_obs),
FORK(fill_bandwidth_history),
FORK(get_bandwidth_lines),
FORK(rephist_v3_onions),
END_OF_TESTCASES
};