diff options
Diffstat (limited to 'src')
65 files changed, 2354 insertions, 496 deletions
diff --git a/src/common/compat_threads.c b/src/common/compat_threads.c index 208d3138d9..6adcaab956 100644 --- a/src/common/compat_threads.c +++ b/src/common/compat_threads.c @@ -352,12 +352,7 @@ alert_sockets_close(alert_sockets_t *socks) socks->read_fd = socks->write_fd = -1; } -/* - * XXXX We might be smart to move to compiler intrinsics or real atomic - * XXXX operations at some point. But not yet. - * - */ - +#ifndef HAVE_STDATOMIC_H /** Initialize a new atomic counter with the value 0 */ void atomic_counter_init(atomic_counter_t *counter) @@ -397,4 +392,16 @@ atomic_counter_get(atomic_counter_t *counter) tor_mutex_release(&counter->mutex); return val; } +/** Replace the value of an atomic counter; return the old one. */ +size_t +atomic_counter_exchange(atomic_counter_t *counter, size_t newval) +{ + size_t oldval; + tor_mutex_acquire(&counter->mutex); + oldval = counter->val; + counter->val = newval; + tor_mutex_release(&counter->mutex); + return oldval; +} +#endif /* !defined(HAVE_STDATOMIC_H) */ diff --git a/src/common/compat_threads.h b/src/common/compat_threads.h index 42f14eab2a..ce86b15e9d 100644 --- a/src/common/compat_threads.h +++ b/src/common/compat_threads.h @@ -14,6 +14,10 @@ #include <pthread.h> #endif +#ifdef HAVE_STDATOMIC_H +#include <stdatomic.h> +#endif + #if defined(_WIN32) #define USE_WIN32_THREADS #elif defined(HAVE_PTHREAD_H) && defined(HAVE_PTHREAD_CREATE) @@ -150,16 +154,68 @@ void tor_threadlocal_set(tor_threadlocal_t *threadlocal, void *value); /** * Atomic counter type; holds a size_t value. */ +#ifdef HAVE_STDATOMIC_H +typedef struct atomic_counter_t { + atomic_size_t val; +} atomic_counter_t; +#define ATOMIC_LINKAGE static +#else /* !(defined(HAVE_STDATOMIC_H)) */ typedef struct atomic_counter_t { tor_mutex_t mutex; size_t val; } atomic_counter_t; - -void atomic_counter_init(atomic_counter_t *counter); -void atomic_counter_destroy(atomic_counter_t *counter); -void atomic_counter_add(atomic_counter_t *counter, size_t add); -void atomic_counter_sub(atomic_counter_t *counter, size_t sub); -size_t atomic_counter_get(atomic_counter_t *counter); +#define ATOMIC_LINKAGE +#endif /* defined(HAVE_STDATOMIC_H) */ + +ATOMIC_LINKAGE void atomic_counter_init(atomic_counter_t *counter); +ATOMIC_LINKAGE void atomic_counter_destroy(atomic_counter_t *counter); +ATOMIC_LINKAGE void atomic_counter_add(atomic_counter_t *counter, size_t add); +ATOMIC_LINKAGE void atomic_counter_sub(atomic_counter_t *counter, size_t sub); +ATOMIC_LINKAGE size_t atomic_counter_get(atomic_counter_t *counter); +ATOMIC_LINKAGE size_t atomic_counter_exchange(atomic_counter_t *counter, + size_t newval); +#undef ATOMIC_LINKAGE + +#ifdef HAVE_STDATOMIC_H +/** Initialize a new atomic counter with the value 0 */ +static inline void +atomic_counter_init(atomic_counter_t *counter) +{ + atomic_init(&counter->val, 0); +} +/** Clean up all resources held by an atomic counter. */ +static inline void +atomic_counter_destroy(atomic_counter_t *counter) +{ + (void)counter; +} +/** Add a value to an atomic counter. */ +static inline void +atomic_counter_add(atomic_counter_t *counter, size_t add) +{ + (void) atomic_fetch_add(&counter->val, add); +} +/** Subtract a value from an atomic counter. */ +static inline void +atomic_counter_sub(atomic_counter_t *counter, size_t sub) +{ + (void) atomic_fetch_sub(&counter->val, sub); +} +/** Return the current value of an atomic counter */ +static inline size_t +atomic_counter_get(atomic_counter_t *counter) +{ + return atomic_load(&counter->val); +} +/** Replace the value of an atomic counter; return the old one. */ +static inline size_t +atomic_counter_exchange(atomic_counter_t *counter, size_t newval) +{ + return atomic_exchange(&counter->val, newval); +} + +#else /* !(defined(HAVE_STDATOMIC_H)) */ +#endif /* defined(HAVE_STDATOMIC_H) */ #endif /* !defined(TOR_COMPAT_THREADS_H) */ diff --git a/src/common/crypto.c b/src/common/crypto.c index 6fe3c661c8..380f038f42 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -645,11 +645,21 @@ crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) return 0; } +/** A PEM callback that always reports a failure to get a password */ +static int +pem_no_password_cb(char *buf, int size, int rwflag, void *u) +{ + (void)buf; + (void)size; + (void)rwflag; + (void)u; + return 0; +} + /** Read a PEM-encoded private key from the <b>len</b>-byte string <b>s</b> * into <b>env</b>. Return 0 on success, -1 on failure. If len is -1, * the string is nul-terminated. */ -/* Used here, and used for testing. */ int crypto_pk_read_private_key_from_string(crypto_pk_t *env, const char *s, ssize_t len) @@ -668,7 +678,7 @@ crypto_pk_read_private_key_from_string(crypto_pk_t *env, if (env->key) RSA_free(env->key); - env->key = PEM_read_bio_RSAPrivateKey(b,NULL,NULL,NULL); + env->key = PEM_read_bio_RSAPrivateKey(b,NULL,pem_no_password_cb,NULL); BIO_free(b); @@ -800,7 +810,7 @@ crypto_pk_read_public_key_from_string(crypto_pk_t *env, const char *src, if (env->key) RSA_free(env->key); - env->key = PEM_read_bio_RSAPublicKey(b, NULL, NULL, NULL); + env->key = PEM_read_bio_RSAPublicKey(b, NULL, pem_no_password_cb, NULL); BIO_free(b); if (!env->key) { crypto_log_errors(LOG_WARN, "reading public key from string"); @@ -2865,8 +2875,17 @@ crypto_strongest_rand_syscall(uint8_t *out, size_t out_len) tor_assert(errno != EAGAIN); tor_assert(errno != EINTR); - /* Probably ENOSYS. */ - log_warn(LD_CRYPTO, "Can't get entropy from getrandom()."); + /* Useful log message for errno. */ + if (errno == ENOSYS) { + log_warn(LD_CRYPTO, "Can't get entropy from getrandom(). " + " You are running a version of Tor built to support" + " getrandom(), but the kernel doesn't implement this" + " implement this function--probably because it is too old?"); + } else { + log_warn(LD_CRYPTO, "Can't get entropy from getrandom(): %s.", + strerror(errno)); + } + getrandom_works = 0; /* Don't bother trying again. */ return -1; /* LCOV_EXCL_STOP */ diff --git a/src/common/sandbox.c b/src/common/sandbox.c index 8cb78bd28e..aec9857e94 100644 --- a/src/common/sandbox.c +++ b/src/common/sandbox.c @@ -62,6 +62,9 @@ #include <time.h> #include <poll.h> +#ifdef HAVE_GNU_LIBC_VERSION_H +#include <gnu/libc-version.h> +#endif #ifdef HAVE_LINUX_NETFILTER_IPV4_H #include <linux/netfilter_ipv4.h> #endif @@ -133,6 +136,9 @@ static int filter_nopar_gen[] = { SCMP_SYS(clone), SCMP_SYS(epoll_create), SCMP_SYS(epoll_wait), +#ifdef __NR_epoll_pwait + SCMP_SYS(epoll_pwait), +#endif #ifdef HAVE_EVENTFD SCMP_SYS(eventfd2), #endif @@ -397,6 +403,52 @@ sb_mmap2(scmp_filter_ctx ctx, sandbox_cfg_t *filter) } #endif /* defined(__NR_mmap2) */ +#ifdef HAVE_GNU_LIBC_VERSION_H +#ifdef HAVE_GNU_GET_LIBC_VERSION +#define CHECK_LIBC_VERSION +#endif +#endif + +/* Return true if we think we're running with a libc that always uses + * openat on linux. */ +static int +libc_uses_openat_for_everything(void) +{ +#ifdef CHECK_LIBC_VERSION + const char *version = gnu_get_libc_version(); + if (version == NULL) + return 0; + + int major = -1; + int minor = -1; + + tor_sscanf(version, "%d.%d", &major, &minor); + if (major >= 3) + return 1; + else if (major == 2 && minor >= 26) + return 1; + else + return 0; +#else /* !(defined(CHECK_LIBC_VERSION)) */ + return 0; +#endif /* defined(CHECK_LIBC_VERSION) */ +} + +/** Allow a single file to be opened. If <b>use_openat</b> is true, + * we're using a libc that remaps all the opens into openats. */ +static int +allow_file_open(scmp_filter_ctx ctx, int use_openat, const char *file) +{ + if (use_openat) { + return seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), + SCMP_CMP_STR(0, SCMP_CMP_EQ, AT_FDCWD), + SCMP_CMP_STR(1, SCMP_CMP_EQ, file)); + } else { + return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), + SCMP_CMP_STR(0, SCMP_CMP_EQ, file)); + } +} + /** * Function responsible for setting up the open syscall for * the seccomp filter sandbox. @@ -407,14 +459,15 @@ sb_open(scmp_filter_ctx ctx, sandbox_cfg_t *filter) int rc; sandbox_cfg_t *elem = NULL; + int use_openat = libc_uses_openat_for_everything(); + // for each dynamic parameter filters for (elem = filter; elem != NULL; elem = elem->next) { smp_param_t *param = elem->param; if (param != NULL && param->prot == 1 && param->syscall == SCMP_SYS(open)) { - rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), - SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); + rc = allow_file_open(ctx, use_openat, param->value); if (rc != 0) { log_err(LD_BUG,"(Sandbox) failed to add open syscall, received " "libseccomp error %d", rc); @@ -432,6 +485,15 @@ sb_open(scmp_filter_ctx ctx, sandbox_cfg_t *filter) return rc; } + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(openat), + SCMP_CMP_MASKED(2, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, + O_RDONLY)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add openat syscall, received " + "libseccomp error %d", rc); + return rc; + } + return 0; } @@ -621,7 +683,7 @@ sb_socket(scmp_filter_ctx ctx, sandbox_cfg_t *filter) rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), SCMP_CMP(0, SCMP_CMP_EQ, PF_NETLINK), - SCMP_CMP(1, SCMP_CMP_EQ, SOCK_RAW), + SCMP_CMP_MASKED(1, SOCK_CLOEXEC, SOCK_RAW), SCMP_CMP(2, SCMP_CMP_EQ, 0)); if (rc) return rc; @@ -1054,6 +1116,19 @@ sb_stat64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) } #endif /* defined(__NR_stat64) */ +static int +sb_kill(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + (void) filter; +#ifdef __NR_kill + /* Allow killing anything with signal 0 -- it isn't really a kill. */ + return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(kill), + SCMP_CMP(1, SCMP_CMP_EQ, 0)); +#else + return 0; +#endif /* defined(__NR_kill) */ +} + /** * Array of function pointers responsible for filtering different syscalls at * a parameter level. @@ -1090,10 +1165,10 @@ static sandbox_filter_func_t filter_func[] = { sb_setsockopt, sb_getsockopt, sb_socketpair, - #ifdef HAVE_KIST_SUPPORT sb_ioctl, #endif + sb_kill }; const char * @@ -1601,7 +1676,8 @@ add_param_filter(scmp_filter_ctx ctx, sandbox_cfg_t* cfg) // function pointer for (i = 0; i < ARRAY_LENGTH(filter_func); i++) { - if ((filter_func[i])(ctx, cfg)) { + rc = filter_func[i](ctx, cfg); + if (rc) { log_err(LD_BUG,"(Sandbox) failed to add syscall %d, received libseccomp " "error %d", i, rc); return rc; diff --git a/src/common/storagedir.c b/src/common/storagedir.c index 31933f64c2..c471ea911f 100644 --- a/src/common/storagedir.c +++ b/src/common/storagedir.c @@ -187,14 +187,19 @@ storage_dir_get_usage(storage_dir_t *d) return d->usage; } -/** Mmap a specified file within <b>d</b>. */ +/** Mmap a specified file within <b>d</b>. + * + * On failure, return NULL and set errno as for tor_mmap_file(). */ tor_mmap_t * storage_dir_map(storage_dir_t *d, const char *fname) { char *path = NULL; tor_asprintf(&path, "%s/%s", d->directory, fname); tor_mmap_t *result = tor_mmap_file(path); + int errval = errno; tor_free(path); + if (result == NULL) + errno = errval; return result; } @@ -364,6 +369,10 @@ storage_dir_save_labeled_to_file(storage_dir_t *d, * the data's size into *<b>sz_out</b>. On success, also return a tor_mmap_t * object whose contents should not be used -- it needs to be kept around, * though, for as long as <b>data_out</b> is going to be valid. + * + * On failure, set errno as for tor_mmap_file() if the file was missing or + * empty, and set errno to EINVAL if the file was not in the labeled + * format expected. */ tor_mmap_t * storage_dir_map_labeled(storage_dir_t *dir, @@ -373,13 +382,20 @@ storage_dir_map_labeled(storage_dir_t *dir, size_t *sz_out) { tor_mmap_t *m = storage_dir_map(dir, fname); - if (! m) + int errval; + if (! m) { + errval = errno; goto err; + } const char *nulp = memchr(m->data, '\0', m->size); - if (! nulp) + if (! nulp) { + errval = EINVAL; goto err; - if (labels_out && config_get_lines(m->data, labels_out, 0) < 0) + } + if (labels_out && config_get_lines(m->data, labels_out, 0) < 0) { + errval = EINVAL; goto err; + } size_t offset = nulp - m->data + 1; tor_assert(offset <= m->size); *data_out = (const uint8_t *)(m->data + offset); @@ -388,6 +404,7 @@ storage_dir_map_labeled(storage_dir_t *dir, return m; err: tor_munmap_file(m); + errno = errval; return NULL; } diff --git a/src/or/channel.c b/src/or/channel.c index 0b5a7fde90..7590ba8a5b 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -4726,6 +4726,16 @@ channel_set_circid_type,(channel_t *chan, } } +static int +channel_sort_by_ed25519_identity(const void **a_, const void **b_) +{ + const channel_t *a = *a_, + *b = *b_; + return fast_memcmp(&a->ed25519_identity.pubkey, + &b->ed25519_identity.pubkey, + sizeof(a->ed25519_identity.pubkey)); +} + /** Helper for channel_update_bad_for_new_circs(): Perform the * channel_update_bad_for_new_circs operation on all channels in <b>lst</b>, * all of which MUST have the same RSA ID. (They MAY have different @@ -4734,44 +4744,52 @@ static void channel_rsa_id_group_set_badness(struct channel_list_s *lst, int force) { /*XXXX This function should really be about channels. 15056 */ - channel_t *chan; + channel_t *chan = TOR_LIST_FIRST(lst); + + if (!chan) + return; + + /* if there is only one channel, don't bother looping */ + if (PREDICT_LIKELY(!TOR_LIST_NEXT(chan, next_with_same_id))) { + connection_or_single_set_badness_( + time(NULL), BASE_CHAN_TO_TLS(chan)->conn, force); + return; + } + + smartlist_t *channels = smartlist_new(); - /* First, get a minimal list of the ed25519 identites */ - smartlist_t *ed_identities = smartlist_new(); TOR_LIST_FOREACH(chan, lst, next_with_same_id) { - uint8_t *id_copy = - tor_memdup(&chan->ed25519_identity.pubkey, DIGEST256_LEN); - smartlist_add(ed_identities, id_copy); + if (BASE_CHAN_TO_TLS(chan)->conn) { + smartlist_add(channels, chan); + } } - smartlist_sort_digests256(ed_identities); - smartlist_uniq_digests256(ed_identities); - /* Now, for each Ed identity, build a smartlist and find the best entry on - * it. */ + smartlist_sort(channels, channel_sort_by_ed25519_identity); + + const ed25519_public_key_t *common_ed25519_identity = NULL; + /* it would be more efficient to do a slice, but this case is rare */ smartlist_t *or_conns = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(ed_identities, const uint8_t *, ed_id) { - TOR_LIST_FOREACH(chan, lst, next_with_same_id) { - channel_tls_t *chantls = BASE_CHAN_TO_TLS(chan); - if (tor_memneq(ed_id, &chan->ed25519_identity.pubkey, DIGEST256_LEN)) - continue; - or_connection_t *orconn = chantls->conn; - if (orconn) { - tor_assert(orconn->chan == chantls); - smartlist_add(or_conns, orconn); - } + SMARTLIST_FOREACH_BEGIN(channels, channel_t *, channel) { + if (!common_ed25519_identity) + common_ed25519_identity = &channel->ed25519_identity; + + if (! ed25519_pubkey_eq(&channel->ed25519_identity, + common_ed25519_identity)) { + connection_or_group_set_badness_(or_conns, force); + smartlist_clear(or_conns); + common_ed25519_identity = &channel->ed25519_identity; } - connection_or_group_set_badness_(or_conns, force); - smartlist_clear(or_conns); - } SMARTLIST_FOREACH_END(ed_id); + smartlist_add(or_conns, BASE_CHAN_TO_TLS(channel)->conn); + } SMARTLIST_FOREACH_END(channel); + + connection_or_group_set_badness_(or_conns, force); /* XXXX 15056 we may want to do something special with connections that have * no set Ed25519 identity! */ smartlist_free(or_conns); - - SMARTLIST_FOREACH(ed_identities, uint8_t *, ed_id, tor_free(ed_id)); - smartlist_free(ed_identities); + smartlist_free(channels); } /** Go through all the channels (or if <b>digest</b> is non-NULL, just diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 7f0bcc4150..2e6b63b4d6 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2707,12 +2707,16 @@ extend_info_from_node(const node_t *node, int for_direct_connect) node_describe(node)); } + /* Retrieve the curve25519 pubkey. */ + const curve25519_public_key_t *curve_pubkey = + node_get_curve25519_onion_key(node); + if (valid_addr && node->ri) return extend_info_new(node->ri->nickname, node->identity, ed_pubkey, node->ri->onion_pkey, - node->ri->onion_curve25519_pkey, + curve_pubkey, &ap.addr, ap.port); else if (valid_addr && node->rs && node->md) @@ -2720,7 +2724,7 @@ extend_info_from_node(const node_t *node, int for_direct_connect) node->identity, ed_pubkey, node->md->onion_pkey, - node->md->onion_curve25519_pkey, + curve_pubkey, &ap.addr, ap.port); else diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index 923a6d794f..b8421a3c7e 100644 --- a/src/or/circuitstats.c +++ b/src/or/circuitstats.c @@ -431,9 +431,14 @@ circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, if (num > 0) { if (num != cbt->liveness.num_recent_circs) { int8_t *recent_circs; - log_notice(LD_CIRC, "The Tor Directory Consensus has changed how many " - "circuits we must track to detect network failures from %d " - "to %d.", cbt->liveness.num_recent_circs, num); + if (cbt->liveness.num_recent_circs > 0) { + log_notice(LD_CIRC, "The Tor Directory Consensus has changed how " + "many circuits we must track to detect network failures " + "from %d to %d.", cbt->liveness.num_recent_circs, num); + } else { + log_notice(LD_CIRC, "Upon receiving a consensus directory, " + "re-enabling circuit-based network failure detection."); + } tor_assert(cbt->liveness.timeouts_after_firsthop || cbt->liveness.num_recent_circs == 0); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 62cc865f48..aa0df95652 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -543,8 +543,7 @@ circuit_expire_building(void) cutoff = begindir_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) cutoff = close_cutoff; - else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || - victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) + else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) cutoff = c_intro_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) cutoff = s_intro_cutoff; @@ -661,12 +660,13 @@ circuit_expire_building(void) TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED; break; case CIRCUIT_PURPOSE_C_INTRODUCING: - /* We keep old introducing circuits around for - * a while in parallel, and they can end up "opened". - * We decide below if we're going to mark them timed - * out and eventually close them. - */ - break; + /* That purpose means that the intro point circuit has been opened + * succesfully but the INTRODUCE1 cell hasn't been sent yet because + * the client is waiting for the rendezvous point circuit to open. + * Keep this circuit open while waiting for the rendezvous circuit. + * We let the circuit idle timeout take care of cleaning this + * circuit if it never used. */ + continue; case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: @@ -757,8 +757,6 @@ circuit_expire_building(void) NULL) break; /* fallthrough! */ - case CIRCUIT_PURPOSE_C_INTRODUCING: - /* connection_ap_handshake_attach_circuit() will relaunch for us */ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: /* If we have reached this line, we want to spare the circ for now. */ diff --git a/src/or/config.c b/src/or/config.c index 16e6445616..d589876785 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1094,6 +1094,7 @@ static const char *default_authorities[] = { "199.58.81.140:80 74A9 1064 6BCE EFBC D2E8 74FC 1DC9 9743 0F96 8145", "bastet orport=443 " "v3ident=27102BC123E7AF1D4741AE047E160C91ADC76B21 " + "ipv6=[2620:13:4000:6000::1000:118]:443 " "204.13.164.118:80 24E2 F139 121D 4394 C54B 5BCC 368B 3B41 1857 C413", NULL }; @@ -1858,7 +1859,7 @@ options_act(const or_options_t *old_options) "given FD."); return -1; } -#endif +#endif /* defined(_WIN32) */ } /* Load state */ @@ -6584,7 +6585,6 @@ port_cfg_new(size_t namelen) cfg->entry_cfg.ipv6_traffic = 1; cfg->entry_cfg.dns_request = 1; cfg->entry_cfg.onion_traffic = 1; - cfg->entry_cfg.cache_ipv4_answers = 1; cfg->entry_cfg.prefer_ipv6_virtaddr = 1; return cfg; } @@ -6900,7 +6900,7 @@ parse_port_config(smartlist_t *out, bind_ipv4_only = 0, bind_ipv6_only = 0, ipv4_traffic = 1, ipv6_traffic = 1, prefer_ipv6 = 0, dns_request = 1, onion_traffic = 1, - cache_ipv4 = 1, use_cached_ipv4 = 0, + cache_ipv4 = 0, use_cached_ipv4 = 0, cache_ipv6 = 0, use_cached_ipv6 = 0, prefer_ipv6_automap = 1, world_writable = 0, group_writable = 0, relax_dirmode_check = 0, diff --git a/src/or/confparse.h b/src/or/confparse.h index 6f0b3b325c..022886e811 100644 --- a/src/or/confparse.h +++ b/src/or/confparse.h @@ -68,7 +68,7 @@ typedef union { config_line_t **LINELIST_V; routerset_t **ROUTERSET; } confparse_dummy_values_t; -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** An abbreviation for a configuration option allowed on the command line. */ typedef struct config_abbrev_t { @@ -132,13 +132,13 @@ typedef struct config_var_t { { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL, { .INT=NULL } } #define DUMMY_TYPECHECK_INSTANCE(tp) \ static tp tp ## _dummy -#else +#else /* !(defined(TOR_UNIT_TESTS)) */ #define CONF_TEST_MEMBERS(tp, conftype, member) #define END_OF_CONFIG_VARS { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } /* Repeatedly declarable incomplete struct to absorb redundant semicolons */ #define DUMMY_TYPECHECK_INSTANCE(tp) \ struct tor_semicolon_eater -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Type of a callback to validate whether a given configuration is * well-formed and consistent. See options_trial_assign() for documentation diff --git a/src/or/connection.c b/src/or/connection.c index 24f5a64622..92ccd3d5c8 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -768,6 +768,10 @@ connection_close_immediate(connection_t *conn) connection_unregister_events(conn); + /* Prevent the event from getting unblocked. */ + conn->read_blocked_on_bw = + conn->write_blocked_on_bw = 0; + if (SOCKET_OK(conn->s)) tor_close_socket(conn->s); conn->s = TOR_INVALID_SOCKET; diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 7af1f2b645..fdf1b2ebb1 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -965,6 +965,36 @@ connection_or_mark_bad_for_new_circs(or_connection_t *or_conn) * too old for new circuits? */ #define TIME_BEFORE_OR_CONN_IS_TOO_OLD (60*60*24*7) +/** Expire an or_connection if it is too old. Helper for + * connection_or_group_set_badness_ and fast path for + * channel_rsa_id_group_set_badness. + * + * Returns 1 if the connection was already expired, else 0. + */ +int +connection_or_single_set_badness_(time_t now, + or_connection_t *or_conn, + int force) +{ + /* XXXX this function should also be about channels? */ + if (or_conn->base_.marked_for_close || + connection_or_is_bad_for_new_circs(or_conn)) + return 1; + + if (force || + or_conn->base_.timestamp_created + TIME_BEFORE_OR_CONN_IS_TOO_OLD + < now) { + log_info(LD_OR, + "Marking OR conn to %s:%d as too old for new circuits " + "(fd "TOR_SOCKET_T_FORMAT", %d secs old).", + or_conn->base_.address, or_conn->base_.port, or_conn->base_.s, + (int)(now - or_conn->base_.timestamp_created)); + connection_or_mark_bad_for_new_circs(or_conn); + } + + return 0; +} + /** Given a list of all the or_connections with a given * identity, set elements of that list as is_bad_for_new_circs as * appropriate. Helper for connection_or_set_bad_connections(). @@ -995,19 +1025,8 @@ connection_or_group_set_badness_(smartlist_t *group, int force) /* Pass 1: expire everything that's old, and see what the status of * everything else is. */ SMARTLIST_FOREACH_BEGIN(group, or_connection_t *, or_conn) { - if (or_conn->base_.marked_for_close || - connection_or_is_bad_for_new_circs(or_conn)) + if (connection_or_single_set_badness_(now, or_conn, force)) continue; - if (force || - or_conn->base_.timestamp_created + TIME_BEFORE_OR_CONN_IS_TOO_OLD - < now) { - log_info(LD_OR, - "Marking OR conn to %s:%d as too old for new circuits " - "(fd "TOR_SOCKET_T_FORMAT", %d secs old).", - or_conn->base_.address, or_conn->base_.port, or_conn->base_.s, - (int)(now - or_conn->base_.timestamp_created)); - connection_or_mark_bad_for_new_circs(or_conn); - } if (connection_or_is_bad_for_new_circs(or_conn)) { ++n_old; diff --git a/src/or/connection_or.h b/src/or/connection_or.h index ee66b7c528..644df5c2c9 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -112,6 +112,9 @@ void var_cell_free(var_cell_t *cell); #define MIN_LINK_PROTO_FOR_CHANNEL_PADDING 5 #define MAX_LINK_PROTO MIN_LINK_PROTO_FOR_CHANNEL_PADDING +int connection_or_single_set_badness_(time_t now, + or_connection_t *or_conn, + int force); void connection_or_group_set_badness_(smartlist_t *group, int force); #ifdef TOR_UNIT_TESTS diff --git a/src/or/conscache.c b/src/or/conscache.c index 1407d0f0b9..9a90ff4dd2 100644 --- a/src/or/conscache.c +++ b/src/or/conscache.c @@ -539,9 +539,20 @@ consensus_cache_rescan(consensus_cache_t *cache) map = storage_dir_map_labeled(cache->dir, fname, &labels, &body, &bodylen); if (! map) { - /* Can't load this; continue */ - log_warn(LD_FS, "Unable to map file %s from consensus cache: %s", - escaped(fname), strerror(errno)); + /* The ERANGE error might come from tor_mmap_file() -- it means the file + * was empty. EINVAL might come from ..map_labeled() -- it means the + * file was misformatted. In both cases, we should just delete it. + */ + if (errno == ERANGE || errno == EINVAL) { + log_warn(LD_FS, "Found %s file %s in consensus cache; removing it.", + errno == ERANGE ? "empty" : "misformatted", + escaped(fname)); + storage_dir_remove_file(cache->dir, fname); + } else { + /* Can't load this; continue */ + log_warn(LD_FS, "Unable to map file %s from consensus cache: %s", + escaped(fname), strerror(errno)); + } continue; } consensus_cache_entry_t *ent = diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c index 1d63d59577..e539b61484 100644 --- a/src/or/consdiffmgr.c +++ b/src/or/consdiffmgr.c @@ -283,6 +283,10 @@ cdm_diff_ht_set_status(consensus_flavor_t flav, int status, consensus_cache_entry_handle_t *handle) { + if (handle == NULL) { + tor_assert_nonfatal(status != CDM_DIFF_PRESENT); + } + struct cdm_diff_t search, *ent; memset(&search, 0, sizeof(cdm_diff_t)); search.flavor = flav; @@ -1589,8 +1593,13 @@ consensus_diff_worker_replyfn(void *work_) for (u = 0; u < ARRAY_LENGTH(handles); ++u) { compress_method_t method = compress_diffs_with[u]; if (cache) { - cdm_diff_ht_set_status(flav, from_sha3, to_sha3, method, status, - handles[u]); + consensus_cache_entry_handle_t *h = handles[u]; + int this_status = status; + if (h == NULL) { + this_status = CDM_DIFF_ERROR; + } + tor_assert_nonfatal(h != NULL || this_status == CDM_DIFF_ERROR); + cdm_diff_ht_set_status(flav, from_sha3, to_sha3, method, this_status, h); } else { consensus_cache_entry_handle_free(handles[u]); } diff --git a/src/or/control.c b/src/or/control.c index e17e923650..de08b2ee7d 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -58,7 +58,9 @@ #include "entrynodes.h" #include "geoip.h" #include "hibernate.h" +#include "hs_cache.h" #include "hs_common.h" +#include "hs_control.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -2014,36 +2016,89 @@ getinfo_helper_dir(control_connection_t *control_conn, SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); smartlist_free(sl); } else if (!strcmpstart(question, "hs/client/desc/id/")) { - rend_cache_entry_t *e = NULL; + hostname_type_t addr_type; question += strlen("hs/client/desc/id/"); - if (strlen(question) != REND_SERVICE_ID_LEN_BASE32) { + if (rend_valid_v2_service_id(question)) { + addr_type = ONION_V2_HOSTNAME; + } else if (hs_address_is_valid(question)) { + addr_type = ONION_V3_HOSTNAME; + } else { *errmsg = "Invalid address"; return -1; } - if (!rend_cache_lookup_entry(question, -1, &e)) { - /* Descriptor found in cache */ - *answer = tor_strdup(e->desc); + if (addr_type == ONION_V2_HOSTNAME) { + rend_cache_entry_t *e = NULL; + if (!rend_cache_lookup_entry(question, -1, &e)) { + /* Descriptor found in cache */ + *answer = tor_strdup(e->desc); + } else { + *errmsg = "Not found in cache"; + return -1; + } } else { - *errmsg = "Not found in cache"; - return -1; + ed25519_public_key_t service_pk; + const char *desc; + + /* The check before this if/else makes sure of this. */ + tor_assert(addr_type == ONION_V3_HOSTNAME); + + if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) { + *errmsg = "Invalid v3 address"; + return -1; + } + + desc = hs_cache_lookup_encoded_as_client(&service_pk); + if (desc) { + *answer = tor_strdup(desc); + } else { + *errmsg = "Not found in cache"; + return -1; + } } } else if (!strcmpstart(question, "hs/service/desc/id/")) { - rend_cache_entry_t *e = NULL; + hostname_type_t addr_type; question += strlen("hs/service/desc/id/"); - if (strlen(question) != REND_SERVICE_ID_LEN_BASE32) { + if (rend_valid_v2_service_id(question)) { + addr_type = ONION_V2_HOSTNAME; + } else if (hs_address_is_valid(question)) { + addr_type = ONION_V3_HOSTNAME; + } else { *errmsg = "Invalid address"; return -1; } + rend_cache_entry_t *e = NULL; - if (!rend_cache_lookup_v2_desc_as_service(question, &e)) { - /* Descriptor found in cache */ - *answer = tor_strdup(e->desc); + if (addr_type == ONION_V2_HOSTNAME) { + if (!rend_cache_lookup_v2_desc_as_service(question, &e)) { + /* Descriptor found in cache */ + *answer = tor_strdup(e->desc); + } else { + *errmsg = "Not found in cache"; + return -1; + } } else { - *errmsg = "Not found in cache"; - return -1; + ed25519_public_key_t service_pk; + char *desc; + + /* The check before this if/else makes sure of this. */ + tor_assert(addr_type == ONION_V3_HOSTNAME); + + if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) { + *errmsg = "Invalid v3 address"; + return -1; + } + + desc = hs_service_lookup_current_desc(&service_pk); + if (desc) { + /* Newly allocated string, we have ownership. */ + *answer = desc; + } else { + *errmsg = "Not found in cache"; + return -1; + } } } else if (!strcmpstart(question, "md/id/")) { const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0); @@ -2624,9 +2679,16 @@ circuit_describe_status_for_controller(origin_circuit_t *circ) } } - if (circ->rend_data != NULL) { - smartlist_add_asprintf(descparts, "REND_QUERY=%s", - rend_data_get_address(circ->rend_data)); + if (circ->rend_data != NULL || circ->hs_ident != NULL) { + char addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + const char *onion_address; + if (circ->rend_data) { + onion_address = rend_data_get_address(circ->rend_data); + } else { + hs_build_address(&circ->hs_ident->identity_pk, HS_VERSION_THREE, addr); + onion_address = addr; + } + smartlist_add_asprintf(descparts, "REND_QUERY=%s", onion_address); } { @@ -4277,9 +4339,11 @@ handle_control_hspost(control_connection_t *conn, const char *body) { static const char *opt_server = "SERVER="; + static const char *opt_hsaddress = "HSADDRESS="; smartlist_t *hs_dirs = NULL; const char *encoded_desc = body; size_t encoded_desc_len = len; + const char *onion_address = NULL; char *cp = memchr(body, '\n', len); if (cp == NULL) { @@ -4309,15 +4373,16 @@ handle_control_hspost(control_connection_t *conn, server); goto done; } - if (!node->rs->is_hs_dir) { - connection_printf_to_buf(conn, "552 Server \"%s\" is not a HSDir" - "\r\n", server); - goto done; - } /* Valid server, add it to our local list. */ if (!hs_dirs) hs_dirs = smartlist_new(); smartlist_add(hs_dirs, node->rs); + } else if (!strcasecmpstart(arg, opt_hsaddress)) { + if (!hs_address_is_valid(arg)) { + connection_printf_to_buf(conn, "512 Malformed onion address\r\n"); + goto done; + } + onion_address = arg; } else { connection_printf_to_buf(conn, "512 Unexpected argument \"%s\"\r\n", arg); @@ -4326,6 +4391,19 @@ handle_control_hspost(control_connection_t *conn, } SMARTLIST_FOREACH_END(arg); } + /* Handle the v3 case. */ + if (onion_address) { + char *desc_str = NULL; + read_escaped_data(encoded_desc, encoded_desc_len, &desc_str); + if (hs_control_hspost_command(desc_str, onion_address, hs_dirs) < 0) { + connection_printf_to_buf(conn, "554 Invalid descriptor\r\n"); + } + tor_free(desc_str); + goto done; + } + + /* From this point on, it is only v2. */ + /* Read the dot encoded descriptor, and parse it. */ rend_encoded_v2_service_descriptor_t *desc = tor_malloc_zero(sizeof(rend_encoded_v2_service_descriptor_t)); @@ -4370,6 +4448,52 @@ handle_control_hspost(control_connection_t *conn, return 0; } +/* Helper function for ADD_ONION that adds an ephemeral service depending on + * the given hs_version. + * + * The secret key in pk depends on the hs_version. The ownership of the key + * used in pk is given to the HS subsystem so the caller must stop accessing + * it after. + * + * The port_cfgs is a list of service port. Ownership transfered to service. + * The max_streams refers to the MaxStreams= key. + * The max_streams_close_circuit refers to the MaxStreamsCloseCircuit key. + * The auth_type is the authentication type of the clients in auth_clients. + * The ownership of that list is transfered to the service. + * + * On success (RSAE_OKAY), the address_out points to a newly allocated string + * containing the onion address without the .onion part. On error, address_out + * is untouched. */ +static hs_service_add_ephemeral_status_t +add_onion_helper_add_service(int hs_version, + add_onion_secret_key_t *pk, + smartlist_t *port_cfgs, int max_streams, + int max_streams_close_circuit, int auth_type, + smartlist_t *auth_clients, char **address_out) +{ + hs_service_add_ephemeral_status_t ret; + + tor_assert(pk); + tor_assert(port_cfgs); + tor_assert(address_out); + + switch (hs_version) { + case HS_VERSION_TWO: + ret = rend_service_add_ephemeral(pk->v2, port_cfgs, max_streams, + max_streams_close_circuit, auth_type, + auth_clients, address_out); + break; + case HS_VERSION_THREE: + ret = hs_service_add_ephemeral(pk->v3, port_cfgs, max_streams, + max_streams_close_circuit, address_out); + break; + default: + tor_assert_unreached(); + } + + return ret; +} + /** Called when we get a ADD_ONION command; parse the body, and set up * the new ephemeral Onion Service. */ static int @@ -4551,15 +4675,15 @@ handle_control_add_onion(control_connection_t *conn, } /* Parse the "keytype:keyblob" argument. */ - crypto_pk_t *pk = NULL; + int hs_version = 0; + add_onion_secret_key_t pk; const char *key_new_alg = NULL; char *key_new_blob = NULL; char *err_msg = NULL; - pk = add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, - &key_new_alg, &key_new_blob, - &err_msg); - if (!pk) { + if (add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, + &key_new_alg, &key_new_blob, &pk, &hs_version, + &err_msg) < 0) { if (err_msg) { connection_write_str_to_buf(err_msg, conn); tor_free(err_msg); @@ -4568,16 +4692,23 @@ handle_control_add_onion(control_connection_t *conn, } tor_assert(!err_msg); + /* Hidden service version 3 don't have client authentication support so if + * ClientAuth was given, send back an error. */ + if (hs_version == HS_VERSION_THREE && auth_clients) { + connection_printf_to_buf(conn, "513 ClientAuth not supported\r\n"); + goto out; + } + /* Create the HS, using private key pk, client authentication auth_type, * the list of auth_clients, and port config port_cfg. * rend_service_add_ephemeral() will take ownership of pk and port_cfg, * regardless of success/failure. */ char *service_id = NULL; - int ret = rend_service_add_ephemeral(pk, port_cfgs, max_streams, - max_streams_close_circuit, - auth_type, auth_clients, - &service_id); + int ret = add_onion_helper_add_service(hs_version, &pk, port_cfgs, + max_streams, + max_streams_close_circuit, auth_type, + auth_clients, &service_id); port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */ auth_clients = NULL; /* so is auth_clients */ switch (ret) { @@ -4670,9 +4801,10 @@ handle_control_add_onion(control_connection_t *conn, * Note: The error messages returned are deliberately vague to avoid echoing * key material. */ -STATIC crypto_pk_t * +STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, const char **key_new_alg_out, char **key_new_blob_out, + add_onion_secret_key_t *decoded_key, int *hs_version, char **err_msg_out) { smartlist_t *key_args = smartlist_new(); @@ -4680,7 +4812,7 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, const char *key_new_alg = NULL; char *key_new_blob = NULL; char *err_msg = NULL; - int ok = 0; + int ret = -1; smartlist_split_string(key_args, arg, ":", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(key_args) != 2) { @@ -4692,6 +4824,7 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, static const char *key_type_new = "NEW"; static const char *key_type_best = "BEST"; static const char *key_type_rsa1024 = "RSA1024"; + static const char *key_type_ed25519_v3 = "ED25519-V3"; const char *key_type = smartlist_get(key_args, 0); const char *key_blob = smartlist_get(key_args, 1); @@ -4704,9 +4837,23 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, goto err; } if (crypto_pk_num_bits(pk) != PK_BYTES*8) { + crypto_pk_free(pk); err_msg = tor_strdup("512 Invalid RSA key size\r\n"); goto err; } + decoded_key->v2 = pk; + *hs_version = HS_VERSION_TWO; + } else if (!strcasecmp(key_type_ed25519_v3, key_type)) { + /* "ED25519-V3:<Base64 Blob>" - Loading a pre-existing ed25519 key. */ + ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); + if (base64_decode((char *) sk->seckey, sizeof(sk->seckey), key_blob, + strlen(key_blob)) != sizeof(sk->seckey)) { + tor_free(sk); + err_msg = tor_strdup("512 Failed to decode ED25519-V3 key\r\n"); + goto err; + } + decoded_key->v3 = sk; + *hs_version = HS_VERSION_THREE; } else if (!strcasecmp(key_type_new, key_type)) { /* "NEW:<Algorithm>" - Generating a new key, blob as algorithm. */ if (!strcasecmp(key_type_rsa1024, key_blob) || @@ -4720,12 +4867,38 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, } if (!discard_pk) { if (crypto_pk_base64_encode(pk, &key_new_blob)) { + crypto_pk_free(pk); tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", key_type_rsa1024); goto err; } key_new_alg = key_type_rsa1024; } + decoded_key->v2 = pk; + *hs_version = HS_VERSION_TWO; + } else if (!strcasecmp(key_type_ed25519_v3, key_blob)) { + ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); + if (ed25519_secret_key_generate(sk, 1) < 0) { + tor_free(sk); + tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", + key_type_ed25519_v3); + goto err; + } + if (!discard_pk) { + ssize_t len = base64_encode_size(sizeof(sk->seckey), 0) + 1; + key_new_blob = tor_malloc_zero(len); + if (base64_encode(key_new_blob, len, (const char *) sk->seckey, + sizeof(sk->seckey), 0) != (len - 1)) { + tor_free(sk); + tor_free(key_new_blob); + tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", + key_type_ed25519_v3); + goto err; + } + key_new_alg = key_type_ed25519_v3; + } + decoded_key->v3 = sk; + *hs_version = HS_VERSION_THREE; } else { err_msg = tor_strdup("513 Invalid key type\r\n"); goto err; @@ -4736,8 +4909,7 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, } /* Succeded in loading or generating a private key. */ - tor_assert(pk); - ok = 1; + ret = 0; err: SMARTLIST_FOREACH(key_args, char *, cp, { @@ -4746,10 +4918,6 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, }); smartlist_free(key_args); - if (!ok) { - crypto_pk_free(pk); - pk = NULL; - } if (err_msg_out) { *err_msg_out = err_msg; } else { @@ -4758,7 +4926,7 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, *key_new_alg_out = key_new_alg; *key_new_blob_out = key_new_blob; - return pk; + return ret; } /** Helper function to handle parsing a ClientAuth argument to the @@ -4827,6 +4995,7 @@ handle_control_del_onion(control_connection_t *conn, uint32_t len, const char *body) { + int hs_version = 0; smartlist_t *args; (void) len; /* body is nul-terminated; it's safe to ignore the length */ args = getargs_helper("DEL_ONION", conn, body, 1, 1); @@ -4834,7 +5003,11 @@ handle_control_del_onion(control_connection_t *conn, return 0; const char *service_id = smartlist_get(args, 0); - if (!rend_valid_v2_service_id(service_id)) { + if (rend_valid_v2_service_id(service_id)) { + hs_version = HS_VERSION_TWO; + } else if (hs_address_is_valid(service_id)) { + hs_version = HS_VERSION_THREE; + } else { connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); goto out; } @@ -4861,8 +5034,20 @@ handle_control_del_onion(control_connection_t *conn, if (onion_services == NULL) { connection_printf_to_buf(conn, "552 Unknown Onion Service id\r\n"); } else { - int ret = rend_service_del_ephemeral(service_id); - if (ret) { + int ret = -1; + switch (hs_version) { + case HS_VERSION_TWO: + ret = rend_service_del_ephemeral(service_id); + break; + case HS_VERSION_THREE: + ret = hs_service_del_ephemeral(service_id); + break; + default: + /* The ret value will be -1 thus hitting the warning below. This should + * never happen because of the check at the start of the function. */ + break; + } + if (ret < 0) { /* This should *NEVER* fail, since the service is on either the * per-control connection list, or the global one. */ @@ -4932,9 +5117,16 @@ connection_control_closed(control_connection_t *conn) * The list and it's contents are scrubbed/freed in connection_free_. */ if (conn->ephemeral_onion_services) { - SMARTLIST_FOREACH(conn->ephemeral_onion_services, char *, cp, { - rend_service_del_ephemeral(cp); - }); + SMARTLIST_FOREACH_BEGIN(conn->ephemeral_onion_services, char *, cp) { + if (rend_valid_v2_service_id(cp)) { + rend_service_del_ephemeral(cp); + } else if (hs_address_is_valid(cp)) { + hs_service_del_ephemeral(cp); + } else { + /* An invalid .onion in our list should NEVER happen */ + tor_fragile_assert(); + } + } SMARTLIST_FOREACH_END(cp); } if (conn->is_owning_control_connection) { @@ -7012,27 +7204,33 @@ rend_hsaddress_str_or_unknown(const char *onion_address) * <b>rend_query</b> is used to fetch requested onion address and auth type. * <b>hs_dir</b> is the description of contacting hs directory. * <b>desc_id_base32</b> is the ID of requested hs descriptor. + * <b>hsdir_index</b> is the HSDir fetch index value for v3, an hex string. */ void -control_event_hs_descriptor_requested(const rend_data_t *rend_query, +control_event_hs_descriptor_requested(const char *onion_address, + rend_auth_type_t auth_type, const char *id_digest, - const char *desc_id_base32) + const char *desc_id, + const char *hsdir_index) { - if (!id_digest || !rend_query || !desc_id_base32) { - log_warn(LD_BUG, "Called with rend_query==%p, " - "id_digest==%p, desc_id_base32==%p", - rend_query, id_digest, desc_id_base32); + char *hsdir_index_field = NULL; + + if (BUG(!id_digest || !desc_id)) { return; } + if (hsdir_index) { + tor_asprintf(&hsdir_index_field, " HSDIR_INDEX=%s", hsdir_index); + } + send_control_event(EVENT_HS_DESC, - "650 HS_DESC REQUESTED %s %s %s %s\r\n", - rend_hsaddress_str_or_unknown( - rend_data_get_address(rend_query)), - rend_auth_type_to_string( - TO_REND_DATA_V2(rend_query)->auth_type), + "650 HS_DESC REQUESTED %s %s %s %s%s\r\n", + rend_hsaddress_str_or_unknown(onion_address), + rend_auth_type_to_string(auth_type), node_describe_longname_by_id(id_digest), - desc_id_base32); + desc_id, + hsdir_index_field ? hsdir_index_field : ""); + tor_free(hsdir_index_field); } /** For an HS descriptor query <b>rend_data</b>, using the @@ -7081,89 +7279,87 @@ get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp) /** send HS_DESC CREATED event when a local service generates a descriptor. * - * <b>service_id</b> is the descriptor onion address. - * <b>desc_id_base32</b> is the descriptor ID. - * <b>replica</b> is the the descriptor replica number. + * <b>onion_address</b> is service address. + * <b>desc_id</b> is the descriptor ID. + * <b>replica</b> is the the descriptor replica number. If it is negative, it + * is ignored. */ void -control_event_hs_descriptor_created(const char *service_id, - const char *desc_id_base32, +control_event_hs_descriptor_created(const char *onion_address, + const char *desc_id, int replica) { - if (!service_id || !desc_id_base32) { - log_warn(LD_BUG, "Called with service_digest==%p, " - "desc_id_base32==%p", service_id, desc_id_base32); + char *replica_field = NULL; + + if (BUG(!onion_address || !desc_id)) { return; } + if (replica >= 0) { + tor_asprintf(&replica_field, " REPLICA=%d", replica); + } + send_control_event(EVENT_HS_DESC, - "650 HS_DESC CREATED %s UNKNOWN UNKNOWN %s " - "REPLICA=%d\r\n", - service_id, - desc_id_base32, - replica); + "650 HS_DESC CREATED %s UNKNOWN UNKNOWN %s%s\r\n", + onion_address, desc_id, + replica_field ? replica_field : ""); + tor_free(replica_field); } /** send HS_DESC upload event. * - * <b>service_id</b> is the descriptor onion address. + * <b>onion_address</b> is service address. * <b>hs_dir</b> is the description of contacting hs directory. - * <b>desc_id_base32</b> is the ID of requested hs descriptor. + * <b>desc_id</b> is the ID of requested hs descriptor. */ void -control_event_hs_descriptor_upload(const char *service_id, +control_event_hs_descriptor_upload(const char *onion_address, const char *id_digest, - const char *desc_id_base32) + const char *desc_id, + const char *hsdir_index) { - if (!service_id || !id_digest || !desc_id_base32) { - log_warn(LD_BUG, "Called with service_digest==%p, " - "desc_id_base32==%p, id_digest==%p", service_id, - desc_id_base32, id_digest); + char *hsdir_index_field = NULL; + + if (BUG(!onion_address || !id_digest || !desc_id)) { return; } + if (hsdir_index) { + tor_asprintf(&hsdir_index_field, " HSDIR_INDEX=%s", hsdir_index); + } + send_control_event(EVENT_HS_DESC, - "650 HS_DESC UPLOAD %s UNKNOWN %s %s\r\n", - service_id, + "650 HS_DESC UPLOAD %s UNKNOWN %s %s%s\r\n", + onion_address, node_describe_longname_by_id(id_digest), - desc_id_base32); + desc_id, + hsdir_index_field ? hsdir_index_field : ""); + tor_free(hsdir_index_field); } /** send HS_DESC event after got response from hs directory. * * NOTE: this is an internal function used by following functions: - * control_event_hs_descriptor_received - * control_event_hs_descriptor_failed + * control_event_hsv2_descriptor_received + * control_event_hsv2_descriptor_failed + * control_event_hsv3_descriptor_failed * * So do not call this function directly. */ -void -control_event_hs_descriptor_receive_end(const char *action, - const char *onion_address, - const rend_data_t *rend_data, - const char *id_digest, - const char *reason) +static void +event_hs_descriptor_receive_end(const char *action, + const char *onion_address, + const char *desc_id, + rend_auth_type_t auth_type, + const char *hsdir_id_digest, + const char *reason) { - char *desc_id_field = NULL; char *reason_field = NULL; - char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - const char *desc_id = NULL; - if (!action || !rend_data || !onion_address) { - log_warn(LD_BUG, "Called with action==%p, rend_data==%p, " - "onion_address==%p", action, rend_data, onion_address); + if (BUG(!action || !onion_address)) { return; } - desc_id = get_desc_id_from_query(rend_data, id_digest); - if (desc_id != NULL) { - /* Set the descriptor ID digest to base32 so we can send it. */ - base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, - DIGEST_LEN); - /* Extra whitespace is needed before the value. */ - tor_asprintf(&desc_id_field, " %s", desc_id_base32); - } - if (reason) { tor_asprintf(&reason_field, " REASON=%s", reason); } @@ -7172,14 +7368,13 @@ control_event_hs_descriptor_receive_end(const char *action, "650 HS_DESC %s %s %s %s%s%s\r\n", action, rend_hsaddress_str_or_unknown(onion_address), - rend_auth_type_to_string( - TO_REND_DATA_V2(rend_data)->auth_type), - id_digest ? - node_describe_longname_by_id(id_digest) : "UNKNOWN", - desc_id_field ? desc_id_field : "", + rend_auth_type_to_string(auth_type), + hsdir_id_digest ? + node_describe_longname_by_id(hsdir_id_digest) : + "UNKNOWN", + desc_id ? desc_id : "", reason_field ? reason_field : ""); - tor_free(desc_id_field); tor_free(reason_field); } @@ -7199,9 +7394,7 @@ control_event_hs_descriptor_upload_end(const char *action, { char *reason_field = NULL; - if (!action || !id_digest) { - log_warn(LD_BUG, "Called with action==%p, id_digest==%p", action, - id_digest); + if (BUG(!action || !id_digest)) { return; } @@ -7224,17 +7417,54 @@ control_event_hs_descriptor_upload_end(const char *action, * called when we successfully received a hidden service descriptor. */ void -control_event_hs_descriptor_received(const char *onion_address, - const rend_data_t *rend_data, - const char *id_digest) +control_event_hsv2_descriptor_received(const char *onion_address, + const rend_data_t *rend_data, + const char *hsdir_id_digest) { - if (!rend_data || !id_digest || !onion_address) { - log_warn(LD_BUG, "Called with rend_data==%p, id_digest==%p, " - "onion_address==%p", rend_data, id_digest, onion_address); + char *desc_id_field = NULL; + const char *desc_id; + + if (BUG(!rend_data || !hsdir_id_digest || !onion_address)) { return; } - control_event_hs_descriptor_receive_end("RECEIVED", onion_address, - rend_data, id_digest, NULL); + + desc_id = get_desc_id_from_query(rend_data, hsdir_id_digest); + if (desc_id != NULL) { + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + /* Set the descriptor ID digest to base32 so we can send it. */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + /* Extra whitespace is needed before the value. */ + tor_asprintf(&desc_id_field, " %s", desc_id_base32); + } + + event_hs_descriptor_receive_end("RECEIVED", onion_address, desc_id_field, + TO_REND_DATA_V2(rend_data)->auth_type, + hsdir_id_digest, NULL); + tor_free(desc_id_field); +} + +/* Send HS_DESC RECEIVED event + * + * Called when we successfully received a hidden service descriptor. */ +void +control_event_hsv3_descriptor_received(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest) +{ + char *desc_id_field = NULL; + + if (BUG(!onion_address || !desc_id || !hsdir_id_digest)) { + return; + } + + /* Because DescriptorID is an optional positional value, we need to add a + * whitespace before in order to not be next to the HsDir value. */ + tor_asprintf(&desc_id_field, " %s", desc_id); + + event_hs_descriptor_receive_end("RECEIVED", onion_address, desc_id_field, + REND_NO_AUTH, hsdir_id_digest, NULL); + tor_free(desc_id_field); } /** send HS_DESC UPLOADED event @@ -7245,9 +7475,7 @@ void control_event_hs_descriptor_uploaded(const char *id_digest, const char *onion_address) { - if (!id_digest) { - log_warn(LD_BUG, "Called with id_digest==%p", - id_digest); + if (BUG(!id_digest)) { return; } @@ -7261,17 +7489,58 @@ control_event_hs_descriptor_uploaded(const char *id_digest, * add it to REASON= field. */ void -control_event_hs_descriptor_failed(const rend_data_t *rend_data, - const char *id_digest, - const char *reason) +control_event_hsv2_descriptor_failed(const rend_data_t *rend_data, + const char *hsdir_id_digest, + const char *reason) { - if (!rend_data) { - log_warn(LD_BUG, "Called with rend_data==%p", rend_data); + char *desc_id_field = NULL; + const char *desc_id; + + if (BUG(!rend_data)) { return; } - control_event_hs_descriptor_receive_end("FAILED", - rend_data_get_address(rend_data), - rend_data, id_digest, reason); + + desc_id = get_desc_id_from_query(rend_data, hsdir_id_digest); + if (desc_id != NULL) { + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + /* Set the descriptor ID digest to base32 so we can send it. */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + /* Extra whitespace is needed before the value. */ + tor_asprintf(&desc_id_field, " %s", desc_id_base32); + } + + event_hs_descriptor_receive_end("FAILED", rend_data_get_address(rend_data), + desc_id_field, + TO_REND_DATA_V2(rend_data)->auth_type, + hsdir_id_digest, reason); + tor_free(desc_id_field); +} + +/** Send HS_DESC event to inform controller that the query to + * <b>onion_address</b> failed to retrieve hidden service descriptor + * <b>desc_id</b> from directory identified by <b>hsdir_id_digest</b>. If + * NULL, "UNKNOWN" is used. If <b>reason</b> is not NULL, add it to REASON= + * field. */ +void +control_event_hsv3_descriptor_failed(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest, + const char *reason) +{ + char *desc_id_field = NULL; + + if (BUG(!onion_address || !desc_id || !reason)) { + return; + } + + /* Because DescriptorID is an optional positional value, we need to add a + * whitespace before in order to not be next to the HsDir value. */ + tor_asprintf(&desc_id_field, " %s", desc_id); + + event_hs_descriptor_receive_end("FAILED", onion_address, desc_id_field, + REND_NO_AUTH, hsdir_id_digest, reason); + tor_free(desc_id_field); } /** Send HS_DESC_CONTENT event after completion of a successful fetch from hs @@ -7321,9 +7590,7 @@ control_event_hs_descriptor_upload_failed(const char *id_digest, const char *onion_address, const char *reason) { - if (!id_digest) { - log_warn(LD_BUG, "Called with id_digest==%p", - id_digest); + if (BUG(!id_digest)) { return; } control_event_hs_descriptor_upload_end("FAILED", onion_address, diff --git a/src/or/control.h b/src/or/control.h index 7ec182cb78..28ffeaed86 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -115,32 +115,39 @@ void control_event_transport_launched(const char *mode, tor_addr_t *addr, uint16_t port); const char *rend_auth_type_to_string(rend_auth_type_t auth_type); MOCK_DECL(const char *, node_describe_longname_by_id,(const char *id_digest)); -void control_event_hs_descriptor_requested(const rend_data_t *rend_query, - const char *desc_id_base32, - const char *hs_dir); -void control_event_hs_descriptor_created(const char *service_id, - const char *desc_id_base32, +void control_event_hs_descriptor_requested(const char *onion_address, + rend_auth_type_t auth_type, + const char *id_digest, + const char *desc_id, + const char *hsdir_index); +void control_event_hs_descriptor_created(const char *onion_address, + const char *desc_id, int replica); -void control_event_hs_descriptor_upload(const char *service_id, - const char *desc_id_base32, - const char *hs_dir); -void control_event_hs_descriptor_receive_end(const char *action, - const char *onion_address, - const rend_data_t *rend_data, - const char *id_digest, - const char *reason); +void control_event_hs_descriptor_upload(const char *onion_address, + const char *desc_id, + const char *hs_dir, + const char *hsdir_index); void control_event_hs_descriptor_upload_end(const char *action, const char *onion_address, const char *hs_dir, const char *reason); -void control_event_hs_descriptor_received(const char *onion_address, - const rend_data_t *rend_data, - const char *id_digest); void control_event_hs_descriptor_uploaded(const char *hs_dir, const char *onion_address); -void control_event_hs_descriptor_failed(const rend_data_t *rend_data, - const char *id_digest, - const char *reason); +/* Hidden service v2 HS_DESC specific. */ +void control_event_hsv2_descriptor_failed(const rend_data_t *rend_data, + const char *id_digest, + const char *reason); +void control_event_hsv2_descriptor_received(const char *onion_address, + const rend_data_t *rend_data, + const char *id_digest); +/* Hidden service v3 HS_DESC specific. */ +void control_event_hsv3_descriptor_failed(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest, + const char *reason); +void control_event_hsv3_descriptor_received(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest); void control_event_hs_descriptor_upload_failed(const char *hs_dir, const char *onion_address, const char *reason); @@ -256,10 +263,22 @@ void format_cell_stats(char **event_string, circuit_t *circ, cell_stats_t *cell_stats); STATIC char *get_bw_samples(void); -STATIC crypto_pk_t *add_onion_helper_keyarg(const char *arg, int discard_pk, - const char **key_new_alg_out, - char **key_new_blob_out, - char **err_msg_out); +/* ADD_ONION secret key to create an ephemeral service. The command supports + * multiple versions so this union stores the key and passes it to the HS + * subsystem depending on the requested version. */ +typedef union add_onion_secret_key_t { + /* Hidden service v2 secret key. */ + crypto_pk_t *v2; + /* Hidden service v3 secret key. */ + ed25519_secret_key_t *v3; +} add_onion_secret_key_t; + +STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, + const char **key_new_alg_out, + char **key_new_blob_out, + add_onion_secret_key_t *decoded_key, + int *hs_version, char **err_msg_out); + STATIC rend_authorized_client_t * add_onion_helper_clientauth(const char *arg, int *created, char **err_msg_out); diff --git a/src/or/directory.c b/src/or/directory.c index e7a5da8139..1a7c016c3e 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -25,6 +25,7 @@ #include "geoip.h" #include "hs_cache.h" #include "hs_common.h" +#include "hs_control.h" #include "hs_client.h" #include "main.h" #include "microdesc.h" @@ -116,7 +117,8 @@ static void dir_routerdesc_download_failed(smartlist_t *failed, int was_extrainfo, int was_descriptor_digests); static void dir_microdesc_download_failed(smartlist_t *failed, - int status_code); + int status_code, + const char *dir_id); static int client_likes_consensus(const struct consensus_cache_entry_t *ent, const char *want_url); @@ -469,7 +471,7 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, log_warn(LD_BUG, "Called when we have UseBridges set."); if (should_use_directory_guards(options)) { - const node_t *node = guards_choose_dirguard(guard_state_out); + const node_t *node = guards_choose_dirguard(dir_purpose, guard_state_out); if (node) rs = node->rs; } else { @@ -603,7 +605,7 @@ directory_get_from_dirserver,( * sort of dir fetch we'll be doing, so it won't return a bridge * that can't answer our question. */ - const node_t *node = guards_choose_dirguard(&guard_state); + const node_t *node = guards_choose_dirguard(dir_purpose, &guard_state); if (node && node->ri) { /* every bridge has a routerinfo. */ routerinfo_t *ri = node->ri; @@ -1011,48 +1013,6 @@ directory_must_use_begindir(const or_options_t *options) return !public_server_mode(options); } -struct directory_request_t { - /** - * These fields specify which directory we're contacting. Routerstatus, - * if present, overrides the other fields. - * - * @{ */ - tor_addr_port_t or_addr_port; - tor_addr_port_t dir_addr_port; - char digest[DIGEST_LEN]; - - const routerstatus_t *routerstatus; - /** @} */ - /** One of DIR_PURPOSE_* other than DIR_PURPOSE_SERVER. Describes what - * kind of operation we'll be doing (upload/download), and of what kind - * of document. */ - uint8_t dir_purpose; - /** One of ROUTER_PURPOSE_*; used for uploads and downloads of routerinfo - * and extrainfo docs. */ - uint8_t router_purpose; - /** Enum: determines whether to anonymize, and whether to use dirport or - * orport. */ - dir_indirection_t indirection; - /** Alias to the variable part of the URL for this request */ - const char *resource; - /** Alias to the payload to upload (if any) */ - const char *payload; - /** Number of bytes to upload from payload</b> */ - size_t payload_len; - /** Value to send in an if-modified-since header, or 0 for none. */ - time_t if_modified_since; - /** Hidden-service-specific information v2. */ - const rend_data_t *rend_query; - /** Extra headers to append to the request */ - config_line_t *additional_headers; - /** Hidden-service-specific information for v3+. */ - const hs_ident_dir_conn_t *hs_ident; - /** Used internally to directory.c: gets informed when the attempt to - * connect to the directory succeeds or fails, if that attempt bears on the - * directory's usability as a directory guard. */ - circuit_guard_state_t *guard_state; -}; - /** Evaluate the situation and decide if we should use an encrypted * "begindir-style" connection for this directory request. * 0) If there is no DirPort, yes. @@ -2245,8 +2205,6 @@ static int handle_response_fetch_detached_signatures(dir_connection_t *, const response_handler_args_t *); static int handle_response_fetch_desc(dir_connection_t *, const response_handler_args_t *); -static int handle_response_fetch_microdesc(dir_connection_t *, - const response_handler_args_t *); static int handle_response_upload_dir(dir_connection_t *, const response_handler_args_t *); static int handle_response_upload_vote(dir_connection_t *, @@ -2887,7 +2845,7 @@ handle_response_fetch_desc(dir_connection_t *conn, conn->router_purpose, conn->base_.address)) { time_t now = approx_time(); - directory_info_has_arrived(now, 0, 0); + directory_info_has_arrived(now, 0, 1); } } } @@ -2914,7 +2872,7 @@ handle_response_fetch_desc(dir_connection_t *conn, * Handler function: processes a response to a request for a group of * microdescriptors **/ -static int +STATIC int handle_response_fetch_microdesc(dir_connection_t *conn, const response_handler_args_t *args) { @@ -2931,6 +2889,7 @@ handle_response_fetch_microdesc(dir_connection_t *conn, conn->base_.port); tor_assert(conn->requested_resource && !strcmpstart(conn->requested_resource, "d/")); + tor_assert_nonfatal(!tor_mem_is_zero(conn->identity_digest, DIGEST_LEN)); which = smartlist_new(); dir_split_resource_into_fingerprints(conn->requested_resource+2, which, NULL, @@ -2941,7 +2900,7 @@ handle_response_fetch_microdesc(dir_connection_t *conn, "soon.", status_code, escaped(reason), conn->base_.address, (int)conn->base_.port, conn->requested_resource); - dir_microdesc_download_failed(which, status_code); + dir_microdesc_download_failed(which, status_code, conn->identity_digest); SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); smartlist_free(which); return 0; @@ -2953,7 +2912,7 @@ handle_response_fetch_microdesc(dir_connection_t *conn, now, which); if (smartlist_len(which)) { /* Mark remaining ones as failed. */ - dir_microdesc_download_failed(which, status_code); + dir_microdesc_download_failed(which, status_code, conn->identity_digest); } if (mds && smartlist_len(mds)) { control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, @@ -3132,10 +3091,19 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn, /* We got something: Try storing it in the cache. */ if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) { log_warn(LD_REND, "Failed to store hidden service descriptor"); + /* Fire control port FAILED event. */ + hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, + "BAD_DESC"); + hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, + NULL); } else { log_info(LD_REND, "Stored hidden service descriptor successfully."); TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; hs_client_desc_has_arrived(conn->hs_ident); + /* Fire control port RECEIVED event. */ + hs_control_desc_event_received(conn->hs_ident, conn->identity_digest); + hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, + body); } break; case 404: @@ -3143,13 +3111,22 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn, * tries to clean this conn up. */ log_info(LD_REND, "Fetching hidden service v3 descriptor not found: " "Retrying at another directory."); - /* TODO: Inform the control port */ + /* Fire control port FAILED event. */ + hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, + "NOT_FOUND"); + hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, + NULL); break; case 400: log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " "http status 400 (%s). Dirserver didn't like our " "query? Retrying at another directory.", escaped(reason)); + /* Fire control port FAILED event. */ + hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, + "QUERY_REJECTED"); + hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, + NULL); break; default: log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " @@ -3157,6 +3134,11 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn, "'%s:%d'. Retrying at another directory.", status_code, escaped(reason), TO_CONN(conn)->address, TO_CONN(conn)->port); + /* Fire control port FAILED event. */ + hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, + "UNEXPECTED"); + hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, + NULL); break; } @@ -3178,9 +3160,9 @@ handle_response_fetch_renddesc_v2(dir_connection_t *conn, const size_t body_len = args->body_len; #define SEND_HS_DESC_FAILED_EVENT(reason) \ - (control_event_hs_descriptor_failed(conn->rend_data, \ - conn->identity_digest, \ - reason)) + (control_event_hsv2_descriptor_failed(conn->rend_data, \ + conn->identity_digest, \ + reason)) #define SEND_HS_DESC_FAILED_CONTENT() \ (control_event_hs_descriptor_content( \ rend_data_get_address(conn->rend_data), \ @@ -3215,9 +3197,9 @@ handle_response_fetch_renddesc_v2(dir_connection_t *conn, /* success. notify pending connections about this. */ log_info(LD_REND, "Successfully fetched v2 rendezvous " "descriptor."); - control_event_hs_descriptor_received(service_id, - conn->rend_data, - conn->identity_digest); + control_event_hsv2_descriptor_received(service_id, + conn->rend_data, + conn->identity_digest); control_event_hs_descriptor_content(service_id, conn->requested_resource, conn->identity_digest, @@ -3334,14 +3316,16 @@ handle_response_upload_hsdesc(dir_connection_t *conn, case 200: log_info(LD_REND, "Uploading hidden service descriptor: " "finished with status 200 (%s)", escaped(reason)); - /* XXX: Trigger control event. */ + hs_control_desc_event_uploaded(conn->hs_ident, conn->identity_digest); break; case 400: - log_warn(LD_REND, "Uploading hidden service descriptor: http " - "status 400 (%s) response from dirserver " - "'%s:%d'. Malformed hidden service descriptor?", - escaped(reason), conn->base_.address, conn->base_.port); - /* XXX: Trigger control event. */ + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Uploading hidden service descriptor: http " + "status 400 (%s) response from dirserver " + "'%s:%d'. Malformed hidden service descriptor?", + escaped(reason), conn->base_.address, conn->base_.port); + hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, + "UPLOAD_REJECTED"); break; default: log_warn(LD_REND, "Uploading hidden service descriptor: http " @@ -3349,7 +3333,8 @@ handle_response_upload_hsdesc(dir_connection_t *conn, "'%s:%d').", status_code, escaped(reason), conn->base_.address, conn->base_.port); - /* XXX: Trigger control event. */ + hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, + "UNEXPECTED"); break; } @@ -5793,13 +5778,14 @@ dir_routerdesc_download_failed(smartlist_t *failed, int status_code, * every 10 or 60 seconds (FOO_DESCRIPTOR_RETRY_INTERVAL) in main.c. */ } -/** Called when a connection to download microdescriptors has failed in whole - * or in part. <b>failed</b> is a list of every microdesc digest we didn't - * get. <b>status_code</b> is the http status code we received. Reschedule the - * microdesc downloads as appropriate. */ +/** Called when a connection to download microdescriptors from relay with + * <b>dir_id</b> has failed in whole or in part. <b>failed</b> is a list + * of every microdesc digest we didn't get. <b>status_code</b> is the http + * status code we received. Reschedule the microdesc downloads as + * appropriate. */ static void dir_microdesc_download_failed(smartlist_t *failed, - int status_code) + int status_code, const char *dir_id) { networkstatus_t *consensus = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); @@ -5810,17 +5796,26 @@ dir_microdesc_download_failed(smartlist_t *failed, if (! consensus) return; + + /* We failed to fetch a microdescriptor from 'dir_id', note it down + * so that we don't try the same relay next time... */ + microdesc_note_outdated_dirserver(dir_id); + SMARTLIST_FOREACH_BEGIN(failed, const char *, d) { rs = router_get_mutable_consensus_status_by_descriptor_digest(consensus,d); if (!rs) continue; dls = &rs->dl_status; if (dls->n_download_failures >= - get_options()->TestingMicrodescMaxDownloadTries) + get_options()->TestingMicrodescMaxDownloadTries) { continue; - { + } + + { /* Increment the failure count for this md fetch */ char buf[BASE64_DIGEST256_LEN+1]; digest256_to_base64(buf, d); + log_info(LD_DIR, "Failed to download md %s from %s", + buf, hex_str(dir_id, DIGEST_LEN)); download_status_increment_failure(dls, status_code, buf, server, now); } diff --git a/src/or/directory.h b/src/or/directory.h index 904bdfae46..3aef600716 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -183,6 +183,48 @@ typedef struct response_handler_args_t { const char *headers; } response_handler_args_t; +struct directory_request_t { + /** + * These fields specify which directory we're contacting. Routerstatus, + * if present, overrides the other fields. + * + * @{ */ + tor_addr_port_t or_addr_port; + tor_addr_port_t dir_addr_port; + char digest[DIGEST_LEN]; + + const routerstatus_t *routerstatus; + /** @} */ + /** One of DIR_PURPOSE_* other than DIR_PURPOSE_SERVER. Describes what + * kind of operation we'll be doing (upload/download), and of what kind + * of document. */ + uint8_t dir_purpose; + /** One of ROUTER_PURPOSE_*; used for uploads and downloads of routerinfo + * and extrainfo docs. */ + uint8_t router_purpose; + /** Enum: determines whether to anonymize, and whether to use dirport or + * orport. */ + dir_indirection_t indirection; + /** Alias to the variable part of the URL for this request */ + const char *resource; + /** Alias to the payload to upload (if any) */ + const char *payload; + /** Number of bytes to upload from payload</b> */ + size_t payload_len; + /** Value to send in an if-modified-since header, or 0 for none. */ + time_t if_modified_since; + /** Hidden-service-specific information v2. */ + const rend_data_t *rend_query; + /** Extra headers to append to the request */ + config_line_t *additional_headers; + /** Hidden-service-specific information for v3+. */ + const hs_ident_dir_conn_t *hs_ident; + /** Used internally to directory.c: gets informed when the attempt to + * connect to the directory succeeds or fails, if that attempt bears on the + * directory's usability as a directory guard. */ + struct circuit_guard_state_t *guard_state; +}; + struct get_handler_args_t; STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn, const struct get_handler_args_t *args); @@ -193,6 +235,8 @@ STATIC void warn_disallowed_anonymous_compression_method(compress_method_t); STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn, const response_handler_args_t *args); +STATIC int handle_response_fetch_microdesc(dir_connection_t *conn, + const response_handler_args_t *args); STATIC int handle_response_fetch_consensus(dir_connection_t *conn, const response_handler_args_t *args); diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 432fe6ae2b..ce737d0b1d 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -1903,21 +1903,28 @@ version_from_platform(const char *platform) /** Helper: write the router-status information in <b>rs</b> into a newly * allocated character buffer. Use the same format as in network-status * documents. If <b>version</b> is non-NULL, add a "v" line for the platform. + * + * consensus_method is the current consensus method when format is + * NS_V3_CONSENSUS or NS_V3_CONSENSUS_MICRODESC. It is ignored for other + * formats: pass ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD. + * * Return 0 on success, -1 on failure. * * The format argument has one of the following values: * NS_V2 - Output an entry suitable for a V2 NS opinion document * NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry + * for consensus_method. * NS_V3_CONSENSUS_MICRODESC - Output the first portion of a V3 microdesc - * consensus entry. + * consensus entry for consensus_method. * NS_V3_VOTE - Output a complete V3 NS vote. If <b>vrs</b> is present, * it contains additional information for the vote. - * NS_CONTROL_PORT - Output a NS document for the control port + * NS_CONTROL_PORT - Output a NS document for the control port. */ char * routerstatus_format_entry(const routerstatus_t *rs, const char *version, const char *protocols, routerstatus_format_type_t format, + int consensus_method, const vote_routerstatus_t *vrs) { char *summary; @@ -1948,8 +1955,10 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, * networkstatus_type_t values, with an additional control port value * added -MP */ - /* V3 microdesc consensuses don't have "a" lines. */ - if (format == NS_V3_CONSENSUS_MICRODESC) + /* V3 microdesc consensuses only have "a" lines in later consensus methods + */ + if (format == NS_V3_CONSENSUS_MICRODESC && + consensus_method < MIN_METHOD_FOR_A_LINES_IN_MICRODESC_CONSENSUS) goto done; /* Possible "a" line. At most one for now. */ @@ -1958,7 +1967,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, fmt_addrport(&rs->ipv6_addr, rs->ipv6_orport)); } - if (format == NS_V3_CONSENSUS) + if (format == NS_V3_CONSENSUS || format == NS_V3_CONSENSUS_MICRODESC) goto done; smartlist_add_asprintf(chunks, diff --git a/src/or/dirserv.h b/src/or/dirserv.h index 46967a6cb2..9dc239f213 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -150,6 +150,7 @@ char *routerstatus_format_entry( const char *version, const char *protocols, routerstatus_format_type_t format, + int consensus_method, const vote_routerstatus_t *vrs); void dirserv_free_all(void); void cached_dir_decref(cached_dir_t *d); diff --git a/src/or/dirvote.c b/src/or/dirvote.c index ce82a5ef4a..24a6ed42be 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -278,7 +278,9 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, vote_microdesc_hash_t *h; rsf = routerstatus_format_entry(&vrs->status, vrs->version, vrs->protocols, - NS_V3_VOTE, vrs); + NS_V3_VOTE, + ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD, + vrs); if (rsf) smartlist_add(chunks, rsf); @@ -519,7 +521,7 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method, /* compare_vote_rs_() sorts the items by identity digest (all the same), * then by SD digest. That way, if we have a tie that the published_on - * date cannot tie, we use the descriptor with the smaller digest. + * date cannot break, we use the descriptor with the smaller digest. */ smartlist_sort(votes, compare_vote_rs_); SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) { @@ -795,6 +797,9 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) output = smartlist_new(); SMARTLIST_FOREACH_BEGIN(param_list, const char *, param) { + /* resolve spurious clang shallow analysis null pointer errors */ + tor_assert(param); + const char *next_param; int ok=0; eq = strchr(param, '='); @@ -807,8 +812,7 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities) next_param = NULL; else next_param = smartlist_get(param_list, param_sl_idx+1); - /* resolve spurious clang shallow analysis null pointer errors */ - tor_assert(param); + if (!next_param || strncmp(next_param, param, cur_param_len)) { /* We've reached the end of a series. */ /* Make sure enough authorities voted on this param, unless the @@ -1315,8 +1319,9 @@ compute_nth_protocol_set(int n, int n_voters, const smartlist_t *votes) /** Given a list of vote networkstatus_t in <b>votes</b>, our public * authority <b>identity_key</b>, our private authority <b>signing_key</b>, * and the number of <b>total_authorities</b> that we believe exist in our - * voting quorum, generate the text of a new v3 consensus vote, and return the - * value in a newly allocated string. + * voting quorum, generate the text of a new v3 consensus or microdescriptor + * consensus (depending on <b>flavor</b>), and return the value in a newly + * allocated string. * * Note: this function DOES NOT check whether the votes are from * recognized authorities. (dirvote_add_vote does that.) @@ -2099,7 +2104,8 @@ networkstatus_compute_consensus(smartlist_t *votes, char *buf; /* Okay!! Now we can write the descriptor... */ /* First line goes into "buf". */ - buf = routerstatus_format_entry(&rs_out, NULL, NULL, rs_format, NULL); + buf = routerstatus_format_entry(&rs_out, NULL, NULL, + rs_format, consensus_method, NULL); if (buf) smartlist_add(chunks, buf); } @@ -3829,7 +3835,10 @@ dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method) smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf); } + /* We originally put a lines in the micrdescriptors, but then we worked out + * that we needed them in the microdesc consensus. See #20916. */ if (consensus_method >= MIN_METHOD_FOR_A_LINES && + consensus_method < MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC && !tor_addr_is_null(&ri->ipv6_addr) && ri->ipv6_orport) smartlist_add_asprintf(chunks, "a %s\n", fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport)); @@ -3938,7 +3947,9 @@ static const struct consensus_method_range_t { {MIN_METHOD_FOR_P6_LINES, MIN_METHOD_FOR_NTOR_KEY - 1}, {MIN_METHOD_FOR_NTOR_KEY, MIN_METHOD_FOR_ID_HASH_IN_MD - 1}, {MIN_METHOD_FOR_ID_HASH_IN_MD, MIN_METHOD_FOR_ED25519_ID_IN_MD - 1}, - {MIN_METHOD_FOR_ED25519_ID_IN_MD, MAX_SUPPORTED_CONSENSUS_METHOD}, + {MIN_METHOD_FOR_ED25519_ID_IN_MD, + MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC - 1}, + {MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC, MAX_SUPPORTED_CONSENSUS_METHOD}, {-1, -1} }; diff --git a/src/or/dirvote.h b/src/or/dirvote.h index 72a35fea6d..c9cb527788 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -51,11 +51,15 @@ #define MIN_VOTE_INTERVAL_TESTING_INITIAL \ ((MIN_VOTE_SECONDS_TESTING)+(MIN_DIST_SECONDS_TESTING)+1) +/* A placeholder for routerstatus_format_entry() when the consensus method + * argument is not applicable. */ +#define ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD 0 + /** The lowest consensus method that we currently support. */ #define MIN_SUPPORTED_CONSENSUS_METHOD 13 /** The highest consensus method that we currently support. */ -#define MAX_SUPPORTED_CONSENSUS_METHOD 26 +#define MAX_SUPPORTED_CONSENSUS_METHOD 28 /** Lowest consensus method where microdesc consensuses omit any entry * with no microdesc. */ @@ -115,6 +119,14 @@ * instead of 0. See #14881 */ #define MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE 26 +/** Lowest consensus method where the microdesc consensus contains relay IPv6 + * addresses. See #23826 and #20916. */ +#define MIN_METHOD_FOR_A_LINES_IN_MICRODESC_CONSENSUS 27 + +/** Lowest consensus method where microdescriptors do not contain relay IPv6 + * addresses. See #23828 and #20916. */ +#define MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC 28 + /** Default bandwidth to clip unmeasured bandwidths to using method >= * MIN_METHOD_TO_CLIP_UNMEASURED_BW. (This is not a consensus method; do not * get confused with the above macros.) */ diff --git a/src/or/dns.c b/src/or/dns.c index c992c8c358..f140051e81 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -1578,10 +1578,11 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, escaped_safe_str(hostname)); tor_free(escaped_address); } else if (count) { - log_warn(LD_EXIT, "eventdns returned only non-IPv4 answers for %s.", + log_info(LD_EXIT, "eventdns returned only unrecognized answer types " + " for %s.", escaped_safe_str(string_address)); } else { - log_warn(LD_BUG, "eventdns returned no addresses or error for %s!", + log_info(LD_EXIT, "eventdns returned no addresses or error for %s.", escaped_safe_str(string_address)); } } diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 76a8f591b9..f9438361f0 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -740,7 +740,8 @@ node_is_possible_guard(const node_t *node) node->is_stable && node->is_fast && node->is_valid && - node_is_dir(node)); + node_is_dir(node) && + !router_digest_is_me(node->identity)); } /** @@ -966,7 +967,7 @@ entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, * violate it. */ STATIC int -num_reachable_filtered_guards(guard_selection_t *gs, +num_reachable_filtered_guards(const guard_selection_t *gs, const entry_guard_restriction_t *rst) { int n_reachable_filtered_guards = 0; @@ -1460,6 +1461,94 @@ guard_in_node_family(const entry_guard_t *guard, const node_t *node) } } +/* Allocate and return a new exit guard restriction (where <b>exit_id</b> is of + * size DIGEST_LEN) */ +STATIC entry_guard_restriction_t * +guard_create_exit_restriction(const uint8_t *exit_id) +{ + entry_guard_restriction_t *rst = NULL; + rst = tor_malloc_zero(sizeof(entry_guard_restriction_t)); + rst->type = RST_EXIT_NODE; + memcpy(rst->exclude_id, exit_id, DIGEST_LEN); + return rst; +} + +/** If we have fewer than this many possible usable guards, don't set + * MD-availability-based restrictions: we might blacklist all of them. */ +#define MIN_GUARDS_FOR_MD_RESTRICTION 10 + +/** Return true if we should set md dirserver restrictions. We might not want + * to set those if our guard options are too restricted, since we don't want + * to blacklist all of them. */ +static int +should_set_md_dirserver_restriction(void) +{ + const guard_selection_t *gs = get_guard_selection_info(); + int num_usable_guards = num_reachable_filtered_guards(gs, NULL); + + /* Don't set restriction if too few reachable filtered guards. */ + if (num_usable_guards < MIN_GUARDS_FOR_MD_RESTRICTION) { + log_info(LD_GUARD, "Not setting md restriction: only %d" + " usable guards.", num_usable_guards); + return 0; + } + + /* We have enough usable guards: set MD restriction */ + return 1; +} + +/** Allocate and return an outdated md guard restriction. Return NULL if no + * such restriction is needed. */ +STATIC entry_guard_restriction_t * +guard_create_dirserver_md_restriction(void) +{ + entry_guard_restriction_t *rst = NULL; + + if (!should_set_md_dirserver_restriction()) { + log_debug(LD_GUARD, "Not setting md restriction: too few " + "filtered guards."); + return NULL; + } + + rst = tor_malloc_zero(sizeof(entry_guard_restriction_t)); + rst->type = RST_OUTDATED_MD_DIRSERVER; + + return rst; +} + +/* Return True if <b>guard</b> obeys the exit restriction <b>rst</b>. */ +static int +guard_obeys_exit_restriction(const entry_guard_t *guard, + const entry_guard_restriction_t *rst) +{ + tor_assert(rst->type == RST_EXIT_NODE); + + // Exclude the exit ID and all of its family. + const node_t *node = node_get_by_id((const char*)rst->exclude_id); + if (node && guard_in_node_family(guard, node)) + return 0; + + return tor_memneq(guard->identity, rst->exclude_id, DIGEST_LEN); +} + +/** Return True if <b>guard</b> should be used as a dirserver for fetching + * microdescriptors. */ +static int +guard_obeys_md_dirserver_restriction(const entry_guard_t *guard) +{ + /* If this guard is an outdated dirserver, don't use it. */ + if (microdesc_relay_is_outdated_dirserver(guard->identity)) { + log_info(LD_GENERAL, "Skipping %s dirserver: outdated", + hex_str(guard->identity, DIGEST_LEN)); + return 0; + } + + log_debug(LD_GENERAL, "%s dirserver obeys md restrictions", + hex_str(guard->identity, DIGEST_LEN)); + + return 1; +} + /** * Return true iff <b>guard</b> obeys the restrictions defined in <b>rst</b>. * (If <b>rst</b> is NULL, there are no restrictions.) @@ -1472,13 +1561,14 @@ entry_guard_obeys_restriction(const entry_guard_t *guard, if (! rst) return 1; // No restriction? No problem. - // Only one kind of restriction exists right now: excluding an exit - // ID and all of its family. - const node_t *node = node_get_by_id((const char*)rst->exclude_id); - if (node && guard_in_node_family(guard, node)) - return 0; + if (rst->type == RST_EXIT_NODE) { + return guard_obeys_exit_restriction(guard, rst); + } else if (rst->type == RST_OUTDATED_MD_DIRSERVER) { + return guard_obeys_md_dirserver_restriction(guard); + } - return tor_memneq(guard->identity, rst->exclude_id, DIGEST_LEN); + tor_assert_nonfatal_unreached(); + return 0; } /** @@ -2105,7 +2195,7 @@ entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b) } /** Release all storage held in <b>restriction</b> */ -static void +STATIC void entry_guard_restriction_free(entry_guard_restriction_t *rst) { tor_free(rst); @@ -3358,8 +3448,8 @@ guards_choose_guard(cpath_build_state_t *state, /* We're building to a targeted exit node, so that node can't be * chosen as our guard for this circuit. Remember that fact in a * restriction. */ - rst = tor_malloc_zero(sizeof(entry_guard_restriction_t)); - memcpy(rst->exclude_id, exit_id, DIGEST_LEN); + rst = guard_create_exit_restriction(exit_id); + tor_assert(rst); } if (entry_guard_pick_for_circuit(get_guard_selection_info(), GUARD_USAGE_TRAFFIC, @@ -3411,12 +3501,20 @@ remove_all_entry_guards(void) /** Helper: pick a directory guard, with whatever algorithm is used. */ const node_t * -guards_choose_dirguard(circuit_guard_state_t **guard_state_out) +guards_choose_dirguard(uint8_t dir_purpose, + circuit_guard_state_t **guard_state_out) { const node_t *r = NULL; + entry_guard_restriction_t *rst = NULL; + + /* If we are fetching microdescs, don't query outdated dirservers. */ + if (dir_purpose == DIR_PURPOSE_FETCH_MICRODESC) { + rst = guard_create_dirserver_md_restriction(); + } + if (entry_guard_pick_for_circuit(get_guard_selection_info(), GUARD_USAGE_DIRGUARD, - NULL, + rst, &r, guard_state_out) < 0) { tor_assert(r == NULL); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 86f0517df2..9d1293d02a 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -272,22 +272,28 @@ struct guard_selection_s { struct entry_guard_handle_t; +/** Types of restrictions we impose when picking guard nodes */ +typedef enum guard_restriction_type_t { + /* Don't pick the same guard node as our exit node (or its family) */ + RST_EXIT_NODE = 0, + /* Don't pick dirguards that have previously shown to be outdated */ + RST_OUTDATED_MD_DIRSERVER = 1 +} guard_restriction_type_t; + /** * A restriction to remember which entry guards are off-limits for a given * circuit. * - * Right now, we only use restrictions to block a single guard and its family - * from being selected; this mechanism is designed to be more extensible in - * the future, however. - * * Note: This mechanism is NOT for recording which guards are never to be * used: only which guards cannot be used on <em>one particular circuit</em>. */ struct entry_guard_restriction_t { - /** - * The guard's RSA identity digest must not equal this; and it must not - * be in the same family as any node with this digest. - */ + /* What type of restriction are we imposing? */ + guard_restriction_type_t type; + + /* In case of restriction type RST_EXIT_NODE, the guard's RSA identity + * digest must not equal this; and it must not be in the same family as any + * node with this digest. */ uint8_t exclude_id[DIGEST_LEN]; }; @@ -316,7 +322,8 @@ struct circuit_guard_state_t { int guards_update_all(void); const node_t *guards_choose_guard(cpath_build_state_t *state, circuit_guard_state_t **guard_state_out); -const node_t *guards_choose_dirguard(circuit_guard_state_t **guard_state_out); +const node_t *guards_choose_dirguard(uint8_t dir_purpose, + circuit_guard_state_t **guard_state_out); #if 1 /* XXXX NM I would prefer that all of this stuff be private to @@ -518,7 +525,7 @@ STATIC void entry_guard_consider_retry(entry_guard_t *guard); STATIC void make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard); STATIC void entry_guards_update_confirmed(guard_selection_t *gs); STATIC void entry_guards_update_primary(guard_selection_t *gs); -STATIC int num_reachable_filtered_guards(guard_selection_t *gs, +STATIC int num_reachable_filtered_guards(const guard_selection_t *gs, const entry_guard_restriction_t *rst); STATIC void sampled_guards_update_from_consensus(guard_selection_t *gs); /** @@ -554,6 +561,14 @@ STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs, unsigned old_state); STATIC int entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b); STATIC char *getinfo_helper_format_single_entry_guard(const entry_guard_t *e); + +STATIC entry_guard_restriction_t *guard_create_exit_restriction( + const uint8_t *exit_id); + +STATIC entry_guard_restriction_t *guard_create_dirserver_md_restriction(void); + +STATIC void entry_guard_restriction_free(entry_guard_restriction_t *rst); + #endif /* defined(ENTRYNODES_PRIVATE) */ void remove_all_entry_guards_for_guard_selection(guard_selection_t *gs); diff --git a/src/or/git_revision.h b/src/or/git_revision.h index 1ceaeedf16..5613cb4335 100644 --- a/src/or/git_revision.h +++ b/src/or/git_revision.h @@ -8,5 +8,5 @@ extern const char tor_git_revision[]; -#endif +#endif /* !defined(TOR_GIT_REVISION_H) */ diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 3ebe13fb4d..b864a0f717 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -706,6 +706,24 @@ cache_clean_v3_as_client(time_t now) } /** Public API: Given the HS ed25519 identity public key in <b>key</b>, return + * its HS encoded descriptor if it's stored in our cache, or NULL if not. */ +const char * +hs_cache_lookup_encoded_as_client(const ed25519_public_key_t *key) +{ + hs_cache_client_descriptor_t *cached_desc = NULL; + + tor_assert(key); + + cached_desc = lookup_v3_desc_as_client(key->pubkey); + if (cached_desc) { + tor_assert(cached_desc->encoded_desc); + return cached_desc->encoded_desc; + } + + return NULL; +} + +/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return * its HS descriptor if it's stored in our cache, or NULL if not. */ const hs_descriptor_t * hs_cache_lookup_as_client(const ed25519_public_key_t *key) diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h index 2dcc518a71..a141634cc4 100644 --- a/src/or/hs_cache.h +++ b/src/or/hs_cache.h @@ -81,6 +81,8 @@ int hs_cache_lookup_as_dir(uint32_t version, const char *query, const hs_descriptor_t * hs_cache_lookup_as_client(const ed25519_public_key_t *key); +const char * +hs_cache_lookup_encoded_as_client(const ed25519_public_key_t *key); int hs_cache_store_as_client(const char *desc_str, const ed25519_public_key_t *identity_pk); void hs_cache_clean_as_client(time_t now); diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 26fe22eb7b..a232a40c80 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -617,6 +617,7 @@ get_lspecs_from_node(const node_t *node, smartlist_t *lspecs) link_specifier_set_un_ipv6_port(ls, ap.port); /* Sixteen bytes IPv6 and two bytes port. */ link_specifier_set_ls_len(ls, addr_len + sizeof(ap.port)); + smartlist_add(lspecs, ls); } } @@ -1100,9 +1101,19 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ, tor_assert(ip); tor_assert(subcredential); + /* It is undefined behavior in hs_cell_introduce1_data_clear() if intro1_data + * has been declared on the stack but not initialized. Here, we set it to 0. + */ + memset(&intro1_data, 0, sizeof(hs_cell_introduce1_data_t)); + /* This takes various objects in order to populate the introduce1 data * object which is used to build the content of the cell. */ const node_t *exit_node = build_state_get_exit_node(rend_circ->build_state); + if (exit_node == NULL) { + log_info(LD_REND, "Unable to get rendezvous point for circuit %u. " + "Failing.", TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } setup_introduce1_data(ip, exit_node, subcredential, &intro1_data); /* If we didn't get any link specifiers, it's because our node was * bad. */ diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 0a1186dbaa..b92fb4e494 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -67,7 +67,7 @@ create_rp_circuit_identifier(const hs_service_t *service, const curve25519_public_key_t *server_pk, const hs_ntor_rend_cell_keys_t *keys); -#endif +#endif /* defined(HS_CIRCUIT_PRIVATE) */ #endif /* !defined(TOR_HS_CIRCUIT_H) */ diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 9ac653c721..666860155c 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -21,6 +21,7 @@ #include "config.h" #include "directory.h" #include "hs_client.h" +#include "hs_control.h" #include "router.h" #include "routerset.h" #include "circuitlist.h" @@ -349,6 +350,10 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, safe_str_client(base64_blinded_pubkey), safe_str_client(routerstatus_describe(hsdir))); + /* Fire a REQUESTED event on the control port. */ + hs_control_desc_event_requested(onion_identity_pk, base64_blinded_pubkey, + hsdir); + /* Cleanup memory. */ memwipe(&blinded_pubkey, 0, sizeof(blinded_pubkey)); memwipe(base64_blinded_pubkey, 0, sizeof(base64_blinded_pubkey)); diff --git a/src/or/hs_common.h b/src/or/hs_common.h index c95e59a6f8..58fcd1b93c 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -130,6 +130,17 @@ typedef enum { HS_AUTH_KEY_TYPE_ED25519 = 2, } hs_auth_key_type_t; +/* Return value when adding an ephemeral service through the ADD_ONION + * control port command. Both v2 and v3 share these. */ +typedef enum { + RSAE_BADAUTH = -5, /**< Invalid auth_type/auth_clients */ + RSAE_BADVIRTPORT = -4, /**< Invalid VIRTPORT/TARGET(s) */ + RSAE_ADDREXISTS = -3, /**< Onion address collision */ + RSAE_BADPRIVKEY = -2, /**< Invalid public key */ + RSAE_INTERNAL = -1, /**< Internal error */ + RSAE_OKAY = 0 /**< Service added as expected */ +} hs_service_add_ephemeral_status_t; + /* Represents the mapping from a virtual port of a rendezvous service to a * real port on some IP. */ typedef struct rend_service_port_config_t { diff --git a/src/or/hs_control.c b/src/or/hs_control.c new file mode 100644 index 0000000000..1b1fe78575 --- /dev/null +++ b/src/or/hs_control.c @@ -0,0 +1,256 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_control.c + * \brief Contains control port event related code. + **/ + +#include "or.h" +#include "control.h" +#include "hs_common.h" +#include "hs_control.h" +#include "hs_descriptor.h" +#include "hs_service.h" +#include "nodelist.h" + +/* Send on the control port the "HS_DESC REQUESTEDÂ [...]" event. + * + * The onion_pk is the onion service public key, base64_blinded_pk is the + * base64 encoded blinded key for the service and hsdir_rs is the routerstatus + * object of the HSDir that this request is for. */ +void +hs_control_desc_event_requested(const ed25519_public_key_t *onion_pk, + const char *base64_blinded_pk, + const routerstatus_t *hsdir_rs) +{ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + const uint8_t *hsdir_index; + const node_t *hsdir_node; + + tor_assert(onion_pk); + tor_assert(base64_blinded_pk); + tor_assert(hsdir_rs); + + hs_build_address(onion_pk, HS_VERSION_THREE, onion_address); + + /* Get the node from the routerstatus object to get the HSDir index used for + * this request. We can't have a routerstatus entry without a node and we + * can't pick a node without an hsdir_index. */ + hsdir_node = node_get_by_id(hsdir_rs->identity_digest); + tor_assert(hsdir_node); + tor_assert(hsdir_node->hsdir_index); + /* This is a fetch event. */ + hsdir_index = hsdir_node->hsdir_index->fetch; + + /* Trigger the event. */ + control_event_hs_descriptor_requested(onion_address, REND_NO_AUTH, + hsdir_rs->identity_digest, + base64_blinded_pk, + hex_str((const char *) hsdir_index, + DIGEST256_LEN)); + memwipe(onion_address, 0, sizeof(onion_address)); +} + +/* Send on the control port the "HS_DESC FAILED [...]" event. + * + * Using a directory connection identifier, the HSDir identity digest and a + * reason for the failure. None can be NULL. */ +void +hs_control_desc_event_failed(const hs_ident_dir_conn_t *ident, + const char *hsdir_id_digest, + const char *reason) +{ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + + tor_assert(ident); + tor_assert(hsdir_id_digest); + tor_assert(reason); + + /* Build onion address and encoded blinded key. */ + IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, + &ident->blinded_pk) < 0) { + return; + } + hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); + + control_event_hsv3_descriptor_failed(onion_address, base64_blinded_pk, + hsdir_id_digest, reason); +} + +/* Send on the control port the "HS_DESC RECEIVED [...]" event. + * + * Using a directory connection identifier and the HSDir identity digest. + * None can be NULL. */ +void +hs_control_desc_event_received(const hs_ident_dir_conn_t *ident, + const char *hsdir_id_digest) +{ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + + tor_assert(ident); + tor_assert(hsdir_id_digest); + + /* Build onion address and encoded blinded key. */ + IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, + &ident->blinded_pk) < 0) { + return; + } + hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); + + control_event_hsv3_descriptor_received(onion_address, base64_blinded_pk, + hsdir_id_digest); +} + +/* Send on the control port the "HS_DESC CREATED [...]" event. + * + * Using the onion address of the descriptor's service and the blinded public + * key of the descriptor as a descriptor ID. None can be NULL. */ +void +hs_control_desc_event_created(const char *onion_address, + const ed25519_public_key_t *blinded_pk) +{ + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + + tor_assert(onion_address); + tor_assert(blinded_pk); + + /* Build base64 encoded blinded key. */ + IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, blinded_pk) < 0) { + return; + } + + /* Version 3 doesn't use the replica number in its descriptor ID computation + * so we pass negative value so the control port subsystem can ignore it. */ + control_event_hs_descriptor_created(onion_address, base64_blinded_pk, -1); +} + +/* Send on the control port the "HS_DESC UPLOAD [...]" event. + * + * Using the onion address of the descriptor's service, the HSDir identity + * digest, the blinded public key of the descriptor as a descriptor ID and the + * HSDir index for this particular request. None can be NULL. */ +void +hs_control_desc_event_upload(const char *onion_address, + const char *hsdir_id_digest, + const ed25519_public_key_t *blinded_pk, + const uint8_t *hsdir_index) +{ + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + + tor_assert(onion_address); + tor_assert(hsdir_id_digest); + tor_assert(blinded_pk); + tor_assert(hsdir_index); + + /* Build base64 encoded blinded key. */ + IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, blinded_pk) < 0) { + return; + } + + control_event_hs_descriptor_upload(onion_address, hsdir_id_digest, + base64_blinded_pk, + hex_str((const char *) hsdir_index, + DIGEST256_LEN)); +} + +/* Send on the control port the "HS_DESC UPLOADED [...]" event. + * + * Using the directory connection identifier and the HSDir identity digest. + * None can be NULL. */ +void +hs_control_desc_event_uploaded(const hs_ident_dir_conn_t *ident, + const char *hsdir_id_digest) +{ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + tor_assert(ident); + tor_assert(hsdir_id_digest); + + hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); + + control_event_hs_descriptor_uploaded(hsdir_id_digest, onion_address); +} + +/* Send on the control port the "HS_DESC_CONTENT [...]" event. + * + * Using the directory connection identifier, the HSDir identity digest and + * the body of the descriptor (as it was received from the directory). None + * can be NULL. */ +void +hs_control_desc_event_content(const hs_ident_dir_conn_t *ident, + const char *hsdir_id_digest, + const char *body) +{ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + + tor_assert(ident); + tor_assert(hsdir_id_digest); + + /* Build onion address and encoded blinded key. */ + IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, + &ident->blinded_pk) < 0) { + return; + } + hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); + + control_event_hs_descriptor_content(onion_address, base64_blinded_pk, + hsdir_id_digest, body); +} + +/* Handle the "HSPOST [...]" command. The body is an encoded descriptor for + * the given onion_address. The descriptor will be uploaded to each directory + * in hsdirs_rs. If NULL, the responsible directories for the current time + * period will be selected. + * + * Return -1 on if the descriptor plaintext section is not decodable. Else, 0 + * on success. */ +int +hs_control_hspost_command(const char *body, const char *onion_address, + const smartlist_t *hsdirs_rs) +{ + int ret = -1; + ed25519_public_key_t identity_pk; + hs_desc_plaintext_data_t plaintext; + smartlist_t *hsdirs = NULL; + + tor_assert(body); + tor_assert(onion_address); + + /* This can't fail because we require the caller to pass us a valid onion + * address that has passed hs_address_is_valid(). */ + hs_parse_address(onion_address, &identity_pk, NULL, NULL); + + /* Only decode the plaintext part which is what the directory will do to + * validate before caching. */ + if (hs_desc_decode_plaintext(body, &plaintext) < 0) { + goto done; + } + + /* No HSDir(s) given, we'll compute what the current ones should be. */ + if (hsdirs_rs == NULL) { + hsdirs = smartlist_new(); + hs_get_responsible_hsdirs(&plaintext.blinded_pubkey, + hs_get_time_period_num(0), + 0, /* Always the current descriptor which uses + * the first hsdir index. */ + 0, /* It is for storing on a directory. */ + hsdirs); + hsdirs_rs = hsdirs; + } + + SMARTLIST_FOREACH_BEGIN(hsdirs_rs, const routerstatus_t *, rs) { + hs_service_upload_desc_to_dir(body, plaintext.version, &identity_pk, + &plaintext.blinded_pubkey, rs); + } SMARTLIST_FOREACH_END(rs); + ret = 0; + + done: + /* We don't have ownership of the objects in this list. */ + smartlist_free(hsdirs); + return ret; +} + diff --git a/src/or/hs_control.h b/src/or/hs_control.h new file mode 100644 index 0000000000..95c46e655e --- /dev/null +++ b/src/or/hs_control.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_control.h + * \brief Header file containing control port event related code. + **/ + +#ifndef TOR_HS_CONTROL_H +#define TOR_HS_CONTROL_H + +#include "hs_ident.h" + +/* Event "HS_DESC REQUESTED [...]" */ +void hs_control_desc_event_requested(const ed25519_public_key_t *onion_pk, + const char *base64_blinded_pk, + const routerstatus_t *hsdir_rs); + +/* Event "HS_DESC FAILED [...]" */ +void hs_control_desc_event_failed(const hs_ident_dir_conn_t *ident, + const char *hsdir_id_digest, + const char *reason); + +/* Event "HS_DESC RECEIVED [...]" */ +void hs_control_desc_event_received(const hs_ident_dir_conn_t *ident, + const char *hsdir_id_digest); + +/* Event "HS_DESC CREATED [...]" */ +void hs_control_desc_event_created(const char *onion_address, + const ed25519_public_key_t *blinded_pk); + +/* Event "HS_DESC UPLOAD [...]" */ +void hs_control_desc_event_upload(const char *onion_address, + const char *hsdir_id_digest, + const ed25519_public_key_t *blinded_pk, + const uint8_t *hsdir_index); + +/* Event "HS_DESC UPLOADED [...]" */ +void hs_control_desc_event_uploaded(const hs_ident_dir_conn_t *ident, + const char *hsdir_id_digest); + +/* Event "HS_DESC_CONTENT [...]" */ +void hs_control_desc_event_content(const hs_ident_dir_conn_t *ident, + const char *hsdir_id_digest, + const char *body); + +/* Command "HSPOST [...]" */ +int hs_control_hspost_command(const char *body, const char *onion_address, + const smartlist_t *hsdirs_rs); + +#endif /* !defined(TOR_HS_CONTROL_H) */ + diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 8e2f52dcf0..f65b59ae1c 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -30,6 +30,7 @@ #include "hs_circuit.h" #include "hs_common.h" #include "hs_config.h" +#include "hs_control.h" #include "hs_circuit.h" #include "hs_descriptor.h" #include "hs_ident.h" @@ -1431,6 +1432,9 @@ build_service_descriptor(hs_service_t *service, time_t now, /* Assign newly built descriptor to the next slot. */ *desc_out = desc; + /* Fire a CREATED control port event. */ + hs_control_desc_event_created(service->onion_address, + &desc->blinded_kp.pubkey); return; err: @@ -2199,16 +2203,12 @@ static void upload_descriptor_to_hsdir(const hs_service_t *service, hs_service_descriptor_t *desc, const node_t *hsdir) { - char version_str[4] = {0}, *encoded_desc = NULL; - directory_request_t *dir_req; - hs_ident_dir_conn_t ident; + char *encoded_desc = NULL; tor_assert(service); tor_assert(desc); tor_assert(hsdir); - memset(&ident, 0, sizeof(ident)); - /* Let's avoid doing that if tor is configured to not publish. */ if (!get_options()->PublishHidServDescriptors) { log_info(LD_REND, "Service %s not publishing descriptor. " @@ -2224,29 +2224,10 @@ upload_descriptor_to_hsdir(const hs_service_t *service, goto end; } - /* Setup the connection identifier. */ - hs_ident_dir_conn_init(&service->keys.identity_pk, &desc->blinded_kp.pubkey, - &ident); - - /* This is our resource when uploading which is used to construct the URL - * with the version number: "/tor/hs/<version>/publish". */ - tor_snprintf(version_str, sizeof(version_str), "%u", - service->config.version); - - /* Build the directory request for this HSDir. */ - dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC); - directory_request_set_routerstatus(dir_req, hsdir->rs); - directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS); - directory_request_set_resource(dir_req, version_str); - directory_request_set_payload(dir_req, encoded_desc, - strlen(encoded_desc)); - /* The ident object is copied over the directory connection object once - * the directory request is initiated. */ - directory_request_upload_set_hs_ident(dir_req, &ident); - - /* Initiate the directory request to the hsdir.*/ - directory_initiate_request(dir_req); - directory_request_free(dir_req); + /* Time to upload the descriptor to the directory. */ + hs_service_upload_desc_to_dir(encoded_desc, service->config.version, + &service->keys.identity_pk, + &desc->blinded_kp.pubkey, hsdir->rs); /* Add this node to previous_hsdirs list */ service_desc_note_upload(desc, hsdir); @@ -2263,9 +2244,12 @@ upload_descriptor_to_hsdir(const hs_service_t *service, desc->desc->plaintext_data.revision_counter, safe_str_client(node_describe(hsdir)), safe_str_client(hex_str((const char *) index, 32))); + + /* Fire a UPLOAD control port event. */ + hs_control_desc_event_upload(service->onion_address, hsdir->identity, + &desc->blinded_kp.pubkey, index); } - /* XXX: Inform control port of the upload event (#20699). */ end: tor_free(encoded_desc); return; @@ -2900,6 +2884,205 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) /* Public API */ /* ========== */ +/* Upload an encoded descriptor in encoded_desc of the given version. This + * descriptor is for the service identity_pk and blinded_pk used to setup the + * directory connection identifier. It is uploaded to the directory hsdir_rs + * routerstatus_t object. + * + * NOTE: This function does NOT check for PublishHidServDescriptors because it + * is only used by the control port command HSPOST outside of this subsystem. + * Inside this code, upload_descriptor_to_hsdir() should be used. */ +void +hs_service_upload_desc_to_dir(const char *encoded_desc, + const uint8_t version, + const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + const routerstatus_t *hsdir_rs) +{ + char version_str[4] = {0}; + directory_request_t *dir_req; + hs_ident_dir_conn_t ident; + + tor_assert(encoded_desc); + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(hsdir_rs); + + /* Setup the connection identifier. */ + memset(&ident, 0, sizeof(ident)); + hs_ident_dir_conn_init(identity_pk, blinded_pk, &ident); + + /* This is our resource when uploading which is used to construct the URL + * with the version number: "/tor/hs/<version>/publish". */ + tor_snprintf(version_str, sizeof(version_str), "%u", version); + + /* Build the directory request for this HSDir. */ + dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC); + directory_request_set_routerstatus(dir_req, hsdir_rs); + directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS); + directory_request_set_resource(dir_req, version_str); + directory_request_set_payload(dir_req, encoded_desc, + strlen(encoded_desc)); + /* The ident object is copied over the directory connection object once + * the directory request is initiated. */ + directory_request_upload_set_hs_ident(dir_req, &ident); + + /* Initiate the directory request to the hsdir.*/ + directory_initiate_request(dir_req); + directory_request_free(dir_req); +} + +/* Add the ephemeral service using the secret key sk and ports. Both max + * streams parameter will be set in the newly created service. + * + * Ownership of sk and ports is passed to this routine. Regardless of + * success/failure, callers should not touch these values after calling this + * routine, and may assume that correct cleanup has been done on failure. + * + * Return an appropriate hs_service_add_ephemeral_status_t. */ +hs_service_add_ephemeral_status_t +hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports, + int max_streams_per_rdv_circuit, + int max_streams_close_circuit, char **address_out) +{ + hs_service_add_ephemeral_status_t ret; + hs_service_t *service = NULL; + + tor_assert(sk); + tor_assert(ports); + tor_assert(address_out); + + service = hs_service_new(get_options()); + + /* Setup the service configuration with specifics. A default service is + * HS_VERSION_TWO so explicitely set it. */ + service->config.version = HS_VERSION_THREE; + service->config.max_streams_per_rdv_circuit = max_streams_per_rdv_circuit; + service->config.max_streams_close_circuit = !!max_streams_close_circuit; + service->config.is_ephemeral = 1; + smartlist_free(service->config.ports); + service->config.ports = ports; + + /* Handle the keys. */ + memcpy(&service->keys.identity_sk, sk, sizeof(service->keys.identity_sk)); + if (ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk) < 0) { + log_warn(LD_CONFIG, "Unable to generate ed25519 public key" + "for v3 service."); + ret = RSAE_BADPRIVKEY; + goto err; + } + + /* Make sure we have at least one port. */ + if (smartlist_len(service->config.ports) == 0) { + log_warn(LD_CONFIG, "At least one VIRTPORT/TARGET must be specified " + "for v3 service."); + ret = RSAE_BADVIRTPORT; + goto err; + } + + /* The only way the registration can fail is if the service public key + * already exists. */ + if (BUG(register_service(hs_service_map, service) < 0)) { + log_warn(LD_CONFIG, "Onion Service private key collides with an " + "existing v3 service."); + ret = RSAE_ADDREXISTS; + goto err; + } + + /* Last step is to build the onion address. */ + hs_build_address(&service->keys.identity_pk, + (uint8_t) service->config.version, + service->onion_address); + *address_out = tor_strdup(service->onion_address); + + log_info(LD_CONFIG, "Added ephemeral v3 onion service: %s", + safe_str_client(service->onion_address)); + ret = RSAE_OKAY; + goto end; + + err: + hs_service_free(service); + + end: + memwipe(sk, 0, sizeof(ed25519_secret_key_t)); + tor_free(sk); + return ret; +} + +/* For the given onion address, delete the ephemeral service. Return 0 on + * success else -1 on error. */ +int +hs_service_del_ephemeral(const char *address) +{ + uint8_t version; + ed25519_public_key_t pk; + hs_service_t *service = NULL; + + tor_assert(address); + + if (hs_parse_address(address, &pk, NULL, &version) < 0) { + log_warn(LD_CONFIG, "Requested malformed v3 onion address for removal."); + goto err; + } + + if (version != HS_VERSION_THREE) { + log_warn(LD_CONFIG, "Requested version of onion address for removal " + "is not supported."); + goto err; + } + + service = find_service(hs_service_map, &pk); + if (service == NULL) { + log_warn(LD_CONFIG, "Requested non-existent v3 hidden service for " + "removal."); + goto err; + } + + if (!service->config.is_ephemeral) { + log_warn(LD_CONFIG, "Requested non-ephemeral v3 hidden service for " + "removal."); + goto err; + } + + /* Close circuits, remove from map and finally free. */ + close_service_circuits(service); + remove_service(hs_service_map, service); + hs_service_free(service); + + log_info(LD_CONFIG, "Removed ephemeral v3 hidden service: %s", + safe_str_client(address)); + return 0; + + err: + return -1; +} + +/* Using the ed25519 public key pk, find a service for that key and return the + * current encoded descriptor as a newly allocated string or NULL if not + * found. This is used by the control port subsystem. */ +char * +hs_service_lookup_current_desc(const ed25519_public_key_t *pk) +{ + const hs_service_t *service; + + tor_assert(pk); + + service = find_service(hs_service_map, pk); + if (service && service->desc_current) { + char *encoded_desc = NULL; + /* No matter what is the result (which should never be a failure), return + * the encoded variable, if success it will contain the right thing else + * it will be NULL. */ + hs_desc_encode_descriptor(service->desc_current->desc, + &service->desc_current->signing_kp, + &encoded_desc); + return encoded_desc; + } + + return NULL; +} + /* Return the number of service we have configured and usable. */ unsigned int hs_service_get_num_services(void) @@ -2928,7 +3111,9 @@ hs_service_intro_circ_has_closed(origin_circuit_t *circ) get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); if (service == NULL) { - log_warn(LD_REND, "Unable to find any hidden service associated " + /* This is possible if the circuits are closed and the service is + * immediately deleted. */ + log_info(LD_REND, "Unable to find any hidden service associated " "identity key %s on intro circuit %u.", ed25519_fmt(&circ->hs_ident->identity_pk), TO_CIRCUIT(circ)->n_circ_id); diff --git a/src/or/hs_service.h b/src/or/hs_service.h index ed1053d850..678f24b0a2 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -271,6 +271,21 @@ int hs_service_receive_introduce2(origin_circuit_t *circ, void hs_service_intro_circ_has_closed(origin_circuit_t *circ); +char *hs_service_lookup_current_desc(const ed25519_public_key_t *pk); + +hs_service_add_ephemeral_status_t +hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports, + int max_streams_per_rdv_circuit, + int max_streams_close_circuit, char **address_out); +int hs_service_del_ephemeral(const char *address); + +/* Used outside of the HS subsystem by the control port command HSPOST. */ +void hs_service_upload_desc_to_dir(const char *encoded_desc, + const uint8_t version, + const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + const routerstatus_t *hsdir_rs); + #ifdef HS_SERVICE_PRIVATE #ifdef TOR_UNIT_TESTS diff --git a/src/or/include.am b/src/or/include.am index b783f4855a..1c66cd2de3 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -60,6 +60,7 @@ LIBTOR_A_SOURCES = \ src/or/hs_client.c \ src/or/hs_common.c \ src/or/hs_config.c \ + src/or/hs_control.c \ src/or/hs_descriptor.c \ src/or/hs_ident.c \ src/or/hs_intropoint.c \ @@ -196,11 +197,12 @@ ORHEADERS = \ src/or/hibernate.h \ src/or/hs_cache.h \ src/or/hs_cell.h \ - src/or/hs_config.h \ src/or/hs_circuit.h \ src/or/hs_circuitmap.h \ src/or/hs_client.h \ src/or/hs_common.h \ + src/or/hs_config.h \ + src/or/hs_control.h \ src/or/hs_descriptor.h \ src/or/hs_ident.h \ src/or/hs_intropoint.h \ diff --git a/src/or/main.c b/src/or/main.c index b70f47abeb..aae98dd8ab 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -3207,7 +3207,7 @@ tor_init(int argc, char *argv[]) log_notice(LD_GENERAL, "%s", rust_str); } tor_free(rust_str); -#endif +#endif /* defined(HAVE_RUST) */ if (network_init()<0) { log_err(LD_BUG,"Error initializing network; exiting."); diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 3fe5e933d0..d8a4660af1 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -74,6 +74,102 @@ HT_GENERATE2(microdesc_map, microdesc_t, node, microdesc_hash_, microdesc_eq_, 0.6, tor_reallocarray_, tor_free_) +/************************* md fetch fail cache *****************************/ + +/* If we end up with too many outdated dirservers, something probably went + * wrong so clean up the list. */ +#define TOO_MANY_OUTDATED_DIRSERVERS 30 + +/** List of dirservers with outdated microdesc information. The smartlist is + * filled with the hex digests of outdated dirservers. */ +static smartlist_t *outdated_dirserver_list = NULL; + +/** Note that we failed to fetch a microdescriptor from the relay with + * <b>relay_digest</b> (of size DIGEST_LEN). */ +void +microdesc_note_outdated_dirserver(const char *relay_digest) +{ + char relay_hexdigest[HEX_DIGEST_LEN+1]; + + /* Don't register outdated dirservers if we don't have a live consensus, + * since we might be trying to fetch microdescriptors that are not even + * currently active. */ + if (!networkstatus_get_live_consensus(approx_time())) { + return; + } + + if (!outdated_dirserver_list) { + outdated_dirserver_list = smartlist_new(); + } + + tor_assert(outdated_dirserver_list); + + /* If the list grows too big, clean it up */ + if (BUG(smartlist_len(outdated_dirserver_list) > + TOO_MANY_OUTDATED_DIRSERVERS)) { + microdesc_reset_outdated_dirservers_list(); + } + + /* Turn the binary relay digest to a hex since smartlists have better support + * for strings than digests. */ + base16_encode(relay_hexdigest,sizeof(relay_hexdigest), + relay_digest, DIGEST_LEN); + + /* Make sure we don't add a dirauth as an outdated dirserver */ + if (router_get_trusteddirserver_by_digest(relay_digest)) { + log_info(LD_GENERAL, "Auth %s gave us outdated dirinfo.", relay_hexdigest); + return; + } + + /* Don't double-add outdated dirservers */ + if (smartlist_contains_string(outdated_dirserver_list, relay_hexdigest)) { + return; + } + + /* Add it to the list of outdated dirservers */ + smartlist_add_strdup(outdated_dirserver_list, relay_hexdigest); + + log_info(LD_GENERAL, "Noted %s as outdated md dirserver", relay_hexdigest); +} + +/** Return True if the relay with <b>relay_digest</b> (size DIGEST_LEN) is an + * outdated dirserver */ +int +microdesc_relay_is_outdated_dirserver(const char *relay_digest) +{ + char relay_hexdigest[HEX_DIGEST_LEN+1]; + + if (!outdated_dirserver_list) { + return 0; + } + + /* Convert identity digest to hex digest */ + base16_encode(relay_hexdigest, sizeof(relay_hexdigest), + relay_digest, DIGEST_LEN); + + /* Last time we tried to fetch microdescs, was this directory mirror missing + * any mds we asked for? */ + if (smartlist_contains_string(outdated_dirserver_list, relay_hexdigest)) { + return 1; + } + + return 0; +} + +/** Reset the list of outdated dirservers. */ +void +microdesc_reset_outdated_dirservers_list(void) +{ + if (!outdated_dirserver_list) { + return; + } + + SMARTLIST_FOREACH(outdated_dirserver_list, char *, cp, tor_free(cp)); + smartlist_clear(outdated_dirserver_list); +} + +/****************************************************************************/ + /** Write the body of <b>md</b> into <b>f</b>, with appropriate annotations. * On success, return the total number of bytes written, and set * *<b>annotation_len_out</b> to the number of bytes written as @@ -789,6 +885,11 @@ microdesc_free_all(void) tor_free(the_microdesc_cache->journal_fname); tor_free(the_microdesc_cache); } + + if (outdated_dirserver_list) { + SMARTLIST_FOREACH(outdated_dirserver_list, char *, cp, tor_free(cp)); + smartlist_free(outdated_dirserver_list); + } } /** If there is a microdescriptor in <b>cache</b> whose sha256 digest is diff --git a/src/or/microdesc.h b/src/or/microdesc.h index b40d66b3e3..1be12156a4 100644 --- a/src/or/microdesc.h +++ b/src/or/microdesc.h @@ -50,5 +50,9 @@ int we_fetch_microdescriptors(const or_options_t *options); int we_fetch_router_descriptors(const or_options_t *options); int we_use_microdescriptors_for_circuits(const or_options_t *options); +void microdesc_note_outdated_dirserver(const char *relay_digest); +int microdesc_relay_is_outdated_dirserver(const char *relay_digest); +void microdesc_reset_outdated_dirservers_list(void); + #endif /* !defined(TOR_MICRODESC_H) */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index d382cc8033..d426d4858e 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -2023,6 +2023,9 @@ networkstatus_set_current_consensus(const char *consensus, tor_free(flavormsg); } + /* We got a new consesus. Reset our md fetch fail cache */ + microdesc_reset_outdated_dirservers_list(); + router_dir_info_changed(); result = 0; @@ -2206,7 +2209,9 @@ signed_descs_update_status_from_consensus_networkstatus(smartlist_t *descs) char * networkstatus_getinfo_helper_single(const routerstatus_t *rs) { - return routerstatus_format_entry(rs, NULL, NULL, NS_CONTROL_PORT, NULL); + return routerstatus_format_entry(rs, NULL, NULL, NS_CONTROL_PORT, + ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD, + NULL); } /** Alloc and return a string describing routerstatuses for the most diff --git a/src/or/protover.c b/src/or/protover.c index ae955296e6..748c0c2f50 100644 --- a/src/or/protover.c +++ b/src/or/protover.c @@ -737,6 +737,11 @@ protocol_list_contains(const smartlist_t *protos, const char * protover_compute_for_old_tor(const char *version) { + if (version == NULL) { + /* No known version; guess the oldest series that is still supported. */ + version = "0.2.5.15"; + } + if (tor_version_as_new_as(version, FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS)) { return ""; @@ -778,5 +783,5 @@ protover_free_all(void) } } -#endif +#endif /* !defined(HAVE_RUST) */ diff --git a/src/or/protover.h b/src/or/protover.h index a4dbc8bfc2..83a728e626 100644 --- a/src/or/protover.h +++ b/src/or/protover.h @@ -84,7 +84,7 @@ STATIC const char *protocol_type_to_str(protocol_type_t pr); STATIC int str_to_protocol_type(const char *s, protocol_type_t *pr_out); STATIC void proto_entry_free(proto_entry_t *entry); -#endif +#endif /* !defined(HAVE_RUST) && defined(TOR_UNIT_TESTS) */ #endif /* defined(PROTOVER_PRIVATE) */ diff --git a/src/or/protover_rust.c b/src/or/protover_rust.c index 0c409b1681..26e21cc1c5 100644 --- a/src/or/protover_rust.c +++ b/src/or/protover_rust.c @@ -15,5 +15,5 @@ /* Define for compatibility, used in main.c */ void protover_free_all(void) {} -#endif +#endif /* defined(HAVE_RUST) */ diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 3274819241..8291e5abfb 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -459,7 +459,8 @@ directory_get_from_hs_dir(const char *desc_id, hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32); if (!hs_dir) { /* No suitable hs dir can be found, stop right now. */ - control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR"); + control_event_hsv2_descriptor_failed(rend_query, NULL, + "QUERY_NO_HSDIR"); control_event_hs_descriptor_content(rend_data_get_address(rend_query), desc_id_base32, NULL, NULL); return 0; @@ -482,7 +483,7 @@ directory_get_from_hs_dir(const char *desc_id, REND_DESC_COOKIE_LEN, 0)<0) { log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); - control_event_hs_descriptor_failed(rend_query, hsdir_fp, "BAD_DESC"); + control_event_hsv2_descriptor_failed(rend_query, hsdir_fp, "BAD_DESC"); control_event_hs_descriptor_content(rend_data_get_address(rend_query), desc_id_base32, hsdir_fp, NULL); return 0; @@ -515,9 +516,10 @@ directory_get_from_hs_dir(const char *desc_id, (rend_data->auth_type == REND_NO_AUTH ? "[none]" : escaped_safe_str_client(descriptor_cookie_base64)), routerstatus_describe(hs_dir)); - control_event_hs_descriptor_requested(rend_query, + control_event_hs_descriptor_requested(rend_data->onion_address, + rend_data->auth_type, hs_dir->identity_digest, - desc_id_base32); + desc_id_base32, NULL); return 1; } diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 60234a5c1f..15a0017d66 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -847,9 +847,9 @@ rend_config_service(const config_line_t *line_, * after calling this routine, and may assume that correct cleanup has * been done on failure. * - * Return an appropriate rend_service_add_ephemeral_status_t. + * Return an appropriate hs_service_add_ephemeral_status_t. */ -rend_service_add_ephemeral_status_t +hs_service_add_ephemeral_status_t rend_service_add_ephemeral(crypto_pk_t *pk, smartlist_t *ports, int max_streams_per_circuit, @@ -1829,6 +1829,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, time_t now = time(NULL); time_t elapsed; int replay; + ssize_t keylen; /* Do some initial validation and logging before we parse the cell */ if (circuit->base_.purpose != CIRCUIT_PURPOSE_S_INTRO) { @@ -1903,9 +1904,10 @@ rend_service_receive_introduction(origin_circuit_t *circuit, } /* check for replay of PK-encrypted portion. */ + keylen = crypto_pk_keysize(intro_key); replay = replaycache_add_test_and_elapsed( intro_point->accepted_intro_rsa_parts, - parsed_req->ciphertext, parsed_req->ciphertext_len, + parsed_req->ciphertext, MIN(parsed_req->ciphertext_len, keylen), &elapsed); if (replay) { @@ -3574,7 +3576,7 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, "directories to post descriptors to."); control_event_hs_descriptor_upload(service_id, "UNKNOWN", - "UNKNOWN"); + "UNKNOWN", NULL); goto done; } } @@ -3629,7 +3631,7 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, hs_dir->or_port); control_event_hs_descriptor_upload(service_id, hs_dir->identity_digest, - desc_id_base32); + desc_id_base32, NULL); tor_free(hs_dir_ip); /* Remember successful upload to this router for next time. */ if (!smartlist_contains_digest(successful_uploads, @@ -3912,6 +3914,10 @@ remove_invalid_intro_points(rend_service_t *service, log_info(LD_REND, "Expiring %s as intro point for %s.", safe_str_client(extend_info_describe(intro->extend_info)), safe_str_client(service->service_id)); + /* We might have put it in the retry list if so, undo. */ + if (retry_nodes) { + smartlist_remove(retry_nodes, intro); + } smartlist_add(service->expiring_nodes, intro); SMARTLIST_DEL_CURRENT(service->intro_nodes, intro); /* Intro point is expired, we need a new one thus don't consider it diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 5946e31861..15badce6ab 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -187,16 +187,7 @@ void rend_service_port_config_free(rend_service_port_config_t *p); void rend_authorized_client_free(rend_authorized_client_t *client); -/** Return value from rend_service_add_ephemeral. */ -typedef enum { - RSAE_BADAUTH = -5, /**< Invalid auth_type/auth_clients */ - RSAE_BADVIRTPORT = -4, /**< Invalid VIRTPORT/TARGET(s) */ - RSAE_ADDREXISTS = -3, /**< Onion address collision */ - RSAE_BADPRIVKEY = -2, /**< Invalid public key */ - RSAE_INTERNAL = -1, /**< Internal error */ - RSAE_OKAY = 0 /**< Service added as expected */ -} rend_service_add_ephemeral_status_t; -rend_service_add_ephemeral_status_t rend_service_add_ephemeral(crypto_pk_t *pk, +hs_service_add_ephemeral_status_t rend_service_add_ephemeral(crypto_pk_t *pk, smartlist_t *ports, int max_streams_per_circuit, int max_streams_close_circuit, diff --git a/src/or/rephist.c b/src/or/rephist.c index 51e800bc31..87b5a93713 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -1239,9 +1239,9 @@ rep_hist_load_mtbf_data(time_t now) * totals? */ #define NUM_SECS_ROLLING_MEASURE 10 /** How large are the intervals for which we track and report bandwidth use? */ -#define NUM_SECS_BW_SUM_INTERVAL (4*60*60) +#define NUM_SECS_BW_SUM_INTERVAL (24*60*60) /** How far in the past do we remember and publish bandwidth use? */ -#define NUM_SECS_BW_SUM_IS_VALID (24*60*60) +#define NUM_SECS_BW_SUM_IS_VALID (5*24*60*60) /** How many bandwidth usage intervals do we remember? (derived) */ #define NUM_TOTALS (NUM_SECS_BW_SUM_IS_VALID/NUM_SECS_BW_SUM_INTERVAL) diff --git a/src/or/router.c b/src/or/router.c index 64bd9fdfcd..cd1848ee7b 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -2500,7 +2500,7 @@ mark_my_descriptor_dirty(const char *reason) /** How frequently will we republish our descriptor because of large (factor * of 2) shifts in estimated bandwidth? Note: We don't use this constant * if our previous bandwidth estimate was exactly 0. */ -#define MAX_BANDWIDTH_CHANGE_FREQ (20*60) +#define MAX_BANDWIDTH_CHANGE_FREQ (3*60*60) /** Check whether bandwidth has changed a lot since the last time we announced * bandwidth. If so, mark our descriptor dirty. */ diff --git a/src/or/routerlist.c b/src/or/routerlist.c index bd200a783d..93c75ef583 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -2839,7 +2839,10 @@ router_choose_random_node(smartlist_t *excludedsmartlist, } } SMARTLIST_FOREACH_END(node); - if ((r = routerlist_find_my_routerinfo())) + /* If the node_t is not found we won't be to exclude ourself but we + * won't be able to pick ourself in router_choose_random_node() so + * this is fine to at least try with our routerinfo_t object. */ + if ((r = router_get_my_routerinfo())) routerlist_add_node_and_family(excludednodes, r); router_add_running_nodes_to_smartlist(sl, need_uptime, need_capacity, @@ -5197,10 +5200,23 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, log_warn(LD_BUG, "Failed to re-parse a router."); continue; } + /* need to compute this now, since add_to_routerlist may free. */ + char time_cert_expires[ISO_TIME_LEN+1]; + format_iso_time(time_cert_expires, ri->cert_expiration_time); + r = router_add_to_routerlist(ri, &msg, 1, 0); if (WRA_WAS_OUTDATED(r)) { - log_warn(LD_DIR, "Couldn't add re-parsed router: %s", + log_warn(LD_DIR, "Couldn't add re-parsed router: %s. This isn't " + "usually a big deal, but you should make sure that your " + "clock and timezone are set correctly.", msg?msg:"???"); + if (r == ROUTER_CERTS_EXPIRED) { + char time_cons[ISO_TIME_LEN+1]; + format_iso_time(time_cons, consensus->valid_after); + log_warn(LD_DIR, " (I'm looking at a consensus from %s; This " + "router's certificates began expiring at %s.)", + time_cons, time_cert_expires); + } } } SMARTLIST_FOREACH_END(sd); routerlist_assert_ok(rl); diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 510c16b217..cd047d5a75 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -171,6 +171,8 @@ STATIC smartlist_t *channels_pending = NULL; */ STATIC struct event *run_sched_ev = NULL; +static int have_logged_kist_suddenly_disabled = 0; + /***************************************************************************** * Scheduling system static function definitions * @@ -252,13 +254,32 @@ select_scheduler(void) case SCHEDULER_KIST: if (!scheduler_can_use_kist()) { #ifdef HAVE_KIST_SUPPORT - log_notice(LD_SCHED, "Scheduler type KIST has been disabled by " - "the consensus or no kernel support."); + if (!have_logged_kist_suddenly_disabled) { + /* We should only log this once in most cases. If it was the kernel + * losing support for kist that caused scheduler_can_use_kist() to + * return false, then this flag makes sure we only log this message + * once. If it was the consensus that switched from "yes use kist" + * to "no don't use kist", then we still set the flag so we log + * once, but we unset the flag elsewhere if we ever can_use_kist() + * again. + */ + have_logged_kist_suddenly_disabled = 1; + log_notice(LD_SCHED, "Scheduler type KIST has been disabled by " + "the consensus or no kernel support."); + } #else /* !(defined(HAVE_KIST_SUPPORT)) */ log_info(LD_SCHED, "Scheduler type KIST not built in"); #endif /* defined(HAVE_KIST_SUPPORT) */ continue; } + /* This flag will only get set in one of two cases: + * 1 - the kernel lost support for kist. In that case, we don't expect to + * ever end up here + * 2 - the consensus went from "yes use kist" to "no don't use kist". + * We might end up here if the consensus changes back to "yes", in which + * case we might want to warn the user again if it goes back to "no" + * yet again. Thus we unset the flag */ + have_logged_kist_suddenly_disabled = 0; new_scheduler = get_kist_scheduler(); scheduler_kist_set_full_mode(); goto end; diff --git a/src/or/scheduler_kist.c b/src/or/scheduler_kist.c index d1726ba345..fea92705d4 100644 --- a/src/or/scheduler_kist.c +++ b/src/or/scheduler_kist.c @@ -525,9 +525,13 @@ kist_scheduler_schedule(void) monotime_get(&now); /* If time is really monotonic, we can never have now being smaller than the - * last scheduler run. The scheduler_last_run at first is set to 0. */ + * last scheduler run. The scheduler_last_run at first is set to 0. + * Unfortunately, not all platforms guarantee monotonic time so we log at + * info level but don't make it more noisy. */ diff = monotime_diff_msec(&scheduler_last_run, &now); - IF_BUG_ONCE(diff < 0) { + if (diff < 0) { + log_info(LD_SCHED, "Monotonic time between now and last run of scheduler " + "is negative: %" PRId64 ". Setting diff to 0.", diff); diff = 0; } if (diff < sched_run_interval) { diff --git a/src/or/tor_api_internal.h b/src/or/tor_api_internal.h index a69ba76420..10b6278b7b 100644 --- a/src/or/tor_api_internal.h +++ b/src/or/tor_api_internal.h @@ -16,5 +16,5 @@ struct tor_main_configuration_t { char **argv; }; -#endif +#endif /* !defined(TOR_API_INTERNAL_H) */ diff --git a/src/rust/tor_rust/include.am b/src/rust/tor_rust/include.am index 90f37a9f1b..c02324cb77 100644 --- a/src/rust/tor_rust/include.am +++ b/src/rust/tor_rust/include.am @@ -2,17 +2,23 @@ EXTRA_DIST +=\ src/rust/tor_rust/Cargo.toml \ src/rust/tor_rust/lib.rs +EXTRA_CARGO_OPTIONS= + src/rust/target/release/@TOR_RUST_STATIC_NAME@: FORCE - ( cd "$(abs_top_srcdir)/src/rust/tor_rust" ; \ + ( cd "$(abs_top_builddir)/src/rust" ; \ CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \ CARGO_HOME="$(abs_top_builddir)/src/rust" \ - $(CARGO) build --release --quiet $(CARGO_ONLINE) ) + $(CARGO) build --release $(EXTRA_CARGO_OPTIONS) \ + $(CARGO_ONLINE) \ + --manifest-path "$(abs_top_srcdir)/src/rust/tor_rust/Cargo.toml" ) distclean-rust: - ( cd "$(abs_top_srcdir)/src/rust/tor_rust" ; \ + ( cd "$(abs_top_builddir)/src/rust" ; \ CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \ CARGO_HOME="$(abs_top_builddir)/src/rust" \ - $(CARGO) clean --quiet $(CARGO_ONLINE) ) + $(CARGO) clean $(EXTRA_CARGO_OPTIONS) \ + $(CARGO_ONLINE) \ + --manifest-path "$(abs_top_srcdir)/src/rust/tor_rust/Cargo.toml" ) rm -rf "$(abs_top_builddir)/src/rust/registry" if USE_RUST diff --git a/src/test/include.am b/src/test/include.am index bbf8a370e2..b5c5dd24f7 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -125,6 +125,7 @@ src_test_test_SOURCES = \ src/test/test_hs_service.c \ src/test/test_hs_client.c \ src/test/test_hs_intropoint.c \ + src/test/test_hs_control.c \ src/test/test_handles.c \ src/test/test_hs_cache.c \ src/test/test_hs_descriptor.c \ diff --git a/src/test/test.c b/src/test/test.c index 00857c2386..432df35c34 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1201,6 +1201,7 @@ struct testgroup_t testgroups[] = { { "hs_cell/", hs_cell_tests }, { "hs_common/", hs_common_tests }, { "hs_config/", hs_config_tests }, + { "hs_control/", hs_control_tests }, { "hs_descriptor/", hs_descriptor }, { "hs_ntor/", hs_ntor_tests }, { "hs_service/", hs_service_tests }, diff --git a/src/test/test.h b/src/test/test.h index b1a3366a80..b37fe25b33 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -210,6 +210,7 @@ extern struct testcase_t hs_cache[]; extern struct testcase_t hs_cell_tests[]; extern struct testcase_t hs_common_tests[]; extern struct testcase_t hs_config_tests[]; +extern struct testcase_t hs_control_tests[]; extern struct testcase_t hs_descriptor[]; extern struct testcase_t hs_ntor_tests[]; extern struct testcase_t hs_service_tests[]; diff --git a/src/test/test_config.c b/src/test/test_config.c index 79f72fca1f..4290d0dc6d 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -4003,7 +4003,7 @@ test_config_parse_port_config__ports__ports_given(void *data) tt_int_op(port_cfg->entry_cfg.dns_request, OP_EQ, 1); tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 1); tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1); - tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 1); + tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.prefer_ipv6_virtaddr, OP_EQ, 1); #endif /* defined(_WIN32) */ @@ -4346,7 +4346,7 @@ test_config_parse_port_config__ports__ports_given(void *data) tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); port_cfg = (port_cfg_t *)smartlist_get(slout, 0); - tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 1); + tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.cache_ipv6_answers, OP_EQ, 1); // Test success with no cache ipv4 DNS @@ -5455,7 +5455,7 @@ test_config_check_bridge_distribution_setting_not_a_bridge(void *arg) or_options_t* options = get_options_mutable(); or_options_t* old_options = options; or_options_t* default_options = options; - char* message = (char*)(""); + char* message = NULL; int ret; (void)arg; @@ -5466,8 +5466,11 @@ test_config_check_bridge_distribution_setting_not_a_bridge(void *arg) ret = options_validate(old_options, options, default_options, 0, &message); tt_int_op(ret, OP_EQ, -1); + tt_str_op(message, OP_EQ, "You set BridgeDistribution, but you " + "didn't set BridgeRelay!"); done: - return; + tor_free(message); + options->BridgeDistribution = NULL; } /* If the BridgeDistribution setting was valid, 0 should be returned. */ diff --git a/src/test/test_controller.c b/src/test/test_controller.c index 472fcb8c53..af19f63f6c 100644 --- a/src/test/test_controller.c +++ b/src/test/test_controller.c @@ -6,6 +6,7 @@ #include "bridges.h" #include "control.h" #include "entrynodes.h" +#include "hs_common.h" #include "networkstatus.h" #include "rendservice.h" #include "routerlist.h" @@ -13,10 +14,87 @@ #include "test_helpers.h" static void -test_add_onion_helper_keyarg(void *arg) +test_add_onion_helper_keyarg_v3(void *arg) { - crypto_pk_t *pk = NULL; - crypto_pk_t *pk2 = NULL; + int ret, hs_version; + add_onion_secret_key_t pk; + char *key_new_blob = NULL; + char *err_msg = NULL; + const char *key_new_alg = NULL; + + (void) arg; + + memset(&pk, 0, sizeof(pk)); + + /* Test explicit ED25519-V3 key generation. */ + ret = add_onion_helper_keyarg("NEW:ED25519-V3", 0, &key_new_alg, + &key_new_blob, &pk, &hs_version, + &err_msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(hs_version, OP_EQ, HS_VERSION_THREE); + tt_assert(pk.v3); + tt_str_op(key_new_alg, OP_EQ, "ED25519-V3"); + tt_assert(key_new_blob); + tt_ptr_op(err_msg, OP_EQ, NULL); + tor_free(pk.v3); pk.v3 = NULL; + tor_free(key_new_blob); + + /* Test discarding the private key. */ + ret = add_onion_helper_keyarg("NEW:ED25519-V3", 1, &key_new_alg, + &key_new_blob, &pk, &hs_version, + &err_msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(hs_version, OP_EQ, HS_VERSION_THREE); + tt_assert(pk.v3); + tt_ptr_op(key_new_alg, OP_EQ, NULL); + tt_ptr_op(key_new_blob, OP_EQ, NULL); + tt_ptr_op(err_msg, OP_EQ, NULL); + tor_free(pk.v3); pk.v3 = NULL; + tor_free(key_new_blob); + + /* Test passing a key blob. */ + { + /* The base64 key and hex key are the same. Hex key is 64 bytes long. The + * sk has been generated randomly using python3. */ + const char *base64_sk = + "a9bT19PqGC9Y+BmOo1IQvCGjjwxMiaaxEXZ+FKMxpEQW" + "6AmSV5roThUGMRCaqQSCnR2jI1vL2QxHORzI4RxMmw=="; + const char *hex_sk = + "\x6b\xd6\xd3\xd7\xd3\xea\x18\x2f\x58\xf8\x19\x8e\xa3\x52\x10\xbc" + "\x21\xa3\x8f\x0c\x4c\x89\xa6\xb1\x11\x76\x7e\x14\xa3\x31\xa4\x44" + "\x16\xe8\x09\x92\x57\x9a\xe8\x4e\x15\x06\x31\x10\x9a\xa9\x04\x82" + "\x9d\x1d\xa3\x23\x5b\xcb\xd9\x0c\x47\x39\x1c\xc8\xe1\x1c\x4c\x9b"; + char *key_blob = NULL; + + tor_asprintf(&key_blob, "ED25519-V3:%s", base64_sk); + tt_assert(key_blob); + ret = add_onion_helper_keyarg(key_blob, 1, &key_new_alg, + &key_new_blob, &pk, &hs_version, + &err_msg); + tor_free(key_blob); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(hs_version, OP_EQ, HS_VERSION_THREE); + tt_assert(pk.v3); + tt_mem_op(pk.v3, OP_EQ, hex_sk, 64); + tt_ptr_op(key_new_alg, OP_EQ, NULL); + tt_ptr_op(key_new_blob, OP_EQ, NULL); + tt_ptr_op(err_msg, OP_EQ, NULL); + tor_free(pk.v3); pk.v3 = NULL; + tor_free(key_new_blob); + } + + done: + tor_free(pk.v3); + tor_free(key_new_blob); + tor_free(err_msg); +} + +static void +test_add_onion_helper_keyarg_v2(void *arg) +{ + int ret, hs_version; + add_onion_secret_key_t pk; + crypto_pk_t *pk1 = NULL; const char *key_new_alg = NULL; char *key_new_blob = NULL; char *err_msg = NULL; @@ -25,83 +103,100 @@ test_add_onion_helper_keyarg(void *arg) (void) arg; + memset(&pk, 0, sizeof(pk)); + /* Test explicit RSA1024 key generation. */ - pk = add_onion_helper_keyarg("NEW:RSA1024", 0, &key_new_alg, &key_new_blob, - &err_msg); - tt_assert(pk); + ret = add_onion_helper_keyarg("NEW:RSA1024", 0, &key_new_alg, &key_new_blob, + &pk, &hs_version, &err_msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO); + tt_assert(pk.v2); tt_str_op(key_new_alg, OP_EQ, "RSA1024"); tt_assert(key_new_blob); tt_ptr_op(err_msg, OP_EQ, NULL); /* Test "BEST" key generation (Assumes BEST = RSA1024). */ - crypto_pk_free(pk); + crypto_pk_free(pk.v2); pk.v2 = NULL; tor_free(key_new_blob); - pk = add_onion_helper_keyarg("NEW:BEST", 0, &key_new_alg, &key_new_blob, - &err_msg); - tt_assert(pk); + ret = add_onion_helper_keyarg("NEW:BEST", 0, &key_new_alg, &key_new_blob, + &pk, &hs_version, &err_msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO); + tt_assert(pk.v2); tt_str_op(key_new_alg, OP_EQ, "RSA1024"); tt_assert(key_new_blob); tt_ptr_op(err_msg, OP_EQ, NULL); /* Test discarding the private key. */ - crypto_pk_free(pk); + crypto_pk_free(pk.v2); pk.v2 = NULL; tor_free(key_new_blob); - pk = add_onion_helper_keyarg("NEW:BEST", 1, &key_new_alg, &key_new_blob, - &err_msg); - tt_assert(pk); + ret = add_onion_helper_keyarg("NEW:BEST", 1, &key_new_alg, &key_new_blob, + &pk, &hs_version, &err_msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO); + tt_assert(pk.v2); tt_ptr_op(key_new_alg, OP_EQ, NULL); tt_ptr_op(key_new_blob, OP_EQ, NULL); tt_ptr_op(err_msg, OP_EQ, NULL); /* Test generating a invalid key type. */ - crypto_pk_free(pk); - pk = add_onion_helper_keyarg("NEW:RSA512", 0, &key_new_alg, &key_new_blob, - &err_msg); - tt_ptr_op(pk, OP_EQ, NULL); + crypto_pk_free(pk.v2); pk.v2 = NULL; + ret = add_onion_helper_keyarg("NEW:RSA512", 0, &key_new_alg, &key_new_blob, + &pk, &hs_version, &err_msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO); + tt_assert(!pk.v2); tt_ptr_op(key_new_alg, OP_EQ, NULL); tt_ptr_op(key_new_blob, OP_EQ, NULL); tt_assert(err_msg); /* Test loading a RSA1024 key. */ tor_free(err_msg); - pk = pk_generate(0); - tt_int_op(0, OP_EQ, crypto_pk_base64_encode(pk, &encoded)); + pk1 = pk_generate(0); + tt_int_op(0, OP_EQ, crypto_pk_base64_encode(pk1, &encoded)); tor_asprintf(&arg_str, "RSA1024:%s", encoded); - pk2 = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, - &err_msg); - tt_assert(pk2); + ret = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, + &pk, &hs_version, &err_msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO); + tt_assert(pk.v2); tt_ptr_op(key_new_alg, OP_EQ, NULL); tt_ptr_op(key_new_blob, OP_EQ, NULL); tt_ptr_op(err_msg, OP_EQ, NULL); - tt_int_op(crypto_pk_cmp_keys(pk, pk2), OP_EQ, 0); + tt_int_op(crypto_pk_cmp_keys(pk1, pk.v2), OP_EQ, 0); /* Test loading a invalid key type. */ tor_free(arg_str); - crypto_pk_free(pk); pk = NULL; + crypto_pk_free(pk1); pk1 = NULL; + crypto_pk_free(pk.v2); pk.v2 = NULL; tor_asprintf(&arg_str, "RSA512:%s", encoded); - pk = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, - &err_msg); - tt_ptr_op(pk, OP_EQ, NULL); + ret = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, + &pk, &hs_version, &err_msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO); + tt_assert(!pk.v2); tt_ptr_op(key_new_alg, OP_EQ, NULL); tt_ptr_op(key_new_blob, OP_EQ, NULL); tt_assert(err_msg); /* Test loading a invalid key. */ tor_free(arg_str); - crypto_pk_free(pk); pk = NULL; + crypto_pk_free(pk.v2); pk.v2 = NULL; tor_free(err_msg); encoded[strlen(encoded)/2] = '\0'; tor_asprintf(&arg_str, "RSA1024:%s", encoded); - pk = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, - &err_msg); - tt_ptr_op(pk, OP_EQ, NULL); + ret = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, + &pk, &hs_version, &err_msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO); + tt_assert(!pk.v2); tt_ptr_op(key_new_alg, OP_EQ, NULL); tt_ptr_op(key_new_blob, OP_EQ, NULL); tt_assert(err_msg); done: - crypto_pk_free(pk); - crypto_pk_free(pk2); + crypto_pk_free(pk1); + crypto_pk_free(pk.v2); tor_free(key_new_blob); tor_free(err_msg); tor_free(encoded); @@ -1370,7 +1465,10 @@ test_download_status_bridge(void *arg) } struct testcase_t controller_tests[] = { - { "add_onion_helper_keyarg", test_add_onion_helper_keyarg, 0, NULL, NULL }, + { "add_onion_helper_keyarg_v2", test_add_onion_helper_keyarg_v2, 0, + NULL, NULL }, + { "add_onion_helper_keyarg_v3", test_add_onion_helper_keyarg_v3, 0, + NULL, NULL }, { "getinfo_helper_onion", test_getinfo_helper_onion, 0, NULL, NULL }, { "rend_service_parse_port_config", test_rend_service_parse_port_config, 0, NULL, NULL }, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index f9d981953d..80ebebe3f8 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -7,6 +7,7 @@ #define STATEFILE_PRIVATE #define ENTRYNODES_PRIVATE #define ROUTERLIST_PRIVATE +#define DIRECTORY_PRIVATE #include "or.h" #include "test.h" @@ -15,6 +16,7 @@ #include "circuitlist.h" #include "config.h" #include "confparse.h" +#include "directory.h" #include "entrynodes.h" #include "nodelist.h" #include "networkstatus.h" @@ -129,6 +131,14 @@ big_fake_network_setup(const struct testcase_t *testcase) n->rs->has_bandwidth = 1; n->rs->bandwidth_kb = 30; + /* Make a random nickname for each node */ + { + char nickname_binary[8]; + crypto_rand(nickname_binary, sizeof(nickname_binary)); + base64_encode(n->rs->nickname, sizeof(n->rs->nickname), + nickname_binary, sizeof(nickname_binary), 0); + } + /* Call half of the nodes a possible guard. */ if (i % 2 == 0) { n->is_possible_guard = 1; @@ -1719,6 +1729,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) /* Simpler cases: no gaurds are confirmed yet. */ (void)arg; guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); + entry_guard_restriction_t *rst = NULL; /* simple starting configuration */ entry_guards_update_primary(gs); @@ -1800,14 +1811,13 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) tt_ptr_op(g2, OP_EQ, g); /* But if we impose a restriction, we don't get the same guard */ - entry_guard_restriction_t rst; - memset(&rst, 0, sizeof(rst)); - memcpy(rst.exclude_id, g->identity, DIGEST_LEN); - g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, &rst, &state); + rst = guard_create_exit_restriction((uint8_t*)g->identity); + g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, rst, &state); tt_ptr_op(g2, OP_NE, g); done: guard_selection_free(gs); + entry_guard_restriction_free(rst); } static void @@ -1817,6 +1827,7 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) guards, we use a confirmed guard. */ (void)arg; int i; + entry_guard_restriction_t *rst = NULL; guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); const int N_CONFIRMED = 10; @@ -1877,10 +1888,8 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) get_options_mutable()->EnforceDistinctSubnets = 0; g = smartlist_get(gs->confirmed_entry_guards, smartlist_len(gs->primary_entry_guards)+2); - entry_guard_restriction_t rst; - memset(&rst, 0, sizeof(rst)); - memcpy(rst.exclude_id, g->identity, DIGEST_LEN); - g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, &rst, &state); + rst = guard_create_exit_restriction((uint8_t*)g->identity); + g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, rst, &state); tt_ptr_op(g2, OP_NE, NULL); tt_ptr_op(g2, OP_NE, g); tt_int_op(g2->confirmed_idx, OP_EQ, @@ -1906,13 +1915,13 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) // Regression test for bug 22753/TROVE-2017-006. get_options_mutable()->EnforceDistinctSubnets = 1; g = smartlist_get(gs->confirmed_entry_guards, 0); - memset(&rst, 0, sizeof(rst)); - memcpy(rst.exclude_id, g->identity, DIGEST_LEN); - g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, &rst, &state); + memcpy(rst->exclude_id, g->identity, DIGEST_LEN); + g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, rst, &state); tt_ptr_op(g2, OP_EQ, NULL); done: guard_selection_free(gs); + entry_guard_restriction_free(rst); } static void @@ -2493,9 +2502,7 @@ test_entry_guard_upgrade_not_blocked_by_restricted_circ_complete(void *arg) /* Once more, let circ1 become complete. But this time, we'll claim * that circ2 was restricted to not use the same guard as circ1. */ data->guard2_state->restrictions = - tor_malloc_zero(sizeof(entry_guard_restriction_t)); - memcpy(data->guard2_state->restrictions->exclude_id, - data->guard1->identity, DIGEST_LEN); + guard_create_exit_restriction((uint8_t*)data->guard1->identity); smartlist_t *result = smartlist_new(); int r; @@ -2604,9 +2611,7 @@ test_entry_guard_upgrade_not_blocked_by_restricted_circ_pending(void *arg) } data->guard2_state->restrictions = - tor_malloc_zero(sizeof(entry_guard_restriction_t)); - memcpy(data->guard2_state->restrictions->exclude_id, - data->guard1->identity, DIGEST_LEN); + guard_create_exit_restriction((uint8_t*)data->guard1->identity); smartlist_t *result = smartlist_new(); int r; @@ -2674,6 +2679,114 @@ test_enty_guard_should_expire_waiting(void *arg) tor_free(fake_state); } +static void +mock_directory_initiate_request(directory_request_t *req) +{ + if (req->guard_state) { + circuit_guard_state_free(req->guard_state); + } +} + +static networkstatus_t *mock_ns_val = NULL; +static networkstatus_t * +mock_ns_get_by_flavor(consensus_flavor_t f) +{ + (void)f; + return mock_ns_val; +} + +/** Test that when we fetch microdescriptors we skip guards that have + * previously failed to serve us needed microdescriptors. */ +static void +test_entry_guard_outdated_dirserver_exclusion(void *arg) +{ + int retval; + response_handler_args_t *args = NULL; + dir_connection_t *conn = NULL; + (void) arg; + + /* Test prep: Make a new guard selection */ + guard_selection_t *gs = get_guard_selection_by_name("default", + GS_TYPE_NORMAL, 1); + + /* ... we want to use entry guards */ + or_options_t *options = get_options_mutable(); + options->UseEntryGuards = 1; + options->UseBridges = 0; + + /* ... prepare some md digests we want to download in the future */ + smartlist_t *digests = smartlist_new(); + const char *prose = "unhurried and wise, we perceive."; + for (int i = 0; i < 20; i++) { + smartlist_add(digests, (char*)prose); + } + + tt_int_op(smartlist_len(digests), OP_EQ, 20); + + /* ... now mock some functions */ + mock_ns_val = tor_malloc_zero(sizeof(networkstatus_t)); + MOCK(networkstatus_get_latest_consensus_by_flavor, mock_ns_get_by_flavor); + MOCK(directory_initiate_request, mock_directory_initiate_request); + + /* Test logic: + * 0. Create a proper guard set and primary guard list. + * 1. Pretend to fail microdescriptor fetches from all the primary guards. + * 2. Order another microdescriptor fetch and make sure that primary guards + * get skipped since they failed previous fetches. + */ + + { /* Setup primary guard list */ + int i; + entry_guards_update_primary(gs); + for (i = 0; i < DFLT_N_PRIMARY_GUARDS; ++i) { + entry_guard_t *guard = smartlist_get(gs->sampled_entry_guards, i); + make_guard_confirmed(gs, guard); + } + entry_guards_update_primary(gs); + } + + { + /* Fail microdesc fetches with all the primary guards */ + args = tor_malloc_zero(sizeof(response_handler_args_t)); + args->status_code = 404; + args->reason = NULL; + args->body = NULL; + args->body_len = 0; + + conn = tor_malloc_zero(sizeof(dir_connection_t)); + conn->requested_resource = tor_strdup("d/jlinblackorigami"); + conn->base_.purpose = DIR_PURPOSE_FETCH_MICRODESC; + + /* Pretend to fail fetches with all primary guards */ + SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards,const entry_guard_t *,g) { + memcpy(conn->identity_digest, g->identity, DIGEST_LEN); + + retval = handle_response_fetch_microdesc(conn, args); + tt_int_op(retval, OP_EQ, 0); + } SMARTLIST_FOREACH_END(g); + } + + { + /* Now order the final md download */ + setup_full_capture_of_logs(LOG_INFO); + initiate_descriptor_downloads(NULL, DIR_PURPOSE_FETCH_MICRODESC, + digests, 3, 7, 0); + + /* ... and check that because we failed to fetch microdescs from all our + * primaries, we didnt end up selecting a primary for fetching dir info */ + expect_log_msg_containing("No primary or confirmed guards available."); + teardown_capture_of_logs(); + } + + done: + smartlist_free(digests); + tor_free(args); + if (conn) { + tor_free(conn->requested_resource); + tor_free(conn); + } +} + static const struct testcase_setup_t big_fake_network = { big_fake_network_setup, big_fake_network_cleanup }; @@ -2734,6 +2847,7 @@ struct testcase_t entrynodes_tests[] = { BFN_TEST(select_for_circuit_highlevel_primary_retry), BFN_TEST(select_and_cancel), BFN_TEST(drop_guards), + BFN_TEST(outdated_dirserver_exclusion), UPGRADE_TEST(upgrade_a_circuit, "c1-done c2-done"), UPGRADE_TEST(upgrade_blocked_by_live_primary_guards, "c1-done c2-done"), diff --git a/src/test/test_hs.c b/src/test/test_hs.c index 7737499f50..55c6218dd1 100644 --- a/src/test/test_hs.c +++ b/src/test/test_hs.c @@ -258,8 +258,9 @@ test_hs_desc_event(void *arg) sizeof(desc_id_base32)); /* test request event */ - control_event_hs_descriptor_requested(&rend_query.base_, HSDIR_EXIST_ID, - STR_DESC_ID_BASE32); + control_event_hs_descriptor_requested(rend_query.onion_address, + rend_query.auth_type, HSDIR_EXIST_ID, + STR_DESC_ID_BASE32, NULL); expected_msg = "650 HS_DESC REQUESTED "STR_HS_ADDR" NO_AUTH "\ STR_HSDIR_EXIST_LONGNAME " " STR_DESC_ID_BASE32 "\r\n"; tt_assert(received_msg); @@ -268,8 +269,8 @@ test_hs_desc_event(void *arg) /* test received event */ rend_query.auth_type = REND_BASIC_AUTH; - control_event_hs_descriptor_received(rend_query.onion_address, - &rend_query.base_, HSDIR_EXIST_ID); + control_event_hsv2_descriptor_received(rend_query.onion_address, + &rend_query.base_, HSDIR_EXIST_ID); expected_msg = "650 HS_DESC RECEIVED "STR_HS_ADDR" BASIC_AUTH "\ STR_HSDIR_EXIST_LONGNAME " " STR_DESC_ID_BASE32"\r\n"; tt_assert(received_msg); @@ -278,7 +279,7 @@ test_hs_desc_event(void *arg) /* test failed event */ rend_query.auth_type = REND_STEALTH_AUTH; - control_event_hs_descriptor_failed(&rend_query.base_, + control_event_hsv2_descriptor_failed(&rend_query.base_, HSDIR_NONE_EXIST_ID, "QUERY_REJECTED"); expected_msg = "650 HS_DESC FAILED "STR_HS_ADDR" STEALTH_AUTH "\ @@ -289,7 +290,7 @@ test_hs_desc_event(void *arg) /* test invalid auth type */ rend_query.auth_type = 999; - control_event_hs_descriptor_failed(&rend_query.base_, + control_event_hsv2_descriptor_failed(&rend_query.base_, HSDIR_EXIST_ID, "QUERY_REJECTED"); expected_msg = "650 HS_DESC FAILED "STR_HS_ADDR" UNKNOWN "\ @@ -301,7 +302,7 @@ test_hs_desc_event(void *arg) /* test no HSDir fingerprint type */ rend_query.auth_type = REND_NO_AUTH; - control_event_hs_descriptor_failed(&rend_query.base_, NULL, + control_event_hsv2_descriptor_failed(&rend_query.base_, NULL, "QUERY_NO_HSDIR"); expected_msg = "650 HS_DESC FAILED "STR_HS_ADDR" NO_AUTH " \ "UNKNOWN REASON=QUERY_NO_HSDIR\r\n"; diff --git a/src/test/test_hs_control.c b/src/test/test_hs_control.c new file mode 100644 index 0000000000..207a55de6d --- /dev/null +++ b/src/test/test_hs_control.c @@ -0,0 +1,199 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_control.c + * \brief Unit tests for hidden service control port event and command. + **/ + +#define CONTROL_PRIVATE +#define CIRCUITBUILD_PRIVATE +#define RENDCOMMON_PRIVATE +#define RENDSERVICE_PRIVATE +#define HS_SERVICE_PRIVATE + +#include "or.h" +#include "test.h" +#include "control.h" +#include "config.h" +#include "hs_common.h" +#include "hs_control.h" +#include "nodelist.h" +//#include "rendcommon.h" +//#include "rendservice.h" +//#include "routerset.h" +//#include "circuitbuild.h" +#include "test_helpers.h" + +/* mock ID digest and longname for node that's in nodelist */ +#define HSDIR_EXIST_ID \ + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" \ + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" +#define STR_HSDIR_EXIST_LONGNAME \ + "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=TestDir" +#define STR_HSDIR_NONE_EXIST_LONGNAME \ + "$BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + +/* Helper global variable for hidden service descriptor event test. + * It's used as a pointer to dynamically created message buffer in + * send_control_event_string_replacement function, which mocks + * send_control_event_string function. + * + * Always free it after use! */ +static char *received_msg = NULL; + +/** Mock function for send_control_event_string + */ +static void +queue_control_event_string_replacement(uint16_t event, char *msg) +{ + (void) event; + tor_free(received_msg); + received_msg = msg; +} + +/** Mock function for node_describe_longname_by_id, it returns either + * STR_HSDIR_EXIST_LONGNAME or STR_HSDIR_NONE_EXIST_LONGNAME + */ +static const char * +node_describe_longname_by_id_replacement(const char *id_digest) +{ + if (!strcmp(id_digest, HSDIR_EXIST_ID)) { + return STR_HSDIR_EXIST_LONGNAME; + } else { + return STR_HSDIR_NONE_EXIST_LONGNAME; + } +} + +/* HSDir fetch index is a series of 'D' */ +#define HSDIR_INDEX_FETCH_HEX \ + "4343434343434343434343434343434343434343434343434343434343434343" +#define HSDIR_INDEX_STORE_HEX \ + "4444444444444444444444444444444444444444444444444444444444444444" + +static const node_t * +mock_node_get_by_id(const char *digest) +{ + static node_t node; + memcpy(node.identity, digest, DIGEST_LEN); + node.hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t)); + memset(node.hsdir_index->fetch, 'C', DIGEST256_LEN); + memset(node.hsdir_index->store_first, 'D', DIGEST256_LEN); + return &node; +} + +static void +test_hs_desc_event(void *arg) +{ + int ret; + char *expected_msg = NULL; + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + ed25519_keypair_t identity_kp; + ed25519_public_key_t blinded_pk; + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + routerstatus_t hsdir_rs; + hs_ident_dir_conn_t ident; + + (void) arg; + MOCK(queue_control_event_string, + queue_control_event_string_replacement); + MOCK(node_describe_longname_by_id, + node_describe_longname_by_id_replacement); + MOCK(node_get_by_id, mock_node_get_by_id); + + /* Setup what we need for this test. */ + ed25519_keypair_generate(&identity_kp, 0); + hs_build_address(&identity_kp.pubkey, HS_VERSION_THREE, onion_address); + ret = hs_address_is_valid(onion_address); + tt_int_op(ret, OP_EQ, 1); + memset(&blinded_pk, 'B', sizeof(blinded_pk)); + memset(&hsdir_rs, 0, sizeof(hsdir_rs)); + memcpy(hsdir_rs.identity_digest, HSDIR_EXIST_ID, DIGEST_LEN); + ret = ed25519_public_to_base64(base64_blinded_pk, &blinded_pk); + tt_int_op(ret, OP_EQ, 0); + memcpy(&ident.identity_pk, &identity_kp.pubkey, + sizeof(ed25519_public_key_t)); + memcpy(&ident.blinded_pk, &blinded_pk, sizeof(blinded_pk)); + + /* HS_DESC REQUESTED ... */ + hs_control_desc_event_requested(&identity_kp.pubkey, base64_blinded_pk, + &hsdir_rs); + tor_asprintf(&expected_msg, "650 HS_DESC REQUESTED %s NO_AUTH " + STR_HSDIR_EXIST_LONGNAME " %s HSDIR_INDEX=" + HSDIR_INDEX_FETCH_HEX "\r\n", + onion_address, base64_blinded_pk); + tt_assert(received_msg); + tt_str_op(received_msg, OP_EQ, expected_msg); + tor_free(received_msg); + tor_free(expected_msg); + + /* HS_DESC CREATED... */ + hs_control_desc_event_created(onion_address, &blinded_pk); + tor_asprintf(&expected_msg, "650 HS_DESC CREATED %s UNKNOWN " + "UNKNOWN %s\r\n", + onion_address, base64_blinded_pk); + tt_assert(received_msg); + tt_str_op(received_msg, OP_EQ, expected_msg); + tor_free(received_msg); + tor_free(expected_msg); + + /* HS_DESC UPLOAD... */ + uint8_t hsdir_index_store[DIGEST256_LEN]; + memset(hsdir_index_store, 'D', sizeof(hsdir_index_store)); + hs_control_desc_event_upload(onion_address, HSDIR_EXIST_ID, + &blinded_pk, hsdir_index_store); + tor_asprintf(&expected_msg, "650 HS_DESC UPLOAD %s UNKNOWN " + STR_HSDIR_EXIST_LONGNAME " %s " + "HSDIR_INDEX=" HSDIR_INDEX_STORE_HEX "\r\n", + onion_address, base64_blinded_pk); + tt_assert(received_msg); + tt_str_op(received_msg, OP_EQ, expected_msg); + tor_free(received_msg); + tor_free(expected_msg); + + /* HS_DESC FAILED... */ + hs_control_desc_event_failed(&ident, HSDIR_EXIST_ID, "BAD_DESC"); + tor_asprintf(&expected_msg, "650 HS_DESC FAILED %s NO_AUTH " + STR_HSDIR_EXIST_LONGNAME " %s " + "REASON=BAD_DESC\r\n", + onion_address, base64_blinded_pk); + tt_assert(received_msg); + tt_str_op(received_msg, OP_EQ, expected_msg); + tor_free(received_msg); + tor_free(expected_msg); + + /* HS_DESC RECEIVED... */ + hs_control_desc_event_received(&ident, HSDIR_EXIST_ID); + tor_asprintf(&expected_msg, "650 HS_DESC RECEIVED %s NO_AUTH " + STR_HSDIR_EXIST_LONGNAME " %s\r\n", + onion_address, base64_blinded_pk); + tt_assert(received_msg); + tt_str_op(received_msg, OP_EQ, expected_msg); + tor_free(received_msg); + tor_free(expected_msg); + + /* HS_DESC UPLOADED... */ + hs_control_desc_event_uploaded(&ident, HSDIR_EXIST_ID); + tor_asprintf(&expected_msg, "650 HS_DESC UPLOADED %s UNKNOWN " + STR_HSDIR_EXIST_LONGNAME "\r\n", + onion_address); + tt_assert(received_msg); + tt_str_op(received_msg, OP_EQ, expected_msg); + tor_free(received_msg); + tor_free(expected_msg); + + done: + UNMOCK(queue_control_event_string); + UNMOCK(node_describe_longname_by_id); + UNMOCK(node_get_by_id); + tor_free(received_msg); + tor_free(expected_msg); +} + +struct testcase_t hs_control_tests[] = { + { "hs_desc_event", test_hs_desc_event, TT_FORK, + NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_rust.sh b/src/test/test_rust.sh index 8d7900e1df..133f2bb940 100755 --- a/src/test/test_rust.sh +++ b/src/test/test_rust.sh @@ -8,8 +8,12 @@ exitcode=0 set -e for crate in $crates; do - cd "${abs_top_srcdir:-.}/src/rust/${crate}" - CARGO_TARGET_DIR="${abs_top_builddir:-../../..}/src/rust/target" CARGO_HOME="${abs_top_builddir:-../../..}/src/rust" "${CARGO:-cargo}" test ${CARGO_ONLINE-"--frozen"} || exitcode=1 + cd "${abs_top_builddir:-../../..}/src/rust" + CARGO_TARGET_DIR="${abs_top_builddir:-../../..}/src/rust/target" \ + CARGO_HOME="${abs_top_builddir:-../../..}/src/rust" \ + "${CARGO:-cargo}" test ${CARGO_ONLINE-"--frozen"} \ + --manifest-path "${abs_top_srcdir:-.}/src/rust/${crate}/Cargo.toml" \ + || exitcode=1 cd - done |