From 62d682e62422eecd40f2e2ba4d222bb2795dad53 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 1 Jul 2020 13:29:33 +0300 Subject: [PATCH 01/17] Make room for v3: Rename rephist objects to signify they are v2-only. --- src/feature/rend/rendcache.c | 2 +- src/feature/stats/rephist.c | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/feature/rend/rendcache.c b/src/feature/rend/rendcache.c index 53fec7532f..715e70eba7 100644 --- a/src/feature/rend/rendcache.c +++ b/src/feature/rend/rendcache.c @@ -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++; diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index b6730e1226..b66eed80ef 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1720,7 +1720,7 @@ typedef struct hs_stats_t { /** 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; + digestmap_t *v2_onions_seen_this_period; } hs_stats_t; /** Our statistics structure singleton. */ @@ -1731,7 +1731,7 @@ static hs_stats_t * hs_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(); + new_hs_stats->v2_onions_seen_this_period = digestmap_new(); return new_hs_stats; } @@ -1747,7 +1747,7 @@ hs_stats_free_(hs_stats_t *victim_hs_stats) return; } - digestmap_free(victim_hs_stats->onions_seen_this_period, NULL); + digestmap_free(victim_hs_stats->v2_onions_seen_this_period, NULL); tor_free(victim_hs_stats); } @@ -1773,8 +1773,8 @@ rep_hist_reset_hs_stats(time_t now) hs_stats->rp_relay_cells_seen = 0; - digestmap_free(hs_stats->onions_seen_this_period, NULL); - hs_stats->onions_seen_this_period = digestmap_new(); + digestmap_free(hs_stats->v2_onions_seen_this_period, NULL); + hs_stats->v2_onions_seen_this_period = digestmap_new(); start_of_hs_stats_interval = now; } @@ -1802,7 +1802,7 @@ rep_hist_seen_new_rp_cell(void) * pubkey. 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]; @@ -1820,9 +1820,9 @@ 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_stats->v2_onions_seen_this_period, pubkey_hash)) { - digestmap_set(hs_stats->onions_seen_this_period, + digestmap_set(hs_stats->v2_onions_seen_this_period, pubkey_hash, (void*)(uintptr_t)1); } } @@ -1870,7 +1870,7 @@ rep_hist_format_hs_stats(time_t now) uint64_t rounded_onions_seen = round_uint64_to_next_multiple_of((size_t)digestmap_size( - hs_stats->onions_seen_this_period), + hs_stats->v2_onions_seen_this_period), ONIONS_SEEN_BIN_SIZE); rounded_onions_seen = MIN(rounded_onions_seen, INT64_MAX); obfuscated_onions_seen = add_laplace_noise((int64_t)rounded_onions_seen, From 1de7843658e00baa2271048fb3e3e436639e70c4 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 26 Oct 2020 18:19:25 +0200 Subject: [PATCH 02/17] Make room for v3: s/hs_stats/hs_v2_stats/ --- src/core/mainloop/mainloop.c | 2 +- src/feature/stats/rephist.c | 98 ++++++++++++++++++------------------ src/feature/stats/rephist.h | 8 +-- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index c75039b378..4b4ac5c4a7 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1937,7 +1937,7 @@ 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_v2_stats_write(now); if (next_write && next_write < next_time_to_write_stats_files) next_time_to_write_stats_files = next_write; } diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index b66eed80ef..739ead195f 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1710,92 +1710,92 @@ 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 { +typedef struct hs_v2_stats_t { /** How many relay cells have we seen as rendezvous points? */ uint64_t rp_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_stats_t; +} hs_v2_stats_t; /** Our statistics structure singleton. */ -static hs_stats_t *hs_stats = NULL; +static hs_v2_stats_t *hs_v2_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->v2_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->v2_onions_seen_this_period, NULL); - tor_free(victim_hs_stats); + digestmap_free(victim_hs_v2_stats->v2_onions_seen_this_period, NULL); + tor_free(victim_hs_v2_stats); } /** Initialize hidden service statistics. */ void -rep_hist_hs_stats_init(time_t now) +rep_hist_hs_v2_stats_init(time_t now) { - if (!hs_stats) { - hs_stats = hs_stats_new(); + if (!hs_v2_stats) { + hs_v2_stats = hs_v2_stats_new(); } - start_of_hs_stats_interval = now; + start_of_hs_v2_stats_interval = now; } /** Clear history of hidden service statistics and set the measurement * interval start to now. */ 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_relay_cells_seen = 0; - digestmap_free(hs_stats->v2_onions_seen_this_period, NULL); - hs_stats->v2_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(). */ void -rep_hist_hs_stats_term(void) +rep_hist_hs_v2_stats_term(void) { - rep_hist_reset_hs_stats(0); + rep_hist_reset_hs_v2_stats(0); } /** We saw a new HS relay cell, Count it! */ void rep_hist_seen_new_rp_cell(void) { - if (!hs_stats) { + if (!hs_v2_stats) { return; // We're not collecting stats } - hs_stats->rp_relay_cells_seen++; + hs_v2_stats->rp_relay_cells_seen++; } /** As HSDirs, we saw another hidden service with public key @@ -1806,7 +1806,7 @@ 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,9 +1820,9 @@ rep_hist_hsdir_stored_maybe_new_v2_onion(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->v2_onions_seen_this_period, + if (!digestmap_get(hs_v2_stats->v2_onions_seen_this_period, pubkey_hash)) { - digestmap_set(hs_stats->v2_onions_seen_this_period, + digestmap_set(hs_v2_stats->v2_onions_seen_this_period, pubkey_hash, (void*)(uintptr_t)1); } } @@ -1853,15 +1853,15 @@ rep_hist_hsdir_stored_maybe_new_v2_onion(const crypto_pk_t *pubkey) /** 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) +rep_hist_format_hs_v2_stats(time_t now) { char t[ISO_TIME_LEN+1]; - char *hs_stats_string; + char *hs_v2_stats_string; int64_t obfuscated_cells_seen; int64_t obfuscated_onions_seen; uint64_t rounded_cells_seen - = round_uint64_to_next_multiple_of(hs_stats->rp_relay_cells_seen, + = round_uint64_to_next_multiple_of(hs_v2_stats->rp_relay_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, @@ -1870,7 +1870,7 @@ rep_hist_format_hs_stats(time_t now) uint64_t rounded_onions_seen = round_uint64_to_next_multiple_of((size_t)digestmap_size( - hs_stats->v2_onions_seen_this_period), + hs_v2_stats->v2_onions_seen_this_period), ONIONS_SEEN_BIN_SIZE); rounded_onions_seen = MIN(rounded_onions_seen, INT64_MAX); obfuscated_onions_seen = add_laplace_noise((int64_t)rounded_onions_seen, @@ -1878,19 +1878,19 @@ rep_hist_format_hs_stats(time_t now) ONIONS_SEEN_EPSILON); format_iso_time(t, now); - tor_asprintf(&hs_stats_string, "hidserv-stats-end %s (%d s)\n" + tor_asprintf(&hs_v2_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), + t, (unsigned) (now - start_of_hs_v2_stats_interval), (obfuscated_cells_seen), REND_CELLS_DELTA_F, REND_CELLS_EPSILON, REND_CELLS_BIN_SIZE, (obfuscated_onions_seen), ONIONS_SEEN_DELTA_F, ONIONS_SEEN_EPSILON, ONIONS_SEEN_BIN_SIZE); - return hs_stats_string; + return hs_v2_stats_string; } /** If 24 hours have passed since the beginning of the current HS @@ -1899,23 +1899,23 @@ rep_hist_format_hs_stats(time_t now) * when we would next want to write buffer stats or 0 if we never want to * write. */ time_t -rep_hist_hs_stats_write(time_t now) +rep_hist_hs_v2_stats_write(time_t now) { char *str = NULL; - if (!start_of_hs_stats_interval) { + if (!start_of_hs_v2_stats_interval) { return 0; /* Not initialized. */ } - if (start_of_hs_stats_interval + WRITE_STATS_INTERVAL > now) { + if (start_of_hs_v2_stats_interval + WRITE_STATS_INTERVAL > now) { goto done; /* Not ready to write */ } /* Generate history string. */ - str = rep_hist_format_hs_stats(now); + str = rep_hist_format_hs_v2_stats(now); /* Reset HS history. */ - rep_hist_reset_hs_stats(now); + rep_hist_reset_hs_v2_stats(now); /* Try to write to disk. */ if (!check_or_create_data_subdir("stats")) { @@ -1925,7 +1925,7 @@ rep_hist_hs_stats_write(time_t now) done: tor_free(str); - return start_of_hs_stats_interval + WRITE_STATS_INTERVAL; + return start_of_hs_v2_stats_interval + WRITE_STATS_INTERVAL; } static uint64_t link_proto_count[MAX_LINK_PROTO+1][2]; @@ -2134,7 +2134,7 @@ 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); digestmap_free(history_map, free_or_history); tor_free(exit_bytes_read); diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index c9ebc5c328..abcb70249f 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -63,10 +63,10 @@ void rep_hist_log_circuit_handshake_stats(time_t now); MOCK_DECL(int, rep_hist_get_circuit_handshake_requested, (uint16_t type)); MOCK_DECL(int, rep_hist_get_circuit_handshake_assigned, (uint16_t type)); -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_hs_v2_stats_init(time_t now); +void rep_hist_hs_v2_stats_term(void); +time_t rep_hist_hs_v2_stats_write(time_t now); +char *rep_hist_get_hs_v2_stats_string(void); void rep_hist_seen_new_rp_cell(void); void rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey); From 5ed7fcec41db16820c3777451d083d0d74f124ce Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 26 Oct 2020 18:27:16 +0200 Subject: [PATCH 03/17] Make room for v3: Complete move from hs_stats_t to hs_v2_stats_t. --- src/feature/stats/rephist.c | 31 ++++++++++++++-------------- src/feature/stats/rephist.h | 40 +++++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 739ead195f..bde65ea9d9 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1712,20 +1712,11 @@ rep_hist_log_circuit_handshake_stats(time_t now) * not collecting hidden service statistics. */ static time_t start_of_hs_v2_stats_interval; -/** Carries the various hidden service statistics, and any other - * information needed. */ -typedef struct hs_v2_stats_t { - /** How many relay cells have we seen as rendezvous points? */ - uint64_t rp_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; - -/** Our statistics structure singleton. */ +/** Our v2 statistics structure singleton. */ static hs_v2_stats_t *hs_v2_stats = NULL; +/** HSv2 stats */ + /** Allocate, initialize and return an hs_v2_stats_t structure. */ static hs_v2_stats_t * hs_v2_stats_new(void) @@ -1771,7 +1762,7 @@ rep_hist_reset_hs_v2_stats(time_t now) hs_v2_stats = hs_v2_stats_new(); } - hs_v2_stats->rp_relay_cells_seen = 0; + hs_v2_stats->rp_v2_relay_cells_seen = 0; digestmap_free(hs_v2_stats->v2_onions_seen_this_period, NULL); hs_v2_stats->v2_onions_seen_this_period = digestmap_new(); @@ -1861,7 +1852,7 @@ rep_hist_format_hs_v2_stats(time_t now) int64_t obfuscated_onions_seen; uint64_t rounded_cells_seen - = round_uint64_to_next_multiple_of(hs_v2_stats->rp_relay_cells_seen, + = round_uint64_to_next_multiple_of(hs_v2_stats->rp_v2_relay_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, @@ -1886,8 +1877,7 @@ rep_hist_format_hs_v2_stats(time_t now) t, (unsigned) (now - start_of_hs_v2_stats_interval), (obfuscated_cells_seen), REND_CELLS_DELTA_F, REND_CELLS_EPSILON, REND_CELLS_BIN_SIZE, - (obfuscated_onions_seen), - ONIONS_SEEN_DELTA_F, + (obfuscated_onions_seen), ONIONS_SEEN_DELTA_F, ONIONS_SEEN_EPSILON, ONIONS_SEEN_BIN_SIZE); return hs_v2_stats_string; @@ -2155,3 +2145,12 @@ 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; +} + diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index abcb70249f..d5ad21e228 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -64,11 +64,10 @@ MOCK_DECL(int, rep_hist_get_circuit_handshake_requested, (uint16_t type)); MOCK_DECL(int, rep_hist_get_circuit_handshake_assigned, (uint16_t type)); void rep_hist_hs_v2_stats_init(time_t now); -void rep_hist_hs_v2_stats_term(void); time_t rep_hist_hs_v2_stats_write(time_t now); char *rep_hist_get_hs_v2_stats_string(void); void rep_hist_seen_new_rp_cell(void); -void rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey); +void rep_hist_hsdir_stored_maybe_new_v2_onion(const crypto_pk_t *pubkey); void rep_hist_free_all(void); @@ -83,6 +82,38 @@ 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 */ + digestmap_t *v3_onions_seen_this_period; +} hs_v3_stats_t; + +STATIC char *rep_hist_format_hs_v2_stats(time_t now, bool is_v3); +#endif /* defined(REPHIST_PRIVATE) */ + /** * Represents the type of a cell for padding accounting */ @@ -108,4 +139,9 @@ 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 +typedef struct hs_v2_stats_t hs_v2_stats_t; +const hs_v2_stats_t *rep_hist_get_hs_v2_stats(void); +#endif + #endif /* !defined(TOR_REPHIST_H) */ From bd28551763bd008be5a6f676410d9b6b1d011bf6 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 21 Oct 2020 14:32:30 +0300 Subject: [PATCH 04/17] Introduce v3_stats_t structure and some of its methods. --- src/app/main/main.c | 2 + src/core/mainloop/mainloop.c | 4 ++ src/feature/hs/hs_cache.c | 1 + src/feature/relay/router.c | 5 ++ src/feature/stats/rephist.c | 98 ++++++++++++++++++++++++++++++++++++ src/feature/stats/rephist.h | 8 ++- 6 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/app/main/main.c b/src/app/main/main.c index ff530c0ad0..c2ced30851 100644 --- a/src/app/main/main.c +++ b/src/app/main/main.c @@ -1070,6 +1070,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"); @@ -1095,6 +1096,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"); diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 4b4ac5c4a7..25555a3f22 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1940,6 +1940,10 @@ write_stats_file_callback(time_t now, const or_options_t *options) time_t next_write = rep_hist_hs_v2_stats_write(now); if (next_write && next_write < next_time_to_write_stats_files) next_time_to_write_stats_files = next_write; + + next_write = rep_hist_hs_v3_stats_write(now); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; } if (options->ExitPortStatistics) { time_t next_write = rep_hist_exit_stats_write(now); diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c index 44cd2505fd..0688d7765d 100644 --- a/src/feature/hs/hs_cache.c +++ b/src/feature/hs/hs_cache.c @@ -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" diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index 5ca21964b6..ea631e18fe 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -3288,6 +3288,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) { diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index bde65ea9d9..daf9db074c 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1818,6 +1818,96 @@ rep_hist_hsdir_stored_maybe_new_v2_onion(const crypto_pk_t *pubkey) } } +/*** 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 = digestmap_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; + } + + digestmap_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 now. */ +static void +rep_hist_reset_hs_v3_stats(time_t now) +{ + if (!hs_v3_stats) { + hs_v3_stats = hs_v3_stats_new(); + } + + digestmap_free(hs_v3_stats->v3_onions_seen_this_period, NULL); + hs_v3_stats->v3_onions_seen_this_period = digestmap_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) and hence we don't want to collect statistics if it's + * not yet the time to do so. + */ +static bool +should_collect_v3_stats(void) +{ + return start_of_hs_v3_stats_interval <= approx_time(); +} + /* 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 greather than 0. */ @@ -2125,6 +2215,7 @@ void rep_hist_free_all(void) { 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); @@ -2154,3 +2245,10 @@ 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) */ diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index d5ad21e228..e6c1509498 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -69,6 +69,9 @@ char *rep_hist_get_hs_v2_stats_string(void); void rep_hist_seen_new_rp_cell(void); void rep_hist_hsdir_stored_maybe_new_v2_onion(const crypto_pk_t *pubkey); +time_t rep_hist_hs_v3_stats_write(time_t now); +char *rep_hist_get_hs_v3_stats_string(void); + void rep_hist_free_all(void); void rep_hist_note_negotiated_link_proto(unsigned link_proto, @@ -111,7 +114,8 @@ typedef struct hs_v3_stats_t { digestmap_t *v3_onions_seen_this_period; } hs_v3_stats_t; -STATIC char *rep_hist_format_hs_v2_stats(time_t now, bool is_v3); +STATIC char *rep_hist_format_hs_v2_stats(time_t now); +STATIC char *rep_hist_format_hs_v3_stats(time_t now); #endif /* defined(REPHIST_PRIVATE) */ /** @@ -142,6 +146,8 @@ void rep_hist_padding_count_timers(uint64_t num_timers); #ifdef TOR_UNIT_TESTS typedef struct hs_v2_stats_t hs_v2_stats_t; const hs_v2_stats_t *rep_hist_get_hs_v2_stats(void); +typedef struct hs_v3_stats_t hs_v3_stats_t; +const hs_v3_stats_t *rep_hist_get_hs_v3_stats(void); #endif #endif /* !defined(TOR_REPHIST_H) */ From 05880d238a95b09c08b600e546870d0870f856fd Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 1 Jul 2020 13:57:11 +0300 Subject: [PATCH 05/17] Implement support for "unique v3 onions" stat. --- src/feature/hs/hs_cache.c | 5 ++++- src/feature/stats/rephist.c | 30 +++++++++++++++++++++++++++--- src/feature/stats/rephist.h | 1 + 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c index 0688d7765d..b57f133362 100644 --- a/src/feature/hs/hs_cache.c +++ b/src/feature/hs/hs_cache.c @@ -175,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; diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index daf9db074c..5858f14245 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1789,9 +1789,8 @@ rep_hist_seen_new_rp_cell(void) hs_v2_stats->rp_relay_cells_seen++; } -/** As HSDirs, we saw another hidden service with public key - * pubkey. Check whether we have counted it before, if not - * count it now! */ +/** As HSDirs, we saw another v2 onion with public key pubkey. Check + * whether we have counted it before, if not count it now! */ void rep_hist_hsdir_stored_maybe_new_v2_onion(const crypto_pk_t *pubkey) { @@ -1908,6 +1907,31 @@ should_collect_v3_stats(void) return start_of_hs_v3_stats_interval <= approx_time(); } +/** We just received a new descriptor with blinded_key. 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 = !!digestmap_get(hs_v3_stats->v3_onions_seen_this_period, + (char*)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) { + digestmap_set(hs_v3_stats->v3_onions_seen_this_period, + (char*)blinded_key, (void*)(uintptr_t)1); + } +} + /* 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 greather than 0. */ diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index e6c1509498..5873594781 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -71,6 +71,7 @@ void rep_hist_hsdir_stored_maybe_new_v2_onion(const crypto_pk_t *pubkey); time_t rep_hist_hs_v3_stats_write(time_t now); 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); From 3cbc513ae767aa872f690ab23f94535aa121975d Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 21 Oct 2020 13:50:32 +0300 Subject: [PATCH 06/17] Implement support for "v3 rend traffic" stat. --- src/core/or/command.c | 27 +++++++++++++++++++++++++-- src/core/or/or_circuit_st.h | 6 ++++++ src/feature/stats/rephist.c | 14 ++++++++++++++ src/feature/stats/rephist.h | 2 +- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/core/or/command.c b/src/core/or/command.c index 9226309ff7..35dadb9fc8 100644 --- a/src/core/or/command.c +++ b/src/core/or/command.c @@ -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(); + /** 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 (TO_OR_CIRCUIT(circ)->used_legacy_circuit_handshake) { + is_v2 = true; + } else if (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 = TO_OR_CIRCUIT(circ)->rend_splice; + if (splice->used_legacy_circuit_handshake) { + is_v2 = true; + } + } + rep_hist_seen_new_rp_cell(is_v2); } } diff --git a/src/core/or/or_circuit_st.h b/src/core/or/or_circuit_st.h index 4e17b1c143..4da88889ce 100644 --- a/src/core/or/or_circuit_st.h +++ b/src/core/or/or_circuit_st.h @@ -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; diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 5858f14245..2ad86ff6d9 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1932,6 +1932,20 @@ rep_hist_hsdir_stored_maybe_new_v3_onion(const uint8_t *blinded_key) } } +/** We saw a new HS relay cell: count it! + * If is_v2 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++; + } +} + /* 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 greather than 0. */ diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index 5873594781..3bb4f996a2 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -66,7 +66,7 @@ MOCK_DECL(int, rep_hist_get_circuit_handshake_assigned, (uint16_t type)); void rep_hist_hs_v2_stats_init(time_t now); time_t rep_hist_hs_v2_stats_write(time_t now); char *rep_hist_get_hs_v2_stats_string(void); -void rep_hist_seen_new_rp_cell(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); time_t rep_hist_hs_v3_stats_write(time_t now); From f2eff171264595de9a534c01628909d0ba6cb9fb Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 3 Nov 2020 11:04:13 +0200 Subject: [PATCH 07/17] Introduce generic HS stats methods that apply to v2 and v3. --- src/feature/stats/rephist.c | 62 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 2ad86ff6d9..7f5ec93597 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1742,17 +1742,6 @@ hs_v2_stats_free_(hs_v2_stats_t *victim_hs_v2_stats) tor_free(victim_hs_v2_stats); } -/** Initialize hidden service statistics. */ -void -rep_hist_hs_v2_stats_init(time_t now) -{ - if (!hs_v2_stats) { - hs_v2_stats = hs_v2_stats_new(); - } - - start_of_hs_v2_stats_interval = now; -} - /** Clear history of hidden service statistics and set the measurement * interval start to now. */ static void @@ -1770,25 +1759,6 @@ rep_hist_reset_hs_v2_stats(time_t 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(). */ -void -rep_hist_hs_v2_stats_term(void) -{ - rep_hist_reset_hs_v2_stats(0); -} - -/** We saw a new HS relay cell, Count it! */ -void -rep_hist_seen_new_rp_cell(void) -{ - if (!hs_v2_stats) { - return; // We're not collecting stats - } - - hs_v2_stats->rp_relay_cells_seen++; -} - /** As HSDirs, we saw another v2 onion with public key pubkey. Check * whether we have counted it before, if not count it now! */ void @@ -1946,6 +1916,38 @@ rep_hist_seen_new_rp_cell(bool is_v2) } } +/** 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 greather than 0. */ From d0be2ae7f99fe1fe4d97f30b0ea565930f63c698 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 2 Nov 2020 12:42:08 +0200 Subject: [PATCH 08/17] Extend get_voting_interval() so that it's callable by relays. In the past, only authorities and clients had to use that function because of the SRV subsystem. However, because of its use in rep_hist_hs_stats_init() it will now also be used by relays when bootstrapping without a consensus. Make it do something sensible. Another approach (instead of using magic values) would be to wait initialization of HSv3 stats until we get a consensus but that seems messy to schedule. Another approach would be to make dirauth_sched_get_configured_interval() also work for relays (particularly when TestingNetwork is enabled), but that also seems a good amount of work. --- src/feature/hs_common/shared_random_client.c | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/feature/hs_common/shared_random_client.c b/src/feature/hs_common/shared_random_client.c index c2ea5afe32..2c7d6c8d90 100644 --- a/src/feature/hs_common/shared_random_client.c +++ b/src/feature/hs_common/shared_random_client.c @@ -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; From 5c00bee1b1cde4bb77a2e3fa1f5110850ddede6e Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 21 Oct 2020 14:43:29 +0300 Subject: [PATCH 09/17] Introduce v3 stat formatting functions. They will be merged with the v2 ones in later commits. --- src/feature/stats/rephist.c | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 7f5ec93597..24712707ca 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -2048,6 +2048,82 @@ rep_hist_hs_v2_stats_write(time_t now) return start_of_hs_v2_stats_interval + WRITE_STATS_INTERVAL; } +/** 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_v3_stats(time_t now) +{ + char t[ISO_TIME_LEN+1]; + char *hs_v3_stats_string; + int64_t obfuscated_onions_seen, obfuscated_cells_seen; + + uint64_t rounded_cells_seen + = round_uint64_to_next_multiple_of(hs_v3_stats->rp_v3_relay_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_v3_stats->v3_onions_seen_this_period), + 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_v3_stats_string, "hidserv-v3-stats-end %s (%d s)\n" + "hidserv-rend-v3-relayed-cells %"PRId64" delta_f=%d " + "epsilon=%.2f bin_size=%d\n" + "hidserv-dir-v3-onions-seen %"PRId64" delta_f=%d " + "epsilon=%.2f bin_size=%d\n", + t, (unsigned) (now - start_of_hs_v3_stats_interval), + (obfuscated_cells_seen), REND_CELLS_DELTA_F, + REND_CELLS_EPSILON, REND_CELLS_BIN_SIZE, + (obfuscated_onions_seen), ONIONS_SEEN_DELTA_F, + ONIONS_SEEN_EPSILON, ONIONS_SEEN_BIN_SIZE); + + return hs_v3_stats_string; +} + +/** If 24 hours have passed since the beginning of the current HS + * 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. */ +time_t +rep_hist_hs_v3_stats_write(time_t now) +{ + char *str = NULL; + + if (!start_of_hs_v3_stats_interval) { + return 0; /* Not initialized. */ + } + + if (start_of_hs_v3_stats_interval + WRITE_STATS_INTERVAL > now) { + goto done; /* Not ready to write */ + } + + /* Generate history string. */ + str = rep_hist_format_hs_v3_stats(now); + + /* Reset HS history. */ + rep_hist_reset_hs_v3_stats(now); + + /* Try to write to disk. */ + if (!check_or_create_data_subdir("stats")) { + write_to_data_subdir("stats", "hidserv-v3-stats", str, + "hidden service stats"); + } + + done: + tor_free(str); + return start_of_hs_v3_stats_interval + WRITE_STATS_INTERVAL; +} + static uint64_t link_proto_count[MAX_LINK_PROTO+1][2]; /** Note that we negotiated link protocol version link_proto, on From 131da887d75bd112e668db3c1695ad8cc5a76433 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 21 Oct 2020 14:17:30 +0300 Subject: [PATCH 10/17] Write unittests for v3 metrics. --- src/feature/stats/rephist.c | 6 +- src/feature/stats/rephist.h | 11 ++-- src/test/hs_test_helpers.c | 19 ++++-- src/test/hs_test_helpers.h | 4 ++ src/test/test_stats.c | 124 ++++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 12 deletions(-) diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 24712707ca..ada19b447a 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1871,8 +1871,8 @@ rep_hist_reset_hs_v3_stats(time_t now) * on the real network) and hence we don't want to collect statistics if it's * not yet the time to do so. */ -static bool -should_collect_v3_stats(void) +MOCK_IMPL(STATIC bool, +should_collect_v3_stats,(void)) { return start_of_hs_v3_stats_interval <= approx_time(); } @@ -1973,7 +1973,7 @@ rep_hist_hs_stats_term(void) /** Allocate and return a string containing hidden service stats that * are meant to be placed in the extra-info descriptor. */ -static char * +STATIC char * rep_hist_format_hs_v2_stats(time_t now) { char t[ISO_TIME_LEN+1]; diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index 3bb4f996a2..b2a4a5048d 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -117,6 +117,9 @@ typedef struct hs_v3_stats_t { STATIC char *rep_hist_format_hs_v2_stats(time_t now); STATIC char *rep_hist_format_hs_v3_stats(time_t now); + +MOCK_DECL(STATIC bool, should_collect_v3_stats,(void)); + #endif /* defined(REPHIST_PRIVATE) */ /** @@ -145,10 +148,10 @@ void rep_hist_prep_published_padding_counts(time_t now); void rep_hist_padding_count_timers(uint64_t num_timers); #ifdef TOR_UNIT_TESTS -typedef struct hs_v2_stats_t hs_v2_stats_t; -const hs_v2_stats_t *rep_hist_get_hs_v2_stats(void); -typedef struct hs_v3_stats_t hs_v3_stats_t; -const hs_v3_stats_t *rep_hist_get_hs_v3_stats(void); +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) */ diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c index e9aafa4760..e1ecf9fe56 100644 --- a/src/test/hs_test_helpers.c +++ b/src/test/hs_test_helpers.c @@ -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 diff --git a/src/test/hs_test_helpers.h b/src/test/hs_test_helpers.h index 23d11f2a4a..e22295b660 100644 --- a/src/test/hs_test_helpers.h +++ b/src/test/hs_test_helpers.h @@ -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, diff --git a/src/test/test_stats.c b/src/test/test_stats.c index b6849b0b6d..3ddc3bb31c 100644 --- a/src/test/test_stats.c +++ b/src/test/test_stats.c @@ -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 @@ -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,126 @@ 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(digestmap_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(digestmap_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(digestmap_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(digestmap_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(digestmap_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_v3_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 +629,7 @@ struct testcase_t stats_tests[] = { FORK(add_obs), FORK(fill_bandwidth_history), FORK(get_bandwidth_lines), + FORK(rephist_v3_onions), END_OF_TESTCASES }; From 6178a64fcf86b280890a222864ea1c09960d058f Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 21 Oct 2020 16:43:39 +0300 Subject: [PATCH 11/17] Abstract v2/v3 "write stats to file" logic into a single function. --- src/core/mainloop/mainloop.c | 4 +-- src/feature/stats/rephist.c | 63 +++++++++++------------------------- src/feature/stats/rephist.h | 7 ++-- 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 25555a3f22..c64f0a8e82 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -1937,11 +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_v2_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_v3_stats_write(now); + 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; } diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index ada19b447a..91def19019 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -2013,41 +2013,6 @@ rep_hist_format_hs_v2_stats(time_t now) return hs_v2_stats_string; } -/** If 24 hours have passed since the beginning of the current HS - * stats period, write buffer stats to $DATADIR/stats/hidserv-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. */ -time_t -rep_hist_hs_v2_stats_write(time_t now) -{ - char *str = NULL; - - if (!start_of_hs_v2_stats_interval) { - return 0; /* Not initialized. */ - } - - if (start_of_hs_v2_stats_interval + WRITE_STATS_INTERVAL > now) { - goto done; /* Not ready to write */ - } - - /* Generate history string. */ - str = rep_hist_format_hs_v2_stats(now); - - /* Reset HS history. */ - 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"); - } - - done: - tor_free(str); - return start_of_hs_v2_stats_interval + WRITE_STATS_INTERVAL; -} - /** Allocate and return a string containing hidden service stats that * are meant to be placed in the extra-info descriptor. */ STATIC char * @@ -2093,35 +2058,45 @@ rep_hist_format_hs_v3_stats(time_t now) * 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 is_v3. + */ time_t -rep_hist_hs_v3_stats_write(time_t now) +rep_hist_hs_stats_write(time_t now, bool is_v3) { char *str = NULL; - if (!start_of_hs_v3_stats_interval) { + 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. */ } - if (start_of_hs_v3_stats_interval + WRITE_STATS_INTERVAL > now) { + if (start_of_hs_stats_interval + WRITE_STATS_INTERVAL > now) { goto done; /* Not ready to write */ } /* Generate history string. */ - str = rep_hist_format_hs_v3_stats(now); + str = is_v3 ? + rep_hist_format_hs_v3_stats(now) : rep_hist_format_hs_v2_stats(now); /* Reset HS history. */ - rep_hist_reset_hs_v3_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-v3-stats", str, - "hidden service stats"); + write_to_data_subdir("stats", + is_v3 ? "hidserv-v3-stats" : "hidserv-stats", + str, "hidden service stats"); } done: tor_free(str); - return start_of_hs_v3_stats_interval + WRITE_STATS_INTERVAL; + return start_of_hs_stats_interval + WRITE_STATS_INTERVAL; } static uint64_t link_proto_count[MAX_LINK_PROTO+1][2]; diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index b2a4a5048d..c68b854242 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -63,13 +63,14 @@ void rep_hist_log_circuit_handshake_stats(time_t now); MOCK_DECL(int, rep_hist_get_circuit_handshake_requested, (uint16_t type)); MOCK_DECL(int, rep_hist_get_circuit_handshake_assigned, (uint16_t type)); -void rep_hist_hs_v2_stats_init(time_t now); -time_t rep_hist_hs_v2_stats_write(time_t now); +void rep_hist_hs_stats_init(time_t now); +void rep_hist_hs_stats_term(void); +time_t rep_hist_hs_stats_write(time_t now, bool is_v3); + 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); -time_t rep_hist_hs_v3_stats_write(time_t now); char *rep_hist_get_hs_v3_stats_string(void); void rep_hist_hsdir_stored_maybe_new_v3_onion(const uint8_t *blinded_key); From a96432ab06f2d6699b89ed522afd2661ca7e9860 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 21 Oct 2020 17:00:08 +0300 Subject: [PATCH 12/17] Abstract v2/v3 "format stats to str" logic into a single function. --- src/feature/stats/rephist.c | 84 +++++++++++-------------------------- src/feature/stats/rephist.h | 4 +- src/test/test_stats.c | 2 +- 3 files changed, 27 insertions(+), 63 deletions(-) diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 91def19019..37250108d1 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1972,86 +1972,53 @@ rep_hist_hs_stats_term(void) #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. */ + * are meant to be placed in the extra-info descriptor. + * + * Function works for both v2 and v3 stats depending on is_v3. */ STATIC char * -rep_hist_format_hs_v2_stats(time_t now) +rep_hist_format_hs_stats(time_t now, bool is_v3) { char t[ISO_TIME_LEN+1]; - char *hs_v2_stats_string; - int64_t obfuscated_cells_seen; - int64_t obfuscated_onions_seen; - - uint64_t rounded_cells_seen - = round_uint64_to_next_multiple_of(hs_v2_stats->rp_v2_relay_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_v2_stats->v2_onions_seen_this_period), - 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_v2_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_v2_stats_interval), - (obfuscated_cells_seen), REND_CELLS_DELTA_F, - REND_CELLS_EPSILON, REND_CELLS_BIN_SIZE, - (obfuscated_onions_seen), ONIONS_SEEN_DELTA_F, - ONIONS_SEEN_EPSILON, ONIONS_SEEN_BIN_SIZE); - - return hs_v2_stats_string; -} - -/** 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_v3_stats(time_t now) -{ - char t[ISO_TIME_LEN+1]; - char *hs_v3_stats_string; + char *hs_stats_string; 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 ? + digestmap_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_v3_stats->rp_v3_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_v3_stats->v3_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_v3_stats_string, "hidserv-v3-stats-end %s (%d s)\n" - "hidserv-rend-v3-relayed-cells %"PRId64" delta_f=%d " - "epsilon=%.2f bin_size=%d\n" - "hidserv-dir-v3-onions-seen %"PRId64" delta_f=%d " - "epsilon=%.2f bin_size=%d\n", - t, (unsigned) (now - start_of_hs_v3_stats_interval), + tor_asprintf(&hs_stats_string, "%s %s (%d 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, (unsigned) (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, + 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_v3_stats_string; + return hs_stats_string; } /** If 24 hours have passed since the beginning of the current HS @@ -2077,8 +2044,7 @@ rep_hist_hs_stats_write(time_t now, bool is_v3) } /* Generate history string. */ - str = is_v3 ? - rep_hist_format_hs_v3_stats(now) : rep_hist_format_hs_v2_stats(now); + str = rep_hist_format_hs_stats(now, is_v3); /* Reset HS history. */ if (is_v3) { diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index c68b854242..a2caa4fc15 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -116,11 +116,9 @@ typedef struct hs_v3_stats_t { digestmap_t *v3_onions_seen_this_period; } hs_v3_stats_t; -STATIC char *rep_hist_format_hs_v2_stats(time_t now); -STATIC char *rep_hist_format_hs_v3_stats(time_t now); - 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) */ /** diff --git a/src/test/test_stats.c b/src/test/test_stats.c index 3ddc3bb31c..dc02c9e784 100644 --- a/src/test/test_stats.c +++ b/src/test/test_stats.c @@ -608,7 +608,7 @@ test_rephist_v3_onions(void *arg) /* 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_v3_stats(approx_time(), true); + stats_string = rep_hist_format_hs_stats(approx_time(), true); tt_assert(strstr(stats_string, "hidserv-dir-v3-onions-seen")); done: From f2da7b05b0a8e6d353b94ea496f0ce99aa076ffa Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 27 Oct 2020 11:40:08 +0200 Subject: [PATCH 13/17] Add changes file for v3 metrics. Closes ticket #23126. --- changes/bug23126 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changes/bug23126 diff --git a/changes/bug23126 b/changes/bug23126 new file mode 100644 index 0000000000..76ba393205 --- /dev/null +++ b/changes/bug23126 @@ -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. \ No newline at end of file From 9a98d1da30a25b1f263859cae21a3c0863d8c91d Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 3 Nov 2020 17:34:46 +0200 Subject: [PATCH 14/17] Switch v3_onions_seen_this_period to digest256map_t. --- src/feature/stats/rephist.c | 19 ++++++++++--------- src/feature/stats/rephist.h | 2 +- src/test/test_stats.c | 15 ++++++++++----- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 37250108d1..7b4bc97bda 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1828,7 +1828,7 @@ 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 = digestmap_new(); + new_hs_v3_stats->v3_onions_seen_this_period = digest256map_new(); return new_hs_v3_stats; } @@ -1844,7 +1844,7 @@ hs_v3_stats_free_(hs_v3_stats_t *victim_hs_v3_stats) return; } - digestmap_free(victim_hs_v3_stats->v3_onions_seen_this_period, NULL); + digest256map_free(victim_hs_v3_stats->v3_onions_seen_this_period, NULL); tor_free(victim_hs_v3_stats); } @@ -1857,8 +1857,8 @@ rep_hist_reset_hs_v3_stats(time_t now) hs_v3_stats = hs_v3_stats_new(); } - digestmap_free(hs_v3_stats->v3_onions_seen_this_period, NULL); - hs_v3_stats->v3_onions_seen_this_period = digestmap_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; @@ -1888,8 +1888,9 @@ rep_hist_hsdir_stored_maybe_new_v3_onion(const uint8_t *blinded_key) return; } - bool seen_before = !!digestmap_get(hs_v3_stats->v3_onions_seen_this_period, - (char*)blinded_key); + 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)), @@ -1897,8 +1898,8 @@ rep_hist_hsdir_stored_maybe_new_v3_onion(const uint8_t *blinded_key) /* Count it if we haven't seen it before. */ if (!seen_before) { - digestmap_set(hs_v3_stats->v3_onions_seen_this_period, - (char*)blinded_key, (void*)(uintptr_t)1); + digest256map_set(hs_v3_stats->v3_onions_seen_this_period, + blinded_key, (void*)(uintptr_t)1); } } @@ -1985,7 +1986,7 @@ rep_hist_format_hs_stats(time_t now, bool is_v3) 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 ? - digestmap_size(hs_v3_stats->v3_onions_seen_this_period) : + 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; diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index a2caa4fc15..de27b16ae0 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -113,7 +113,7 @@ typedef struct hs_v3_stats_t { /* The number of unique v3 onion descriptors (actually, unique v3 blind keys) * we've seen during the measurement period */ - digestmap_t *v3_onions_seen_this_period; + digest256map_t *v3_onions_seen_this_period; } hs_v3_stats_t; MOCK_DECL(STATIC bool, should_collect_v3_stats,(void)); diff --git a/src/test/test_stats.c b/src/test/test_stats.c index dc02c9e784..64d89cf1e9 100644 --- a/src/test/test_stats.c +++ b/src/test/test_stats.c @@ -528,7 +528,8 @@ test_rephist_v3_onions(void *arg) /* HS stats should be zero here */ hs_v3_stats = rep_hist_get_hs_v3_stats(); - tt_int_op(digestmap_size(hs_v3_stats->v3_onions_seen_this_period), OP_EQ, 0); + 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); @@ -542,7 +543,8 @@ test_rephist_v3_onions(void *arg) 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(digestmap_size(hs_v3_stats->v3_onions_seen_this_period), OP_EQ, 1); + tt_int_op(digest256map_size(hs_v3_stats->v3_onions_seen_this_period), + OP_EQ, 1); /* cleanup */ hs_descriptor_free(desc1); @@ -560,7 +562,8 @@ test_rephist_v3_onions(void *arg) 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(digestmap_size(hs_v3_stats->v3_onions_seen_this_period), OP_EQ, 2); + 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); @@ -580,7 +583,8 @@ test_rephist_v3_onions(void *arg) /* 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(digestmap_size(hs_v3_stats->v3_onions_seen_this_period), OP_EQ, 2); + tt_int_op(digest256map_size(hs_v3_stats->v3_onions_seen_this_period), + OP_EQ, 2); /* cleanup */ hs_descriptor_free(desc1); @@ -600,7 +604,8 @@ test_rephist_v3_onions(void *arg) /* 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(digestmap_size(hs_v3_stats->v3_onions_seen_this_period), OP_EQ, 3); + tt_int_op(digest256map_size(hs_v3_stats->v3_onions_seen_this_period), + OP_EQ, 3); /* cleanup */ hs_descriptor_free(desc1); From 7ae576edaf94159c824900ef09bee5bea99c8255 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 3 Nov 2020 17:36:18 +0200 Subject: [PATCH 15/17] Use CONST_TO_OR_CIRCUIT() in v2/v3 rend cell detection. --- src/core/or/command.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/or/command.c b/src/core/or/command.c index 35dadb9fc8..7767217275 100644 --- a/src/core/or/command.c +++ b/src/core/or/command.c @@ -597,19 +597,19 @@ command_process_relay_cell(cell_t *cell, channel_t *chan) onion service stats */ if (options->HiddenServiceStatistics && !CIRCUIT_IS_ORIGIN(circ) && - TO_OR_CIRCUIT(circ)->circuit_carries_hs_traffic_stats) { + 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 (TO_OR_CIRCUIT(circ)->used_legacy_circuit_handshake) { + if (CONST_TO_OR_CIRCUIT(circ)->used_legacy_circuit_handshake) { is_v2 = true; - } else if (TO_OR_CIRCUIT(circ)->rend_splice) { + } 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 = TO_OR_CIRCUIT(circ)->rend_splice; + or_circuit_t *splice = CONST_TO_OR_CIRCUIT(circ)->rend_splice; if (splice->used_legacy_circuit_handshake) { is_v2 = true; } From 810183aaf17aa23eb68260038345cb540a856f75 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 3 Nov 2020 17:42:36 +0200 Subject: [PATCH 16/17] Improve string formatting in rep_hist_format_hs_stats(). --- src/feature/stats/rephist.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 7b4bc97bda..e0d8669cba 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -2006,17 +2006,17 @@ rep_hist_format_hs_stats(time_t now, bool is_v3) ONIONS_SEEN_EPSILON); format_iso_time(t, now); - tor_asprintf(&hs_stats_string, "%s %s (%d s)\n" + 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, (unsigned) (now - start_of_hs_stats_interval), + 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, + obfuscated_cells_seen, REND_CELLS_DELTA_F, REND_CELLS_EPSILON, REND_CELLS_BIN_SIZE, is_v3 ? "hidserv-dir-v3-onions-seen" :"hidserv-dir-onions-seen", - (obfuscated_onions_seen), ONIONS_SEEN_DELTA_F, + obfuscated_onions_seen, ONIONS_SEEN_DELTA_F, ONIONS_SEEN_EPSILON, ONIONS_SEEN_BIN_SIZE); return hs_stats_string; From 0812ecd517af406aa82e5a5deddbbe799e9d8b49 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 3 Nov 2020 19:19:02 +0200 Subject: [PATCH 17/17] Add more docs about the HSv3 stat collection period. --- src/feature/stats/rephist.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index e0d8669cba..1501e46b14 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1868,8 +1868,11 @@ rep_hist_reset_hs_v3_stats(time_t 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) and hence we don't want to collect statistics if it's - * not yet the time to do so. + * 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))